Introduce DFG based combinational logic optimizer (#3527)
Added a new data-flow graph (DFG) based combinational logic optimizer.
The capabilities of this covers a combination of V3Const and V3Gate, but
is also more capable of transforming combinational logic into simplified
forms and more.
This entail adding a new internal representation, `DfgGraph`, and
appropriate `astToDfg` and `dfgToAst` conversion functions. The graph
represents some of the combinational equations (~continuous assignments)
in a module, and for the duration of the DFG passes, it takes over the
role of AstModule. A bulk of the Dfg vertices represent expressions.
These vertex classes, and the corresponding conversions to/from AST are
mostly auto-generated by astgen, together with a DfgVVisitor that can be
used for dynamic dispatch based on vertex (operation) types.
The resulting combinational logic graph (a `DfgGraph`) is then optimized
in various ways. Currently we perform common sub-expression elimination,
variable inlining, and some specific peephole optimizations, but there
is scope for more optimizations in the future using the same
representation. The optimizer is run directly before and after inlining.
The pre inline pass can operate on smaller graphs and hence converges
faster, but still has a chance of substantially reducing the size of the
logic on some designs, making inlining both faster and less memory
intensive. The post inline pass can then optimize across the inlined
module boundaries. No optimization is performed across a module
boundary.
For debugging purposes, each peephole optimization can be disabled
individually via the -fno-dfg-peepnole-<OPT> option, where <OPT> is one
of the optimizations listed in V3DfgPeephole.h, for example
-fno-dfg-peephole-remove-not-not.
The peephole patterns currently implemented were mostly picked based on
the design that inspired this work, and on that design the optimizations
yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As
you can imagine not having to haul around redundant combinational
networks in the rest of the compilation pipeline also helps with memory
consumption, and up to 30% peak memory usage of Verilator was observed
on the same design.
Gains on other arbitrary designs are smaller (and can be improved by
analyzing those designs). For example OpenTitan gains between 1-15%
speedup depending on build type.
2022-09-23 17:46:22 +02:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Dataflow based optimization of combinational logic
|
|
|
|
|
//
|
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2026-01-27 02:24:34 +01:00
|
|
|
// 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
|
Introduce DFG based combinational logic optimizer (#3527)
Added a new data-flow graph (DFG) based combinational logic optimizer.
The capabilities of this covers a combination of V3Const and V3Gate, but
is also more capable of transforming combinational logic into simplified
forms and more.
This entail adding a new internal representation, `DfgGraph`, and
appropriate `astToDfg` and `dfgToAst` conversion functions. The graph
represents some of the combinational equations (~continuous assignments)
in a module, and for the duration of the DFG passes, it takes over the
role of AstModule. A bulk of the Dfg vertices represent expressions.
These vertex classes, and the corresponding conversions to/from AST are
mostly auto-generated by astgen, together with a DfgVVisitor that can be
used for dynamic dispatch based on vertex (operation) types.
The resulting combinational logic graph (a `DfgGraph`) is then optimized
in various ways. Currently we perform common sub-expression elimination,
variable inlining, and some specific peephole optimizations, but there
is scope for more optimizations in the future using the same
representation. The optimizer is run directly before and after inlining.
The pre inline pass can operate on smaller graphs and hence converges
faster, but still has a chance of substantially reducing the size of the
logic on some designs, making inlining both faster and less memory
intensive. The post inline pass can then optimize across the inlined
module boundaries. No optimization is performed across a module
boundary.
For debugging purposes, each peephole optimization can be disabled
individually via the -fno-dfg-peepnole-<OPT> option, where <OPT> is one
of the optimizations listed in V3DfgPeephole.h, for example
-fno-dfg-peephole-remove-not-not.
The peephole patterns currently implemented were mostly picked based on
the design that inspired this work, and on that design the optimizations
yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As
you can imagine not having to haul around redundant combinational
networks in the rest of the compilation pipeline also helps with memory
consumption, and up to 30% peak memory usage of Verilator was observed
on the same design.
Gains on other arbitrary designs are smaller (and can be improved by
analyzing those designs). For example OpenTitan gains between 1-15%
speedup depending on build type.
2022-09-23 17:46:22 +02:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// High level entry points from Ast world to the DFG optimizer.
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
|
2023-10-18 12:37:46 +02:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
Introduce DFG based combinational logic optimizer (#3527)
Added a new data-flow graph (DFG) based combinational logic optimizer.
The capabilities of this covers a combination of V3Const and V3Gate, but
is also more capable of transforming combinational logic into simplified
forms and more.
This entail adding a new internal representation, `DfgGraph`, and
appropriate `astToDfg` and `dfgToAst` conversion functions. The graph
represents some of the combinational equations (~continuous assignments)
in a module, and for the duration of the DFG passes, it takes over the
role of AstModule. A bulk of the Dfg vertices represent expressions.
These vertex classes, and the corresponding conversions to/from AST are
mostly auto-generated by astgen, together with a DfgVVisitor that can be
used for dynamic dispatch based on vertex (operation) types.
The resulting combinational logic graph (a `DfgGraph`) is then optimized
in various ways. Currently we perform common sub-expression elimination,
variable inlining, and some specific peephole optimizations, but there
is scope for more optimizations in the future using the same
representation. The optimizer is run directly before and after inlining.
The pre inline pass can operate on smaller graphs and hence converges
faster, but still has a chance of substantially reducing the size of the
logic on some designs, making inlining both faster and less memory
intensive. The post inline pass can then optimize across the inlined
module boundaries. No optimization is performed across a module
boundary.
For debugging purposes, each peephole optimization can be disabled
individually via the -fno-dfg-peepnole-<OPT> option, where <OPT> is one
of the optimizations listed in V3DfgPeephole.h, for example
-fno-dfg-peephole-remove-not-not.
The peephole patterns currently implemented were mostly picked based on
the design that inspired this work, and on that design the optimizations
yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As
you can imagine not having to haul around redundant combinational
networks in the rest of the compilation pipeline also helps with memory
consumption, and up to 30% peak memory usage of Verilator was observed
on the same design.
Gains on other arbitrary designs are smaller (and can be improved by
analyzing those designs). For example OpenTitan gains between 1-15%
speedup depending on build type.
2022-09-23 17:46:22 +02:00
|
|
|
#include "V3DfgOptimizer.h"
|
|
|
|
|
|
2026-04-01 11:52:56 +02:00
|
|
|
#include "V3Const.h"
|
Introduce DFG based combinational logic optimizer (#3527)
Added a new data-flow graph (DFG) based combinational logic optimizer.
The capabilities of this covers a combination of V3Const and V3Gate, but
is also more capable of transforming combinational logic into simplified
forms and more.
This entail adding a new internal representation, `DfgGraph`, and
appropriate `astToDfg` and `dfgToAst` conversion functions. The graph
represents some of the combinational equations (~continuous assignments)
in a module, and for the duration of the DFG passes, it takes over the
role of AstModule. A bulk of the Dfg vertices represent expressions.
These vertex classes, and the corresponding conversions to/from AST are
mostly auto-generated by astgen, together with a DfgVVisitor that can be
used for dynamic dispatch based on vertex (operation) types.
The resulting combinational logic graph (a `DfgGraph`) is then optimized
in various ways. Currently we perform common sub-expression elimination,
variable inlining, and some specific peephole optimizations, but there
is scope for more optimizations in the future using the same
representation. The optimizer is run directly before and after inlining.
The pre inline pass can operate on smaller graphs and hence converges
faster, but still has a chance of substantially reducing the size of the
logic on some designs, making inlining both faster and less memory
intensive. The post inline pass can then optimize across the inlined
module boundaries. No optimization is performed across a module
boundary.
For debugging purposes, each peephole optimization can be disabled
individually via the -fno-dfg-peepnole-<OPT> option, where <OPT> is one
of the optimizations listed in V3DfgPeephole.h, for example
-fno-dfg-peephole-remove-not-not.
The peephole patterns currently implemented were mostly picked based on
the design that inspired this work, and on that design the optimizations
yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As
you can imagine not having to haul around redundant combinational
networks in the rest of the compilation pipeline also helps with memory
consumption, and up to 30% peak memory usage of Verilator was observed
on the same design.
Gains on other arbitrary designs are smaller (and can be improved by
analyzing those designs). For example OpenTitan gains between 1-15%
speedup depending on build type.
2022-09-23 17:46:22 +02:00
|
|
|
#include "V3Dfg.h"
|
|
|
|
|
#include "V3DfgPasses.h"
|
2026-04-09 14:31:12 +02:00
|
|
|
#include "V3Error.h"
|
Introduce DFG based combinational logic optimizer (#3527)
Added a new data-flow graph (DFG) based combinational logic optimizer.
The capabilities of this covers a combination of V3Const and V3Gate, but
is also more capable of transforming combinational logic into simplified
forms and more.
This entail adding a new internal representation, `DfgGraph`, and
appropriate `astToDfg` and `dfgToAst` conversion functions. The graph
represents some of the combinational equations (~continuous assignments)
in a module, and for the duration of the DFG passes, it takes over the
role of AstModule. A bulk of the Dfg vertices represent expressions.
These vertex classes, and the corresponding conversions to/from AST are
mostly auto-generated by astgen, together with a DfgVVisitor that can be
used for dynamic dispatch based on vertex (operation) types.
The resulting combinational logic graph (a `DfgGraph`) is then optimized
in various ways. Currently we perform common sub-expression elimination,
variable inlining, and some specific peephole optimizations, but there
is scope for more optimizations in the future using the same
representation. The optimizer is run directly before and after inlining.
The pre inline pass can operate on smaller graphs and hence converges
faster, but still has a chance of substantially reducing the size of the
logic on some designs, making inlining both faster and less memory
intensive. The post inline pass can then optimize across the inlined
module boundaries. No optimization is performed across a module
boundary.
For debugging purposes, each peephole optimization can be disabled
individually via the -fno-dfg-peepnole-<OPT> option, where <OPT> is one
of the optimizations listed in V3DfgPeephole.h, for example
-fno-dfg-peephole-remove-not-not.
The peephole patterns currently implemented were mostly picked based on
the design that inspired this work, and on that design the optimizations
yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As
you can imagine not having to haul around redundant combinational
networks in the rest of the compilation pipeline also helps with memory
consumption, and up to 30% peak memory usage of Verilator was observed
on the same design.
Gains on other arbitrary designs are smaller (and can be improved by
analyzing those designs). For example OpenTitan gains between 1-15%
speedup depending on build type.
2022-09-23 17:46:22 +02:00
|
|
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2025-08-05 15:29:33 +02:00
|
|
|
class DataflowOptimize final {
|
Introduce DFG based combinational logic optimizer (#3527)
Added a new data-flow graph (DFG) based combinational logic optimizer.
The capabilities of this covers a combination of V3Const and V3Gate, but
is also more capable of transforming combinational logic into simplified
forms and more.
This entail adding a new internal representation, `DfgGraph`, and
appropriate `astToDfg` and `dfgToAst` conversion functions. The graph
represents some of the combinational equations (~continuous assignments)
in a module, and for the duration of the DFG passes, it takes over the
role of AstModule. A bulk of the Dfg vertices represent expressions.
These vertex classes, and the corresponding conversions to/from AST are
mostly auto-generated by astgen, together with a DfgVVisitor that can be
used for dynamic dispatch based on vertex (operation) types.
The resulting combinational logic graph (a `DfgGraph`) is then optimized
in various ways. Currently we perform common sub-expression elimination,
variable inlining, and some specific peephole optimizations, but there
is scope for more optimizations in the future using the same
representation. The optimizer is run directly before and after inlining.
The pre inline pass can operate on smaller graphs and hence converges
faster, but still has a chance of substantially reducing the size of the
logic on some designs, making inlining both faster and less memory
intensive. The post inline pass can then optimize across the inlined
module boundaries. No optimization is performed across a module
boundary.
For debugging purposes, each peephole optimization can be disabled
individually via the -fno-dfg-peepnole-<OPT> option, where <OPT> is one
of the optimizations listed in V3DfgPeephole.h, for example
-fno-dfg-peephole-remove-not-not.
The peephole patterns currently implemented were mostly picked based on
the design that inspired this work, and on that design the optimizations
yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As
you can imagine not having to haul around redundant combinational
networks in the rest of the compilation pipeline also helps with memory
consumption, and up to 30% peak memory usage of Verilator was observed
on the same design.
Gains on other arbitrary designs are smaller (and can be improved by
analyzing those designs). For example OpenTitan gains between 1-15%
speedup depending on build type.
2022-09-23 17:46:22 +02:00
|
|
|
// NODE STATE
|
2025-08-05 11:24:54 +02:00
|
|
|
// 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
|
2026-03-26 11:04:31 +01:00
|
|
|
// - bit4: Has READWRITE references
|
|
|
|
|
// - bit31-5: Reference count, how many DfgVertexVar represent this variable
|
2025-08-05 11:24:54 +02:00
|
|
|
//
|
|
|
|
|
// AstNode::user2/user3/user4 can be used by various DFG algorithms
|
2025-08-05 15:29:33 +02:00
|
|
|
const VNUser1InUse m_user1InUse;
|
Introduce DFG based combinational logic optimizer (#3527)
Added a new data-flow graph (DFG) based combinational logic optimizer.
The capabilities of this covers a combination of V3Const and V3Gate, but
is also more capable of transforming combinational logic into simplified
forms and more.
This entail adding a new internal representation, `DfgGraph`, and
appropriate `astToDfg` and `dfgToAst` conversion functions. The graph
represents some of the combinational equations (~continuous assignments)
in a module, and for the duration of the DFG passes, it takes over the
role of AstModule. A bulk of the Dfg vertices represent expressions.
These vertex classes, and the corresponding conversions to/from AST are
mostly auto-generated by astgen, together with a DfgVVisitor that can be
used for dynamic dispatch based on vertex (operation) types.
The resulting combinational logic graph (a `DfgGraph`) is then optimized
in various ways. Currently we perform common sub-expression elimination,
variable inlining, and some specific peephole optimizations, but there
is scope for more optimizations in the future using the same
representation. The optimizer is run directly before and after inlining.
The pre inline pass can operate on smaller graphs and hence converges
faster, but still has a chance of substantially reducing the size of the
logic on some designs, making inlining both faster and less memory
intensive. The post inline pass can then optimize across the inlined
module boundaries. No optimization is performed across a module
boundary.
For debugging purposes, each peephole optimization can be disabled
individually via the -fno-dfg-peepnole-<OPT> option, where <OPT> is one
of the optimizations listed in V3DfgPeephole.h, for example
-fno-dfg-peephole-remove-not-not.
The peephole patterns currently implemented were mostly picked based on
the design that inspired this work, and on that design the optimizations
yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As
you can imagine not having to haul around redundant combinational
networks in the rest of the compilation pipeline also helps with memory
consumption, and up to 30% peak memory usage of Verilator was observed
on the same design.
Gains on other arbitrary designs are smaller (and can be improved by
analyzing those designs). For example OpenTitan gains between 1-15%
speedup depending on build type.
2022-09-23 17:46:22 +02:00
|
|
|
|
2025-08-05 15:29:33 +02:00
|
|
|
// STATE
|
|
|
|
|
V3DfgContext m_ctx; // The context holding values that need to persist across multiple graphs
|
|
|
|
|
|
2026-04-09 13:32:57 +02:00
|
|
|
void endOfStage(const std::string& name) {
|
2026-04-10 14:46:59 +02:00
|
|
|
if (VL_UNLIKELY(v3Global.opt.stats())) V3Stats::statsStage("dfg-" + name);
|
2026-03-21 11:49:39 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-09 13:32:57 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 11:49:39 +01:00
|
|
|
// Mark variables with external references
|
|
|
|
|
void markExternallyReferencedVariables(AstNetlist* netlistp) {
|
2026-04-09 14:31:12 +02:00
|
|
|
netlistp->foreach([](AstNode* nodep) {
|
2025-09-19 15:44:34 +02:00
|
|
|
// Check variable flags
|
2026-04-09 14:31:12 +02:00
|
|
|
if (AstVarScope* const vscp = VN_CAST(nodep, VarScope)) {
|
|
|
|
|
const AstVar* const varp = vscp->varp();
|
|
|
|
|
// Force and trace have already been processed
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
const bool hasExtRd = //
|
|
|
|
|
varp->isPrimaryIO() // Top level port - readable
|
|
|
|
|
|| varp->isSigUserRdPublic() // Readable by user
|
|
|
|
|
|| varp->constPoolEntry() // Stored in AstConstPool hashmap, but read only
|
|
|
|
|
;
|
|
|
|
|
const bool hasExtWr = //
|
|
|
|
|
(varp->isPrimaryIO() && varp->isNonOutput()) // Top level port - writable
|
|
|
|
|
|| varp->isSigUserRWPublic() // Writable by user
|
|
|
|
|
;
|
2026-04-09 14:31:12 +02:00
|
|
|
if (hasExtRd) DfgVertexVar::setHasExtRdRefs(vscp);
|
|
|
|
|
if (hasExtWr) DfgVertexVar::setHasExtWrRefs(vscp);
|
|
|
|
|
return;
|
2025-09-10 13:38:49 +02:00
|
|
|
}
|
2026-04-09 14:31:12 +02:00
|
|
|
// 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");
|
2025-09-10 13:38:49 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2026-04-09 14:31:12 +02:00
|
|
|
UASSERT_OBJ(!VN_IS(nodep, VarXRef), nodep, "V3Scope should have removed");
|
2025-09-10 13:38:49 +02:00
|
|
|
// Check cell ports
|
|
|
|
|
if (const AstCell* const cellp = VN_CAST(nodep, Cell)) {
|
2026-04-09 14:31:12 +02:00
|
|
|
// Why does this not hold?
|
|
|
|
|
UASSERT_OBJ(true || !cellp->pinsp(), cellp, "Pins should have been lowered");
|
2025-09-10 13:38:49 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 15:29:33 +02:00
|
|
|
void optimize(DfgGraph& dfg) {
|
2026-02-17 09:28:06 +01:00
|
|
|
// Remove unobservable variabels and logic that drives only such variables
|
|
|
|
|
V3DfgPasses::removeUnobservable(dfg, m_ctx);
|
2026-04-09 13:32:57 +02:00
|
|
|
endOfStage("removeUnobservable", dfg, {});
|
2026-02-17 09:28:06 +01: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.
2025-08-19 16:06:38 +02:00
|
|
|
// Synthesize DfgLogic vertices
|
|
|
|
|
V3DfgPasses::synthesize(dfg, m_ctx);
|
2026-04-09 13:32:57 +02:00
|
|
|
endOfStage("synthesize", dfg, {});
|
2025-08-05 15:29:33 +02:00
|
|
|
|
|
|
|
|
// 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.
|
2026-03-21 11:49:39 +01:00
|
|
|
std::vector<std::unique_ptr<DfgGraph>> cyclicComps = dfg.extractCyclicComponents("cyclic");
|
2026-04-09 13:32:57 +02:00
|
|
|
endOfStage("extractCyclic", dfg, cyclicComps);
|
2025-08-05 15:29:33 +02:00
|
|
|
|
|
|
|
|
// Attempt to convert cyclic components into acyclic ones
|
|
|
|
|
std::vector<std::unique_ptr<DfgGraph>> madeAcyclicComponents;
|
2025-08-08 23:53:12 +02:00
|
|
|
if (v3Global.opt.fDfgBreakCycles()) {
|
2026-03-21 11:49:39 +01:00
|
|
|
for (auto it = cyclicComps.begin(); it != cyclicComps.end();) {
|
2026-07-07 21:51:40 +02:00
|
|
|
const bool madeAcyclic = V3DfgPasses::breakCycles(**it, m_ctx);
|
|
|
|
|
// If not made acyclic, keep it in 'cyclicComps'
|
|
|
|
|
if (!madeAcyclic) {
|
2025-08-05 15:29:33 +02:00
|
|
|
++it;
|
2026-07-07 21:51:40 +02:00
|
|
|
continue;
|
2025-08-05 15:29:33 +02:00
|
|
|
}
|
2026-07-07 21:51:40 +02:00
|
|
|
// Otherwise move to 'madeAcyclicComponents'
|
|
|
|
|
madeAcyclicComponents.emplace_back(std::move(*it));
|
|
|
|
|
it = cyclicComps.erase(it);
|
2025-08-05 15:29:33 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Merge those that were made acyclic back to the graph, this enables optimizing more
|
|
|
|
|
dfg.mergeGraphs(std::move(madeAcyclicComponents));
|
2026-04-09 13:32:57 +02:00
|
|
|
endOfStage("breakCycles", dfg, cyclicComps);
|
Introduce DFG based combinational logic optimizer (#3527)
Added a new data-flow graph (DFG) based combinational logic optimizer.
The capabilities of this covers a combination of V3Const and V3Gate, but
is also more capable of transforming combinational logic into simplified
forms and more.
This entail adding a new internal representation, `DfgGraph`, and
appropriate `astToDfg` and `dfgToAst` conversion functions. The graph
represents some of the combinational equations (~continuous assignments)
in a module, and for the duration of the DFG passes, it takes over the
role of AstModule. A bulk of the Dfg vertices represent expressions.
These vertex classes, and the corresponding conversions to/from AST are
mostly auto-generated by astgen, together with a DfgVVisitor that can be
used for dynamic dispatch based on vertex (operation) types.
The resulting combinational logic graph (a `DfgGraph`) is then optimized
in various ways. Currently we perform common sub-expression elimination,
variable inlining, and some specific peephole optimizations, but there
is scope for more optimizations in the future using the same
representation. The optimizer is run directly before and after inlining.
The pre inline pass can operate on smaller graphs and hence converges
faster, but still has a chance of substantially reducing the size of the
logic on some designs, making inlining both faster and less memory
intensive. The post inline pass can then optimize across the inlined
module boundaries. No optimization is performed across a module
boundary.
For debugging purposes, each peephole optimization can be disabled
individually via the -fno-dfg-peepnole-<OPT> option, where <OPT> is one
of the optimizations listed in V3DfgPeephole.h, for example
-fno-dfg-peephole-remove-not-not.
The peephole patterns currently implemented were mostly picked based on
the design that inspired this work, and on that design the optimizations
yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As
you can imagine not having to haul around redundant combinational
networks in the rest of the compilation pipeline also helps with memory
consumption, and up to 30% peak memory usage of Verilator was observed
on the same design.
Gains on other arbitrary designs are smaller (and can be improved by
analyzing those designs). For example OpenTitan gains between 1-15%
speedup depending on build type.
2022-09-23 17:46:22 +02:00
|
|
|
|
2026-06-11 17:00:30 +02:00
|
|
|
// Remove redundant selects
|
|
|
|
|
V3DfgPasses::removeSelects(dfg, m_ctx.m_removeSelectsContext);
|
|
|
|
|
for (std::unique_ptr<DfgGraph>& compp : cyclicComps) {
|
|
|
|
|
V3DfgPasses::removeSelects(*compp, m_ctx.m_removeSelectsContext);
|
|
|
|
|
}
|
|
|
|
|
endOfStage("removeSelects", dfg, cyclicComps);
|
|
|
|
|
|
2025-08-05 15:29:33 +02:00
|
|
|
// Split the acyclic DFG into [weakly] connected components
|
2026-03-21 11:49:39 +01:00
|
|
|
std::vector<std::unique_ptr<DfgGraph>> acyclicComps = dfg.splitIntoComponents("acyclic");
|
2025-08-05 15:29:33 +02:00
|
|
|
UASSERT(dfg.size() == 0, "DfgGraph should have become empty");
|
2026-04-09 13:32:57 +02:00
|
|
|
endOfStage("splitAcyclic", dfg, acyclicComps);
|
2022-09-28 15:42:18 +02:00
|
|
|
|
2025-09-10 13:38:49 +02:00
|
|
|
// Optimize each acyclic component
|
2026-04-09 13:32:57 +02:00
|
|
|
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);
|
2026-04-09 14:32:16 +02:00
|
|
|
// Accumulate patterns for reporting
|
2026-04-21 13:07:19 +02:00
|
|
|
if (v3Global.opt.dumpDfgPatterns()) {
|
2026-04-09 14:32:16 +02:00
|
|
|
V3DfgPasses::dumpPatterns(acyclicComps);
|
2026-04-21 13:07:19 +02:00
|
|
|
endOfStage("dumpPatterns");
|
2026-04-09 14:32:16 +02:00
|
|
|
}
|
2026-04-09 13:32:57 +02:00
|
|
|
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);
|
|
|
|
|
|
2025-09-10 13:38:49 +02:00
|
|
|
// Merge everything back under the main DFG
|
2026-03-21 11:49:39 +01:00
|
|
|
dfg.mergeGraphs(std::move(acyclicComps));
|
|
|
|
|
dfg.mergeGraphs(std::move(cyclicComps));
|
2026-04-09 13:32:57 +02:00
|
|
|
endOfStage("optimized", dfg, {});
|
2025-07-01 23:55:08 +02:00
|
|
|
|
2025-09-10 13:38:49 +02:00
|
|
|
// 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);
|
2026-04-09 13:32:57 +02:00
|
|
|
endOfStage("regularize", dfg, {});
|
2025-08-05 15:29:33 +02:00
|
|
|
}
|
2025-07-01 23:55:08 +02:00
|
|
|
|
2026-04-01 11:52:56 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 14:31:12 +02:00
|
|
|
DataflowOptimize(AstNetlist* netlistp) {
|
2025-08-26 12:24:15 +02:00
|
|
|
// Mark interfaces that might be referenced by a virtual interface
|
|
|
|
|
if (v3Global.hasVirtIfaces()) {
|
2025-09-02 17:50:40 +02:00
|
|
|
netlistp->typeTablep()->foreach([](const AstIfaceRefDType* nodep) {
|
2025-08-26 12:24:15 +02:00
|
|
|
if (!nodep->isVirtual()) return;
|
|
|
|
|
nodep->ifaceViaCellp()->setHasVirtualRef();
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-09-10 13:38:49 +02:00
|
|
|
// Mark variables with external references
|
2026-03-21 11:49:39 +01:00
|
|
|
markExternallyReferencedVariables(netlistp);
|
2026-04-09 13:32:57 +02:00
|
|
|
// Dump stage stats
|
|
|
|
|
endOfStage("init");
|
2026-04-09 14:31:12 +02:00
|
|
|
// 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);
|
2026-04-09 13:32:57 +02:00
|
|
|
endOfStage("astToDfg", *dfgp, {});
|
2026-04-09 14:31:12 +02:00
|
|
|
// Actually process the graph
|
|
|
|
|
optimize(*dfgp);
|
|
|
|
|
// Convert back to Ast
|
|
|
|
|
V3DfgPasses::dfgToAst(*dfgp, m_ctx);
|
2026-04-09 13:32:57 +02:00
|
|
|
endOfStage("dfgToAst", *dfgp, {});
|
2026-04-09 14:31:12 +02:00
|
|
|
// Some sentrees might have become constant, remove them
|
|
|
|
|
removeNeverActives(netlistp);
|
2025-09-07 21:38:50 +02:00
|
|
|
// Reset interned types so the corresponding Ast types can be garbage collected
|
|
|
|
|
DfgDataType::reset();
|
2026-04-09 13:32:57 +02:00
|
|
|
// Dump stage stats
|
|
|
|
|
endOfStage("fini");
|
2025-08-05 15:29:33 +02:00
|
|
|
}
|
2024-02-11 16:41:10 +01:00
|
|
|
|
2025-08-05 15:29:33 +02:00
|
|
|
public:
|
2026-04-09 14:31:12 +02:00
|
|
|
static void apply(AstNetlist* netlistp) { DataflowOptimize{netlistp}; }
|
2025-08-05 15:29:33 +02:00
|
|
|
};
|
2024-02-11 16:41:10 +01:00
|
|
|
|
2026-04-09 14:31:12 +02:00
|
|
|
void V3DfgOptimizer::optimize(AstNetlist* netlistp) {
|
2025-08-05 15:29:33 +02:00
|
|
|
UINFO(2, __FUNCTION__ << ":");
|
2026-04-09 14:31:12 +02:00
|
|
|
DataflowOptimize::apply(netlistp);
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("dfg-optimize", 0, dumpTreeEitherLevel() >= 3);
|
Introduce DFG based combinational logic optimizer (#3527)
Added a new data-flow graph (DFG) based combinational logic optimizer.
The capabilities of this covers a combination of V3Const and V3Gate, but
is also more capable of transforming combinational logic into simplified
forms and more.
This entail adding a new internal representation, `DfgGraph`, and
appropriate `astToDfg` and `dfgToAst` conversion functions. The graph
represents some of the combinational equations (~continuous assignments)
in a module, and for the duration of the DFG passes, it takes over the
role of AstModule. A bulk of the Dfg vertices represent expressions.
These vertex classes, and the corresponding conversions to/from AST are
mostly auto-generated by astgen, together with a DfgVVisitor that can be
used for dynamic dispatch based on vertex (operation) types.
The resulting combinational logic graph (a `DfgGraph`) is then optimized
in various ways. Currently we perform common sub-expression elimination,
variable inlining, and some specific peephole optimizations, but there
is scope for more optimizations in the future using the same
representation. The optimizer is run directly before and after inlining.
The pre inline pass can operate on smaller graphs and hence converges
faster, but still has a chance of substantially reducing the size of the
logic on some designs, making inlining both faster and less memory
intensive. The post inline pass can then optimize across the inlined
module boundaries. No optimization is performed across a module
boundary.
For debugging purposes, each peephole optimization can be disabled
individually via the -fno-dfg-peepnole-<OPT> option, where <OPT> is one
of the optimizations listed in V3DfgPeephole.h, for example
-fno-dfg-peephole-remove-not-not.
The peephole patterns currently implemented were mostly picked based on
the design that inspired this work, and on that design the optimizations
yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As
you can imagine not having to haul around redundant combinational
networks in the rest of the compilation pipeline also helps with memory
consumption, and up to 30% peak memory usage of Verilator was observed
on the same design.
Gains on other arbitrary designs are smaller (and can be improved by
analyzing those designs). For example OpenTitan gains between 1-15%
speedup depending on build type.
2022-09-23 17:46:22 +02:00
|
|
|
}
|