mirror of
https://github.com/verilator/verilator.git
synced 2025-01-01 04:07:34 +00:00
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:
parent
4154584c4b
commit
fcf0d03cd4
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
36
src/V3Ast.h
36
src/V3Ast.h
@ -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; }
|
||||
|
@ -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(); }
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)};
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
@ -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
|
||||
|
186
src/V3Timing.cpp
186
src/V3Timing.cpp
@ -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,26 +525,90 @@ private:
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
void visit(AstEventControl* nodep) override {
|
||||
if (m_classp) nodep->v3warn(E_UNSUPPORTED, "Unsupported: event controls in methods");
|
||||
auto* const sensesp = m_finder.getSenTree(nodep->sensesp());
|
||||
nodep->sensesp()->unlinkFrBack()->deleteTree();
|
||||
// Get this sentree's trigger scheduler
|
||||
FileLine* const flp = nodep->fileline();
|
||||
// Replace self with a 'co_await trigSched.trigger()'
|
||||
auto* const triggerMethodp = new AstCMethodHard{
|
||||
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());
|
||||
// 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
|
||||
FileLine* const flp = nodep->fileline();
|
||||
// Replace self with a 'co_await trigSched.trigger()'
|
||||
auto* const triggerMethodp = new AstCMethodHard{
|
||||
flp, new AstVarRef{flp, getCreateTriggerSchedulerp(sensesp), VAccess::WRITE},
|
||||
"trigger"};
|
||||
triggerMethodp->dtypeSetVoid();
|
||||
addEventDebugInfo(triggerMethodp, sensesp);
|
||||
// Create the co_await
|
||||
auto* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp};
|
||||
awaitp->statement(true);
|
||||
nodep->replaceWith(awaitp);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
@ -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
|
@ -10,15 +10,14 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--timing"],
|
||||
fails => $Self->{vlt_all},
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
) if !$Self->{vlt_all};
|
||||
if (!$Self->have_coroutines) {
|
||||
skip("No coroutine support");
|
||||
}
|
||||
else {
|
||||
compile(
|
||||
verilator_flags2 => ["--timing"],
|
||||
);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
@ -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
|
@ -10,15 +10,14 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
|
||||
|
||||
scenarios(simulator => 1);
|
||||
|
||||
compile(
|
||||
verilator_flags2 => ["--timing"],
|
||||
fails => $Self->{vlt_all},
|
||||
expect_filename => $Self->{golden_filename},
|
||||
);
|
||||
|
||||
execute(
|
||||
check_finished => 1,
|
||||
) if !$Self->{vlt_all};
|
||||
if (!$Self->have_coroutines) {
|
||||
skip("No coroutine support");
|
||||
}
|
||||
else {
|
||||
compile(
|
||||
verilator_flags2 => ["--timing"],
|
||||
);
|
||||
}
|
||||
|
||||
ok(1);
|
||||
1;
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
@ -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"],
|
||||
|
6
test_regress/t/t_timing_localevent_unsup.out
Normal file
6
test_regress/t/t_timing_localevent_unsup.out
Normal 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
|
24
test_regress/t/t_timing_localevent_unsup.v
Normal file
24
test_regress/t/t_timing_localevent_unsup.v
Normal 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
|
Loading…
Reference in New Issue
Block a user