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: Implementations of simple passes over DfgGraph
|
|
|
|
|
//
|
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
|
|
|
|
// Copyright 2003-2022 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
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
|
|
|
|
|
#include "config_build.h"
|
|
|
|
|
|
|
|
|
|
#include "V3DfgPasses.h"
|
|
|
|
|
|
|
|
|
|
#include "V3Dfg.h"
|
|
|
|
|
#include "V3Global.h"
|
|
|
|
|
#include "V3String.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
|
|
|
|
V3DfgCseContext::~V3DfgCseContext() {
|
|
|
|
|
V3Stats::addStat("Optimizations, DFG " + m_label + " CSE, expressions eliminated",
|
|
|
|
|
m_eliminated);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DfgRemoveVarsContext::~DfgRemoveVarsContext() {
|
|
|
|
|
V3Stats::addStat("Optimizations, DFG " + m_label + " Remove vars, variables removed",
|
|
|
|
|
m_removed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::string getPrefix(const std::string& label) {
|
|
|
|
|
if (label.empty()) return "";
|
|
|
|
|
std::string str = VString::removeWhitespace(label);
|
|
|
|
|
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { //
|
|
|
|
|
return c == ' ' ? '-' : std::tolower(c);
|
|
|
|
|
});
|
|
|
|
|
str += "-";
|
|
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
V3DfgOptimizationContext::V3DfgOptimizationContext(const std::string& label)
|
|
|
|
|
: m_label{label}
|
|
|
|
|
, m_prefix{getPrefix(label)} {}
|
|
|
|
|
|
|
|
|
|
V3DfgOptimizationContext::~V3DfgOptimizationContext() {
|
|
|
|
|
const string prefix = "Optimizations, DFG " + m_label + " ";
|
|
|
|
|
V3Stats::addStat(prefix + "General, modules", m_modules);
|
2022-09-25 17:03:15 +02:00
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, coalesced assignments", m_coalescedAssignments);
|
2022-09-26 15:21:05 +02:00
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, input equations", m_inputEquations);
|
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
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, representable", m_representable);
|
|
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, non-representable (dtype)", m_nonRepDType);
|
|
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, non-representable (impure)", m_nonRepImpure);
|
|
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, non-representable (timing)", m_nonRepTiming);
|
|
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, non-representable (lhs)", m_nonRepLhs);
|
|
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, non-representable (node)", m_nonRepNode);
|
|
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, non-representable (unknown)", m_nonRepUnknown);
|
|
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, non-representable (var ref)", m_nonRepVarRef);
|
|
|
|
|
V3Stats::addStat(prefix + "Ast2Dfg, non-representable (width)", m_nonRepWidth);
|
|
|
|
|
V3Stats::addStat(prefix + "Dfg2Ast, intermediate variables", m_intermediateVars);
|
|
|
|
|
V3Stats::addStat(prefix + "Dfg2Ast, replaced variables", m_replacedVars);
|
|
|
|
|
V3Stats::addStat(prefix + "Dfg2Ast, result equations", m_resultEquations);
|
2022-09-26 15:21:05 +02:00
|
|
|
|
|
|
|
|
// Check the stats are consistent
|
|
|
|
|
UASSERT(m_inputEquations
|
|
|
|
|
== m_representable + m_nonRepDType + m_nonRepImpure + m_nonRepTiming + m_nonRepLhs
|
|
|
|
|
+ m_nonRepNode + m_nonRepUnknown + m_nonRepVarRef + m_nonRepWidth,
|
|
|
|
|
"Inconsistent statistics");
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 'Inline' DfgVar nodes with known drivers
|
|
|
|
|
void V3DfgPasses::inlineVars(DfgGraph& dfg) {
|
|
|
|
|
dfg.forEachVertex([](DfgVertex& vtx) {
|
|
|
|
|
// For each DfgVar that has a known driver
|
|
|
|
|
if (DfgVar* const varVtxp = vtx.cast<DfgVar>()) {
|
2022-09-25 17:03:15 +02:00
|
|
|
if (varVtxp->isDrivenFullyByDfg()) {
|
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
|
|
|
// Make consumers of the DfgVar consume the driver directly
|
2022-09-25 17:03:15 +02:00
|
|
|
DfgVertex* const driverp = varVtxp->source(0);
|
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
|
|
|
varVtxp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Common subexpression elimination
|
|
|
|
|
void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) {
|
|
|
|
|
DfgVertex::HashCache hashCache;
|
|
|
|
|
DfgVertex::EqualsCache equalsCache;
|
|
|
|
|
std::unordered_multimap<V3Hash, DfgVertex*> verticesWithEqualHashes;
|
|
|
|
|
|
|
|
|
|
// In reverse, as the graph is sometimes in reverse topological order already
|
|
|
|
|
dfg.forEachVertexInReverse([&](DfgVertex& vtx) {
|
|
|
|
|
// Don't merge constants
|
|
|
|
|
if (vtx.is<DfgConst>()) return;
|
|
|
|
|
// For everything else...
|
|
|
|
|
const V3Hash hash = vtx.hash(hashCache);
|
|
|
|
|
auto pair = verticesWithEqualHashes.equal_range(hash);
|
|
|
|
|
for (auto it = pair.first, end = pair.second; it != end; ++it) {
|
|
|
|
|
DfgVertex* const candidatep = it->second;
|
|
|
|
|
if (candidatep->equals(vtx, equalsCache)) {
|
|
|
|
|
++ctx.m_eliminated;
|
|
|
|
|
vtx.replaceWith(candidatep);
|
|
|
|
|
vtx.unlinkDelete(dfg);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
verticesWithEqualHashes.emplace(hash, &vtx);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) {
|
|
|
|
|
dfg.forEachVertex([&](DfgVertex& vtx) {
|
|
|
|
|
// We can eliminate certain redundant DfgVar vertices
|
|
|
|
|
DfgVar* const varp = vtx.cast<DfgVar>();
|
|
|
|
|
if (!varp) return;
|
|
|
|
|
|
|
|
|
|
// Can't remove if it has consumers
|
|
|
|
|
if (varp->hasSinks()) return;
|
|
|
|
|
|
|
|
|
|
// Can't remove if read in the module and driven here (i.e.: it's an output of the DFG)
|
2022-09-25 17:03:15 +02:00
|
|
|
if (varp->hasModRefs() && varp->isDrivenByDfg()) return;
|
|
|
|
|
|
|
|
|
|
// Can't remove if only partially driven by the DFG
|
|
|
|
|
if (varp->isDrivenByDfg() && !varp->isDrivenFullyByDfg()) return;
|
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
|
|
|
|
|
|
|
|
// Can't remove if referenced externally, or other special reasons
|
|
|
|
|
if (varp->keep()) return;
|
|
|
|
|
|
|
|
|
|
// If the driver of this variable has multiple non-variable sinks, then we would need
|
|
|
|
|
// a temporary when rendering the graph. Instead of introducing a temporary, keep the
|
|
|
|
|
// first variable that is driven by that driver
|
2022-09-25 17:03:15 +02:00
|
|
|
if (varp->isDrivenByDfg()) {
|
|
|
|
|
DfgVertex* const driverp = varp->source(0);
|
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
|
|
|
unsigned nonVarSinks = 0;
|
|
|
|
|
const DfgVar* firstSinkVarp = nullptr;
|
|
|
|
|
const bool keepFirst = driverp->findSink<DfgVertex>([&](const DfgVertex& sink) {
|
|
|
|
|
if (const DfgVar* const sinkVarp = sink.cast<DfgVar>()) {
|
|
|
|
|
if (!firstSinkVarp) firstSinkVarp = sinkVarp;
|
|
|
|
|
} else {
|
|
|
|
|
++nonVarSinks;
|
|
|
|
|
}
|
|
|
|
|
// We can stop as soon as we found the first var, and 2 non-var sinks
|
|
|
|
|
return firstSinkVarp && nonVarSinks >= 2;
|
|
|
|
|
});
|
|
|
|
|
// Keep this DfgVar if needed
|
|
|
|
|
if (keepFirst && firstSinkVarp == varp) return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-25 17:03:15 +02:00
|
|
|
// OK, we can delete this DfgVar
|
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
|
|
|
++ctx.m_removed;
|
|
|
|
|
|
|
|
|
|
// If not referenced outside the DFG, then also delete the referenced AstVar,
|
|
|
|
|
// as it is now unused.
|
|
|
|
|
if (!varp->hasRefs()) varp->varp()->unlinkFrBack()->deleteTree();
|
|
|
|
|
|
|
|
|
|
// Unlink and delete vertex
|
|
|
|
|
vtx.unlinkDelete(dfg);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void V3DfgPasses::removeUnused(DfgGraph& dfg) {
|
|
|
|
|
const auto processVertex = [&](DfgVertex& vtx) {
|
|
|
|
|
// Keep variables
|
|
|
|
|
if (vtx.is<DfgVar>()) return false;
|
|
|
|
|
// Keep if it has sinks
|
|
|
|
|
if (vtx.hasSinks()) return false;
|
|
|
|
|
// Unlink and delete vertex
|
|
|
|
|
vtx.unlinkDelete(dfg);
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dfg.runToFixedPoint(processVertex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
|
|
|
|
|
// There is absolutely nothing useful we can do with a graph of size 2 or less
|
|
|
|
|
if (dfg.size() <= 2) return;
|
|
|
|
|
|
2022-09-25 17:03:15 +02:00
|
|
|
// We consider a DFG trivial if it contains no more than 1 non-variable, non-constant vertex,
|
|
|
|
|
// or if if it contains a DfgConcat, which can be introduced through assinment coalescing.
|
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
|
|
|
unsigned excitingVertices = 0;
|
|
|
|
|
const bool isTrivial = !dfg.findVertex<DfgVertex>([&](const DfgVertex& vtx) { //
|
|
|
|
|
if (vtx.is<DfgVar>()) return false;
|
|
|
|
|
if (vtx.is<DfgConst>()) return false;
|
2022-09-25 17:03:15 +02:00
|
|
|
if (vtx.is<DfgConcat>()) return true;
|
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
|
|
|
return ++excitingVertices >= 2;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
int passNumber = 0;
|
|
|
|
|
|
|
|
|
|
const auto apply = [&](int dumpLevel, const string name, std::function<void()> pass) {
|
|
|
|
|
pass();
|
|
|
|
|
if (dumpDfg() >= dumpLevel) {
|
|
|
|
|
const string strippedName = VString::removeWhitespace(name);
|
|
|
|
|
const string label
|
|
|
|
|
= ctx.prefix() + "pass-" + cvtToStr(passNumber) + "-" + strippedName;
|
|
|
|
|
dfg.dumpDotFilePrefixed(label);
|
|
|
|
|
}
|
|
|
|
|
++passNumber;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!isTrivial) {
|
|
|
|
|
// Optimize non-trivial graph
|
|
|
|
|
if (dumpDfg() >= 8) { dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "input"); }
|
|
|
|
|
apply(3, "input ", [&]() {});
|
|
|
|
|
apply(4, "inlineVars ", [&]() { inlineVars(dfg); });
|
|
|
|
|
apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext0); });
|
|
|
|
|
if (v3Global.opt.fDfgPeephole()) {
|
|
|
|
|
apply(4, "peephole ", [&]() { peephole(dfg, ctx.m_peepholeContext); });
|
|
|
|
|
}
|
|
|
|
|
apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); });
|
|
|
|
|
apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext1); });
|
|
|
|
|
apply(3, "optimized ", [&]() { removeUnused(dfg); });
|
|
|
|
|
if (dumpDfg() >= 8) { dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized"); }
|
|
|
|
|
} else {
|
|
|
|
|
// We can still eliminate redundancies from trivial graphs
|
|
|
|
|
apply(5, "trivial-input ", [&]() {});
|
|
|
|
|
apply(6, "trivial-inlineVars ", [&]() { inlineVars(dfg); });
|
|
|
|
|
apply(5, "trivial-optimized ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); });
|
|
|
|
|
}
|
|
|
|
|
}
|