From 87b4f6103967bf882ba96ae2771649c1aebbf239 Mon Sep 17 00:00:00 2001 From: wsxarcher Date: Tue, 30 Jun 2026 15:30:18 +0200 Subject: [PATCH] Fix disable of fork..join/join_any from a branch not killing delayed siblings (#7856) --- src/V3Fork.cpp | 28 ++++-- test_regress/t/t_disable_fork_join.py | 18 ++++ test_regress/t/t_disable_fork_join.v | 134 ++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 test_regress/t/t_disable_fork_join.py create mode 100644 test_regress/t/t_disable_fork_join.v diff --git a/src/V3Fork.cpp b/src/V3Fork.cpp index ee02c0781..806d42079 100644 --- a/src/V3Fork.cpp +++ b/src/V3Fork.cpp @@ -620,6 +620,13 @@ class ForkVisitor final : public VNVisitor { beginp->addStmtsp(delayp); } } + static bool forkIsDisableable(const AstFork* const nodep) { + for (const AstBegin* itemp = nodep->forksp(); itemp; + itemp = VN_AS(itemp->nextp(), Begin)) { + if (isDisableQueuePushSelfStmt(itemp->stmtsp())) return true; + } + return false; + } // VISITORS void visit(AstNodeModule* nodep) override { @@ -635,17 +642,13 @@ class ForkVisitor final : public VNVisitor { } void visit(AstFork* nodep) override { - if (nodep->joinType().join()) { - iterateChildren(nodep); - return; - } - // IEEE 1800-2023 9.3.2: In all cases, processes spawned by a fork-join block shall not - // start executing until the parent process is blocked or terminates. - // Because join and join_any block the parent process, it is only needed when join_none - // is used. - if (nodep->joinType().joinNone()) { - UINFO(9, "Visiting fork..join_none " << nodep); + // start executing until the parent process is blocked or terminates. Because join and + // join_any block the parent process, deferring branch start with a synthetic #0 delay is + // normally only needed for join_none. A fork that can be disabled by name needs the same + // deferral for every join type. + if (nodep->joinType().joinNone() || forkIsDisableable(nodep)) { + UINFO(9, "Adding fork branch start sentinels " << nodep); FileLine* fl = nodep->fileline(); // We use a sentinel value of UINT64_MAX to mark this delay so that it goes to the // ACTIVE region with a delay value of 0. @@ -661,6 +664,11 @@ class ForkVisitor final : public VNVisitor { } } + if (nodep->joinType().join()) { + iterateChildren(nodep); + return; + } + iterateAndNextNull(nodep->declsp()); iterateAndNextNull(nodep->stmtsp()); std::vector wrappedp; diff --git a/test_regress/t/t_disable_fork_join.py b/test_regress/t/t_disable_fork_join.py new file mode 100644 index 000000000..fdbd25744 --- /dev/null +++ b/test_regress/t/t_disable_fork_join.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", "-Wno-ZERODLY"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_disable_fork_join.v b/test_regress/t/t_disable_fork_join.v new file mode 100644 index 000000000..1db7277a4 --- /dev/null +++ b/test_regress/t/t_disable_fork_join.v @@ -0,0 +1,134 @@ +// 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 + +// Disabling a named fork..join / fork..join_any block from one of its own +// branches must terminate the sibling branches, including any that are +// suspended on a delay. This is the join/join_any analogue of the +// fork..join_none cases in t_disable_inside (#6591): the bug was that a sibling +// spawned later in source order registered its process for kill only after the +// disabling branch had already issued the kill, so the sibling survived and its +// $stop fired one time unit later. + +// verilog_format: off +`define stop $stop +`define checkt(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0t exp=%0t (%t !== %t)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + + // fork..join: the disabling branch comes first, the delayed sibling second. + initial begin + fork : fork_blk2 + begin + `checkt($time, 0); + disable fork_blk2; + $stop; + end + begin + `checkt($time, 0); + #1 $stop; + end + join + end + + // fork..join: the delayed sibling comes first, the disabling branch second. + initial begin + fork : fork_blk3 + begin + `checkt($time, 0); + #1 $stop; + end + begin + `checkt($time, 0); + disable fork_blk3; + $stop; + end + join + end + + // fork..join_any: the disabling branch comes first, the delayed sibling second. + initial begin + fork : fork_blk2a + begin + `checkt($time, 0); + disable fork_blk2a; + $stop; + end + begin + `checkt($time, 0); + #1 $stop; + end + join_any + end + + // fork..join_any: the delayed sibling comes first, the disabling branch second. + initial begin + fork : fork_blk3a + begin + `checkt($time, 0); + #1 $stop; + end + begin + `checkt($time, 0); + disable fork_blk3a; + $stop; + end + join_any + end + + // fork..join that can be disabled, but takes the path where disable is not + // reached: both branches must run to completion with their timing intact. + initial begin + fork : fork_blk4 + begin + `checkt($time, 0); + if ($c("false")) begin + disable fork_blk4; + $stop; + end + #1; + `checkt($time, 1); + end + begin + `checkt($time, 0); + #1; + `checkt($time, 1); + end + join + end + + // Deeply nested fork..join: the named fork is several levels deep inside other + // forks and one of its own branches disables it. The delayed sibling at that + // deep level must still be killed, so the fix has to work when the disable-able + // fork is itself nested inside other (suspending) forks. + initial begin + fork + begin + fork + begin + fork : fork_deep + begin + `checkt($time, 0); + disable fork_deep; + $stop; + end + begin + `checkt($time, 0); + #1 $stop; + end + join + end + join + end + join + end + + initial begin + #10; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule