Fix event triggering (#6932)
This commit is contained in:
parent
e41436bd4a
commit
446bec3d1a
|
|
@ -593,15 +593,17 @@ object.
|
|||
|
||||
This class manages processes that await events (triggers). There is one
|
||||
such object per each trigger awaited by coroutines. Coroutines ``co_await``
|
||||
this object's ``trigger`` function. They are stored in two stages -
|
||||
`uncommitted` and `ready`. First, they land in the `uncommitted` stage, and
|
||||
cannot be resumed. The ``resume`` function resumes all coroutines from the
|
||||
`ready` stage and moves `uncommitted` coroutines into `ready`. The
|
||||
``commit`` function only moves `uncommitted` coroutines into `ready`.
|
||||
this object's ``trigger`` function. They are stored in three stages -
|
||||
`awaiting`, `fired` and `toResume`. First, they land in the `awaiting` stage, and
|
||||
cannot be resumed. The ``ready`` function moves all coroutines from the
|
||||
`awaiting` stage into the `fired` stage. The ``moveToResumeQueue`` function moves
|
||||
`fired` coroutines into `toResume`. Finally, function `resume` resumes
|
||||
all coroutines from the `toResume` stage.
|
||||
|
||||
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.
|
||||
This split is done to avoid self-triggering, triggering coroutines
|
||||
multiple times and triggering coroutines in the same iteration
|
||||
they were suspended. See the `Scheduling with timing` section
|
||||
for details on how this is used.
|
||||
|
||||
``VlDynamicTriggerScheduler``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -719,16 +721,35 @@ in that process. When ordering code using ``V3Order``, these triggers are
|
|||
provided as external domains of these variables. This ensures that the
|
||||
necessary combinational logic is triggered after a coroutine resumption.
|
||||
|
||||
Every call to a `VlTriggerScheduler`'s `trigger()` method is preempt by
|
||||
a call to a proper `__VbeforeTrig` function which evaluates all the necessary
|
||||
triggers so, the information about order of suspension/resumption is not lost.
|
||||
The triggers necessary to evaluate are ones dependent on the same events
|
||||
as the `trigger()` - e.g.: if `triggers()` awaits for event `a` or `b`, then
|
||||
every trigger that depends on any of those shall be evaluated. If they wouldn't
|
||||
be evaluated and next coroutine after resumption would fire the event `a` then
|
||||
it is impossible to get to know whether await or fire on event `a` was called
|
||||
first - which is necessary to know.
|
||||
|
||||
There are two functions for managing timing logic called by ``_eval()``:
|
||||
|
||||
* ``_timing_commit()``, which commits all coroutines whose triggers were
|
||||
* ``_timing_ready()``, which commits all coroutines whose triggers were
|
||||
not set in the current iteration,
|
||||
* ``_timing_resume()``, which calls `resume()` on all trigger and delay
|
||||
schedulers whose triggers were set in the current iteration.
|
||||
|
||||
Thanks to this separation, a coroutine awaiting a trigger cannot be
|
||||
suspended and resumed in the same iteration, and it cannot be resumed
|
||||
before it suspends.
|
||||
Thanks to this separation a coroutine:
|
||||
* awaiting a trigger cannot be suspended and resumed in the same iteration
|
||||
(``test_regress/t/t_timing_eval_act.v``) - which is necessary to make
|
||||
Verilator more predictable; this is the reason for introduction of 3rd stage
|
||||
in `VlTriggerScheduler` and thanks to this it is guaranteed that downstream
|
||||
logic will be evaluated before resumption (assuming that the coroutine wasn't
|
||||
already triggered in previous iteration);
|
||||
* cannot be resumed before it is suspended -
|
||||
``test_regress/t/t_event_control_double_excessive.v``;
|
||||
* firing cannot cannot be lost
|
||||
(``test_regress/t/t_event_control_double_lost.v``) - which is possible when
|
||||
triggers are not evaluated right before awaiting.
|
||||
|
||||
All coroutines are committed and resumed in the 'act' eval loop. With
|
||||
timing features enabled, the ``_eval()`` function takes this form:
|
||||
|
|
|
|||
|
|
@ -127,44 +127,65 @@ void VlTriggerScheduler::resume(const char* eventDescription) {
|
|||
VL_DEBUG_IF(dump(eventDescription);
|
||||
VL_DBG_MSGF(" Resuming processes waiting for %s\n", eventDescription););
|
||||
#endif
|
||||
std::swap(m_ready, m_resumeQueue);
|
||||
for (VlCoroutineHandle& coro : m_resumeQueue) coro.resume();
|
||||
m_resumeQueue.clear();
|
||||
commit(eventDescription);
|
||||
for (VlCoroutineHandle& coro : m_toResume) coro.resume();
|
||||
m_toResume.clear();
|
||||
}
|
||||
|
||||
void VlTriggerScheduler::commit(const char* eventDescription) {
|
||||
void VlTriggerScheduler::moveToResumeQueue(const char* eventDescription) {
|
||||
#ifdef VL_DEBUG
|
||||
if (!m_uncommitted.empty()) {
|
||||
if (!m_fired.empty()) {
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" Moving to resume queue processes waiting for %s:\n",
|
||||
eventDescription);
|
||||
for (const auto& susp
|
||||
: m_fired) {
|
||||
VL_DBG_MSGF(" - ");
|
||||
susp.dump();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
std::swap(m_fired, m_toResume);
|
||||
}
|
||||
|
||||
void VlTriggerScheduler::ready(const char* eventDescription) {
|
||||
#ifdef VL_DEBUG
|
||||
if (!m_awaiting.empty()) {
|
||||
VL_DEBUG_IF(
|
||||
VL_DBG_MSGF(" Committing processes waiting for %s:\n", eventDescription);
|
||||
for (const auto& susp
|
||||
: m_uncommitted) {
|
||||
: m_awaiting) {
|
||||
VL_DBG_MSGF(" - ");
|
||||
susp.dump();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
m_ready.reserve(m_ready.size() + m_uncommitted.size());
|
||||
m_ready.insert(m_ready.end(), std::make_move_iterator(m_uncommitted.begin()),
|
||||
std::make_move_iterator(m_uncommitted.end()));
|
||||
m_uncommitted.clear();
|
||||
const size_t expectedSize = m_fired.size() + m_awaiting.size();
|
||||
if (m_fired.capacity() < expectedSize) m_fired.reserve(expectedSize * 2);
|
||||
m_fired.insert(m_fired.end(), std::make_move_iterator(m_awaiting.begin()),
|
||||
std::make_move_iterator(m_awaiting.end()));
|
||||
m_awaiting.clear();
|
||||
}
|
||||
|
||||
#ifdef VL_DEBUG
|
||||
void VlTriggerScheduler::dump(const char* eventDescription) const {
|
||||
if (m_ready.empty()) {
|
||||
VL_DBG_MSGF(" No ready processes waiting for %s\n", eventDescription);
|
||||
if (m_toResume.empty()) {
|
||||
VL_DBG_MSGF(" No process to resume waiting for %s\n", eventDescription);
|
||||
} else {
|
||||
for (const auto& susp : m_ready) {
|
||||
VL_DBG_MSGF(" Ready processes waiting for %s:\n", eventDescription);
|
||||
for (const auto& susp : m_toResume) {
|
||||
VL_DBG_MSGF(" Processes to resume waiting for %s:\n", eventDescription);
|
||||
VL_DBG_MSGF(" - ");
|
||||
susp.dump();
|
||||
}
|
||||
}
|
||||
if (!m_uncommitted.empty()) {
|
||||
VL_DBG_MSGF(" Uncommitted processes waiting for %s:\n", eventDescription);
|
||||
for (const auto& susp : m_uncommitted) {
|
||||
if (!m_fired.empty()) {
|
||||
VL_DBG_MSGF(" Triggered processes waiting for %s:\n", eventDescription);
|
||||
for (const auto& susp : m_awaiting) {
|
||||
VL_DBG_MSGF(" - ");
|
||||
susp.dump();
|
||||
}
|
||||
}
|
||||
if (!m_awaiting.empty()) {
|
||||
VL_DBG_MSGF(" Not triggered processes waiting for %s:\n", eventDescription);
|
||||
for (const auto& susp : m_awaiting) {
|
||||
VL_DBG_MSGF(" - ");
|
||||
susp.dump();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -233,38 +233,40 @@ public:
|
|||
|
||||
//=============================================================================
|
||||
// VlTriggerScheduler stores coroutines to be resumed by a trigger. It does not keep track of its
|
||||
// trigger, relying on calling code to resume when appropriate. Coroutines are kept in two stages
|
||||
// - 'uncommitted' and 'ready'. Whenever a coroutine is suspended, it lands in the 'uncommitted'
|
||||
// stage. Only when commit() is called, these coroutines get moved to the 'ready' stage. That's
|
||||
// when they can be resumed. This is done to avoid resuming processes before they start waiting.
|
||||
// trigger, relying on calling code to resume when appropriate. Coroutines are kept in three stages
|
||||
// - 'awaiting', 'fired' and 'toResume'. Whenever a coroutine is suspended, it lands in the
|
||||
// 'awaiting' stage. Only when ready() is called, these coroutines get moved to the 'fired' stage.
|
||||
// When moveToResumeQueue() is begin called all coroutines from 'ready' are moved to 'toResume'.
|
||||
// That's when they can be resumed. This is done to avoid resuming processes before they start
|
||||
// waiting.
|
||||
|
||||
class VlTriggerScheduler final {
|
||||
// TYPES
|
||||
using VlCoroutineVec = std::vector<VlCoroutineHandle>;
|
||||
|
||||
// MEMBERS
|
||||
VlCoroutineVec m_uncommitted; // Coroutines suspended before commit() was called
|
||||
// (not resumable)
|
||||
VlCoroutineVec m_ready; // Coroutines that can be resumed (all coros from m_uncommitted are
|
||||
// moved here in commit())
|
||||
VlCoroutineVec m_resumeQueue; // Coroutines being resumed by resume(); kept as a field to
|
||||
// avoid reallocation. Resumed coroutines are moved to
|
||||
// m_resumeQueue to allow adding coroutines to m_ready
|
||||
// during resume(). Outside of resume() should always be empty.
|
||||
VlCoroutineVec m_awaiting; // Coroutines suspended before ready() was called
|
||||
// (not resumable)
|
||||
VlCoroutineVec m_fired; // Coroutines that were triggered (all coros from m_awaiting are moved
|
||||
// here in ready())
|
||||
VlCoroutineVec m_toResume; // Coroutines to resume in next resumePrep()
|
||||
// - moved here in commit()
|
||||
|
||||
public:
|
||||
// METHODS
|
||||
// Resumes all coroutines from the 'ready' stage
|
||||
// Resumes all coroutines from the m_toResume
|
||||
void resume(const char* eventDescription = VL_UNKNOWN);
|
||||
// Moves all coroutines from m_uncommitted to m_ready
|
||||
void commit(const char* eventDescription = VL_UNKNOWN);
|
||||
// Moves all coroutines from m_fired to m_toResume
|
||||
void moveToResumeQueue(const char* eventDescription = VL_UNKNOWN);
|
||||
// Moves all coroutines from m_awaiting to m_fired
|
||||
void ready(const char* eventDescription = VL_UNKNOWN);
|
||||
// Are there no coroutines awaiting?
|
||||
bool empty() const { return m_ready.empty() && m_uncommitted.empty(); }
|
||||
bool empty() const { return m_fired.empty() && m_awaiting.empty(); }
|
||||
#ifdef VL_DEBUG
|
||||
void dump(const char* eventDescription) const;
|
||||
#endif
|
||||
// Used by coroutines for co_awaiting a certain trigger
|
||||
auto trigger(bool commit, VlProcessRef process, const char* eventDescription = VL_UNKNOWN,
|
||||
auto trigger(bool ready, VlProcessRef process, const char* eventDescription = VL_UNKNOWN,
|
||||
const char* filename = VL_UNKNOWN, int lineno = 0) {
|
||||
VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n",
|
||||
eventDescription, filename, lineno););
|
||||
|
|
@ -279,8 +281,7 @@ public:
|
|||
}
|
||||
void await_resume() const {}
|
||||
};
|
||||
return Awaitable{commit ? m_ready : m_uncommitted, process,
|
||||
VlFileLineDebug{filename, lineno}};
|
||||
return Awaitable{ready ? m_fired : m_awaiting, process, VlFileLineDebug{filename, lineno}};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -820,7 +820,9 @@ public:
|
|||
RNG_SET_RANDSTATE,
|
||||
SCHED_ANY_TRIGGERED,
|
||||
SCHED_AWAITING_CURRENT_TIME,
|
||||
SCHED_READY,
|
||||
SCHED_COMMIT,
|
||||
SCHED_MOVE_TO_RESUME_QUEUE,
|
||||
SCHED_DELAY,
|
||||
SCHED_DO_POST_UPDATES,
|
||||
SCHED_ENQUEUE,
|
||||
|
|
@ -950,7 +952,9 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
|
|||
{RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false}, \
|
||||
{SCHED_ANY_TRIGGERED, "anyTriggered", false}, \
|
||||
{SCHED_AWAITING_CURRENT_TIME, "awaitingCurrentTime", true}, \
|
||||
{SCHED_READY, "ready", false}, \
|
||||
{SCHED_COMMIT, "commit", false}, \
|
||||
{SCHED_MOVE_TO_RESUME_QUEUE, "moveToResumeQueue", false}, \
|
||||
{SCHED_DELAY, "delay", false}, \
|
||||
{SCHED_DO_POST_UPDATES, "doPostUpdates", false}, \
|
||||
{SCHED_ENQUEUE, "enqueue", false}, \
|
||||
|
|
|
|||
|
|
@ -421,7 +421,7 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde
|
|||
// Gather the relevant sensitivity expressions and create the trigger kit
|
||||
const auto& senTreeps = getSenTreesUsedBy({&comb, &hybrid});
|
||||
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBulider, {},
|
||||
senTreeps, "stl", extraTriggers, true);
|
||||
senTreeps, "stl", extraTriggers, true, false);
|
||||
|
||||
// Remap sensitivities (comb has none, so only do the hybrid)
|
||||
remapSensitivities(hybrid, trigKit.mapVec());
|
||||
|
|
@ -446,7 +446,11 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde
|
|||
// Inner loop statements
|
||||
nullptr,
|
||||
// Prep statements: Compute the current 'stl' triggers
|
||||
trigKit.newCompCall(),
|
||||
[&trigKit] {
|
||||
AstNodeStmt* const stmtp = trigKit.newCompBaseCall();
|
||||
if (stmtp) stmtp->addNext(trigKit.newDumpCall(trigKit.vscp(), trigKit.name(), true));
|
||||
return stmtp;
|
||||
}(),
|
||||
// Work statements: Invoke the 'stl' function
|
||||
util::callVoidFunc(stlFuncp));
|
||||
|
||||
|
|
@ -499,7 +503,8 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
|||
// Gather the relevant sensitivity expressions and create the trigger kit
|
||||
const auto& senTreeps = getSenTreesUsedBy({&logic});
|
||||
const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {},
|
||||
senTreeps, "ico", extraTriggers, false);
|
||||
senTreeps, "ico", extraTriggers, false, false);
|
||||
std::ignore = senExprBuilder.getAndClearResults();
|
||||
|
||||
if (dpiExportTriggerVscp) {
|
||||
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
|
||||
|
|
@ -549,7 +554,11 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
|||
// Inner loop statements
|
||||
nullptr,
|
||||
// Prep statements: Compute the current 'ico' triggers
|
||||
trigKit.newCompCall(),
|
||||
[&trigKit] {
|
||||
AstNodeStmt* const stmtp = trigKit.newCompBaseCall();
|
||||
if (stmtp) stmtp->addNext(trigKit.newDumpCall(trigKit.vscp(), trigKit.name(), true));
|
||||
return stmtp;
|
||||
}(),
|
||||
// Work statements: Invoke the 'ico' function
|
||||
util::callVoidFunc(icoFuncp));
|
||||
|
||||
|
|
@ -586,8 +595,8 @@ void createEval(AstNetlist* netlistp, //
|
|||
) {
|
||||
FileLine* const flp = netlistp->fileline();
|
||||
|
||||
// 'createResume' consumes the contents that 'createCommit' needs, so do the right order
|
||||
AstCCall* const timingCommitp = timingKit.createCommit(netlistp);
|
||||
// 'createResume' consumes the contents that 'createReady' needs, so do the right order
|
||||
AstCCall* const timingReadyp = timingKit.createReady(netlistp);
|
||||
AstCCall* const timingResumep = timingKit.createResume(netlistp);
|
||||
|
||||
// Create the active eval loop
|
||||
|
|
@ -598,9 +607,16 @@ void createEval(AstNetlist* netlistp, //
|
|||
// Prep statements
|
||||
[&]() {
|
||||
// Compute the current 'act' triggers - the NBA triggers are the latched value
|
||||
AstNodeStmt* stmtsp = trigKit.newCompCall(nbaKit.m_vscp);
|
||||
// Commit trigger awaits from the previous iteration
|
||||
if (timingCommitp) stmtsp = AstNode::addNext(stmtsp, timingCommitp->makeStmt());
|
||||
AstNodeStmt* stmtsp = trigKit.newCompBaseCall();
|
||||
AstNodeStmt* const dumpp
|
||||
= stmtsp ? trigKit.newDumpCall(trigKit.vscp(), trigKit.name(), true) : nullptr;
|
||||
// Mark as ready for triggered awaits
|
||||
if (timingReadyp) stmtsp = AstNode::addNext(stmtsp, timingReadyp->makeStmt());
|
||||
if (AstVarScope* const vscAccp = trigKit.vscAccp()) {
|
||||
stmtsp = AstNode::addNext(stmtsp, trigKit.newOrIntoCall(actKit.m_vscp, vscAccp));
|
||||
}
|
||||
stmtsp = AstNode::addNext(stmtsp, trigKit.newCompExtCall(nbaKit.m_vscp));
|
||||
stmtsp = AstNode::addNext(stmtsp, dumpp);
|
||||
// Latch the 'act' triggers under the 'nba' triggers
|
||||
stmtsp = AstNode::addNext(stmtsp, trigKit.newOrIntoCall(nbaKit.m_vscp, actKit.m_vscp));
|
||||
//
|
||||
|
|
@ -609,8 +625,15 @@ void createEval(AstNetlist* netlistp, //
|
|||
// Work statements
|
||||
[&]() {
|
||||
AstNodeStmt* workp = nullptr;
|
||||
if (AstVarScope* const actAccp = trigKit.vscAccp()) {
|
||||
AstCMethodHard* const cCallp = new AstCMethodHard{
|
||||
flp, new AstVarRef{flp, actAccp, VAccess::WRITE}, VCMethod::UNPACKED_FILL,
|
||||
new AstConst{flp, AstConst::Unsized64{}, 0}};
|
||||
cCallp->dtypeSetVoid();
|
||||
workp = AstNode::addNext(workp, cCallp->makeStmt());
|
||||
}
|
||||
// Resume triggered timing schedulers
|
||||
if (timingResumep) workp = timingResumep->makeStmt();
|
||||
if (timingResumep) workp = AstNode::addNext(workp, timingResumep->makeStmt());
|
||||
// Invoke the 'act' function
|
||||
workp = AstNode::addNext(workp, util::callVoidFunc(actKit.m_funcp));
|
||||
//
|
||||
|
|
@ -650,7 +673,7 @@ void createEval(AstNetlist* netlistp, //
|
|||
netlistp->nbaEventp(nullptr);
|
||||
netlistp->nbaEventTriggerp(nullptr);
|
||||
|
||||
// If a dynamic NBA is pending, clear the pending flag and fire the commit event
|
||||
// If a dynamic NBA is pending, clear the pending flag and fire the ready event
|
||||
AstIf* const ifp = new AstIf{flp, new AstVarRef{flp, nbaEventTriggerp, VAccess::READ}};
|
||||
ifp->addThensp(util::setVar(continuep, 1));
|
||||
ifp->addThensp(util::setVar(nbaEventTriggerp, 0));
|
||||
|
|
@ -867,11 +890,12 @@ void schedule(AstNetlist* netlistp) {
|
|||
&logicRegions.m_obs, //
|
||||
&logicRegions.m_react, //
|
||||
&timingKit.m_lbs});
|
||||
const TriggerKit trigKit = TriggerKit::create(netlistp, staticp, senExprBuilder, preTreeps,
|
||||
senTreeps, "act", extraTriggers, false);
|
||||
const TriggerKit trigKit
|
||||
= TriggerKit::create(netlistp, staticp, senExprBuilder, preTreeps, senTreeps, "act",
|
||||
extraTriggers, false, v3Global.usesTiming());
|
||||
|
||||
// Add post updates from the timing kit
|
||||
if (timingKit.m_postUpdates) trigKit.compp()->addStmtsp(timingKit.m_postUpdates);
|
||||
if (timingKit.m_postUpdates) trigKit.compBasep()->addStmtsp(timingKit.m_postUpdates);
|
||||
|
||||
if (dpiExportTriggerVscp) {
|
||||
trigKit.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
|
||||
|
|
@ -996,7 +1020,44 @@ void schedule(AstNetlist* netlistp) {
|
|||
createEval(netlistp, icoLoopp, trigKit, actKit, nbaKit, obsKit, reactKit, postponedFuncp,
|
||||
timingKit);
|
||||
|
||||
// Step 15: Clean up
|
||||
// Step 15: Add neccessary evaluation before awaits
|
||||
if (AstCCall* const readyp = timingKit.createReady(netlistp)) {
|
||||
staticp->addStmtsp(readyp->makeStmt());
|
||||
beforeTrigVisitor(netlistp, senExprBuilder, trigKit);
|
||||
} else {
|
||||
// beforeTrigVisitor clears Sentree pointers in AstCAwaits (as these sentrees will get
|
||||
// deleted later) if there was no need to call it, SenTrees have to be cleaned manually
|
||||
netlistp->foreach([](AstCAwait* const cAwaitp) { cAwaitp->clearSentreep(); });
|
||||
}
|
||||
if (AstVarScope* const trigAccp = trigKit.vscAccp()) {
|
||||
// Copy trigger vector to accumulator at the end of static initialziation so,
|
||||
// triggers fired during initialization persist to the first resume.
|
||||
const AstUnpackArrayDType* const trigAccDTypep
|
||||
= VN_AS(trigAccp->dtypep(), UnpackArrayDType);
|
||||
UASSERT_OBJ(
|
||||
trigAccDTypep->right() == 0, trigAccp,
|
||||
"Expected that trigger vector and accumulator start elements enumeration from 0");
|
||||
UASSERT_OBJ(trigAccDTypep->left() >= 0, trigAccp,
|
||||
"Expected that trigger vector and accumulator has no negative indexes");
|
||||
FileLine* const flp = trigAccp->fileline();
|
||||
AstVarScope* const vscp = netlistp->topScopep()->scopep()->createTemp("__Vi", 32);
|
||||
AstLoop* const loopp = new AstLoop{flp};
|
||||
loopp->addStmtsp(
|
||||
new AstAssign{flp,
|
||||
new AstArraySel{flp, new AstVarRef{flp, trigAccp, VAccess::WRITE},
|
||||
new AstVarRef{flp, vscp, VAccess::READ}},
|
||||
new AstArraySel{flp, new AstVarRef{flp, actKit.m_vscp, VAccess::READ},
|
||||
new AstVarRef{flp, vscp, VAccess::READ}}});
|
||||
loopp->addStmtsp(util::incrementVar(vscp));
|
||||
loopp->addStmtsp(new AstLoopTest{
|
||||
flp, loopp,
|
||||
new AstLte{flp, new AstVarRef{flp, vscp, VAccess::READ},
|
||||
new AstConst{flp, AstConst::WidthedValue{}, 32,
|
||||
static_cast<uint32_t>(trigAccDTypep->left())}}});
|
||||
staticp->addStmtsp(loopp);
|
||||
}
|
||||
|
||||
// Step 16: Clean up
|
||||
netlistp->clearStlFirstIterationp();
|
||||
|
||||
// Haven't split static initializer yet
|
||||
|
|
|
|||
|
|
@ -184,10 +184,12 @@ struct LogicReplicas final {
|
|||
// "Extra" triggers, with "Sense" triggers taking up the bulk of the bits.
|
||||
//
|
||||
class TriggerKit final {
|
||||
public:
|
||||
// Triggers are storead as an UnpackedArray with a fixed word size
|
||||
static constexpr uint32_t WORD_SIZE_LOG2 = 6; // 64-bits / VL_QUADSIZE
|
||||
static constexpr uint32_t WORD_SIZE = 1 << WORD_SIZE_LOG2;
|
||||
|
||||
private:
|
||||
const std::string m_name; // TriggerKit name
|
||||
const bool m_slow; // TriggerKit is for schedulign 'slow' code
|
||||
const uint32_t m_nSenseWords; // Number of words for Sense triggers
|
||||
|
|
@ -195,6 +197,8 @@ class TriggerKit final {
|
|||
const uint32_t m_nPreWords; // Number of words for 'pre' part
|
||||
const uint32_t m_nVecWords = m_nSenseWords + m_nExtraWords; // Number of words in 'vec' part
|
||||
|
||||
// SenItems to corresponding bit indexes
|
||||
std::unordered_map<VNRef<const AstSenItem>, size_t> m_senItem2TrigIdx;
|
||||
// Data type of a single trigger word
|
||||
AstNodeDType* m_wordDTypep = nullptr;
|
||||
// Data type of a trigger vector holding one copy of all triggers
|
||||
|
|
@ -204,8 +208,14 @@ class TriggerKit final {
|
|||
AstUnpackArrayDType* m_trigExtDTypep = nullptr;
|
||||
// The AstVarScope representing the extended trigger vector
|
||||
AstVarScope* m_vscp = nullptr;
|
||||
// The AstCFunc that computes the current active triggers
|
||||
AstCFunc* m_compp = nullptr;
|
||||
// The AstVarScope representing the trigger accumulator vector
|
||||
// It is used to accumulate triggers that were found fired and cleared in beforeTrigger's
|
||||
// in current 'act' region iteration
|
||||
AstVarScope* m_vscAccp = nullptr;
|
||||
// The AstCFunc that computes the current active base triggers
|
||||
AstCFunc* m_compVecp = nullptr;
|
||||
// The AstCFunc that computes the current active extended triggers
|
||||
AstCFunc* m_compExtp = nullptr;
|
||||
// The AstCFunc that dumps a trigger vector
|
||||
AstCFunc* m_dumpp = nullptr;
|
||||
// The AstCFunc that dumps an exended trigger vector - create lazily
|
||||
|
|
@ -214,8 +224,7 @@ class TriggerKit final {
|
|||
mutable AstCFunc* m_anySetVecp = nullptr;
|
||||
mutable AstCFunc* m_anySetExtp = nullptr;
|
||||
// The AstCFunc setting bits in a trigger vector that are set in another - create lazily
|
||||
mutable AstCFunc* m_orIntoVecp = nullptr;
|
||||
mutable AstCFunc* m_orIntoExtp = nullptr;
|
||||
mutable std::array<AstCFunc*, 4> m_orIntoVecps = {nullptr};
|
||||
// The AstCFunc setting a trigger vector to all zeroes - create lazily
|
||||
mutable AstCFunc* m_clearp = nullptr;
|
||||
|
||||
|
|
@ -228,13 +237,15 @@ class TriggerKit final {
|
|||
AstCFunc* createDumpExtFunc() const;
|
||||
AstCFunc* createAnySetFunc(AstUnpackArrayDType* const dtypep) const;
|
||||
AstCFunc* createClearFunc() const;
|
||||
AstCFunc* createOrIntoFunc(AstUnpackArrayDType* const iDtypep) const;
|
||||
AstCFunc* createOrIntoFunc(AstUnpackArrayDType* const oDtypep,
|
||||
AstUnpackArrayDType* const iDtypep) const;
|
||||
|
||||
// Create an AstSenTree that is sensitive to the given trigger indices
|
||||
AstSenTree* newTriggerSenTree(AstVarScope* vscp, const std::vector<uint32_t>& indices) const;
|
||||
|
||||
TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords, uint32_t nExtraWords,
|
||||
uint32_t nPreWords);
|
||||
uint32_t nPreWords,
|
||||
std::unordered_map<VNRef<const AstSenItem>, size_t> senItem2TrigIdx, bool useAcc);
|
||||
VL_UNCOPYABLE(TriggerKit);
|
||||
TriggerKit& operator=(TriggerKit&&) = delete;
|
||||
|
||||
|
|
@ -258,6 +269,9 @@ public:
|
|||
}
|
||||
uint32_t size() const { return m_descriptions.size(); }
|
||||
};
|
||||
// Generates list of assignments that fills
|
||||
static AstAssign* createSenTrigVecAssignment(AstVarScope* const target,
|
||||
std::vector<AstNodeExpr*>& trigps);
|
||||
|
||||
// Create a TriggerKit for the given AstSenTree vector
|
||||
static TriggerKit create(AstNetlist* netlistp, //
|
||||
|
|
@ -267,11 +281,17 @@ public:
|
|||
const std::vector<const AstSenTree*>& senTreeps, //
|
||||
const string& name, //
|
||||
const ExtraTriggers& extraTriggers, //
|
||||
bool slow);
|
||||
bool slow, //
|
||||
bool useAcc);
|
||||
|
||||
// ACCESSORS
|
||||
AstVarScope* vscp() const { return m_vscp; }
|
||||
AstCFunc* compp() const { return m_compp; }
|
||||
AstVarScope* vscAccp() const { return m_vscAccp; }
|
||||
size_t senItem2TrigIdx(const AstSenItem* senItemp) const {
|
||||
return m_senItem2TrigIdx.at(*senItemp);
|
||||
}
|
||||
AstCFunc* compBasep() const { return m_compVecp; }
|
||||
const std::string& name() const { return m_name; }
|
||||
const std::unordered_map<const AstSenTree*, AstSenTree*>& mapPre() const { return m_mapPre; }
|
||||
const std::unordered_map<const AstSenTree*, AstSenTree*>& mapVec() const { return m_mapVec; }
|
||||
|
||||
|
|
@ -281,7 +301,8 @@ public:
|
|||
AstNodeStmt* newClearCall(AstVarScope* vscp) const;
|
||||
AstNodeStmt* newOrIntoCall(AstVarScope* op, AstVarScope* ip) const;
|
||||
// Helpers for code generation
|
||||
AstNodeStmt* newCompCall(AstVarScope* vscp = nullptr) const;
|
||||
AstNodeStmt* newCompBaseCall() const;
|
||||
AstNodeStmt* newCompExtCall(AstVarScope* vscp) const;
|
||||
AstNodeStmt* newDumpCall(AstVarScope* vscp, const std::string& tag, bool debugOnly) const;
|
||||
// Create a new (non-extended) trigger vector - might return nullptr if there are no triggers
|
||||
AstVarScope* newTrigVec(const std::string& name) const;
|
||||
|
|
@ -296,7 +317,7 @@ public:
|
|||
// Everything needed for combining timing with static scheduling.
|
||||
class TimingKit final {
|
||||
AstCFunc* m_resumeFuncp = nullptr; // Global timing resume function
|
||||
AstCFunc* m_commitFuncp = nullptr; // Global timing commit function
|
||||
AstCFunc* m_readyFuncp = nullptr; // Global timing ready function
|
||||
|
||||
// Additional var sensitivities for V3Order
|
||||
std::map<const AstVarScope*, std::set<AstSenTree*>> m_externalDomains;
|
||||
|
|
@ -310,8 +331,8 @@ public:
|
|||
const std::unordered_map<const AstSenTree*, AstSenTree*>& trigMap) const VL_MT_DISABLED;
|
||||
// Creates a timing resume call (if needed, else returns null)
|
||||
AstCCall* createResume(AstNetlist* const netlistp) VL_MT_DISABLED;
|
||||
// Creates a timing commit call (if needed, else returns null)
|
||||
AstCCall* createCommit(AstNetlist* const netlistp) VL_MT_DISABLED;
|
||||
// Creates a timing ready call (if needed, else returns null)
|
||||
AstCCall* createReady(AstNetlist* const netlistp) VL_MT_DISABLED;
|
||||
|
||||
TimingKit() = default;
|
||||
TimingKit(LogicByScope&& lbs, AstNodeStmt* postUpdates,
|
||||
|
|
@ -403,6 +424,9 @@ void splitCheck(AstCFunc* ofuncp);
|
|||
AstIf* createIfFromSenTree(AstSenTree* senTreep);
|
||||
} // namespace util
|
||||
|
||||
void beforeTrigVisitor(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
|
||||
const TriggerKit& trigKit);
|
||||
|
||||
} // namespace V3Sched
|
||||
|
||||
#endif // Guard
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
//
|
||||
// Functions defined in this file are used by V3Sched.cpp to properly integrate
|
||||
// static scheduling with timing features. They create external domains for
|
||||
// variables, remap them to trigger vectors, and create timing resume/commit
|
||||
// variables, remap them to trigger vectors, and create timing resume/ready
|
||||
// calls for the global eval loop. There is also a function that transforms
|
||||
// forks into emittable constructs.
|
||||
//
|
||||
|
|
@ -68,6 +68,24 @@ AstCCall* TimingKit::createResume(AstNetlist* const netlistp) {
|
|||
m_resumeFuncp->declPrivate(true);
|
||||
scopeTopp->addBlocksp(m_resumeFuncp);
|
||||
|
||||
for (const auto& p : m_lbs) {
|
||||
AstActive* const activep = p.second;
|
||||
activep->foreach([this](AstCMethodHard* const exprp) {
|
||||
if (exprp->method() != VCMethod::SCHED_RESUME) return;
|
||||
AstNodeExpr* const fromp = exprp->fromp();
|
||||
if (VN_AS(fromp->dtypep(), BasicDType)->keyword()
|
||||
!= VBasicDTypeKwd::TRIGGER_SCHEDULER) {
|
||||
return;
|
||||
}
|
||||
AstCMethodHard* const moveToResumep = new AstCMethodHard{
|
||||
fromp->fileline(), fromp->cloneTree(false),
|
||||
VCMethod::SCHED_MOVE_TO_RESUME_QUEUE,
|
||||
exprp->pinsp() ? exprp->pinsp()->cloneTree(true) : nullptr};
|
||||
moveToResumep->dtypeSetVoid();
|
||||
m_resumeFuncp->addStmtsp(moveToResumep->makeStmt());
|
||||
});
|
||||
}
|
||||
|
||||
// Put all the timing actives in the resume function
|
||||
AstIf* dlyShedIfp = nullptr;
|
||||
for (auto& p : m_lbs) {
|
||||
|
|
@ -77,13 +95,12 @@ AstCCall* TimingKit::createResume(AstNetlist* const netlistp) {
|
|||
AstVarRef* const schedrefp = VN_AS(
|
||||
VN_AS(VN_AS(activep->stmtsp(), StmtExpr)->exprp(), CMethodHard)->fromp(), VarRef);
|
||||
|
||||
AstIf* const ifp = V3Sched::util::createIfFromSenTree(activep->sentreep());
|
||||
ifp->addThensp(activep->stmtsp()->unlinkFrBackWithNext());
|
||||
|
||||
AstNode* const actionp = activep->stmtsp()->unlinkFrBackWithNext();
|
||||
if (schedrefp->varScopep()->dtypep()->basicp()->isDelayScheduler()) {
|
||||
dlyShedIfp = ifp;
|
||||
dlyShedIfp = V3Sched::util::createIfFromSenTree(activep->sentreep());
|
||||
dlyShedIfp->addThensp(actionp);
|
||||
} else {
|
||||
m_resumeFuncp->addStmtsp(ifp);
|
||||
m_resumeFuncp->addStmtsp(actionp);
|
||||
}
|
||||
}
|
||||
if (dlyShedIfp) m_resumeFuncp->addStmtsp(dlyShedIfp);
|
||||
|
|
@ -97,10 +114,10 @@ AstCCall* TimingKit::createResume(AstNetlist* const netlistp) {
|
|||
}
|
||||
|
||||
//============================================================================
|
||||
// Creates a timing commit call (if needed, else returns null)
|
||||
// Creates a timing ready call (if needed, else returns null)
|
||||
|
||||
AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) {
|
||||
if (!m_commitFuncp) {
|
||||
AstCCall* TimingKit::createReady(AstNetlist* const netlistp) {
|
||||
if (!m_readyFuncp) {
|
||||
for (auto& p : m_lbs) {
|
||||
AstActive* const activep = p.second;
|
||||
auto* const resumep = VN_AS(VN_AS(activep->stmtsp(), StmtExpr)->exprp(), CMethodHard);
|
||||
|
|
@ -111,67 +128,35 @@ AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) {
|
|||
|| schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler(),
|
||||
schedulerp, "Unexpected type");
|
||||
if (!schedulerp->dtypep()->basicp()->isTriggerScheduler()) continue;
|
||||
// Create the global commit function only if we have trigger schedulers
|
||||
if (!m_commitFuncp) {
|
||||
// Create the global ready function only if we have trigger schedulers
|
||||
if (!m_readyFuncp) {
|
||||
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
||||
m_commitFuncp
|
||||
= new AstCFunc{netlistp->fileline(), "_timing_commit", scopeTopp, ""};
|
||||
m_commitFuncp->dontCombine(true);
|
||||
m_commitFuncp->isLoose(true);
|
||||
m_commitFuncp->isConst(false);
|
||||
m_commitFuncp->declPrivate(true);
|
||||
scopeTopp->addBlocksp(m_commitFuncp);
|
||||
m_readyFuncp = new AstCFunc{netlistp->fileline(), "_timing_ready", scopeTopp, ""};
|
||||
m_readyFuncp->dontCombine(true);
|
||||
m_readyFuncp->isLoose(true);
|
||||
m_readyFuncp->isConst(false);
|
||||
m_readyFuncp->declPrivate(true);
|
||||
scopeTopp->addBlocksp(m_readyFuncp);
|
||||
}
|
||||
|
||||
// There is a somewhat complicate dance here. Given a suspendable
|
||||
// process of the form:
|
||||
// ->evntA;
|
||||
// $display("Fired evntA");
|
||||
// @(evntA or evntB);
|
||||
// The firing of the event cannot trigger the event control
|
||||
// following it, as the process is not yet sensitive to the event
|
||||
// when it fires (same applies for change detects). The way the
|
||||
// scheduling works, the @evnt will suspend the process before
|
||||
// the firing of the event is recognized on the next iteration of
|
||||
// the 'act' loop, and hence could incorrectly resume the @evnt
|
||||
// statement. To make this work, whenever a process suspends, it
|
||||
// goes into an "uncommitted" state, so it cannot be resumed
|
||||
// immediately on the next iteration of the 'act' loop, which is
|
||||
// what we want. The question then is, when should the suspended
|
||||
// process be "committed" and hence possible to be resumed. This is
|
||||
// done when it is know for sure the suspending expression was not
|
||||
// triggered on the current iteration of the 'act' loop. With
|
||||
// multiple events in the suspending expression, all events need
|
||||
// to be not triggered to safely commit the suspended process.
|
||||
//
|
||||
// This is is consistent with IEEE scheduling semantics, and
|
||||
// behaves as if the above was executed as:
|
||||
// ->evntA;
|
||||
// $display("Fired evnt");
|
||||
// ... all other statements in the 'act' loop that might fire evntA or evntB ...
|
||||
// @(evntA or evntB);
|
||||
// which is a valid execution. Race conditions be fun to debug,
|
||||
// but they are a responsibility of the user.
|
||||
|
||||
AstSenTree* const senTreep = activep->sentreep();
|
||||
FileLine* const flp = senTreep->fileline();
|
||||
|
||||
// Create an 'AstIf' sensitive to the suspending triggers
|
||||
AstIf* const ifp = V3Sched::util::createIfFromSenTree(senTreep);
|
||||
m_commitFuncp->addStmtsp(ifp);
|
||||
m_readyFuncp->addStmtsp(ifp);
|
||||
|
||||
// Commit the processes suspended on this sensitivity expression
|
||||
// in the **else** branch, when the event is known to be not fired.
|
||||
// Mark as ready the processes resumed on this sensitivity expression
|
||||
AstVarRef* const refp = new AstVarRef{flp, schedulerp, VAccess::READWRITE};
|
||||
AstCMethodHard* const callp = new AstCMethodHard{flp, refp, VCMethod::SCHED_COMMIT};
|
||||
AstCMethodHard* const callp = new AstCMethodHard{flp, refp, VCMethod::SCHED_READY};
|
||||
callp->dtypeSetVoid();
|
||||
if (resumep->pinsp()) callp->addPinsp(resumep->pinsp()->cloneTree(false));
|
||||
ifp->addElsesp(callp->makeStmt());
|
||||
ifp->addThensp(callp->makeStmt());
|
||||
}
|
||||
// We still haven't created a commit function (no trigger schedulers), return null
|
||||
if (!m_commitFuncp) return nullptr;
|
||||
// We still haven't created a ready function (no trigger schedulers), return null
|
||||
if (!m_readyFuncp) return nullptr;
|
||||
}
|
||||
AstCCall* const callp = new AstCCall{m_commitFuncp->fileline(), m_commitFuncp};
|
||||
AstCCall* const callp = new AstCCall{m_readyFuncp->fileline(), m_readyFuncp};
|
||||
callp->dtypeSetVoid();
|
||||
return callp;
|
||||
}
|
||||
|
|
@ -223,7 +208,7 @@ class AwaitVisitor final : public VNVisitor {
|
|||
if (schedulerp->dtypep()->basicp()->isTriggerScheduler()) {
|
||||
UASSERT_OBJ(methodp->pinsp(), methodp,
|
||||
"Trigger method should have pins from V3Timing");
|
||||
// The first pin is the commit boolean, the rest (if any) should be debug info
|
||||
// The first pin is the ready boolean, the rest (if any) should be debug info
|
||||
// See V3Timing for details
|
||||
if (AstNode* const dbginfop = methodp->pinsp()->nextp()) {
|
||||
if (methodp->pinsp()) addResumePins(resumep, static_cast<AstNodeExpr*>(dbginfop));
|
||||
|
|
@ -267,7 +252,6 @@ class AwaitVisitor final : public VNVisitor {
|
|||
void visit(AstCAwait* nodep) override {
|
||||
if (AstSenTree* const sentreep = nodep->sentreep()) {
|
||||
if (!sentreep->user1SetOnce()) createResumeActive(nodep);
|
||||
nodep->clearSentreep(); // Clear as these sentrees will get deleted later
|
||||
if (m_inProcess) m_processDomains.insert(sentreep);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,19 +225,21 @@ AstCFunc* TriggerKit::createClearFunc() const {
|
|||
// Done
|
||||
return funcp;
|
||||
}
|
||||
AstCFunc* TriggerKit::createOrIntoFunc(AstUnpackArrayDType* const iDtypep) const {
|
||||
AstCFunc* TriggerKit::createOrIntoFunc(AstUnpackArrayDType* const oDtypep,
|
||||
AstUnpackArrayDType* const iDtypep) const {
|
||||
AstNetlist* const netlistp = v3Global.rootp();
|
||||
FileLine* const flp = netlistp->topScopep()->fileline();
|
||||
AstNodeDType* const u32DTypep = netlistp->findUInt32DType();
|
||||
|
||||
// Create function
|
||||
std::string name = "_trigger_orInto__" + m_name;
|
||||
name += iDtypep == m_trigVecDTypep ? "" : "_ext";
|
||||
name += iDtypep == m_trigVecDTypep ? "_vec" : "_ext";
|
||||
name += oDtypep == m_trigVecDTypep ? "_vec" : "_ext";
|
||||
AstCFunc* const funcp = util::makeSubFunction(netlistp, name, m_slow);
|
||||
funcp->isStatic(true);
|
||||
|
||||
// Add arguments
|
||||
AstVarScope* const oVscp = newArgument(funcp, m_trigVecDTypep, "out", VDirection::INOUT);
|
||||
AstVarScope* const oVscp = newArgument(funcp, oDtypep, "out", VDirection::INOUT);
|
||||
AstVarScope* const iVscp = newArgument(funcp, iDtypep, "in", VDirection::CONSTREF);
|
||||
|
||||
// Add loop counter variable
|
||||
|
|
@ -257,10 +259,14 @@ AstCFunc* TriggerKit::createOrIntoFunc(AstUnpackArrayDType* const iDtypep) const
|
|||
AstNodeExpr* const oWordp = new AstArraySel{flp, rd(oVscp), rd(nVscp)};
|
||||
AstNodeExpr* const iWordp = new AstArraySel{flp, rd(iVscp), rd(nVscp)};
|
||||
AstNodeExpr* const rhsp = new AstOr{flp, oWordp, iWordp};
|
||||
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, m_nVecWords};
|
||||
AstConst* const outputRangeLeftp = VN_AS(oDtypep->rangep()->leftp(), Const);
|
||||
AstConst* const inputRangeLeftp = VN_AS(iDtypep->rangep()->leftp(), Const);
|
||||
AstNodeExpr* const limp = outputRangeLeftp->num().toSInt() < inputRangeLeftp->num().toSInt()
|
||||
? outputRangeLeftp->cloneTreePure(false)
|
||||
: inputRangeLeftp->cloneTreePure(false);
|
||||
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
|
||||
loopp->addStmtsp(util::incrementVar(nVscp));
|
||||
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
|
||||
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLte{flp, rd(nVscp), limp}});
|
||||
|
||||
// Done
|
||||
return funcp;
|
||||
|
|
@ -297,16 +303,16 @@ AstNodeStmt* TriggerKit::newClearCall(AstVarScope* const vscp) const {
|
|||
}
|
||||
AstNodeStmt* TriggerKit::newOrIntoCall(AstVarScope* const oVscp, AstVarScope* const iVscp) const {
|
||||
if (!m_nVecWords) return nullptr;
|
||||
UASSERT_OBJ(oVscp->dtypep() == m_trigVecDTypep, oVscp, "Bad trigger vector type");
|
||||
AstCFunc* funcp = nullptr;
|
||||
if (iVscp->dtypep() == m_trigVecDTypep) {
|
||||
if (!m_orIntoVecp) m_orIntoVecp = createOrIntoFunc(m_trigVecDTypep);
|
||||
funcp = m_orIntoVecp;
|
||||
} else if (iVscp->dtypep() == m_trigExtDTypep) {
|
||||
if (!m_orIntoExtp) m_orIntoExtp = createOrIntoFunc(m_trigExtDTypep);
|
||||
funcp = m_orIntoExtp;
|
||||
} else {
|
||||
iVscp->v3fatalSrc("Bad trigger vector type");
|
||||
UASSERT_OBJ(iVscp->dtypep() == m_trigVecDTypep || iVscp->dtypep() == m_trigExtDTypep, iVscp,
|
||||
"Bad input trigger vector type");
|
||||
UASSERT_OBJ(oVscp->dtypep() == m_trigVecDTypep || oVscp->dtypep() == m_trigExtDTypep, oVscp,
|
||||
"Bad output trigger vector type");
|
||||
const size_t mask
|
||||
= ((oVscp->dtypep() == m_trigExtDTypep) << 1) | (iVscp->dtypep() == m_trigExtDTypep);
|
||||
AstCFunc*& funcp = m_orIntoVecps[mask];
|
||||
if (!funcp) {
|
||||
funcp = createOrIntoFunc(VN_AS(oVscp->dtypep(), UnpackArrayDType),
|
||||
VN_AS(iVscp->dtypep(), UnpackArrayDType));
|
||||
}
|
||||
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
|
||||
AstCCall* const callp = new AstCCall{flp, funcp};
|
||||
|
|
@ -316,13 +322,19 @@ AstNodeStmt* TriggerKit::newOrIntoCall(AstVarScope* const oVscp, AstVarScope* co
|
|||
return callp->makeStmt();
|
||||
}
|
||||
|
||||
AstNodeStmt* TriggerKit::newCompCall(AstVarScope* vscp) const {
|
||||
AstNodeStmt* TriggerKit::newCompBaseCall() const {
|
||||
if (!m_nVecWords) return nullptr;
|
||||
// If there are pre triggers, we need the argument
|
||||
UASSERT(!m_nPreWords || vscp, "Need latched values for pre trigger compute");
|
||||
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
|
||||
AstCCall* const callp = new AstCCall{flp, m_compp};
|
||||
if (m_nPreWords) callp->addArgsp(new AstVarRef{flp, vscp, VAccess::READ});
|
||||
AstCCall* const callp = new AstCCall{flp, m_compVecp};
|
||||
callp->dtypeSetVoid();
|
||||
return callp->makeStmt();
|
||||
}
|
||||
|
||||
AstNodeStmt* TriggerKit::newCompExtCall(AstVarScope* vscp) const {
|
||||
if (!m_nPreWords) return nullptr;
|
||||
FileLine* const flp = v3Global.rootp()->topScopep()->fileline();
|
||||
AstCCall* const callp = new AstCCall{flp, m_compExtp};
|
||||
callp->addArgsp(new AstVarRef{flp, vscp, VAccess::READ});
|
||||
callp->dtypeSetVoid();
|
||||
return callp->makeStmt();
|
||||
}
|
||||
|
|
@ -402,24 +414,28 @@ void TriggerKit::addExtraTriggerAssignment(AstVarScope* vscp, uint32_t index, bo
|
|||
AstNodeExpr* const wordp = new AstArraySel{flp, refp, static_cast<int>(wordIndex)};
|
||||
AstNodeExpr* const trigLhsp = new AstSel{flp, wordp, static_cast<int>(bitIndex), 1};
|
||||
AstNodeExpr* const trigRhsp = new AstVarRef{flp, vscp, VAccess::READ};
|
||||
AstNodeStmt* const setp = new AstAssign{flp, trigLhsp, trigRhsp};
|
||||
AstNode* const setp = new AstAssign{flp, trigLhsp, trigRhsp};
|
||||
if (clear) {
|
||||
// Clear the input variable
|
||||
AstNodeStmt* const clrp = new AstAssign{flp, new AstVarRef{flp, vscp, VAccess::WRITE},
|
||||
new AstConst{flp, AstConst::BitFalse{}}};
|
||||
// Note these are added in reverse order, so 'setp' executes before 'clrp'
|
||||
m_compp->stmtsp()->addHereThisAsNext(clrp);
|
||||
setp->addNext(new AstAssign{flp, new AstVarRef{flp, vscp, VAccess::WRITE},
|
||||
new AstConst{flp, AstConst::BitFalse{}}});
|
||||
}
|
||||
m_compp->stmtsp()->addHereThisAsNext(setp);
|
||||
if (AstNode* const nodep = m_compVecp->stmtsp()) {
|
||||
setp->addNext(setp, nodep->unlinkFrBackWithNext());
|
||||
}
|
||||
m_compVecp->addStmtsp(setp);
|
||||
}
|
||||
|
||||
TriggerKit::TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords,
|
||||
uint32_t nExtraWords, uint32_t nPreWords)
|
||||
uint32_t nExtraWords, uint32_t nPreWords,
|
||||
std::unordered_map<VNRef<const AstSenItem>, size_t> senItem2TrigIdx,
|
||||
bool useAcc)
|
||||
: m_name{name}
|
||||
, m_slow{slow}
|
||||
, m_nSenseWords{nSenseWords}
|
||||
, m_nExtraWords{nExtraWords}
|
||||
, m_nPreWords{nPreWords} {
|
||||
, m_nPreWords{nPreWords}
|
||||
, m_senItem2TrigIdx{std::move(senItem2TrigIdx)} {
|
||||
// If no triggers, we don't need to generate anything
|
||||
if (!m_nVecWords) return;
|
||||
// Othewise construc the parts of the kit
|
||||
|
|
@ -437,6 +453,7 @@ TriggerKit::TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords,
|
|||
AstRange* const ep = new AstRange{flp, static_cast<int>(m_nVecWords + m_nPreWords - 1), 0};
|
||||
m_trigExtDTypep = new AstUnpackArrayDType{flp, m_wordDTypep, ep};
|
||||
netlistp->typeTablep()->addTypesp(m_trigExtDTypep);
|
||||
m_compExtp = util::makeSubFunction(netlistp, "_eval_triggers_ext__" + m_name, m_slow);
|
||||
} else {
|
||||
m_trigExtDTypep = m_trigVecDTypep;
|
||||
}
|
||||
|
|
@ -444,11 +461,40 @@ TriggerKit::TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords,
|
|||
m_vscp = scopep->createTemp("__V" + m_name + "Triggered", m_trigExtDTypep);
|
||||
m_vscp->varp()->isInternal(true);
|
||||
// The trigger computation function
|
||||
m_compp = util::makeSubFunction(netlistp, "_eval_triggers__" + m_name, m_slow);
|
||||
m_compVecp = util::makeSubFunction(netlistp, "_eval_triggers_vec__" + m_name, m_slow);
|
||||
// The debug dump function, always 'slow'
|
||||
m_dumpp = util::makeSubFunction(netlistp, "_dump_triggers__" + m_name, true);
|
||||
m_dumpp->isStatic(true);
|
||||
m_dumpp->ifdef("VL_DEBUG");
|
||||
if (useAcc) {
|
||||
m_vscAccp = scopep->createTemp("__V" + m_name + "TriggeredAcc", m_trigVecDTypep);
|
||||
m_vscAccp->varp()->isInternal(true);
|
||||
}
|
||||
}
|
||||
|
||||
AstAssign* TriggerKit::createSenTrigVecAssignment(AstVarScope* const target,
|
||||
std::vector<AstNodeExpr*>& trigps) {
|
||||
FileLine* const flp = target->fileline();
|
||||
AstAssign* trigStmtsp = nullptr;
|
||||
// Assign sense triggers vector one word at a time
|
||||
for (size_t i = 0; i < trigps.size(); i += WORD_SIZE) {
|
||||
// Concatenate all bits in this trigger word using a balanced
|
||||
for (uint32_t level = 0; level < WORD_SIZE_LOG2; ++level) {
|
||||
const uint32_t stride = 1 << level;
|
||||
for (uint32_t j = 0; j < WORD_SIZE; j += 2 * stride) {
|
||||
trigps[i + j] = new AstConcat{trigps[i + j]->fileline(), trigps[i + j + stride],
|
||||
trigps[i + j]};
|
||||
trigps[i + j + stride] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the whole word in the trigger vector
|
||||
const int wordIndex = static_cast<int>(i / WORD_SIZE);
|
||||
AstArraySel* const aselp
|
||||
= new AstArraySel{flp, new AstVarRef{flp, target, VAccess::WRITE}, wordIndex};
|
||||
trigStmtsp = AstNode::addNext(trigStmtsp, new AstAssign{flp, aselp, trigps[i]});
|
||||
}
|
||||
return trigStmtsp;
|
||||
}
|
||||
|
||||
TriggerKit TriggerKit::create(AstNetlist* netlistp, //
|
||||
|
|
@ -458,7 +504,8 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
|
|||
const std::vector<const AstSenTree*>& senTreeps, //
|
||||
const string& name, //
|
||||
const ExtraTriggers& extraTriggers, //
|
||||
bool slow) {
|
||||
bool slow, //
|
||||
bool useAcc) {
|
||||
// Need to gather all the unique SenItems under the given SenTrees
|
||||
|
||||
// List of unique SenItems used by all 'senTreeps'
|
||||
|
|
@ -512,7 +559,7 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
|
|||
const uint32_t nExtraWords = nExtraTriggers / WORD_SIZE;
|
||||
|
||||
// We can now construct the trigger kit - this constructs all items that will be kept
|
||||
TriggerKit kit{name, slow, nSenseWords, nExtraWords, nPreWords};
|
||||
TriggerKit kit{name, slow, nSenseWords, nExtraWords, nPreWords, senItem2TrigIdx, useAcc};
|
||||
|
||||
// If there are no triggers we are done
|
||||
if (!kit.m_nVecWords) return kit;
|
||||
|
|
@ -587,7 +634,11 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
|
|||
AstNodeExpr* const wordp = new AstArraySel{flp, wr(kit.m_vscp), wrdIndex};
|
||||
AstNodeExpr* const lhsp = new AstSel{flp, wordp, bitIndex, 1};
|
||||
AstNodeExpr* const rhsp = new AstConst{flp, AstConst::BitTrue{}};
|
||||
initialTrigsp = AstNode::addNext(initialTrigsp, new AstAssign{flp, lhsp, rhsp});
|
||||
if (useAcc) {
|
||||
initFuncp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
|
||||
} else {
|
||||
initialTrigsp = AstNode::addNext(initialTrigsp, new AstAssign{flp, lhsp, rhsp});
|
||||
}
|
||||
}
|
||||
|
||||
// Add a debug statement for this trigger
|
||||
|
|
@ -601,25 +652,7 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
|
|||
}
|
||||
UASSERT(trigps.size() == nSenseTriggers, "Inconsistent number of trigger expressions");
|
||||
|
||||
// Assign sense triggers vector one word at a time
|
||||
AstNodeStmt* trigStmtsp = nullptr;
|
||||
for (size_t i = 0; i < nSenseTriggers; i += WORD_SIZE) {
|
||||
// Concatenate all bits in this trigger word using a balanced
|
||||
for (uint32_t level = 0; level < WORD_SIZE_LOG2; ++level) {
|
||||
const uint32_t stride = 1 << level;
|
||||
for (uint32_t j = 0; j < WORD_SIZE; j += 2 * stride) {
|
||||
trigps[i + j] = new AstConcat{trigps[i + j]->fileline(), trigps[i + j + stride],
|
||||
trigps[i + j]};
|
||||
trigps[i + j + stride] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the whole word in the trigger vector
|
||||
const int wordIndex = static_cast<int>(i / WORD_SIZE);
|
||||
AstArraySel* const aselp = new AstArraySel{flp, wr(kit.m_vscp), wordIndex};
|
||||
trigStmtsp = AstNode::addNext(trigStmtsp, new AstAssign{flp, aselp, trigps[i]});
|
||||
}
|
||||
trigps.clear();
|
||||
AstAssign* const trigStmtsp = createSenTrigVecAssignment(kit.m_vscp, trigps);
|
||||
|
||||
// Add a print for each of the extra triggers
|
||||
for (unsigned i = 0; i < extraTriggers.size(); ++i) {
|
||||
|
|
@ -652,18 +685,18 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
|
|||
}
|
||||
|
||||
// Get the SenExprBuilder results
|
||||
const SenExprBuilder::Results senResults = senExprBuilder.getAndClearResults();
|
||||
const SenExprBuilder::Results senResults = senExprBuilder.getResultsAndClearUpdates();
|
||||
|
||||
// Add the SenExprBuilder init statements to the static initialization functino
|
||||
for (AstNodeStmt* const nodep : senResults.m_inits) initFuncp->addStmtsp(nodep);
|
||||
|
||||
// Assemble the trigger computation function
|
||||
// Assemble the base trigger computation function
|
||||
AstScope* const scopep = netlistp->topScopep()->scopep();
|
||||
{
|
||||
AstCFunc* const fp = kit.m_compp;
|
||||
AstScope* const scopep = netlistp->topScopep()->scopep();
|
||||
AstCFunc* const fp = kit.m_compVecp;
|
||||
// Profiling push
|
||||
if (v3Global.opt.profExec()) {
|
||||
fp->addStmtsp(AstCStmt::profExecSectionPush(flp, "trig " + name));
|
||||
fp->addStmtsp(AstCStmt::profExecSectionPush(flp, "trigBase " + name));
|
||||
}
|
||||
// Trigger computation
|
||||
for (AstNodeStmt* const nodep : senResults.m_preUpdates) fp->addStmtsp(nodep);
|
||||
|
|
@ -678,39 +711,39 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
|
|||
ifp->addThensp(util::setVar(initVscp, 1));
|
||||
ifp->addThensp(initialTrigsp);
|
||||
}
|
||||
// If there are 'pre' triggers, compute them
|
||||
if (kit.m_nPreWords) {
|
||||
// Add an argument to the function that takes the latched values
|
||||
AstVarScope* const latchedp
|
||||
= newArgument(fp, kit.m_trigVecDTypep, "latched", VDirection::CONSTREF);
|
||||
// Add loop counter variable - this can't be local because we call util::splitCheck
|
||||
AstVarScope* const nVscp = scopep->createTemp("__V" + name + "TrigPreLoopCounter", 32);
|
||||
nVscp->varp()->noReset(true);
|
||||
// Add a loop to compute the pre words
|
||||
AstLoop* const loopp = new AstLoop{flp};
|
||||
fp->addStmtsp(util::setVar(nVscp, 0));
|
||||
fp->addStmtsp(loopp);
|
||||
// Loop body
|
||||
AstNodeExpr* const offsetp = new AstConst{flp, kit.m_nVecWords};
|
||||
AstNodeExpr* const lIdxp = new AstAdd{flp, rd(nVscp), offsetp};
|
||||
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(kit.m_vscp), lIdxp};
|
||||
AstNodeExpr* const aWordp = new AstArraySel{flp, rd(kit.m_vscp), rd(nVscp)};
|
||||
AstNodeExpr* const bWordp = new AstArraySel{flp, rd(latchedp), rd(nVscp)};
|
||||
AstNodeExpr* const rhsp = new AstAnd{flp, aWordp, new AstNot{flp, bWordp}};
|
||||
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, nPreWords};
|
||||
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
|
||||
loopp->addStmtsp(util::incrementVar(nVscp));
|
||||
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
|
||||
}
|
||||
// Add a call to the dumping function if debug is enabled
|
||||
fp->addStmtsp(kit.newDumpCall(kit.m_vscp, name, true));
|
||||
// Profiling pop
|
||||
if (v3Global.opt.profExec()) {
|
||||
fp->addStmtsp(AstCStmt::profExecSectionPop(flp, "trig " + name));
|
||||
fp->addStmtsp(AstCStmt::profExecSectionPop(flp, "trigBase " + name));
|
||||
}
|
||||
// Done with the trigger computation function, split as might be large
|
||||
util::splitCheck(fp);
|
||||
};
|
||||
// If there are 'pre' triggers, compute them
|
||||
if (kit.m_nPreWords) {
|
||||
AstCFunc* const fp = kit.m_compExtp;
|
||||
// Add an argument to the function that takes the latched values
|
||||
AstVarScope* const latchedp
|
||||
= newArgument(fp, kit.m_trigVecDTypep, "latched", VDirection::CONSTREF);
|
||||
// Add loop counter variable - this can't be local because we call util::splitCheck
|
||||
AstVarScope* const nVscp = scopep->createTemp("__V" + name + "TrigPreLoopCounter", 32);
|
||||
nVscp->varp()->noReset(true);
|
||||
// Add a loop to compute the pre words
|
||||
AstLoop* const loopp = new AstLoop{flp};
|
||||
fp->addStmtsp(util::setVar(nVscp, 0));
|
||||
fp->addStmtsp(loopp);
|
||||
// Loop body
|
||||
AstNodeExpr* const offsetp = new AstConst{flp, kit.m_nVecWords};
|
||||
AstNodeExpr* const lIdxp = new AstAdd{flp, rd(nVscp), offsetp};
|
||||
AstNodeExpr* const lhsp = new AstArraySel{flp, wr(kit.m_vscp), lIdxp};
|
||||
AstNodeExpr* const aWordp = new AstArraySel{flp, rd(kit.m_vscp), rd(nVscp)};
|
||||
AstNodeExpr* const bWordp = new AstArraySel{flp, rd(latchedp), rd(nVscp)};
|
||||
AstNodeExpr* const rhsp = new AstAnd{flp, aWordp, new AstNot{flp, bWordp}};
|
||||
AstNodeExpr* const limp = new AstConst{flp, AstConst::WidthedValue{}, 32, nPreWords};
|
||||
loopp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
|
||||
loopp->addStmtsp(util::incrementVar(nVscp));
|
||||
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLt{flp, rd(nVscp), limp}});
|
||||
util::splitCheck(fp);
|
||||
}
|
||||
// Done with the trigger computation function, split as might be large
|
||||
|
||||
// The debug code might leak signal names, so simply delete it when using --protect-ids
|
||||
if (v3Global.opt.protectIds()) kit.m_dumpp->stmtsp()->unlinkFrBackWithNext()->deleteTree();
|
||||
|
|
@ -720,4 +753,280 @@ TriggerKit TriggerKit::create(AstNetlist* netlistp, //
|
|||
return kit;
|
||||
}
|
||||
|
||||
// Find all CAwaits, clear SenTrees inside them, generate before-trigger functions (functions that
|
||||
// shall be called before awaiting for a VCMethod::SCHED_TRIGGER) and add thier calls before
|
||||
// proper CAwaits
|
||||
class AwaitBeforeTrigVisitor final : public VNVisitor {
|
||||
const VNUser1InUse m_user1InUse;
|
||||
|
||||
/**
|
||||
* AstCAwait::user1() -> bool. True if node has been visited
|
||||
* AstSenTree::user1p() -> AstCFunc*. Function that has to be called before awaiting
|
||||
* for CAwait pointing to this SenTree
|
||||
* AstCFunc::user1p() -> AstVarScope* Function's local temporary extended trigger
|
||||
* vector variable scope
|
||||
*/
|
||||
|
||||
// Netlist - needed for using util::makeSubFunction()
|
||||
AstNetlist* const m_netlistp;
|
||||
// Trigger kit - for accessing trigger vectors and mapping senItems to thier indexes
|
||||
const TriggerKit& m_trigKit;
|
||||
// Expression builder - for building expressions from SenItems
|
||||
SenExprBuilder& m_senExprBuilder;
|
||||
// Generator of unique names for before-trigger function
|
||||
V3UniqueNames m_beforeTriggerFuncUniqueName;
|
||||
|
||||
// Vector containing every generated CFuncs and related SenTree
|
||||
std::vector<std::pair<AstCFunc*, AstSenTree*>> m_generatedFuncs;
|
||||
// Map from SenTree to coresponding scheduler
|
||||
std::map<AstSenTree*, AstNodeExpr*> m_senTreeToSched;
|
||||
// Map containing vectors of SenItems that share the same prevValue variable
|
||||
std::unordered_map<VNRef<AstNode>, std::vector<AstSenItem*>> m_senExprToSenItem;
|
||||
|
||||
// Returns node which is used for grouping SenItems in `m_senExprToSenItem`
|
||||
static AstNode* getSenHashNode(const AstSenItem* const nodep) {
|
||||
if (AstVarRef* const varRefp = VN_CAST(nodep->sensp(), VarRef)) return varRefp;
|
||||
return nodep->sensp();
|
||||
}
|
||||
|
||||
// Populates `m_senExprToSenItem` with every group of SenItems that share the same prevValue
|
||||
// variable. Groups that contain only one type of an edge are omitted.
|
||||
void fillSenExprToSenItem() {
|
||||
for (auto senTreeSched : m_senTreeToSched) {
|
||||
AstSenTree* const senTreep = senTreeSched.first;
|
||||
|
||||
for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
|
||||
senItemp = VN_AS(senItemp->nextp(), SenItem)) {
|
||||
const VEdgeType edge = senItemp->edgeType();
|
||||
if (edge.anEdge() || edge == VEdgeType::ET_CHANGED
|
||||
|| edge == VEdgeType::ET_HYBRID) {
|
||||
m_senExprToSenItem[*getSenHashNode(senItemp)].push_back(senItemp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<VNRef<AstNode>> toRemove;
|
||||
for (const auto& senExprToSenTree : m_senExprToSenItem) {
|
||||
std::vector<AstSenItem*> senItemps = senExprToSenTree.second;
|
||||
toRemove.push_back(senExprToSenTree.first);
|
||||
for (size_t i = 1; i < senItemps.size(); ++i) {
|
||||
if (senItemps[i]->edgeType() != senItemps[i - 1]->edgeType()) {
|
||||
toRemove.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (VNRef<AstNode> it : toRemove) m_senExprToSenItem.erase(it);
|
||||
}
|
||||
|
||||
// For set of bits indexes (of sensitivity vector) return map from those indexes to set
|
||||
// of schedulers sensitive to these indexes. Indices are split into word index and bit
|
||||
// masking this index within given word
|
||||
std::map<size_t, std::map<size_t, std::set<AstNodeExpr*>>>
|
||||
getUsedTriggersToTrees(const std::set<size_t>& usedTriggers) {
|
||||
std::map<size_t, std::map<size_t, std::set<AstNodeExpr*>>> usedTrigsToUsingTrees;
|
||||
for (auto senTreeSched : m_senTreeToSched) {
|
||||
const AstSenTree* const senTreep = senTreeSched.first;
|
||||
AstNodeExpr* const shedp = senTreeSched.second;
|
||||
|
||||
// Find all common SenItem indexes for `senTreep` and `usedTriggers`
|
||||
std::set<size_t> usedTriggersInSenTree;
|
||||
for (AstSenItem* senItemp = senTreep->sensesp(); senItemp;
|
||||
senItemp = VN_AS(senItemp->nextp(), SenItem)) {
|
||||
const size_t idx = m_trigKit.senItem2TrigIdx(senItemp);
|
||||
if (usedTriggers.find(idx) != usedTriggers.end()) {
|
||||
usedTrigsToUsingTrees[idx / TriggerKit::WORD_SIZE]
|
||||
[1 << (idx % TriggerKit::WORD_SIZE)]
|
||||
.insert(shedp);
|
||||
}
|
||||
}
|
||||
}
|
||||
return usedTrigsToUsingTrees;
|
||||
}
|
||||
|
||||
// Returns a CCall to a before-trigger function for a given SenTree,
|
||||
// Constructs such a function if it doesn't exist yet
|
||||
AstCCall* getBeforeTriggerStmt(AstSenTree* const senTreep) {
|
||||
FileLine* const flp = senTreep->fileline();
|
||||
if (!senTreep->user1p()) {
|
||||
AstCFunc* const funcp = util::makeSubFunction(
|
||||
m_netlistp, m_beforeTriggerFuncUniqueName.get(senTreep), false);
|
||||
senTreep->user1p(funcp);
|
||||
|
||||
// Create a local temporary extended vector
|
||||
AstVarScope* const vscAccp = m_trigKit.vscAccp();
|
||||
AstVarScope* const tmpp = vscAccp->scopep()->createTempLike("__VTmp", vscAccp);
|
||||
AstVar* const tmpVarp = tmpp->varp()->unlinkFrBack();
|
||||
funcp->user1p(tmpp);
|
||||
funcp->addVarsp(tmpVarp);
|
||||
tmpVarp->funcLocal(true);
|
||||
tmpVarp->noReset(true);
|
||||
|
||||
AstVar* const argp = new AstVar{flp, VVarType::BLOCKTEMP, "__VeventDescription",
|
||||
senTreep->findBasicDType(VBasicDTypeKwd::CHARPTR)};
|
||||
argp->funcLocal(true);
|
||||
argp->direction(VDirection::INPUT);
|
||||
funcp->addArgsp(argp);
|
||||
// Scope is created in the constructor after iterate finishes
|
||||
|
||||
m_generatedFuncs.emplace_back(funcp, senTreep);
|
||||
}
|
||||
AstCCall* const callp = new AstCCall{flp, VN_AS(senTreep->user1p(), CFunc)};
|
||||
callp->dtypeSetVoid();
|
||||
return callp;
|
||||
}
|
||||
|
||||
void visit(AstCAwait* const nodep) override {
|
||||
if (nodep->user1SetOnce()) return;
|
||||
|
||||
// Check whether it is a CAwait for a VCMethod::SCHED_TRIGGER
|
||||
if (const AstCMethodHard* const cMethodHardp = VN_CAST(nodep->exprp(), CMethodHard)) {
|
||||
if (cMethodHardp->method() == VCMethod::SCHED_TRIGGER) {
|
||||
AstCCall* const beforeTrigp = getBeforeTriggerStmt(nodep->sentreep());
|
||||
|
||||
FileLine* const flp = nodep->fileline();
|
||||
// Add eventDescription argument value to a CCall - it is used for --runtime-debug
|
||||
AstNode* const pinp = cMethodHardp->pinsp()->nextp()->nextp();
|
||||
UASSERT_OBJ(pinp, cMethodHardp, "No event description");
|
||||
beforeTrigp->addArgsp(VN_AS(pinp, NodeExpr)->cloneTree(false));
|
||||
|
||||
// Change CAwait Expression into StmtExpr that calls to a before-trigger function
|
||||
// first and then return CAwait
|
||||
VNRelinker relinker;
|
||||
nodep->unlinkFrBack(&relinker);
|
||||
AstExprStmt* const exprstmtp
|
||||
= new AstExprStmt{flp, beforeTrigp->makeStmt(), nodep};
|
||||
relinker.relink(exprstmtp);
|
||||
m_senTreeToSched.emplace(nodep->sentreep(), cMethodHardp->fromp());
|
||||
}
|
||||
}
|
||||
nodep->clearSentreep(); // Clear as these sentrees will get deleted later
|
||||
iterate(nodep);
|
||||
}
|
||||
|
||||
void visit(AstNode* const nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
AwaitBeforeTrigVisitor(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
|
||||
const TriggerKit& trigKit)
|
||||
: m_netlistp{netlistp}
|
||||
, m_trigKit{trigKit}
|
||||
, m_senExprBuilder{senExprBuilder}
|
||||
, m_beforeTriggerFuncUniqueName{"__VbeforeTrig"} {
|
||||
iterate(netlistp);
|
||||
|
||||
fillSenExprToSenItem();
|
||||
|
||||
std::vector<AstNodeExpr*> trigps;
|
||||
std::set<size_t> usedTriggers;
|
||||
// In each of before-trigger functions check if anything was triggered and mark as ready
|
||||
// triggered schedulers
|
||||
for (const auto& funcToUsedTriggers : m_generatedFuncs) {
|
||||
AstCFunc* const funcp = funcToUsedTriggers.first;
|
||||
AstVarScope* const vscp = VN_AS(funcp->user1p(), VarScope);
|
||||
FileLine* const flp = funcp->fileline();
|
||||
|
||||
// Generate trigger evaluation
|
||||
{
|
||||
AstSenTree* const senTreep = funcToUsedTriggers.second;
|
||||
// Puts `exprp` at `pos` and makes sure that trigps.size() is multiple of
|
||||
// TriggerKit::WORD_SIZE
|
||||
const auto emplaceAt
|
||||
= [flp, &trigps, &usedTriggers](AstNodeExpr* const exprp, const size_t pos) {
|
||||
const size_t targetSize
|
||||
= vlstd::roundUpToMultipleOf<TriggerKit::WORD_SIZE>(pos + 1);
|
||||
if (trigps.capacity() < targetSize) trigps.reserve(targetSize * 2);
|
||||
while (trigps.size() < targetSize) {
|
||||
trigps.push_back(new AstConst{flp, AstConst::BitFalse{}});
|
||||
}
|
||||
trigps[pos]->deleteTree();
|
||||
trigps[pos] = exprp;
|
||||
usedTriggers.insert(pos);
|
||||
};
|
||||
|
||||
// Find all trigger indexes of SenItems inside `senTreep`
|
||||
// and add them to `trigps` and `usedTriggers`
|
||||
for (const AstSenItem* itemp = senTreep->sensesp(); itemp;
|
||||
itemp = VN_AS(itemp->nextp(), SenItem)) {
|
||||
const size_t idx = m_trigKit.senItem2TrigIdx(itemp);
|
||||
emplaceAt(m_senExprBuilder.build(itemp).first, idx);
|
||||
auto iter = m_senExprToSenItem.find(*getSenHashNode(itemp));
|
||||
if (iter != m_senExprToSenItem.end()) {
|
||||
for (AstSenItem* const additionalItemp : iter->second) {
|
||||
const size_t idx = m_trigKit.senItem2TrigIdx(additionalItemp);
|
||||
emplaceAt(m_senExprBuilder.build(additionalItemp).first, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the function with neccessary statements
|
||||
SenExprBuilder::Results results = m_senExprBuilder.getResultsAndClearUpdates();
|
||||
for (AstNodeStmt* const stmtsp : results.m_inits) funcp->addStmtsp(stmtsp);
|
||||
for (AstNodeStmt* const stmtsp : results.m_preUpdates) funcp->addStmtsp(stmtsp);
|
||||
funcp->addStmtsp(TriggerKit::createSenTrigVecAssignment(vscp, trigps));
|
||||
trigps.clear();
|
||||
for (AstNodeStmt* const stmtsp : results.m_postUpdates) funcp->addStmtsp(stmtsp);
|
||||
}
|
||||
|
||||
std::map<size_t, std::map<size_t, std::set<AstNodeExpr*>>> usedTrigsToUsingTrees
|
||||
= getUsedTriggersToTrees(usedTriggers);
|
||||
usedTriggers.clear();
|
||||
|
||||
// Helper returning expression getting array index `idx` from `scocep` with access
|
||||
// `access`
|
||||
const auto getIdx = [flp](AstVarScope* const scocep, VAccess access, size_t idx) {
|
||||
return new AstArraySel{flp, new AstVarRef{flp, scocep, access},
|
||||
new AstConst{flp, AstConst::Unsized64{}, idx}};
|
||||
};
|
||||
|
||||
// Get eventDescription argument
|
||||
AstVarScope* const argpVscp = new AstVarScope{flp, funcp->scopep(), funcp->argsp()};
|
||||
funcp->scopep()->addVarsp(argpVscp);
|
||||
|
||||
// Mark as ready triggered schedulers
|
||||
for (const auto& triggersToTrees : usedTrigsToUsingTrees) {
|
||||
const size_t word = triggersToTrees.first;
|
||||
|
||||
for (const auto& bitsToTrees : triggersToTrees.second) {
|
||||
const size_t bit = bitsToTrees.first;
|
||||
const auto& schedulers = bitsToTrees.second;
|
||||
|
||||
// Check if given bit is fired - single bits are checked since
|
||||
// usually there is only a few of them (only one most of the times as we await
|
||||
// only for one event)
|
||||
AstConst* const maskConstp = new AstConst{flp, AstConst::Unsized64{}, bit};
|
||||
AstAnd* const condp
|
||||
= new AstAnd{flp, getIdx(vscp, VAccess::READ, word), maskConstp};
|
||||
AstIf* const ifp = new AstIf{flp, condp};
|
||||
|
||||
// Call ready() on each scheduler sensitive to `condp`
|
||||
for (AstNodeExpr* const schedp : schedulers) {
|
||||
AstCMethodHard* const callp = new AstCMethodHard{
|
||||
flp, schedp->cloneTree(false), VCMethod::SCHED_READY};
|
||||
callp->dtypeSetVoid();
|
||||
callp->addPinsp(new AstVarRef{flp, argpVscp, VAccess::READ});
|
||||
ifp->addThensp(callp->makeStmt());
|
||||
}
|
||||
funcp->addStmtsp(ifp);
|
||||
}
|
||||
}
|
||||
|
||||
AstVarScope* const vscAccp = m_trigKit.vscAccp();
|
||||
// Add touched values to accumulator
|
||||
for (const auto& triggersToTrees : usedTrigsToUsingTrees) {
|
||||
const size_t word = triggersToTrees.first;
|
||||
funcp->addStmtsp(new AstAssign{flp, getIdx(vscAccp, VAccess::WRITE, word),
|
||||
new AstOr{flp, getIdx(vscAccp, VAccess::READ, word),
|
||||
getIdx(vscp, VAccess::READ, word)}});
|
||||
}
|
||||
}
|
||||
}
|
||||
~AwaitBeforeTrigVisitor() override = default;
|
||||
};
|
||||
|
||||
void beforeTrigVisitor(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
|
||||
const TriggerKit& trigKit) {
|
||||
AwaitBeforeTrigVisitor{netlistp, senExprBuilder, trigKit};
|
||||
}
|
||||
|
||||
} // namespace V3Sched
|
||||
|
|
|
|||
|
|
@ -322,6 +322,14 @@ public:
|
|||
return {resultp, firedAtInitialization};
|
||||
}
|
||||
|
||||
Results getResultsAndClearUpdates() {
|
||||
m_hasPreUpdate.clear();
|
||||
m_hasPostUpdate.clear();
|
||||
Results ans = std::move(m_results);
|
||||
m_results = {};
|
||||
return ans;
|
||||
}
|
||||
|
||||
Results getAndClearResults() {
|
||||
m_curr.clear();
|
||||
m_prev.clear();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2026 by Wilson Snyder. This program is free software; you can
|
||||
# redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["--binary"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2026 by Antmicro.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module x;
|
||||
event a;
|
||||
int counter = 0;
|
||||
|
||||
initial begin
|
||||
fork
|
||||
begin
|
||||
@a;
|
||||
->a;
|
||||
@a;
|
||||
counter++;
|
||||
end
|
||||
join_none
|
||||
#1;
|
||||
->a;
|
||||
end
|
||||
always begin
|
||||
@a;
|
||||
->a;
|
||||
@a;
|
||||
counter++;
|
||||
end
|
||||
final begin
|
||||
if (counter != 1) $stop;
|
||||
$write("*-* All Finished *-*\n");
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2026 by Wilson Snyder. This program is free software; you can
|
||||
# redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["--binary"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2026 by Antmicro.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module x;
|
||||
event a;
|
||||
event b;
|
||||
|
||||
bit ok = 0;
|
||||
|
||||
task do_the_job();
|
||||
static bit first = 1;
|
||||
if (first) begin
|
||||
first = 0;
|
||||
@a;
|
||||
ok = 1;
|
||||
end
|
||||
else begin
|
||||
->a;
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
#1 ->b;
|
||||
#100;
|
||||
if (!ok) $stop;
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
always @b do_the_job();
|
||||
always @b do_the_job();
|
||||
endmodule
|
||||
|
|
@ -125,7 +125,7 @@ test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_clas
|
|||
test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "_classes.mk", "vm_classes_2")
|
||||
|
||||
# Check combine count
|
||||
test.file_grep(test.stats, r'Node count, CFILE + (\d+)', (279 if test.vltmt else 262))
|
||||
test.file_grep(test.stats, r'Node count, CFILE + (\d+)', (276 if test.vltmt else 259))
|
||||
test.file_grep(test.stats, r'Makefile targets, VM_CLASSES_FAST + (\d+)', 2)
|
||||
test.file_grep(test.stats, r'Makefile targets, VM_CLASSES_SLOW + (\d+)', 2)
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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,25 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2026 by Antmicro.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module x;
|
||||
bit val = 0;
|
||||
bit ok = 0;
|
||||
|
||||
initial #1 begin
|
||||
val = 1;
|
||||
@(val);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial @(posedge val) begin
|
||||
val = 0;
|
||||
ok = 1;
|
||||
@(edge val);
|
||||
$stop;
|
||||
end
|
||||
initial #10 $stop;
|
||||
endmodule
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,181 @@
|
|||
-V{t#,#}- Verilated::debug is on. Message prefix indicates {<thread>,<sequence_number>}.
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___ctor_var_reset
|
||||
-V{t#,#}+++++TOP Evaluate Vt_timing_eval_act::eval_step
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_debug_assertions
|
||||
-V{t#,#}+ Initial
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_static
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_ready
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_initial
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_initial__TOP__Vtiming__0
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root____VbeforeTrig_h########__0
|
||||
-V{t#,#} Suspending process waiting for @([event] t.a) at t/t_timing_eval_act.v:18
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_initial__TOP__Vtiming__1
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root____VbeforeTrig_h########__0
|
||||
-V{t#,#} Suspending process waiting for @(posedge t.clk_inv) at t/t_timing_eval_act.v:25
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_initial__TOP__Vtiming__2
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root____VbeforeTrig_h########__0
|
||||
-V{t#,#} Suspending process waiting for @([event] t.a) at t/t_timing_eval_act.v:33
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_initial__TOP__Vtiming__3
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_settle
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__stl
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_triggers_vec__stl
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___dump_triggers__stl
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__stl
|
||||
-V{t#,#} 'stl' region trigger index 0 is active: Internal 'stl' trigger - first iteration
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__stl
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_stl
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___act_comb__TOP__0
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__stl
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_triggers_vec__stl
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___dump_triggers__stl
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__stl
|
||||
-V{t#,#} No 'stl' region triggers active
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__stl
|
||||
-V{t#,#}+ Eval
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_triggers_vec__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_ready
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___dump_triggers__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#} No 'act' region triggers active
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__nba
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#}End-of-eval cleanup
|
||||
-V{t#,#}+++++TOP Evaluate Vt_timing_eval_act::eval_step
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_debug_assertions
|
||||
-V{t#,#}+ Eval
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_triggers_vec__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_ready
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___dump_triggers__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#} 'act' region trigger index 3 is active: @([true] __VdlySched.awaitingCurrentTime())
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_resume
|
||||
-V{t#,#} No process to resume waiting for @([event] t.a)
|
||||
-V{t#,#} Not triggered processes waiting for @([event] t.a):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:18
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:33
|
||||
-V{t#,#} Resuming processes waiting for @([event] t.a)
|
||||
-V{t#,#} No process to resume waiting for @(posedge t.clk_inv)
|
||||
-V{t#,#} Not triggered processes waiting for @(posedge t.clk_inv):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:25
|
||||
-V{t#,#} Resuming processes waiting for @(posedge t.clk_inv)
|
||||
-V{t#,#} No process to resume waiting for @([event] t.e)
|
||||
-V{t#,#} Resuming processes waiting for @([event] t.e)
|
||||
-V{t#,#} Delayed processes:
|
||||
-V{t#,#} Awaiting time 1: Process waiting at t/t_timing_eval_act.v:39
|
||||
-V{t#,#} Resuming delayed processes
|
||||
-V{t#,#} Resuming: Process waiting at t/t_timing_eval_act.v:39
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___act_comb__TOP__0
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_triggers_vec__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_ready
|
||||
-V{t#,#} Committing processes waiting for @([event] t.a):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:18
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:33
|
||||
-V{t#,#} Committing processes waiting for @(posedge t.clk_inv):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:25
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___dump_triggers__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#} 'act' region trigger index 0 is active: @([event] t.a)
|
||||
-V{t#,#} 'act' region trigger index 1 is active: @(posedge t.clk_inv)
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_resume
|
||||
-V{t#,#} Moving to resume queue processes waiting for @([event] t.a):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:18
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:33
|
||||
-V{t#,#} Moving to resume queue processes waiting for @(posedge t.clk_inv):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:25
|
||||
-V{t#,#} Processes to resume waiting for @([event] t.a):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:18
|
||||
-V{t#,#} Processes to resume waiting for @([event] t.a):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:33
|
||||
-V{t#,#} Resuming processes waiting for @([event] t.a)
|
||||
-V{t#,#} Resuming: Process waiting at t/t_timing_eval_act.v:18
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root____VbeforeTrig_h########__0
|
||||
-V{t#,#} Suspending process waiting for @(posedge t.clk_inv) at t/t_timing_eval_act.v:19
|
||||
-V{t#,#} Resuming: Process waiting at t/t_timing_eval_act.v:33
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root____VbeforeTrig_h########__0
|
||||
-V{t#,#} Suspending process waiting for @([event] t.e) at t/t_timing_eval_act.v:34
|
||||
-V{t#,#} Processes to resume waiting for @(posedge t.clk_inv):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:25
|
||||
-V{t#,#} Not triggered processes waiting for @(posedge t.clk_inv):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:19
|
||||
-V{t#,#} Resuming processes waiting for @(posedge t.clk_inv)
|
||||
-V{t#,#} Resuming: Process waiting at t/t_timing_eval_act.v:25
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root____VbeforeTrig_h########__0
|
||||
-V{t#,#} Committing processes waiting for @([event] t.e):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:34
|
||||
-V{t#,#} Suspending process waiting for @([event] t.e) at t/t_timing_eval_act.v:28
|
||||
-V{t#,#} No process to resume waiting for @([event] t.e)
|
||||
-V{t#,#} Triggered processes waiting for @([event] t.e):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:28
|
||||
-V{t#,#} Not triggered processes waiting for @([event] t.e):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:28
|
||||
-V{t#,#} Resuming processes waiting for @([event] t.e)
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___act_comb__TOP__0
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_triggers_vec__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_ready
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___dump_triggers__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#} 'act' region trigger index 2 is active: @([event] t.e)
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_resume
|
||||
-V{t#,#} Moving to resume queue processes waiting for @([event] t.e):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:34
|
||||
-V{t#,#} No process to resume waiting for @([event] t.a)
|
||||
-V{t#,#} Resuming processes waiting for @([event] t.a)
|
||||
-V{t#,#} No process to resume waiting for @(posedge t.clk_inv)
|
||||
-V{t#,#} Not triggered processes waiting for @(posedge t.clk_inv):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:19
|
||||
-V{t#,#} Resuming processes waiting for @(posedge t.clk_inv)
|
||||
-V{t#,#} Processes to resume waiting for @([event] t.e):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:34
|
||||
-V{t#,#} Not triggered processes waiting for @([event] t.e):
|
||||
-V{t#,#} - Process waiting at t/t_timing_eval_act.v:28
|
||||
-V{t#,#} Resuming processes waiting for @([event] t.e)
|
||||
-V{t#,#} Resuming: Process waiting at t/t_timing_eval_act.v:34
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___act_comb__TOP__0
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_triggers_vec__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_ready
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___dump_triggers__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#} No 'act' region triggers active
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__nba
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_nba
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___act_comb__TOP__0
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_clear__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_triggers_vec__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___timing_ready
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___dump_triggers__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#} No 'act' region triggers active
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_phase__nba
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___trigger_anySet__act
|
||||
-V{t#,#}End-of-eval cleanup
|
||||
-V{t#,#}+ Vt_timing_eval_act___024root___eval_final
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2026 by Wilson Snyder. This program is free software; you
|
||||
# can redistribute it and/or modify it under the terms of either the GNU
|
||||
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||
# Version 2.0.
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
test.compile(verilator_flags2=["--binary", "--runtime-debug"])
|
||||
|
||||
test.file_grep(
|
||||
test.obj_dir + "/" + test.vm_prefix + "___024root__0.cpp", r'void\s+' + test.vm_prefix +
|
||||
r'___024root___timing_resume\(' + test.vm_prefix + r'___024root\*\s+vlSelf\)\s+\{' +
|
||||
r'\n((?!})(.|\n))*' + r'\/\*' + test.top_filename + r':18\s0x[0-9a-f]+\*\/\s*' +
|
||||
r'vlSelfRef\.__VtrigSched_[\d\w]*\.resume\([\n\s]*\"@\(\[event\]\st\.a\)\"\);\n\s+' + r'\/\*' +
|
||||
test.top_filename + r':19\s0x[0-9a-f]+\*\/\s*' +
|
||||
r'vlSelfRef\.__VtrigSched_[\d\w]*\.resume\([\n\s]*\"@\(posedge\st\.clk_inv\)\"\);\n\s+' +
|
||||
r'\/\*' + test.top_filename + r':20\s0x[0-9a-f]+\*\/\s*' +
|
||||
r'vlSelfRef\.__VtrigSched_[\d\w]*\.resume\([\n\s]*\"@\(\[event\]\st\.e\)\"\);')
|
||||
|
||||
test.execute(all_run_flags=["+verilator+debug"], expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2026 by Antmicro.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t;
|
||||
logic clk = 1;
|
||||
logic clk_inv;
|
||||
event a;
|
||||
event e;
|
||||
|
||||
// This $c is required to prevent inlining clk_inv as ~clk
|
||||
assign clk_inv = $c(1) & ~clk;
|
||||
|
||||
// This is needed to provide right order of resumption in scheduler
|
||||
initial begin
|
||||
@a;
|
||||
@(posedge clk_inv);
|
||||
@e;
|
||||
end
|
||||
|
||||
initial begin
|
||||
forever begin
|
||||
@(posedge clk_inv) begin
|
||||
clk = 1;
|
||||
->e;
|
||||
@e;
|
||||
end
|
||||
end
|
||||
end
|
||||
initial begin
|
||||
@a;
|
||||
@e;
|
||||
if (clk_inv != 0) $stop;
|
||||
$finish;
|
||||
end
|
||||
initial begin
|
||||
#1;
|
||||
->a;
|
||||
clk = 0;
|
||||
#2 $stop;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -16,11 +16,11 @@ internalsDump:
|
|||
-V{t#,#}+ Eval
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers_vec__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
|
||||
-V{t#,#} No 'act' region triggers active
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_orInto__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
|
||||
-V{t#,#}End-of-eval cleanup
|
||||
|
|
@ -29,22 +29,22 @@ internalsDump:
|
|||
-V{t#,#}+ Eval
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers_vec__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
|
||||
-V{t#,#} 'act' region trigger index 0 is active: @(posedge clk)
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_orInto__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_nba
|
||||
*-* All Finished *-*
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_clear__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers_vec__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
|
||||
-V{t#,#} No 'act' region triggers active
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_orInto__act
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_orInto__act_vec_vec
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___eval_phase__nba
|
||||
-V{t#,#}+ Vt_verilated_debug___024root___trigger_anySet__act
|
||||
-V{t#,#}End-of-eval cleanup
|
||||
|
|
|
|||
Loading…
Reference in New Issue