vpi: implement extended VCD ($dumpports) writer

Implement the IEEE 1364-2005 Clause 18 extended VCD task family
($dumpports / $dumpportsall / $dumpportsoff / $dumpportson /
$dumpportsflush / $dumpportslimit), which were previously registered as
"not implemented" stubs.

The new vpi/sys_evcd.c reaches each port's value-bearing net through the
port's vpiLowConn relationship and reads per-bit drive strength with
vpiStrengthVal, emitting $var port declarations and p-records (per-bit
state character plus strength0/strength1 components). Scalar and vector
ports are supported; the initial checkpoint is deferred to the read-only
synch so it records settled values. The encoding is byte-compatible with
the GHDL --evcd writer so one waveform reader works across Verilog and
VHDL.

To resolve a port to its net, the VPI is completed: vpiLowConn and
vpiHighConn are now implemented on vpiPortInfo. tgt-vvp emits the port's
low (formal) and high (actual) net symbols in the .port_info directive,
the vvp assembler parses them (compile_port_info), and vpiPortInfo
resolves each to the __vpiSignal sharing that net. New vpi_user.h
constants vpiHighConn (135) and vpiLowConn (136) use iverilog's private
numbering to avoid colliding with vpiArgument/vpiBit.

Tests: ivtest evcd_basic (scalar in/out/inout), evcd_bus (vector ports)
and evcd_onoff ($dumpportsoff/on/all), each gold-diffed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
William P. Moore 2026-06-19 15:40:36 -06:00
parent de415b2f03
commit 96cf1674a4
18 changed files with 821 additions and 41 deletions

View File

@ -126,3 +126,29 @@ Compile, run, and view waveforms with GTKWave using these commands:
Click on the 'test', then 'c1' in the top left box of GTKWave, then drag the
signals to the Signals box. You will be able to add signals to display,
scanning by scope.
Extended VCD (port dumps)
-------------------------
In addition to the four-state VCD produced by $dumpvars, Icarus Verilog can
write an *extended* VCD file (IEEE 1364-2005 Clause 18) that records the
ports of one or more module instances together with their direction and drive
strength. This is enabled with the $dumpports system task instead of
$dumpfile/$dumpvars:
.. code-block:: verilog
initial
begin
$dumpports(dut_instance, "ports.evcd");
end
Each port is written with a ``$var port`` declaration and its value changes are
recorded as ``p`` records carrying a per-bit state character (input ``D``/``U``,
output ``L``/``H``, three-state, etc.) and the strength0/strength1 components.
The companion tasks $dumpportsall, $dumpportsoff, $dumpportson,
$dumpportsflush, and $dumpportslimit mirror the corresponding $dumpall family.
The extended VCD format is byte-compatible with the GHDL ``--evcd`` writer, so
the same waveform reader can consume port dumps from both Verilog and VHDL
designs.

View File

@ -0,0 +1,35 @@
$date
Fri Jun 19 15:35:19 2026
$end
$version
Icarus Verilog
$end
$timescale
1s
$end
$scope module evcd_basic.u $end
$var port 1 <0 a $end
$var port 1 <1 b $end
$var port 1 <2 y $end
$var port 1 <3 io $end
$upscope $end
$enddefinitions $end
#0
$dumpports
pD 6 0 <0
pD 6 0 <1
pL 6 0 <2
pF 0 0 <3
$end
#5
pU 0 6 <0
p0 6 0 <3
#10
pU 0 6 <1
p1 0 6 <3
pH 0 6 <2
#15
pD 6 0 <0
pF 0 0 <3
pL 6 0 <2
#20

View File

@ -0,0 +1,29 @@
$date
Fri Jun 19 15:35:20 2026
$end
$version
Icarus Verilog
$end
$timescale
1s
$end
$scope module evcd_bus.u $end
$var port [3:0] <0 din $end
$var port [3:0] <1 dout $end
$var port [3:0] <2 bus $end
$upscope $end
$enddefinitions $end
#0
$dumpports
pDDDD 6666 0000 <0
pHHHH 0000 6666 <1
pFFFF 0000 0000 <2
$end
#5
pUDUD 0606 6060 <0
pLHLH 6060 0606 <1
#10
pUUUU 0000 6666 <0
pLLLL 6666 0000 <1
p1111 0000 6666 <2
#15

View File

@ -0,0 +1,47 @@
$date
Fri Jun 19 15:35:20 2026
$end
$version
Icarus Verilog
$end
$timescale
1s
$end
$scope module evcd_onoff.u $end
$var port 1 <0 clk $end
$var port 1 <1 d $end
$var port 1 <2 q $end
$upscope $end
$enddefinitions $end
#0
$dumpports
pD 6 0 <0
pD 6 0 <1
pX 6 6 <2
$end
#5
pU 0 6 <0
pL 6 0 <2
#7
pU 0 6 <1
#10
pD 6 0 <0
#13
pN 6 6 <0
pN 6 6 <1
pX 6 6 <2
#25
pU 0 6 <0
pD 6 0 <1
pH 0 6 <2
pL 6 0 <2
#30
pD 6 0 <0
#35
pD 6 0 <0
pD 6 0 <1
pL 6 0 <2
pU 0 6 <0
#40
pD 6 0 <0
#40

View File

@ -0,0 +1,20 @@
// Extended VCD ($dumpports): scalar input / output / inout ports.
module evcd_basic_dut (input wire a, input wire b,
output wire y, inout wire io);
assign y = a & b;
assign io = a ? b : 1'bz;
endmodule
module evcd_basic;
reg a, b;
wire y, io;
evcd_basic_dut u (.a(a), .b(b), .y(y), .io(io));
initial begin
$dumpports(u, "work/evcd_basic.evcd");
a = 1'b0; b = 1'b0; #5;
a = 1'b1; b = 1'b0; #5;
a = 1'b1; b = 1'b1; #5;
a = 1'b0; b = 1'b1; #5;
$finish;
end
endmodule

View File

@ -0,0 +1,19 @@
// Extended VCD ($dumpports): vector (bus) input / output / inout ports.
module evcd_bus_dut (input wire [3:0] din, output wire [3:0] dout,
inout wire [3:0] bus);
assign dout = ~din;
assign bus = din[0] ? din : 4'bzzzz;
endmodule
module evcd_bus;
reg [3:0] din;
wire [3:0] dout, bus;
evcd_bus_dut u (.din(din), .dout(dout), .bus(bus));
initial begin
$dumpports(u, "work/evcd_bus.evcd");
din = 4'b0000; #5;
din = 4'b1010; #5;
din = 4'b1111; #5;
$finish;
end
endmodule

View File

@ -0,0 +1,24 @@
// Extended VCD: $dumpportsoff suspends (X checkpoint), $dumpportson resumes,
// $dumpportsall forces a checkpoint.
module evcd_onoff_dut (input wire clk, input wire d, output wire q);
reg r;
assign q = r;
always @(posedge clk) r <= d;
endmodule
module evcd_onoff;
reg clk, d;
wire q;
evcd_onoff_dut u (.clk(clk), .d(d), .q(q));
always #5 clk = ~clk;
initial begin
$dumpports(u, "work/evcd_onoff.evcd");
clk = 0; d = 0;
#7 d = 1;
#6 $dumpportsoff;
#10 d = 0;
#2 $dumpportson;
#10 $dumpportsall;
#5 $finish;
end
endmodule

View File

@ -515,6 +515,9 @@ drive_strength3 normal ivltests
dummy7 normal ivltests gold=dummy7.gold
dump_memword normal ivltests diff=work/test.vcd:gold/dump_memword.vcd:2
dumpvars normal ivltests # PR#174: dumpvars non-hierarchical arguments
evcd_basic normal ivltests diff=work/evcd_basic.evcd:gold/evcd_basic.evcd.gold:2
evcd_bus normal ivltests diff=work/evcd_bus.evcd:gold/evcd_bus.evcd.gold:2
evcd_onoff normal ivltests diff=work/evcd_onoff.evcd:gold/evcd_onoff.evcd.gold:2
eeq normal ivltests # === and !== in structural context
else1 normal ivltests # ifdef with else
else2 normal ivltests # compound ifdef with else

View File

@ -2321,6 +2321,69 @@ static const char *vvp_port_info_type_str(ivl_signal_port_t ptype)
}
}
/* Emit the low (inner/formal) and, when present, high (outer/actual)
connection nets of a module port into a .port_info record, so that VPI
consumers can reach them via vpiLowConn / vpiHighConn. The two nets are
the signals that share the port's nexus in the module's own scope and
in the instantiating (parent) scope respectively. */
static void emit_mod_port_conns(ivl_scope_t mod, unsigned idx, const char*pname)
{
ivl_net_logic_t buffer = ivl_scope_mod_module_port_buffer(mod, idx);
ivl_scope_t parent = ivl_scope_parent(mod);
ivl_nexus_t port_nex = ivl_scope_mod_port(mod, idx);
ivl_signal_t inner = 0, outer = 0;
unsigned sigs, si;
int have_low = 0;
/* The formal (inner) signal is the port signal of this scope with
the port's name. Its nexus is shared with the actual net in the
instantiating scope (the same collapsing the netlist uses for
port connections), so the high connection is found there. */
sigs = ivl_scope_sigs(mod);
for (si = 0 ; si < sigs ; si += 1) {
ivl_signal_t s = ivl_scope_sig(mod, si);
if (ivl_signal_port(s) == IVL_SIP_NONE)
continue;
if (pname && strcmp(ivl_signal_basename(s), pname) == 0) {
inner = s;
break;
}
}
if (inner) {
ivl_nexus_t nex = ivl_signal_nex(inner, 0);
if (nex) {
unsigned k, np = ivl_nexus_ptrs(nex);
for (k = 0 ; k < np ; k += 1) {
ivl_signal_t s = ivl_nexus_ptr_sig(ivl_nexus_ptr(nex, k));
if (s && parent && ivl_signal_scope(s) == parent) {
outer = s;
break;
}
}
}
}
/* Low (inner) connection: prefer an explicit port buffer, then the
formal signal, then any signal on the module-port nexus. */
if (buffer) {
fprintf(vvp_out, " L_%p", buffer);
have_low = 1;
} else if (inner) {
fprintf(vvp_out, " v%p_0", (void*)inner);
have_low = 1;
} else if (port_nex) {
fprintf(vvp_out, " %s", draw_input_from_net(port_nex));
have_low = 1;
}
/* High (outer) connection, only meaningful alongside a low one. */
if (have_low && outer)
fprintf(vvp_out, " v%p_0", (void*)outer);
fprintf(vvp_out, ";\n");
}
int draw_scope(ivl_scope_t net, ivl_scope_t parent)
{
unsigned idx;
@ -2401,14 +2464,15 @@ int draw_scope(ivl_scope_t net, ivl_scope_t parent)
const char *name = ivl_scope_mod_module_port_name(net,idx);
ivl_signal_port_t ptype = ivl_scope_mod_module_port_type(net,idx);
unsigned width = ivl_scope_mod_module_port_width(net,idx);
ivl_net_logic_t buffer = ivl_scope_mod_module_port_buffer(net,idx);
if( name == 0 )
name = "";
fprintf( vvp_out, " .port_info %u %s %u \"%s\"",
idx, vvp_port_info_type_str(ptype), width,
vvp_mangle_name(name) );
if (buffer) fprintf( vvp_out, " L_%p;\n", buffer);
else fprintf( vvp_out, ";\n");
/* Emit the port's low (formal, inside) and high (actual,
outside) connection nets so VPI consumers can reach them
via vpiLowConn / vpiHighConn. */
emit_mod_port_conns(net, idx, name);
}
}

View File

@ -57,7 +57,7 @@ O = sys_table.o sys_convert.o sys_countdrivers.o sys_darray.o sys_deposit.o \
sys_display.o \
sys_fileio.o sys_finish.o sys_icarus.o sys_plusargs.o sys_queue.o \
sys_random.o sys_random_mti.o sys_readmem.o sys_readmem_lex.o sys_scanf.o \
sys_sdf.o sys_time.o sys_vcd.o sys_vcdoff.o vcd_priv.o mt19937int.o \
sys_sdf.o sys_time.o sys_vcd.o sys_evcd.o sys_vcdoff.o vcd_priv.o mt19937int.o \
sys_priv.o sdf_parse.o sdf_lexor.o stringheap.o vams_simparam.o \
table_mod.o table_mod_parse.o table_mod_lexor.o
OPP = vcd_priv2.o

492
vpi/sys_evcd.c Normal file
View File

@ -0,0 +1,492 @@
/*
* Copyright (c) 2026 Stephen Williams (steve@icarus.com)
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
# include "sys_priv.h"
/*
* This file implements the IEEE 1364-2005 Clause 18 Extended VCD
* ($dumpports) task family. The port direction comes from the vpiPort
* (vpiPortInfo) meta-data; the value-bearing net is reached through the
* port's vpiLowConn relationship, and its per-bit drive strength is read
* with vpiStrengthVal. The output is byte-compatible (for scalar ports)
* with the GHDL --evcd writer so a single waveform reader works across
* Verilog and VHDL.
*
* PHASE 1: scalar (1-bit) input/output/inout ports -- full pipeline
* (header, $scope/$var/$upscope, initial checkpoint, value changes).
* Vector ports are declared with their range but only bit 0 is dumped;
* the full bus encoding is pinned against the GHDL reference in Phase 2.
*/
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <assert.h>
# include <time.h>
# include "ivl_alloc.h"
static FILE *evcd_file = NULL;
static int evcd_no_date = 0;
static PLI_UINT64 evcd_cur_time = 0;
static int evcd_time_set = 0;
static int evcd_is_off = 0;
static int evcd_started = 0; /* initial checkpoint emitted yet? */
static const char*units_names[] = { "s", "ms", "us", "ns", "ps", "fs" };
/* One tracked port. */
struct evcd_port {
vpiHandle net; /* value net (vpiLowConn of the port) */
int dir; /* vpiInput / vpiOutput / vpiInout */
unsigned width;
int id; /* EVCD identifier integer */
struct evcd_port*next;
};
static struct evcd_port*evcd_list = NULL;
static struct evcd_port*evcd_tail = NULL; /* append here to keep id order */
static int evcd_next_id = 0;
/* ------------------------------------------------------------------ */
/* State-char and strength mapping (GHDL byte-compatible) */
/* ------------------------------------------------------------------ */
/* Map a 4-state logic value (vpi0/vpi1/vpiZ/vpiX, with vpiH/vpiL folded
onto 1/0) and a port direction to the EVCD state character. */
static char evcd_state_char(int logic, int dir)
{
int v; /* 0, 1, 2=Z, 3=X */
switch (logic) {
case vpi0: case vpiL: v = 0; break;
case vpi1: case vpiH: v = 1; break;
case vpiZ: v = 2; break;
default: v = 3; break;
}
if (dir == vpiInput) {
switch (v) { case 0: return 'D'; case 1: return 'U';
case 2: return 'Z'; default: return 'N'; }
} else if (dir == vpiOutput) {
switch (v) { case 0: return 'L'; case 1: return 'H';
case 2: return 'T'; default: return 'X'; }
} else { /* inout / unknown direction */
switch (v) { case 0: return '0'; case 1: return '1';
case 2: return 'F'; default: return '?'; }
}
}
/* The strength0 / strength1 components for a value, using the
GHDL-compatible convention: a driven level is strong (6), the opposite
rail is high-Z (0); three-state is 0/0; unknown is 6/6. */
static void evcd_strengths(int logic, int*s0, int*s1)
{
switch (logic) {
case vpi0: case vpiL: *s0 = 6; *s1 = 0; break;
case vpi1: case vpiH: *s0 = 0; *s1 = 6; break;
case vpiZ: *s0 = 0; *s1 = 0; break;
default: *s0 = 6; *s1 = 6; break;
}
}
/* Emit one value record for a port:
scalar -> p<state> <s0> <s1> <<id>
vector -> p<state...> <s0...> <s1...> <<id>
For an N-bit port the state and each strength component carry N
characters, most-significant bit first (matching the [msb:lsb] $var
declaration), and the encoding is byte-compatible with GHDL --evcd. */
static void evcd_emit_value(struct evcd_port*p)
{
s_vpi_value val;
int logic, s0, s1, idx;
val.format = vpiStrengthVal;
vpi_get_value(p->net, &val);
if (p->width <= 1) {
logic = (int)val.value.strength[0].logic;
evcd_strengths(logic, &s0, &s1);
fprintf(evcd_file, "p%c %d %d <%d\n",
evcd_state_char(logic, p->dir), s0, s1, p->id);
return;
}
/* Bit 0 of the strength array is the LSB; emit MSB first. */
fputc('p', evcd_file);
for (idx = (int)p->width - 1 ; idx >= 0 ; idx -= 1)
fputc(evcd_state_char((int)val.value.strength[idx].logic, p->dir),
evcd_file);
fputc(' ', evcd_file);
for (idx = (int)p->width - 1 ; idx >= 0 ; idx -= 1) {
evcd_strengths((int)val.value.strength[idx].logic, &s0, &s1);
fputc('0' + s0, evcd_file);
}
fputc(' ', evcd_file);
for (idx = (int)p->width - 1 ; idx >= 0 ; idx -= 1) {
evcd_strengths((int)val.value.strength[idx].logic, &s0, &s1);
fputc('0' + s1, evcd_file);
}
fprintf(evcd_file, " <%d\n", p->id);
}
/* ------------------------------------------------------------------ */
/* Time + value-change callback */
/* ------------------------------------------------------------------ */
static PLI_UINT64 evcd_now(void)
{
s_vpi_time now;
now.type = vpiSimTime;
vpi_get_time(0, &now);
return ((PLI_UINT64)now.high << 32) | (PLI_UINT64)now.low;
}
static void evcd_emit_time(PLI_UINT64 t)
{
if (!evcd_time_set || t != evcd_cur_time) {
fprintf(evcd_file, "#%" PLI_UINT64_FMT "\n", t);
evcd_cur_time = t;
evcd_time_set = 1;
}
}
static PLI_INT32 evcd_value_cb(struct t_cb_data*cb)
{
struct evcd_port*p = (struct evcd_port*)cb->user_data;
/* Value changes while the design settles at the start time are
folded into the initial checkpoint, so ignore them until it has
been emitted (at the read-only synch of the current step). */
if (evcd_file == NULL || evcd_is_off || !evcd_started)
return 0;
evcd_emit_time(evcd_now());
evcd_emit_value(p);
return 0;
}
/* ------------------------------------------------------------------ */
/* Header */
/* ------------------------------------------------------------------ */
static void evcd_emit_header(void)
{
int prec = vpi_get(vpiTimePrecision, 0);
unsigned scale = 1, udx = 0;
time_t walltime;
time(&walltime);
assert(prec >= -15);
while (prec < 0) { udx += 1; prec += 3; }
while (prec > 0) { scale *= 10; prec -= 1; }
if (!evcd_no_date) {
fprintf(evcd_file, "$date\n\t%s$end\n",
asctime(localtime(&walltime)));
}
fprintf(evcd_file, "$version\n\tIcarus Verilog\n$end\n");
fprintf(evcd_file, "$timescale\n\t%u%s\n$end\n", scale, units_names[udx]);
}
/* ------------------------------------------------------------------ */
/* Scope / port scan */
/* ------------------------------------------------------------------ */
static const char*evcd_dir_name(int dir)
{
switch (dir) {
case vpiInput: return "input";
case vpiOutput: return "output";
case vpiInout: return "inout";
default: return "?";
}
}
/* Declare every port of one module instance scope: emit its $var record,
track it, and register a value-change callback on its net. */
static void evcd_scan_scope(vpiHandle scope)
{
vpiHandle ports, port;
fprintf(evcd_file, "$scope module %s $end\n",
vpi_get_str(vpiFullName, scope));
ports = vpi_iterate(vpiPort, scope);
while (ports && (port = vpi_scan(ports))) {
struct evcd_port*p;
s_cb_data cb;
s_vpi_time tm;
const char*name = vpi_get_str(vpiName, port);
int dir = vpi_get(vpiDirection, port);
unsigned width = vpi_get(vpiSize, port);
vpiHandle net = vpi_handle(vpiLowConn, port);
if (net == NULL) {
vpi_printf("EVCD warning: port %s has no value net; "
"skipping.\n", name ? name : "?");
continue;
}
p = (struct evcd_port*)calloc(1, sizeof *p);
p->net = net;
p->dir = dir;
p->width = width;
p->id = evcd_next_id++;
p->next = NULL;
/* Append so checkpoints emit records in ascending id order
(matching the GHDL --evcd writer). */
if (evcd_tail) evcd_tail->next = p;
else evcd_list = p;
evcd_tail = p;
/* $var port <size> <<id> <reference> $end. Use the port's
actual declared [msb:lsb] range when it is a vector. */
if (width > 1 || vpi_get(vpiLeftRange, net) != 0)
fprintf(evcd_file, "$var port [%d:%d] <%d %s $end\n",
(int)vpi_get(vpiLeftRange, net),
(int)vpi_get(vpiRightRange, net), p->id, name);
else
fprintf(evcd_file, "$var port 1 <%d %s $end\n", p->id, name);
/* Track value changes on the port's value net. */
memset(&cb, 0, sizeof cb);
tm.type = vpiSimTime;
cb.reason = cbValueChange;
cb.cb_rtn = evcd_value_cb;
cb.obj = net;
cb.time = &tm;
cb.value = NULL;
cb.user_data = (PLI_BYTE8*)p;
vpi_register_cb(&cb);
(void)evcd_dir_name; /* reserved for diagnostics */
}
fprintf(evcd_file, "$upscope $end\n");
}
/* Dump the current value of every tracked port (checkpoint). */
static void evcd_checkpoint(void)
{
struct evcd_port*p;
for (p = evcd_list ; p ; p = p->next)
evcd_emit_value(p);
}
/* ------------------------------------------------------------------ */
/* End of simulation */
/* ------------------------------------------------------------------ */
static PLI_INT32 evcd_end_cb(struct t_cb_data*cb)
{
(void)cb;
if (evcd_file) {
fprintf(evcd_file, "#%" PLI_UINT64_FMT "\n", evcd_now());
fflush(evcd_file);
}
return 0;
}
/* The initial checkpoint, deferred to the read-only synch of the start
time so it records settled values: #<t> $dumpports <values> $end. */
static PLI_INT32 evcd_initial_cb(struct t_cb_data*cb)
{
(void)cb;
if (evcd_file == NULL)
return 0;
evcd_cur_time = evcd_now();
evcd_time_set = 1;
fprintf(evcd_file, "#%" PLI_UINT64_FMT "\n$dumpports\n", evcd_cur_time);
evcd_checkpoint();
fprintf(evcd_file, "$end\n");
evcd_started = 1;
return 0;
}
/* ------------------------------------------------------------------ */
/* $dumpports */
/* ------------------------------------------------------------------ */
static PLI_INT32 sys_dumpports_calltf(ICARUS_VPI_CONST PLI_BYTE8*name)
{
vpiHandle systf, argv, arg;
vpiHandle scopes[64];
unsigned nscopes = 0;
char*path = NULL;
s_cb_data cb;
unsigned i;
(void)name;
if (evcd_file != NULL) {
vpi_printf("EVCD warning: $dumpports already active; ignored.\n");
return 0;
}
systf = vpi_handle(vpiSysTfCall, 0);
argv = vpi_iterate(vpiArgument, systf);
/* Arguments are module instances to dump plus, optionally, a
trailing file name (string). Anything that is not a module is
treated as the file-name expression. */
while (argv && (arg = vpi_scan(argv))) {
if (vpi_get(vpiType, arg) == vpiModule) {
if (nscopes < 64)
scopes[nscopes++] = arg;
} else if (path == NULL) {
s_vpi_value v;
v.format = vpiStringVal;
vpi_get_value(arg, &v);
if (v.value.str && v.value.str[0])
path = strdup(v.value.str);
}
}
/* Default scope = the module containing the call; default file. */
if (nscopes == 0) {
vpiHandle sc = vpi_handle(vpiScope, systf);
if (sc) scopes[nscopes++] = sc;
}
if (path == NULL)
path = strdup("dumpports.vcd");
evcd_file = fopen(path, "w");
if (evcd_file == NULL) {
vpi_printf("EVCD error: cannot open %s for output.\n", path);
free(path);
return 0;
}
vpi_printf("EVCD info: dumpports file %s opened for output.\n", path);
free(path);
evcd_emit_header();
for (i = 0 ; i < nscopes ; i += 1)
evcd_scan_scope(scopes[i]);
fprintf(evcd_file, "$enddefinitions $end\n");
/* Defer the initial checkpoint to the read-only synch of the
current time so it captures settled values. */
{
s_vpi_time tm;
tm.type = vpiSimTime;
tm.high = 0;
tm.low = 0;
memset(&cb, 0, sizeof cb);
cb.reason = cbReadOnlySynch;
cb.cb_rtn = evcd_initial_cb;
cb.time = &tm;
cb.obj = NULL;
vpi_register_cb(&cb);
}
/* Close the file cleanly at the end of simulation. */
memset(&cb, 0, sizeof cb);
cb.reason = cbEndOfSimulation;
cb.cb_rtn = evcd_end_cb;
cb.obj = NULL;
vpi_register_cb(&cb);
return 0;
}
/* $dumpportsall: force a checkpoint of all port values now. */
static PLI_INT32 sys_dumpportsall_calltf(ICARUS_VPI_CONST PLI_BYTE8*name)
{
(void)name;
if (evcd_file == NULL) return 0;
evcd_emit_time(evcd_now());
evcd_checkpoint();
return 0;
}
/* $dumpportsoff / $dumpportson: suspend / resume dumping. */
static PLI_INT32 sys_dumpportsoff_calltf(ICARUS_VPI_CONST PLI_BYTE8*name)
{
struct evcd_port*p;
(void)name;
if (evcd_file == NULL || evcd_is_off) return 0;
evcd_emit_time(evcd_now());
/* Checkpoint every bit of every port as X while suspended. */
for (p = evcd_list ; p ; p = p->next) {
int s0, s1;
unsigned b;
char c = evcd_state_char(vpiX, p->dir);
evcd_strengths(vpiX, &s0, &s1);
fputc('p', evcd_file);
for (b = 0 ; b < p->width ; b += 1) fputc(c, evcd_file);
fputc(' ', evcd_file);
for (b = 0 ; b < p->width ; b += 1) fputc('0' + s0, evcd_file);
fputc(' ', evcd_file);
for (b = 0 ; b < p->width ; b += 1) fputc('0' + s1, evcd_file);
fprintf(evcd_file, " <%d\n", p->id);
}
evcd_is_off = 1;
return 0;
}
static PLI_INT32 sys_dumpportson_calltf(ICARUS_VPI_CONST PLI_BYTE8*name)
{
(void)name;
if (evcd_file == NULL || !evcd_is_off) return 0;
evcd_is_off = 0;
evcd_emit_time(evcd_now());
evcd_checkpoint();
return 0;
}
/* $dumpportsflush: flush the output buffer. */
static PLI_INT32 sys_dumpportsflush_calltf(ICARUS_VPI_CONST PLI_BYTE8*name)
{
(void)name;
if (evcd_file) fflush(evcd_file);
return 0;
}
/* $dumpportslimit(size [, file]): accepted for compatibility. The
file-size cap is not enforced in Phase 1. */
static PLI_INT32 sys_dumpportslimit_calltf(ICARUS_VPI_CONST PLI_BYTE8*name)
{
(void)name;
return 0;
}
/* ------------------------------------------------------------------ */
/* Registration */
/* ------------------------------------------------------------------ */
static void register_evcd_task(const char*tfname,
PLI_INT32 (*calltf)(ICARUS_VPI_CONST PLI_BYTE8*))
{
s_vpi_systf_data tf;
memset(&tf, 0, sizeof tf);
tf.type = vpiSysTask;
tf.tfname = (PLI_BYTE8*)tfname;
tf.calltf = calltf;
tf.compiletf = NULL;
tf.sizetf = NULL;
tf.user_data = (PLI_BYTE8*)tfname;
vpi_register_systf(&tf);
}
void sys_evcd_register(void)
{
register_evcd_task("$dumpports", sys_dumpports_calltf);
register_evcd_task("$dumpportsall", sys_dumpportsall_calltf);
register_evcd_task("$dumpportsoff", sys_dumpportsoff_calltf);
register_evcd_task("$dumpportson", sys_dumpportson_calltf);
register_evcd_task("$dumpportsflush", sys_dumpportsflush_calltf);
register_evcd_task("$dumpportslimit", sys_dumpportslimit_calltf);
}

View File

@ -172,35 +172,8 @@ void sys_special_register(void)
res = vpi_register_systf(&tf_data);
vpip_make_systf_system_defined(res);
tf_data.tfname = "$dumpports";
tf_data.user_data = "$dumpports";
res = vpi_register_systf(&tf_data);
vpip_make_systf_system_defined(res);
tf_data.tfname = "$dumpportsoff";
tf_data.user_data = "$dumpportsoff";
res = vpi_register_systf(&tf_data);
vpip_make_systf_system_defined(res);
tf_data.tfname = "$dumpportson";
tf_data.user_data = "$dumpportson";
res = vpi_register_systf(&tf_data);
vpip_make_systf_system_defined(res);
tf_data.tfname = "$dumpportsall";
tf_data.user_data = "$dumpportsall";
res = vpi_register_systf(&tf_data);
vpip_make_systf_system_defined(res);
tf_data.tfname = "$dumpportslimit";
tf_data.user_data = "$dumpportslimit";
res = vpi_register_systf(&tf_data);
vpip_make_systf_system_defined(res);
tf_data.tfname = "$dumpportsflush";
tf_data.user_data = "$dumpportsflush";
res = vpi_register_systf(&tf_data);
vpip_make_systf_system_defined(res);
/* The $dumpports* extended-VCD task family is implemented in
sys_evcd.c (registered via sys_evcd_register); no stubs here. */
/* The following optional system tasks/functions are not implemented
* in Icarus Verilog (from Annex C 1364-2005). */

View File

@ -40,6 +40,7 @@ extern void sys_scanf_register(void);
extern void sys_sdf_register(void);
extern void sys_time_register(void);
extern void sys_vcd_register(void);
extern void sys_evcd_register(void);
extern void sys_vcdoff_register(void);
extern void sys_special_register(void);
extern void table_model_register(void);
@ -214,6 +215,7 @@ void (*vlog_startup_routines[])(void) = {
sys_scanf_register,
sys_time_register,
sys_lxt_or_vcd_register,
sys_evcd_register,
sys_sdf_register,
sys_special_register,
table_model_register,

View File

@ -293,6 +293,13 @@ typedef struct t_vpi_delay {
#define vpiPathTerm 43
#define vpiPort 44
#define vpiPortBit 45
/* vpiHighConn/vpiLowConn are the IEEE 1364 port-connection relationships.
Icarus uses its own constant numbering, so these take the next free
values rather than the standard 89/90 (already used here for other
relationships). vpiLowConn is the net inside the module (the formal),
vpiHighConn the net outside (the actual). */
#define vpiHighConn 135
#define vpiLowConn 136
#define vpiRealVar 47
#define vpiReg 48
#define vpiRegBit 49

View File

@ -518,7 +518,7 @@ extern void compile_var_queue(char*label, char*name, unsigned size);
* nets connected through module ports.
*/
extern void compile_port_info( unsigned index, int vpi_port_type, unsigned width, const char *name, char* buffer );
extern void compile_port_info( unsigned index, int vpi_port_type, unsigned width, const char *name, char* buffer, char* buffer_high = nullptr );
/*

View File

@ -717,13 +717,18 @@ statement
/* Port information for scopes... currently this is just meta-data for VPI queries */
| K_PORT_INFO T_NUMBER port_type T_NUMBER T_STRING T_SYMBOL T_SYMBOL ';'
{ compile_port_info( $2 /* port_index */, $3, $4 /* width */,
$5 /*&name */, $6 /* low conn */,
$7 /* high conn */ ); }
| K_PORT_INFO T_NUMBER port_type T_NUMBER T_STRING T_SYMBOL ';'
{ compile_port_info( $2 /* port_index */, $3, $4 /* width */,
$5 /*&name */, $6 /* buffer */ ); }
$5 /*&name */, $6 /* low conn */ ); }
| K_PORT_INFO T_NUMBER port_type T_NUMBER T_STRING ';'
{ compile_port_info( $2 /* port_index */, $3, $4 /* width */,
$5 /*&name */, nullptr /* buffer */ ); }
$5 /*&name */, nullptr /* low conn */ ); }
| K_TIMESCALE T_NUMBER T_NUMBER';'
{ compile_timescale($2, $3); }

View File

@ -443,7 +443,8 @@ class vpiPortInfo : public __vpiHandle {
int vpi_direction,
unsigned width,
const char *name,
char* buffer );
char* buffer,
char* buffer_high = nullptr );
~vpiPortInfo() override;
int get_type_code(void) const override { return vpiPort; }
@ -466,7 +467,8 @@ class vpiPortInfo : public __vpiHandle {
int direction_;
unsigned width_;
const char *name_;
vvp_net_t *ref_;
vvp_net_t *ref_; // low (inner) connection: the formal net
vvp_net_t *ref_high_; // high (outer) connection: the actual net
};
class vpiPortBitInfo : public __vpiHandle {

View File

@ -666,7 +666,8 @@ vpiPortInfo::vpiPortInfo( __vpiScope *parent,
int vpi_direction,
unsigned width,
const char *name,
char* buffer) :
char* buffer,
char* buffer_high) :
parent_(parent),
index_(index),
direction_(vpi_direction),
@ -675,6 +676,8 @@ vpiPortInfo::vpiPortInfo( __vpiScope *parent,
{
if (buffer != nullptr) functor_ref_lookup(&ref_, buffer);
else ref_ = nullptr;
if (buffer_high != nullptr) functor_ref_lookup(&ref_high_, buffer_high);
else ref_high_ = nullptr;
}
vpiPortInfo::~vpiPortInfo()
@ -718,6 +721,24 @@ char *vpiPortInfo::vpi_get_str(int code)
}
/* Find the signal handle in `scope` whose underlying net is `net`. This
recovers the port-connection signal that a vpiPort's low/high
connection refers to, so VPI consumers (e.g. the extended-VCD writer)
can read its value and register value-change callbacks. The low
connection's signal lives in the module's own scope; the high
connection's signal lives in the instantiating (parent) scope. */
static vpiHandle port_conn(__vpiScope*scope, vvp_net_t*net)
{
if (scope == 0 || net == 0)
return 0;
for (size_t idx = 0 ; idx < scope->intern.size() ; idx += 1) {
__vpiSignal*sig = dynamic_cast<__vpiSignal*>(scope->intern[idx]);
if (sig && sig->node == net)
return sig;
}
return 0;
}
vpiHandle vpiPortInfo::vpi_handle(int code)
{
@ -727,6 +748,17 @@ vpiHandle vpiPortInfo::vpi_handle(int code)
case vpiScope:
case vpiModule:
return parent_;
case vpiLowConn:
// The low (inner) connection of the port is the formal
// net inside the module. compile_port_info() resolved that
// net into ref_; recover its signal handle by matching the
// net against the signals declared in this scope.
return port_conn(parent_, ref_);
case vpiHighConn:
// The high (outer) connection is the actual net in the
// instantiating scope (parent_->scope), resolved into
// ref_high_. Returns 0 for an unconnected or top-level port.
return port_conn(parent_ ? parent_->scope : 0, ref_high_);
default :
break;
}
@ -807,10 +839,10 @@ int vpiPortBitInfo::vpi_get(int code)
* code-generators etc. There are no actual nets corresponding to instances of module ports
* as elaboration directly connects nets connected through module ports.
*/
void compile_port_info( unsigned index, int vpi_direction, unsigned width, const char *name, char* buffer )
void compile_port_info( unsigned index, int vpi_direction, unsigned width, const char *name, char* buffer, char* buffer_high )
{
vpiPortInfo* obj = new vpiPortInfo( vpip_peek_current_scope(),
index, vpi_direction, width, name, buffer );
index, vpi_direction, width, name, buffer, buffer_high );
vpip_attach_to_current_scope(obj);
// Create vpiPortBit objects