From 7a9563db57ef2c3f46e5b82c5a92ef26556e49b7 Mon Sep 17 00:00:00 2001 From: Edmund Lam Date: Tue, 9 Jun 2026 11:28:39 -0400 Subject: [PATCH] more tests and fixes --- src/V3Param.cpp | 64 ++++++++++++++--- .../t/t_class_lparam_generate_chain.py | 18 +++++ .../t/t_class_lparam_generate_chain.v | 65 +++++++++++++++++ test_regress/t/t_class_lparam_iface_chain.py | 18 +++++ test_regress/t/t_class_lparam_iface_chain.v | 66 +++++++++++++++++ test_regress/t/t_class_lparam_module_chain.py | 18 +++++ test_regress/t/t_class_lparam_module_chain.v | 71 +++++++++++++++++++ .../t/t_class_lparam_typedef_chain.py | 18 +++++ test_regress/t/t_class_lparam_typedef_chain.v | 60 ++++++++++++++++ 9 files changed, 390 insertions(+), 8 deletions(-) create mode 100755 test_regress/t/t_class_lparam_generate_chain.py create mode 100644 test_regress/t/t_class_lparam_generate_chain.v create mode 100755 test_regress/t/t_class_lparam_iface_chain.py create mode 100644 test_regress/t/t_class_lparam_iface_chain.v create mode 100755 test_regress/t/t_class_lparam_module_chain.py create mode 100644 test_regress/t/t_class_lparam_module_chain.v create mode 100755 test_regress/t/t_class_lparam_typedef_chain.py create mode 100644 test_regress/t/t_class_lparam_typedef_chain.v diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 063c55d3a..fc584e499 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -2178,6 +2178,50 @@ public: }); } + // Resolve `class::member` Dots in `rootp`'s tree, and also in any deferred + // lparam reachable transitively through VarRefs from `rootp`. By V3Param + // time the surviving such Dots are paramed-class refs that need + // linkDotParamed for scope, but `resolveDotToTypedef` can handle the + // typedef-aliased subset inline. Resolving them here turns a deferred + // lparam's value into a Const so subsequent constify on `rootp` can fold + // expressions that reference the lparam. + // `modp` is the enclosing module — resolveDotToTypedef may call into + // classRefDeparam which needs m_modp set for level/recursion bookkeeping. + void resolveDeferredDotsReachableFrom(AstNode* rootp, AstNodeModule* modp) { + std::vector dotps; + const auto& deferredVarps = v3Global.rootp()->deferredParamVarps(); + std::set reachedDeferred; + auto collectDots = [&](AstNode* p) { + p->foreach([&](AstDot* dotp) { + if (VN_IS(dotp->lhsp(), ClassOrPackageRef)) dotps.push_back(dotp); + }); + }; + std::vector workVarps; + auto enqueueRefs = [&](AstNode* p) { + p->foreach([&](const AstVarRef* refp) { + AstVar* const refVarp = refp->varp(); + if (refVarp && refVarp->varType() == VVarType::LPARAM + && deferredVarps.count(refVarp) && reachedDeferred.insert(refVarp).second) { + workVarps.push_back(refVarp); + } + }); + }; + collectDots(rootp); + enqueueRefs(rootp); + while (!workVarps.empty()) { + AstVar* const varp = workVarps.back(); + workVarps.pop_back(); + if (!varp->valuep()) continue; + collectDots(varp->valuep()); + enqueueRefs(varp->valuep()); + } + if (dotps.empty()) return; + VL_RESTORER(m_modp); + m_modp = modp; + // Reverse-iterate so inner Dots resolve before outer. + for (auto it = dotps.rbegin(); it != dotps.rend(); ++it) resolveDotToTypedef(*it); + } + AstNodeModule* nodeDeparam(AstNode* nodep, AstNodeModule* srcModp, AstNodeModule* modp, const string& someInstanceName) { // Return new or reused de-parameterized module @@ -2218,14 +2262,9 @@ public: } // Create new module name with _'s between the constants UINFOTREE(10, nodep, "", "cell"); - // Resolve `class::member` Dots in pin values so constify sees Consts. - // Single-pass: filter-collect class-scoped Dots, then reverse-iterate so inner - // Dots resolve before outer (vector stays empty for cells with no class Dots). - std::vector dotps; - nodep->foreach([&](AstDot* dotp) { - if (VN_IS(dotp->lhsp(), ClassOrPackageRef)) dotps.push_back(dotp); - }); - for (auto it = dotps.rbegin(); it != dotps.rend(); ++it) resolveDotToTypedef(*it); + // Resolve `class::member` Dots in pin values, and in any deferred + // lparam reachable from the pin tree, so constify sees Consts. + resolveDeferredDotsReachableFrom(nodep, modp); // Evaluate all module constants V3Const::constifyParamsEdit(nodep); // Set name for warnings for when we param propagate the module @@ -3173,6 +3212,9 @@ class ParamVisitor final : public VNVisitor { void visit(AstGenIf* nodep) override { UINFO(9, " GENIF " << nodep); iterateAndNextNull(nodep->condp()); + // condp may reference deferred lparams whose Dot is still pending; + // resolve them so widthing/constify can see Consts. + m_processor.resolveDeferredDotsReachableFrom(nodep->condp(), m_modp); // We suppress errors when widthing params since short-circuiting in // the conditional evaluation may mean these error can never occur. We // then make sure that short-circuiting is used by constifyParamsEdit. @@ -3208,6 +3250,9 @@ class ParamVisitor final : public VNVisitor { iterateAndNextNull(forp->initsp()); iterateAndNextNull(forp->condp()); iterateAndNextNull(forp->incsp()); + // Cond/init/inc may reference deferred lparams whose Dot is still + // pending; resolve them so widthing/constify can see Consts. + m_processor.resolveDeferredDotsReachableFrom(forp, m_modp); V3Width::widthParamsEdit(forp); // Param typed widthing will NOT recurse the body // Outer wrapper around generate used to hold genvar, and to ensure genvar // doesn't conflict in V3LinkDot resolution with other genvars @@ -3241,6 +3286,9 @@ class ParamVisitor final : public VNVisitor { AstNode* keepp = nullptr; iterateAndNextNull(nodep->exprp()); V3Case::caseLint(nodep); + // expr / case items may reference deferred lparams whose Dot is still + // pending; resolve them so widthing/constify can see Consts. + m_processor.resolveDeferredDotsReachableFrom(nodep, m_modp); V3Width::widthParamsEdit(nodep); // Param typed widthing will NOT recurse the // body, don't trigger errors yet. V3Const::constifyParamsEdit(nodep->exprp()); // exprp may change diff --git a/test_regress/t/t_class_lparam_generate_chain.py b/test_regress/t/t_class_lparam_generate_chain.py new file mode 100755 index 000000000..33b3f6c52 --- /dev/null +++ b/test_regress/t/t_class_lparam_generate_chain.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Edmund Lam +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_lparam_generate_chain.v b/test_regress/t/t_class_lparam_generate_chain.v new file mode 100644 index 000000000..9b2074178 --- /dev/null +++ b/test_regress/t/t_class_lparam_generate_chain.v @@ -0,0 +1,65 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Generate-for / generate-if / generate-case conditions that reference +// a class-scope-resolved deferred localparam (e.g. inst::b). V3Param's +// visit(AstGenIf/Block/Case) calls V3Width::widthParamsEdit / +// constify on the cond/expr; the same transitive Dot-resolution used +// for cell pins must be applied here so the deferred lparam folds. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Edmund Lam +// 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 + +// Cell whose presence is observable, used inside generate blocks +// below to confirm each generate flavour elaborated the right way. +module Tag #( + parameter int ID +) (); + initial $write("Tag ID=%0d\n", ID); +endmodule + +module t; + virtual class C #( + parameter int a + ); + localparam int b = a; + endclass + + typedef C#(3) c3; + typedef C#(5) c5; + + // Deferred lparams + localparam int b3 = c3::b; + localparam int b5 = c5::b; + + // (1) Generate-for bound = deferred lparam + for (genvar i = 0; i < b3; i++) begin : gf + Tag #(100 + i) inst (); + end + + // (2) Generate-if cond = deferred lparam + if (b5 > b3) begin : gi_t + Tag #(200) inst (); + end else begin : gi_f + Tag #(201) inst (); + end + + // (3) Generate-case selector = deferred lparam + case (b5) + 3: begin : gc Tag #(303) inst (); end + 5: begin : gc Tag #(305) inst (); end + default: begin : gc Tag #(399) inst (); end + endcase + + initial begin + `checkh(b3, 32'd3); + `checkh(b5, 32'd5); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_lparam_iface_chain.py b/test_regress/t/t_class_lparam_iface_chain.py new file mode 100755 index 000000000..33b3f6c52 --- /dev/null +++ b/test_regress/t/t_class_lparam_iface_chain.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Edmund Lam +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_lparam_iface_chain.v b/test_regress/t/t_class_lparam_iface_chain.v new file mode 100644 index 000000000..0fb5b8ff7 --- /dev/null +++ b/test_regress/t/t_class_lparam_iface_chain.v @@ -0,0 +1,66 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Interface-instance parameter pins whose value chains through a +// class-scope-resolved localparam (typedef alias of a parameterized +// class, e.g. inst::b). Same cell-deparam fix that handles module +// instances must also handle interface instances and downstream +// port-bound consumers. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Edmund Lam +// 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 + +interface SubIface #( + parameter int IW +) (); + logic [IW-1:0] data; +endinterface + +// Module that takes an interface port — must elaborate after the iface +// cell pin is fully constified. +module Consumer ( + SubIface si +); +endmodule + +module t; + virtual class C #( + parameter int a + ); + localparam int b = a; + endclass + + typedef C#(8) c8; + typedef C#(13) c13; + + // Deferred lparams + localparam int b8 = c8::b; + localparam int b13 = c13::b; + // Chained + localparam int b8_alias = b8; + + // Iface pin = bare VarRef to deferred lparam + SubIface #(b8) i_bare (); + // Iface pin = chained VarRef + SubIface #(b8_alias) i_chain (); + // Iface pin = expression mixing deferred lparams + SubIface #(b8 + b13) i_expr (); + // Iface used as a module port — exercises the iface-cell-first path + Consumer cons (.si(i_bare)); + + initial begin + `checkh(b8, 32'd8); + `checkh(b13, 32'd13); + `checkh(b8_alias, 32'd8); + `checkh($bits(i_bare.data), 32'd8); + `checkh($bits(i_chain.data), 32'd8); + `checkh($bits(i_expr.data), 32'd21); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_lparam_module_chain.py b/test_regress/t/t_class_lparam_module_chain.py new file mode 100755 index 000000000..33b3f6c52 --- /dev/null +++ b/test_regress/t/t_class_lparam_module_chain.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Edmund Lam +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_lparam_module_chain.v b/test_regress/t/t_class_lparam_module_chain.v new file mode 100644 index 000000000..c2d6667e8 --- /dev/null +++ b/test_regress/t/t_class_lparam_module_chain.v @@ -0,0 +1,71 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Module-instance parameter pins whose value chains through a +// class-scope-resolved localparam (typedef alias of a parameterized +// class, e.g. inst::b). V3LinkDot defers the inst::b Dot until +// post-V3Param; the cell deparam must follow VarRefs into any +// deferred lparam reachable from the pin tree and resolve its Dots +// inline so constify on the cell can fold. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Edmund Lam +// 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 Sub #( + parameter int WIDTH +) (); +endmodule + +// Two-level: outer module forwards its param to an inner cell, so the +// deferred lparam value must flow through the cell-deparam chain. +module SubL1 #( + parameter int W +) (); + Sub #(W) inner (); +endmodule + +module t; + virtual class C #( + parameter int a + ); + localparam int b = a; + endclass + + typedef C#(4) c4; + typedef C#(5) c5; + typedef C#(8) c8; + + // Deferred lparams (value contains class::member Dot) + localparam int b4 = c4::b; + localparam int b5 = c5::b; + localparam int b8 = c8::b; + // Chained: value references another deferred lparam + localparam int c8_ref = b8; + localparam int d8_ref = c8_ref + 1; + + // Pin is bare VarRef to a deferred lparam + Sub #(b8) m_bare (); + // Pin chains through multiple deferred lparams (b8 -> c8_ref -> d8_ref) + Sub #(d8_ref) m_chain (); + // Pin is an expression mixing two deferred lparams + Sub #(b4 + b5) m_expr (); + // Pin mixes a Dot and a deferred lparam in the same expression + Sub #(c4::b + b5) m_mix (); + // Two-level: outer module forwards param to inner cell + SubL1 #(b8) m_l1 (); + + initial begin + `checkh(b4, 32'd4); + `checkh(b5, 32'd5); + `checkh(b8, 32'd8); + `checkh(c8_ref, 32'd8); + `checkh(d8_ref, 32'd9); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_lparam_typedef_chain.py b/test_regress/t/t_class_lparam_typedef_chain.py new file mode 100755 index 000000000..33b3f6c52 --- /dev/null +++ b/test_regress/t/t_class_lparam_typedef_chain.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Edmund Lam +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_lparam_typedef_chain.v b/test_regress/t/t_class_lparam_typedef_chain.v new file mode 100644 index 000000000..98ed4457a --- /dev/null +++ b/test_regress/t/t_class_lparam_typedef_chain.v @@ -0,0 +1,60 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Typedefs and class type parameters that resolve through a +// class-scope-resolved localparam (typedef alias of a parameterized +// class, e.g. inst::b). Exercises three sub-cases: +// - Deferred lparam used as a packed range bound in a typedef +// - Deferred lparam used as a value argument to a parameterized class +// - Class-scope-resolved typedef that itself depends on the param +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Edmund Lam +// 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; + virtual class C #( + parameter int a + ); + localparam int b = a; + typedef logic [a-1:0] inner_t; + // localparam derived from a typedef inside the same class — + // resolveDotToTypedef must constify this when reached via CFG::width. + localparam int width = $bits(inner_t); + endclass + + typedef C#(8) c8; + + // Deferred lparam (direct Dot) + localparam int b8 = c8::b; + + // (1) typedef range driven by a deferred lparam + typedef logic [b8-1:0] data_t; + data_t data_value; + + // (2) class type-arg uses a deferred lparam value + typedef C#(b8) c_from_def; + localparam int from_def_b = c_from_def::b; + + // (3) class-scope-resolved typedef computed via $bits(inner_t) + localparam int via_bits = c8::width; + + // Wire whose range comes from a deferred lparam + logic [b8-1:0] wide_bus; + + initial begin + `checkh(b8, 32'd8); + `checkh(from_def_b, 32'd8); + `checkh(via_bits, 32'd8); + data_value = '1; + `checkh(data_value, 8'hff); + wide_bus = '1; + `checkh(wide_bus, 8'hff); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule