diff --git a/Documentation/usage/waveform_viewer.rst b/Documentation/usage/waveform_viewer.rst index c07bb5a4e..b3d95fd2e 100644 --- a/Documentation/usage/waveform_viewer.rst +++ b/Documentation/usage/waveform_viewer.rst @@ -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.) diff --git a/elaborate.cc b/elaborate.cc index 584745249..fcf5d642f 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -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. diff --git a/ivl_target.h b/ivl_target.h index b33ea9aa4..bf20fd771 100644 --- a/ivl_target.h +++ b/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); diff --git a/ivtest/gold/evcd_basic.evcd.gold b/ivtest/gold/evcd_basic.evcd.gold new file mode 100644 index 000000000..1db23d1c7 --- /dev/null +++ b/ivtest/gold/evcd_basic.evcd.gold @@ -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 diff --git a/ivtest/gold/evcd_bus.evcd.gold b/ivtest/gold/evcd_bus.evcd.gold new file mode 100644 index 000000000..22386a86c --- /dev/null +++ b/ivtest/gold/evcd_bus.evcd.gold @@ -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 diff --git a/ivtest/gold/evcd_inout.evcd.gold b/ivtest/gold/evcd_inout.evcd.gold new file mode 100644 index 000000000..f0863455d --- /dev/null +++ b/ivtest/gold/evcd_inout.evcd.gold @@ -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 diff --git a/ivtest/gold/evcd_onoff.evcd.gold b/ivtest/gold/evcd_onoff.evcd.gold new file mode 100644 index 000000000..f00622772 --- /dev/null +++ b/ivtest/gold/evcd_onoff.evcd.gold @@ -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 diff --git a/ivtest/ivltests/evcd_basic.v b/ivtest/ivltests/evcd_basic.v new file mode 100644 index 000000000..b369376af --- /dev/null +++ b/ivtest/ivltests/evcd_basic.v @@ -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 diff --git a/ivtest/ivltests/evcd_bus.v b/ivtest/ivltests/evcd_bus.v new file mode 100644 index 000000000..248fb386d --- /dev/null +++ b/ivtest/ivltests/evcd_bus.v @@ -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 diff --git a/ivtest/ivltests/evcd_inout.v b/ivtest/ivltests/evcd_inout.v new file mode 100644 index 000000000..8e5ceb340 --- /dev/null +++ b/ivtest/ivltests/evcd_inout.v @@ -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 diff --git a/ivtest/ivltests/evcd_onoff.v b/ivtest/ivltests/evcd_onoff.v new file mode 100644 index 000000000..d3af19548 --- /dev/null +++ b/ivtest/ivltests/evcd_onoff.v @@ -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 diff --git a/ivtest/regress-vlg.list b/ivtest/regress-vlg.list index a2a1f86ed..e0a2ed0fd 100644 --- a/ivtest/regress-vlg.list +++ b/ivtest/regress-vlg.list @@ -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 diff --git a/net_scope.cc b/net_scope.cc index be9d1c4c6..d8883a2c1 100644 --- a/net_scope.cc +++ b/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(); diff --git a/netlist.h b/netlist.h index d485a5be8..3fd2b94c9 100644 --- a/netlist.h +++ b/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_assigns() const { return cont_assigns_; } + const std::map& 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 children_; + std::vector cont_assigns_; std::map interface_port_aliases_; std::map > interface_port_alias_arrays_; diff --git a/t-dll-api.cc b/t-dll-api.cc index 7a33018f8..34004ae88 100644 --- a/t-dll-api.cc +++ b/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; diff --git a/t-dll.cc b/t-dll.cc index 1d130dbca..f6161db8e 100644 --- a/t-dll.cc +++ b/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&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::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::listroots = des->find_root_scopes(); + for (std::list::const_iterator it = roots.begin() + ; it != roots.end() ; ++ it) + add_cont_assigns(*it); + if (verbose_flag) { cout << " ... invoking target_design" << endl; } diff --git a/t-dll.h b/t-dll.h index 44d5c3724..6c2797abc 100644 --- a/t-dll.h +++ b/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::vectorswitches; + std::vectorcassigns_; signed int time_precision :8; signed int time_units :8; diff --git a/tgt-vvp/draw_net_input.c b/tgt-vvp/draw_net_input.c index d50eaaf38..9dcc0499b 100644 --- a/tgt-vvp/draw_net_input.c +++ b/tgt-vvp/draw_net_input.c @@ -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) diff --git a/tgt-vvp/vvp_process.c b/tgt-vvp/vvp_process.c index 8382b94ab..809823dd7 100644 --- a/tgt-vvp/vvp_process.c +++ b/tgt-vvp/vvp_process.c @@ -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); diff --git a/tgt-vvp/vvp_scope.c b/tgt-vvp/vvp_scope.c index 5600e1abd..a8753ba7e 100644 --- a/tgt-vvp/vvp_scope.c +++ b/tgt-vvp/vvp_scope.c @@ -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); diff --git a/vpi/Makefile.in b/vpi/Makefile.in index 4fdee22d6..59291fa5f 100644 --- a/vpi/Makefile.in +++ b/vpi/Makefile.in @@ -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 diff --git a/vpi/sys_evcd.c b/vpi/sys_evcd.c new file mode 100644 index 000000000..a499bd7f5 --- /dev/null +++ b/vpi/sys_evcd.c @@ -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 +# include +# include +# include +# include +# 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< 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 < + vector -> p < + + 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 < $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: # $dumpports $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); +} diff --git a/vpi/sys_icarus.c b/vpi/sys_icarus.c index d3b0bb005..6c2ba899b 100644 --- a/vpi/sys_icarus.c +++ b/vpi/sys_icarus.c @@ -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). */ diff --git a/vpi/sys_table.c b/vpi/sys_table.c index 1067b238c..48a078282 100644 --- a/vpi/sys_table.c +++ b/vpi/sys_table.c @@ -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, diff --git a/vpi_user.h b/vpi_user.h index c347fe15e..7e2bc94a7 100644 --- a/vpi_user.h +++ b/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 diff --git a/vvp/compile.cc b/vvp/compile.cc index fa5391717..f6eb2d424 100644 --- a/vvp/compile.cc +++ b/vvp/compile.cc @@ -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(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) diff --git a/vvp/compile.h b/vvp/compile.h index f2e949d66..0408bd100 100644 --- a/vvp/compile.h +++ b/vvp/compile.h @@ -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 ); /* diff --git a/vvp/lexor.lex b/vvp/lexor.lex index 1a6ce88ef..504784eb0 100644 --- a/vvp/lexor.lex +++ b/vvp/lexor.lex @@ -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; } diff --git a/vvp/parse.y b/vvp/parse.y index 2fc3dc93c..fb772d558 100644 --- a/vvp/parse.y +++ b/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); } diff --git a/vvp/resolv.cc b/vvp/resolv.cc index ab9f03f44..593426959 100644 --- a/vvp/resolv.cc +++ b/vvp/resolv.cc @@ -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_); diff --git a/vvp/resolv.h b/vvp/resolv.h index a7095a056..21eb90e63 100644 --- a/vvp/resolv.h +++ b/vvp/resolv.h @@ -21,6 +21,9 @@ # include "config.h" # include "vvp_net.h" +# include + +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; diff --git a/vvp/vpi_priv.cc b/vvp/vpi_priv.cc index c3aabdfa9..105d48c40 100644 --- a/vvp/vpi_priv.cc +++ b/vvp/vpi_priv.cc @@ -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: diff --git a/vvp/vpi_priv.h b/vvp/vpi_priv.h index 3b253c999..9a8eaec3e 100644 --- a/vvp/vpi_priv.h +++ b/vvp/vpi_priv.h @@ -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(); diff --git a/vvp/vpi_scope.cc b/vvp/vpi_scope.cc index 9ad25320e..cd4619996 100644 --- a/vvp/vpi_scope.cc +++ b/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 diff --git a/vvp/vpi_signal.cc b/vvp/vpi_signal.cc index aeb41f424..b73696a16 100644 --- a/vvp/vpi_signal.cc +++ b/vvp/vpi_signal.cc @@ -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 + (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(rfp->node->fun) : 0; + if (core == 0 || core->driver_count() == 0) + return 0; + unsigned n = core->driver_count(); + vpiHandle*args = static_cast(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; }