Fix cross module disable (Part 5 of #7857) (#8006)

This commit is contained in:
Marco Bartoli 2026-07-30 15:56:07 +02:00 committed by GitHub
parent 42e043cb01
commit 4b770cffb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 682 additions and 18 deletions

View File

@ -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;

View File

@ -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 {

View File

@ -96,6 +96,30 @@ static string extractDottedPath(AstNode* nodep, bool& hasPartSelect) {
}
return "";
}
static string lexicalDisablePath(AstNode* const targetp) {
std::vector<string> 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});
}
}
}
}
}

View File

@ -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;
}

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(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -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

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(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -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

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(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -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

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(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -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

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(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -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