From eb114eb96f0039b5989e8b7077274b0759c82490 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Fri, 27 Mar 2026 15:18:09 +0100 Subject: [PATCH] Fix wait() hang in interface with always @* using process::self() and VIF function calls --- src/V3Timing.cpp | 7 ++- src/V3Width.cpp | 10 +++++ test_regress/t/t_wait_iface_vif.py | 18 ++++++++ test_regress/t/t_wait_iface_vif.v | 71 ++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100755 test_regress/t/t_wait_iface_vif.py create mode 100755 test_regress/t/t_wait_iface_vif.v diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 316a9c642..602b7de06 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -892,7 +892,12 @@ class TimingControlVisitor final : public VNVisitor { m_underProcedure = true; // Workaround for killing `always` processes (doing that is pretty much UB) // TODO: Disallow killing `always` at runtime (throw an error) - if (hasFlags(nodep, T_HAS_PROC)) addFlags(nodep, T_SUSPENDEE); + // Do not make combinational always blocks (always @*) suspendable just because + // they need process context (e.g. for uvm_fatal's process::self()). Combo blocks + // have no timing controls and would spin forever as coroutines. + if (hasFlags(nodep, T_HAS_PROC) + && !(m_activep && m_activep->sentreep() && m_activep->sentreep()->hasCombo())) + addFlags(nodep, T_SUSPENDEE); iterateChildren(nodep); if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess(); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index a4a8c0c2d..f5b1244a8 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -4464,6 +4464,16 @@ class WidthVisitor final : public VNVisitor { if (AstNodeFTask* const ftaskp = VN_CAST(m_memberMap.findMember(ifacep, nodep->name()), NodeFTask)) { UINFO(5, __FUNCTION__ << "AstNodeFTask" << nodep); + // When a function/task is called through a virtual interface, its body may + // write to interface member variables. Mark all members as sensIfacep so + // optimization passes do not constant-fold them across instances. + if (adtypep->isVirtual()) { + for (AstNode* itemp = ifacep->stmtsp(); itemp; itemp = itemp->nextp()) { + if (AstVar* const mvarp = VN_CAST(itemp, Var)) { + mvarp->sensIfacep(ifacep); + } + } + } userIterate(ftaskp, nullptr); if (ftaskp->isStatic()) { AstArg* const argsp = nodep->argsp(); diff --git a/test_regress/t/t_wait_iface_vif.py b/test_regress/t/t_wait_iface_vif.py new file mode 100755 index 000000000..557b1b992 --- /dev/null +++ b/test_regress/t/t_wait_iface_vif.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: 2025 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', '--timescale 1ns/1ps']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_wait_iface_vif.v b/test_regress/t/t_wait_iface_vif.v new file mode 100755 index 000000000..efdc2a65c --- /dev/null +++ b/test_regress/t/t_wait_iface_vif.v @@ -0,0 +1,71 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2025 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 +/* verilator lint_off ZERODLY */ + +interface my_if(); + logic clk; + realtime clk_period; + bit clk_active = 0; + + initial begin + wait (clk_active); + forever begin + #(clk_period); + if (clk_active) begin + case (clk) + 1'b0: clk = 1'b1; + default: clk = 1'b0; + endcase + end + end + end + + // always @* with process::self() must not become a spinning coroutine + always @* begin + if (clk_active && clk_period == 0.0) begin + automatic process p = process::self(); + $display("%m: active with 0 period (proc=%p)", p); + $stop; + end + end + + function void set_period(realtime p); + clk_period = p; + endfunction + + function void start_clk(); + if (clk_period) clk_active = 1; + endfunction +endinterface + +class Driver; + virtual my_if vif; + + task run(); + vif.set_period(5ns); + #10; + vif.start_clk(); + endtask +endclass + +module t; + my_if intf(); + + initial begin + automatic Driver d = new; + d.vif = intf; + d.run(); + repeat (4) @(posedge intf.clk); + $write("*-* All Finished *-*\n"); + $finish; + end + + initial begin + #1000; + $display("TIMEOUT"); + $stop; + end +endmodule