Fix generic interface param resolution in module/cell parameterization (#7970) (#7971)

This commit is contained in:
David Garau 2026-08-03 02:18:59 -07:00 committed by GitHub
parent 2022bacd38
commit a24dd8b2dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 575 additions and 5 deletions

View File

@ -57,6 +57,7 @@ Dan Ruelas-Petrisko
Daniel Bates
Danny Oler
Dave Sargeant
David Garau
David Horton
David Ledger
David Metz

View File

@ -3653,8 +3653,11 @@ class LinkDotResolveVisitor final : public VNVisitor {
return nullptr;
}
static const AstVar* getNextVarp(const AstNode* stmtsp) {
// Only IO ports, as parameters are on paramsp(), not the pinsp() list paired here
while (stmtsp) {
if (const AstVar* const varp = VN_CAST(stmtsp, Var)) return varp;
if (const AstVar* const varp = VN_CAST(stmtsp, Var)) {
if (varp->isIO()) return varp;
}
stmtsp = stmtsp->nextp();
}
return nullptr;
@ -4470,7 +4473,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
AstIfaceRefDType* const ifacerefp
= LinkDotState::ifaceRefFromArray(varp->subDTypep());
if (varp->isIfaceRef() && m_genericIfaceModule
&& VN_IS(varp->childDTypep(), IfaceGenericDType)) {
&& VN_IS(varp->childDTypep(), IfaceGenericDType) && !start) {
// Defer only dotted member access ('d.PARAM'), as V3Param must specialize
// first; a standalone ref ('.x(d)') resolves via allowVar below now
ok = true;
m_ds.m_unresolvedGenericIface = true;
} else if (ifacerefp && varp->isIfaceRef()) {

View File

@ -2529,12 +2529,17 @@ class ParamVisitor final : public VNVisitor {
std::deque<std::string> m_strings; // Allocator for temporary strings
std::map<const AstRefDType*, bool>
m_isCircular; // Stores information whether `AstRefDType` is circular
using VarsByName = std::unordered_map<std::string, AstVar*>;
// Persists across modules; one specialized interface clone serves every module bound to it
std::unordered_map<const AstNodeModule*, VarsByName> m_ifaceParams;
// STATE - for current visit position (use VL_RESTORER)
AstNodeModule* m_modp = nullptr; // Module iterating
std::unordered_set<std::string> m_ifacePortNames; // Interface port names in current module
std::unordered_map<std::string, AstCell*>
m_ifaceInstCells; // Local interface instance cells in current module, keyed by name
VarsByName m_modIfaceRefs; // Interface-ref Vars in current module, keyed by name
bool m_modIfaceRefsDone = false; // m_modIfaceRefs has been gathered for m_modp
string m_generateHierName; // Generate portion of hierarchy name
// METHODS
@ -2573,7 +2578,10 @@ class ParamVisitor final : public VNVisitor {
VL_RESTORER(m_modp);
VL_RESTORER_CLEAR(m_ifacePortNames);
VL_RESTORER_CLEAR(m_ifaceInstCells);
VL_RESTORER_CLEAR(m_modIfaceRefs);
VL_RESTORER(m_modIfaceRefsDone);
m_modp = modp;
m_modIfaceRefsDone = false;
iterateChildren(modp);
}
}
@ -2925,6 +2933,76 @@ class ParamVisitor final : public VNVisitor {
if (!VN_IS(nodep->classOrPackageNodep(), Typedef)) visitCellOrClassRef(nodep, false);
}
// Recurse into AstGenBlock as generate blocks aren't flattened until V3Begin::debeginAll,
// well after V3Param runs
static void gatherVars(AstNode* stmtsp, bool (*matchp)(const AstVar*), VarsByName& varsr) {
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
if (AstVar* const varp = VN_CAST(nodep, Var)) {
// emplace, not assign, so the first declaration of a name wins
if (matchp(varp)) varsr.emplace(varp->name(), varp);
} else if (AstGenBlock* const genp = VN_CAST(nodep, GenBlock)) {
gatherVars(genp->itemsp(), matchp, varsr);
}
}
}
const VarsByName& modIfaceRefs() {
if (!m_modIfaceRefsDone) {
gatherVars(
m_modp->stmtsp(), [](const AstVar* varp) { return varp->isIfaceRef(); },
m_modIfaceRefs);
m_modIfaceRefsDone = true;
}
return m_modIfaceRefs;
}
const VarsByName& ifaceParams(AstNodeModule* ifacep) {
const auto pair = m_ifaceParams.emplace(ifacep, VarsByName{});
if (pair.second) {
gatherVars(
ifacep->stmtsp(), [](const AstVar* varp) { return varp->isParam(); },
pair.first->second);
}
return pair.first->second;
}
// Resolve a generic-interface 'ifacePort.member' Dot left unlinked by V3LinkDot, now that
// ifacePort may have been specialized. Returns nullptr if still unresolvable
AstNode* tryResolveGenericIfaceDot(AstDot* dotp) {
AstParseRef* const lhsp = VN_CAST(dotp->lhsp(), ParseRef);
AstParseRef* const rhsp = VN_CAST(dotp->rhsp(), ParseRef);
if (!lhsp || !rhsp) return nullptr;
// Always called from visit(AstVar) inside processWorkQ()'s VL_RESTORER(m_modp) scope
if (VL_UNCOVERABLE(!m_modp)) return nullptr;
const auto& refVars = modIfaceRefs();
const auto ifaceVarIt = refVars.find(lhsp->name());
// V3LinkDot only defers Dots whose lhs is a non-array iface-ref var, so this can't fail
if (VL_UNCOVERABLE(ifaceVarIt == refVars.end())) return nullptr;
AstVar* const ifaceVarp = ifaceVarIt->second;
AstIfaceRefDType* const ifacerefp = VN_CAST(ifaceVarp->subDTypep(), IfaceRefDType);
if (VL_UNCOVERABLE(!ifacerefp || !ifacerefp->ifacep())) return nullptr;
const auto& ifaceVars = ifaceParams(ifacerefp->ifacep());
const auto targetVarIt = ifaceVars.find(rhsp->name());
if (targetVarIt == ifaceVars.end()) return nullptr;
AstVar* const targetVarp = targetVarIt->second;
// processWorkQ() visits interface cells first, so the interface is already constified
if (VL_UNCOVERABLE(!VN_IS(targetVarp->valuep(), Const))) iterate(targetVarp);
if (VL_UNCOVERABLE(!targetVarp->valuep() || !VN_IS(targetVarp->valuep(), Const))) {
return nullptr; // LCOV_EXCL_LINE
}
return targetVarp->valuep()->cloneTree(false);
}
void resolveGenericIfaceDotsIn(AstNode* nodep) {
// Collect first: the deleteTree() below frees a node foreach() would still read from
std::vector<AstDot*> dotps;
nodep->foreach([&](AstDot* dotp) { dotps.push_back(dotp); });
for (AstDot* const dotp : dotps) {
if (AstNode* const newp = tryResolveGenericIfaceDot(dotp)) {
dotp->replaceWith(newp);
VL_DO_DANGLING(dotp->deleteTree(), dotp);
}
}
}
// Make sure all parameters are constantified
void visit(AstVar* nodep) override {
if (nodep->user2SetOnce()) return; // Process once
@ -2937,6 +3015,8 @@ class ParamVisitor final : public VNVisitor {
nodep->v3error("Parameter without default value is never given value"
<< " (IEEE 1800-2023 6.20.1): " << nodep->prettyNameQ());
} else if (nodep->valuep()) {
// Resolve now: a sibling cell's pin fold may need this before linkDotParamed
resolveGenericIfaceDotsIn(nodep->valuep());
// If the value expression contains a VarXRef to an interface
// localparam whose value is not yet constant, defer constification
// to avoid premature widthing with unresolved values (see
@ -2952,9 +3032,8 @@ class ParamVisitor final : public VNVisitor {
}
});
if (hasUnresolvedLparamXRef) return;
// 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); });
// Defer if value has any unresolved Dot, or references a deferred lparam
const bool hasDot = nodep->valuep()->exists([](AstDot*) { return true; });
bool refsDeferred = false;
if (!hasDot) {
const auto& deferredVarps = v3Global.rootp()->deferredParamVarps();

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=["--timing"])
test.execute()
test.passes()

View File

@ -0,0 +1,40 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
// 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 inf #(parameter int PARAM = 1);
logic [PARAM-1:0] v;
endinterface
module leaf (inf d);
initial begin
#1;
`checkd(d.v, 13);
`checkd(d.PARAM, 5);
end
endmodule
module mid2 (interface c);
leaf leaf_i(.d(c));
endmodule
module mid1 (interface b);
mid2 mid2_i(.c(b));
endmodule
module t;
inf #(.PARAM(5)) a();
mid1 mid1_i(.b(a));
initial begin
a.v = 13;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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=["--timing"])
test.execute()
test.passes()

View File

@ -0,0 +1,37 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2026 PlanV GmbH
// SPDX-License-Identifier: CC0-1.0
// 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 inf #(parameter int PARAM = 1);
logic [PARAM-1:0] v;
endinterface
module leaf (inf c);
initial begin
#1;
`checkd(c.v, 13)
`checkd(c.PARAM, 5)
end
endmodule
module mid #(int PARAM = 1) (interface b);
leaf leaf_i(.c(b));
initial `checkd(PARAM,7)
endmodule
module t;
inf #(.PARAM(5)) a();
mid #(.PARAM(7)) mid_i (.b(a));
initial begin
a.v = 13;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--timing'])
test.execute()
test.passes()

View File

@ -0,0 +1,45 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2025 Antmicro
// SPDX-License-Identifier: CC0-1.0
// 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 inf #(parameter int PARAM = 1);
logic [PARAM-1:0] v;
modport mp (input v);
endinterface
module Leaf #(parameter int PARAM = 1) (inf.mp leaf_a);
initial begin
#1;
`checkd(leaf_a.PARAM, PARAM);
end
endmodule
module GenericModule (interface.mp a);
localparam LOC_PARAM = a.PARAM;
// A generic interface's parameter, parameterizing a sibling interface cell
inf #(.PARAM(LOC_PARAM)) nested_inst();
Leaf #(.PARAM(LOC_PARAM)) leaf (nested_inst);
initial begin
#1;
`checkd(a.v, 7);
`checkd(a.PARAM, 13);
`checkd(LOC_PARAM, a.PARAM);
end
endmodule
module t;
inf #(.PARAM(13)) inf_inst();
GenericModule genericModule (inf_inst);
initial begin
inf_inst.v = 7;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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=['--timing'])
test.execute()
test.passes()

View File

@ -0,0 +1,45 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 David Garau
// SPDX-License-Identifier: CC0-1.0
// 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 leaf_if;
localparam int LEAF_PARAM = 42;
logic dummy;
endinterface
interface inf #(parameter int PARAM = 1);
logic [PARAM-1:0] v;
// Makes 'a.nested.LEAF_PARAM' a chained Dot, whose lhsp is itself a Dot
leaf_if nested();
parameter int ARR[2] = '{PARAM, PARAM + 1};
modport mp (input v);
endinterface
module GenericModule (interface.mp a);
localparam int LOC_CHAIN = a.nested.LEAF_PARAM;
// Makes the Dot's rhsp an indexed select, not a plain identifier
localparam int LOC_ARR = a.ARR[0];
initial begin
#1;
`checkd(a.v, 7);
`checkd(LOC_CHAIN, 42);
`checkd(LOC_ARR, 13);
end
endmodule
module t;
inf #(.PARAM(13)) inf_inst();
GenericModule genericModule (inf_inst);
initial begin
inf_inst.v = 7;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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=['--timing'])
test.execute()
test.passes()

View File

@ -0,0 +1,53 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 David Garau
// SPDX-License-Identifier: CC0-1.0
// 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 inf #(parameter int PARAM = 1);
logic [PARAM-1:0] v;
// A non-Var/non-GenBlock item the member gather must skip past
modport mp (input v);
if (1) begin : blk_decoy
// Same name as the real parameter below, but not a parameter
logic PARAM_IF;
end
if (1) begin : blk_if
localparam int PARAM_IF = PARAM + 100;
end
for (genvar i = 0; i < 1; i++) begin : blk_for
localparam int PARAM_FOR = PARAM + 200;
end
endinterface
module GenericModule (interface.mp a);
// Two references to the same member, so the second reuses the gathered members
localparam int LOC_PARAM1 = a.PARAM;
localparam int LOC_PARAM2 = a.PARAM;
// Members inside generate blocks, reachable only by recursing into them
localparam int LOC_IF = a.PARAM_IF;
localparam int LOC_FOR = a.PARAM_FOR;
initial begin
#1;
`checkd(a.v, 7);
`checkd(LOC_PARAM1, 13);
`checkd(LOC_PARAM2, 13);
`checkd(LOC_IF, 113);
`checkd(LOC_FOR, 213);
end
endmodule
module t;
inf #(.PARAM(13)) inf_inst();
GenericModule genericModule (inf_inst);
initial begin
inf_inst.v = 7;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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=['--timing'])
test.execute()
test.passes()

View File

@ -0,0 +1,41 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 David Garau
// SPDX-License-Identifier: CC0-1.0
// 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 inf #(parameter int PARAM = 1);
logic [PARAM-1:0] v;
modport mp (input v);
endinterface
module GenericModule (interface.mp a);
// Dotted localparam inside a generate block, feeding a sibling cell in the same block.
// The interface-ref gather must recurse in here yet still find port 'a' at module level.
// Generate blocks on the interface side are covered by t_interface_generic_iface_param_genblock.
if (1) begin : blk
localparam int LOC_PARAM = a.PARAM;
inf #(.PARAM(LOC_PARAM)) inner();
end
initial begin
#1;
`checkd(a.v, 7);
`checkd(blk.LOC_PARAM, 13);
`checkd(blk.inner.PARAM, 13);
end
endmodule
module t;
inf #(.PARAM(13)) inf_inst();
GenericModule genericModule (inf_inst);
initial begin
inf_inst.v = 7;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

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: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(verilator_flags2=['--timing'])
test.execute()
test.passes()

View File

@ -0,0 +1,41 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2025 Antmicro
// SPDX-License-Identifier: CC0-1.0
// 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 inf #(int PARAM = 0);
logic[PARAM-1:0] v;
modport port_in (
input v
);
modport port_out (
output v
);
endinterface
module GenericModule #(PARAM=0) (interface.port_in a);
localparam int LOC_PARAM = a.PARAM;
initial begin
#1;
`checkd(a.v, 7);
`checkd(a.PARAM, 13);
`checkd(LOC_PARAM, a.PARAM);
end
endmodule
module t;
inf #(.PARAM(13)) inf_inst();
GenericModule genericModule (inf_inst);
initial begin
inf_inst.v = 7;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,7 @@
%Error: t/t_interface_generic_iface_param_ref_bad.v:19:32: Can't find definition of 'NONEXISTENT_PARAM' in dotted variable/method: 'a.NONEXISTENT_PARAM'
: ... note: In instance 't.genericModule'
19 | localparam int LOC_PARAM = a.NONEXISTENT_PARAM;
| ^~~~~~~~~~~~~~~~~
... Known scopes under 'a': <no instances found>
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

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()

View File

@ -0,0 +1,34 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 David Garau
// SPDX-License-Identifier: CC0-1.0
// 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 inf #(parameter int PARAM = 1);
logic [PARAM-1:0] v;
modport mp (input v);
endinterface
module GenericModule (interface.mp a);
// A member that does not exist on the (now concrete) interface
localparam int LOC_PARAM = a.NONEXISTENT_PARAM;
initial begin
#1;
`checkd(a.v, 7);
end
endmodule
module t;
inf #(.PARAM(13)) inf_inst();
GenericModule genericModule (inf_inst);
initial begin
inf_inst.v = 7;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule