Merge f402e2142d into 0723d9a477
This commit is contained in:
commit
8fd0e00031
|
|
@ -126,3 +126,39 @@ 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.
|
||||
|
||||
For ``inout`` ports, Icarus separates the module-side drive from the external
|
||||
(testbench-side) drive and emits the full IEEE 1364-2005 conflict-state
|
||||
characters: ``0``/``1`` (both sides agree), ``A``/``a``/``B``/``b``/``C``/``c``
|
||||
(the two sides drive conflicting values), ``d``/``u``/``l``/``h`` (same value
|
||||
but differing drive strength), ``F`` (neither side drives), and the
|
||||
directional ``D``/``U``/``H``/``L`` forms when only one side drives. This is
|
||||
more precise than a plain resolved value, which would collapse a genuine bus
|
||||
conflict to ``?``.
|
||||
|
||||
The extended VCD format is otherwise byte-compatible with the GHDL ``--evcd``
|
||||
writer, so the same waveform reader can consume port dumps from both Verilog
|
||||
and VHDL designs. (GHDL does not separate inout drive sides, so it emits the
|
||||
resolved-value states only; Icarus is a strict superset there.)
|
||||
|
|
|
|||
|
|
@ -174,6 +174,12 @@ void PGAssign::elaborate(Design*des, NetScope*scope) const
|
|||
ivl_assert(*this, lval && rval);
|
||||
ivl_assert(*this, rval->pin_count() == 1);
|
||||
|
||||
// Keep a faithful record of this continuous assignment (its
|
||||
// l-value net, the r-value expression's net, location and drive
|
||||
// strengths) so the target API can expose it as a vpiContAssign.
|
||||
scope->add_cont_assign(lval, rval, get_file(), get_lineno(),
|
||||
drive.drive0, drive.drive1);
|
||||
|
||||
// Detect the case that the rvalue-expression is a simple
|
||||
// expression. In this case, we will need to create a driver
|
||||
// (later) to carry strengths.
|
||||
|
|
|
|||
11
ivl_target.h
11
ivl_target.h
|
|
@ -187,6 +187,7 @@ typedef struct ivl_net_probe_s*ivl_net_probe_t;
|
|||
typedef struct ivl_nexus_s *ivl_nexus_t;
|
||||
typedef struct ivl_nexus_ptr_s*ivl_nexus_ptr_t;
|
||||
typedef struct ivl_parameter_s*ivl_parameter_t;
|
||||
typedef struct ivl_cont_assign_s*ivl_cont_assign_t;
|
||||
typedef struct ivl_process_s *ivl_process_t;
|
||||
typedef struct ivl_scope_s *ivl_scope_t;
|
||||
typedef struct ivl_signal_s *ivl_signal_t;
|
||||
|
|
@ -1888,6 +1889,16 @@ extern unsigned ivl_scope_logs(ivl_scope_t net);
|
|||
extern ivl_net_logic_t ivl_scope_log(ivl_scope_t net, unsigned idx);
|
||||
extern unsigned ivl_scope_lpms(ivl_scope_t net);
|
||||
extern ivl_lpm_t ivl_scope_lpm(ivl_scope_t, unsigned idx);
|
||||
/* CONTINUOUS ASSIGNMENTS
|
||||
* These methods enumerate the continuous assignments (`assign lhs = rhs;`)
|
||||
* of a scope and access their l-value, r-value and source location. */
|
||||
extern unsigned ivl_scope_cassigns(ivl_scope_t net);
|
||||
extern ivl_cont_assign_t ivl_scope_cassign(ivl_scope_t net, unsigned idx);
|
||||
extern ivl_signal_t ivl_cassign_lval(ivl_cont_assign_t net);
|
||||
extern ivl_signal_t ivl_cassign_rval(ivl_cont_assign_t net);
|
||||
extern const char* ivl_cassign_file(ivl_cont_assign_t net);
|
||||
extern unsigned ivl_cassign_lineno(ivl_cont_assign_t net);
|
||||
extern ivl_scope_t ivl_cassign_scope(ivl_cont_assign_t net);
|
||||
extern const char* ivl_scope_name(ivl_scope_t net);
|
||||
extern const char* ivl_scope_basename(ivl_scope_t net);
|
||||
extern unsigned ivl_scope_params(ivl_scope_t net);
|
||||
|
|
|
|||
|
|
@ -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,42 @@
|
|||
$date
|
||||
Fri Jun 19 17:42:51 2026
|
||||
$end
|
||||
$version
|
||||
Icarus Verilog
|
||||
$end
|
||||
$timescale
|
||||
1s
|
||||
$end
|
||||
$scope module test.u $end
|
||||
$var port 1 <0 io $end
|
||||
$var port 1 <1 oe $end
|
||||
$var port 1 <2 ov $end
|
||||
$upscope $end
|
||||
$enddefinitions $end
|
||||
#0
|
||||
$dumpports
|
||||
pF 0 0 <0
|
||||
pD 6 0 <1
|
||||
pD 6 0 <2
|
||||
$end
|
||||
#5
|
||||
pU 0 6 <1
|
||||
pU 0 6 <2
|
||||
pH 0 6 <0
|
||||
#10
|
||||
pD 6 0 <1
|
||||
pD 6 0 <2
|
||||
pF 0 0 <0
|
||||
pU 0 6 <0
|
||||
#15
|
||||
pU 0 6 <1
|
||||
pU 0 6 <2
|
||||
pA 6 6 <0
|
||||
#20
|
||||
pD 6 0 <2
|
||||
p0 6 0 <0
|
||||
pB 6 6 <0
|
||||
#25
|
||||
pU 0 6 <2
|
||||
p1 0 6 <0
|
||||
#30
|
||||
|
|
@ -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,32 @@
|
|||
/*
|
||||
* Extended-VCD ($dumpports) inout conflict-state test. The DUT and the
|
||||
* testbench each drive the shared inout `io` through a conditional (tri)
|
||||
* driver, so the resolver retains both sides and the writer emits the IEEE
|
||||
* 1364-2005 18.4.3 state characters: F (neither drives), H (module drives,
|
||||
* external hi-Z), U (external drives, module hi-Z), A (in 0 / out 1),
|
||||
* B (in 1 / out 0) and 1 (both drive 1).
|
||||
*/
|
||||
module dut(inout wire io, input wire oe, input wire ov);
|
||||
assign io = oe ? ov : 1'bz;
|
||||
endmodule
|
||||
|
||||
module test;
|
||||
reg oe, ov; // control the module-side (output) drive
|
||||
reg te, tv; // control the external (testbench) drive
|
||||
wire io;
|
||||
|
||||
assign io = te ? tv : 1'bz;
|
||||
|
||||
dut u(.io(io), .oe(oe), .ov(ov));
|
||||
|
||||
initial begin
|
||||
$dumpports(u, "work/evcd_inout.evcd");
|
||||
oe = 0; ov = 0; te = 0; tv = 0; #5; // F : neither side drives
|
||||
oe = 1; ov = 1; te = 0; tv = 0; #5; // H : module 1, external hi-Z
|
||||
oe = 0; ov = 0; te = 1; tv = 1; #5; // U : external 1, module hi-Z
|
||||
oe = 1; ov = 1; te = 1; tv = 0; #5; // A : external 0, module 1
|
||||
oe = 1; ov = 0; te = 1; tv = 1; #5; // B : external 1, module 0
|
||||
oe = 1; ov = 1; te = 1; tv = 1; #5; // 1 : both drive 1
|
||||
$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,10 @@ 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
|
||||
evcd_inout normal ivltests diff=work/evcd_inout.evcd:gold/evcd_inout.evcd.gold:2
|
||||
eeq normal ivltests # === and !== in structural context
|
||||
else1 normal ivltests # ifdef with else
|
||||
else2 normal ivltests # compound ifdef with else
|
||||
|
|
|
|||
14
net_scope.cc
14
net_scope.cc
|
|
@ -174,6 +174,20 @@ NetScope::~NetScope()
|
|||
/* name_ and module_name_ are perm-allocated. */
|
||||
}
|
||||
|
||||
void NetScope::add_cont_assign(NetNet*lval, NetNet*rval, perm_string file,
|
||||
unsigned lineno, ivl_drive_t drive0,
|
||||
ivl_drive_t drive1)
|
||||
{
|
||||
cont_assign_t rec;
|
||||
rec.lval = lval;
|
||||
rec.rval = rval;
|
||||
rec.file = file;
|
||||
rec.lineno = lineno;
|
||||
rec.drive0 = drive0;
|
||||
rec.drive1 = drive1;
|
||||
cont_assigns_.push_back(rec);
|
||||
}
|
||||
|
||||
void NetScope::set_line(const LineInfo*info)
|
||||
{
|
||||
file_ = info->get_file();
|
||||
|
|
|
|||
18
netlist.h
18
netlist.h
|
|
@ -1009,6 +1009,23 @@ class NetScope : public Definitions, public Attrib {
|
|||
/* Search the scope hierarchy for the scope where 'type' was defined. */
|
||||
NetScope*find_typedef_scope(const Design*des, const typedef_t*type_i);
|
||||
|
||||
/* Continuous assignments (the `assign lhs = rhs;` form) are lowered
|
||||
to gates/LPM during elaboration. We keep a faithful record of each
|
||||
one here so the target API can present them as vpiContAssign
|
||||
objects with their l-value, r-value and source location. */
|
||||
struct cont_assign_t {
|
||||
NetNet*lval;
|
||||
NetNet*rval;
|
||||
perm_string file;
|
||||
unsigned lineno;
|
||||
ivl_drive_t drive0;
|
||||
ivl_drive_t drive1;
|
||||
};
|
||||
void add_cont_assign(NetNet*lval, NetNet*rval, perm_string file,
|
||||
unsigned lineno, ivl_drive_t drive0, ivl_drive_t drive1);
|
||||
const std::vector<cont_assign_t>& cont_assigns() const { return cont_assigns_; }
|
||||
const std::map<hname_t,NetScope*>& children() const { return children_; }
|
||||
|
||||
/* Parameters exist within a scope, and these methods allow
|
||||
one to manipulate the set. In these cases, the name is the
|
||||
*simple* name of the parameter, the hierarchy is implicit in
|
||||
|
|
@ -1411,6 +1428,7 @@ class NetScope : public Definitions, public Attrib {
|
|||
NetScope*unit_;
|
||||
NetScope*up_;
|
||||
std::map<hname_t,NetScope*> children_;
|
||||
std::vector<cont_assign_t> cont_assigns_;
|
||||
std::map<perm_string,interface_port_alias_t> interface_port_aliases_;
|
||||
std::map<perm_string,std::map<long,interface_port_alias_t> > interface_port_alias_arrays_;
|
||||
|
||||
|
|
|
|||
43
t-dll-api.cc
43
t-dll-api.cc
|
|
@ -2222,6 +2222,49 @@ extern "C" ivl_lpm_t ivl_scope_lpm(ivl_scope_t net, unsigned idx)
|
|||
return net->lpm_[idx];
|
||||
}
|
||||
|
||||
extern "C" unsigned ivl_scope_cassigns(ivl_scope_t net)
|
||||
{
|
||||
assert(net);
|
||||
return net->cassigns_.size();
|
||||
}
|
||||
|
||||
extern "C" ivl_cont_assign_t ivl_scope_cassign(ivl_scope_t net, unsigned idx)
|
||||
{
|
||||
assert(net);
|
||||
assert(idx < net->cassigns_.size());
|
||||
return net->cassigns_[idx];
|
||||
}
|
||||
|
||||
extern "C" ivl_signal_t ivl_cassign_lval(ivl_cont_assign_t net)
|
||||
{
|
||||
assert(net);
|
||||
return net->lhs_;
|
||||
}
|
||||
|
||||
extern "C" ivl_signal_t ivl_cassign_rval(ivl_cont_assign_t net)
|
||||
{
|
||||
assert(net);
|
||||
return net->rhs_;
|
||||
}
|
||||
|
||||
extern "C" const char* ivl_cassign_file(ivl_cont_assign_t net)
|
||||
{
|
||||
assert(net);
|
||||
return net->file;
|
||||
}
|
||||
|
||||
extern "C" unsigned ivl_cassign_lineno(ivl_cont_assign_t net)
|
||||
{
|
||||
assert(net);
|
||||
return net->lineno;
|
||||
}
|
||||
|
||||
extern "C" ivl_scope_t ivl_cassign_scope(ivl_cont_assign_t net)
|
||||
{
|
||||
assert(net);
|
||||
return net->scope_;
|
||||
}
|
||||
|
||||
static unsigned scope_name_len(ivl_scope_t net)
|
||||
{
|
||||
unsigned len = 0;
|
||||
|
|
|
|||
58
t-dll.cc
58
t-dll.cc
|
|
@ -233,6 +233,54 @@ ivl_signal_t dll_target::find_signal(ivl_design_s &des, const NetNet*net)
|
|||
return 0;
|
||||
}
|
||||
|
||||
ivl_signal_t dll_target::find_signal_opt(ivl_design_s &des, const NetNet*net)
|
||||
{
|
||||
if (net == 0 || net->scope() == 0)
|
||||
return 0;
|
||||
|
||||
perm_string nname = net->name();
|
||||
if (nname.str() == 0) // unnamed net (e.g. some array elements)
|
||||
return 0;
|
||||
|
||||
ivl_scope_t scop = find_scope(des, net->scope());
|
||||
if (scop == 0)
|
||||
return 0;
|
||||
|
||||
for (unsigned idx = 0 ; idx < scop->sigs_.size() ; idx += 1) {
|
||||
const char*sn = scop->sigs_[idx]->name_.str();
|
||||
if (sn && strcmp(sn, nname.str()) == 0)
|
||||
return scop->sigs_[idx];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dll_target::add_cont_assigns(const NetScope*ns)
|
||||
{
|
||||
ivl_scope_t scope = find_scope(des_, ns);
|
||||
const std::vector<NetScope::cont_assign_t>&list = ns->cont_assigns();
|
||||
for (unsigned idx = 0 ; idx < list.size() ; idx += 1) {
|
||||
ivl_signal_t lhs = find_signal_opt(des_, list[idx].lval);
|
||||
ivl_signal_t rhs = find_signal_opt(des_, list[idx].rval);
|
||||
if (scope == 0 || lhs == 0)
|
||||
continue; // need at least the l-value net
|
||||
// rhs may be 0 for an expression r-value (no single net).
|
||||
|
||||
ivl_cont_assign_t obj = new struct ivl_cont_assign_s;
|
||||
obj->scope_ = scope;
|
||||
obj->lhs_ = lhs;
|
||||
obj->rhs_ = rhs;
|
||||
obj->file = list[idx].file;
|
||||
obj->lineno = list[idx].lineno;
|
||||
obj->drive0 = list[idx].drive0;
|
||||
obj->drive1 = list[idx].drive1;
|
||||
scope->cassigns_.push_back(obj);
|
||||
}
|
||||
|
||||
for (std::map<hname_t,NetScope*>::const_iterator it = ns->children().begin()
|
||||
; it != ns->children().end() ; ++ it)
|
||||
add_cont_assigns(it->second);
|
||||
}
|
||||
|
||||
static ivl_nexus_t nexus_sig_make(ivl_signal_t net, unsigned pin)
|
||||
{
|
||||
ivl_nexus_t tmp = new struct ivl_nexus_s;
|
||||
|
|
@ -658,10 +706,18 @@ bool dll_target::start_design(const Design*des)
|
|||
* Here ivl is telling us that the design is scanned completely, and
|
||||
* here is where we call the API to process the constructed design.
|
||||
*/
|
||||
int dll_target::end_design(const Design*)
|
||||
int dll_target::end_design(const Design*des)
|
||||
{
|
||||
int rc;
|
||||
if (errors == 0) {
|
||||
/* All scopes and signals exist now, so attach the preserved
|
||||
continuous assignments to their ivl_scope objects before
|
||||
handing the design to the code-generator target. */
|
||||
std::list<NetScope*>roots = des->find_root_scopes();
|
||||
for (std::list<NetScope*>::const_iterator it = roots.begin()
|
||||
; it != roots.end() ; ++ it)
|
||||
add_cont_assigns(*it);
|
||||
|
||||
if (verbose_flag) {
|
||||
cout << " ... invoking target_design" << endl;
|
||||
}
|
||||
|
|
|
|||
22
t-dll.h
22
t-dll.h
|
|
@ -162,6 +162,11 @@ struct dll_target : public target_t, public expr_scan_t {
|
|||
|
||||
static ivl_scope_t find_scope(ivl_design_s &des, const NetScope*cur);
|
||||
static ivl_signal_t find_signal(ivl_design_s &des, const NetNet*net);
|
||||
/* Like find_signal but returns 0 instead of asserting when the net
|
||||
has no matching ivl_signal (used when preserving cont assigns). */
|
||||
static ivl_signal_t find_signal_opt(ivl_design_s &des, const NetNet*net);
|
||||
/* Walk the NetScope tree building ivl_cont_assign_s records. */
|
||||
void add_cont_assigns(const NetScope*ns);
|
||||
static ivl_parameter_t scope_find_param(ivl_scope_t scope_i,
|
||||
const char*name);
|
||||
|
||||
|
|
@ -653,6 +658,22 @@ struct ivl_process_s {
|
|||
ivl_process_t next_;
|
||||
};
|
||||
|
||||
/*
|
||||
* A continuous assignment (the `assign lhs = rhs;` form), preserved through
|
||||
* elaboration so it can be presented as a vpiContAssign. lhs_ and rhs_ are
|
||||
* the l-value and r-value nets; the location and drive strengths come from
|
||||
* the source statement.
|
||||
*/
|
||||
struct ivl_cont_assign_s {
|
||||
ivl_scope_t scope_;
|
||||
ivl_signal_t lhs_;
|
||||
ivl_signal_t rhs_;
|
||||
perm_string file;
|
||||
unsigned lineno;
|
||||
ivl_drive_t drive0;
|
||||
ivl_drive_t drive1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Scopes are kept in a tree. Each scope points to its first child,
|
||||
* and also to any siblings. Thus a parent can scan all its children
|
||||
|
|
@ -713,6 +734,7 @@ struct ivl_scope_s {
|
|||
} u_;
|
||||
|
||||
std::vector<ivl_switch_t>switches;
|
||||
std::vector<ivl_cont_assign_t>cassigns_;
|
||||
|
||||
signed int time_precision :8;
|
||||
signed int time_units :8;
|
||||
|
|
|
|||
|
|
@ -546,6 +546,23 @@ static void display_multi_driver_error(ivl_nexus_t nex, unsigned ndrivers,
|
|||
static ivl_nexus_ptr_t *drivers = 0x0;
|
||||
static unsigned adrivers = 0;
|
||||
|
||||
/* The scope a nexus driver lives in. Used to tag each resolver input so the
|
||||
runtime can tell an inout's module-side drivers from its external ones,
|
||||
which is what the extended-VCD ($dumpports) writer needs to emit the IEEE
|
||||
1364 conflict-state characters. Every driver kind carries a scope. */
|
||||
static ivl_scope_t driver_scope_of(ivl_nexus_ptr_t nptr)
|
||||
{
|
||||
ivl_net_logic_t log = ivl_nexus_ptr_log(nptr);
|
||||
if (log) return ivl_logic_scope(log);
|
||||
ivl_lpm_t lpm = ivl_nexus_ptr_lpm(nptr);
|
||||
if (lpm) return ivl_lpm_scope(lpm);
|
||||
ivl_net_const_t con = ivl_nexus_ptr_con(nptr);
|
||||
if (con) return ivl_const_scope(con);
|
||||
ivl_signal_t sig = ivl_nexus_ptr_sig(nptr);
|
||||
if (sig) return ivl_signal_scope(sig);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void EOC_cleanup_drivers(void)
|
||||
{
|
||||
free(drivers);
|
||||
|
|
@ -758,6 +775,14 @@ static void draw_net_input_x(ivl_nexus_t nex,
|
|||
display_multi_driver_error(nex, ndrivers, MDRV_REAL);
|
||||
}
|
||||
|
||||
/* Capture each driver's scope now, before drawing the driver
|
||||
inputs below: draw_net_input_drive() can recurse into
|
||||
draw_net_input_x() and refill the shared static drivers[] array,
|
||||
so it is not safe to read drivers[] after that loop. */
|
||||
ivl_scope_t*driver_scopes = malloc(ndrivers * sizeof(ivl_scope_t));
|
||||
for (idx = 0; idx < ndrivers; idx += 1)
|
||||
driver_scopes[idx] = driver_scope_of(drivers[idx]);
|
||||
|
||||
driver_labels = malloc(ndrivers * sizeof(char*));
|
||||
ivl_signal_t path_sig = find_modpath(nex);
|
||||
if (path_sig) {
|
||||
|
|
@ -782,6 +807,18 @@ static void draw_net_input_x(ivl_nexus_t nex,
|
|||
fprintf(vvp_out, ";\n");
|
||||
free(driver_labels);
|
||||
|
||||
/* Tag each resolver input with the scope of its driver, so the
|
||||
runtime can expose vpiDriver and separate an inout's two drive
|
||||
sides. Emitted as a companion directive keyed by the resolver. */
|
||||
fprintf(vvp_out, " .resolv_drv RS_%p", nex);
|
||||
for (idx = 0; idx < ndrivers; idx += 1) {
|
||||
ivl_scope_t dsc = driver_scopes[idx];
|
||||
if (dsc) fprintf(vvp_out, ", S_%p", dsc);
|
||||
else fprintf(vvp_out, ", S_%p", nex); /* never expected */
|
||||
}
|
||||
fprintf(vvp_out, ";\n");
|
||||
free(driver_scopes);
|
||||
|
||||
snprintf(result, sizeof result, "RS_%p", nex);
|
||||
|
||||
if (island)
|
||||
|
|
|
|||
|
|
@ -2375,6 +2375,22 @@ int draw_process(ivl_process_t net, void*x)
|
|||
local_count = 0;
|
||||
fprintf(vvp_out, " .scope S_%p;\n", scope);
|
||||
|
||||
/* Emit a structural record so the runtime can expose this process
|
||||
as a vpiProcess (always/initial/final) object on its scope, with
|
||||
the source file/line of the process statement. The type code is
|
||||
0=initial, 1=always(any flavour), 2=final. */
|
||||
{
|
||||
unsigned ptype;
|
||||
switch (ivl_process_type(net)) {
|
||||
case IVL_PR_INITIAL: ptype = 0; break;
|
||||
case IVL_PR_FINAL: ptype = 2; break;
|
||||
default: ptype = 1; break; /* always / always_* */
|
||||
}
|
||||
fprintf(vvp_out, " .process %u %u %u;\n", ptype,
|
||||
ivl_file_table_index(ivl_stmt_file(stmt)),
|
||||
ivl_stmt_lineno(stmt));
|
||||
}
|
||||
|
||||
/* Generate the entry label. Just give the thread a number so
|
||||
that we are certain the label is unique. */
|
||||
fprintf(vvp_out, "T_%u ;\n", thread_count);
|
||||
|
|
|
|||
|
|
@ -933,6 +933,57 @@ static void draw_equiv_impl_in_scope(ivl_net_logic_t lptr)
|
|||
fprintf(vvp_out, "L_%p .functor %s 1, %s, %s, C4<0>, C4<0>;\n", lptr, ltype, lval, rval);
|
||||
}
|
||||
|
||||
/* Map an Icarus gate type to the IEEE 1364 vpiPrimType subtype code, so the
|
||||
runtime can answer vpi_get(vpiPrimType, prim_handle). Returns 0 for gate
|
||||
types that have no 1364 primitive subtype. The numeric codes match the
|
||||
vpi*Prim values in vpi_user.h. */
|
||||
static int vpi_prim_type_code(ivl_logic_t t)
|
||||
{
|
||||
switch (t) {
|
||||
case IVL_LO_AND: return 1; /* vpiAndPrim */
|
||||
case IVL_LO_NAND: return 2; /* vpiNandPrim */
|
||||
case IVL_LO_NOR: return 3; /* vpiNorPrim */
|
||||
case IVL_LO_OR: return 4; /* vpiOrPrim */
|
||||
case IVL_LO_XOR: return 5; /* vpiXorPrim */
|
||||
case IVL_LO_XNOR: return 6; /* vpiXnorPrim */
|
||||
case IVL_LO_BUF:
|
||||
case IVL_LO_BUFZ:
|
||||
case IVL_LO_BUFT: return 7; /* vpiBufPrim */
|
||||
case IVL_LO_NOT: return 8; /* vpiNotPrim */
|
||||
case IVL_LO_BUFIF0: return 9; /* vpiBufif0Prim */
|
||||
case IVL_LO_BUFIF1: return 10; /* vpiBufif1Prim */
|
||||
case IVL_LO_NOTIF0: return 11; /* vpiNotif0Prim */
|
||||
case IVL_LO_NOTIF1: return 12; /* vpiNotif1Prim */
|
||||
case IVL_LO_NMOS: return 13; /* vpiNmosPrim */
|
||||
case IVL_LO_PMOS: return 14; /* vpiPmosPrim */
|
||||
case IVL_LO_CMOS: return 15; /* vpiCmosPrim */
|
||||
case IVL_LO_RNMOS: return 16; /* vpiRnmosPrim */
|
||||
case IVL_LO_RPMOS: return 17; /* vpiRpmosPrim */
|
||||
case IVL_LO_RCMOS: return 18; /* vpiRcmosPrim */
|
||||
case IVL_LO_PULLUP: return 25; /* vpiPullupPrim */
|
||||
case IVL_LO_PULLDOWN: return 26; /* vpiPulldownPrim */
|
||||
case IVL_LO_UDP: return 28; /* vpiCombPrim (Icarus UDPs) */
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Emit a structural .primitive record for one gate so the runtime exposes it
|
||||
as a vpiPrimitive on its scope (one-to-many from the module per 1364
|
||||
26.6.1/26.6.13). This is independent of the simulation functor that
|
||||
draw_logic_in_scope() draws; it carries the gate subtype, the number of
|
||||
terminals (pin 0 is the output, the rest are inputs), the source location
|
||||
and the gate's base name. */
|
||||
static void draw_primitive_in_scope(ivl_net_logic_t lptr)
|
||||
{
|
||||
const char*bn = ivl_logic_basename(lptr);
|
||||
fprintf(vvp_out, " .primitive %d %u %u %u, \"%s\";\n",
|
||||
vpi_prim_type_code(ivl_logic_type(lptr)),
|
||||
ivl_logic_pins(lptr),
|
||||
ivl_file_table_index(ivl_logic_file(lptr)),
|
||||
ivl_logic_lineno(lptr),
|
||||
bn ? vvp_mangle_name(bn) : "");
|
||||
}
|
||||
|
||||
static void draw_logic_in_scope(ivl_net_logic_t lptr)
|
||||
{
|
||||
unsigned pdx;
|
||||
|
|
@ -2321,6 +2372,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 +2515,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2484,6 +2599,7 @@ int draw_scope(ivl_scope_t net, ivl_scope_t parent)
|
|||
|
||||
for (idx = 0 ; idx < ivl_scope_logs(net) ; idx += 1) {
|
||||
ivl_net_logic_t lptr = ivl_scope_log(net, idx);
|
||||
draw_primitive_in_scope(lptr);
|
||||
draw_logic_in_scope(lptr);
|
||||
}
|
||||
|
||||
|
|
@ -2519,6 +2635,28 @@ int draw_scope(ivl_scope_t net, ivl_scope_t parent)
|
|||
draw_switch_in_scope(sw);
|
||||
}
|
||||
|
||||
/* Emit the preserved continuous assignments so the runtime can
|
||||
expose them as vpiContAssign objects on this scope, each with its
|
||||
l-value and r-value nets (by signal label, word 0) and source
|
||||
location. */
|
||||
for (idx = 0 ; idx < ivl_scope_cassigns(net) ; idx += 1) {
|
||||
ivl_cont_assign_t ca = ivl_scope_cassign(net, idx);
|
||||
ivl_signal_t lval = ivl_cassign_lval(ca);
|
||||
ivl_signal_t rval = ivl_cassign_rval(ca);
|
||||
/* Only reference signals that are actually emitted as named
|
||||
vpi objects. Local (synthesised/elided) nets have no
|
||||
lookupable label, so skip a local l-value entirely and omit
|
||||
a local r-value (vpiRhs is then null). */
|
||||
if (ivl_signal_local(lval))
|
||||
continue;
|
||||
fprintf(vvp_out, " .contassign %u %u, v%p_0",
|
||||
ivl_file_table_index(ivl_cassign_file(ca)),
|
||||
ivl_cassign_lineno(ca), lval);
|
||||
if (rval && !ivl_signal_local(rval))
|
||||
fprintf(vvp_out, ", v%p_0", rval);
|
||||
fprintf(vvp_out, ";\n");
|
||||
}
|
||||
|
||||
if (ivl_scope_type(net) == IVL_SCT_TASK)
|
||||
draw_task_definition(net);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,627 @@
|
|||
/*
|
||||
* 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) */
|
||||
vpiHandle inst; /* the dumped instance scope (module side) */
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Map (input-side value, output-side value) to the IEEE 1364-2005 18.4.3
|
||||
extended-VCD state character for an inout (unknown-direction) port. Value
|
||||
codes are 0, 1, 2=X, 3=Z, where Z means that side is not driving. A side
|
||||
that is hi-Z lets the other side determine the input/output mnemonic; both
|
||||
driving give the agreement (0/1) or conflict (A/a/B/b/C/c) characters. */
|
||||
static char evcd_inout_char(int in_v, int out_v)
|
||||
{
|
||||
static const char tab[4][4] = {
|
||||
/* out= 0 1 X Z */
|
||||
/*in0*/ {'0', 'A', 'a', 'D'},
|
||||
/*in1*/ {'B', '1', 'b', 'U'},
|
||||
/*inX*/ {'C', 'c', '?', 'N'},
|
||||
/*inZ*/ {'L', 'H', 'X', 'F'}
|
||||
};
|
||||
return tab[in_v & 3][out_v & 3];
|
||||
}
|
||||
|
||||
/* Reduce a vpi logic value to the 0/1/2=X/3=Z code used above. */
|
||||
static int evcd_vcode(int logic)
|
||||
{
|
||||
switch (logic) {
|
||||
case vpi0: case vpiL: return 0;
|
||||
case vpi1: case vpiH: return 1;
|
||||
case vpiZ: return 3;
|
||||
default: return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Combine a new driver value into one side's accumulated value. Z means the
|
||||
driver is not contributing; two real drivers that disagree make X. */
|
||||
static int evcd_side_combine(int acc, int v)
|
||||
{
|
||||
if (v == 3) return acc;
|
||||
if (acc == 3) return v;
|
||||
if (acc == v) return acc;
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* The drive strength level (0..7) encoded in a vpi strength bitmask
|
||||
(1<<level). Per IEEE 1364-2005 18.4.3.2, levels 5..7 are "strong" and
|
||||
1..4 are "weak"; this distinction selects the d/u/l/h conflict states. */
|
||||
static int evcd_strlevel(unsigned mask)
|
||||
{
|
||||
int lvl = 0;
|
||||
while (mask > 1) { mask >>= 1; lvl += 1; }
|
||||
return lvl;
|
||||
}
|
||||
|
||||
/* Non-zero if scope `s` is the instance `inst` or nested within it. */
|
||||
static int evcd_scope_within(vpiHandle s, vpiHandle inst)
|
||||
{
|
||||
int guard = 0;
|
||||
while (s && guard < 4096) {
|
||||
if (vpi_compare_objects(s, inst)) return 1;
|
||||
s = vpi_handle(vpiScope, s);
|
||||
guard += 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* For a scalar inout port, separate the external (input-side) and module
|
||||
(output-side) drives by walking the net's vpiDriver objects and
|
||||
classifying each by whether its scope lies inside the dumped instance,
|
||||
then map the pair to the EVCD state character. s0 and s1 receive the
|
||||
resolved strengths. Returns 0 when the net exposes no drivers (the caller
|
||||
then falls back to the plain resolved-value encoding). */
|
||||
static char evcd_inout_state(struct evcd_port*p, int*s0, int*s1)
|
||||
{
|
||||
vpiHandle it = vpi_iterate(vpiDriver, p->net);
|
||||
vpiHandle d;
|
||||
int in_v = 3, out_v = 3; /* side values: 0,1,2=X,3=Z */
|
||||
int in_lvl = 0, out_lvl = 0; /* strongest level seen per side */
|
||||
int any = 0;
|
||||
char c;
|
||||
s_vpi_value rv;
|
||||
|
||||
if (it == NULL) return 0;
|
||||
|
||||
while ((d = vpi_scan(it))) {
|
||||
s_vpi_value val;
|
||||
vpiHandle dscope = vpi_handle(vpiScope, d);
|
||||
int v, lvl;
|
||||
any = 1;
|
||||
val.format = vpiStrengthVal;
|
||||
vpi_get_value(d, &val);
|
||||
v = evcd_vcode((int)val.value.strength[0].logic);
|
||||
/* Strength of the driven rail (s1 for a 1, s0 otherwise). */
|
||||
lvl = evcd_strlevel((v == 1) ? val.value.strength[0].s1
|
||||
: val.value.strength[0].s0);
|
||||
if (dscope && evcd_scope_within(dscope, p->inst)) {
|
||||
out_v = evcd_side_combine(out_v, v);
|
||||
if (lvl > out_lvl) out_lvl = lvl;
|
||||
} else {
|
||||
in_v = evcd_side_combine(in_v, v);
|
||||
if (lvl > in_lvl) in_lvl = lvl;
|
||||
}
|
||||
}
|
||||
|
||||
if (!any) return 0;
|
||||
|
||||
rv.format = vpiStrengthVal;
|
||||
vpi_get_value(p->net, &rv);
|
||||
evcd_strengths((int)rv.value.strength[0].logic, s0, s1);
|
||||
|
||||
c = evcd_inout_char(in_v, out_v);
|
||||
|
||||
/* When both sides drive the same level (0 or 1) but with different
|
||||
strength ranges, the IEEE state is d/u (input stronger) or l/h
|
||||
(output stronger) rather than the plain agreement 0/1. */
|
||||
if (in_v == out_v && (in_v == 0 || in_v == 1)) {
|
||||
int in_strong = in_lvl >= 5;
|
||||
int out_strong = out_lvl >= 5;
|
||||
if (in_strong && !out_strong)
|
||||
c = (in_v == 0) ? 'd' : 'u';
|
||||
else if (!in_strong && out_strong)
|
||||
c = (in_v == 0) ? 'l' : 'h';
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
/* For an inout, separate the two drive sides so genuine
|
||||
conflicts become the IEEE conflict-state characters
|
||||
(A/a/B/b/C/c) rather than a generic '?'. Falls back to the
|
||||
resolved-value encoding when no per-driver info exists. */
|
||||
if (p->dir == vpiInout) {
|
||||
int cs0 = s0, cs1 = s1;
|
||||
char cc = evcd_inout_state(p, &cs0, &cs1);
|
||||
if (cc) {
|
||||
fprintf(evcd_file, "p%c %d %d <%d\n",
|
||||
cc, cs0, cs1, p->id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
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->inst = scope;
|
||||
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,
|
||||
|
|
|
|||
67
vpi_user.h
67
vpi_user.h
|
|
@ -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
|
||||
|
|
@ -326,6 +333,66 @@ typedef struct t_vpi_delay {
|
|||
|
||||
#define vpiGenScope 134
|
||||
|
||||
/******** Structural-connectivity object/relationship codes (Icarus) *********
|
||||
|
||||
These IEEE 1364/1800 object, relationship and property codes were not
|
||||
previously implemented by Icarus. Standard values are used where they do
|
||||
not collide with Icarus's existing numbering; where the standard value is
|
||||
already taken by an unrelated Icarus object code (noted inline) an
|
||||
Icarus-local value continues the 13x extension block used above for
|
||||
vpiHighConn/vpiLowConn. The flowtracer-style single-source plugins guard
|
||||
these with #ifndef, so they pick up whatever value each simulator's header
|
||||
defines. */
|
||||
#define vpiContAssign 8 /* continuous assignment (standard) */
|
||||
#define vpiProcess 99 /* always/initial/final process (standard) */
|
||||
#define vpiPrimitive 103 /* gate/switch/UDP primitive (standard) */
|
||||
#define vpiPrimTerm 46 /* primitive terminal (standard) */
|
||||
#define vpiInitial 24 /* initial process (standard) */
|
||||
#define vpiAlways 137 /* always process (std 1 == vpiType; Icarus-local) */
|
||||
#define vpiFinal 138 /* final process (no 1364 value; Icarus-local) */
|
||||
/* Relationships / one-to-one navigations */
|
||||
#define vpiStmt 104 /* statement of a process (standard) */
|
||||
#define vpiLhs 77 /* left-hand side of an assignment (standard) */
|
||||
#define vpiRhs 82 /* right-hand side of an assignment (standard) */
|
||||
#define vpiDriver 91 /* driver of a net (standard) */
|
||||
#define vpiLoad 93 /* load on a net or reg (standard) */
|
||||
/* vpiPrimType is a property (additional type code) of a vpiPrimitive. The
|
||||
standard value 33 is taken by vpiNamedBegin in Icarus, so this is an
|
||||
Icarus-local value. Its *result* values (vpiAndPrim ... vpiCombPrim) are
|
||||
the standard 1364 ones; they live in the additional-type-code namespace
|
||||
and so legitimately share integer values with unrelated object/property
|
||||
codes (exactly as the 1364 reference header does). */
|
||||
#define vpiPrimType 139 /* primitive subtype (std 33 == vpiNamedBegin) */
|
||||
#define vpiTermIndex 140 /* primitive terminal index (std 30 == vpiMemoryWord) */
|
||||
#define vpiAndPrim 1
|
||||
#define vpiNandPrim 2
|
||||
#define vpiNorPrim 3
|
||||
#define vpiOrPrim 4
|
||||
#define vpiXorPrim 5
|
||||
#define vpiXnorPrim 6
|
||||
#define vpiBufPrim 7
|
||||
#define vpiNotPrim 8
|
||||
#define vpiBufif0Prim 9
|
||||
#define vpiBufif1Prim 10
|
||||
#define vpiNotif0Prim 11
|
||||
#define vpiNotif1Prim 12
|
||||
#define vpiNmosPrim 13
|
||||
#define vpiPmosPrim 14
|
||||
#define vpiCmosPrim 15
|
||||
#define vpiRnmosPrim 16
|
||||
#define vpiRpmosPrim 17
|
||||
#define vpiRcmosPrim 18
|
||||
#define vpiRtranPrim 19
|
||||
#define vpiRtranif0Prim 20
|
||||
#define vpiRtranif1Prim 21
|
||||
#define vpiTranPrim 22
|
||||
#define vpiTranif0Prim 23
|
||||
#define vpiTranif1Prim 24
|
||||
#define vpiPullupPrim 25
|
||||
#define vpiPulldownPrim 26
|
||||
#define vpiSeqPrim 27
|
||||
#define vpiCombPrim 28
|
||||
|
||||
/* PROPERTIES */
|
||||
#define vpiUndefined (-1)
|
||||
#define vpiType 1
|
||||
|
|
|
|||
|
|
@ -1684,6 +1684,37 @@ void compile_shiftr(char*label, long wid, bool signed_flag,
|
|||
make_arith(arith, label, argc, argv);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tag the inputs of an already-defined resolver with the scope each driver
|
||||
* lives in. This lets the runtime expose vpiDriver and, in particular, lets
|
||||
* the extended-VCD writer separate an inout port's module-side drivers from
|
||||
* its external drivers when computing the IEEE 1364 conflict-state character.
|
||||
* The resolver and the driver scopes are all defined earlier in the file, so
|
||||
* the lookups resolve immediately.
|
||||
*/
|
||||
void compile_resolv_drv(char*resolver, unsigned argc, struct symb_s*argv)
|
||||
{
|
||||
vvp_net_t*net = lookup_functor_symbol(resolver);
|
||||
resolv_core*core = net ? dynamic_cast<resolv_core*>(net->fun) : 0;
|
||||
if (core) {
|
||||
core->prepare_driver_scopes(argc);
|
||||
for (unsigned idx = 0 ; idx < argc ; idx += 1) {
|
||||
/* A driver scope may be declared later in the file than
|
||||
the resolver, so resolve it through the deferred
|
||||
vpi-handle lookup, which fills the (stable) slot once
|
||||
the scope is defined. The lookup takes ownership of
|
||||
the label string. */
|
||||
compile_vpi_lookup((vpiHandle*)(void*)core->driver_scope_ref(idx),
|
||||
argv[idx].text);
|
||||
}
|
||||
} else {
|
||||
for (unsigned idx = 0 ; idx < argc ; idx += 1)
|
||||
free(argv[idx].text);
|
||||
}
|
||||
free(resolver);
|
||||
free(argv);
|
||||
}
|
||||
|
||||
void compile_resolver(char*label, char*type, unsigned argc, struct symb_s*argv)
|
||||
{
|
||||
vvp_net_t*net = new vvp_net_t;
|
||||
|
|
@ -2029,6 +2060,29 @@ void compile_thread(char*start_sym, char*flag)
|
|||
free(flag);
|
||||
}
|
||||
|
||||
/*
|
||||
* Record an always/initial/final process as a vpiProcess object attached
|
||||
* to the current scope. The scope was just selected by the preceding
|
||||
* ".scope" directive, so vpip_attach_to_current_scope() lands it in the
|
||||
* right module/instance.
|
||||
*/
|
||||
void compile_process(long type, long file_idx, long lineno)
|
||||
{
|
||||
vpiHandle obj = vpip_make_process(type, (unsigned)file_idx,
|
||||
(unsigned)lineno);
|
||||
vpip_attach_to_current_scope(obj);
|
||||
}
|
||||
|
||||
void compile_primitive(long primtype, long npins,
|
||||
long file_idx, long lineno, char*name)
|
||||
{
|
||||
vpiHandle obj = vpip_make_primitive((int)primtype, (unsigned)npins,
|
||||
(unsigned)file_idx, (unsigned)lineno,
|
||||
name);
|
||||
vpip_attach_to_current_scope(obj);
|
||||
free(name);
|
||||
}
|
||||
|
||||
void compile_param_logic(char*label, const char*name, char*value, bool signed_flag,
|
||||
bool local_flag,
|
||||
long file_idx, long lineno)
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ extern void compile_functor(char*label, char*type, unsigned width,
|
|||
extern void compile_resolver(char*label, char*type,
|
||||
unsigned argc, struct symb_s*argv);
|
||||
|
||||
extern void compile_resolv_drv(char*resolver,
|
||||
unsigned argc, struct symb_s*argv);
|
||||
|
||||
extern void compile_concat(char*label, unsigned w0, unsigned w1,
|
||||
unsigned w2, unsigned w3,
|
||||
unsigned argc, struct symb_s*argv);
|
||||
|
|
@ -493,6 +496,32 @@ extern void compile_scope_recall(char*sym);
|
|||
*/
|
||||
extern void compile_thread(char*start_sym, char*flag);
|
||||
|
||||
/*
|
||||
* The parser uses this to record an always/initial/final process as a
|
||||
* vpiProcess object on the current scope. type is 0=initial, 1=always,
|
||||
* 2=final; file_idx/lineno locate the process statement in the source.
|
||||
*/
|
||||
extern void compile_process(long type, long file_idx, long lineno);
|
||||
|
||||
/*
|
||||
* The parser uses this to record a continuous assignment as a vpiContAssign
|
||||
* on the current scope. lhs/rhs are the l-value and r-value signal symbols
|
||||
* (resolved by deferred lookup); ownership of both strings passes to this
|
||||
* function.
|
||||
*/
|
||||
extern void compile_contassign(long file_idx, long lineno,
|
||||
char*lhs, char*rhs);
|
||||
|
||||
/*
|
||||
* The parser uses this to record a gate/switch/UDP primitive as a
|
||||
* vpiPrimitive object on the current scope. primtype is the vpiPrimType
|
||||
* subtype code, npins the terminal count (pin 0 is the output), file_idx and
|
||||
* lineno locate the gate, and name is its base name (parser-owned, freed
|
||||
* here).
|
||||
*/
|
||||
extern void compile_primitive(long primtype, long npins,
|
||||
long file_idx, long lineno, char*name);
|
||||
|
||||
/*
|
||||
* This function is called to create a var vector with the given name.
|
||||
*
|
||||
|
|
@ -518,7 +547,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 );
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ inline uint64_t strtouint64(const char*str, char**endptr, int base)
|
|||
".cmp/weq" { return K_CMP_WEQ; }
|
||||
".cmp/wne" { return K_CMP_WNE; }
|
||||
".concat" { return K_CONCAT; }
|
||||
".contassign" { return K_CONTASSIGN; }
|
||||
".concat8" { return K_CONCAT8; }
|
||||
".delay" { return K_DELAY; }
|
||||
".dff/n" { return K_DFF_N; }
|
||||
|
|
@ -214,6 +215,8 @@ inline uint64_t strtouint64(const char*str, char**endptr, int base)
|
|||
".part/v.s" { return K_PART_V_S; }
|
||||
".port" { return K_PORT; }
|
||||
".port_info" { return K_PORT_INFO; }
|
||||
".primitive" { return K_PRIMITIVE; }
|
||||
".process" { return K_PROCESS; }
|
||||
".reduce/and" { return K_REDUCE_AND; }
|
||||
".reduce/or" { return K_REDUCE_OR; }
|
||||
".reduce/xor" { return K_REDUCE_XOR; }
|
||||
|
|
@ -222,6 +225,7 @@ inline uint64_t strtouint64(const char*str, char**endptr, int base)
|
|||
".reduce/xnor" { return K_REDUCE_XNOR; }
|
||||
".repeat" { return K_REPEAT; }
|
||||
".resolv" { return K_RESOLV; }
|
||||
".resolv_drv" { return K_RESOLV_DRV; }
|
||||
".rtran" { return K_RTRAN; }
|
||||
".rtranif0" { return K_RTRANIF0; }
|
||||
".rtranif1" { return K_RTRANIF1; }
|
||||
|
|
|
|||
53
vvp/parse.y
53
vvp/parse.y
|
|
@ -86,16 +86,16 @@ static struct __vpiModPath*modpath_dst = 0;
|
|||
%token K_CMP_EEQ K_CMP_EQ K_CMP_EQX K_CMP_EQZ K_CMP_WEQ K_CMP_WNE
|
||||
%token K_CMP_EQ_R K_CMP_NEE K_CMP_NE K_CMP_NE_R
|
||||
%token K_CMP_GE K_CMP_GE_R K_CMP_GE_S K_CMP_GT K_CMP_GT_R K_CMP_GT_S
|
||||
%token K_CONCAT K_CONCAT8 K_DEBUG K_DELAY K_DFF_N K_DFF_N_ACLR
|
||||
%token K_CONCAT K_CONCAT8 K_CONTASSIGN K_DEBUG K_DELAY K_DFF_N K_DFF_N_ACLR
|
||||
%token K_DFF_N_ASET K_DFF_P K_DFF_P_ACLR K_DFF_P_ASET
|
||||
%token K_ENUM2 K_ENUM2_S K_ENUM4 K_ENUM4_S K_EVENT K_EVENT_OR
|
||||
%token K_EXPORT K_EXTEND_S K_FUNCTOR K_IMPORT K_ISLAND K_LATCH K_MODPATH
|
||||
%token K_NET K_NET_S K_NET_R K_NET_2S K_NET_2U
|
||||
%token K_NET8 K_NET8_2S K_NET8_2U K_NET8_S
|
||||
%token K_PARAM_STR K_PARAM_L K_PARAM_REAL K_PART K_PART_PV
|
||||
%token K_PART_V K_PART_V_S K_PORT K_PORT_INFO K_PV K_REDUCE_AND K_REDUCE_OR K_REDUCE_XOR
|
||||
%token K_PART_V K_PART_V_S K_PORT K_PORT_INFO K_PRIMITIVE K_PROCESS K_PV K_REDUCE_AND K_REDUCE_OR K_REDUCE_XOR
|
||||
%token K_REDUCE_NAND K_REDUCE_NOR K_REDUCE_XNOR K_REPEAT
|
||||
%token K_RESOLV K_RTRAN K_RTRANIF0 K_RTRANIF1
|
||||
%token K_RESOLV K_RESOLV_DRV K_RTRAN K_RTRANIF0 K_RTRANIF1
|
||||
%token K_SCOPE K_SFUNC K_SFUNC_E K_SHIFTL K_SHIFTR K_SHIFTRS
|
||||
%token K_SUBSTITUTE
|
||||
%token K_THREAD K_TIMESCALE K_TRAN K_TRANIF0 K_TRANIF1 K_TRANVP
|
||||
|
|
@ -279,6 +279,16 @@ statement
|
|||
compile_resolver($1, $3, obj.cnt, obj.vect);
|
||||
}
|
||||
|
||||
/* Tags the inputs of a resolver with the scope each driver lives in,
|
||||
so the runtime can expose vpiDriver and separate an inout port's
|
||||
module-side and external drives. First symbol is the resolver; the
|
||||
rest are the per-input driver scopes, in input order. */
|
||||
|
||||
| K_RESOLV_DRV T_SYMBOL ',' symbols ';'
|
||||
{ struct symbv_s obj = $4;
|
||||
compile_resolv_drv($2, obj.cnt, obj.vect);
|
||||
}
|
||||
|
||||
/* Part select statements take a single netlist input, and numbers
|
||||
that define the part to be selected out of the input. */
|
||||
|
||||
|
|
@ -717,13 +727,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); }
|
||||
|
|
@ -738,6 +753,34 @@ statement
|
|||
starting address must already be defined. The .thread statement
|
||||
may also take an optional flag word. */
|
||||
|
||||
/* A process statement records an always/initial/final process as a
|
||||
structural object attached to the current scope, so the runtime can
|
||||
expose it through vpi_iterate(vpiProcess, scope). Arguments are the
|
||||
process type (0=initial, 1=always, 2=final), the file-table index and
|
||||
the source line number of the process statement. */
|
||||
|
||||
| K_PROCESS T_NUMBER T_NUMBER T_NUMBER ';'
|
||||
{ compile_process($2, $3, $4); }
|
||||
|
||||
/* A continuous-assignment statement records an `assign lhs = rhs;` as a
|
||||
vpiContAssign on the current scope. Arguments are the file-table index,
|
||||
the source line, and the l-value and r-value signal symbols. */
|
||||
|
||||
| K_CONTASSIGN T_NUMBER T_NUMBER ',' T_SYMBOL ',' T_SYMBOL ';'
|
||||
{ compile_contassign($2, $3, $5, $7); }
|
||||
|
||||
| K_CONTASSIGN T_NUMBER T_NUMBER ',' T_SYMBOL ';'
|
||||
{ compile_contassign($2, $3, $5, 0); }
|
||||
|
||||
/* A primitive statement records a gate/switch/UDP as a structural object
|
||||
attached to the current scope, exposed through vpi_iterate(vpiPrimitive,
|
||||
scope). Arguments are the vpiPrimType subtype code, the number of
|
||||
terminals (pin 0 is the output), the file-table index, the source line,
|
||||
and the gate's base name. */
|
||||
|
||||
| K_PRIMITIVE T_NUMBER T_NUMBER T_NUMBER T_NUMBER ',' T_STRING ';'
|
||||
{ compile_primitive($2, $3, $4, $5, $7); }
|
||||
|
||||
| K_THREAD T_SYMBOL ';'
|
||||
{ compile_thread($2, 0); }
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,23 @@ resolv_tri::~resolv_tri()
|
|||
delete[] val_;
|
||||
}
|
||||
|
||||
__vpiScope* resolv_core::driver_scope(unsigned idx) const
|
||||
{
|
||||
if (idx < driver_scopes_.size())
|
||||
return driver_scopes_[idx];
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool resolv_tri::driver_value(unsigned idx, vvp_vector8_t&val) const
|
||||
{
|
||||
if (idx >= nports_)
|
||||
return false;
|
||||
if (val_[idx].size() == 0)
|
||||
return false;
|
||||
val = val_[idx];
|
||||
return true;
|
||||
}
|
||||
|
||||
void resolv_tri::recv_vec4_(unsigned port, const vvp_vector4_t&bit)
|
||||
{
|
||||
recv_vec8_(port, vvp_vector8_t(bit, 6,6 /* STRONG */));
|
||||
|
|
@ -214,6 +231,18 @@ resolv_wired_logic::~resolv_wired_logic()
|
|||
delete[] val_;
|
||||
}
|
||||
|
||||
bool resolv_wired_logic::driver_value(unsigned idx, vvp_vector8_t&val) const
|
||||
{
|
||||
if (idx >= nports_)
|
||||
return false;
|
||||
if (val_[idx].size() == 0)
|
||||
return false;
|
||||
/* Wired-logic drivers carry plain 4-state values; present them as
|
||||
strong for the per-driver view. */
|
||||
val = vvp_vector8_t(val_[idx], 6, 6);
|
||||
return true;
|
||||
}
|
||||
|
||||
void resolv_wired_logic::recv_vec4_(unsigned port, const vvp_vector4_t&bit)
|
||||
{
|
||||
assert(port < nports_);
|
||||
|
|
|
|||
23
vvp/resolv.h
23
vvp/resolv.h
|
|
@ -21,6 +21,9 @@
|
|||
|
||||
# include "config.h"
|
||||
# include "vvp_net.h"
|
||||
# include <vector>
|
||||
|
||||
class __vpiScope;
|
||||
|
||||
/*
|
||||
* Resolver nodes are similar to wide functors, in that they may have
|
||||
|
|
@ -36,6 +39,23 @@ class resolv_core : public vvp_net_fun_t {
|
|||
explicit resolv_core(unsigned nports, vvp_net_t*net);
|
||||
virtual ~resolv_core() override;
|
||||
|
||||
// Per-driver introspection, used to expose vpiDriver and to let
|
||||
// the extended-VCD writer separate an inout's input-side and
|
||||
// output-side drives. driver_count() is the number of leaf input
|
||||
// drivers; driver_value() reads one driver's current contributed
|
||||
// value (with strength); the scope each driver lives in is tagged
|
||||
// from the .resolv_drv directive so callers can classify a driver
|
||||
// as inside or outside a given module instance.
|
||||
unsigned driver_count() const { return nports_; }
|
||||
// Pre-size the per-driver scope table to a fixed size so the slot
|
||||
// addresses are stable; the .resolv_drv directive then fills them
|
||||
// through the deferred vpi-handle resolver (driver scopes can be
|
||||
// declared later in the file than the resolver).
|
||||
void prepare_driver_scopes(unsigned n) { driver_scopes_.assign(n, 0); }
|
||||
__vpiScope** driver_scope_ref(unsigned idx) { return &driver_scopes_[idx]; }
|
||||
__vpiScope* driver_scope(unsigned idx) const;
|
||||
virtual bool driver_value(unsigned idx, vvp_vector8_t&val) const = 0;
|
||||
|
||||
void recv_vec4(vvp_net_ptr_t port, const vvp_vector4_t&bit,
|
||||
vvp_context_t) override
|
||||
{ recv_vec4_(port.port(), bit); }
|
||||
|
|
@ -66,6 +86,7 @@ class resolv_core : public vvp_net_fun_t {
|
|||
protected:
|
||||
unsigned nports_;
|
||||
vvp_net_t*net_;
|
||||
std::vector<__vpiScope*> driver_scopes_;
|
||||
};
|
||||
|
||||
class resolv_extend : public vvp_net_fun_t {
|
||||
|
|
@ -116,6 +137,7 @@ class resolv_tri : public resolv_core {
|
|||
~resolv_tri() override;
|
||||
|
||||
void count_drivers(unsigned bit_idx, unsigned counts[3]) override;
|
||||
bool driver_value(unsigned idx, vvp_vector8_t&val) const override;
|
||||
|
||||
private:
|
||||
void recv_vec4_(unsigned port, const vvp_vector4_t&bit) override;
|
||||
|
|
@ -144,6 +166,7 @@ class resolv_wired_logic : public resolv_core {
|
|||
virtual ~resolv_wired_logic() override;
|
||||
|
||||
void count_drivers(unsigned bit_idx, unsigned counts[3]) override;
|
||||
bool driver_value(unsigned idx, vvp_vector8_t&val) const override;
|
||||
|
||||
protected:
|
||||
virtual vvp_vector4_t wired_logic_math_(vvp_vector4_t&a, vvp_vector4_t&b) =0;
|
||||
|
|
|
|||
|
|
@ -376,6 +376,12 @@ const char* vpi_type_as_string(PLI_INT32 code)
|
|||
return "vpiMemoryWord";
|
||||
case vpiModule:
|
||||
return "vpiModule";
|
||||
case vpiAlways:
|
||||
return "vpiAlways";
|
||||
case vpiInitial:
|
||||
return "vpiInitial";
|
||||
case vpiFinal:
|
||||
return "vpiFinal";
|
||||
case vpiNamedBegin:
|
||||
return "vpiNamedBegin";
|
||||
case vpiNamedEvent:
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -998,6 +1000,17 @@ vpiHandle vpip_make_real_const(double value);
|
|||
vpiHandle vpip_make_real_param(const char*name, double value, bool local_flag,
|
||||
long file_idx, long lineno);
|
||||
|
||||
/* Make a vpiProcess object (always/initial/final). type is the encoding
|
||||
emitted by tgt-vvp: 0=initial, 1=always, 2=final. */
|
||||
vpiHandle vpip_make_process(long type, unsigned file_idx, unsigned lineno);
|
||||
|
||||
/* Make a vpiPrimitive object (gate/switch/UDP). primtype is the vpiPrimType
|
||||
subtype code, npins the terminal count (pin 0 = output). The name is copied
|
||||
(interned); the caller retains ownership of the passed string. */
|
||||
vpiHandle vpip_make_primitive(int primtype, unsigned npins,
|
||||
unsigned file_idx, unsigned lineno,
|
||||
const char*name);
|
||||
|
||||
class __vpiNullConst : public __vpiHandle {
|
||||
public:
|
||||
explicit __vpiNullConst();
|
||||
|
|
|
|||
351
vvp/vpi_scope.cc
351
vvp/vpi_scope.cc
|
|
@ -114,6 +114,16 @@ static void delete_sub_scopes(__vpiScope *scope)
|
|||
case vpiPortBit:
|
||||
port_bit_delete(item);
|
||||
break;
|
||||
case vpiAlways:
|
||||
case vpiInitial:
|
||||
case vpiFinal:
|
||||
/* __vpiProcess holds no sub-objects of its own. */
|
||||
delete item;
|
||||
break;
|
||||
case vpiContAssign:
|
||||
/* __vpiContAssign references signals it does not own. */
|
||||
delete item;
|
||||
break;
|
||||
case vpiStringVar:
|
||||
string_delete(item);
|
||||
break;
|
||||
|
|
@ -287,6 +297,12 @@ static int compare_types(int code, int type)
|
|||
type == vpiPackage) )
|
||||
return 1;
|
||||
|
||||
if ( code == vpiProcess &&
|
||||
(type == vpiAlways ||
|
||||
type == vpiInitial ||
|
||||
type == vpiFinal) )
|
||||
return 1;
|
||||
|
||||
if ( code == vpiVariables &&
|
||||
(type == vpiIntegerVar ||
|
||||
type == vpiBitVar ||
|
||||
|
|
@ -337,6 +353,303 @@ static vpiHandle module_iter(int code, vpiHandle obj)
|
|||
return make_subset_iterator_(code, ref->intern);
|
||||
}
|
||||
|
||||
/*
|
||||
* vpiProcess: an always/initial/final process. It is attached to its scope
|
||||
* (via vpip_attach_to_current_scope) so that vpi_iterate(vpiProcess, scope)
|
||||
* enumerates the processes of a module. The object reports its kind through
|
||||
* vpiType (vpiAlways / vpiInitial / vpiFinal) and carries the source
|
||||
* file/line of the process statement.
|
||||
*/
|
||||
class __vpiProcess : public __vpiHandle {
|
||||
public:
|
||||
__vpiProcess(int type_code, unsigned file_idx, unsigned lineno)
|
||||
: type_code_(type_code), scope_(vpip_peek_current_scope()),
|
||||
file_idx_(file_idx), lineno_(lineno) { }
|
||||
|
||||
int get_type_code(void) const override { return type_code_; }
|
||||
int vpi_get(int code) override;
|
||||
char* vpi_get_str(int code) override;
|
||||
vpiHandle vpi_handle(int code) override;
|
||||
|
||||
private:
|
||||
int type_code_;
|
||||
__vpiScope* scope_;
|
||||
unsigned file_idx_;
|
||||
unsigned lineno_;
|
||||
};
|
||||
|
||||
int __vpiProcess::vpi_get(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiType:
|
||||
return type_code_;
|
||||
case vpiLineNo:
|
||||
return (int)lineno_;
|
||||
case vpiAutomatic:
|
||||
return 0;
|
||||
default:
|
||||
return vpiUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
char* __vpiProcess::vpi_get_str(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiType:
|
||||
switch (type_code_) {
|
||||
case vpiAlways: return simple_set_rbuf_str("vpiAlways");
|
||||
case vpiInitial: return simple_set_rbuf_str("vpiInitial");
|
||||
case vpiFinal: return simple_set_rbuf_str("vpiFinal");
|
||||
default: return 0;
|
||||
}
|
||||
case vpiFile:
|
||||
if (file_idx_ < file_names.size())
|
||||
return simple_set_rbuf_str(file_names[file_idx_]);
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
vpiHandle __vpiProcess::vpi_handle(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiScope:
|
||||
case vpiModule:
|
||||
return scope_;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
vpiHandle vpip_make_process(long type, unsigned file_idx, unsigned lineno)
|
||||
{
|
||||
int tc;
|
||||
switch (type) {
|
||||
case 0: tc = vpiInitial; break;
|
||||
case 2: tc = vpiFinal; break;
|
||||
default: tc = vpiAlways; break;
|
||||
}
|
||||
return new __vpiProcess(tc, file_idx, lineno);
|
||||
}
|
||||
|
||||
/*
|
||||
* vpiPrimitive (gate/switch/UDP) and its vpiPrimTerm terminals, per 1364
|
||||
* 26.6.13. The primitive is attached to its scope so vpi_iterate(vpiPrimitive,
|
||||
* scope) enumerates the gates of a module. vpiSize is the number of inputs
|
||||
* (terminal count minus the single output, pin 0). Each terminal reports
|
||||
* vpiDirection (pin 0 output, the rest inputs) and vpiTermIndex.
|
||||
*/
|
||||
class __vpiPrimitive;
|
||||
|
||||
class __vpiPrimTerm : public __vpiHandle {
|
||||
public:
|
||||
__vpiPrimTerm(__vpiPrimitive*prim, unsigned index)
|
||||
: prim_(prim), index_(index) { }
|
||||
int get_type_code(void) const override { return vpiPrimTerm; }
|
||||
int vpi_get(int code) override;
|
||||
vpiHandle vpi_handle(int code) override;
|
||||
private:
|
||||
__vpiPrimitive*prim_;
|
||||
unsigned index_;
|
||||
};
|
||||
|
||||
class __vpiPrimitive : public __vpiHandle {
|
||||
public:
|
||||
__vpiPrimitive(int primtype, unsigned npins, unsigned file_idx,
|
||||
unsigned lineno, const char*name)
|
||||
: primtype_(primtype), npins_(npins), scope_(vpip_peek_current_scope()),
|
||||
file_idx_(file_idx), lineno_(lineno)
|
||||
{
|
||||
name_ = vpip_name_string(name ? name : "");
|
||||
terms_ = npins_ ? new __vpiPrimTerm*[npins_] : 0;
|
||||
for (unsigned i = 0 ; i < npins_ ; i += 1)
|
||||
terms_[i] = new __vpiPrimTerm(this, i);
|
||||
}
|
||||
~__vpiPrimitive()
|
||||
{
|
||||
for (unsigned i = 0 ; i < npins_ ; i += 1) delete terms_[i];
|
||||
delete[] terms_;
|
||||
}
|
||||
int get_type_code(void) const override { return vpiPrimitive; }
|
||||
int vpi_get(int code) override;
|
||||
char* vpi_get_str(int code) override;
|
||||
vpiHandle vpi_handle(int code) override;
|
||||
vpiHandle vpi_iterate(int code) override;
|
||||
private:
|
||||
int primtype_;
|
||||
unsigned npins_;
|
||||
__vpiScope*scope_;
|
||||
unsigned file_idx_;
|
||||
unsigned lineno_;
|
||||
const char*name_;
|
||||
__vpiPrimTerm**terms_;
|
||||
};
|
||||
|
||||
int __vpiPrimitive::vpi_get(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiPrimType:
|
||||
return primtype_;
|
||||
case vpiSize:
|
||||
return (int)(npins_ ? npins_ - 1 : 0); /* number of inputs */
|
||||
case vpiLineNo:
|
||||
return (int)lineno_;
|
||||
default:
|
||||
return vpiUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
char* __vpiPrimitive::vpi_get_str(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiName:
|
||||
case vpiDefName:
|
||||
return simple_set_rbuf_str(name_);
|
||||
case vpiFullName: {
|
||||
char buf[4096];
|
||||
buf[0] = 0;
|
||||
if (scope_) {
|
||||
construct_scope_fullname(scope_, buf);
|
||||
strcat(buf, ".");
|
||||
}
|
||||
strcat(buf, name_);
|
||||
return simple_set_rbuf_str(buf);
|
||||
}
|
||||
case vpiFile:
|
||||
if (file_idx_ < file_names.size())
|
||||
return simple_set_rbuf_str(file_names[file_idx_]);
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
vpiHandle __vpiPrimitive::vpi_handle(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiScope:
|
||||
case vpiModule:
|
||||
return scope_;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
vpiHandle __vpiPrimitive::vpi_iterate(int code)
|
||||
{
|
||||
if (code == vpiPrimTerm && npins_ > 0) {
|
||||
vpiHandle*args = (vpiHandle*)calloc(npins_, sizeof(vpiHandle));
|
||||
for (unsigned i = 0 ; i < npins_ ; i += 1) args[i] = terms_[i];
|
||||
return vpip_make_iterator(npins_, args, true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __vpiPrimTerm::vpi_get(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiTermIndex:
|
||||
return (int)index_;
|
||||
case vpiDirection:
|
||||
return index_ == 0 ? vpiOutput : vpiInput;
|
||||
default:
|
||||
return vpiUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
vpiHandle __vpiPrimTerm::vpi_handle(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiParent:
|
||||
case vpiPrimitive:
|
||||
return prim_;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
vpiHandle vpip_make_primitive(int primtype, unsigned npins, unsigned file_idx,
|
||||
unsigned lineno, const char*name)
|
||||
{
|
||||
return new __vpiPrimitive(primtype, npins, file_idx, lineno, name);
|
||||
}
|
||||
|
||||
/*
|
||||
* vpiContAssign: a continuous assignment (`assign lhs = rhs;`), preserved
|
||||
* through elaboration and attached to its scope so vpi_iterate(vpiContAssign,
|
||||
* scope) enumerates them. vpiLhs / vpiRhs navigate to the l-value and r-value
|
||||
* nets; vpiSize is the l-value width; vpiLineNo/vpiFile give the location.
|
||||
*/
|
||||
class __vpiContAssign : public __vpiHandle {
|
||||
public:
|
||||
__vpiContAssign(unsigned file_idx, unsigned lineno)
|
||||
: lhs_(0), rhs_(0), scope_(vpip_peek_current_scope()),
|
||||
file_idx_(file_idx), lineno_(lineno) { }
|
||||
|
||||
int get_type_code(void) const override { return vpiContAssign; }
|
||||
int vpi_get(int code) override;
|
||||
char* vpi_get_str(int code) override;
|
||||
vpiHandle vpi_handle(int code) override;
|
||||
|
||||
// Filled in by the deferred vpi-handle lookup in compile_contassign.
|
||||
vpiHandle lhs_;
|
||||
vpiHandle rhs_;
|
||||
private:
|
||||
__vpiScope*scope_;
|
||||
unsigned file_idx_;
|
||||
unsigned lineno_;
|
||||
};
|
||||
|
||||
int __vpiContAssign::vpi_get(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiLineNo:
|
||||
return (int)lineno_;
|
||||
case vpiSize:
|
||||
return lhs_ ? lhs_->vpi_get(vpiSize) : 0;
|
||||
default:
|
||||
return vpiUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
char* __vpiContAssign::vpi_get_str(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiFile:
|
||||
if (file_idx_ < file_names.size())
|
||||
return simple_set_rbuf_str(file_names[file_idx_]);
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
vpiHandle __vpiContAssign::vpi_handle(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiLhs: return lhs_;
|
||||
case vpiRhs: return rhs_;
|
||||
case vpiScope:
|
||||
case vpiModule: return scope_;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void compile_contassign(long file_idx, long lineno, char*lhs, char*rhs)
|
||||
{
|
||||
__vpiContAssign*obj = new __vpiContAssign((unsigned)file_idx,
|
||||
(unsigned)lineno);
|
||||
vpip_attach_to_current_scope(obj);
|
||||
/* The l-value and r-value signals may be defined later in the file,
|
||||
so resolve them through the deferred vpi-handle lookup, which
|
||||
takes ownership of the label strings. The r-value is absent when
|
||||
the assignment's r-value is an expression with no single net. */
|
||||
compile_vpi_lookup(&obj->lhs_, lhs);
|
||||
if (rhs)
|
||||
compile_vpi_lookup(&obj->rhs_, rhs);
|
||||
}
|
||||
|
||||
|
||||
__vpiScope::__vpiScope(const char*nam, const char*tnam, bool auto_flag)
|
||||
: is_automatic_(auto_flag)
|
||||
|
|
@ -666,7 +979,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 +989,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 +1034,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 +1061,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 +1152,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
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
# include "compile.h"
|
||||
# include "vpi_priv.h"
|
||||
# include "resolv.h"
|
||||
# include "vvp_net_sig.h"
|
||||
# include "vvp_island.h"
|
||||
# include "schedule.h"
|
||||
|
|
@ -680,6 +681,67 @@ static vpiHandle signal_get_handle(int code, vpiHandle ref)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* vpiDriver: one input driver of a resolved (tri/inout) net. It exposes the
|
||||
* value (with strength) that this single driver contributes — read from the
|
||||
* resolver's retained per-input value — and the scope the driver lives in.
|
||||
* The extended-VCD ($dumpports) writer uses the scope to tell an inout's
|
||||
* module-side drivers from its external ones and so emit the IEEE 1364
|
||||
* conflict-state characters.
|
||||
*/
|
||||
class __vpiDriver : public __vpiHandle {
|
||||
public:
|
||||
__vpiDriver(resolv_core*core, unsigned idx) : core_(core), idx_(idx) { }
|
||||
int get_type_code(void) const override { return vpiDriver; }
|
||||
vpiHandle vpi_handle(int code) override;
|
||||
void vpi_get_value(p_vpi_value vp) override;
|
||||
private:
|
||||
resolv_core*core_;
|
||||
unsigned idx_;
|
||||
};
|
||||
|
||||
vpiHandle __vpiDriver::vpi_handle(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case vpiScope:
|
||||
case vpiModule:
|
||||
return core_->driver_scope(idx_);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void __vpiDriver::vpi_get_value(p_vpi_value vp)
|
||||
{
|
||||
/* Only vpiStrengthVal is meaningful for a driver: it carries the
|
||||
per-bit value and drive strength this driver contributes. */
|
||||
vvp_vector8_t v8;
|
||||
bool ok = core_->driver_value(idx_, v8);
|
||||
unsigned wid = (ok && v8.size() > 0) ? v8.size() : 1;
|
||||
|
||||
s_vpi_strengthval*op = static_cast<s_vpi_strengthval*>
|
||||
(need_result_buf(wid * sizeof(s_vpi_strengthval), RBUF_VAL));
|
||||
|
||||
for (unsigned idx = 0 ; idx < wid ; idx += 1) {
|
||||
if (!ok) {
|
||||
/* Driver not contributing -> three-state. */
|
||||
op[idx].logic = vpiZ; op[idx].s0 = vpiHiZ; op[idx].s1 = vpiHiZ;
|
||||
continue;
|
||||
}
|
||||
vvp_scalar_t val = v8.value(idx);
|
||||
unsigned s0 = 1 << val.strength0();
|
||||
unsigned s1 = 1 << val.strength1();
|
||||
switch (val.value()) {
|
||||
case BIT4_0: op[idx].logic = vpi0; op[idx].s0 = s0|s1; op[idx].s1 = 0; break;
|
||||
case BIT4_1: op[idx].logic = vpi1; op[idx].s0 = 0; op[idx].s1 = s0|s1; break;
|
||||
case BIT4_X: op[idx].logic = vpiX; op[idx].s0 = s0; op[idx].s1 = s1; break;
|
||||
default: op[idx].logic = vpiZ; op[idx].s0 = vpiHiZ; op[idx].s1 = vpiHiZ; break;
|
||||
}
|
||||
}
|
||||
vp->format = vpiStrengthVal;
|
||||
vp->value.strength = op;
|
||||
}
|
||||
|
||||
static vpiHandle signal_iterate(int code, vpiHandle ref)
|
||||
{
|
||||
struct __vpiSignal*rfp = dynamic_cast<__vpiSignal*>(ref);
|
||||
|
|
@ -689,6 +751,20 @@ static vpiHandle signal_iterate(int code, vpiHandle ref)
|
|||
return rfp->is_netarray ? rfp->id.index->vpi_iterate(code) : NULL;
|
||||
}
|
||||
|
||||
/* Drivers of a resolved net (tri/inout): expose each input of the
|
||||
resolver feeding this signal as a vpiDriver. */
|
||||
if (code == vpiDriver) {
|
||||
resolv_core*core = rfp->node
|
||||
? dynamic_cast<resolv_core*>(rfp->node->fun) : 0;
|
||||
if (core == 0 || core->driver_count() == 0)
|
||||
return 0;
|
||||
unsigned n = core->driver_count();
|
||||
vpiHandle*args = static_cast<vpiHandle*>(calloc(n, sizeof(vpiHandle)));
|
||||
for (unsigned idx = 0 ; idx < n ; idx += 1)
|
||||
args[idx] = new __vpiDriver(core, idx);
|
||||
return vpip_make_iterator(n, args, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue