From efd60df2bea79ac8edbf4c0e43fabd73fffda16e Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Sat, 4 Apr 2026 01:04:10 +0200 Subject: [PATCH] Fix virtual interface select from sub-interface instance (#7203) (#7370) Fixes #7203. --- src/V3EmitCFunc.h | 8 +- src/V3Width.cpp | 32 +++++++- .../t/t_interface_virtual_sub_iface.py | 18 +++++ .../t/t_interface_virtual_sub_iface.v | 74 +++++++++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100755 test_regress/t/t_interface_virtual_sub_iface.py create mode 100644 test_regress/t/t_interface_virtual_sub_iface.v diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 465b6b585..6192c8c33 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1517,7 +1517,13 @@ public: void visit(AstMemberSel* nodep) override { iterateAndNextConstNull(nodep->fromp()); putnbs(nodep, "->"); - puts(nodep->varp()->nameProtect()); + if (nodep->varp()->isIfaceRef()) { + // varp is the __Viftop companion (e.g. "tx__Viftop"); use the + // MemberSel name which matches the cell's C++ member (e.g. "tx"). + puts(nodep->nameProtect()); + } else { + puts(nodep->varp()->nameProtect()); + } } void visit(AstStructSel* nodep) override { iterateAndNextConstNull(nodep->fromp()); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 8e3f54e14..760ec7714 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3574,6 +3574,32 @@ class WidthVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); return; } + if (AstCell* const cellp = VN_CAST(foundp, Cell)) { + // Sub-interface cell selection (e.g. vif.tx): resolve to the + // companion __Viftop var created by V3LinkCells for its dtype. + if (VN_IS(cellp->modp(), Iface)) { + const string viftopName = cellp->name() + "__Viftop"; + AstNodeModule* const parentIfacep = adtypep->ifaceViaCellp(); + AstVar* viftopVarp = nullptr; + for (AstNode* itemp = parentIfacep->stmtsp(); itemp; + itemp = itemp->nextp()) { + if (AstVar* const vp = VN_CAST(itemp, Var)) { + if (vp->name() == viftopName) { + viftopVarp = vp; + break; + } + } + } + UASSERT_OBJ(viftopVarp, nodep, + "No __Viftop variable for sub-interface cell"); + if (!viftopVarp->didWidth()) userIterate(viftopVarp, nullptr); + nodep->dtypep(viftopVarp->dtypep()); + nodep->varp(viftopVarp); + viftopVarp->sensIfacep(VN_AS(cellp->modp(), Iface)); + nodep->didWidth(true); + return; + } + } UINFO(1, "found object " << foundp); nodep->v3fatalSrc("MemberSel of non-variable\n" << nodep->warnContextPrimary() << '\n' @@ -3695,12 +3721,14 @@ class WidthVisitor final : public VNVisitor { AstNode* memberSelIface(AstMemberSel* nodep, AstIfaceRefDType* adtypep) { // Returns node if ok // No need to width-resolve the interface, as it was done when we did the child - AstNodeModule* const ifacep = adtypep->ifacep(); + // ifaceViaCellp() handles dtypes with cellp-only (no ifacep), as produced + // by sub-interface selection, enabling chained access (e.g. vif.tx.Tx). + AstNodeModule* const ifacep = adtypep->ifaceViaCellp(); UASSERT_OBJ(ifacep, nodep, "Unlinked"); VSpellCheck speller; for (AstNode* itemp = ifacep->stmtsp(); itemp; itemp = itemp->nextp()) { if (itemp->name() == nodep->name()) return itemp; - if (VN_IS(itemp, Var) || VN_IS(itemp, Modport)) { + if (VN_IS(itemp, Var) || VN_IS(itemp, Modport) || VN_IS(itemp, Cell)) { speller.pushCandidate(itemp->prettyName()); } } diff --git a/test_regress/t/t_interface_virtual_sub_iface.py b/test_regress/t/t_interface_virtual_sub_iface.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_sub_iface.v b/test_regress/t/t_interface_virtual_sub_iface.v new file mode 100644 index 000000000..53ad587b4 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sub_iface.v @@ -0,0 +1,74 @@ +// 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 + +// Issue #7203: virtual interface select from sub-interface instance. +// The original reproducer: vip_agent holds vip_vif; vip_driver selects +// agent.vif.tx (a vip_tx_if sub-interface) into tx_vif. + +interface vip_tx_if ( + output reg Tx +); +endinterface + +interface vip_if ( + output reg Tx +); + vip_tx_if tx (Tx); +endinterface + +package vip_pkg; + typedef virtual vip_if vip_vif; + typedef virtual vip_tx_if vip_tx_vif; + + class vip_agent; + vip_vif vif; + endclass + + class vip_driver; + vip_vif vif; + vip_tx_vif tx_vif; + virtual function void build_phase(vip_agent agent); + // Sub-interface select: dtype(agent.vif) -> vip_vif -> vip_if + vif = agent.vif; + tx_vif = agent.vif.tx; + endfunction + // Chained member access through sub-interface + virtual function void drive(logic val); + vif.tx.Tx = val; + endfunction + endclass +endpackage + +module t; + logic wire_Tx; + vip_if vif_inst (.Tx(wire_Tx)); + + initial begin + automatic vip_pkg::vip_agent agent = new; + automatic vip_pkg::vip_driver driver = new; + + agent.vif = vif_inst; + driver.vif = vif_inst; + + // Test 1 (issue reproducer): sub-interface select compiles and runs + driver.build_phase(agent); + + // Test 2: tx_vif now points to the sub-interface; write through it + driver.tx_vif.Tx = 1'b1; + `checkd(wire_Tx, 1'b1) + + // Test 3: chained member write through virtual interface + driver.drive(1'b0); + `checkd(wire_Tx, 1'b0) + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule