diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index ddcee2d20..84c3ff69c 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -1442,6 +1442,8 @@ class AstEnumItemRef final : public AstNodeExpr { // @astgen ptr := m_itemp : Optional[AstEnumItem] // [AfterLink] Pointer to item // @astgen ptr := m_classOrPackagep : Optional[AstNodeModule] // Class/package defined in string m_name; // Name of enum (for param relink) + string m_dotted; // Dotted part of scope the name()'ed reference is under or "" + bool m_containsGenBlock = false; // Contains gen block reference public: AstEnumItemRef(FileLine* fl, AstEnumItem* itemp, AstNodeModule* classOrPackagep) : ASTGEN_SUPER_EnumItemRef(fl) @@ -1458,10 +1460,15 @@ public: int instrCount() const override { return 0; } bool sameNode(const AstNode* samep) const override { const AstEnumItemRef* const sp = VN_DBG_AS(samep, EnumItemRef); - return itemp() == sp->itemp(); + return itemp() == sp->itemp() && dotted() == sp->dotted() + && containsGenBlock() == sp->containsGenBlock(); } AstEnumItem* itemp() const VL_MT_STABLE { return m_itemp; } void itemp(AstEnumItem* nodep) { m_itemp = nodep; } + string dotted() const { return m_dotted; } + void dotted(const string& dotted) { m_dotted = dotted; } + bool containsGenBlock() const { return m_containsGenBlock; } + void containsGenBlock(const bool flag) { m_containsGenBlock = flag; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index e451cddff..2f82d9e79 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1482,6 +1482,8 @@ const char* AstEnumItemRef::broken() const { } void AstEnumItemRef::dump(std::ostream& str) const { Super::dump(str); + if (containsGenBlock()) str << " [GENBLK]"; + if (!dotted().empty()) str << " .=" << dotted(); str << " -> "; if (itemp()) { itemp()->dump(str); @@ -1489,7 +1491,11 @@ void AstEnumItemRef::dump(std::ostream& str) const { str << "UNLINKED"; } } -void AstEnumItemRef::dumpJson(std::ostream& str) const { dumpJsonGen(str); } +void AstEnumItemRef::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, containsGenBlock); + dumpJsonStrFunc(str, dotted); + dumpJsonGen(str); +} AstNodeBiop* AstEq::newTyped(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* rhsp) { if (lhsp->isString() && rhsp->isString()) { return new AstEqN{fl, lhsp, rhsp}; diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index b9320ba53..6411925ab 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -3510,6 +3510,37 @@ class LinkDotResolveVisitor final : public VNVisitor { << " search start due to inlinedDots=" << inlinedDots); return dotSymp; } + VSymEnt* findDottedRefSymp(AstNode* nodep, VSymEnt* lookupSymp, const string& dotted, + string& baddot, VSymEnt*& okSymp, bool* modportp = nullptr) { + // Return scope to search for a relinked hierarchical reference + // (AstVarXRef or AstEnumItemRef), or nullptr after error if not found + // If modportp is given, set true when the reference went through a modport + if (modportp) *modportp = false; + VSymEnt* dotSymp = m_statep->findDotted(nodep->fileline(), lookupSymp, dotted, baddot, + okSymp, true); // Maybe nullptr + if (!dotSymp) { + nodep->v3error( + "Can't find definition of " + << (!baddot.empty() ? AstNode::prettyNameQ(baddot) : nodep->prettyNameQ()) << '\n' + << nodep->warnContextPrimary()); + return nullptr; + } + if (const AstVar* const varp = VN_CAST(dotSymp->nodep(), Var)) { + if (const AstIfaceRefDType* const ifaceRefp + = VN_CAST(varp->childDTypep(), IfaceRefDType)) { + if (ifaceRefp->modportp()) { + dotSymp = m_statep->getNodeSym(ifaceRefp->modportp()); + if (modportp) *modportp = true; + } else { + dotSymp = m_statep->getNodeSym(ifaceRefp->ifacep()); + } + } + } else if (const AstModportClockingRef* const clockingRefp + = VN_CAST(dotSymp->nodep(), ModportClockingRef)) { + dotSymp = m_statep->getNodeSym(clockingRefp->clockingp()); + } + return dotSymp; + } static bool isParamedClassRefDType(const AstNode* classp) { while (const AstRefDType* const refp = VN_CAST(classp, RefDType)) @@ -4731,8 +4762,20 @@ class LinkDotResolveVisitor final : public VNVisitor { } } else if (AstEnumItem* const valuep = VN_CAST(foundp->nodep(), EnumItem)) { if (allowVar) { - AstNode* const newp + AstEnumItemRef* const refp = new AstEnumItemRef{nodep->fileline(), valuep, foundp->classOrPackagep()}; + AstNode* newp = refp; + // Hierarchical reference, relinked after V3Param as the referenced + // module may be specialized, similar to AstVarXRef + refp->dotted(m_ds.m_dotText); + refp->containsGenBlock(m_ds.m_genBlk); + if (m_ds.m_unresolvedCell && m_ds.m_unlinkedScopep) { + UINFO(9, indent() << "deferring until post-V3Param: " << refp); + newp = new AstUnlinkedRef{nodep->fileline(), refp, refp->name(), + m_ds.m_unlinkedScopep->unlinkFrBack()}; + m_ds.m_unlinkedScopep = nullptr; + m_ds.m_unresolvedCell = false; + } nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); ok = true; @@ -5028,38 +5071,16 @@ class LinkDotResolveVisitor final : public VNVisitor { UINFO(9, "Dead module for " << nodep); nodep->varp(nullptr); } else { + VSymEnt* lookupSymp = m_curSymp; // Start search at current scope + if (nodep->inlinedDots() != "") { // Correct for current post-inlined scope + lookupSymp = findInlinedDotsSym(nodep, nodep->inlinedDots()); + } string baddot; VSymEnt* okSymp; - VSymEnt* dotSymp = m_curSymp; // Start search at current scope - if (nodep->inlinedDots() != "") { // Correct for current post-inlined scope - dotSymp = findInlinedDotsSym(nodep, nodep->inlinedDots()); - } - dotSymp = m_statep->findDotted(nodep->fileline(), dotSymp, nodep->dotted(), baddot, - okSymp, true); // Maybe nullptr - if (!dotSymp) { - nodep->v3error( - "Can't find definition of " - << (!baddot.empty() ? AstNode::prettyNameQ(baddot) : nodep->prettyNameQ()) - << '\n' - << nodep->warnContextPrimary()); - return; - } - - bool modport = false; - if (const AstVar* const varp = VN_CAST(dotSymp->nodep(), Var)) { - if (const AstIfaceRefDType* const ifaceRefp - = VN_CAST(varp->childDTypep(), IfaceRefDType)) { - if (ifaceRefp->modportp()) { - dotSymp = m_statep->getNodeSym(ifaceRefp->modportp()); - modport = true; - } else { - dotSymp = m_statep->getNodeSym(ifaceRefp->ifacep()); - } - } - } else if (const AstModportClockingRef* const clockingRefp - = VN_CAST(dotSymp->nodep(), ModportClockingRef)) { - dotSymp = m_statep->getNodeSym(clockingRefp->clockingp()); - } + bool modport; + VSymEnt* const dotSymp + = findDottedRefSymp(nodep, lookupSymp, nodep->dotted(), baddot, okSymp, &modport); + if (!dotSymp) return; if (!m_statep->forScopeCreation()) { VSymEnt* foundp = nullptr; @@ -5157,7 +5178,27 @@ class LinkDotResolveVisitor final : public VNVisitor { // EnumItemRefs are created by the first pass, but V3Param may regenerate due to // a parameterized class/module, so we shouldn't get can't find errors. // No checkNoDot; created and iterated from a parseRef + if (nodep->user3SetOnce()) return; LINKDOT_VISIT_START(); + if (m_statep->forParamed() && !nodep->dotted().empty() && m_modSymp) { + // Hierarchical reference, relink as V3Param may have specialized the module + string baddot; + VSymEnt* okSymp = nullptr; + VSymEnt* const dotSymp + = findDottedRefSymp(nodep, m_curSymp, nodep->dotted(), baddot, okSymp); + if (!dotSymp) return; + VSymEnt* const foundp + = m_statep->findSymPrefixed(dotSymp, nodep->name(), baddot, true); + AstEnumItem* const itemp = foundp ? VN_CAST(foundp->nodep(), EnumItem) : nullptr; + if (!itemp) { + nodep->v3error("Enum item " << nodep->prettyNameQ() << " not found in " + << AstNode::prettyNameQ(nodep->dotted()) << '\n' + << nodep->warnContextPrimary()); + return; + } + nodep->itemp(itemp); + UINFO(9, indent() << " relinked " << nodep); + } if (!nodep->itemp()) { UINFO(9, indent() << "linkEnumRef se" << cvtToHex(m_curSymp) << " n=" << nodep); UASSERT_OBJ(m_curSymp, nodep, "nullptr lookup symbol table"); diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 53b202c72..5bdab008b 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -3387,6 +3387,11 @@ class ParamVisitor final : public VNVisitor { } iterateChildren(nodep); } + void visit(AstEnumItemRef* nodep) override { + // Needs relink, as may remove pointed-to item + if (nodep->containsGenBlock()) nodep->itemp(nullptr); + iterateChildren(nodep); + } void visit(AstVarXRef* nodep) override { if (nodep->containsGenBlock()) { // Needs relink, as may remove pointed-to var @@ -3466,10 +3471,13 @@ class ParamVisitor final : public VNVisitor { void visit(AstUnlinkedRef* nodep) override { AstVarXRef* const varxrefp = VN_CAST(nodep->refp(), VarXRef); AstNodeFTaskRef* const taskrefp = VN_CAST(nodep->refp(), NodeFTaskRef); + AstEnumItemRef* const enumrefp = VN_CAST(nodep->refp(), EnumItemRef); if (varxrefp) { m_unlinkedTxt = varxrefp->dotted(); } else if (taskrefp) { m_unlinkedTxt = taskrefp->dotted(); + } else if (enumrefp) { + m_unlinkedTxt = enumrefp->dotted(); } else { nodep->v3fatalSrc("Unexpected AstUnlinkedRef node"); return; @@ -3478,8 +3486,10 @@ class ParamVisitor final : public VNVisitor { if (varxrefp) { varxrefp->dotted(m_unlinkedTxt); - } else { + } else if (taskrefp) { taskrefp->dotted(m_unlinkedTxt); + } else { + enumrefp->dotted(m_unlinkedTxt); } nodep->replaceWith(nodep->refp()->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(nodep), nodep); diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index dd46c9d89..ac337b0cd 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -307,6 +307,16 @@ module Vt_debug_emitv_t; $stop; end sub.r = 62.0; + do begin + if ((SUB_B !== 2'h1)) begin + begin + $write("%%Error: %s:%0d: got=%0d exp=%0d\n", + 136'h742f745f64656275675f656d6974762e76, + 'sh105, SUB_B, 2'h1); + $stop; + end + end + end while (1'h0) mod_res = (mod_val % 'sh5); $display("%g", $log10(r)); $display("%g", $ln(r)); @@ -1498,6 +1508,12 @@ interface Vt_debug_emitv_Iface; endinterface module Vt_debug_emitv_sub; input logic clk; + /* generate */ begin : sub_gen + typedef enum logic [1:0] { + SUB_A = 2'h0, + SUB_B = 2'h1 + } sub_e; + end : sub_gen task inc; input int signed i; output int signed o; diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index c8e85368b..5f74e8783 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -5,6 +5,8 @@ // SPDX-FileCopyrightText: 2020 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); + package Pkg; localparam PKG_PARAM = 1; @@ -256,6 +258,7 @@ module t (/*AUTOARG*/ if (Pkg::PKG_PARAM != 1) $stop; sub.r = 62.0; + `checkd(sub.sub_gen.SUB_B, 2'd1); mod_res = mod_val % 5; @@ -479,6 +482,9 @@ module t (/*AUTOARG*/ endmodule module sub(input logic clk); + if (1) begin : sub_gen + typedef enum logic [1:0] {SUB_A, SUB_B} sub_e; + end task inc(input int i, output int o); o = {1'b0, i[31:1]} + 32'd1; endtask diff --git a/test_regress/t/t_enum_param_xref.py b/test_regress/t/t_enum_param_xref.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_enum_param_xref.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_enum_param_xref.v b/test_regress/t/t_enum_param_xref.v new file mode 100644 index 000000000..131dccdf1 --- /dev/null +++ b/test_regress/t/t_enum_param_xref.v @@ -0,0 +1,91 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Saqib Khan +// SPDX-License-Identifier: CC0-1.0 + +// Enum item referenced through a parameterized instance (#8347) (#8389) + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +interface ifc #( + parameter int W = 1 +); + typedef enum logic [3:0] { + I_IDLE = 1, + I_WORK = 4'(W) + } state_t; +endinterface + +module child #( + parameter int W = 1 +); + typedef enum logic [4:0] { + S_IDLE = 0, + S_WORK = 5'(W) + } state_t; + state_t fsm = S_WORK; + if (1) begin : blk + typedef enum logic [4:0] { + B_IDLE = 0, + B_WORK = 5'(W + 10) + } blk_t; + end + if (W == 1) begin : gif + typedef enum logic [4:0] {G_ONE = 21} one_t; + end else begin : gif + typedef enum logic [4:0] {G_OTHER = 22} other_t; + end +endmodule + +module sub ( + ifc i +); + int work; + initial work = int'(i.I_WORK); +endmodule + +module t; + child #(.W(2)) a (); + child #(.W(3)) b (); + child c (); + for (genvar g = 0; g < 2; ++g) begin : gen + child #(.W(g + 4)) u (); + wire working = u.fsm == u.S_WORK; + int work = int'(u.S_WORK); + end + ifc #(.W(6)) i6 (); + ifc #(.W(7)) i7[2] (); + sub s (.i(i6)); + + wire working = a.fsm == a.S_WORK; + + initial begin + #1; + `checkd(working, 1'b1); + `checkd(a.S_IDLE, 0); + `checkd(a.S_WORK, 2); + `checkd(b.S_WORK, 3); + `checkd(c.S_WORK, 1); + `checkd(a.fsm, a.S_WORK); + `checkd(b.fsm, b.S_WORK); + `checkd(a.blk.B_WORK, 12); + `checkd(t.b.blk.B_WORK, 13); + `checkd(b.gif.G_OTHER, 22); + `checkd(gen[0].working, 1'b1); + `checkd(gen[1].working, 1'b1); + `checkd(gen[0].work, 4); + `checkd(gen[1].work, 5); + `checkd(gen[0].u.S_WORK, 4); + `checkd(gen[1].u.S_WORK, 5); + `checkd(i6.I_WORK, 6); + `checkd(i7[0].I_WORK, 7); + `checkd(i7[1].I_WORK, 7); + `checkd(s.work, 6); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_enum_param_xref_bad.out b/test_regress/t/t_enum_param_xref_bad.out new file mode 100644 index 000000000..dca38d7ad --- /dev/null +++ b/test_regress/t/t_enum_param_xref_bad.out @@ -0,0 +1,10 @@ +%Error: t/t_enum_param_xref_bad.v:26:27: Can't find definition of 'u' + : ... note: In instance 't' + 26 | int bad = int'(gen[2].u.S_WORK); + | ^~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_enum_param_xref_bad.v:27:27: Enum item 'G_OTHER' not found in 'one.gif' + : ... note: In instance 't' + 27 | int bad2 = int'(one.gif.G_OTHER); + | ^~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_enum_param_xref_bad.py b/test_regress/t/t_enum_param_xref_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_enum_param_xref_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_enum_param_xref_bad.v b/test_regress/t/t_enum_param_xref_bad.v new file mode 100644 index 000000000..281fe9545 --- /dev/null +++ b/test_regress/t/t_enum_param_xref_bad.v @@ -0,0 +1,28 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Saqib Khan +// SPDX-License-Identifier: CC0-1.0 + +module child #( + parameter int W = 1 +); + typedef enum logic [4:0] { + S_IDLE = 0, + S_WORK = 5'(W) + } state_t; + if (W == 1) begin : gif + typedef enum logic [4:0] {G_ONE = 21} one_t; + end else begin : gif + typedef enum logic [4:0] {G_OTHER = 22} other_t; + end +endmodule + +module t; + for (genvar g = 0; g < 2; ++g) begin : gen + child #(.W(g + 4)) u (); + end + child #(.W(1)) one (); + int bad = int'(gen[2].u.S_WORK); + int bad2 = int'(one.gif.G_OTHER); +endmodule diff --git a/test_regress/t/t_enum_param_xref_with.py b/test_regress/t/t_enum_param_xref_with.py new file mode 100755 index 000000000..647090189 --- /dev/null +++ b/test_regress/t/t_enum_param_xref_with.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(verilator_flags2=["--timing"]) + +test.passes() diff --git a/test_regress/t/t_enum_param_xref_with.v b/test_regress/t/t_enum_param_xref_with.v new file mode 100644 index 000000000..56ec2b4a8 --- /dev/null +++ b/test_regress/t/t_enum_param_xref_with.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Saqib Khan +// SPDX-License-Identifier: CC0-1.0 + +// Enum item through a parameterized instance in randomize-with (#8347) (#8389) + +class Item; + rand int x; +endclass + +module child #( + parameter int W = 1 +); + typedef enum logic [1:0] { + S_IDLE = 0, + S_WORK = 2'(W) + } state_t; +endmodule + +module t; + child #(.W(2)) a (); + child #(.W(3)) b (); + Item items[4]; + + initial begin + items[a.S_WORK] = new; + items[b.S_WORK] = new; + // Same enum item reference in randomize object and constraint + void'(items[a.S_WORK].randomize() with {items[a.S_WORK].x == 5;}); + // Different enum item references + void'(items[a.S_WORK].randomize() with {items[b.S_WORK].x == 5;}); + end +endmodule