Release fork join/join_any on killed task processes.
This commit is contained in:
parent
4756d21cc0
commit
c96f1a2a06
|
|
@ -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--;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}, \
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue