// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Convert BLOCKTEMPs to local variables // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2021 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 // //************************************************************************* // LOCALIZE TRANSFORMATIONS: // All modules: // VAR(BLOCKTEMP... // if only referenced in a CFUNC, make it local to that CFUNC // VAR(others // if non-public, set before used, and in single CFUNC, make it local // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include "V3Global.h" #include "V3Localize.h" #include "V3Stats.h" #include "V3Ast.h" #include //###################################################################### // Localize base class class LocalizeBaseVisitor VL_NOT_FINAL : public AstNVisitor { protected: // NODE STATE // Cleared on entire tree // AstVar::user1p() -> CFunc which references the variable // AstVar::user2() -> VarFlags. Flag state // AstVar::user4() -> AstVarRef*. First place signal set; must be first assignment // METHODS VL_DEBUG_FUNC; // Declare debug() // TYPES union VarFlags { // Per-variable flags // Used in user()'s so initializes to all zeros struct { int m_notOpt : 1; // NOT optimizable int m_notStd : 1; // NOT optimizable if a non-blocktemp signal int m_stdFuncAsn : 1; // Found simple assignment int m_done : 1; // Removed }; // cppcheck-suppress unusedStructMember uint32_t m_flags; explicit VarFlags(AstNode* nodep) { m_flags = nodep->user2(); } void setNodeFlags(AstNode* nodep) { nodep->user2(m_flags); } }; }; //###################################################################### // Localize class functions class LocalizeDehierVisitor final : public LocalizeBaseVisitor { private: // NODE STATE/TYPES // See above // METHODS virtual void visit(AstVarRef* nodep) override { // cppcheck-suppress unreadVariable // cppcheck 1.90 bug VarFlags flags(nodep->varp()); if (flags.m_done) { nodep->hiernameToProt(""); // Remove this-> nodep->hiernameToUnprot(""); // Remove this-> nodep->hierThis(true); } } virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS explicit LocalizeDehierVisitor(AstNetlist* nodep) { iterate(nodep); } virtual ~LocalizeDehierVisitor() override = default; }; //###################################################################### // Localize class functions class LocalizeVisitor final : public LocalizeBaseVisitor { private: // NODE STATE/TYPES // See above AstUser1InUse m_inuser1; AstUser2InUse m_inuser2; AstUser4InUse m_inuser4; // STATE VDouble0 m_statLocVars; // Statistic tracking AstCFunc* m_cfuncp = nullptr; // Current active function std::vector m_varps; // List of variables to consider for deletion // METHODS void clearOptimizable(AstVar* nodep, const char* reason) { UINFO(4, " NoOpt " << reason << " " << nodep << endl); VarFlags flags(nodep); flags.m_notOpt = true; flags.setNodeFlags(nodep); } void clearStdOptimizable(AstVar* nodep, const char* reason) { UINFO(4, " NoStd " << reason << " " << nodep << endl); VarFlags flags(nodep); flags.m_notStd = true; flags.setNodeFlags(nodep); } void moveVars() { for (AstVar* nodep : m_varps) { if (nodep->valuep()) clearOptimizable(nodep, "HasInitValue"); if (!VarFlags(nodep).m_stdFuncAsn) clearStdOptimizable(nodep, "NoStdAssign"); VarFlags flags(nodep); if ((nodep->isMovableToBlock() // Blocktemp || !flags.m_notStd) // Or used only in block && !flags.m_notOpt // Optimizable && !nodep->isClassMember() && nodep->user1p()) { // Single cfunc // We don't need to test for tracing; it would be in the tracefunc if it was needed UINFO(4, " ModVar->BlkVar " << nodep << endl); ++m_statLocVars; AstCFunc* newfuncp = VN_CAST(nodep->user1p(), CFunc); nodep->unlinkFrBack(); newfuncp->addInitsp(nodep); // Done flags.m_done = true; flags.setNodeFlags(nodep); } else { clearOptimizable(nodep, "NotDone"); } } m_varps.clear(); } // VISITORS virtual void visit(AstNetlist* nodep) override { iterateChildren(nodep); moveVars(); } virtual void visit(AstCFunc* nodep) override { UINFO(4, " CFUNC " << nodep << endl); VL_RESTORER(m_cfuncp); { m_cfuncp = nodep; searchFuncStmts(nodep->argsp()); searchFuncStmts(nodep->initsp()); searchFuncStmts(nodep->stmtsp()); searchFuncStmts(nodep->finalsp()); iterateChildren(nodep); } } void searchFuncStmts(AstNode* nodep) { // Search for basic assignments to allow moving non-blocktemps // For now we only find simple assignments not under any other statement. // This could be more complicated; allow always-set under both branches of a IF. // If so, check for ArrayRef's and such, as they aren't acceptable. for (; nodep; nodep = nodep->nextp()) { if (VN_IS(nodep, NodeAssign)) { if (AstVarRef* varrefp = VN_CAST(VN_CAST(nodep, NodeAssign)->lhsp(), VarRef)) { UASSERT_OBJ(varrefp->access().isWriteOrRW(), varrefp, "LHS assignment not lvalue"); if (!varrefp->varp()->user4p()) { UINFO(4, " FuncAsn " << varrefp << endl); varrefp->varp()->user4p(varrefp); VarFlags flags(varrefp->varp()); flags.m_stdFuncAsn = true; flags.setNodeFlags(varrefp->varp()); } } } } } virtual void visit(AstVar* nodep) override { if (!nodep->isSigPublic() && !nodep->isPrimaryIO() && !m_cfuncp) { // Not already inside a function UINFO(4, " BLKVAR " << nodep << endl); m_varps.push_back(nodep); } // No iterate; Don't want varrefs under it } virtual void visit(AstVarRef* nodep) override { if (!VarFlags(nodep->varp()).m_notOpt) { if (!m_cfuncp) { // Not in function, can't optimize // Perhaps impossible, but better safe clearOptimizable(nodep->varp(), "BVnofunc"); // LCOV_EXCL_LINE } else { // If we're scoping down to it, it isn't really in the same block if (!nodep->hierThis()) clearOptimizable(nodep->varp(), "HierRef"); // Allow a variable to appear in only a single function AstNode* oldfunc = nodep->varp()->user1p(); if (!oldfunc) { UINFO(4, " BVnewref " << nodep << endl); nodep->varp()->user1p(m_cfuncp); // Remember where it was used } else if (m_cfuncp == oldfunc) { // Same usage } else { // Used in multiple functions clearOptimizable(nodep->varp(), "BVmultiF"); } // First varref in function must be assignment found earlier AstVarRef* firstasn = static_cast(nodep->varp()->user4p()); if (firstasn && nodep != firstasn) { clearStdOptimizable(nodep->varp(), "notFirstAsn"); nodep->varp()->user4p(nullptr); } } } // No iterate; Don't want varrefs under it } virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS explicit LocalizeVisitor(AstNetlist* nodep) { iterate(nodep); } virtual ~LocalizeVisitor() override { V3Stats::addStat("Optimizations, Vars localized", m_statLocVars); } }; //###################################################################### // Localize class functions void V3Localize::localizeAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); { LocalizeVisitor visitor(nodep); // Fix up hiernames LocalizeDehierVisitor dvisitor(nodep); } // Destruct before checking V3Global::dumpCheckGlobalTree("localize", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 6); }