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.
This commit is contained in:
Giles Atkinson 2024-05-03 11:08:35 +01:00 committed by Holger Vogt
parent 4481531baf
commit e201f144d5
7 changed files with 219 additions and 24 deletions

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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 (;;)

View File

@ -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

View File

@ -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