diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 955d22e6a..a6dd7692b 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -67,6 +67,7 @@ Drew Ranck Drew Taussig Driss Hafdi Edgar E. Iglesias +Edmund Lam Eric Mejdrich Eric Müller Eric Rippey diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 72c33a6e7..e3146dbd7 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1288,10 +1288,84 @@ class ParamProcessor final { // Param/lparam member: substitute its constant value so the caller's constify can // succeed. if (varp->isParam() && varp->valuep()) { - if (!VN_IS(varp->valuep(), Const)) V3Const::constifyParamsEdit(varp); + if (!VN_IS(varp->valuep(), Const)) { + // If the value contains nested class::member Dots (e.g. this + // member's expression references another paramed class), resolve + // those first so constify can fold the whole chain. + std::vector nestedDots; + varp->valuep()->foreach([&](AstDot* const dp) { + if (VN_IS(dp->lhsp(), ClassOrPackageRef)) nestedDots.push_back(dp); + }); + for (auto it = nestedDots.rbegin(); it != nestedDots.rend(); ++it) { + resolveDotToTypedef(*it); + } + V3Const::constifyParamsEdit(varp); + } if (AstConst* const constp = VN_CAST(varp->valuep(), Const)) { - dotp->replaceWith(constp->cloneTree(false)); - VL_DO_DANGLING(dotp->deleteTree(), dotp); + // Walk up any outer `.fieldN[.fieldM...]` chain (struct lparam + // member access like `CFG::cfg.jt.cam_type`) and accumulate the + // packed-struct bit offset. Replace the topmost Dot with a + // Sel(constp, lsb, width) matching V3Width::memberSelStruct. + int totalLsb = 0; + int sliceWidth = varp->width(); + AstNodeDType* curDTypep = varp->dtypep(); + AstNode* topp = dotp; + AstDot* outerDotp = VN_CAST(dotp->backp(), Dot); + const auto errorRecover = [&](AstDot* const badp, const string& msg) { + badp->v3error(msg); + badp->replaceWith(new AstConst{badp->fileline(), AstConst::Signed32{}, 0}); + VL_DO_DANGLING(badp->deleteTree(), badp); + }; + bool errored = false; + while (outerDotp && outerDotp->lhsp() == topp) { // LCOV_EXCL_BR_LINE + const AstParseRef* const fieldRefp = VN_CAST(outerDotp->rhsp(), ParseRef); + if (!fieldRefp) { + errorRecover(outerDotp, "Malformed dotted select in parameter value"); + errored = true; + break; + } + UASSERT_OBJ(curDTypep, outerDotp, + "curDTypep null in struct field chain walk"); + AstNodeDType* const skippedp = curDTypep->skipRefp(); + AstNodeUOrStructDType* const structp + = VN_CAST(skippedp, NodeUOrStructDType); + if (!structp) { + errorRecover(outerDotp, + "Dotted member select on non-struct parameter value"); + errored = true; + break; + } + AstMemberDType* const foundMemp = VN_CAST( + m_memberMap.findMember(structp, fieldRefp->name()), MemberDType); + if (!foundMemp) { + errorRecover(outerDotp, "Member " + fieldRefp->prettyNameQ() + + " not found in structure"); + errored = true; + break; + } + UASSERT_OBJ(foundMemp->subDTypep(), outerDotp, + "member has null subDTypep"); + totalLsb += foundMemp->lsb(); + sliceWidth = foundMemp->width(); + curDTypep = foundMemp->subDTypep(); + topp = outerDotp; + outerDotp = VN_CAST(outerDotp->backp(), Dot); + } + if (errored) return; + if (topp == dotp) { + dotp->replaceWith(constp->cloneTree(false)); + VL_DO_DANGLING(dotp->deleteTree(), dotp); + } else { + AstConst* const clonep = static_cast(constp->cloneTree(false)); + AstSel* const selp + = new AstSel{topp->fileline(), clonep, totalLsb, sliceWidth}; + // Match V3Width::memberSelStruct: skip RefDTypes to surface + // enum dtype, and mark didWidth so V3Width doesn't reflatten. + selp->dtypep(curDTypep->skipRefToEnump()); + selp->didWidth(true); + topp->replaceWith(selp); + VL_DO_DANGLING(topp->deleteTree(), topp); + } } } } @@ -2192,6 +2266,58 @@ public: }); } + // Walk every subtree reachable from `rootp` for deferred work V3LinkDot left behind + // when `class#(...)::member` couldn't yet be resolved: + // (1) `AstDot` with a ClassOrPackageRef lhs - a `class::lparam` value Dot to + // constify (e.g. `c8::b` in a pin expression). + // (2) `AstRefDType` with a typedefp - descend into the typedef's subDType + // to find buried Dots (typedef range like `logic [(CFG::w - 1):0]`) and + // unresolved class-scoped struct-member RefDTypes (`CFG::input_meta_t` + // as a struct member). + // (3) `AstVarRef` to a deferred lparam - descend into its valuep so the + // chain `pin -> lparam -> ... -> class::lparam Dot` reaches the Dot. + // Discoveries of (2) and (3) feed back into the walk worklist; (1) and (2) + // are also kept in lists for the resolution passes at the end. + void resolveDeferredDotsReachableFrom(AstNode* rootp, AstNodeModule* modp) { + std::vector dotps; + std::vector tdefps; + const auto& deferredVarps = v3Global.rootp()->deferredParamVarps(); + std::set reachedDeferred; + std::set reachedTypedefs; + std::vector worklist{rootp}; + while (!worklist.empty()) { + AstNode* const curDotp = worklist.back(); + worklist.pop_back(); + curDotp->foreach([&](AstNode* const np) { + if (AstDot* const dotp = VN_CAST(np, Dot)) { + if (VN_IS(dotp->lhsp(), ClassOrPackageRef)) dotps.push_back(dotp); + } else if (const AstRefDType* const refp = VN_CAST(np, RefDType)) { + AstTypedef* const tdefp = refp->typedefp(); + if (tdefp && reachedTypedefs.insert(tdefp).second) { + tdefps.push_back(tdefp); + worklist.push_back(tdefp->subDTypep()); + } + } else if (const AstVarRef* const refp = VN_CAST(np, VarRef)) { + AstVar* const varp = refp->varp(); + if (varp && varp->varType() == VVarType::LPARAM && deferredVarps.count(varp) + && reachedDeferred.insert(varp).second) { + UASSERT_OBJ(varp->valuep(), varp, "VarRef should have non-null valuep"); + worklist.push_back(varp->valuep()); + } + } + }); + } + if (dotps.empty() && tdefps.empty()) return; + VL_RESTORER(m_modp); + m_modp = modp; + // Link unresolved class-scoped RefDTypes buried in any typedef reachable from + // the pin/expr tree (e.g. `$bits(wrap_t)` where `wrap_t`'s struct members + // reference `CFG::data_t` from a parameterized-class typedef alias). + for (AstTypedef* const tdefp : tdefps) resolveParamClassRefDType(tdefp->subDTypep()); + // 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 @@ -2232,14 +2358,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 @@ -3185,6 +3306,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. @@ -3220,6 +3344,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 @@ -3253,6 +3380,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..46d1fe4c0 --- /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 Wilson Snyder +# 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..00f9ed8dd --- /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 Wilson Snyder +// 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..46d1fe4c0 --- /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 Wilson Snyder +# 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..5395f3bf9 --- /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 Wilson Snyder +// 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..46d1fe4c0 --- /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 Wilson Snyder +# 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..09af9308e --- /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 Wilson Snyder +// 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_nested_chain.py b/test_regress/t/t_class_lparam_nested_chain.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_class_lparam_nested_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 Wilson Snyder +# 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_nested_chain.v b/test_regress/t/t_class_lparam_nested_chain.v new file mode 100644 index 000000000..b9607b634 --- /dev/null +++ b/test_regress/t/t_class_lparam_nested_chain.v @@ -0,0 +1,72 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Multi-level class hierarchies where one parameterized class's +// localparam value references another parameterized class via its +// own class-scope Dot (e.g. B::width = inner_a::v where inner_a is +// a typedef of A#(...)). resolveDotToTypedef must recursively resolve +// nested class::member Dots in a member's value before constifying, +// otherwise cell-pin deparam fails for the outer Dot. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// 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 + +module t; + // Two-level: B's lparam value contains a class::member Dot of its own + virtual class A #( + parameter int x + ); + localparam int v = x * 2; + endclass + + virtual class B #( + parameter int y + ); + typedef A#(y + 1) inner_a; + localparam int width = inner_a::v; // nested Dot inside B's lparam + endclass + + // Three-level: C wraps B wraps A + virtual class C #( + parameter int z + ); + typedef B#(z * 3) inner_b; + localparam int total = inner_b::width; // double-nested Dot + endclass + + typedef B#(5) BInst; + typedef C#(2) CInst; + + // (1) Standalone localparam - resolves via deferred-lparam path + localparam int two_level = BInst::width; // = A#(6)::v = 12 + localparam int three_level = CInst::total; // = B#(6)::width = A#(7)::v = 14 + + // (2) Cell pin uses the nested-class value directly - exercises the + // recursive Dot resolution inside resolveDotToTypedef + Sub #(BInst::width) m_pin_direct (); + Sub #(CInst::total) m_pin_deep (); + Sub #(BInst::width + 1) m_pin_expr (); + + // (3) Cell pin chains through a deferred lparam whose value is + // itself a nested-class Dot + localparam int chain = BInst::width; + Sub #(chain) m_pin_chain (); + + initial begin + `checkh(two_level, 32'd12); + `checkh(three_level, 32'd14); + `checkh(chain, 32'd12); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_lparam_struct_field_chain.py b/test_regress/t/t_class_lparam_struct_field_chain.py new file mode 100644 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_field_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 Wilson Snyder +# 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_struct_field_chain.v b/test_regress/t/t_class_lparam_struct_field_chain.v new file mode 100644 index 000000000..d5b924943 --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_field_chain.v @@ -0,0 +1,76 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Struct localparam field access via a class-scope Dot used in a cell +// parameter pin (e.g. `CFG::cfg.jt.cam_type`), where CFG is a typedef +// alias of a parameterized class. V3LinkDot defers the `CFG::cfg` Dot +// until post-V3Param; when the cell is deparameterized, resolveDotToTypedef +// folds the class localparam to a Const and must then walk up the outer +// `.field[.field...]` Dot chain, accumulating the packed-struct bit offset, +// and replace the topmost Dot with a Sel(const, lsb, width) matching +// V3Width::memberSelStruct. Without this, the struct-field select on the +// deferred class localparam cannot be constified for the cell pin. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// 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 + +typedef struct packed { + logic [7:0] cam_type; + logic [7:0] depth; +} inner_t; + +typedef struct packed { + inner_t jt; + logic [7:0] tag; +} cfg_t; + +class C #(parameter int W = 1); + // Struct localparam whose fields derive from the class parameter. + localparam cfg_t cfg = '{jt: '{cam_type: W[7:0], depth: W[7:0] + 8'd1}, + tag: W[7:0] + 8'd2}; +endclass + +// Wrapper class containing the use of another class +class D #(parameter int W = 1); + typedef C#(W) CC; + localparam int q = int'(CC::cfg.tag) + 100; +endclass + +module Sub #(parameter int WIDTH = 0) (); +endmodule + +// Parameterized wrapper so the class specialization is deferred until +// V3Param processes the cell instance. +module Mid #(parameter int W = 8) (); + typedef C#(W) CFG; + // (1) single-level struct field access -> one loop iteration + Sub #(int'(CFG::cfg.tag)) u_tag (); + // (2) nested struct field access -> two loop iterations, lsb accumulation + Sub #(int'(CFG::cfg.jt.cam_type)) u_cam (); + Sub #(int'(CFG::cfg.jt.depth)) u_depth (); + // (3) nested struct-field Dot buried in a wrapper class's lparam value + typedef D#(W) DD; + Sub #(int'(DD::q)) u_q (); +endmodule + +module t; + Mid #(.W(8)) u (); + + initial begin + // cfg.tag = W + 2 = 10 (offset above the 16-bit jt struct) + `checkh(u.u_tag.WIDTH, 32'd10); + // cfg.jt.cam_type = W = 8 (nested, lsb 0) + `checkh(u.u_cam.WIDTH, 32'd8); + // cfg.jt.depth = W + 1 = 9 (nested, lsb 8) + `checkh(u.u_depth.WIDTH, 32'd9); + // q = cfg.tag + 100 = 10 + 100 = 110 + `checkh(u.u_q.WIDTH, 32'd110); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_lparam_struct_field_chain_bad.out b/test_regress/t/t_class_lparam_struct_field_chain_bad.out new file mode 100644 index 000000000..d84a5df97 --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_field_chain_bad.out @@ -0,0 +1,14 @@ +%Error: t/t_class_lparam_struct_field_chain_bad.v:37:25: Malformed dotted select in parameter value + : ... note: In instance 't.u' + 37 | Sub #(int'(CFG::cfg.jt.cam_type[0])) u_fieldref (); + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_class_lparam_struct_field_chain_bad.v:39:26: Dotted member select on non-struct parameter value + : ... note: In instance 't.u' + 39 | Sub #(int'(CFG::cfg.tag.bogus)) u_nonstruct (); + | ^ +%Error: t/t_class_lparam_struct_field_chain_bad.v:41:25: Member 'nosuchfield' not found in structure + : ... note: In instance 't.u' + 41 | Sub #(int'(CFG::cfg.jt.nosuchfield)) u_nomember (); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_class_lparam_struct_field_chain_bad.py b/test_regress/t/t_class_lparam_struct_field_chain_bad.py new file mode 100644 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_field_chain_bad.py @@ -0,0 +1,16 @@ +#!/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 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_class_lparam_struct_field_chain_bad.v b/test_regress/t/t_class_lparam_struct_field_chain_bad.v new file mode 100644 index 000000000..00cf7abfe --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_field_chain_bad.v @@ -0,0 +1,46 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Negative test for the struct-localparam field-access folding in V3Param +// (resolveDotToTypedef). Malformed outer `.field` chains on a class +// localparam value must produce clean user-facing errors, NOT an internal +// error or a crash in the following width pass. Exercises the three +// mismatch guards in the field-chain walk: +// (1) outer Dot rhs is not a plain field identifier (bit-select) +// (2) `.field` applied to a non-struct value +// (3) field name not a member of the struct +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +typedef struct packed { + logic [7:0] cam_type; + logic [7:0] depth; +} inner_t; + +typedef struct packed { + inner_t jt; + logic [7:0] tag; +} cfg_t; + +class C #(parameter int W = 1); + localparam cfg_t cfg = '{jt: '{cam_type: W[7:0], depth: W[7:0] + 8'd1}, + tag: W[7:0] + 8'd2}; +endclass + +module Sub #(parameter int WIDTH = 0) (); +endmodule + +module Mid #(parameter int W = 8) (); + typedef C#(W) CFG; + // (1) bit-select after the field chain: outer Dot rhs is not a ParseRef + Sub #(int'(CFG::cfg.jt.cam_type[0])) u_fieldref (); + // (2) `.bogus` on the scalar field `tag`: dotting into a non-struct + Sub #(int'(CFG::cfg.tag.bogus)) u_nonstruct (); + // (3) `.nosuchfield` not a member of struct `jt` + Sub #(int'(CFG::cfg.jt.nosuchfield)) u_nomember (); +endmodule + +module t; + Mid #(.W(8)) u (); +endmodule diff --git a/test_regress/t/t_class_lparam_struct_member_chain.py b/test_regress/t/t_class_lparam_struct_member_chain.py new file mode 100644 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_member_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 Wilson Snyder +# 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_struct_member_chain.v b/test_regress/t/t_class_lparam_struct_member_chain.v new file mode 100644 index 000000000..9c044821a --- /dev/null +++ b/test_regress/t/t_class_lparam_struct_member_chain.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// A struct typedef whose member references a class-scope-resolved typedef +// from a parameterized-class typedef alias (e.g. CFG::data_t). The struct +// is consumed by `$bits(...)` on a cell parameter pin, which forces V3Param +// to constify the pin during cell deparameterization. V3Param must follow +// typedef references in the pin tree and resolve the unresolved struct-member +// RefDTypes that target the parameterized class, otherwise V3Width hits +// "REFDTYPE 'data_t' not linked to type" (V3AstNodes.cpp). +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// 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 + +package pkg; + virtual class C #(parameter int W = 1); + typedef logic [W-1:0] data_t; + endclass +endpackage + +module Sink #(parameter int BITS = 1) (); + logic [BITS-1:0] data; +endmodule + +module Sub #(parameter int W = 8) (); + typedef pkg::C#(W) CFG; + + // Struct typedef whose member is a class-scope-resolved typedef from + // a parameterized-class typedef alias. Without the V3Param fix, the + // member's RefDType stays UNLINKED and the cell pin's $bits trips an + // internal error. + typedef struct packed { + CFG::data_t payload; + logic v; + } wrap_t; + + Sink #(.BITS($bits(wrap_t))) u_sink (); +endmodule + +module t; + Sub #(.W(8)) u (); + + initial begin + // $bits(wrap_t) = 8 (payload) + 1 (v) = 9 + `checkh($bits(u.u_sink.data), 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..46d1fe4c0 --- /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 Wilson Snyder +# 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..090a33eb2 --- /dev/null +++ b/test_regress/t/t_class_lparam_typedef_chain.v @@ -0,0 +1,94 @@ +// 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 five 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 +// - Class-scope Dot used DIRECTLY (no intermediate lparam) in a typedef +// range, consumed by `$bits` on a child cell pin +// - Struct lparam field access via class-scope Dot +// (`CFG::struct_lparam.field`) inside a parameterized module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// 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 W = 8) (); endmodule + +// Sub-case (5): struct lparam field access via class-scope Dot. Needs a +// parameterized-module wrapper so the class specialization is deferred until +// V3Param processes the cell instance. +typedef struct packed { logic [7:0] hi; logic [7:0] lo; } pair_t; + +class P #(parameter pair_t p); + localparam pair_t pp = p; +endclass + +module PairHolder #(parameter pair_t cfg = '{hi:'d7, lo:'d11}) (); + typedef P#(cfg) PALIAS; + localparam logic [7:0] hi_val = PALIAS::pp.hi; + localparam logic [7:0] lo_val = PALIAS::pp.lo; +endmodule + +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; + + // (4) Cell pin $bits consumes a typedef whose packed range is built from + // class-scope Dots directly (no intermediate deferred lparam). + typedef logic [(c8::b + c8::b - 1):0] direct_use_t; + Sub #(.W($bits(direct_use_t))) u_sub (); + + // (5) struct lparam field access via class-scope Dot + PairHolder #(.cfg('{hi:'d7, lo:'d11})) u_ph (); + + 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); + // sub-case (4): $bits(direct_use_t) = c8::b + c8::b = 8 + 8 = 16 + `checkh($bits(direct_use_t), 32'd16); + // sub-case (5): struct lparam field access folds to the right field + `checkh(u_ph.hi_val, 8'd7); + `checkh(u_ph.lo_val, 8'd11); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule