verilator/src/V3CfgBuilder.cpp

292 lines
11 KiB
C++
Raw Normal View History

Optimize complex combinational logic in DFG (#6298) This patch adds DfgLogic, which is a vertex that represents a whole, arbitrarily complex combinational AstAlways or AstAssignW in the DfgGraph. Implementing this requires computing the variables live at entry to the AstAlways (variables read by the block), so there is a new ControlFlowGraph data structure and a classical data-flow analysis based live variable analysis to do that at the variable level (as opposed to bit/element level). The actual CFG construction and live variable analysis is best effort, and might fail for currently unhandled constructs or data types. This can be extended later. V3DfgAstToDfg is changed to convert the Ast into an initial DfgGraph containing only DfgLogic, DfgVertexSplice and DfgVertexVar vertices. The DfgLogic are then subsequently synthesized into primitive operations by the new V3DfgSynthesize pass, which is a combination of the old V3DfgAstToDfg conversion and new code to handle AstAlways blocks with complex flow control. V3DfgSynthesize by default will synthesize roughly the same constructs as V3DfgAstToDfg used to handle before, plus any logic that is part of a combinational cycle within the DfgGraph. This enables breaking up these cycles, for which there are extensions to V3DfgBreakCycles in this patch as well. V3DfgSynthesize will then delete all non synthesized or non synthesizable DfgLogic vertices and the rest of the Dfg pipeline is identical, with minor changes to adjust for the changed representation. Because with this change we can now eliminate many more UNOPTFLAT, DFG has been disabled in all the tests that specifically target testing the scheduling and reporting of circular combinational logic.
2025-08-19 16:06:38 +02:00
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Control flow graph (CFG) builder
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// Control flow graph (CFG) builder
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3Cfg.h"
#include "V3EmitV.h"
#include <deque>
#include <unordered_map>
#include <unordered_set>
VL_DEFINE_DEBUG_FUNCTIONS;
class CfgBuilder final : VNVisitorConst {
// STATE
// The graph being built, or nullptr if failed to build one
std::unique_ptr<ControlFlowGraph> m_cfgp{new ControlFlowGraph};
// Current basic block to add statements to
BasicBlock* m_currBBp = nullptr;
// Continuation block for given JumpBlock
std::unordered_map<AstJumpBlock*, BasicBlock*> m_jumpBlockContp;
// METHODS
// Create new Basicblock block in the CFG
BasicBlock& addBasicBlock() { return *new BasicBlock{m_cfgp.get()}; }
// Create new taken (or unconditional) ControlFlowEdge in the CFG
void addTakenEdge(BasicBlock& src, BasicBlock& dst) {
UASSERT_OBJ(src.outEmpty(), &src, "Taken edge should be added first");
new ControlFlowEdge{m_cfgp.get(), &src, &dst};
}
// Create new untaken ControlFlowEdge in the CFG
void addUntknEdge(BasicBlock& src, BasicBlock& dst) {
UASSERT_OBJ(src.outSize1(), &src, "Untaken edge shold be added second");
UASSERT_OBJ(src.outEdges().frontp()->top() != &dst, &src,
"Untaken branch targets the same block as the taken branch");
new ControlFlowEdge{m_cfgp.get(), &src, &dst};
}
// Add the given statement to the current BasicBlock
void addStmt(AstNodeStmt* nodep) { m_currBBp->m_stmtps.emplace_back(nodep); }
// Used to handle statements not representable in the CFG
void nonRepresentable(AstNodeStmt*) {
if (!m_cfgp) return;
m_cfgp.reset();
}
// Used to handle simple (non-branching) statements in the CFG
void simpleStatement(AstNodeStmt* nodep, bool representable = true) {
if (!m_cfgp) return;
// If non-representable, reset graph
if (!representable) {
m_cfgp.reset();
return;
}
// Just add to current block
addStmt(nodep);
}
// VISITORS
// Eventually we should handle all procedural statements, however, what
// is a procedural statemen is a bit unclear (#6280), so in the first
// instance we will only handle select statemetns that cover the requied
// use cases, and in the base case we conservatively assume the statement
// is non-representable. More visits can be added case by case if needed.
void visit(AstNode* nodep) override {
if (!m_cfgp) return;
UINFO(9, "Unhandled AstNode type " << nodep->typeName());
m_cfgp.reset();
}
// Non-representable statements
void visit(AstAssignDly* nodep) override { nonRepresentable(nodep); }
void visit(AstCase* nodep) override { nonRepresentable(nodep); } // V3Case will eliminate
void visit(AstCReset* nodep) override { nonRepresentable(nodep); }
void visit(AstDelay* nodep) override { nonRepresentable(nodep); }
// Representable non control-flow statements
void visit(AstAssign* nodep) override { simpleStatement(nodep, !nodep->timingControlp()); }
void visit(AstComment*) override {} // ignore entirely
void visit(AstDisplay* nodep) override { simpleStatement(nodep); }
void visit(AstFinish* nodep) override { simpleStatement(nodep); }
void visit(AstStmtExpr* nodep) override { simpleStatement(nodep); }
void visit(AstStop* nodep) override { simpleStatement(nodep); }
// Representable control flow statements
void visit(AstIf* nodep) override {
if (!m_cfgp) return;
// Add terminator statement to current block - semantically the condition check only ...
addStmt(nodep);
// Create then/else/continuation blocks
BasicBlock& thenBB = addBasicBlock();
BasicBlock& elseBB = addBasicBlock();
BasicBlock& contBB = addBasicBlock();
addTakenEdge(*m_currBBp, thenBB);
addUntknEdge(*m_currBBp, elseBB);
// Build then branch
m_currBBp = &thenBB;
iterateAndNextConstNull(nodep->thensp());
if (!m_cfgp) return;
if (m_currBBp) addTakenEdge(*m_currBBp, contBB);
// Build else branch
m_currBBp = &elseBB;
iterateAndNextConstNull(nodep->elsesp());
if (!m_cfgp) return;
if (m_currBBp) addTakenEdge(*m_currBBp, contBB);
// Set continuation
m_currBBp = &contBB;
}
void visit(AstWhile* nodep) override {
if (!m_cfgp) return;
// Create the header block
BasicBlock& headBB = addBasicBlock();
addTakenEdge(*m_currBBp, headBB);
// The While goes in the header block - semantically the condition check only ...
m_currBBp = &headBB;
addStmt(nodep);
// Create the body/continuation blocks
BasicBlock& bodyBB = addBasicBlock();
BasicBlock& contBB = addBasicBlock();
addTakenEdge(headBB, bodyBB);
addUntknEdge(headBB, contBB);
// Build the body
m_currBBp = &bodyBB;
iterateAndNextConstNull(nodep->stmtsp());
iterateAndNextConstNull(nodep->incsp());
if (!m_cfgp) return;
if (m_currBBp) addTakenEdge(*m_currBBp, headBB);
// Set continuation
m_currBBp = &contBB;
}
void visit(AstJumpBlock* nodep) override {
if (!m_cfgp) return;
// Don't acutally need to add this 'nodep' to any block - but we could later if needed
// Create continuation block
BasicBlock& contBB = addBasicBlock();
const bool newEntry = m_jumpBlockContp.emplace(nodep, &contBB).second;
UASSERT_OBJ(newEntry, nodep, "AstJumpBlock visited twice");
// Build the body
iterateAndNextConstNull(nodep->stmtsp());
if (!m_cfgp) return;
if (m_currBBp) addTakenEdge(*m_currBBp, contBB);
// Set continuation
m_currBBp = &contBB;
}
void visit(AstJumpGo* nodep) override {
if (!m_cfgp) return;
// Non-representable if not last in statement list (V3Const will fix this later)
if (nodep->nextp()) {
m_cfgp.reset();
return;
}
// Don't acutally need to add this 'nodep' to any block - but we could later if needed
// Make current block go to the continuation of the JumpBlock
addTakenEdge(*m_currBBp, *m_jumpBlockContp.at(nodep->blockp()));
// There should be no statements after a JumpGo!
m_currBBp = nullptr;
}
// CONSTRUCTOR
explicit CfgBuilder(const AstNodeProcedure* nodep) {
// Build the graph, starting from the entry block
m_currBBp = &addBasicBlock();
m_cfgp->m_enterp = m_currBBp;
// Visit each statement to build the control flow graph
iterateAndNextConstNull(nodep->stmtsp());
if (!m_cfgp) return;
// The final block is the exit block
m_cfgp->m_exitp = m_currBBp;
// Remove empty blocks - except enter/exit
for (V3GraphVertex* const vtxp : m_cfgp->vertices().unlinkable()) {
if (vtxp == &m_cfgp->enter()) continue;
if (vtxp == &m_cfgp->exit()) continue;
BasicBlock* const bbp = vtxp->as<BasicBlock>();
if (!bbp->stmtps().empty()) continue;
UASSERT(bbp->outSize1(), "Empty block should have a single successor");
BasicBlock* const succp = const_cast<BasicBlock*>(bbp->takenSuccessorp());
for (V3GraphEdge* const edgep : bbp->inEdges().unlinkable()) edgep->relinkTop(succp);
vtxp->unlinkDelete(m_cfgp.get());
}
// Remove redundant entry block
while (m_cfgp->enter().stmtps().empty() && m_cfgp->enter().outSize1()) {
BasicBlock* const succp = m_cfgp->enter().outEdges().frontp()->top()->as<BasicBlock>();
if (!succp->inSize1()) break;
m_cfgp->m_enterp->unlinkDelete(m_cfgp.get());
m_cfgp->m_enterp = succp;
}
// Remove redundant exit block
while (m_cfgp->exit().stmtps().empty() && m_cfgp->exit().inSize1()) {
BasicBlock* const prep = m_cfgp->exit().inEdges().frontp()->fromp()->as<BasicBlock>();
if (!prep->outSize1()) break;
m_cfgp->m_exitp->unlinkDelete(m_cfgp.get());
m_cfgp->m_exitp = prep;
}
// Compute reverse post-order enumeration and sort blocks, assign IDs
{
// Simple recursive algorith will do ...
std::vector<BasicBlock*> postOrderEnumeration;
std::unordered_set<BasicBlock*> visited;
const std::function<void(BasicBlock*)> visitBasicBlock = [&](BasicBlock* bbp) {
// Mark and skip if already visited
if (!visited.emplace(bbp).second) return;
// Visit successors
for (const V3GraphEdge& e : bbp->outEdges()) {
visitBasicBlock(static_cast<BasicBlock*>(e.top()));
}
// Add to post order enumeration
postOrderEnumeration.emplace_back(bbp);
};
visitBasicBlock(m_cfgp->m_enterp);
const uint32_t n = postOrderEnumeration.size();
UASSERT_OBJ(n == m_cfgp->vertices().size(), nodep, "Inconsistent enumeration size");
// Set size in graph
m_cfgp->m_nBasicBlocks = n;
// Assign ids equal to the reverse post order number and sort vertices
for (uint32_t i = 0; i < postOrderEnumeration.size(); ++i) {
BasicBlock* const bbp = postOrderEnumeration[n - 1 - i];
bbp->user(i);
m_cfgp->vertices().unlink(bbp);
m_cfgp->vertices().linkBack(bbp);
}
}
// Assign IDs to edges
{
size_t nEdges = 0;
for (V3GraphVertex& v : m_cfgp->vertices()) {
for (V3GraphEdge& e : v.outEdges()) e.user(nEdges++);
}
// Set size in graph
m_cfgp->m_nEdges = nEdges;
}
if (dumpGraphLevel() >= 9) m_cfgp->dumpDotFilePrefixed("cfgbuilder");
}
public:
static std::unique_ptr<ControlFlowGraph> apply(const AstNodeProcedure* nodep) {
return std::move(CfgBuilder{nodep}.m_cfgp);
}
};
std::unique_ptr<const ControlFlowGraph> V3Cfg::build(const AstNodeProcedure* nodep) {
return CfgBuilder::apply(nodep);
}