Commentary: Changes update

This commit is contained in:
Wilson Snyder 2026-03-18 20:35:08 -04:00
parent 087cabcf35
commit 7b2277f584
9 changed files with 173 additions and 160 deletions

View File

@ -33,6 +33,7 @@ Verilator 5.047 devel
* Support dist and solve...before inside foreach constraints (#7245) (#7253). [Yilou Wang]
* Support array and struct info metadata in FST traces (#7255). [Geza Lore, Testorrent USA, Inc.]
* Support dynamic array .size in inline randomize() with constraints (#7258) (#7266). [Yilou Wang]
* Support modport export/import task prototypes and out-of-block definitions (#7277). [Yilou Wang]
* Add VPI callback support to --main (#7145).
* Add V3LiftExpr pass to lower impure expressions and calls (#7141) (#7164). [Geza Lore, Testorrent USA, Inc.]
* Add --func-recursion-depth CLI option (#7175) (#7179).

View File

@ -594,16 +594,15 @@ object.
This class manages processes that await events (triggers). There is one
such object per each trigger awaited by coroutines. Coroutines ``co_await``
this object's ``trigger`` function. They are stored in three stages -
`awaiting`, `fired` and `toResume`. First, they land in the `awaiting` stage, and
cannot be resumed. The ``ready`` function moves all coroutines from the
`awaiting` stage into the `fired` stage. The ``moveToResumeQueue`` function moves
`fired` coroutines into `toResume`. Finally, function `resume` resumes
all coroutines from the `toResume` stage.
`awaiting`, `fired` and `toResume`. First, they land in the `awaiting`
stage, and cannot be resumed. The ``ready`` function moves all coroutines
from the `awaiting` stage into the `fired` stage. The ``moveToResumeQueue``
function moves `fired` coroutines into `toResume`. Finally, function
`resume` resumes all coroutines from the `toResume` stage.
This split is done to avoid self-triggering, triggering coroutines
multiple times and triggering coroutines in the same iteration
they were suspended. See the `Scheduling with timing` section
for details on how this is used.
This split is done to avoid self-triggering, triggering coroutines multiple
times and triggering coroutines in the same iteration they were suspended.
See the `Scheduling with timing` section for details on how this is used.
``VlDynamicTriggerScheduler``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -1844,33 +1843,25 @@ algorithmic stage. An example:
The following summarizes the above example dump, with more detail on each
field in the section below.
+---------------+--------------------------------------------------------+
| ``1:2:`` | The hierarchy of the ``VAR`` is the ``op2p`` |
| | pointer under the ``MODULE``, which in turn is the |
| | ``op1p`` pointer under the ``NETLIST``. |
+---------------+--------------------------------------------------------+
| ``VAR`` | The AstNodeType (e.g. ``AstVar``). |
+---------------+--------------------------------------------------------+
| ``0x91a780`` | Address of this node. |
+---------------+--------------------------------------------------------+
| ``<e74>`` | The 74th edit to the netlist was the last |
| | modification to this node. |
+---------------+--------------------------------------------------------+
| ``{a22ah}`` | This node is related to the source filename |
| | "a", where "a" is the first file read, "z" the 26th, |
| | and "aa" the 27th. Then line 22 in that file, then |
| | column 8 (aa=0, az=25, ba=26, ...). |
+---------------+--------------------------------------------------------+
| ``@dt=0x...`` | The address of the data type this node references. |
+---------------+--------------------------------------------------------+
| ``w32`` | The data-type width() is 32 bits. |
+---------------+--------------------------------------------------------+
| ``out_wide`` | The name() of the node, in this case, the name of the |
| | variable. |
+---------------+--------------------------------------------------------+
| ``[O]`` | Flags which vary with the type of node, in this |
| | case of a VAR, it means the variable is an output. |
+---------------+--------------------------------------------------------+
============= =============================================================
``1:2:`` The hierarchy of the ``VAR`` is the ``op2p`` pointer under
the ``MODULE``, which in turn is the ``op1p`` pointer under
the ``NETLIST``.
``VAR`` The AstNodeType (e.g. ``AstVar``).
``0x91a780`` Address of this node.
``<e74>`` The 74th edit to the netlist was the last modification to
this node.
``{a22ah}`` This node is related to the source filename "a", where "a" is
the first file read, "z" the 26th, and "aa" the 27th. Then
line 22 in that file, then column 8 (aa=0, az=25, ba=26,
...).
``@dt=0x...`` The address of the data type this node references.
``w32`` The data-type width() is 32 bits.
``out_wide`` The name() of the node, in this case, the name of the
variable.
``[O]`` Flags which vary with the type of node, in this case of a
VAR, it means the variable is an output.
============= =============================================================
In more detail, the following fields are dumped common to all nodes. They
are produced by the ``AstNode::dump()`` method:

View File

@ -36,7 +36,8 @@ module t;
arr4 = '{10, 20, 30, 40};
if (arr4 == '{10, 20, 30, 40}) begin
// expected
end else begin
end
else begin
$display("FAIL: arr4 == pattern");
$stop;
end
@ -44,7 +45,8 @@ module t;
// 2. Fixed-size array: NEQ with assignment pattern
if (arr4 != '{10, 20, 30, 99}) begin
// expected
end else begin
end
else begin
$display("FAIL: arr4 != pattern");
$stop;
end
@ -52,7 +54,8 @@ module t;
// 3. Pattern on LHS of comparison
if ('{10, 20, 30, 40} == arr4) begin
// expected
end else begin
end
else begin
$display("FAIL: pattern == arr4 (LHS)");
$stop;
end
@ -60,7 +63,8 @@ module t;
// 4. Pattern on LHS of NEQ
if ('{10, 20, 30, 99} != arr4) begin
// expected
end else begin
end
else begin
$display("FAIL: pattern != arr4 (LHS NEQ)");
$stop;
end
@ -72,7 +76,8 @@ module t;
sub = q[1:3];
if (sub == '{20, 30, 40}) begin
// expected
end else begin
end
else begin
$display("FAIL: queue slice == pattern");
$stop;
end
@ -80,7 +85,8 @@ module t;
// 6. Queue: NEQ with assignment pattern
if (sub != '{20, 30, 99}) begin
// expected
end else begin
end
else begin
$display("FAIL: queue slice != pattern");
$stop;
end
@ -91,7 +97,8 @@ module t;
p1 = '{a: 8'hAA, b: 8'h55};
if (p1 == '{a: 8'hAA, b: 8'h55}) begin
// expected
end else begin
end
else begin
$display("FAIL: struct == pattern");
$stop;
end
@ -99,7 +106,8 @@ module t;
// 8. Struct: NEQ with assignment pattern
if (p1 != '{a: 8'hAA, b: 8'hFF}) begin
// expected
end else begin
end
else begin
$display("FAIL: struct != pattern");
$stop;
end
@ -111,7 +119,8 @@ module t;
parr[1] = '{a: 8'h03, b: 8'h04};
if (parr == '{'{a: 8'h01, b: 8'h02}, '{a: 8'h03, b: 8'h04}}) begin
// expected
end else begin
end
else begin
$display("FAIL: array of structs == nested pattern");
$stop;
end
@ -122,7 +131,8 @@ module t;
wide_arr = '{32'hDEAD_BEEF, 32'hCAFE_BABE, 32'h1234_5678};
if (wide_arr == '{32'hDEAD_BEEF, 32'hCAFE_BABE, 32'h1234_5678}) begin
// expected
end else begin
end
else begin
$display("FAIL: wide array == pattern");
$stop;
end
@ -130,7 +140,8 @@ module t;
byte_arr = '{8'hAB, 8'hCD};
if (byte_arr == '{8'hAB, 8'hCD}) begin
// expected
end else begin
end
else begin
$display("FAIL: byte array == pattern");
$stop;
end
@ -148,14 +159,16 @@ module t;
// -------------------------------------------------------
if (arr4_t'{10, 20, 30, 40} == arr4_t'{10, 20, 30, 40}) begin
// expected
end else begin
end
else begin
$display("FAIL: typed pattern == typed pattern");
$stop;
end
if (arr4_t'{10, 20, 30, 40} != arr4_t'{10, 20, 30, 99}) begin
// expected
end else begin
end
else begin
$display("FAIL: typed pattern != typed pattern");
$stop;
end

View File

@ -14,21 +14,21 @@ module t;
CROSS_NEG2 = -2,
CROSS_NEG1, // -1
CROSS_ZERO, // 0
CROSS_POS1 // 1
CROSS_POS1 // 1
} cross_zero_e;
// Case 2: signed enum starting at zero
typedef enum int {
START_ZERO = 0,
START_ONE, // 1
START_TWO // 2
START_ONE, // 1
START_TWO // 2
} start_zero_e;
// Case 3: signed enum all negative
typedef enum int {
ALL_NEG3 = -3,
ALL_NEG2, // -2
ALL_NEG1 // -1
ALL_NEG1 // -1
} all_neg_e;
// Case 4: signed enum starting at large negative, crossing zero
@ -38,19 +38,17 @@ module t;
WIDE_NEG1, // -1
WIDE_ZERO, // 0
WIDE_POS1, // 1
WIDE_POS2 // 2
WIDE_POS2 // 2
} wide_cross_e;
// Case 5: signed enum single value at zero
typedef enum int {
SINGLE_ZERO = 0
} single_zero_e;
typedef enum int {SINGLE_ZERO = 0} single_zero_e;
// Case 6: signed enum starting at -1, crossing zero
typedef enum int {
FROM_NEG1 = -1,
FROM_ZERO, // 0
FROM_POS1 // 1
FROM_POS1 // 1
} from_neg1_e;
initial begin

View File

@ -20,7 +20,7 @@ endpackage
// Inner types interface (mirrors xyz_types_if).
// Provides types derived from cfg that the outer interface imports.
interface inner_types_if #(
parameter cfg3_pkg::cfg_t cfg = '0
parameter cfg3_pkg::cfg_t cfg = '0
) ();
typedef logic [$clog2(cfg.NumIds)-1:0] trans_id_t;
typedef logic [$clog2(cfg.NumThreads)-1:0] tl_index_t;
@ -30,13 +30,13 @@ endinterface
// Instantiates inner_types_if, imports types from it, and builds
// compound struct typedefs that include those imported types.
interface outer_types_if #(
parameter cfg3_pkg::cfg_t cfg = '0
parameter cfg3_pkg::cfg_t cfg = '0
) ();
localparam int NUM_ROWS = (cfg.Capacity / cfg.Slices) / 8;
typedef logic [$clog2(NUM_ROWS)-1:0] row_addr_t;
// Nested interface - this is the trigger.
inner_types_if #(cfg) inner_types();
inner_types_if #(cfg) inner_types ();
typedef inner_types.trans_id_t trans_id_t;
typedef inner_types.tl_index_t tl_index_t;
@ -57,39 +57,39 @@ interface outer_types_if #(
} addr_t;
typedef struct packed {
logic en;
trans_id_t tag;
tl_index_t tl;
logic is_read;
logic needs_resp;
logic en;
trans_id_t tag;
tl_index_t tl;
logic is_read;
logic needs_resp;
} meta_t;
typedef struct packed {
meta_t meta;
addr_t addr;
meta_t meta;
addr_t addr;
logic [63:0] data;
} rq_t;
// Compound packet type (mirrors iq_pkt_t)
typedef struct packed {
pkt_hdr_t hdr;
rq_t payload;
rq_t payload;
} pkt_t;
endinterface
// Width-parameterized FIFO (mirrors ring_fifo).
module fifo3 #(
parameter int p_width = 1,
parameter int p_depth = 2
parameter int p_width = 1,
parameter int p_depth = 2
) (
input logic clk,
input logic rst_n,
input logic [p_width-1:0] push_dat_i,
input logic push_vld_i,
output logic [p_width-1:0] front_dat_o,
output logic not_empty_o
input logic clk,
input logic rst_n,
input logic [p_width-1:0] push_dat_i,
input logic push_vld_i,
output logic [p_width-1:0] front_dat_o,
output logic not_empty_o
);
logic [p_width-1:0] mem [p_depth];
logic [p_width-1:0] mem[p_depth];
logic [$clog2(p_depth)-1:0] wptr, rptr;
logic [p_depth:0] count;
assign not_empty_o = (count != 0);
@ -99,7 +99,8 @@ module fifo3 #(
wptr <= '0;
rptr <= '0;
count <= '0;
end else if (push_vld_i) begin
end
else if (push_vld_i) begin
mem[wptr] <= push_dat_i;
wptr <= wptr + 1;
count <= count + 1;
@ -114,19 +115,19 @@ endmodule
// cell (in wrapper3's cell loop), before outer_types_if__Az1's nested
// inner_types_if is deparameterized.
module slice3 #(
parameter cfg3_pkg::cfg_t cfg = '0,
parameter int SLICE_IDX = 0,
parameter type iq_pkt_t = logic,
parameter type oq_pkt_t = logic
parameter cfg3_pkg::cfg_t cfg = '0,
parameter int SLICE_IDX = 0,
parameter type iq_pkt_t = logic,
parameter type oq_pkt_t = logic
) (
input logic clk,
input logic rst_n,
input logic in_vld,
output logic out_vld,
output iq_pkt_t fe_ot_pkt_o
input logic clk,
input logic rst_n,
input logic in_vld,
output logic out_vld,
output iq_pkt_t fe_ot_pkt_o
);
// Local outer_types_if for other local types
outer_types_if #(cfg) types();
outer_types_if #(cfg) types ();
typedef types.pkt_nid_t pkt_nid_t;
iq_pkt_t rqq_in, rqq_ot;
@ -149,15 +150,15 @@ module slice3 #(
// the nested inner_types_if inside outer_types_if__Az1 hasn't
// been deparameterized, so trans_id_t/tl_index_t have wrong widths.
fifo3 #(
.p_width($bits(iq_pkt_t)),
.p_depth(4)
.p_width($bits(iq_pkt_t)),
.p_depth(4)
) rqq (
.clk(clk),
.rst_n(rst_n),
.push_dat_i(rqq_in),
.push_vld_i(in_vld),
.front_dat_o(rqq_ot),
.not_empty_o(rqq_ot_vld)
.clk(clk),
.rst_n(rst_n),
.push_dat_i(rqq_in),
.push_vld_i(in_vld),
.front_dat_o(rqq_ot),
.not_empty_o(rqq_ot_vld)
);
assign fe_ot_pkt_o = rqq_ot;
@ -168,14 +169,14 @@ endmodule
// Creates outer_types_if, imports pkt_t, and passes it as a type
// parameter to slice3 instances in a generate loop.
module wrapper3 #(
parameter cfg3_pkg::cfg_t cfg = '0
parameter cfg3_pkg::cfg_t cfg = '0
) (
input logic clk,
input logic rst_n
input logic clk,
input logic rst_n
);
outer_types_if #(cfg) types();
outer_types_if #(cfg) types ();
typedef types.pkt_t iq_pkt_t;
typedef types.rq_t oq_pkt_t;
typedef types.rq_t oq_pkt_t;
// Capture $bits of the type parameter for external checking.
// If the nested inner_types_if hasn't been deparameterized before
@ -188,16 +189,16 @@ module wrapper3 #(
for (genvar i = 0; i < cfg.NumThreads; i++) begin : gen_slices
iq_pkt_t fe_pkt;
slice3 #(
.cfg(cfg),
.SLICE_IDX(i),
.iq_pkt_t(iq_pkt_t),
.oq_pkt_t(oq_pkt_t)
.cfg(cfg),
.SLICE_IDX(i),
.iq_pkt_t(iq_pkt_t),
.oq_pkt_t(oq_pkt_t)
) u_slice (
.clk(clk),
.rst_n(rst_n),
.in_vld(1'b1),
.out_vld(out_vld[i]),
.fe_ot_pkt_o(fe_pkt)
.clk(clk),
.rst_n(rst_n),
.in_vld(1'b1),
.out_vld(out_vld[i]),
.fe_ot_pkt_o(fe_pkt)
);
end
endgenerate
@ -210,12 +211,7 @@ module t;
int cyc = 0;
localparam cfg3_pkg::cfg_t MY_CFG = '{
Capacity: 8192,
Slices: 8,
NumThreads: 4,
NumIds: 16
};
localparam cfg3_pkg::cfg_t MY_CFG = '{Capacity: 8192, Slices: 8, NumThreads: 4, NumIds: 16};
// Expected widths:
// trans_id_t = $clog2(16) = 4 bits
// tl_index_t = $clog2(4) = 2 bits
@ -231,19 +227,21 @@ module t;
// These computations happen in the testbench module context where
// the cfg parameter values are known constants, not through the
// nested interface PARAMTYPEDTYPE chain.
localparam int EXP_TRANS_ID_W = $clog2(MY_CFG.NumIds); // 4
localparam int EXP_TL_INDEX_W = $clog2(MY_CFG.NumThreads); // 2
localparam int EXP_ROW_ADDR_W = $clog2((MY_CFG.Capacity / MY_CFG.Slices) / 8); // 7
localparam int EXP_PKT_NID_W = $clog2(MY_CFG.NumThreads); // 2
localparam int EXP_PKT_HDR_W = 2 * EXP_PKT_NID_W; // 4
localparam int EXP_ADDR_W = EXP_ROW_ADDR_W + EXP_TL_INDEX_W + 6; // 15
localparam int EXP_META_W = 1 + EXP_TRANS_ID_W + EXP_TL_INDEX_W + 1 + 1; // 9
localparam int EXP_RQ_W = EXP_META_W + EXP_ADDR_W + 64; // 88
localparam int EXP_PKT_W = EXP_PKT_HDR_W + EXP_RQ_W; // 92
localparam int EXP_TRANS_ID_W = $clog2(MY_CFG.NumIds); // 4
localparam int EXP_TL_INDEX_W = $clog2(MY_CFG.NumThreads); // 2
localparam int EXP_ROW_ADDR_W = $clog2((MY_CFG.Capacity / MY_CFG.Slices) / 8); // 7
localparam int EXP_PKT_NID_W = $clog2(MY_CFG.NumThreads); // 2
localparam int EXP_PKT_HDR_W = 2 * EXP_PKT_NID_W; // 4
localparam int EXP_ADDR_W = EXP_ROW_ADDR_W + EXP_TL_INDEX_W + 6; // 15
localparam int EXP_META_W = 1 + EXP_TRANS_ID_W + EXP_TL_INDEX_W + 1 + 1; // 9
localparam int EXP_RQ_W = EXP_META_W + EXP_ADDR_W + 64; // 88
localparam int EXP_PKT_W = EXP_PKT_HDR_W + EXP_RQ_W; // 92
wrapper3 #(.cfg(MY_CFG)) u_wrapper (
.clk(clk),
.rst_n(rst_n)
wrapper3 #(
.cfg(MY_CFG)
) u_wrapper (
.clk(clk),
.rst_n(rst_n)
);
// Self-check: verify that $bits(pkt_t) as seen through the nested
@ -253,7 +251,8 @@ module t;
// cfg.NumIds=0 instead of the actual value 16.
initial begin
if (u_wrapper.PKT_WIDTH != EXP_PKT_W) begin
$display("%%Error: t_iface_nested_width3.v: $bits(pkt_t) = %0d, expected %0d", u_wrapper.PKT_WIDTH, EXP_PKT_W);
$display("%%Error: t_iface_nested_width3.v: $bits(pkt_t) = %0d, expected %0d",
u_wrapper.PKT_WIDTH, EXP_PKT_W);
$stop;
end
end

View File

@ -2,10 +2,10 @@
18 | task clk.send(input logic [7:0] val);
| ^~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_modport_export_bad.v:25:8: Interface port not found for out-of-block definition: 'nonexistent' (IEEE 1800-2023 25.7)
25 | task nonexistent.send(input logic [7:0] val);
%Error: t/t_modport_export_bad.v:27:8: Interface port not found for out-of-block definition: 'nonexistent' (IEEE 1800-2023 25.7)
27 | task nonexistent.send(input logic [7:0] val);
| ^~~~~~~~~~~
%Error: t/t_modport_export_bad.v:40:8: No matching export prototype found in interface bus_if_noexport for task 'send' (IEEE 1800-2023 25.7)
40 | task port.send(input logic [7:0] val);
%Error: t/t_modport_export_bad.v:42:8: No matching export prototype found in interface bus_if_noexport for task 'send' (IEEE 1800-2023 25.7)
42 | task port.send(input logic [7:0] val);
| ^~~~
%Error: Exiting due to

View File

@ -7,21 +7,23 @@
interface bus_if;
logic [7:0] data;
modport provider(
output data,
export task send(input logic [7:0] val)
);
modport provider(output data, export task send(input logic [7:0] val));
endinterface
// Error 1: 'clk' is a plain wire port, not an interface port
module driver1(bus_if.provider port, input logic clk);
module driver1 (
bus_if.provider port,
input logic clk
);
task clk.send(input logic [7:0] val); // lint_off: UNUSED
port.data = val;
endtask
endmodule
// Error 2: 'nonexistent' is not a port on this module
module driver2(bus_if.provider port);
module driver2 (
bus_if.provider port
);
task nonexistent.send(input logic [7:0] val);
port.data = val;
endtask
@ -30,23 +32,26 @@ endmodule
interface bus_if_noexport;
logic [7:0] data;
modport provider(
output data
);
modport provider(output data);
endinterface
// Error 3: no export prototype for 'send' in interface
module driver3(bus_if_noexport.provider port);
module driver3 (
bus_if_noexport.provider port
);
task port.send(input logic [7:0] val);
port.data = val;
endtask
endmodule
module t;
bus_if bif();
bus_if bif ();
logic clk;
driver1 drv1(.port(bif.provider), .clk(clk));
driver2 drv2(.port(bif.provider));
bus_if_noexport bif2();
driver3 drv3(.port(bif2.provider));
driver1 drv1 (
.port(bif.provider),
.clk(clk)
);
driver2 drv2 (.port(bif.provider));
bus_if_noexport bif2 ();
driver3 drv3 (.port(bif2.provider));
endmodule

View File

@ -4,28 +4,32 @@
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0)
// verilog_format: on
interface bus_if;
logic [7:0] data;
logic [7:0] result;
modport provider(
output data,
output result,
export task send(input logic [7:0] val),
export task accumulate(input logic [7:0] a, input logic [7:0] b)
output data,
output result,
export task send(input logic [7:0] val),
export task accumulate(input logic [7:0] a, input logic [7:0] b)
);
modport consumer(
input data,
input result,
import task send(input logic [7:0] val),
import task accumulate(input logic [7:0] a, input logic [7:0] b)
input data,
input result,
import task send(input logic [7:0] val),
import task accumulate(input logic [7:0] a, input logic [7:0] b)
);
endinterface
module driver(bus_if.provider port);
module driver (
bus_if.provider port
);
task port.send(input logic [7:0] val);
port.data = val;
port.result = val + 8'h01;
@ -38,8 +42,8 @@ module driver(bus_if.provider port);
endmodule
module t;
bus_if bif();
driver drv(.port(bif.provider));
bus_if bif ();
driver drv (.port(bif.provider));
initial begin
// Test 1: send -- multiple statements in task body

View File

@ -4,8 +4,10 @@
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
module t;
initial begin