verilator/src/V3Life.cpp

521 lines
20 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Lifelicate variable assignment elimination
//
2019-11-08 04:33:59 +01:00
// Code available from: https://verilator.org
//
//*************************************************************************
//
2025-01-01 14:30:25 +01:00
// Copyright 2003-2025 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
//
//*************************************************************************
// LIFE TRANSFORMATIONS:
// Build control-flow graph with assignments and var usages
// All modules:
// ASSIGN(x,...), ASSIGN(x,...) => delete first one
// We also track across if statements:
// ASSIGN(X,...) IF( ..., ASSIGN(X,...), ASSIGN(X,...)) => deletes first
// We don't do the opposite yet though (remove assigns in if followed by outside if)
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Life.h"
#include "V3Const.h"
#include "V3Stats.h"
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Structure for global state
class LifeState final {
// NODE STATE
// See below
const VNUser1InUse m_inuser1;
// STATE
public:
VDouble0 m_statAssnDel; // Statistic tracking
VDouble0 m_statAssnCon; // Statistic tracking
VDouble0 m_statCResetDel; // Statistic tracking
// CONSTRUCTORS
LifeState() = default;
~LifeState() {
V3Stats::addStatSum("Optimizations, Lifetime assign deletions", m_statAssnDel);
V3Stats::addStatSum("Optimizations, Lifetime creset deletions", m_statCResetDel);
V3Stats::addStatSum("Optimizations, Lifetime constant prop", m_statAssnCon);
}
};
//######################################################################
// Structure for each variable encountered
class LifeVarEntry final {
// Last assignment to this varscope, nullptr if no longer relevant
AstNodeStmt* m_assignp = nullptr;
AstConst* m_constp = nullptr; // Known constant value
bool m_isNew = true; // Is just created
// First access was a set (and thus block above may have a set that can be deleted
bool m_setBeforeUse = false;
// Was ever assigned (and thus above block may not preserve constant propagation)
bool m_everSet = false;
public:
LifeVarEntry() = default;
~LifeVarEntry() = default;
void init(bool setBeforeUse) {
UASSERT(m_isNew, "Not a new entry");
m_isNew = false;
m_setBeforeUse = setBeforeUse;
}
void simpleAssign(AstNodeAssign* nodep) { // New simple A=.... assignment
UASSERT_OBJ(!m_isNew, nodep, "Uninitialzized new entry");
m_assignp = nodep;
m_constp = nullptr;
m_everSet = true;
if (VN_IS(nodep->rhsp(), Const)) m_constp = VN_AS(nodep->rhsp(), Const);
}
void resetStatement(AstCReset* nodep) { // New CReset(A) assignment
UASSERT_OBJ(!m_isNew, nodep, "Uninitialzized new entry");
m_assignp = nodep;
m_constp = nullptr;
m_everSet = true;
}
void complexAssign() { // A[x]=... or some complicated assignment
UASSERT(!m_isNew, "Uninitialzized new entry");
m_assignp = nullptr;
m_constp = nullptr;
m_everSet = true;
}
void consumed() { // Rvalue read of A
UASSERT(!m_isNew, "Uninitialzized new entry");
m_assignp = nullptr;
}
AstNodeStmt* assignp() const { return m_assignp; }
AstConst* constNodep() const { return m_constp; }
bool isNew() const { return m_isNew; }
bool setBeforeUse() const { return m_setBeforeUse; }
bool everSet() const { return m_everSet; }
};
//######################################################################
// Structure for all variables under a given meta-basic block
class LifeBlock final {
// NODE STATE
// Cleared each AstIf:
// AstVarScope::user1() -> int. Used in combining to detect duplicates
// LIFE MAP
// For each basic block, we'll make a new map of what variables that if/else is changing
// Current active lifetime map for current scope
std::unordered_map<AstVarScope*, LifeVarEntry> m_map;
LifeBlock* const m_aboveLifep; // Upper life, or nullptr
LifeState* const m_statep; // Current global state
bool m_replacedVref = false; // Replaced a variable reference since last clearing
VNDeleter m_deleter; // Used to delay deletion of nodes
public:
LifeBlock(LifeBlock* aboveLifep, LifeState* statep)
: m_aboveLifep{aboveLifep} // Null if top
, m_statep{statep} {}
~LifeBlock() = default;
// METHODS
void checkRemoveAssign(const AstVarScope* vscp, LifeVarEntry& entr) {
const AstVar* const varp = vscp->varp();
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
// We don't optimize any public sigs
if (varp->isSigPublic()) return;
if (varp->sensIfacep()) return;
// Check the var entry, and remove if appropriate
AstNodeStmt* const oldassp = entr.assignp();
if (!oldassp) return;
UINFO(7, " PREV: " << oldassp);
// Redundant assignment, in same level block
// Don't delete it now as it will confuse iteration since it maybe WAY
// above our current iteration point.
UINFOTREE(7, oldassp, "", "REMOVE/SAMEBLK");
entr.complexAssign();
oldassp->unlinkFrBack();
if (VN_IS(oldassp, CReset)) {
++m_statep->m_statCResetDel;
} else {
++m_statep->m_statAssnDel;
}
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
VL_DO_DANGLING(m_deleter.pushDeletep(oldassp), oldassp);
}
void resetStatement(AstVarScope* nodep, AstCReset* rstp) {
// Do we have a old assignment we can nuke?
UINFO(4, " CRESETof: " << nodep);
UINFO(7, " new: " << rstp);
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) {
entr.init(true);
} else {
checkRemoveAssign(nodep, entr);
}
entr.resetStatement(rstp);
// lifeDump();
}
void simpleAssign(AstVarScope* nodep, AstNodeAssign* assp) {
// Do we have a old assignment we can nuke?
UINFO(4, " ASSIGNof: " << nodep);
UINFO(7, " new: " << assp);
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) {
entr.init(true);
} else {
checkRemoveAssign(nodep, entr);
}
entr.simpleAssign(assp);
// lifeDump();
}
void complexAssign(AstVarScope* nodep) {
UINFO(4, " clearof: " << nodep);
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) entr.init(false);
entr.complexAssign();
}
void clearReplaced() { m_replacedVref = false; }
bool replaced() const { return m_replacedVref; }
void varUsageReplace(AstVarScope* nodep, AstVarRef* varrefp) {
2022-03-31 02:17:59 +02:00
// Variable rvalue. If it references a constant, we can replace it
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) {
entr.init(false);
} else {
if (AstConst* const constp = entr.constNodep()) {
if (!varrefp->varp()->isSigPublic() && !varrefp->varp()->sensIfacep()) {
// Aha, variable is constant; substitute in.
// We'll later constant propagate
UINFO(4, " replaceconst: " << varrefp);
varrefp->replaceWith(constp->cloneTree(false));
m_replacedVref = true;
VL_DO_DANGLING(varrefp->deleteTree(), varrefp);
++m_statep->m_statAssnCon;
return; // **DONE, no longer a var reference**
}
}
UINFO(4, " usage: " << nodep);
}
entr.consumed();
}
void complexAssignFind(AstVarScope* nodep) {
UINFO(4, " casfind: " << nodep);
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) entr.init(false);
entr.complexAssign();
}
void consumedFind(AstVarScope* nodep) {
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) entr.init(false);
entr.consumed();
}
void lifeToAbove() {
// Any varrefs under a if/else branch affect statements outside and after the if/else
UASSERT(m_aboveLifep, "Pushing life when already at the top level");
for (auto& itr : m_map) {
AstVarScope* const nodep = itr.first;
m_aboveLifep->complexAssignFind(nodep);
if (itr.second.everSet()) {
// Record there may be an assignment, so we don't constant propagate across the if.
complexAssignFind(nodep);
} else {
// Record consumption, so we don't eliminate earlier assignments
consumedFind(nodep);
}
}
}
void dualBranch(LifeBlock* life1p, LifeBlock* life2p) {
// Find any common sets on both branches of IF and propagate upwards
// life1p->lifeDump();
// life2p->lifeDump();
AstNode::user1ClearTree(); // user1p() used on entire tree
for (auto& itr : life1p->m_map) {
// When the if branch sets a var before it's used, mark that variable
if (itr.second.setBeforeUse()) itr.first->user1(1);
}
for (auto& itr : life2p->m_map) {
// When the else branch sets a var before it's used
AstVarScope* const nodep = itr.first;
if (itr.second.setBeforeUse() && nodep->user1()) {
// Both branches set the var, we can remove the assignment before the IF.
UINFO(4, "DUALBRANCH " << nodep);
const auto itab = m_map.find(nodep);
if (itab != m_map.end()) checkRemoveAssign(nodep, itab->second);
}
}
// this->lifeDump();
}
void clear() { m_map.clear(); }
// DEBUG
void lifeDump() {
UINFO(5, " LifeMap:");
for (const auto& itr : m_map) {
UINFO(5,
" Ent: " << (itr.second.setBeforeUse() ? "[F] " : " ") << itr.first);
if (itr.second.assignp()) { //
UINFO(5, " Ass: " << itr.second.assignp());
}
}
}
};
//######################################################################
// Life state, as a visitor of each AstNode
class LifeVisitor final : public VNVisitor {
// STATE
LifeState* const m_statep; // Current state
bool m_containsTiming = false; // Statement contains timing control
bool m_sideEffect = false; // Side effects discovered in assign RHS
bool m_noopt = false; // Disable optimization of variables in this block
bool m_tracingCall = false; // Iterating into a CCall to a CFunc
LifeBlock* m_lifep = nullptr; // Current active lifetime map for current scope
// METHODS
void setNoopt() {
m_noopt = true;
m_lifep->clear();
}
2009-01-21 22:56:50 +01:00
// VISITORS
void visit(AstVarRef* nodep) override {
// Consumption/generation of a variable,
// it's used so can't elim assignment before this use.
UASSERT_OBJ(nodep->varScopep(), nodep, "nullptr");
//
AstVarScope* const vscp = nodep->varScopep();
UASSERT_OBJ(vscp, nodep, "Scope not assigned");
if (nodep->access().isWriteOrRW()) {
2019-07-06 02:09:56 +02:00
m_sideEffect = true; // $sscanf etc may have RHS vars that are lvalues
m_lifep->complexAssign(vscp);
} else {
VL_DO_DANGLING(m_lifep->varUsageReplace(vscp, nodep), nodep);
}
}
void visit(AstNodeAssign* nodep) override {
if (nodep->isTimingControl() || VN_IS(nodep, AssignForce)) {
// V3Life doesn't understand time sense nor force assigns - don't optimize
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
setNoopt();
if (nodep->isTimingControl()) m_containsTiming = true;
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
iterateChildren(nodep);
return;
}
// Collect any used variables first, as lhs may also be on rhs
// Similar code in V3Dead
VL_RESTORER(m_sideEffect);
m_sideEffect = false;
m_lifep->clearReplaced();
iterateAndNextNull(nodep->rhsp());
if (m_lifep->replaced()) {
// We changed something, try to constant propagate, but don't delete the
// assignment as we still need nodep to remain.
V3Const::constifyEdit(nodep->rhsp()); // rhsp may change
}
// Has to be direct assignment without any EXTRACTing.
if (VN_IS(nodep->lhsp(), VarRef) && !m_sideEffect && !m_noopt) {
AstVarScope* const vscp = VN_AS(nodep->lhsp(), VarRef)->varScopep();
UASSERT_OBJ(vscp, nodep, "Scope lost on variable");
m_lifep->simpleAssign(vscp, nodep);
} else {
iterateAndNextNull(nodep->lhsp());
}
}
void visit(AstCReset* nodep) override {
if (!m_noopt) {
AstVarScope* const vscp = nodep->varrefp()->varScopep();
UASSERT_OBJ(vscp, nodep, "Scope lost on variable");
m_lifep->resetStatement(vscp, nodep);
} else {
iterateAndNextNull(nodep->varrefp());
}
}
void visit(AstAssignDly* nodep) override {
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
// V3Life doesn't understand time sense
if (nodep->isTimingControl()) {
// Don't optimize
setNoopt();
m_containsTiming = true;
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
}
// Don't treat as normal assign
iterateChildren(nodep);
}
//---- Track control flow changes
void visit(AstNodeIf* nodep) override {
UINFO(4, " IF " << nodep);
// Condition is part of PREVIOUS block
iterateAndNextNull(nodep->condp());
LifeBlock* const prevLifep = m_lifep;
2022-11-20 19:11:01 +01:00
LifeBlock* const ifLifep = new LifeBlock{prevLifep, m_statep};
LifeBlock* const elseLifep = new LifeBlock{prevLifep, m_statep};
{
m_lifep = ifLifep;
iterateAndNextNull(nodep->thensp());
}
{
m_lifep = elseLifep;
iterateAndNextNull(nodep->elsesp());
}
2019-10-25 03:48:45 +02:00
m_lifep = prevLifep;
UINFO(4, " join ");
// Find sets on both flows
m_lifep->dualBranch(ifLifep, elseLifep);
// For the next assignments, clear any variables that were read or written in the block
ifLifep->lifeToAbove();
elseLifep->lifeToAbove();
VL_DO_DANGLING(delete ifLifep, ifLifep);
VL_DO_DANGLING(delete elseLifep, elseLifep);
}
void visit(AstLoop* nodep) override {
// Similar problem to AstJumpBlock, don't optimize loop bodies - most are unrolled
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
VL_RESTORER(m_containsTiming);
{
VL_RESTORER(m_noopt);
VL_RESTORER(m_lifep);
m_lifep = new LifeBlock{m_lifep, m_statep};
setNoopt();
iterateAndNextNull(nodep->stmtsp());
UINFO(4, " joinloop");
// For the next assignments, clear any variables that were read or written in the block
m_lifep->lifeToAbove();
VL_DO_DANGLING(delete m_lifep, m_lifep);
}
if (m_containsTiming) setNoopt();
}
void visit(AstJumpBlock* nodep) override {
// As with Loop's we can't predict if a JumpGo will kill us or not
// It's worse though as an IF(..., JUMPGO) may change the control flow.
// Just don't optimize blocks with labels; they're rare - so far.
VL_RESTORER(m_containsTiming);
{
VL_RESTORER(m_noopt);
VL_RESTORER(m_lifep);
m_lifep = new LifeBlock{m_lifep, m_statep};
setNoopt();
iterateAndNextNull(nodep->stmtsp());
UINFO(4, " joinjump");
// For the next assignments, clear any variables that were read or written in the block
m_lifep->lifeToAbove();
VL_DO_DANGLING(delete m_lifep, m_lifep);
}
if (m_containsTiming) setNoopt();
2010-02-14 16:01:21 +01:00
}
void visit(AstNodeCCall* nodep) override {
// UINFO(4, " CCALL " << nodep);
iterateChildren(nodep);
// Enter the function and trace it
// else is non-inline or public function we optimize separately
if (nodep->funcp()->entryPoint()) {
setNoopt();
} else {
m_tracingCall = true;
iterate(nodep->funcp());
}
}
void visit(AstCFunc* nodep) override {
// UINFO(4, " CFUNC " << nodep);
if (!m_tracingCall && !nodep->entryPoint()) return;
m_tracingCall = false;
if (nodep->recursive()) setNoopt();
if (nodep->dpiImportPrototype() && !nodep->dpiPure()) {
m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment
}
iterateChildren(nodep);
}
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
void visit(AstCExprUser* nodep) override {
m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment
iterateChildren(nodep);
}
void visit(AstCExpr* nodep) override {
m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment
iterateChildren(nodep);
}
void visit(AstVar*) override {} // Don't want varrefs under it
2022-09-16 17:15:10 +02:00
void visit(AstNode* nodep) override {
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
if (nodep->isTimingControl()) {
// V3Life doesn't understand time sense - don't optimize
setNoopt();
m_containsTiming = true;
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
}
iterateChildren(nodep);
}
public:
// CONSTRUCTORS
LifeVisitor(AstNode* nodep, LifeState* statep)
: m_statep{statep} {
UINFO(4, " LifeVisitor on " << nodep);
{
2022-11-20 19:11:01 +01:00
m_lifep = new LifeBlock{nullptr, m_statep};
iterate(nodep);
if (m_lifep) VL_DO_CLEAR(delete m_lifep, m_lifep = nullptr);
}
}
~LifeVisitor() override {
if (m_lifep) VL_DO_CLEAR(delete m_lifep, m_lifep = nullptr);
2015-10-04 04:33:06 +02:00
}
2020-01-09 01:33:47 +01:00
VL_UNCOPYABLE(LifeVisitor);
};
//######################################################################
class LifeTopVisitor final : public VNVisitor {
// Visit all top nodes searching for functions that are entry points we want to start
// finding code within.
private:
// STATE
LifeState* const m_statep; // Current state
// VISITORS
void visit(AstCFunc* nodep) override {
if (nodep->entryPoint()) {
// Usage model 1: Simulate all C code, doing lifetime analysis
LifeVisitor{nodep, m_statep};
}
}
void visit(AstNodeProcedure* nodep) override {
// Usage model 2: Cleanup basic blocks
LifeVisitor{nodep, m_statep};
}
void visit(AstVar*) override {} // Accelerate
void visit(AstNodeStmt*) override {} // Accelerate
void visit(AstNodeExpr*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
LifeTopVisitor(AstNetlist* nodep, LifeState* statep)
: m_statep{statep} {
iterate(nodep);
}
~LifeTopVisitor() override = default;
};
//######################################################################
// Life class functions
void V3Life::lifeAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{
LifeState state;
LifeTopVisitor{nodep, &state};
} // Destruct before checking
VIsCached::clearCacheTree(); // Removing assignments may affect isPure
V3Global::dumpCheckGlobalTree("life", 0, dumpTreeEitherLevel() >= 3);
}