parent
42e043cb01
commit
4b770cffb4
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue