Dynamic triggers for non-static contexts (#3599)

In non-static contexts like class objects or stack frames, the use of
global trigger evaluation is not feasible. The concept of dynamic
triggers allows for trigger evaluation in such cases. These triggers are
simply local variables, and coroutines are themselves responsible for
evaluating them. They await the global dynamic trigger scheduler object,
which is responsible for resuming them during the trigger evaluation
step in the 'act' eval region. Once the trigger is set, they await the
dynamic trigger scheduler once again, and then get resumed during the
resumption step in the 'act' eval region.

Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
This commit is contained in:
Krzysztof Bieganski 2022-10-22 14:05:39 +00:00 committed by GitHub
parent 4154584c4b
commit fcf0d03cd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 947 additions and 611 deletions

View File

@ -588,6 +588,31 @@ This split is done to avoid self-triggering and triggering coroutines multiple
times. See the `Scheduling with timing` section for details on how this is
used.
``VlDynamicTriggerScheduler``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Like ``VlTriggerScheduler``, ``VlDynamicTriggerScheduler`` manages processes
that await triggers. However, it does not rely on triggers evaluated externally
by the 'act' trigger eval function. Instead, it is also responsible for trigger
evaluation. Coroutines that make use of this scheduler must adhere to a certain
procedure:
::
__Vtrigger = 0;
<locals and inits required for trigger eval>
while (!__Vtrigger) {
co_await __VdynSched.evaluation();
<pre updates>;
__Vtrigger = <trigger eval>;
[optionally] co_await __VdynSched.postUpdate();
<post updates>;
}
co_await __VdynSched.resumption();
The coroutines get resumed at trigger evaluation time, evaluate their local
triggers, optionally await the post update step, and if the trigger is set,
await proper resumption in the 'act' eval step.
``VlForkSync``
^^^^^^^^^^^^^^
@ -616,6 +641,11 @@ The visitor in ``V3Timing.cpp`` transforms each timing control into a ``co_await
``trigger`` method. The awaited trigger scheduler is the one corresponding to
the sentree referenced by the event control. This sentree is also referenced
by the ``AstCAwait`` node, to be used later by the static scheduling code.
* if an event control waits on a local variable or class member, it uses a
local trigger which it evaluates inline. It awaits a dynamic trigger
scheduler multiple times: for trigger evaluation, updates, and resumption.
The dynamic trigger scheduler is responsible for resuming the coroutine at
the correct point of evaluation.
* delays are turned into ``co_await`` on a delay scheduler's ``delay`` method.
The created ``AstCAwait`` nodes also reference a special sentree related to
delays, to be used later by the static scheduling code.

View File

@ -139,6 +139,54 @@ void VlTriggerScheduler::dump(const char* eventDescription) const {
}
#endif
//======================================================================
// VlDynamicTriggerScheduler:: Methods
bool VlDynamicTriggerScheduler::evaluate() {
VL_DEBUG_IF(dump(););
std::swap(m_suspended, m_evaluated);
for (auto& coro : m_evaluated) coro.resume();
m_evaluated.clear();
return !m_triggered.empty();
}
void VlDynamicTriggerScheduler::doPostUpdates() {
VL_DEBUG_IF(if (!m_post.empty())
VL_DBG_MSGF(" Doing post updates for processes:\n"); //
for (const auto& susp
: m_post) {
VL_DBG_MSGF(" - ");
susp.dump();
});
for (auto& coro : m_post) coro.resume();
m_post.clear();
}
void VlDynamicTriggerScheduler::resume() {
VL_DEBUG_IF(if (!m_triggered.empty()) VL_DBG_MSGF(" Resuming processes:\n"); //
for (const auto& susp
: m_triggered) {
VL_DBG_MSGF(" - ");
susp.dump();
});
for (auto& coro : m_triggered) coro.resume();
m_triggered.clear();
}
#ifdef VL_DEBUG
void VlDynamicTriggerScheduler::dump() const {
if (m_suspended.empty()) {
VL_DBG_MSGF(" No suspended processes waiting for dynamic trigger evaluation\n");
} else {
for (const auto& susp : m_suspended) {
VL_DBG_MSGF(" Suspended processes waiting for dynamic trigger evaluation:\n");
VL_DBG_MSGF(" - ");
susp.dump();
}
}
}
#endif
//======================================================================
// VlForkSync:: Methods

View File

@ -238,6 +238,80 @@ public:
}
};
//=============================================================================
// VlDynamicTriggerScheduler is used for cases where triggers cannot be statically referenced and
// evaluated. Coroutines that make use of this scheduler must adhere to a certain procedure:
// __Vtrigger = 0;
// <locals and inits required for trigger eval>
// while (!__Vtrigger) {
// co_await __VdynSched.evaluation();
// <pre updates>;
// __Vtrigger = <trigger eval>;
// [optionally] co_await __VdynSched.postUpdate();
// <post updates>;
// }
// co_await __VdynSched.resumption();
// The coroutines get resumed at trigger evaluation time, evaluate their local triggers, optionally
// await the post update step, and if the trigger is set, await proper resumption in the 'act' eval
// step.
class VlDynamicTriggerScheduler final {
// TYPES
using VlCoroutineVec = std::vector<VlCoroutineHandle>;
// MEMBERS
VlCoroutineVec m_suspended; // Suspended coroutines awaiting trigger evaluation
VlCoroutineVec m_evaluated; // Coroutines currently being evaluated (for evaluate())
VlCoroutineVec m_triggered; // Coroutines whose triggers were set, and are awaiting resumption
VlCoroutineVec m_post; // Coroutines awaiting the post update step (only relevant for triggers
// with destructive post updates, e.g. named events)
// METHODS
auto awaitable(VlCoroutineVec& queue, const char* filename, int lineno) {
struct Awaitable {
VlCoroutineVec& suspended; // Coros waiting on trigger
VlFileLineDebug fileline;
bool await_ready() const { return false; } // Always suspend
void await_suspend(std::coroutine_handle<> coro) {
suspended.emplace_back(coro, fileline);
}
void await_resume() const {}
};
return Awaitable{queue, VlFileLineDebug{filename, lineno}};
}
public:
// Evaluates all dynamic triggers (resumed coroutines that co_await evaluation())
bool evaluate();
// Runs post updates for all dynamic triggers (resumes coroutines that co_await postUpdate())
void doPostUpdates();
// Resumes all coroutines whose triggers are set (those that co_await resumption())
void resume();
#ifdef VL_DEBUG
void dump() const;
#endif
// Used by coroutines for co_awaiting trigger evaluation
auto evaluation(const char* eventDescription, const char* filename, int lineno) {
VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n",
eventDescription, filename, lineno););
return awaitable(m_suspended, filename, lineno);
}
// Used by coroutines for co_awaiting the trigger post update step
auto postUpdate(const char* eventDescription, const char* filename, int lineno) {
VL_DEBUG_IF(
VL_DBG_MSGF(" Process waiting for %s at %s:%d awaiting the post update step\n",
eventDescription, filename, lineno););
return awaitable(m_post, filename, lineno);
}
// Used by coroutines for co_awaiting the resumption step (in 'act' eval)
auto resumption(const char* eventDescription, const char* filename, int lineno) {
VL_DEBUG_IF(VL_DBG_MSGF(" Process waiting for %s at %s:%d awaiting resumption\n",
eventDescription, filename, lineno););
return awaitable(m_triggered, filename, lineno);
}
};
//=============================================================================
// VlNow is a helper awaitable type that always suspends, and then immediately resumes a coroutine.
// Allows forcing the move of coroutine locals to the heap.

View File

@ -460,6 +460,7 @@ public:
TRIGGERVEC,
DELAY_SCHEDULER,
TRIGGER_SCHEDULER,
DYNAMIC_TRIGGER_SCHEDULER,
FORK_SYNC,
// Unsigned and two state; fundamental types
UINT32,
@ -490,6 +491,7 @@ public:
"VlTriggerVec",
"VlDelayScheduler",
"VlTriggerScheduler",
"VlDynamicTriggerScheduler",
"VlFork",
"IData",
"QData",
@ -498,30 +500,12 @@ public:
return names[m_e];
}
const char* dpiType() const {
static const char* const names[] = {"%E-unk",
"svBit",
"char",
"void*",
"char",
"int",
"%E-integer",
"svLogic",
"long long",
"double",
"short",
"%E-time",
"const char*",
"dpiScope",
"const char*",
"%E-mtaskstate",
"%E-triggervec",
"%E-dly-sched",
"%E-trig-sched",
"%E-fork",
"IData",
"QData",
"%E-logic-implct",
" MAX"};
static const char* const names[]
= {"%E-unk", "svBit", "char", "void*", "char",
"int", "%E-integer", "svLogic", "long long", "double",
"short", "%E-time", "const char*", "dpiScope", "const char*",
"%E-mtaskstate", "%E-triggervec", "%E-dly-sched", "%E-trig-sched", "%E-dyn-sched",
"%E-fork", "IData", "QData", "%E-logic-implct", " MAX"};
return names[m_e];
}
static void selfTest() {
@ -558,6 +542,7 @@ public:
case TRIGGERVEC: return 0; // opaque
case DELAY_SCHEDULER: return 0; // opaque
case TRIGGER_SCHEDULER: return 0; // opaque
case DYNAMIC_TRIGGER_SCHEDULER: return 0; // opaque
case FORK_SYNC: return 0; // opaque
case UINT32: return 32;
case UINT64: return 64;
@ -596,7 +581,8 @@ public:
bool isOpaque() const VL_MT_SAFE { // IE not a simple number we can bit optimize
return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|| m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER
|| m_e == TRIGGER_SCHEDULER || m_e == FORK_SYNC || m_e == DOUBLE);
|| m_e == TRIGGER_SCHEDULER || m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC
|| m_e == DOUBLE);
}
bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; }
bool isEvent() const { return m_e == EVENT; }

View File

@ -440,6 +440,9 @@ public:
bool isTriggerScheduler() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::TRIGGER_SCHEDULER;
}
bool isDynamicTriggerScheduler() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::DYNAMIC_TRIGGER_SCHEDULER;
}
bool isOpaque() const VL_MT_SAFE { return keyword().isOpaque(); }
bool isString() const VL_MT_SAFE { return keyword().isString(); }
bool isZeroInit() const { return keyword().isZeroInit(); }

View File

@ -754,6 +754,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const {
info.m_type = "VlDelayScheduler";
} else if (bdtypep->isTriggerScheduler()) {
info.m_type = "VlTriggerScheduler";
} else if (bdtypep->isDynamicTriggerScheduler()) {
info.m_type = "VlDynamicTriggerScheduler";
} else if (bdtypep->isForkSync()) {
info.m_type = "VlForkSync";
} else if (bdtypep->isEvent()) {

View File

@ -694,6 +694,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
return "";
} else if (basicp && basicp->isTriggerScheduler()) {
return "";
} else if (basicp && basicp->isDynamicTriggerScheduler()) {
return "";
} else if (basicp) {
const bool zeroit
= (varp->attrFileDescr() // Zero so we don't do file IO if never $fopen

View File

@ -363,7 +363,8 @@ public:
//============================================================================
// Create a TRIGGERVEC and the related TriggerKit for the given AstSenTree vector
const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
SenExprBuilder& senExprBuilder,
const std::vector<const AstSenTree*>& senTreeps,
const string& name, const ExtraTriggers& extraTriggers,
bool slow = false) {
@ -463,7 +464,10 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui
//
++triggerNumber;
}
// Add the update statements
// Add the init and update statements
for (AstNodeStmt* const nodep : senExprBuilder.getAndClearInits()) {
initFuncp->addStmtsp(nodep);
}
for (AstNodeStmt* const nodep : senExprBuilder.getAndClearPostUpdates()) {
funcp->addStmtsp(nodep);
}
@ -604,7 +608,7 @@ std::pair<AstVarScope*, AstNode*> makeEvalLoop(AstNetlist* netlistp, const strin
//============================================================================
// Order the combinational logic to create the settle loop
void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider,
void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilder& senExprBulider,
LogicClasses& logicClasses) {
AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_settle", true);
@ -622,8 +626,8 @@ void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider,
// Gather the relevant sensitivity expressions and create the trigger kit
const auto& senTreeps = getSenTreesUsedBy({&comb, &hybrid});
const TriggerKit& trig
= createTriggers(netlistp, senExprBulider, senTreeps, "stl", extraTriggers, true);
const TriggerKit& trig = createTriggers(netlistp, initFuncp, senExprBulider, senTreeps, "stl",
extraTriggers, true);
// Remap sensitivities (comb has none, so only do the hybrid)
remapSensitivities(hybrid, trig.m_map);
@ -662,8 +666,8 @@ void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider,
//============================================================================
// Order the replicated combinational logic to create the 'ico' region
AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
LogicByScope& logic) {
AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
SenExprBuilder& senExprBuilder, LogicByScope& logic) {
// Nothing to do if no combinational logic is sensitive to top level inputs
if (logic.empty()) return nullptr;
@ -693,7 +697,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde
// Gather the relevant sensitivity expressions and create the trigger kit
const auto& senTreeps = getSenTreesUsedBy({&logic});
const TriggerKit& trig
= createTriggers(netlistp, senExprBuilder, senTreeps, "ico", extraTriggers);
= createTriggers(netlistp, initFuncp, senExprBuilder, senTreeps, "ico", extraTriggers);
if (dpiExportTriggerVscp) {
trig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
@ -904,10 +908,12 @@ void schedule(AstNetlist* netlistp) {
// We pass around a single SenExprBuilder instance, as we only need one set of 'prev' variables
// for edge/change detection in sensitivity expressions, which this keeps track of.
SenExprBuilder senExprBuilder{netlistp, initp};
AstTopScope* const topScopep = netlistp->topScopep();
AstScope* const scopeTopp = topScopep->scopep();
SenExprBuilder senExprBuilder{scopeTopp};
// Step 4: Create 'settle' region that restores the combinational invariant
createSettle(netlistp, senExprBuilder, logicClasses);
createSettle(netlistp, initp, senExprBuilder, logicClasses);
if (v3Global.opt.stats()) V3Stats::statsStage("sched-settle");
// Step 5: Partition the clocked and combinational (including hybrid) logic into pre/act/nba.
@ -932,7 +938,8 @@ void schedule(AstNetlist* netlistp) {
}
// Step 7: Create input combinational logic loop
AstNode* const icoLoopp = createInputCombLoop(netlistp, senExprBuilder, logicReplicas.m_ico);
AstNode* const icoLoopp
= createInputCombLoop(netlistp, initp, senExprBuilder, logicReplicas.m_ico);
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-ico");
// Step 8: Create the pre/act/nba triggers
@ -949,15 +956,15 @@ void schedule(AstNetlist* netlistp) {
&logicRegions.m_nba, //
&timingKit.m_lbs});
const TriggerKit& actTrig
= createTriggers(netlistp, senExprBuilder, senTreeps, "act", extraTriggers);
= createTriggers(netlistp, initp, senExprBuilder, senTreeps, "act", extraTriggers);
// Add post updates from the timing kit
if (timingKit.m_postUpdates) actTrig.m_funcp->addStmtsp(timingKit.m_postUpdates);
if (dpiExportTriggerVscp) {
actTrig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
}
AstTopScope* const topScopep = netlistp->topScopep();
AstScope* const scopeTopp = topScopep->scopep();
AstVarScope* const actTrigVscp = actTrig.m_vscp;
AstVarScope* const preTrigVscp = scopeTopp->createTempLike("__VpreTriggered", actTrigVscp);
AstVarScope* const nbaTrigVscp = scopeTopp->createTempLike("__VnbaTriggered", actTrigVscp);

View File

@ -125,6 +125,7 @@ class TimingKit final {
public:
LogicByScope m_lbs; // Actives that resume timing schedulers
AstNodeStmt* m_postUpdates = nullptr; // Post updates for the trigger eval function
// Remaps external domains using the specified trigger map
std::map<const AstVarScope*, std::vector<AstSenTree*>>
@ -135,10 +136,11 @@ public:
AstCCall* createCommit(AstNetlist* const netlistp);
TimingKit() = default;
TimingKit(LogicByScope&& lbs,
TimingKit(LogicByScope&& lbs, AstNodeStmt* postUpdates,
std::map<const AstVarScope*, std::set<AstSenTree*>>&& externalDomains)
: m_externalDomains{externalDomains}
, m_lbs{lbs} {}
, m_lbs{lbs}
, m_postUpdates{postUpdates} {}
VL_UNCOPYABLE(TimingKit);
TimingKit(TimingKit&&) = default;
TimingKit& operator=(TimingKit&&) = default;

View File

@ -89,7 +89,8 @@ AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) {
UASSERT_OBJ(!resumep->nextp(), resumep, "Should be the only statement here");
AstVarScope* const schedulerp = VN_AS(resumep->fromp(), VarRef)->varScopep();
UASSERT_OBJ(schedulerp->dtypep()->basicp()->isDelayScheduler()
|| schedulerp->dtypep()->basicp()->isTriggerScheduler(),
|| schedulerp->dtypep()->basicp()->isTriggerScheduler()
|| schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler(),
schedulerp, "Unexpected type");
if (!schedulerp->dtypep()->basicp()->isTriggerScheduler()) continue;
// Create the global commit function only if we have trigger schedulers
@ -143,6 +144,7 @@ TimingKit prepareTiming(AstNetlist* const netlistp) {
bool m_gatherVars = false; // Should we gather vars in m_writtenBySuspendable?
AstScope* const m_scopeTopp; // Scope at the top
LogicByScope& m_lbs; // Timing resume actives
AstNodeStmt*& m_postUpdatesr; // Post updates for the trigger eval function
// Additional var sensitivities
std::map<const AstVarScope*, std::set<AstSenTree*>>& m_externalDomains;
std::set<AstSenTree*> m_processDomains; // Sentrees from the current process
@ -159,11 +161,15 @@ TimingKit prepareTiming(AstNetlist* const netlistp) {
// Create a resume() call on the timing scheduler
auto* const resumep = new AstCMethodHard{
flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "resume"};
if (schedulerp->dtypep()->basicp()->isTriggerScheduler() && methodp->pinsp()) {
resumep->addPinsp(methodp->pinsp()->cloneTree(false));
}
resumep->statement(true);
resumep->dtypeSetVoid();
if (schedulerp->dtypep()->basicp()->isTriggerScheduler()) {
if (methodp->pinsp()) resumep->addPinsp(methodp->pinsp()->cloneTree(false));
} else if (schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler()) {
auto* const postp = resumep->cloneTree(false);
postp->name("doPostUpdates");
m_postUpdatesr = AstNode::addNext(m_postUpdatesr, postp);
}
// Put it in an active and put that in the global resume function
auto* const activep = new AstActive{flp, "_timing", sensesp};
activep->addStmtsp(resumep);
@ -215,19 +221,21 @@ TimingKit prepareTiming(AstNetlist* const netlistp) {
public:
// CONSTRUCTORS
explicit AwaitVisitor(AstNetlist* nodep, LogicByScope& lbs,
explicit AwaitVisitor(AstNetlist* nodep, LogicByScope& lbs, AstNodeStmt*& postUpdatesr,
std::map<const AstVarScope*, std::set<AstSenTree*>>& externalDomains)
: m_scopeTopp{nodep->topScopep()->scopep()}
, m_lbs{lbs}
, m_postUpdatesr{postUpdatesr}
, m_externalDomains{externalDomains} {
iterate(nodep);
}
~AwaitVisitor() override = default;
};
LogicByScope lbs;
AstNodeStmt* postUpdates = nullptr;
std::map<const AstVarScope*, std::set<AstSenTree*>> externalDomains;
AwaitVisitor{netlistp, lbs, externalDomains};
return {std::move(lbs), std::move(externalDomains)};
AwaitVisitor{netlistp, lbs, postUpdates, externalDomains};
return {std::move(lbs), postUpdates, std::move(externalDomains)};
}
//============================================================================

View File

@ -29,10 +29,10 @@
class SenExprBuilder final {
// STATE
AstCFunc* const m_initp; // The initialization function
AstScope* const m_scopeTopp; // Top level scope
AstScope* const m_scopep; // The scope
std::vector<AstVar*> m_locals; // Trigger eval local variables
std::vector<AstNodeStmt*> m_inits; // Initialization statements for prevoius values
std::vector<AstNodeStmt*> m_preUpdates; // Pre update assignments
std::vector<AstNodeStmt*> m_postUpdates; // Post update assignments
@ -76,8 +76,8 @@ class SenExprBuilder final {
= new AstVar{flp, VVarType::BLOCKTEMP, m_currNames.get(exprp), exprp->dtypep()};
varp->funcLocal(true);
m_locals.push_back(varp);
AstVarScope* vscp = new AstVarScope{flp, m_scopeTopp, varp};
m_scopeTopp->addVarsp(vscp);
AstVarScope* vscp = new AstVarScope{flp, m_scopep, varp};
m_scopep->addVarsp(vscp);
result.first->second = vscp;
}
AstVarScope* const currp = result.first->second;
@ -106,13 +106,23 @@ class SenExprBuilder final {
name = m_prevNames.get(exprp);
}
AstVarScope* const prevp = m_scopeTopp->createTemp(name, exprp->dtypep());
AstVarScope* prevp;
if (m_scopep->isTop()) {
prevp = m_scopep->createTemp(name, exprp->dtypep());
} else {
AstVar* const varp = new AstVar{flp, VVarType::BLOCKTEMP, m_prevNames.get(exprp),
exprp->dtypep()};
varp->funcLocal(true);
m_locals.push_back(varp);
prevp = new AstVarScope{flp, m_scopep, varp};
m_scopep->addVarsp(prevp);
}
it = m_prev.emplace(*exprp, prevp).first;
// Add the initializer init
AstNode* const initp = exprp->cloneTree(false);
m_initp->addStmtsp(
new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE}, initp});
AstAssign* const initp = new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE},
exprp->cloneTree(false)};
m_inits.push_back(initp);
}
AstVarScope* const prevp = it->second;
@ -222,6 +232,7 @@ public:
return {resultp, firedAtInitialization};
}
std::vector<AstNodeStmt*> getAndClearInits() { return std::move(m_inits); }
std::vector<AstVar*> getAndClearLocals() { return std::move(m_locals); }
std::vector<AstNodeStmt*> getAndClearPreUpdates() {
@ -235,9 +246,8 @@ public:
}
// CONSTRUCTOR
SenExprBuilder(AstNetlist* netlistp, AstCFunc* initp)
: m_initp{initp}
, m_scopeTopp{netlistp->topScopep()->scopep()} {}
SenExprBuilder(AstScope* scopep)
: m_scopep{scopep} {}
};
#endif // Guard

View File

@ -50,6 +50,7 @@
#include "V3Const.h"
#include "V3EmitV.h"
#include "V3Graph.h"
#include "V3SenExprBuilder.h"
#include "V3SenTree.h"
#include "V3UniqueNames.h"
@ -114,6 +115,7 @@ private:
V3UniqueNames m_intraLsbNames{"__Vintralsb"}; // Intra assign delay LSB var names
V3UniqueNames m_forkNames{"__Vfork"}; // Fork name generator
V3UniqueNames m_trigSchedNames{"__VtrigSched"}; // Trigger scheduler name generator
V3UniqueNames m_dynTrigNames{"__VdynTrigger"}; // Dynamic trigger name generator
// DTypes
AstBasicDType* m_forkDtp = nullptr; // Fork variable type
@ -121,7 +123,9 @@ private:
// Timing-related globals
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
AstSenTree* m_dynamicSensesp = nullptr; // Domain to trigger if a dynamic trigger is set
// Other
V3Graph m_depGraph; // Dependency graph where a node is a dependency of another if it being
@ -228,6 +232,44 @@ private:
m_netlistp->topScopep()->addSenTreesp(m_delaySensesp);
return m_delaySensesp;
}
// Creates the global dynamic trigger scheduler variable
AstVarScope* getCreateDynamicTriggerScheduler() {
if (m_dynamicSchedp) return m_dynamicSchedp;
auto* const dynSchedDtp
= new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::DYNAMIC_TRIGGER_SCHEDULER,
VSigning::UNSIGNED};
m_netlistp->typeTablep()->addTypesp(dynSchedDtp);
m_dynamicSchedp = m_scopeTopp->createTemp("__VdynSched", dynSchedDtp);
return m_dynamicSchedp;
}
// Creates the dynamic trigger sentree
AstSenTree* getCreateDynamicTriggerSenTree() {
if (m_dynamicSensesp) return m_dynamicSensesp;
FileLine* const flp = m_scopeTopp->fileline();
auto* const awaitingCurrentTimep = new AstCMethodHard{
flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::READ},
"evaluate"};
awaitingCurrentTimep->dtypeSetBit();
m_dynamicSensesp
= new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE, awaitingCurrentTimep}};
m_netlistp->topScopep()->addSenTreesp(m_dynamicSensesp);
return m_dynamicSensesp;
}
// 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 {
return m_classp || nodep->exists([](const AstNodeVarRef* const refp) {
return refp->varp()->isFuncLocal();
});
}
// Returns true if the given trigger expression needs a destructive post update after trigger
// evaluation. Currently this only applies to named events.
bool destructivePostUpdate(AstNode* const exprp) const {
return exprp->exists([](const AstNodeVarRef* const refp) {
AstBasicDType* const dtypep = refp->dtypep()->basicp();
return dtypep && dtypep->isEvent();
});
}
// Creates a trigger scheduler variable
AstVarScope* getCreateTriggerSchedulerp(AstSenTree* const sensesp) {
if (!sensesp->user1p()) {
@ -483,7 +525,75 @@ private:
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstEventControl* nodep) override {
if (m_classp) nodep->v3warn(E_UNSUPPORTED, "Unsupported: event controls in methods");
// Do not allow waiting on local named events, as they get enqueued for clearing, but can
// go out of scope before that happens
if (nodep->sensesp()->exists([](const AstNodeVarRef* refp) {
AstBasicDType* const dtypep = refp->dtypep()->skipRefp()->basicp();
return dtypep && dtypep->isEvent() && refp->varp()->isFuncLocal();
})) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: waiting on local event variables");
}
FileLine* const flp = nodep->fileline();
// Relink child statements after the event control
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext());
if (needDynamicTrigger(nodep->sensesp())) {
// Create the trigger variable and init it with 0
AstVarScope* const trigvscp
= createTemp(flp, m_dynTrigNames.get(nodep), nodep->findBitDType(), nodep);
auto* const initp = new AstAssign{flp, new AstVarRef{flp, trigvscp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}};
nodep->addHereThisAsNext(initp);
// Await the eval step with the dynamic trigger scheduler. First, create the method
// call
auto* const evalMethodp = new AstCMethodHard{
flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::WRITE},
"evaluation"};
evalMethodp->dtypeSetVoid();
auto* const sensesp = nodep->sensesp();
addEventDebugInfo(evalMethodp, sensesp);
// Create the co_await
auto* const awaitEvalp
= new AstCAwait{flp, evalMethodp, getCreateDynamicTriggerSenTree()};
awaitEvalp->statement(true);
// Construct the sen expression for this sentree
SenExprBuilder senExprBuilder{m_scopep};
auto* const assignp = new AstAssign{flp, new AstVarRef{flp, trigvscp, VAccess::WRITE},
senExprBuilder.build(sensesp).first};
// Put all the locals and inits before the trigger eval loop
for (AstVar* const varp : senExprBuilder.getAndClearLocals()) {
nodep->addHereThisAsNext(varp);
}
for (AstNodeStmt* const stmtp : senExprBuilder.getAndClearInits()) {
nodep->addHereThisAsNext(stmtp);
}
// Create the trigger eval loop, which will await the evaluation step and check the
// trigger
auto* const loopp = new AstWhile{
flp, new AstLogNot{flp, new AstVarRef{flp, trigvscp, VAccess::READ}}, awaitEvalp};
// Put pre updates before the trigger check and assignment
for (AstNodeStmt* const stmtp : senExprBuilder.getAndClearPreUpdates()) {
loopp->addStmtsp(stmtp);
}
// Then the trigger check and assignment
loopp->addStmtsp(assignp);
// If the post update is destructive (e.g. event vars are cleared), create an await for
// the post update step
if (destructivePostUpdate(sensesp)) {
auto* const awaitPostUpdatep = awaitEvalp->cloneTree(false);
VN_AS(awaitPostUpdatep->exprp(), CMethodHard)->name("postUpdate");
loopp->addStmtsp(awaitPostUpdatep);
}
// Put the post updates at the end of the loop
for (AstNodeStmt* const stmtp : senExprBuilder.getAndClearPostUpdates()) {
loopp->addStmtsp(stmtp);
}
// Finally, await the resumption step in 'act'
auto* const awaitResumep = awaitEvalp->cloneTree(false);
VN_AS(awaitResumep->exprp(), CMethodHard)->name("resumption");
AstNode::addNext<AstNode, AstNode>(loopp, awaitResumep);
// Replace the event control with the loop
nodep->replaceWith(loopp);
} else {
auto* const sensesp = m_finder.getSenTree(nodep->sensesp());
nodep->sensesp()->unlinkFrBack()->deleteTree();
// Get this sentree's trigger scheduler
@ -493,16 +603,12 @@ private:
flp, new AstVarRef{flp, getCreateTriggerSchedulerp(sensesp), VAccess::WRITE},
"trigger"};
triggerMethodp->dtypeSetVoid();
// Add debug info
addEventDebugInfo(triggerMethodp, sensesp);
// Create the co_await
auto* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp};
awaitp->statement(true);
// Relink child statements after the co_await
if (nodep->stmtsp()) {
AstNode::addNext<AstNode, AstNode>(awaitp, nodep->stmtsp()->unlinkFrBackWithNext());
}
nodep->replaceWith(awaitp);
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstNodeAssign* nodep) override {
@ -584,22 +690,14 @@ private:
}
void visit(AstWait* nodep) override {
// Wait on changed events related to the vars in the wait statement
AstSenItem* const senItemsp = varRefpsToSenItemsp(nodep->condp());
AstNode* const condp = nodep->condp()->unlinkFrBack();
FileLine* const flp = nodep->fileline();
AstNode* const stmtsp = nodep->stmtsp();
if (stmtsp) stmtsp->unlinkFrBackWithNext();
FileLine* const flp = nodep->fileline();
if (senItemsp) {
// Put the event control in a while loop with the wait expression as condition
AstNode* const loopp
= new AstWhile{flp, new AstLogNot{flp, condp},
new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr}};
if (stmtsp) loopp->addNext(stmtsp);
nodep->replaceWith(loopp);
} else {
AstNode* const condp = V3Const::constifyEdit(nodep->condp()->unlinkFrBack());
auto* const constp = VN_CAST(condp, Const);
if (constp) {
condp->v3warn(WAITCONST, "Wait statement condition is constant");
auto* constCondp = VN_AS(V3Const::constifyEdit(condp), Const);
if (constCondp->isZero()) {
if (constp->isZero()) {
// We have to await forever instead of simply returning in case we're deep in a
// callstack
auto* const awaitp = new AstCAwait{flp, new AstCStmt{flp, "VlForever{}"}};
@ -607,10 +705,30 @@ private:
nodep->replaceWith(awaitp);
if (stmtsp) VL_DO_DANGLING(stmtsp->deleteTree(), stmtsp);
} else if (stmtsp) {
// Just put the body there
// Just put the statements there
nodep->replaceWith(stmtsp);
}
VL_DO_DANGLING(constCondp->deleteTree(), condp);
VL_DO_DANGLING(condp->deleteTree(), condp);
} else if (needDynamicTrigger(condp)) {
// No point in making a sentree, just use the expression as sensitivity
// Put the event control in an if so we only wait if the condition isn't met already
auto* const ifp = new AstIf{
flp, new AstLogNot{flp, condp},
new AstEventControl{flp,
new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE,
condp->cloneTree(false)}},
nullptr}};
if (stmtsp) AstNode::addNext<AstNode, AstNode>(ifp, stmtsp);
nodep->replaceWith(ifp);
} else {
AstSenItem* const senItemsp = varRefpsToSenItemsp(condp);
UASSERT_OBJ(senItemsp, nodep, "No varrefs in wait statement condition");
// Put the event control in a while loop with the wait expression as condition
auto* const loopp
= new AstWhile{flp, new AstLogNot{flp, condp},
new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr}};
if (stmtsp) AstNode::addNext<AstNode, AstNode>(loopp, stmtsp);
nodep->replaceWith(loopp);
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}

View File

@ -1,17 +0,0 @@
%Error-UNSUPPORTED: t/t_mailbox_class.v:21:25: Unsupported: event controls in methods
: ... In instance $unit::mailbox_cls
21 | if (m_bound != 0) wait (m_q.size() < m_bound);
| ^~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_mailbox_class.v:35:7: Unsupported: event controls in methods
: ... In instance $unit::mailbox_cls
35 | wait (m_q.size() != 0);
| ^~~~
%Error-UNSUPPORTED: t/t_mailbox_class.v:49:7: Unsupported: event controls in methods
: ... In instance $unit::mailbox_cls
49 | wait (m_q.size() != 0);
| ^~~~
%Error-UNSUPPORTED: t/t_mailbox_class.v:21:31: Unsupported: Cannot detect changes on expression of complex type (see combinational cycles reported by UNOPTFLAT)
21 | if (m_bound != 0) wait (m_q.size() < m_bound);
| ^~~
%Error: Exiting due to

View File

@ -10,15 +10,14 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
scenarios(simulator => 1);
compile(
if (!$Self->have_coroutines) {
skip("No coroutine support");
}
else {
compile(
verilator_flags2 => ["--timing"],
fails => $Self->{vlt_all},
expect_filename => $Self->{golden_filename},
);
execute(
check_finished => 1,
) if !$Self->{vlt_all};
}
ok(1);
1;

View File

@ -1,6 +0,0 @@
%Error-UNSUPPORTED: t/t_semaphore_class.v:17:7: Unsupported: event controls in methods
: ... In instance $unit::semaphore_cls
17 | wait (m_keys >= keyCount);
| ^~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -10,15 +10,14 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
scenarios(simulator => 1);
compile(
if (!$Self->have_coroutines) {
skip("No coroutine support");
}
else {
compile(
verilator_flags2 => ["--timing"],
fails => $Self->{vlt_all},
expect_filename => $Self->{golden_filename},
);
execute(
check_finished => 1,
) if !$Self->{vlt_all};
}
ok(1);
1;

View File

@ -15,27 +15,95 @@ module t;
// EVENTS
class EventClass;
event e;
int trig_count;
task sleep; /* @e; */ endtask // Unsupported
task wake; ->e; endtask
function new;
trig_count = 0;
endfunction
task inc_trig_count;
trig_count++;
endtask;
task sleep;
@e inc_trig_count;
`WRITE_VERBOSE(("Event in class triggered at time %0t!\n", $time));
endtask
task wake;
->e;
endtask
endclass
class WaitClass;
int a;
int b;
logic ok;
function new;
a = 0;
b = 0;
ok = 0;
endfunction
task await;
wait(a == 4 && b > 16) if (a != 4 || b <= 16) $stop;
ok = 1;
`WRITE_VERBOSE(("Condition in object met at time %0t!\n", $time));
endtask
endclass
class LocalWaitClass;
logic ok;
function new;
ok = 0;
endfunction
task await;
int a = 0;
int b = 100;
fork
wait(a == 42 || b != 100) if (a != 42 && b == 100) $stop;
#10 a = 42;
join
ok = 1;
`WRITE_VERBOSE(("Condition with local variables met at time %0t!\n", $time));
endtask
endclass
EventClass ec = new;
int event_trig_count = 0;
WaitClass wc = new;
LocalWaitClass lc = new;
initial begin
@ec.e;
ec.sleep;
if (wc.ok) $stop;
wc.await;
if (lc.ok) $stop;
lc.await;
end
initial #25 ec.wake;
initial #50 ->ec.e;
initial #20 ec.wake;
initial #40 ->ec.e;
initial begin
wc.a = #50 4;
wc.b = #10 32;
end
always @ec.e begin
event_trig_count++;
ec.inc_trig_count;
`WRITE_VERBOSE(("Event in class triggered at time %0t!\n", $time));
end
initial begin
#80
if (ec.trig_count != 3) $stop;
if (!wc.ok) $stop;
if (!lc.ok) $stop;
end
// =============================================
// DELAYS
virtual class DelayClass;
@ -120,7 +188,6 @@ module t;
fork #5 dAsgn.x = 0; join_none
dAsgn.do_assign;
if ($time != 80) $stop;
if (event_trig_count != 2) $stop;
if (dAsgn.y != 1) $stop;
// Test if the object is deleted before do_assign finishes:
fork dAsgn.do_assign; join_none

View File

@ -1,6 +0,0 @@
%Error-UNSUPPORTED: t/t_timing_class_unsup.v:10:17: Unsupported: event controls in methods
: ... In instance $unit::EventClass
10 | task sleep; @e; endtask
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -1,12 +0,0 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2022 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
class EventClass;
event e;
task sleep; @e; endtask
task wake; ->e; endtask
endclass

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ if (!$Self->have_coroutines) {
skip("No coroutine support");
}
else {
top_filename("t/t_timing_fork_join.v");
top_filename("t/t_timing_class.v");
compile(
verilator_flags2 => ["--exe --main --timing"],

View File

@ -0,0 +1,6 @@
%Error-UNSUPPORTED: t/t_timing_localevent_unsup.v:12:17: Unsupported: waiting on local event variables
: ... In instance t::Sleeper
12 | @e;
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,24 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2022 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module t;
class Sleeper;
task sleep;
event e;
fork
@e;
#1 ->e;
join;
endtask
endclass
initial begin
Sleeper sleeper = new;
sleeper.sleep;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule