diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index e41d614d2b6..7b61629899b 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1191,6 +1191,7 @@ class AstNetlist final : public AstNode { AstCFunc* m_evalNbap = nullptr; // The '_eval__nba' function AstVarScope* m_dpiExportTriggerp = nullptr; // The DPI export trigger variable AstVar* m_delaySchedulerp = nullptr; // The delay scheduler variable + AstVarScope* m_nbaEventp = nullptr; // The NBA event variable AstTopScope* m_topScopep = nullptr; // The singleton AstTopScope under the top module VTimescale m_timeunit; // Global time unit VTimescale m_timeprecision; // Global time precision @@ -1220,6 +1221,8 @@ class AstNetlist final : public AstNode { void dpiExportTriggerp(AstVarScope* varScopep) { m_dpiExportTriggerp = varScopep; } AstVar* delaySchedulerp() const { return m_delaySchedulerp; } void delaySchedulerp(AstVar* const varScopep) { m_delaySchedulerp = varScopep; } + AstVarScope* nbaEventp() const { return m_nbaEventp; } + void nbaEventp(AstVarScope* const varScopep) { m_nbaEventp = varScopep; } void stdPackagep(AstPackage* const packagep) { m_stdPackagep = packagep; } AstPackage* stdPackagep() const { return m_stdPackagep; } AstTopScope* topScopep() const { return m_topScopep; } diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index eba736d4864..12f74ac8d8b 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -527,8 +527,12 @@ class DelayedVisitor final : public VNVisitor { m_nextDlyp = VN_CAST(nodep->nextp(), AssignDly); // Next assignment in same block, maybe nullptr. if (m_cfuncp) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: Delayed assignment inside public function/task"); + if (!v3Global.rootp()->nbaEventp()) { + nodep->v3warn( + E_NOTIMING, + "Delayed assignment in a non-inlined function/task requires --timing"); + } + return; } UASSERT_OBJ(m_procp, nodep, "Delayed assignment not under process"); const bool isArray = VN_IS(nodep->lhsp(), ArraySel) diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 22ed00a1b5f..6c5cf8540ad 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -942,29 +942,43 @@ void createEval(AstNetlist* netlistp, // .stmtsp; // Create the NBA eval loop. This uses the Active eval loop in the trigger section. - AstNodeStmt* topEvalLoopp - = makeEvalLoop( - netlistp, "nba", "NBA", nbaKit.m_vscp, nbaKit.m_dumpp, - [&]() { // Trigger - // Reset NBA triggers - AstNodeStmt* resultp = createTriggerClearCall(flp, nbaKit.m_vscp); - // Run the Active eval loop - resultp = AstNode::addNext(resultp, activeEvalLoopp); - return resultp; - }, - [&]() { // Body - AstCCall* const callp = new AstCCall{flp, nbaKit.m_funcp}; - callp->dtypeSetVoid(); - AstNodeStmt* resultp = callp->makeStmt(); - // Latch the NBA trigger flags under the following region's trigger flags - AstVarScope* const nextVscp = obsKit.m_vscp ? obsKit.m_vscp : reactKit.m_vscp; - if (nextVscp) { - resultp = AstNode::addNext( - resultp, createTriggerSetCall(flp, nextVscp, nbaKit.m_vscp)); - } - return resultp; - }) - .second; + const auto& nbaEvalLoop = makeEvalLoop( + netlistp, "nba", "NBA", nbaKit.m_vscp, nbaKit.m_dumpp, + [&]() { // Trigger + // Reset NBA triggers + AstNodeStmt* resultp = createTriggerClearCall(flp, nbaKit.m_vscp); + // Run the Active eval loop + resultp = AstNode::addNext(resultp, activeEvalLoopp); + return resultp; + }, + [&]() { // Body + AstCCall* const callp = new AstCCall{flp, nbaKit.m_funcp}; + callp->dtypeSetVoid(); + AstNodeStmt* resultp = callp->makeStmt(); + // Latch the NBA trigger flags under the following region's trigger flags + AstVarScope* const nextVscp = obsKit.m_vscp ? obsKit.m_vscp : reactKit.m_vscp; + if (nextVscp) { + resultp = AstNode::addNext(resultp, + createTriggerSetCall(flp, nextVscp, nbaKit.m_vscp)); + } + return resultp; + }); + + // If the NBA event exists, trigger it in 'nba' + if (netlistp->nbaEventp()) { + AstCMethodHard* const triggeredp = new AstCMethodHard{ + flp, new AstVarRef{flp, netlistp->nbaEventp(), VAccess::READ}, "isTriggered"}; + triggeredp->dtypeSetBit(); + AstIf* const ifp = new AstIf{flp, new AstLogNot{flp, triggeredp}}; + ifp->addThensp(setVar(nbaEvalLoop.continuep, 1)); + AstCMethodHard* const firep = new AstCMethodHard{ + flp, new AstVarRef{flp, netlistp->nbaEventp(), VAccess::WRITE}, "fire"}; + firep->dtypeSetVoid(); + ifp->addThensp(firep->makeStmt()); + nbaEvalLoop.stmtsp->addNext(ifp); + } + + AstNodeStmt* topEvalLoopp = nbaEvalLoop.stmtsp; if (obsKit.m_funcp) { // Create the Observed eval loop. This uses the NBA eval loop in the trigger section. diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 9d0fc5e488a..35ddd98a01c 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -409,6 +409,7 @@ class TimingControlVisitor final : public VNVisitor { AstBasicDType* m_trigSchedDtp = nullptr; // Trigger scheduler type // Timing-related globals + AstVarScope* m_nbaEventp = nullptr; // Event triggered in NBA region AstVarScope* m_delaySchedp = nullptr; // Global delay scheduler AstVarScope* m_dynamicSchedp = nullptr; // Global dynamic trigger scheduler AstSenTree* m_delaySensesp = nullptr; // Domain to trigger if a delayed coroutine is resumed @@ -518,6 +519,22 @@ class TimingControlVisitor final : public VNVisitor { m_netlistp->topScopep()->addSenTreesp(m_dynamicSensesp); return m_dynamicSensesp; } + // Creates the event variable to trigger in NBA region + AstEventControl* createNbaEventControl(FileLine* flp) { + if (!m_nbaEventp) { + auto* const nbaEventDtp = new AstBasicDType{m_scopeTopp->fileline(), + VBasicDTypeKwd::EVENT, VSigning::UNSIGNED}; + m_netlistp->typeTablep()->addTypesp(nbaEventDtp); + m_nbaEventp = m_scopeTopp->createTemp("__VnbaEvent", nbaEventDtp); + m_netlistp->nbaEventp(m_nbaEventp); + v3Global.setHasEvents(); + } + return new AstEventControl{ + flp, + new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_EVENT, + new AstVarRef{flp, m_nbaEventp, VAccess::READ}}}, + nullptr}; + } // Returns true if we are under a class or the given tree has any references to locals. These // are cases where static, globally-evaluated triggers are not suitable. bool needDynamicTrigger(AstNode* const nodep) const { @@ -922,17 +939,29 @@ class TimingControlVisitor final : public VNVisitor { void visit(AstNodeAssign* nodep) override { // Only process once to avoid infinite loops (due to the net delay) if (nodep->user1SetOnce()) return; - AstNode* const controlp = factorOutTimingControl(nodep); - if (!controlp) return; - // Handle the intra assignment timing control FileLine* const flp = nodep->fileline(); - if (VN_IS(nodep, AssignDly)) { + AstNode* controlp = factorOutTimingControl(nodep); + const bool inAssignDly = VN_IS(nodep, AssignDly); + const bool nonInlined = !VN_IS(m_procp, NodeProcedure); + // Transform if: + // * there's a timing control in the assignment + // * the assignment is an AssignDly and it's in a non-inlined function + if (!controlp && !(inAssignDly && nonInlined)) return; + // Handle the intra assignment timing control + if (inAssignDly) { // If it's an NBA with an intra assignment delay, put it in a fork auto* const forkp = new AstFork{flp, "", nullptr}; forkp->joinType(VJoinType::JOIN_NONE); + if (nonInlined) { + auto nbaEventControlp = createNbaEventControl(flp); + nodep->replaceWith(nbaEventControlp); + nbaEventControlp->addStmtsp(nodep); + if (!controlp) controlp = nbaEventControlp; + } controlp->replaceWith(forkp); forkp->addStmtsp(controlp); } + UASSERT_OBJ(nodep, controlp, "Assignment should have timing control"); // Insert new vars before the timing control if we're in a function; in a process we can't // do that. These intra-assignment vars will later be passed to forked processes by value. AstNode* const insertBeforep = m_classp ? controlp : nullptr; diff --git a/test_regress/t/t_uvm_pkg_todo.vh b/test_regress/t/t_uvm_pkg_todo.vh index f3f21fa0df0..ebff264fd24 100644 --- a/test_regress/t/t_uvm_pkg_todo.vh +++ b/test_regress/t/t_uvm_pkg_todo.vh @@ -866,9 +866,7 @@ task uvm_wait_for_nba_region; int nba; int next_nba; next_nba++; -//TODO issue #4496 - Delayed assignment inside public function/task -//TODO %Error-UNSUPPORTED: t/t_uvm_pkg_todo.vh:875:7: Unsupported: Delayed assignment inside public function/task -//TODO nba <= next_nba; + nba <= next_nba; @(nba); endtask function automatic void uvm_split_string (string str, byte sep, ref string values[$]);