You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

318 lines
8.5 KiB

/*
* 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;
}