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:
Geza Lore
2025-08-19 15:06:38 +01:00
committed by GitHub
parent 6a67c0a0e5
commit 636a6b8cd2
95 changed files with 4511 additions and 1518 deletions
+35 -6
View File
@@ -103,7 +103,7 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
DfgVertex* const sentinelp = reinterpret_cast<DfgVertex*>(&dfg);
DfgVertex* workListp = sentinelp;
// Add all unused vertices to the work list. This also allocates all DfgVertex::user.
// Add all unused operation vertices to the work list. This also allocates all DfgVertex::user.
for (DfgVertex& vtx : dfg.opVertices()) {
if (vtx.hasSinks()) {
// This vertex is used. Allocate user, but don't add to work list.
@@ -115,6 +115,19 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
}
}
// Also add all unused temporaries created during synthesis
for (DfgVertexVar& vtx : dfg.varVertices()) {
if (!vtx.tmpForp()) continue;
if (vtx.hasSinks() || vtx.hasDfgRefs()) {
// This vertex is used. Allocate user, but don't add to work list.
vtx.setUser<DfgVertex*>(nullptr);
} else {
// This vertex is unused. Add to work list.
vtx.setUser<DfgVertex*>(workListp);
workListp = &vtx;
}
}
// Process the work list
while (workListp != sentinelp) {
// Pick up the head
@@ -123,12 +136,23 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
workListp = vtxp->getUser<DfgVertex*>();
// Prefetch next item
VL_PREFETCH_RW(workListp);
// This item is now off the work list
vtxp->setUser<DfgVertex*>(nullptr);
// DfgLogic should have been synthesized or removed
UASSERT_OBJ(!vtxp->is<DfgLogic>(), vtxp, "Should not be DfgLogic");
// If used, then nothing to do, so move on
if (vtxp->hasSinks()) continue;
// If temporary used in another graph, we need to keep it
if (const DfgVertexVar* const varp = vtxp->cast<DfgVertexVar>()) {
UASSERT_OBJ(varp->tmpForp(), varp, "Non-temporary variable should not be visited");
if (varp->hasDfgRefs()) continue;
}
// Add sources of unused vertex to work list
vtxp->forEachSource([&](DfgVertex& src) {
// We only remove actual operation vertices in this loop
if (src.is<DfgConst>() || src.is<DfgVertexVar>()) return;
// We only remove actual operation vertices and synthesis temporaries in this loop
if (src.is<DfgConst>()) return;
const DfgVertexVar* const varp = src.cast<DfgVertexVar>();
if (varp && !varp->tmpForp()) return;
// If already in work list then nothing to do
if (src.getUser<DfgVertex*>()) return;
// Actually add to work list.
@@ -282,7 +306,7 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
// Required data types
AstNodeDType* const idxDTypep = srcp->dtypep();
AstNodeDType* const bitDTypep = DfgVertex::dtypeForWidth(1);
AstNodeDType* const bitDTypep = DfgGraph::dtypePacked(1);
AstUnpackArrayDType* const tabDTypep = new AstUnpackArrayDType{
flp, bitDTypep, new AstRange{flp, static_cast<int>(nBits - 1), 0}};
v3Global.rootp()->typeTablep()->addTypesp(tabDTypep);
@@ -453,8 +477,13 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
DfgVarPacked* const varp = vtxp->cast<DfgVarPacked>();
if (!varp) continue;
// Can't remove if it has external drivers
if (!varp->isDrivenFullyByDfg()) continue;
if (!varp->tmpForp()) {
// Can't remove regular variable if it has external drivers
if (!varp->isDrivenFullyByDfg()) continue;
} else {
// Can't remove partially driven used temporaries
if (!varp->isDrivenFullyByDfg() && varp->hasSinks()) continue;
}
// Can't remove if referenced external to the module/netlist
if (varp->hasExtRefs()) continue;