From e201f144d512614f33f7039fcc186bf0453aa929 Mon Sep 17 00:00:00 2001 From: Giles Atkinson <“gatk555@gmail.com”> Date: Fri, 3 May 2024 11:08:35 +0100 Subject: [PATCH] Add support for Verilator's --timing option, allowing use of delays in Verilog source code. Also add two parameters to d_cosim: sim_args is used to pass string arguments to a Verilator simulation; and lib_args is for future use. In vlnggen, also check for two causes of failure: a verilator error may lead to creation of interfering header files; and misleading instances of verilated_shim.cpp can cause an obscure failure (reported by Diarmuid Collins). Use a generic name for the generated DLL in MSVC.CMD. --- src/include/ngspice/cosim.h | 20 ++++-- src/xspice/icm/digital/d_cosim/cfunc.mod | 63 ++++++++++++++++--- src/xspice/icm/digital/d_cosim/ifspec.ifs | 22 +++++++ src/xspice/verilog/MSVC.CMD | 2 +- src/xspice/verilog/verilator_main.cpp | 2 +- src/xspice/verilog/verilator_shim.cpp | 76 ++++++++++++++++++++++- src/xspice/verilog/vlnggen | 58 ++++++++++++++--- 7 files changed, 219 insertions(+), 24 deletions(-) diff --git a/src/include/ngspice/cosim.h b/src/include/ngspice/cosim.h index dd6b97094..fd198b8b8 100644 --- a/src/include/ngspice/cosim.h +++ b/src/include/ngspice/cosim.h @@ -1,4 +1,6 @@ -/* Header file for the shim code between d_cosim and a co-simulator. */ +/* Header file for the shim code between XSPICE and a co-simulator + * attached by the d_cosim code model. + */ #if __cplusplus extern "C" { @@ -12,7 +14,7 @@ extern "C" { * so step() must be called after input. */ -typedef enum {Normal, After_input} Cosim_method; + typedef enum {Normal, After_input, Both} Cosim_method; /* Structure used by Cosim_setup() to pass and return * co-simulation interface information. @@ -26,7 +28,8 @@ struct co_info { 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. + * it is unloaded at the end of a simulation run. It should not free + * this structure. */ void (*cleanup)(struct co_info *); @@ -59,8 +62,17 @@ struct co_info { void (*out_fn)(struct co_info *, unsigned int, Digital_t *); void *handle; // Co-simulator's private handle - double vtime; // Time in the co-simulation. + volatile double vtime; // Time in the co-simulation. Cosim_method method; // May be set in Cosim_setup; + + /* Arguments for the co-simulator shim and the simulation itself + * are taken from parameters in the .model card. + */ + + int lib_argc; + int sim_argc; + const char * const * const lib_argv; + const char * const * const sim_argv; }; extern void Cosim_setup(struct co_info *pinfo); // This must exist. diff --git a/src/xspice/icm/digital/d_cosim/cfunc.mod b/src/xspice/icm/digital/d_cosim/cfunc.mod index 5aa0603dd..84d66ac21 100644 --- a/src/xspice/icm/digital/d_cosim/cfunc.mod +++ b/src/xspice/icm/digital/d_cosim/cfunc.mod @@ -39,7 +39,7 @@ char *dlerror(void) // Lifted from dev.c. if (rc == 0) { /* FormatMessage failed */ (void) sprintf(errstr, errstr_fmt, (unsigned long) GetLastError()); } else { - snprintf(errstr, sizeof errstr, errstr_fmt, lpMsgBuf); + snprintf(errstr, sizeof errstr, "%s", lpMsgBuf); LocalFree(lpMsgBuf); } return errstr; @@ -94,6 +94,10 @@ static void callback(ARGS, Mif_Callback_Reason_t reason) return; if (ip->info.cleanup) (*ip->info.cleanup)(&ip->info); + if (ip->info.lib_argv) + free((void *)ip->info.lib_argv); + if (ip->info.sim_argv) + free((void *)ip->info.sim_argv); if (ip->so_handle) dlclose(ip->so_handle); if (ip->q) @@ -232,8 +236,9 @@ static int advance(struct instance *ip, ARGS) * Try and recover: this may lead to a warning on output. */ - DBG("Attempting recovey from failed timestep lop %.16g->%.16g", - TIME, when); + DBG("Attempting recovery from failed timestep truncation: %s " + "%.16g->%.16g", + cm_message_get_errmsg(), TIME, when); delta = (TIME - ip->info.vtime) / 1000; when = ip->info.vtime; if (when < ip->last_step) { @@ -298,7 +303,8 @@ static void run(struct instance *ip, ARGS) /* Step the simulation forward to the input event time. */ ip->info.vtime = rp->when; - if (ip->info.method == Normal && advance(ip, XSPICE_ARG)) { + if ((ip->info.method == Normal || ip->info.method == Both) && + advance(ip, XSPICE_ARG)) { ip->q_index = -1; return; } @@ -316,7 +322,8 @@ static void run(struct instance *ip, ARGS) /* Simulator requested to run after input change. */ - if (ip->info.method == After_input && advance(ip, XSPICE_ARG)) { + if ((ip->info.method == After_input || ip->info.method == Both) && + advance(ip, XSPICE_ARG)) { ip->q_index = -1; return; } @@ -427,7 +434,39 @@ void ucm_d_cosim(ARGS) ip->info.out_fn = accept_output; CALLBACK = callback; - /* Store the simulation interface information. */ + if (PARAM_NULL(lib_args)) { + ip->info.lib_argc = 0; + *(void **)&ip->info.lib_argv = NULL; + } else { + char **args; + + ip->info.lib_argc = PARAM_SIZE(lib_args); + args = malloc((ip->info.lib_argc + 1) * sizeof (char *)); + if (args) { + for (i = 0; i < ip->info.lib_argc; ++i) + args[i] = PARAM(lib_args[i]); + args[i] = NULL; + } + *(char ***)&ip->info.lib_argv = args; + } + + if (PARAM_NULL(sim_args)) { + ip->info.sim_argc = 0; + *(void **)&ip->info.sim_argv = NULL; + } else { + char **args; + + ip->info.sim_argc = PARAM_SIZE(sim_args); + args = malloc((ip->info.sim_argc + 1) * sizeof (char *)); + if (args) { + for (i = 0; i < ip->info.sim_argc; ++i) + args[i] = PARAM(sim_args[i]); + args[i] = NULL; + } + *(char ***)&ip->info.sim_argv = args; + } + + /* Get the simulation interface information. */ (*ifp)(&ip->info); @@ -597,11 +636,21 @@ void ucm_d_cosim(ARGS) * forward, replaying any saved input events. */ - if (TIME <= ip->info.vtime) + if (ip->last_step == 0.0) { + /* First step. "Step" the co-simulation to time zero, + * as that may trigger initialisations. + */ + + if (advance(ip, XSPICE_ARG)) + return; + } + + 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 index 8138b53f2..cb77502c3 100644 --- a/src/xspice/icm/digital/d_cosim/ifspec.ifs +++ b/src/xspice/icm/digital/d_cosim/ifspec.ifs @@ -67,6 +67,28 @@ Vector: no Vector_Bounds: - Null_Allowed: no +PARAMETER_TABLE: + +Parameter_Name: lib_args +Description: "Argument strings made available to the shared library" +Data_Type: string +Default_Value: - +Limits: - +Vector: yes +Vector_Bounds: - +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: sim_args +Description: "Argument strings made available to the simulation" +Data_Type: string +Default_Value: - +Limits: - +Vector: yes +Vector_Bounds: - +Null_Allowed: yes + /* 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 diff --git a/src/xspice/verilog/MSVC.CMD b/src/xspice/verilog/MSVC.CMD index bd9ab9696..97a3c127f 100644 --- a/src/xspice/verilog/MSVC.CMD +++ b/src/xspice/verilog/MSVC.CMD @@ -1 +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 +CL /O2 /LD /EHsc /Fe..\Verilated.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/verilator_main.cpp b/src/xspice/verilog/verilator_main.cpp index c6b6eef15..e8812f0d8 100644 --- a/src/xspice/verilog/verilator_main.cpp +++ b/src/xspice/verilog/verilator_main.cpp @@ -4,7 +4,7 @@ #include "ngspice/cosim.h" // For struct co_info and prototypes int main(int argc, char** argv, char**) { - struct co_info info; + struct co_info info = {}; Cosim_setup(&info); for (;;) diff --git a/src/xspice/verilog/verilator_shim.cpp b/src/xspice/verilog/verilator_shim.cpp index 186d4a61c..df508658d 100644 --- a/src/xspice/verilog/verilator_shim.cpp +++ b/src/xspice/verilog/verilator_shim.cpp @@ -93,6 +93,11 @@ static void accept_input(struct co_info *pinfo, /* The step function that calls the Verilator code. */ +#ifndef WITH_TIMING +/* The simple case is when the Verilog source contained no time delays, + * or was compiled with compiled with --no-timing. + */ + #define VL_DATA(size, name, msb, lsb) \ for (i = msb; i >= lsb; --i) { \ if (topp->name & (1 << i)) \ @@ -106,7 +111,7 @@ static void accept_input(struct co_info *pinfo, } \ ++index; \ } - + static void step(struct co_info *pinfo) { static Digital_t oval = {ZERO, STRONG}; @@ -123,6 +128,71 @@ static void step(struct co_info *pinfo) #include "outputs.h" #include "inouts.h" } + +#else /* WITH_TIMING */ + +#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]) { \ + stop = 1; \ + 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}; + VerilatedContext *contextp; + Vlng *topp; + double tick; + uint64_t target, next; + int index, i, stop = 0; + unsigned char bit; + + /* When Verilog source was compiled with --timing, run queued evants + * until the Verilog simulation catches up with SPICE. + */ + + topp = (Vlng *)pinfo->handle; + contextp = topp->contextp(); + tick = pow(10, contextp->timeprecision()); + target = pinfo->vtime / tick; + + /* Step the Verilog simulation towards the target time. */ + + do { + if (topp->eventsPending()) + next = topp->nextTimeSlot(); + else + next = target; + if (next >= target) { + stop = 1; + next = target; + } + contextp->time(next); + topp->eval(); + + /* Scan for output and stop early if found. */ + + index = 0; +#include "outputs.h" +#include "inouts.h" + } while (!stop); + + /* Update the shared simulation time on early exit. */ + + if (next < target) + pinfo->vtime = next * tick; +} + +#endif /* WITH_TIMING */ #undef VL_DATA extern "C" void Cosim_setup(struct co_info *pinfo) @@ -152,6 +222,10 @@ extern "C" void Cosim_setup(struct co_info *pinfo) pinfo->out_count = outs; pinfo->inout_count = inouts; pinfo->in_fn = accept_input; +#ifdef WITH_TIMING + pinfo->method = Both; // There may be immediate results from input. +#else pinfo->method = After_input; // Verilator requires input to advance. +#endif } #undef VL_DATA diff --git a/src/xspice/verilog/vlnggen b/src/xspice/verilog/vlnggen index 956d345c5..0486259ae 100644 --- a/src/xspice/verilog/vlnggen +++ b/src/xspice/verilog/vlnggen @@ -1,5 +1,5 @@ *ng_script_with_params -// This Ngspice interpreter script accepts arbitrary argiments to +// This Ngspice interpreter script accepts arbitrary arguments 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 @@ -30,7 +30,7 @@ set noglob // Compilation option for C/C++: -fpic is required by GCC for a shared library -if $oscompiled = 8 // VisualC++ - Verilator is a Perl script +if $oscompiled = 8 // VisualC++ setcs cflags="--CFLAGS -fpic --compiler msvc" else setcs cflags="--CFLAGS -fpic" // For g++ @@ -41,7 +41,7 @@ if $oscompiled = 2 | $oscompiled = 3 | $oscompiled = 8 // Windows set dirsep1="\\" set dirsep2="/" set vloc="C:/mingw64/bin/verilator" // Expected location on Windows - set run_verilator="perl $vloc" + set run_verilator="perl $vloc" // Verilator is a Perl script else set windows=0 set dirsep1="/" @@ -55,14 +55,32 @@ else set macos=0 end +// Check for an input.h file in the current directory. If present it may +// override the generated one with incorrect results. A previous failure +// may create such files. + +set silent_fileio +fopen fh inputs.h +if $fh >= 0 + echo File inputs.h (and any other header files) in current directory + echo may interfere with compilation. + quit +end +unset silent_fileio + // 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 +set timing=1 repeat $argc set base="$argv[$&index]" let index = index + 1 + if $timing <> 0 + // Additional check for --timing option, preceeding any *.v files. + strcmp timing "$base" "--timing" + end strstr l "$base" "" if $l > 2 // Look for xxxx.v strslice tail "$base" -2 2 @@ -122,6 +140,9 @@ setcs prefix="Vlng" // Run Verilator on the given input files. shell $run_verilator --Mdir $objdir --prefix $prefix $cflags --cc $argv +if $shellstatus > 0 + quit +end // Parse the primary interface Class definition for members representing // the ports of the top-level Verilog module. @@ -214,6 +235,8 @@ set shimfile=verilator_shim.cpp set shimobj=verilator_shim.o set mainfile=verilator_main.cpp set srcdir=src +set hfile="cmtypes.h" +set hpath="ngspice$dirsep1$hfile" set silent_fileio // Silences fopen complaints let i=1 @@ -221,15 +244,22 @@ repeat $#sourcepath set stem="$sourcepath[$&i]" let i = i + 1 set fn="$stem$dirsep1$shimfile" - fopen fh $fn - if $fh > 0 - break + fopen fh "$fn" + if $fh < 0 + // Look in any "src" subdirectory (probably in installed tree). + set stem="$stem$dirsep1$srcdir" + set fn="$stem$dirsep1$shimfile" + fopen fh $fn end - set stem="$stem$dirsep1$srcdir" - set fn="$stem$dirsep1$shimfile" - fopen fh $fn if $fh > 0 - break + // Found verilator_shim.cpp, but it needs header files on relative path. + fclose $fh + set hn="$stem$dirsep1$hpath" + fopen fh "$hn" + if $fh > 0 + break + end + echo Ignoring source file "$fn" as "$hn" was not found. end end @@ -263,6 +293,11 @@ else end end +// verilator_shim.cpp has conditionally-compiled sections for --timing. + +if $timing = 0 + setcs cflags="--CFLAGS -DWITH_TIMING ""$cflags" +end // Compile the code. Verilator only does that when building an executable, // so include verilator_main.cpp. @@ -277,6 +312,9 @@ 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" + if $timing = 0 + set v_objs="$v_objs $objdir/verilated_timing.o" + end setcs tail="__ALL.a" setcs v_lib="$objdir/$prefix$tail" // Like Vlng__ALL.a