Fix generic interface port forwarded to a nested instance (#7454) (#7457)

Fixes #7454.
This commit is contained in:
Yilou Wang 2026-04-28 03:15:25 +02:00 committed by GitHub
parent c460f0e6a0
commit 5d1b4fe8a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 126 additions and 1 deletions

View File

@ -3609,7 +3609,36 @@ class LinkDotResolveVisitor final : public VNVisitor {
while (const AstNodePreSel* const preSelp = VN_CAST(exprp, NodePreSel)) {
exprp = preSelp->fromp();
}
if (const AstVarRef* const varRefp = VN_CAST(exprp, VarRef)) {
// Resolve pin expression to the enclosing module's port Var. At primary
// LinkDot the expression may still be an AstParseRef, so also look up by name.
AstVar* enclosingVarp = nullptr;
const AstVarRef* const varRefp = VN_CAST(exprp, VarRef);
if (varRefp) {
enclosingVarp = varRefp->varp();
} else if (const AstParseRef* const parseRefp = VN_CAST(exprp, ParseRef)) {
if (m_modp) {
if (VSymEnt* const symp
= m_statep->getNodeSym(m_modp)->findIdFlat(parseRefp->name())) {
enclosingVarp = VN_CAST(symp->nodep(), Var);
}
}
}
if (enclosingVarp && enclosingVarp->varType() == VVarType::IFACEREF
&& VN_IS(enclosingVarp->childDTypep()->skipRefp(), IfaceGenericDType)) {
// Nested generic-iface forwarding (#7454): enclosing port is itself still
// generic, so emit a placeholder __VGIfaceParam pin carrying a VarRef to
// the outer port. V3Param rewrites it to the concrete IfaceRefDType once
// the enclosing module is specialized (see
// ParamProcessor::resolveGenericIfaceForwardingPins for the ordering
// constraint that keeps the rewrite inside V3Param).
AstVarRef* const fwdRefp
= new AstVarRef{exprp->fileline(), enclosingVarp, VAccess::READ};
AstPin* const newPinp = new AstPin{
pinp->fileline(), paramNum, "__VGIfaceParam" + modIfaceVarp->name(), fwdRefp};
newPinp->param(true);
visit(newPinp);
nodep->addParamsp(newPinp);
} else if (varRefp) {
const AstVar* const varp = varRefp->varp();
if (const AstIfaceRefDType* const refp
= VN_CAST(getElemDTypep(varp->childDTypep()), IfaceRefDType)) {

View File

@ -1360,6 +1360,40 @@ class ParamProcessor final {
return isEq.isNeqZero();
}
// Rewrite placeholder __VGIfaceParam pins emitted by
// LinkDotResolveVisitor::addImplicitParametersOfGenericIface for a nested
// generic-interface forwarding chain (#7454). Must run here (not in
// V3LinkDot's Param/Resolve visitor at LDS_PARAMED) because:
// * linkDotParamed runs AFTER V3Param, at which point cellDeparam has
// already consumed the pin via moduleFindOrClone (variant naming off
// pinp->exprp()'s IfaceRefDType) and genericInterfaceVarSetup (hard
// VN_AS cast to IfaceRefDType); and
// * the enclosing module's port only becomes a concrete IfaceRefDType
// after its own nodeDeparam has specialized it. That specialization
// happens in V3Param's top-down walk, so the window between "outer
// port resolved" and "inner cellDeparam starts" exists only here.
void resolveGenericIfaceForwardingPins(AstPin* paramsp) {
for (AstPin* pinp = paramsp; pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (!pinp->exprp()) continue;
if (!VString::startsWith(pinp->name(), "__VGIfaceParam")) continue;
const AstNodeVarRef* const fwdRefp = VN_CAST(pinp->exprp(), NodeVarRef);
if (!fwdRefp) continue;
const AstIfaceRefDType* const resolvedp
= VN_CAST(fwdRefp->varp()->childDTypep()->skipRefp(), IfaceRefDType);
UASSERT_OBJ(resolvedp, pinp,
"Generic interface forwarding pin not specialized before use");
AstIfaceRefDType* const newIrefp = new AstIfaceRefDType{
resolvedp->fileline(), resolvedp->modportFileline(), resolvedp->cellName(),
resolvedp->ifaceName(), resolvedp->modportName()};
newIrefp->ifacep(resolvedp->ifacep());
if (resolvedp->paramsp()) {
newIrefp->addParamsp(resolvedp->paramsp()->cloneTree(true));
}
pinp->exprp()->unlinkFrBack()->deleteTree();
pinp->exprp(newIrefp);
}
}
void cellPinCleanup(AstNode* nodep, AstPin* pinp, AstPin* paramsp, AstNodeModule* srcModp,
string& longnamer, bool& any_overridesr) {
if (!pinp->exprp()) return; // No-connect
@ -2114,6 +2148,10 @@ public:
}
pinp = nextp;
}
// Nested generic-iface forwarding (#7454): rewrite VarRef placeholders
// left by V3LinkDot to concrete IfaceRefDTypes now that the enclosing
// module has been specialized.
resolveGenericIfaceForwardingPins(cellp->paramsp());
}
// Create new module name with _'s between the constants
UINFOTREE(10, nodep, "", "cell");

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