mirror of
https://github.com/verilator/verilator.git
synced 2026-10-07 18:42:58 +02:00
+102
-43
@@ -12,11 +12,27 @@
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
// TimingSuspendableVisitor locates all C++ functions and processes that contain timing controls,
|
||||
// and marks them as suspendable. If a process calls a suspendable function, then it is also marked
|
||||
// as suspendable. If a function calls or overrides a suspendable function, it is also marked as
|
||||
// suspendable. TimingSuspendableVisitor creates a dependency graph to propagate this property. It
|
||||
// does not perform any AST transformations.
|
||||
// TimingSuspendableVisitor does not perform any AST transformations.
|
||||
// Instead it propagates two types of flags:
|
||||
// - flag "suspendable": (for detecting what will need to become a coroutine)
|
||||
// The visitor locates all C++ functions and processes that contain timing controls,
|
||||
// and marks them as suspendable. If a process calls a suspendable function,
|
||||
// then it is also marked as suspendable. If a function calls or overrides
|
||||
// a suspendable function, it is also marked as suspendable.
|
||||
// TimingSuspendableVisitor creates a dependency graph to propagate this property.
|
||||
// - flag "needs process": (for detecting what needs a VlProcess argument in signature)
|
||||
// The visitor distinguishes 4 types of nodes:
|
||||
// - T_ALLOCS_PROC: nodes that can allocate VlProcess (forks, always, initial etc.),
|
||||
// - T_FORCES_PROC: nodes that make it necessary for the previous type to allocate VlProcess
|
||||
// (like process::self which then wraps it, allowing use inside Verilog).
|
||||
// - T_NEEDS_PROC: nodes that should obtain VlProcess if it will be allocated
|
||||
// (all of the previous type + timing controls, so they could update process state),
|
||||
// - T_HAS_PROC: nodes that are going to be emitted with a VlProcess argument.
|
||||
// T_FORCES_PROC and T_NEEDS_PROC are propagated upwards up to the nodes of type T_ALLOCS_PROC,
|
||||
// this is to detect which processes have to allocate VlProcess. Then nodes of these processes
|
||||
// get marked as T_HAS_PROC and the flag is propagated downwards through nodes
|
||||
// type T_NEEDS_PROC. Using only nodes type T_NEEDS_PROC assures the flags are only propagated
|
||||
// through paths leading to nodes that actually use VlProcess.
|
||||
//
|
||||
// TimingControlVisitor is the one that actually performs transformations:
|
||||
// - for each intra-assignment timing control:
|
||||
@@ -70,8 +86,10 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
enum NodeFlag : uint8_t {
|
||||
T_SUSPENDEE = 1 << 0, // Suspendable (due to dependence on another suspendable)
|
||||
T_SUSPENDER = 1 << 1, // Suspendable (has timing control)
|
||||
T_HAS_PROC = 1 << 2, // Has an associated std::process
|
||||
T_CALLS_PROC_SELF = 1 << 3, // Calls std::process::self
|
||||
T_ALLOCS_PROC = 1 << 2, // Can allocate VlProcess
|
||||
T_FORCES_PROC = 1 << 3, // Forces VlProcess allocation
|
||||
T_NEEDS_PROC = 1 << 4, // Needs access to VlProcess if it's allocated
|
||||
T_HAS_PROC = 1 << 5, // Has VlProcess argument in the signature
|
||||
};
|
||||
|
||||
enum ForkType : uint8_t {
|
||||
@@ -86,6 +104,11 @@ enum PropagationType : uint8_t {
|
||||
P_SIGNATURE = 3, // Propagation required to maintain C++ function's signature requirements
|
||||
};
|
||||
|
||||
// Add timing flag to a node
|
||||
static void addFlags(AstNode* const nodep, uint8_t flags) { nodep->user2(nodep->user2() | flags); }
|
||||
// Check if a node has ALL of the expected flags set
|
||||
static bool hasFlags(AstNode* const nodep, uint8_t flags) { return !(~nodep->user2() & flags); }
|
||||
|
||||
// ######################################################################
|
||||
// Detect nodes affected by timing and/or requiring a process
|
||||
|
||||
@@ -126,8 +149,8 @@ private:
|
||||
class SuspendDepVtx final : public DepVtx {
|
||||
VL_RTTI_IMPL(SuspendDepVtx, DepVtx)
|
||||
string dotColor() const override {
|
||||
if (nodep()->user2() & T_SUSPENDER) return "red";
|
||||
if (nodep()->user2() & T_SUSPENDEE) return "blue";
|
||||
if (hasFlags(nodep(), T_SUSPENDER)) return "red";
|
||||
if (hasFlags(nodep(), T_SUSPENDEE)) return "blue";
|
||||
return "black";
|
||||
}
|
||||
|
||||
@@ -140,8 +163,9 @@ private:
|
||||
class NeedsProcDepVtx final : public DepVtx {
|
||||
VL_RTTI_IMPL(NeedsProcDepVtx, DepVtx)
|
||||
string dotColor() const override {
|
||||
if (nodep()->user2() & T_CALLS_PROC_SELF) return "red";
|
||||
if (nodep()->user2() & T_HAS_PROC) return "blue";
|
||||
if (hasFlags(nodep(), T_HAS_PROC)) return "blue";
|
||||
if (hasFlags(nodep(), T_NEEDS_PROC)) return "green";
|
||||
if (hasFlags(nodep(), T_FORCES_PROC)) return "red";
|
||||
return "black";
|
||||
}
|
||||
|
||||
@@ -198,10 +222,10 @@ private:
|
||||
if (!nodep->user5p()) nodep->user5p(new NeedsProcDepVtx{&m_procGraph, nodep, classp});
|
||||
return nodep->user5u().to<NeedsProcDepVtx*>();
|
||||
}
|
||||
// Set timing flag of a node
|
||||
// Pass timing flag between nodes
|
||||
bool passFlag(const AstNode* from, AstNode* to, NodeFlag flag) {
|
||||
if ((from->user2() & flag) && !(to->user2() & flag)) {
|
||||
to->user2(to->user2() | flag);
|
||||
addFlags(to, flag);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -216,6 +240,15 @@ private:
|
||||
}
|
||||
}
|
||||
template <typename Predicate>
|
||||
void propagateFlagsIf(DepVtx* const vxp, NodeFlag flag, Predicate p) {
|
||||
auto* const parentp = vxp->nodep();
|
||||
for (V3GraphEdge* edgep = vxp->outBeginp(); edgep; edgep = edgep->outNextp()) {
|
||||
auto* const depVxp = static_cast<DepVtx*>(edgep->top());
|
||||
AstNode* const depp = depVxp->nodep();
|
||||
if (p(edgep) && passFlag(parentp, depp, flag)) propagateFlagsIf(depVxp, flag, p);
|
||||
}
|
||||
}
|
||||
template <typename Predicate>
|
||||
void propagateFlagsReversedIf(DepVtx* const vxp, NodeFlag flag, Predicate p) {
|
||||
auto* const parentp = vxp->nodep();
|
||||
for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
||||
@@ -236,17 +269,22 @@ private:
|
||||
void visit(AstNodeProcedure* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
if (nodep->needProcess()) nodep->user2(T_HAS_PROC | T_CALLS_PROC_SELF);
|
||||
getNeedsProcDepVtx(nodep);
|
||||
addFlags(nodep, T_ALLOCS_PROC);
|
||||
if (VN_IS(nodep, Always)) {
|
||||
UINFO(1, "Always does " << (nodep->needProcess() ? "" : "NOT ") << "need process\n");
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstDisableFork* nodep) override {
|
||||
visit(static_cast<AstNode*>(nodep));
|
||||
addFlags(m_procp, T_FORCES_PROC | T_NEEDS_PROC);
|
||||
}
|
||||
void visit(AstCFunc* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
iterateChildren(nodep);
|
||||
if (nodep->needProcess()) nodep->user2(T_HAS_PROC | T_CALLS_PROC_SELF);
|
||||
if (nodep->needProcess()) addFlags(nodep, T_FORCES_PROC | T_NEEDS_PROC);
|
||||
DepVtx* const sVxp = getSuspendDepVtx(nodep);
|
||||
DepVtx* const pVxp = getNeedsProcDepVtx(nodep);
|
||||
if (!m_classp) return;
|
||||
@@ -287,9 +325,12 @@ private:
|
||||
new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep->funcp()),
|
||||
getSuspendDepVtx(m_procp), m_underFork ? P_FORK : P_CALL};
|
||||
|
||||
if (!m_underFork)
|
||||
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()),
|
||||
getNeedsProcDepVtx(m_procp), P_CALL};
|
||||
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep->funcp()),
|
||||
getNeedsProcDepVtx(m_procp), P_CALL};
|
||||
|
||||
if (m_underFork && !(m_underFork & F_MIGHT_SUSPEND)) {
|
||||
addFlags(nodep, T_NEEDS_PROC | T_ALLOCS_PROC);
|
||||
}
|
||||
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
@@ -301,9 +342,12 @@ private:
|
||||
new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(nodep), getSuspendDepVtx(m_procp),
|
||||
m_underFork ? P_FORK : P_CALL};
|
||||
|
||||
if (!m_underFork)
|
||||
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep), getNeedsProcDepVtx(m_procp),
|
||||
P_CALL};
|
||||
new V3GraphEdge{&m_procGraph, getNeedsProcDepVtx(nodep), getNeedsProcDepVtx(m_procp),
|
||||
P_CALL};
|
||||
|
||||
if (m_underFork && !(m_underFork & F_MIGHT_SUSPEND)) {
|
||||
addFlags(nodep, T_NEEDS_PROC | T_ALLOCS_PROC);
|
||||
}
|
||||
|
||||
m_procp = nodep;
|
||||
m_underFork = 0;
|
||||
@@ -316,7 +360,7 @@ private:
|
||||
// so that transformForks() in V3SchedTiming gets called and
|
||||
// removes all forks and begins
|
||||
if (nodep->isTimingControl() && m_procp) {
|
||||
m_procp->user2(T_SUSPENDEE | T_SUSPENDER);
|
||||
addFlags(m_procp, T_SUSPENDEE | T_SUSPENDER);
|
||||
m_underFork |= F_MIGHT_SUSPEND;
|
||||
}
|
||||
m_underFork |= F_MIGHT_NEED_PROC;
|
||||
@@ -325,7 +369,7 @@ private:
|
||||
void visit(AstNode* nodep) override {
|
||||
if (nodep->isTimingControl()) {
|
||||
v3Global.setUsesTiming();
|
||||
if (m_procp) m_procp->user2(T_SUSPENDEE | T_SUSPENDER);
|
||||
if (m_procp) addFlags(m_procp, T_SUSPENDEE | T_SUSPENDER | T_NEEDS_PROC);
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
@@ -342,22 +386,35 @@ public:
|
||||
// Propagate suspendability
|
||||
for (V3GraphVertex* vxp = m_suspGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
||||
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
|
||||
if (depVxp->nodep()->user2() & T_SUSPENDEE) propagateFlags(depVxp, T_SUSPENDEE);
|
||||
if (hasFlags(depVxp->nodep(), T_SUSPENDEE)) propagateFlags(depVxp, T_SUSPENDEE);
|
||||
}
|
||||
if (dumpGraphLevel() >= 6) m_suspGraph.dumpDotFilePrefixed("timing_deps");
|
||||
// Propagate process
|
||||
|
||||
// Propagate T_HAS_PROCESS
|
||||
for (V3GraphVertex* vxp = m_procGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
||||
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
|
||||
if (depVxp->nodep()->user2() & T_HAS_PROC) propagateFlags(depVxp, T_HAS_PROC);
|
||||
}
|
||||
// Propagate process downwards (from caller to callee) for suspendable calls
|
||||
for (V3GraphVertex* vxp = m_suspGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
||||
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
|
||||
if (depVxp->nodep()->user2() & T_HAS_PROC)
|
||||
propagateFlagsReversedIf(depVxp, T_HAS_PROC, [&](const V3GraphEdge* e) -> bool {
|
||||
return (e->weight() != P_FORK)
|
||||
&& (static_cast<DepVtx*>(e->top())->nodep()->user2() & T_SUSPENDEE);
|
||||
// Find processes that'll allocate VlProcess
|
||||
if (hasFlags(depVxp->nodep(), T_FORCES_PROC)) {
|
||||
propagateFlagsIf(depVxp, T_FORCES_PROC, [&](const V3GraphEdge* e) -> bool {
|
||||
return !hasFlags(static_cast<DepVtx*>(e->fromp())->nodep(), T_ALLOCS_PROC);
|
||||
});
|
||||
}
|
||||
// Mark nodes on paths between processes and statements that use VlProcess
|
||||
if (hasFlags(depVxp->nodep(), T_NEEDS_PROC)) {
|
||||
propagateFlagsIf(depVxp, T_NEEDS_PROC, [&](const V3GraphEdge* e) -> bool {
|
||||
return !hasFlags(static_cast<DepVtx*>(e->top())->nodep(), T_ALLOCS_PROC);
|
||||
});
|
||||
}
|
||||
}
|
||||
for (V3GraphVertex* vxp = m_procGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
||||
DepVtx* const depVxp = static_cast<DepVtx*>(vxp);
|
||||
// Mark nodes that will be emitted with a VlProcess argument
|
||||
if (hasFlags(depVxp->nodep(), T_ALLOCS_PROC | T_FORCES_PROC)) {
|
||||
addFlags(depVxp->nodep(), T_HAS_PROC);
|
||||
propagateFlagsReversedIf(depVxp, T_HAS_PROC, [&](const V3GraphEdge* e) -> bool {
|
||||
return hasFlags(static_cast<DepVtx*>(e->fromp())->nodep(), T_NEEDS_PROC);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (dumpGraphLevel() >= 6) m_procGraph.dumpDotFilePrefixed("proc_deps");
|
||||
}
|
||||
@@ -586,7 +643,7 @@ private:
|
||||
void addProcessInfo(AstCMethodHard* const methodp) const {
|
||||
FileLine* const flp = methodp->fileline();
|
||||
AstCExpr* const ap = new AstCExpr{
|
||||
flp, m_procp && (m_procp->user2() & T_HAS_PROC) ? "vlProcess" : "nullptr", 0};
|
||||
flp, m_procp && (hasFlags(m_procp, T_HAS_PROC)) ? "vlProcess" : "nullptr", 0};
|
||||
ap->dtypeSetVoid();
|
||||
methodp->addPinsp(ap);
|
||||
}
|
||||
@@ -697,8 +754,8 @@ private:
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
iterateChildren(nodep);
|
||||
if (nodep->user2() & T_SUSPENDEE) nodep->setSuspendable();
|
||||
if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess();
|
||||
if (hasFlags(nodep, T_SUSPENDEE)) nodep->setSuspendable();
|
||||
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
|
||||
}
|
||||
void visit(AstInitial* nodep) override {
|
||||
visit(static_cast<AstNodeProcedure*>(nodep));
|
||||
@@ -719,11 +776,11 @@ private:
|
||||
|
||||
// Workaround for killing `always` processes (doing that is pretty much UB)
|
||||
// TODO: Disallow killing `always` at runtime (throw an error)
|
||||
if (nodep->user2() & T_HAS_PROC) nodep->user2(nodep->user2() | T_SUSPENDEE);
|
||||
if (hasFlags(nodep, T_HAS_PROC)) addFlags(nodep, T_SUSPENDEE);
|
||||
|
||||
iterateChildren(nodep);
|
||||
if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess();
|
||||
if (!(nodep->user2() & T_SUSPENDEE)) return;
|
||||
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
|
||||
if (!hasFlags(nodep, T_SUSPENDEE)) return;
|
||||
nodep->setSuspendable();
|
||||
FileLine* const flp = nodep->fileline();
|
||||
AstSenTree* const sensesp = m_activep->sensesp();
|
||||
@@ -744,8 +801,8 @@ private:
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
iterateChildren(nodep);
|
||||
if (nodep->user2() & T_HAS_PROC) nodep->setNeedProcess();
|
||||
if (!(nodep->user2() & T_SUSPENDEE)) return;
|
||||
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
|
||||
if (!(hasFlags(nodep, T_SUSPENDEE))) return;
|
||||
|
||||
nodep->rtnType("VlCoroutine");
|
||||
// If in a class, create a shared pointer to 'this'
|
||||
@@ -768,7 +825,7 @@ private:
|
||||
}
|
||||
}
|
||||
void visit(AstNodeCCall* nodep) override {
|
||||
if ((nodep->funcp()->user2() & T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable
|
||||
if (hasFlags(nodep->funcp(), T_SUSPENDEE) && !nodep->user1SetOnce()) { // If suspendable
|
||||
VNRelinker relinker;
|
||||
nodep->unlinkFrBack(&relinker);
|
||||
AstCAwait* const awaitp = new AstCAwait{nodep->fileline(), nodep};
|
||||
@@ -1048,6 +1105,7 @@ private:
|
||||
void visit(AstBegin* nodep) override {
|
||||
VL_RESTORER(m_procp);
|
||||
m_procp = nodep;
|
||||
if (hasFlags(nodep, T_HAS_PROC)) nodep->setNeedProcess();
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstFork* nodep) override {
|
||||
@@ -1061,6 +1119,7 @@ private:
|
||||
while (stmtp) {
|
||||
if (!VN_IS(stmtp, Begin)) {
|
||||
auto* const beginp = new AstBegin{stmtp->fileline(), "", nullptr};
|
||||
if (hasFlags(stmtp, T_HAS_PROC)) addFlags(beginp, T_HAS_PROC);
|
||||
stmtp->replaceWith(beginp);
|
||||
beginp->addStmtsp(stmtp);
|
||||
stmtp = beginp;
|
||||
|
||||
Reference in New Issue
Block a user