Add co-simulation with VHDL, using the GHDL compiler and d_cosim.

This commit is contained in:
Giles Atkinson 2025-01-26 16:09:12 +00:00
parent da8e237e11
commit 63eb332436
23 changed files with 1319 additions and 3 deletions

View File

@ -1443,6 +1443,7 @@ AC_CONFIG_FILES([Makefile
src/xspice/ipc/Makefile
src/xspice/idn/Makefile
src/xspice/verilog/Makefile
src/xspice/vhdl/Makefile
src/osdi/Makefile
tests/Makefile
tests/bsim1/Makefile

View File

@ -0,0 +1,10 @@
VHDL-controlled simple timer.
* This is the model for an RS flip-flop implemented by Icarus Verilog.
.model vlog_ff d_cosim simulation="./timer_core" sim_args=["555"]
* The bulk of the circuit is in a shared file.
.include ../verilator/555.shared
.end

View File

@ -0,0 +1,29 @@
-- Very simple logic for a 555 timer simulation
library ieee;
use ieee.std_logic_1164.all;
entity timer_core is
port (
Trigger, Threshold, Reset : in std_logic;
Q, Qbar : out std_logic
);
end timer_core;
architecture rtl of timer_core is
signal result : std_logic := '0';
signal ireset, go : std_logic;
begin
go <= Trigger and Reset;
ireset <= (Threshold and not go) or not Reset;
Q <= result;
Qbar <= not result;
process (go, ireset)
begin
if rising_edge(go) or rising_edge(ireset) then
result <= go;
end if;
end process;
end rtl;

View File

@ -0,0 +1,4 @@
ngspice -- ghnggen -top timer_core 555.vhd
ngspice ghnggen pwm.vhd
ngspice ghnggen adc.vhd
ngspice ghnggen mc.vhd

View File

@ -0,0 +1 @@
del /q *.obj *.so* *.vpi *.DLL *.cf *.raw

View File

@ -0,0 +1,36 @@
The circuits in this directory illustrate the use of the d_cosim
XSPICE code model as a container for a VHDL simulation using
the GHDL compiler. Use a version of GHDL built with the LLVM back-end.
The circuits and steps below are intended to be used from the directory
containing this file, certainly ouput files from GHDL should be in
the current directory when simulating.
The example circuits are:
555.cir: The probably familiar NE555 oscillator provides a minimal example
of combined simulation with SPICE and GHDL.
The digital part of the IC, a simple SR flip-flop, is expressed in VHDL.
pwm.c: VHDL "wait" statements control a pulse-width modulated output to
generate an approximate sine wave.
adc.cir: Slightly more complex VHDL describes the controlling part
of a switched-capacitor ADC.
mc.cir: A motor-control simulation with the control algorithm written
in non-sythesisable VHDL. It is used as a general-purpose programming
language rather than a HDL. Apart from showing that this is easy to do,
no part should be taken seriously: the algorithm is poor (but simple),
the motor parameters are invented and the voltages and currents representing
mechanical quantities do not match standard units.
Before a simulation can be run, the associated VHDL code must be compiled
and an additional shared library, ghdlng.vpi must be built. A library script
isavailable to simplify the steps, run like this:
ngspice ghnggen adc.vhd
A command interpreter script, ./build or BUILD.CMD, is provided to build
all four examples, with clean/CLEAN.CMD to remove the files created.

View File

@ -0,0 +1,10 @@
Simulation of a switched-capacitor SAR ADC with GHDL and d_cosim.
* Model line for the digital control implemented by GHDL.
.model dut d_cosim simulation="./adc" sim_args=["./adc"]
* The bulk of the circuit is in a shared file.
.include ../verilator/adc.shared
.end

View File

@ -0,0 +1,62 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity adc is
generic ( Bits : integer := 6 );
port (
Clk : in std_logic;
Comp : in std_logic;
Start : in std_logic;
Sample : out std_logic;
Done : out std_logic;
Result : out unsigned(0 to Bits - 1)
);
end entity;
architecture ghdl_adc of adc is
signal SR : unsigned(0 to Bits - 1);
signal Result_Reg : unsigned(0 to Bits - 1);
signal Sample_Reg : std_logic := '0';
signal Running : std_logic := '0';
begin
Result <= Result_Reg;
Sample <= Sample_Reg;
process (Clk)
constant Zeros : unsigned(0 to Bits - 1) := (others => '0');
variable NextSR : unsigned(0 to Bits - 1);
begin
if rising_edge(Clk) then
if Running = '1' then
if Sample_Reg = '1' then
Sample_Reg <= '0';
SR(Bits - 1) <= '1';
Result_Reg(Bits - 1) <= '1';
else
if SR /= 0 then
NextSR := shift_left(SR, 1);
if Comp = '1' then
Result_Reg <= (Result_Reg and not SR) or NextSR;
else
Result_Reg <= Result_Reg or NextSR;
end if;
SR <= NextSR;
else
Running <= '0';
Done <= '1';
end if;
end if;
else
if Start = '1' then
Running <= '1';
Sample_Reg <= '1';
Done <= '0';
SR <= Zeros;
Result_Reg <= Zeros;
end if;
end if;
end if;
end process;
end architecture;

4
examples/xspice/ghdl/build Executable file
View File

@ -0,0 +1,4 @@
ngspice -- ghnggen -top timer_core 555.vhd
ngspice ghnggen pwm.vhd
ngspice ghnggen adc.vhd
ngspice ghnggen mc.vhd

2
examples/xspice/ghdl/clean Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
rm -f *~ *.o *.so* *.vpi *.cf *.dylib *.raw

View File

@ -0,0 +1,82 @@
Simulation of elecric motor and controller in behavioural VHDL.
* Power from a wall socket.
Vmains live 0 SIN 0 250 50
* Behavioral power-line zero-crossing detector,
Bcross cross 0 V=(V(live) > -2 && V(live) < 2) ? 5 : 0
* Motor and its rotor
Xmotor live mt1 tacho 0 motor
* A triac to control the motor is replaced by a voltage-controlled switch
* to avoid including a manufacurer's triac library.
*Xtriac mt1 gate 0 BTA16-600B
Striac mt1 0 drive 0 triac off
.model triac sw vt=1.5 vh=1 roff=1meg
* The controller is implemented as VHDL code. The argument "-gTarget=5000"
* in the model line overrides the default target motor speed in the VHDL file.
* Tuning parameters Full_Band and Dead_band may also be set.
Ademo [ cross tacho ] [ drive ] controller
.model controller d_cosim simulation="./mc"
+ sim_args=["./mc.so" "-gTarget=4500"]
* Motor electrical circuit - inductance, resistance and back-EMF
* Loosley based on:
* https://www.precisionmicrodrives.com/content/ab-025-using-spice-to-model-dc-motors/
* No resemblance is claimed to any actual motor, living or dead.
.subckt motor live neutral tacho1 tacho2
+ Lm=0.3 Rm=30 BackC=20 // Electrical
+ TorqueC=30 Linertia=20 Rdrag=20 Load={50} // Mechanical
* Electrical inductance and resistance of the motor.
Lm live internal {Lm}
Rm internal back_emf {Rm}
* Back EMF: here I(Bmech) is proportional to speed, I(Bback) is motor current.
Bback back_emf neutral V=-I(Bmech)*I(Bback)*{BackC}
* Mechanical simulation: torque (V), speed (I), inertia (L) and
* friction/load (R).
* Series (Universal) motor so torque proportional to current squared.
Bmech torque 0 V=I(Bback)*I(Bback)*{TorqueC} // Motor torque
Lmech torque inertia {Linertia}
Rmech inertia load {Rdrag}
* In addition to friction (Rdrag) there is a varying load.
* Delay
* | Rise time
* | | Fall time
* | | | Pulse width
* | | | | Period
* | | | | |
* V V V V V
Vload load 0 PULSE 0 {Load} 1.2 10m 10m 200m 400m
* Tachometer: integrate speed for position and pulse.
Bpos i1 0 I=I(Bmech)
Cpos i1 0 0.01
.ic v(i1)=0
Btach tacho1 tacho2 V=((V(i1) - floor(V(i1)) > 0.5) ? 5 : 0)
.ends
.control
save drive @b.xmotor.bback[i] @b.xmotor.bmech[i] xmotor.load
tran 50u 3
plot @b.xmotor.bback[i] @b.xmotor.bmech[i]*-10 drive xmotor.load/10
.endc
.end

View File

@ -0,0 +1,80 @@
-- Test crude algorithm for a simple motor controller.
library ieee;
use ieee.std_logic_1164.all;
entity mc is
-- Control values that may be overrriden by the "sim_args" option
-- in a netlist's .model line. Intgers are used as GHDL does not
-- support overriding Real values.
generic (
Target : Integer := 4000; -- notional RPM
Dead_Band : Integer := 200;
Full_Band : Integer := 600
);
port (
Zero : in std_logic;
Tach : in std_logic;
Trigger : out std_logic
);
end mc;
architecture mc_arch of mc is
shared variable Speed : Real := 0.0;
begin
Tachometer : process (Tach) is
variable Time : Real := 0.0;
variable Last_pulse : Real := 0.0;
begin
if rising_edge(Tach) or falling_edge(Tach) then
Time := real(now / 1 ns) * 1.0e-9;
Speed := 30.0 / (Time - Last_pulse);
Last_pulse := Time;
end if;
end process Tachometer;
Controller : process (Zero) is
variable Skip : Integer := 0;
variable Count : Integer := 0;
variable Even : Boolean := True;
variable Was_even : Boolean := True;
variable Error : Integer;
begin
-- Trigger triac on zero crossings, preventing DC current.
if rising_edge(Zero) then
Even := not Even;
if (Count >= Skip) and (Even /= Was_even) then
Trigger <= '1';
Was_even := Even;
if Count > Skip then
Count := 0;
else
Count := -1;
end if;
else
Trigger <= '0';
end if;
Count := Count + 1;
-- A very crude feedback mechanism.
Error := integer(Speed) - Target;
if Error > Full_Band then
-- No drive
Skip := 1;
Count := 0;
elsif Error < -Full_Band then
-- Full Drive
Skip := 0;
elsif Error > Dead_Band then
Skip := Skip + 1;
elsif Error < -Dead_Band then
if Skip > 0 then
Skip := Skip - 1;
end if;
end if;
end if;
end process Controller;
end mc_arch;

View File

@ -0,0 +1,12 @@
* Waveform generation by PWM in VHDL.
adut null [ out ] pwm_sin
.model pwm_sin d_cosim simulation="./pwm" sim_args = [ "pwm" ]
r1 out smooth 100k
c1 smooth 0 1u
.control
tran 1m 2
plot out-3.3 smooth
.endc
.end

View File

@ -0,0 +1,37 @@
-- Very simple logic for PWM waveform generation.
library ieee;
use ieee.std_logic_1164.all;
use ieee.math_real.all;
entity pwm is
port ( output : out std_logic := '1');
end pwm;
architecture pwm_impl of pwm is
constant Cycles : Integer := 1000;
constant Samples : Integer := 1000;
constant uSec : Time := 1 us;
begin
process
variable j : Integer := 0;
variable width : Integer := Cycles / 2;
variable sine : Real;
begin
wait for width * uSec;
output <= '0';
wait for (Cycles - width) * uSec;
j := j + 1;
if j = Samples then
j := 0;
end if;
sine := sin(real(j) * MATH_2_PI / real(Samples));
width := integer(real(Samples) * (1.0 + sine) / 2.0);
if width = 0 then
output <= '0';
else
output <= '1';
end if;
end process;
end pwm_impl;

View File

@ -3,13 +3,15 @@
EXTRA_DIST = README examples icm xspice.c .gitignore \
verilog/vlnggen \
verilog/verilator_shim.cpp verilog/verilator_main.cpp \
verilog/libvvp.def verilog/MSVC.CMD verilog/README.txt
verilog/libvvp.def verilog/MSVC.CMD verilog/README.txt \
vhdl/ghnggen \
vhdl/ghdl_shim.h vhdl/ghdl_shim.c vhdl/ghdl_vpi.c
## This is removed because icm relies upon the existance of all other
## libs. It is currently compiled manually, last.
##SUBDIRS = mif cm enh evt ipc idn icm
SUBDIRS = mif cm enh evt ipc idn cmpp icm verilog
SUBDIRS = mif cm enh evt ipc idn cmpp icm verilog vhdl
dist-hook:
rm -f "$(distdir)/icm/makedefs"

View File

@ -13,7 +13,8 @@ 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
../../include/ngspice/cmtypes.h \
./coroutine.h ./coroutine_cosim.h ./coroutine_shim.h
# Icarus Verilog support: build two shared libraries.

View File

@ -0,0 +1,11 @@
## Process this file with automake to produce Makefile.in
MAINTAINERCLEANFILES = Makefile.in
# GHDL support: files installed to script directory and below.
initdatadir = $(pkgdatadir)/scripts
initdata_DATA = ghnggen
initdata1dir = $(pkgdatadir)/scripts/src
initdata1_DATA = ghdl_shim.h ghdl_shim.c ghdl_vpi.c

281
src/xspice/vhdl/ghdl_shim.c Normal file
View File

@ -0,0 +1,281 @@
/* Main file to be linked with code generated by GHDL to make a shared library
* to be loaded by ngspice's d_cosim code model to connect an instance of a
* VHDL simulation into a Ngspice netlist.
* 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 GHDL code runs on its own stack, handled by cr_xxx() functions. */
#include "ngspice/coroutine_shim.h"
#include "ngspice/cmtypes.h" // For Digital_t
#include "ngspice/cosim.h"
/* This header file defines the external (d_cosim) interface. It also contains
* an initial comment that describes how this shared library is used.
*/
#include "ghdl_shim.h"
extern int ghdl_main(int argc, char **argv); // Not in any header.
/* 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_ghdl *ctx = (struct ng_ghdl *)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 GHDL simulation forward. */
static void step(struct co_info *pinfo)
{
struct ng_ghdl *ctx = (struct ng_ghdl *)pinfo->handle;
int i;
/* Let GHDL 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_ghdl *ctx = (struct ng_ghdl *)pinfo->handle;
if (!ctx)
return;
/* Tell GHDL to exit. */
ctx->stop = 1;
cr_yield_to_sim(&ctx->cr_ctx);
cr_cleanup(&ctx->cr_ctx);
free(ctx->ports);
free(ctx);
pinfo->handle = NULL;
}
/* Thread start function runs the VHDL simulation. */
void *run_ghdl(void *arg)
{
struct co_info *pinfo = (struct co_info *)arg;
struct ng_ghdl *ctx, **ctx_ptr;
const char *file;
void *vpi_handle;
const char *new_argv[pinfo->sim_argc + 2];
int new_argc;
char vpi_buf[256];
cr_safety(); // Make safe with signals.
if (pinfo->sim_argc == 0) {
new_argc = 1;
new_argv[0] = "dummy_arg_0";
} else {
/* Copy the simulation arguments to the extended array. */
for (new_argc = 0; new_argc < pinfo->sim_argc; new_argc++)
new_argv[new_argc] = pinfo->sim_argv[new_argc];
}
/* Determing the file name for our VPI module. */
if (pinfo->lib_argc >= 2 && pinfo->lib_argv[1][0]) // Explicit VPI file.
file = pinfo->lib_argv[1];
else
file = "./ghdlng.vpi";
/* Normally, GHDL would load the VPI module. Here it is preloaded and
* an internal variable is set to point to the shared co_info struct.
*/
ctx_ptr = NULL;
vpi_handle = dlopen(file, RTLD_GLOBAL | RTLD_NOW);
if (vpi_handle)
ctx_ptr = dlsym(vpi_handle, CTX_VAR_NAME);
if (!ctx_ptr) {
fprintf(stderr,
"The GHDL shim can not initialise VPI module %s: %s.\n",
file, dlerror());
return NULL;
}
ctx = (struct ng_ghdl *)pinfo->handle;
*ctx_ptr = ctx;
/* The GHDL code is passed the VPI module name in a command argument. */
snprintf(vpi_buf, sizeof vpi_buf, "--vpi=%s", file);
new_argv[new_argc++] = vpi_buf;
new_argv[new_argc] = NULL;
ghdl_main(new_argc, (char **)new_argv);
/* The simulation has finished. Do nothing until destroyed. */
dlclose(vpi_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)
{
struct ng_ghdl *context;
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_ghdl));
if (!context)
fail("malloc", errno);
context->cosim_context = pinfo;
pinfo->handle = context;
/* Set-up the execution stack for the GHDL-generated code and start it. */
cr_init(&context->cr_ctx, run_ghdl, 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,68 @@
#ifndef _GHDL_SHIM_H_
#define _GHDL_SHIM_H_
/* This is the interface definition file for the shim code (ghdl_shim.c)
* and associated Verilog VPI module (ghdl_shim.vpi).
*
* This software allows execution of VHDL code generated by the GHDL compiler
* 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/entity_name.so"', where "entity_name"
* represents a top-level entity defined in VHDL. The shared library
* (or DLL) named is built by the "ghdl -e" command using output from
* a VHDL compilation ("ghdl -a") and the provided ghdl_shim.c file.
*
* The VPI module ghdl_vpi.c is called on loading: its first task is to obtain
* the list of ports for the top-level VHDL entity. After that the VPI code
* controls the execution of the VHDL code by blocking execution until
* commanded to proceed by the d_cosim instance, always regaining control
* via a VPI callback before the VHDL code moves ahead of the SPICE simulation.
*/
typedef uint32_t __vpiHandle; // Compatible with GHDL's vpi_user.h
/* 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.
__vpiHandle *handle; // Handle to the port's variable.
struct ng_ghdl *ctx; // Pointer back to parent.
};
#define IN_PENDING 1
#define OUT_PENDING 2
/* Data strucure used to share context between the ngspice and GHDL threads. */
struct ng_ghdl {
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; // GHDL's time unit.
__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.
};
/* The VPI module, ghdlng.vpi, contains a global variable with this name,
* used during initialisation.
*/
#define CTX_VAR GHDLNG_VPI_context
#define STR(s) #s
#define XSTR(s) STR(s) // Puts quotes on its argument.
#define CTX_VAR_NAME XSTR(CTX_VAR)
#endif // _GHDL_SHIM_H_

387
src/xspice/vhdl/ghdl_vpi.c Normal file
View File

@ -0,0 +1,387 @@
/*
* 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>
#if defined(__MINGW32__) || defined(_MSC_VER)
#include <windows.h> // Windows has a simple co-routine library - "Fibers"
#else
#include <pthread.h>
#endif
/* The VVP code runs on its own stack, handled by cr_xxx() functions. */
#include "ngspice/coroutine_cosim.h"
#include "ngspice/cmtypes.h" // For Digital_t
#include "ngspice/cosim.h"
/* This header file defines external interfaces. It also contains an initial
* comment that describes how this VPI module is used.
*/
#include "ghdl_shim.h"
/* Debugging printfs(). */
//#define DEBUG
#ifdef DEBUG
#define DBG(...) vpi_printf(__VA_ARGS__)
#else
#define DBG(...)
#endif
/* This global variable is used only during initialisation and safe
* so long as XSPICE instance initialisation is single-threaded. Ugly!
*/
__declspec(dllexport) struct ng_ghdl *CTX_VAR; // Name is a macro.
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_ghdl *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_ghdl *ctx)
{
static struct t_vpi_time now;
static struct t_cb_data cbd = { .cb_rtn = next_advance_cb, .time = &now };
now.type = vpiSimTime;
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, VHDL runs some more.
*/
static PLI_INT32 next_advance_cb(struct t_cb_data *cb)
{
struct ng_ghdl *ctx = (struct ng_ghdl *)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 GHDL 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 GHDL 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("VHDL 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 "
"GHDL 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 (VHDL) to %g (SPICE): %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_ghdl *ctx = pp->ctx;
DBG("Output: %s is %d now %g (VHDL) %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 VHDL cycle: cancel the current
* stop CB and request a new one at the next GHDL 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 = vpiSimTime };
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_ghdl *ctx = (struct ng_ghdl *)cb->user_data;
vpiHandle iter, top, item;
PLI_INT32 direction;
char *name;
int ii, oi, ioi;
DBG("Precision %d\n", vpi_get(vpiTimePrecision, NULL));
ctx->tick_length = pow(10.0, vpi_get(vpiTimePrecision, NULL));
/* Find the (unique?) top-level module. */
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))) {
struct ngvp_port *pp;
int first;
direction = vpi_get(vpiDirection, item);
name = vpi_get_str(vpiName, item);
/* It seems that in GHDL, a port and the underlying net are the
* same object.
*/
DBG("Port %s direction %d size %d, type %d\n",
name, direction, vpi_get(vpiSize, item),
vpi_get(vpiType, item));
switch (direction) {
case vpiInput:
first = !ii;
pp = ctx->ports + ii++;
init(item);
break;
case vpiOutput:
first = !oi;
pp = ctx->ports + ctx->ins + oi++;
watch(item, pp);
break;
case vpiInout:
first = !ioi;
init(item);
pp = ctx->ports + ctx->ins + ctx->outs + ioi++;
watch(item, 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 = item;
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 vhdlng.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 *)CTX_VAR;
vpi_register_cb(&cbd);
}
/* This is a table of registration functions. It is the external symbol
* that the GHDL simulator looks for when loading this .vpi module.
*/
void (*vlog_startup_routines[])(void) = {
start,
0
};

188
src/xspice/vhdl/ghnggen Normal file
View File

@ -0,0 +1,188 @@
*ng_script_with_params
// This Ngspice interpreter script accepts arbitrary arguments to
// the GHDL compiler (VHDL to LLVM) and builds a shared library
// or DLL that can be loaded by the d_cosim XSPICE code model.
// Instances of the model are then digital circuit elements whose
// behaviour is controlled by the Verilog source.
set bad=0
if $?argc = 0
set bad=1
end
if $argc <= 0
set bad=1
end
if $bad
echo Arguments acceptable to GHDL are required.
quit
end
// Disable special processing of '{'.
set noglob
if $oscompiled = 2 | $oscompiled = 3 | $oscompiled = 8 // Windows
set windows=1
set dirsep1="\\"
set dirsep2="/"
else
set windows=0
set dirsep1="/"
if $oscompiled = 7 // MacOS needs an option to allow undefined symbols.
setcs ld_magic="-Wl,-undefined,dynamic_lookup"
else
set ld_magic=""
end
end
// Is there a "-top" option first?
strcmp top "$argv[1]" "-top"
if $top = 0
if $argc < 3
echo There must be at least one source file!
quit
end
set base = "$argv[2]" // The top entity name for "ghdl -e".
shift
shift
else
// Loop through the arguments to find GHDL source: some_path/xxxx.vhd
// or similar. The output file will have the same base name.
let index=1
set off=1 // Avoid error in dowhile
repeat $argc
set base="$argv[$&index]"
let index = index + 1
strstr l "$base" "-" // Is it an option?
if $l = 0
continue
end
strstr l "$base" "" // Get string length
dowhile $off >= 0 // Strip leading directories
strstr off "$base" "$dirsep1"
if $windows
if $off < 0
strstr off "$base" "$dirsep2"
end
end
if $off >= 0
let off=$off+1
strslice base "$base" $&off $l
end
end
strstr off "$base" "." // Strip any file type suffix.
if $off >= 0
strslice base "$base" 0 $off
end
strstr l "$base" "" // Check for zero-length string
if $l > 0
break
end
end
if index - 1 > $argc
echo No file for top-level entity was found.
quit
end
end
// Default base name of output file.
if $windows
setcs tail=".DLL"
else
setcs tail=".so"
end
setcs soname="$base$tail"
// The shared library/DLL contains some ngspice source code as
// well as that created by GHDL. Find it by scanning $sourcepath.
set shimfile=ghdl_shim.c
set shimobj=ghdl_shim.o
set srcdir=src
set hfile="cmtypes.h"
set hpath="ngspice$dirsep1$hfile"
set silent_fileio // Silences fopen complaints
let i=1
repeat $#sourcepath
set stem="$sourcepath[$&i]"
let i = i + 1
set fn="$stem$dirsep1$shimfile"
fopen fh "$fn"
if $fh < 0
// Look in any "src" subdirectory (probably in installed tree).
set stem="$stem$dirsep1$srcdir"
set fn="$stem$dirsep1$shimfile"
fopen fh $fn
end
if $fh > 0
// Found ghdl_shim.c, but it needs header files on relative path.
fclose $fh
set hn="$stem$dirsep1$hpath"
fopen fh "$hn"
if $fh > 0
break
end
echo Ignoring source file "$fn" as "$hn" was not found.
end
end
if $fh > 0
fclose $fh
else
echo Can not find C source file $shimfile
quit
end
// Some header files are with the source.
strstr off "$stem" "."
if $off <> 0
setcs include="-I$stem"
else
setcs include="-I..$dirsep1$stem" // Relative path
end
// Check for ghdl_shim.o in the current directory. If not present, build it.
set silent_fileio
fopen fh ghdl_shim.o
if $fh < 0
shell clang -c -g -fPIC $include -o ghdl_shim.o $stem/ghdl_shim.c
else
fclose $fh
end
unset silent_fileio
// Compile the VHDL code.
shell ghdl -a $argv
strcmp bad "$shellstatus" "0"
if $bad = 0
shell ghdl -e -shared "-Wl,ghdl_shim.o" $base
else
quit
end
// Check for ghdlng.vpi in the current directory. If not present, build it.
set silent_fileio
fopen fh ghdlng.vpi
if $fh < 0
shell ghdl --vpi-compile clang -g -c -fdeclspec -Wno-ignored-attributes $include $stem/ghdl_vpi.c -o ghdl_vpi.o
shell ghdl --vpi-link clang $ld_magic ghdl_vpi.o -o ghdlng.vpi -lm -lpthread
else
fclose $fh
end
unset silent_fileio
quit

View File

@ -79,3 +79,7 @@ copy xspice\verilog\*.cpp %dst%\share\ngspice\scripts\src
copy include\ngspice\cosim.h %dst%\share\ngspice\scripts\src\ngspice
copy include\ngspice\miftypes.h %dst%\share\ngspice\scripts\src\ngspice
copy include\ngspice\cmtypes.h %dst%\share\ngspice\scripts\src\ngspice
copy xspice\verilog\coroutine*.h %dst%\share\ngspice\scripts\src\ngspice
copy xspice\vhdl\ghnggen %dst%\share\ngspice\scripts
copy xspice\vhdl\ghdl_shim.* %dst%\share\ngspice\scripts\src
copy xspice\vhdl\ghdl_vpi.c %dst%\share\ngspice\scripts\src

View File

@ -80,3 +80,7 @@ copy xspice\verilog\*.cpp %dst%\share\ngspice\scripts\src
copy include\ngspice\cosim.h %dst%\share\ngspice\scripts\src\ngspice
copy include\ngspice\miftypes.h %dst%\share\ngspice\scripts\src\ngspice
copy include\ngspice\cmtypes.h %dst%\share\ngspice\scripts\src\ngspice
copy xspice\verilog\coroutine*.h %dst%\share\ngspice\scripts\src\ngspice
copy xspice\vhdl\ghnggen %dst%\share\ngspice\scripts
copy xspice\vhdl\ghdl_shim.* %dst%\share\ngspice\scripts\src
copy xspice\vhdl\ghdl_vpi.c %dst%\share\ngspice\scripts\src