974 lines
46 KiB
C++
974 lines
46 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Prepare AST for timing features
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Copyright 2003-2023 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
|
|
//
|
|
// 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.
|
|
//
|
|
// TimingControlVisitor is the one that actually performs transformations:
|
|
// - for each intra-assignment timing control:
|
|
// - if it's a continuous assignment, transform it into an always
|
|
// - introduce an intermediate variable
|
|
// - write the original RHS to the intermediate variable before the timing control
|
|
// - write the intermediate variable to the original LHS after the timing control
|
|
// - for each delay:
|
|
// - scale it according to the module's timescale
|
|
// - replace it with a CAwait statement waiting on the global delay scheduler (with the
|
|
// specified delay value)
|
|
// - if there is no global delay scheduler (see verilated_timing.{h,cpp}), create it
|
|
// - for each event control:
|
|
// - if there is no corresponding trigger scheduler (see verilated_timing.{h,cpp}), create it
|
|
// - replace with a CAwait statement waiting on the corresponding trigger scheduler
|
|
// - for each wait(cond) statement:
|
|
// - replace it with a loop like: while (!cond) @(<vars from cond>)
|
|
// - for each fork:
|
|
// - put each statement in a begin if it isn't in one already
|
|
// - if it's not a fork..join_none:
|
|
// - create a join sync variable
|
|
// - create statements that sync the main process with its children
|
|
//
|
|
// See the internals documentation docs/internals.rst for more details.
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "config_build.h"
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3Timing.h"
|
|
|
|
#include "V3Ast.h"
|
|
#include "V3Const.h"
|
|
#include "V3EmitV.h"
|
|
#include "V3Graph.h"
|
|
#include "V3MemberMap.h"
|
|
#include "V3SenExprBuilder.h"
|
|
#include "V3SenTree.h"
|
|
#include "V3UniqueNames.h"
|
|
|
|
#include <queue>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
// ######################################################################
|
|
|
|
enum TimingFlag : uint8_t {
|
|
// Properties of flags with higher numbers include properties of flags with
|
|
// lower numbers
|
|
T_NORM = 0, // Normal non-suspendable process
|
|
T_SUSP = 1, // Suspendable
|
|
T_PROC = 2 // Suspendable with process metadata
|
|
};
|
|
|
|
// ######################################################################
|
|
// Detect nodes affected by timing
|
|
|
|
class TimingSuspendableVisitor final : public VNVisitor {
|
|
private:
|
|
// TYPES
|
|
// Vertex of a dependency graph of suspendable nodes, e.g. if a node (process or task) is
|
|
// suspendable, all its dependents should also be suspendable
|
|
class TimingDependencyVertex final : public V3GraphVertex {
|
|
AstNode* const m_nodep; // AST node represented by this graph vertex
|
|
|
|
// ACCESSORS
|
|
string name() const override VL_MT_STABLE {
|
|
return cvtToHex(nodep()) + ' ' + nodep()->prettyTypeName();
|
|
}
|
|
FileLine* fileline() const override { return nodep()->fileline(); }
|
|
string dotColor() const override { return nodep()->user2() ? "red" : "black"; }
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
TimingDependencyVertex(V3Graph* graphp, AstNode* nodep)
|
|
: V3GraphVertex{graphp}
|
|
, m_nodep{nodep} {}
|
|
~TimingDependencyVertex() override = default;
|
|
|
|
// ACCESSORS
|
|
virtual AstNode* nodep() const VL_MT_STABLE { return m_nodep; }
|
|
};
|
|
|
|
// NODE STATE
|
|
// AstClass::user1() -> bool. Set true if the class
|
|
// member cache has been
|
|
// refreshed.
|
|
// Ast{NodeProcedure,CFunc,Begin}::user2() -> int. Set to >= T_SUSP if
|
|
// process/task suspendable
|
|
// and to T_PROC if it
|
|
// needs process metadata.
|
|
// Ast{NodeProcedure,CFunc,Begin}::user3() -> DependencyVertex*. Vertex in m_depGraph
|
|
const VNUser1InUse m_user1InUse;
|
|
const VNUser2InUse m_user2InUse;
|
|
const VNUser3InUse m_user3InUse;
|
|
|
|
// STATE
|
|
VMemberMap memberMap; // Member names cached for fast lookup
|
|
AstClass* m_classp = nullptr; // Current class
|
|
AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under
|
|
V3Graph m_depGraph; // Dependency graph where a node is a dependency of another if it being
|
|
// suspendable makes the other node suspendable
|
|
|
|
// METHODS
|
|
// Get or create the dependency vertex for the given node
|
|
TimingDependencyVertex* getDependencyVertex(AstNode* const nodep) {
|
|
if (!nodep->user3p()) nodep->user3p(new TimingDependencyVertex{&m_depGraph, nodep});
|
|
return nodep->user3u().to<TimingDependencyVertex*>();
|
|
}
|
|
// Set timing flag of a node
|
|
bool setTimingFlag(AstNode* nodep, int flag) {
|
|
// Properties of flags with higher numbers include properties of flags with lower
|
|
// numbers, so modify nodep->user2() only if it will increase.
|
|
if (nodep->user2() < flag) {
|
|
nodep->user2(flag);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
// Propagate suspendable/needProcess flag to all nodes that depend on the given one
|
|
void propagateTimingFlags(TimingDependencyVertex* const vxp) {
|
|
auto* const parentp = vxp->nodep();
|
|
for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
|
|
auto* const depVxp = static_cast<TimingDependencyVertex*>(edgep->fromp());
|
|
AstNode* const depp = depVxp->nodep();
|
|
if (setTimingFlag(depp, parentp->user2())) propagateTimingFlags(depVxp);
|
|
}
|
|
}
|
|
|
|
// VISITORS
|
|
void visit(AstClass* nodep) override {
|
|
UASSERT(!m_classp, "Class under class");
|
|
VL_RESTORER(m_classp);
|
|
m_classp = nodep;
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstNodeProcedure* nodep) override {
|
|
VL_RESTORER(m_procp);
|
|
m_procp = nodep;
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstCFunc* nodep) override {
|
|
VL_RESTORER(m_procp);
|
|
m_procp = nodep;
|
|
iterateChildren(nodep);
|
|
TimingDependencyVertex* const vxp = getDependencyVertex(nodep);
|
|
if (nodep->needProcess()) nodep->user2(T_PROC);
|
|
if (!m_classp) return;
|
|
|
|
// Go over overridden functions
|
|
|
|
std::queue<AstClassExtends*> extends;
|
|
if (m_classp->extendsp()) extends.push(m_classp->extendsp());
|
|
|
|
while (!extends.empty()) {
|
|
AstClassExtends* ext_list = extends.front();
|
|
extends.pop();
|
|
|
|
for (AstClassExtends* cextp = ext_list; cextp;
|
|
cextp = VN_AS(cextp->nextp(), ClassExtends)) {
|
|
// TODO: It is possible that a methods the same name in the base class is not
|
|
// actually overridden by our method. If this causes a problem, traverse to
|
|
// the root of the inheritance hierarchy and check if the original method is
|
|
// virtual or not.
|
|
if (auto* const overriddenp
|
|
= VN_CAST(memberMap.findMember(cextp->classp(), nodep->name()), CFunc)) {
|
|
setTimingFlag(nodep, overriddenp->user2());
|
|
if (nodep->user2()
|
|
< T_PROC) { // Add a vertex only if the flag can still change
|
|
// Make a dependency cycle, as being suspendable should propagate both up
|
|
// and down the inheritance tree
|
|
TimingDependencyVertex* const overriddenVxp
|
|
= getDependencyVertex(overriddenp);
|
|
new V3GraphEdge{&m_depGraph, vxp, overriddenVxp, 1};
|
|
new V3GraphEdge{&m_depGraph, overriddenVxp, vxp, 1};
|
|
}
|
|
} else {
|
|
AstClassExtends* more_extends = cextp->classp()->extendsp();
|
|
if (more_extends) extends.push(more_extends);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void visit(AstNodeCCall* nodep) override {
|
|
setTimingFlag(m_procp, nodep->funcp()->user2());
|
|
if (m_procp->user2() < T_PROC) { // Add a vertex only if the flag can still change
|
|
TimingDependencyVertex* const procVxp = getDependencyVertex(m_procp);
|
|
TimingDependencyVertex* const funcVxp = getDependencyVertex(nodep->funcp());
|
|
new V3GraphEdge{&m_depGraph, procVxp, funcVxp, 1};
|
|
iterateChildren(nodep);
|
|
}
|
|
}
|
|
void visit(AstBegin* nodep) override {
|
|
VL_RESTORER(m_procp);
|
|
m_procp = nodep;
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstFork* nodep) override {
|
|
v3Global.setUsesTiming(); // Even if there are no event controls, we have to set this flag
|
|
// so that transformForks() in V3SchedTiming gets called and
|
|
// removes all forks and begins
|
|
if (nodep->isTimingControl() && m_procp) m_procp->user2(T_SUSP);
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstNode* nodep) override {
|
|
if (nodep->isTimingControl()) {
|
|
v3Global.setUsesTiming();
|
|
if (m_procp) m_procp->user2(T_SUSP);
|
|
}
|
|
iterateChildren(nodep);
|
|
}
|
|
|
|
//--------------------
|
|
void visit(AstVar*) override {} // Accelerate
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit TimingSuspendableVisitor(AstNetlist* nodep) {
|
|
iterate(nodep);
|
|
m_depGraph.removeTransitiveEdges();
|
|
for (V3GraphVertex* vxp = m_depGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
|
|
TimingDependencyVertex* const depVxp = static_cast<TimingDependencyVertex*>(vxp);
|
|
if (depVxp->nodep()->user2()) propagateTimingFlags(depVxp);
|
|
}
|
|
if (dumpGraphLevel() >= 6) m_depGraph.dumpDotFilePrefixed("timing_deps");
|
|
}
|
|
~TimingSuspendableVisitor() override = default;
|
|
};
|
|
|
|
// ######################################################################
|
|
// Transform nodes affected by timing
|
|
|
|
class TimingControlVisitor final : public VNVisitor {
|
|
private:
|
|
// NODE STATE
|
|
// Ast{Always,NodeCCall,Fork,NodeAssign}::user1() -> bool. Set true if the node has
|
|
// been processed.
|
|
// AstSenTree::user1() -> AstVarScope*. Trigger scheduler assigned
|
|
// to this sentree
|
|
// Ast{NodeProcedure,CFunc,Begin}::user2() -> bool. Set true if process/task
|
|
// is suspendable
|
|
// Ast{EventControl}::user2() -> bool. Set true if event control
|
|
// should immediately be
|
|
// committed
|
|
// AstSenTree::user2() -> AstCExpr*. Debug info passed to the
|
|
// timing schedulers
|
|
// const VNUser1InUse m_user1InUse; (Allocated for use in SuspendableVisitor)
|
|
// const VNUser2InUse m_user2InUse; (Allocated for use in SuspendableVisitor)
|
|
|
|
// STATE
|
|
// Current context
|
|
AstNetlist* const m_netlistp; // Root node
|
|
AstScope* const m_scopeTopp = m_netlistp->topScopep()->scopep(); // Scope at the top
|
|
AstClass* m_classp = nullptr; // Current class
|
|
AstScope* m_scopep = nullptr; // Current scope
|
|
AstActive* m_activep = nullptr; // Current active
|
|
AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under
|
|
double m_timescaleFactor = 1.0; // Factor to scale delays by
|
|
int m_forkCnt = 0; // Number of forks inside a module
|
|
|
|
// Unique names
|
|
V3UniqueNames m_contAssignVarNames{"__VassignWtmp"}; // Names for temp AssignW vars
|
|
V3UniqueNames m_intraValueNames{"__Vintraval"}; // Intra assign delay value var names
|
|
V3UniqueNames m_intraIndexNames{"__Vintraidx"}; // Intra assign delay index var names
|
|
V3UniqueNames m_intraLsbNames{"__Vintralsb"}; // Intra assign delay LSB var names
|
|
V3UniqueNames m_trigSchedNames{"__VtrigSched"}; // Trigger scheduler name generator
|
|
V3UniqueNames m_dynTrigNames{"__VdynTrigger"}; // Dynamic trigger name generator
|
|
|
|
// DTypes
|
|
AstBasicDType* m_forkDtp = nullptr; // Fork variable type
|
|
AstBasicDType* m_trigSchedDtp = nullptr; // Trigger scheduler type
|
|
|
|
// Timing-related globals
|
|
AstVarScope* m_delaySchedp = nullptr; // Global delay scheduler
|
|
AstVarScope* m_dynamicSchedp = nullptr; // Global dynamic trigger scheduler
|
|
AstSenTree* m_delaySensesp = nullptr; // Domain to trigger if a delayed coroutine is resumed
|
|
AstSenTree* m_dynamicSensesp = nullptr; // Domain to trigger if a dynamic trigger is set
|
|
|
|
// Other
|
|
SenTreeFinder m_finder{m_netlistp}; // Sentree finder and uniquifier
|
|
SenExprBuilder* m_senExprBuilderp = nullptr; // Sens expression builder for current m_scope
|
|
|
|
// METHODS
|
|
// Find net delay on the LHS of an assignment
|
|
AstDelay* getLhsNetDelay(AstNodeAssign* nodep) const {
|
|
bool foundWrite = false;
|
|
AstDelay* delayp = nullptr;
|
|
nodep->lhsp()->foreach([&](const AstNodeVarRef* const refp) {
|
|
if (!refp->access().isWriteOrRW()) return;
|
|
UASSERT_OBJ(!foundWrite, nodep, "Should only be one variable written to on the LHS");
|
|
foundWrite = true;
|
|
if (refp->varp()->delayp()) {
|
|
delayp = refp->varp()->delayp();
|
|
delayp->unlinkFrBack();
|
|
}
|
|
});
|
|
return delayp;
|
|
}
|
|
// Transform an assignment with an intra timing control into a timing control with the
|
|
// assignment under it
|
|
AstNode* factorOutTimingControl(AstNodeAssign* nodep) const {
|
|
AstNode* stmtp = nodep;
|
|
AstDelay* delayp = getLhsNetDelay(nodep);
|
|
FileLine* const flp = nodep->fileline();
|
|
AstNode* const controlp = nodep->timingControlp();
|
|
if (controlp) {
|
|
controlp->unlinkFrBack();
|
|
if (auto* const assignDelayp = VN_CAST(controlp, Delay)) {
|
|
if (delayp) {
|
|
delayp->lhsp(new AstAdd{flp, delayp->lhsp()->unlinkFrBack(),
|
|
assignDelayp->lhsp()->unlinkFrBack()});
|
|
VL_DO_DANGLING(assignDelayp->deleteTree(), nodep);
|
|
} else {
|
|
delayp = assignDelayp;
|
|
}
|
|
}
|
|
}
|
|
if (delayp) {
|
|
stmtp->replaceWith(delayp);
|
|
delayp->addStmtsp(stmtp);
|
|
stmtp = delayp;
|
|
}
|
|
if (auto* const sensesp = VN_CAST(controlp, SenTree)) {
|
|
auto* const eventControlp = new AstEventControl{flp, sensesp, nullptr};
|
|
stmtp->replaceWith(eventControlp);
|
|
eventControlp->addStmtsp(stmtp);
|
|
stmtp = eventControlp;
|
|
} else if (auto* const beginp = VN_CAST(controlp, Begin)) {
|
|
// Begin from V3AssertPre
|
|
stmtp->replaceWith(beginp);
|
|
beginp->addStmtsp(stmtp);
|
|
stmtp = beginp;
|
|
}
|
|
return stmtp == nodep ? nullptr : stmtp;
|
|
}
|
|
// Calculate the factor to scale delays by
|
|
double calculateTimescaleFactor(VTimescale timeunit) const {
|
|
int scalePowerOfTen = timeunit.powerOfTen() - m_netlistp->timeprecision().powerOfTen();
|
|
return std::pow(10.0, scalePowerOfTen);
|
|
}
|
|
// Construct SenItems from VarRefs in an expression
|
|
AstSenItem* varRefpsToSenItemsp(AstNode* const nodep) const {
|
|
AstNode* senItemsp = nullptr;
|
|
const VNUser4InUse user4InUse;
|
|
nodep->foreach([&](AstNodeVarRef* refp) {
|
|
if (refp->access().isWriteOnly()) return;
|
|
AstVarScope* const vscp = refp->varScopep();
|
|
if (vscp->user4SetOnce()) return;
|
|
const bool isEvent = vscp->dtypep() && vscp->dtypep()->basicp()
|
|
&& vscp->dtypep()->basicp()->isEvent();
|
|
const auto edgeType = isEvent ? VEdgeType::ET_EVENT : VEdgeType::ET_CHANGED;
|
|
senItemsp = AstNode::addNext(
|
|
senItemsp, new AstSenItem{refp->fileline(), edgeType,
|
|
new AstVarRef{refp->fileline(), vscp, VAccess::READ}});
|
|
});
|
|
return VN_AS(senItemsp, SenItem);
|
|
}
|
|
// Creates the global delay scheduler variable
|
|
AstVarScope* getCreateDelayScheduler() {
|
|
if (m_delaySchedp) return m_delaySchedp;
|
|
auto* const dlySchedDtp = new AstBasicDType{
|
|
m_scopeTopp->fileline(), VBasicDTypeKwd::DELAY_SCHEDULER, VSigning::UNSIGNED};
|
|
m_netlistp->typeTablep()->addTypesp(dlySchedDtp);
|
|
m_delaySchedp = m_scopeTopp->createTemp("__VdlySched", dlySchedDtp);
|
|
// Delay scheduler has to be accessible from top
|
|
m_delaySchedp->varp()->sigPublic(true);
|
|
m_netlistp->delaySchedulerp(m_delaySchedp->varp());
|
|
return m_delaySchedp;
|
|
}
|
|
// Creates the delay sentree
|
|
AstSenTree* getCreateDelaySenTree() {
|
|
if (m_delaySensesp) return m_delaySensesp;
|
|
FileLine* const flp = m_scopeTopp->fileline();
|
|
auto* const awaitingCurrentTimep
|
|
= new AstCMethodHard{flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::READ},
|
|
"awaitingCurrentTime"};
|
|
awaitingCurrentTimep->dtypeSetBit();
|
|
m_delaySensesp
|
|
= new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE, awaitingCurrentTimep}};
|
|
m_netlistp->topScopep()->addSenTreesp(m_delaySensesp);
|
|
return m_delaySensesp;
|
|
}
|
|
// Creates the global dynamic trigger scheduler variable
|
|
AstVarScope* getCreateDynamicTriggerScheduler() {
|
|
if (m_dynamicSchedp) return m_dynamicSchedp;
|
|
auto* const dynSchedDtp
|
|
= new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::DYNAMIC_TRIGGER_SCHEDULER,
|
|
VSigning::UNSIGNED};
|
|
m_netlistp->typeTablep()->addTypesp(dynSchedDtp);
|
|
m_dynamicSchedp = m_scopeTopp->createTemp("__VdynSched", dynSchedDtp);
|
|
return m_dynamicSchedp;
|
|
}
|
|
// Creates the dynamic trigger sentree
|
|
AstSenTree* getCreateDynamicTriggerSenTree() {
|
|
if (m_dynamicSensesp) return m_dynamicSensesp;
|
|
FileLine* const flp = m_scopeTopp->fileline();
|
|
auto* const awaitingCurrentTimep = new AstCMethodHard{
|
|
flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::READ},
|
|
"evaluate"};
|
|
awaitingCurrentTimep->dtypeSetBit();
|
|
m_dynamicSensesp
|
|
= new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE, awaitingCurrentTimep}};
|
|
m_netlistp->topScopep()->addSenTreesp(m_dynamicSensesp);
|
|
return m_dynamicSensesp;
|
|
}
|
|
// Returns true if we are under a class or the given tree has any references to locals. These
|
|
// are cases where static, globally-evaluated triggers are not suitable.
|
|
bool needDynamicTrigger(AstNode* const nodep) const {
|
|
return m_classp || nodep->exists([](const AstNodeVarRef* const refp) {
|
|
return refp->varp()->isFuncLocal();
|
|
});
|
|
}
|
|
// Returns true if the given trigger expression needs a destructive post update after trigger
|
|
// evaluation. Currently this only applies to named events.
|
|
bool destructivePostUpdate(AstNode* const exprp) const {
|
|
return exprp->exists([](const AstNodeVarRef* const refp) {
|
|
AstBasicDType* const dtypep = refp->dtypep()->basicp();
|
|
return dtypep && dtypep->isEvent();
|
|
});
|
|
}
|
|
// Creates a trigger scheduler variable
|
|
AstVarScope* getCreateTriggerSchedulerp(AstSenTree* const sensesp) {
|
|
if (!sensesp->user1p()) {
|
|
if (!m_trigSchedDtp) {
|
|
m_trigSchedDtp
|
|
= new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::TRIGGER_SCHEDULER,
|
|
VSigning::UNSIGNED};
|
|
m_netlistp->typeTablep()->addTypesp(m_trigSchedDtp);
|
|
}
|
|
AstVarScope* const trigSchedp
|
|
= m_scopeTopp->createTemp(m_trigSchedNames.get(sensesp), m_trigSchedDtp);
|
|
sensesp->user1p(trigSchedp);
|
|
}
|
|
return VN_AS(sensesp->user1p(), VarScope);
|
|
}
|
|
// Creates a string describing the sentree
|
|
AstCExpr* createEventDescription(AstSenTree* const sensesp) const {
|
|
if (!sensesp->user2p()) {
|
|
std::stringstream ss;
|
|
ss << '"';
|
|
V3EmitV::verilogForTree(sensesp, ss);
|
|
ss << '"';
|
|
auto* const commentp = new AstCExpr{sensesp->fileline(), ss.str(), 0};
|
|
commentp->dtypeSetString();
|
|
sensesp->user2p(commentp);
|
|
return commentp;
|
|
}
|
|
return VN_AS(sensesp->user2p(), CExpr)->cloneTree(false);
|
|
}
|
|
// Adds debug info to a hardcoded method call
|
|
void addDebugInfo(AstCMethodHard* const methodp) const {
|
|
if (v3Global.opt.protectIds()) return;
|
|
FileLine* const flp = methodp->fileline();
|
|
AstCExpr* const ap = new AstCExpr{flp, '"' + flp->filename() + '"', 0};
|
|
ap->dtypeSetString();
|
|
methodp->addPinsp(ap);
|
|
AstCExpr* const bp = new AstCExpr{flp, cvtToStr(flp->lineno()), 0};
|
|
bp->dtypeSetString();
|
|
methodp->addPinsp(bp);
|
|
}
|
|
// Adds debug info to a trigSched.trigger() call
|
|
void addEventDebugInfo(AstCMethodHard* const methodp, AstSenTree* const sensesp) const {
|
|
if (v3Global.opt.protectIds()) return;
|
|
methodp->addPinsp(createEventDescription(sensesp));
|
|
addDebugInfo(methodp);
|
|
}
|
|
// Adds process pointer to a hardcoded method call
|
|
void addProcessInfo(AstCMethodHard* const methodp) const {
|
|
FileLine* const flp = methodp->fileline();
|
|
AstCExpr* const ap = new AstCExpr{
|
|
flp, m_procp && m_procp->user2() == T_PROC ? "vlProcess" : "nullptr", 0};
|
|
ap->dtypeSetVoid();
|
|
methodp->addPinsp(ap);
|
|
}
|
|
// Creates the fork handle type and returns it
|
|
AstBasicDType* getCreateForkSyncDTypep() {
|
|
if (m_forkDtp) return m_forkDtp;
|
|
m_forkDtp = new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::FORK_SYNC,
|
|
VSigning::UNSIGNED};
|
|
m_netlistp->typeTablep()->addTypesp(m_forkDtp);
|
|
return m_forkDtp;
|
|
}
|
|
// Create a temp variable and optionally put it before the specified node (mark local if so)
|
|
AstVarScope* createTemp(FileLine* const flp, const std::string& name,
|
|
AstNodeDType* const dtypep, AstNode* const insertBeforep = nullptr) {
|
|
AstVar* varp;
|
|
if (insertBeforep) {
|
|
varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep};
|
|
varp->funcLocal(true);
|
|
insertBeforep->addHereThisAsNext(varp);
|
|
} else {
|
|
varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
|
|
m_scopep->modp()->addStmtsp(varp);
|
|
}
|
|
AstVarScope* vscp = new AstVarScope{flp, m_scopep, varp};
|
|
m_scopep->addVarsp(vscp);
|
|
return vscp;
|
|
}
|
|
// Add a done() call on the fork sync
|
|
void addForkDone(AstBegin* const beginp, AstVarScope* const forkVscp) const {
|
|
FileLine* const flp = beginp->fileline();
|
|
auto* const donep = new AstCMethodHard{
|
|
beginp->fileline(), new AstVarRef{flp, forkVscp, VAccess::WRITE}, "done"};
|
|
donep->dtypeSetVoid();
|
|
addDebugInfo(donep);
|
|
beginp->addStmtsp(donep->makeStmt());
|
|
}
|
|
// Handle the 'join' part of a fork..join
|
|
void makeForkJoin(AstFork* const forkp) {
|
|
// Create a fork sync var
|
|
FileLine* const flp = forkp->fileline();
|
|
// If we're in a function, insert the sync var directly before the fork
|
|
AstNode* const insertBeforep = m_classp ? forkp : nullptr;
|
|
AstVarScope* forkVscp
|
|
= createTemp(flp, forkp->name() + "__sync", getCreateForkSyncDTypep(), insertBeforep);
|
|
unsigned joinCount = 0; // Needed for join counter
|
|
// Add a <fork sync>.done() to each begin
|
|
for (AstNode* beginp = forkp->stmtsp(); beginp; beginp = beginp->nextp()) {
|
|
addForkDone(VN_AS(beginp, Begin), forkVscp);
|
|
joinCount++;
|
|
}
|
|
if (forkp->joinType().joinAny()) joinCount = 1;
|
|
// Set the join counter
|
|
auto* const initp = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE},
|
|
"init", new AstConst{flp, joinCount}};
|
|
initp->dtypeSetVoid();
|
|
addProcessInfo(initp);
|
|
forkp->addHereThisAsNext(initp->makeStmt());
|
|
// Await the join at the end
|
|
auto* const joinp
|
|
= new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, "join"};
|
|
joinp->dtypeSetVoid();
|
|
addProcessInfo(joinp);
|
|
addDebugInfo(joinp);
|
|
AstCAwait* const awaitp = new AstCAwait{flp, joinp};
|
|
awaitp->dtypeSetVoid();
|
|
forkp->addNextHere(awaitp->makeStmt());
|
|
}
|
|
|
|
// VISITORS
|
|
void visit(AstNodeModule* nodep) override {
|
|
UASSERT(!m_classp, "Module or class under class");
|
|
VL_RESTORER(m_classp);
|
|
m_classp = VN_CAST(nodep, Class);
|
|
VL_RESTORER(m_timescaleFactor);
|
|
m_timescaleFactor = calculateTimescaleFactor(nodep->timeunit());
|
|
VL_RESTORER(m_forkCnt);
|
|
m_forkCnt = 0;
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstScope* nodep) override {
|
|
VL_RESTORER(m_scopep);
|
|
m_scopep = nodep;
|
|
SenExprBuilder senExprBuilder{m_scopep};
|
|
{ // Restore m_senExprBuilderp before destroying senExprBuilder
|
|
VL_RESTORER(m_senExprBuilderp);
|
|
m_senExprBuilderp = &senExprBuilder;
|
|
iterateChildren(nodep);
|
|
}
|
|
}
|
|
void visit(AstActive* nodep) override {
|
|
m_activep = nodep;
|
|
iterateChildren(nodep);
|
|
m_activep = nullptr;
|
|
}
|
|
void visit(AstNodeProcedure* nodep) override {
|
|
VL_RESTORER(m_procp);
|
|
m_procp = nodep;
|
|
iterateChildren(nodep);
|
|
if (nodep->user2() >= T_SUSP) nodep->setSuspendable();
|
|
if (nodep->user2() >= T_PROC) nodep->setNeedProcess();
|
|
}
|
|
void visit(AstInitial* nodep) override {
|
|
visit(static_cast<AstNodeProcedure*>(nodep));
|
|
if (nodep->needProcess() && !nodep->user1SetOnce()) {
|
|
nodep->addStmtsp(
|
|
new AstCStmt{nodep->fileline(), "vlProcess->state(VlProcess::FINISHED);\n"});
|
|
}
|
|
}
|
|
void visit(AstAlways* nodep) override {
|
|
if (nodep->user1SetOnce()) return;
|
|
VL_RESTORER(m_procp);
|
|
m_procp = nodep;
|
|
iterateChildren(nodep);
|
|
if (!nodep->user2()) return;
|
|
if (nodep->user2() == T_PROC) nodep->setNeedProcess();
|
|
nodep->setSuspendable();
|
|
FileLine* const flp = nodep->fileline();
|
|
AstSenTree* const sensesp = m_activep->sensesp();
|
|
if (sensesp->hasClocked()) {
|
|
AstNode* const bodysp = nodep->stmtsp()->unlinkFrBackWithNext();
|
|
auto* const controlp = new AstEventControl{flp, sensesp->cloneTree(false), bodysp};
|
|
nodep->addStmtsp(controlp);
|
|
iterate(controlp);
|
|
}
|
|
// Note: The 'while (true)' outer loop will be added in V3Sched
|
|
auto* const activep = new AstActive{
|
|
flp, "", new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}};
|
|
activep->sensesStorep(activep->sensesp());
|
|
activep->addStmtsp(nodep->unlinkFrBack());
|
|
m_activep->addNextHere(activep);
|
|
}
|
|
void visit(AstCFunc* nodep) override {
|
|
VL_RESTORER(m_procp);
|
|
m_procp = nodep;
|
|
iterateChildren(nodep);
|
|
if (!nodep->user2()) return;
|
|
nodep->rtnType("VlCoroutine");
|
|
// If in a class, create a shared pointer to 'this'
|
|
if (m_classp) nodep->addInitsp(new AstCStmt{nodep->fileline(), "VL_KEEP_THIS;\n"});
|
|
AstNode* firstCoStmtp = nullptr; // First co_* statement in the function
|
|
nodep->exists([&](AstCAwait* const awaitp) -> bool { return (firstCoStmtp = awaitp); });
|
|
if (!firstCoStmtp) {
|
|
// It's a coroutine but has no awaits (a class method that overrides/is
|
|
// overridden by a suspendable, but doesn't have any awaits itself). Add a
|
|
// co_return at the end (either that or a co_await is required in a
|
|
// coroutine)
|
|
firstCoStmtp = new AstCStmt{nodep->fileline(), "co_return;\n"};
|
|
nodep->addStmtsp(firstCoStmtp);
|
|
}
|
|
if (nodep->dpiExportImpl()) {
|
|
// A DPI-exported coroutine won't be able to block the calling code
|
|
// Error on the await node; fall back to the function node
|
|
firstCoStmtp->v3warn(E_UNSUPPORTED,
|
|
"Unsupported: Timing controls inside DPI-exported tasks");
|
|
}
|
|
if (nodep->user2() == T_PROC) nodep->setNeedProcess();
|
|
}
|
|
void visit(AstNodeCCall* nodep) override {
|
|
if (nodep->funcp()->user2() && !nodep->user1SetOnce()) { // If suspendable
|
|
VNRelinker relinker;
|
|
nodep->unlinkFrBack(&relinker);
|
|
AstCAwait* const awaitp = new AstCAwait{nodep->fileline(), nodep};
|
|
awaitp->dtypeSetVoid();
|
|
relinker.relink(awaitp);
|
|
}
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstDelay* nodep) override {
|
|
UASSERT_OBJ(!nodep->isCycleDelay(), nodep,
|
|
"Cycle delays should have been handled in V3AssertPre");
|
|
FileLine* const flp = nodep->fileline();
|
|
AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
|
|
auto* const constp = VN_CAST(valuep, Const);
|
|
if (constp && constp->isZero()) {
|
|
nodep->v3warn(ZERODLY, "Unsupported: #0 delays do not schedule process resumption in "
|
|
"the Inactive region");
|
|
} else {
|
|
// Scale the delay
|
|
if (valuep->dtypep()->isDouble()) {
|
|
valuep = new AstRToIRoundS{
|
|
flp,
|
|
new AstMulD{flp, valuep,
|
|
new AstConst{flp, AstConst::RealDouble{}, m_timescaleFactor}}};
|
|
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
|
|
} else {
|
|
valuep->dtypeSetBitSized(64, VSigning::UNSIGNED);
|
|
valuep = new AstMul{flp, valuep,
|
|
new AstConst{flp, AstConst::Unsized64{},
|
|
static_cast<uint64_t>(m_timescaleFactor)}};
|
|
}
|
|
}
|
|
// Replace self with a 'co_await dlySched.delay(<valuep>)'
|
|
auto* const delayMethodp = new AstCMethodHard{
|
|
flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::WRITE}, "delay", valuep};
|
|
delayMethodp->dtypeSetVoid();
|
|
addProcessInfo(delayMethodp);
|
|
addDebugInfo(delayMethodp);
|
|
// Create the co_await
|
|
AstCAwait* const awaitp = new AstCAwait{flp, delayMethodp, getCreateDelaySenTree()};
|
|
awaitp->dtypeSetVoid();
|
|
AstStmtExpr* const awaitStmtp = awaitp->makeStmt();
|
|
// Relink child statements after the co_await
|
|
if (nodep->stmtsp()) {
|
|
AstNode::addNext<AstNode, AstNode>(awaitStmtp,
|
|
nodep->stmtsp()->unlinkFrBackWithNext());
|
|
}
|
|
nodep->replaceWith(awaitStmtp);
|
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
|
}
|
|
void visit(AstEventControl* nodep) override {
|
|
// Do not allow waiting on local named events, as they get enqueued for clearing, but can
|
|
// go out of scope before that happens
|
|
if (!nodep->sensesp()) nodep->v3warn(E_UNSUPPORTED, "Unsupported: no sense equation (@*)");
|
|
if (nodep->sensesp()->exists([](const AstNodeVarRef* refp) {
|
|
AstBasicDType* const dtypep = refp->dtypep()->skipRefp()->basicp();
|
|
return dtypep && dtypep->isEvent() && refp->varp()->isFuncLocal();
|
|
})) {
|
|
nodep->v3warn(E_UNSUPPORTED, "Unsupported: waiting on local event variables");
|
|
}
|
|
FileLine* const flp = nodep->fileline();
|
|
// Relink child statements after the event control
|
|
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext());
|
|
if (needDynamicTrigger(nodep->sensesp())) {
|
|
// Create the trigger variable and init it with 0
|
|
AstVarScope* const trigvscp
|
|
= createTemp(flp, m_dynTrigNames.get(nodep), nodep->findBitDType(), nodep);
|
|
auto* const initp = new AstAssign{flp, new AstVarRef{flp, trigvscp, VAccess::WRITE},
|
|
new AstConst{flp, AstConst::BitFalse{}}};
|
|
nodep->addHereThisAsNext(initp);
|
|
// Await the eval step with the dynamic trigger scheduler. First, create the method
|
|
// call
|
|
auto* const evalMethodp = new AstCMethodHard{
|
|
flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::WRITE},
|
|
"evaluation"};
|
|
evalMethodp->dtypeSetVoid();
|
|
addProcessInfo(evalMethodp);
|
|
auto* const sensesp = nodep->sensesp();
|
|
addEventDebugInfo(evalMethodp, sensesp);
|
|
// Create the co_await
|
|
AstCAwait* const awaitEvalp
|
|
= new AstCAwait{flp, evalMethodp, getCreateDynamicTriggerSenTree()};
|
|
awaitEvalp->dtypeSetVoid();
|
|
// Construct the sen expression for this sentree
|
|
UASSERT_OBJ(m_senExprBuilderp, nodep, "No SenExprBuilder for this scope");
|
|
auto* const assignp = new AstAssign{flp, new AstVarRef{flp, trigvscp, VAccess::WRITE},
|
|
m_senExprBuilderp->build(sensesp).first};
|
|
// Put all the locals and inits before the trigger eval loop
|
|
for (AstVar* const varp : m_senExprBuilderp->getAndClearLocals()) {
|
|
nodep->addHereThisAsNext(varp);
|
|
}
|
|
for (AstNodeStmt* const stmtp : m_senExprBuilderp->getAndClearInits()) {
|
|
nodep->addHereThisAsNext(stmtp);
|
|
}
|
|
// Create the trigger eval loop, which will await the evaluation step and check the
|
|
// trigger
|
|
AstWhile* const loopp = new AstWhile{
|
|
flp, new AstLogNot{flp, new AstVarRef{flp, trigvscp, VAccess::READ}},
|
|
awaitEvalp->makeStmt()};
|
|
// Put pre updates before the trigger check and assignment
|
|
for (AstNodeStmt* const stmtp : m_senExprBuilderp->getAndClearPreUpdates()) {
|
|
loopp->addStmtsp(stmtp);
|
|
}
|
|
// Then the trigger check and assignment
|
|
loopp->addStmtsp(assignp);
|
|
// If the post update is destructive (e.g. event vars are cleared), create an await for
|
|
// the post update step
|
|
if (destructivePostUpdate(sensesp)) {
|
|
AstCAwait* const awaitPostUpdatep = awaitEvalp->cloneTree(false);
|
|
VN_AS(awaitPostUpdatep->exprp(), CMethodHard)->name("postUpdate");
|
|
loopp->addStmtsp(awaitPostUpdatep->makeStmt());
|
|
}
|
|
// Put the post updates at the end of the loop
|
|
for (AstNodeStmt* const stmtp : m_senExprBuilderp->getAndClearPostUpdates()) {
|
|
loopp->addStmtsp(stmtp);
|
|
}
|
|
// Finally, await the resumption step in 'act'
|
|
AstCAwait* const awaitResumep = awaitEvalp->cloneTree(false);
|
|
VN_AS(awaitResumep->exprp(), CMethodHard)->name("resumption");
|
|
AstNode::addNext<AstNodeStmt, AstNodeStmt>(loopp, awaitResumep->makeStmt());
|
|
// Replace the event control with the loop
|
|
nodep->replaceWith(loopp);
|
|
} else {
|
|
auto* const sensesp = m_finder.getSenTree(nodep->sensesp());
|
|
nodep->sensesp()->unlinkFrBack()->deleteTree();
|
|
// Get this sentree's trigger scheduler
|
|
// Replace self with a 'co_await trigSched.trigger()'
|
|
auto* const triggerMethodp = new AstCMethodHard{
|
|
flp, new AstVarRef{flp, getCreateTriggerSchedulerp(sensesp), VAccess::WRITE},
|
|
"trigger"};
|
|
triggerMethodp->dtypeSetVoid();
|
|
// If it should be committed immediately, pass true, otherwise false
|
|
triggerMethodp->addPinsp(nodep->user2() ? new AstConst{flp, AstConst::BitTrue{}}
|
|
: new AstConst{flp, AstConst::BitFalse{}});
|
|
addProcessInfo(triggerMethodp);
|
|
addEventDebugInfo(triggerMethodp, sensesp);
|
|
// Create the co_await
|
|
AstCAwait* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp};
|
|
awaitp->dtypeSetVoid();
|
|
nodep->replaceWith(awaitp->makeStmt());
|
|
}
|
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
|
}
|
|
void visit(AstNodeAssign* nodep) override {
|
|
// Only process once to avoid infinite loops (due to the net delay)
|
|
if (nodep->user1SetOnce()) return;
|
|
AstNode* const controlp = factorOutTimingControl(nodep);
|
|
if (!controlp) return;
|
|
// Handle the intra assignment timing control
|
|
FileLine* const flp = nodep->fileline();
|
|
if (VN_IS(nodep, AssignDly)) {
|
|
// If it's an NBA with an intra assignment delay, put it in a fork
|
|
auto* const forkp = new AstFork{flp, "", nullptr};
|
|
forkp->joinType(VJoinType::JOIN_NONE);
|
|
controlp->replaceWith(forkp);
|
|
forkp->addStmtsp(controlp);
|
|
}
|
|
// Insert new vars before the timing control if we're in a function; in a process we can't
|
|
// do that. These intra-assignment vars will later be passed to forked processes by value.
|
|
AstNode* const insertBeforep = m_classp ? controlp : nullptr;
|
|
// Function for replacing values with intermediate variables
|
|
const auto replaceWithIntermediate = [&](AstNodeExpr* const valuep,
|
|
const std::string& name) {
|
|
AstVarScope* const newvscp = createTemp(flp, name, valuep->dtypep(), insertBeforep);
|
|
valuep->replaceWith(new AstVarRef{flp, newvscp, VAccess::READ});
|
|
controlp->addHereThisAsNext(
|
|
new AstAssign{flp, new AstVarRef{flp, newvscp, VAccess::WRITE}, valuep});
|
|
};
|
|
// Create the intermediate select vars. Note: because 'foreach' proceeds in
|
|
// pre-order, and we replace indices in selects with variables, we cannot
|
|
// reach another select under the index position. This is exactly what
|
|
// we want as only the top level selects are LValues. As an example,
|
|
// this transforms 'x[a[i]][b[j]] = y'
|
|
// into 't1 = a[i]; t0 = b[j]; x[t1][t0] = y'.
|
|
nodep->lhsp()->foreach([&](AstSel* selp) {
|
|
if (VN_IS(selp->lsbp(), Const)) return;
|
|
replaceWithIntermediate(selp->lsbp(), m_intraLsbNames.get(nodep));
|
|
// widthp should be const
|
|
});
|
|
nodep->lhsp()->foreach([&](AstNodeSel* selp) {
|
|
if (VN_IS(selp->bitp(), Const)) return;
|
|
replaceWithIntermediate(selp->bitp(), m_intraIndexNames.get(nodep));
|
|
});
|
|
// Replace the RHS with an intermediate value var
|
|
replaceWithIntermediate(nodep->rhsp(), m_intraValueNames.get(nodep));
|
|
}
|
|
void visit(AstAssignW* nodep) override {
|
|
AstDelay* const netDelayp = getLhsNetDelay(nodep);
|
|
if (!netDelayp && !nodep->timingControlp()) return;
|
|
// This assignment will be converted to an always. In some cases this may generate an
|
|
// UNOPTFLAT, e.g.: assign #1 clk = ~clk. We create a temp var for the LHS of this
|
|
// assign, to disable the UNOPTFLAT warning for it.
|
|
// TODO: Find a way to do this without introducing this var. Perhaps make
|
|
// V3SchedAcyclic recognize awaits and prevent it from treating this kind of logic as
|
|
// cyclic
|
|
AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack();
|
|
std::string varname;
|
|
if (auto* const refp = VN_CAST(lhsp, VarRef)) {
|
|
varname = m_contAssignVarNames.get(refp->name());
|
|
} else {
|
|
varname = m_contAssignVarNames.get(lhsp);
|
|
}
|
|
auto* const tempvscp = m_scopep->createTemp(varname, lhsp->dtypep());
|
|
tempvscp->varp()->delayp(netDelayp);
|
|
FileLine* const flp = nodep->fileline();
|
|
flp->modifyWarnOff(V3ErrorCode::UNOPTFLAT, true);
|
|
tempvscp->fileline(flp);
|
|
tempvscp->varp()->fileline(flp);
|
|
// Remap the LHS to the new temp var
|
|
nodep->lhsp(new AstVarRef{flp, tempvscp, VAccess::WRITE});
|
|
// Convert it to an always; the new assign with intra delay will be handled by
|
|
// visit(AstNodeAssign*)
|
|
AstAlways* const alwaysp = nodep->convertToAlways();
|
|
visit(alwaysp);
|
|
// Put the LHS back in the AssignW; put the temp var on the RHS
|
|
nodep->lhsp(lhsp);
|
|
nodep->rhsp(new AstVarRef{flp, tempvscp, VAccess::READ});
|
|
// Put the AssignW right after the always. Different order can produce UNOPTFLAT on the LHS
|
|
// var
|
|
alwaysp->addNextHere(nodep);
|
|
}
|
|
void visit(AstWait* nodep) override {
|
|
// Wait on changed events related to the vars in the wait statement
|
|
FileLine* const flp = nodep->fileline();
|
|
AstNode* const stmtsp = nodep->stmtsp();
|
|
if (stmtsp) stmtsp->unlinkFrBackWithNext();
|
|
AstNodeExpr* const condp = V3Const::constifyEdit(nodep->condp()->unlinkFrBack());
|
|
auto* const constp = VN_CAST(condp, Const);
|
|
if (constp) {
|
|
condp->v3warn(WAITCONST, "Wait statement condition is constant");
|
|
if (constp->isZero()) {
|
|
// We have to await forever instead of simply returning in case we're deep in a
|
|
// callstack
|
|
AstCExpr* const foreverp = new AstCExpr{flp, "VlForever{}", 0, true};
|
|
foreverp->dtypeSetVoid(); // TODO: this is sloppy but harmless
|
|
AstCAwait* const awaitp = new AstCAwait{flp, foreverp};
|
|
awaitp->dtypeSetVoid();
|
|
nodep->replaceWith(awaitp->makeStmt());
|
|
if (stmtsp) VL_DO_DANGLING(stmtsp->deleteTree(), stmtsp);
|
|
VL_DO_DANGLING(condp->deleteTree(), condp);
|
|
} else if (stmtsp) {
|
|
// Just put the statements there
|
|
nodep->replaceWith(stmtsp);
|
|
} else {
|
|
nodep->unlinkFrBack();
|
|
}
|
|
} else if (needDynamicTrigger(condp)) {
|
|
// No point in making a sentree, just use the expression as sensitivity
|
|
// Put the event control in an if so we only wait if the condition isn't met already
|
|
auto* const ifp = new AstIf{
|
|
flp, new AstLogNot{flp, condp},
|
|
new AstEventControl{flp,
|
|
new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE,
|
|
condp->cloneTree(false)}},
|
|
nullptr}};
|
|
if (stmtsp) AstNode::addNext<AstNode, AstNode>(ifp, stmtsp);
|
|
nodep->replaceWith(ifp);
|
|
} else {
|
|
AstSenItem* const senItemsp = varRefpsToSenItemsp(condp);
|
|
UASSERT_OBJ(senItemsp, nodep, "No varrefs in wait statement condition");
|
|
// Put the event control in a while loop with the wait expression as condition
|
|
AstEventControl* const controlp
|
|
= new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr};
|
|
controlp->user2(true); // Commit immediately
|
|
AstWhile* const loopp = new AstWhile{flp, new AstLogNot{flp, condp}, controlp};
|
|
if (stmtsp) AstNode::addNext<AstNode, AstNode>(loopp, stmtsp);
|
|
nodep->replaceWith(loopp);
|
|
}
|
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
|
}
|
|
void visit(AstBegin* nodep) override {
|
|
VL_RESTORER(m_procp);
|
|
m_procp = nodep;
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstFork* nodep) override {
|
|
if (nodep->user1SetOnce()) return;
|
|
v3Global.setUsesTiming();
|
|
// Create a unique name for this fork
|
|
nodep->name("__Vfork_" + cvtToStr(++m_forkCnt));
|
|
unsigned idx = 0; // Index for naming begins
|
|
AstNode* stmtp = nodep->stmtsp();
|
|
// Put each statement in a begin
|
|
while (stmtp) {
|
|
if (!VN_IS(stmtp, Begin)) {
|
|
auto* const beginp = new AstBegin{stmtp->fileline(), "", nullptr};
|
|
stmtp->replaceWith(beginp);
|
|
beginp->addStmtsp(stmtp);
|
|
stmtp = beginp;
|
|
}
|
|
auto* const beginp = VN_AS(stmtp, Begin);
|
|
stmtp = beginp->nextp();
|
|
iterate(beginp);
|
|
// Even if we do not find any awaits, we cannot simply inline the process here, as new
|
|
// awaits could be added later.
|
|
// Name the begin (later the name will be used for a new function)
|
|
beginp->name(nodep->name() + "__" + cvtToStr(idx++));
|
|
}
|
|
if (!nodep->joinType().joinNone()) makeForkJoin(nodep);
|
|
}
|
|
|
|
//--------------------
|
|
void visit(AstVar*) override {} // Accelerate
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit TimingControlVisitor(AstNetlist* nodep)
|
|
: m_netlistp{nodep} {
|
|
iterate(nodep);
|
|
}
|
|
~TimingControlVisitor() override = default;
|
|
};
|
|
|
|
//######################################################################
|
|
// Timing class functions
|
|
|
|
void V3Timing::timingAll(AstNetlist* nodep) {
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
|
TimingSuspendableVisitor susVisitor{nodep};
|
|
if (v3Global.usesTiming()) TimingControlVisitor{nodep};
|
|
V3Global::dumpCheckGlobalTree("timing", 0, dumpTreeLevel() >= 3);
|
|
}
|