Fix enum item reference through parameterized instance (#8347) (#8567)

This commit is contained in:
Saqib Khan
2026-10-04 06:57:31 -04:00
committed by GitHub
parent 3e96aba1d6
commit 19a3d2d6b3
13 changed files with 334 additions and 34 deletions
+8 -1
View File
@@ -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; }
+7 -1
View File
@@ -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};
+72 -31
View File
@@ -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");
+11 -1
View File
@@ -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);
+16
View File
@@ -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;
+6
View File
@@ -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
+18
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()
+91
View File
@@ -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
+10
View File
@@ -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
+16
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()
+28
View File
@@ -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
+16
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(verilator_flags2=["--timing"])
test.passes()
+35
View File
@@ -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