diff --git a/compile_linux.sh b/compile_linux.sh index 5454ba0ec..0181a08d4 100755 --- a/compile_linux.sh +++ b/compile_linux.sh @@ -16,7 +16,8 @@ # external OpenVAF Verilig-A compiler, will allow access to advanced compact # device models writen in Verilog-A. # Please see the ngspice manual, chapt. 13, for more info on using OSDI/OpenVAF. -# CIDER, XSPICE, and OpenMP may be selected at will. +# --enable-klu will add the new matrix solver in addition to Sparse 1.3. +# CIDER, XSPICE, KLU, and OpenMP may be selected at will. # --disable-debug will give O2 optimization (versus O0 for debug) and removes all debugging info. # ngspice as shared library: @@ -48,13 +49,13 @@ if test "$1" = "d"; then if [ $? -ne 0 ]; then echo "cd debug failed"; exit 1 ; fi echo "configuring for 64 bit debug" echo - ../configure --with-x --enable-xspice --enable-cider --enable-predictor --enable-osdi --with-readline=yes --enable-openmp CFLAGS="-g -m64 -O0 -Wall -Wno-unused-but-set-variable" LDFLAGS="-m64 -g" + ../configure --with-x --enable-xspice --enable-cider --enable-predictor --enable-osdi --enable-klu --with-readline=yes --enable-openmp --prefix="/usr/local" --libdir="/usr/local/lib" CFLAGS="-g -m64 -O0 -Wall -Wno-unused-but-set-variable" LDFLAGS="-m64 -g" else cd release if [ $? -ne 0 ]; then echo "cd release failed"; exit 1 ; fi echo "configuring for 64 bit release" echo - ../configure --with-x --enable-xspice --enable-cider --enable-predictor --enable-osdi --with-readline=yes --enable-openmp --disable-debug CFLAGS="-m64 -O2" LDFLAGS="-m64 -s" + ../configure --with-x --enable-xspice --enable-cider --enable-predictor --enable-osdi --enable-klu --with-readline=yes --enable-openmp --disable-debug --prefix="/usr/local" --libdir="/usr/local/lib" CFLAGS="-m64 -O2" LDFLAGS="-m64 -s" fi if [ $? -ne 0 ]; then echo "../configure failed"; exit 1 ; fi diff --git a/compile_linux_shared.sh b/compile_linux_shared.sh index 5d28d3930..02e3a0908 100755 --- a/compile_linux_shared.sh +++ b/compile_linux_shared.sh @@ -11,7 +11,7 @@ # --enable-osdi will add the osdi interface which allows to dynamically load compiled Verilog-A # compact models. Compiling the VA code of the models is done by the OpenVAF compiler. # Please see the ngspice manual, chapt. 13, for more info on OSDI/OpenVAF. -# CIDER, XSPICE, and OpenMP may be selected at will. +# CIDER, XSPICE, KLU, and OpenMP may be selected at will. # --disable-debug will give O2 optimization (versus O0 for debug) and removes all debugging info. # Add (optionally) --enable-relpath to avoid absolute paths when searching for code models. @@ -44,7 +44,7 @@ if test "$1" = "d"; then echo # The --prefix (and perhaps --libdir) may be used to determine a different install location # (depending on the Linux distribution, and on the calling programs search path). - ../configure --with-ngshared --enable-xspice --enable-cider --enable-openmp --enable-osdi --prefix=/usr CFLAGS="-g -m64 -O0 -Wall" LDFLAGS="-m64 -g" + ../configure --with-ngshared --enable-xspice --enable-cider --enable-openmp --enable-osdi --enable-klu --prefix=/usr CFLAGS="-g -m64 -O0 -Wall" LDFLAGS="-m64 -g" else cd releasesh if [ $? -ne 0 ]; then echo "cd releasesh failed"; exit 1 ; fi @@ -52,7 +52,7 @@ else echo # The --prefix (and perhaps --libdir) may be used to determine a different install location # (depending on the Linux distribution, and on the calling programs search path). - ../configure --with-ngshared --enable-xspice --enable-cider --enable-openmp --disable-debug --enable-osdi --prefix=/usr CFLAGS="-m64 -O2" LDFLAGS="-m64 -s" + ../configure --with-ngshared --enable-xspice --enable-cider --enable-openmp --enable-osdi --enable-klu --disable-debug --prefix=/usr CFLAGS="-m64 -O2" LDFLAGS="-m64 -s" fi if [ $? -ne 0 ]; then echo "../configure failed"; exit 1 ; fi diff --git a/compile_min_shared.sh b/compile_min_shared.sh index ce8bc9565..547306116 100644 --- a/compile_min_shared.sh +++ b/compile_min_shared.sh @@ -10,10 +10,8 @@ # for debug version of shared ngspice # Options: -# --adms and --enable-adms will install extra HICUM, EKV and MEXTRAM models via the -# adms interface. # Please see http://ngspice.sourceforge.net/admshowto.html for more info on adms. -# CIDER, XSPICE, and OpenMP may be selected at will. +# CIDER, XSPICE, KLU, and OpenMP may be selected at will. # --disable-debug will give O2 optimization (versus O0 for debug) and removes all debugging info. # To obtain a 32 bit executable, replace -m64 by -m32 ./configure lines. @@ -38,27 +36,19 @@ fi ./autogen.sh if [ $? -ne 0 ]; then echo "./autogen.sh failed"; exit 1 ; fi -# Alternatively, if compiling sources from git, and want to add adms created devices, -# you may need to uncomment the following two lines (and don't forget to add adms option -# to the ../configure statement): -#./autogen.sh --adms -#if [ $? -ne 0 ]; then echo "./autogen.sh failed"; exit 1 ; fi - echo if test "$1" = "d"; then cd debug-sh if [ $? -ne 0 ]; then echo "cd debug-sh failed"; exit 1 ; fi echo "configuring for 64 bit debug" echo -# You may add --enable-adms to the following command for adding adms generated devices - ../configure --with-ngshared --enable-xspice --enable-cider --enable-openmp --enable-osdi --enable-relpath --disable-debug prefix="C:/Spice64d" CFLAGS="-m64 -g -O0 -Wall" LDFLAGS="-m64" + ../configure --with-ngshared --enable-xspice --enable-cider --enable-openmp --enable-osdi --enable-klu --enable-relpath prefix="C:/Spice64d" CFLAGS="-m64 -g -O0 -Wall" LDFLAGS="-m64" else cd release-sh if [ $? -ne 0 ]; then echo "cd release-sh failed"; exit 1 ; fi echo "configuring for 64 bit release" echo -# You may add --enable-adms to the following command for adding adms generated devices - ../configure --with-ngshared --enable-xspice --enable-cider --enable-openmp --enable-osdi --enable-relpath --disable-debug prefix="C:/Spice64" CFLAGS="-m64 -O2" LDFLAGS="-m64 -s" + ../configure --with-ngshared --enable-xspice --enable-cider --enable-openmp --enable-osdi --enable-klu --enable-relpath --disable-debug prefix="C:/Spice64" CFLAGS="-m64 -O2" LDFLAGS="-m64 -s" fi if [ $? -ne 0 ]; then echo "../configure failed"; exit 1 ; fi diff --git a/examples/xspice/original-examples/diffpair.in b/examples/xspice/original-examples/diffpair.in index da321ce89..acaf2bad6 100644 --- a/examples/xspice/original-examples/diffpair.in +++ b/examples/xspice/original-examples/diffpair.in @@ -19,10 +19,10 @@ q4 7 7 9 qnl rbias 7 8 20k .model qnl npn(bf=80 rb=100 ccs=2pf tf=0.3ns tr=6ns cje=3pf cjc=2pf + va=50) -.print dc v(4) v(5) -.plot dc v(5) -.print ac vm(5) vp(5) -.plot ac vm(5) vp(5) +*.print dc v(4) v(5) +*.plot dc v(5) +*.print ac vm(5) vp(5) +*.plot ac vm(5) vp(5) .print tran v(4) v(5) .plot tran v(5) .end diff --git a/examples/xspice/original-examples/mosmem.in b/examples/xspice/original-examples/mosmem.in index 2654ca8ec..82e36d96b 100644 --- a/examples/xspice/original-examples/mosmem.in +++ b/examples/xspice/original-examples/mosmem.in @@ -21,7 +21,7 @@ m11 8 4 0 0 mod w=250u l=5u m12 9 9 8 0 mod w=5u l=5u .model mod nmos(vto=0.5 phi=0.7 kp=1.0e-6 gamma=1.83 lambda=0.115 + level=1 cgso=1u cgdo=1u cbd=50p cbs=50p) -.print dc v(5) v(6) -.plot dc v(6) +*.print dc v(5) v(6) +*.plot dc v(6) .plot tran v(6) v(5) v(7) v(1) v(2) .end diff --git a/examples/xspice/verilator/README.txt b/examples/xspice/verilator/README.txt new file mode 100644 index 000000000..e919ceab5 --- /dev/null +++ b/examples/xspice/verilator/README.txt @@ -0,0 +1,10 @@ +The circuit adc.cir in this directory illustrates the use of the d_cosim +XSPICE code model as a container for a Verilog simulation. Before the +simulation can be run, the Verilog code must be compiled by Verilator +using the command: + + ngspice vlnggen adc.v + +That should create a shared library file, adc.so (or adc.DLL on Windows) +that will be loaded by the d_cosim code model. The compiled Verilog code that +it contains will be executed during simulation. diff --git a/examples/xspice/verilator/adc.cir b/examples/xspice/verilator/adc.cir new file mode 100644 index 000000000..133a7dbf6 --- /dev/null +++ b/examples/xspice/verilator/adc.cir @@ -0,0 +1,103 @@ +Simulation of a switched-capacitor SAR ADC with Verilator and d_cosim + +.subckt sar_adc input vref start valid d5 d4 d3 d2 d1 d0 clk + +* A transmission gate connects the input to the capacitor set. + +xsample input iin sample vref tgate +rin iin test_v 1k + +* Capacitors and controlling inverters + +xb5 test_v vref d5 ccap c=1p +xb4 test_v vref d4 ccap c={1p / 2} +xb3 test_v vref d3 ccap c={1p / 4} +xb2 test_v vref d2 ccap c={1p / 8} +xb1 test_v vref d1 ccap c={1p / 16} +xb0 test_v vref d0 ccap c={1p / 32} +clast test_v 0 {1p / 32} + +* An XSPICE ADC bridge functions as a comparator. + +acomp [%vd(test_v vref)] [comp] comparator +.model comparator adc_bridge in_low=0 in_high=0 + +* The digital portion of the circuit is specified in compiled Verilog. +* Outputs inverted to cancel the inverter in subcircuit ccap, +* and produce the correct numerical output value. + +adut [ Clk Comp Start] [Sample Valid ~d5 ~d4 ~d3 ~d2 ~d1 ~d0] null dut +.model dut d_cosim simulation="./adc.so" +.ends // SUBCKT sar_adc + +* Some MOS transistors complete the circuit. +* Models from https://homepages.rpi.edu/~sawyes/AIMSPICE_TutorialManual.pdf + +.model p1 pmos ++ level=2 vto=-0.5 kp=8.5e-6 gamma=0.4 phi=0.65 lambda=0.05 xj=0.5e-6 +.model n1 nmos ++ level=2 vto=0.5 kp=24e-6 gamma=0.15 phi=0.65 lambda=0.015 xj=0.5e-6 + +* Use those for an inverter. + +.subckt ainv in out vdd +mn out in 0 0 n1 +mp out in vdd vdd p1 +.ends + +* A transmission gate modelled by a switch. + +.subckt mos_tgate a b ctl vdd +mn a ctl b b n1 +xinv ctl ictl vdd ainv +mp b ictl a a p1 +.ends + +.subckt tgate a b ctl vdd +switch a b ctl 0 tg +.model tg sw vt=1.5 ron=2k +.ends + +* The per-bit subcircuit in the adc + +.subckt ccap in vcc ctl c=10p +xinv ctl tail vcc ainv +cb in tail {c} +.ends + +**** End of the ADC and its subcircuits. Begin test circuit **** + +.param vcc=3.3 +vcc vcc 0 {vcc} + +* Digital clock signal + +aclock 0 clk clock +.model clock d_osc cntl_array=[-1 1] freq_array=[1Meg 1Meg] + +* A simple DAC so that the result may be compared to the input. + +r5 d5 sum 2 +r4 d4 sum 4 +r3 d3 sum 8 +r2 d2 sum 16 +r1 d1 sum 32 +r0 d0 sum 64 + +vamm sum 0 0 + +* Pulse the Start signal high for 1.3uS each 10uS + +Vpulse Start 0 PULSE 0 {vcc} 0.2u 10n 10n 1.3u 10u +Vtest input 0 PULSE 0 3 0 200u 200u 1u 401u + +* The ADC for testing + +xtest input vcc start valid d5 d4 d3 d2 d1 d0 clk sar_adc + + +.control +tran 100n 250u +plot input xtest.test_v vamm#branch clk/2 start/3 xtest.sample/3 valid +.endc +.end diff --git a/examples/xspice/verilator/adc.v b/examples/xspice/verilator/adc.v new file mode 100644 index 000000000..14bfcaccf --- /dev/null +++ b/examples/xspice/verilator/adc.v @@ -0,0 +1,45 @@ +// Digital control for a successive approximation ADC with switched capacitors. + +module adc(Clk, Comp, Start, Sample, Done, Result); + input wire Clk, Comp, Start; + output reg Sample, Done; + output reg [Bits - 1 : 0] Result; + + parameter Bits=6; + + reg [Bits - 1 : 0] SR; + reg Running; + + initial begin + $display("ADC simulation starting"); + Done = 0; + Sample = 0; + Result = 0; + Running = 0; + end + + always @(posedge(Clk)) begin + if (Running) begin + if (Sample) begin + Sample <= 0; + SR[Bits - 1] = 1; + Result[Bits - 1] = 1; + end else if (SR != 0) begin + if (Comp) + Result &= ~SR; + SR >>= 1; + Result |= SR; + if (SR == 0) begin + Running <= 0; + Done <= 1; + end + end + end else if (Start) begin + Running <= 1; + Sample <= 1; + Done <= 0; + SR = 0; + Result = 0; + end + end +endmodule diff --git a/src/frontend/Makefile.am b/src/frontend/Makefile.am index 14ae122ef..8781b8610 100644 --- a/src/frontend/Makefile.am +++ b/src/frontend/Makefile.am @@ -34,6 +34,8 @@ libfte_la_SOURCES = \ com_dump.h \ com_echo.c \ com_echo.h \ + com_fileio.c \ + com_fileio.h \ com_ghelp.c \ com_ghelp.h \ com_gnuplot.h \ diff --git a/src/frontend/com_fileio.c b/src/frontend/com_fileio.c new file mode 100644 index 000000000..078baa2c3 --- /dev/null +++ b/src/frontend/com_fileio.c @@ -0,0 +1,181 @@ +/* Commands for opening and reading arbitrary files. */ + +#include +#include +#include + +#include "ngspice/ngspice.h" + +#include "ngspice/bool.h" +#include "ngspice/wordlist.h" + +#include "com_strcmp.h" +#include "variable.h" + +/* Track open files with these structures, indexed by the underlying + * descriptor. Not many should be needed. + */ + +#define MAX_OPEN_FILES 20 +#define MAX_TEXT_LINE 8192 + +static struct { + FILE *fp; + char *name; +} Open_Files[MAX_OPEN_FILES]; + +/* Check whether error messages should be suppressed. That is useful when + * opening a file to see if it exists. + */ + +static int verbose(void) +{ + return !cp_getvar("silent_fileio", CP_BOOL, NULL, 0); +} + +/* fopen handle file_name [mode] + * + * For example: fopen handle result.txt r + * + * The underlying file descriptor (or -1) is returned in the variable "handle". + */ + +void com_fopen(wordlist *wl) +{ + char *var, *file_name, *mode; + FILE *fp; + int fd; + + var = wl->wl_word; + wl = wl->wl_next; + file_name = cp_unquote(wl->wl_word); + wl = wl->wl_next; + mode = wl ? cp_unquote(wl->wl_word) : "r"; + fp = fopen(file_name, mode); + if (fp) { + fd = fileno(fp); + if (fd < MAX_OPEN_FILES) { + if (Open_Files[fd].fp) // Not expected! + fclose(Open_Files[fd].fp); + if (Open_Files[fd].name) + tfree(Open_Files[fd].name); + Open_Files[fd].fp = fp; + Open_Files[fd].name = copy(file_name); + } else { + fclose(fp); + fprintf(stderr, + "com_fopen() cannot open %s: too many open files\n", + file_name); + fd = -1; + } + } else { + fd = -1; + if (verbose()) { + fprintf(stderr, "com_fopen() cannot open %s: %s\n", + file_name, strerror(errno)); + } + } + tfree(file_name); + if (wl) + tfree(mode); + cp_vset(var, CP_NUM, &fd); +} + +/* Command looks like: + * fread result handle [length] + * where handle is a small positive integer, result names a variable + * and length is the name of a variable used to return the length of the line. + * The returned length is -1 at EOF, -2 on failure. + */ + +void com_fread(wordlist *wl) +{ + char *handle, *result, *lvar; + int fd, length; + char buf[MAX_TEXT_LINE]; + + result = cp_unquote(wl->wl_word); + wl = wl->wl_next; + handle = cp_unquote(wl->wl_word); + fd = atoi(handle); + tfree(handle); + wl = wl->wl_next; + if (wl) + lvar = cp_unquote(wl->wl_word); + else + lvar = NULL; + + if (fd >= 0 && fd < MAX_OPEN_FILES) { + if (!Open_Files[fd].fp) { + /* Allow stdin, for example. */ + + Open_Files[fd].fp = fdopen(fd, "r"); + if (!Open_Files[fd].fp && verbose()) { + fprintf(stderr, "com_fread() cannot open handle %d\n", fd); + goto err; + } + } + + if (fgets(buf, sizeof buf, Open_Files[fd].fp)) { + length = strlen(buf); + if (length > 0 && buf[length - 1] == '\n') { + --length; + if (length > 0 && buf[length - 1] == '\r') { + /* Windows CRLF line termination. */ + + --length; + } + buf[length] = '\0'; + } else if (verbose()) { + fprintf(stderr, + "com_fread() found line in %s " + "too long for buffer\n", + Open_Files[fd].name); + } + } else { + if (feof(Open_Files[fd].fp)) { + length = -1; + } else if (verbose()) { + fprintf(stderr, + "com_fread() error reading %s: %s\n", + Open_Files[fd].name, strerror(errno)); + length = -2; + } + *buf = '\0'; + } + } else if (verbose()) { + fprintf(stderr, + "com_fread(): file handle %d is not in accepted range.\n", + fd); + err: + length = -1; + *buf = '\0'; + } + cp_vset(result, CP_STRING, buf); + tfree(result); + if (lvar) { + cp_vset(lvar, CP_NUM, &length); + tfree(lvar); + } +} + +void com_fclose(wordlist *wl) +{ + char *handle; + int fd; + + handle = cp_unquote(wl->wl_word); + fd = atoi(handle); + tfree(handle); + if (fd <= 2 || fd >= MAX_OPEN_FILES) + return; + if (Open_Files[fd].fp) { + fclose(Open_Files[fd].fp); + Open_Files[fd].fp = NULL; + } + if (Open_Files[fd].name) + tfree(Open_Files[fd].name); +} + + + diff --git a/src/frontend/com_fileio.h b/src/frontend/com_fileio.h new file mode 100644 index 000000000..ddb160555 --- /dev/null +++ b/src/frontend/com_fileio.h @@ -0,0 +1,9 @@ +#ifndef ngspice_COM_FILEIO_H +#define ngspice_COM_FILEIO_H + + +extern void com_fopen(wordlist *wl); +extern void com_fread(wordlist *wl); +extern void com_fclose(wordlist *wl); + +#endif diff --git a/src/frontend/com_measure2.c b/src/frontend/com_measure2.c index 5b947eacf..7033abbec 100644 --- a/src/frontend/com_measure2.c +++ b/src/frontend/com_measure2.c @@ -400,6 +400,11 @@ com_measure_when( return MEASUREMENT_FAILURE; } + if (dScale->v_realdata ==NULL && dScale->v_compdata == NULL) { + fprintf(cp_err, "Error: scale vector time, frequency or dc has no data.\n"); + return MEASUREMENT_FAILURE; + } + prevValue = 0.; prevValue2 = 0.; prevScaleValue = 0.; @@ -658,6 +663,11 @@ measure_at( return MEASUREMENT_FAILURE; } + if (dScale->v_realdata == NULL && dScale->v_compdata == NULL) { + fprintf(cp_err, "Error: scale vector time, frequency or dc has no data.\n"); + return MEASUREMENT_FAILURE; + } + /* ----------------------------------------------------------------- * Take the string tests outside of the loop for speed. * ----------------------------------------------------------------- */ @@ -768,7 +778,12 @@ measure_minMaxAvg( } if (dScale == NULL) { - fprintf(cp_err, "Error: no such vector as time, frquency or v-sweep.\n"); + fprintf(cp_err, "Error: no such vector as time, frequency or v-sweep.\n"); + return MEASUREMENT_FAILURE; + } + + if (dScale->v_realdata == NULL && dScale->v_compdata == NULL) { + fprintf(cp_err, "Error: scale vector time, frequency or v-sweep has no data.\n"); return MEASUREMENT_FAILURE; } @@ -944,7 +959,12 @@ measure_rms_integral( } if (xScale == NULL) { - fprintf(cp_err, "Error: no such vector as time.\n"); + fprintf(cp_err, "Error: no such vector as time, frequency or v-sweep.\n"); + return MEASUREMENT_FAILURE; + } + + if (xScale->v_realdata == NULL && xScale->v_compdata == NULL) { + fprintf(cp_err, "Error: scale vector time, frequency or v-sweep has no data.\n"); return MEASUREMENT_FAILURE; } diff --git a/src/frontend/com_strcmp.c b/src/frontend/com_strcmp.c index 014a8025a..4663d59b3 100644 --- a/src/frontend/com_strcmp.c +++ b/src/frontend/com_strcmp.c @@ -23,3 +23,54 @@ com_strcmp(wordlist *wl) tfree(s2); cp_vset(var, CP_NUM, &i); } + +/* These must be more evil still. */ + +void com_strstr(wordlist *wl) +{ + char *var, *s1, *s2; + int i; + + s1 = cp_unquote(wl->wl_next->wl_word); + s2 = cp_unquote(wl->wl_next->wl_next->wl_word); + if (*s2) { + var = strstr(s1, s2); // Search for s2 in s1 + if (var) + i = var - s1; // Offset to match + else + i = -1; + } else { + i = strlen(s1); // Length + } + tfree(s1); + tfree(s2); + cp_vset(wl->wl_word, CP_NUM, &i); +} + +void com_strslice(wordlist *wl) +{ + char *var, *s1, *tp, tmp; + int offset, length, actual; + + var = wl->wl_word; + wl = wl->wl_next; + s1 = cp_unquote(wl->wl_word); + wl = wl->wl_next; + offset = atoi(wl->wl_word); + length = atoi(wl->wl_next->wl_word); + actual = strlen(s1); + if (offset < 0) + offset = actual + offset; + if (length + offset > actual) + length = actual - offset; + if (length > 0 && offset >= 0) { + tp = s1 + offset + length; + tmp = *tp; + *tp = '\0'; + cp_vset(var, CP_STRING, s1 + offset); + *tp = tmp; + } else { + cp_vset(var, CP_STRING, ""); + } + tfree(s1); +} diff --git a/src/frontend/com_strcmp.h b/src/frontend/com_strcmp.h index cdaeaffda..e9a3dd68d 100644 --- a/src/frontend/com_strcmp.h +++ b/src/frontend/com_strcmp.h @@ -1,7 +1,8 @@ #ifndef ngspice_COM_STRCMP_H #define ngspice_COM_STRCMP_H - -void com_strcmp(wordlist *wl); +extern void com_strcmp(wordlist *wl); +extern void com_strstr(wordlist *wl); +extern void com_strslice(wordlist *wl); #endif diff --git a/src/frontend/commands.c b/src/frontend/commands.c index 8e2988c56..36f30f72f 100644 --- a/src/frontend/commands.c +++ b/src/frontend/commands.c @@ -82,6 +82,7 @@ #include "resource.h" #include "diff.h" #include "com_strcmp.h" +#include "com_fileio.h" #include "ngspice/randnumb.h" #include "../spicelib/analysis/com_optran.h" #include "com_wr_ic.h" @@ -613,6 +614,30 @@ struct comm spcp_coms[] = { { 0, 0, 0, 0 }, E_DEFHMASK, 3, 3, NULL, "varname s1 s2 : Set $varname to strcmp(s1, s2)." } , + { "strstr", com_strstr, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 3, 3, + NULL, + "varname s1 s2 : Set $varname to strstr(s1, s2)." } , + { "strslice", com_strslice, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 4, 4, + NULL, + "varname s1 offset length : " + "Set $varname to s1[offset ... offset+length]" } , + { "fopen", com_fopen, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 2, 3, + NULL, + "handle file_name [mode] : " + "Open file_name with mode, return handle in $handle" } , + { "fread", com_fread, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 2, 3, + NULL, + "handle result [length] : " + "Read a line from open file handle, " + "data in $result, status in $length" } , + { "fclose", com_fclose, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 1, 1, + NULL, + "handle : Close open file" } , { "linearize", com_linearize, FALSE, FALSE, { 040000, 040000, 040000, 040000 }, E_DEFHMASK, 0, LOTS, NULL, @@ -1033,6 +1058,30 @@ struct comm nutcp_coms[] = { { 0, 0, 0, 0 }, E_DEFHMASK, 3, 3, NULL, "varname s1 s2 : Set $varname to strcmp(s1, s2)." } , + { "strstr", com_strstr, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 3, 3, + NULL, + "varname s1 s2 : Set $varname to strstr(s1, s2)." } , + { "strslice", com_strslice, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 4, 4, + NULL, + "varname s1 offset length : " + "Set $varname to s1[offset ... offset+length]" } , + { "fopen", com_fopen, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 2, 3, + NULL, + "handle file_name [mode] : " + "Open file_name with mode, return handle in $handle" } , + { "fread", com_fread, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 2, 3, + NULL, + "handle result [length] : " + "Read a line from open file handle, " + "data in $result, status in $length" } , + { "fclose", com_fclose, FALSE, FALSE, + { 0, 0, 0, 0 }, E_DEFHMASK, 1, 1, + NULL, + "handle : Close open file" } , { "linearize", com_linearize, TRUE, FALSE, { 040000, 040000, 040000, 040000 }, E_DEFHMASK, 0, LOTS, NULL, diff --git a/src/frontend/dvec.c b/src/frontend/dvec.c index 5bc7b5df6..71e45c125 100644 --- a/src/frontend/dvec.c +++ b/src/frontend/dvec.c @@ -1,6 +1,54 @@ + + +#if defined(__MINGW32__) || defined(_MSC_VER) +#include +#endif + #include "ngspice/ngspice.h" #include "ngspice/dvec.h" +#if defined SHARED_MODULE + +/*Use Windows threads if on W32 without pthreads*/ +#ifndef HAVE_LIBPTHREAD + +#if defined(__MINGW32__) || defined(_MSC_VER) +//#if defined(_MSC_VER) +#ifdef SRW +#define mutex_lock(a) AcquireSRWLockExclusive(a) +#define mutex_unlock(a) ReleaseSRWLockExclusive(a) +typedef SRWLOCK mutexType; +#else +#define mutex_lock(a) EnterCriticalSection(a) +#define mutex_unlock(a) LeaveCriticalSection(a) +typedef CRITICAL_SECTION mutexType; +#endif +#define thread_self() GetCurrentThread() +#define threadid_self() GetCurrentThreadId() +typedef HANDLE threadId_t; +#define WIN_THREADS +#define THREADS + +#endif + +#else + +#include +#define mutex_lock(a) pthread_mutex_lock(a) +#define mutex_unlock(a) pthread_mutex_unlock(a) +#define thread_self() pthread_self() +#define threadid_self() 0 //FIXME t.b.d. +typedef pthread_mutex_t mutexType; +typedef pthread_t threadId_t; +#define THREADS +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +static bool cont_condition; + +#endif + +extern mutexType vecreallocMutex; + +#endif struct dvec *dvec_alloc(/* NOT const -- assigned to char */ char *name, int type, short flags, int length, void *storage) @@ -58,7 +106,7 @@ struct dvec *dvec_alloc(/* NOT const -- assigned to char */ char *name, } /* end of function dvec_alloc */ -/* Resize dvec to length if storage is NULL orr replace +/* Resize dvec to length if storage is NULL or replace * its existing allocation with storage if not */ void dvec_realloc(struct dvec *v, int length, void *storage) @@ -86,17 +134,26 @@ void dvec_realloc(struct dvec *v, int length, void *storage) v->v_alloc_length = length; } /* end of function dvec_realloc */ - +/* called from plotAddReal(Complex)Value, to increase + storage for result vectors. + In shared ngspice this may be locked, e.g. during plotting in the primary + thread, while the simulation is running in the background thread. Locking and unlocking + is done by API functions ngSpice_LockRealloc(), ngSpice_UnlockRealloc(). */ void dvec_extend(struct dvec *v, int length) { +#if defined SHARED_MODULE + mutex_lock(&vecreallocMutex); +#endif if (isreal(v)) { v->v_realdata = TREALLOC(double, v->v_realdata, length); } else { v->v_compdata = TREALLOC(ngcomplex_t, v->v_compdata, length); } - v->v_alloc_length = length; +#if defined SHARED_MODULE + mutex_unlock(&vecreallocMutex); +#endif } /* end of function dvec_extend */ diff --git a/src/frontend/inp.c b/src/frontend/inp.c index aba8450d1..3a1a22300 100644 --- a/src/frontend/inp.c +++ b/src/frontend/inp.c @@ -32,6 +32,7 @@ Author: 1985 Wayne A. Christopher #include "subckt.h" #include "spiceif.h" #include "com_let.h" +#include "com_set.h" #include "com_commands.h" #ifdef XSPICE @@ -57,8 +58,9 @@ static wordlist *inp_savecurrents(struct card *deck, struct card *options, wordlist *wl, wordlist *controls); static void recifeval(struct card *pdeck); static char *upper(register char *string); +#ifdef REM_UNUSED static void rem_unused_mos_models(struct card* deck); - +#endif extern void com_optran(wordlist * wl); extern void tprint(struct card *deck); @@ -88,6 +90,17 @@ extern void exec_controls(wordlist *controls); extern void SetSource(char *Name); #endif +#if defined (_MSC_VER) +typedef struct timeval { + long tv_sec; + long tv_usec; +} timeval; +#endif +#if defined (_MSC_VER) || defined (__MINGW32__) +extern int gettimeofday(struct timeval* tp, void* unused); +#endif + + /* structure used to save expression parse trees for .model and * device instance lines */ @@ -100,6 +113,7 @@ struct pt_temper { struct pt_temper *next; }; + static int inp_parse_temper(struct card *deck, struct pt_temper **motdlist_p, struct pt_temper **devtlist_p); @@ -438,9 +452,10 @@ eval_opt(struct card* deck) char* token = gettok(&begtok); /* option seed=random [seed='random'] */ if (eq(token, "random") || eq(token, "{random}")) { - time_t acttime = time(NULL); - /* get random value from time in seconds since 1.1.1970 */ - int rseed = (int)(acttime - 1600000000); + struct timeval tv; + gettimeofday(&tv, NULL); + /* get random value from current timestamp microseconds */ + int rseed = (int)(tv.tv_usec); cp_vset("rndseed", CP_NUM, &rseed); com_sseed(NULL); has_seed = TRUE; @@ -623,10 +638,71 @@ inp_spsource(FILE *fp, bool comfile, char *filename, bool intfile) * the cards .control and .endc, unless comfile is TRUE, in which * case every line must be a front-end command. There are too * many problems with matching the first word on the line. */ + ld = deck; if (comfile) { + bool with_params = FALSE; + +#ifndef SHARED_MODULE + if (ciprefix("*ng_script_with_params", deck->line)) { + extern char **Copy_of_argv; // main.c + extern int optind; // Library function getopt() + static const char header[] = "argc = %u argv = ( "; + wordlist *setarg; + unsigned int argc, size; + char *p_buf_active; /* buffer in use */ + char buf[BSIZE_SP]; + + /* Not just a command script, but one requesting arguments + * from the program's command line. + * This is similar to cp_oddcomm() (cpitf.c), + * but arguments are taken from the original program command. + */ + + with_params = TRUE; + size = sizeof header + 10; // Allow for %u and close. + for (argc = 0; Copy_of_argv[optind + argc]; ++argc) + size += strlen(Copy_of_argv[optind + argc]); + size += 3 * argc; // Spaces and quotes. + if (size <= sizeof buf) + p_buf_active = buf; + else + p_buf_active = TMALLOC(char, size); + + /* Fill the buffer. */ + + size = sprintf(p_buf_active, header, argc); + while (Copy_of_argv[optind]) { + char c, *fmt; + + c = Copy_of_argv[optind][0]; + if ((c >= '0' && c <= '9') || c == '+' || c == '-' || !c) { + /* Looks like a number or empty string - quote it. */ + + fmt = " \"%s\""; + } else { + fmt = " %s"; + } + size += sprintf(p_buf_active + size, fmt, + Copy_of_argv[optind++]); + } + strcpy(p_buf_active + size, " )"); + + /* Treat the buffer as a "set" command to create argv and argc. */ + + setarg = cp_lexer(p_buf_active); + com_set(setarg); + wl_free(setarg); + + /* Free buffer allocation if made */ + + if (p_buf_active != buf) + txfree(p_buf_active); + } +#endif /* Process each command, except 'option' which is assembled in a list and ingnored here */ + for (dd = deck; dd; dd = ld) { ld = dd->nextcard; if ((dd->line[0] == '*') && (dd->line[1] != '#')) @@ -641,6 +717,12 @@ inp_spsource(FILE *fp, bool comfile, char *filename, bool intfile) cp_evloop(dd->line); } } + + if (with_params) { + cp_remvar("argc"); + cp_remvar("argv"); + } + /* free the control deck */ line_free(deck, TRUE); /* set to NULL to allow generation of a new dbs */ @@ -929,10 +1011,11 @@ inp_spsource(FILE *fp, bool comfile, char *filename, bool intfile) } /* merge the two option line structs - com_options (comfile == TRUE, filled in from spinit, .spiceinit, and *ng_sript), and - options (comfile == FALSE, filled in from circuit with .OPTIONS) + com_options (comfile == TRUE, filled in from spinit, + .spiceinit, and *ng_script), and options + (comfile == FALSE, filled in from circuit with .OPTIONS) into options, thus keeping com_options, - options is loaded into circuit and freed when circuit is removed */ + options is loaded into circuit and freed when circuit is removed */ options = line_reverse(line_nconc(options, inp_deckcopy(com_options))); /* List of all expressions found in instance and .model lines */ @@ -955,14 +1038,14 @@ inp_spsource(FILE *fp, bool comfile, char *filename, bool intfile) /* If user wants all currents saved (.options savecurrents), add .save to wl_first with all terminal currents available on selected devices */ wl_first = inp_savecurrents(deck, options, wl_first, controls); - +#ifdef REM_UNUSED /* Circuit is flat, all numbers expanded. So again try to remove unused MOS models. All binning models are still here when w or l have been determined by an expression. */ -// if (newcompat.hs || newcompat.spe) -// rem_unused_mos_models(deck->nextcard); - + if (newcompat.hs || newcompat.spe) + rem_unused_mos_models(deck->nextcard); +#endif /* now load deck into ft_curckt -- the current circuit. */ if(inp_dodeck(deck, tt, wl_first, FALSE, options, filename) != 0) return 1; @@ -2541,6 +2624,7 @@ struct mlist { bool checked; }; +#ifdef REM_UNUSED /* Finally get rid of unused MOS models */ static void rem_unused_mos_models(struct card* deck) { struct card *tmpc, *tmppc = NULL; @@ -2744,3 +2828,4 @@ static void rem_unused_mos_models(struct card* deck) { modellist = tlist; } } +#endif diff --git a/src/frontend/inpcom.c b/src/frontend/inpcom.c index f3ede2101..327311b71 100644 --- a/src/frontend/inpcom.c +++ b/src/frontend/inpcom.c @@ -584,6 +584,7 @@ static void delete_names(struct names *p) } #ifndef _MSC_VER +#ifdef CIDER /* concatenate 2 strings, with space if spa == TRUE, return malloced string (replacement for tprintf, which is not efficient enough when reading PDKs @@ -613,6 +614,7 @@ static char *cat2strings(char *s1, char *s2, bool spa) return strsum; } #endif +#endif /* line1 @@ -1589,8 +1591,11 @@ struct inp_read_t inp_read( FILE *fp, int call_depth, const char *dir_name, /* add Inp_Path to buffer while keeping the sourcepath variable contents */ if (ciprefix("set", buffer)) { - char *p = strstr(buffer, "sourcepath"); - if (p) { + char *p; + + p = skip_ws(buffer + 3); // Next word + if (strncmp(p, "sourcepath", 10) == 0 && + skip_non_ws(p) == p + 10) { p = strchr(buffer, ')'); if (p) { *p = 0; // clear ) and insert Inp_Path in between @@ -3108,7 +3113,8 @@ void inp_casefix(char *string) /* Special treatment of code model file input. */ if (ciprefix(".model", string)) - tmpstr = strstr(string, "file="); + tmpstr = strstr(string, "file=\""); + #endif keepquotes = ciprefix(".param", string); // Allow string params @@ -3184,8 +3190,6 @@ static void inp_stripcomments_deck(struct card *c, bool cf) If there is only white space before the end-of-line comment the the whole line is converted to a normal comment line (i.e. one that begins with a '*'). - BUG: comment characters in side of string literals are not ignored - ('$' outside of .control section is o.k. however). If the comaptibility mode is PS, LTPS or LTPSA, '$' is treated as a valid character, not as end-of-line comment delimiter, except for that it is @@ -3202,6 +3206,24 @@ static void inp_stripcomments_line(char *s, bool cs) /* look for comments */ while ((c = *d) != '\0') { d++; + + /* Skip over single or double-quoted strings. */ + + if (c == '"') { + while ((c = *d) && (c != '"' || d[-1] == '\\')) + ++d; + if (c) + ++d; + continue; + } + if (c == '\'') { + while ((c = *d) && (c != '\'' || d[-1] == '\\')) + ++d; + if (c) + ++d; + continue; + } + if (*d == ';') { break; } @@ -3310,7 +3332,8 @@ static char *inp_fix_subckt(struct names *subckt_w_params, char *s) char *equal, *beg, *buffer, *ptr1, *ptr2, *new_str; equal = strchr(s, '='); - if (equal && !strstr(s, "params:")) { + if (equal && + (!strstr(s, "params:") || !isspace_c(s[-1]))) { /* get subckt name (ptr1 will point to name) */ ptr1 = skip_non_ws(s); ptr1 = skip_ws(ptr1); @@ -3481,7 +3504,7 @@ static void inp_fix_for_numparam( if (ciprefix(".subckt", c->line) || ciprefix("x", c->line)) { /* remove params: */ char *str_ptr = strstr(c->line, "params:"); - if (str_ptr) + if (str_ptr && isspace_c(str_ptr[-1])) memcpy(str_ptr, " ", 7); } @@ -3782,13 +3805,14 @@ static int inp_fix_subckt_multiplier(struct names *subckt_w_params, char *subckt_param_names[], char *subckt_param_values[]) { struct card *card; - char *new_str; + char *new_str, *s; subckt_param_names[num_subckt_params] = copy("m"); subckt_param_values[num_subckt_params] = copy("1"); num_subckt_params++; - if (!strstr(subckt_card->line, "params:")) { + s = strstr(subckt_card->line, "params:"); + if (!s || !isspace_c(s[-1])) { new_str = tprintf("%s params: m=1", subckt_card->line); add_name(subckt_w_params, get_subckt_model_name(subckt_card->line)); } @@ -7842,6 +7866,9 @@ static void inp_quote_params(struct card *c, struct card *end_c, { bool in_control = FALSE; + if (ft_skywaterpdk) + return; + for (; c && c != end_c; c = c->nextcard) { int i, j, num_terminals; @@ -8333,6 +8360,7 @@ static void inp_check_syntax(struct card *deck) bool mwarn = FALSE; char* subs[10]; /* store subckt lines */ int ends = 0; /* store .ends line numbers */ + static bool nesting_once = TRUE; /* prevent crash in inp.c, fcn inp_spsource: */ if (ciprefix(".param", deck->line) || ciprefix(".meas", deck->line)) { @@ -8405,10 +8433,12 @@ static void inp_check_syntax(struct card *deck) } } // nesting may be critical if params are involved - if (check_subs > 0 && strchr(cut_line, '=')) + if (nesting_once && check_subs > 0 && strchr(cut_line, '=')) { fprintf(cp_err, - "\nWarning: Nesting of subcircuits with parameters " - "is only marginally supported!\n\n"); + "\nWarning: Nesting of subcircuits with parameters " + "is only marginally supported!\n\n"); + nesting_once = FALSE; + } if (check_subs < 10) subs[check_subs] = cut_line; else diff --git a/src/frontend/options.c b/src/frontend/options.c index d246ad318..ac12dc0b3 100644 --- a/src/frontend/options.c +++ b/src/frontend/options.c @@ -23,7 +23,7 @@ Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group bool ft_acctprint = FALSE, ft_noacctprint = FALSE, ft_listprint = FALSE; bool ft_nodesprint = FALSE, ft_optsprint = FALSE, ft_noinitprint = FALSE; -bool ft_norefprint = FALSE; +bool ft_norefprint = FALSE, ft_skywaterpdk = FALSE; bool ft_ngdebug = FALSE, ft_nginfo = FALSE, ft_stricterror = FALSE; static void setdb(char *str); @@ -307,6 +307,8 @@ cp_usrset(struct variable *var, bool isset) ft_ngdebug = isset; } else if (eq(var->va_name, "nginfo")) { ft_nginfo = isset; + } else if (eq(var->va_name, "skywaterpdk")) { + ft_skywaterpdk = isset; } else if (eq(var->va_name, "noinit")) { ft_noinitprint = isset; } else if (eq(var->va_name, "norefvalue")) { diff --git a/src/frontend/outitf.c b/src/frontend/outitf.c index d557bf5a3..b4d6955ac 100644 --- a/src/frontend/outitf.c +++ b/src/frontend/outitf.c @@ -1542,6 +1542,7 @@ InterpFileAdd(runDesc *run, IFvalue *refValue, IFvalue *valuePtr) newval = (timestep - run->circuit->CKTstep - timeold)/(timenew - timeold) * (valuenew[i] - valueold[i]) + valueold[i]; fileAddRealValue(run->fp, run->binary, newval); valueold[i] = valuenew[i]; + timeold = refValue->rValue; } else if (nodata) /* Just keep the transient output value corresponding to timeold, @@ -1573,6 +1574,7 @@ InterpFileAdd(runDesc *run, IFvalue *refValue, IFvalue *valuePtr) newval = (timestep - run->circuit->CKTstep - timeold)/(timenew - timeold) * (valuenew[i] - valueold[i]) + valueold[i]; fileAddRealValue(run->fp, run->binary, newval); valueold[i] = valuenew[i]; + timeold = refValue->rValue; } else if (nodata) /* Just keep the transient output value corresponding to timeold, @@ -1705,6 +1707,7 @@ InterpPlotAdd(runDesc *run, IFvalue *refValue, IFvalue *valuePtr) newval = (timestep - run->circuit->CKTstep - timeold)/(timenew - timeold) * (valuenew[i] - valueold[i]) + valueold[i]; plotAddRealValue(&run->data[i], newval); valueold[i] = valuenew[i]; + timeold = refValue->rValue; } else if (nodata) /* Just keep the transient output value corresponding to timeold, @@ -1727,6 +1730,7 @@ InterpPlotAdd(runDesc *run, IFvalue *refValue, IFvalue *valuePtr) newval = (timestep - run->circuit->CKTstep - timeold)/(timenew - timeold) * (valuenew[i] - valueold[i]) + valueold[i]; plotAddRealValue(&run->data[i], newval); valueold[i] = valuenew[i]; + timeold = refValue->rValue; } else if (nodata) /* Just keep the transient output value corresponding to timeold, diff --git a/src/frontend/subckt.c b/src/frontend/subckt.c index 6de79afd8..d72a4ef90 100644 --- a/src/frontend/subckt.c +++ b/src/frontend/subckt.c @@ -61,6 +61,7 @@ Modified: 2000 AlansFixes #include "ngspice/fteinp.h" #include "ngspice/stringskip.h" #include "ngspice/compatmode.h" +#include "ngspice/hash.h" #include @@ -95,14 +96,17 @@ static int translate(struct card *deck, char *formal, int flen, char *actual, struct bxx_buffer; static void finishLine(struct bxx_buffer *dst, char *src, char *scname); static int settrans(char *formal, int flen, char *actual, const char *subname); -static char *gettrans(const char *name, const char *name_end); +static char *gettrans(const char *name, const char *name_end, bool *isglobal); static int numnodes(const char *line, struct subs *subs, wordlist const *modnames); static int numdevs(char *s); static wordlist *modtranslate(struct card *deck, char *subname, wordlist *new_modnames); static void devmodtranslate(struct card *deck, char *subname, wordlist * const orig_modnames); static int inp_numnodes(char c); -#define N_GLOBAL_NODES 1005 +/* hash table to store the global nodes + * For now its use is limited to avoid double entries in global_nodes[] */ +static NGHASHPTR glonodes = NULL; +#define DUMMYDATA ((void *)42) /*--------------------------------------------------------------------- * table is used in settrans and gettrans -- it holds the netnames used @@ -137,19 +141,16 @@ static bool use_numparams = FALSE; static char start[32], sbend[32], invoke[32], model[32]; -static char *global_nodes[N_GLOBAL_NODES]; -static int num_global_nodes; - - static void collect_global_nodes(struct card *c) { - num_global_nodes = 0; - - global_nodes[num_global_nodes++] = copy("0"); + /* hash table for global nodes */ + glonodes = nghash_init(NGHASH_MIN_SIZE); + /* add 0 and null as global nodes */ + nghash_insert(glonodes, "0", DUMMYDATA); #ifdef XSPICE - global_nodes[num_global_nodes++] = copy("null"); + nghash_insert(glonodes, "null", DUMMYDATA); #endif for (; c; c = c->nextcard) @@ -157,17 +158,20 @@ collect_global_nodes(struct card *c) char *s = c->line; s = nexttok(s); while (*s) { - if (num_global_nodes == N_GLOBAL_NODES) { - fprintf(stderr, "ERROR, N_GLOBAL_NODES overflow\n"); - controlled_exit(EXIT_FAILURE); - } char *t = skip_non_ws(s); - global_nodes[num_global_nodes++] = copy_substring(s, t); + /* global node name */ + char *gnode = copy_substring(s, t); + /* insert only if not yet found in table */ + if (gnode && *gnode != '\0' && nghash_find(glonodes, gnode) == NULL) { + nghash_insert(glonodes, gnode, DUMMYDATA); + } + tfree(gnode); s = skip_ws(t); } c->line[0] = '*'; /* comment it out */ } + #ifdef TRACE { int i; @@ -184,10 +188,7 @@ collect_global_nodes(struct card *c) static void free_global_nodes(void) { - int i; - for (i = 0; i < num_global_nodes; i++) - tfree(global_nodes[i]); - num_global_nodes = 0; + nghash_free(glonodes, NULL, NULL); } @@ -1107,14 +1108,19 @@ bxx_buffer(struct bxx_buffer *t) static void translate_node_name(struct bxx_buffer *buffer, const char *scname, const char *name, const char *name_e) { - const char *t; + bool isglobal; + if (!name_e) name_e = strchr(name, '\0'); - t = gettrans(name, name_e); + t = gettrans(name, name_e, &isglobal); + if (t) { bxx_put_cstring(buffer, t); + /* free only if t is global node, nodes from table[].t_new are freed elsewhere */ + if(isglobal) + tfree(t); } else { bxx_put_cstring(buffer, scname); bxx_putc(buffer, '.'); @@ -1624,21 +1630,28 @@ eq_substr(const char *str, const char *end, const char *cstring) * otherwise it returns NULL. *------------------------------------------------------------------------------*/ static char * -gettrans(const char *name, const char *name_end) +gettrans(const char *name, const char *name_end, bool *isglobal) { int i; + *isglobal = FALSE; if (!name_end) name_end = strchr(name, '\0'); /* Added by H.Tanaka to translate global nodes */ - for (i = 0; iwl_word + i, cp_dol)) != NULL) { + while ((s_dollar = strchr(wl->wl_word + i, '$')) != NULL) { int prefix_len = (int) (s_dollar - wl->wl_word); @@ -1002,6 +1009,8 @@ wordlist *vareval(/* NOT const */ char *string) if (eq(v->va_name, string)) break; if (!v && isdigit_c(*string)) { + /* Treat $i for some integer i as argv[i] - positional parameters. */ + for (v = variables; v; v = v->va_next) { if (eq(v->va_name, "argv")) { break; diff --git a/src/include/ngspice/cmconstants.h b/src/include/ngspice/cmconstants.h index abfd45fa4..73563e14f 100644 --- a/src/include/ngspice/cmconstants.h +++ b/src/include/ngspice/cmconstants.h @@ -52,6 +52,7 @@ NON-STANDARD FEATURES #define ANALOG MIF_ANALOG #define EVENT MIF_EVENT_DRIVEN +#define STEP_PENDING MIF_STEP_PENDING #endif diff --git a/src/include/ngspice/cmproto.h b/src/include/ngspice/cmproto.h index 7ab6ffbd5..17518160c 100644 --- a/src/include/ngspice/cmproto.h +++ b/src/include/ngspice/cmproto.h @@ -97,6 +97,7 @@ int cm_message_printf(const char *fmt, ...); double cm_netlist_get_c(void); double cm_netlist_get_l(void); +void cm_irreversible(unsigned int); const char *cm_get_node_name(const char *, unsigned int); bool cm_probe_node(unsigned int, unsigned int, void *); bool cm_schedule_output(unsigned int, unsigned int, double, void *); diff --git a/src/include/ngspice/cosim.h b/src/include/ngspice/cosim.h new file mode 100644 index 000000000..dd6b97094 --- /dev/null +++ b/src/include/ngspice/cosim.h @@ -0,0 +1,71 @@ +/* Header file for the shim code between d_cosim and a co-simulator. */ + +#if __cplusplus +extern "C" { +#endif + +/* A value of this type controls how the step() function is called. + * The normal method is to call step() to advance to the time of a + * queued input, then supply the input, then call step again to + * advance to the next input time or the end of the current SPICE + * timestep. But Verilator does nothing without an input change, + * so step() must be called after input. + */ + +typedef enum {Normal, After_input} Cosim_method; + +/* Structure used by Cosim_setup() to pass and return + * co-simulation interface information. + */ + +struct co_info { + /* The co-simulator must set the number of ports in Cosim_setup(). */ + + unsigned int in_count; + unsigned int out_count; + unsigned int inout_count; + + /* The co-simulator may specify a function to be called just before + * it is unloaded at the end of a simulation run. + */ + + void (*cleanup)(struct co_info *); + + /* Function called by SPICE to advance the co-simulation. + * A pointer to this structure is passed, so it has access to its handle + * and the target simulation time, vtime. The co-simulator should + * pause the step when output is produced and update vtime. + */ + + void (*step)(struct co_info *pinfo); // Advance simulation. + + /* Function called by SPICE to pass input to input and inout ports. + * (Inouts after inputs.) + * Called as: + * struct co_info info; + * (*in_fn)(&info, bit_number, &value); + * Function provided by co-simulator. + */ + + void (*in_fn)(struct co_info *, unsigned int, Digital_t *); + + /* Function called by co-simulator to report output on + * output and inout ports. (Inouts after outputs.) + * Called as: + * struct co_info *p_info; + * (*out_fn)(p_info, bit_number, &value); + * It will usually be called inside a call to step(). + */ + + void (*out_fn)(struct co_info *, unsigned int, Digital_t *); + void *handle; // Co-simulator's private handle + double vtime; // Time in the co-simulation. + Cosim_method method; // May be set in Cosim_setup; +}; + +extern void Cosim_setup(struct co_info *pinfo); // This must exist. +extern void Cosim_step(struct co_info *pinfo); // Exists for Verilator. + +#if __cplusplus +} +#endif diff --git a/src/include/ngspice/cpextern.h b/src/include/ngspice/cpextern.h index b051e0bcd..00ab7f7da 100644 --- a/src/include/ngspice/cpextern.h +++ b/src/include/ngspice/cpextern.h @@ -156,7 +156,6 @@ extern bool cp_ignoreeof; extern bool cp_noclobber; extern bool cp_noglob; extern bool cp_nonomatch; -extern char cp_dol; extern void cp_remvar(char *varname); void cp_vset(const char *varname, enum cp_types type, const void *value); extern struct variable *cp_setparse(wordlist *wl); diff --git a/src/include/ngspice/dllitf.h b/src/include/ngspice/dllitf.h index 1d9295d81..eaeef3d77 100644 --- a/src/include/ngspice/dllitf.h +++ b/src/include/ngspice/dllitf.h @@ -59,6 +59,7 @@ struct coreInfo_t { int ((*dllitf_cm_message_send)(char *)); double ((*dllitf_cm_netlist_get_c)(void)); double ((*dllitf_cm_netlist_get_l)(void)); + void ((*dllitf_cm_irreversible)(unsigned int)); const char * ((*dllitf_cm_get_node_name)(const char *, unsigned int)); bool ((*dllitf_cm_probe_node)(unsigned int, unsigned int, void *)); diff --git a/src/include/ngspice/evt.h b/src/include/ngspice/evt.h index 7dfb35c27..0f01779d5 100644 --- a/src/include/ngspice/evt.h +++ b/src/include/ngspice/evt.h @@ -91,35 +91,27 @@ struct Evt_Node_Info { }; struct Evt_Inst_Info { - Evt_Inst_Info_t *next; /* the next in the linked list of node info */ + Evt_Inst_Info_t *next; /* the next in the linked list */ MIFinstance *inst_ptr; /* Pointer to MIFinstance struct for this instance */ }; struct Evt_Info { - Evt_Inst_Info_t *inst_list; /* static info about event/hybrid instances */ - Evt_Node_Info_t *node_list; /* static info about event nodes */ - Evt_Port_Info_t *port_list; /* static info about event ports */ - Evt_Output_Info_t *output_list; /* static info about event outputs */ - int *hybrid_index; /* vector of inst indexs for hybrids */ - Evt_Inst_Info_t **inst_table; /* vector of pointers to elements in inst_list */ - Evt_Node_Info_t **node_table; /* vector of pointers to elements in node_list */ - Evt_Port_Info_t **port_table; /* vector of pointers to elements in port_list */ - Evt_Output_Info_t **output_table; /* vector of pointers to elements in output_list */ + Evt_Inst_Info_t *inst_list; /* static info about event instances */ + Evt_Node_Info_t *node_list; /* static info about event nodes */ + Evt_Port_Info_t *port_list; /* static info about event ports */ + Evt_Output_Info_t *output_list; /* static info about event outputs */ + MIFinstance **hybrids; /* vector of inst pointers for hybrids */ + Evt_Inst_Info_t **inst_table; /* vector of pointers to elements in inst_list */ + Evt_Node_Info_t **node_table; /* vector of pointers to elements in node_list */ + Evt_Port_Info_t **port_table; /* vector of pointers to elements in port_list */ + Evt_Output_Info_t **output_table; /* vector of pointers to elements in output_list */ }; - - - - - - /* *************** */ /* Queue structure */ /* *************** */ - - struct Evt_Inst_Event { Evt_Inst_Event_t *next; /* the next in the linked list */ double event_time; /* Time for this event to happen */ diff --git a/src/include/ngspice/evtproto.h b/src/include/ngspice/evtproto.h index f37bb0fa7..ea2bd5f92 100644 --- a/src/include/ngspice/evtproto.h +++ b/src/include/ngspice/evtproto.h @@ -92,7 +92,9 @@ void EVTqueue_inst( void EVTdequeue(CKTcircuit *ckt, double time); -int EVTload(CKTcircuit *ckt, int inst_index); +int EVTload(CKTcircuit *ckt, MIFinstance *inst); + +int EVTload_with_event(CKTcircuit *ckt, MIFinstance *inst, Mif_Call_Type_t type); void EVTprint(wordlist *wl); void EVTprintvcd(wordlist *wl); diff --git a/src/include/ngspice/fteext.h b/src/include/ngspice/fteext.h index 9f6848503..589c62564 100644 --- a/src/include/ngspice/fteext.h +++ b/src/include/ngspice/fteext.h @@ -268,6 +268,7 @@ extern struct card *inp_getoptsc(char *line, struct card *options); extern bool ft_ngdebug; extern bool ft_nginfo; extern bool ft_stricterror; +extern bool ft_skywaterpdk; /* parse.c */ diff --git a/src/include/ngspice/mifdefs.h b/src/include/ngspice/mifdefs.h index 801166f44..c3564e754 100644 --- a/src/include/ngspice/mifdefs.h +++ b/src/include/ngspice/mifdefs.h @@ -80,6 +80,7 @@ struct MIFinstance { Mif_Boolean_t analog; /* true if this inst is analog or hybrid type */ Mif_Boolean_t event_driven; /* true if this inst is event-driven or hybrid type */ + unsigned int irreversible; /* non-zero for special treatment */ int inst_index; /* Index into inst_table in evt struct in ckt */ Mif_Callback_t callback; /* instance callback function */ diff --git a/src/include/ngspice/miftypes.h b/src/include/ngspice/miftypes.h index aa8c6f4b5..532b884e1 100644 --- a/src/include/ngspice/miftypes.h +++ b/src/include/ngspice/miftypes.h @@ -75,6 +75,7 @@ typedef enum { typedef enum { MIF_ANALOG, /* Analog call */ MIF_EVENT_DRIVEN, /* Event-driven call */ + MIF_STEP_PENDING, /* Special event call for irreversible Code Models */ } Mif_Call_Type_t; diff --git a/src/include/ngspice/optdefs.h b/src/include/ngspice/optdefs.h index a39f57b23..6e8a5549f 100644 --- a/src/include/ngspice/optdefs.h +++ b/src/include/ngspice/optdefs.h @@ -124,6 +124,7 @@ enum { #ifdef KLU OPT_SPARSE, + OPT_KLU, OPT_KLU_MEMGROW_FACTOR, #endif diff --git a/src/include/ngspice/swec.h b/src/include/ngspice/swec.h index 28c644fdd..e5ffe8720 100644 --- a/src/include/ngspice/swec.h +++ b/src/include/ngspice/swec.h @@ -184,7 +184,7 @@ typedef struct linked_lists_of_Bpoint{ } BPOINT, *BPOINTPTR; typedef struct linked_lists_of_nodeName{ - char id[24]; + char id[256]; struct linked_lists_of_nodeName *left, *right; NODE *nd; } NDname, *NDnamePt; diff --git a/src/main.c b/src/main.c index e3999a865..0dc5db052 100644 --- a/src/main.c +++ b/src/main.c @@ -164,6 +164,8 @@ double EpsNorm, VNorm, NNorm, LNorm, TNorm, JNorm, GNorm, ENorm; /* end cider globals */ #endif /* CIDER */ +char **Copy_of_argv; // Used to recover args for *ng_script_with_params files + struct variable *(*if_getparam)(CKTcircuit *ckt, char **name, char *param, int ind, int do_model); /* static functions */ @@ -1382,13 +1384,16 @@ int main(int argc, char **argv) while (optind < argc) { char *arg = argv[optind++]; FILE *tp; + /* Copy the the path of the first filename only */ if (!Infile_Path) { Infile_Path = ngdirname(arg); } - /* unquote the input string, needed if it results from double clicking the filename */ #if defined(HAS_WINGUI) + /* Unquote the input string, needed if it results + * from double clicking the filename. + */ arg = cp_unquote(arg); #endif /* Copy all the arguments into the temporary file */ @@ -1401,15 +1406,22 @@ int main(int argc, char **argv) tp = fopen(p, "r"); tfree(p); } + if (!tp) { - perror(arg); - err = 1; - break; + /* Try and find it in a directory in $sourcepath. */ + + tp = inp_pathopen(arg, "r"); + if (!tp) { + perror(arg); + err = 1; + break; + } } } - /* Copy the input file name which otherwise will be lost due to the - temporary file */ + /* Copy the input file name which otherwise will be lost + * due to the temporary file. + */ dname = copy(arg); #if defined(HAS_WINGUI) /* write source file name into source window */ @@ -1418,11 +1430,32 @@ int main(int argc, char **argv) tfree(arg); #endif + if (!gotone) { + char line[256]; + + /* Check for "*ng_script_with_params" as first line. */ + + if (fgets(line, sizeof line, tp) && + ciprefix("*ng_script_with_params", line)) { + /* Special script file: remaining arguments are + * script parameters. + */ + + fclose(tempfile); + tempfile = tp; + Copy_of_argv = argv; + break; + } else { + fseek(tp, 0L, SEEK_SET); + gotone = TRUE; + } + } append_to_stream(tempfile, tp); fclose(tp); } fseek(tempfile, 0L, SEEK_SET); + gotone = FALSE; // Re-use if (tempfile && (!err || !ft_batchmode)) { /* Copy the input file name for becoming another file search path */ @@ -1450,7 +1483,6 @@ int main(int argc, char **argv) } if (ft_batchmode) { - int error3 = 1; /* If we get back here in batch mode then something is wrong, diff --git a/src/maths/cmaths/cmath2.h b/src/maths/cmaths/cmath2.h index 897518721..31bd4c1a0 100644 --- a/src/maths/cmaths/cmath2.h +++ b/src/maths/cmaths/cmath2.h @@ -23,6 +23,7 @@ void * cx_mean(void *data, short int type, int length, int *newlength, short int void * cx_stddev(void *data, short int type, int length, int *newlength, short int *newtype); void * cx_length(void *data, short int type, int length, int *newlength, short int *newtype); void * cx_vector(void *data, short int type, int length, int *newlength, short int *newtype); +void * cx_cvector(void *data, short int type, int length, int *newlength, short int *newtype); void * cx_unitvec(void *data, short int type, int length, int *newlength, short int *newtype); void * cx_plus(void *data1, void *data2, short int datatype1, short int datatype2, int length); void * cx_minus(void *data1, void *data2, short int datatype1, short int datatype2, int length); diff --git a/src/maths/ni/niconv.c b/src/maths/ni/niconv.c index c147caf43..519f039d3 100644 --- a/src/maths/ni/niconv.c +++ b/src/maths/ni/niconv.c @@ -26,6 +26,7 @@ NIconvTest(CKTcircuit *ckt) double old; double new; double tol; + static int nancount = 0; node = ckt->CKTnodes; size = SMPmatSize(ckt->CKTmatrix); @@ -41,8 +42,14 @@ NIconvTest(CKTcircuit *ckt) new = ckt->CKTrhs [i] ; old = ckt->CKTrhsOld [i] ; if (isnan(new)) { - if (ft_ngdebug) + if (ft_ngdebug && nancount < 10) { fprintf(stderr, "Warning: non-convergence, node %s is nan\n", CKTnodName(ckt, i)); + nancount++; + } + else if (ft_ngdebug && nancount == 10) { + fprintf(stderr, " non-convergence warnings (nan) limited to 10\n", CKTnodName(ckt, i)); + nancount++; + } return 1; } if(node->type == SP_VOLTAGE) { diff --git a/src/maths/ni/niiter.c b/src/maths/ni/niiter.c index 941859b4e..f3e546c5a 100644 --- a/src/maths/ni/niiter.c +++ b/src/maths/ni/niiter.c @@ -174,7 +174,14 @@ NIiter(CKTcircuit *ckt, int maxIter) ckt->CKTstat->STATreorderTime += SPfrontEnd->IFseconds() - startTime; if (error) { SMPgetError(ckt->CKTmatrix, &i, &j); - SPfrontEnd->IFerrorf (ERR_WARNING, "singular matrix: check nodes %s and %s\n", NODENAME(ckt, i), NODENAME(ckt, j)); + if (ft_ngdebug || msgcount < 6) { + SMPgetError(ckt->CKTmatrix, &i, &j); + if (eq(NODENAME(ckt, i), NODENAME(ckt, j))) + SPfrontEnd->IFerrorf(ERR_WARNING, "singular matrix: check node %s\n", NODENAME(ckt, i)); + else + SPfrontEnd->IFerrorf(ERR_WARNING, "singular matrix: check nodes %s and %s\n", NODENAME(ckt, i), NODENAME(ckt, j)); + msgcount += 1; + } /* CKTload(ckt); */ /* SMPprint(ckt->CKTmatrix, stdout); */ diff --git a/src/maths/poly/interpolate.c b/src/maths/poly/interpolate.c index 6397f5cb7..a736aa15d 100644 --- a/src/maths/poly/interpolate.c +++ b/src/maths/poly/interpolate.c @@ -5,7 +5,7 @@ #include "polyeval.h" #include "polyfit.h" -/* Returns thestrchr of the last element that was calculated. oval is +/* Returns the strchr of the last element that was calculated. oval is * the value of the old scale at the end of the interval that is being * interpolated from, and sign is 1 if the old scale was increasing, * and -1 if it was decreasing. */ @@ -30,14 +30,17 @@ putinterval(double *poly, int degree, double *nvec, /* Interpolate data from oscale to nscale. data is assumed to be olen long, * ndata will be nlen long. Returns FALSE if the scales are too strange * to deal with. Note that we are guaranteed that either both scales are - * strictly increasing or both are strictly decreasing. + * increasing or both are decreasing. */ + +#define EDGE_FACTOR 1e-3 + bool ft_interpolate(double *data, double *ndata, double *oscale, int olen, double *nscale, int nlen, int degree) { - double *result, *scratch, *xdata, *ydata; - int sign, lastone, i, l; + double *result, *scratch, *xdata, *ydata, diff; + int sign, lastone, i, l, middle, tdegree; if ((olen < 2) || (nlen < 2)) { fprintf(cp_err, "Error: lengths too small to interpolate.\n"); @@ -49,30 +52,72 @@ ft_interpolate(double *data, double *ndata, double *oscale, int olen, return (FALSE); } - if (oscale[1] < oscale[0]) - sign = -1; - else - sign = 1; + for (i = 0; i < olen - 1; ++i) { + if (oscale[i + 1] < oscale[i]) { + sign = -1; + break; + } else if (oscale[i + 1] > oscale[i]) { + sign = 1; + break; + } + } + if (i >= olen) { + fprintf(cp_err, "Error: bad scale, can't interpolate.\n"); + return FALSE; + } scratch = TMALLOC(double, (degree + 1) * (degree + 2)); result = TMALLOC(double, degree + 1); xdata = TMALLOC(double, degree + 1); ydata = TMALLOC(double, degree + 1); - /* Deal with the first degree pieces. */ - memcpy(ydata, data, (size_t) (degree + 1) * sizeof (double)); - memcpy(xdata, oscale, (size_t) (degree + 1) * sizeof (double)); + /* Initial load of the values to be analysed by ft_polyfit(), + * skipping irrelevant points and checking for and fudging vertical edges. + */ - while (!ft_polyfit(xdata, ydata, result, degree, scratch)) { + i = l = 0; + middle = (degree + 1) / 2; + if (sign > 0) { + while (l < olen - degree && oscale[l + middle] < nscale[0]) + ++l; + } else { + while (l < olen - degree && oscale[l + middle] > nscale[0]) + ++l; + } + ydata[0] = data[l]; + xdata[0] = oscale[l]; + do { + if (oscale[l + 1] == oscale[l]) { + if (i == 0) { + ydata[0] = data[++l]; // Ignore first point. + } else { + /* Push the previous x value back, making edge a slope. */ + + diff = xdata[i] - xdata[i - 1]; + xdata[i] -= sign * diff * EDGE_FACTOR; + } + } + xdata[++i] = oscale[++l]; + ydata[i] = data[l]; + } while (i < degree && l < olen - 1); + + if (i < degree) { + fprintf(cp_err, "Error: too few points to calculate polynomial\n"); + return FALSE; + } + + i = 0; + tdegree = degree; + while (!ft_polyfit(xdata + i, ydata + i, result, tdegree, scratch)) { /* If it doesn't work this time, bump the interpolation * degree down by one. */ - - if (--degree == 0) { + if (--tdegree == 0) { fprintf(cp_err, "ft_interpolate: Internal Error.\n"); return (FALSE); } - + if (tdegree % 2) + ++i; // Drop left point. } /* Add this part of the curve. What we do is evaluate the polynomial @@ -81,18 +126,19 @@ ft_interpolate(double *data, double *ndata, double *oscale, int olen, * if the scale is decreasing at the end of the interval we are looking * at. */ - lastone = -1; - for (i = 0; i < degree; i++) { - lastone = putinterval(result, degree, ndata, lastone, - nscale, nlen, xdata[i], sign); - } + + lastone = putinterval(result, tdegree, ndata, -1, + nscale, nlen, xdata[middle], sign); /* Now plot the rest, piece by piece. l is the * last element under consideration. */ - for (l = degree + 1; l < olen; l++) { + for (++l; l < olen; l++) { + double out; /* Shift the old stuff by one and get another value. */ + + out = xdata[0]; for (i = 0; i < degree; i++) { xdata[i] = xdata[i + 1]; ydata[i] = ydata[i + 1]; @@ -100,16 +146,44 @@ ft_interpolate(double *data, double *ndata, double *oscale, int olen, ydata[i] = data[l]; xdata[i] = oscale[l]; - while (!ft_polyfit(xdata, ydata, result, degree, scratch)) { - if (--degree == 0) { - fprintf(cp_err, - "interpolate: Internal Error.\n"); + /* Check for vertical edge. */ + + if (oscale[l] == xdata[i - 1]) { + if (degree == 1) + diff = xdata[0] - out; + else + diff = xdata[i - 1] - xdata[i - 2]; + xdata[i - 1] -= sign * diff * EDGE_FACTOR; + } + + /* Skip input points until the next output point is framed. */ + + if (l < olen - degree) { + if (sign > 0 && xdata[middle] < nscale[lastone + 1]) + continue; + else if (sign < 0 && xdata[middle] > nscale[lastone + 1]) + continue; + } + + i = 0; + tdegree = degree; + while (!ft_polyfit(xdata + i, ydata + i, result, tdegree, scratch)) { + /* If it doesn't work this time, bump the interpolation + * degree down by one. + */ + + if (--tdegree == 0) { + fprintf(cp_err, "ft_interpolate: Internal Error.\n"); return (FALSE); } + if (!((degree - tdegree) & 1)) + ++i; // Drop left point after right. } - lastone = putinterval(result, degree, ndata, lastone, - nscale, nlen, xdata[i], sign); + lastone = putinterval(result, tdegree, ndata, lastone, + nscale, nlen, xdata[middle], sign); } + lastone = putinterval(result, degree, ndata, lastone, + nscale, nlen, oscale[olen - 1], sign); if (lastone < nlen - 1) /* ??? */ ndata[nlen - 1] = data[olen - 1]; tfree(scratch); diff --git a/src/misc/win_time.c b/src/misc/win_time.c new file mode 100644 index 000000000..fd1788d7e --- /dev/null +++ b/src/misc/win_time.c @@ -0,0 +1,32 @@ +#define WIN32_LEAN_AND_MEAN +//#include "ngspice/ngspice.h" +#include +#include +#include // portable: uint64_t MSVC: __int64 + +/*/ MSVC defines this in winsock2.h!? +typedef struct timeval { + long tv_sec; + long tv_usec; +} timeval; +*/ +int gettimeofday(struct timeval * tp, void * unused) +{ + // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's + // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC) + // until 00:00:00 January 1, 1970 + static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime( &system_time ); + SystemTimeToFileTime( &system_time, &file_time ); + time = ((uint64_t)file_time.dwLowDateTime ) ; + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long) ((time - EPOCH) / 10000000L); + tp->tv_usec = (long) (system_time.wMilliseconds * 1000); + return 0; +} diff --git a/src/osdi/osdiregistry.c b/src/osdi/osdiregistry.c index 1fb37ca17..db9097567 100644 --- a/src/osdi/osdiregistry.c +++ b/src/osdi/osdiregistry.c @@ -335,7 +335,7 @@ extern OsdiObjectFile load_object_file(const char *input) { * multiple times. We use the handle as a key because the same SO will always * return the SAME pointer as long as dlclose is not called. * nghash_insert returns NULL if the key (handle) was not already in the table - * and the data (DUMMYDATA) that was previously insered (!= NULL) otherwise*/ + * and the data (DUMMYDATA) that was previously inserted (!= NULL) otherwise*/ if (nghash_insert(known_object_files, handle, DUMMYDATA)) { txfree(path); return EMPTY_OBJECT; diff --git a/src/sharedspice.c b/src/sharedspice.c index 2685e3686..523f1c2bf 100644 --- a/src/sharedspice.c +++ b/src/sharedspice.c @@ -396,6 +396,7 @@ unsigned int main_id, ng_id, command_id; mutexType triggerMutex; mutexType allocMutex; mutexType fputsMutex; +mutexType vecreallocMutex; #endif /* initialization status */ @@ -774,6 +775,8 @@ read_initialisation_file(const char *dir, const char *name) /* The functions exported explicitely from shared ngspice */ /**********************************************************/ + + #ifdef THREADS /* Checks if ngspice is running in the background */ @@ -857,16 +860,19 @@ ngSpice_Init(SendChar* printfcn, SendStat* statusfcn, ControlledExit* ngspiceexi pthread_mutex_init(&triggerMutex, NULL); pthread_mutex_init(&allocMutex, NULL); pthread_mutex_init(&fputsMutex, NULL); + pthread_mutex_init(&vecreallocMutex, NULL); cont_condition = FALSE; #else #ifdef SRW InitializeSRWLock(&triggerMutex); InitializeSRWLock(&allocMutex); InitializeSRWLock(&fputsMutex); + InitializeSRWLock(&vecreallocMutex); #else InitializeCriticalSection(&triggerMutex); InitializeCriticalSection(&allocMutex); InitializeCriticalSection(&fputsMutex); + InitializeCriticalSection(&vecreallocMutex); #endif #endif // Id of primary thread @@ -1336,6 +1342,20 @@ char** ngSpice_AllEvtNodes(void) } #endif +/* Lock/unlock realloc of result vectors during plotting */ +IMPEXP +int ngSpice_LockRealloc(void) +{ + mutex_lock(&vecreallocMutex); + return 1; +} + +IMPEXP +int ngSpice_UnlockRealloc(void) +{ + mutex_unlock(&vecreallocMutex); + return 1; +} /* add the preliminary breakpoints to the list. called from dctran.c */ diff --git a/src/spicelib/analysis/cktacct.c b/src/spicelib/analysis/cktacct.c index 91253f90b..44f3c85c4 100644 --- a/src/spicelib/analysis/cktacct.c +++ b/src/spicelib/analysis/cktacct.c @@ -57,11 +57,18 @@ CKTacct(CKTcircuit *ckt, JOB *anal, int which, IFvalue *val) case OPT_FILLNZ: if ( ckt->CKTmatrix != NULL ) { #ifdef KLU - if (ckt->CKTmatrix->CKTkluMODE) - val->iValue = ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric->lnz + ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric->unz - - (int)ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNZ ; - else + if (ckt->CKTmatrix->CKTkluMODE) { + if (!ckt->CKTmatrix->SMPkluMatrix || + !ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric) { + return -1; + } + val->iValue = + ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric->lnz + + ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric->unz - + (int)ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNZ ; + } else { val->iValue = spFillinCount(ckt->CKTmatrix->SPmatrix); + } #else val->iValue = spFillinCount(ckt->CKTmatrix->SPmatrix); #endif @@ -72,10 +79,14 @@ CKTacct(CKTcircuit *ckt, JOB *anal, int which, IFvalue *val) case OPT_TOTALNZ: if ( ckt->CKTmatrix != NULL ) { #ifdef KLU - if (ckt->CKTmatrix->CKTkluMODE) - val->iValue = ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric->lnz + ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric->unz ; - else - val->iValue = spElementCount(ckt->CKTmatrix->SPmatrix); + if (ckt->CKTmatrix->CKTkluMODE && ckt->CKTmatrix->SMPkluMatrix && + ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric) { + val->iValue = + ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric->lnz + + ckt->CKTmatrix->SMPkluMatrix->KLUmatrixNumeric->unz ; + } else { + val->iValue = 0; + } #else val->iValue = spElementCount(ckt->CKTmatrix->SPmatrix); #endif diff --git a/src/spicelib/analysis/cktntask.c b/src/spicelib/analysis/cktntask.c index 2ad44e3b0..be681b2b6 100644 --- a/src/spicelib/analysis/cktntask.c +++ b/src/spicelib/analysis/cktntask.c @@ -141,7 +141,7 @@ CKTnewTask(CKTcircuit *ckt, TSKtask **taskPtr, IFuid taskName, TSKtask **defPtr) tsk->TSKepsmin = 1e-28; #ifdef KLU - tsk->TSKkluMODE = CKTkluON; + tsk->TSKkluMODE = CKTkluOFF; tsk->TSKkluMemGrowFactor = 1.2 ; #endif diff --git a/src/spicelib/analysis/cktsopt.c b/src/spicelib/analysis/cktsopt.c index d69e878f5..0c1f723e1 100644 --- a/src/spicelib/analysis/cktsopt.c +++ b/src/spicelib/analysis/cktsopt.c @@ -180,7 +180,9 @@ CKTsetOpt(CKTcircuit *ckt, JOB *anal, int opt, IFvalue *val) case OPT_SPARSE: task->TSKkluMODE = (val->iValue == 0); break; - + case OPT_KLU: + task->TSKkluMODE = (val->iValue != 0); + break; case OPT_KLU_MEMGROW_FACTOR: task->TSKkluMemGrowFactor = (val->rValue == 1.2); break; @@ -348,6 +350,8 @@ static IFparm OPTtbl[] = { #ifdef KLU { "sparse", OPT_SPARSE, IF_SET|IF_FLAG, "Set SPARSE 1.3 as Direct Linear Solver" }, + { "klu", OPT_KLU, IF_SET|IF_FLAG, + "Set KLU as Direct Linear Solver" }, { "klu_memgrow_factor", OPT_KLU_MEMGROW_FACTOR, IF_SET|IF_REAL, "KLU Memory Grow Factor (default is 1.2)" } #endif diff --git a/src/spicelib/analysis/dctran.c b/src/spicelib/analysis/dctran.c index 3a913cefc..e7da2097c 100644 --- a/src/spicelib/analysis/dctran.c +++ b/src/spicelib/analysis/dctran.c @@ -787,14 +787,6 @@ resume: converged = NIiter(ckt,ckt->CKTtranMaxIter); -#ifdef XSPICE - if(ckt->evt->counts.num_insts > 0) { - g_mif_info.circuit.evt_step = ckt->CKTtime; - EVTcall_hybrids(ckt); - } -/* gtri - end - wbk - Call all hybrids */ - -#endif ckt->CKTstat->STATtimePts ++; ckt->CKTmode = (ckt->CKTmode&MODEUIC)|MODETRAN | MODEINITPRED; if(firsttime) { @@ -830,8 +822,10 @@ resume: #ifdef XSPICE /* gtri - begin - wbk - Add Breakpoint stuff */ - /* Force backup if temporary breakpoint is < current time */ } else if(g_mif_info.breakpoint.current < ckt->CKTtime) { + /* Force backup if temporary breakpoint is < current time */ + + past_breakpoint: ckt->CKTsaveDelta = ckt->CKTdelta; ckt->CKTtime -= ckt->CKTdelta; ckt->CKTdelta = g_mif_info.breakpoint.current - ckt->CKTtime; @@ -879,6 +873,27 @@ resume: return(error); } if (newdelta > .9 * ckt->CKTdelta) { +#if defined(XSPICE) + /* The timestep has succeeded. XSPICE instances with + * both analog and event ports ("hybrids") and others + * that have called cm_irreversible() receive an EVENT + * call here that allows them to capture their final + * port values and advance co-simulations. As this is an EVENT + * call, they are not expected to do any integrations, + * so there is no need for a further convergence test. + */ + + if (ckt->evt->counts.num_hybrids > 0) { + g_mif_info.circuit.evt_step = ckt->CKTtime; + EVTcall_hybrids(ckt); + if (g_mif_info.breakpoint.current < ckt->CKTtime) { + /* A hybrid requested a breakpoint in the past. */ + + goto past_breakpoint; + } + } +#endif + if ((ckt->CKTorder == 1) && (ckt->CKTmaxOrder > 1)) { /* don't rise the order for backward Euler */ newdelta = ckt->CKTdelta; ckt->CKTorder = 2; @@ -893,6 +908,7 @@ resume: } /* time point OK - 630 */ ckt->CKTdelta = newdelta; + #ifdef NDEV if (!ft_norefprint) { /* show a time process indicator, by Gong Ding, gdiso@ustc.edu */ diff --git a/src/spicelib/devices/cpl/cplhash.c b/src/spicelib/devices/cpl/cplhash.c index 2aaeb5a15..ac2b81586 100644 --- a/src/spicelib/devices/cpl/cplhash.c +++ b/src/spicelib/devices/cpl/cplhash.c @@ -126,6 +126,8 @@ void my_key_free(void * key) void mem_delete(void) { #ifdef DB char buf[128]; + if (!memory_table) + return; printf("CPL GC memory allocated %d times, freed %d times\n", mem_in, mem_out); printf("CPL GC size of hash table to be freed: %d entries.\n", nghash_get_size(memory_table)); #ifdef DB_FULL @@ -142,6 +144,7 @@ void mem_delete(void) { #endif gc_is_on = 0; nghash_free(memory_table, NULL, my_key_free); + memory_table = NULL; #ifdef DB /* printf via sh_printf will need some info from variables that have been deleted already, therefore we use fputs */ diff --git a/src/spicelib/devices/cpl/cplsetup.c b/src/spicelib/devices/cpl/cplsetup.c index f80da3dd0..7e9052fb1 100644 --- a/src/spicelib/devices/cpl/cplsetup.c +++ b/src/spicelib/devices/cpl/cplsetup.c @@ -1934,7 +1934,8 @@ insert_ND(char* name, NDnamePt* ndn) memsaved(p); p->nd = NULL; p->right = p->left = NULL; - strcpy(p->id, name); + strncpy(p->id, name, 255); + p->id[255] = '\0'; return(p); } cmp = strcmp((*ndn)->id, name); diff --git a/src/spicelib/devices/dio/diosetup.c b/src/spicelib/devices/dio/diosetup.c index 2cae77ca0..6bf8e14da 100644 --- a/src/spicelib/devices/dio/diosetup.c +++ b/src/spicelib/devices/dio/diosetup.c @@ -16,6 +16,7 @@ Modified by Paolo Nenzi 2003 and Dietmar Warning 2012 #include "ngspice/sperror.h" #include "ngspice/suffix.h" #include "ngspice/fteext.h" +#include "ngspice/compatmode.h" int DIOsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt, int *states) @@ -195,7 +196,13 @@ DIOsetup(SMPmatrix *matrix, GENmodel *inModel, CKTcircuit *ckt, int *states) } if((!model->DIOresistGiven) || (model->DIOresist==0)) { - model->DIOconductance = 0.0; + if (newcompat.ps || newcompat.lt) { + model->DIOconductance = 1e4; /* improved convergence */ + if (ft_ngdebug) + fprintf(stderr, "Diode series resistance in model %s set to 100 microOhm\n", model->gen.GENmodName); + } + else + model->DIOconductance = 0.0; } else { model->DIOconductance = 1/model->DIOresist; } diff --git a/src/spicelib/devices/txl/txlsetup.c b/src/spicelib/devices/txl/txlsetup.c index 01192d6d4..effc0be8b 100644 --- a/src/spicelib/devices/txl/txlsetup.c +++ b/src/spicelib/devices/txl/txlsetup.c @@ -1035,7 +1035,8 @@ insert_ND(char *name, NDnamePt *ndn) p = *ndn = TMALLOC(NDname, 1); p->nd = NULL; p->right = p->left = NULL; - strcpy(p->id, name); + strncpy(p->id, name, 255); + p->id[255] = '\0'; return(p); } cmp = strcmp((*ndn)->id, name); diff --git a/src/spicelib/devices/vdmos/vdmosload.c b/src/spicelib/devices/vdmos/vdmosload.c index 69fac79fd..644487245 100644 --- a/src/spicelib/devices/vdmos/vdmosload.c +++ b/src/spicelib/devices/vdmos/vdmosload.c @@ -84,6 +84,16 @@ VDMOSload(GENmodel *inModel, CKTcircuit *ckt) else Check_th = 0; + /* FIXME: + this is not a fix, but a hack: + with selfheat, op and op for ac don't work, NaN in self heating evaluation of + first iteration in CKTop(). Calling CKTop() from acan uses flag MODEDCOP, + changing this to MODETRANOP, as used by CKTop() called from dctran, then op is o.k. + */ + if (selfheat) + if(ckt->CKTmode == 528) /* includes MODEDCOP */ + ckt->CKTmode = 544; /* includes MODETRANOP */ + /* first, we compute a few useful values - these could be * pre-computed, but for historical reasons are still done * here. They may be moved at the expense of instance size diff --git a/src/winmain.c b/src/winmain.c index 2fb22aa0c..1bf3bb5fb 100644 --- a/src/winmain.c +++ b/src/winmain.c @@ -850,7 +850,6 @@ ElementWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) static int MakeArgcArgv(char *cmdline, int *argc, char ***argv) { - char *pC1; /* a temporary character pointer */ char *pWorkString = NULL; /* a working copy of cmdline */ int i; /* a loop counter */ int quoteflag = 0; /* for the finite state machine parsing cmdline */ @@ -899,13 +898,13 @@ MakeArgcArgv(char *cmdline, int *argc, char ***argv) #endif /* If we still have a string left, parse it for all the arguments. */ - if (strlen(pWorkString)) + if (pWorkString[0]) { /* This could probably be done with strtok as well but strtok is destructive if I wanted to look for " \"" and I couldn't tell what delimiter that I had bumped against */ - for (i = 0; i < (signed)strlen(pWorkString); i++) + for (i = 0; pWorkString[i]; i++) switch (pWorkString[i]) { case SPACE: @@ -935,7 +934,7 @@ MakeArgcArgv(char *cmdline, int *argc, char ***argv) } } /* malloc an argv */ - tmpargv = (char**) malloc((unsigned)numargs * sizeof(char *)); + tmpargv = (char**) malloc((unsigned)(numargs + 1) * sizeof(char *)); if (NULL == tmpargv) { status = -1; @@ -949,18 +948,15 @@ MakeArgcArgv(char *cmdline, int *argc, char ***argv) deli[0] = DELIMITER; deli[1] = '\0'; /* delimiter for strtok */ - pC1 = NULL; /* Now actually strdup all the arguments out of the string and store them in the argv */ for (i = 1; i < numargs; i++) { - if (NULL == pC1) - pC1 = pWorkString; - if (i == 1) - tmpargv[i] = copy(strtok(pC1, deli)); + tmpargv[i] = copy(strtok(pWorkString, deli)); else tmpargv[i] = copy(strtok(NULL, deli)); } + tmpargv[i] = NULL; /* copy the working values over to the arguments */ *argc = numargs; diff --git a/src/xspice/Makefile.am b/src/xspice/Makefile.am index 2718b4f79..9ff281f97 100644 --- a/src/xspice/Makefile.am +++ b/src/xspice/Makefile.am @@ -1,6 +1,8 @@ # Process this file with automake -EXTRA_DIST = README examples icm xspice.c .gitignore +EXTRA_DIST = README examples icm xspice.c .gitignore \ + verilog/vlnggen \ + verilog/verilator_shim.cpp verilog/verilator_main.cpp ## This is removed because icm relies upon the existance of all other ## libs. It is currently compiled manually, last. @@ -8,7 +10,15 @@ EXTRA_DIST = README examples icm xspice.c .gitignore SUBDIRS = mif cm enh evt ipc idn cmpp icm +initdatadir = $(pkgdatadir)/scripts +initdata_DATA = verilog/vlnggen +initdata1dir = $(pkgdatadir)/scripts/src +initdata1_DATA = verilog/verilator_shim.cpp verilog/verilator_main.cpp + +initdata2dir = $(pkgdatadir)/scripts/src/ngspice +initdata2_DATA = ../include/ngspice/cosim.h ../include/ngspice/miftypes.h \ + ../include/ngspice/cmtypes.h dist-hook: rm -f "$(distdir)/icm/makedefs" rm -f "$(distdir)/icm/GNUmakefile" diff --git a/src/xspice/cm/cm.c b/src/xspice/cm/cm.c index 61ba08819..8a3a283e6 100644 --- a/src/xspice/cm/cm.c +++ b/src/xspice/cm/cm.c @@ -38,6 +38,7 @@ INTERFACES cm_get_path() cm_get_circuit() + cm_irreversible() cm_get_node_name() cm_probe_node() @@ -57,6 +58,7 @@ NON-STANDARD FEATURES #include "ngspice/enh.h" #include "ngspice/mif.h" #include "ngspice/cktdefs.h" +#include "ngspice/cpextern.h" //#include "util.h" @@ -730,6 +732,100 @@ CKTcircuit *cm_get_circuit(void) return(g_mif_info.ckt); } +/* Set the "irreversible" flag on the current instance and shuffle it to the + * requested position among any other irreversibles in the hybrid_index array. + * Array entries are sorted so that non-zero values of instance->irreversible + * are decreasing: an instance with instance->irreversible == 1 is fully + * protected. + */ + +static void duplicate(MIFinstance *instance) +{ + fprintf(cp_err, + "Warning: Duplicate value %d in cm_irreversible() " + "for instance %s.\n", + instance->irreversible, instance->gen.GENname); +} + +void cm_irreversible(unsigned int place) +{ + MIFinstance *instance; + Evt_Ckt_Data_t *evt; + int num_hybrids; + MIFinstance **hybrids; + int old_index, i; + unsigned int value; + + instance = g_mif_info.instance; + if (!g_mif_info.circuit.init) { + fprintf(cp_err, + "%s: Ignoring call to cm_irreversible(): not in INIT\n", + instance->gen.GENname); + return; + } + if (instance->irreversible || place == 0) { + if (instance->irreversible != place) { + fprintf(cp_err, "%s: Ignoring new value %d in cm_irreversible()\n", + instance->gen.GENname, place); + } + return; + } + instance->irreversible = place; + + evt = g_mif_info.ckt->evt; + num_hybrids = evt->counts.num_hybrids; + hybrids = evt->info.hybrids; + + /* Already a hybrid? */ + + for (old_index = 0; old_index < num_hybrids; ++old_index) { + if (hybrids[old_index] == instance) + break; + } + + if (old_index < num_hybrids) { + /* Existing hybrid, move down, shuffling other entries up. */ + + for (i = old_index + 1; i < num_hybrids; ++i) { + value = hybrids[i]->irreversible; + if (value == 0 || value > place) { + hybrids[i - 1] = hybrids[i]; + } else if (value == place) { + duplicate(instance); + break; + } else { + break; + } + } + hybrids[i - 1] = instance; + } else { + /* Instance is not hybrid, add an entry. */ + + num_hybrids++; + hybrids = TREALLOC(MIFinstance *, hybrids, num_hybrids); + evt->counts.num_hybrids = num_hybrids; + evt->info.hybrids = hybrids; + if (hybrids == NULL) { + fprintf(cp_err, "Allocation failed in cm_irreversible()\n"); + abort(); + } + + /* Shuffle entries down. */ + + for (i = num_hybrids - 2; i >= 0; --i) { + value = hybrids[i]->irreversible; + if (value != 0 && value < place) { + hybrids[i + 1] = hybrids[i]; + } else if (value == place) { + duplicate(instance); + } else { + break; + } + } + hybrids[i + 1] = instance; + } +} + /* Get the name of a circuit node connected to a port. */ const char *cm_get_node_name(const char *port_name, unsigned int index) diff --git a/src/xspice/cm/cmexport.c b/src/xspice/cm/cmexport.c index 0e65a1f75..eb7846363 100644 --- a/src/xspice/cm/cmexport.c +++ b/src/xspice/cm/cmexport.c @@ -3,8 +3,6 @@ #include "ngspice/cm.h" #include "ngspice/dllitf.h" -extern bool cp_getvar(char *, enum cp_types, void *, size_t); - /*how annoying!, needed for structure below*/ static void *tcalloc(size_t a, size_t b) { return tmalloc(a*b); /* FIXME, tcalloc must zero !?!? */ @@ -60,6 +58,7 @@ struct coreInfo_t coreInfo = cm_message_send, cm_netlist_get_c, cm_netlist_get_l, + cm_irreversible, cm_get_node_name, cm_probe_node, cm_schedule_output, diff --git a/src/xspice/cm/cmutil.c b/src/xspice/cm/cmutil.c index ee69a0f99..88787e8ac 100644 --- a/src/xspice/cm/cmutil.c +++ b/src/xspice/cm/cmutil.c @@ -52,8 +52,6 @@ NON-STANDARD FEATURES #include #include "ngspice/cm.h" -extern void controlled_exit(const int); - /* Corner Smoothing Function ************************************ * * * The following function smooths the transition between two * diff --git a/src/xspice/evt/evtcall_hybrids.c b/src/xspice/evt/evtcall_hybrids.c index f8a82106c..a45f2506e 100644 --- a/src/xspice/evt/evtcall_hybrids.c +++ b/src/xspice/evt/evtcall_hybrids.c @@ -20,10 +20,12 @@ MODIFICATIONS SUMMARY This file contains function EVTcall_hybrids which calls all models - which have both analog and event-driven ports. It is called following + which have both analog and event-driven ports or have declared + themselves to be irreversible (no back-out). It is called following successful evaluation of an analog iteration attempt to allow events to be scheduled by the hybrid models. The 'CALL_TYPE' is set - to 'EVENT_DRIVEN' when the model is called from this function. + to 'EVENT_DRIVEN' or 'STEP_PENDING' when the model is called + from this function. INTERFACES @@ -60,18 +62,18 @@ void EVTcall_hybrids( CKTcircuit *ckt) /* the main circuit structure */ { - int i; - int num_hybrids; - - int *hybrid_index; + int i; + int num_hybrids; + MIFinstance **hybrids; /* Get needed data for fast access */ + num_hybrids = ckt->evt->counts.num_hybrids; - hybrid_index = ckt->evt->info.hybrid_index; + hybrids = ckt->evt->info.hybrids; /* Call EVTload for all hybrids */ - for(i = 0; i < num_hybrids; i++) - EVTload(ckt, hybrid_index[i]); + for(i = 0; i < num_hybrids; i++) + EVTload_with_event(ckt, hybrids[i], MIF_STEP_PENDING); } diff --git a/src/xspice/evt/evtdest.c b/src/xspice/evt/evtdest.c index c80367686..4fa4dfca9 100644 --- a/src/xspice/evt/evtdest.c +++ b/src/xspice/evt/evtdest.c @@ -330,5 +330,5 @@ Evt_Info_destroy(Evt_Info_t *info) } tfree(info->output_table); - tfree(info->hybrid_index); + tfree(info->hybrids); } diff --git a/src/xspice/evt/evtinit.c b/src/xspice/evt/evtinit.c index 283c1ea79..17883d8b1 100644 --- a/src/xspice/evt/evtinit.c +++ b/src/xspice/evt/evtinit.c @@ -207,7 +207,7 @@ static int EVTinit_info( Evt_Port_Info_t **port_table = NULL; Evt_Output_Info_t **output_table = NULL; - int *hybrid_index = NULL; + MIFinstance **hybrids = NULL; int num_hybrids; @@ -253,15 +253,14 @@ static int EVTinit_info( ckt->evt->info.output_table = output_table; - /* Allocate and create table of indexes into inst_table for hybrids */ + /* Allocate and create table of hybrids */ num_hybrids = ckt->evt->counts.num_hybrids; - CKALLOC(hybrid_index, num_hybrids, int) + CKALLOC(hybrids, num_hybrids, MIFinstance *) for(i = 0, j = 0; i < num_insts; i++) { if(inst_table[i]->inst_ptr->analog) - hybrid_index[j++] = i; + hybrids[j++] = inst_table[i]->inst_ptr; } - ckt->evt->info.hybrid_index = hybrid_index; - + ckt->evt->info.hybrids = hybrids; /* Return */ return(OK); diff --git a/src/xspice/evt/evtiter.c b/src/xspice/evt/evtiter.c index 7f906a355..f72573a5f 100644 --- a/src/xspice/evt/evtiter.c +++ b/src/xspice/evt/evtiter.c @@ -256,7 +256,7 @@ int EVTiter( for(i = 0; i < num_to_call; i++) { inst_index = inst_queue->to_call_index[i]; inst_queue->to_call[inst_index] = MIF_FALSE; - EVTload(ckt, inst_index); + EVTload(ckt, ckt->evt->info.inst_table[inst_index]->inst_ptr); } inst_queue->num_to_call = 0; diff --git a/src/xspice/evt/evtload.c b/src/xspice/evt/evtload.c index a2603e818..a0b75326a 100644 --- a/src/xspice/evt/evtload.c +++ b/src/xspice/evt/evtload.c @@ -82,8 +82,18 @@ ignored. */ int EVTload( - CKTcircuit *ckt, /* The circuit structure */ - int inst_index) /* The instance to call code model for */ + CKTcircuit *ckt, /* The circuit structure */ + MIFinstance *inst) /* The instance to call */ +{ + return EVTload_with_event(ckt, inst, MIF_EVENT_DRIVEN); +} + +/* "Internal" version, also used by EVTcall_hybrids(). */ + +int EVTload_with_event( + CKTcircuit *ckt, /* The circuit structure */ + MIFinstance *inst, /* The instance to call */ + Mif_Call_Type_t type) /* Type of call (EVENT or STEP_PENDING). */ { int i; @@ -96,18 +106,16 @@ int EVTload( Mif_Conn_Data_t *conn; Mif_Port_Data_t *port; Evt_Node_Data_t *node_data; - MIFinstance *inst; Mif_Private_t cm_data; - + void *value_ptr; /* ***************************** */ /* Prepare the code model inputs */ /* ***************************** */ - /* Get pointer to instance data structure and other data */ - /* needed for fast access */ - inst = ckt->evt->info.inst_table[inst_index]->inst_ptr; + /* Get pointer to data structure needed for fast access */ + node_data = ckt->evt->data.node; /* Setup circuit data in struct to be passed to code model function */ @@ -125,7 +133,14 @@ int EVTload( else cm_data.circuit.time = 0.0; - cm_data.circuit.call_type = MIF_EVENT_DRIVEN; + /* Instances that have declared themselves as irreversible + * are expected to distinguish STEP_PENDING from ordinary events. + */ + + if (type == MIF_STEP_PENDING && inst->irreversible) + cm_data.circuit.call_type = MIF_STEP_PENDING; + else + cm_data.circuit.call_type = MIF_EVENT_DRIVEN; cm_data.circuit.temperature = ckt->CKTtemp - 273.15; /* Setup data needed by cm_... functions */ @@ -142,11 +157,12 @@ int EVTload( /* If after initialization and in transient analysis mode */ - /* create a new state for the instance */ - - if((g_mif_info.circuit.anal_type == MIF_TRAN) && inst->initialized) - EVTcreate_state(ckt, inst_index); + /* create a new state for the instance, */ + /* except analog-only irreversibles. */ + if((g_mif_info.circuit.anal_type == MIF_TRAN) && inst->initialized && + inst->inst_index >= 0) + EVTcreate_state(ckt, inst->inst_index); /* Loop through all connections on the instance and setup */ /* load, total_load, and msg on all ports, and changed flag */ diff --git a/src/xspice/icm/GNUmakefile.in b/src/xspice/icm/GNUmakefile.in index 78e578e99..e1344da26 100644 --- a/src/xspice/icm/GNUmakefile.in +++ b/src/xspice/icm/GNUmakefile.in @@ -97,12 +97,12 @@ cm-descr := \ # When recursively making clean, cm-objs and cm-gens do not contain # the files generated for individual code models, as cmpp has already gone -# and modlist and udnlist are empty. Those files are explicitly removed. +# and modlist and udnlist are empty. The files are explicitly removed. cm-clean : -rm -f $(cm)/$(cm).cm -rm -f $(cm-descr) $(cm-objs) $(cm-gens) - -rm -f $(cm)/*/*.o $(cm)/*/*.c $(cm)/*/.deps/* + -rm -f $(cm)/*/ifspec.c $(cm)/*/cfunc.c $(cm)/*/*.o $(cm)/*/.deps/* -rm -f $(cm-deps) cm-distclean : @@ -135,10 +135,10 @@ $(cm-dirs) $(cm-dep-dirs) : %/cmextrn.h %/cminfo.h %/udnextrn.h %/udninfo.h %/objects.inc : $(srcdir)/%/modpath.lst $(srcdir)/%/udnpath.lst CMPP_IDIR=$(srcdir)/$(@D) CMPP_ODIR=$(@D) $(CMPP) -lst -%/ifspec.c : $(srcdir)/%/ifspec.ifs +%/ifspec.c : $(srcdir)/%/ifspec.ifs $(cmpp) CMPP_IDIR=$(srcdir)/$(@D) CMPP_ODIR=$(@D) $(CMPP) -ifs -%/cfunc.c : $(srcdir)/%/cfunc.mod +%/cfunc.c : $(srcdir)/%/cfunc.mod $(cmpp) CMPP_IDIR=$(srcdir)/$(@D) CMPP_ODIR=$(@D) $(CMPP) -mod diff --git a/src/xspice/icm/digital/d_cosim/cfunc.mod b/src/xspice/icm/digital/d_cosim/cfunc.mod new file mode 100644 index 000000000..fb653a47d --- /dev/null +++ b/src/xspice/icm/digital/d_cosim/cfunc.mod @@ -0,0 +1,578 @@ +/* Code model d_cosim. + * + * XSPICE code model for running a co-simulation with no support + * for abandoning the current timestep. + */ + +#include +#include +#include + +#if defined (__MINGW32__) || defined (__CYGWIN__) || defined (_MSC_VER) +/* MS WINDOWS. */ +#undef BOOLEAN +#include + +#define dlopen(name, type) LoadLibrary(name) +#define dlsym(handle, name) (void *)GetProcAddress(handle, name) +#define dlclose(handle) FreeLibrary(handle) + +char *dlerror(void) // Lifted from dev.c. +{ + static const char errstr_fmt[] = + "Unable to find message in dlerr(). System code = %lu"; + static char errstr[256]; + LPVOID lpMsgBuf; + + DWORD rc = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + if (rc == 0) { /* FormatMessage failed */ + (void) sprintf(errstr, errstr_fmt, (unsigned long) GetLastError()); + } else { + snprintf(errstr, sizeof errstr, lpMsgBuf); + LocalFree(lpMsgBuf); + } + return errstr; +} /* end of function dlerror */ +#else +#include +#endif + +#include "ngspice/cosim.h" + +/* The argument passed to code model functions. */ + +#define XSPICE_ARG mif_private + +#define DBG(...) +//#define DBG(...) cm_message_printf(__VA_ARGS__) + +/* Structure used to hold queued inputs. */ + +struct pend_in { + double when; // Due time. + unsigned int which; // Index of input. + Digital_t what; // The value. +}; + +/* Structure to maintain context, pointed to by STATIC VAR cosim_instance. */ + +struct instance { + struct co_info info; // Co-simulation interface - MUST BE FIRST. + int q_index; // Queue index (last active entry). + unsigned int q_length; // Size of input queue. + struct pend_in *q; // The input queue. + unsigned int in_ports; // Number of XSPICE inputs. + unsigned int out_ports; // Number of XSPICE outputs. + unsigned int inout_ports; // Number of XSPICE inout ports. + unsigned int op_pending; // Output is pending. + Digital_t *out_vals; // The new output values. + double extra; // Margin to extend timestep. + void *so_handle; // dlopen() handle to the simulation binary. +}; + +/* Called at end of simulation run to free memory. */ + +static void callback(ARGS, Mif_Callback_Reason_t reason) +{ + struct instance *ip; + + ip = (struct instance *)STATIC_VAR(cosim_instance); + if (reason == MIF_CB_DESTROY) { + if (!ip) + return; + if (ip->info.cleanup) + (*ip->info.cleanup)(&ip->info); + if (ip->so_handle) + dlclose(ip->so_handle); + if (ip->q) + free(ip->q); + if (ip->out_vals) + free(ip->out_vals); + free(ip); + STATIC_VAR(cosim_instance) = NULL; + } +} + +/* Function called when a co-simulator output changes. + * Out-of-range values for bit_num must be ignored. + */ + +void accept_output(struct co_info *pinfo, unsigned int bit_num, Digital_t *val) +{ + struct instance *ip = (struct instance *)pinfo; // First member. + Digital_t *out_vals; // XSPICE rotating memory. + + if (bit_num >= ip->out_ports + ip->inout_ports) + return; + out_vals = (Digital_t *)cm_event_get_ptr(1, 0); + DBG("Change %s %d/%d->%d/%d vtime %g", + cm_get_node_name("d_out", bit_num), + out_vals[bit_num].state, out_vals[bit_num].strength, + val->state, val->strength, + ip->info.vtime); + if (ip->op_pending == 0) { + /* Prepare pending output. */ + + memcpy(ip->out_vals, out_vals, ip->out_ports * sizeof *ip->out_vals); + ip->op_pending = 1; + } + ip->out_vals[bit_num] = *val; +} + +/* Push pending outputs, usually sent back from the future. + * It is safe to use OUTPUT() here, although it mays seem that this + * function may be called twice in a single call to cm_d_cosim(). + * There will never be any input changes when cm_d_cosim() is called + * with pending output, as all input for the shortened time-step has + * already been processed. + */ + +static void output(struct instance *ip, ARGS) +{ + double delay; + Digital_t *out_vals; // XSPICE rotating memory + int i, j; + + delay = PARAM(delay) - (TIME - ip->info.vtime); + if (delay <= 0) { + cm_message_printf("WARNING: output scheduled with impossible " + "delay (%g) at %g.", delay, TIME); + delay = 1e-12; + } + out_vals = (Digital_t *)cm_event_get_ptr(1, 0); + + /* Output to d_out. */ + + for (i = 0; i < ip->out_ports; ++i) { + if (ip->out_vals[i].state != out_vals[i].state || + ip->out_vals[i].strength != out_vals[i].strength) { + DBG("%g: OUT %s %d/%d->%d/%d vtime %g with delay %g", + TIME, cm_get_node_name("d_out", i), + out_vals[i].state, out_vals[i].strength, + ip->out_vals[i].state, ip->out_vals[i].strength, + ip->info.vtime, delay); + *(Digital_t *)OUTPUT(d_out[i]) = out_vals[i] = ip->out_vals[i]; + OUTPUT_DELAY(d_out[i]) = delay; + OUTPUT_CHANGED(d_out[i]) = TRUE; + } else { + OUTPUT_CHANGED(d_out[i]) = FALSE; + } + } + + /* Output to d_inout. */ + + for (i = 0, j = ip->out_ports; i < ip->inout_ports; ++i, ++j) { + if (ip->out_vals[j].state != out_vals[j].state || + ip->out_vals[j].strength != out_vals[j].strength) { + DBG("%g: inOUT %s %d/%d->%d/%d vtime %g with delay %g", + TIME, cm_get_node_name("d_inout", i), + out_vals[j].state, out_vals[j].strength, + ip->out_vals[j].state, ip->out_vals[j].strength, + ip->info.vtime, delay); + *(Digital_t *)OUTPUT(d_inout[i]) = out_vals[j] = ip->out_vals[j]; + OUTPUT_DELAY(d_inout[i]) = delay; + OUTPUT_CHANGED(d_inout[i]) = TRUE; + } else { + OUTPUT_CHANGED(d_inout[i]) = FALSE; + } + } + ip->op_pending = 0; +} + +/* Run the co-simulation. Return 1 if the timestep was truncated. */ + +static int advance(struct instance *ip, ARGS) +{ + /* The co-simulator should advance to the time in ip->info.vtime, + * but should pause when output is generated and update vtime. + */ + + (*ip->info.step)(&ip->info); + + if (ip->op_pending) { + + /* The co-simulator produced some output. */ + + if (TIME - ip->info.vtime <= PARAM(delay)) { +#if 1 + DBG("Direct output with SPICE %.16g CS %.16g", + TIME, ip->info.vtime); + output(ip, XSPICE_ARG); // Normal output, unlikely. +#else + cm_event_queue((TIME + ip->info.vtime + PARAM(delay)) / 2.0); +#endif + } else { + + /* Something changed that may alter the future of the + * SPICE simulation. Truncate the current timestep so that + * SPICE will see the pending output, which currently occurred + * in the past. + */ + + DBG("Truncating timestep to %.16g", ip->info.vtime + ip->extra); + cm_analog_set_temp_bkpt(ip->info.vtime + ip->extra); + + /* Any remaining input events are in an alternate future. */ + + ip->q_index = -1; + return 1; + } + } + return 0; +} + +/* Called from the main function to run the co-simulation. */ + +static void run(struct instance *ip, ARGS) +{ + struct pend_in *rp; + double sim_started; + int i; + + if (ip->q_index < 0) { + /* No queued input, advance to current TIME. */ + + DBG("Advancing vtime without input %.16g -> %.16g", + ip->info.vtime , TIME); + ip->info.vtime = TIME; + advance(ip, XSPICE_ARG); + return; + } + + /* Scan the queue. */ + + DBG("%.16g: Running Q with %d entries", TIME, ip->q_index + 1); + sim_started = ip->info.vtime; + + for (i = 0; i <= ip->q_index; ++i) { + rp = ip->q + i; + if (rp->when <= sim_started) { + /* Not expected. */ + + cm_message_printf("Warning simulated event is in the past:\n" + "XSPICE %.16g\n" + "Event %.16g\n" + "Cosim %.16g", + TIME, rp->when, ip->info.vtime); + cm_message_printf("i=%d index=%d", i, ip->q_index); + continue; + } + + /* Step the simulation forward to the input event time. */ + + ip->info.vtime = rp->when; + if (ip->info.method == Normal && advance(ip, XSPICE_ARG)) { + ip->q_index = -1; + return; + } + + /* Pass input change to simulation. */ + + (*ip->info.in_fn)(&ip->info, rp->which, &rp->what); + while (i < ip->q_index && ip->q[i + 1].when == rp->when) { + /* Another change at the same time. */ + + ++i; + rp = ip->q + i; + (*ip->info.in_fn)(&ip->info, rp->which, &rp->what); + } + + /* Simulator requested to run after input change. */ + + if (ip->info.method == After_input && advance(ip, XSPICE_ARG)) { + ip->q_index = -1; + return; + } + } + + /* All input was processed. Advance to end of the timestep. */ + + ip->q_index = -1; + if (ip->info.method == Normal && TIME > ip->info.vtime) { + ip->info.vtime = TIME; + advance(ip, XSPICE_ARG); + } +} + +/* Check whether an input value has changed. + * To reduce the number of arguments, a struture pointer is passed. + */ + +static bool check_input(struct instance *ip, Digital_t *ovp, + struct pend_in *rp) +{ + if (ovp->state != rp->what.state || + ovp->strength != rp->what.strength) { + if (++ip->q_index < ip->q_length) { + /* Record this event. */ + + ip->q[ip->q_index] = *rp; + } else { + /* Queue is full. Handle that by forcing a shorter timestep. */ + + --ip->q_index; + while (ip->q_index >= 0 && ip->q[ip->q_index].when >= rp->when) + --ip->q_index; + if (ip->q_index >= 0) { + cm_analog_set_temp_bkpt( + (rp->when + ip->q[ip->q_index].when) / 2); + } else { + /* This should never happen. */ + + cm_message_printf("Error: Event queue overflow at %e.", + rp->when); + } + } + return true; + } + return false; +} + +/* The code model's main function. */ + +void ucm_d_cosim(ARGS) +{ + struct instance *ip; + Digital_t *in_vals; // XSPICE rotating memory + int i, index; + + if (INIT) { + int ins, outs, inouts; + unsigned int alloc_size; + void *handle; + void (*ifp)(struct co_info *); + char *fn; + + /* Initialise outputs. Done early in case of failure. */ + + outs = PORT_NULL(d_out) ? 0 : PORT_SIZE(d_out); + for (i = 0; i < outs; ++i) { + OUTPUT_STATE(d_out[i]) = ZERO; + OUTPUT_STRENGTH(d_out[i]) = STRONG; + OUTPUT_DELAY(d_out[i]) = PARAM(delay); + } + + inouts = PORT_NULL(d_inout) ? 0 : PORT_SIZE(d_inout); + for (i = 0; i < inouts; ++i) { + OUTPUT_STATE(d_inout[i]) = ZERO; + OUTPUT_STRENGTH(d_inout[i]) = STRONG; + OUTPUT_DELAY(d_inout[i]) = PARAM(delay); + } + + /* Load the shared library containing the co-simulator. */ + + fn = PARAM(simulation); + handle = dlopen(fn, RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + cm_message_send("Failed to load simulation binary. " + "Try setting LD_LIBRARY_PATH."); + cm_message_send(dlerror()); + return; + } + ifp = dlsym(handle, "Cosim_setup"); + if (*ifp == NULL) { + cm_message_printf("ERROR: no entry function in %s", fn); + cm_message_send(dlerror()); + dlclose(handle); + return; + } + + /* Get the instance data and initialise it. */ + + ip = (struct instance *)calloc(1, sizeof *ip); + if (!ip) + goto no_ip; + ip->so_handle = handle; + ip->info.vtime = 0.0; + ip->info.out_fn = accept_output; + CALLBACK = callback; + + /* Store the simulation interface information. */ + + (*ifp)(&ip->info); + + /* Check lengths. */ + + ins = PORT_NULL(d_in) ? 0 : PORT_SIZE(d_in); + if (ins != ip->info.in_count) { + cm_message_printf("Warning: mismatched XSPICE/co-simulator " + "input counts: %d/%d.", + ins, ip->info.in_count); + } + if (outs != ip->info.out_count) { + cm_message_printf("Warning: mismatched XSPICE/co-simulator " + "output counts: %d/%d.", + outs, ip->info.out_count); + } + + if (inouts != ip->info.inout_count) { + cm_message_printf("Warning: mismatched XSPICE/co-simulator " + "inout counts: %d/%d.", + inouts, ip->info.inout_count); + } + + /* Create input queue and output buffer. */ + + ip->q_index = -1; + ip->q_length = PARAM(queue_size); + ip->in_ports = ins; + ip->out_ports = outs; + ip->inout_ports = inouts; + if (ins + inouts > ip->q_length) { + cm_message_send("WARNING: Input queue size should be greater than " + "number of input ports. Size increased."); + ip->q_length = ins + inouts + 16; + } + alloc_size = ip->q_length * sizeof (struct pend_in); + ip->q = (struct pend_in *)malloc(alloc_size); + if (!ip->q) + goto no_q; + ip->op_pending = 0; + ip->out_vals = (Digital_t *)calloc(outs + inouts, sizeof (Digital_t)); + if (!ip->out_vals) + goto no_out_vals; + ip->extra = PARAM(delay) / 3; // FIXME? + STATIC_VAR(cosim_instance) = ip; + + /* Allocate XSPICE rotating storage to track changes. */ + + cm_event_alloc(0, (ins + inouts) * sizeof (Digital_t)); + cm_event_alloc(1, (outs + inouts) * sizeof (Digital_t)); + + /* Declare irreversible. */ + + if (PARAM(irreversible) > 0) + cm_irreversible(PARAM(irreversible)); + return; + + /* Handle malloc failures. */ + no_out_vals: + free(ip->q); + no_q: + free(ip); + no_ip: + cm_message_send("No memory!"); + return; + } + + ip = STATIC_VAR(cosim_instance); + if (!ip) { + int ports; + + /* Error state. Do nothing at all. */ + + ports = PORT_NULL(d_out) ? 0 : PORT_SIZE(d_out); + for (i = 0; i < ports; ++i) + OUTPUT_CHANGED(d_out[i]) = FALSE; + ports = PORT_NULL(d_inout) ? 0 : PORT_SIZE(d_inout); + for (i = 0; i < ports; ++i) + OUTPUT_CHANGED(d_inout[i]) = FALSE; + return; + } + in_vals = (Digital_t *)cm_event_get_ptr(0, 0); + + if (TIME == 0.0) { + /* Starting, so inputs may be settling. */ + + for (i = 0; i < ip->in_ports; ++i) { + Digital_t ival; + + ival = *(Digital_t *)INPUT(d_in[i]); + (*ip->info.in_fn)(&ip->info, i, &ival); + in_vals[i] = ival; + } + + for (i = 0; i < ip->out_ports; ++i) + OUTPUT_CHANGED(d_out[i]) = FALSE; + + for (i = 0; i < ip->inout_ports; ++i) { + Digital_t ival; + + ival = *(Digital_t *)INPUT(d_inout[i]); + (*ip->info.in_fn)(&ip->info, i + ip->in_ports, &ival); + in_vals[i + ip->in_ports] = ival; + OUTPUT_CHANGED(d_inout[i]) = FALSE; + } + return; + } + + if (CALL_TYPE == ANALOG) // Belt and braces + return; + + /* Check for pending output. */ + + if (ip->op_pending) { + output(ip, XSPICE_ARG); + } else { + for (i = 0; i < ip->out_ports; ++i) + OUTPUT_CHANGED(d_out[i]) = FALSE; + for (i = 0; i < ip->inout_ports; ++i) + OUTPUT_CHANGED(d_inout[i]) = FALSE; + } + + /* Check TIME as it may have gone backwards after a failed time-step. */ + + index = ip->q_index; + while (index >= 0 && TIME < ip->q[index].when) + --index; + ip->q_index = index; + + if (CALL_TYPE == EVENT) { + struct pend_in input; + unsigned int limit, max; + + /* New input is expected here. */ + + input.when = TIME; + + limit = ip->info.in_count + ip->info.inout_count; + max = limit < ip->in_ports ? limit : ip->in_ports; + limit -= max; + + for (input.which = 0; input.which < max; ++input.which) { + input.what = *(Digital_t *)INPUT(d_in[input.which]); + if (check_input(ip, in_vals + input.which, &input)) { + DBG("%.16g: IN %s %d/%d->%d/%d", + TIME, cm_get_node_name("d_in", input.which), + in_vals[input.which].state, in_vals[input.which].strength, + input.what.state, input.what.strength); + in_vals[input.which] = input.what; + } + } + + if (limit > ip->inout_ports) + limit = ip->inout_ports; + for (i = 0; i < limit; ++i, ++input.which) { + input.what = *(Digital_t *)INPUT(d_inout[i]); + if (check_input(ip, in_vals + input.which, &input)) { + DBG("%.16g: INout %s %d/%d->%d/%d", + TIME, cm_get_node_name("d_inout", i), + in_vals[input.which].state, in_vals[input.which].strength, + input.what.state, input.what.strength); + in_vals[ip->in_ports + i] = input.what; + } + } + } else if (CALL_TYPE == STEP_PENDING) { + /* The current timestep succeeded. Run the co-simulator code + * forward, replaying any saved input events. + */ + + if (TIME <= ip->info.vtime) + cm_message_printf("XSPICE time is behind vtime:\n" + "XSPICE %.16g\n" + "Cosim %.16g", + TIME, ip->info.vtime); + run(ip, XSPICE_ARG); + } +} diff --git a/src/xspice/icm/digital/d_cosim/ifspec.ifs b/src/xspice/icm/digital/d_cosim/ifspec.ifs new file mode 100644 index 000000000..8138b53f2 --- /dev/null +++ b/src/xspice/icm/digital/d_cosim/ifspec.ifs @@ -0,0 +1,102 @@ +/* Copyright 2023 Giles Atkinson +SUMMARY + + This file contains the interface specification file for the + d_cosim code model for general digital co-simulation. + +=============================================================================*/ + +NAME_TABLE: + +Spice_Model_Name: d_cosim +C_Function_Name: ucm_d_cosim +Description: "Bridge to an irreversible digital model" + +PORT_TABLE: + +Port_Name: d_in +Description: "digital input" +Direction: in +Default_Type: d +Allowed_Types: [d] +Vector: yes +Vector_Bounds: [0 -] +Null_Allowed: yes + +PORT_TABLE: + +Port_Name: d_out +Description: "digital output" +Direction: out +Default_Type: d +Allowed_Types: [d] +Vector: yes +Vector_Bounds: [0 -] +Null_Allowed: yes + +PORT_TABLE: + +Port_Name: d_inout +Description: "digital bidirectional port" +Direction: inout +Default_Type: d +Allowed_Types: [d] +Vector: yes +Vector_Bounds: [0 -] +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: delay +Description: "output delay time" +Data_Type: real +Default_Value: 1.0e-9 +Limits: [1e-12 -] +Vector: no +Vector_bounds: - +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: simulation +Description: "A shared library containing a digital model" +Data_Type: string +Default_Value: - +Limits: - +Vector: no +Vector_Bounds: - +Null_Allowed: no + +/* Instances maintain an internal input event queue that should be at least + * as large as the number of inputs. Performance with clocked logic may + * be improved by making it larger than (2 * F) / MTS, where F is + * the clock frequency and MTS is the maximum timestep for .tran. + */ + +PARAMETER_TABLE: + +Parameter_Name: queue_size +Description: "input queue size" +Data_Type: int +Default_Value: 128 +Limits: [1 -] +Vector: no +Vector_bounds: - +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: irreversible +Description: "Parameter passed to library function cm_irreversible()" +Data_Type: int +Default_Value: 1 +Limits: - +Vector: no +Vector_Bounds: - +Null_Allowed: yes + +STATIC_VAR_TABLE: + +Static_Var_Name: cosim_instance +Data_Type: pointer +Description: "Per-instance structure" diff --git a/src/xspice/icm/digital/modpath.lst b/src/xspice/icm/digital/modpath.lst index 323c94c19..0cbd2e973 100644 --- a/src/xspice/icm/digital/modpath.lst +++ b/src/xspice/icm/digital/modpath.lst @@ -29,3 +29,4 @@ d_tff d_tristate d_xnor d_xor +d_cosim diff --git a/src/xspice/icm/dlmain.c b/src/xspice/icm/dlmain.c index 87f6050f9..6c93d4c4b 100644 --- a/src/xspice/icm/dlmain.c +++ b/src/xspice/icm/dlmain.c @@ -340,6 +340,11 @@ double cm_netlist_get_l(void) { return (coreitf->dllitf_cm_netlist_get_l)(); } +void cm_irreversible(unsigned int place) +{ + (coreitf->dllitf_cm_irreversible)(place); +} + const char *cm_get_node_name(const char *port, unsigned int index) { return coreitf->dllitf_cm_get_node_name(port, index); } diff --git a/src/xspice/verilog/MSVC.CMD b/src/xspice/verilog/MSVC.CMD new file mode 100644 index 000000000..bd9ab9696 --- /dev/null +++ b/src/xspice/verilog/MSVC.CMD @@ -0,0 +1 @@ +CL /O2 /LD /EHsc /Fe..\adc.DLL /I. /IC:\mingw64\share\verilator\include\vltstd /IC:\mingw64\share\verilator\include Vlng__ALL.cpp verilator_shim.cpp C:\mingw64\share\verilator\include\verilated.cpp C:\mingw64\share\verilator\include\verilated_threads.cpp /link /DLL /EXPORT:Cosim_setup diff --git a/src/xspice/verilog/README.txt b/src/xspice/verilog/README.txt new file mode 100644 index 000000000..6eea6d832 --- /dev/null +++ b/src/xspice/verilog/README.txt @@ -0,0 +1,3 @@ +This directory contains Ngspice scripts and other files used to prepare +Verilog (and possibly VHDL) code to be included in an Ngspice simulation. +An example circuit can be found in examples/xspice/verilator. diff --git a/src/xspice/verilog/verilator_main.cpp b/src/xspice/verilog/verilator_main.cpp new file mode 100644 index 000000000..c6b6eef15 --- /dev/null +++ b/src/xspice/verilog/verilator_main.cpp @@ -0,0 +1,13 @@ +/* Dummy main() for Verilator. */ + +#include "ngspice/cmtypes.h" // For Digital_t +#include "ngspice/cosim.h" // For struct co_info and prototypes + +int main(int argc, char** argv, char**) { + struct co_info info; + + Cosim_setup(&info); + for (;;) + (*info.step)(&info); + return 0; +} diff --git a/src/xspice/verilog/verilator_shim.cpp b/src/xspice/verilog/verilator_shim.cpp new file mode 100644 index 000000000..186d4a61c --- /dev/null +++ b/src/xspice/verilog/verilator_shim.cpp @@ -0,0 +1,157 @@ +/* This is a very mangled version of Vadc__main.cpp as generated by Verilator. */ + +// Verilated -*- C++ -*- +// DESCRIPTION: main() calling loop, created with Verilator --main + +#include "verilated.h" +#include "Vlng.h" + +#include "ngspice/cmtypes.h" // For Digital_t +#include "ngspice/cosim.h" // For struct co_info and prototypes + +//====================== + +/* Structure for the input table. */ + +struct input { + const char *name; + unsigned int offset; + unsigned int count; +}; + +/* This VL_DATA macro is used in header files inputs.h, outputs.h and inouts.h + * to write functions accept_input() and step(). + * The macro is used several times with different definitions. + */ + +/* Generate the previous values table used by step(). */ + +#define VL_DATA(size, name, msb, lsb) + msb - lsb + 1 +static const unsigned int outs = 0 +#include "outputs.h" + ; +static const unsigned int inouts = 0 +#include "inouts.h" + ; + +static unsigned char previous_output[outs + inouts]; + +#undef VL_DATA + +/* The input function: it should ignore out-of-range values of index. */ + +#define VL_DATA(size, name, msb, lsb) \ + if (index >= msb - lsb + 1) { \ + index -= msb - lsb + 1; \ + } else if (msb == 0 && lsb == 0) { \ + topp->name = val ? 1 : 0; \ + return; \ + } else { \ + if (val) \ + topp->name |= (1 << (msb - index)); \ + else \ + topp->name &= (1 << (msb - index)); \ + return; \ + } + +static void accept_input(struct co_info *pinfo, + unsigned int index, Digital_t *vp) +{ + Vlng *topp = (Vlng *)pinfo->handle; + unsigned int val, offset; + + val = vp->state; + if (val == UNKNOWN) + return; // Verilator simulations are two-state. + +#include "inputs.h" + + /* For inout ports the new value must be stored to detect changes. */ + + offset = outs; +#undef VL_DATA +#define VL_DATA(size, name, msb, lsb) \ + if (index >= msb - lsb + 1) { \ + index -= msb - lsb + 1; \ + offset += msb - lsb + 1; \ + } else if (msb == 0 && lsb == 0) { \ + topp->name = val ? 1 : 0; \ + previous_output[index + offset] = val; \ + return; \ + } else { \ + if (val) \ + topp->name | (1 << (msb - index)); \ + else \ + topp->name &= (1 << (msb - index)); \ + previous_output[index + offset] = val; \ + return; \ + } + +#include "inouts.h" +} +#undef VL_DATA + +/* The step function that calls the Verilator code. */ + +#define VL_DATA(size, name, msb, lsb) \ + for (i = msb; i >= lsb; --i) { \ + if (topp->name & (1 << i)) \ + bit = 1; \ + else \ + bit = 0; \ + if (bit ^ previous_output[index]) { \ + previous_output[index] = bit; \ + oval.state = (Digital_State_t)bit; \ + (*pinfo->out_fn)(pinfo, index, &oval); \ + } \ + ++index; \ + } + +static void step(struct co_info *pinfo) +{ + static Digital_t oval = {ZERO, STRONG}; + Vlng *topp; + int index, i; + unsigned char bit; + + topp = (Vlng *)pinfo->handle; + topp->eval(); + + /* Now scan the outputs for changes. */ + + index = 0; +#include "outputs.h" +#include "inouts.h" +} +#undef VL_DATA + +extern "C" void Cosim_setup(struct co_info *pinfo) +{ + int i; + + // Setup context, and defaults + + Verilated::debug(0); + const std::unique_ptr contextp{new VerilatedContext}; + + // Construct the Verilated model, from Vtop.h generated from Verilating + + Vlng *topp{new Vlng{contextp.get()}}; + + /* Return information to caller. */ + + pinfo->handle = topp; + pinfo->step = step; + +#define VL_DATA(size, name, msb, lsb) i += msb - lsb + 1; // Count ports + + i = 0; +#include "inputs.h" + pinfo->in_count = i; + + pinfo->out_count = outs; + pinfo->inout_count = inouts; + pinfo->in_fn = accept_input; + pinfo->method = After_input; // Verilator requires input to advance. +} +#undef VL_DATA diff --git a/src/xspice/verilog/vlnggen b/src/xspice/verilog/vlnggen new file mode 100644 index 000000000..956d345c5 --- /dev/null +++ b/src/xspice/verilog/vlnggen @@ -0,0 +1,316 @@ +*ng_script_with_params +// This Ngspice interpreter script accepts arbitrary argiments to +// the Verilator compiler (Verilog to C++) and builds a shared library +// or DLL that can be loaded by the d_cosim XSPICE code model. +// Instances of the model are then digital circuit elements whose +// behaviour is controlled by the Verilog source. + +set bad=0 +if $?argc = 0 + set bad=1 +end + +if $argc <= 0 + set bad=1 +end + +if $bad + echo Arguments acceptable to Verilator are required. + quit +end + +// Disable special processing of '{'. + +set noglob + +// Set parameters for Windows or Unix-like OS. +// For setting CFLAGS (passed to Verilator) it is somewhat arbitrarily +// assumed that if Ngspice was compiled with VisualC++, then that is +// the compiler to be used with Verilator. Edit to change. + +// Compilation option for C/C++: -fpic is required by GCC for a shared library + +if $oscompiled = 8 // VisualC++ - Verilator is a Perl script + setcs cflags="--CFLAGS -fpic --compiler msvc" +else + setcs cflags="--CFLAGS -fpic" // For g++ +end + +if $oscompiled = 2 | $oscompiled = 3 | $oscompiled = 8 // Windows + set windows=1 + set dirsep1="\\" + set dirsep2="/" + set vloc="C:/mingw64/bin/verilator" // Expected location on Windows + set run_verilator="perl $vloc" +else + set windows=0 + set dirsep1="/" + set run_verilator=verilator +end + +if $oscompiled = 7 // MacOS + set macos=1 + setcs cflags="$cflags --compiler clang" +else + set macos=0 +end + +// Loop through the arguments to find Verilog source: some_path/xxxx.v +// The output file will have the same base name. + +let index=1 +set off=1 // Avoid error in dowhile +repeat $argc + set base="$argv[$&index]" + let index = index + 1 + strstr l "$base" "" + if $l > 2 // Look for xxxx.v + strslice tail "$base" -2 2 + strcmp bad "$tail" ".v" + if $bad <> 0 + set base="" + continue + end + let l = $l - 2 + strslice base "$base" 0 $&l + else + set base="" + continue + end + + dowhile $off >= 0 // Strip leading directories + strstr off "$base" "$dirsep1" + if $windows + if $off < 0 + strstr off "$base" "$dirsep2" + end + end + if $off >= 0 + let off=$off+1 + strslice base "$base" $&off $l + end + end + + strstr l "$base" "" // Check for zero-length string + if $l > 0 + break + end +end + +if index - 1 > $argc + set base=verilated // Default name +end + +// Define working directory for Verilator + +set tail="_obj_dir" +setcs objdir="$base$tail" + +// Default base name of output file. + +if $windows + setcs tail=".DLL" +else + setcs tail=".so" +end +setcs soname="$base$tail" + +// First convert to C++, PREFIX determines the file names. + +setcs prefix="Vlng" + +// Run Verilator on the given input files. + +shell $run_verilator --Mdir $objdir --prefix $prefix $cflags --cc $argv + +// Parse the primary interface Class definition for members representing +// the ports of the top-level Verilog module. +// Example conversion: VL_IN8(&Clk,0,0); ==> VL_DATA(8,Clk,0,0) + +cd $objdir +echo "/* Generated code: do not edit. */" > inouts.h +echo "/* Generated code: do not edit. */" > inputs.h +echo "/* Generated code: do not edit. */" > outputs.h + +// This loop is intended to have the same effect as: +// sed --quiet -e 's/VL_IN\([0-9]*\)(&\(.*\);/VL_DATA(\1,\2/p' \ +// obj_dir/${PREFIX}.h >> inputs.h + +set htail=".h" +setcs inout="VL_INOUT" +setcs in="VL_IN" +setcs out="VL_OUT" + +set fn="$prefix$htail" // Like foo-obj_dir/Vlng.h +fopen fh $fn +if $fh < 0 + quit +end + +while 1 + fread line $fh l + if $l < 0 + break + end + + // Does it contain a closing parenthesis? + + strstr off "$line" ")" + if $off < 0 + continue // No ")", ignore. + end + let off = $off + 1 + strslice line "$line" 0 $&off // Slice off tail. + + // Is it an inout port definition? + + strstr off "$line" $inout + if $off >= 0 // Match found + let off = $off + 8 // strlen("VL_INOUT") == 8 + strslice line "$line" $&off $l + strstr off "$line" "(" + strslice size "$line" 0 $off + let off = $off + 2 // strlen("(&") == 2 + strslice line "$line" $&off $l + echo VL_DATA($size,$line >> inouts.h // New macro invocation + continue + end + + // Input port? + + strstr off "$line" $in + if $off >= 0 // Match found + let off = $off + 5 // strlen("VL_IN") == 5 + strslice line "$line" $&off $l + strstr off "$line" "(" + strslice size "$line" 0 $off + let off = $off + 2 // strlen("(&") == 2 + strslice line "$line" $&off $l + echo VL_DATA($size,$line >> inputs.h // New macro invocation + continue + end + + // Output port? + + strstr off "$line" $out + if $off >= 0 // Match found + let off = $off + 6 // strlen("VL_OUT") == 6 + strslice line "$line" $&off $l + strstr off "$line" "(" + strslice size "$line" 0 $off + let off = $off + 2 // strlen("(&") == 2 + strslice line "$line" $&off $l + echo VL_DATA($size,$line >> outputs.h // New macro invocation + continue + end +end +fclose $fh +cd .. + +// The shared library/DLL contains some ngspice source code as +// well as that created by Verilator. Find it by scanning $sourcepath. + +set shimfile=verilator_shim.cpp +set shimobj=verilator_shim.o +set mainfile=verilator_main.cpp +set srcdir=src +set silent_fileio // Silences fopen complaints + +let i=1 +repeat $#sourcepath + set stem="$sourcepath[$&i]" + let i = i + 1 + set fn="$stem$dirsep1$shimfile" + fopen fh $fn + if $fh > 0 + break + end + set stem="$stem$dirsep1$srcdir" + set fn="$stem$dirsep1$shimfile" + fopen fh $fn + if $fh > 0 + break + end +end + +if $fh > 0 + fclose $fh + set fn_main="$stem$dirsep1$mainfile" +else + echo Can not find C++ source file $shimfile + quit +end + +if $windows + // Verilator makes a mess of absolute include paths passed by --CFLAGS. + // Copy the files instead. + + set incdir=ngspice + shell xcopy /i "$stem$dirsep1$incdir" "$objdir$dirsep1$incdir" + setcs include="--CFLAGS -I." + + // Copy verilator_shim.cpp for MSVC.CMD. + + shell copy "$fn" "$objdir" +else + // Some header files are with the source. + + strstr off "$stem" "." + if $off <> 0 + setcs include="--CFLAGS -I$stem" + else + setcs include="--CFLAGS -I..$dirsep1$stem" // Relative path + end +end + + +// Compile the code. Verilator only does that when building an executable, +// so include verilator_main.cpp. + +shell $run_verilator --Mdir $objdir --prefix $prefix $include $cflags ++ --cc --build --exe ++ $fn_main $fn $argv + +strcmp bad "$shellstatus" "0" + +if $bad = 0 + // g++ must be available: make a shared library/DLL. + + set v_objs="$objdir$dirsep1$shimobj $objdir/verilated.o $objdir/verilated_threads.o" + setcs tail="__ALL.a" + setcs v_lib="$objdir/$prefix$tail" // Like Vlng__ALL.a + + shell g++ --shared $v_objs $v_lib -pthread -lpthread -o $soname +else + // Assume we have CL.EXE and use that. A script avoids multiple \escapes. + + if $windows = 0 + quit + end + + // Look for MSVC.CMD + + set msvcfile=MSVC.CMD + let i=1 + repeat $#sourcepath + set stem="$sourcepath[$&i]" + let i = i + 1 + set fn="$stem$dirsep1$msvcfile" + fopen fh $fn + if $fh > 0 + break + end + end + if $fh > 0 + fclose $fh + else + echo Can not find bulid file $msvcfile + quit + end + + echo Building with MSVC compiler, CL.EXE. + cd $objdir + shell $fn + cd .. +end +quit diff --git a/visualc/make-install-vngspice.bat b/visualc/make-install-vngspice.bat index 854150643..6894d84b2 100644 --- a/visualc/make-install-vngspice.bat +++ b/visualc/make-install-vngspice.bat @@ -12,7 +12,6 @@ set cmsrc=.\codemodels\Win32\Release mkdir %dst%\bin mkdir %dst%\lib\ngspice -mkdir %dst%\share\ngspice\scripts copy "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.OPENMP\vcomp140.dll" %dst%\bin\ copy %cmsrc%\analog.cm %dst%\lib\ngspice\analog.cm @@ -21,8 +20,6 @@ copy %cmsrc%\table.cm %dst%\lib\ngspice\table.cm copy %cmsrc%\xtraevt.cm %dst%\lib\ngspice\xtraevt.cm copy %cmsrc%\xtradev.cm %dst%\lib\ngspice\xtradev.cm copy %cmsrc%\spice2poly.cm %dst%\lib\ngspice\spice2poly.cm -copy .\spinit_all %dst%\share\ngspice\scripts\spinit -copy .\spinitr .\spinit if "%2" == "fftw" goto copy2 if "%3" == "fftw" goto copy2 @@ -42,7 +39,6 @@ set cmsrc=.\codemodels\x64\Release mkdir %dst%\bin mkdir %dst%\lib\ngspice -mkdir %dst%\share\ngspice\scripts copy "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x64\Microsoft.VC140.OPENMP\vcomp140.dll" %dst%\bin\ copy %cmsrc%\analog64.cm %dst%\lib\ngspice\analog.cm @@ -51,8 +47,6 @@ copy %cmsrc%\table64.cm %dst%\lib\ngspice\table.cm copy %cmsrc%\xtraevt64.cm %dst%\lib\ngspice\xtraevt.cm copy %cmsrc%\xtradev64.cm %dst%\lib\ngspice\xtradev.cm copy %cmsrc%\spice2poly64.cm %dst%\lib\ngspice\spice2poly.cm -copy .\spinit_all %dst%\share\ngspice\scripts\spinit -copy .\spinitr64 .\spinit if "%2" == "fftw" goto copy2-64 if "%3" == "fftw" goto copy2-64 @@ -65,3 +59,18 @@ copy %1\ngspice.exe %dst%\bin\ copy ..\..\fftw-3.3-dll64\libfftw3-3.dll %dst%\bin\ :end +mkdir %dst%\share\ngspice\scripts\src\ngspice +copy .\spinit_all %dst%\share\ngspice\scripts\spinit +copy .\spinitr .\spinit +cd ..\src +copy ciderinit %dst%\share\ngspice\scripts +copy devaxis %dst%\share\ngspice\scripts +copy devload %dst%\share\ngspice\scripts +copy setplot %dst%\share\ngspice\scripts +copy spectrum %dst%\share\ngspice\scripts +copy xspice\verilog\vlnggen %dst%\share\ngspice\scripts +copy xspice\verilog\MSVC.CMD %dst%\share\ngspice\scripts +copy xspice\verilog\*.cpp %dst%\share\ngspice\scripts\src +copy include\ngspice\cosim.h %dst%\share\ngspice\scripts\src\ngspice +copy include\ngspice\miftypes.h %dst%\share\ngspice\scripts\src\ngspice +copy include\ngspice\cmtypes.h %dst%\share\ngspice\scripts\src\ngspice diff --git a/visualc/make-install-vngspiced.bat b/visualc/make-install-vngspiced.bat index 67d168c12..529f324b4 100644 --- a/visualc/make-install-vngspiced.bat +++ b/visualc/make-install-vngspiced.bat @@ -12,7 +12,6 @@ set cmsrc=.\codemodels\Win32\Debug mkdir %dst%\bin mkdir %dst%\lib\ngspice -mkdir %dst%\share\ngspice\scripts copy "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.OPENMP\vcomp140.dll" %dst%\bin\ copy %cmsrc%\analog.cm %dst%\lib\ngspice\analog.cm @@ -21,8 +20,6 @@ copy %cmsrc%\table.cm %dst%\lib\ngspice\table.cm copy %cmsrc%\xtraevt.cm %dst%\lib\ngspice\xtraevt.cm copy %cmsrc%\xtradev.cm %dst%\lib\ngspice\xtradev.cm copy %cmsrc%\spice2poly.cm %dst%\lib\ngspice\spice2poly.cm -copy .\spinit_all %dst%\share\ngspice\scripts\spinit -copy .\spinitd .\spinit if "%2" == "fftw" goto copy2 if "%3" == "fftw" goto copy2 @@ -42,7 +39,6 @@ set cmsrc=.\codemodels\x64\Debug mkdir %dst%\bin mkdir %dst%\lib\ngspice -mkdir %dst%\share\ngspice\scripts copy "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x64\Microsoft.VC140.OPENMP\vcomp140.dll" %dst%\bin\ copy %cmsrc%\analog64.cm %dst%\lib\ngspice\analog.cm @@ -51,8 +47,6 @@ copy %cmsrc%\table64.cm %dst%\lib\ngspice\table.cm copy %cmsrc%\xtraevt64.cm %dst%\lib\ngspice\xtraevt.cm copy %cmsrc%\xtradev64.cm %dst%\lib\ngspice\xtradev.cm copy %cmsrc%\spice2poly64.cm %dst%\lib\ngspice\spice2poly.cm -copy .\spinit_all %dst%\share\ngspice\scripts\spinit -copy .\spinitd64 .\spinit if "%2" == "fftw" goto copy2-64 if "%3" == "fftw" goto copy2-64 @@ -65,3 +59,18 @@ copy %1\ngspice.exe %dst%\bin\ copy ..\..\fftw-3.3-dll64\libfftw3-3.dll %dst%\bin\ :end +mkdir %dst%\share\ngspice\scripts\src\ngspice +copy .\spinit_all %dst%\share\ngspice\scripts\spinit +copy .\spinitr .\spinit +cd ..\src +copy ciderinit %dst%\share\ngspice\scripts +copy devaxis %dst%\share\ngspice\scripts +copy devload %dst%\share\ngspice\scripts +copy setplot %dst%\share\ngspice\scripts +copy spectrum %dst%\share\ngspice\scripts +copy xspice\verilog\vlnggen %dst%\share\ngspice\scripts +copy xspice\verilog\MSVC.CMD %dst%\share\ngspice\scripts +copy xspice\verilog\*.cpp %dst%\share\ngspice\scripts\src +copy include\ngspice\cosim.h %dst%\share\ngspice\scripts\src\ngspice +copy include\ngspice\miftypes.h %dst%\share\ngspice\scripts\src\ngspice +copy include\ngspice\cmtypes.h %dst%\share\ngspice\scripts\src\ngspice diff --git a/visualc/sharedspice.vcxproj b/visualc/sharedspice.vcxproj index 6ddb78d3c..cf35c3d7a 100644 --- a/visualc/sharedspice.vcxproj +++ b/visualc/sharedspice.vcxproj @@ -395,6 +395,7 @@ + @@ -1007,6 +1008,7 @@ + @@ -1220,6 +1222,7 @@ + diff --git a/visualc/vngspice-fftw.vcxproj b/visualc/vngspice-fftw.vcxproj index a076526e6..1a93e9cc2 100644 --- a/visualc/vngspice-fftw.vcxproj +++ b/visualc/vngspice-fftw.vcxproj @@ -854,6 +854,7 @@ lib /machine:x64 /def:..\..\fftw-3.3-dll64\libfftw3-3.def /out:$(IntDir)libfftw3 + @@ -1467,6 +1468,7 @@ lib /machine:x64 /def:..\..\fftw-3.3-dll64\libfftw3-3.def /out:$(IntDir)libfftw3 + @@ -1686,6 +1688,7 @@ lib /machine:x64 /def:..\..\fftw-3.3-dll64\libfftw3-3.def /out:$(IntDir)libfftw3 + diff --git a/visualc/vngspice.vcxproj b/visualc/vngspice.vcxproj index f366c96c3..476250aa7 100644 --- a/visualc/vngspice.vcxproj +++ b/visualc/vngspice.vcxproj @@ -885,6 +885,7 @@ + @@ -1498,6 +1499,7 @@ + @@ -1700,6 +1702,7 @@ + @@ -2893,4 +2896,4 @@ - \ No newline at end of file + diff --git a/visualc/xspice/digital.vcxproj b/visualc/xspice/digital.vcxproj index 47e7cb7d7..bd4d42487 100644 --- a/visualc/xspice/digital.vcxproj +++ b/visualc/xspice/digital.vcxproj @@ -322,6 +322,10 @@ ..\..\src\xspice\%(RelativeDir);%(AdditionalIncludeDirectories) + + + ..\..\src\xspice\%(RelativeDir);%(AdditionalIncludeDirectories) +