diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index a346dff7e..cfc015dd4 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -88,6 +88,7 @@ enum NodeFlag : uint8_t { T_FORCES_PROC = 1 << 3, // Forces VlProcess allocation T_NEEDS_PROC = 1 << 4, // Needs access to VlProcess if it's allocated T_HAS_PROC = 1 << 5, // Has VlProcess argument in the signature + T_NBA_UPDATE = 1 << 6, // Fork branch of a pending NBA update, which is not a subprocess }; enum ForkType : uint8_t { @@ -1222,6 +1223,7 @@ class TimingControlVisitor final : public VNVisitor { AstBegin* beginp = VN_CAST(controlp, Begin); if (!beginp) beginp = new AstBegin{nodep->fileline(), "", controlp, false}; forkp->addForksp(beginp); + addFlags(beginp, T_NBA_UPDATE); controlp = forkp; } UASSERT_OBJ(nodep, controlp, "Assignment should have timing control"); @@ -1438,6 +1440,9 @@ class TimingControlVisitor final : public VNVisitor { VL_RESTORER(m_procp); VL_RESTORER(m_hasProcess); m_hasProcess |= hasFlags(nodep, T_HAS_PROC); + // A pending NBA update is not a subprocess of the process that scheduled it, so it is + // unaffected by 'disable fork' and 'wait fork' (IEEE 1800-2023 9.6.1, 9.6.3) + if (hasFlags(nodep, T_NBA_UPDATE)) m_hasProcess = false; m_procp = nodep; if (m_hasProcess) nodep->setNeedProcess(); iterateChildren(nodep); diff --git a/test_regress/t/t_disable_fork_nba.py b/test_regress/t/t_disable_fork_nba.py new file mode 100755 index 000000000..bd4fe2b4e --- /dev/null +++ b/test_regress/t/t_disable_fork_nba.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Pending NBA updates are not subprocesses for disable/wait fork +# +# 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_disable_fork_nba.v b/test_regress/t/t_disable_fork_nba.v new file mode 100644 index 000000000..1e694e0e8 --- /dev/null +++ b/test_regress/t/t_disable_fork_nba.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Pending NBA updates are not subprocesses for disable/wait fork +// +// 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 stop $stop +`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 + +interface bus_if; + int data; +endinterface + +class Cls; + virtual bus_if vif; + task set(int v); + vif.data <= v; + endtask +endclass + +module t; + bit clk; + int dly_value; + int wait_value; + bit [1:0] drive_value; + bus_if bus (); + Cls obj = new; + + always #5 clk = ~clk; + + default clocking cb @(posedge clk); + output drive_value; + endclocking + + // 'disable fork' does not cancel pending updates (IEEE 1800-2023 9.6.3) + initial begin + obj.vif = bus; + dly_value <= #2 1; + obj.set(2); + cb.drive_value <= ##1 3; + disable fork; + #20; + `checkd(dly_value, 1) + `checkd(bus.data, 2) + `checkd(drive_value, 3) + end + + // 'wait fork' does not wait for pending updates (IEEE 1800-2023 9.6.1) + initial begin + #30; + wait_value <= #5 4; + wait fork; + `checkd($time, 30) + #10; + `checkd(wait_value, 4) + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule