From 5d1b4fe8a88a0c1141499e4580d3c4d6fb931fa6 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Tue, 28 Apr 2026 03:15:25 +0200 Subject: [PATCH] Fix generic interface port forwarded to a nested instance (#7454) (#7457) Fixes #7454. --- src/V3LinkDot.cpp | 31 +++++++++++++- src/V3Param.cpp | 38 ++++++++++++++++++ .../t/t_interface_generic_iface_forward.py | 18 +++++++++ .../t/t_interface_generic_iface_forward.v | 40 +++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100755 test_regress/t/t_interface_generic_iface_forward.py create mode 100644 test_regress/t/t_interface_generic_iface_forward.v diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 148e639ab..47401a68b 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -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)) { diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 480432dae..13ead637a 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -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"); diff --git a/test_regress/t/t_interface_generic_iface_forward.py b/test_regress/t/t_interface_generic_iface_forward.py new file mode 100755 index 000000000..6ac2815da --- /dev/null +++ b/test_regress/t/t_interface_generic_iface_forward.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=["--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_generic_iface_forward.v b/test_regress/t/t_interface_generic_iface_forward.v new file mode 100644 index 000000000..2608a3ac9 --- /dev/null +++ b/test_regress/t/t_interface_generic_iface_forward.v @@ -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