Support NBAs in non-inlined functions/tasks (#4496) (#4572)

This commit is contained in:
Krzysztof Bieganski 2023-10-21 02:01:45 +02:00 committed by GitHub
parent c7a0613c57
commit 7b12f6a1dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 306 additions and 95 deletions

View File

@ -1195,6 +1195,8 @@ 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
AstVarScope* m_nbaEventTriggerp = nullptr; // If set to 1, the NBA event should get triggered
AstTopScope* m_topScopep = nullptr; // The singleton AstTopScope under the top module
VTimescale m_timeunit; // Global time unit
VTimescale m_timeprecision; // Global time precision
@ -1224,6 +1226,10 @@ public:
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; }
AstVarScope* nbaEventTriggerp() const { return m_nbaEventTriggerp; }
void nbaEventTriggerp(AstVarScope* const varScopep) { m_nbaEventTriggerp = varScopep; }
void stdPackagep(AstPackage* const packagep) { m_stdPackagep = packagep; }
AstPackage* stdPackagep() const { return m_stdPackagep; }
AstTopScope* topScopep() const { return m_topScopep; }

View File

@ -1936,6 +1936,8 @@ const char* AstNetlist::broken() const {
BROKEN_RTN(m_dpiExportTriggerp && !m_dpiExportTriggerp->brokeExists());
BROKEN_RTN(m_topScopep && !m_topScopep->brokeExists());
BROKEN_RTN(m_delaySchedulerp && !m_delaySchedulerp->brokeExists());
BROKEN_RTN(m_nbaEventp && !m_nbaEventp->brokeExists());
BROKEN_RTN(m_nbaEventTriggerp && !m_nbaEventTriggerp->brokeExists());
return nullptr;
}
AstPackage* AstNetlist::dollarUnitPkgAddp() {

View File

@ -520,8 +520,12 @@ private:
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)

View File

@ -587,18 +587,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<void(AstVarScope*, AstWhile*)>& build) //
AstNodeStmt* buildLoop(AstNetlist* netlistp, AstVarScope* const condp,
const std::function<void(AstWhile*)>& 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
@ -607,16 +616,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<AstVarScope*, AstNodeStmt*> makeEvalLoop(AstNetlist* netlistp, const string& tag,
const string& name, AstVarScope* trigVscp,
AstCFunc* trigDumpp,
std::function<AstNodeStmt*()> computeTriggers,
std::function<AstNodeStmt*()> makeBody) {
EvalLoop makeEvalLoop(AstNetlist* netlistp, const string& tag, const string& name,
AstVarScope* trigVscp, AstCFunc* trigDumpp,
std::function<AstNodeStmt*()> computeTriggers,
std::function<AstNodeStmt*()> makeBody) {
UASSERT_OBJ(trigVscp->dtypep()->basicp()->isTriggerVec(), trigVscp, "Not TRIGGERVEC");
AstTopScope* const topScopep = netlistp->topScopep();
AstScope* const scopeTopp = topScopep->scopep();
@ -625,8 +633,11 @@ std::pair<AstVarScope*, AstNodeStmt*> 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
@ -678,7 +689,7 @@ std::pair<AstVarScope*, AstNodeStmt*> makeEvalLoop(AstNetlist* netlistp, const s
}
}));
return {counterp, nodep};
return {counterp, continuep, nodep};
}
//============================================================================
@ -723,7 +734,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};
@ -737,10 +748,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);
}
//============================================================================
@ -812,7 +823,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};
@ -826,10 +837,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;
}
//============================================================================
@ -884,72 +895,86 @@ void createEval(AstNetlist* netlistp, //
if (icoLoop) funcp->addStmtsp(icoLoop);
// Create the active eval loop
AstNodeStmt* const activeEvalLoopp
= makeEvalLoop(
netlistp, "act", "Active", actKit.m_vscp, actKit.m_dumpp,
[&]() { // Trigger
AstNodeStmt* resultp = nullptr;
const auto& activeEvalLoop = makeEvalLoop(
netlistp, "act", "Active", actKit.m_vscp, actKit.m_dumpp,
[&]() { // Trigger
AstNodeStmt* resultp = nullptr;
// Compute the current triggers
{
AstCCall* const trigsp = new AstCCall{flp, actKit.m_triggerComputep};
trigsp->dtypeSetVoid();
resultp = AstNode::addNext(resultp, trigsp->makeStmt());
}
// Compute the current triggers
{
AstCCall* const trigsp = new AstCCall{flp, actKit.m_triggerComputep};
trigsp->dtypeSetVoid();
resultp = AstNode::addNext(resultp, trigsp->makeStmt());
}
// Commit trigger awaits from the previous iteration
if (AstCCall* const commitp = timingKit.createCommit(netlistp)) {
resultp = AstNode::addNext(resultp, commitp->makeStmt());
}
// Commit trigger awaits from the previous iteration
if (AstCCall* const commitp = timingKit.createCommit(netlistp)) {
resultp = AstNode::addNext(resultp, commitp->makeStmt());
}
return resultp;
},
[&]() { // Body
// Compute the pre triggers
AstNodeStmt* resultp
= createTriggerAndNotCall(flp, preTrigsp, actKit.m_vscp, nbaKit.m_vscp);
// Latch the active trigger flags under the NBA trigger flags
resultp = AstNode::addNext(
resultp, createTriggerSetCall(flp, nbaKit.m_vscp, actKit.m_vscp));
// Resume triggered timing schedulers
if (AstCCall* const resumep = timingKit.createResume(netlistp)) {
resultp = AstNode::addNext(resultp, resumep->makeStmt());
}
// Invoke body function
{
AstCCall* const callp = new AstCCall{flp, actKit.m_funcp};
callp->dtypeSetVoid();
resultp = AstNode::addNext(resultp, callp->makeStmt());
}
return resultp;
},
[&]() { // Body
// Compute the pre triggers
AstNodeStmt* resultp
= createTriggerAndNotCall(flp, preTrigsp, actKit.m_vscp, nbaKit.m_vscp);
// Latch the active trigger flags under the NBA trigger flags
resultp = AstNode::addNext(resultp,
createTriggerSetCall(flp, nbaKit.m_vscp, actKit.m_vscp));
// Resume triggered timing schedulers
if (AstCCall* const resumep = timingKit.createResume(netlistp)) {
resultp = AstNode::addNext(resultp, resumep->makeStmt());
}
// Invoke body function
{
AstCCall* const callp = new AstCCall{flp, actKit.m_funcp};
callp->dtypeSetVoid();
resultp = AstNode::addNext(resultp, callp->makeStmt());
}
return resultp;
})
.second;
return resultp;
});
// 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, activeEvalLoop.stmtsp);
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()) {
UASSERT(netlistp->nbaEventTriggerp(), "NBA event trigger var should exist");
AstIf* const ifp
= new AstIf{flp, new AstVarRef{flp, netlistp->nbaEventTriggerp(), VAccess::READ}};
ifp->addThensp(setVar(nbaEvalLoop.continuep, 1));
ifp->addThensp(setVar(netlistp->nbaEventTriggerp(), 0));
AstCMethodHard* const firep = new AstCMethodHard{
flp, new AstVarRef{flp, netlistp->nbaEventp(), VAccess::WRITE}, "fire"};
firep->dtypeSetVoid();
ifp->addThensp(firep->makeStmt());
activeEvalLoop.stmtsp->addNext(ifp);
netlistp->nbaEventp(nullptr);
netlistp->nbaEventTriggerp(nullptr);
}
AstNodeStmt* topEvalLoopp = nbaEvalLoop.stmtsp;
if (obsKit.m_funcp) {
// Create the Observed eval loop. This uses the NBA eval loop in the trigger section.
@ -974,7 +999,7 @@ void createEval(AstNetlist* netlistp, //
}
return resultp;
})
.second;
.stmtsp;
}
if (reactKit.m_funcp) {
@ -993,7 +1018,7 @@ void createEval(AstNetlist* netlistp, //
callp->dtypeSetVoid();
return callp->makeStmt();
})
.second;
.stmtsp;
}
funcp->addStmtsp(topEvalLoopp);

View File

@ -365,6 +365,10 @@ private:
m_underFork |= F_MIGHT_NEED_PROC;
iterateChildren(nodep);
}
void visit(AstAssignDly* nodep) override {
if (!VN_IS(m_procp, NodeProcedure)) v3Global.setUsesTiming();
visit(static_cast<AstNode*>(nodep));
}
void visit(AstNode* nodep) override {
if (nodep->isTimingControl()) {
v3Global.setUsesTiming();
@ -451,8 +455,10 @@ private:
double m_timescaleFactor = 1.0; // Factor to scale delays by
int m_forkCnt = 0; // Number of forks inside a module
bool m_underJumpBlock = false; // True if we are inside of a jump-block
bool m_underProcedure = false; // True if we are under an always or initial
// Unique names
V3UniqueNames m_dlyforkNames{"__Vdlyfork"}; // Names for temp AssignW vars
V3UniqueNames m_contAssignVarNames{"__VassignWtmp"}; // Names for temp AssignW vars
V3UniqueNames m_intraValueNames{"__Vintraval"}; // Intra assign delay value var names
V3UniqueNames m_intraIndexNames{"__Vintraidx"}; // Intra assign delay index var names
@ -574,6 +580,31 @@ private:
m_netlistp->topScopep()->addSenTreesp(m_dynamicSensesp);
return m_dynamicSensesp;
}
// Creates the event variable to trigger in NBA region
AstEventControl* createNbaEventControl(FileLine* flp) {
if (!m_netlistp->nbaEventp()) {
auto* const nbaEventDtp = new AstBasicDType{m_scopeTopp->fileline(),
VBasicDTypeKwd::EVENT, VSigning::UNSIGNED};
m_netlistp->typeTablep()->addTypesp(nbaEventDtp);
m_netlistp->nbaEventp(m_scopeTopp->createTemp("__VnbaEvent", nbaEventDtp));
v3Global.setHasEvents();
}
return new AstEventControl{
flp,
new AstSenTree{
flp, new AstSenItem{flp, VEdgeType::ET_EVENT,
new AstVarRef{flp, m_netlistp->nbaEventp(), VAccess::READ}}},
nullptr};
}
// Creates the variable that, if set, causes the NBA event to be triggered
AstAssign* createNbaEventTriggerAssignment(FileLine* flp) {
if (!m_netlistp->nbaEventTriggerp()) {
m_netlistp->nbaEventTriggerp(m_scopeTopp->createTemp("__VnbaEventTrigger", 1));
}
return new AstAssign{flp,
new AstVarRef{flp, m_netlistp->nbaEventTriggerp(), VAccess::WRITE},
new AstConst{flp, AstConst::BitTrue{}}};
}
// 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 {
@ -755,6 +786,8 @@ private:
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
VL_RESTORER(m_underProcedure);
m_underProcedure = true;
iterateChildren(nodep);
if (hasFlags(nodep, T_SUSPENDEE)) nodep->setSuspendable();
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
@ -775,7 +808,8 @@ private:
if (nodep->user1SetOnce()) return;
VL_RESTORER(m_procp);
m_procp = nodep;
VL_RESTORER(m_underProcedure);
m_underProcedure = true;
// Workaround for killing `always` processes (doing that is pretty much UB)
// TODO: Disallow killing `always` at runtime (throw an error)
if (hasFlags(nodep, T_HAS_PROC)) addFlags(nodep, T_SUSPENDEE);
@ -981,20 +1015,37 @@ private:
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)) {
// If it's an NBA with an intra assignment delay, put it in a fork
AstNode* controlp = factorOutTimingControl(nodep);
const bool inAssignDly = VN_IS(nodep, AssignDly);
// Handle the intra assignment timing control
// 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 || m_underProcedure)) return;
// 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* insertBeforep = m_underProcedure ? nullptr : controlp;
// Special case for NBA
if (inAssignDly) {
// Put it in a fork so it doesn't block
auto* const forkp = new AstFork{flp, "", nullptr};
forkp->joinType(VJoinType::JOIN_NONE);
if (!m_underProcedure) {
// If it's in a function, it won't be handled by V3Delayed
// Put it behind an additional named event that gets triggered in the NBA region
AstEventControl* const nbaEventControlp = createNbaEventControl(flp);
AstAssign* const trigAssignp = createNbaEventTriggerAssignment(flp);
nodep->replaceWith(trigAssignp);
trigAssignp->addNextHere(nbaEventControlp);
nbaEventControlp->addStmtsp(nodep);
insertBeforep = forkp;
if (!controlp) controlp = nbaEventControlp;
}
controlp->replaceWith(forkp);
forkp->addStmtsp(controlp);
}
// 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;
UASSERT_OBJ(nodep, controlp, "Assignment should have timing control");
addCLocalScope(flp, insertBeforep);
// Function for replacing values with intermediate variables
const auto replaceWithIntermediate = [&](AstNodeExpr* const valuep,

View File

@ -0,0 +1,32 @@
#!/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,
);
compile(
verilator_flags2 => ["--exe --main --timing +define+WITH_DELAY"],
make_main => 0,
);
execute(
check_finished => 1,
);
ok(1);
1;

View File

@ -0,0 +1,55 @@
// 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
`ifdef WITH_DELAY
`define DELAY #1
`define TIME_AFTER_FIRST_WAIT 2
`define TIME_AFTER_SECOND_WAIT 3
`else
`define DELAY
`define TIME_AFTER_FIRST_WAIT 1
`define TIME_AFTER_SECOND_WAIT 1
`endif
class nba_waiter;
// Task taken from UVM
task wait_for_nba_region;
int nba;
int next_nba;
next_nba++;
nba <= `DELAY next_nba;
@(nba);
endtask
endclass
module t;
nba_waiter waiter = new;
event e;
int cnt = 0;
initial begin
#1 ->e;
if (cnt != 0) $stop;
cnt++;
waiter.wait_for_nba_region;
->e;
if (cnt != 2) $stop;
if ($time != `TIME_AFTER_FIRST_WAIT) $stop;
cnt++;
waiter.wait_for_nba_region;
if (cnt != 4) $stop;
if ($time != `TIME_AFTER_SECOND_WAIT) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
initial begin
@e if (cnt != 1) $stop;
cnt++;
@e if (cnt != 3) $stop;
cnt++;
end
endmodule

View File

@ -0,0 +1,6 @@
%Error-NOTIMING: t/t_assigndly_dynamic_notiming.v:10:13: Delayed assignment in a non-inlined function/task requires --timing
: ... note: In instance '$unit::foo'
10 | qux <= '1;
| ^~
... For error description see https://verilator.org/warn/NOTIMING?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,20 @@
#!/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 => ["--no-timing"],
fails => 1,
expect_filename => $Self->{golden_filename},
);
ok(1);
1;

View File

@ -0,0 +1,12 @@
// 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 foo;
task bar;
int qux;
qux <= '1;
endtask
endclass

View File

@ -918,9 +918,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[$]);