From f18aaeea8375b93788fa2a9aebd44d22ed83f9c7 Mon Sep 17 00:00:00 2001 From: Edmund Lam Date: Tue, 9 Jun 2026 13:03:00 -0400 Subject: [PATCH] fix typedefs --- src/V3Param.cpp | 33 ++++++++- test_regress/t/t_class_lparam_nested_chain.py | 18 +++++ test_regress/t/t_class_lparam_nested_chain.v | 72 +++++++++++++++++++ .../t/t_class_lparam_struct_member_chain.py | 18 +++++ .../t/t_class_lparam_struct_member_chain.v | 54 ++++++++++++++ 5 files changed, 192 insertions(+), 3 deletions(-) create mode 100755 test_regress/t/t_class_lparam_nested_chain.py create mode 100644 test_regress/t/t_class_lparam_nested_chain.v create mode 100644 test_regress/t/t_class_lparam_struct_member_chain.py create mode 100644 test_regress/t/t_class_lparam_struct_member_chain.v diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 76422a89d..ea60bd8f1 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1288,7 +1288,19 @@ 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* 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); @@ -2182,13 +2194,21 @@ public: // VarRefs to all leaf dependencies. Then resolve these to typedefs. void resolveDeferredDotsReachableFrom(AstNode* rootp, AstNodeModule* modp) { std::vector dotps; + std::vector tdefps; const auto& deferredVarps = v3Global.rootp()->deferredParamVarps(); std::set reachedDeferred; + std::set reachedTypedefs; auto collectDots = [&](AstNode* p) { p->foreach([&](AstDot* dotp) { if (VN_IS(dotp->lhsp(), ClassOrPackageRef)) dotps.push_back(dotp); }); }; + auto collectTypedefs = [&](AstNode* p) { + p->foreach([&](const AstRefDType* refp) { + AstTypedef* const tdefp = refp->typedefp(); + if (tdefp && reachedTypedefs.insert(tdefp).second) tdefps.push_back(tdefp); + }); + }; std::vector workVarps; auto enqueueRefs = [&](AstNode* p) { p->foreach([&](const AstVarRef* refp) { @@ -2200,18 +2220,25 @@ public: }); }; collectDots(rootp); + collectTypedefs(rootp); enqueueRefs(rootp); - // Handle deferred VarRefs recursively until no more, accumulating Dots along the way. + // Handle deferred VarRefs recursively until no more, accumulating Dots and + // typedef references along the way. while (!workVarps.empty()) { AstVar* const varp = workVarps.back(); workVarps.pop_back(); if (!varp->valuep()) continue; collectDots(varp->valuep()); + collectTypedefs(varp->valuep()); enqueueRefs(varp->valuep()); } - if (dotps.empty()) return; + if (dotps.empty() && tdefps.empty()) return; VL_RESTORER(m_modp); m_modp = modp; + // Resolve 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); } 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..076f9abda --- /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_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..7affc9a07 --- /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