diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index b5c58af77..060ac2348 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -892,7 +892,11 @@ 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); + // Skip for combinational blocks; if the body has timing controls + // iterateChildren will add T_SUSPENDEE, otherwise it would spin. + 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/test_regress/t/t_wait_iface_vif.py b/test_regress/t/t_wait_iface_vif.py new file mode 100755 index 000000000..46d1fe4c0 --- /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: 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_wait_iface_vif.v b/test_regress/t/t_wait_iface_vif.v new file mode 100644 index 000000000..7d697e8fe --- /dev/null +++ b/test_regress/t/t_wait_iface_vif.v @@ -0,0 +1,79 @@ +// 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 +/* 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(5); + #10; + vif.start_clk(); + endtask +endclass + +module t; + my_if intf(); + + // Verify combinational always with timing controls still works as coroutine + int combo_timing_count = 0; + always @* begin + combo_timing_count = combo_timing_count + 1; + #1; + end + + initial begin + automatic Driver d = new; + d.vif = intf; + d.run(); + repeat (4) @(posedge intf.clk); + if (combo_timing_count == 0) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + + initial begin + #1000; + $display("TIMEOUT"); + $stop; + end +endmodule