verilator/src/V3DfgOptimizer.cpp

235 lines
10 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Dataflow based optimization of combinational logic
//
// 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
//
//*************************************************************************
//
// High level entry points from Ast world to the DFG optimizer.
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3DfgOptimizer.h"
#include "V3Const.h"
#include "V3Dfg.h"
#include "V3DfgPasses.h"
#include "V3Error.h"
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
class DataflowOptimize final {
// NODE STATE
// AstVar::user1, AstVarScope::user1 -> int, used as a bit-field
// - bit0: Read via AstVarXRef (hierarchical reference)
// - bit1: Written via AstVarXRef (hierarchical reference)
// - bit2: Read by logic in same module/netlist not represented in DFG
// - bit3: Written by logic in same module/netlist not represented in DFG
// - bit4: Has READWRITE references
// - bit31-5: Reference count, how many DfgVertexVar represent this variable
//
// AstNode::user2/user3/user4 can be used by various DFG algorithms
const VNUser1InUse m_user1InUse;
// STATE
V3DfgContext m_ctx; // The context holding values that need to persist across multiple graphs
void endOfStage(const std::string& name) {
if (VL_UNLIKELY(v3Global.opt.stats())) V3Stats::statsStage("dfg-" + name);
}
void endOfStage(const std::string& name, const DfgGraph& dfg,
const std::vector<std::unique_ptr<DfgGraph>>& componentps) {
// Dump the graphs for debugging
if (VL_UNLIKELY(dumpDfgLevel() >= 5)) {
if (dfg.size() > 0) dfg.dumpDotFilePrefixed(name);
for (const std::unique_ptr<DfgGraph>& componentp : componentps) {
if (componentp->size() > 0) componentp->dumpDotFilePrefixed(name);
}
}
// Type check the graphs
if (VL_UNLIKELY(v3Global.opt.debugCheck())) {
V3DfgPasses::typeCheck(dfg);
for (const std::unique_ptr<DfgGraph>& componentp : componentps) {
V3DfgPasses::typeCheck(*componentp);
}
}
// Dump stage stats
endOfStage(name);
}
// Mark variables with external references
void markExternallyReferencedVariables(AstNetlist* netlistp) {
netlistp->foreach([](AstNode* nodep) {
// Check variable flags
if (AstVarScope* const vscp = VN_CAST(nodep, VarScope)) {
const AstVar* const varp = vscp->varp();
// Force and trace have already been processed
const bool hasExtRd = varp->isPrimaryIO() || varp->isSigUserRdPublic();
const bool hasExtWr
= (varp->isPrimaryIO() && varp->isNonOutput()) || varp->isSigUserRWPublic();
if (hasExtRd) DfgVertexVar::setHasExtRdRefs(vscp);
if (hasExtWr) DfgVertexVar::setHasExtWrRefs(vscp);
return;
}
// Check references
if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
if (refp->access().isRW()) DfgVertexVar::setHasRWRefs(refp->varScopep());
UASSERT_OBJ(!refp->classOrPackagep(), refp, "V3Scope should have removed");
return;
}
UASSERT_OBJ(!VN_IS(nodep, VarXRef), nodep, "V3Scope should have removed");
// Check cell ports
if (const AstCell* const cellp = VN_CAST(nodep, Cell)) {
// Why does this not hold?
UASSERT_OBJ(true || !cellp->pinsp(), cellp, "Pins should have been lowered");
return;
}
});
}
void optimize(DfgGraph& dfg) {
// Remove unobservable variabels and logic that drives only such variables
V3DfgPasses::removeUnobservable(dfg, m_ctx);
endOfStage("removeUnobservable", dfg, {});
// Synthesize DfgLogic vertices
V3DfgPasses::synthesize(dfg, m_ctx);
endOfStage("synthesize", dfg, {});
// Extract the cyclic sub-graphs. We do this because a lot of the optimizations assume a
// DAG, and large, mostly acyclic graphs could not be optimized due to the presence of
// small cycles.
std::vector<std::unique_ptr<DfgGraph>> cyclicComps = dfg.extractCyclicComponents("cyclic");
endOfStage("extractCyclic", dfg, cyclicComps);
// Attempt to convert cyclic components into acyclic ones
std::vector<std::unique_ptr<DfgGraph>> madeAcyclicComponents;
if (v3Global.opt.fDfgBreakCycles()) {
for (auto it = cyclicComps.begin(); it != cyclicComps.end();) {
auto result = V3DfgPasses::breakCycles(**it, m_ctx);
if (!result.first) {
// No improvement, moving on.
++it;
} else if (!result.second) {
// Improved, but still cyclic. Replace the original cyclic component.
*it = std::move(result.first);
++it;
} else {
// Result became acyclic. Move to madeAcyclicComponents, delete original.
madeAcyclicComponents.emplace_back(std::move(result.first));
it = cyclicComps.erase(it);
}
}
}
// Merge those that were made acyclic back to the graph, this enables optimizing more
dfg.mergeGraphs(std::move(madeAcyclicComponents));
endOfStage("breakCycles", dfg, cyclicComps);
// Split the acyclic DFG into [weakly] connected components
std::vector<std::unique_ptr<DfgGraph>> acyclicComps = dfg.splitIntoComponents("acyclic");
UASSERT(dfg.size() == 0, "DfgGraph should have become empty");
endOfStage("splitAcyclic", dfg, acyclicComps);
// Optimize each acyclic component
for (auto& cp : acyclicComps) V3DfgPasses::inlineVars(*cp);
endOfStage("inlineVars", dfg, acyclicComps);
for (auto& cp : acyclicComps) V3DfgPasses::cse(*cp, m_ctx.m_cseContext0);
endOfStage("cse0", dfg, acyclicComps);
for (auto& cp : acyclicComps) V3DfgPasses::binToOneHot(*cp, m_ctx.m_binToOneHotContext);
endOfStage("binToOneHot", dfg, acyclicComps);
for (auto& cp : acyclicComps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext);
endOfStage("peephole", dfg, acyclicComps);
// Accumulate patterns for reporting
if (v3Global.opt.dumpDfgPatterns()) {
V3DfgPasses::dumpPatterns(acyclicComps);
endOfStage("dumpPatterns");
}
for (auto& cp : acyclicComps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext);
endOfStage("pushDownSels", dfg, acyclicComps);
for (auto& cp : acyclicComps) V3DfgPasses::cse(*cp, m_ctx.m_cseContext1);
endOfStage("cse1", dfg, acyclicComps);
// Merge everything back under the main DFG
dfg.mergeGraphs(std::move(acyclicComps));
dfg.mergeGraphs(std::move(cyclicComps));
endOfStage("optimized", dfg, {});
// Regularize the graph after merging it all back together so all
// references are known and we only need to iterate the Ast once
// to replace redundant variables.
V3DfgPasses::regularize(dfg, m_ctx.m_regularizeContext);
endOfStage("regularize", dfg, {});
}
void removeNeverActives(AstNetlist* netlistp) {
std::vector<AstActive*> neverActiveps;
netlistp->foreach([&](AstActive* activep) {
AstSenTree* const senTreep = activep->sentreep();
if (!senTreep) return;
const AstNode* const nodep = V3Const::constifyEdit(senTreep);
UASSERT_OBJ(nodep == senTreep, nodep, "Should not have been repalced");
if (senTreep->sensesp()->isNever()) {
UASSERT_OBJ(!senTreep->sensesp()->nextp(), nodep,
"Never senitem should be alone, else the never should be eliminated.");
neverActiveps.emplace_back(activep);
}
});
for (AstActive* const activep : neverActiveps) {
VL_DO_DANGLING(activep->unlinkFrBack()->deleteTree(), activep);
}
}
DataflowOptimize(AstNetlist* netlistp) {
// Mark interfaces that might be referenced by a virtual interface
if (v3Global.hasVirtIfaces()) {
netlistp->typeTablep()->foreach([](const AstIfaceRefDType* nodep) {
if (!nodep->isVirtual()) return;
nodep->ifaceViaCellp()->setHasVirtualRef();
});
}
// Mark variables with external references
markExternallyReferencedVariables(netlistp);
// Dump stage stats
endOfStage("init");
// Post V3Scope application. Run on whole netlist.
UINFO(4, "Applying DFG optimization to entire netlist");
// Build the DFG of the entire netlist
const std::unique_ptr<DfgGraph> dfgp = V3DfgPasses::astToDfg(*netlistp, m_ctx);
endOfStage("astToDfg", *dfgp, {});
// Actually process the graph
optimize(*dfgp);
// Convert back to Ast
V3DfgPasses::dfgToAst(*dfgp, m_ctx);
endOfStage("dfgToAst", *dfgp, {});
// Some sentrees might have become constant, remove them
removeNeverActives(netlistp);
// Reset interned types so the corresponding Ast types can be garbage collected
DfgDataType::reset();
// Dump stage stats
endOfStage("fini");
}
public:
static void apply(AstNetlist* netlistp) { DataflowOptimize{netlistp}; }
};
void V3DfgOptimizer::optimize(AstNetlist* netlistp) {
UINFO(2, __FUNCTION__ << ":");
DataflowOptimize::apply(netlistp);
V3Global::dumpCheckGlobalTree("dfg-optimize", 0, dumpTreeEitherLevel() >= 3);
}