diff --git a/configure.ac b/configure.ac index 6a64fd635..2fd124576 100644 --- a/configure.ac +++ b/configure.ac @@ -409,12 +409,15 @@ fi #### ### check for operating system at compile time +AM_CONDITIONAL([DLIBS_FULLY_RESOLVED], false) case $host_os in *mingw* | *msys* ) AC_DEFINE([OS_COMPILED], [1], [MINGW for MS Windows]) + AM_CONDITIONAL([DLIBS_FULLY_RESOLVED], true) ;; *cygwin* ) AC_DEFINE([OS_COMPILED], [2], [Cygwin for MS Windows]) + AM_CONDITIONAL([DLIBS_FULLY_RESOLVED], true) ;; *freebsd* ) AC_DEFINE([OS_COMPILED], [3], [FreeBSD]) @@ -989,10 +992,12 @@ AM_CONDITIONAL([RELPATH], [test "x$enable_relpath" = xyes]) if test "x$enable_relpath" = xyes; then AC_DEFINE_UNQUOTED([NGSPICEBINDIR], ["`echo ../bin`"], [Define the directory for executables]) AC_DEFINE_UNQUOTED([NGSPICEDATADIR], ["`echo ../share/ngspice`"], [Define the directory for architecture independent data files]) + AC_DEFINE_UNQUOTED([NGSPICELIBDIR], ["`echo ../lib/ngspice`"], [Define the directory for architecture-dependent libraries]) AC_DEFINE([HAS_RELPATH], [1], [rel. path of libraries and scripts]) else AC_DEFINE_UNQUOTED([NGSPICEBINDIR], ["`echo $dprefix/bin`"], [Define the directory for executables]) AC_DEFINE_UNQUOTED([NGSPICEDATADIR], ["`echo $dprefix/share/ngspice`"], [Define the directory for architecture independent data files]) +AC_DEFINE_UNQUOTED([NGSPICELIBDIR], ["`echo $dprefix/lib/ngspice`"], [Define the directory for architecture-dependent libraries]) fi # Create timestamp, may be overruled by setting env var SOURCE_DATE_EPOCH @@ -1441,6 +1446,7 @@ AC_CONFIG_FILES([Makefile src/xspice/enh/Makefile src/xspice/ipc/Makefile src/xspice/idn/Makefile + src/xspice/verilog/Makefile src/osdi/Makefile tests/Makefile tests/bsim1/Makefile diff --git a/src/xspice/Makefile.am b/src/xspice/Makefile.am index 9ff281f97..53669c8b3 100644 --- a/src/xspice/Makefile.am +++ b/src/xspice/Makefile.am @@ -8,17 +8,8 @@ EXTRA_DIST = README examples icm xspice.c .gitignore \ ## libs. It is currently compiled manually, last. ##SUBDIRS = mif cm enh evt ipc idn icm -SUBDIRS = mif cm enh evt ipc idn cmpp icm +SUBDIRS = mif cm enh evt ipc idn cmpp icm verilog -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/verilog/Makefile.am b/src/xspice/verilog/Makefile.am new file mode 100644 index 000000000..50ecc0131 --- /dev/null +++ b/src/xspice/verilog/Makefile.am @@ -0,0 +1,51 @@ + ## Process this file with automake to produce Makefile.in + +MAINTAINERCLEANFILES = Makefile.in + +# Verilator support: files installed to script directory and below. + +initdatadir = $(pkgdatadir)/scripts +initdata_DATA = vlnggen + +initdata1dir = $(pkgdatadir)/scripts/src +initdata1_DATA = verilator_shim.cpp verilator_main.cpp + +initdata2dir = $(pkgdatadir)/scripts/src/ngspice +initdata2_DATA = ../../include/ngspice/cosim.h \ + ../../include/ngspice/miftypes.h \ + ../../include/ngspice/cmtypes.h + +# Icarus Verilog support: build two shared libraries. + +pkglib_LTLIBRARIES = ivlng.la ivlngvpi.la +ivlng_la_SOURCES = icarus_shim.c icarus_shim.h +ivlng_la_CFLAGS = -I../../../../src/include +ivlng_la_LDFLAGS = -module -shared -avoid-version + +ivlngvpi_la_SOURCES = vpi.c icarus_shim.h vpi_user_dummy.h +ivlngvpi_la_CFLAGS = -I../../../../src/include +ivlngvpi_la_LDFLAGS = -module -shared -avoid-version + +# On Windows, symbols in DLLs must be fully resolved. +# Create a dummy libvvp.DLL so that Icarus Verilog need not be installed +# for building. + +if DLIBS_FULLY_RESOLVED +pkglib_LTLIBRARIES += libvvp.la +libvvp_la_SOURCES = vpi_dummy.c vpi_user_dummy.h +libvvp_la_LDFLAGS = -no-undefined -module -shared -avoid-version +ivlng_la_LDFLAGS += -no-undefined +ivlngvpi_la_LIBADD = libvvp.la ivlng.la +ivlngvpi_la_LDFLAGS += -no-undefined +endif + +# Libtool installs unwanted libraries, remove them after installation. +# On Windows, the dummy libvvp.* files are removed also. + +install-exec-hook: + cd $(DESTDIR)$(pkglibdir); \ + rm -f ivlng*a libvvp* ; \ + mv ivlngvpi.* ivlng.vpi + +uninstall-hook: + rm -f $(DESTDIR)$(pkglibdir)/ivlng.vpi $(DESTDIR)$(pkglibdir)/ivlng.so diff --git a/src/xspice/verilog/README.txt b/src/xspice/verilog/README.txt index 6eea6d832..cb789f2af 100644 --- a/src/xspice/verilog/README.txt +++ b/src/xspice/verilog/README.txt @@ -1,3 +1,19 @@ 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. +Verilog code to be included in an Ngspice simulation, using Verilator +or Icarus Verilog. + + +For Verilator the relevant files are vlnggen (an Ngspice script), +verilator_main.cpp and verilator_shim.cpp. The two C++ files are +compiled together with C++ source code generated from the Verilog input. +The compilation is handled by Verilator unless the Microsoft Visual C++ +compiler is used. MSVC.CMD contains an example command for that. + +Example circuits can be found in examples/xspice/verilator. + + +The following files are for Icarus Verilog support and are built into +shared libraries while compiling Ngspice: icarus_shim.h, icarus_shim.c, +vpi.c, vpi_dummy.c, user_vpi_dummy.h, coroutine*.h and libvvp.def. + +Example circuits can be found in examples/xspice/icarus_verilog. diff --git a/src/xspice/verilog/coroutine.h b/src/xspice/verilog/coroutine.h new file mode 100644 index 000000000..09e1c9247 --- /dev/null +++ b/src/xspice/verilog/coroutine.h @@ -0,0 +1,29 @@ +#ifndef _COSIM_COROUTINE_H_ +#define _COSIM_COROUTINE_H_ + +#if defined(__MINGW32__) || defined(_MSC_VER) +#include // Windows has a simple co-routine library - "Fibers" + +/* Coroutine context information. */ + +struct cr_ctx { + LPVOID spice_fiber; // OS-provided coroutine context. + LPVOID cosim_fiber; // OS-provided context and stack. +}; +#else +/* On a Unix-like OS pthreads are used to give a co-simulation its own stack, + * but setcontext() and friends would avoid the overhead of a OS thread. + */ + +#include + +struct cr_ctx { + pthread_t thread; // Thread for VVP execution. + pthread_mutex_t mutex; + pthread_cond_t spice_cond; // Condition variables for each thread. + pthread_cond_t cosim_cond; +}; + +#endif /* pthread code. */ +#endif // _COSIM_COROUTINE_H_ + diff --git a/src/xspice/verilog/coroutine_cosim.h b/src/xspice/verilog/coroutine_cosim.h new file mode 100644 index 000000000..81513e0f0 --- /dev/null +++ b/src/xspice/verilog/coroutine_cosim.h @@ -0,0 +1,28 @@ +#ifndef _COSIM_COROUTINE_SIM_H_ +#define _COSIM_COROUTINE_SIM_H_ + +/* Code that supplies a set of simple, portable functions for running + * a co-simulation as a co-routine inside Ngspice: co-simulator side. + */ + +#include "coroutine.h" + +#if defined(__MINGW32__) || defined(_MSC_VER) +static void cr_yield_to_spice(struct cr_ctx *ctx) { + SwitchToFiber(ctx->spice_fiber); +} + +#define cr_init(X) /* All initialisation was done in the primary fiber. */ +#else +/* On a Unix-like OS pthreads are used to give libvvp its own stack. */ + +static void cr_yield_to_spice(struct cr_ctx *ctx) { + pthread_cond_signal(&ctx->spice_cond); + pthread_cond_wait(&ctx->cosim_cond, &ctx->mutex); +} + +static void cr_init(struct cr_ctx *ctx) { + pthread_mutex_lock(&ctx->mutex); +} +#endif +#endif // _COSIM_COROUTINE_SIM_H_ diff --git a/src/xspice/verilog/coroutine_shim.h b/src/xspice/verilog/coroutine_shim.h new file mode 100644 index 000000000..63a3fa692 --- /dev/null +++ b/src/xspice/verilog/coroutine_shim.h @@ -0,0 +1,147 @@ +#ifndef _COSIM_COROUTINE_SHIM_H_ +#define _COSIM_COROUTINE_SHIM_H_ + +/* Code that supplies a set of simple, portable functions for running + * a co-simulation as a co-routine inside Ngspice: d_cosim shim side. + * For Windows it also emulates the Unix dlopen() family of functions + * for dynamic loading. + */ + +#include "coroutine.h" + +static void fail(const char *what, int why); + +#if defined(__MINGW32__) || defined(_MSC_VER) +#define dlopen(name, type) LoadLibrary(name) +#define dlsym(handle, name) (void *)GetProcAddress(handle, name) +#define dlclose(handle) FreeLibrary(handle) +#define cr_safety() while (0) // Not needed with Fibers. + +static 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, "%s", (char *)lpMsgBuf); + LocalFree(lpMsgBuf); + } + return errstr; +} /* end of function dlerror */ + +static void cr_yield_to_sim(struct cr_ctx *ctx) { + SwitchToFiber(ctx->cosim_fiber); +} + +static void cr_init(struct cr_ctx *ctx, void *(*fn)(void *), void *data) { + ctx->spice_fiber = ConvertThreadToFiber(NULL); + + /* Start the cosimulator fiber and wait for it to be ready. */ + + ctx->cosim_fiber = CreateFiber(1024*1024, (void (*)(void *))fn, data); + cr_yield_to_sim(ctx); +} + +static void cr_cleanup(struct cr_ctx *ctx) { + DeleteFiber(ctx->cosim_fiber); +} + +static void cr_yield_to_spice(struct cr_ctx *ctx) { + SwitchToFiber(ctx->spice_fiber); +} + +#else // Pthreads + +#include +#include + +static void cr_yield_to_sim(struct cr_ctx *ctx) { + int err; + + err = pthread_cond_signal(&ctx->cosim_cond); + if (err) + fail("pthread_cond_signal (sim)", err); + err = pthread_cond_wait(&ctx->spice_cond, &ctx->mutex); + if (err) + fail("pthread_cond_wait (spice)", err); +} + +static void cr_init(struct cr_ctx *ctx, void *(*fn)(void *), void *data) { + int err; + + /* Create pthread apparatus. */ + + err = pthread_mutex_init(&ctx->mutex, NULL); + if (err) + fail("pthread_mutex_init", err); + err = pthread_cond_init(&ctx->spice_cond, NULL); + if (!err) + err = pthread_cond_init(&ctx->cosim_cond, NULL); + if (err) + fail("pthread_cond_init", err); + + /* Start the cosimulator thread and wait for it to be ready. */ + + pthread_mutex_lock(&ctx->mutex); + err = pthread_create(&ctx->thread, NULL, fn, data); + if (err) + fail("pthread_create", err); + err = pthread_cond_wait(&ctx->spice_cond, &ctx->mutex); + if (err) + fail("pthread_cond_wait", err); +} + +static void cr_safety(void) { + sigset_t set; + + /* Signals that are handled with longjump() in signal_handler.c + * must be blocked to prevent threads sharing a stack. + */ + + sigemptyset(&set); + sigaddset(&set, SIGINT); + sigaddset(&set, SIGFPE); + sigaddset(&set, SIGTTIN); + sigaddset(&set, SIGTTOU); + sigaddset(&set, SIGTSTP); + sigaddset(&set, SIGCONT); + pthread_sigmask(SIG_BLOCK, &set, NULL); +} + +static void cr_cleanup(struct cr_ctx *ctx) { + /* For now, just cancel the cosimulator thread. + * It should be in pthread_cond_wait() and will go quickly. + */ + + pthread_cancel(ctx->thread); + pthread_mutex_unlock(&ctx->mutex); + pthread_cond_signal(&ctx->cosim_cond); // Make it run + pthread_join(ctx->thread, NULL); // Wait for it. + pthread_cond_destroy(&ctx->spice_cond); + pthread_cond_destroy(&ctx->cosim_cond); + pthread_mutex_destroy(&ctx->mutex); +} + +static void cr_yield_to_spice(struct cr_ctx *ctx) { + pthread_cond_signal(&ctx->spice_cond); + pthread_cond_wait(&ctx->cosim_cond, &ctx->mutex); +} +#endif /* pthread code. */ +#endif // _COSIM_COROUTINE_SHIM_H_ + diff --git a/src/xspice/verilog/icarus_shim.c b/src/xspice/verilog/icarus_shim.c new file mode 100644 index 000000000..e1bcb0950 --- /dev/null +++ b/src/xspice/verilog/icarus_shim.c @@ -0,0 +1,318 @@ +/* + * Main file for the shared library ivlng.so that is used + * by ngspice's d_cosim code model to connect an instance of + * an Icarus Verilog simulation (libvvp.so). + * Licensed on the same terms as Ngspice. + * + * Copyright (c) 2024 Giles Atkinson + */ + +#include +#include +#include +#include +#include +#include + +/* The VVP code runs on its own stack, handled by cr_xxx() functions. */ + +#include "coroutine_shim.h" + +#include "ngspice/cmtypes.h" // For Digital_t +#include "ngspice/cosim.h" + + +#ifndef NGSPICELIBDIR +#if defined(__MINGW32__) || defined(_MSC_VER) +#define NGSPICELIBDIR "C:\\Spice64\\lib\\ngspice" +#else +#define NGSPICELIBDIR "/usr/local/lib/ngspice" +#endif +#endif + +/* This header file defines the external interface. It also contains an initial + * comment that describes how this shared library is used. + */ + +#include "icarus_shim.h" + +/* Report fatal errors. */ + +static void fail(const char *what, int why) +{ + fprintf(stderr, "Icarus shim failed in function %s: %s.\n", + what, strerror(why)); + abort(); +} + +static void input(struct co_info *pinfo, unsigned int bit, Digital_t *val) +{ + struct ng_vvp *ctx = (struct ng_vvp *)pinfo->handle; + struct ngvp_port *pp; + int count, a, b, dirty; + + /* Convert the value. */ + + if (val->strength <= HI_IMPEDANCE && val->state != UNKNOWN) { + a = val->state; // Normal - '0'/'1'. + b = 0; + } else if (val->strength == HI_IMPEDANCE) { + a = 0; // High impedance - 'z'. + b = 1; + } else { + a = 1; // Undefined - 'x'. + b = 1; + } + + /* Find the port. */ + + if (bit >= pinfo->in_count) { + bit -= pinfo->in_count; + if (bit >= pinfo->inout_count) + return; + pp = ctx->ports + ctx->ins + ctx->outs; // Point at inouts. + count = ctx->inouts; + } else { + pp = ctx->ports; + count = ctx->ins; + } + + while (count--) { + if (pp[count].position <= bit) + break; + } + pp = pp + count; + bit -= pp->position; + + /* Check and update. */ + + dirty = 0; + bit = pp->bits - bit - 1; // Bit position for big-endian. + a <<= bit; + if (a ^ pp->previous.aval) { + if (a) + pp->previous.aval |= a; + else + pp->previous.aval &= ~(1 << bit); + dirty = 1; + } + b <<= bit; + if (b ^ pp->previous.bval) { + if (b) + pp->previous.bval |= b; + else + pp->previous.bval &= ~(1 << bit); + dirty = 1; + } + + if (dirty && !(pp->flags & IN_PENDING)) { + pp->flags |= IN_PENDING; + ++ctx->in_pending; + } +} + +/* Move the VVP simulation forward. */ + +static void step(struct co_info *pinfo) +{ + struct ng_vvp *ctx = (struct ng_vvp *)pinfo->handle; + int i; + + /* Let VVP run. It will stop when it has caught up with SPICE time + * (pinfo->vtime) or produced output. + */ + + cr_yield_to_sim(&ctx->cr_ctx); + + /* Check for output. */ + + if (ctx->out_pending) { + struct ngvp_port *pp; + uint32_t changed, mask; + int limit, i, bit; + + limit = ctx->outs + ctx->inouts; + for (i = 0, pp = ctx->ports + ctx->ins; i < limit; ++i, ++pp) { + if (!(pp->flags & OUT_PENDING)) + continue; + + pp->flags &= ~OUT_PENDING; + changed = (pp->new.aval ^ pp->previous.aval) | + (pp->new.bval ^ pp->previous.bval); + if (changed) { + bit = pp->position; + mask = 1 << (pp->bits - 1); + while (changed) { + if (mask & changed) { + const Digital_t lv_vals[] = + { {ZERO, STRONG}, {ONE, STRONG}, + {ZERO, HI_IMPEDANCE}, {UNKNOWN, STRONG} }; + int a, b; + + a = (pp->new.aval & mask) != 0; + b = (pp->new.bval & mask) != 0; + a += (b << 1); + pinfo->out_fn(pinfo, bit, (Digital_t *)lv_vals + a); + changed &= ~mask; + } + mask >>= 1; + ++bit; + } + pp->previous.aval = pp->new.aval; + pp->previous.bval = pp->new.bval; + } + if (--ctx->out_pending == 0) + break; + } + if (ctx->out_pending) + abort(); + } +} + +static void cleanup(struct co_info *pinfo) +{ + struct ng_vvp *ctx = (struct ng_vvp *)pinfo->handle; + + if (!ctx) + return; + + /* Tell VVP to exit. */ + + ctx->stop = 1; + cr_yield_to_sim(&ctx->cr_ctx); + cr_cleanup(&ctx->cr_ctx); + free(ctx->ports); + dlclose(ctx->vvp_handle); + free(ctx); + pinfo->handle = NULL; +} + +/* Static variable and function for passing context from this library + * to an instance of ivlng.vpi running in the VVP thread. + * Get_ng_vvp() is called in the VVP thread and must synchronise. + * XSPICE initialisation is single-threaded, so a static is OK. + */ + +static struct ng_vvp *context; + +struct ng_vvp *Get_ng_vvp(void) +{ + return context; +} + +/* Thread start function runs the Verilog simulation. */ + +void *run_vvp(void *arg) +{ + static const char * const fn_names[] = { VVP_FN_0, VVP_FN_1, VVP_FN_2, + VVP_FN_3, VVP_FN_4, 0 }; + struct co_info *pinfo = (struct co_info *)arg; + struct vvp_ptrs fns; + void **fpptr; + const char *file; + struct ng_vvp *ctx; + int i; + + cr_safety(); // Make safe with signals. + + /* Find the functions to be called in libvvp. */ + + fpptr = (void **)&fns; + for (i = 0; ; ++i, ++fpptr) { + if (!fn_names[i]) + break; + *fpptr = dlsym(context->vvp_handle, fn_names[i]); + if (!*fpptr) { + fprintf(stderr, "Icarus shim failed to find VVP function: %s.\n", + dlerror()); + abort(); + } + } + + /* Start the simulation. */ + + fns.add_module_path("."); + file = (pinfo->lib_argc >= 3) ? pinfo->lib_argv[2] : NULL; // VVP log file. + fns.init(file, pinfo->sim_argc, (char **)pinfo->sim_argv); + fns.no_signals(); + + /* The VPI file will usually be /usr/local/lib/ngspice/ivlng.vpi + * or C:\Spice64\lib\ngspice\ivlng.vpi. + */ + + if (pinfo->lib_argc >= 2 && pinfo->lib_argv[1][0]) // Explicit VPI file. + file = pinfo->lib_argv[1]; + else +#ifdef STAND_ALONE + file = "./ivlng"; +#else + file = NGSPICELIBDIR "/ivlng"; +#endif + fns.load_module(file); + fns.run(pinfo->sim_argv[0]); + + /* The simulation has finished. Do nothing until destroyed. */ + + ctx = (struct ng_vvp *)pinfo->handle; + ctx->stop = 1; + for (;;) + cr_yield_to_spice(&ctx->cr_ctx); + + return NULL; +} + +/* Entry point to this shared library. Called by d_cosim. */ + +void Cosim_setup(struct co_info *pinfo) +{ + char *file; + struct ngvp_port *last_port; + + /* It is assumed that there is no parallel access to this function + * as ngspice initialisation is single-threaded. + */ + + context = calloc(1, sizeof (struct ng_vvp)); + if (!context) + fail("malloc", errno); + context->cosim_context = pinfo; + pinfo->handle = context; + + /* Load libvvp. It is not statically linked as that would create + * an unwanted build dependency on Icarus Verilog. + */ + + if (pinfo->lib_argc > 0 && pinfo->lib_argv[0][0]) // Explicit path to VVP? + file = (char *)pinfo->lib_argv[0]; + else //libvvp is assumed to be in the OS search path. +#if defined(__MINGW32__) || defined(_MSC_VER) + file = "libvvp.DLL"; +#else + file = "libvvp.so"; +#endif + context->vvp_handle = dlopen(file, RTLD_GLOBAL | RTLD_NOW); + if (!context->vvp_handle) { + fprintf(stderr, "Icarus shim failed to load VVP library: %s.\n", + dlerror()); + abort(); + } + + /* Set-up the execution stack for libvvp and start it. */ + + cr_init(&context->cr_ctx, run_vvp, pinfo); + + /* Return required values in *pinfo. */ + + last_port = context->ports + context->ins - 1; + pinfo->in_count = context->ins ? last_port->position + last_port->bits : 0; + last_port += context->outs; + pinfo->out_count = + context->outs ? last_port->position + last_port->bits : 0; + last_port += context->inouts; + pinfo->inout_count = + context->inouts ? last_port->position + last_port->bits : 0; + pinfo->cleanup = cleanup; + pinfo->step = step; + pinfo->in_fn = input; + pinfo->method = Normal; +} diff --git a/src/xspice/verilog/icarus_shim.h b/src/xspice/verilog/icarus_shim.h new file mode 100644 index 000000000..0a9559635 --- /dev/null +++ b/src/xspice/verilog/icarus_shim.h @@ -0,0 +1,81 @@ +#ifndef _ICARUS_SHIM_H_ +#define _ICARUS_SHIM_H_ + +/* This is the interface definition file for the shim library (ivlng.so) + * and associated Verilog VPI module (icarus_shim.vpi). + * + * Together, the two libraries allow execution of Verilog code using the + * shared library version of Icarus Verilog's VVP program inside a SPICE + * simulation performed by ngspice. + * + * Use of these components starts with a SPICE netlist containing an A-device + * (XSPICE device) whose model card specifies the d_cosim code model and + * parameter 'simulation="some_path/ivlng.so"'. During initialisation, + * the shim library finds the VVP file to be run as a model card parameter, + * loads libvvp.so, and creates a thread to run it, passing the path + * to the VVP file and options to load the VPI module. + * + * The VPI module is called on loading: its first task is to obtain the + * list of ports for the top-level Verilog module. After that the VPI code + * controls the execution of the Verilog code by blocking execution until + * commanded to proceed by the d_cosim instance, always regaining control via + * a VPI callback before the Verilog code moves ahead of the SPICE simulation. + */ + +/* Structure holding pointers to functions in libvvp.so, the Icarus runtime. */ + +struct vvp_ptrs { + void (*add_module_path)(const char *path); +#define VVP_FN_0 "vpip_add_module_path" + void (*init)(const char *logfile_name, int argc, char*argv[]); +#define VVP_FN_1 "vvp_init" + void (*no_signals)(void); +#define VVP_FN_2 "vvp_no_signals" + void (*load_module)(const char *name); +#define VVP_FN_3 "vpip_load_module" + int (*run)(const char *design_path); +#define VVP_FN_4 "vvp_run" +}; + +/* Data stored for each port. */ + +struct ngvp_port { + uint16_t bits; // How many bits? + uint16_t flags; // I/O pending. + uint32_t position; // Number of bits before this port. + struct { // Like struct t_vpi_vecval. + int32_t aval; + int32_t bval; + } previous, new; // Previous and new values. + struct __vpiHandle *handle; // Handle to the port's variable. + struct ng_vvp *ctx; // Pointer back to parent. +}; + +#define IN_PENDING 1 +#define OUT_PENDING 2 + +/* Data strucure used to share context between the ngspice and VVP threads. */ + +struct ng_vvp { + struct cr_ctx cr_ctx; // Coroutine context. + int stop; // Indicates simulation is over. + struct co_info *cosim_context; + uint32_t ins; // Port counts by type. + uint32_t outs; + uint32_t inouts; + double base_time; // SPICE time on entry. + double tick_length; // VVP's time unit. + struct __vpiHandle *stop_cb; // Handle to end-of-tick callback. + volatile uint32_t in_pending; // Counts of changed ports. + volatile uint32_t out_pending; + struct ngvp_port *ports; // Port information array. + void *vvp_handle; // dlopen() handle for libvvp. +}; + +/* Function to find the current d_cosim instance data. Called by VPI code + * and valid only during initialisation: while ngspice thread is waiting. + */ + +struct ng_vvp *Get_ng_vvp(void); + +#endif // _ICARUS_SHIM_H_ diff --git a/src/xspice/verilog/libvvp.def b/src/xspice/verilog/libvvp.def new file mode 100644 index 000000000..0ae7d5a00 --- /dev/null +++ b/src/xspice/verilog/libvvp.def @@ -0,0 +1,57 @@ +; +; Definition file of libvvp.DLL +; Automatic generated by gendef +; written by Kai Tietz 2008 +; +LIBRARY "libvvp.DLL" +EXPORTS +vpi_chk_error +vpi_compare_objects +vpi_control +vpi_flush +vpi_fopen +vpi_free_object +vpi_get +vpi_get_delays +vpi_get_file +vpi_get_str +vpi_get_systf_info +vpi_get_time +vpi_get_userdata +vpi_get_value +vpi_get_vlog_info +vpi_handle +vpi_handle_by_index +vpi_handle_by_name +vpi_handle_multi +vpi_iterate +vpi_mcd_close +vpi_mcd_flush +vpi_mcd_name +vpi_mcd_open +vpi_mcd_printf +vpi_mcd_vprintf +vpi_mode_flag DATA +vpi_printf +vpi_put_delays +vpi_put_userdata +vpi_put_value +vpi_register_cb +vpi_register_systf +vpi_release_handle +vpi_remove_cb +vpi_routines DATA +vpi_scan +vpi_sim_control +vpi_sim_vcontrol +vpi_trace DATA +vpi_vprintf +vpip_add_module_path +vpip_clear_module_paths +vpip_load_module +vvp_init +vvp_no_signals +vvp_run +vvp_set_stop_is_finish +vvp_set_stop_is_finish_exit_code +vvp_set_verbose_flag diff --git a/src/xspice/verilog/vpi.c b/src/xspice/verilog/vpi.c new file mode 100644 index 000000000..a2a0f28a6 --- /dev/null +++ b/src/xspice/verilog/vpi.c @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2002 Stephen Williams (steve@icarus.com) + * Copyright (c) 2023 Giles Atkinson + * + * This source code is free software; you can redistribute it + * and/or modify it in source code form under the terms of the GNU + * General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +//#include +#include "vpi_user_dummy.h" + +#include "ngspice/cmtypes.h" // For Digital_t +#include "ngspice/cosim.h" + +/* The VVP code runs on its own stack, handled by cr_xxx() functions. */ + +#include "coroutine_cosim.h" + +/* This header file defines external interfaces. It also contains an initial + * comment that describes how this VPI module is used. + */ + +#include "icarus_shim.h" + +/* Functions that VPI calls have a returned parameter argument that + * is not used here. + */ + +#define UNUSED __attribute__((unused)) + +/* Debugging printfs(). */ + +//#define DEBUG +#ifdef DEBUG +#define DBG(...) vpi_printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +static PLI_INT32 next_advance_cb(struct t_cb_data *cb); + +/* Get current simulation time: no module-specific values. */ + +static double get_time(struct ng_vvp *ctx) +{ + static struct t_vpi_time now = { .type = vpiSimTime }; + uint64_t ticks; + + vpi_get_time(NULL, &now); + ticks = ((uint64_t)now.high << 32) + now.low; + return ticks * ctx->tick_length; +} + +/* Arrange for end_advance_cb() to be called in the future. */ + +static vpiHandle set_stop(uint64_t length, struct ng_vvp *ctx) +{ + static struct t_vpi_time now = { .type = vpiSimTime }; + static struct t_cb_data cbd = { .cb_rtn = next_advance_cb, .time = &now }; + + now.low = length; + now.high = length >> 32; + if (length == 0) + cbd.reason = cbReadWriteSynch; + else + cbd.reason = cbAfterDelay; + + /* Callback after delay. */ + + cbd.user_data = (PLI_BYTE8 *)ctx; + return vpi_register_cb(&cbd); +} + +/* Timed callback at end of simulation advance: wait for a command + * from the main thread, and schedule the next callback. + * On return, VVP runs some more. + */ + +static PLI_INT32 next_advance_cb(struct t_cb_data *cb) +{ + struct ng_vvp *ctx = (struct ng_vvp *)cb->user_data; + struct t_vpi_value val; + double vl_time; + uint64_t ticks; + unsigned int i; + + for (;;) { + /* Still wanted? */ + + if (ctx->stop) { + vpi_control(vpiFinish, 0); // Returns after scheduling $finish. + return 0; + } + + /* Save base time for next slice. */ + + ctx->base_time = ctx->cosim_context->vtime; + + /* Repeatedly wait for instructions from the main thread + * until VVP can advance at least one time unit. + */ + + cr_yield_to_spice(&ctx->cr_ctx); + + /* Check for input. */ + + val.format = vpiVectorVal; + i = ctx->ins ? 0 : ctx->outs; + while (ctx->in_pending) { + if (ctx->ports[i].flags & IN_PENDING) { + ctx->ports[i].flags ^= IN_PENDING; + val.value.vector = + (struct t_vpi_vecval *)&ctx->ports[i].previous; + vpi_put_value(ctx->ports[i].handle, &val, NULL, vpiNoDelay); + ctx->in_pending--; + DBG("VPI input %d/%d on %s\n", + val.value.vector->aval, val.value.vector->bval, + vpi_get_str(vpiName, ctx->ports[i].handle)); + } else if (++i == ctx->ins) { + i = ctx->ins + ctx->outs; // Now scan inouts + } + } + + /* How many VVP ticks to advance? */ + + vl_time = get_time(ctx); + if (ctx->cosim_context->vtime < vl_time) { + /* This can occur legitimately as the two times need not + * align exactly. But it should be less than one SPICE timestep. + */ + + DBG("VVP time %.16g ahead of SPICE target %.16g\n", + vl_time, ctx->cosim_context->vtime); + if (ctx->cosim_context->vtime + ctx->tick_length < vl_time) { + fprintf(stderr, + "Error: time reversal (%.10g->%.10g) in " + "Icarus Shim VPI!\n", + vl_time, ctx->cosim_context->vtime); + } + continue; + } + + ticks = (ctx->cosim_context->vtime - vl_time) / ctx->tick_length; + if (ticks > 0) { + DBG("Advancing from %g to %g: %lu ticks\n", + vl_time, ctx->cosim_context->vtime, ticks); + ctx->stop_cb = set_stop(ticks, ctx); + return 0; + } + } +} + +/* Callback function - new output value. */ + +static PLI_INT32 output_cb(struct t_cb_data *cb) +{ + struct ngvp_port *pp = (struct ngvp_port *)cb->user_data; + struct ng_vvp *ctx = pp->ctx; + + DBG("Output: %s is %d now %g (VL) %g (SPICE)\n", + vpi_get_str(vpiName, cb->obj), + cb->value->value.vector->aval, + get_time(ctx), ctx->cosim_context->vtime); + + if (ctx->stop_cb) { + /* First call in the current VVP cycle: cancel the current + * stop CB and request a new one before the next VVP time point. + * That allows all output events in the current timestep + * to be gathered before stopping. + */ + + vpi_remove_cb(ctx->stop_cb); + ctx->stop_cb = NULL; + set_stop(0, ctx); + + /* Set the output time in SPICE format. + * It must not be earlier than entry time. + */ + + ctx->cosim_context->vtime = get_time(ctx); + if (ctx->cosim_context->vtime < ctx->base_time) + ctx->cosim_context->vtime = ctx->base_time; + } + + /* Record the value. */ + + pp->new.aval = cb->value->value.vector->aval; + pp->new.bval = cb->value->value.vector->bval; + if (!(pp->flags & OUT_PENDING)) { + pp->flags |= OUT_PENDING; + ++ctx->out_pending; + } + return 0; +} + +/* Utilty functions for start_cb() - initialise or set watch on a variable.*/ + +static void init(vpiHandle handle) +{ + static struct t_vpi_value val = { .format = vpiIntVal }; + + DBG("Initialising %s to 0\n", vpi_get_str(vpiName, handle)); + + vpi_put_value(handle, &val, NULL, vpiNoDelay); +} + +static void watch(vpiHandle handle, void *pp) +{ + static struct t_vpi_time time = { .type = vpiSuppressTime }; + static struct t_vpi_value val = { .format = vpiVectorVal }; + static struct t_cb_data cb = { + .reason = cbValueChange, .cb_rtn = output_cb, + .time = &time, .value = &val + }; + + cb.obj = handle; + cb.user_data = pp; + vpi_register_cb(&cb); +} + +/* Callback function - simulation is starting. */ + +static PLI_INT32 start_cb(struct t_cb_data *cb) +{ + struct ng_vvp *ctx = (struct ng_vvp *)cb->user_data; + vpiHandle iter, top, item; + PLI_INT32 direction; + char *name; + int ii, oi, ioi; + + DBG("Unit %d precision %d\n", + vpi_get(vpiTimeUnit, NULL), vpi_get(vpiTimePrecision, NULL)); + ctx->tick_length = pow(10.0, vpi_get(vpiTimeUnit, NULL)); + + /* Find the (unique?) top-level module and the one inside it. */ + + iter = vpi_iterate(vpiModule, NULL); + top = vpi_scan(iter); + vpi_free_object(iter); + DBG("Top %s\n", vpi_get_str(vpiName, top)); + + /* Count the ports. */ + + iter = vpi_iterate(vpiPort, top); + if (!iter) + vpi_printf("Top module has no ports!\n"); // vpi_scan() aborts. + ctx->ins = ctx->outs = ctx->inouts = 0; + while ((item = vpi_scan(iter))) { + direction = vpi_get(vpiDirection, item); + switch (direction) { + case vpiInput: + ++ctx->ins; + break; + case vpiOutput: + ++ctx->outs; + break; + case vpiInout: + ++ctx->inouts; + break; + default: + break; + } + } + ctx->ports = (struct ngvp_port *)malloc( + (ctx->ins + ctx->outs + ctx->inouts) * + sizeof (struct ngvp_port)); + if (!ctx->ports) { + vpi_printf("No memory for ports at " __FILE__ ":%d\n", __LINE__); + abort(); + } + + /* Get the ports. */ + + iter = vpi_iterate(vpiPort, top); + ii = oi = ioi = 0; + while ((item = vpi_scan(iter))) { + vpiHandle namesake; + struct ngvp_port *pp; + int first; + + direction = vpi_get(vpiDirection, item); + name = vpi_get_str(vpiName, item); + + /* Assume that there is an object with the same name as the port + * whose value may be read or set as needed. This is an assumption + * about how code for VVP is generated. + */ + + namesake = vpi_handle_by_name(name, top); + DBG("Port %s direction %d size %d, namesake type %d\n", + name, direction, vpi_get(vpiSize, item), + vpi_get(vpiType, namesake)); + + switch (direction) { + case vpiInput: + first = !ii; + pp = ctx->ports + ii++; + init(namesake); + break; + case vpiOutput: + first = !oi; + pp = ctx->ports + ctx->ins + oi++; + watch(namesake, pp); + break; + case vpiInout: + first = !ioi; + init(namesake); + pp = ctx->ports + ctx->ins + ctx->outs + ioi++; + watch(namesake, pp); + break; + default: + continue; + } + pp->bits = vpi_get(vpiSize, item); + pp->flags = 0; + pp->position = first ? 0 : pp[-1].position + pp[-1].bits; + pp->previous.aval = pp->previous.bval = 0; + pp->handle = namesake; + pp->ctx = ctx; + } + + /* Make a direct call to the "end-of-advance" callback to start running. */ + + cr_init(&ctx->cr_ctx); + cb->user_data = (PLI_BYTE8 *)ctx; + next_advance_cb(cb); + return 0; +} + +/* VPI initialisation. */ + +static void start(void) +{ + static struct t_vpi_time now = { .type = vpiSimTime }; + static struct t_cb_data cbd = { .reason = cbStartOfSimulation, + .time = &now, .cb_rtn = start_cb }; +#ifdef DEBUG + struct t_vpi_vlog_info info; + + /* Get the program name. */ + + if (vpi_get_vlog_info(&info)) { + vpi_printf("Starting ivlng.vpi in %s\n", info.argv[0]); + for (int i = 0; i < info.argc; ++i) + vpi_printf("%d: %s\n", i, info.argv[i]); + vpi_printf("P: %s V: %s\n", info.product, info.version); + } else { + vpi_printf("Failed to get invocation information.\n"); + } +#endif + + /* The first step is to find the top-level module and query its ports. + * At this point they do not exist, so request a callback once they do. + */ + + cbd.user_data = (PLI_BYTE8 *)Get_ng_vvp(); + vpi_register_cb(&cbd); +} + +/* This is a table of registration functions. It is the external symbol + * that the VVP simulator looks for when loading this .vpi module. + */ + +void (*vlog_startup_routines[])(void) = { + start, + 0 +}; diff --git a/src/xspice/verilog/vpi_dummy.c b/src/xspice/verilog/vpi_dummy.c new file mode 100644 index 000000000..2283b4e63 --- /dev/null +++ b/src/xspice/verilog/vpi_dummy.c @@ -0,0 +1,24 @@ +/* Dummy implementations of the functions defined in vpi_user_dummy.h, + * used to make a DLL to replace libvvp.DLL when linking, so that + * Icarus Verilog need not be installed when linking components for it. + */ + +#include +#include "vpi_user_dummy.h" + +PLI_INT32 vpi_printf(const char *, ...) {return 0;} +PLI_INT32 vpi_get_vlog_info(struct t_vpi_vlog_info *p) {return 0;} +void vpi_get_time(vpiHandle h, struct t_vpi_time *p) {} +vpiHandle vpi_register_cb(struct t_cb_data *p) {return (void *)0;} +PLI_INT32 vpi_remove_cb(vpiHandle h) {return 0;} +PLI_INT32 vpi_free_object(vpiHandle h) {return 0;} + +vpiHandle vpi_put_value(vpiHandle h, struct t_vpi_value *p1, + struct t_vpi_time *p2, PLI_INT32 i) {return (void *)0;} +char *vpi_get_str(PLI_INT32 i, vpiHandle h) {return (char *)0;} +PLI_INT32 vpi_get(int, vpiHandle) {return 0;} + +vpiHandle vpi_iterate(PLI_INT32, vpiHandle) {return (void *)0;} +vpiHandle vpi_scan(vpiHandle) {return (void *)0;} +vpiHandle vpi_handle_by_name(const char *, vpiHandle) {return (void *)0;} +void vpi_control(PLI_INT32 operation, ...) {} diff --git a/src/xspice/verilog/vpi_user_dummy.h b/src/xspice/verilog/vpi_user_dummy.h new file mode 100644 index 000000000..ce109548b --- /dev/null +++ b/src/xspice/verilog/vpi_user_dummy.h @@ -0,0 +1,102 @@ +/* A minimal extract from the Verilog Standard's vpi_user.h, so that + * Icarus Verilog support for ngspice can be compiled + * without an installed copy. + */ + +typedef char PLI_BYTE8; +typedef uint32_t PLI_UINT32; +typedef int32_t PLI_INT32; + +struct t_vpi_time { + PLI_INT32 type; + PLI_UINT32 high; + PLI_UINT32 low; + double real; +}; + +#define vpiScaledRealTime 1 +#define vpiSimTime 2 +#define vpiSuppressTime 3 + +struct t_vpi_vecval { + PLI_INT32 aval, bval; +}; + +struct t_vpi_value { + PLI_INT32 format; + union { + char *str; + PLI_INT32 scalar; + PLI_INT32 integer; + double real; + struct t_vpi_time *time; + struct t_vpi_vecval *vector; + struct t_vpi_strengthval *strength; + char *misc; + } value; +}; + +#define vpiIntVal 6 +#define vpiVectorVal 9 + +typedef struct __vpiHandle *vpiHandle; + +struct t_cb_data { + PLI_INT32 reason; + PLI_INT32 (*cb_rtn)(struct t_cb_data *); + vpiHandle obj; + struct t_vpi_time *time; + struct t_vpi_value *value; + PLI_INT32 index; + const PLI_BYTE8 *user_data; +}; + +#define cbValueChange 1 +#define cbReadWriteSynch 6 +#define cbReadOnlySynch 7 +#define cbNextSimTime 8 +#define cbAfterDelay 9 +#define cbStartOfSimulation 11 + +struct t_vpi_vlog_info +{ + PLI_INT32 argc; + char **argv; + char *product; + char *version; +}; + +extern PLI_INT32 vpi_printf(const char *, ...); +extern PLI_INT32 vpi_get_vlog_info(struct t_vpi_vlog_info *); +extern void vpi_get_time(vpiHandle, struct t_vpi_time *); +extern vpiHandle vpi_register_cb(struct t_cb_data *); +extern PLI_INT32 vpi_remove_cb(vpiHandle); +extern PLI_INT32 vpi_free_object(vpiHandle); + +#define vpiNoDelay 1 +extern vpiHandle vpi_put_value(vpiHandle, struct t_vpi_value *, + struct t_vpi_time *, PLI_INT32); + +#define vpiType 1 +#define vpiName 2 +#define vpiSize 4 +#define vpiTimeUnit 11 +#define vpiTimePrecision 12 +#define vpiDirection 20 + +#define vpiInput 1 +#define vpiOutput 2 +#define vpiInout 3 + +#define vpiModule 32 +#define vpiPort 44 + +extern char *vpi_get_str(PLI_INT32, vpiHandle); +extern PLI_INT32 vpi_get(int, vpiHandle); +extern vpiHandle vpi_iterate(PLI_INT32, vpiHandle); +extern vpiHandle vpi_scan(vpiHandle); +extern vpiHandle vpi_handle_by_name(const char *, vpiHandle); + +#define vpiFinish 67 + +extern void vpi_control(PLI_INT32 operation, ...); diff --git a/visualc/make-install-vngspice.bat b/visualc/make-install-vngspice.bat index 871abf6d9..3891a718e 100644 --- a/visualc/make-install-vngspice.bat +++ b/visualc/make-install-vngspice.bat @@ -20,6 +20,8 @@ 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 xspice\verilog\ivlng.dll %dst%\lib\ngspice\ivlng.dll +copy xspice\verilog\shim.vpi %dst%\lib\ngspice\ivlng.vpi if "%2" == "fftw" goto copy2 if "%3" == "fftw" goto copy2 @@ -47,6 +49,8 @@ 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 xspice\verilog\ivlng.dll %dst%\lib\ngspice\ivlng.dll +copy xspice\verilog\shim.vpi %dst%\lib\ngspice\ivlng.vpi if "%2" == "fftw" goto copy2-64 if "%3" == "fftw" goto copy2-64 diff --git a/visualc/make-install-vngspiced.bat b/visualc/make-install-vngspiced.bat index a75f8c705..3de5fc68a 100644 --- a/visualc/make-install-vngspiced.bat +++ b/visualc/make-install-vngspiced.bat @@ -20,6 +20,8 @@ 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 xspice\verilog\ivlng.dll %dst%\lib\ngspice\ivlng.dll +copy xspice\verilog\shim.vpi %dst%\lib\ngspice\ivlng.vpi if "%2" == "fftw" goto copy2 if "%3" == "fftw" goto copy2 @@ -47,6 +49,8 @@ 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 xspice\verilog\ivlng.dll %dst%\lib\ngspice\ivlng.dll +copy xspice\verilog\shim.vpi %dst%\lib\ngspice\ivlng.vpi if "%2" == "fftw" goto copy2-64 if "%3" == "fftw" goto copy2-64 diff --git a/visualc/xspice/aux-digital.bat b/visualc/xspice/aux-digital.bat new file mode 100644 index 000000000..26920bb3b --- /dev/null +++ b/visualc/xspice/aux-digital.bat @@ -0,0 +1,18 @@ +rem Pre-build commands for digital.cm. +rem Make support components for Icarus Verilog co-simulation: +rem ivlng.dll and ivlng.vpi. Then run aux-cfunc.bat. + +md verilog +pushd verilog +set src=..\..\..\src\xspice\verilog +set inc=..\..\..\src\include +CL.EXE /O2 /LD /EHsc /Feivlng.DLL /I%src% /I%inc% %src%\icarus_shim.c ^ +/link /EXPORT:Cosim_setup /EXPORT:Get_ng_vvp + +rem Make a dummy libvvp.obj, needed for shim.vpi (to be renamed ivlng.vpi). + +lib.exe /def:%src%\libvvp.def /machine:X64 +CL.EXE /O2 /LD /EHsc /Feshim.vpi /I. /I%inc% %src%\vpi.c libvvp.lib ivlng.lib /link /DLL /EXPORT:vlog_startup_routines +dir +popd +.\aux-cfunc.bat digital diff --git a/visualc/xspice/digital.vcxproj b/visualc/xspice/digital.vcxproj index 53616fa1d..1585a1551 100644 --- a/visualc/xspice/digital.vcxproj +++ b/visualc/xspice/digital.vcxproj @@ -87,8 +87,8 @@ - generate cfunc.c and ifspec.c files - call .\aux-cfunc.bat $(ProjectName) + Generate Iverilog support, cfunc.c and ifspec.c files + call .\aux-digital.bat $(ProjectName) @@ -400,4 +400,4 @@ - \ No newline at end of file +