mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 10:03:44 +02:00
Optimize multiplexers in Dfg synthesis (#6331)
The previous algorithm was designed to handle the general case where a full control flow path predicate is required to select which value to use when synthesizing control flow join point in an always block. Here we add a better algorithm that tries to use the predicate of the closest dominating branch if the branch paths dominate the joining paths. This is almost universally true in synthesizable logic (RTLMeter has no exceptions), however there are cases where this is not applicable, for which we fall back on the previous generic algorithm. Overall this significantly simplifies the synthesized Dfg graphs and enables further optimization.
This commit is contained in:
+259
-28
@@ -29,24 +29,9 @@
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
//######################################################################
|
||||
// ControlFlowGraph method definitions
|
||||
// CfgBlock method definitions
|
||||
|
||||
bool ControlFlowGraph::containsLoop() const {
|
||||
for (const V3GraphVertex& vtx : vertices()) {
|
||||
const BasicBlock& current = static_cast<const BasicBlock&>(vtx);
|
||||
for (const V3GraphEdge& edge : current.outEdges()) {
|
||||
const BasicBlock& successor = *static_cast<const BasicBlock*>(edge.top());
|
||||
// IDs are the reverse post-order numbering, so easy to check for a back-edge
|
||||
if (successor.id() < current.id()) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// BasicBlock method definitions
|
||||
|
||||
std::string BasicBlock::name() const {
|
||||
std::string CfgBlock::name() const {
|
||||
std::stringstream ss;
|
||||
ss << "BB " + std::to_string(id()) + ":\n";
|
||||
for (AstNode* nodep : m_stmtps) {
|
||||
@@ -62,25 +47,25 @@ std::string BasicBlock::name() const {
|
||||
V3EmitV::debugVerilogForTree(nodep, ss);
|
||||
}
|
||||
}
|
||||
std::string text = VString::replaceSubstr(ss.str(), "\n", "\\l ");
|
||||
if (inEmpty()) text = "**ENTER**\n" + text;
|
||||
if (outEmpty()) text = text + "\n**EXIT**";
|
||||
std::string text = VString::replaceSubstr(
|
||||
VString::replaceSubstr(ss.str(), "\n", "\\l "), "\"", "\\\"");
|
||||
if (isEnter()) text = "**ENTER**\n" + text;
|
||||
if (isExit()) text = text + "\n**EXIT**";
|
||||
return text;
|
||||
}
|
||||
std::string BasicBlock::dotShape() const { return "rect"; }
|
||||
std::string BasicBlock::dotRank() const {
|
||||
if (inEmpty()) return "source";
|
||||
if (outEmpty()) return "sink";
|
||||
std::string CfgBlock::dotShape() const { return "rect"; }
|
||||
std::string CfgBlock::dotRank() const {
|
||||
if (isEnter()) return "source";
|
||||
if (isExit()) return "sink";
|
||||
return "";
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// ControlFlowEdge method definitions
|
||||
// CfgEdge method definitions
|
||||
|
||||
std::string ControlFlowEdge::dotLabel() const {
|
||||
std::string CfgEdge::dotLabel() const {
|
||||
std::string label = "E" + std::to_string(id());
|
||||
const BasicBlock& source = *fromp()->as<BasicBlock>();
|
||||
const ControlFlowEdge* const untknp = source.untknEdgep();
|
||||
const CfgEdge* const untknp = srcp()->untknEdgep();
|
||||
if (this == untknp) {
|
||||
label += " / F";
|
||||
} else if (untknp) {
|
||||
@@ -88,3 +73,249 @@ std::string ControlFlowEdge::dotLabel() const {
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// CfgGraph method definitions
|
||||
|
||||
static void cfgOrderVisitBlock(std::vector<CfgBlock*>& postOrderEnumeration, CfgBlock* bbp) {
|
||||
// Mark visited
|
||||
bbp->user(1);
|
||||
// Visit un-visited successors
|
||||
if (CfgBlock* const takenp = bbp->takenp()) {
|
||||
if (!takenp->user()) cfgOrderVisitBlock(postOrderEnumeration, takenp);
|
||||
if (CfgBlock* const untknp = bbp->untknp()) {
|
||||
if (!untknp->user()) cfgOrderVisitBlock(postOrderEnumeration, untknp);
|
||||
}
|
||||
}
|
||||
// Add to post order enumeration
|
||||
postOrderEnumeration.emplace_back(bbp);
|
||||
};
|
||||
|
||||
void CfgGraph::rpoBlocks() {
|
||||
UASSERT_OBJ(m_nEdits != m_nLastOrdered, m_enterp, "Redundant 'CfgGraph::order' call");
|
||||
m_nLastOrdered = m_nEdits;
|
||||
|
||||
// Reset marks
|
||||
for (V3GraphVertex& v : vertices()) v.user(0);
|
||||
|
||||
// Compute post-order enumeration. Simple recursive algorith will do.
|
||||
std::vector<CfgBlock*> postOrderEnumeration;
|
||||
postOrderEnumeration.reserve(m_nBlocks);
|
||||
cfgOrderVisitBlock(postOrderEnumeration, m_enterp);
|
||||
UASSERT_OBJ(postOrderEnumeration.size() == m_nBlocks, m_enterp, "Inconsistent block count");
|
||||
|
||||
// Assign block IDs equal to the reverse post-order number and sort vertices
|
||||
for (size_t i = 0; i < postOrderEnumeration.size(); ++i) {
|
||||
CfgBlock* const bbp = postOrderEnumeration[m_nBlocks - 1 - i];
|
||||
bbp->m_rpoNumber = i;
|
||||
vertices().unlink(bbp);
|
||||
vertices().linkBack(bbp);
|
||||
}
|
||||
|
||||
// Assign edge IDs
|
||||
size_t edgeCount = 0;
|
||||
for (V3GraphVertex& v : vertices()) {
|
||||
for (V3GraphEdge& e : v.outEdges()) static_cast<CfgEdge&>(e).m_id = edgeCount++;
|
||||
}
|
||||
UASSERT_OBJ(edgeCount == m_nEdges, m_enterp, "Inconsistent edge count");
|
||||
}
|
||||
|
||||
bool CfgGraph::containsLoop() const {
|
||||
for (const V3GraphVertex& vtx : vertices()) {
|
||||
const CfgBlock& current = static_cast<const CfgBlock&>(vtx);
|
||||
for (const V3GraphEdge& edge : current.outEdges()) {
|
||||
const CfgBlock& successor = *static_cast<const CfgBlock*>(edge.top());
|
||||
// IDs are the reverse post-order numbering, so easy to check for a back-edge
|
||||
if (successor.id() < current.id()) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CfgGraph::minimize() {
|
||||
// Remove empty blocks (except enter and exit)
|
||||
for (V3GraphVertex* const vtxp : vertices().unlinkable()) {
|
||||
CfgBlock* const bbp = static_cast<CfgBlock*>(vtxp);
|
||||
if (bbp->isEnter()) continue;
|
||||
if (bbp->isExit()) continue;
|
||||
if (!bbp->stmtps().empty()) continue;
|
||||
UASSERT(!bbp->isBranch(), "Empty block should have a single successor");
|
||||
CfgBlock* const succp = bbp->takenp();
|
||||
for (V3GraphEdge* const edgep : bbp->inEdges().unlinkable()) edgep->relinkTop(succp);
|
||||
++m_nEdits;
|
||||
--m_nEdges;
|
||||
--m_nBlocks;
|
||||
VL_DO_DANGLING(bbp->unlinkDelete(this), bbp);
|
||||
}
|
||||
|
||||
// Combine sequential blocks
|
||||
for (V3GraphVertex* const vtxp : vertices().unlinkable()) {
|
||||
CfgBlock* const srcp = static_cast<CfgBlock*>(vtxp);
|
||||
if (srcp->isExit()) continue;
|
||||
if (srcp->isBranch()) continue;
|
||||
CfgBlock* const dstp = srcp->takenp();
|
||||
if (dstp->isJoin()) continue;
|
||||
// Combine them
|
||||
if (srcp->isEnter()) m_enterp = dstp;
|
||||
std::vector<AstNodeStmt*> stmtps{std::move(srcp->m_stmtps)};
|
||||
stmtps.reserve(stmtps.size() + dstp->m_stmtps.size());
|
||||
stmtps.insert(stmtps.end(), dstp->m_stmtps.begin(), dstp->m_stmtps.end());
|
||||
dstp->m_stmtps = std::move(stmtps);
|
||||
for (V3GraphEdge* const edgep : srcp->inEdges().unlinkable()) edgep->relinkTop(dstp);
|
||||
++m_nEdits;
|
||||
--m_nEdges;
|
||||
--m_nBlocks;
|
||||
VL_DO_DANGLING(srcp->unlinkDelete(this), srcp);
|
||||
}
|
||||
|
||||
if (m_nEdits != m_nLastOrdered) rpoBlocks();
|
||||
if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("cfg-minimize");
|
||||
}
|
||||
|
||||
void CfgGraph::breakCriticalEdges() {
|
||||
// Gather critical edges
|
||||
std::vector<CfgEdge*> criticalEdges;
|
||||
criticalEdges.reserve(m_nEdges);
|
||||
for (V3GraphVertex& vtx : vertices()) {
|
||||
const CfgBlock& bb = static_cast<const CfgBlock&>(vtx);
|
||||
if (!bb.isBranch()) continue;
|
||||
for (V3GraphEdge& edge : vtx.outEdges()) {
|
||||
const CfgBlock& succ = static_cast<const CfgBlock&>(*edge.top());
|
||||
if (!succ.isJoin()) continue;
|
||||
criticalEdges.emplace_back(static_cast<CfgEdge*>(&edge));
|
||||
}
|
||||
}
|
||||
// Insert blocks
|
||||
for (CfgEdge* const edgep : criticalEdges) {
|
||||
CfgBlock* const newp = addBlock();
|
||||
addTakenEdge(newp, edgep->dstp());
|
||||
edgep->relinkTop(newp);
|
||||
}
|
||||
|
||||
if (m_nEdits != m_nLastOrdered) rpoBlocks();
|
||||
if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("cfg-breakCriticalEdges");
|
||||
}
|
||||
|
||||
// Given a branching basic block, if the sub-graph below this branch, up until
|
||||
// the point where all of its control flow path convertes is series-parallel,
|
||||
// then return the (potentially newly created) basic block with exactly 2
|
||||
// predecessors where the two control flow paths from this branch have joined.
|
||||
// If the relevant sub-graph is not series-parallel (there is a control flow
|
||||
// path between the branches, or to a path not dominated by the given branch),
|
||||
// then return nullptr. Cached results in the given map
|
||||
CfgBlock* CfgGraph::getOrCreateTwoWayJoinFor(CfgBlock* bbp) {
|
||||
UASSERT_OBJ(bbp->isBranch(), bbp, "Not a branch");
|
||||
|
||||
// Mark visited
|
||||
UASSERT_OBJ(!bbp->user(), bbp, "Should not visit twice");
|
||||
bbp->user(1);
|
||||
|
||||
// We need the edge converting to a join block along both path. This is how we find it:
|
||||
const auto chaseEdge = [&](CfgEdge* edgep) -> CfgEdge* {
|
||||
while (true) {
|
||||
CfgBlock* dstp = edgep->dstp();
|
||||
// Stop if found the joining block along this path
|
||||
if (dstp->isJoin()) return edgep;
|
||||
// If the successor is a branch, recursively get it's 2-way join block
|
||||
while (dstp->isBranch()) {
|
||||
dstp = getOrCreateTwoWayJoinFor(dstp);
|
||||
// If the subgarph below dstp is not series-parallel, then no solution
|
||||
if (!dstp) return nullptr;
|
||||
}
|
||||
UASSERT_OBJ(!dstp->isExit(), bbp, "Non-convergent branch - multiple Exit blocks?");
|
||||
edgep = dstp->takenEdgep();
|
||||
}
|
||||
};
|
||||
|
||||
// Walk down both paths
|
||||
CfgEdge* const takenEdgep = chaseEdge(bbp->takenEdgep());
|
||||
if (!takenEdgep) return nullptr;
|
||||
CfgEdge* const untknEdgep = chaseEdge(bbp->untknEdgep());
|
||||
if (!untknEdgep) return nullptr;
|
||||
// If we ended up at different joining blocks, then there is a path from one
|
||||
// of the branches into a path of another branch before 'bbp', no solution
|
||||
if (takenEdgep->dstp() != untknEdgep->dstp()) return nullptr;
|
||||
|
||||
// Pick up the common successor
|
||||
CfgBlock* const succp = takenEdgep->dstp();
|
||||
// If the common successor is a 2-way join, we can use it directly
|
||||
if (succp->isTwoWayJoin()) return succp;
|
||||
// Otherwise insert a new block to join the 2 paths of the original block
|
||||
CfgBlock* const joinp = addBlock();
|
||||
addTakenEdge(joinp, succp);
|
||||
takenEdgep->relinkTop(joinp);
|
||||
untknEdgep->relinkTop(joinp);
|
||||
return joinp;
|
||||
}
|
||||
|
||||
bool CfgGraph::insertTwoWayJoins() {
|
||||
// Reset marks
|
||||
for (V3GraphVertex& v : vertices()) v.user(0);
|
||||
|
||||
bool isSeriesParallel = true;
|
||||
|
||||
// We will be adding vertices at the end. That's OK, they don't need to be visited again
|
||||
for (V3GraphVertex& v : vertices()) {
|
||||
CfgBlock& bb = static_cast<CfgBlock&>(v);
|
||||
// Skip if already visited
|
||||
if (bb.user()) continue;
|
||||
// Skip if not a branch
|
||||
if (!bb.isBranch()) continue;
|
||||
// Fix it up, record if failed
|
||||
if (!getOrCreateTwoWayJoinFor(&bb)) isSeriesParallel = false;
|
||||
}
|
||||
|
||||
if (m_nEdits != m_nLastOrdered) rpoBlocks();
|
||||
if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("cfg-insertTwoWayJoins");
|
||||
|
||||
return isSeriesParallel;
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// CfgDominatorTree
|
||||
|
||||
const CfgBlock* CfgDominatorTree::intersect(const CfgBlock* ap, const CfgBlock* bp) {
|
||||
while (ap != bp) {
|
||||
while (*ap > *bp) ap = m_bb2Idom[*ap];
|
||||
while (*bp > *ap) bp = m_bb2Idom[*bp];
|
||||
}
|
||||
return ap;
|
||||
}
|
||||
|
||||
CfgDominatorTree::CfgDominatorTree(const CfgGraph& cfg)
|
||||
: m_bb2Idom{cfg.makeBlockMap<const CfgBlock*>()} {
|
||||
|
||||
// Build the immediate dominator map, using algorithm from:
|
||||
// "A Simple, Fast Dominance Algorithm", Keith D. Cooper et al., 2006
|
||||
// Immediate dominator of the enter block
|
||||
|
||||
// Point enteer block to itself, while computing below
|
||||
m_bb2Idom[cfg.enter()] = &cfg.enter();
|
||||
// Iterate until settled
|
||||
for (bool changed = true; changed;) {
|
||||
changed = false;
|
||||
// For each vertex except enter block
|
||||
for (const V3GraphVertex& vtx : cfg.vertices()) {
|
||||
const CfgBlock& curr = static_cast<const CfgBlock&>(vtx);
|
||||
if (curr.isEnter()) continue; // Skip entry block
|
||||
|
||||
// For each predecessor of current block
|
||||
const CfgBlock* idom = nullptr;
|
||||
for (const V3GraphEdge& edge : curr.inEdges()) {
|
||||
const CfgBlock& pred = static_cast<const CfgBlock&>(*edge.fromp());
|
||||
// Skip if perdecessor not yet processed
|
||||
if (!m_bb2Idom[pred]) continue;
|
||||
// Pick first, then use intersect
|
||||
idom = !idom ? &pred : intersect(&pred, idom);
|
||||
}
|
||||
|
||||
// If chenged, record it, else move on
|
||||
if (idom == m_bb2Idom[curr]) continue;
|
||||
m_bb2Idom[curr] = idom;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The enter block is the root of the tree and does not itself have an immediate dominator
|
||||
m_bb2Idom[cfg.enter()] = nullptr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user