diff --git a/src/V3Task.cpp b/src/V3Task.cpp index d05e44254..6c9a5578e 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -251,6 +251,13 @@ private: if (nodep->dpiImport()) m_curVxp->noInline(true); if (nodep->classMethod()) m_curVxp->noInline(true); // Until V3Task supports it if (nodep->recursive()) m_curVxp->noInline(true); + // V3Scope resolves virtual-interface MethodCalls via user2p (last-wins), + // so inlining would bake in the wrong instance's VarScope refs. + if (v3Global.hasVirtIfaces()) { + if (const AstScope* const scopep = VN_CAST(nodep->user3p(), Scope)) { + if (VN_IS(scopep->modp(), Iface)) m_curVxp->noInline(true); + } + } if (nodep->isConstructor()) { m_curVxp->noInline(true); m_ctorp = nodep; @@ -1690,10 +1697,12 @@ class TaskVisitor final : public VNVisitor { } const bool noInline = m_statep->ftaskNoInline(nodep); - // Warn if not inlining an impure ftask (unless method or recursvie). + // Warn if not inlining an impure ftask (unless method, recursive, + // or interface function -- interface member access is not truly external). // Will likely not schedule correctly. // TODO: Why not if recursive? It will not work ... - if (noInline && !nodep->classMethod() && !nodep->recursive()) { + if (noInline && !nodep->classMethod() && !nodep->recursive() + && !VN_IS(m_modp, Iface)) { if (AstNode* const impurep = m_statep->checkImpure(nodep)) { nodep->v3warn( IMPURE, diff --git a/test_regress/t/t_interface_virtual_func_wait.py b/test_regress/t/t_interface_virtual_func_wait.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_wait.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"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_func_wait.v b/test_regress/t/t_interface_virtual_func_wait.v new file mode 100644 index 000000000..7136bf1bb --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_wait.v @@ -0,0 +1,41 @@ +// 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 + +interface my_if; + logic clk = 0; + bit clk_active = 0; + + initial begin + wait (clk_active); + forever #5 clk = ~clk; + end + + function void start_clk(); + clk_active = 1; + endfunction +endinterface + +class Driver; + virtual my_if vif; + task run(); + #10; + vif.start_clk(); + endtask +endclass + +module t; + my_if intf(); + my_if intf_unused(); // Second instance triggered the bug + + initial begin + automatic Driver d = new; + d.vif = intf; + d.run(); + repeat (4) @(posedge intf.clk); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule