diff --git a/src/V3Param.cpp b/src/V3Param.cpp index be9641e22..902f5477f 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1396,8 +1396,7 @@ class ParamProcessor final { } AstConst* const exprp = VN_CAST(pinp->exprp(), Const); AstConst* const origp = VN_CAST(modvarp->valuep(), Const); - // Width the pin value to match the port type so that the same - // logical value always produces the same specialization name. + // Width the pin to the port's type so equal values hash the same (#5479). AstConst* normedNamep = nullptr; if (exprp && !exprp->num().isDouble() && !exprp->num().isString()) { AstVar* cloneVarp = modvarp->cloneTree(false); @@ -1406,21 +1405,41 @@ class ParamProcessor final { VL_DO_DANGLING(oldValuep->deleteTree(), oldValuep); } cloneVarp->valuep(exprp->cloneTree(false)); - // Clone the dtype and resolve VarRefs to other parameters - // with their already-constified pin values (e.g., N-1 in - // logic [N-1:0] becomes Const-1 which can be folded). if (AstNodeDType* const origDTypep = modvarp->subDTypep()) { AstNodeDType* const dtypeClonep = origDTypep->cloneTree(false); - dtypeClonep->foreach([&](AstVarRef* varrefp) { - for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) { - if (pp->modVarp() == varrefp->varp()) { - if (AstConst* const constp = VN_CAST(pp->exprp(), Const)) { - varrefp->replaceWith(constp->cloneTree(false)); - VL_DO_DANGLING(varrefp->deleteTree(), varrefp); + // Inline every param ref so widthing doesn't reach back into the template + // (#7411). Cycle detector for dependent parameters in the same module. + constexpr int maxSubstIters = 1000; + for (int it = 0; it < maxSubstIters; ++it) { + bool any = false; + dtypeClonep->foreach([&](AstVarRef* varrefp) { + AstVar* const targetp = varrefp->varp(); + AstNode* replacep = nullptr; + for (AstPin* pp = paramsp; pp; pp = VN_AS(pp->nextp(), Pin)) { + if (pp->modVarp() == targetp) { + if (AstConst* const constp = VN_CAST(pp->exprp(), Const)) { + replacep = constp->cloneTree(false); + } + break; } - break; } - } + if (!replacep && targetp->valuep()) { + replacep = targetp->valuep()->cloneTree(false); + } + if (replacep) { + varrefp->replaceWith(replacep); + VL_DO_DANGLING(varrefp->deleteTree(), varrefp); + any = true; + } + }); + if (!any) break; + } + // Bail if anything still points at the template. + dtypeClonep->foreach([&](AstVarRef* varrefp) { + varrefp->v3fatalSrc( + "Unresolved VarRef '" + << varrefp->prettyName() << "' in pin dtype clone. Pin: " + << pinp->prettyNameQ() << " of " << nodep->prettyNameQ()); }); if (cloneVarp->childDTypep()) cloneVarp->childDTypep()->unlinkFrBack()->deleteTree(); @@ -1429,8 +1448,7 @@ class ParamProcessor final { } V3Const::constifyParamsEdit(cloneVarp); if (AstConst* const widthedp = VN_CAST(cloneVarp->valuep(), Const)) { - // Set the constant's dtype to the port's widthed type - // so identical values hash the same in paramValueNumber. + // Stamp the port's type on the const so equal values hash the same. if (cloneVarp->dtypep()) widthedp->dtypep(cloneVarp->dtypep()); widthedp->unlinkFrBack(); normedNamep = widthedp; diff --git a/test_regress/t/t_param_localparam_recompute.py b/test_regress/t/t_param_localparam_recompute.py new file mode 100755 index 000000000..519f45e75 --- /dev/null +++ b/test_regress/t/t_param_localparam_recompute.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", "-Wno-WIDTHEXPAND"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_param_localparam_recompute.v b/test_regress/t/t_param_localparam_recompute.v new file mode 100644 index 000000000..6f8eae0b8 --- /dev/null +++ b/test_regress/t/t_param_localparam_recompute.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Reproducer for issue #7411. +// +// when V3Param widths a pin value against the port dtype the widthing must not +// edit any referenced template vars in place. A dependent localparam like +// dirs_lp = dims_p*2+1 must still recompute from the overridden dims_p on +// each specialization. (basically - don't poison the template) +// +// 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 + +module sub #( + parameter int dims_p = 2, + parameter int dirs_lp = dims_p*2 + 1, + parameter bit [1:0][dirs_lp-1:0][dirs_lp-1:0] matrix_p = '0 +) (); +endmodule + +module t; + localparam bit [1:0][4:0][4:0] big_matrix = '1; + localparam bit [1:0][2:0][2:0] small_matrix = '1; + + // First instance processes matrix_p with the template's default dims_p=2. + // Before the fix, this froze dirs_lp on the template at 5. + sub #(.matrix_p(big_matrix)) s1 (); + + // Second instance overrides dims_p=1, so dirs_lp must recompute to 3. + sub #(.dims_p(1), .matrix_p(small_matrix)) s2 (); + + initial begin + if (s1.dirs_lp !== 5) begin + $write("%%Error: s1.dirs_lp=%0d expected 5\n", s1.dirs_lp); + $stop; + end + if (s2.dirs_lp !== 3) begin + $write("%%Error: s2.dirs_lp=%0d expected 3\n", s2.dirs_lp); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule