verilator/src/V3Timing.cpp

660 lines
31 KiB
C++
Raw Normal View History

Timing support (#3363) Adds timing support to Verilator. It makes it possible to use delays, event controls within processes (not just at the start), wait statements, and forks. Building a design with those constructs requires a compiler that supports C++20 coroutines (GCC 10, Clang 5). The basic idea is to have processes and tasks with delays/event controls implemented as C++20 coroutines. This allows us to suspend and resume them at any time. There are five main runtime classes responsible for managing suspended coroutines: * `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle` with move semantics and automatic cleanup. * `VlDelayScheduler`, for coroutines suspended by delays. It resumes them at a proper simulation time. * `VlTriggerScheduler`, for coroutines suspended by event controls. It resumes them if its corresponding trigger was set. * `VlForkSync`, used for syncing `fork..join` and `fork..join_any` blocks. * `VlCoroutine`, the return type of all verilated coroutines. It allows for suspending a stack of coroutines (normally, C++ coroutines are stackless). There is a new visitor in `V3Timing.cpp` which: * scales delays according to the timescale, * simplifies intra-assignment timing controls and net delays into regular timing controls and assignments, * simplifies wait statements into loops with event controls, * marks processes and tasks with timing controls in them as suspendable, * creates delay, trigger scheduler, and fork sync variables, * transforms timing controls and fork joins into C++ awaits There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`) that integrate static scheduling with timing. This involves providing external domains for variables, so that the necessary combinational logic gets triggered after coroutine resumption, as well as statements that need to be injected into the design eval function to perform this resumption at the correct time. There is also a function that transforms forked processes into separate functions. See the comments in `verilated_timing.h`, `verilated_timing.cpp`, `V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals documentation for more details. Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Prepare AST for timing features
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2022 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
//
// TimingVisitor 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
// - for each process or C++ function, if it has CAwait statements, mark it as suspendable
// - if we mark a virtual function as suspendable, mark all overriding and overridden functions
// as suspendable, as well as calling processes
//
// 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 "V3SenTree.h"
#include "V3UniqueNames.h"
//######################################################################
// Transform nodes affected by timing
class TimingVisitor 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 DependencyVertex final : public V3GraphVertex {
AstNode* const m_nodep; // AST node represented by this graph vertex
// ACCESSORS
virtual string name() const override {
return cvtToHex(nodep()) + ' ' + nodep()->prettyTypeName();
}
virtual FileLine* fileline() const override { return nodep()->fileline(); }
virtual string dotColor() const override { return nodep()->user2() ? "red" : "black"; }
public:
// CONSTRUCTORS
DependencyVertex(V3Graph* graphp, AstNode* nodep)
: V3GraphVertex{graphp}
, m_nodep{nodep} {}
virtual ~DependencyVertex() override = default;
// ACCESSORS
virtual AstNode* nodep() const { return m_nodep; }
};
// NODE STATE
// AstNode::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
// AstSenTree::user2() -> AstText*. Debug info passed to the
// timing schedulers
// Ast{NodeProcedure,CFunc,Begin}::user3() -> DependencyVertex*. Vertex in m_depGraph
const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
const VNUser3InUse m_user3InUse;
// 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/Fork we're under
double m_timescaleFactor; // Factor to scale delays by
// 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_forkNames{"__Vfork__"}; // Fork name generator
V3UniqueNames m_trigSchedNames{"__VtrigSched"}; // Trigger scheduler 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
AstSenTree* m_delaySensesp = nullptr; // Domain to trigger if a delayed coroutine is resumed
// Other
V3Graph m_depGraph; // Dependency graph where a node is a dependency of another if it being
// suspendable makes the other node suspendable
SenTreeFinder m_finder{m_netlistp}; // Sentree finder and uniquifier
// METHODS
VL_DEBUG_FUNC; // Declare debug()
// Get or create the dependency vertex for the given node
DependencyVertex* getDependencyVertex(AstNode* const nodep) {
if (!nodep->user3p()) nodep->user3p(new DependencyVertex{&m_depGraph, nodep});
return nodep->user3u().to<DependencyVertex*>();
}
// Find net delay on the LHS of an assignment
AstNode* getLhsNetDelay(AstNodeAssign* nodep) const {
bool foundWrite = false;
AstNode* delayp = nullptr;
nodep->lhsp()->foreach<AstNodeVarRef>([&](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()->cloneTree(false);
});
return delayp;
}
// Transform an assignment with an intra timing control into a timing control with the
// assignment under it
AstNodeStmt* factorOutTimingControl(AstNodeAssign* nodep) const {
AstNodeStmt* stmtp = nodep;
AstNode* delayp = getLhsNetDelay(nodep);
FileLine* const flp = nodep->fileline();
AstNode* const controlp = nodep->timingControlp();
if (controlp) {
controlp->unlinkFrBack();
if (!VN_IS(controlp, SenTree)) {
delayp = delayp ? new AstAdd{flp, delayp, controlp} : controlp;
}
}
if (delayp) {
auto* const delayStmtp = new AstDelay{flp, delayp, nullptr};
stmtp->replaceWith(delayStmtp);
delayStmtp->stmtsp(stmtp);
stmtp = delayStmtp;
}
if (auto* const sensesp = VN_CAST(controlp, SenTree)) {
auto* const eventControlp = new AstEventControl{flp, sensesp, nullptr};
stmtp->replaceWith(eventControlp);
eventControlp->stmtsp(stmtp);
stmtp = eventControlp;
}
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>([&](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()->addSenTreep(m_delaySensesp);
return m_delaySensesp;
}
// 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
AstText* createEventDescription(AstSenTree* const sensesp) const {
if (!sensesp->user2p()) {
std::stringstream ss;
ss << '"';
V3EmitV::verilogForTree(sensesp, ss);
ss << '"';
auto* const commentp = new AstText{sensesp->fileline(), ss.str()};
sensesp->user2p(commentp);
return commentp;
}
return VN_AS(sensesp->user2p(), Text)->cloneTree(false);
}
// 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()->addStmtp(varp);
}
AstVarScope* vscp = new AstVarScope{flp, m_scopep, varp};
m_scopep->addVarp(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();
donep->statement(true);
// Add debug info
donep->addPinsp(new AstText{flp, '"' + flp->filename() + '"'});
donep->addPinsp(new AstText{flp, cvtToStr(flp->lineno())});
beginp->addStmtsp(donep);
}
// 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 = VN_IS(m_procp, CFunc) ? 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();
initp->statement(true);
forkp->addHereThisAsNext(initp);
// Await the join at the end
auto* const joinp
= new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, "join"};
joinp->dtypeSetVoid();
// Add debug info
joinp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'});
joinp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())});
auto* const awaitp = new AstCAwait{flp, joinp};
awaitp->statement(true);
forkp->addNextHere(awaitp);
}
// VISITORS
virtual 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());
iterateChildren(nodep);
}
virtual void visit(AstScope* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
iterateChildren(nodep);
}
virtual void visit(AstActive* nodep) override {
m_activep = nodep;
iterateChildren(nodep);
m_activep = nullptr;
}
virtual void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
if (nodep->user2()) nodep->setSuspendable();
}
virtual void visit(AstAlways* nodep) override {
visit(static_cast<AstNodeProcedure*>(nodep));
if (nodep->isSuspendable() && !nodep->user1SetOnce()) {
FileLine* const flp = nodep->fileline();
AstSenTree* const sensesp = m_activep->sensesp();
if (sensesp->hasClocked()) {
AstNode* bodysp = nodep->bodysp()->unlinkFrBackWithNext();
auto* const controlp = new AstEventControl{flp, sensesp->cloneTree(false), bodysp};
nodep->addStmtp(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);
}
}
virtual void visit(AstCFunc* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
DependencyVertex* const vxp = getDependencyVertex(nodep);
if (m_classp && nodep->isVirtual()
&& !nodep->user1SetOnce()) { // If virtual (only visit once)
// Go over overridden functions
m_classp->repairCache();
for (auto* cextp = m_classp->extendsp(); cextp;
cextp = VN_AS(cextp->nextp(), ClassExtends)) {
if (auto* const overriddenp
= VN_CAST(cextp->classp()->findMember(nodep->name()), CFunc)) {
if (overriddenp->user2()) { // If suspendable
if (!nodep->user2()) {
// It should be a coroutine but it has no awaits. Add a co_return at
// the end (either that or a co_await is required in a coroutine)
nodep->addStmtsp(new AstCStmt{nodep->fileline(), "co_return;\n"});
}
nodep->user2(true);
// If it's suspendable already, no need to add it as our dependency or
// self to its dependencies
} else {
DependencyVertex* const overriddenVxp = getDependencyVertex(overriddenp);
new V3GraphEdge{&m_depGraph, vxp, overriddenVxp, 1};
new V3GraphEdge{&m_depGraph, overriddenVxp, vxp, 1};
}
}
}
}
if (nodep->user2() && !nodep->isCoroutine()) { // If first marked as suspendable
nodep->rtnType("VlCoroutine");
// Revisit dependent nodes if needed
for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
auto* const depVxp = static_cast<DependencyVertex*>(edgep->fromp());
AstNode* const depp = depVxp->nodep();
if (!depp->user2()) { // If dependent not suspendable
depp->user2(true);
if (auto* const funcp = VN_CAST(depp, CFunc)) {
// 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)
funcp->addStmtsp(new AstCStmt{funcp->fileline(), "co_return;\n"});
}
}
iterate(depp);
}
}
}
virtual void visit(AstNodeCCall* nodep) override {
if (nodep->funcp()->user2()) { // If suspendable
auto* const awaitp = new AstCAwait{nodep->fileline(), nullptr};
nodep->replaceWith(awaitp);
awaitp->exprp(nodep);
} else {
// Add our process/func as the CFunc's dependency as we might have to put an await here
DependencyVertex* const procVxp = getDependencyVertex(m_procp);
DependencyVertex* const funcVxp = getDependencyVertex(nodep->funcp());
new V3GraphEdge{&m_depGraph, procVxp, funcVxp, 1};
}
iterateChildren(nodep);
}
virtual void visit(AstCAwait* nodep) override {
v3Global.setUsesTiming();
m_procp->user2(true);
}
virtual void visit(AstDelay* nodep) override {
FileLine* const flp = nodep->fileline();
AstNode* 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}}};
} else {
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();
// Add debug info
delayMethodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'});
delayMethodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())});
// Create the co_await
auto* const awaitp = new AstCAwait{flp, delayMethodp, getCreateDelaySenTree()};
awaitp->statement(true);
// Relink child statements after the co_await
if (nodep->stmtsp()) awaitp->addNext(nodep->stmtsp()->unlinkFrBackWithNext());
nodep->replaceWith(awaitp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
virtual void visit(AstEventControl* nodep) override {
if (m_classp) nodep->v3warn(E_UNSUPPORTED, "Unsupported: event controls in methods");
auto* const sensesp = m_finder.getSenTree(nodep->sensesp());
nodep->sensesp()->unlinkFrBack()->deleteTree();
// Get this sentree's trigger scheduler
FileLine* const flp = nodep->fileline();
// Replace self with a 'co_await trigSched.trigger()'
auto* const triggerMethodp = new AstCMethodHard{
flp, new AstVarRef{flp, getCreateTriggerSchedulerp(sensesp), VAccess::WRITE},
"trigger"};
triggerMethodp->dtypeSetVoid();
// Add debug info
triggerMethodp->addPinsp(createEventDescription(sensesp));
triggerMethodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'});
triggerMethodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())});
// Create the co_await
auto* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp};
awaitp->statement(true);
// Relink child statements after the co_await
if (nodep->stmtsp()) awaitp->addNext(nodep->stmtsp()->unlinkFrBackWithNext());
nodep->replaceWith(awaitp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
virtual void visit(AstNodeAssign* nodep) override {
iterateChildren(nodep);
// 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 = VN_IS(m_procp, CFunc) ? controlp : nullptr;
// Function for replacing values with intermediate variables
const auto replaceWithIntermediate = [&](AstNode* 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>([&](AstSel* selp) {
if (VN_IS(selp->lsbp(), Const)) return;
replaceWithIntermediate(selp->lsbp(), m_intraLsbNames.get(nodep));
// widthp should be const
});
nodep->lhsp()->foreach<AstNodeSel>([&](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));
}
virtual void visit(AstAssignW* nodep) override {
iterateChildren(nodep);
auto* 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
AstNode* 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);
}
virtual void visit(AstWait* nodep) override {
// Wait on changed events related to the vars in the wait statement
AstSenItem* const senItemsp = varRefpsToSenItemsp(nodep->condp());
AstNode* const condp = nodep->condp()->unlinkFrBack();
AstNode* const bodysp = nodep->bodysp();
if (bodysp) bodysp->unlinkFrBackWithNext();
FileLine* const flp = nodep->fileline();
if (senItemsp) {
// Put the event control in a while loop with the wait expression as condition
auto* const loopp
= new AstWhile{flp, new AstLogNot{flp, condp},
new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr}};
loopp->addNextNull(bodysp);
nodep->replaceWith(loopp);
} else {
condp->v3warn(WAITCONST, "Wait statement condition is constant");
auto* constCondp = VN_AS(V3Const::constifyEdit(condp), Const);
if (constCondp->isZero()) {
// We have to await forever instead of simply returning in case we're deep in a
// callstack
auto* const awaitp = new AstCAwait{flp, new AstCStmt{flp, "VlForever{}"}};
awaitp->statement(true);
nodep->replaceWith(awaitp);
if (bodysp) VL_DO_DANGLING(bodysp->deleteTree(), bodysp);
} else if (bodysp) {
// Just put the body there
nodep->replaceWith(bodysp);
}
VL_DO_DANGLING(constCondp->deleteTree(), condp);
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
virtual void visit(AstFork* nodep) override {
if (nodep->user1SetOnce()) return;
// Create a unique name for this fork
nodep->name(m_forkNames.get(nodep));
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();
VL_RESTORER(m_procp);
m_procp = beginp;
iterate(beginp);
if (!m_procp->user2()) {
// No awaits, we can inline this process
if (auto* const stmtsp = beginp->stmtsp()) {
nodep->addHereThisAsNext(stmtsp->unlinkFrBackWithNext());
}
VL_DO_DANGLING(beginp->unlinkFrBack()->deleteTree(), beginp);
// We inlined at least one process, so we can consider it joined; convert join_any
// to join_none
if (nodep->joinType().joinAny()) nodep->joinType(VJoinType::JOIN_NONE);
} else {
// Name the begin (later the name will be used for a new function)
beginp->name(nodep->name() + "__" + cvtToStr(idx++));
}
}
if (!nodep->stmtsp()) {
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (!nodep->joinType().joinNone()) {
makeForkJoin(nodep);
}
}
//--------------------
virtual void visit(AstNodeMath*) override {} // Accelerate
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit TimingVisitor(AstNetlist* nodep)
: m_netlistp{nodep} {
iterate(nodep);
if (debug() >= 6) m_depGraph.dumpDotFilePrefixed("timing_deps");
}
virtual ~TimingVisitor() override = default;
};
//######################################################################
// Timing class functions
void V3Timing::timingAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
TimingVisitor{nodep};
V3Global::dumpCheckGlobalTree("timing", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
}