diff --git a/src/include/ngspice/cosim.h b/src/include/ngspice/cosim.h new file mode 100644 index 000000000..dd6b97094 --- /dev/null +++ b/src/include/ngspice/cosim.h @@ -0,0 +1,71 @@ +/* Header file for the shim code between d_cosim and a co-simulator. */ + +#if __cplusplus +extern "C" { +#endif + +/* A value of this type controls how the step() function is called. + * The normal method is to call step() to advance to the time of a + * queued input, then supply the input, then call step again to + * advance to the next input time or the end of the current SPICE + * timestep. But Verilator does nothing without an input change, + * so step() must be called after input. + */ + +typedef enum {Normal, After_input} Cosim_method; + +/* Structure used by Cosim_setup() to pass and return + * co-simulation interface information. + */ + +struct co_info { + /* The co-simulator must set the number of ports in Cosim_setup(). */ + + unsigned int in_count; + unsigned int out_count; + 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. + */ + + void (*cleanup)(struct co_info *); + + /* Function called by SPICE to advance the co-simulation. + * A pointer to this structure is passed, so it has access to its handle + * and the target simulation time, vtime. The co-simulator should + * pause the step when output is produced and update vtime. + */ + + void (*step)(struct co_info *pinfo); // Advance simulation. + + /* Function called by SPICE to pass input to input and inout ports. + * (Inouts after inputs.) + * Called as: + * struct co_info info; + * (*in_fn)(&info, bit_number, &value); + * Function provided by co-simulator. + */ + + void (*in_fn)(struct co_info *, unsigned int, Digital_t *); + + /* Function called by co-simulator to report output on + * output and inout ports. (Inouts after outputs.) + * Called as: + * struct co_info *p_info; + * (*out_fn)(p_info, bit_number, &value); + * It will usually be called inside a call to step(). + */ + + void (*out_fn)(struct co_info *, unsigned int, Digital_t *); + void *handle; // Co-simulator's private handle + double vtime; // Time in the co-simulation. + Cosim_method method; // May be set in Cosim_setup; +}; + +extern void Cosim_setup(struct co_info *pinfo); // This must exist. +extern void Cosim_step(struct co_info *pinfo); // Exists for Verilator. + +#if __cplusplus +} +#endif diff --git a/src/xspice/icm/digital/d_cosim/cfunc.mod b/src/xspice/icm/digital/d_cosim/cfunc.mod new file mode 100644 index 000000000..fb653a47d --- /dev/null +++ b/src/xspice/icm/digital/d_cosim/cfunc.mod @@ -0,0 +1,578 @@ +/* Code model d_cosim. + * + * XSPICE code model for running a co-simulation with no support + * for abandoning the current timestep. + */ + +#include +#include +#include + +#if defined (__MINGW32__) || defined (__CYGWIN__) || defined (_MSC_VER) +/* MS WINDOWS. */ +#undef BOOLEAN +#include + +#define dlopen(name, type) LoadLibrary(name) +#define dlsym(handle, name) (void *)GetProcAddress(handle, name) +#define dlclose(handle) FreeLibrary(handle) + +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, lpMsgBuf); + LocalFree(lpMsgBuf); + } + return errstr; +} /* end of function dlerror */ +#else +#include +#endif + +#include "ngspice/cosim.h" + +/* The argument passed to code model functions. */ + +#define XSPICE_ARG mif_private + +#define DBG(...) +//#define DBG(...) cm_message_printf(__VA_ARGS__) + +/* Structure used to hold queued inputs. */ + +struct pend_in { + double when; // Due time. + unsigned int which; // Index of input. + Digital_t what; // The value. +}; + +/* Structure to maintain context, pointed to by STATIC VAR cosim_instance. */ + +struct instance { + struct co_info info; // Co-simulation interface - MUST BE FIRST. + int q_index; // Queue index (last active entry). + unsigned int q_length; // Size of input queue. + struct pend_in *q; // The input queue. + unsigned int in_ports; // Number of XSPICE inputs. + unsigned int out_ports; // Number of XSPICE outputs. + unsigned int inout_ports; // Number of XSPICE inout ports. + unsigned int op_pending; // Output is pending. + Digital_t *out_vals; // The new output values. + double extra; // Margin to extend timestep. + void *so_handle; // dlopen() handle to the simulation binary. +}; + +/* Called at end of simulation run to free memory. */ + +static void callback(ARGS, Mif_Callback_Reason_t reason) +{ + struct instance *ip; + + ip = (struct instance *)STATIC_VAR(cosim_instance); + if (reason == MIF_CB_DESTROY) { + if (!ip) + return; + if (ip->info.cleanup) + (*ip->info.cleanup)(&ip->info); + if (ip->so_handle) + dlclose(ip->so_handle); + if (ip->q) + free(ip->q); + if (ip->out_vals) + free(ip->out_vals); + free(ip); + STATIC_VAR(cosim_instance) = NULL; + } +} + +/* Function called when a co-simulator output changes. + * Out-of-range values for bit_num must be ignored. + */ + +void accept_output(struct co_info *pinfo, unsigned int bit_num, Digital_t *val) +{ + struct instance *ip = (struct instance *)pinfo; // First member. + Digital_t *out_vals; // XSPICE rotating memory. + + if (bit_num >= ip->out_ports + ip->inout_ports) + return; + out_vals = (Digital_t *)cm_event_get_ptr(1, 0); + DBG("Change %s %d/%d->%d/%d vtime %g", + cm_get_node_name("d_out", bit_num), + out_vals[bit_num].state, out_vals[bit_num].strength, + val->state, val->strength, + ip->info.vtime); + if (ip->op_pending == 0) { + /* Prepare pending output. */ + + memcpy(ip->out_vals, out_vals, ip->out_ports * sizeof *ip->out_vals); + ip->op_pending = 1; + } + ip->out_vals[bit_num] = *val; +} + +/* Push pending outputs, usually sent back from the future. + * It is safe to use OUTPUT() here, although it mays seem that this + * function may be called twice in a single call to cm_d_cosim(). + * There will never be any input changes when cm_d_cosim() is called + * with pending output, as all input for the shortened time-step has + * already been processed. + */ + +static void output(struct instance *ip, ARGS) +{ + double delay; + Digital_t *out_vals; // XSPICE rotating memory + int i, j; + + delay = PARAM(delay) - (TIME - ip->info.vtime); + if (delay <= 0) { + cm_message_printf("WARNING: output scheduled with impossible " + "delay (%g) at %g.", delay, TIME); + delay = 1e-12; + } + out_vals = (Digital_t *)cm_event_get_ptr(1, 0); + + /* Output to d_out. */ + + for (i = 0; i < ip->out_ports; ++i) { + if (ip->out_vals[i].state != out_vals[i].state || + ip->out_vals[i].strength != out_vals[i].strength) { + DBG("%g: OUT %s %d/%d->%d/%d vtime %g with delay %g", + TIME, cm_get_node_name("d_out", i), + out_vals[i].state, out_vals[i].strength, + ip->out_vals[i].state, ip->out_vals[i].strength, + ip->info.vtime, delay); + *(Digital_t *)OUTPUT(d_out[i]) = out_vals[i] = ip->out_vals[i]; + OUTPUT_DELAY(d_out[i]) = delay; + OUTPUT_CHANGED(d_out[i]) = TRUE; + } else { + OUTPUT_CHANGED(d_out[i]) = FALSE; + } + } + + /* Output to d_inout. */ + + for (i = 0, j = ip->out_ports; i < ip->inout_ports; ++i, ++j) { + if (ip->out_vals[j].state != out_vals[j].state || + ip->out_vals[j].strength != out_vals[j].strength) { + DBG("%g: inOUT %s %d/%d->%d/%d vtime %g with delay %g", + TIME, cm_get_node_name("d_inout", i), + out_vals[j].state, out_vals[j].strength, + ip->out_vals[j].state, ip->out_vals[j].strength, + ip->info.vtime, delay); + *(Digital_t *)OUTPUT(d_inout[i]) = out_vals[j] = ip->out_vals[j]; + OUTPUT_DELAY(d_inout[i]) = delay; + OUTPUT_CHANGED(d_inout[i]) = TRUE; + } else { + OUTPUT_CHANGED(d_inout[i]) = FALSE; + } + } + ip->op_pending = 0; +} + +/* Run the co-simulation. Return 1 if the timestep was truncated. */ + +static int advance(struct instance *ip, ARGS) +{ + /* The co-simulator should advance to the time in ip->info.vtime, + * but should pause when output is generated and update vtime. + */ + + (*ip->info.step)(&ip->info); + + if (ip->op_pending) { + + /* The co-simulator produced some output. */ + + if (TIME - ip->info.vtime <= PARAM(delay)) { +#if 1 + DBG("Direct output with SPICE %.16g CS %.16g", + TIME, ip->info.vtime); + output(ip, XSPICE_ARG); // Normal output, unlikely. +#else + cm_event_queue((TIME + ip->info.vtime + PARAM(delay)) / 2.0); +#endif + } else { + + /* Something changed that may alter the future of the + * SPICE simulation. Truncate the current timestep so that + * SPICE will see the pending output, which currently occurred + * in the past. + */ + + DBG("Truncating timestep to %.16g", ip->info.vtime + ip->extra); + cm_analog_set_temp_bkpt(ip->info.vtime + ip->extra); + + /* Any remaining input events are in an alternate future. */ + + ip->q_index = -1; + return 1; + } + } + return 0; +} + +/* Called from the main function to run the co-simulation. */ + +static void run(struct instance *ip, ARGS) +{ + struct pend_in *rp; + double sim_started; + int i; + + if (ip->q_index < 0) { + /* No queued input, advance to current TIME. */ + + DBG("Advancing vtime without input %.16g -> %.16g", + ip->info.vtime , TIME); + ip->info.vtime = TIME; + advance(ip, XSPICE_ARG); + return; + } + + /* Scan the queue. */ + + DBG("%.16g: Running Q with %d entries", TIME, ip->q_index + 1); + sim_started = ip->info.vtime; + + for (i = 0; i <= ip->q_index; ++i) { + rp = ip->q + i; + if (rp->when <= sim_started) { + /* Not expected. */ + + cm_message_printf("Warning simulated event is in the past:\n" + "XSPICE %.16g\n" + "Event %.16g\n" + "Cosim %.16g", + TIME, rp->when, ip->info.vtime); + cm_message_printf("i=%d index=%d", i, ip->q_index); + continue; + } + + /* Step the simulation forward to the input event time. */ + + ip->info.vtime = rp->when; + if (ip->info.method == Normal && advance(ip, XSPICE_ARG)) { + ip->q_index = -1; + return; + } + + /* Pass input change to simulation. */ + + (*ip->info.in_fn)(&ip->info, rp->which, &rp->what); + while (i < ip->q_index && ip->q[i + 1].when == rp->when) { + /* Another change at the same time. */ + + ++i; + rp = ip->q + i; + (*ip->info.in_fn)(&ip->info, rp->which, &rp->what); + } + + /* Simulator requested to run after input change. */ + + if (ip->info.method == After_input && advance(ip, XSPICE_ARG)) { + ip->q_index = -1; + return; + } + } + + /* All input was processed. Advance to end of the timestep. */ + + ip->q_index = -1; + if (ip->info.method == Normal && TIME > ip->info.vtime) { + ip->info.vtime = TIME; + advance(ip, XSPICE_ARG); + } +} + +/* Check whether an input value has changed. + * To reduce the number of arguments, a struture pointer is passed. + */ + +static bool check_input(struct instance *ip, Digital_t *ovp, + struct pend_in *rp) +{ + if (ovp->state != rp->what.state || + ovp->strength != rp->what.strength) { + if (++ip->q_index < ip->q_length) { + /* Record this event. */ + + ip->q[ip->q_index] = *rp; + } else { + /* Queue is full. Handle that by forcing a shorter timestep. */ + + --ip->q_index; + while (ip->q_index >= 0 && ip->q[ip->q_index].when >= rp->when) + --ip->q_index; + if (ip->q_index >= 0) { + cm_analog_set_temp_bkpt( + (rp->when + ip->q[ip->q_index].when) / 2); + } else { + /* This should never happen. */ + + cm_message_printf("Error: Event queue overflow at %e.", + rp->when); + } + } + return true; + } + return false; +} + +/* The code model's main function. */ + +void ucm_d_cosim(ARGS) +{ + struct instance *ip; + Digital_t *in_vals; // XSPICE rotating memory + int i, index; + + if (INIT) { + int ins, outs, inouts; + unsigned int alloc_size; + void *handle; + void (*ifp)(struct co_info *); + char *fn; + + /* Initialise outputs. Done early in case of failure. */ + + outs = PORT_NULL(d_out) ? 0 : PORT_SIZE(d_out); + for (i = 0; i < outs; ++i) { + OUTPUT_STATE(d_out[i]) = ZERO; + OUTPUT_STRENGTH(d_out[i]) = STRONG; + OUTPUT_DELAY(d_out[i]) = PARAM(delay); + } + + inouts = PORT_NULL(d_inout) ? 0 : PORT_SIZE(d_inout); + for (i = 0; i < inouts; ++i) { + OUTPUT_STATE(d_inout[i]) = ZERO; + OUTPUT_STRENGTH(d_inout[i]) = STRONG; + OUTPUT_DELAY(d_inout[i]) = PARAM(delay); + } + + /* Load the shared library containing the co-simulator. */ + + fn = PARAM(simulation); + handle = dlopen(fn, RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + cm_message_send("Failed to load simulation binary. " + "Try setting LD_LIBRARY_PATH."); + cm_message_send(dlerror()); + return; + } + ifp = dlsym(handle, "Cosim_setup"); + if (*ifp == NULL) { + cm_message_printf("ERROR: no entry function in %s", fn); + cm_message_send(dlerror()); + dlclose(handle); + return; + } + + /* Get the instance data and initialise it. */ + + ip = (struct instance *)calloc(1, sizeof *ip); + if (!ip) + goto no_ip; + ip->so_handle = handle; + ip->info.vtime = 0.0; + ip->info.out_fn = accept_output; + CALLBACK = callback; + + /* Store the simulation interface information. */ + + (*ifp)(&ip->info); + + /* Check lengths. */ + + ins = PORT_NULL(d_in) ? 0 : PORT_SIZE(d_in); + if (ins != ip->info.in_count) { + cm_message_printf("Warning: mismatched XSPICE/co-simulator " + "input counts: %d/%d.", + ins, ip->info.in_count); + } + if (outs != ip->info.out_count) { + cm_message_printf("Warning: mismatched XSPICE/co-simulator " + "output counts: %d/%d.", + outs, ip->info.out_count); + } + + if (inouts != ip->info.inout_count) { + cm_message_printf("Warning: mismatched XSPICE/co-simulator " + "inout counts: %d/%d.", + inouts, ip->info.inout_count); + } + + /* Create input queue and output buffer. */ + + ip->q_index = -1; + ip->q_length = PARAM(queue_size); + ip->in_ports = ins; + ip->out_ports = outs; + ip->inout_ports = inouts; + if (ins + inouts > ip->q_length) { + cm_message_send("WARNING: Input queue size should be greater than " + "number of input ports. Size increased."); + ip->q_length = ins + inouts + 16; + } + alloc_size = ip->q_length * sizeof (struct pend_in); + ip->q = (struct pend_in *)malloc(alloc_size); + if (!ip->q) + goto no_q; + ip->op_pending = 0; + ip->out_vals = (Digital_t *)calloc(outs + inouts, sizeof (Digital_t)); + if (!ip->out_vals) + goto no_out_vals; + ip->extra = PARAM(delay) / 3; // FIXME? + STATIC_VAR(cosim_instance) = ip; + + /* Allocate XSPICE rotating storage to track changes. */ + + cm_event_alloc(0, (ins + inouts) * sizeof (Digital_t)); + cm_event_alloc(1, (outs + inouts) * sizeof (Digital_t)); + + /* Declare irreversible. */ + + if (PARAM(irreversible) > 0) + cm_irreversible(PARAM(irreversible)); + return; + + /* Handle malloc failures. */ + no_out_vals: + free(ip->q); + no_q: + free(ip); + no_ip: + cm_message_send("No memory!"); + return; + } + + ip = STATIC_VAR(cosim_instance); + if (!ip) { + int ports; + + /* Error state. Do nothing at all. */ + + ports = PORT_NULL(d_out) ? 0 : PORT_SIZE(d_out); + for (i = 0; i < ports; ++i) + OUTPUT_CHANGED(d_out[i]) = FALSE; + ports = PORT_NULL(d_inout) ? 0 : PORT_SIZE(d_inout); + for (i = 0; i < ports; ++i) + OUTPUT_CHANGED(d_inout[i]) = FALSE; + return; + } + in_vals = (Digital_t *)cm_event_get_ptr(0, 0); + + if (TIME == 0.0) { + /* Starting, so inputs may be settling. */ + + for (i = 0; i < ip->in_ports; ++i) { + Digital_t ival; + + ival = *(Digital_t *)INPUT(d_in[i]); + (*ip->info.in_fn)(&ip->info, i, &ival); + in_vals[i] = ival; + } + + for (i = 0; i < ip->out_ports; ++i) + OUTPUT_CHANGED(d_out[i]) = FALSE; + + for (i = 0; i < ip->inout_ports; ++i) { + Digital_t ival; + + ival = *(Digital_t *)INPUT(d_inout[i]); + (*ip->info.in_fn)(&ip->info, i + ip->in_ports, &ival); + in_vals[i + ip->in_ports] = ival; + OUTPUT_CHANGED(d_inout[i]) = FALSE; + } + return; + } + + if (CALL_TYPE == ANALOG) // Belt and braces + return; + + /* Check for pending output. */ + + if (ip->op_pending) { + output(ip, XSPICE_ARG); + } else { + for (i = 0; i < ip->out_ports; ++i) + OUTPUT_CHANGED(d_out[i]) = FALSE; + for (i = 0; i < ip->inout_ports; ++i) + OUTPUT_CHANGED(d_inout[i]) = FALSE; + } + + /* Check TIME as it may have gone backwards after a failed time-step. */ + + index = ip->q_index; + while (index >= 0 && TIME < ip->q[index].when) + --index; + ip->q_index = index; + + if (CALL_TYPE == EVENT) { + struct pend_in input; + unsigned int limit, max; + + /* New input is expected here. */ + + input.when = TIME; + + limit = ip->info.in_count + ip->info.inout_count; + max = limit < ip->in_ports ? limit : ip->in_ports; + limit -= max; + + for (input.which = 0; input.which < max; ++input.which) { + input.what = *(Digital_t *)INPUT(d_in[input.which]); + if (check_input(ip, in_vals + input.which, &input)) { + DBG("%.16g: IN %s %d/%d->%d/%d", + TIME, cm_get_node_name("d_in", input.which), + in_vals[input.which].state, in_vals[input.which].strength, + input.what.state, input.what.strength); + in_vals[input.which] = input.what; + } + } + + if (limit > ip->inout_ports) + limit = ip->inout_ports; + for (i = 0; i < limit; ++i, ++input.which) { + input.what = *(Digital_t *)INPUT(d_inout[i]); + if (check_input(ip, in_vals + input.which, &input)) { + DBG("%.16g: INout %s %d/%d->%d/%d", + TIME, cm_get_node_name("d_inout", i), + in_vals[input.which].state, in_vals[input.which].strength, + input.what.state, input.what.strength); + in_vals[ip->in_ports + i] = input.what; + } + } + } else if (CALL_TYPE == STEP_PENDING) { + /* The current timestep succeeded. Run the co-simulator code + * forward, replaying any saved input events. + */ + + if (TIME <= ip->info.vtime) + cm_message_printf("XSPICE time is behind vtime:\n" + "XSPICE %.16g\n" + "Cosim %.16g", + TIME, ip->info.vtime); + run(ip, XSPICE_ARG); + } +} diff --git a/src/xspice/icm/digital/d_cosim/ifspec.ifs b/src/xspice/icm/digital/d_cosim/ifspec.ifs new file mode 100644 index 000000000..8138b53f2 --- /dev/null +++ b/src/xspice/icm/digital/d_cosim/ifspec.ifs @@ -0,0 +1,102 @@ +/* Copyright 2023 Giles Atkinson +SUMMARY + + This file contains the interface specification file for the + d_cosim code model for general digital co-simulation. + +=============================================================================*/ + +NAME_TABLE: + +Spice_Model_Name: d_cosim +C_Function_Name: ucm_d_cosim +Description: "Bridge to an irreversible digital model" + +PORT_TABLE: + +Port_Name: d_in +Description: "digital input" +Direction: in +Default_Type: d +Allowed_Types: [d] +Vector: yes +Vector_Bounds: [0 -] +Null_Allowed: yes + +PORT_TABLE: + +Port_Name: d_out +Description: "digital output" +Direction: out +Default_Type: d +Allowed_Types: [d] +Vector: yes +Vector_Bounds: [0 -] +Null_Allowed: yes + +PORT_TABLE: + +Port_Name: d_inout +Description: "digital bidirectional port" +Direction: inout +Default_Type: d +Allowed_Types: [d] +Vector: yes +Vector_Bounds: [0 -] +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: delay +Description: "output delay time" +Data_Type: real +Default_Value: 1.0e-9 +Limits: [1e-12 -] +Vector: no +Vector_bounds: - +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: simulation +Description: "A shared library containing a digital model" +Data_Type: string +Default_Value: - +Limits: - +Vector: no +Vector_Bounds: - +Null_Allowed: no + +/* 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 + * the clock frequency and MTS is the maximum timestep for .tran. + */ + +PARAMETER_TABLE: + +Parameter_Name: queue_size +Description: "input queue size" +Data_Type: int +Default_Value: 128 +Limits: [1 -] +Vector: no +Vector_bounds: - +Null_Allowed: yes + +PARAMETER_TABLE: + +Parameter_Name: irreversible +Description: "Parameter passed to library function cm_irreversible()" +Data_Type: int +Default_Value: 1 +Limits: - +Vector: no +Vector_Bounds: - +Null_Allowed: yes + +STATIC_VAR_TABLE: + +Static_Var_Name: cosim_instance +Data_Type: pointer +Description: "Per-instance structure" diff --git a/src/xspice/icm/digital/modpath.lst b/src/xspice/icm/digital/modpath.lst index 323c94c19..0cbd2e973 100644 --- a/src/xspice/icm/digital/modpath.lst +++ b/src/xspice/icm/digital/modpath.lst @@ -29,3 +29,4 @@ d_tff d_tristate d_xnor d_xor +d_cosim diff --git a/visualc/xspice/digital.vcxproj b/visualc/xspice/digital.vcxproj index 47e7cb7d7..bd4d42487 100644 --- a/visualc/xspice/digital.vcxproj +++ b/visualc/xspice/digital.vcxproj @@ -322,6 +322,10 @@ ..\..\src\xspice\%(RelativeDir);%(AdditionalIncludeDirectories) + + + ..\..\src\xspice\%(RelativeDir);%(AdditionalIncludeDirectories) +