committed by
Holger Vogt
23 changed files with 1319 additions and 3 deletions
-
1configure.ac
-
10examples/xspice/ghdl/555.cir
-
29examples/xspice/ghdl/555.vhd
-
4examples/xspice/ghdl/BUILD.CMD
-
1examples/xspice/ghdl/CLEAN.CMD
-
36examples/xspice/ghdl/README.txt
-
10examples/xspice/ghdl/adc.cir
-
62examples/xspice/ghdl/adc.vhd
-
4examples/xspice/ghdl/build
-
2examples/xspice/ghdl/clean
-
82examples/xspice/ghdl/mc.cir
-
80examples/xspice/ghdl/mc.vhd
-
12examples/xspice/ghdl/pwm.cir
-
37examples/xspice/ghdl/pwm.vhd
-
6src/xspice/Makefile.am
-
3src/xspice/verilog/Makefile.am
-
11src/xspice/vhdl/Makefile.am
-
281src/xspice/vhdl/ghdl_shim.c
-
68src/xspice/vhdl/ghdl_shim.h
-
387src/xspice/vhdl/ghdl_vpi.c
-
188src/xspice/vhdl/ghnggen
-
4visualc/make-install-vngspice.bat
-
4visualc/make-install-vngspiced.bat
@ -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 |
||||
@ -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; |
||||
|
|
||||
@ -0,0 +1,4 @@ |
|||||
|
ngspice -- ghnggen -top timer_core 555.vhd |
||||
|
ngspice ghnggen pwm.vhd |
||||
|
ngspice ghnggen adc.vhd |
||||
|
ngspice ghnggen mc.vhd |
||||
@ -0,0 +1 @@ |
|||||
|
del /q *.obj *.so* *.vpi *.DLL *.cf *.raw |
||||
@ -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. |
||||
|
|
||||
@ -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 |
||||
@ -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; |
||||
|
|
||||
@ -0,0 +1,4 @@ |
|||||
|
ngspice -- ghnggen -top timer_core 555.vhd |
||||
|
ngspice ghnggen pwm.vhd |
||||
|
ngspice ghnggen adc.vhd |
||||
|
ngspice ghnggen mc.vhd |
||||
@ -0,0 +1,2 @@ |
|||||
|
#!/bin/sh |
||||
|
rm -f *~ *.o *.so* *.vpi *.cf *.dylib *.raw |
||||
@ -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 |
||||
@ -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; |
||||
@ -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 |
||||
@ -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; |
||||
|
|
||||
@ -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 |
||||
@ -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; |
||||
|
} |
||||
@ -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_ |
||||
@ -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 |
||||
|
}; |
||||
@ -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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue