diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 0fd82701e..ec50ae7ca 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -37,6 +37,7 @@ #include "V3Error.h" #include "V3UniqueNames.h" +#include #include VL_DEFINE_DEBUG_FUNCTIONS; @@ -65,6 +66,7 @@ class LinkJumpVisitor final : public VNVisitor { std::vector m_blockStack; // All begin blocks above current node V3UniqueNames m_queueNames{ "__VprocessQueue"}; // Names for queues needed for 'disable' handling + std::unordered_map 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& 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 forks; for (AstBegin* itemp = forkp->forksp(); itemp; itemp = VN_AS(itemp->nextp(), Begin)) { diff --git a/test_regress/t/t_disable_bad.out b/test_regress/t/t_disable_bad.out index d708f635c..90b807ff6 100644 --- a/test_regress/t/t_disable_bad.out +++ b/test_regress/t/t_disable_bad.out @@ -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 diff --git a/test_regress/t/t_disable_task_by_name.py b/test_regress/t/t_disable_task_by_name.py new file mode 100644 index 000000000..390bba1b8 --- /dev/null +++ b/test_regress/t/t_disable_task_by_name.py @@ -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() diff --git a/test_regress/t/t_disable_task_by_name.v b/test_regress/t/t_disable_task_by_name.v new file mode 100644 index 000000000..d2f86c909 --- /dev/null +++ b/test_regress/t/t_disable_task_by_name.v @@ -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 diff --git a/test_regress/t/t_disable_task_scope_bad.out b/test_regress/t/t_disable_task_scope_bad.out new file mode 100644 index 000000000..2fa1c2987 --- /dev/null +++ b/test_regress/t/t_disable_task_scope_bad.out @@ -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': + ... 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': +%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': +%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 diff --git a/test_regress/t/t_disable_task_scope_bad.py b/test_regress/t/t_disable_task_scope_bad.py new file mode 100644 index 000000000..40c047e86 --- /dev/null +++ b/test_regress/t/t_disable_task_scope_bad.py @@ -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() + diff --git a/test_regress/t/t_disable_task_scope_bad.v b/test_regress/t/t_disable_task_scope_bad.v new file mode 100644 index 000000000..8afd8b52c --- /dev/null +++ b/test_regress/t/t_disable_task_scope_bad.v @@ -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 diff --git a/test_regress/t/t_disable_task_target_bad.out b/test_regress/t/t_disable_task_target_bad.out new file mode 100644 index 000000000..613440744 --- /dev/null +++ b/test_regress/t/t_disable_task_target_bad.out @@ -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 diff --git a/test_regress/t/t_disable_task_unsup.py b/test_regress/t/t_disable_task_target_bad.py old mode 100755 new mode 100644 similarity index 85% rename from test_regress/t/t_disable_task_unsup.py rename to test_regress/t/t_disable_task_target_bad.py index 382ad0d44..38cf36b43 --- a/test_regress/t/t_disable_task_unsup.py +++ b/test_regress/t/t_disable_task_target_bad.py @@ -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) diff --git a/test_regress/t/t_disable_task_target_bad.v b/test_regress/t/t_disable_task_target_bad.v new file mode 100644 index 000000000..50116fff5 --- /dev/null +++ b/test_regress/t/t_disable_task_target_bad.v @@ -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 diff --git a/test_regress/t/t_disable_task_unsup.out b/test_regress/t/t_disable_task_unsup.out deleted file mode 100644 index 024eadd80..000000000 --- a/test_regress/t/t_disable_task_unsup.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_disable_task_unsup.v b/test_regress/t/t_disable_task_unsup.v deleted file mode 100644 index 6331ba64f..000000000 --- a/test_regress/t/t_disable_task_unsup.v +++ /dev/null @@ -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