parent
42e043cb01
commit
4b770cffb4
|
|
@ -730,7 +730,7 @@ public:
|
||||||
|
|
||||||
// ACCESSORS for specific types
|
// ACCESSORS for specific types
|
||||||
// Alas these can't be virtual or they break when passed a nullptr
|
// Alas these can't be virtual or they break when passed a nullptr
|
||||||
bool isDisableQueuePushSelfStmt() const;
|
bool isDisableQueuePushSelfStmt();
|
||||||
inline bool isClassHandleValue() const;
|
inline bool isClassHandleValue() const;
|
||||||
inline bool isNull() const;
|
inline bool isNull() const;
|
||||||
inline bool isZero() const;
|
inline bool isZero() const;
|
||||||
|
|
|
||||||
|
|
@ -108,15 +108,19 @@ int AstNodeSel::bitConst() const {
|
||||||
return (constp ? constp->toSInt() : 0);
|
return (constp ? constp->toSInt() : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AstNode::isDisableQueuePushSelfStmt() const {
|
bool AstNode::isDisableQueuePushSelfStmt() {
|
||||||
// Detect LinkJump-generated registration:
|
// Detect LinkJump-generated registration:
|
||||||
// __VprocessQueue_*.push_back(std::process::self())
|
// __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;
|
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;
|
if (!methodp || methodp->name() != "push_back") return false;
|
||||||
const AstVarRef* const queueRefp = VN_CAST(methodp->fromp(), VarRef);
|
AstNode* const basep = AstArraySel::baseFromp(methodp->fromp(), false);
|
||||||
return queueRefp && queueRefp->varp()->processQueue();
|
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); }
|
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 {
|
const char* AstDisable::broken() const {
|
||||||
BROKEN_RTN((m_targetp && targetRefp()) || ((!m_targetp && !targetRefp())));
|
BROKEN_RTN(!m_targetp && !targetRefp());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
void AstDisable::dump(std::ostream& str) const {
|
void AstDisable::dump(std::ostream& str) const {
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,30 @@ static string extractDottedPath(AstNode* nodep, bool& hasPartSelect) {
|
||||||
}
|
}
|
||||||
return "";
|
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)
|
// Matcher classes (for suggestion matching)
|
||||||
|
|
@ -6177,6 +6201,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
|
||||||
void visit(AstDisable* nodep) override {
|
void visit(AstDisable* nodep) override {
|
||||||
LINKDOT_VISIT_START();
|
LINKDOT_VISIT_START();
|
||||||
checkNoDot(nodep);
|
checkNoDot(nodep);
|
||||||
|
bool hasPartSelect = false;
|
||||||
|
const string targetPath
|
||||||
|
= nodep->targetRefp() ? extractDottedPath(nodep->targetRefp(), hasPartSelect) : "";
|
||||||
VL_RESTORER_COPY(m_ds);
|
VL_RESTORER_COPY(m_ds);
|
||||||
m_ds.init(m_curSymp);
|
m_ds.init(m_curSymp);
|
||||||
m_ds.m_dotPos = DP_FIRST;
|
m_ds.m_dotPos = DP_FIRST;
|
||||||
|
|
@ -6193,8 +6220,16 @@ class LinkDotResolveVisitor final : public VNVisitor {
|
||||||
pushDeletep(nodep->unlinkFrBack());
|
pushDeletep(nodep->unlinkFrBack());
|
||||||
}
|
}
|
||||||
if (nodep->targetp()) {
|
if (nodep->targetp()) {
|
||||||
// If the target is already linked, there is no need to store reference as child
|
nodep->targetRefp()->unlinkFrBack()->deleteTree();
|
||||||
VL_DO_DANGLING(nodep->targetRefp()->unlinkFrBack()->deleteTree(), nodep);
|
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;
|
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(const AstNodeBlock* const targetp) const {
|
||||||
AstBegin* innerForkBranchp = nullptr;
|
AstBegin* innerForkBranchp = nullptr;
|
||||||
AstNodeBlock* prevBlockp = nullptr;
|
AstNodeBlock* prevBlockp = nullptr;
|
||||||
|
|
@ -188,17 +205,24 @@ class LinkJumpVisitor final : public VNVisitor {
|
||||||
new AstMethodCall{flp, queueRefp, "push_back",
|
new AstMethodCall{flp, queueRefp, "push_back",
|
||||||
new AstArg{flp, "", v3Global.rootp()->stdPackageProcessSelfp(flp)}}};
|
new AstArg{flp, "", v3Global.rootp()->stdPackageProcessSelfp(flp)}}};
|
||||||
}
|
}
|
||||||
static AstVarRef* newQueueRefp(FileLine* const fl, AstVar* const processQueuep,
|
static AstNodeVarRef* newQueueRefp(FileLine* const fl, AstVar* const processQueuep,
|
||||||
const VAccess& access) {
|
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();
|
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
|
||||||
return new AstVarRef{fl, topPkgp, processQueuep, access};
|
return new AstVarRef{fl, topPkgp, processQueuep, access};
|
||||||
}
|
}
|
||||||
static AstStmtExpr* getQueuePushProcessSelfp(FileLine* const fl, AstVar* const processQueuep) {
|
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);
|
return getQueuePushProcessSelfp(queueWriteRefp);
|
||||||
}
|
}
|
||||||
static AstStmtExpr* getQueueKillStmtp(FileLine* const fl, AstVar* const processQueuep) {
|
static AstStmtExpr* getQueueKillStmtp(FileLine* const fl, AstVar* const processQueuep,
|
||||||
AstVarRef* const queueRefp = newQueueRefp(fl, processQueuep, VAccess::READWRITE);
|
const std::string& dotted = "") {
|
||||||
|
AstNodeVarRef* const queueRefp
|
||||||
|
= newQueueRefp(fl, processQueuep, VAccess::READWRITE, dotted);
|
||||||
AstTaskRef* killQueueCall = nullptr;
|
AstTaskRef* killQueueCall = nullptr;
|
||||||
for (AstNode* itemp = v3Global.rootp()->stdPackageProcessp()->stmtsp(); itemp;
|
for (AstNode* itemp = v3Global.rootp()->stdPackageProcessp()->stmtsp(); itemp;
|
||||||
itemp = itemp->nextp()) {
|
itemp = itemp->nextp()) {
|
||||||
|
|
@ -248,10 +272,19 @@ class LinkJumpVisitor final : public VNVisitor {
|
||||||
return processQueuep;
|
return processQueuep;
|
||||||
}
|
}
|
||||||
AstVar* getProcessQueuep(AstNode* const nodep, FileLine* const fl) {
|
AstVar* getProcessQueuep(AstNode* const nodep, FileLine* const fl) {
|
||||||
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
|
AstNodeModule* const ownerp = findOwnerModulep(nodep);
|
||||||
AstVar* const processQueuep = newProcessQueuep(nodep, fl, VVarType::VAR);
|
|
||||||
|
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);
|
processQueuep->lifetime(VLifetime::STATIC_EXPLICIT);
|
||||||
topPkgp->addStmtsp(processQueuep);
|
ownerp->addStmtsp(processQueuep);
|
||||||
return processQueuep;
|
return processQueuep;
|
||||||
}
|
}
|
||||||
AstVar* getOrCreateTaskDisableQueuep(AstTask* const taskp, FileLine* const fl) {
|
AstVar* getOrCreateTaskDisableQueuep(AstTask* const taskp, FileLine* const fl) {
|
||||||
|
|
@ -327,7 +360,9 @@ class LinkJumpVisitor final : public VNVisitor {
|
||||||
return processQueuep;
|
return processQueuep;
|
||||||
}
|
}
|
||||||
AstStmtExpr* insertKillStmtp(AstDisable* const nodep, AstVar* const 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);
|
nodep->addNextHere(killStmtp);
|
||||||
return 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