This commit is contained in:
Edmund Lam 2026-07-14 22:35:05 -04:00 committed by GitHub
commit bf0320ca89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 842 additions and 11 deletions

View File

@ -67,6 +67,7 @@ Drew Ranck
Drew Taussig Drew Taussig
Driss Hafdi Driss Hafdi
Edgar E. Iglesias Edgar E. Iglesias
Edmund Lam
Eric Mejdrich Eric Mejdrich
Eric Müller Eric Müller
Eric Rippey Eric Rippey

View File

@ -1288,10 +1288,84 @@ class ParamProcessor final {
// Param/lparam member: substitute its constant value so the caller's constify can // Param/lparam member: substitute its constant value so the caller's constify can
// succeed. // succeed.
if (varp->isParam() && varp->valuep()) { 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<AstDot*> 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)) { if (AstConst* const constp = VN_CAST(varp->valuep(), Const)) {
dotp->replaceWith(constp->cloneTree(false)); // Walk up any outer `.fieldN[.fieldM...]` chain (struct lparam
VL_DO_DANGLING(dotp->deleteTree(), dotp); // 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<AstConst*>(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<AstDot*> dotps;
std::vector<AstTypedef*> tdefps;
const auto& deferredVarps = v3Global.rootp()->deferredParamVarps();
std::set<AstVar*> reachedDeferred;
std::set<const AstTypedef*> reachedTypedefs;
std::vector<AstNode*> 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, AstNodeModule* nodeDeparam(AstNode* nodep, AstNodeModule* srcModp, AstNodeModule* modp,
const string& someInstanceName) { const string& someInstanceName) {
// Return new or reused de-parameterized module // Return new or reused de-parameterized module
@ -2232,14 +2358,9 @@ public:
} }
// Create new module name with _'s between the constants // Create new module name with _'s between the constants
UINFOTREE(10, nodep, "", "cell"); UINFOTREE(10, nodep, "", "cell");
// Resolve `class::member` Dots in pin values so constify sees Consts. // Resolve `class::member` Dots in pin values, and in any deferred
// Single-pass: filter-collect class-scoped Dots, then reverse-iterate so inner // lparam reachable from the pin tree, so constify sees Consts.
// Dots resolve before outer (vector stays empty for cells with no class Dots). resolveDeferredDotsReachableFrom(nodep, modp);
std::vector<AstDot*> 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);
// Evaluate all module constants // Evaluate all module constants
V3Const::constifyParamsEdit(nodep); V3Const::constifyParamsEdit(nodep);
// Set name for warnings for when we param propagate the module // 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 { void visit(AstGenIf* nodep) override {
UINFO(9, " GENIF " << nodep); UINFO(9, " GENIF " << nodep);
iterateAndNextNull(nodep->condp()); 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 // We suppress errors when widthing params since short-circuiting in
// the conditional evaluation may mean these error can never occur. We // the conditional evaluation may mean these error can never occur. We
// then make sure that short-circuiting is used by constifyParamsEdit. // then make sure that short-circuiting is used by constifyParamsEdit.
@ -3220,6 +3344,9 @@ class ParamVisitor final : public VNVisitor {
iterateAndNextNull(forp->initsp()); iterateAndNextNull(forp->initsp());
iterateAndNextNull(forp->condp()); iterateAndNextNull(forp->condp());
iterateAndNextNull(forp->incsp()); 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 V3Width::widthParamsEdit(forp); // Param typed widthing will NOT recurse the body
// Outer wrapper around generate used to hold genvar, and to ensure genvar // Outer wrapper around generate used to hold genvar, and to ensure genvar
// doesn't conflict in V3LinkDot resolution with other genvars // doesn't conflict in V3LinkDot resolution with other genvars
@ -3253,6 +3380,9 @@ class ParamVisitor final : public VNVisitor {
AstNode* keepp = nullptr; AstNode* keepp = nullptr;
iterateAndNextNull(nodep->exprp()); iterateAndNextNull(nodep->exprp());
V3Case::caseLint(nodep); 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 V3Width::widthParamsEdit(nodep); // Param typed widthing will NOT recurse the
// body, don't trigger errors yet. // body, don't trigger errors yet.
V3Const::constifyParamsEdit(nodep->exprp()); // exprp may change V3Const::constifyParamsEdit(nodep->exprp()); // exprp may change

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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