diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 687a978a0..b94513cba 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -201,6 +201,7 @@ Maarten De Braekeleer Maciej Sobkowski Marcel Chang Marco Bartoli +Marco Brambilla Marco Widmer Mariusz Glebocki Markus Krause diff --git a/src/V3SchedUtil.cpp b/src/V3SchedUtil.cpp index 0f3fa30c2..0d7ec5ec7 100644 --- a/src/V3SchedUtil.cpp +++ b/src/V3SchedUtil.cpp @@ -157,6 +157,40 @@ void splitCheckFinishSubFunc(AstCFunc* ofuncp, AstCFunc* subFuncp, } } +// Compute, for each top level statement of 'ofuncp', whether a new sub-function may begin there. +// Sub-functions are emitted as separate C++ functions, so they cannot see each other's automatic +// storage. A function-local AstVar declared among the top level statements must therefore end up +// in the same sub-function as every reference to it, otherwise the emitted C++ refers to an +// undeclared identifier (and the AstVar can be deleted from under the still-live references). +static std::vector splitCheckBreakable(const AstCFunc* ofuncp) { + // Gather the top level statements, and where each locally declared variable is declared + std::vector stmtps; + std::unordered_map declIdx; // Local var -> index it is declared at + for (const AstNode* nodep = ofuncp->stmtsp(); nodep; nodep = nodep->nextp()) { + if (const AstVar* const varp = VN_CAST(nodep, Var)) declIdx.emplace(varp, stmtps.size()); + stmtps.push_back(nodep); + } + + // Find the last statement referencing each locally declared variable + std::vector lastUse(stmtps.size()); + for (size_t i = 0; i < stmtps.size(); ++i) { + lastUse[i] = i; // A declaration is live at least where it is declared + stmtps[i]->foreach([&](const AstNodeVarRef* refp) { + const auto it = declIdx.find(refp->varp()); // 'end()' if not one of our locals + if (it != declIdx.end()) lastUse[it->second] = std::max(lastUse[it->second], i); + }); + } + + // A break before statement 'i' is allowed only if no local declared before 'i' is still live + std::vector breakable(stmtps.size(), true); + size_t liveEnd = 0; // Last index any so far declared local is referenced at + for (size_t i = 0; i < stmtps.size(); ++i) { + breakable[i] = liveEnd < i; + if (VN_IS(stmtps[i], Var)) liveEnd = std::max(liveEnd, lastUse[i]); + } + return breakable; +} + // Split large function according to --output-split-cfuncs void splitCheck(AstCFunc* const ofuncp) { if (!ofuncp) return; @@ -164,6 +198,9 @@ void splitCheck(AstCFunc* const ofuncp) { if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return; if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return; + // Statement boundaries that would separate a local declaration from a reference to it + const std::vector breakable = splitCheckBreakable(ofuncp); + // Need to find the AstVarScopes for the function arguments. They should be in the same Scope. std::unordered_map argVscps; for (AstVar* argp = ofuncp->argsp(); argp; argp = VN_AS(argp->nextp(), Var)) { @@ -187,13 +224,13 @@ void splitCheck(AstCFunc* const ofuncp) { // Move statements one by one to the new sub-functions AstNode* stmtsp = ofuncp->stmtsp()->unlinkFrBackWithNext(); - while (AstNode* const itemp = stmtsp) { + for (size_t index = 0; AstNode* const itemp = stmtsp; ++index) { stmtsp = stmtsp->nextp(); if (stmtsp) stmtsp->unlinkFrBackWithNext(); const size_t itemSize = static_cast(itemp->nodeCount()); size += itemSize; - if (size > static_cast(v3Global.opt.outputSplitCFuncs())) { + if (size > static_cast(v3Global.opt.outputSplitCFuncs()) && breakable[index]) { if (subFuncp) splitCheckFinishSubFunc(ofuncp, subFuncp, argVscps); subFuncp = nullptr; size = itemSize; diff --git a/test_regress/t/t_timing_wait_fork_split.py b/test_regress/t/t_timing_wait_fork_split.py new file mode 100755 index 000000000..67e888c17 --- /dev/null +++ b/test_regress/t/t_timing_wait_fork_split.py @@ -0,0 +1,26 @@ +#!/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') + +# --output-split-cfuncs forces the process to be split into sub-functions +test.compile(verilator_flags2=["--binary", "--output-split-cfuncs", "20"]) + +# Confirm the split actually happened: the 'initial' timing process is emitted +# as function sub-parts (__Vtiming__0__0 / __Vtiming__0__1). Without the split +# this test would not exercise the bug it guards against -- a dynamic 'wait +# fork' trigger temporary separated from its uses across a sub-function boundary. +if test.vlt_all: + test.file_grep(test.obj_dir + "/V" + test.name + "_t__0.cpp", r'__Vtiming__0__1\b') + +test.execute() + +test.passes() diff --git a/test_regress/t/t_timing_wait_fork_split.v b/test_regress/t/t_timing_wait_fork_split.v new file mode 100644 index 000000000..baf6824fe --- /dev/null +++ b/test_regress/t/t_timing_wait_fork_split.v @@ -0,0 +1,40 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// A 'wait fork' needs a dynamic trigger temporary, which is function-local to +// the process it appears in. Splitting the process into sub-functions must not +// separate that declaration from its uses, as sub-functions are emitted as +// separate C++ functions. +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk = 1'b0; + int cnt = 0; + + always #5 clk = ~clk; + + task automatic phase(int n); + fork begin @(posedge clk); cnt += n; end join_none + wait fork; + endtask + + initial begin + fork @(posedge clk); join_none + wait fork; + phase(1); + fork @(negedge clk); join_none + wait fork; + phase(2); + fork @(posedge clk); join_none + wait fork; + phase(4); + if (cnt != 7) begin + $write("%%Error: cnt=%0d exp=7\n", cnt); + $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule