Implement disable task by name, add regression testing.

This commit is contained in:
Nick Brereton 2026-02-23 10:00:47 -05:00
parent 7c923bb330
commit 4756d21cc0
12 changed files with 547 additions and 88 deletions

View File

@ -37,6 +37,7 @@
#include "V3Error.h"
#include "V3UniqueNames.h"
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -65,6 +66,7 @@ class LinkJumpVisitor final : public VNVisitor {
std::vector<AstNodeBlock*> m_blockStack; // All begin blocks above current node
V3UniqueNames m_queueNames{
"__VprocessQueue"}; // Names for queues needed for 'disable' handling
std::unordered_map<const AstTask*, AstVar*> m_taskDisableQueues; // Per-task process queues
// METHODS
// Get (and create if necessary) the JumpBlock for this statement
@ -177,6 +179,42 @@ class LinkJumpVisitor final : public VNVisitor {
return new AstStmtExpr{
fl, new AstMethodCall{fl, queueRefp, "push_back", new AstArg{fl, "", processSelfp}}};
}
static AstStmtExpr* getQueueKillStmtp(FileLine* const fl, AstVar* const processQueuep) {
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
AstClass* const processClassp
= VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
AstVarRef* const queueRefp = new AstVarRef{fl, topPkgp, processQueuep, VAccess::READWRITE};
AstTaskRef* const killQueueCall
= new AstTaskRef{fl, VN_AS(getMemberp(processClassp, "killQueue"), Task),
new AstArg{fl, "", queueRefp}};
killQueueCall->classOrPackagep(processClassp);
return new AstStmtExpr{fl, killQueueCall};
}
AstVar* getOrCreateTaskDisableQueuep(AstTask* const taskp, FileLine* const fl) {
const auto it = m_taskDisableQueues.find(taskp);
if (it != m_taskDisableQueues.end()) return it->second;
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
AstClass* const processClassp
= VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
AstVar* const processQueuep = new AstVar{
fl, VVarType::VAR, m_queueNames.get(taskp->name()), VFlagChildDType{},
new AstQueueDType{fl, VFlagChildDType{},
new AstClassRefDType{fl, processClassp, nullptr}, nullptr}};
processQueuep->lifetime(VLifetime::STATIC_EXPLICIT);
topPkgp->addStmtsp(processQueuep);
AstVarRef* const queueWriteRefp
= new AstVarRef{fl, topPkgp, processQueuep, VAccess::WRITE};
AstStmtExpr* const pushCurrentProcessp = getQueuePushProcessSelfp(queueWriteRefp);
if (taskp->stmtsp()) {
taskp->stmtsp()->addHereThisAsNext(pushCurrentProcessp);
} else {
taskp->addStmtsp(pushCurrentProcessp);
}
m_taskDisableQueues.emplace(taskp, processQueuep);
return processQueuep;
}
void handleDisableOnFork(AstDisable* const nodep, const std::vector<AstBegin*>& forks) {
// The support utilizes the process::kill()` method. For each `disable` a queue of
// processes is declared. At the beginning of each fork that can be disabled, its process
@ -215,12 +253,7 @@ class LinkJumpVisitor final : public VNVisitor {
beginp->stmtsp()->addHereThisAsNext(pushCurrentProcessp);
}
}
AstVarRef* const queueRefp = new AstVarRef{fl, topPkgp, processQueuep, VAccess::READWRITE};
AstTaskRef* const killQueueCall
= new AstTaskRef{fl, VN_AS(getMemberp(processClassp, "killQueue"), Task),
new AstArg{fl, "", queueRefp}};
killQueueCall->classOrPackagep(processClassp);
AstStmtExpr* const killStmtp = new AstStmtExpr{fl, killQueueCall};
AstStmtExpr* const killStmtp = getQueueKillStmtp(fl, processQueuep);
nodep->addNextHere(killStmtp);
// 'process::kill' does not immediately kill the current process
@ -417,9 +450,23 @@ class LinkJumpVisitor final : public VNVisitor {
void visit(AstDisable* nodep) override {
UINFO(8, " DISABLE " << nodep);
AstNode* const targetp = nodep->targetp();
UASSERT_OBJ(targetp, nodep, "Unlinked disable statement");
if (VN_IS(targetp, Task)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: disabling task by name");
if (!targetp) {
// Linking errors on the disable target are already reported upstream.
// Drop this node to avoid cascading into an internal assertion.
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
if (AstTask* const taskp = VN_CAST(targetp, Task)) {
AstVar* const processQueuep = getOrCreateTaskDisableQueuep(taskp, nodep->fileline());
AstStmtExpr* const killStmtp = getQueueKillStmtp(nodep->fileline(), processQueuep);
nodep->addNextHere(killStmtp);
// process::kill does not terminate the currently running process immediately.
// If we disable the current task by name from inside itself, jump to its end.
if (m_ftaskp == taskp) {
AstJumpBlock* const blockp = getJumpBlock(taskp, false);
killStmtp->addNextHere(new AstJumpGo{nodep->fileline(), blockp});
}
} else if (AstFork* const forkp = VN_CAST(targetp, Fork)) {
std::vector<AstBegin*> forks;
for (AstBegin* itemp = forkp->forksp(); itemp; itemp = VN_AS(itemp->nextp(), Begin)) {

View File

@ -2,7 +2,4 @@
9 | disable abcd;
| ^~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Internal Error: t/t_disable_bad.v:9:5: ../V3LinkJump.cpp:#: Unlinked disable statement
9 | disable abcd;
| ^~~~~~~
... This fatal error may be caused by the earlier error(s); resolve those first.
%Error: Exiting due to

View File

@ -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(timing_loop=True, verilator_flags2=["--timing"])
test.execute()
test.passes()

View File

@ -0,0 +1,354 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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
int x = 0;
int y = 0;
int self_entry = 0;
int self_after_disable = 0;
int par_started = 0;
int par_finished = 0;
int par_parent_continued = 0;
class StaticCls;
static int ticks = 0;
static task run();
forever begin
#10;
ticks++;
end
endtask
static task stop();
disable run;
endtask
endclass
task increment_x;
x++;
#2;
x++;
endtask
task increment_y;
y++;
#5;
y++;
endtask
task self_stop;
self_entry = 1;
disable self_stop;
self_after_disable = 1;
endtask
task worker;
par_started++;
#20;
par_finished++;
endtask
task parent_disables_worker;
fork
worker();
worker();
join_none
#5;
disable worker;
par_parent_continued = 1;
endtask
class Cls;
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
class NamedA;
int v = 0;
task run();
forever begin
#10;
v++;
end
endtask
task stop();
disable run;
endtask
endclass
class NamedB;
int v = 0;
task run();
forever begin
#10;
v++;
end
endtask
endclass
class BaseNamed;
int v = 0;
task run();
forever begin
#10;
v++;
end
endtask
endclass
class ChildNamed extends BaseNamed;
int child_v = 0;
task run();
forever begin
#10;
child_v++;
end
endtask
task stop();
disable run;
endtask
endclass
package PkgRun;
int v = 0;
task run();
forever begin
#10;
v++;
end
endtask
task stop();
disable run;
endtask
endpackage
interface Ifc;
int v = 0;
task run();
forever begin
#10;
v++;
end
endtask
task stop();
disable run;
endtask
endinterface
program Prog;
int v = 0;
task run();
forever begin
#10;
v++;
end
endtask
task stop();
disable run;
endtask
endprogram
module WorkerMod;
int m = 0;
task run();
forever begin
#10;
m++;
end
endtask
endmodule
module t;
Ifc ifc1();
Prog prog1();
WorkerMod mod1();
initial begin
automatic Cls c = new;
automatic NamedA a = new;
automatic NamedB b = new;
automatic BaseNamed base_obj = new;
automatic ChildNamed child_obj = new;
int m_time_before_shutdown;
int mod1_before;
int a_before;
int b_before;
int static_before;
int base_before;
int child_before;
int ifc_before;
int prog_before;
int pkg_before;
// Module task disabled by sibling process in a fork
fork
increment_x();
#1 disable increment_x;
join_none
#10;
if (x != 1) $stop;
// Another basic module-task disable-by-name case
fork
increment_y();
#3 disable increment_y;
join_none
#10;
if (y != 1) $stop;
// Self-disable in task by name
self_stop();
if (self_entry != 1) $stop;
if (self_after_disable != 0) $stop;
// Same task launched in parallel, disabled from parent task context
parent_disables_worker();
#30;
if (par_started != 2) $stop;
if (par_finished != 0) $stop;
if (par_parent_continued != 1) $stop;
// Same task launched in parallel, disabled from sibling process context
par_started = 0;
par_finished = 0;
fork
worker();
worker();
begin
#5;
disable worker;
end
join_none
#30;
if (par_started != 2) $stop;
if (par_finished != 0) $stop;
// Static class task disabled by name from another static task
fork
StaticCls::run();
StaticCls::run();
join_none
#25;
if (StaticCls::ticks == 0) $stop;
static_before = StaticCls::ticks;
StaticCls::stop();
#30;
if (StaticCls::ticks != static_before) $stop;
// Same task name in different class scopes: disable only one scope
fork
a.run();
b.run();
join_none
#25;
if (a.v == 0 || b.v == 0) $stop;
a_before = a.v;
b_before = b.v;
a.stop();
#30;
if (a.v != a_before) $stop;
if (b.v <= b_before) $stop;
// Same task name across inheritance scopes: disable only derived task
fork
base_obj.run();
child_obj.run();
join_none
#25;
if (base_obj.v == 0 || child_obj.child_v == 0) $stop;
base_before = base_obj.v;
child_before = child_obj.child_v;
child_obj.stop();
#30;
if (child_obj.child_v != child_before) $stop;
if (base_obj.v <= base_before) $stop;
// Interface task disabled by name through interface scope
fork
ifc1.run();
join_none
#25;
if (ifc1.v == 0) $stop;
ifc_before = ifc1.v;
ifc1.stop();
#30;
if (ifc1.v != ifc_before) $stop;
// Program task disabled by name through program scope
fork
prog1.run();
join_none
#25;
if (prog1.v == 0) $stop;
prog_before = prog1.v;
prog1.stop();
#30;
if (prog1.v != prog_before) $stop;
// Package task disabled by name through package scope
fork
PkgRun::run();
join_none
#25;
if (PkgRun::v == 0) $stop;
pkg_before = PkgRun::v;
PkgRun::stop();
#30;
if (PkgRun::v != pkg_before) $stop;
// Dotted hierarchical task disable of module task by instance path
fork
mod1.run();
join_none
#25;
if (mod1.m == 0) $stop;
mod1_before = mod1.m;
disable mod1.run;
#30;
if (mod1.m != mod1_before) $stop;
// Class task disabled by name from outside that task
fork
c.get_and_send();
join_none
#35;
if (c.m_time == 0) $fatal;
m_time_before_shutdown = c.m_time;
c.post_shutdown_phase();
#30;
if (c.m_time != m_time_before_shutdown) $fatal;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,20 @@
%Error: t/t_disable_task_scope_bad.v:34:18: Can't find definition of 'missing_task' in dotted block/task: 'ifc1.missing_task'
34 | disable ifc1.missing_task;
| ^~~~~~~~~~~~
... Known scopes under 'ifc1': <no instances found>
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_disable_task_scope_bad.v:35:19: Can't find definition of 'missing_task' in dotted block/task: 'prog1.missing_task'
35 | disable prog1.missing_task;
| ^~~~~~~~~~~~
... Known scopes under 'prog1': <no instances found>
%Error: t/t_disable_task_scope_bad.v:36:26: Can't find definition of 'missing_task' in dotted block/task: 'outer1.inner.missing_task'
36 | disable outer1.inner.missing_task;
| ^~~~~~~~~~~~
... Known scopes under 'outer1.inner': <no instances found>
%Error: t/t_disable_task_scope_bad.v:37:18: Found definition of 'ifc1.data' as a VAR but expected a block/task
37 | disable ifc1.data;
| ^~~~
%Error: t/t_disable_task_scope_bad.v:38:19: Found definition of 'prog1.data' as a VAR but expected a block/task
38 | disable prog1.data;
| ^~~~
%Error: Exiting due to

View File

@ -0,0 +1,17 @@
#!/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('linter')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,42 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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
interface Ifc;
task run;
endtask
int data = 0;
endinterface
program Prog;
task run;
endtask
int data = 0;
endprogram
module Inner;
task run;
endtask
endmodule
module Outer;
Inner inner();
endmodule
module t;
Ifc ifc1();
Prog prog1();
Outer outer1();
initial begin
disable ifc1.missing_task;
disable prog1.missing_task;
disable outer1.inner.missing_task;
disable ifc1.data;
disable prog1.data;
end
endmodule

View File

@ -0,0 +1,11 @@
%Error: t/t_disable_task_target_bad.v:21:13: Found definition of 'foo' as a VAR but expected a block/task
21 | disable foo;
| ^~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_disable_task_target_bad.v:22:13: Found definition of 'c' as a VAR but expected a block/task
22 | disable c.run;
| ^
%Error: t/t_disable_task_target_bad.v:22:15: Can't find definition of block/task: 'run'
22 | disable c.run;
| ^~~
%Error: Exiting due to

View File

@ -4,12 +4,12 @@
# 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 Wilson Snyder
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.scenarios('linter')
test.lint(fails=True, expect_filename=test.golden_filename)

View File

@ -0,0 +1,26 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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
class Cls;
task run;
endtask
endclass
module t;
task foo;
endtask
Cls c;
initial begin
int foo;
c = new;
disable foo;
disable c.run;
end
endmodule

View File

@ -1,8 +0,0 @@
%Error-UNSUPPORTED: t/t_disable_task_unsup.v:37:10: Unsupported: disabling task by name
37 | #1 disable increment_x;
| ^~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_disable_task_unsup.v:26:5: Unsupported: disabling task by name
26 | disable get_and_send;
| ^~~~~~~
%Error: Exiting due to

View File

@ -1,65 +0,0 @@
// 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;
task increment_x;
x++;
#2;
x++;
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;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule