From 3d126b77cd932eb65eafcb5190c638c77067eae5 Mon Sep 17 00:00:00 2001 From: em2machine <92717390+em2machine@users.noreply.github.com> Date: Thu, 28 May 2026 17:40:18 -0400 Subject: [PATCH] Fix for access to parameters via class::localparam (#7609) (#7671) --- src/V3AstNodeOther.h | 6 +-- src/V3Param.cpp | 20 ++++++++-- test_regress/t/t_class_lparam_chain.py | 18 +++++++++ test_regress/t/t_class_lparam_chain.v | 53 ++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 7 deletions(-) create mode 100755 test_regress/t/t_class_lparam_chain.py create mode 100644 test_regress/t/t_class_lparam_chain.v diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 5d3df96f9..88a084d48 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1285,7 +1285,7 @@ class AstNetlist final : public AstNode { bool m_timescaleSpecified = false; // Input HDL specified timescale uint32_t m_nTraceCodes = 0; // Number of trace codes used by design // V3Param-deferred params awaiting V3LinkDot::linkDotParamed scope-resolution. - std::vector m_deferredParamVarps; + std::set m_deferredParamVarps; // Sparse metadata for constants produced from named parameters/localparams. Keep this off // AstConst itself, as AstConst is a very common node and only a small fraction carry this // name. @@ -1295,8 +1295,8 @@ public: AstNetlist(); ASTGEN_MEMBERS_AstNetlist; const char* broken() const override; - void pushDeferredParamVarp(AstVar* varp) { m_deferredParamVarps.push_back(varp); } - const std::vector& deferredParamVarps() const { return m_deferredParamVarps; } + void pushDeferredParamVarp(AstVar* varp) { m_deferredParamVarps.insert(varp); } + const std::set& deferredParamVarps() const { return m_deferredParamVarps; } void clearDeferredParamVarps() { m_deferredParamVarps.clear(); } void deleteContents(); void cloneRelink() override { V3ERROR_NA; } // Not cloneable diff --git a/src/V3Param.cpp b/src/V3Param.cpp index f6a885ca5..a0064a22e 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -2924,10 +2924,22 @@ class ParamVisitor final : public VNVisitor { } }); if (hasUnresolvedLparamXRef) return; - // Defer if value contains a class::member Dot. By V3Param time the only - // surviving such Dots are paramed-class refs awaiting linkDotParamed. - if (nodep->valuep()->exists( - [](AstDot* dotp) { return VN_IS(dotp->lhsp(), ClassOrPackageRef); })) { + // Defer if value has a class::member Dot, or references a deferred lparam + const bool hasDot = nodep->valuep()->exists( + [](AstDot* dotp) { return VN_IS(dotp->lhsp(), ClassOrPackageRef); }); + bool refsDeferred = false; + if (!hasDot) { + const auto& deferredVarps = v3Global.rootp()->deferredParamVarps(); + nodep->valuep()->foreach([&](const AstVarRef* refp) { + if (refsDeferred) return; + AstVar* const refVarp = refp->varp(); + if (refVarp && refVarp->varType() == VVarType::LPARAM + && deferredVarps.count(refVarp)) { + refsDeferred = true; + } + }); + } + if (hasDot || refsDeferred) { v3Global.rootp()->pushDeferredParamVarp(nodep); return; } diff --git a/test_regress/t/t_class_lparam_chain.py b/test_regress/t/t_class_lparam_chain.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_class_lparam_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_chain.v b/test_regress/t/t_class_lparam_chain.v new file mode 100644 index 000000000..cafc79a96 --- /dev/null +++ b/test_regress/t/t_class_lparam_chain.v @@ -0,0 +1,53 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// localparams that chain 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; V3Param must also defer any +// localparam whose value transitively depends on a deferred one, including +// same-module VarRef chains (b -> c -> d) and not just cross-module +// VarXRefs. +// +// 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 t; + virtual class C #(parameter int a); + localparam int b = a; + endclass + + typedef C#(0) inst0; + typedef C#(42) inst42; + + // Direct: inst::b Dot in the value + localparam int b0 = inst0::b; + localparam int b42 = inst42::b; + + // One-step chain: refers to a deferred lparam + localparam int c0 = b0; + localparam int c42 = b42; + + // Multi-step chain: d -> c -> b -> inst::b + localparam int d0 = c0; + localparam int d42 = c42; + + // Expression referencing two deferred lparams + localparam int sum = b0 + b42; + + initial begin + `checkh(b0, 32'd0); + `checkh(b42, 32'd42); + `checkh(c0, 32'd0); + `checkh(c42, 32'd42); + `checkh(d0, 32'd0); + `checkh(d42, 32'd42); + `checkh(sum, 32'd42); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule