verilator/src/V3Premit.cpp

439 lines
18 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add temporaries, such as for premit nodes
//
2019-11-08 04:33:59 +01:00
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// V3Premit's Transformations:
//
// Each module:
// For each wide OP, make a a temporary variable with the wide value
// For each deep expression, assign expression to temporary.
//
2019-09-09 13:50:21 +02:00
// Each display (independent transformation; here as Premit is a good point)
// If autoflush, insert a flush
2008-07-16 20:06:08 +02:00
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Premit.h"
#include "V3Stats.h"
#include "V3UniqueNames.h"
VL_DEFINE_DEBUG_FUNCTIONS;
constexpr int STATIC_CONST_MIN_WIDTH = 256; // Minimum size to extract to static constant
//######################################################################
// Premit state, as a visitor of each AstNode
class PremitVisitor final : public VNVisitor {
// NODE STATE
// AstNodeExpr::user() -> bool. True if iterated already
// *::user3() -> Used when visiting AstNodeAssign
const VNUser1InUse m_inuser1;
2023-05-27 15:43:23 +02:00
// STATE - across all visitors
VDouble0 m_extractedToConstPool; // Statistic tracking
VDouble0 m_temporaryVarsCreated; // Statistic tracking
2023-05-27 15:43:23 +02:00
// STATE - for current visit position (use VL_RESTORER)
AstCFunc* m_cfuncp = nullptr; // Current block
size_t m_tmpVarCnt = 0; // Number of temporary variables created inside a function
AstNode* m_stmtp = nullptr; // Current statement
bool m_assignLhs = false; // Inside assignment lhs, don't breakup extracts
// METHODS
bool needsTemp(AstNodeExpr* nodep) {
// Consider adding a temp for this expression.
if (!m_stmtp) return false; // Not under a statement
if (nodep->user1SetOnce()) return false; // Already processed
if (!nodep->isWide()) return false; // Not wide
if (m_assignLhs) return false; // This is an lvalue!
UASSERT_OBJ(!VN_IS(nodep->firstAbovep(), ArraySel), nodep, "Should have been ignored");
return true;
}
void checkNode(AstNodeExpr* nodep) {
if (needsTemp(nodep)) createTemp(nodep);
}
AstVarRef* isRhsOfAssignToVar(AstNodeExpr* nodep) {
// If enclosing statement is an assign
AstNodeAssign* const assignp = VN_CAST(m_stmtp, NodeAssign);
if (!assignp) return nullptr;
// of which 'nodep' is the RHS of
if (assignp->rhsp() != nodep) return nullptr;
// and the LHS is a sipmple variable reference
AstVarRef* const lRefp = VN_CAST(assignp->lhsp(), VarRef);
if (!lRefp) return nullptr;
AstVar* const varp = lRefp->varp();
// And the RHS does not use the same variable
if (nodep->exists([varp](const AstVarRef* refp) { return refp->varp() == varp; })) {
return nullptr;
}
// Then return the LHS reference
return lRefp;
}
// Create a new temporary that can hold the value of the given expression
AstVar* newTmpFor(AstNodeExpr* nodep) {
FileLine* const flp = nodep->fileline();
const std::string name = "__Vtemp_" + std::to_string(++m_tmpVarCnt);
AstVar* const varp = new AstVar{flp, VVarType::STMTTEMP, name, nodep->dtypep()};
varp->funcLocal(true);
varp->noReset(true);
m_cfuncp->addVarsp(varp);
++m_temporaryVarsCreated;
return varp;
}
AstVar* createTemp(AstNodeExpr* nodep) {
UASSERT_OBJ(m_stmtp, nodep, "Attempting to create temporary with no insertion point");
UINFO(4, "createTemp: " << nodep);
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
FileLine* const flp = nodep->fileline();
AstConst* const constp = VN_CAST(nodep, Const);
const bool useConstPool = constp // Is a constant
&& (constp->width() >= STATIC_CONST_MIN_WIDTH) // Large enough
&& !constp->num().isFourState() // Not four state
&& !constp->num().isString(); // Not a string
AstVar* varp = nullptr;
if (useConstPool) {
// Extract into constant pool.
const bool merge = v3Global.opt.fMergeConstPool();
varp = v3Global.rootp()->constPoolp()->findConst(constp, merge)->varp();
VL_DO_DANGLING(nodep->deleteTree(), nodep);
++m_extractedToConstPool;
} else {
// Keep as local temporary.
varp = newTmpFor(nodep);
// Assignment to put before the referencing statement
AstVarRef* const refp = new AstVarRef{flp, varp, VAccess::WRITE};
AstAssign* const assignp = new AstAssign{flp, refp, nodep};
// Insert before the statement
m_stmtp->addHereThisAsNext(assignp);
}
// Replace node with VarRef to new Var
relinker.relink(new AstVarRef{flp, varp, VAccess::READ});
// Return the temporary variable
return varp;
}
void visitShift(AstNodeBiop* nodep) {
// Shifts of > 32/64 bits in C++ will wrap-around and generate non-0s
UINFO(4, " ShiftFix " << nodep);
const AstConst* const shiftp = VN_CAST(nodep->rhsp(), Const);
if (shiftp && shiftp->num().mostSetBitP1() > 32) {
2025-09-20 14:19:42 +02:00
shiftp->v3warn(
E_UNSUPPORTED,
"Unsupported: Shifting of by over 32-bit number isn't supported."
2025-09-20 14:19:42 +02:00
<< " (This isn't a shift of 32 bits, but a shift of 2^32, or 4 billion!)\n");
}
if (nodep->widthMin() <= 64 // Else we'll use large operators which work right
// C operator's width must be < maximum shift which is
// based on Verilog width
&& nodep->width() < (1LL << nodep->rhsp()->widthMin())) {
AstNode* newp;
if (VN_IS(nodep, ShiftL)) {
newp = new AstShiftLOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(),
nodep->rhsp()->unlinkFrBack()};
} else if (VN_IS(nodep, ShiftR)) {
newp = new AstShiftROvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(),
nodep->rhsp()->unlinkFrBack()};
} else {
UASSERT_OBJ(VN_IS(nodep, ShiftRS), nodep, "Bad case");
newp = new AstShiftRSOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(),
nodep->rhsp()->unlinkFrBack()};
}
nodep->replaceWithKeepDType(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
iterateChildren(nodep);
checkNode(nodep);
}
static bool rhsReadsLhs(const AstNodeAssign* nodep) {
const VNUser3InUse user3InUse;
nodep->lhsp()->foreach([](const AstVarRef* refp) {
if (refp->access().isWriteOrRW()) refp->varp()->user3(true);
});
return nodep->rhsp()->exists([](const AstVarRef* refp) {
return refp->access().isReadOnly() && refp->varp()->user3();
});
}
// VISITORS
void visit(AstCFunc* nodep) override {
UASSERT_OBJ(!m_cfuncp, nodep, "Should not nest");
VL_RESTORER(m_cfuncp);
VL_RESTORER(m_tmpVarCnt);
m_cfuncp = nodep;
m_tmpVarCnt = 0;
iterateChildren(nodep);
}
// VISITORS - Statements
#define START_STATEMENT_OR_RETURN(stmtp) \
if (!m_cfuncp) return; \
if ((stmtp)->user1SetOnce()) return; \
VL_RESTORER(m_assignLhs); \
VL_RESTORER(m_stmtp); \
m_assignLhs = false; \
m_stmtp = (stmtp);
void visit(AstNodeAssign* nodep) override {
START_STATEMENT_OR_RETURN(nodep);
if (AstCvtArrayToPacked* const packedp = VN_CAST(nodep->lhsp(), CvtArrayToPacked)) {
// AstCvtArrayToPacked is converted to VL_PACK, which returns rvalue,
// so it shouldn't be on the LHS. It is now replaced with unpacking of RHS.
AstNodeExpr* const exprLhsp = packedp->fromp()->unlinkFrBack();
packedp->replaceWith(exprLhsp);
VL_DO_DANGLING(pushDeletep(packedp), packedp);
AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
nodep->rhsp(new AstCvtPackedToArray{rhsp->fileline(), rhsp, exprLhsp->dtypep()});
nodep->dtypeFrom(exprLhsp);
}
// Direct assignment to a simple variable
if (VN_IS(nodep->lhsp(), VarRef) && !AstVar::scVarRecurse(nodep->lhsp())) {
AstNode* const rhsp = nodep->rhsp();
// Rhs is already a var ref, so nothing to do
if (VN_IS(rhsp, VarRef) && !AstVar::scVarRecurse(rhsp)) return;
if (VN_IS(rhsp, Cond)) {
// Do replace Cond on RHS, even if a simple assignment
} else if (!VN_IS(rhsp, Const)) {
// Don't replace the rhs, it's already a simple assignment
rhsp->user1(true);
} else if (rhsp->width() < STATIC_CONST_MIN_WIDTH) {
// It's a small constant, so nothing to do, otherwise will be put to const pool
return;
}
}
// If the RHS reads the LHS, we need a temporary unless the update is atomic
const bool isAtomic = VN_IS(nodep->lhsp(), VarRef) && !nodep->lhsp()->isWide();
if (!isAtomic && rhsReadsLhs(nodep)) {
createTemp(nodep->rhsp());
} else {
iterateAndNextNull(nodep->rhsp());
}
m_assignLhs = true; // Restored by VL_RESTORER in START_STATEMENT_OR_RETURN
iterateAndNextNull(nodep->lhsp());
// It's possible we end up with an 'a' = 'a' after expanding an AstCond
if (AstVarRef* const rhsp = VN_CAST(nodep->rhsp(), VarRef)) {
if (AstVarRef* const lhsp = VN_CAST(nodep->lhsp(), VarRef)) {
if (lhsp->varp() == rhsp->varp()) {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
}
}
}
void visit(AstDisplay* nodep) override {
START_STATEMENT_OR_RETURN(nodep);
iterateChildren(nodep);
if (v3Global.opt.autoflush()) {
const AstNode* searchp = nodep->nextp();
while (searchp && VN_IS(searchp, Comment)) searchp = searchp->nextp();
if (searchp && VN_IS(searchp, Display)
&& nodep->filep()->sameGateTree(VN_AS(searchp, Display)->filep())) {
// There's another display next; we can just wait to flush
} else {
UINFO(4, "Autoflush " << nodep);
nodep->addNextHere(
new AstFFlush{nodep->fileline(),
nodep->filep() ? nodep->filep()->cloneTreePure(true) : nullptr});
}
}
}
void visit(AstNodeStmt* nodep) override {
START_STATEMENT_OR_RETURN(nodep);
iterateChildren(nodep);
}
#undef START_STATEMENT_OR_RETURN
// VISITORS - Expressions
void visit(AstShiftL* nodep) override { visitShift(nodep); }
void visit(AstShiftR* nodep) override { visitShift(nodep); }
void visit(AstShiftRS* nodep) override { visitShift(nodep); }
void visit(AstConst* nodep) override { checkNode(nodep); }
// Operators
void visit(AstNodeTermop* nodep) override { checkNode(nodep); }
void visit(AstNodeUniop* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstNodeBiop* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstRand* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstRandRNG* nodep) override {
iterateChildren(nodep);
checkNode(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 {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstCMethodHard* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstCvtArrayToPacked* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
2025-04-15 17:50:43 +02:00
void visit(AstCvtPackedToArray* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
if (!VN_IS(nodep->backp(), NodeAssign)) createTemp(nodep);
2025-04-15 17:50:43 +02:00
}
void visit(AstCvtUnpackedToQueue* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstSel* nodep) override {
iterateAndNextNull(nodep->fromp());
{ // Only the 'from' is part of the assignment LHS
VL_RESTORER(m_assignLhs);
m_assignLhs = false;
iterateAndNextNull(nodep->lsbp());
// AstSel::widthp() must remain a constant, so not iterating
}
checkNode(nodep);
}
void visit(AstArraySel* nodep) override {
2025-04-15 17:50:43 +02:00
iterateAndNextNull(nodep->fromp());
{ // Only the 'from' is part of the assignment LHS
VL_RESTORER(m_assignLhs);
2019-12-01 17:52:48 +01:00
m_assignLhs = false;
2025-04-15 17:50:43 +02:00
iterateAndNextNull(nodep->bitp());
2019-12-01 17:52:48 +01:00
}
// ArraySel are just pointer arithmetic and should never be replaced
2019-12-01 17:52:48 +01:00
}
void visit(AstAssocSel* nodep) override {
2019-12-01 17:52:48 +01:00
iterateAndNextNull(nodep->fromp());
{ // Only the 'from' is part of the assignment LHS
VL_RESTORER(m_assignLhs);
m_assignLhs = false;
iterateAndNextNull(nodep->bitp());
}
checkNode(nodep);
}
void visit(AstMatchMasked* nodep) override {
iterateChildren(nodep);
if (!nodep->user1SetOnce()) {
// Don't want this replicated by V3Expand
AstVar* const varp = createTemp(nodep);
varp->noSubst(true); // Do not re-inline in V3Subst
}
}
void visit(AstCond* nodep) override {
// Convert AstCond to AstIf in order to avoid evaluating
// sub-expressions in both branches unconditionally.
if (needsTemp(nodep)) {
// Check if LHS variable could be used directly
AstVarRef* const lRefp = isRhsOfAssignToVar(nodep);
// If not, create a new temporary variable
AstVar* varp = lRefp ? lRefp->varp() : newTmpFor(nodep);
// Can't substitute across basic blocks
varp->noSubst(true);
FileLine* const flp = nodep->fileline();
// Create 'then' assignment
AstVarRef* const thenRefp = new AstVarRef{flp, varp, VAccess::WRITE};
if (lRefp) thenRefp->selfPointer(lRefp->selfPointer());
AstNodeExpr* const thenExprp = nodep->thenp()->unlinkFrBack();
AstAssign* const thenAsspp = new AstAssign{flp, thenRefp, thenExprp};
// Create 'else' assignment
AstVarRef* const elseRefp = new AstVarRef{flp, varp, VAccess::WRITE};
if (lRefp) elseRefp->selfPointer(lRefp->selfPointer());
AstNodeExpr* const elseExprp = nodep->elsep()->unlinkFrBack();
AstAssign* const elseAsspp = new AstAssign{flp, elseRefp, elseExprp};
// Creae 'if' and insert it before the statement
AstNodeExpr* const condp = nodep->condp()->unlinkFrBack();
AstIf* const ifp = new AstIf{flp, condp, thenAsspp, elseAsspp};
m_stmtp->addHereThisAsNext(ifp);
// Replace the AstCond with the result variable
nodep->replaceWith(new AstVarRef{flp, varp, VAccess::READ});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
// Splitting to multiple statements can change purity
VIsCached::clearCacheTree();
// Iterate the resulting assignments
iterate(ifp);
return;
}
iterateChildren(nodep);
}
void visit(AstSFormatF* nodep) override {
iterateChildren(nodep);
// Any strings sent to a display must be var of string data type,
// to avoid passing a pointer to a temporary.
AstNodeExpr* exprsp = nodep->exprsp();
if (nodep->exprFormat()) exprsp = VN_AS(exprsp->nextp(), NodeExpr);
for (AstNodeExpr *argp = exprsp, *nextp; argp; argp = nextp) {
nextp = VN_AS(argp->nextp(), NodeExpr);
AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg);
AstNodeExpr* const subargp = fargp ? fargp->exprp() : argp;
// Must avoid taking address of rvalue, so even Const needs a temp
if (subargp->isString() && !VN_IS(subargp, VarRef)) {
AstVar* const varp = createTemp(subargp);
// Do not remove VarRefs to this in V3Const
varp->noSubst(true);
}
}
}
2008-07-16 20:06:08 +02:00
//--------------------
// Default: Just iterate
void visit(AstVar*) override {} // Don't hit varrefs under vars
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
2019-09-12 13:22:22 +02:00
// CONSTRUCTORS
explicit PremitVisitor(AstNetlist* nodep) { iterate(nodep); }
~PremitVisitor() override {
V3Stats::addStat("Optimizations, Prelim extracted value to ConstPool",
m_extractedToConstPool);
V3Stats::addStat("Optimizations, Prelim temporary variables created",
m_temporaryVarsCreated);
}
};
//######################################################################
// Premit class functions
void V3Premit::premitAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ PremitVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("premit", 0, dumpTreeEitherLevel() >= 3);
}