// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Add temporaries, such as for premit nodes // // 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. // // Each display (independent transformation; here as Premit is a good point) // If autoflush, insert a flush // //************************************************************************* #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; // STATE - across all visitors VDouble0 m_extractedToConstPool; // Statistic tracking VDouble0 m_temporaryVarsCreated; // Statistic tracking // 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) { shiftp->v3warn( E_UNSUPPORTED, "Unsupported: Shifting of by over 32-bit number isn't supported." << " (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); } 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); } void visit(AstCvtPackedToArray* nodep) override { iterateChildren(nodep); checkNode(nodep); if (!VN_IS(nodep->backp(), NodeAssign)) createTemp(nodep); } 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 { iterateAndNextNull(nodep->fromp()); { // Only the 'from' is part of the assignment LHS VL_RESTORER(m_assignLhs); m_assignLhs = false; iterateAndNextNull(nodep->bitp()); } // ArraySel are just pointer arithmetic and should never be replaced } void visit(AstAssocSel* nodep) override { 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(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); } } } //-------------------- // Default: Just iterate void visit(AstVar*) override {} // Don't hit varrefs under vars void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // 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); }