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..92443edcf42 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -527,8 +527,10 @@ 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()) return; + nodep->v3warn( + E_UNSUPPORTED, + "Unsupported: Delayed assignment in a non-inlined function/task without --timing"); } 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 b3849955dd8..121594b883e 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -591,18 +591,27 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp, return {vscp, funcp, dumpp, map}; } +//============================================================================ +// EvalLoop contains elements of an evaluation loop created by makeEvalLoop() + +struct EvalLoop { + // Loop iteration counter for enforcing the converge limit + AstVarScope* counterp = nullptr; + // The loop condition + AstVarScope* continuep = nullptr; + // The loop itself and statements around it + AstNodeStmt* stmtsp = nullptr; +}; + //============================================================================ // Helpers to construct an evaluation loop. -AstNodeStmt* buildLoop(AstNetlist* netlistp, const string& name, - const std::function& build) // +AstNodeStmt* buildLoop(AstNetlist* netlistp, AstVarScope* const condp, + const std::function& build) // { AstTopScope* const topScopep = netlistp->topScopep(); AstScope* const scopeTopp = topScopep->scopep(); FileLine* const flp = scopeTopp->fileline(); - // Create the loop condition variable - AstVarScope* const condp = scopeTopp->createTemp("__V" + name + "Continue", 1); - condp->varp()->noReset(true); // Initialize the loop condition variable to true AstNodeStmt* const resp = setVar(condp, 1); // Add the loop @@ -611,16 +620,15 @@ AstNodeStmt* buildLoop(AstNetlist* netlistp, const string& name, // Clear the loop condition variable in the loop loopp->addStmtsp(setVar(condp, 0)); // Build the body - build(condp, loopp); + build(loopp); // Done return resp; }; -std::pair makeEvalLoop(AstNetlist* netlistp, const string& tag, - const string& name, AstVarScope* trigVscp, - AstCFunc* trigDumpp, - std::function computeTriggers, - std::function makeBody) { +EvalLoop makeEvalLoop(AstNetlist* netlistp, const string& tag, const string& name, + AstVarScope* trigVscp, AstCFunc* trigDumpp, + std::function computeTriggers, + std::function makeBody) { UASSERT_OBJ(trigVscp->dtypep()->basicp()->isTriggerVec(), trigVscp, "Not TRIGGERVEC"); AstTopScope* const topScopep = netlistp->topScopep(); AstScope* const scopeTopp = topScopep->scopep(); @@ -629,8 +637,11 @@ std::pair makeEvalLoop(AstNetlist* netlistp, const s AstVarScope* const counterp = scopeTopp->createTemp("__V" + tag + "IterCount", 32); counterp->varp()->noReset(true); + AstVarScope* const continuep = scopeTopp->createTemp("__V" + tag + "Continue", 1); + continuep->varp()->noReset(true); + AstNodeStmt* nodep = setVar(counterp, 0); - nodep->addNext(buildLoop(netlistp, tag, [&](AstVarScope* continuep, AstWhile* loopp) { + nodep->addNext(buildLoop(netlistp, continuep, [&](AstWhile* loopp) { // Compute triggers loopp->addStmtsp(computeTriggers()); // Invoke body if triggered @@ -682,7 +693,7 @@ std::pair makeEvalLoop(AstNetlist* netlistp, const s } })); - return {counterp, nodep}; + return {counterp, continuep, nodep}; } //============================================================================ @@ -727,7 +738,7 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde splitCheck(stlFuncp); // Create the eval loop - const auto& pair = makeEvalLoop( + const auto& loop = makeEvalLoop( netlistp, "stl", "Settle", trig.m_vscp, trig.m_dumpp, [&]() { // Trigger AstCCall* const callp = new AstCCall{stlFuncp->fileline(), trig.m_funcp}; @@ -741,10 +752,10 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde }); // Add the first iteration trigger to the trigger computation function - trig.addFirstIterationTriggerAssignment(pair.first, firstIterationTrigger); + trig.addFirstIterationTriggerAssignment(loop.counterp, firstIterationTrigger); // Add the eval loop to the top function - funcp->addStmtsp(pair.second); + funcp->addStmtsp(loop.stmtsp); } //============================================================================ @@ -816,7 +827,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, splitCheck(icoFuncp); // Create the eval loop - const auto& pair = makeEvalLoop( + const auto& loop = makeEvalLoop( netlistp, "ico", "Input combinational", trig.m_vscp, trig.m_dumpp, [&]() { // Trigger AstCCall* const callp = new AstCCall{icoFuncp->fileline(), trig.m_funcp}; @@ -830,10 +841,10 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, }); // Add the first iteration trigger to the trigger computation function - trig.addFirstIterationTriggerAssignment(pair.first, firstIterationTrigger); + trig.addFirstIterationTriggerAssignment(loop.counterp, firstIterationTrigger); // Return the eval loop itself - return pair.second; + return loop.stmtsp; } //============================================================================ @@ -927,11 +938,10 @@ void createEval(AstNetlist* netlistp, // } return resultp; - }) - .second; + }).stmtsp; // Create the NBA eval loop. This uses the Active eval loop in the trigger section. - AstNodeStmt* topEvalLoopp + const auto& nbaEvalLoop = makeEvalLoop( netlistp, "nba", "NBA", nbaKit.m_vscp, nbaKit.m_dumpp, [&]() { // Trigger @@ -952,8 +962,23 @@ void createEval(AstNetlist* netlistp, // resultp, createTriggerSetCall(flp, nextVscp, nbaKit.m_vscp)); } return resultp; - }) - .second; + }); + + // 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. @@ -978,7 +1003,7 @@ void createEval(AstNetlist* netlistp, // } return resultp; }) - .second; + .stmtsp; } if (reactKit.m_funcp) { @@ -997,7 +1022,7 @@ void createEval(AstNetlist* netlistp, // callp->dtypeSetVoid(); return callp->makeStmt(); }) - .second; + .stmtsp; } funcp->addStmtsp(topEvalLoopp); diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 9d0fc5e488a..7863953ef5c 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,31 @@ 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_dynamic_nba.pl b/test_regress/t/t_dynamic_nba.pl new file mode 100755 index 00000000000..bc632ee012e --- /dev/null +++ b/test_regress/t/t_dynamic_nba.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2019 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_dynamic_nba.v b/test_regress/t/t_dynamic_nba.v new file mode 100644 index 00000000000..8e8776bd5e4 --- /dev/null +++ b/test_regress/t/t_dynamic_nba.v @@ -0,0 +1,26 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2023 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +class nba_waiter; + task wait_for_nba_region; + int nba; + int next_nba; + next_nba++; + nba <= next_nba; + @(nba); + endtask +endclass + +module t; + nba_waiter waiter = new; + initial begin + waiter.wait_for_nba_region; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule + +