diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp index 58883a08b..55ebda85e 100644 --- a/include/verilated_timing.cpp +++ b/include/verilated_timing.cpp @@ -284,13 +284,13 @@ void VlDynamicTriggerScheduler::dump() const { //====================================================================== // VlForkSync:: Methods -void VlProcess::forkSyncOnKill(VlForkSyncState* forkSyncp) { +void VlProcess::forkSyncOnKill(std::shared_ptr forkSyncp) { m_forkSyncOnKillp = forkSyncp; m_forkSyncOnKillDone = false; } void VlProcess::forkSyncOnKillClear(VlForkSyncState* forkSyncp) { - if (m_forkSyncOnKillp != forkSyncp) return; + if (m_forkSyncOnKillp.get() != forkSyncp) return; m_forkSyncOnKillp = nullptr; m_forkSyncOnKillDone = false; } @@ -301,19 +301,15 @@ void VlProcess::state(int s) { m_forkSyncOnKillDone = true; m_state = s; m_forkSyncOnKillp->done(); + m_forkSyncOnKillp = nullptr; return; } m_state = s; } -VlForkSyncState::~VlForkSyncState() { - for (const VlProcessRef& processp : m_onKillProcessps) processp->forkSyncOnKillClear(this); -} - void VlForkSync::onKill(VlProcessRef process) { if (!process) return; - m_state->m_onKillProcessps.emplace_back(process); - process->forkSyncOnKill(m_state.get()); + process->forkSyncOnKill(m_state); } void VlForkSyncState::done(const char* filename, int lineno) { @@ -337,16 +333,18 @@ void VlForkSyncState::done(const char* filename, int lineno) { } //====================================================================== -// VlCoroutine:: Methods +// VlPromise:: Methods -VlCoroutine::VlPromise::~VlPromise() { +VlCoroutine VlPromise::get_return_object() { return {this}; } + +VlPromise::~VlPromise() { // Indicate to the return object that the coroutine has finished or been destroyed if (m_corop) m_corop->m_promisep = nullptr; // If there is a continuation, destroy it if (m_continuation) m_continuation.destroy(); } -std::suspend_never VlCoroutine::VlPromise::final_suspend() noexcept { +std::suspend_never VlPromise::final_suspend() noexcept { // Indicate to the return object that the coroutine has finished if (m_corop) { m_corop->m_promisep = nullptr; diff --git a/include/verilated_timing.h b/include/verilated_timing.h index 4f4d6abac..84802c319 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -99,11 +99,34 @@ public: // cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got // suspended, another VlCoroutineHandle was created to manage it. +class VlCoroutine; + +struct VlPromise final { + std::coroutine_handle + m_continuation; // Coroutine to resume after this one finishes + VlCoroutine* m_corop = nullptr; // Pointer to the coroutine return object + bool m_forever = false; // Coroutine suspended forever + + ~VlPromise(); + + VlCoroutine get_return_object(); + + // Never suspend at the start of the coroutine + std::suspend_never initial_suspend() const { return {}; } + + // Never suspend at the end of the coroutine (thanks to this, the coroutine will clean up + // after itself) + std::suspend_never final_suspend() noexcept; + + void unhandled_exception() const { std::abort(); } // LCOV_EXCL_LINE + void return_void() const {} +}; + class VlCoroutineHandle final { VL_UNCOPYABLE(VlCoroutineHandle); // MEMBERS - std::coroutine_handle<> m_coro; // The wrapped coroutine handle + std::coroutine_handle m_coro; // The wrapped coroutine handle VlProcessRef m_process; // Data of the suspended process, null if not needed VlFileLineDebug m_fileline; @@ -117,7 +140,8 @@ public: , m_process{process} { if (m_process) m_process->state(VlProcess::WAITING); } - VlCoroutineHandle(std::coroutine_handle<> coro, VlProcessRef process, VlFileLineDebug fileline) + VlCoroutineHandle(std::coroutine_handle coro, VlProcessRef process, + VlFileLineDebug fileline) : m_coro{coro} , m_process{process} , m_fileline{fileline} { @@ -135,10 +159,11 @@ public: // Usually these coroutines should get resumed; we only need to clean up if we destroy a // model with some coroutines suspended if (VL_UNLIKELY(m_coro)) { - m_coro.destroy(); - if (m_process && m_process->state() != VlProcess::KILLED) { + if (m_process && m_process->state() != VlProcess::KILLED + && !m_coro.promise().m_forever) { m_process->state(VlProcess::FINISHED); } + m_coro.destroy(); } } // METHODS @@ -173,7 +198,13 @@ class VlDelayScheduler final { std::vector m_zeroDelayed; // Coroutines waiting for #0 // Coroutines that waited for #0 and are being resumed now. As member to avoid reallocations std::vector m_zeroDelayesSwap; - + std::vector + m_forevered; // Coroutines, that called wait(0), and are to be freed. + // Their callers are freed recursively through m_continuation. + // + // NOTE: it would be possible to clean up forevered coroutines immedietely without storing + // them, but it involves a lot of nasty edge cases. Defering cleanup to end of eval step + // simplifies logic a lot by ensuring that whole "coroutine stack" was already suspended. public: // CONSTRUCTORS explicit VlDelayScheduler(VerilatedContext& context) @@ -195,6 +226,7 @@ public: } // Are there coroutines to resume in the inactive region after a #0 delay? bool awaitingZeroDelay() const { return !m_context.gotFinish() && !m_zeroDelayed.empty(); } + void cleanupForevered() { m_forevered.clear(); }; #ifdef VL_DEBUG void dump() const; #endif @@ -210,7 +242,7 @@ public: const VlFileLineDebug fileline; bool await_ready() const { return false; } // Always suspend - void await_suspend(std::coroutine_handle<> coro) { + void await_suspend(std::coroutine_handle coro) { // Both active delays and fork..join_none #0 are resumed out of the time queue. if (phase != VlDelayPhase::INACTIVE) { queue.emplace(delay, VlCoroutineHandle{coro, process, fileline}); @@ -233,6 +265,26 @@ public: m_zeroDelayed, m_context.time() + delay, phase, VlFileLineDebug{filename, lineno}}; } + + // Helper awaitable func for suspending coroutines forever. + // Used for constant wait statements. + auto waitForever(VlProcessRef process, const char* filename = VL_UNKNOWN, int lineno = 0) { + VL_DEBUG_IF( + VL_DBG_MSGF(" Awaiting join of fork at: %s:%d\n", filename, lineno);); + struct Awaitable final { + VlProcessRef process; // Data of the suspended process, null if not needed + std::vector& forevered; + VlFileLineDebug fileline; + + bool await_ready() { return false; } + void await_suspend(std::coroutine_handle coro) { + coro.promise().m_forever = true; + forevered.emplace_back(VlCoroutineHandle{coro, process, fileline}); + } + void await_resume() const {} // LCOV_EXCL_LINE + }; + return Awaitable{process, m_forevered, VlFileLineDebug{filename, lineno}}; + } }; //============================================================================= @@ -280,7 +332,7 @@ public: VlFileLineDebug fileline; bool await_ready() const { return false; } // Always suspend - void await_suspend(std::coroutine_handle<> coro) { + void await_suspend(std::coroutine_handle coro) { suspended.emplace_back(coro, process, fileline); } void await_resume() const {} @@ -327,7 +379,7 @@ class VlDynamicTriggerScheduler final { VlFileLineDebug fileline; bool await_ready() const { return false; } // Always suspend - void await_suspend(std::coroutine_handle<> coro) { + void await_suspend(std::coroutine_handle coro) { suspended.emplace_back(coro, process, fileline); } void await_resume() const {} @@ -371,16 +423,6 @@ public: } }; -//============================================================================= -// VlForever is a helper awaitable type for suspending coroutines forever. Used for constant -// wait statements. - -struct VlForever final { - bool await_ready() const { return false; } // Always suspend - void await_suspend(std::coroutine_handle<> coro) const { coro.destroy(); } - void await_resume() const {} -}; - //============================================================================= // VlForkSync is used to manage fork..join and fork..join_any constructs. @@ -393,11 +435,9 @@ public: size_t m_pendingDones = 0; // done() calls seen before init() (e.g. early killed branch) bool m_inDone = false; // Guard against re-entrant resume recursion from nested kills bool m_resumePending = false; // Join reached zero again while inside done() - std::vector m_onKillProcessps; // Branches registered for kill hooks VlForkSyncState() // Construct with a null coroutine handle : m_susp{VlProcessRef{}} {} - ~VlForkSyncState(); void done(const char* filename = VL_UNKNOWN, int lineno = 0); }; @@ -431,7 +471,7 @@ public: VlFileLineDebug fileline; bool await_ready() { return state->m_counter == 0; } // Suspend if join still exists - void await_suspend(std::coroutine_handle<> coro) { + void await_suspend(std::coroutine_handle coro) { state->m_susp = {coro, process, fileline}; } void await_resume() const {} @@ -445,31 +485,10 @@ public: // Return value of a coroutine. Used for chaining coroutine suspension/resumption. class VlCoroutine final { -private: - // TYPES - struct VlPromise final { - std::coroutine_handle<> m_continuation; // Coroutine to resume after this one finishes - VlCoroutine* m_corop = nullptr; // Pointer to the coroutine return object - - ~VlPromise(); - - VlCoroutine get_return_object() { return {this}; } - - // Never suspend at the start of the coroutine - std::suspend_never initial_suspend() const { return {}; } - - // Never suspend at the end of the coroutine (thanks to this, the coroutine will clean up - // after itself) - std::suspend_never final_suspend() noexcept; - - void unhandled_exception() const { std::abort(); } - void return_void() const {} - }; - +public: // MEMBERS VlPromise* m_promisep; // The promise created for this coroutine -public: // TYPES using promise_type = VlPromise; // promise_type has to be public @@ -495,7 +514,13 @@ public: // Suspend the awaiter if the coroutine is suspended (the promise exists) bool await_ready() const noexcept { return !m_promisep; } // Set the awaiting coroutine as the continuation of the current coroutine - void await_suspend(std::coroutine_handle<> coro) { m_promisep->m_continuation = coro; } + // If the current coroutine is suspended forever, propagate that to the awaiting coroutine + // In that case, m_continuation will be used for cleaning up the whole suspended coroutine + // stack + void await_suspend(std::coroutine_handle awaiting_coro) { + if (m_promisep->m_forever) awaiting_coro.promise().m_forever = true; + m_promisep->m_continuation = awaiting_coro; + } void await_resume() const noexcept {} }; diff --git a/include/verilated_types.h b/include/verilated_types.h index 67bc5e88e..76a24d01b 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -322,7 +322,7 @@ class VlProcess final { int m_state; // Current state of the process VlProcessRef m_parentp = nullptr; // Parent process, if exists std::set m_children; // Active child processes - VlForkSyncState* m_forkSyncOnKillp + std::shared_ptr m_forkSyncOnKillp = nullptr; // Optional fork..join counter to decrement on kill bool m_forkSyncOnKillDone = false; // Ensure on-kill callback fires only once VlRNG m_rng; // Per-process RNG (IEEE 1800-2023 18.14) @@ -364,13 +364,14 @@ public: void disable() { state(KILLED); disableFork(); + m_forkSyncOnKillp = nullptr; } void disableFork() { // childp->disable() may resume coroutines and mutate m_children const std::set children = m_children; for (VlProcess* childp : children) childp->disable(); } - void forkSyncOnKill(VlForkSyncState* forkSyncp); + void forkSyncOnKill(std::shared_ptr forkSyncp); void forkSyncOnKillClear(VlForkSyncState* forkSyncp); bool completed() const { return state() == FINISHED || state() == KILLED; } bool completedFork() const { diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 814c82a86..3855008d0 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -925,6 +925,7 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) { macro(SCHED_RESUME_ZERO_DELAY, "resumeZeroDelay", false) \ macro(SCHED_RESUMPTION, "resumption", false) \ macro(SCHED_TRIGGER, "trigger", false) \ + macro(SCHED_WAIT_FOREVER, "waitForever", false) \ macro(UNPACKED_ASSIGN, "assign", false) \ macro(UNPACKED_FILL, "fill", false) \ macro(UNPACKED_NEQ, "neq", true) diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 270ada718..aab6dcef3 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -461,6 +461,12 @@ class EmitCModel final : public EmitCFunc { puts("\nvoid " + EmitCUtil::topClassName() + "::evalEnd() {\n"); putsDecoration(nullptr, "// Evaluate cleanup\n"); puts("Verilated::endOfEval(vlSymsp->__Vm_evalMsgQp);\n"); + if (AstVar* const delaySchedp = v3Global.rootp()->delaySchedulerp()) { + puts("vlSymsp->TOP."); + puts(delaySchedp->nameProtect()); + puts(".cleanupForevered();\n"); + } + puts("}\n"); // Evaluation entry points diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 1d8c844e5..2b6546126 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -1393,8 +1393,13 @@ class TimingControlVisitor final : public VNVisitor { if (constp->isZero()) { // We have to await forever instead of simply returning in case we're deep in a // callstack - AstCExpr* const foreverp = new AstCExpr{flp, "VlForever{}"}; - AstCAwait* const awaitp = new AstCAwait{flp, foreverp}; + AstCMethodHard* const foreverMethodp = new AstCMethodHard{ + flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::WRITE}, + VCMethod::SCHED_WAIT_FOREVER}; + foreverMethodp->dtypeSetVoid(); + addProcessInfo(foreverMethodp); + addDebugInfo(foreverMethodp); + AstCAwait* const awaitp = new AstCAwait{flp, foreverMethodp}; nodep->replaceWith(awaitp); if (stmtsp) VL_DO_DANGLING(stmtsp->deleteTree(), stmtsp); VL_DO_DANGLING(condp->deleteTree(), condp); diff --git a/test_regress/t/t_disable_fork_noop.py b/test_regress/t/t_disable_fork_noop.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_disable_fork_noop.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(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_disable_fork_noop.v b/test_regress/t/t_disable_fork_noop.v new file mode 100644 index 000000000..186bf7187 --- /dev/null +++ b/test_regress/t/t_disable_fork_noop.v @@ -0,0 +1,46 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); $stop; end while(0); +// verilog_format: on + +module t; + bit disabled_fork = 0; + bit disabled_victim = 0; + bit joined = 0; + + initial begin + fork + begin : victim + disable fork; // should be no-op + disabled_fork=1; + forever begin + #1; + end + end + begin + #2; + disable victim; + disabled_victim=1; + end + join + joined=1; + end + + initial begin + #1; #0; + `checkd(disabled_fork, 1); + `checkd(disabled_victim, 0); + `checkd(joined, 0); + #2; #0; + `checkd(disabled_fork, 1); + `checkd(disabled_victim, 1); + `checkd(joined, 1); + $info("*-* All Finished *-*"); + $finish; + end +endmodule diff --git a/test_regress/t/t_process_self_memusage.py b/test_regress/t/t_process_self_memusage.py new file mode 100755 index 000000000..ba996a944 --- /dev/null +++ b/test_regress/t/t_process_self_memusage.py @@ -0,0 +1,22 @@ +#!/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 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--binary']) +test.execute() + +mem_usage_mb = int(test.file_grep(test.run_log_filename, r'allocated +(\d+) MB')[0]) + +if mem_usage_mb > 128 and not test.have_dev_asan: # ASAN inflates memory usage by retaining freed stuff + test.error('Consumed over 128MB memory') + +test.passes() diff --git a/test_regress/t/t_process_self_memusage.v b/test_regress/t/t_process_self_memusage.v new file mode 100644 index 000000000..77f4054e2 --- /dev/null +++ b/test_regress/t/t_process_self_memusage.v @@ -0,0 +1,19 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial begin + repeat (1000000) begin + fork + begin + automatic process p = process::self(); + #1; + end + join + end + $finish; + end +endmodule diff --git a/test_regress/t/t_wait_0.py b/test_regress/t/t_wait_0.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_wait_0.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(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_wait_0.v b/test_regress/t/t_wait_0.v new file mode 100644 index 000000000..31b87bcfb --- /dev/null +++ b/test_regress/t/t_wait_0.v @@ -0,0 +1,55 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); $stop; end while(0); + +// Fork branch that waits forever at wait(0). +`define FORK_BRANCH_WAIT_FOREVER do begin; ++hanged_branches; wait(0); $fatal(2, "should not get here"); end while(0); +// Fork branch that finishes without waiting. +`define FORK_BRANCH_FINISH do begin; ++finished_branches; end while(0); +// verilog_format: on + +module t; + int hanged_branches = 0; + int finished_branches = 0; + int resumed_joins = 0; + + initial begin : join_any2 + fork + #10 `FORK_BRANCH_WAIT_FOREVER; + #10 `FORK_BRANCH_WAIT_FOREVER; + `FORK_BRANCH_FINISH; + join_any + resumed_joins++; + end + + initial begin : join_any3 + fork + `FORK_BRANCH_WAIT_FOREVER; + `FORK_BRANCH_WAIT_FOREVER; + #10 `FORK_BRANCH_FINISH; + join_any + resumed_joins++; + end + + initial begin : join_none1 + fork + `FORK_BRANCH_WAIT_FOREVER + `FORK_BRANCH_WAIT_FOREVER + join_none + resumed_joins++; + end + + initial begin + #15; + `checkd(hanged_branches, 6); + `checkd(finished_branches, 2); + `checkd(resumed_joins, 3); + $info("*-* All Finished *-*"); + $finish; + end +endmodule diff --git a/test_regress/t/t_wait_0_kill.py b/test_regress/t/t_wait_0_kill.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_wait_0_kill.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(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_wait_0_kill.v b/test_regress/t/t_wait_0_kill.v new file mode 100644 index 000000000..87c725c28 --- /dev/null +++ b/test_regress/t/t_wait_0_kill.v @@ -0,0 +1,165 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); $stop; end while(0); +// verilog_format: on + +module kill_sibling; + bit proc_a_hanged = 0; + bit proc_a_killed = 0; + bit proc_b_finished = 0; + bit joined = 0; + + initial begin : kill_hanged_sibling + fork + process p; + begin : process_a + p = process::self(); + proc_a_hanged = 1; + wait(0); + $fatal(2, "shouldn't get here"); + end + begin : process_b + #2; + p.kill(); + proc_a_killed = 1; + #5; + proc_b_finished = 1; + end + join_any + joined = 1; + end + + initial begin + #0; + `checkd(proc_a_hanged, 1); + `checkd(proc_a_killed, 0); + `checkd(proc_b_finished, 0); + `checkd(joined, 0); + #2; #0; + `checkd(proc_a_hanged, 1); + `checkd(proc_a_killed, 1); + `checkd(proc_b_finished, 0); + `checkd(joined, 1); + #5; #0; + `checkd(proc_a_hanged, 1); + `checkd(proc_a_killed, 1); + `checkd(proc_b_finished, 1); + `checkd(joined, 1); + end +endmodule + +module kill_proc_next_cyc; + process p; + bit proc_a_hanged = 0; + bit proc_a_killed = 0; + bit joined = 0; + + initial begin + #1; + p.kill(); + proc_a_killed = 1; + end + + initial begin + fork + begin : process_a + p = process::self(); + proc_a_hanged = 1; + wait(0); + $fatal(2, "shouldn't get here"); + end + join_any + joined = 1; + end + + initial begin + #0; + `checkd(proc_a_hanged, 1); + `checkd(proc_a_killed, 0); + `checkd(joined, 0); + #1; #0; + `checkd(proc_a_hanged, 1); + `checkd(proc_a_killed, 1); + `checkd(joined, 1); + end +endmodule + +module kill_proc_same_cyc; + process p; + bit proc_a_hanged = 0; + bit proc_a_killed = 0; + bit joined = 0; + + initial begin + #0; // Do it same cycle, just after process_a hanged + p.kill(); + proc_a_killed = 1; + end + + initial begin + fork + begin : process_a + p = process::self(); + proc_a_hanged = 1; + wait(0); + $fatal(2, "shouldn't get here"); + end + join_any + joined = 1; + end + + initial begin + #0; #0; + `checkd(proc_a_hanged, 1); + `checkd(proc_a_killed, 1); + `checkd(joined, 1); + end +endmodule + +module disable_fork; + bit hanged=0; + bit finished=0; + bit joined=0; + bit disabled_fork=0; + initial begin + fork + begin hanged=1; wait(0); end + begin finished=1; end + join_any + joined=1; + #5; + disable fork; + disabled_fork=1; + end + + initial begin + #0; + `checkd(hanged, 1); + `checkd(finished, 1); + `checkd(joined, 1); + `checkd(disabled_fork, 0); + #5; #0; + `checkd(hanged, 1); + `checkd(finished, 1); + `checkd(joined, 1); + `checkd(disabled_fork, 1); + end +endmodule + +module t; + kill_sibling kill_sibling(); + kill_proc_next_cyc kill_proc_next_cyc(); + kill_proc_same_cyc kill_proc_same_cyc(); + disable_fork disable_fork(); + + initial begin + #15; + $info("*-* All Finished *-*"); + $finish; + end +endmodule diff --git a/test_regress/t/t_wait_0_leaky.py b/test_regress/t/t_wait_0_leaky.py new file mode 100755 index 000000000..74f3db2f1 --- /dev/null +++ b/test_regress/t/t_wait_0_leaky.py @@ -0,0 +1,19 @@ +#!/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.leak_check_disable() # unreachable join is expected to leak +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_wait_0_leaky.v b/test_regress/t/t_wait_0_leaky.v new file mode 100644 index 000000000..1e9da480f --- /dev/null +++ b/test_regress/t/t_wait_0_leaky.v @@ -0,0 +1,119 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); $stop; end while(0); + +// Fork branch that waits forever at wait(0). +`define FORK_BRANCH_WAIT_FOREVER do begin; ++hanged_branches; wait(0); $fatal(2, "should not get here"); end while(0); +// Fork branch that finishes without waiting. +`define FORK_BRANCH_FINISH do begin; ++finished_branches; end while(0); +// verilog_format: on + +module t; + int hanged_branches = 0; + int finished_branches = 0; + int resumed_joins = 0; + + initial begin : join1 + fork + `FORK_BRANCH_WAIT_FOREVER + `FORK_BRANCH_WAIT_FOREVER + join + ++resumed_joins; + $fatal(2, "should not get here"); + end + + initial begin : join2 + fork + #10 `FORK_BRANCH_WAIT_FOREVER; + #10 `FORK_BRANCH_WAIT_FOREVER; + `FORK_BRANCH_FINISH; + join + ++resumed_joins; + $fatal(2, "should not get here"); + end + + initial begin : join3 + fork + `FORK_BRANCH_WAIT_FOREVER; + `FORK_BRANCH_WAIT_FOREVER; + #10 `FORK_BRANCH_FINISH; + join + ++resumed_joins; + $fatal(2, "should not get here"); + end + + initial begin : join4 + fork + begin + fork + #10 `FORK_BRANCH_WAIT_FOREVER; + join + end + `FORK_BRANCH_FINISH; + join + ++resumed_joins; + $fatal(2, "should not get here"); + end + + initial begin : join_any1 + fork + `FORK_BRANCH_WAIT_FOREVER + `FORK_BRANCH_WAIT_FOREVER + join_any + ++resumed_joins; + $fatal(2, "should not get here"); + end + + initial begin : join_mixed1 + fork + fork + `FORK_BRANCH_WAIT_FOREVER + join + `FORK_BRANCH_WAIT_FOREVER + join_any + ++resumed_joins; + $fatal(2, "should not get here"); + end + + initial begin : join_mixed2 + fork + begin + fork + #10 `FORK_BRANCH_WAIT_FOREVER + join + ++resumed_joins; + $fatal(2, "should not get here"); + end + `FORK_BRANCH_FINISH + join_any + ++resumed_joins; + end + + initial begin : join_mixed3 + fork + begin + fork + `FORK_BRANCH_WAIT_FOREVER + join + ++resumed_joins; + $fatal(2, "should not get here"); + end + #10 `FORK_BRANCH_FINISH + join_any + ++resumed_joins; + end + + initial begin + #20; + `checkd(hanged_branches, 13); + `checkd(finished_branches, 5); + `checkd(resumed_joins, 2); + $info("*-* All Finished *-*"); + $finish; + end +endmodule diff --git a/test_regress/t/t_wait_0_nested_funcs.py b/test_regress/t/t_wait_0_nested_funcs.py new file mode 100755 index 000000000..74f3db2f1 --- /dev/null +++ b/test_regress/t/t_wait_0_nested_funcs.py @@ -0,0 +1,19 @@ +#!/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.leak_check_disable() # unreachable join is expected to leak +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_wait_0_nested_funcs.v b/test_regress/t/t_wait_0_nested_funcs.v new file mode 100644 index 000000000..c5992fd71 --- /dev/null +++ b/test_regress/t/t_wait_0_nested_funcs.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`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 + + +class blocking_sequence; + bit m_called; + bit m_called_body; + bit m_returned_body; + bit m_returned; + + task body(); + m_called_body = 1; + wait (0); + m_returned_body = 1; + $fatal(2, "did wait(0) - SHOULD NOT GET HERE"); + endtask + + task start(); + fork + begin + #0; + m_called = 1; + body(); + m_returned = 1; + $fatal(2, "called body() - SHOULD NOT GET HERE"); + #0; + end + join + endtask +endclass + +module t; + bit timeout; + initial begin + blocking_sequence b; + b = new; + fork + begin + b.start(); + end + begin + #10; + timeout = 1; + end + join_any + `checkh(b.m_called, 1); + `checkh(b.m_called_body, 1); + `checkh(b.m_returned, 0); + `checkh(b.m_returned_body, 0); + `checkh(timeout, 1); + $finish; + end + +endmodule