verilator/src/V3Inline.cpp

953 lines
42 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add temporaries, such as for inline nodes
//
2019-11-08 04:33:59 +01:00
// 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 <unordered_set>
#include <vector>
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<InlineModModuleVertex>();
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<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;
}
}
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;
}
};
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
// 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);
}
2019-09-12 13:22:22 +02:00
// 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<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
}
~InlineModGraphBuilder() override = default;
public:
static std::unique_ptr<InlineModGraph> 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<std::string> m_renamedInterfaces; // Name of renamed interface variables
std::unordered_set<std::string>
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<const AstVarXRef*>
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);
2026-02-04 22:27:14 +01:00
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:
2019-09-12 13:22:22 +02:00
// 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
2025-08-26 00:47:08 +02:00
// 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
2025-08-26 00:47:08 +02:00
// 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);
2026-02-04 22:27:14 +01:00
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) {
2025-12-29 03:30:16 +01:00
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
2025-12-29 03:30:16 +01:00
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;
2026-05-22 23:57:11 +02:00
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
2025-09-09 23:39:44 +02:00
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<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);
}
// 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<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;
}
// 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");
// Inline the modles we decided to inline
ModuleInliner::process(nodep, *graphp);
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("inlinemod-inlined");
V3Global::dumpCheckGlobalTree("inline", 0, dumpTreeEitherLevel() >= 3);
}