mirror of
https://github.com/verilator/verilator.git
synced 2026-10-02 16:13:34 +02:00
Fix nonblocking event trigger never firing in suspendable processes (#8485)
This commit is contained in:
+38
-12
@@ -300,6 +300,7 @@ class DelayedVisitor final : public VNVisitor {
|
||||
bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK
|
||||
bool m_inNonCombLogic = false; // We are in non-combinational logic
|
||||
bool m_needsInitialTrigger = false; // Whether a NodeProcedure needs a initial trigger
|
||||
std::vector<AstSenTree*> m_nbaEventSenTreeps; // Sensitivities of '->>' in the process
|
||||
AstVarRef* m_currNbaLhsRefp = nullptr; // Current NBA LHS variable reference
|
||||
|
||||
// STATE - during NBA conversion (after visit)
|
||||
@@ -1190,6 +1191,7 @@ class DelayedVisitor final : public VNVisitor {
|
||||
}
|
||||
void visit(AstNodeProcedure* nodep) override {
|
||||
VL_RESTORER(m_needsInitialTrigger);
|
||||
VL_RESTORER_CLEAR(m_nbaEventSenTreeps);
|
||||
const size_t firstNBAAddedIndex = m_nbas.size();
|
||||
{
|
||||
VL_RESTORER(m_inSuspendableOrFork);
|
||||
@@ -1241,6 +1243,8 @@ class DelayedVisitor final : public VNVisitor {
|
||||
for (size_t i = firstNBAAddedIndex; i < m_nbas.size(); ++i) {
|
||||
m_vscpInfo(m_nbas[i].vscp).addSensitivity(senItemp);
|
||||
}
|
||||
for (AstSenTree* const senTreep : m_nbaEventSenTreeps)
|
||||
senTreep->addSensesp(senItemp->cloneTree(true));
|
||||
// Done with these
|
||||
VL_DO_DANGLING(senItemp->deleteTree(), senItemp);
|
||||
}
|
||||
@@ -1266,8 +1270,15 @@ class DelayedVisitor final : public VNVisitor {
|
||||
cstmtp->add(");");
|
||||
|
||||
AstNode* newp = cstmtp;
|
||||
if (nodep->isDelayed()) {
|
||||
const AstVarRef* const vrefp = VN_AS(eventp, VarRef);
|
||||
const AstVarRef* const vrefp = VN_CAST(eventp, VarRef);
|
||||
if (nodep->isDelayed() && (m_cfuncp || !vrefp)) {
|
||||
// V3Timing converts these with --timing
|
||||
nodep->v3warn(E_NOTIMING, "Nonblocking event trigger "
|
||||
<< (m_cfuncp ? "in a non-inlined function/task"
|
||||
: "of a class or interface member or "
|
||||
"array element")
|
||||
<< " requires --timing");
|
||||
} else if (nodep->isDelayed()) {
|
||||
const std::string newvarname = "__Vdly__" + vrefp->varp()->shortName();
|
||||
AstVarScope* const dlyvscp
|
||||
= createTemp(flp, vrefp->varScopep()->scopep(), newvarname, 1);
|
||||
@@ -1276,20 +1287,35 @@ class DelayedVisitor final : public VNVisitor {
|
||||
return new AstVarRef{flp, dlyvscp, access};
|
||||
};
|
||||
|
||||
AstAlwaysPre* const prep = new AstAlwaysPre{flp};
|
||||
prep->addStmtsp(new AstAssign{flp, dlyRef(VAccess::WRITE),
|
||||
new AstConst{flp, AstConst::BitFalse{}}});
|
||||
AstAlwaysPost* const postp = new AstAlwaysPost{flp};
|
||||
{
|
||||
AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)};
|
||||
postp->addStmtsp(ifp);
|
||||
ifp->addThensp(newp);
|
||||
}
|
||||
AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)};
|
||||
postp->addStmtsp(ifp);
|
||||
|
||||
UASSERT_OBJ(m_activep, nodep, "No active to handle FireEvent");
|
||||
AstActive* const activep = new AstActive{flp, "nba-event", m_activep->sentreep()};
|
||||
AstSenTree* senTreep = m_activep->sentreep();
|
||||
AstActive* activep = nullptr;
|
||||
if (m_inSuspendableOrFork && (senTreep->hasInitial() || senTreep->hasClocked())) {
|
||||
// Suspendable code can trigger whenever it resumes, so fire when its NBAs are
|
||||
// committed (see visit(AstNodeProcedure*)), clearing the flag like
|
||||
// Scheme::FlagUnique
|
||||
senTreep = new AstSenTree{
|
||||
flp, senTreep->hasClocked() ? senTreep->sensesp()->cloneTree(true) : nullptr};
|
||||
m_nbaEventSenTreeps.push_back(senTreep);
|
||||
m_needsInitialTrigger |= m_timingDomains.empty();
|
||||
dlyvscp->varp()->setIgnorePostWrite();
|
||||
ifp->addThensp(new AstAssign{flp, dlyRef(VAccess::WRITE),
|
||||
new AstConst{flp, AstConst::BitFalse{}}});
|
||||
activep = new AstActive{flp, "nba-event", senTreep};
|
||||
activep->senTreeStorep(senTreep);
|
||||
} else {
|
||||
activep = new AstActive{flp, "nba-event", senTreep};
|
||||
AstAlwaysPre* const prep = new AstAlwaysPre{flp};
|
||||
prep->addStmtsp(new AstAssign{flp, dlyRef(VAccess::WRITE),
|
||||
new AstConst{flp, AstConst::BitFalse{}}});
|
||||
activep->addStmtsp(prep);
|
||||
}
|
||||
ifp->addThensp(newp);
|
||||
m_activep->addNextHere(activep);
|
||||
activep->addStmtsp(prep);
|
||||
activep->addStmtsp(postp);
|
||||
|
||||
newp = new AstAssign{flp, dlyRef(VAccess::WRITE),
|
||||
|
||||
@@ -389,6 +389,13 @@ class TimingSuspendableVisitor final : public VNVisitor {
|
||||
if (!VN_IS(m_procp, NodeProcedure)) v3Global.setUsesTiming();
|
||||
visit(static_cast<AstNode*>(nodep));
|
||||
}
|
||||
void visit(AstFireEvent* nodep) override {
|
||||
if (nodep->isDelayed()
|
||||
&& (!VN_IS(m_procp, NodeProcedure) || !VN_IS(nodep->operandp(), VarRef))) {
|
||||
v3Global.setUsesTiming();
|
||||
}
|
||||
visit(static_cast<AstNode*>(nodep));
|
||||
}
|
||||
void visit(AstAssignW* nodep) override {
|
||||
if (nodep->timingControlp()) v3Global.setUsesTiming();
|
||||
// Containing process will not suspend, don't mark it
|
||||
@@ -1259,6 +1266,51 @@ class TimingControlVisitor final : public VNVisitor {
|
||||
// Replace the RHS with an intermediate value var
|
||||
replaceWithIntermediate(nodep->rhsp(), m_intraValueNames.get(nodep));
|
||||
}
|
||||
void visit(AstFireEvent* nodep) override {
|
||||
// V3Delayed handles '->>' of a variable in a process. Like NBAs in non-inlined functions,
|
||||
// trigger other events from a fork awaiting the NBA region.
|
||||
if (!nodep->isDelayed() || (m_underProcedure && VN_IS(nodep->operandp(), VarRef))) {
|
||||
iterateChildren(nodep);
|
||||
return;
|
||||
}
|
||||
FileLine* const flp = nodep->fileline();
|
||||
AstAssign* const trigAssignp = createNbaEventTriggerAssignment(flp);
|
||||
nodep->replaceWith(trigAssignp);
|
||||
AstFork* const forkp = new AstFork{flp, VJoinType::JOIN_NONE};
|
||||
trigAssignp->addNextHere(forkp);
|
||||
if (m_underJumpBlock) addCLocalScope(flp, forkp);
|
||||
// The triggered event is the one referenced now, so evaluate handles and indices now
|
||||
AstNodeExpr* const eventp = nodep->operandp()->unlinkFrBack();
|
||||
const auto evalNow = [&](AstNodeExpr* const valuep, V3UniqueNames& names) {
|
||||
AstVarScope* const vscp = createTemp(flp, names.get(nodep), valuep->dtypep(), forkp);
|
||||
valuep->replaceWith(new AstVarRef{flp, vscp, VAccess::READ});
|
||||
// Unlike an event, a handle selecting one is only read
|
||||
valuep->foreach([](AstNode* const np) {
|
||||
if (AstNodeVarRef* const refp = VN_CAST(np, NodeVarRef)) {
|
||||
refp->access(VAccess::READ);
|
||||
} else if (AstMemberSel* const selp = VN_CAST(np, MemberSel)) {
|
||||
selp->access(VAccess::READ);
|
||||
}
|
||||
});
|
||||
forkp->addHereThisAsNext(
|
||||
new AstAssign{flp, new AstVarRef{flp, vscp, VAccess::WRITE}, valuep});
|
||||
};
|
||||
AstNodeExpr* refp = eventp;
|
||||
while (AstNodeSel* const selp = VN_CAST(refp, NodeSel)) {
|
||||
if (!VN_IS(selp->bitp(), Const)) evalNow(selp->bitp(), m_intraIndexNames);
|
||||
refp = selp->fromp();
|
||||
}
|
||||
// Handle of a class (or virtual interface)
|
||||
if (AstMemberSel* const selp = VN_CAST(refp, MemberSel)) {
|
||||
evalNow(selp->fromp(), m_intraValueNames);
|
||||
}
|
||||
AstEventControl* const controlp = createNbaEventControl(flp);
|
||||
controlp->addStmtsp(new AstFireEvent{flp, eventp, false});
|
||||
AstBegin* const beginp = new AstBegin{flp, "", controlp, false};
|
||||
addFlags(beginp, T_NBA_UPDATE);
|
||||
forkp->addForksp(beginp);
|
||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||
}
|
||||
void visit(AstAssignW* nodep) override {
|
||||
FileLine* const flp = nodep->fileline();
|
||||
// Get the net delay unless this assignment was created for handling the net delay (user1)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
%Error-NOTIMING: t/t_event_nba_notiming_bad.v:10:5: Nonblocking event trigger in a non-inlined function/task requires --timing
|
||||
: ... note: In instance '$unit::Cls'
|
||||
10 | ->>e;
|
||||
| ^~~
|
||||
... For error description see https://verilator.org/warn/NOTIMING?v=latest
|
||||
%Error-NOTIMING: t/t_event_nba_notiming_bad.v:20:5: Nonblocking event trigger of a class or interface member or array element requires --timing
|
||||
20 | ->>c.e;
|
||||
| ^~~
|
||||
%Error-NOTIMING: t/t_event_nba_notiming_bad.v:21:5: Nonblocking event trigger of a class or interface member or array element requires --timing
|
||||
21 | ->>ea[idx];
|
||||
| ^~~
|
||||
%Error: Exiting due to
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Nonblocking event triggers requiring --timing
|
||||
#
|
||||
# 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-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(verilator_flags2=["--no-timing"], fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,23 @@
|
||||
// DESCRIPTION: Verilator: Nonblocking event triggers requiring --timing
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
class Cls;
|
||||
event e;
|
||||
function void trig();
|
||||
->>e;
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
module t;
|
||||
Cls c = new;
|
||||
event ea[2];
|
||||
int idx;
|
||||
initial begin
|
||||
c.trig();
|
||||
->>c.e;
|
||||
->>ea[idx];
|
||||
end
|
||||
endmodule
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Nonblocking event triggers in suspendable processes
|
||||
#
|
||||
# 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-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["--binary"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,134 @@
|
||||
// DESCRIPTION: Verilator: Nonblocking event triggers in suspendable processes
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
|
||||
`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=\"%s\" exp=\"%s\"\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
|
||||
// verilog_format: on
|
||||
|
||||
class Cls;
|
||||
event e;
|
||||
event ea[2];
|
||||
static event se;
|
||||
Cls next;
|
||||
task trig_dly();
|
||||
#1 ->>e;
|
||||
`checkd(e.triggered, 0)
|
||||
endtask
|
||||
endclass
|
||||
|
||||
interface ifc;
|
||||
event e;
|
||||
task trig();
|
||||
->>e;
|
||||
endtask
|
||||
endinterface
|
||||
|
||||
module t;
|
||||
bit clk;
|
||||
event e_init;
|
||||
event e_dly;
|
||||
event e_clk;
|
||||
event e_arr[2];
|
||||
int init_time = -1;
|
||||
int dly_time = -1;
|
||||
int clk_times;
|
||||
Cls c1 = new;
|
||||
Cls c2 = new;
|
||||
Cls c;
|
||||
ifc i ();
|
||||
virtual ifc vif;
|
||||
int idx;
|
||||
int x;
|
||||
string log;
|
||||
|
||||
always #5 clk = ~clk;
|
||||
|
||||
initial begin
|
||||
@e_init;
|
||||
init_time = int'($time);
|
||||
end
|
||||
initial begin
|
||||
@e_dly;
|
||||
dly_time = int'($time);
|
||||
end
|
||||
always @e_clk ++clk_times;
|
||||
|
||||
always @(c1.e) log = {log, $sformatf("c1.e@%0d ", $time)};
|
||||
always @(Cls::se) log = {log, $sformatf("se@%0d ", $time)};
|
||||
always @(c1.ea[0]) log = {log, $sformatf("c1.ea[0]@%0d ", $time)};
|
||||
always @(c1.ea[1]) begin
|
||||
`checkd(x, 3)
|
||||
log = {log, $sformatf("c1.ea[1]@%0d ", $time)};
|
||||
end
|
||||
always @(c2.ea[0]) log = {log, $sformatf("c2.ea[0]@%0d ", $time)};
|
||||
always @(c2.ea[1]) log = {log, $sformatf("c2.ea[1]@%0d ", $time)};
|
||||
always @(c2.e) log = {log, $sformatf("c2.e@%0d ", $time)};
|
||||
always @(e_arr[0]) log = {log, $sformatf("e_arr[0]@%0d ", $time)};
|
||||
always @(e_arr[1]) log = {log, $sformatf("e_arr[1]@%0d ", $time)};
|
||||
always @(i.e) log = {log, $sformatf("i.e@%0d ", $time)};
|
||||
|
||||
// The event is triggered in the NBA region (IEEE 1800-2023 15.5.1)
|
||||
initial ->>e_init;
|
||||
|
||||
initial begin
|
||||
#1 ->>e_dly;
|
||||
`checkd(e_dly.triggered, 0)
|
||||
#0 `checkd(e_dly.triggered, 0)
|
||||
end
|
||||
|
||||
always begin
|
||||
@(posedge clk);
|
||||
->>e_clk;
|
||||
end
|
||||
|
||||
initial begin
|
||||
vif = i;
|
||||
// From a suspended class method
|
||||
c1.trig_dly();
|
||||
// Static class member
|
||||
#1 ->>Cls::se;
|
||||
// The event triggered is the one referenced when '->>' executes
|
||||
#1 c = c1;
|
||||
idx = 1;
|
||||
->>c.ea[idx];
|
||||
x <= 3;
|
||||
c = c2;
|
||||
idx = 0;
|
||||
#1 idx = 1;
|
||||
->>e_arr[idx];
|
||||
idx = 0;
|
||||
// From an interface task, called by two processes
|
||||
#1 vif.trig();
|
||||
// In a loop with a jump
|
||||
#2 for (int k = 0; k < 2; ++k) begin
|
||||
if (k == 1) break;
|
||||
->>c2.ea[k];
|
||||
end
|
||||
// Not a subprocess, so not disabled (IEEE 1800-2023 9.6.3)
|
||||
#1 fork
|
||||
#1 `stop;
|
||||
join_none
|
||||
->>c2.e;
|
||||
disable fork;
|
||||
// Including a handle selected from another
|
||||
#1 c1.next = c2;
|
||||
->>c1.next.e;
|
||||
c1.next = null;
|
||||
end
|
||||
initial #6 vif.trig();
|
||||
|
||||
initial begin
|
||||
#22;
|
||||
`checkd(init_time, 0)
|
||||
`checkd(dly_time, 1)
|
||||
`checkd(clk_times, 2)
|
||||
`checks(log, "c1.e@1 se@2 c1.ea[1]@3 e_arr[1]@4 i.e@5 i.e@6 c2.ea[0]@7 c2.e@8 c2.e@9 ")
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Reference in New Issue
Block a user