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:
parent
de415b2f03
commit
96cf1674a4
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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). */
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue