diff --git a/src/V3Param.cpp b/src/V3Param.cpp index a0064a22e..b631f957c 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -2510,7 +2510,8 @@ class ParamVisitor final : public VNVisitor { // STATE - for current visit position (use VL_RESTORER) AstNodeModule* m_modp = nullptr; // Module iterating std::unordered_set m_ifacePortNames; // Interface port names in current module - std::unordered_set m_ifaceInstNames; // Interface decl names in current module + std::unordered_map + m_ifaceInstCells; // Local interface instance cells in current module, keyed by name string m_generateHierName; // Generate portion of hierarchy name // METHODS @@ -2548,10 +2549,10 @@ class ParamVisitor final : public VNVisitor { { VL_RESTORER(m_modp); VL_RESTORER(m_ifacePortNames); - VL_RESTORER(m_ifaceInstNames); + VL_RESTORER(m_ifaceInstCells); m_modp = modp; m_ifacePortNames.clear(); - m_ifaceInstNames.clear(); + m_ifaceInstCells.clear(); iterateChildren(modp); } } @@ -2723,7 +2724,7 @@ class ParamVisitor final : public VNVisitor { const string refname = getRefBaseName(refp); isIfaceRef = !refname.empty() - && (m_ifacePortNames.count(refname) || m_ifaceInstNames.count(refname)); + && (m_ifacePortNames.count(refname) || m_ifaceInstCells.count(refname)); } if (!isIfaceRef) { @@ -2780,7 +2781,7 @@ class ParamVisitor final : public VNVisitor { const string refname = getRefBaseName(refp); if (!refname.empty() && (m_ifacePortNames.count(refname) - || m_ifaceInstNames.count(refname))) + || m_ifaceInstCells.count(refname))) return true; } } @@ -2879,8 +2880,7 @@ class ParamVisitor final : public VNVisitor { } void visit(AstCell* nodep) override { checkParamNotHier(nodep->paramsp()); - // Build cache of locally declared interface instance names - if (VN_IS(nodep->modp(), Iface)) { m_ifaceInstNames.insert(nodep->name()); } + if (VN_IS(nodep->modp(), Iface)) m_ifaceInstCells.emplace(nodep->name(), nodep); visitCellOrClassRef(nodep, VN_IS(nodep->modp(), Iface)); } void visit(AstIfaceRefDType* nodep) override { @@ -2984,6 +2984,21 @@ class ParamVisitor final : public VNVisitor { } return false; } + + void deparamIfaceCellNow(AstCell* cellp) { + if (!cellp->paramsp()) return; + if (!VN_IS(cellp->modp(), Iface)) return; + AstNodeModule* const srcModp = cellp->modp(); + AstNodeModule* const newModp + = m_processor.nodeDeparam(cellp, srcModp, m_modp, m_modp->someInstanceName()); + if (newModp && newModp != srcModp) { + if (V3LinkDotIfaceCapture::enabled()) { + m_processor.retargetIfaceRefs(m_modp, cellp->name()); + } + specializeNestedIfaceCells(newModp); + } + } + void visit(AstNodeFTaskRef* nodep) override { if (nodep->containsGenBlock()) { // Needs relink, as may remove pointed-to task/func @@ -3034,9 +3049,10 @@ class ParamVisitor final : public VNVisitor { } } // Interfaces declared in this module have cells - else if (const AstCell* const cellp = ifacerefp->cellp()) { + else if (AstCell* const cellp = ifacerefp->cellp()) { if (dotted == cellp->name()) { UINFO(9, "Iface matching scope: " << cellp); + deparamIfaceCellNow(cellp); if (ifaceParamReplace(nodep, cellp->modp()->stmtsp())) { // return; } @@ -3044,6 +3060,13 @@ class ParamVisitor final : public VNVisitor { } } } + // Fallback: a direct local interface instance ("inst.PARAM"), not reached via a port. + const auto ifaceCellIt = m_ifaceInstCells.find(dotted); + if (ifaceCellIt != m_ifaceInstCells.end()) { + AstCell* const cellp = ifaceCellIt->second; + deparamIfaceCellNow(cellp); + ifaceParamReplace(nodep, cellp->modp()->stmtsp()); + } } } diff --git a/test_regress/t/t_interface_modport_param.py b/test_regress/t/t_interface_modport_param.py new file mode 100755 index 000000000..e7721d960 --- /dev/null +++ b/test_regress/t/t_interface_modport_param.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_st") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_modport_param.v b/test_regress/t/t_interface_modport_param.v new file mode 100644 index 000000000..65835766a --- /dev/null +++ b/test_regress/t/t_interface_modport_param.v @@ -0,0 +1,58 @@ +// DESCRIPTION: Verilator: Get parameter from modport interface +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// 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 intf #( + parameter int ITEM_QTY = 1 +); + logic item; + + modport source(input item); +endinterface + +module pass_through ( + intf.source in_port, + output logic [31:0] item_qty +); + intf #( + .ITEM_QTY(in_port.ITEM_QTY) + ) internal_port (); + + if (internal_port.ITEM_QTY == 1) begin : g_saw_default_item_qty + $error("generate if evaluated internal_port.ITEM_QTY as interface default 1"); + end + else if (internal_port.ITEM_QTY != 20) begin : g_bad_item_qty + $error("generate if evaluated internal_port.ITEM_QTY as neither 1 nor 20"); + end + + assign internal_port.item = in_port.item; + assign item_qty = internal_port.ITEM_QTY + internal_port.item; +endmodule + +module t; + intf #( + .ITEM_QTY(20) + ) in_port (); + + logic [31:0] item_qty; + + assign in_port.item = 1'b0; + + pass_through dut ( + .in_port (in_port), + .item_qty(item_qty) + ); + + initial begin + `checkd(item_qty, 20); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule