Home / Resources / Hardware/software co-simulation between C and Spice with swig and TCLSpice
Introduction
Ngspice is an open-source mixed-level/mixed-signal electronic circuit simulator. It is a successor of the latest stable release of Berkeley SPICE, version 3f.5, which was released in 1993. A small group of maintainers and the user community contribute to the ngspice project by providing new features, enhancements and bug fixes.
Ngspice may be compiled into a shared library readily to be integrated into a calling program. Its interface provides access to all simulation parameters, input and output data. TCLSpice, another shared library version, offers an interface to Tcl/Tk for better integration with other softwares.
The Simplified Wrapper and Interface Generator (SWIG) is an open-source software tool used to connect computer programs or libraries written in C or C++ with scripting languages such as Lua, Perl, PHP, Python, R, Ruby, Tcl, and other language implementations like C#, Java, JavaScript, Go, D, OCaml, Octave, Scilab and Scheme.
C code
Let's imagine that we want to simulate a hardware device which provides serial data of arbitrary input, bit after bit. Also, this device has an enable bit that can be set from the hardware.
To interact with the hardware, we can share access functions or global variables. Let's do both: a global variable to store the value of the enable bit and access functions to set the arbitrary data and get the next bit to be provided.
unsigned int isEnabled = 0;
char theData[DATA_SIZE]; //!< data to be transmitted bit after bit
unsigned int theIndex = 0; //!< index of bit to obtain with \ref getBit().
void setData(
char *theAdress, //!< address of the data to overwrite from
unsigned int theSize //!< number of Bytes to copy to data
) {
memset(&theData[0], 0xff, DATA_SIZE);
memcpy(&theData[0], theAdress, MIN(theSize, DATA_SIZE));
theIndex = 0;
}
unsigned int getBit(
void
) {
char theValue;
theIndex = (theIndex % (8*DATA_SIZE)); // to avoid overflows
theValue = theData[theIndex / 8]; // select the Byte
theValue = (theValue >> (theIndex % 8)) & 0x01; // select the bit in Byte
if ( isEnabled != 0 ) {
theIndex++;
}
return theValue;
}
SWIG configuration file
SWIG allows to export specific variables and functions to the target language, in our case TCL to interact with TCLSpice. We write a configuration file as follows:
%module foreign
%{
#include "software.h"
%}
extern unsigned int isEnabled; // shares a variable
// share functions
extern void setData(char *theAdress, unsigned int theSize);
extern unsigned int getBit(void);
Spice
In the hardware, we can define anything we want. At least, a voltage source is needed so that we can switch its value from the testbench according to the C functions. Here we only simulate a voltage divider to verify that Spice calculates an operating point.
VIN IN 0 DC(0)
R1 IN OUT R=1k
R2 OUT 0 R=1k
.END
TCL testbench
Our testbench is written in TCL, where we can load the library created using SWIG and interact with Spice using TCLSpice. First we load all the packages:
# after SWIG and compilation:
set dir [file dirname [file normalize [info script]]]
load [file join ${dir} "software.so"] foreign
# load ngspice module ; this provides TCLSpice:
source "/usr/share/ngspice/pkgIndex.tcl"
load /usr/lib/x86_64-linux-gnu/libspice.so
# packages to plot data:
package require Tk
package require BLT
Then, we can load the hardware, set the value of the C shared variables, alter the hardware, simulate with TCLSpice, etc.
# to store the whole data to plot:
blt::vector create theTime
blt::vector create thePlot
# to store the data of one step:
blt::vector create theTmp
# load the hardware with TCLspice:
spice::source "hardware.cir"
# set the value of the C shared variable:
set isEnabled 1
# for each bit transmitted, we alter the DC of the input source and perform an
# operating point analysis with spice.
for {set i 0} {$i < 8*[string length ${theData}]} {incr i} {
spice::alter "vin" dc = [getBit]
spice::op
# add the measure of V(OUT) to the vector to plot:
theTime append [expr {(2 * $i) * 1e-6}]
spice::vectoblt "v(out)" theTmp
thePlot append theTmp
# deactivate C data after 3 Bytes:
if { $i >= 8*3 } {
set isEnabled 0
}
}
In the end, we plot the data using BLT and Tk packages:
blt::graph .graph -title "V(OUT)" -height 2i
.graph element create "V(OUT)" \
-color green -xdata theTime -ydata thePlot -pixels 2
pack .graph -fill x
Makefile
Execution requires several steps: creation of the wrapper with SWIG, compilation of the C code and execution of the TCL testbench.
Here is a Makefile that provides the recipe (make sure to indent with tabulations):
all: testbench.tcl software.so hardware.cir
tclsh $<
software.so: software.o software_wrap.o
gcc -shared $^ -o $@
%.o: %.c
gcc -fpic -c -Wall -Werror -I "/usr/include/tcl8.6/" $^ -o $@
software_wrap.c: swig.i software.h
swig -Wall -o $@ -tcl $<
clean:
$(RM) software.o software.so
$(RM) software_wrap.c software_wrap.o
Associated resources
Here is an archive which contains the discussed example and its Makefile.