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