diff --git a/include/verilated_std.sv b/include/verilated_std.sv index b86340695..941c79795 100644 --- a/include/verilated_std.sv +++ b/include/verilated_std.sv @@ -181,8 +181,9 @@ package std; static task killQueue(ref process processQueue[$]); `ifdef VERILATOR_TIMING - while (processQueue.size() > 0) begin - processQueue.pop_back().kill(); + int initialSize = processQueue.size(); + for (int i = 0; i < initialSize; ++i) begin + processQueue.pop_front().kill(); end `endif endtask diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 9630371f3..1c246805d 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -67,8 +67,11 @@ class LinkJumpVisitor final : public VNVisitor { V3UniqueNames m_queueNames{ "__VprocessQueue"}; // Names for queues needed for 'disable' handling std::unordered_map m_taskDisableQueues; // Per-task process queues + std::unordered_map m_beginDisableQueues; // Per-begin process queues std::unordered_map m_taskDisableBegins; // Per-task process wrappers + std::unordered_map + m_beginDisableBegins; // Per-begin process wrappers // METHODS // Get (and create if necessary) the JumpBlock for this statement @@ -181,6 +184,12 @@ class LinkJumpVisitor final : public VNVisitor { return new AstStmtExpr{ fl, new AstMethodCall{fl, queueRefp, "push_back", new AstArg{fl, "", processSelfp}}}; } + static AstStmtExpr* getQueuePushProcessSelfp(FileLine* const fl, AstVar* const processQueuep) { + AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); + AstVarRef* const queueWriteRefp + = new AstVarRef{fl, topPkgp, processQueuep, VAccess::WRITE}; + return getQueuePushProcessSelfp(queueWriteRefp); + } static AstStmtExpr* getQueueKillStmtp(FileLine* const fl, AstVar* const processQueuep) { AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); AstClass* const processClassp @@ -192,6 +201,24 @@ class LinkJumpVisitor final : public VNVisitor { killQueueCall->classOrPackagep(processClassp); return new AstStmtExpr{fl, killQueueCall}; } + static void prependStmtsp(AstNodeFTask* 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; @@ -220,18 +247,57 @@ class LinkJumpVisitor final : public VNVisitor { processQueuep->lifetime(VLifetime::STATIC_EXPLICIT); topPkgp->addStmtsp(processQueuep); - AstVarRef* const queueWriteRefp - = new AstVarRef{fl, topPkgp, processQueuep, VAccess::WRITE}; - AstStmtExpr* const pushCurrentProcessp = getQueuePushProcessSelfp(queueWriteRefp); + AstStmtExpr* const pushCurrentProcessp = getQueuePushProcessSelfp(fl, processQueuep); AstBegin* const taskBodyp = getOrCreateTaskDisableBeginp(taskp, fl); - if (taskBodyp->stmtsp()) { - taskBodyp->stmtsp()->addHereThisAsNext(pushCurrentProcessp); - } else { - taskBodyp->addStmtsp(pushCurrentProcessp); - } + prependStmtsp(taskBodyp, pushCurrentProcessp); m_taskDisableQueues.emplace(taskp, processQueuep); return processQueuep; } + AstBegin* getOrCreateBeginDisableBeginp(AstBegin* const beginp, FileLine* const fl) { + const auto it = m_beginDisableBegins.find(beginp); + if (it != m_beginDisableBegins.end()) return it->second; + + AstBegin* const beginBodyp = new AstBegin{fl, "", nullptr, false}; + if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext()); + + AstFork* const forkp = new AstFork{fl, VJoinType::JOIN}; + forkp->addForksp(beginBodyp); + beginp->addStmtsp(forkp); + + m_beginDisableBegins.emplace(beginp, beginBodyp); + return beginBodyp; + } + AstVar* getOrCreateBeginDisableQueuep(AstBegin* const beginp, FileLine* const fl) { + const auto it = m_beginDisableQueues.find(beginp); + if (it != m_beginDisableQueues.end()) return it->second; + + AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp(); + AstClass* const processClassp + = VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class); + AstVar* const processQueuep = new AstVar{ + fl, VVarType::VAR, m_queueNames.get(beginp->name()), VFlagChildDType{}, + new AstQueueDType{fl, VFlagChildDType{}, + new AstClassRefDType{fl, processClassp, nullptr}, nullptr}}; + processQueuep->lifetime(VLifetime::STATIC_EXPLICIT); + topPkgp->addStmtsp(processQueuep); + + AstStmtExpr* const pushCurrentProcessp = getQueuePushProcessSelfp(fl, processQueuep); + AstBegin* const beginBodyp = getOrCreateBeginDisableBeginp(beginp, fl); + prependStmtsp(beginBodyp, pushCurrentProcessp); + + // 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); + } + }); + m_beginDisableQueues.emplace(beginp, processQueuep); + return processQueuep; + } void handleDisableOnFork(AstDisable* const nodep, const std::vector& 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 @@ -265,10 +331,7 @@ class LinkJumpVisitor final : public VNVisitor { if (pushCurrentProcessp->backp()) { pushCurrentProcessp = pushCurrentProcessp->cloneTree(false); } - if (beginp->stmtsp()) { - // There is no need to add it to empty block - beginp->stmtsp()->addHereThisAsNext(pushCurrentProcessp); - } + prependStmtsp(beginp, pushCurrentProcessp); } AstStmtExpr* const killStmtp = getQueueKillStmtp(fl, processQueuep); nodep->addNextHere(killStmtp); @@ -289,12 +352,6 @@ class LinkJumpVisitor final : public VNVisitor { } } } - static bool directlyUnderFork(const AstNode* const nodep) { - if (nodep->backp()->nextp() == nodep) return directlyUnderFork(nodep->backp()); - if (VN_IS(nodep->backp(), Fork)) return true; - return false; - } - // VISITORS void visit(AstNodeModule* nodep) override { if (nodep->dead()) return; @@ -495,22 +552,37 @@ class LinkJumpVisitor final : public VNVisitor { handleDisableOnFork(nodep, forks); } else if (AstBegin* const beginp = VN_CAST(targetp, Begin)) { if (existsBlockAbove(beginp->name())) { - if (beginp->user3()) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: disabling block that contains a fork"); - } else { + if (!beginp->user3()) { // Jump to the end of the named block AstJumpBlock* const blockp = getJumpBlock(beginp, false); nodep->addNextHere(new AstJumpGo{nodep->fileline(), blockp}); + } else { + AstVar* const processQueuep + = getOrCreateBeginDisableQueuep(beginp, nodep->fileline()); + AstStmtExpr* const killStmtp + = getQueueKillStmtp(nodep->fileline(), processQueuep); + nodep->addNextHere(killStmtp); + + // 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}); + } } } else { - if (directlyUnderFork(beginp)) { - std::vector forks{beginp}; - handleDisableOnFork(nodep, forks); - } else { - nodep->v3warn(E_UNSUPPORTED, "disable isn't underneath a begin with name: '" - << beginp->name() << "'"); - } + AstVar* const processQueuep + = getOrCreateBeginDisableQueuep(beginp, nodep->fileline()); + AstStmtExpr* const killStmtp = getQueueKillStmtp(nodep->fileline(), processQueuep); + nodep->addNextHere(killStmtp); } } else { nodep->v3fatalSrc("Disable linked with node of unhandled type " diff --git a/test_regress/t/t_disable_task_by_name.v b/test_regress/t/t_disable_task_by_name.v index e9df8c391..2cefedeae 100644 --- a/test_regress/t/t_disable_task_by_name.v +++ b/test_regress/t/t_disable_task_by_name.v @@ -17,6 +17,10 @@ int self_after_disable = 0; int par_started = 0; int par_finished = 0; int par_parent_continued = 0; +int named_block_fork_fail = 0; +int task_named_block_fork_fail = 0; +event named_block_fork_ev; +event task_named_block_fork_ev; class StaticCls; static int ticks = 0; @@ -75,6 +79,23 @@ task parent_disables_worker; par_parent_continued = 1; endtask +task task_named_block_fork_disable; + begin : t_named_block + fork + begin + #5; + task_named_block_fork_fail = 1; + end + begin + @task_named_block_fork_ev; + disable t_named_block; + task_named_block_fork_fail = 2; + end + join + task_named_block_fork_fail = 3; + end +endtask + class Cls; int m_time = 0; @@ -249,6 +270,37 @@ module t; #10; if (y != 1) $stop; + // Disable named block containing fork from inside a fork branch + fork + begin : named_block_under_test + fork + begin + #5; + named_block_fork_fail = 1; + end + begin + @named_block_fork_ev; + disable named_block_under_test; + named_block_fork_fail = 2; + end + join + named_block_fork_fail = 3; + end + join_none + #2; + ->named_block_fork_ev; + #10; + if (named_block_fork_fail != 0) $stop; + + // Same case as above, but with the named block inside a task + fork + task_named_block_fork_disable(); + join_none + #2; + ->task_named_block_fork_ev; + #10; + if (task_named_block_fork_fail != 0) $stop; + // Disabling a task after it already finished is a no-op finish_z(); if (z != 1) $stop;