// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Add temporaries, such as for inline 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 // //************************************************************************* // V3Inline's Transformations: // // Each module: // Look for CELL... PRAGMA INLINE_MODULE // Replicate the cell's module // Convert pins to wires that make assignments // Rename vars to include cell name // Insert cell's module statements into the upper module // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Inline.h" #include "V3AstUserAllocator.h" #include "V3Graph.h" #include "V3Inst.h" #include "V3Stats.h" #include #include VL_DEFINE_DEBUG_FUNCTIONS; // CONFIG static const int INLINE_MODS_SMALLER = 100; // If a mod is < this # nodes, can always inline it //###################################################################### // Bipartite module instantiation graph containing module and cell vertices class InlineModModuleVertex; class InlineModCellVertex; class InlineModGraph final : public V3Graph { // NODE STATE // AstNodeModule::user4p() -> InlineModModuleVertex*, the module vertex // AstCell::user4p() -> InlineModCellVertex*, the cell vertex VNUser4InUse m_user4InUse; public: InlineModGraph() : V3Graph{} {} ~InlineModGraph() override = default; // METHODS InlineModModuleVertex* getInlineModModuleVertexp(AstNodeModule* modp); InlineModCellVertex* getInlineModCellVertexp(AstCell* cellp); void addEdge(InlineModModuleVertex& from, InlineModCellVertex& to); void addEdge(InlineModCellVertex& from, InlineModModuleVertex& to); // debug std::string dotRankDir() const override { return "LR"; } }; class InlineModEitherVertex VL_NOT_FINAL : public V3GraphVertex { VL_RTTI_IMPL(InlineModEitherVertex, V3GraphVertex) protected: explicit InlineModEitherVertex(InlineModGraph& graph) : V3GraphVertex{&graph} {} }; class InlineModModuleVertex final : public InlineModEitherVertex { VL_RTTI_IMPL(InlineModModuleVertex, InlineModEitherVertex) AstNodeModule* const m_modp; // The module const char* m_noInlineHardWyp = nullptr; // First reason the module can never be inlined const char* m_noInlineSoftWyp = nullptr; // First reason not to inline unless forced const char* m_shouldInlineWhyp = nullptr; // First reason why this module should be inlined size_t m_size = 0; // The size (statement count) of the module size_t mutable m_flattenedSize = 0; // The size of the module if flattened bool mutable m_flattenedSizeValid = false; // Whether the flattened size is valid size_t mutable m_instanceCount = 0; // The number of total instances of this module bool mutable m_instanceCountValid = false; // Whether the instance count is valid public: InlineModModuleVertex(InlineModGraph& graph, AstNodeModule* modp) : InlineModEitherVertex{graph} , m_modp{modp} {} ~InlineModModuleVertex() override = default; // ACCESSORS AstNodeModule* modp() const { return m_modp; } size_t size() const { return m_size; } void size(size_t value) { m_size = value; } void sizeInc(size_t value = 1) { m_size += value; } bool noInlineHard() const { return m_noInlineHardWyp; } void setNoInlineHard(const char* whyp) { if (!m_noInlineHardWyp) m_noInlineHardWyp = whyp; } bool noInlineSoft() const { return m_noInlineSoftWyp; } void setNoInlineSoft(const char* whyp) { if (!m_noInlineSoftWyp) m_noInlineSoftWyp = whyp; } bool shouldInline() const { return m_shouldInlineWhyp; } void setShouldInline(const char* whyp) { if (!m_shouldInlineWhyp) m_shouldInlineWhyp = whyp; } // Mark every instance below this module for inlining void setFlatten(); // Total size of module, with all hierarchy below flattened size_t flattenedSize() const { if (!m_flattenedSizeValid) { m_flattenedSizeValid = true; m_flattenedSize = m_size; for (const V3GraphEdge& e1 : outEdges()) { for (const V3GraphEdge& e2 : e1.top()->outEdges()) { InlineModModuleVertex* const mVtxp = e2.top()->as(); m_flattenedSize += mVtxp->flattenedSize(); } } } return m_flattenedSize; } // Total number of instances of this module in the whole hierarchy of the design size_t instanceCount() const { if (!m_instanceCountValid) { m_instanceCountValid = true; m_instanceCount = 0; for (const V3GraphEdge& e1 : inEdges()) { for (const V3GraphEdge& e2 : e1.fromp()->inEdges()) { InlineModModuleVertex* const mVtxp = e2.fromp()->as(); m_instanceCount += mVtxp->instanceCount(); } } if (!m_instanceCount) { UASSERT_OBJ(m_modp->isTop(), m_modp, "non-top level module should have instances"); m_instanceCount = 1; } } return m_instanceCount; } // debug FileLine* fileline() const override { return m_modp->fileline(); } std::string dotShape() const override { return "box"; } std::string dotColor() const override { return m_noInlineHardWyp ? "red" : m_shouldInlineWhyp ? "blue" : m_noInlineSoftWyp ? "orange" : "black"; } std::string name() const override VL_MT_STABLE { std::string str = m_modp->typeName() + " "s + cvtToHex(m_modp); str += "\n" + m_modp->name() + " @ " + fileline()->ascii(); str += "\ninstanceCount: " + std::to_string(instanceCount()); str += "\nsize: " + std::to_string(m_size); str += "\nflattenedSize: " + std::to_string(flattenedSize()); if (m_shouldInlineWhyp) str += "\nShouldInline: "s + m_shouldInlineWhyp; if (m_noInlineHardWyp) str += "\nNoInlineHard: "s + m_noInlineHardWyp; if (m_noInlineSoftWyp) str += "\nNoInlineSoft: "s + m_noInlineSoftWyp; str += "\n"; return str; } }; class InlineModCellVertex final : public InlineModEitherVertex { VL_RTTI_IMPL(InlineModCellVertex, InlineModEitherVertex) AstCell* const m_cellp; // The cell (instance) const char* m_doInlineWyp = nullptr; // First reason this instance should be inlined bool m_flatten = false; // Whether this cell and below already flattened (avoid O(n^2)) public: InlineModCellVertex(InlineModGraph& graph, AstCell* cellp) : InlineModEitherVertex{graph} , m_cellp{cellp} {} ~InlineModCellVertex() override = default; // ACCESSORS AstCell* cellp() const { return m_cellp; } bool doInline() const { return m_doInlineWyp; } void setDoInline(const char* whyp) { if (!m_doInlineWyp) m_doInlineWyp = whyp; } bool flatten() const { return m_flatten; } void setFlatten() { m_flatten = true; } // The module vertx this cell is instantiating InlineModModuleVertex& instanceOf() const { UASSERT_OBJ(outSize1(), this, "Cell should have exactly one outgoing edge"); return *outEdges().frontp()->top()->as(); } // The module vertex this cell is instantiated in InlineModModuleVertex& instanceIn() const { UASSERT_OBJ(inSize1(), this, "Cell should have exactly one incoming edge"); return *inEdges().frontp()->fromp()->as(); } // debug FileLine* fileline() const override { return m_cellp->fileline(); } std::string dotColor() const override { return m_doInlineWyp ? "green" : "black"; } std::string dotShape() const override { return "ellipse"; } std::string name() const override VL_MT_STABLE { std::string str = m_cellp->typeName() + " "s + cvtToHex(m_cellp); str += "\n" + m_cellp->name() + " @ " + fileline()->ascii(); if (m_doInlineWyp) str += "\nDoInline: "s + m_doInlineWyp; str += "\n"; return str; } }; InlineModModuleVertex* InlineModGraph::getInlineModModuleVertexp(AstNodeModule* modp) { if (!modp->user4p()) modp->user4p(new InlineModModuleVertex{*this, modp}); return modp->user4u().to(); } InlineModCellVertex* InlineModGraph::getInlineModCellVertexp(AstCell* cellp) { if (!cellp->user4p()) cellp->user4p(new InlineModCellVertex{*this, cellp}); return cellp->user4u().to(); } void InlineModGraph::addEdge(InlineModModuleVertex& parent, InlineModCellVertex& cell) { UASSERT_OBJ(cell.inEmpty(), &cell, "Cell should have at most one incoming edge"); new V3GraphEdge{this, &parent, &cell, 1, /* cutable: */ false}; } void InlineModGraph::addEdge(InlineModCellVertex& cell, InlineModModuleVertex& submodule) { UASSERT_OBJ(cell.outEmpty(), &cell, "Cell should have at most one outgoing edge"); new V3GraphEdge{this, &cell, &submodule, 1, /* cutable: */ false}; } void InlineModModuleVertex::setFlatten() { for (V3GraphEdge& edge : outEdges()) { InlineModCellVertex& cVtx = *edge.top()->as(); if (cVtx.flatten()) continue; cVtx.setFlatten(); InlineModModuleVertex& iVtx = cVtx.instanceOf(); if (!iVtx.noInlineHard() && !iVtx.noInlineSoft()) cVtx.setDoInline("flatten parent"); iVtx.setFlatten(); } } //###################################################################### // Visitor that builds the bipartite module instantiation graph class InlineModGraphBuilder final : public VNVisitor { // STATE std::unique_ptr m_graphp{new InlineModGraph}; // The graph being built InlineModModuleVertex* m_modVtxp = nullptr; // Vertex of module currently being iterated // VISITORS void visit(AstNodeModule* nodep) override { if (nodep == v3Global.rootp()->constPoolp()->modp()) return; // Ignore const pool module UASSERT_OBJ(!m_modVtxp, nodep, "Unsupported: Nested modules"); // Create the module vertex InlineModModuleVertex* const vtxp = m_graphp->getInlineModModuleVertexp(nodep); // Check if the module itself is not inlineable // Inlining an interface means we no longer have a cell handle to resolve to. // If inlining moves post-scope this can perhaps be relaxed. if (VN_IS(nodep, Iface)) vtxp->setNoInlineHard("Interface"); // Never inline packages - TODO: conceptually fine, but why not? if (VN_IS(nodep, Package)) vtxp->setNoInlineHard("Package"); // A --lib-create library stub instance that needs tracing must not be // inlined, so we still know it is a lib stub in V3TraceDecl (see #7001) if (nodep->verilatorLib() && v3Global.opt.trace()) { vtxp->setNoInlineHard("verilatorLib with --trace"); } // Don't inline public modules by default if (nodep->modPublic()) vtxp->setNoInlineSoft("Public module"); // Iterate children VL_RESTORER(m_modVtxp); m_modVtxp = vtxp; iterateChildrenConst(nodep); } void visit(AstClass* nodep) override { // TODO allow inlining of modules that contain classes if (m_modVtxp) m_modVtxp->setNoInlineHard("Contains class"); iterateChildrenConst(nodep); // TODO: this is only needed or FTaskRef cleanup } // Cells instantiate modules void visit(AstCell* nodep) override { UASSERT_OBJ(m_modVtxp, nodep, "Cell should be under a module"); // Create the cell vertex InlineModCellVertex* const vtxp = m_graphp->getInlineModCellVertexp(nodep); // Add containing-module/instantiated-module edges m_graphp->addEdge(*m_modVtxp, *vtxp); m_graphp->addEdge(*vtxp, *m_graphp->getInlineModModuleVertexp(nodep->modp())); // Iterate children iterateChildrenConst(nodep); } void visit(AstPragma* nodep) override { if (nodep->pragType() == VPragmaType::INLINE_MODULE) { if (!m_modVtxp) { nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE } else { m_modVtxp->setShouldInline("Pragma INLINE_MODULE"); } VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); return; } if (nodep->pragType() == VPragmaType::NO_INLINE_MODULE) { if (!m_modVtxp) { nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE } else { m_modVtxp->setNoInlineSoft("Pragma NO_INLINE_MODULE"); } VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); return; } iterateChildrenConst(nodep); } // TODO: Bit nasty to do this here, but historically present, and still necessary void visit(AstNodeFTaskRef* nodep) override { if (m_modVtxp) m_modVtxp->sizeInc(); // Remove link. V3LinkDot will reestablish it after inlining. // MethodCalls not currently supported by inliner, so keep linked if (!nodep->classOrPackagep() && !VN_IS(nodep, MethodCall)) { nodep->taskp(nullptr); VIsCached::clearCacheTree(); } iterateChildrenConst(nodep); } // Base node void visit(AstNode* nodep) override { if (m_modVtxp) m_modVtxp->sizeInc(); iterateChildrenConst(nodep); } // CONSTRUCTORS explicit InlineModGraphBuilder(AstNetlist* nodep) { // Build the module instantiation graph iterateConst(nodep); // Order vertices (any topological order is fine), can't be cyclic at this point m_graphp->order(); // Check that the first vertex is the top level module (everything, including packages, // have a corresponding AstCell under the top level at this point). UASSERT_OBJ(m_graphp->vertices().frontp()->as()->modp()->isTop(), nodep, "First vertex should be top level module"); #ifdef VL_DEBUG for (const V3GraphVertex& vtx : m_graphp->vertices()) { // First vertex is the top levelmodule, we checked above if (&vtx == m_graphp->vertices().frontp()) continue; // Otherwise it should have instantiations UASSERT_OBJ(!vtx.inEmpty(), &vtx, "Should have edges from root"); } #endif } ~InlineModGraphBuilder() override = default; public: static std::unique_ptr apply(AstNetlist* nodep) { return std::move(InlineModGraphBuilder{nodep}.m_graphp); } }; //###################################################################### // After cell is cloned, relink the new module's contents class InlineRelinkVisitor final : public VNVisitor { // NODE STATE // Input: // See InlineVisitor // STATE std::unordered_set m_renamedInterfaces; // Name of renamed interface variables std::unordered_set m_priorInlinedCells; // Cells previously inlined into the module being inlined here. // Used to recognize VarXRefs whose inlinedDots was stamped by a // prior V3Inline pass (vs. by V3Begin generate-block unrolling). std::unordered_set m_pinSubstitutedXRefs; // VarXRefs created by pin substitution in this relink pass. // Their dotted/inlinedDots already represent the parent's scope // and must not be rewritten on the immediate visit. InlineModGraph& m_graph; // The instance graph AstNodeModule* const m_modp; // The module we are inlining into // The vertex of the module we are inlining into, for updating the graph InlineModModuleVertex* const m_mVtxp = m_graph.getInlineModModuleVertexp(m_modp); const AstCell* const m_cellp; // The cell being inlined size_t m_nPlaceholders = 0; // Unique identifier sequence number for placeholder variables // VISITORS void visit(AstCellInline* nodep) override { // Inlined cell under the inline cell, need to move to avoid conflicts nodep->unlinkFrBack(); m_modp->addInlinesp(nodep); // Rename nodep->name(m_cellp->name() + "__DOT__" + nodep->name()); UINFO(6, " Inline " << nodep); // Do CellInlines under this, but don't move them iterateChildren(nodep); } void visit(AstCell* nodep) override { // Cell under the inline cell, need to rename to avoid conflicts nodep->name(m_cellp->name() + "__DOT__" + nodep->name()); // Need to update graph nodep->user4p(nullptr); // clone copied user4p, reset to make new vertex InlineModCellVertex* const vtxp = m_graph.getInlineModCellVertexp(nodep); m_graph.addEdge(*m_mVtxp, *vtxp); m_graph.addEdge(*vtxp, *m_graph.getInlineModModuleVertexp(nodep->modp())); iterateChildren(nodep); } void visit(AstClass* nodep) override { nodep->name(m_cellp->name() + "__DOT__" + nodep->name()); iterateChildren(nodep); } void visit(AstModule* nodep) override { m_renamedInterfaces.clear(); iterateChildren(nodep); } void visit(AstVar* nodep) override { // Iterate won't hit AstIfaceRefDType directly as it is no longer underneath the module if (AstIfaceRefDType* const ifacerefp = VN_CAST(nodep->dtypep(), IfaceRefDType)) { m_renamedInterfaces.insert(nodep->name()); // Each inlined cell that contain an interface variable need to // copy the IfaceRefDType and point it to the newly cloned // interface cell. AstIfaceRefDType* const newdp = ifacerefp->cloneTree(false); nodep->dtypep(newdp); ifacerefp->addNextHere(newdp); // Relink to point to newly cloned cell if (newdp->cellp()) { if (AstCell* const newcellp = VN_CAST(newdp->cellp()->user3p(), Cell)) { newdp->cellp(newcellp); newdp->cellName(newcellp->name()); // Tag the old ifacerefp to ensure it leaves no stale // reference to the inlined cell. newdp->user1(false); ifacerefp->user1(true); } } } // Variable under the inline cell, need to rename to avoid conflicts // Also clear I/O bits, as it is now local. const string name = m_cellp->name() + "__DOT__" + nodep->name(); if (!nodep->isFuncLocal() && !nodep->isClassMember()) nodep->inlineAttrReset(name); if (!m_cellp->isTrace()) nodep->trace(false); UINFOTREE(9, nodep, "", "varchanged"); } void visit(AstNodeFTask* nodep) override { // Function under the inline cell, need to rename to avoid conflicts nodep->name(m_cellp->name() + "__DOT__" + nodep->name()); iterateChildren(nodep); } void visit(AstTypedef* nodep) override { // Typedef under the inline cell, need to rename to avoid conflicts nodep->name(m_cellp->name() + "__DOT__" + nodep->name()); iterateChildren(nodep); } void visit(AstAlias* nodep) override { // Don't replace port variable in the alias } void visit(AstVarRef* nodep) override { // If the target port is being inlined, replace reference with the // connected expression (a Const, VarRef, or VarXRef). AstNode* const pinExpr = nodep->varp()->user2p(); if (!pinExpr) return; // If it's a constant, inline it if (AstConst* const constp = VN_CAST(pinExpr, Const)) { // You might think we would not try to substitute a constant for // a written variable, but we might need to do this if for example // there is an assignment to an input port, and that input port // is tied to a constant on the cell we are inlining. This does // generate an ASSIGNIN warning, but that can be downgraded to // a warning. (Also assigning to an input can has valid uses if // e.g. done via a hierarchical reference from outside to an input // unconnected on the instance, so we don't want ASSIGNIN fatal.) // Same applies when there is a static initialzier for an input. // To avoid having to special case malformed assignment, or worse // yet emiting code like 0 = 0, we instead substitute a placeholder // variable that will later be pruned (it will otherwise be unreferenced). if (!nodep->access().isReadOnly()) { AstVar* const varp = nodep->varp(); const std::string name = m_cellp->name() + "__vInlPlaceholder_" + std::to_string(++m_nPlaceholders); AstVar* const holdep = new AstVar{varp->fileline(), VVarType::VAR, name, varp}; m_modp->addStmtsp(holdep); AstVarRef* const newp = new AstVarRef{nodep->fileline(), holdep, nodep->access()}; nodep->replaceWith(newp); } else { nodep->replaceWith(constp->cloneTree(false)); } VL_DO_DANGLING(nodep->deleteTree(), nodep); return; } // Handle VarRef: simple retarget if (const AstVarRef* const vrefp = VN_CAST(pinExpr, VarRef)) { nodep->varp(vrefp->varp()); nodep->classOrPackagep(vrefp->classOrPackagep()); return; } // Handle VarXRef: replace VarRef with VarXRef (e.g., nested interface port) const AstVarXRef* const xrefp = VN_AS(pinExpr, VarXRef); AstVarXRef* const newp = new AstVarXRef{nodep->fileline(), xrefp->name(), xrefp->dotted(), nodep->access()}; newp->varp(xrefp->varp()); // The pin expression came from m_modp (the parent we are inlining into), so its // dotted/inlinedDots already describe a path in m_modp's scope. Record this xref // so visit(AstVarXRef) leaves it alone on the immediate visit; later inline // passes will prepend their cell names normally. newp->inlinedDots(xrefp->inlinedDots()); m_pinSubstitutedXRefs.insert(newp); nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } void visit(AstVarXRef* nodep) override { // VarXRefs just created by pin substitution in this pass already describe a path // in m_modp's scope (the parent we are inlining into). Leave them untouched on // this immediate visit; subsequent inline passes will prepend their cell names. if (m_pinSubstitutedXRefs.erase(nodep)) { iterateChildren(nodep); return; } // Track what scope it was originally under so V3LinkDot can resolve it const string origInlinedDots = nodep->inlinedDots(); nodep->inlinedDots(VString::dot(m_cellp->name(), ".", origInlinedDots)); // If origInlinedDots starts with the name of a previously-inlined cell, this // VarXRef came from that cell's body and its dotted refers to that child's // local scope; renaming it against m_renamedInterfaces would wrongly alias it // to a coincidentally-named var in the current module (#5120). VarXRefs whose // inlinedDots was stamped by V3Begin generate-block unrolling are unaffected, // since V3Begin's CellInlines have origModName "__BEGIN__" and don't appear in // m_priorInlinedCells. const string::size_type firstDot = origInlinedDots.find('.'); const string firstSeg = firstDot == string::npos ? origInlinedDots : origInlinedDots.substr(0, firstDot); const bool fromPriorInline = m_priorInlinedCells.count(firstSeg); for (string tryname = nodep->dotted(); true;) { if (m_renamedInterfaces.count(tryname)) { // matchIsRenamed: the matched name itself was created by a prior V3Inline // rename (contains "__DOT__"). When true, we are following the chain of // renames for the same var across nested inlines, so apply the rename // even if the VarXRef came from a prior-inlined child. const bool matchIsRenamed = tryname.find("__DOT__") != string::npos; if (!fromPriorInline || matchIsRenamed) { nodep->dotted(m_cellp->name() + "__DOT__" + nodep->dotted()); } break; } // If foo.bar, and foo is an interface, then need to search again for foo const string::size_type pos = tryname.rfind('.'); if (pos == string::npos || pos == 0) { break; } else { tryname.resize(pos); } } iterateChildren(nodep); } void visit(AstNodeFTaskRef* nodep) override { // Track what scope it was originally under so V3LinkDot can resolve it nodep->inlinedDots(VString::dot(m_cellp->name(), ".", nodep->inlinedDots())); if (m_renamedInterfaces.count(nodep->dotted())) { nodep->dotted(m_cellp->name() + "__DOT__" + nodep->dotted()); } UINFO(8, " " << nodep); iterateChildren(nodep); } // Not needed, as V3LinkDot doesn't care about typedefs // void visit(AstRefDType* nodep) override {} void visit(AstScopeName* nodep) override { // If there's a %m in the display text, we add a special node that will contain the name() // Similar code in V3Begin // To keep correct visual order, must add before exising nodep->scopeAttr("__DOT__" + m_cellp->name() + nodep->scopeAttr()); nodep->scopeEntr("__DOT__" + m_cellp->name() + nodep->scopeEntr()); iterateChildren(nodep); } void visit(AstNodeCoverDecl* nodep) override { // Fix path in coverage statements nodep->hier(VString::dot(m_cellp->prettyName(), ".", nodep->hier())); iterateChildren(nodep); } void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS InlineRelinkVisitor(AstNodeModule* cloneModp, AstNodeModule* oldModp, AstCell* cellp, InlineModGraph& graph) : m_graph{graph} , m_modp{oldModp} , m_cellp{cellp} { // CellInlines added by V3Begin for generate/named blocks have origModName // "__BEGIN__"; only those added by prior V3Inline passes carry a real module // name. Track the latter so visit(AstVarXRef) can distinguish VarXRefs // originating from previously-inlined children. for (AstNode* nodep = cloneModp->inlinesp(); nodep; nodep = nodep->nextp()) { const AstCellInline* const cip = VN_CAST(nodep, CellInline); if (cip && cip->origModName() != "__BEGIN__") { m_priorInlinedCells.insert(cip->name()); } } iterate(cloneModp); } ~InlineRelinkVisitor() override = default; }; //###################################################################### // Module inliner namespace ModuleInliner { // A port variable in an inlined module can be connected 2 ways. // Either add a continuous assignment between the pin expression from // the instance and the port variable, or simply inline the pin expression // in place of the port variable. We will prefer to do the later whenever // possible (and sometimes required). When inlining, we need to create an // alias for the inlined variable, in order to resovle hierarchical references // against it later in V3Scope (and also for tracing, which is inserted //later). Returns ture iff the given port variable should be inlined, // and false if a continuous assignment should be used. bool inlinePort(const AstVar* nodep) { // Interface references are always inlined if (nodep->isIfaceRef()) return true; // Ref ports must be always inlined if (nodep->direction() == VDirection::REF) return true; // Forced signals must not be inlined. The port signal can be // forced separately from the connected signals. if (nodep->isForced()) return false; // Note: For singls marked 'public' (and not 'public_flat') inlining // of their containing modules is disabled so they wont reach here. // TODO: For now, writable public signals inside the cell cannot be // eliminated as they are entered into the VerilatedScope, and // changes would not propagate to it when assigned. (The alias created // for them ensures they would be read correctly, but would not // propagate any changes.) This can be removed when the VerialtedScope // construction in V3EmitCSyms understands aliases. if (nodep->isSigUserRWPublic()) return false; // Otherwise we can repalce the variable return true; } // Connect the given port 'nodep' (being inlined into 'modp') to the given // expression (from the Cell Pin) void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) { UINFO(6, "Connecting " << pinExprp); UINFO(6, " to " << nodep); // Decide whether to inline the port variable or use continuous assignments const bool inlineIt = inlinePort(nodep); // If we deccided to inline it, record the expression to substitute this variable with if (inlineIt) nodep->user2p(pinExprp); FileLine* const flp = nodep->fileline(); // Helper to creates an AstVarRef reference to the port variable const auto portRef = [&](VAccess access) { return new AstVarRef{flp, nodep, access}; }; // If the connected expression is a constant, add an assignment to set // the port variable. The constant can still be inlined, in which case // this is needed for tracing the inlined port variable. if (AstConst* const pinp = VN_CAST(pinExprp, Const)) { AstAssignW* const ap = new AstAssignW{flp, portRef(VAccess::WRITE), pinp->cloneTree(false)}; modp->addStmtsp(new AstAlways{ap}); return; } // Otherwise it must be a variable reference due to having called pinReconnectSimple const AstNodeVarRef* const pinRefp = VN_AS(pinExprp, NodeVarRef); const auto pinRefAsVarRef = [&](VAccess access) -> AstVarRef* { const AstVarRef* const vrp = VN_AS(pinRefp, VarRef); AstVarRef* const newp = new AstVarRef{vrp->fileline(), vrp->varp(), access}; newp->classOrPackagep(vrp->classOrPackagep()); return newp; }; const auto pinRefAsExpr = [&](VAccess access) -> AstNodeExpr* { if (const AstVarRef* const vrp = VN_CAST(pinRefp, VarRef)) { AstVarRef* const newp = new AstVarRef{vrp->fileline(), vrp->varp(), access}; newp->classOrPackagep(vrp->classOrPackagep()); return newp; } else { const AstVarXRef* const xrp = VN_AS(pinRefp, VarXRef); AstVarXRef* const newp = new AstVarXRef{xrp->fileline(), xrp->name(), xrp->dotted(), access}; newp->varp(xrp->varp()); newp->inlinedDots(xrp->inlinedDots()); return newp; } }; // If it is being inlined, create the alias for it if (inlineIt) { UINFO(6, "Inlining port variable: " << nodep); if (nodep->isIfaceRef()) { modp->addStmtsp( new AstAliasScope{flp, portRef(VAccess::WRITE), pinRefAsExpr(VAccess::READ)}); } else { AstVarRef* const aliasArgsp = portRef(VAccess::WRITE); aliasArgsp->addNext(pinRefAsVarRef(VAccess::READ)); modp->addStmtsp(new AstAlias{flp, aliasArgsp}); } // They will become the same variable, so propagate file-line and variable attributes pinRefp->varp()->fileline()->modifyStateInherit(flp); flp->modifyStateInherit(pinRefp->varp()->fileline()); pinRefp->varp()->propagateAttrFrom(nodep); nodep->propagateAttrFrom(pinRefp->varp()); return; } // Otherwise create the continuous assignment between the port var and the pin expression UINFO(6, "Not inlining port variable: " << nodep); if (nodep->direction() == VDirection::INPUT) { AstAssignW* const ap = new AstAssignW{flp, portRef(VAccess::WRITE), pinRefAsExpr(VAccess::READ)}; modp->addStmtsp(new AstAlways{ap}); } else if (nodep->direction() == VDirection::OUTPUT) { AstAssignW* const ap = new AstAssignW{flp, pinRefAsExpr(VAccess::WRITE), portRef(VAccess::READ)}; modp->addStmtsp(new AstAlways{ap}); } else { pinExprp->v3fatalSrc("V3Tristate left INOUT port"); } } // Inline 'cellp' into 'modp'. 'last' indicatest this is tha last instance of the inlined module void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last, InlineModGraph& graph) { UINFO(5, " Inline Cell " << cellp); UINFO(5, " into Module " << modp); const VNUser2InUse user2InUse; // Important: If this is the last cell, then don't clone the instantiated module but // inline the original directly. While this requires some special casing, doing so // saves us having to temporarily clone the module for the last cell, which // significantly reduces Verilator memory usage. This is especially true as often the // top few levels of the hierarchy are singleton wrapper modules, which we always // inline. In this case this special casing saves us from having to clone essentially // the entire netlist, which would in effect double Verilator memory consumption, or // worse if we put off deleting the inlined modules until the end. Not having to clone // large trees also improves speed. // The module we will yank the contents out of and put into 'modp' AstNodeModule* const inlinedp = last ? cellp->modp()->unlinkFrBack() // : cellp->modp()->cloneTree(false); // Compute map from original port variables and cells to their clones for (AstNode *ap = cellp->modp()->stmtsp(), *bp = inlinedp->stmtsp(); ap || bp; ap = ap->nextp(), bp = bp->nextp()) { UASSERT_OBJ(ap && bp, ap ? ap : bp, "Clone has different number of children"); // We only care about AstVar and AstCell, but faster to just set them all ap->user3p(bp); } // Create data for resolving hierarchical references later. modp->addInlinesp( new AstCellInline{cellp->fileline(), cellp->name(), cellp->modp()->origName()}); // Connect the pins on the instance for (AstPin* pinp = cellp->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) { if (!pinp->exprp()) continue; UINFO(6, "Connecting port " << pinp->modVarp()); UINFO(6, " of instance " << cellp); // Make sure the conneccted pin expression is always a VarRef or a Const V3Inst::pinReconnectSimple(pinp, cellp, false); // Warn V3Inst::checkOutputShort(pinp); if (!pinp->exprp()) continue; // Pick up the old and new port variables signal (new is the same on last instance) const AstVar* const oldModVarp = pinp->modVarp(); AstVar* const newModVarp = VN_AS(oldModVarp->user3p(), Var); // Pick up the connected expression (a VarRef or Const due to pinReconnectSimple) AstNodeExpr* const pinExprp = VN_AS(pinp->exprp(), NodeExpr); // Connect up the port connectPort(modp, newModVarp, pinExprp); } // Cleanup var names, etc, to not conflict, relink replaced variables, adjust graph { InlineRelinkVisitor{inlinedp, modp, cellp, graph}; } // Move statements from the inlined module into the module we are inlining into if (AstNode* const stmtsp = inlinedp->stmtsp()) { modp->addStmtsp(stmtsp->unlinkFrBackWithNext()); } // Delete the empty shell of the inlined module VL_DO_DANGLING(inlinedp->deleteTree(), inlinedp); // Remove the cell we just inlined VL_DO_DANGLING(cellp->unlinkFrBack()->deleteTree(), cellp); } // Apply all inlining decisions void process(AstNetlist* netlistp, InlineModGraph& graph) { // NODE STATE // Cleared entire netlist // AstIfaceRefDType::user1() // Whether the cell pointed to by this // // AstIfaceRefDType has been inlined // AstCell::user3p() // AstCell*, the clone // AstVar::user3p() // AstVar*, the clone // Cleared each cell // AstVar::user2p() // AstVarRef*/AstConst* This port is connected to (AstPin::expr()) const VNUser1InUse user1InUse; const VNUser3InUse user3InUse; // Number of inlined instances, for statistics VDouble0 m_nInlined; // Gather all cells that need to be inlined (this is in topological order) std::vector cVtxps; for (V3GraphVertex& vtx : graph.vertices()) { InlineModCellVertex* const cVtxp = vtx.cast(); if (!cVtxp) continue; if (!cVtxp->doInline()) continue; cVtxps.push_back(cVtxp); } // Inline cells bottom up (leaves into roots) for (InlineModCellVertex* const cVtxp : vlstd::reverse_view(cVtxps)) { // Pick up parts before deleting InlineModModuleVertex& mVtx = cVtxp->instanceIn(); InlineModModuleVertex* const iVtxp = &cVtxp->instanceOf(); AstCell* const cellp = cVtxp->cellp(); const bool last = iVtxp->inSize1(); UASSERT_OBJ(!iVtxp->noInlineHard(), cellp, "Should not be inlining if not possible"); // Update ++m_nInlined; mVtx.sizeInc(iVtxp->size()); // For debug dump only // Delete the cell we are inlining VL_DO_DANGLING(cVtxp->unlinkDelete(&graph), cVtxp); // Delete the module we are inlining if this is the last instance if (last) { while (!iVtxp->outEmpty()) { InlineModCellVertex* const tVtxp = iVtxp->outEdges().frontp()->top()->as(); // Bottom up ordering ensures this UASSERT_OBJ(!tVtxp->doInline(), tVtxp, "Should have been inlined"); VL_DO_DANGLING(tVtxp->unlinkDelete(&graph), tVtxp); } VL_DO_DANGLING(iVtxp->unlinkDelete(&graph), iVtxp); } // Do it inlineCell(mVtx.modp(), cellp, last, graph); if (dumpGraphLevel() >= 9) graph.dumpDotFilePrefixed("inlinemod-cell"); } V3Stats::addStat("Optimizations, Inlined instances", m_nInlined); // Clean up AstIfaceRefDType references // If the cell has been removed let's make sure we don't leave a // reference to it. This dtype may still be in use by the // AstAliasScope created earlier but that'll get cleared up later netlistp->typeTablep()->foreach([](AstIfaceRefDType* nodep) { if (nodep->user1()) nodep->cellp(nullptr); }); } } //namespace ModuleInliner //###################################################################### // V3Inline class functions void V3Inline::inlineAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); // Build the bipartite module instantiation graph std::unique_ptr graphp = InlineModGraphBuilder::apply(nodep); if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-graph"); // Decide which instances to inline const size_t designSize = graphp->vertices().frontp()->as()->flattenedSize(); for (V3GraphVertex& vtx : graphp->vertices()) { if (InlineModModuleVertex* const mVtxp = vtx.cast()) { // If this module is less than 10% of the design, flatten this module if (mVtxp->flattenedSize() * 10 < designSize) mVtxp->setFlatten(); // Don't inline if can't inline if (mVtxp->noInlineHard()) continue; // Don't inline if soft off if (mVtxp->noInlineSoft()) continue; // If all instances of this module combined are less than 20% of the design, inline all size_t totalSize = mVtxp->flattenedSize() * mVtxp->instanceCount(); if (totalSize * 5 < designSize) { for (V3GraphEdge& edge : mVtxp->inEdges()) { InlineModCellVertex* const cVtxp = edge.fromp()->as(); cVtxp->setDoInline("< 20% of design"); } } // No more decisions based on module vertex continue; } // The instantiation InlineModCellVertex& cVtx = *vtx.as(); // The module instantiated by this cell InlineModModuleVertex& mVtx = cVtx.instanceOf(); // Don't inline if can't inline, duh! if (mVtx.noInlineHard()) continue; // If it should be inlined, inlined it if (mVtx.shouldInline()) cVtx.setDoInline("should inline"); // If --flatten, inline it if (v3Global.opt.flatten()) cVtx.setDoInline("--flatten"); // Don't inline for other reasons if soft off if (mVtx.noInlineSoft()) continue; // If instatiated in exactly one static site, inline it if (mVtx.inSize1()) cVtx.setDoInline("Single static instance"); // If small, inline it if (mVtx.size() < INLINE_MODS_SMALLER) cVtx.setDoInline("Small"); // If inlineMult is 0, inline it if (v3Global.opt.inlineMult() < 1) cVtx.setDoInline("inlineMult < 1"); // If it would yield less than the given number of ops, inline it const size_t inlinedSize = mVtx.inEdges().size() * mVtx.size(); const size_t limit = v3Global.opt.inlineMult(); if (inlinedSize < limit) cVtx.setDoInline("inlinedSize < inlineMult"); } if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-decision"); // Inline the modles we decided to inline ModuleInliner::process(nodep, *graphp); if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-inlined"); V3Global::dumpCheckGlobalTree("inline", 0, dumpTreeEitherLevel() >= 3); }