// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Replace increments/decrements with new variables // // 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 // //************************************************************************* // V3LinkInc's Transformations: // // prepost_expr_visit // PREINC/PREDEC // Create a temporary __VIncrementX variable, assign the value of // the current variable value to it, substitute the current // variable with the temporary one in the statement. // Increment/decrement the original variable with by the given // value. // POSTINC/POSTDEC // Increment/decrement the current variable by the given value. // Create a temporary __VIncrementX variable, assign the value of // of the current variable (after the operation) to it. Substitute // The original variable with the temporary one in the statement. // // prepost_stmt_visit // PREINC/PREDEC/POSTINC/POSTDEC // Increment/decrement the current variable by the given value. // The order (pre/post) doesn't matter outside statements thus // the pre/post operations are treated equally and there is no // need for a temporary variable. // // prepost_stmt_sel_visit // For e.g. 'array[something_with_side_eff]++', common in UVM etc // PREADD/PRESUB/POSTADD/POSTSUB // Create temporary with array index. // Increment/decrement using index of the temporary. // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3LinkInc.h" #include "V3LinkLValue.h" VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### class LinkIncVisitor final : public VNVisitor { // STATE AstNodeFTask* m_ftaskp = nullptr; // Function or task we're inside AstNodeModule* m_modp = nullptr; // Module we're inside int m_modCompoundAssignmentsNum = 0; // Var name counter AstNode* m_insStmtp = nullptr; // Where to insert statement bool m_unsupportedHere = false; // Used to detect where it's not supported yet // METHODS void insertOnTop(AstNode* newp) { // Add the thing directly under the current TFunc/Module AstNode* stmtsp = nullptr; if (m_ftaskp) { stmtsp = m_ftaskp->stmtsp(); } else if (m_modp) { stmtsp = m_modp->stmtsp(); } UASSERT_OBJ(stmtsp, newp, "Variable not under FTASK/MODULE"); newp->addNext(stmtsp->unlinkFrBackWithNext()); if (m_ftaskp) { m_ftaskp->addStmtsp(newp); } else if (m_modp) { m_modp->addStmtsp(newp); } } void insertBeforeStmt(AstNode* nodep, AstNode* newp) { // Return node that must be visited, if any UINFOTREE(9, newp, "", "newstmt"); UASSERT_OBJ(m_insStmtp, nodep, "Expression not underneath a statement"); m_insStmtp->addHereThisAsNext(newp); } // VISITORS void visit(AstNodeModule* nodep) override { if (nodep->dead()) return; VL_RESTORER(m_modp); VL_RESTORER(m_modCompoundAssignmentsNum); m_modp = nodep; m_modCompoundAssignmentsNum = 0; iterateChildren(nodep); } void visit(AstNodeFTask* nodep) override { VL_RESTORER(m_ftaskp); m_ftaskp = nodep; iterateChildren(nodep); } void visit(AstNodeCoverOrAssert* nodep) override { VL_RESTORER(m_insStmtp); m_insStmtp = nodep; iterateAndNextNull(nodep->propp()); m_insStmtp = nullptr; // Note: no iterating over sentreep here as they will be ignored anyway if (AstAssert* const assertp = VN_CAST(nodep, Assert)) { iterateAndNextNull(assertp->failsp()); } else if (AstAssertIntrinsic* const intrinsicp = VN_CAST(nodep, AssertIntrinsic)) { iterateAndNextNull(intrinsicp->failsp()); } iterateAndNextNull(nodep->passsp()); } void visit(AstLoop* nodep) override { UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump"); m_insStmtp = nullptr; // First thing should be new statement iterateAndNextNull(nodep->stmtsp()); m_insStmtp = nullptr; // Next thing should be new statement } void visit(AstNodeForeach* nodep) override { // Special, as statements need to be put in different places // Body insert just before themselves m_insStmtp = nullptr; // First thing should be new statement iterateChildren(nodep); // Done the loop m_insStmtp = nullptr; // Next thing should be new statement } void visit(AstJumpBlock* nodep) override { // Special, as statements need to be put in different places // Body insert just before themselves m_insStmtp = nullptr; // First thing should be new statement iterateChildren(nodep); // Done the loop m_insStmtp = nullptr; // Next thing should be new statement } void visit(AstNodeIf* nodep) override { m_insStmtp = nodep; iterateAndNextNull(nodep->condp()); m_insStmtp = nullptr; iterateAndNextNull(nodep->thensp()); iterateAndNextNull(nodep->elsesp()); m_insStmtp = nullptr; } void visit(AstCaseItem* nodep) override { { VL_RESTORER(m_unsupportedHere); m_unsupportedHere = true; iterateAndNextNull(nodep->condsp()); } m_insStmtp = nullptr; // Next thing should be new statement iterateAndNextNull(nodep->stmtsp()); } void visit(AstDelay* nodep) override { m_insStmtp = nodep; iterateAndNextNull(nodep->lhsp()); m_insStmtp = nullptr; iterateAndNextNull(nodep->stmtsp()); m_insStmtp = nullptr; } void visit(AstEventControl* nodep) override { m_insStmtp = nullptr; iterateAndNextNull(nodep->stmtsp()); m_insStmtp = nullptr; } void visit(AstWait* nodep) override { m_insStmtp = nodep; iterateAndNextNull(nodep->condp()); m_insStmtp = nullptr; iterateAndNextNull(nodep->stmtsp()); m_insStmtp = nullptr; } void visit(AstBegin* nodep) override { m_insStmtp = nullptr; // Pretend not a statement TODO: parse ++/-- as ExprStmt iterateChildren(nodep); m_insStmtp = nullptr; // Next thing should be new statement } void visit(AstStmtExpr* nodep) override { AstNodeExpr* const exprp = nodep->exprp(); if (VN_IS(exprp, PostInc) || VN_IS(exprp, PostDec) || VN_IS(exprp, PreInc) || VN_IS(exprp, PreDec)) { // Repalce this StmtExpr with the expression, visiting it will turn it into a NodeStmt nodep->replaceWith(exprp->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(nodep), nodep); m_insStmtp = nullptr; iterate(exprp); m_insStmtp = nullptr; return; } visit(static_cast(nodep)); } void visit(AstNodeStmt* nodep) override { m_insStmtp = nodep; iterateChildren(nodep); m_insStmtp = nullptr; // Next thing should be new statement } void unsupported_visit(AstNode* nodep) { VL_RESTORER(m_unsupportedHere); m_unsupportedHere = true; UINFO(9, "Marking unsupported " << nodep); iterateChildren(nodep); } void visit(AstLogAnd* nodep) override { unsupported_visit(nodep); } void visit(AstLogOr* nodep) override { unsupported_visit(nodep); } void visit(AstLogEq* nodep) override { unsupported_visit(nodep); } void visit(AstLogIf* nodep) override { unsupported_visit(nodep); } void visit(AstCond* nodep) override { unsupported_visit(nodep); } void visit(AstPropSpec* nodep) override { unsupported_visit(nodep); } void prepost_visit(AstNodeUniop* const nodep) { // Check if we are underneath a statement AstSelBit* const selbitp = VN_CAST(nodep->lhsp(), SelBit); if (!m_insStmtp && selbitp && VN_IS(selbitp->fromp(), NodeVarRef) && !selbitp->bitp()->isPure()) { prepost_stmt_sel_visit(nodep); } else { if (!m_insStmtp) { prepost_stmt_visit(nodep); } else { prepost_expr_visit(nodep); } } } AstNodeExpr* getOperationp(AstNode* const nodep, AstNodeExpr* const lhsp, AstNodeExpr* const rhsp) { if (VN_IS(nodep, PreDec) || VN_IS(nodep, PostDec)) { return new AstSub{nodep->fileline(), lhsp, rhsp}; } if (VN_IS(nodep, PreInc) || VN_IS(nodep, PostInc)) { return new AstAdd{nodep->fileline(), lhsp, rhsp}; } if (AstAssignCompound* assignp = VN_CAST(nodep, AssignCompound)) { switch (assignp->operation()) { case AstAssignCompound::operation::Add: return new AstAdd{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::And: return new AstAnd{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::Div: return new AstDiv{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::ModDiv: return new AstModDiv{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::Mul: return new AstMul{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::Or: return new AstOr{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::ShiftL: return new AstShiftL{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::ShiftR: return new AstShiftR{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::ShiftRS: return new AstShiftRS{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::Sub: return new AstSub{nodep->fileline(), lhsp, rhsp}; case AstAssignCompound::operation::Xor: return new AstXor{nodep->fileline(), lhsp, rhsp}; } } nodep->v3fatalSrc("Unhandled compound assignment operation"); } void prepost_stmt_sel_visit(AstNodeUniop* const nodep) { // Special case array[something]++, see comments at file top // UINFOTREE(9, nodep, "", "pp-stmt-sel-in"); iterateChildren(nodep); FileLine* fl = nodep->fileline(); V3Number numOne{fl, 32, 1, false}; AstNodeExpr* const exprp = new AstConst{nodep->fileline(), numOne}; prepost_stmt_sel_visit(nodep, nodep->lhsp(), exprp); } void prepost_stmt_sel_visit(AstAssignCompound* const nodep) { // Special case array[something] += expr, see comments at file top // UINFOTREE(9, nodep, "", "pp-stmt-sel-in"); AstNodeExpr* const exprp = nodep->rhsp()->unlinkFrBack(); prepost_stmt_sel_visit(nodep, nodep->lhsp(), exprp); } void prepost_stmt_sel_visit(AstNode* const nodep, AstNodeExpr* const lhsp, AstNodeExpr* const exprp) { AstSelBit* const rdSelbitp = VN_AS(lhsp, SelBit); AstNodeVarRef* const rdFromp = VN_AS(rdSelbitp->fromp()->cloneTreePure(true), NodeVarRef); rdFromp->access(VAccess::READ); AstNodeExpr* const rdBitp = rdSelbitp->bitp()->unlinkFrBack(); AstSelBit* const wrSelbitp = VN_CAST(lhsp, SelBit); AstNodeExpr* const wrFromp = wrSelbitp->fromp()->unlinkFrBack(); V3LinkLValue::linkLValueSet(wrFromp); // Prepare a temporary variable FileLine* const fl = nodep->fileline(); const string name = "__VtempIndex"s + cvtToStr(++m_modCompoundAssignmentsNum); AstVar* const varp = new AstVar{ fl, VVarType::BLOCKTEMP, name, VFlagChildDType{}, new AstRefDType{fl, AstRefDType::FlagTypeOfExpr{}, rdBitp->cloneTree(true)}}; if (m_ftaskp) varp->funcLocal(true); // Declare the variable insertOnTop(varp); // Define what operation will we be doing AstAssign* const varAssignp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, rdBitp}; AstNode* const newp = varAssignp; AstNodeExpr* const valuep = new AstSelBit{fl, rdFromp, new AstVarRef{fl, varp, VAccess::READ}}; AstNodeExpr* const storeTop = new AstSelBit{fl, wrFromp, new AstVarRef{fl, varp, VAccess::READ}}; AstAssign* assignp = new AstAssign{nodep->fileline(), storeTop, getOperationp(nodep, valuep, exprp)}; newp->addNext(assignp); // if (debug() >= 9) newp->dumpTreeAndNext("-pp-stmt-sel-new: "); nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } void prepost_stmt_visit(AstNodeUniop* const nodep) { iterateChildren(nodep); AstNodeExpr* const storeTop = nodep->lhsp()->cloneTreePure(true); AstNodeExpr* const valuep = nodep->lhsp()->unlinkFrBack(); FileLine* const fl = nodep->fileline(); V3Number numOne{fl, 32, 1, false}; AstNodeExpr* const exprp = new AstConst{nodep->fileline(), numOne}; prepost_stmt_visit(nodep, exprp, storeTop, valuep); } void prepost_stmt_visit(AstAssignCompound* const nodep) { AstNodeExpr* const exprp = nodep->rhsp()->unlinkFrBack(); AstNodeExpr* const storeTop = nodep->lhsp()->cloneTreePure(true); AstNodeExpr* const valuep = nodep->lhsp()->unlinkFrBack(); prepost_stmt_visit(nodep, exprp, storeTop, valuep); } void prepost_stmt_visit(AstNode* const nodep, AstNodeExpr* const exprp, AstNodeExpr* const storeTop, AstNodeExpr* const valuep) { V3LinkLValue::linkLValueSet(valuep, false); AstAssign* const assignp = new AstAssign{nodep->fileline(), storeTop, getOperationp(nodep, valuep, exprp)}; nodep->replaceWith(assignp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } void prepost_expr_visit(AstNodeUniop* const nodep) { iterateChildren(nodep); if (m_unsupportedHere) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: Pre/post increment/decrement operator" " within a logical expression (&&, ||, ?:, etc.)"); return; } AstNodeExpr* const readp = nodep->lhsp(); AstNodeExpr* const writep = nodep->lhsp()->cloneTreePure(true); V3LinkLValue::linkLValueSet(readp, false); FileLine* const fl = nodep->fileline(); V3Number numOne{fl, 32, 1, false}; AstNodeExpr* const newconstp = new AstConst{nodep->fileline(), numOne}; // Prepare a temporary variable const string name = "__Vincrement"s + cvtToStr(++m_modCompoundAssignmentsNum); AstVar* const varp = new AstVar{ fl, VVarType::BLOCKTEMP, name, VFlagChildDType{}, new AstRefDType{fl, AstRefDType::FlagTypeOfExpr{}, readp->cloneTreePure(true)}}; varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); if (m_ftaskp) varp->funcLocal(true); // Declare the variable insertOnTop(varp); // Define what operation will we be doing AstNodeExpr* const operp = getOperationp(nodep, readp->cloneTreePure(true), newconstp); if (VN_IS(nodep, PreInc) || VN_IS(nodep, PreDec)) { // PreInc/PreDec operations // Immediately after declaration - increment it by one AstAssign* const assignp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, operp}; // Immediately after incrementing - assign it to the original variable assignp->addNext(new AstAssign{fl, writep, new AstVarRef{fl, varp, VAccess::READ}}); insertBeforeStmt(nodep, assignp); } else { // PostInc/PostDec operations // Assign the original variable to the temporary one AstAssign* const assignp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, readp->cloneTreePure(true)}; // Increment the original variable by one assignp->addNext(new AstAssign{fl, writep, operp}); insertBeforeStmt(nodep, assignp); } // Replace the node with the temporary nodep->replaceWith(new AstVarRef{readp->fileline(), varp, VAccess::READ}); VL_DO_DANGLING(nodep->deleteTree(), nodep); } void visit(AstPreInc* nodep) override { prepost_visit(nodep); } void visit(AstPostInc* nodep) override { prepost_visit(nodep); } void visit(AstPreDec* nodep) override { prepost_visit(nodep); } void visit(AstPostDec* nodep) override { prepost_visit(nodep); } void visit(AstAssignCompound* nodep) override { visit(static_cast(nodep)); AstSelBit* const selbitp = VN_CAST(nodep->lhsp(), SelBit); if (!m_insStmtp && selbitp && VN_IS(selbitp->fromp(), NodeVarRef) && !selbitp->bitp()->isPure()) { prepost_stmt_sel_visit(nodep); } else { prepost_stmt_visit(nodep); } } void visit(AstGenFor* nodep) override { iterateChildren(nodep); } void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS explicit LinkIncVisitor(AstNetlist* nodep) { iterate(nodep); } ~LinkIncVisitor() override = default; }; //###################################################################### // Task class functions void V3LinkInc::linkIncrements(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { LinkIncVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("linkinc", 0, dumpTreeEitherLevel() >= 3); }