2012-04-12 21:08:20 -04:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
2006-08-26 11:35:28 +00:00
|
|
|
//*************************************************************************
|
2026-09-23 19:15:46 +02:00
|
|
|
// DESCRIPTION: Verilator: Module inlining
|
2006-08-26 11:35:28 +00:00
|
|
|
//
|
2019-11-07 22:33:59 -05:00
|
|
|
// Code available from: https://verilator.org
|
2006-08-26 11:35:28 +00:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2026-01-26 20:24:34 -05:00
|
|
|
// 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
|
2020-03-21 11:24:24 -04:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
2006-08-26 11:35:28 +00:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// V3Inline's Transformations:
|
2008-06-09 21:25:10 -04:00
|
|
|
//
|
2006-08-26 11:35:28 +00:00
|
|
|
// Each module:
|
2019-05-19 16:13:13 -04:00
|
|
|
// Look for CELL... PRAGMA INLINE_MODULE
|
|
|
|
|
// Replicate the cell's module
|
|
|
|
|
// Rename vars to include cell name
|
2026-09-23 19:15:46 +02:00
|
|
|
// Insert cell's module declarations into the upper module
|
|
|
|
|
// Merge each SCOPE of the cell's module into the SCOPE above it
|
|
|
|
|
// Reparent and rename the SCOPEs below the inlined instance
|
2006-08-26 11:35:28 +00:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
2019-10-04 20:17:11 -04:00
|
|
|
|
2023-10-18 06:37:46 -04:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
2006-08-26 11:35:28 +00:00
|
|
|
#include "V3Inline.h"
|
2022-08-05 10:56:57 +01:00
|
|
|
|
|
|
|
|
#include "V3AstUserAllocator.h"
|
2026-06-25 14:14:15 +01:00
|
|
|
#include "V3Graph.h"
|
2006-08-26 11:35:28 +00:00
|
|
|
#include "V3Stats.h"
|
|
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
#include <unordered_map>
|
2021-12-22 11:41:29 +00:00
|
|
|
#include <vector>
|
2018-10-14 13:43:24 -04:00
|
|
|
|
2022-09-18 20:53:42 +01:00
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2006-08-26 11:35:28 +00:00
|
|
|
// CONFIG
|
2019-05-19 16:13:13 -04:00
|
|
|
static const int INLINE_MODS_SMALLER = 100; // If a mod is < this # nodes, can always inline it
|
2006-08-26 11:35:28 +00:00
|
|
|
|
|
|
|
|
//######################################################################
|
2026-06-25 14:14:15 +01:00
|
|
|
// Bipartite module instantiation graph containing module and cell vertices
|
2021-12-22 11:41:29 +00:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
class InlineModModuleVertex;
|
|
|
|
|
class InlineModCellVertex;
|
2021-12-22 11:41:29 +00:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
class InlineModGraph final : public V3Graph {
|
|
|
|
|
// NODE STATE
|
|
|
|
|
// AstNodeModule::user4p() -> InlineModModuleVertex*, the module vertex
|
|
|
|
|
// AstCell::user4p() -> InlineModCellVertex*, the cell vertex
|
2021-12-22 11:41:29 +00:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
VNUser4InUse m_user4InUse;
|
2021-12-22 11:41:29 +00:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
public:
|
|
|
|
|
InlineModGraph()
|
|
|
|
|
: V3Graph{} {}
|
|
|
|
|
~InlineModGraph() override = default;
|
2006-08-26 11:35:28 +00:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
// METHODS
|
|
|
|
|
InlineModModuleVertex* getInlineModModuleVertexp(AstNodeModule* modp);
|
|
|
|
|
InlineModCellVertex* getInlineModCellVertexp(AstCell* cellp);
|
|
|
|
|
void addEdge(InlineModModuleVertex& from, InlineModCellVertex& to);
|
|
|
|
|
void addEdge(InlineModCellVertex& from, InlineModModuleVertex& to);
|
2013-05-26 11:17:42 -04:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
// debug
|
|
|
|
|
std::string dotRankDir() const override { return "LR"; }
|
|
|
|
|
};
|
2017-10-01 18:00:27 -04:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
class InlineModEitherVertex VL_NOT_FINAL : public V3GraphVertex {
|
|
|
|
|
VL_RTTI_IMPL(InlineModEitherVertex, V3GraphVertex)
|
|
|
|
|
protected:
|
|
|
|
|
explicit InlineModEitherVertex(InlineModGraph& graph)
|
|
|
|
|
: V3GraphVertex{&graph} {}
|
|
|
|
|
};
|
2017-10-01 18:00:27 -04:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
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
|
2017-10-01 18:00:27 -04:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
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<InlineModModuleVertex>();
|
|
|
|
|
m_flattenedSize += mVtxp->flattenedSize();
|
|
|
|
|
}
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
}
|
|
|
|
|
return m_flattenedSize;
|
|
|
|
|
}
|
|
|
|
|
// Total number of instances of this module in the whole hierarchy of the design
|
2026-09-23 19:15:46 +02:00
|
|
|
// Note this is the same as the number of AstScopes the module has.
|
2026-06-25 14:14:15 +01:00
|
|
|
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<InlineModModuleVertex>();
|
|
|
|
|
m_instanceCount += mVtxp->instanceCount();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!m_instanceCount) {
|
|
|
|
|
UASSERT_OBJ(m_modp->isTop(), m_modp, "non-top level module should have instances");
|
|
|
|
|
m_instanceCount = 1;
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
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<InlineModModuleVertex>();
|
|
|
|
|
}
|
|
|
|
|
// 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<InlineModModuleVertex>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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;
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
InlineModModuleVertex* InlineModGraph::getInlineModModuleVertexp(AstNodeModule* modp) {
|
|
|
|
|
if (!modp->user4p()) modp->user4p(new InlineModModuleVertex{*this, modp});
|
|
|
|
|
return modp->user4u().to<InlineModModuleVertex*>();
|
|
|
|
|
}
|
|
|
|
|
InlineModCellVertex* InlineModGraph::getInlineModCellVertexp(AstCell* cellp) {
|
|
|
|
|
if (!cellp->user4p()) cellp->user4p(new InlineModCellVertex{*this, cellp});
|
|
|
|
|
return cellp->user4u().to<InlineModCellVertex*>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<InlineModCellVertex>();
|
|
|
|
|
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<InlineModGraph> m_graphp{new InlineModGraph}; // The graph being built
|
|
|
|
|
InlineModModuleVertex* m_modVtxp = nullptr; // Vertex of module currently being iterated
|
2026-09-23 19:15:46 +02:00
|
|
|
const AstScope* m_sizedScopep = nullptr; // The scope of current module measured for size
|
2006-08-26 11:35:28 +00:00
|
|
|
|
|
|
|
|
// VISITORS
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstNodeModule* nodep) override {
|
2026-06-25 14:14:15 +01:00
|
|
|
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
|
|
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// TODO: All references are resolved by now, but AstIfaceRefDType::cellp and
|
|
|
|
|
// the AstIntfRef records still name the interface instance, so keep it.
|
2026-06-25 14:14:15 +01:00
|
|
|
if (VN_IS(nodep, Iface)) vtxp->setNoInlineHard("Interface");
|
2026-09-27 14:25:41 +02:00
|
|
|
// Never inline the constant pool, even if packages become inlineable
|
|
|
|
|
if (nodep->isConstPool()) vtxp->setNoInlineHard("Constant pool");
|
2026-06-25 14:14:15 +01:00
|
|
|
// 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");
|
2026-02-13 01:54:03 +00:00
|
|
|
}
|
2017-10-01 18:00:27 -04:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
// Don't inline public modules by default
|
|
|
|
|
if (nodep->modPublic()) vtxp->setNoInlineSoft("Public module");
|
|
|
|
|
|
|
|
|
|
// Iterate children
|
|
|
|
|
VL_RESTORER(m_modVtxp);
|
2026-09-23 19:15:46 +02:00
|
|
|
VL_RESTORER(m_sizedScopep);
|
2026-06-25 14:14:15 +01:00
|
|
|
m_modVtxp = vtxp;
|
2026-09-23 19:15:46 +02:00
|
|
|
m_sizedScopep = nullptr;
|
2026-06-25 14:14:15 +01:00
|
|
|
iterateChildrenConst(nodep);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstClass* nodep) override {
|
2026-06-25 14:14:15 +01:00
|
|
|
// TODO allow inlining of modules that contain classes
|
|
|
|
|
if (m_modVtxp) m_modVtxp->setNoInlineHard("Contains class");
|
2026-09-23 19:15:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit(AstScope* nodep) override {
|
|
|
|
|
// Every instance of a module holds an identical copy of the module body under
|
|
|
|
|
// its own AstScope, so only measure the size of one of them.
|
|
|
|
|
if (!m_sizedScopep) m_sizedScopep = nodep;
|
|
|
|
|
if (m_sizedScopep != nodep) return;
|
|
|
|
|
if (m_modVtxp) m_modVtxp->sizeInc();
|
|
|
|
|
iterateChildrenConst(nodep);
|
2020-04-05 09:30:23 -04:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
|
|
|
|
|
// Cells instantiate modules
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstCell* nodep) override {
|
2026-06-25 14:14:15 +01:00
|
|
|
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);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstPragma* nodep) override {
|
2022-01-02 13:56:40 -05:00
|
|
|
if (nodep->pragType() == VPragmaType::INLINE_MODULE) {
|
2026-06-25 14:14:15 +01:00
|
|
|
if (!m_modVtxp) {
|
2019-06-30 22:37:03 -04:00
|
|
|
nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE
|
2026-06-25 14:14:15 +01:00
|
|
|
} else {
|
|
|
|
|
m_modVtxp->setShouldInline("Pragma INLINE_MODULE");
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2020-01-16 20:17:11 -05:00
|
|
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
2026-06-25 14:14:15 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nodep->pragType() == VPragmaType::NO_INLINE_MODULE) {
|
|
|
|
|
if (!m_modVtxp) {
|
2019-06-30 22:37:03 -04:00
|
|
|
nodep->v3error("Inline pragma not under a module"); // LCOV_EXCL_LINE
|
2026-06-25 14:14:15 +01:00
|
|
|
} else {
|
|
|
|
|
m_modVtxp->setNoInlineSoft("Pragma NO_INLINE_MODULE");
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2020-01-16 20:17:11 -05:00
|
|
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
2026-06-25 14:14:15 +01:00
|
|
|
return;
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
|
|
|
|
|
iterateChildrenConst(nodep);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
|
|
|
|
|
// Base node
|
2022-09-16 11:22:11 +01:00
|
|
|
void visit(AstNode* nodep) override {
|
2026-06-25 14:14:15 +01:00
|
|
|
if (m_modVtxp) m_modVtxp->sizeInc();
|
|
|
|
|
iterateChildrenConst(nodep);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
|
|
|
|
|
2019-09-12 07:22:22 -04:00
|
|
|
// CONSTRUCTORS
|
2026-06-25 14:14:15 +01:00
|
|
|
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<InlineModModuleVertex>()->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
|
2021-12-22 11:41:29 +00:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
~InlineModGraphBuilder() override = default;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
static std::unique_ptr<InlineModGraph> apply(AstNetlist* nodep) {
|
|
|
|
|
return std::move(InlineModGraphBuilder{nodep}.m_graphp);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2013-05-25 10:42:44 -04:00
|
|
|
//######################################################################
|
2026-09-23 19:15:46 +02:00
|
|
|
// Module inliner
|
2025-08-22 21:43:49 +01:00
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
namespace ModuleInliner {
|
2026-02-04 15:26:20 -06:00
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// The scopes instantiated directly under each scope (that is parent -> children links)
|
|
|
|
|
using ScopeChildren = std::unordered_map<const AstScope*, std::vector<AstScope*>>;
|
|
|
|
|
|
|
|
|
|
// Record downward links from parent scopes to their child scopes
|
|
|
|
|
void gatherScopes(const AstNodeModule* modp, ScopeChildren& children) {
|
|
|
|
|
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
|
|
|
if (AstScope* const scopep = VN_CAST(nodep, Scope)) {
|
|
|
|
|
// Note the top scope is held under the AstTopScope, so is not seen here
|
|
|
|
|
UASSERT_OBJ(scopep->aboveScopep(), scopep, "Instance scope should have a scope above");
|
|
|
|
|
children[scopep->aboveScopep()].push_back(scopep);
|
|
|
|
|
} else if (const AstNodeModule* const subModp = VN_CAST(nodep, NodeModule)) {
|
|
|
|
|
// An AstClass holds its scopes under itself
|
|
|
|
|
UASSERT_OBJ(VN_IS(subModp, Class), subModp, "Nested module should be a class");
|
|
|
|
|
gatherScopes(subModp, children);
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2013-05-25 10:42:44 -04:00
|
|
|
}
|
2026-09-23 19:15:46 +02:00
|
|
|
}
|
2013-05-25 10:42:44 -04:00
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// Rename the given scope, and all scopes below it, after the scope named by 'oldPrefix'
|
|
|
|
|
// (the original parent of 'scopep') has been inlined into the scope above it
|
|
|
|
|
void renameScopes(AstScope* scopep, const std::string& oldPrefix, const std::string& newPrefix,
|
|
|
|
|
const ScopeChildren& children) {
|
|
|
|
|
UASSERT_OBJ(VString::startsWith(scopep->name(), oldPrefix), scopep,
|
|
|
|
|
"Scope name should start with the name of the scope above it");
|
|
|
|
|
scopep->name(newPrefix + scopep->name().substr(oldPrefix.size()));
|
|
|
|
|
const auto it = children.find(scopep);
|
|
|
|
|
if (it == children.end()) return;
|
|
|
|
|
for (AstScope* const childp : it->second) {
|
|
|
|
|
renameScopes(childp, oldPrefix, newPrefix, children);
|
2013-05-25 10:42:44 -04:00
|
|
|
}
|
2026-09-23 19:15:46 +02:00
|
|
|
}
|
2013-05-25 10:42:44 -04:00
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// Merge the given scope (instance) of the inlined cell into the scope above it
|
|
|
|
|
void inlineScope(AstScope* scopep, AstCell* cellp, const std::string& prefix,
|
|
|
|
|
AstCellInline* newCellInlinep, ScopeChildren& children) {
|
|
|
|
|
AstScope* const parentScopep = scopep->aboveScopep();
|
|
|
|
|
UASSERT_OBJ(parentScopep, scopep, "Inlined scope should have a scope above");
|
|
|
|
|
UINFO(6, " Inline Scope " << scopep);
|
|
|
|
|
UINFO(6, " into " << parentScopep);
|
|
|
|
|
|
|
|
|
|
// Move the variables of the inlined scope into the scope above
|
|
|
|
|
for (AstVarScope *vscp = scopep->varsp(), *nextp; vscp; vscp = nextp) {
|
|
|
|
|
nextp = VN_AS(vscp->nextp(), VarScope);
|
|
|
|
|
// Note V3Scope attaches variables of non-virtual interface references to the
|
|
|
|
|
// scope of the interface instance, so only update if it is this scope
|
|
|
|
|
if (vscp->scopep() == scopep) vscp->scopep(parentScopep);
|
|
|
|
|
// If the module was cloned, point to the cloned variable
|
|
|
|
|
if (AstVar* const newVarp = VN_CAST(vscp->varp()->user3p(), Var)) {
|
|
|
|
|
vscp->varp(newVarp);
|
|
|
|
|
vscp->dtypeFrom(newVarp);
|
2026-05-15 12:52:26 +01:00
|
|
|
}
|
2026-09-23 19:15:46 +02:00
|
|
|
if (!cellp->isTrace()) vscp->trace(false);
|
|
|
|
|
parentScopep->addVarsp(vscp->unlinkFrBack());
|
2013-05-25 10:42:44 -04:00
|
|
|
}
|
2025-08-22 21:43:49 +01:00
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// Move the logic of the inlined scope into the scope above
|
|
|
|
|
for (AstNode *nodep = scopep->blocksp(), *nextp; nodep; nodep = nextp) {
|
|
|
|
|
nextp = nodep->nextp();
|
|
|
|
|
nodep->unlinkFrBack();
|
|
|
|
|
if (AstNodeFTask* const ftaskp = VN_CAST(nodep, NodeFTask)) {
|
|
|
|
|
ftaskp->name(prefix + ftaskp->name());
|
|
|
|
|
}
|
|
|
|
|
// If the module was cloned, point coverage increments to the cloned declarations
|
|
|
|
|
if (v3Global.opt.coverage()) {
|
|
|
|
|
nodep->foreach([&](AstCoverInc* incp) {
|
|
|
|
|
AstNodeCoverDecl* const declp = incp->declp();
|
|
|
|
|
if (declp->perInstance()) {
|
|
|
|
|
// Not cloned, fix up the path here, as only this scope refers to it
|
|
|
|
|
declp->hier(VString::dot(cellp->prettyName(), ".", declp->hier()));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (AstNodeCoverDecl* const newDeclp = VN_CAST(declp->user3p(), NodeCoverDecl)) {
|
|
|
|
|
incp->declp(newDeclp);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
parentScopep->addBlocksp(nodep);
|
2025-08-22 21:43:49 +01:00
|
|
|
}
|
2021-12-22 11:41:29 +00:00
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// Move the inline records of the inlined scope into the scope above
|
|
|
|
|
for (AstNode *nodep = scopep->inlinesp(), *nextp; nodep; nodep = nextp) {
|
|
|
|
|
nextp = nodep->nextp();
|
|
|
|
|
AstCellInlineScope* const cisp = VN_AS(nodep->unlinkFrBack(), CellInlineScope);
|
|
|
|
|
cisp->scopep(parentScopep);
|
|
|
|
|
// If the module was cloned, point to the cloned inline record
|
|
|
|
|
if (AstCellInline* const newCinlp = VN_CAST(cisp->cellp()->user3p(), CellInline)) {
|
|
|
|
|
cisp->cellp(newCinlp);
|
2026-02-04 15:26:20 -06:00
|
|
|
}
|
2026-09-23 19:15:46 +02:00
|
|
|
parentScopep->addInlinesp(cisp);
|
|
|
|
|
}
|
|
|
|
|
// ... and add one for the instance we are inlining now
|
|
|
|
|
if (v3Global.opt.vpi()) {
|
|
|
|
|
parentScopep->addInlinesp(
|
|
|
|
|
new AstCellInlineScope{cellp->fileline(), parentScopep, newCellInlinep});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reparent and rename the scopes instantiated under the inlined scope
|
|
|
|
|
const std::string oldPrefix = scopep->name() + ".";
|
|
|
|
|
const std::string newPrefix = scopep->name() + "__DOT__";
|
|
|
|
|
std::vector<AstScope*> childScopeps;
|
|
|
|
|
{
|
|
|
|
|
const auto it = children.find(scopep);
|
|
|
|
|
if (it != children.end()) {
|
|
|
|
|
childScopeps = std::move(it->second);
|
|
|
|
|
children.erase(it);
|
2021-12-22 11:41:29 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-09-23 19:15:46 +02:00
|
|
|
for (AstScope* const childScopep : childScopeps) {
|
|
|
|
|
// A class scope would hang off the scope of the module declaring it, but modules
|
|
|
|
|
// containing classes are never inlined
|
|
|
|
|
UASSERT_OBJ(!VN_IS(childScopep->modp(), Class), childScopep,
|
|
|
|
|
"Inlined scope should not contain a class scope");
|
|
|
|
|
if (AstCell* const newCellp = VN_CAST(childScopep->aboveCellp()->user3p(), Cell)) {
|
|
|
|
|
// If the module was cloned, point to the cloned cell
|
|
|
|
|
childScopep->aboveCellp(newCellp);
|
|
|
|
|
}
|
|
|
|
|
childScopep->aboveScopep(parentScopep);
|
|
|
|
|
renameScopes(childScopep, oldPrefix, newPrefix, children);
|
|
|
|
|
}
|
|
|
|
|
// Children of the inlined scope are now children of the scope above. Note this must
|
|
|
|
|
// come after the erase above, as inserting into 'children' can invalidate 'it'.
|
|
|
|
|
std::vector<AstScope*>& parentChildps = children[parentScopep];
|
|
|
|
|
parentChildps.erase(std::remove(parentChildps.begin(), parentChildps.end(), scopep),
|
|
|
|
|
parentChildps.end());
|
|
|
|
|
parentChildps.insert(parentChildps.end(), childScopeps.begin(), childScopeps.end());
|
|
|
|
|
|
|
|
|
|
UASSERT_OBJ(!scopep->varsp() && !scopep->blocksp() && !scopep->inlinesp(), scopep,
|
|
|
|
|
"Inlined scope should be empty");
|
2025-08-22 21:43:49 +01:00
|
|
|
}
|
2021-12-22 11:41:29 +00:00
|
|
|
|
2025-08-22 21:43:49 +01:00
|
|
|
// Inline 'cellp' into 'modp'. 'last' indicatest this is tha last instance of the inlined module
|
2026-09-23 19:15:46 +02:00
|
|
|
void inlineCell(AstNodeModule* modp, AstCell* cellp, bool last, InlineModGraph& graph,
|
|
|
|
|
ScopeChildren& children) {
|
2025-08-22 21:43:49 +01:00
|
|
|
UINFO(5, " Inline Cell " << cellp);
|
|
|
|
|
UINFO(5, " into Module " << modp);
|
|
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// NODE STATE
|
|
|
|
|
// AstNode::user3p() -> AstNode*. The clone of this module level declaration
|
|
|
|
|
const VNUser3InUse user3InUse;
|
|
|
|
|
|
|
|
|
|
VNDeleter deleter;
|
|
|
|
|
deleter.pushDeletep(cellp->unlinkFrBack());
|
|
|
|
|
|
|
|
|
|
AstNodeModule* const subModp = cellp->modp(); // The module being inlined
|
|
|
|
|
|
|
|
|
|
// Unlink all scopes of the instantiated module, so they are not cloned with it
|
|
|
|
|
std::vector<AstScope*> inlineScopeps; // Scopes under 'cellp'
|
|
|
|
|
std::vector<AstScope*> otherScopeps; // Scopes under some other instance
|
|
|
|
|
for (AstNode *nodep = subModp->stmtsp(), *nextp; nodep; nodep = nextp) {
|
|
|
|
|
nextp = nodep->nextp();
|
|
|
|
|
AstScope* const scopep = VN_CAST(nodep, Scope);
|
|
|
|
|
if (!scopep) continue;
|
|
|
|
|
scopep->unlinkFrBack();
|
|
|
|
|
if (scopep->aboveCellp() == cellp) {
|
|
|
|
|
inlineScopeps.push_back(scopep);
|
|
|
|
|
deleter.pushDeletep(scopep);
|
|
|
|
|
} else {
|
|
|
|
|
otherScopeps.push_back(scopep);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-22 21:43:49 +01:00
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// The module we will yank the declarations out of and put into 'modp'
|
|
|
|
|
AstNodeModule* inlinedp;
|
|
|
|
|
if (last) {
|
|
|
|
|
inlinedp = subModp->unlinkFrBack();
|
|
|
|
|
// This is the only instantiation, so all scopes are being inlined
|
|
|
|
|
UASSERT_OBJ(otherScopeps.empty(), cellp, "Last instance, but has other scopes");
|
|
|
|
|
} else {
|
|
|
|
|
inlinedp = subModp->cloneTree(false);
|
|
|
|
|
// Compute map from the original module items to their clones
|
|
|
|
|
for (AstNode *ap = subModp->inlinesp(), *bp = inlinedp->inlinesp(); ap || bp;
|
|
|
|
|
ap = ap->nextp(), bp = bp->nextp()) {
|
|
|
|
|
UASSERT_OBJ(ap && bp, ap ? ap : bp, "Clone has different number of children");
|
|
|
|
|
ap->user3p(bp);
|
|
|
|
|
}
|
|
|
|
|
for (AstNode *ap = subModp->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");
|
|
|
|
|
ap->user3p(bp);
|
|
|
|
|
}
|
|
|
|
|
// Per instance coverage declarations must not be duplicated, drop the clones
|
|
|
|
|
if (v3Global.opt.coverageFsm()) {
|
|
|
|
|
for (AstNode *nodep = inlinedp->stmtsp(), *nextp; nodep; nodep = nextp) {
|
|
|
|
|
nextp = nodep->nextp();
|
|
|
|
|
const AstNodeCoverDecl* const declp = VN_CAST(nodep, NodeCoverDecl);
|
|
|
|
|
if (declp && declp->perInstance()) {
|
|
|
|
|
VL_DO_DANGLING(deleter.pushDeletep(nodep->unlinkFrBack()), nodep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Put back the scopes of the instances we are not inlining this time
|
|
|
|
|
for (AstScope* const scopep : otherScopeps) subModp->addStmtsp(scopep);
|
|
|
|
|
}
|
|
|
|
|
deleter.pushDeletep(inlinedp);
|
|
|
|
|
|
|
|
|
|
// Prefix for renaming inlined declarations
|
|
|
|
|
const std::string prefix = cellp->name() + "__DOT__";
|
|
|
|
|
|
|
|
|
|
// Move the inline records of the inlined module, renaming to avoid conflicts
|
|
|
|
|
for (AstNode *nodep = inlinedp->inlinesp(), *nextp; nodep; nodep = nextp) {
|
|
|
|
|
nextp = nodep->nextp();
|
|
|
|
|
AstCellInline* const cinlp = VN_AS(nodep->unlinkFrBack(), CellInline);
|
|
|
|
|
cinlp->name(prefix + cinlp->name());
|
|
|
|
|
modp->addInlinesp(cinlp);
|
|
|
|
|
}
|
|
|
|
|
// Create inline record for resolving hierarchical references later
|
|
|
|
|
AstCellInline* const newCellInlinep
|
|
|
|
|
= new AstCellInline{cellp->fileline(), cellp->name(), subModp->origName()};
|
|
|
|
|
modp->addInlinesp(newCellInlinep);
|
|
|
|
|
|
|
|
|
|
// Move the module level declarations of the inlined module into 'modp'
|
|
|
|
|
InlineModModuleVertex* const mVtxp = graph.getInlineModModuleVertexp(modp);
|
|
|
|
|
for (AstNode *nodep = inlinedp->stmtsp(), *nextp; nodep; nodep = nextp) {
|
|
|
|
|
nextp = nodep->nextp();
|
|
|
|
|
nodep->unlinkFrBack();
|
|
|
|
|
UASSERT_OBJ(!VN_IS(nodep, Class), nodep,
|
|
|
|
|
"Module containing a class should not be inlined");
|
|
|
|
|
if (AstVar* const varp = VN_CAST(nodep, Var)) {
|
|
|
|
|
varp->name(prefix + varp->name());
|
|
|
|
|
// Variable is now local to 'modp', rename to avoid conflicts and clear I/O bits
|
|
|
|
|
if (varp->direction() == VDirection::INOUT && varp->varType() == VVarType::WIRE) {
|
|
|
|
|
varp->varType(VVarType::TRIWIRE);
|
|
|
|
|
}
|
|
|
|
|
varp->direction(VDirection::NONE);
|
|
|
|
|
if (!cellp->isTrace()) varp->trace(false);
|
|
|
|
|
} else if (AstCell* const subCellp = VN_CAST(nodep, Cell)) {
|
|
|
|
|
subCellp->name(prefix + subCellp->name());
|
|
|
|
|
// Need to update graph. Note the vertex of the original cell was either
|
|
|
|
|
// deleted (if 'last'), or user4p is a copy made by cloneTree, so reset it.
|
|
|
|
|
subCellp->user4p(nullptr);
|
|
|
|
|
InlineModCellVertex* const vtxp = graph.getInlineModCellVertexp(subCellp);
|
|
|
|
|
graph.addEdge(*mVtxp, *vtxp);
|
|
|
|
|
graph.addEdge(*vtxp, *graph.getInlineModModuleVertexp(subCellp->modp()));
|
|
|
|
|
} else if (AstTypedef* const typedefp = VN_CAST(nodep, Typedef)) {
|
|
|
|
|
typedefp->name(prefix + typedefp->name());
|
|
|
|
|
} else if (AstNodeCoverDecl* const declp = VN_CAST(nodep, NodeCoverDecl)) {
|
|
|
|
|
// Fix path in coverage statements. Per instance ones are fixed in inlineScope.
|
|
|
|
|
if (!declp->perInstance()) {
|
|
|
|
|
declp->hier(VString::dot(cellp->prettyName(), ".", declp->hier()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
modp->addStmtsp(nodep);
|
2025-08-22 21:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// Merge each scope (instance) of the inlined cell into the scope above it
|
|
|
|
|
for (AstScope* const scopep : inlineScopeps) {
|
|
|
|
|
inlineScope(scopep, cellp, prefix, newCellInlinep, children);
|
2025-08-22 21:43:49 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply all inlining decisions
|
2026-06-25 14:14:15 +01:00
|
|
|
void process(AstNetlist* netlistp, InlineModGraph& graph) {
|
2025-08-22 21:43:49 +01:00
|
|
|
// Number of inlined instances, for statistics
|
|
|
|
|
VDouble0 m_nInlined;
|
|
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// Record the scope hierarchy - we need the downward links
|
|
|
|
|
ScopeChildren children;
|
|
|
|
|
for (AstNodeModule* modp = netlistp->modulesp(); modp;
|
|
|
|
|
modp = VN_AS(modp->nextp(), NodeModule)) {
|
|
|
|
|
gatherScopes(modp, children);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
// Gather all cells that need to be inlined (this is in topological order)
|
|
|
|
|
std::vector<InlineModCellVertex*> cVtxps;
|
|
|
|
|
for (V3GraphVertex& vtx : graph.vertices()) {
|
|
|
|
|
InlineModCellVertex* const cVtxp = vtx.cast<InlineModCellVertex>();
|
|
|
|
|
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<InlineModCellVertex>();
|
|
|
|
|
// 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);
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
|
|
|
|
|
// Do it
|
2026-09-23 19:15:46 +02:00
|
|
|
inlineCell(mVtx.modp(), cellp, last, graph, children);
|
2026-06-25 14:14:15 +01:00
|
|
|
if (dumpGraphLevel() >= 9) graph.dumpDotFilePrefixed("inlinemod-cell");
|
2016-01-21 19:11:53 -05:00
|
|
|
}
|
2013-05-25 10:42:44 -04:00
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// Restore varp() == varScopep()->varp() on all references, as cloning modules for
|
|
|
|
|
// inlining repointed some AstVarScopes. Hierarchical references can be anywhere.
|
|
|
|
|
netlistp->foreach([](AstNodeVarRef* refp) {
|
|
|
|
|
AstVarScope* const vscp = refp->varScopep();
|
|
|
|
|
if (vscp && refp->varp() != vscp->varp()) refp->varp(vscp->varp());
|
2025-08-22 21:43:49 +01:00
|
|
|
});
|
2026-09-23 19:15:46 +02:00
|
|
|
|
|
|
|
|
V3Stats::addStat("Optimizations, Inlined instances", m_nInlined);
|
2025-08-22 21:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} //namespace ModuleInliner
|
2013-05-25 10:42:44 -04:00
|
|
|
|
2006-08-26 11:35:28 +00:00
|
|
|
//######################################################################
|
2025-08-22 21:43:49 +01:00
|
|
|
// V3Inline class functions
|
2006-08-26 11:35:28 +00:00
|
|
|
|
|
|
|
|
void V3Inline::inlineAll(AstNetlist* nodep) {
|
2025-05-22 20:29:32 -04:00
|
|
|
UINFO(2, __FUNCTION__ << ":");
|
2021-12-22 11:41:29 +00:00
|
|
|
|
2026-06-25 14:14:15 +01:00
|
|
|
// Build the bipartite module instantiation graph
|
|
|
|
|
std::unique_ptr<InlineModGraph> graphp = InlineModGraphBuilder::apply(nodep);
|
|
|
|
|
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-graph");
|
|
|
|
|
|
|
|
|
|
// Decide which instances to inline
|
|
|
|
|
const size_t designSize
|
|
|
|
|
= graphp->vertices().frontp()->as<InlineModModuleVertex>()->flattenedSize();
|
|
|
|
|
for (V3GraphVertex& vtx : graphp->vertices()) {
|
|
|
|
|
if (InlineModModuleVertex* const mVtxp = vtx.cast<InlineModModuleVertex>()) {
|
|
|
|
|
// 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<InlineModCellVertex>();
|
|
|
|
|
cVtxp->setDoInline("< 20% of design");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// No more decisions based on module vertex
|
|
|
|
|
continue;
|
2019-05-19 16:13:13 -04:00
|
|
|
}
|
2026-06-25 14:14:15 +01:00
|
|
|
|
|
|
|
|
// The instantiation
|
|
|
|
|
InlineModCellVertex& cVtx = *vtx.as<InlineModCellVertex>();
|
|
|
|
|
// 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");
|
|
|
|
|
|
2026-09-23 19:15:46 +02:00
|
|
|
// Inline the modules we decided to inline
|
2026-06-25 14:14:15 +01:00
|
|
|
ModuleInliner::process(nodep, *graphp);
|
|
|
|
|
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-inlined");
|
2021-12-22 11:41:29 +00:00
|
|
|
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("inline", 0, dumpTreeEitherLevel() >= 3);
|
2006-08-26 11:35:28 +00:00
|
|
|
}
|