Add support for including Verilog simulation within an instance

of the d_cosim codemodel, using libvvp, the simulation runtime of
Icarus Verilog.  This complements the existing method using Verilator.
The new source code is built into two binary shared libraries,
ivlng.so (or .DLL) and ivlng.vpi that are loaded during simulation.
This commit is contained in:
Giles Atkinson 2024-06-10 11:09:49 +01:00 committed by Holger Vogt
parent 693a9df09f
commit ce1ecca15e
17 changed files with 1275 additions and 15 deletions

View File

@ -409,12 +409,15 @@ fi
#### ####
### check for operating system at compile time ### check for operating system at compile time
AM_CONDITIONAL([DLIBS_FULLY_RESOLVED], false)
case $host_os in case $host_os in
*mingw* | *msys* ) *mingw* | *msys* )
AC_DEFINE([OS_COMPILED], [1], [MINGW for MS Windows]) AC_DEFINE([OS_COMPILED], [1], [MINGW for MS Windows])
AM_CONDITIONAL([DLIBS_FULLY_RESOLVED], true)
;; ;;
*cygwin* ) *cygwin* )
AC_DEFINE([OS_COMPILED], [2], [Cygwin for MS Windows]) AC_DEFINE([OS_COMPILED], [2], [Cygwin for MS Windows])
AM_CONDITIONAL([DLIBS_FULLY_RESOLVED], true)
;; ;;
*freebsd* ) *freebsd* )
AC_DEFINE([OS_COMPILED], [3], [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 if test "x$enable_relpath" = xyes; then
AC_DEFINE_UNQUOTED([NGSPICEBINDIR], ["`echo ../bin`"], [Define the directory for executables]) 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([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]) AC_DEFINE([HAS_RELPATH], [1], [rel. path of libraries and scripts])
else else
AC_DEFINE_UNQUOTED([NGSPICEBINDIR], ["`echo $dprefix/bin`"], [Define the directory for executables]) 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([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 fi
# Create timestamp, may be overruled by setting env var SOURCE_DATE_EPOCH # 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/enh/Makefile
src/xspice/ipc/Makefile src/xspice/ipc/Makefile
src/xspice/idn/Makefile src/xspice/idn/Makefile
src/xspice/verilog/Makefile
src/osdi/Makefile src/osdi/Makefile
tests/Makefile tests/Makefile
tests/bsim1/Makefile tests/bsim1/Makefile

View File

@ -8,17 +8,8 @@ EXTRA_DIST = README examples icm xspice.c .gitignore \
## libs. It is currently compiled manually, last. ## libs. It is currently compiled manually, last.
##SUBDIRS = mif cm enh evt ipc idn icm ##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: dist-hook:
rm -f "$(distdir)/icm/makedefs" rm -f "$(distdir)/icm/makedefs"
rm -f "$(distdir)/icm/GNUmakefile" rm -f "$(distdir)/icm/GNUmakefile"

View File

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

View File

@ -1,3 +1,19 @@
This directory contains Ngspice scripts and other files used to prepare This directory contains Ngspice scripts and other files used to prepare
Verilog (and possibly VHDL) code to be included in an Ngspice simulation. Verilog code to be included in an Ngspice simulation, using Verilator
An example circuit can be found in examples/xspice/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.

View File

@ -0,0 +1,29 @@
#ifndef _COSIM_COROUTINE_H_
#define _COSIM_COROUTINE_H_
#if defined(__MINGW32__) || defined(_MSC_VER)
#include <windows.h> // 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 <pthread.h>
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_

View File

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

View File

@ -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 <dlfcn.h>
#include <signal.h>
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_

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
/* 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;
}

View File

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

View File

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

384
src/xspice/verilog/vpi.c Normal file
View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
//#include <vpi_user.h>
#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
};

View File

@ -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 <stdint.h>
#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, ...) {}

View File

@ -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, ...);

View File

@ -20,6 +20,8 @@ copy %cmsrc%\table.cm %dst%\lib\ngspice\table.cm
copy %cmsrc%\xtraevt.cm %dst%\lib\ngspice\xtraevt.cm copy %cmsrc%\xtraevt.cm %dst%\lib\ngspice\xtraevt.cm
copy %cmsrc%\xtradev.cm %dst%\lib\ngspice\xtradev.cm copy %cmsrc%\xtradev.cm %dst%\lib\ngspice\xtradev.cm
copy %cmsrc%\spice2poly.cm %dst%\lib\ngspice\spice2poly.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 "%2" == "fftw" goto copy2
if "%3" == "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%\xtraevt64.cm %dst%\lib\ngspice\xtraevt.cm
copy %cmsrc%\xtradev64.cm %dst%\lib\ngspice\xtradev.cm copy %cmsrc%\xtradev64.cm %dst%\lib\ngspice\xtradev.cm
copy %cmsrc%\spice2poly64.cm %dst%\lib\ngspice\spice2poly.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 "%2" == "fftw" goto copy2-64
if "%3" == "fftw" goto copy2-64 if "%3" == "fftw" goto copy2-64

View File

@ -20,6 +20,8 @@ copy %cmsrc%\table.cm %dst%\lib\ngspice\table.cm
copy %cmsrc%\xtraevt.cm %dst%\lib\ngspice\xtraevt.cm copy %cmsrc%\xtraevt.cm %dst%\lib\ngspice\xtraevt.cm
copy %cmsrc%\xtradev.cm %dst%\lib\ngspice\xtradev.cm copy %cmsrc%\xtradev.cm %dst%\lib\ngspice\xtradev.cm
copy %cmsrc%\spice2poly.cm %dst%\lib\ngspice\spice2poly.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 "%2" == "fftw" goto copy2
if "%3" == "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%\xtraevt64.cm %dst%\lib\ngspice\xtraevt.cm
copy %cmsrc%\xtradev64.cm %dst%\lib\ngspice\xtradev.cm copy %cmsrc%\xtradev64.cm %dst%\lib\ngspice\xtradev.cm
copy %cmsrc%\spice2poly64.cm %dst%\lib\ngspice\spice2poly.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 "%2" == "fftw" goto copy2-64
if "%3" == "fftw" goto copy2-64 if "%3" == "fftw" goto copy2-64

View File

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

View File

@ -87,8 +87,8 @@
</PropertyGroup> </PropertyGroup>
<ItemDefinitionGroup> <ItemDefinitionGroup>
<PreBuildEvent> <PreBuildEvent>
<Message>generate cfunc.c and ifspec.c files</Message> <Message>Generate Iverilog support, cfunc.c and ifspec.c files</Message>
<Command>call .\aux-cfunc.bat $(ProjectName)</Command> <Command>call .\aux-digital.bat $(ProjectName)</Command>
</PreBuildEvent> </PreBuildEvent>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">