mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 10:03:44 +02:00
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.
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
// -*- 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);
|
||||
}
|
||||
Reference in New Issue
Block a user