Merge e8fd3ae41a into ec124a0905
This commit is contained in:
commit
f0b3b56fe0
|
|
@ -730,6 +730,7 @@ public:
|
|||
|
||||
// ACCESSORS for specific types
|
||||
// Alas these can't be virtual or they break when passed a nullptr
|
||||
bool isDisableQueuePushSelfStmt();
|
||||
inline bool isClassHandleValue() const;
|
||||
inline bool isNull() const;
|
||||
inline bool isZero() const;
|
||||
|
|
|
|||
|
|
@ -108,6 +108,21 @@ int AstNodeSel::bitConst() const {
|
|||
return (constp ? constp->toSInt() : 0);
|
||||
}
|
||||
|
||||
bool AstNode::isDisableQueuePushSelfStmt() {
|
||||
// Detect LinkJump-generated registration:
|
||||
// __VprocessQueue_*.push_back(std::process::self())
|
||||
AstStmtExpr* const stmtExprp = VN_CAST(this, StmtExpr);
|
||||
if (!stmtExprp) return false;
|
||||
AstCMethodHard* const methodp = VN_CAST(stmtExprp->exprp(), CMethodHard);
|
||||
if (!methodp || methodp->name() != "push_back") return false;
|
||||
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); }
|
||||
void AstNodeStmt::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
|
||||
|
||||
|
|
@ -3795,7 +3810,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 {
|
||||
|
|
|
|||
|
|
@ -593,32 +593,50 @@ class ForkVisitor final : public VNVisitor {
|
|||
const AstConst* const constp = VN_CAST(delayp->lhsp(), Const);
|
||||
return constp && (constp->toUQuad() == std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
static bool isDisableQueuePushSelfStmt(const AstNode* const nodep) {
|
||||
// Detect LinkJump-generated registration:
|
||||
// __VprocessQueue_*.push_back(std::process::self())
|
||||
const AstStmtExpr* const stmtExprp = VN_CAST(nodep, StmtExpr);
|
||||
if (!stmtExprp) return false;
|
||||
const 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();
|
||||
static bool isDisableQueuePushSelfPrefix(AstNode* nodep) {
|
||||
while (AstJumpBlock* const jumpBlockp = VN_CAST(nodep, JumpBlock)) {
|
||||
nodep = jumpBlockp->stmtsp();
|
||||
}
|
||||
return nodep && nodep->isDisableQueuePushSelfStmt();
|
||||
}
|
||||
template <typename T_Owner>
|
||||
static bool insertForkSentinelAfterDisableQueuePushes(T_Owner* const ownerp,
|
||||
AstNode* const firstStmtp,
|
||||
AstNode* const delayp) {
|
||||
AstNode* insertBeforep = firstStmtp;
|
||||
while (insertBeforep && insertBeforep->isDisableQueuePushSelfStmt()) {
|
||||
insertBeforep = insertBeforep->nextp();
|
||||
}
|
||||
if (AstJumpBlock* const jumpBlockp = VN_CAST(insertBeforep, JumpBlock)) {
|
||||
if (insertForkSentinelAfterDisableQueuePushes(jumpBlockp, jumpBlockp->stmtsp(),
|
||||
delayp)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (insertBeforep == firstStmtp) return false;
|
||||
if (insertBeforep) {
|
||||
insertBeforep->addHereThisAsNext(delayp);
|
||||
} else {
|
||||
ownerp->addStmtsp(delayp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static void moveForkSentinelAfterDisableQueuePushes(AstBegin* const beginp) {
|
||||
AstNode* const firstStmtp = beginp->stmtsp();
|
||||
if (!isForkJoinNoneSentinelDelay(firstStmtp)) return;
|
||||
|
||||
AstNode* insertBeforep = firstStmtp->nextp();
|
||||
while (insertBeforep && isDisableQueuePushSelfStmt(insertBeforep)) {
|
||||
insertBeforep = insertBeforep->nextp();
|
||||
}
|
||||
if (insertBeforep == firstStmtp->nextp()) return;
|
||||
AstNode* const afterSentinelp = firstStmtp->nextp();
|
||||
if (!isDisableQueuePushSelfPrefix(afterSentinelp)) return;
|
||||
|
||||
AstNode* const delayp = firstStmtp->unlinkFrBack();
|
||||
if (insertBeforep) {
|
||||
insertBeforep->addHereThisAsNext(delayp);
|
||||
} else {
|
||||
beginp->addStmtsp(delayp);
|
||||
const bool moved
|
||||
= insertForkSentinelAfterDisableQueuePushes(beginp, afterSentinelp, delayp);
|
||||
UASSERT_OBJ(moved, beginp, "Failed to move fork sentinel after disable queue pushes");
|
||||
}
|
||||
static bool forkIsDisableable(AstFork* const nodep) {
|
||||
for (AstBegin* itemp = nodep->forksp(); itemp; itemp = VN_AS(itemp->nextp(), Begin)) {
|
||||
if (isDisableQueuePushSelfPrefix(itemp->stmtsp())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
|
|
@ -635,17 +653,14 @@ class ForkVisitor final : public VNVisitor {
|
|||
}
|
||||
|
||||
void visit(AstFork* nodep) override {
|
||||
if (nodep->joinType().join()) {
|
||||
iterateChildren(nodep);
|
||||
return;
|
||||
}
|
||||
|
||||
// IEEE 1800-2023 9.3.2: In all cases, processes spawned by a fork-join block shall not
|
||||
// start executing until the parent process is blocked or terminates.
|
||||
// Because join and join_any block the parent process, it is only needed when join_none
|
||||
// is used.
|
||||
if (nodep->joinType().joinNone()) {
|
||||
UINFO(9, "Visiting fork..join_none " << nodep);
|
||||
// start executing until the parent process is blocked or terminates. Because join and
|
||||
// join_any block the parent process, deferring branch start with a synthetic #0 delay is
|
||||
// normally only needed for join_none. A fork that can be disabled by name needs the same
|
||||
// deferral for every join type so all branches register their processes before any branch
|
||||
// body can disable the block.
|
||||
if (nodep->joinType().joinNone() || forkIsDisableable(nodep)) {
|
||||
UINFO(9, "Adding fork branch start sentinels " << nodep);
|
||||
FileLine* fl = nodep->fileline();
|
||||
// We use a sentinel value of UINT64_MAX to mark this delay so that it goes to the
|
||||
// ACTIVE region with a delay value of 0.
|
||||
|
|
@ -663,6 +678,17 @@ class ForkVisitor final : public VNVisitor {
|
|||
|
||||
iterateAndNextNull(nodep->declsp());
|
||||
iterateAndNextNull(nodep->stmtsp());
|
||||
|
||||
// A plain 'join' blocks the parent until every branch finishes, so a branch cannot
|
||||
// outlive the variables it references and need not be extracted into a task; just
|
||||
// recurse to process any forks nested inside the branches. join_any and join_none
|
||||
// branches may outlive the fork, so each branch body is wrapped in a task that
|
||||
// captures the variables it references.
|
||||
if (nodep->joinType().join()) {
|
||||
iterateAndNextNull(nodep->forksp());
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<AstBegin*> wrappedp;
|
||||
{
|
||||
VL_RESTORER(m_inFork);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -6163,6 +6187,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;
|
||||
|
|
@ -6179,8 +6206,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});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
"__VprocessQueue"}; // Names for queues needed for 'disable' handling
|
||||
std::unordered_map<const AstTask*, AstVar*> m_taskDisableQueues; // Per-task process queues
|
||||
std::unordered_map<const AstBegin*, AstVar*> m_beginDisableQueues; // Per-begin process queues
|
||||
std::unordered_map<const AstFork*, AstVar*> m_forkDisableQueues; // Per-fork process queues
|
||||
std::unordered_map<const AstTask*, AstBegin*>
|
||||
m_taskDisableBegins; // Per-task process wrappers
|
||||
std::unordered_map<const AstBegin*, AstBegin*>
|
||||
|
|
@ -167,6 +168,34 @@ 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;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
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;
|
||||
for (AstNodeBlock* const blockp : vlstd::reverse_view(m_blockStack)) {
|
||||
if (!innerForkBranchp && VN_IS(blockp, Fork)) {
|
||||
innerForkBranchp = VN_CAST(prevBlockp, Begin);
|
||||
}
|
||||
if (blockp == targetp) return innerForkBranchp;
|
||||
prevBlockp = blockp;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
static AstStmtExpr* getQueuePushProcessSelfp(AstVarRef* const queueRefp) {
|
||||
// Constructs queue.push_back(std::process::self()) statement
|
||||
FileLine* const flp = queueRefp->fileline();
|
||||
|
|
@ -175,15 +204,24 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
new AstMethodCall{flp, queueRefp, "push_back",
|
||||
new AstArg{flp, "", v3Global.rootp()->stdPackageProcessSelfp(flp)}}};
|
||||
}
|
||||
static AstStmtExpr* getQueuePushProcessSelfp(FileLine* const fl, AstVar* const processQueuep) {
|
||||
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
|
||||
= new AstVarRef{fl, topPkgp, processQueuep, VAccess::WRITE};
|
||||
= VN_AS(newQueueRefp(fl, processQueuep, VAccess::WRITE), VarRef);
|
||||
return getQueuePushProcessSelfp(queueWriteRefp);
|
||||
}
|
||||
static AstStmtExpr* getQueueKillStmtp(FileLine* const fl, AstVar* const processQueuep) {
|
||||
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
|
||||
AstVarRef* const queueRefp = new AstVarRef{fl, topPkgp, 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()) {
|
||||
|
|
@ -197,24 +235,13 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
killQueueCall->classOrPackagep(v3Global.rootp()->stdPackageProcessp());
|
||||
return new AstStmtExpr{fl, killQueueCall};
|
||||
}
|
||||
static void prependStmtsp(AstNodeFTask* const nodep, AstNode* const stmtp) {
|
||||
static void prependStmtsp(AstBegin* const nodep, AstNode* const stmtp) {
|
||||
if (AstNode* const origStmtsp = nodep->stmtsp()) {
|
||||
origStmtsp->unlinkFrBackWithNext();
|
||||
stmtp->addNext(origStmtsp);
|
||||
}
|
||||
nodep->addStmtsp(stmtp);
|
||||
}
|
||||
static void prependStmtsp(AstNodeBlock* const nodep, AstNode* const stmtp) {
|
||||
if (AstNode* const origStmtsp = nodep->stmtsp()) {
|
||||
origStmtsp->unlinkFrBackWithNext();
|
||||
stmtp->addNext(origStmtsp);
|
||||
}
|
||||
nodep->addStmtsp(stmtp);
|
||||
}
|
||||
static bool directlyUnderFork(const AstNode* const nodep) {
|
||||
if (nodep->backp()->nextp() == nodep) return directlyUnderFork(nodep->backp());
|
||||
return VN_IS(nodep->backp(), Fork);
|
||||
}
|
||||
AstBegin* getOrCreateTaskDisableBeginp(AstTask* const taskp, FileLine* const fl) {
|
||||
const auto it = m_taskDisableBegins.find(taskp);
|
||||
if (it != m_taskDisableBegins.end()) return it->second;
|
||||
|
|
@ -232,18 +259,33 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
m_taskDisableBegins.emplace(taskp, taskBodyp);
|
||||
return taskBodyp;
|
||||
}
|
||||
AstVar* getProcessQueuep(AstNode* const nodep, FileLine* const fl) {
|
||||
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
|
||||
AstVar* newProcessQueuep(AstNode* const nodep, FileLine* const fl, const VVarType& varType) {
|
||||
AstVar* const processQueuep = new AstVar{
|
||||
fl, VVarType::VAR, m_queueNames.get(nodep->name()), VFlagChildDType{},
|
||||
fl, varType, m_queueNames.get(nodep->name()), VFlagChildDType{},
|
||||
new AstQueueDType{
|
||||
fl, VFlagChildDType{},
|
||||
new AstClassRefDType{fl, v3Global.rootp()->stdPackageProcessp(), nullptr},
|
||||
nullptr}};
|
||||
processQueuep->lifetime(VLifetime::STATIC_EXPLICIT);
|
||||
processQueuep->processQueue(true);
|
||||
processQueuep->setIgnoreSchedWrite();
|
||||
topPkgp->addStmtsp(processQueuep);
|
||||
return processQueuep;
|
||||
}
|
||||
AstVar* getProcessQueuep(AstNode* const nodep, FileLine* const fl) {
|
||||
AstNodeModule* ownerp = findOwnerModulep(nodep);
|
||||
if (!ownerp) ownerp = m_modp;
|
||||
UASSERT_OBJ(ownerp, nodep, "Disable queue owner is not under a module");
|
||||
|
||||
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);
|
||||
ownerp->addStmtsp(processQueuep);
|
||||
return processQueuep;
|
||||
}
|
||||
AstVar* getOrCreateTaskDisableQueuep(AstTask* const taskp, FileLine* const fl) {
|
||||
|
|
@ -274,6 +316,20 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
m_beginDisableBegins.emplace(beginp, beginBodyp);
|
||||
return beginBodyp;
|
||||
}
|
||||
void prependForkBranchQueuePushes(AstFork* const forkp, AstVar* const processQueuep,
|
||||
FileLine* const fl, bool needProcess) {
|
||||
for (AstBegin* branchp = forkp->forksp(); branchp;
|
||||
branchp = VN_AS(branchp->nextp(), Begin)) {
|
||||
if (needProcess) branchp->setNeedProcess();
|
||||
prependStmtsp(branchp, getQueuePushProcessSelfp(fl, processQueuep));
|
||||
}
|
||||
}
|
||||
void prependNestedForkBranchQueuePushes(AstNode* const nodep, AstVar* const processQueuep,
|
||||
FileLine* const fl, bool needProcess) {
|
||||
nodep->foreach([&](AstFork* const forkp) {
|
||||
prependForkBranchQueuePushes(forkp, processQueuep, fl, needProcess);
|
||||
});
|
||||
}
|
||||
AstVar* getOrCreateBeginDisableQueuep(AstBegin* const beginp, FileLine* const fl) {
|
||||
const auto it = m_beginDisableQueues.find(beginp);
|
||||
if (it != m_beginDisableQueues.end()) return it->second;
|
||||
|
|
@ -285,24 +341,43 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
|
||||
// Named-block disable must also terminate detached descendants created by forks
|
||||
// under the block, so track each fork branch process in the same queue.
|
||||
beginBodyp->foreach([&](AstFork* const forkp) {
|
||||
for (AstBegin* branchp = forkp->forksp(); branchp;
|
||||
branchp = VN_AS(branchp->nextp(), Begin)) {
|
||||
AstStmtExpr* const pushBranchProcessp
|
||||
= getQueuePushProcessSelfp(fl, processQueuep);
|
||||
prependStmtsp(branchp, pushBranchProcessp);
|
||||
}
|
||||
});
|
||||
prependNestedForkBranchQueuePushes(beginBodyp, processQueuep, fl, false);
|
||||
m_beginDisableQueues.emplace(beginp, processQueuep);
|
||||
return processQueuep;
|
||||
}
|
||||
void handleDisableOnFork(AstDisable* const nodep, const std::vector<AstBegin*>& forks) {
|
||||
// The support utilizes the process::kill()` method. For each `disable` a queue of
|
||||
// processes is declared. At the beginning of each fork that can be disabled, its process
|
||||
// handle is pushed to the queue. `disable` statement is replaced with calling `kill()`
|
||||
// method on each element of the queue.
|
||||
AstVar* getOrCreateForkDisableQueuep(AstFork* const forkp, FileLine* const fl) {
|
||||
const auto it = m_forkDisableQueues.find(forkp);
|
||||
if (it != m_forkDisableQueues.end()) return it->second;
|
||||
|
||||
AstVar* const processQueuep = getProcessQueuep(forkp, fl);
|
||||
prependForkBranchQueuePushes(forkp, processQueuep, fl, true);
|
||||
// Disabling a fork must also terminate detached descendants created by nested forks
|
||||
// under each branch, so track nested fork branch processes in the same queue.
|
||||
for (AstBegin* branchp = forkp->forksp(); branchp;
|
||||
branchp = VN_AS(branchp->nextp(), Begin)) {
|
||||
prependNestedForkBranchQueuePushes(branchp, processQueuep, fl, true);
|
||||
}
|
||||
m_forkDisableQueues.emplace(forkp, processQueuep);
|
||||
return processQueuep;
|
||||
}
|
||||
AstStmtExpr* insertKillStmtp(AstDisable* const nodep, AstVar* const processQueuep) {
|
||||
FileLine* const fl = nodep->fileline();
|
||||
AstNode* const targetp = nodep->targetp();
|
||||
AstStmtExpr* const killStmtp
|
||||
= getQueueKillStmtp(fl, processQueuep, queueDotted(nodep, processQueuep));
|
||||
nodep->addNextHere(killStmtp);
|
||||
return killStmtp;
|
||||
}
|
||||
void addJumpAfterKill(AstStmtExpr* const killStmtp, AstNode* const targetp) {
|
||||
AstJumpBlock* const jmpBlockp = getJumpBlock(targetp, false);
|
||||
killStmtp->addNextHere(new AstJumpGo{killStmtp->fileline(), jmpBlockp});
|
||||
}
|
||||
void handleDisableOnFork(AstDisable* const nodep) {
|
||||
// The support utilizes the process::kill()` method. For each disabled fork a queue of
|
||||
// processes is declared. At the beginning of each fork branch (and each nested fork
|
||||
// branch) its process handle is pushed to the queue. The `disable` statement is replaced
|
||||
// with calling `kill()` on each element of the queue.
|
||||
FileLine* const fl = nodep->fileline();
|
||||
AstFork* const targetp = VN_AS(nodep->targetp(), Fork);
|
||||
if (m_ftaskp) {
|
||||
if (!m_ftaskp->exists(
|
||||
[targetp](const AstNodeBlock* blockp) -> bool { return blockp == targetp; })) {
|
||||
|
|
@ -311,35 +386,18 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
|
||||
AstVar* const processQueuep = getProcessQueuep(targetp, fl);
|
||||
AstVarRef* const queueWriteRefp
|
||||
= new AstVarRef{fl, topPkgp, processQueuep, VAccess::WRITE};
|
||||
AstStmtExpr* pushCurrentProcessp = getQueuePushProcessSelfp(queueWriteRefp);
|
||||
AstVar* const processQueuep = getOrCreateForkDisableQueuep(targetp, fl);
|
||||
AstStmtExpr* const killStmtp = insertKillStmtp(nodep, processQueuep);
|
||||
|
||||
for (AstBegin* const beginp : forks) {
|
||||
if (pushCurrentProcessp->backp()) {
|
||||
pushCurrentProcessp = pushCurrentProcessp->cloneTree(false);
|
||||
}
|
||||
prependStmtsp(beginp, pushCurrentProcessp);
|
||||
}
|
||||
AstStmtExpr* const killStmtp = getQueueKillStmtp(fl, processQueuep);
|
||||
nodep->addNextHere(killStmtp);
|
||||
|
||||
// 'process::kill' does not immediately kill the current process
|
||||
// executing the disable statement (because it's in the running state).
|
||||
// If the disable statement is indeed executed by a process under the
|
||||
// target AstFork, then jump to the end of that fork branch.
|
||||
if (VN_IS(targetp, Fork)) {
|
||||
AstNodeBlock* forkBranchp = nullptr;
|
||||
for (AstNodeBlock* const blockp : vlstd::reverse_view(m_blockStack)) {
|
||||
if (blockp == targetp) {
|
||||
AstJumpBlock* const jmpBlockp = getJumpBlock(VN_AS(forkBranchp, Begin), false);
|
||||
killStmtp->addNextHere(new AstJumpGo{fl, jmpBlockp});
|
||||
break;
|
||||
}
|
||||
forkBranchp = blockp;
|
||||
}
|
||||
// 'process::kill' does not immediately kill the current process executing the disable
|
||||
// statement (because it's in the running state). If the disable runs under the target
|
||||
// fork, jump to the end of the innermost enclosing fork branch (the branch that holds the
|
||||
// running process) so statements after the disable do not execute. Targeting the innermost
|
||||
// branch keeps the jump inside a single emitted coroutine even when the disable sits in a
|
||||
// nested sub-fork; targeting the outer target fork's branch would cross a coroutine
|
||||
// boundary once forks are split into separate functions.
|
||||
if (AstBegin* const branchp = innerForkBranchp(targetp)) {
|
||||
addJumpAfterKill(killStmtp, branchp);
|
||||
}
|
||||
}
|
||||
// VISITORS
|
||||
|
|
@ -522,8 +580,7 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
}
|
||||
if (AstTask* const taskp = VN_CAST(targetp, Task)) {
|
||||
AstVar* const processQueuep = getOrCreateTaskDisableQueuep(taskp, nodep->fileline());
|
||||
AstStmtExpr* const killStmtp = getQueueKillStmtp(nodep->fileline(), processQueuep);
|
||||
nodep->addNextHere(killStmtp);
|
||||
AstStmtExpr* const killStmtp = insertKillStmtp(nodep, processQueuep);
|
||||
|
||||
// process::kill does not terminate the currently running process immediately.
|
||||
// If we disable the current task by name from inside itself, jump to its end.
|
||||
|
|
@ -531,15 +588,10 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
AstNode* jumpTargetp = taskp;
|
||||
const auto it = m_taskDisableBegins.find(taskp);
|
||||
if (it != m_taskDisableBegins.end()) jumpTargetp = it->second;
|
||||
AstJumpBlock* const blockp = getJumpBlock(jumpTargetp, false);
|
||||
killStmtp->addNextHere(new AstJumpGo{nodep->fileline(), blockp});
|
||||
addJumpAfterKill(killStmtp, jumpTargetp);
|
||||
}
|
||||
} else if (AstFork* const forkp = VN_CAST(targetp, Fork)) {
|
||||
std::vector<AstBegin*> forks;
|
||||
for (AstBegin* itemp = forkp->forksp(); itemp; itemp = VN_AS(itemp->nextp(), Begin)) {
|
||||
forks.push_back(itemp);
|
||||
}
|
||||
handleDisableOnFork(nodep, forks);
|
||||
} else if (VN_IS(targetp, Fork)) {
|
||||
handleDisableOnFork(nodep);
|
||||
} else if (AstBegin* const beginp = VN_CAST(targetp, Begin)) {
|
||||
if (existsBlockAbove(beginp->name())) {
|
||||
if (!beginp->user3()) {
|
||||
|
|
@ -549,30 +601,20 @@ class LinkJumpVisitor final : public VNVisitor {
|
|||
} else {
|
||||
AstVar* const processQueuep
|
||||
= getOrCreateBeginDisableQueuep(beginp, nodep->fileline());
|
||||
AstStmtExpr* const killStmtp
|
||||
= getQueueKillStmtp(nodep->fileline(), processQueuep);
|
||||
nodep->addNextHere(killStmtp);
|
||||
AstStmtExpr* const killStmtp = insertKillStmtp(nodep, processQueuep);
|
||||
|
||||
// process::kill does not terminate the currently running process immediately.
|
||||
// If disable executes inside a fork branch of this named block, jump to the
|
||||
// end of that branch to prevent statements after disable from executing.
|
||||
AstBegin* currentBeginp = nullptr;
|
||||
for (AstNodeBlock* const blockp : vlstd::reverse_view(m_blockStack)) {
|
||||
if (VN_IS(blockp, Begin)) {
|
||||
currentBeginp = VN_AS(blockp, Begin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentBeginp && directlyUnderFork(currentBeginp)) {
|
||||
AstJumpBlock* const blockp = getJumpBlock(currentBeginp, false);
|
||||
killStmtp->addNextHere(new AstJumpGo{nodep->fileline(), blockp});
|
||||
}
|
||||
AstBegin* const branchp = innerForkBranchp(beginp);
|
||||
AstBegin* const jumpTargetp
|
||||
= branchp ? branchp : m_beginDisableBegins.at(beginp);
|
||||
addJumpAfterKill(killStmtp, jumpTargetp);
|
||||
}
|
||||
} else {
|
||||
AstVar* const processQueuep
|
||||
= getOrCreateBeginDisableQueuep(beginp, nodep->fileline());
|
||||
AstStmtExpr* const killStmtp = getQueueKillStmtp(nodep->fileline(), processQueuep);
|
||||
nodep->addNextHere(killStmtp);
|
||||
insertKillStmtp(nodep, processQueuep);
|
||||
}
|
||||
} else {
|
||||
nodep->v3fatalSrc("Disable linked with node of unhandled type "
|
||||
|
|
|
|||
|
|
@ -749,15 +749,21 @@ class TimingControlVisitor final : public VNVisitor {
|
|||
addDebugInfo(donep);
|
||||
beginp->addStmtsp(donep->makeStmt());
|
||||
}
|
||||
static bool hasDisableQueuePushSelfPrefix(const AstBegin* const beginp) {
|
||||
static bool hasDisableQueuePushSelfPrefix(AstBegin* const beginp) {
|
||||
// LinkJump prepends disable-by-name registration as:
|
||||
// __VprocessQueue_*.push_back(std::process::self())
|
||||
const AstStmtExpr* const stmtExprp = VN_CAST(beginp->stmtsp(), StmtExpr);
|
||||
if (!stmtExprp) return false;
|
||||
const 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();
|
||||
// V3LiftExpr lifts std::process::self() into an assignment to a temporary, then V3Task
|
||||
// lowers that assignment's function call into a leading comment and AstStmtExpr. Thus the
|
||||
// push_back is no longer necessarily the first statement. Scan across this generated
|
||||
// prefix for it, stopping at the fork-start sentinel or the branch body. Registrations for
|
||||
// nested forks live in sub-blocks and so are not in this statement list.
|
||||
for (AstNode* stmtp = beginp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
if (VN_IS(stmtp, Comment)) continue;
|
||||
AstStmtExpr* const stmtExprp = VN_CAST(stmtp, StmtExpr);
|
||||
if (!stmtExprp) break;
|
||||
if (stmtExprp->isDisableQueuePushSelfStmt()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Register a callback so killing a process-backed fork branch decrements the join counter
|
||||
void addForkOnKill(AstBegin* const beginp, AstVarScope* const forkVscp) const {
|
||||
|
|
|
|||
|
|
@ -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,41 @@
|
|||
// 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 named begin block that contains a fork still has to behave like a normal
|
||||
// named block when it disables itself: execution resumes after the block, so
|
||||
// statements after the disable inside the block must not run. All activity
|
||||
// enabled within the block, including forked child processes, must terminate.
|
||||
|
||||
// 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 t;
|
||||
|
||||
bit after_disable = 1'b0;
|
||||
bit fork_survived = 1'b0;
|
||||
|
||||
initial begin : blk
|
||||
fork
|
||||
begin
|
||||
#5;
|
||||
fork_survived = 1'b1;
|
||||
end
|
||||
join_none
|
||||
#1;
|
||||
disable blk;
|
||||
after_disable = 1'b1;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#10;
|
||||
`checkd(after_disable, 1'b0);
|
||||
`checkd(fork_survived, 1'b0);
|
||||
$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,205 @@
|
|||
// 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 from a deeply nested sub-fork branch must terminate
|
||||
// every process spawned within that fork, at all nesting levels, including
|
||||
// branches suspended on a delay. Previously this crashed Verilator: the bail-out
|
||||
// jump emitted for the disable targeted the outer fork branch and crossed a
|
||||
// coroutine boundary once the nested forks were split into separate functions.
|
||||
// Even disregarding the crash, processes of nested sub-forks were not registered
|
||||
// for the kill, so a nested sibling survived and its $stop fired.
|
||||
//
|
||||
// The disable must also let the process that owns the disabled fork's 'join'
|
||||
// resume after the join (IEEE 1800-2023 9.6.2): once the block is terminated,
|
||||
// execution continues at the point after it. The *_resumed bits below latch that
|
||||
// the parent ran past its join; a hang there leaves the bit clear and is caught
|
||||
// at the end. A fork branch that itself holds a nested fork is killed while
|
||||
// suspended at the inner join, so it must register a kill hook on its parent
|
||||
// fork or the parent join counter is never decremented and the parent hangs.
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkt(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0t exp=%0t (%t !== %t)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
|
||||
`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 t;
|
||||
|
||||
bit d2_resumed = 1'b0;
|
||||
bit d3_resumed = 1'b0;
|
||||
bit d2a_resumed = 1'b0;
|
||||
bit d2r_resumed = 1'b0;
|
||||
bit ja1_resumed = 1'b0;
|
||||
|
||||
// fork..join nested two levels: disabling the outer named fork from inside the
|
||||
// inner fork must kill the inner delayed sibling and the outer delayed sibling.
|
||||
initial begin
|
||||
fork : fork_d2
|
||||
begin
|
||||
fork
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_d2;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join
|
||||
`checkt($time, 0);
|
||||
d2_resumed = 1'b1;
|
||||
end
|
||||
|
||||
// fork..join nested three levels: disabling the outermost named fork from the
|
||||
// innermost branch must kill the delayed siblings at every level.
|
||||
initial begin
|
||||
fork : fork_d3
|
||||
begin
|
||||
fork
|
||||
begin
|
||||
fork
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_d3;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join
|
||||
`checkt($time, 0);
|
||||
d3_resumed = 1'b1;
|
||||
end
|
||||
|
||||
// fork..join_any nested two levels.
|
||||
initial begin
|
||||
fork : fork_d2a
|
||||
begin
|
||||
fork
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_d2a;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join_any
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join_any
|
||||
`checkt($time, 0);
|
||||
d2a_resumed = 1'b1;
|
||||
end
|
||||
|
||||
// fork..join_none nested two levels.
|
||||
initial begin
|
||||
fork : fork_d2n
|
||||
begin
|
||||
fork
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_d2n;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join_none
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join_none
|
||||
end
|
||||
|
||||
// fork..join nested two levels, but the disabling branch comes after the
|
||||
// delayed sibling (in source order) at both levels.
|
||||
initial begin
|
||||
fork : fork_d2r
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
begin
|
||||
fork
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_d2r;
|
||||
$stop;
|
||||
end
|
||||
join
|
||||
end
|
||||
join
|
||||
`checkt($time, 0);
|
||||
d2r_resumed = 1'b1;
|
||||
end
|
||||
|
||||
// fork..join_any whose only branch holds the nested fork that gets disabled.
|
||||
// join_any needs one branch to complete, but the only branch is killed while
|
||||
// suspended at its inner join, so the parent resumes only if that branch
|
||||
// decrements the join counter on kill.
|
||||
initial begin
|
||||
fork : fork_ja1
|
||||
begin
|
||||
fork
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_ja1;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join
|
||||
end
|
||||
join_any
|
||||
`checkt($time, 0);
|
||||
ja1_resumed = 1'b1;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#10;
|
||||
`checkd(d2_resumed, 1'b1);
|
||||
`checkd(d3_resumed, 1'b1);
|
||||
`checkd(d2a_resumed, 1'b1);
|
||||
`checkd(d2r_resumed, 1'b1);
|
||||
`checkd(ja1_resumed, 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,60 @@
|
|||
// 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
|
||||
module t;
|
||||
|
||||
bit survived0;
|
||||
bit done0;
|
||||
bit survived1;
|
||||
bit done1;
|
||||
|
||||
child child0 (
|
||||
.survived(survived0),
|
||||
.done(done0)
|
||||
);
|
||||
|
||||
child child1 (
|
||||
.survived(survived1),
|
||||
.done(done1)
|
||||
);
|
||||
|
||||
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
|
||||
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,81 @@
|
|||
// 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
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
#7;
|
||||
`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,171 @@
|
|||
// 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..join / fork..join_any block from one of its own
|
||||
// branches must terminate the sibling branches, including any that are
|
||||
// suspended on a delay. This is the join/join_any analogue of the
|
||||
// fork..join_none cases in t_disable_inside (#6591): the bug was that a sibling
|
||||
// spawned later in source order registered its process for kill only after the
|
||||
// disabling branch had already issued the kill, so the sibling survived and its
|
||||
// $stop fired one time unit later.
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkt(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0t exp=%0t (%t !== %t)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
|
||||
// verilog_format: on
|
||||
|
||||
module t;
|
||||
|
||||
// fork..join: the disabling branch comes first, the delayed sibling second.
|
||||
initial begin
|
||||
fork : fork_blk2
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_blk2;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join
|
||||
end
|
||||
|
||||
// fork..join: the delayed sibling comes first, the disabling branch second.
|
||||
initial begin
|
||||
fork : fork_blk3
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_blk3;
|
||||
$stop;
|
||||
end
|
||||
join
|
||||
end
|
||||
|
||||
// fork..join: the delayed sibling also contains an unreachable disable, which
|
||||
// makes LinkJump wrap its registration in a JumpBlock. It must still register
|
||||
// before the synthetic fork-start delay so the earlier disable kills it.
|
||||
initial begin
|
||||
fork : fork_blk_wrap
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_blk_wrap;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1;
|
||||
if ($c("false")) disable fork_blk_wrap;
|
||||
$stop;
|
||||
end
|
||||
join
|
||||
end
|
||||
|
||||
// fork..join_any: the disabling branch comes first, the delayed sibling second.
|
||||
initial begin
|
||||
fork : fork_blk2a
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_blk2a;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join_any
|
||||
end
|
||||
|
||||
// fork..join_any: the delayed sibling comes first, the disabling branch second.
|
||||
initial begin
|
||||
fork : fork_blk3a
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_blk3a;
|
||||
$stop;
|
||||
end
|
||||
join_any
|
||||
end
|
||||
|
||||
// fork..join_any: the delayed sibling's own unreachable disable again forces a
|
||||
// JumpBlock wrapper around its registration.
|
||||
initial begin
|
||||
fork : fork_blk_wrap_any
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_blk_wrap_any;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1;
|
||||
if ($c("false")) disable fork_blk_wrap_any;
|
||||
$stop;
|
||||
end
|
||||
join_any
|
||||
end
|
||||
|
||||
// fork..join that can be disabled, but takes the path where disable is not
|
||||
// reached: both branches must run to completion with their timing intact.
|
||||
initial begin
|
||||
fork : fork_blk4
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
if ($c("false")) begin
|
||||
disable fork_blk4;
|
||||
$stop;
|
||||
end
|
||||
#1;
|
||||
`checkt($time, 1);
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1;
|
||||
`checkt($time, 1);
|
||||
end
|
||||
join
|
||||
end
|
||||
|
||||
// Deeply nested fork..join: the named fork is several levels deep inside other
|
||||
// forks and one of its own branches disables it. The delayed sibling at that
|
||||
// deep level must still be killed, so the fix has to work when the disable-able
|
||||
// fork is itself nested inside other (suspending) forks.
|
||||
initial begin
|
||||
fork
|
||||
begin
|
||||
fork
|
||||
begin
|
||||
fork : fork_deep
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
disable fork_deep;
|
||||
$stop;
|
||||
end
|
||||
begin
|
||||
`checkt($time, 0);
|
||||
#1 $stop;
|
||||
end
|
||||
join
|
||||
end
|
||||
join
|
||||
end
|
||||
join
|
||||
end
|
||||
|
||||
initial begin
|
||||
#10;
|
||||
$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