From 4b770cffb442f9b889a6a854db0045d0c0c252ef Mon Sep 17 00:00:00 2001 From: Marco Bartoli Date: Thu, 30 Jul 2026 15:56:07 +0200 Subject: [PATCH] Fix cross module disable (Part 5 of #7857) (#8006) --- src/V3Ast.h | 2 +- src/V3AstNodes.cpp | 16 ++- src/V3LinkDot.cpp | 39 +++++- src/V3LinkJump.cpp | 53 ++++++-- test_regress/t/t_disable_begin_instances.py | 18 +++ test_regress/t/t_disable_begin_instances.v | 67 ++++++++++ test_regress/t/t_disable_fork_hier_inst.py | 18 +++ test_regress/t/t_disable_fork_hier_inst.v | 122 +++++++++++++++++ test_regress/t/t_disable_fork_instances.py | 18 +++ test_regress/t/t_disable_fork_instances.v | 105 +++++++++++++++ test_regress/t/t_disable_fork_reentrant.py | 18 +++ test_regress/t/t_disable_fork_reentrant.v | 137 ++++++++++++++++++++ test_regress/t/t_disable_task_instances.py | 18 +++ test_regress/t/t_disable_task_instances.v | 69 ++++++++++ 14 files changed, 682 insertions(+), 18 deletions(-) create mode 100755 test_regress/t/t_disable_begin_instances.py create mode 100644 test_regress/t/t_disable_begin_instances.v create mode 100755 test_regress/t/t_disable_fork_hier_inst.py create mode 100644 test_regress/t/t_disable_fork_hier_inst.v create mode 100755 test_regress/t/t_disable_fork_instances.py create mode 100644 test_regress/t/t_disable_fork_instances.v create mode 100755 test_regress/t/t_disable_fork_reentrant.py create mode 100644 test_regress/t/t_disable_fork_reentrant.v create mode 100755 test_regress/t/t_disable_task_instances.py create mode 100644 test_regress/t/t_disable_task_instances.v diff --git a/src/V3Ast.h b/src/V3Ast.h index 2fd3ef754..5a4e95245 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -730,7 +730,7 @@ public: // ACCESSORS for specific types // Alas these can't be virtual or they break when passed a nullptr - bool isDisableQueuePushSelfStmt() const; + bool isDisableQueuePushSelfStmt(); inline bool isClassHandleValue() const; inline bool isNull() const; inline bool isZero() const; diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 830c97999..b3b560307 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -108,15 +108,19 @@ int AstNodeSel::bitConst() const { return (constp ? constp->toSInt() : 0); } -bool AstNode::isDisableQueuePushSelfStmt() const { +bool AstNode::isDisableQueuePushSelfStmt() { // Detect LinkJump-generated registration: // __VprocessQueue_*.push_back(std::process::self()) - const AstStmtExpr* const stmtExprp = VN_CAST(this, StmtExpr); + AstStmtExpr* const stmtExprp = VN_CAST(this, StmtExpr); if (!stmtExprp) return false; - const AstCMethodHard* const methodp = VN_CAST(stmtExprp->exprp(), CMethodHard); + AstCMethodHard* const methodp = VN_CAST(stmtExprp->exprp(), CMethodHard); if (!methodp || methodp->name() != "push_back") return false; - const AstVarRef* const queueRefp = VN_CAST(methodp->fromp(), VarRef); - return queueRefp && queueRefp->varp()->processQueue(); + AstNode* const basep = AstArraySel::baseFromp(methodp->fromp(), false); + if (AstVarRef* const refp = VN_CAST(basep, VarRef)) return refp->varp()->processQueue(); + if (AstMemberSel* const selp = VN_CAST(basep, MemberSel)) { + return selp->varp() && selp->varp()->processQueue(); + } + return false; } void AstNodeStmt::dump(std::ostream& str) const { this->AstNode::dump(str); } @@ -3808,7 +3812,7 @@ void AstDelay::dumpJson(std::ostream& str) const { } const char* AstDisable::broken() const { - BROKEN_RTN((m_targetp && targetRefp()) || ((!m_targetp && !targetRefp()))); + BROKEN_RTN(!m_targetp && !targetRefp()); return nullptr; } void AstDisable::dump(std::ostream& str) const { diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 6bfd27823..6e725abf8 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -96,6 +96,30 @@ static string extractDottedPath(AstNode* nodep, bool& hasPartSelect) { } return ""; } +static string lexicalDisablePath(AstNode* const targetp) { + std::vector names; + for (AstNode* curp = targetp; curp; curp = curp->aboveLoopp()) { + if (AstNodeBlock* const blockp = VN_CAST(curp, NodeBlock)) { + if (blockp->name() != "") names.push_back(blockp->name()); + } else if (AstNodeFTask* const ftaskp = VN_CAST(curp, NodeFTask)) { + names.push_back(ftaskp->name()); + } else if (VN_IS(curp, NodeModule)) { + break; + } + } + string path; + for (auto it = names.crbegin(); it != names.crend(); ++it) { + path = VString::dot(path, ".", *it); + } + return path; +} +static string targetInstancePath(AstNode* const targetp, const string& targetPath) { + const string lexicalPath = lexicalDisablePath(targetp); + if (lexicalPath == "" || targetPath == lexicalPath) return ""; + const string suffix = "." + lexicalPath; + if (!VString::endsWith(targetPath, suffix)) return ""; + return targetPath.substr(0, targetPath.size() - suffix.size()); +} // ###################################################################### // Matcher classes (for suggestion matching) @@ -6177,6 +6201,9 @@ class LinkDotResolveVisitor final : public VNVisitor { void visit(AstDisable* nodep) override { LINKDOT_VISIT_START(); checkNoDot(nodep); + bool hasPartSelect = false; + const string targetPath + = nodep->targetRefp() ? extractDottedPath(nodep->targetRefp(), hasPartSelect) : ""; VL_RESTORER_COPY(m_ds); m_ds.init(m_curSymp); m_ds.m_dotPos = DP_FIRST; @@ -6193,8 +6220,16 @@ class LinkDotResolveVisitor final : public VNVisitor { pushDeletep(nodep->unlinkFrBack()); } if (nodep->targetp()) { - // If the target is already linked, there is no need to store reference as child - VL_DO_DANGLING(nodep->targetRefp()->unlinkFrBack()->deleteTree(), nodep); + nodep->targetRefp()->unlinkFrBack()->deleteTree(); + if (!hasPartSelect) { + const string instancePath = targetInstancePath(nodep->targetp(), targetPath); + if (!instancePath.empty()) { + // Keep only the instance prefix so V3LinkJump can reference the selected + // instance's process queue without reparsing the disable target. + nodep->targetRefp( + new AstVarXRef{nodep->fileline(), "", instancePath, VAccess::READ}); + } + } } } } diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 4e80c1c96..ec86c191d 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -168,6 +168,23 @@ class LinkJumpVisitor final : public VNVisitor { } return false; } + static AstNodeModule* findOwnerModulep(AstNode* nodep) { + for (AstNode* curp = nodep; curp; curp = curp->aboveLoopp()) { + if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp; + } + nodep->v3fatalSrc("Disable target is not under a module"); + return nullptr; // LCOV_EXCL_LINE + } + static std::string targetInstancePath(const AstDisable* const nodep) { + if (const AstVarXRef* const xrefp = VN_CAST(nodep->targetRefp(), VarXRef)) { + return xrefp->dotted(); + } + return ""; + } + static std::string queueDotted(const AstDisable* const nodep, + const AstVar* const processQueuep) { + return processQueuep->isTemp() ? targetInstancePath(nodep) : ""; + } AstBegin* innerForkBranchp(const AstNodeBlock* const targetp) const { AstBegin* innerForkBranchp = nullptr; AstNodeBlock* prevBlockp = nullptr; @@ -188,17 +205,24 @@ class LinkJumpVisitor final : public VNVisitor { new AstMethodCall{flp, queueRefp, "push_back", new AstArg{flp, "", v3Global.rootp()->stdPackageProcessSelfp(flp)}}}; } - static AstVarRef* newQueueRefp(FileLine* const fl, AstVar* const processQueuep, - const VAccess& access) { + static AstNodeVarRef* newQueueRefp(FileLine* const fl, AstVar* const processQueuep, + const VAccess& access, const std::string& dotted = "") { + if (dotted != "") return new AstVarXRef{fl, processQueuep, dotted, access}; + if (!processQueuep->lifetime().isStatic() || processQueuep->isTemp()) { + return new AstVarRef{fl, processQueuep, access}; + } AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); return new AstVarRef{fl, topPkgp, processQueuep, access}; } static AstStmtExpr* getQueuePushProcessSelfp(FileLine* const fl, AstVar* const processQueuep) { - AstVarRef* const queueWriteRefp = newQueueRefp(fl, processQueuep, VAccess::WRITE); + AstVarRef* const queueWriteRefp + = VN_AS(newQueueRefp(fl, processQueuep, VAccess::WRITE), VarRef); return getQueuePushProcessSelfp(queueWriteRefp); } - static AstStmtExpr* getQueueKillStmtp(FileLine* const fl, AstVar* const processQueuep) { - AstVarRef* const queueRefp = newQueueRefp(fl, processQueuep, VAccess::READWRITE); + static AstStmtExpr* getQueueKillStmtp(FileLine* const fl, AstVar* const processQueuep, + const std::string& dotted = "") { + AstNodeVarRef* const queueRefp + = newQueueRefp(fl, processQueuep, VAccess::READWRITE, dotted); AstTaskRef* killQueueCall = nullptr; for (AstNode* itemp = v3Global.rootp()->stdPackageProcessp()->stmtsp(); itemp; itemp = itemp->nextp()) { @@ -248,10 +272,19 @@ class LinkJumpVisitor final : public VNVisitor { return processQueuep; } AstVar* getProcessQueuep(AstNode* const nodep, FileLine* const fl) { - AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); - AstVar* const processQueuep = newProcessQueuep(nodep, fl, VVarType::VAR); + AstNodeModule* const ownerp = findOwnerModulep(nodep); + + if (VN_IS(ownerp, Package) || VN_IS(ownerp, Class)) { + AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); + AstVar* const processQueuep = newProcessQueuep(nodep, fl, VVarType::VAR); + processQueuep->lifetime(VLifetime::STATIC_EXPLICIT); + topPkgp->addStmtsp(processQueuep); + return processQueuep; + } + + AstVar* const processQueuep = newProcessQueuep(nodep, fl, VVarType::MODULETEMP); processQueuep->lifetime(VLifetime::STATIC_EXPLICIT); - topPkgp->addStmtsp(processQueuep); + ownerp->addStmtsp(processQueuep); return processQueuep; } AstVar* getOrCreateTaskDisableQueuep(AstTask* const taskp, FileLine* const fl) { @@ -327,7 +360,9 @@ class LinkJumpVisitor final : public VNVisitor { return processQueuep; } AstStmtExpr* insertKillStmtp(AstDisable* const nodep, AstVar* const processQueuep) { - AstStmtExpr* const killStmtp = getQueueKillStmtp(nodep->fileline(), processQueuep); + FileLine* const fl = nodep->fileline(); + AstStmtExpr* const killStmtp + = getQueueKillStmtp(fl, processQueuep, queueDotted(nodep, processQueuep)); nodep->addNextHere(killStmtp); return killStmtp; } diff --git a/test_regress/t/t_disable_begin_instances.py b/test_regress/t/t_disable_begin_instances.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_disable_begin_instances.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(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_disable_begin_instances.v b/test_regress/t/t_disable_begin_instances.v new file mode 100644 index 000000000..a568b8ea1 --- /dev/null +++ b/test_regress/t/t_disable_begin_instances.v @@ -0,0 +1,67 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Disabling a named begin block in one module instance must not terminate +// branches of the same syntactic block in another module instance. + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module child ( + input bit do_disable, + output bit survived, + output bit done +); + + initial begin : blk + fork + begin + #5; + survived = 1'b1; + end + join_none + if (do_disable) begin + #1; + disable blk; + end + else begin + #6; + end + done = 1'b1; + end +endmodule + +module t; + + bit survived0; + bit done0; + bit survived1; + bit done1; + + child child0 ( + .do_disable(1'b1), + .survived(survived0), + .done(done0) + ); + + child child1 ( + .do_disable(1'b0), + .survived(survived1), + .done(done1) + ); + + initial begin + #8; + `checkd(done0, 1'b0); + `checkd(done1, 1'b1); + `checkd(survived0, 1'b0); + `checkd(survived1, 1'b1); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_disable_fork_hier_inst.py b/test_regress/t/t_disable_fork_hier_inst.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_disable_fork_hier_inst.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(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_disable_fork_hier_inst.v b/test_regress/t/t_disable_fork_hier_inst.v new file mode 100644 index 000000000..dab4cd1c1 --- /dev/null +++ b/test_regress/t/t_disable_fork_hier_inst.v @@ -0,0 +1,122 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// A hierarchical disable of a named fork in one module instance must terminate +// only that instance's fork branch. IEEE 1800-2023 A.6.5 permits disable of a +// hierarchical_block_identifier. + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module child ( + output bit survived, + output bit done +); + + initial begin : init + fork : fork_blk + begin + #5; + survived = 1'b1; + end + join_none + #6; + done = 1'b1; + end +endmodule + +// This separate case disables a fork declared in a sibling module, rather than +// another instance of the source fork declaration. +module fork_target ( + output bit survived, + output bit done +); + + initial begin : init + fork : target_fork + begin + #5; + survived = 1'b1; + end + join_none + #6; + done = 1'b1; + end +endmodule + +module disabler ( + output bit survived, + output bit done +); + + initial begin + fork : source_fork + begin + #2; + disable t.target_inst.init.target_fork; + end + begin + #4; + survived = 1'b1; + end + join + done = 1'b1; + end +endmodule + +module t; + + bit survived0; + bit done0; + bit survived1; + bit done1; + bit target_survived; + bit target_done; + bit disabler_survived; + bit disabler_done; + + child child0 ( + .survived(survived0), + .done(done0) + ); + + child child1 ( + .survived(survived1), + .done(done1) + ); + + fork_target target_inst ( + .survived(target_survived), + .done(target_done) + ); + + disabler disabler_inst ( + .survived(disabler_survived), + .done(disabler_done) + ); + + initial begin + #1; + disable child0.init.fork_blk; + #7; + `checkd(done0, 1'b1); + `checkd(done1, 1'b1); + `checkd(survived0, 1'b0); + `checkd(survived1, 1'b1); + $write("*-* All Finished *-*\n"); + $finish; + end + + initial begin + #7; + `checkd(target_done, 1'b1); + `checkd(target_survived, 1'b0); + `checkd(disabler_done, 1'b1); + `checkd(disabler_survived, 1'b1); + end +endmodule diff --git a/test_regress/t/t_disable_fork_instances.py b/test_regress/t/t_disable_fork_instances.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_disable_fork_instances.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(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_disable_fork_instances.v b/test_regress/t/t_disable_fork_instances.v new file mode 100644 index 000000000..213c026ba --- /dev/null +++ b/test_regress/t/t_disable_fork_instances.v @@ -0,0 +1,105 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Disabling a named fork in one module instance must not terminate branches of +// the same syntactic fork in another module instance. + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +class QueueHolder; + typedef int int_queue_t[$]; + int member_queue[$]; + + function automatic int_queue_t temporary_queue(); + int_queue_t result; + return result; + endfunction + + task run_temporary(); + fork + temporary_queue().push_back(2); + join + endtask +endclass + +module child( + input bit do_disable, + output bit survived_a, + output bit survived_b, + output bit done +); + + initial begin + fork : fork_blk + begin + if (do_disable) begin + #1; + disable fork_blk; + $stop; + end else begin + #6; + survived_a = 1'b1; + end + end + begin + if (do_disable) begin + #5 $stop; + end else begin + #6; + survived_b = 1'b1; + end + end + join + done = 1'b1; + end +endmodule + +module t; + + bit do_disable0 = 1'b1; + bit do_disable1 = 1'b0; + bit survived_a0; + bit survived_b0; + bit done0; + bit survived_a1; + bit survived_b1; + bit done1; + QueueHolder holder; + + child child0( + .do_disable(do_disable0), + .survived_a(survived_a0), + .survived_b(survived_b0), + .done(done0) + ); + + child child1( + .do_disable(do_disable1), + .survived_a(survived_a1), + .survived_b(survived_b1), + .done(done1) + ); + + initial begin + // Cover queue receivers that are class members and temporary expressions. + holder = new; + fork + holder.member_queue.push_back(1); + holder.run_temporary(); + join + #7; + `checkd(holder.member_queue.size(), 1); + `checkd(done0, 1'b1); + `checkd(done1, 1'b1); + `checkd(survived_a1, 1'b1); + `checkd(survived_b1, 1'b1); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_disable_fork_reentrant.py b/test_regress/t/t_disable_fork_reentrant.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_disable_fork_reentrant.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(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_disable_fork_reentrant.v b/test_regress/t/t_disable_fork_reentrant.v new file mode 100644 index 000000000..c65b18c46 --- /dev/null +++ b/test_regress/t/t_disable_fork_reentrant.v @@ -0,0 +1,137 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// A disable of a named fork inside one dynamic task invocation must kill +// branches of the same syntactic fork in every concurrent invocation of that +// task. IEEE 1800-2023 9.6.2 says disabling an automatic task or a block inside +// an automatic task proceeds as for regular tasks for all concurrent executions +// of the task. +// +// The cross-module case checks the complementary instance scoping rule: the same +// task declaration can be instantiated under different module instances in +// different wrapper modules, and disabling the named fork in one instance must +// not terminate the other instance's fork. + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module child_task_mod ( + input bit do_disable, + output bit survived, + output bit task_done +); + + task automatic run(); + fork : fork_blk + begin + if (do_disable) begin + #1; + disable fork_blk; + $stop; + end + else begin + #6; + end + end + begin + #5; + survived = 1'b1; + end + join + task_done = 1'b1; + endtask + + initial run(); +endmodule + +module disable_wrapper ( + output bit survived, + output bit task_done +); + child_task_mod child ( + .do_disable(1'b1), + .survived(survived), + .task_done(task_done) + ); +endmodule + +module keep_wrapper ( + output bit survived, + output bit task_done +); + child_task_mod child ( + .do_disable(1'b0), + .survived(survived), + .task_done(task_done) + ); +endmodule + +module t; + + bit [1:0] survived_a = 2'b00; + bit [1:0] survived_b = 2'b00; + bit [1:0] task_done = 2'b00; + bit wrapper_disable_survived; + bit wrapper_disable_done; + bit wrapper_keep_survived; + bit wrapper_keep_done; + + disable_wrapper wrapper0 ( + .survived(wrapper_disable_survived), + .task_done(wrapper_disable_done) + ); + + keep_wrapper wrapper1 ( + .survived(wrapper_keep_survived), + .task_done(wrapper_keep_done) + ); + + task automatic run(input int id, input bit do_disable); + fork : fork_blk + begin + if (do_disable) begin + #1; + disable fork_blk; + $stop; + end + else begin + #6; + survived_a[id] = 1'b1; + end + end + begin + if (do_disable) begin + #5 $stop; + end + else begin + #6; + survived_b[id] = 1'b1; + end + end + join + task_done[id] = 1'b1; + endtask + + initial begin + fork + run(0, 1'b1); + run(1, 1'b0); + join + #1; + `checkd(task_done, 2'b11); + `checkd(survived_a, 2'b00); + `checkd(survived_b, 2'b00); + #6; + `checkd(wrapper_disable_done, 1'b1); + `checkd(wrapper_keep_done, 1'b1); + `checkd(wrapper_disable_survived, 1'b0); + `checkd(wrapper_keep_survived, 1'b1); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_disable_task_instances.py b/test_regress/t/t_disable_task_instances.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_disable_task_instances.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(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_disable_task_instances.v b/test_regress/t/t_disable_task_instances.v new file mode 100644 index 000000000..28911ccb6 --- /dev/null +++ b/test_regress/t/t_disable_task_instances.v @@ -0,0 +1,69 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Disabling a module task by its local name in one module instance must not +// terminate the same task declaration's activation in another module instance. +// Each module instance and task definition creates a distinct hierarchical scope. + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module child ( + input bit do_disable, + output bit survived, + output bit done +); + + task automatic run(); + #5; + survived = 1'b1; + endtask + + initial begin + fork + run(); + join_none + if (do_disable) begin + #1; + disable run; + end + else begin + #6; + end + done = 1'b1; + end +endmodule +module t; + + bit survived0; + bit done0; + bit survived1; + bit done1; + + child child0 ( + .do_disable(1'b1), + .survived(survived0), + .done(done0) + ); + + child child1 ( + .do_disable(1'b0), + .survived(survived1), + .done(done1) + ); + + initial begin + #8; + `checkd(done0, 1'b1); + `checkd(done1, 1'b1); + `checkd(survived0, 1'b0); + `checkd(survived1, 1'b1); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule