Skip to content

Commit

Permalink
Support NBAs in non-inlined tasks/functions
Browse files Browse the repository at this point in the history
Signed-off-by: Krzysztof Bieganski <[email protected]>
  • Loading branch information
kbieganski committed Oct 16, 2023
1 parent 3613d11 commit dbafec0
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 32 deletions.
3 changes: 3 additions & 0 deletions src/V3AstNodeOther.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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; }
Expand Down
8 changes: 6 additions & 2 deletions src/V3Delayed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
60 changes: 37 additions & 23 deletions src/V3Sched.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
})
.stmtsp;
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.
Expand Down
37 changes: 33 additions & 4 deletions src/V3Timing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,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
Expand Down Expand Up @@ -575,6 +576,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 {
Expand Down Expand Up @@ -979,17 +996,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;
Expand Down
32 changes: 32 additions & 0 deletions test_regress/t/t_assigndly_dynamic.pl
Original file line number Diff line number Diff line change
@@ -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;
47 changes: 47 additions & 0 deletions test_regress/t/t_assigndly_dynamic.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// 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_WAIT 2
`else
`define DELAY
`define TIME_AFTER_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;
if (cnt != 2) $stop;
if ($time != `TIME_AFTER_WAIT) $stop;
$write("*-* All Finished *-*\n");
$finish;
end

initial @e begin
`DELAY
if (cnt != 1) $stop;
cnt++;
end
endmodule
6 changes: 6 additions & 0 deletions test_regress/t/t_assigndly_dynamic_notiming.out
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions test_regress/t/t_assigndly_dynamic_notiming.pl
Original file line number Diff line number Diff line change
@@ -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;
12 changes: 12 additions & 0 deletions test_regress/t/t_assigndly_dynamic_notiming.v
Original file line number Diff line number Diff line change
@@ -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
4 changes: 1 addition & 3 deletions test_regress/t/t_uvm_pkg_todo.vh
Original file line number Diff line number Diff line change
Expand Up @@ -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[$]);
Expand Down

0 comments on commit dbafec0

Please sign in to comment.