Release fork join/join_any on killed task processes.

This commit is contained in:
Nick Brereton 2026-02-23 10:11:52 -05:00
parent 4756d21cc0
commit c96f1a2a06
7 changed files with 146 additions and 1 deletions

View File

@ -238,6 +238,26 @@ void VlDynamicTriggerScheduler::dump() const {
//======================================================================
// VlForkSync:: Methods
void VlProcess::forkSyncOnKill(VlForkSync* forkSyncp) {
m_forkSyncOnKillp = forkSyncp;
m_forkSyncOnKillDone = false;
}
void VlProcess::state(int s) {
if (s == KILLED && m_state != KILLED && m_state != FINISHED && m_forkSyncOnKillp
&& !m_forkSyncOnKillDone) {
m_forkSyncOnKillDone = true;
m_state = s;
m_forkSyncOnKillp->done();
return;
}
m_state = s;
}
void VlForkSync::onKill(VlProcessRef process) {
if (process) process->forkSyncOnKill(this);
}
void VlForkSync::done(const char* filename, int lineno) {
VL_DEBUG_IF(VL_DBG_MSGF(" Process forked at %s:%d finished\n", filename, lineno););
if (m_join->m_counter > 0) m_join->m_counter--;

View File

@ -397,6 +397,8 @@ class VlForkSync final {
public:
// Create the join object and set the counter to the specified number
void init(size_t count, VlProcessRef process) { m_join.reset(new VlJoin{count, {process}}); }
// Register process kill callback so killed fork branches still decrement join counter
void onKill(VlProcessRef process);
// Called whenever any of the forked processes finishes. If the join counter reaches 0, the
// main process gets resumed
void done(const char* filename = VL_UNKNOWN, int lineno = 0);

View File

@ -109,12 +109,15 @@ constexpr IData VL_CLOG2_CE_Q(QData lhs) VL_PURE {
// Metadata of processes
using VlProcessRef = std::shared_ptr<VlProcess>;
class VlForkSync;
class VlProcess final {
// MEMBERS
int m_state; // Current state of the process
VlProcessRef m_parentp = nullptr; // Parent process, if exists
std::set<VlProcess*> m_children; // Active child processes
VlForkSync* m_forkSyncOnKillp = nullptr; // Optional fork..join counter to decrement on kill
bool m_forkSyncOnKillDone = false; // Ensure on-kill callback fires only once
public:
// TYPES
@ -145,7 +148,7 @@ public:
void detach(VlProcess* childp) { m_children.erase(childp); }
int state() const { return m_state; }
void state(int s) { m_state = s; }
void state(int s);
void disable() {
state(KILLED);
disableFork();
@ -153,6 +156,7 @@ public:
void disableFork() {
for (VlProcess* childp : m_children) childp->disable();
}
void forkSyncOnKill(VlForkSync* forkSyncp);
bool completed() const { return state() == FINISHED || state() == KILLED; }
bool completedFork() const {
for (const VlProcess* const childp : m_children)

View File

@ -811,6 +811,7 @@ public:
FORK_DONE,
FORK_INIT,
FORK_JOIN,
FORK_ON_KILL,
RANDOMIZER_BASIC_STD_RANDOMIZATION,
RANDOMIZER_CLEARCONSTRAINTS,
RANDOMIZER_CLEARALL,
@ -947,6 +948,7 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
{FORK_DONE, "done", false}, \
{FORK_INIT, "init", false}, \
{FORK_JOIN, "join", false}, \
{FORK_ON_KILL, "onKill", false}, \
{RANDOMIZER_BASIC_STD_RANDOMIZATION, "basicStdRandomization", false}, \
{RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false}, \
{RANDOMIZER_CLEARALL, "clearAll", false}, \

View File

@ -742,6 +742,23 @@ class TimingControlVisitor final : public VNVisitor {
addDebugInfo(donep);
beginp->addStmtsp(donep->makeStmt());
}
// Register a callback so killing a process-backed fork branch decrements the join counter
void addForkOnKill(AstBegin* const beginp, AstVarScope* const forkVscp) const {
if (!beginp->needProcess()) return;
FileLine* const flp = beginp->fileline();
auto* const onKillp = new AstCMethodHard{
flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, VCMethod::FORK_ON_KILL};
onKillp->dtypeSetVoid();
AstCExpr* const processp = new AstCExpr{flp, "vlProcess"};
processp->dtypeSetVoid(); // Opaque process reference; type is irrelevant for hardcoded emit
onKillp->addPinsp(processp);
AstNodeStmt* const stmtp = onKillp->makeStmt();
if (beginp->stmtsp()) {
beginp->stmtsp()->addHereThisAsNext(stmtp);
} else {
beginp->addStmtsp(stmtp);
}
}
// Handle the 'join' part of a fork..join
void makeForkJoin(AstFork* const forkp) {
// Create a fork sync var
@ -754,6 +771,7 @@ class TimingControlVisitor final : public VNVisitor {
unsigned joinCount = 0; // Needed for join counter
// Add a <fork sync>.done() to each begin
for (AstNode* beginp = forkp->forksp(); beginp; beginp = beginp->nextp()) {
addForkOnKill(VN_AS(beginp, Begin), forkVscp);
addForkDone(VN_AS(beginp, Begin), forkVscp);
joinCount++;
}

View File

@ -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: 2024-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()

View File

@ -0,0 +1,80 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2025 Antmicro
// SPDX-License-Identifier: CC0-1.0
int x = 0;
int y = 0;
task increment_x;
x++;
#2;
x++;
endtask
task increment_y;
y++;
#2;
y++;
endtask
class driver;
int m_time = 0;
task get_and_send();
forever begin
#10;
m_time += 10;
end
endtask
task post_shutdown_phase();
disable get_and_send;
endtask
endclass
module t;
driver c;
initial begin
fork
increment_x();
#1 disable increment_x;
join
if (x != 1) $stop;
c = new;
fork
c.get_and_send;
join_none
if (c.m_time != 0) $stop;
#11;
if ($time != 12) $stop;
if (c.m_time != 10) $stop;
#20;
if ($time != 32) $stop;
if (c.m_time != 30) $stop;
c.post_shutdown_phase;
#20;
if ($time != 52) $stop;
if (c.m_time != 30) $stop;
// Additional regression: join_any should also complete when disable kills a forked task
fork
increment_y();
#1 disable increment_y;
join_any
#3;
if (y != 1) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule