From 728ddf33310300b060367edb38ed5ec6bfc911a8 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Wed, 25 Mar 2026 12:16:52 +0100 Subject: [PATCH] Fix modport selection of virtual interface handle (#7321) --- src/V3Width.cpp | 17 +++ .../t/t_interface_virtual_modport_sel.py | 18 +++ .../t/t_interface_virtual_modport_sel.v | 107 ++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100755 test_regress/t/t_interface_virtual_modport_sel.py create mode 100644 test_regress/t/t_interface_virtual_modport_sel.v diff --git a/src/V3Width.cpp b/src/V3Width.cpp index eec39fe8a..d34252725 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3522,6 +3522,23 @@ class WidthVisitor final : public VNVisitor { nodep->didWidth(true); return; } + if (AstModport* const modportp = VN_CAST(foundp, Modport)) { + // Modport selection (e.g. vif.passive_mp) is compile-time + // type narrowing: replace MemberSel with fromp re-typed. + AstIfaceRefDType* const newDtypep = new AstIfaceRefDType{ + nodep->fileline(), nodep->fileline(), adtypep->cellName(), + adtypep->ifaceName(), modportp->name()}; + newDtypep->ifacep(adtypep->ifacep()); + newDtypep->cellp(adtypep->cellp()); + newDtypep->modportp(modportp); + newDtypep->isVirtual(adtypep->isVirtual()); + v3Global.rootp()->typeTablep()->addTypesp(newDtypep); + AstNodeExpr* const fromp = nodep->fromp()->unlinkFrBack(); + fromp->dtypep(newDtypep); + nodep->replaceWith(fromp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } UINFO(1, "found object " << foundp); nodep->v3fatalSrc("MemberSel of non-variable\n" << nodep->warnContextPrimary() << '\n' diff --git a/test_regress/t/t_interface_virtual_modport_sel.py b/test_regress/t/t_interface_virtual_modport_sel.py new file mode 100755 index 000000000..664e902bf --- /dev/null +++ b/test_regress/t/t_interface_virtual_modport_sel.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=["--binary", "--timing"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_modport_sel.v b/test_regress/t/t_interface_virtual_modport_sel.v new file mode 100644 index 000000000..187dbc0f4 --- /dev/null +++ b/test_regress/t/t_interface_virtual_modport_sel.v @@ -0,0 +1,107 @@ +// 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 checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +// Modport selection on virtual interface handles: +// vif.modport_name (direct) +// obj.vif.modport_name (chained through class member) + +interface my_if (input logic clk); + logic [7:0] data; + + clocking mon_cb @(posedge clk); + input data; + endclocking + + modport passive_mp (clocking mon_cb); + modport active_mp (output data); + modport signal_mp (input data); +endinterface + +class Context; + virtual my_if vif; +endclass + +class Monitor; + virtual my_if.passive_mp mp; + + function void connect_chain(Context cntxt); + mp = cntxt.vif.passive_mp; + endfunction + + function void connect_tmp(Context cntxt); + automatic virtual my_if tmp = cntxt.vif; + mp = tmp.passive_mp; + endfunction +endclass + +class Driver; + virtual my_if.active_mp drv; + + function void connect(Context cntxt); + drv = cntxt.vif.active_mp; + endfunction + + task drive(input logic [7:0] val); + drv.data = val; + endtask +endclass + +class Reader; + virtual my_if.signal_mp sig; + + function void connect(Context cntxt); + sig = cntxt.vif.signal_mp; + endfunction +endclass + +module t; + logic clk = 0; + always #5 clk = ~clk; + my_if vif (.clk(clk)); + + initial begin + automatic Context c = new; + automatic Monitor m1 = new; + automatic Monitor m2 = new; + automatic Driver d = new; + automatic Reader r = new; + c.vif = vif; + + // Connect via chain and tmp paths + m1.connect_chain(c); + m2.connect_tmp(c); + d.connect(c); + r.connect(c); + + // Drive data through active_mp, read through passive_mp and signal_mp + d.drive(8'hAB); + @(posedge clk); + @(posedge clk); + + // Verify clocking-block modport (m1 = chain, m2 = tmp) + `checkh(m1.mp.mon_cb.data, 8'hAB); + `checkh(m2.mp.mon_cb.data, 8'hAB); + + // Verify signal modport (no clocking block) + `checkh(r.sig.data, 8'hAB); + + // Drive new value, verify update propagates + d.drive(8'hCD); + @(posedge clk); + @(posedge clk); + + `checkh(m1.mp.mon_cb.data, 8'hCD); + `checkh(r.sig.data, 8'hCD); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule