Add co-simulation with VHDL, using the GHDL compiler and d_cosim.
This commit is contained in:
parent
da8e237e11
commit
63eb332436
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue