Optimize DFG before V3Gate (#6141)

This commit is contained in:
Geza Lore 2025-07-01 22:55:08 +01:00 committed by GitHub
parent e015805194
commit 7a3f1f16ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 555 additions and 181 deletions

View File

@ -597,8 +597,8 @@ Summary:
.. option:: -fno-dfg
Rarely needed. Disable all use of the DFG-based combinational logic
optimizer. Alias for :vlopt:`-fno-dfg-pre-inline` and
:vlopt:`-fno-dfg-post-inline`.
optimizer. Alias for :vlopt:`-fno-dfg-pre-inline`,
:vlopt:`-fno-dfg-post-inline` and :vlopt:`-fno-dfg-scoped`.
.. option:: -fno-dfg-peephole
@ -616,6 +616,10 @@ Summary:
Rarely needed. Do not apply the DFG optimizer before inlining.
.. option:: -fno-dfg-scoped
Rarely needed. Do not apply the DFG optimizer across module scopes.
.. option:: -fno-expand
.. option:: -fno-func-opt

View File

@ -26,8 +26,8 @@ VL_DEFINE_DEBUG_FUNCTIONS;
// DfgGraph
//------------------------------------------------------------------------------
DfgGraph::DfgGraph(AstModule& module, const string& name)
: m_modulep{&module}
DfgGraph::DfgGraph(AstModule* modulep, const string& name)
: m_modulep{modulep}
, m_name{name} {}
DfgGraph::~DfgGraph() {
@ -69,14 +69,31 @@ std::string DfgGraph::makeUniqueName(const std::string& prefix, size_t n) {
return "__Vdfg" + prefix + m_tmpNameStub + std::to_string(n);
}
DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name, AstNodeDType* dtypep) {
// Add AstVar to containing module
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
modulep()->addStmtsp(varp);
DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name, AstNodeDType* dtypep,
AstScope* scopep) {
UASSERT_OBJ(!!scopep != !!modulep(), flp,
"makeNewVar scopep should only be provided for a scoped DfgGraph");
// Create and return the corresponding variable vertex
if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) return new DfgVarArray{*this, varp};
return new DfgVarPacked{*this, varp};
// Create AstVar
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
if (scopep) {
// Add AstVar to the scope's module
scopep->modp()->addStmtsp(varp);
// Create AstVarScope
AstVarScope* const vscp = new AstVarScope{flp, scopep, varp};
// Add to scope
scopep->addVarsp(vscp);
// Create and return the corresponding variable vertex
if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) return new DfgVarArray{*this, vscp};
return new DfgVarPacked{*this, vscp};
} else {
// Add AstVar to containing module
modulep()->addStmtsp(varp);
// Create and return the corresponding variable vertex
if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) return new DfgVarArray{*this, varp};
return new DfgVarPacked{*this, varp};
}
}
static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; }
@ -85,9 +102,10 @@ static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx)
static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
if (const DfgVarPacked* const varVtxp = vtx.cast<DfgVarPacked>()) {
AstNode* const nodep = varVtxp->nodep();
AstVar* const varp = varVtxp->varp();
os << toDotId(vtx);
os << " [label=\"" << varp->name() << "\nW" << varVtxp->width() << " / F"
os << " [label=\"" << nodep->name() << "\nW" << varVtxp->width() << " / F"
<< varVtxp->fanout() << '"';
if (varp->direction() == VDirection::INPUT) {
@ -112,10 +130,11 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
}
if (const DfgVarArray* const arrVtxp = vtx.cast<DfgVarArray>()) {
AstNode* const nodep = arrVtxp->nodep();
AstVar* const varp = arrVtxp->varp();
const int elements = VN_AS(arrVtxp->dtypep(), UnpackArrayDType)->elementsConst();
os << toDotId(vtx);
os << " [label=\"" << varp->name() << "[" << elements << "]\"";
os << " [label=\"" << nodep->name() << "[" << elements << "]\"";
if (varp->direction() == VDirection::INPUT) {
os << ", shape=box3d, style=filled, fillcolor=chartreuse2"; // Green
} else if (varp->direction() == VDirection::OUTPUT) {
@ -299,7 +318,7 @@ void DfgGraph::dumpDotAllVarConesPrefixed(const string& label) const {
if (!sinkp) return;
// Open output file
const string coneName{prefix + sinkp->varp()->name()};
const string coneName{prefix + sinkp->nodep()->name()};
const string fileName{v3Global.debugFilename(coneName) + ".dot"};
const std::unique_ptr<std::ofstream> os{V3File::new_ofstream(fileName)};
if (os->fail()) v3fatal("Can't write file: " << fileName);
@ -476,7 +495,7 @@ DfgVarPacked* DfgVertex::getResultVar() {
return;
}
// Prefer the one with the lexically smaller name
if (const int cmp = resp->varp()->name().compare(varp->varp()->name())) {
if (const int cmp = resp->nodep()->name().compare(varp->nodep()->name())) {
if (cmp > 0) resp = varp;
return;
}
@ -485,6 +504,38 @@ DfgVarPacked* DfgVertex::getResultVar() {
return resp;
}
AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED {
// If this is a variable, we are done
if (DfgVertexVar* const varp = this->cast<DfgVertexVar>()) return varp->varScopep()->scopep();
// Try the result var first if instructed (usully only in the recursive case)
if (tryResultVar) {
if (DfgVertexVar* const varp = this->getResultVar()) return varp->varScopep()->scopep();
}
// Look up cache
const auto pair = cache.emplace(this, nullptr);
if (pair.second) {
// Find scope based on sources, falling back on the root scope
AstScope* const rootp = v3Global.rootp()->topScopep()->scopep();
AstScope* foundp = rootp;
const auto edges = sourceEdges();
for (size_t i = 0; i < edges.second; ++i) {
DfgEdge& edge = edges.first[i];
foundp = edge.sourcep()->scopep(cache, true);
if (foundp != rootp) break;
}
pair.first->second = foundp;
}
// If the cache entry exists, but have not set the mapping yet, then we have a circualr graph
UASSERT_OBJ(pair.first->second, this,
"DfgVertex::scopep called on graph with circular operations");
// Done
return pair.first->second;
}
void DfgVertex::unlinkDelete(DfgGraph& dfg) {
// Unlink source edges
forEachSourceEdge([](DfgEdge& edge, size_t) { edge.unlinkSource(); });
@ -521,15 +572,17 @@ V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; }
// DfgVertexVar ----------
bool DfgVertexVar::selfEquals(const DfgVertex& that) const {
UASSERT_OBJ(varp() != that.as<DfgVertexVar>()->varp(), this,
"There should only be one DfgVertexVar for a given AstVar");
UASSERT_OBJ(nodep()->type() == that.as<DfgVertexVar>()->nodep()->type(), this,
"Both DfgVertexVar should be scoped or unscoped");
UASSERT_OBJ(nodep() != that.as<DfgVertexVar>()->nodep(), this,
"There should only be one DfgVertexVar for a given AstVar or AstVarScope");
return false;
}
V3Hash DfgVertexVar::selfHash() const {
V3Hash hash;
hash += m_varp->name();
hash += m_varp->varType();
hash += nodep()->name();
hash += varp()->varType();
return hash;
}

View File

@ -203,16 +203,17 @@ public:
// Return data type used to represent the type of 'nodep' when converted to a DfgVertex
static AstNodeDType* dtypeFor(const AstNode* nodep) {
UDEBUGONLY(UASSERT_OBJ(isSupportedDType(nodep->dtypep()), nodep, "Unsupported dtype"););
const AstNodeDType* const dtypep = nodep->dtypep()->skipRefp();
UDEBUGONLY(UASSERT_OBJ(isSupportedDType(dtypep), nodep, "Unsupported dtype"););
// For simplicity, all packed types are represented with a fixed type
if (AstUnpackArrayDType* const typep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) {
if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) {
AstNodeDType* const adtypep = new AstUnpackArrayDType{
typep->fileline(), dtypeForWidth(typep->subDTypep()->width()),
typep->rangep()->cloneTree(false)};
v3Global.rootp()->typeTablep()->addTypesp(adtypep);
return adtypep;
}
return dtypeForWidth(nodep->width());
return dtypeForWidth(dtypep->width());
}
// Source location
@ -284,6 +285,18 @@ public:
// or nullptr if no such variable exists in the graph. This is O(fanout).
DfgVarPacked* getResultVar() VL_MT_DISABLED;
// Cache type for 'scopep' below
using ScopeCache = std::unordered_map<const DfgVertex*, AstScope*>;
// Retrieve the prefred AstScope this vertex belongs to. For variable
// vertices this is defined. For operation vertices, we try to find a
// scope based on variables in the upstream logic cone (inputs). If
// there isn't one, (beceuse the whole upstream cone is constant...),
// then the root scope is returned. If 'tryResultVar' is true, we will
// condier the scope of 'getResultVar' first, if it exists.
// Only call this with a scoped DfgGraph
AstScope* scopep(ScopeCache& cache, bool tryResultVar = false) VL_MT_DISABLED;
// If the node has a single sink, return it, otherwise return nullptr
DfgVertex* singleSink() const {
return m_sinksp && !m_sinksp->m_nextp ? m_sinksp->m_sinkp : nullptr;
@ -643,14 +656,15 @@ class DfgGraph final {
size_t m_size = 0; // Number of vertices in the graph
uint32_t m_userCurrent = 0; // Vertex user data generation number currently in use
uint32_t m_userCnt = 0; // Vertex user data generation counter
// Parent of the graph (i.e.: the module containing the logic represented by this graph).
// Parent of the graph (i.e.: the module containing the logic represented by this graph),
// or nullptr when run after V3Scope
AstModule* const m_modulep;
const std::string m_name; // Name of graph - need not be unique
std::string m_tmpNameStub{""}; // Name stub for temporary variables - computed lazy
public:
// CONSTRUCTOR
explicit DfgGraph(AstModule& module, const string& name = "") VL_MT_DISABLED;
explicit DfgGraph(AstModule* modulep, const string& name = "") VL_MT_DISABLED;
~DfgGraph() VL_MT_DISABLED;
VL_UNCOPYABLE(DfgGraph);
@ -662,7 +676,7 @@ public:
inline void removeVertex(DfgVertex& vtx);
// Number of vertices in this graph
size_t size() const { return m_size; }
// Parent module
// Parent module - or nullptr when run after V3Scope
AstModule* modulep() const { return m_modulep; }
// Name of this graph
const string& name() const { return m_name; }
@ -699,8 +713,11 @@ public:
// must be unique (as a pair) in each invocation for this graph.
std::string makeUniqueName(const std::string& prefix, size_t n) VL_MT_DISABLED;
// Create a new variable with the given name and data type
DfgVertexVar* makeNewVar(FileLine*, const std::string& name, AstNodeDType*) VL_MT_DISABLED;
// Create a new variable with the given name and data type. For a Scoped
// Dfg, the AstScope where the corresponding AstVarScope will be inserted
// must be provided
DfgVertexVar* makeNewVar(FileLine*, const std::string& name, AstNodeDType*,
AstScope*) VL_MT_DISABLED;
// Split this graph into individual components (unique sub-graphs with no edges between them).
// Also removes any vertices that are not weakly connected to any variable.
@ -890,6 +907,24 @@ bool DfgVertex::isOnes() const {
return false;
}
//------------------------------------------------------------------------------
// Inline method definitions - for DfgVertexVar
//------------------------------------------------------------------------------
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp, uint32_t initialCapacity)
: DfgVertexVariadic{dfg, type, varp->fileline(), dtypeFor(varp), initialCapacity}
, m_varp{varp}
, m_varScopep{nullptr} {
UASSERT_OBJ(dfg.modulep(), varp, "Un-scoped DfgVertexVar created in scoped DfgGraph");
}
DfgVertexVar::DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp,
uint32_t initialCapacity)
: DfgVertexVariadic{dfg, type, vscp->fileline(), dtypeFor(vscp), initialCapacity}
, m_varp{vscp->varp()}
, m_varScopep{vscp} {
UASSERT_OBJ(!dfg.modulep(), vscp, "Scoped DfgVertexVar created in un-scoped DfgGraph");
}
//------------------------------------------------------------------------------
// Inline method definitions - for DfgGraph
//------------------------------------------------------------------------------

View File

@ -69,6 +69,7 @@ DfgSliceSel* makeVertex<DfgSliceSel, AstSliceSel>(const AstSliceSel*, DfgGraph&)
} // namespace
template <bool T_Scoped>
class AstToDfgVisitor final : public VNVisitor {
// NODE STATE
@ -87,6 +88,9 @@ class AstToDfgVisitor final : public VNVisitor {
, m_lsb{lsb} {}
};
using RootType = std::conditional_t<T_Scoped, AstNetlist, AstModule>;
using VariableType = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
// STATE
DfgGraph* const m_dfgp; // The graph being built
@ -98,14 +102,33 @@ class AstToDfgVisitor final : public VNVisitor {
std::vector<DfgVarArray*> m_varArrayps; // All the DfgVarArray vertices we created.
// METHODS
static VariableType* getTarget(const AstVarRef* refp) {
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return reinterpret_cast<VariableType*>(refp->varScopep());
} else {
return reinterpret_cast<VariableType*>(refp->varp());
}
}
static AstVar* getAstVar(VariableType* vp) {
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return reinterpret_cast<AstVarScope*>(vp)->varp();
} else {
return reinterpret_cast<AstVar*>(vp);
}
}
void markReferenced(AstNode* nodep) {
nodep->foreach([this](const AstVarRef* refp) {
// No need to (and in fact cannot) mark variables with unsupported dtypes
if (!DfgVertex::isSupportedDType(refp->varp()->dtypep())) return;
VariableType* const tgtp = getTarget(refp);
// Mark vertex as having a module reference outside current DFG
getNet(refp->varp())->setHasModRefs();
getNet(tgtp)->setHasModRefs();
// Mark variable as written from non-DFG logic
if (refp->access().isWriteOrRW()) refp->varp()->user3(true);
if (refp->access().isWriteOrRW()) tgtp->user3(true);
});
}
@ -116,24 +139,24 @@ class AstToDfgVisitor final : public VNVisitor {
m_uncommittedVertices.clear();
}
DfgVertexVar* getNet(AstVar* varp) {
if (!varp->user1p()) {
// Note DfgVertexVar vertices are not added to m_uncommittedVertices, because we
DfgVertexVar* getNet(VariableType* vp) {
if (!vp->user1p()) {
// vp DfgVertexVar vertices are not added to m_uncommittedVertices, because we
// want to hold onto them via AstVar::user1p, and the AstVar might be referenced via
// multiple AstVarRef instances, so we will never revert a DfgVertexVar once
// created. We will delete unconnected variable vertices at the end.
if (VN_IS(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
DfgVarArray* const vtxp = new DfgVarArray{*m_dfgp, varp};
varp->user1p();
if (VN_IS(vp->dtypep()->skipRefp(), UnpackArrayDType)) {
DfgVarArray* const vtxp = new DfgVarArray{*m_dfgp, vp};
vp->user1p();
m_varArrayps.push_back(vtxp);
varp->user1p(vtxp);
vp->user1p(vtxp);
} else {
DfgVarPacked* const vtxp = new DfgVarPacked{*m_dfgp, varp};
DfgVarPacked* const vtxp = new DfgVarPacked{*m_dfgp, vp};
m_varPackedps.push_back(vtxp);
varp->user1p(vtxp);
vp->user1p(vtxp);
}
}
return varp->user1u().to<DfgVertexVar*>();
return vp->user1u().template to<DfgVertexVar*>();
}
DfgVertex* getVertex(AstNode* nodep) {
@ -167,7 +190,7 @@ class AstToDfgVisitor final : public VNVisitor {
visit(vrefp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) return false;
getVertex(vrefp)->as<DfgVarPacked>()->addDriver(flp, 0, vtxp);
getVertex(vrefp)->template as<DfgVarPacked>()->addDriver(flp, 0, vtxp);
return true;
}
if (AstSel* const selp = VN_CAST(nodep, Sel)) {
@ -181,7 +204,7 @@ class AstToDfgVisitor final : public VNVisitor {
visit(vrefp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) return false;
getVertex(vrefp)->as<DfgVarPacked>()->addDriver(flp, lsbp->toUInt(), vtxp);
getVertex(vrefp)->template as<DfgVarPacked>()->addDriver(flp, lsbp->toUInt(), vtxp);
return true;
}
if (AstArraySel* const selp = VN_CAST(nodep, ArraySel)) {
@ -195,7 +218,7 @@ class AstToDfgVisitor final : public VNVisitor {
visit(vrefp);
// cppcheck-has-bug-suppress knownConditionTrueFalse
if (m_foundUnhandled) return false;
getVertex(vrefp)->as<DfgVarArray>()->addDriver(flp, idxp->toUInt(), vtxp);
getVertex(vrefp)->template as<DfgVarArray>()->addDriver(flp, idxp->toUInt(), vtxp);
return true;
}
if (AstConcat* const concatp = VN_CAST(nodep, Concat)) {
@ -334,18 +357,21 @@ class AstToDfgVisitor final : public VNVisitor {
const uint32_t bEnd = b.m_lsb + bWidth;
const uint32_t overlapEnd = std::min(aEnd, bEnd) - 1;
if (a.m_fileline->operatorCompare(*b.m_fileline) != 0) {
varp->varp()->v3warn( //
if (a.m_fileline->operatorCompare(*b.m_fileline) != 0
&& !varp->varp()->isUsedLoopIdx() // Loop index often abused, so suppress
) {
AstNode* const vp = varp->varScopep()
? static_cast<AstNode*>(varp->varScopep())
: static_cast<AstNode*>(varp->varp());
vp->v3warn( //
MULTIDRIVEN,
"Bits [" //
<< overlapEnd << ":" << b.m_lsb << "] of signal "
<< varp->varp()->prettyNameQ()
<< " have multiple combinational drivers\n"
<< vp->prettyNameQ() << " have multiple combinational drivers\n"
<< a.m_fileline->warnOther() << "... Location of first driver\n"
<< a.m_fileline->warnContextPrimary() << '\n'
<< b.m_fileline->warnOther() << "... Location of other driver\n"
<< b.m_fileline->warnContextSecondary()
<< varp->varp()->warnOther()
<< b.m_fileline->warnContextSecondary() << vp->warnOther()
<< "... Only the first driver will be respected");
}
@ -437,19 +463,55 @@ class AstToDfgVisitor final : public VNVisitor {
m_foundUnhandled = true;
markReferenced(nodep);
}
void visit(AstNetlist* nodep) override { iterateAndNextNull(nodep->modulesp()); }
void visit(AstModule* nodep) override { iterateAndNextNull(nodep->stmtsp()); }
void visit(AstTopScope* nodep) override { iterate(nodep->scopep()); }
void visit(AstScope* nodep) override { iterateChildren(nodep); }
void visit(AstActive* nodep) override {
if (nodep->hasCombo()) {
iterateChildren(nodep);
} else {
markReferenced(nodep);
}
}
void visit(AstCell* nodep) override { markReferenced(nodep); }
void visit(AstNodeProcedure* nodep) override { markReferenced(nodep); }
void visit(AstVar* nodep) override {
if (nodep->isSc()) return;
// No need to (and in fact cannot) handle variables with unsupported dtypes
if (!DfgVertex::isSupportedDType(nodep->dtypep())) return;
// Mark variables with external references
if (nodep->isIO() // Ports
|| nodep->user2() // Target of a hierarchical reference
|| nodep->isForced() // Forced
) {
getNet(nodep)->setHasExtRefs();
void visit(AstVar* nodep) override {
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return;
} else {
if (nodep->isSc()) return;
// No need to (and in fact cannot) handle variables with unsupported dtypes
if (!DfgVertex::isSupportedDType(nodep->dtypep())) return;
// Mark variables with external references
if (nodep->isIO() // Ports
|| nodep->user2() // Target of a hierarchical reference
|| nodep->isForced() // Forced
) {
getNet(reinterpret_cast<VariableType*>(nodep))->setHasExtRefs();
}
}
}
void visit(AstVarScope* nodep) override {
if VL_CONSTEXPR_CXX17 (!T_Scoped) {
return;
} else {
if (nodep->varp()->isSc()) return;
// No need to (and in fact cannot) handle variables with unsupported dtypes
if (!DfgVertex::isSupportedDType(nodep->dtypep())) return;
// Mark variables with external references
if (nodep->varp()->isIO() // Ports
|| nodep->user2() // Target of a hierarchical reference
|| nodep->varp()->isForced() // Forced
) {
getNet(reinterpret_cast<VariableType*>(nodep))->setHasExtRefs();
}
}
}
@ -538,6 +600,17 @@ class AstToDfgVisitor final : public VNVisitor {
return;
}
// If the referenced variable is not in a regular module, then do not
// convert it. This is especially needed for variabels in interfaces
// which might be referenced via virtual intefaces, which cannot be
// resovled statically.
if (T_Scoped && !VN_IS(nodep->varScopep()->scopep()->modp(), Module)) {
markReferenced(nodep);
m_foundUnhandled = true;
++m_ctx.m_nonRepVarRef;
return;
}
// Sadly sometimes AstVarRef does not have the same dtype as the referenced variable
if (!DfgVertex::isSupportedDType(nodep->varp()->dtypep())) {
m_foundUnhandled = true;
@ -545,7 +618,7 @@ class AstToDfgVisitor final : public VNVisitor {
return;
}
nodep->user1p(getNet(nodep->varp()));
nodep->user1p(getNet(getTarget(nodep)));
}
void visit(AstConst* nodep) override {
@ -585,13 +658,22 @@ class AstToDfgVisitor final : public VNVisitor {
// The rest of the 'visit' methods are generated by 'astgen'
#include "V3Dfg__gen_ast_to_dfg.h"
static DfgGraph* makeDfg(RootType& root) {
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return new DfgGraph{nullptr, "netlist"};
} else {
AstModule* const modp = VN_AS((AstNode*)&(root), Module); // Remove this when C++17
return new DfgGraph{modp, modp->name()};
}
}
// CONSTRUCTOR
explicit AstToDfgVisitor(AstModule& module, V3DfgOptimizationContext& ctx)
: m_dfgp{new DfgGraph{module, module.name()}}
explicit AstToDfgVisitor(RootType& root, V3DfgOptimizationContext& ctx)
: m_dfgp{makeDfg(root)}
, m_ctx{ctx} {
// Build the DFG
iterateChildren(&module);
UASSERT_OBJ(m_uncommittedVertices.empty(), &module, "Uncommitted vertices remain");
iterate(&root);
UASSERT_OBJ(m_uncommittedVertices.empty(), &root, "Uncommitted vertices remain");
// Canonicalize variables
canonicalizePacked();
@ -599,11 +681,15 @@ class AstToDfgVisitor final : public VNVisitor {
}
public:
static DfgGraph* apply(AstModule& module, V3DfgOptimizationContext& ctx) {
return AstToDfgVisitor{module, ctx}.m_dfgp;
static DfgGraph* apply(RootType& root, V3DfgOptimizationContext& ctx) {
return AstToDfgVisitor{root, ctx}.m_dfgp;
}
};
DfgGraph* V3DfgPasses::astToDfg(AstModule& module, V3DfgOptimizationContext& ctx) {
return AstToDfgVisitor::apply(module, ctx);
return AstToDfgVisitor</* T_Scoped: */ false>::apply(module, ctx);
}
DfgGraph* V3DfgPasses::astToDfg(AstNetlist& netlist, V3DfgOptimizationContext& ctx) {
return AstToDfgVisitor</* T_Scoped: */ true>::apply(netlist, ctx);
}

View File

@ -96,7 +96,7 @@ class SplitIntoComponents final {
// Allocate the component graphs
m_components.resize(m_componentCounter - 1);
for (size_t i = 1; i < m_componentCounter; ++i) {
m_components[i - 1].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i - 1)});
m_components[i - 1].reset(new DfgGraph{m_dfg.modulep(), m_prefix + cvtToStr(i - 1)});
}
// Move the vertices to the component graphs
moveVertices(m_dfg.varVertices());
@ -317,9 +317,17 @@ class ExtractCyclicComponents final {
DfgVertexVar*& clonep = m_clones[&vtx][component];
if (!clonep) {
if (DfgVarPacked* const pVtxp = vtx.cast<DfgVarPacked>()) {
clonep = new DfgVarPacked{m_dfg, pVtxp->varp()};
if (AstVarScope* const vscp = pVtxp->varScopep()) {
clonep = new DfgVarPacked{m_dfg, vscp};
} else {
clonep = new DfgVarPacked{m_dfg, pVtxp->varp()};
}
} else if (DfgVarArray* const aVtxp = vtx.cast<DfgVarArray>()) {
clonep = new DfgVarArray{m_dfg, aVtxp->varp()};
if (AstVarScope* const vscp = aVtxp->varScopep()) {
clonep = new DfgVarArray{m_dfg, vscp};
} else {
clonep = new DfgVarArray{m_dfg, aVtxp->varp()};
}
}
UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type");
if (vtx.hasModRefs()) clonep->setHasModRefs();
@ -466,7 +474,7 @@ class ExtractCyclicComponents final {
// Allocate result graphs
m_components.resize(m_nonTrivialSCCs);
for (size_t i = 0; i < m_nonTrivialSCCs; ++i) {
m_components[i].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i)});
m_components[i].reset(new DfgGraph{m_dfg.modulep(), m_prefix + cvtToStr(i)});
}
// Fix up edges crossing components (we can only do this at variable boundaries, and the

View File

@ -125,20 +125,57 @@ AstSliceSel* makeNode<AstSliceSel, DfgSliceSel, AstNodeExpr*, AstNodeExpr*, AstN
} // namespace
template <bool T_Scoped>
class DfgToAstVisitor final : DfgVisitor {
// NODE STATE
// AstScope::user1p // The combinational AstActive under this scope
const VNUser1InUse m_user1InUse;
// TYPES
using VariableType = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
// STATE
AstModule* const m_modp; // The parent/result module
AstModule* const m_modp; // The parent/result module - This is nullptr when T_Scoped
V3DfgOptimizationContext& m_ctx; // The optimization context for stats
AstNodeExpr* m_resultp = nullptr; // The result node of the current traversal
// Map from DfgVertex to the AstVar holding the value of that DfgVertex after conversion
std::unordered_map<const DfgVertex*, AstVar*> m_resultVars;
// Map from an AstVar, to the canonical AstVar that can be substituted for that AstVar
std::unordered_map<AstVar*, AstVar*> m_canonVars;
V3UniqueNames m_tmpNames{"__VdfgTmp"}; // For generating temporary names
// METHODS
static VariableType* getNode(const DfgVertexVar* vtxp) {
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return reinterpret_cast<VariableType*>(vtxp->varScopep());
} else {
return reinterpret_cast<VariableType*>(vtxp->varp());
}
}
static AstActive* getCombActive(AstScope* scopep) {
if (!scopep->user1p()) {
// Try to find the existing combinational AstActive
for (AstNode* nodep = scopep->blocksp(); nodep; nodep = nodep->nextp()) {
AstActive* const activep = VN_CAST(nodep, Active);
if (!activep) continue;
if (activep->hasCombo()) {
scopep->user1p(activep);
break;
}
}
// If there isn't one, create a new one
if (!scopep->user1p()) {
FileLine* const flp = scopep->fileline();
AstSenTree* const senTreep
= new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Combo{}}};
AstActive* const activep = new AstActive{flp, "", senTreep};
activep->sensesStorep(senTreep);
scopep->addBlocksp(activep);
scopep->user1p(activep);
}
}
return VN_AS(scopep->user1p(), Active);
}
AstNodeExpr* convertDfgVertexToAstNodeExpr(DfgVertex* vtxp) {
UASSERT_OBJ(!m_resultp, vtxp, "Result already computed");
UASSERT_OBJ(!vtxp->hasMultipleSinks() || vtxp->is<DfgVertexVar>()
@ -151,8 +188,16 @@ class DfgToAstVisitor final : DfgVisitor {
return resultp;
}
void addResultEquation(FileLine* flp, AstNodeExpr* lhsp, AstNodeExpr* rhsp) {
m_modp->addStmtsp(new AstAssignW{flp, lhsp, rhsp});
void addResultEquation(const DfgVertexVar* vtxp, FileLine* flp, AstNodeExpr* lhsp,
AstNodeExpr* rhsp) {
AstAssignW* const assignp = new AstAssignW{flp, lhsp, rhsp};
if VL_CONSTEXPR_CXX17 (T_Scoped) {
// Add it to the scope holding the target variable
getCombActive(vtxp->varScopep()->scopep())->addStmtsp(assignp);
} else {
// Add it to the parend module of the DfgGraph
m_modp->addStmtsp(assignp);
}
++m_ctx.m_resultEquations;
}
@ -160,9 +205,9 @@ class DfgToAstVisitor final : DfgVisitor {
if (dfgVarp->isDrivenFullyByDfg()) {
// Whole variable is driven. Render driver and assign directly to whole variable.
FileLine* const flp = dfgVarp->driverFileLine(0);
AstVarRef* const lhsp = new AstVarRef{flp, dfgVarp->varp(), VAccess::WRITE};
AstVarRef* const lhsp = new AstVarRef{flp, getNode(dfgVarp), VAccess::WRITE};
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(dfgVarp->source(0));
addResultEquation(flp, lhsp, rhsp);
addResultEquation(dfgVarp, flp, lhsp, rhsp);
} else {
// Variable is driven partially. Render each driver as a separate assignment.
dfgVarp->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) {
@ -171,12 +216,12 @@ class DfgToAstVisitor final : DfgVisitor {
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(edge.sourcep());
// Create select LValue
FileLine* const flp = dfgVarp->driverFileLine(idx);
AstVarRef* const refp = new AstVarRef{flp, dfgVarp->varp(), VAccess::WRITE};
AstVarRef* const refp = new AstVarRef{flp, getNode(dfgVarp), VAccess::WRITE};
AstConst* const lsbp = new AstConst{flp, dfgVarp->driverLsb(idx)};
const int width = static_cast<int>(edge.sourcep()->width());
AstSel* const lhsp = new AstSel{flp, refp, lsbp, width};
// Add assignment of the value to the selected bits
addResultEquation(flp, lhsp, rhsp);
addResultEquation(dfgVarp, flp, lhsp, rhsp);
});
}
}
@ -189,11 +234,11 @@ class DfgToAstVisitor final : DfgVisitor {
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(edge.sourcep());
// Create select LValue
FileLine* const flp = dfgVarp->driverFileLine(idx);
AstVarRef* const refp = new AstVarRef{flp, dfgVarp->varp(), VAccess::WRITE};
AstVarRef* const refp = new AstVarRef{flp, getNode(dfgVarp), VAccess::WRITE};
AstConst* const idxp = new AstConst{flp, dfgVarp->driverIndex(idx)};
AstArraySel* const lhsp = new AstArraySel{flp, refp, idxp};
// Add assignment of the value to the selected bits
addResultEquation(flp, lhsp, rhsp);
addResultEquation(dfgVarp, flp, lhsp, rhsp);
});
}
@ -203,11 +248,11 @@ class DfgToAstVisitor final : DfgVisitor {
} // LCOV_EXCL_STOP
void visit(DfgVarPacked* vtxp) override {
m_resultp = new AstVarRef{vtxp->fileline(), vtxp->varp(), VAccess::READ};
m_resultp = new AstVarRef{vtxp->fileline(), getNode(vtxp), VAccess::READ};
}
void visit(DfgVarArray* vtxp) override {
m_resultp = new AstVarRef{vtxp->fileline(), vtxp->varp(), VAccess::READ};
m_resultp = new AstVarRef{vtxp->fileline(), getNode(vtxp), VAccess::READ};
}
void visit(DfgConst* vtxp) override { //
@ -254,11 +299,13 @@ class DfgToAstVisitor final : DfgVisitor {
}
public:
static AstModule* apply(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
return DfgToAstVisitor{dfg, ctx}.m_modp;
}
static void apply(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { DfgToAstVisitor{dfg, ctx}; }
};
AstModule* V3DfgPasses::dfgToAst(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
return DfgToAstVisitor::apply(dfg, ctx);
void V3DfgPasses::dfgToAst(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
if (dfg.modulep()) {
DfgToAstVisitor</* T_Scoped: */ false>::apply(dfg, ctx);
} else {
DfgToAstVisitor</* T_Scoped: */ true>::apply(dfg, ctx);
}
}

View File

@ -236,75 +236,101 @@ void V3DfgOptimizer::extract(AstNetlist* netlistp) {
V3Global::dumpCheckGlobalTree("dfg-extract", 0, dumpTreeEitherLevel() >= 3);
}
static void process(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
// 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.
const std::vector<std::unique_ptr<DfgGraph>>& cyclicComponents
= dfg.extractCyclicComponents("cyclic");
// Split the remaining acyclic DFG into [weakly] connected components
const std::vector<std::unique_ptr<DfgGraph>>& acyclicComponents
= dfg.splitIntoComponents("acyclic");
// Quick sanity check
UASSERT_OBJ(dfg.size() == 0, dfg.modulep(), "DfgGraph should have become empty");
// For each acyclic component
for (auto& component : acyclicComponents) {
if (dumpDfgLevel() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source");
// Optimize the component
V3DfgPasses::optimize(*component, ctx);
// Add back under the main DFG (we will convert everything back in one go)
dfg.addGraph(*component);
}
// Eliminate redundant variables. Run this on the whole acyclic DFG. It needs to traverse
// the module/netlist to perform variable substitutions. Doing this by component would do
// redundant traversals and can be extremely slow when we have many components.
V3DfgPasses::eliminateVars(dfg, ctx.m_eliminateVarsContext);
// For each cyclic component
for (auto& component : cyclicComponents) {
if (dumpDfgLevel() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source");
// Converting back to Ast assumes the 'regularize' pass was run, so we must run it
V3DfgPasses::regularize(*component, ctx.m_regularizeContext);
// Add back under the main DFG (we will convert everything back in one go)
dfg.addGraph(*component);
}
}
void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) {
UINFO(2, __FUNCTION__ << ":");
// NODE STATE
// AstVar::user1 -> Used by V3DfgPasses::astToDfg, V3DfgPasses::eliminateVars
// AstVar::user1 -> Used by:
// - V3DfgPasses::astToDfg,
// - V3DfgPasses::eliminateVars,
// - V3DfgPasses::dfgToAst,
// AstVar::user2 -> bool: Flag indicating referenced by AstVarXRef (set just below)
// AstVar::user3 -> bool: Flag indicating written by logic not representable as DFG
// AstVar::user3, AstVarScope::user3
// -> bool: Flag indicating written by logic not representable as DFG
// (set by V3DfgPasses::astToDfg)
const VNUser2InUse user2InUse;
const VNUser3InUse user3InUse;
// Mark cross-referenced variables
netlistp->foreach([](const AstVarXRef* xrefp) { xrefp->varp()->user2(true); });
V3DfgOptimizationContext ctx{label};
// Run the optimization phase
for (AstNode* nodep = netlistp->modulesp(); nodep; nodep = nodep->nextp()) {
// Only optimize proper modules
AstModule* const modp = VN_CAST(nodep, Module);
if (!modp) continue;
if (!netlistp->topScopep()) {
// Pre V3Scope application. Run on each module separately.
UINFO(4, "Applying DFG optimization to module '" << modp->name() << "'");
++ctx.m_modules;
// Mark cross-referenced variables
netlistp->foreach([](const AstVarXRef* xrefp) { xrefp->varp()->user2(true); });
// Build the DFG of this module
const std::unique_ptr<DfgGraph> dfg{V3DfgPasses::astToDfg(*modp, ctx)};
// Run the optimization phase
for (AstNode* nodep = netlistp->modulesp(); nodep; nodep = nodep->nextp()) {
// Only optimize proper modules
AstModule* const modp = VN_CAST(nodep, Module);
if (!modp) continue;
UINFO(4, "Applying DFG optimization to module '" << modp->name() << "'");
++ctx.m_modules;
// Build the DFG of this module
const std::unique_ptr<DfgGraph> dfg{V3DfgPasses::astToDfg(*modp, ctx)};
if (dumpDfgLevel() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-input");
// Actually process the graph
process(*dfg, ctx);
// Convert back to Ast
if (dumpDfgLevel() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-optimized");
V3DfgPasses::dfgToAst(*dfg, ctx);
}
} else {
// Post V3Scope application. Run on whole netlist.
UINFO(4, "Applying DFG optimization to entire netlist");
// Build the DFG of the whole design
const std::unique_ptr<DfgGraph> dfg{V3DfgPasses::astToDfg(*netlistp, ctx)};
if (dumpDfgLevel() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-input");
// 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.
const std::vector<std::unique_ptr<DfgGraph>>& cyclicComponents
= dfg->extractCyclicComponents("cyclic");
// Split the remaining acyclic DFG into [weakly] connected components
const std::vector<std::unique_ptr<DfgGraph>>& acyclicComponents
= dfg->splitIntoComponents("acyclic");
// Quick sanity check
UASSERT_OBJ(dfg->size() == 0, nodep, "DfgGraph should have become empty");
// For each acyclic component
for (auto& component : acyclicComponents) {
if (dumpDfgLevel() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source");
// Optimize the component
V3DfgPasses::optimize(*component, ctx);
// Add back under the main DFG (we will convert everything back in one go)
dfg->addGraph(*component);
}
// Eliminate redundant variables. Run this on the whole acyclic DFG. It needs to traverse
// the module to perform variable substitutions. Doing this by component would do
// redundant traversals and can be extremely slow in large modules with many components.
V3DfgPasses::eliminateVars(*dfg, ctx.m_eliminateVarsContext);
// For each cyclic component
for (auto& component : cyclicComponents) {
if (dumpDfgLevel() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source");
// Converting back to Ast assumes the 'regularize' pass was run, so we must run it
V3DfgPasses::regularize(*component, ctx.m_regularizeContext);
// Add back under the main DFG (we will convert everything back in one go)
dfg->addGraph(*component);
}
// Actually process the graph
process(*dfg, ctx);
// Convert back to Ast
if (dumpDfgLevel() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-optimized");
AstModule* const resultModp = V3DfgPasses::dfgToAst(*dfg, ctx);
UASSERT_OBJ(resultModp == modp, modp, "Should be the same module");
V3DfgPasses::dfgToAst(*dfg, ctx);
}
V3Global::dumpCheckGlobalTree("dfg-optimize", 0, dumpTreeEitherLevel() >= 3);

View File

@ -232,6 +232,8 @@ void V3DfgPasses::removeUnused(DfgGraph& dfg) {
}
void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
UASSERT(dfg.modulep(), "binToOneHot only works with unscoped DfgGraphs for now");
const auto userDataInUse = dfg.userDataInUse();
// Structure to keep track of comparison details
@ -372,7 +374,7 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
DfgVarPacked* varp = srcp->getResultVar();
if (!varp) {
const std::string name = dfg.makeUniqueName("BinToOneHot_Idx", nTables);
varp = dfg.makeNewVar(flp, name, idxDTypep)->as<DfgVarPacked>();
varp = dfg.makeNewVar(flp, name, idxDTypep, nullptr)->as<DfgVarPacked>();
varp->varp()->isInternal(true);
varp->addDriver(flp, 0, srcp);
}
@ -392,7 +394,8 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) {
// The table variable
DfgVarArray* const tabVtxp = [&]() {
const std::string name = dfg.makeUniqueName("BinToOneHot_Tab", nTables);
DfgVarArray* const varp = dfg.makeNewVar(flp, name, tabDTypep)->as<DfgVarArray>();
DfgVarArray* const varp
= dfg.makeNewVar(flp, name, tabDTypep, nullptr)->as<DfgVarArray>();
varp->varp()->isInternal(true);
varp->varp()->noReset(true);
varp->setHasModRefs();
@ -496,9 +499,10 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
workListp = &vtx;
};
// List of variables we are replacing
std::vector<AstVar*> replacedVariables;
// List of variables (AstVar or AstVarScope) we are replacing
std::vector<AstNode*> replacedVariables;
// AstVar::user1p() : AstVar* -> The replacement variables
// AstVarScope::user1p() : AstVarScope* -> The replacement variables
const VNUser1InUse user1InUse;
// Process the work list
@ -541,7 +545,7 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
// If it is only referenced in this DFG, it can be removed
++ctx.m_varsRemoved;
varp->replaceWith(varp->source(0));
varp->varp()->unlinkFrBack()->deleteTree();
varp->nodep()->unlinkFrBack()->deleteTree();
} else if (DfgVarPacked* const driverp = varp->source(0)->cast<DfgVarPacked>()) {
// If it's driven from another variable, it can be replaced by that. However, we do not
// want to propagate SystemC variables into the design.
@ -549,9 +553,11 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
// Mark it for replacement
++ctx.m_varsReplaced;
UASSERT_OBJ(!varp->hasSinks(), varp, "Variable inlining should make this impossible");
UASSERT(!varp->varp()->user1p(), "Replacement already exists");
replacedVariables.emplace_back(varp->varp());
varp->varp()->user1p(driverp->varp());
// Grab the AstVar/AstVarScope
AstNode* const nodep = varp->nodep();
UASSERT_OBJ(!nodep->user1p(), nodep, "Replacement already exists");
replacedVariables.emplace_back(nodep);
nodep->user1p(driverp->nodep());
} else {
// Otherwise this *is* the canonical var
continue;
@ -566,16 +572,24 @@ void V3DfgPasses::eliminateVars(DfgGraph& dfg, V3DfgEliminateVarsContext& ctx) {
// Job done if no replacements possible
if (replacedVariables.empty()) return;
// Apply variable replacements in the module
VNDeleter deleter;
dfg.modulep()->foreach([&](AstVarRef* refp) {
AstVar* varp = refp->varp();
while (AstVar* const replacementp = VN_AS(varp->user1p(), Var)) varp = replacementp;
refp->varp(varp);
});
// Apply variable replacements
if (AstModule* const modp = dfg.modulep()) {
modp->foreach([&](AstVarRef* refp) {
AstVar* varp = refp->varp();
while (AstVar* const replacep = VN_AS(varp->user1p(), Var)) varp = replacep;
refp->varp(varp);
});
} else {
v3Global.rootp()->foreach([&](AstVarRef* refp) {
AstVarScope* vscp = refp->varScopep();
while (AstVarScope* const replacep = VN_AS(vscp->user1p(), VarScope)) vscp = replacep;
refp->varScopep(vscp);
refp->varp(vscp->varp());
});
}
// Remove the replaced variables
for (AstVar* const varp : replacedVariables) varp->unlinkFrBack()->deleteTree();
for (AstNode* const nodep : replacedVariables) nodep->unlinkFrBack()->deleteTree();
}
void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
@ -599,7 +613,9 @@ void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) {
apply(3, "input ", [&]() {});
apply(4, "inlineVars ", [&]() { inlineVars(dfg); });
apply(4, "cse0 ", [&]() { cse(dfg, ctx.m_cseContext0); });
apply(4, "binToOneHot ", [&]() { binToOneHot(dfg, ctx.m_binToOneHotContext); });
if (dfg.modulep()) {
apply(4, "binToOneHot ", [&]() { binToOneHot(dfg, ctx.m_binToOneHotContext); });
}
if (v3Global.opt.fDfgPeephole()) {
apply(4, "peephole ", [&]() { peephole(dfg, ctx.m_peepholeContext); });
// We just did CSE above, so without peephole there is no need to run it again these

View File

@ -117,12 +117,14 @@ namespace V3DfgPasses {
// constructed DfgGraph.
DfgGraph* astToDfg(AstModule&, V3DfgOptimizationContext&) VL_MT_DISABLED;
// Same as above, but for the entire netlist, after V3Scope
DfgGraph* astToDfg(AstNetlist&, V3DfgOptimizationContext&) VL_MT_DISABLED;
// Optimize the given DfgGraph
void optimize(DfgGraph&, V3DfgOptimizationContext&) VL_MT_DISABLED;
// Convert DfgGraph back into Ast, and insert converted graph back into its parent module.
// Returns the parent module.
AstModule* dfgToAst(DfgGraph&, V3DfgOptimizationContext&) VL_MT_DISABLED;
// Convert DfgGraph back into Ast, and insert converted graph back into the Ast.
void dfgToAst(DfgGraph&, V3DfgOptimizationContext&) VL_MT_DISABLED;
//===========================================================================
// Intermediate/internal operations

View File

@ -28,7 +28,7 @@ class V3DfgPatternStats final {
static constexpr uint32_t MAX_PATTERN_DEPTH = 4;
std::map<std::string, std::string> m_internedConsts; // Interned constants
std::map<const AstVar*, std::string> m_internedVars; // Interned variables
std::map<const AstNode*, std::string> m_internedVars; // Interned variables
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
@ -51,7 +51,7 @@ class V3DfgPatternStats final {
}
const std::string& internVar(const DfgVertexVar& vtx) {
const auto pair = m_internedVars.emplace(vtx.varp(), "v");
const auto pair = m_internedVars.emplace(vtx.nodep(), "v");
if (pair.second) pair.first->second += toLetters(m_internedVars.size() - 1);
return pair.first->second;
}

View File

@ -403,8 +403,8 @@ class V3DfgPeephole final : public DfgVisitor {
// If both sides are variable references, order the side in some defined way. This
// allows CSE to later merge 'a op b' with 'b op a'.
if (lhsp->is<DfgVertexVar>() && rhsp->is<DfgVertexVar>()) {
AstVar* const lVarp = lhsp->as<DfgVertexVar>()->varp();
AstVar* const rVarp = rhsp->as<DfgVertexVar>()->varp();
AstNode* const lVarp = lhsp->as<DfgVertexVar>()->nodep();
AstNode* const rVarp = rhsp->as<DfgVertexVar>()->nodep();
if (lVarp->name() > rVarp->name()) {
APPLYING(SWAP_VAR_IN_COMMUTATIVE_BINARY) {
Vertex* const replacementp = make<Vertex>(vtxp, rhsp, lhsp);

View File

@ -38,6 +38,10 @@ class DfgRegularize final {
: m_dfg{dfg}
, m_ctx{ctx} {
// Scope cache for below
const bool scoped = !dfg.modulep();
DfgVertex::ScopeCache scopeCache;
// Ensure intermediate values used multiple times are written to variables
for (DfgVertex& vtx : m_dfg.opVertices()) {
const bool needsIntermediateVariable = [&]() {
@ -71,8 +75,9 @@ class DfgRegularize final {
// Need to create an intermediate variable
const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps);
FileLine* const flp = vtx.fileline();
AstScope* const scopep = scoped ? vtx.scopep(scopeCache) : nullptr;
DfgVarPacked* const newp
= m_dfg.makeNewVar(flp, name, vtx.dtypep())->as<DfgVarPacked>();
= m_dfg.makeNewVar(flp, name, vtx.dtypep(), scopep)->as<DfgVarPacked>();
++m_nTmps;
++m_ctx.m_temporariesIntroduced;
// Replace vertex with the variable and add back driver

View File

@ -40,6 +40,7 @@
class DfgVertexVar VL_NOT_FINAL : public DfgVertexVariadic {
AstVar* const m_varp; // The AstVar associated with this vertex (not owned by this vertex)
AstVarScope* const m_varScopep; // The AstVarScope associated with this vertex (not owned)
bool m_hasDfgRefs = false; // This AstVar is referenced in a different DFG of the module
bool m_hasModRefs = false; // This AstVar is referenced outside the DFG, but in the module
bool m_hasExtRefs = false; // This AstVar is referenced from outside the module
@ -48,14 +49,17 @@ class DfgVertexVar VL_NOT_FINAL : public DfgVertexVariadic {
V3Hash selfHash() const final VL_MT_DISABLED;
public:
DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp, uint32_t initialCapacity)
: DfgVertexVariadic{dfg, type, varp->fileline(), dtypeFor(varp), initialCapacity}
, m_varp{varp} {}
inline DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp, uint32_t initialCapacity);
inline DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVarScope* vscp, uint32_t initialCapacity);
ASTGEN_MEMBERS_DfgVertexVar;
bool isDrivenByDfg() const { return arity() > 0; }
AstVar* varp() const { return m_varp; }
AstVarScope* varScopep() const { return m_varScopep; }
AstNode* nodep() const {
return m_varScopep ? static_cast<AstNode*>(m_varScopep) : static_cast<AstNode*>(m_varp);
}
bool hasDfgRefs() const { return m_hasDfgRefs; }
void setHasDfgRefs() { m_hasDfgRefs = true; }
bool hasModRefs() const { return m_hasModRefs; }
@ -73,7 +77,7 @@ public:
// Keep if public
if (varp()->isSigPublic()) return true;
// Keep if written in non-DFG code
if (varp()->user3()) return true;
if (nodep()->user3()) return true;
// Otherwise it can be removed
return false;
}
@ -181,7 +185,11 @@ class DfgVarArray final : public DfgVertexVar {
public:
DfgVarArray(DfgGraph& dfg, AstVar* varp)
: DfgVertexVar{dfg, dfgType(), varp, 4u} {
UASSERT_OBJ(VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType), varp, "Non array DfgVarArray");
UASSERT_OBJ(VN_IS(dtypep(), UnpackArrayDType), varp, "Non array DfgVarArray");
}
DfgVarArray(DfgGraph& dfg, AstVarScope* vscp)
: DfgVertexVar{dfg, dfgType(), vscp, 4u} {
UASSERT_OBJ(VN_IS(dtypep(), UnpackArrayDType), vscp, "Non array DfgVarArray");
}
ASTGEN_MEMBERS_DfgVarArray;
@ -239,6 +247,8 @@ class DfgVarPacked final : public DfgVertexVar {
public:
DfgVarPacked(DfgGraph& dfg, AstVar* varp)
: DfgVertexVar{dfg, dfgType(), varp, 1u} {}
DfgVarPacked(DfgGraph& dfg, AstVarScope* vscp)
: DfgVertexVar{dfg, dfgType(), vscp, 1u} {}
ASTGEN_MEMBERS_DfgVarPacked;
bool isDrivenFullyByDfg() const {

View File

@ -1331,6 +1331,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-fdfg", CbFOnOff, [this](bool flag) {
m_fDfgPreInline = flag;
m_fDfgPostInline = flag;
m_fDfgScoped = flag;
});
DECL_OPTION("-fdfg-peephole", FOnOff, &m_fDfgPeephole);
DECL_OPTION("-fdfg-peephole-", CbPartialMatch, [this](const char* optp) { //
@ -1341,6 +1342,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
});
DECL_OPTION("-fdfg-pre-inline", FOnOff, &m_fDfgPreInline);
DECL_OPTION("-fdfg-post-inline", FOnOff, &m_fDfgPostInline);
DECL_OPTION("-fdfg-scoped", FOnOff, &m_fDfgScoped);
DECL_OPTION("-fexpand", FOnOff, &m_fExpand);
DECL_OPTION("-ffunc-opt", CbFOnOff, [this](bool flag) { //
m_fFuncSplitCat = flag;
@ -2182,6 +2184,7 @@ void V3Options::optimize(int level) {
m_fDedupe = flag;
m_fDfgPreInline = flag;
m_fDfgPostInline = flag;
m_fDfgScoped = flag;
m_fDeadAssigns = flag;
m_fDeadCells = flag;
m_fExpand = flag;

View File

@ -423,6 +423,7 @@ private:
bool m_fDfgPeephole = true; // main switch: -fno-dfg-peephole
bool m_fDfgPreInline; // main switch: -fno-dfg-pre-inline and -fno-dfg
bool m_fDfgPostInline; // main switch: -fno-dfg-post-inline and -fno-dfg
bool m_fDfgScoped; // main switch: -fno-dfg-scoped and -fno-dfg
bool m_fDeadAssigns; // main switch: -fno-dead-assigns: remove dead assigns
bool m_fDeadCells; // main switch: -fno-dead-cells: remove dead cells
bool m_fExpand; // main switch: -fno-expand: expansion of C macros
@ -737,6 +738,7 @@ public:
bool fDfgPeephole() const { return m_fDfgPeephole; }
bool fDfgPreInline() const { return m_fDfgPreInline; }
bool fDfgPostInline() const { return m_fDfgPostInline; }
bool fDfgScoped() const { return m_fDfgScoped; }
bool fDfgPeepholeEnabled(const std::string& name) const {
return !m_fDfgPeepholeDisabled.count(name);
}

View File

@ -405,6 +405,11 @@ static void process() {
// forcing.
V3Force::forceAll(v3Global.rootp());
if (v3Global.opt.fDfgScoped()) {
// Scoped DFG optimization
V3DfgOptimizer::optimize(v3Global.rootp(), "scoped");
}
// Gate-based logic elimination; eliminate signals and push constant across cell
// boundaries Instant propagation makes lots-o-constant reduction possibilities.
if (v3Global.opt.fGate()) {

View File

@ -96,7 +96,7 @@ def check(name):
name = name.lower()
name = re.sub(r'_', ' ', name)
test.file_grep(test.obj_dir + "/obj_opt/Vopt__stats.txt",
r'DFG\s+(pre|post) inline Peephole, ' + name + r'\s+([1-9]\d*)')
r'DFG\s+(pre inline|post inline|scoped) Peephole, ' + name + r'\s+([1-9]\d*)')
# Check all optimizations defined in

View File

@ -12,7 +12,7 @@ import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_dfg_stats_patterns.v"
test.compile(verilator_flags2=["--stats --no-skip-identical -fno-dfg-pre-inline"])
test.compile(verilator_flags2=["--stats --no-skip-identical -fno-dfg-pre-inline -fno-dfg-scoped"])
fn = test.glob_one(test.obj_dir + "/" + test.vm_prefix + "__stats_dfg_patterns*")
test.files_identical(fn, test.golden_filename)

View File

@ -12,7 +12,7 @@ import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_dfg_stats_patterns.v"
test.compile(verilator_flags2=["--stats --no-skip-identical -fno-dfg-post-inline"])
test.compile(verilator_flags2=["--stats --no-skip-identical -fno-dfg-post-inline -fno-dfg-scoped"])
fn = test.glob_one(test.obj_dir + "/" + test.vm_prefix + "__stats_dfg_patterns*")
test.files_identical(fn, test.golden_filename)

View File

@ -0,0 +1,50 @@
DFG 'scoped' patterns with depth 1
3 (NOT vA:a)*:a
2 (AND _A:a _B:a):a
2 (REPLICATE _A:a cA:a)*:b
1 (AND _A:a _B:a)*:a
1 (CONCAT '0:a _A:b):A
1 (NOT _A:a):a
1 (REPLICATE _A:1 cA:a)*:b
1 (REPLICATE _A:a cA:b)*:b
1 (REPLICATE _A:a cA:b)*:c
1 (SEL@0 _A:a):1
1 (SEL@0 _A:a):b
1 (SEL@A _A:a):1
DFG 'scoped' patterns with depth 2
2 (AND (NOT vA:a)*:a (NOT vB:a)*:a):a
1 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a
1 (CONCAT '0:a (REPLICATE _A:a cA:a)*:b):A
1 (NOT (REPLICATE _A:a cA:b)*:b):b
1 (REPLICATE (NOT _A:a):a cA:a)*:b
1 (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c
1 (REPLICATE (REPLICATE _A:a cA:b)*:b cA:b)*:c
1 (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b
1 (REPLICATE (SEL@A _A:a):1 cA:b)*:c
1 (SEL@0 (AND _A:a _B:a)*:a):1
1 (SEL@0 (REPLICATE _A:a cA:a)*:b):c
1 (SEL@A (AND _A:a _B:a)*:a):1
DFG 'scoped' patterns with depth 3
1 (CONCAT '0:a (REPLICATE (REPLICATE _A:b cA:a)*:a cA:a)*:c):A
1 (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b
1 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c
1 (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a
1 (REPLICATE (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b cA:b)*:d
1 (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d
1 (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c
1 (SEL@0 (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1
1 (SEL@0 (REPLICATE (NOT _A:a):a cA:a)*:b):c
1 (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1
DFG 'scoped' patterns with depth 4
1 (CONCAT '0:a (REPLICATE (REPLICATE (REPLICATE _A:b cA:a)*:c cA:a)*:a cA:a)*:d):A
1 (NOT (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a):a
1 (REPLICATE (NOT (REPLICATE (REPLICATE _A:a cA:b)*:c cA:b)*:b):b cA:b)*:d
1 (REPLICATE (REPLICATE (REPLICATE (REPLICATE _A:1 cA:a)*:b cB:a)*:c cB:a)*:a cB:a)*:d
1 (REPLICATE (REPLICATE (REPLICATE (SEL@A _A:a):1 cA:b)*:c cB:b)*:d cB:b)*:b
1 (REPLICATE (REPLICATE (SEL@A (AND _A:a _B:a)*:a):1 cA:b)*:c cB:b)*:d
1 (REPLICATE (SEL@A (AND (NOT vA:a)*:a (NOT vB:a)*:a)*:a):1 cA:b)*:c
1 (SEL@0 (REPLICATE (NOT (REPLICATE _A:a cA:b)*:b):b cA:b)*:c):d

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 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
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_dfg_stats_patterns.v"
test.compile(
verilator_flags2=["--stats --no-skip-identical -fno-dfg-pre-inline -fno-dfg-post-inline"])
fn = test.glob_one(test.obj_dir + "/" + test.vm_prefix + "__stats_dfg_patterns*")
test.files_identical(fn, test.golden_filename)
test.passes()

View File

@ -33,9 +33,9 @@ test.compile(
if test.vltmt:
test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt",
r'Optimizations, Thread schedule count\s+(\d+)', 1)
r'Optimizations, Thread schedule count\s+(\d+)', 2)
test.file_grep(test.obj_dir + "/V" + test.name + "__hier.dir/V" + test.name + "__stats.txt",
r'Optimizations, Thread schedule total tasks\s+(\d+)', 2)
r'Optimizations, Thread schedule total tasks\s+(\d+)', 3)
test.execute()

View File

@ -42,7 +42,7 @@ if test.vlt_all:
# We expect to combine sequent functions across multiple instances of
# l2, l3, l4, l5. If this number drops, please confirm this has not broken.
test.file_grep(test.stats, r'Optimizations, Combined CFuncs\s+(\d+)',
(85 if test.vltmt else 67))
(91 if test.vltmt else 87))
# Everything should use relative references
check_relative_refs("t", True)

View File

@ -15,7 +15,8 @@ test.top_filename = "t/t_inst_tree.v"
out_filename = test.obj_dir + "/V" + test.name + ".tree.json"
test.compile(v_flags2=[
"--no-json-edit-nums", "-fno-dfg-post-inline", test.t_dir + "/t_inst_tree_inl1_pub0.vlt"
"--no-json-edit-nums", "-fno-dfg-post-inline", "-fno-dfg-scoped", test.t_dir +
"/t_inst_tree_inl1_pub0.vlt"
])
if test.vlt_all:

View File

@ -18,7 +18,7 @@ test.compile(verilator_flags2=["-Wno-UNOPTTHREADS", "--stats", test.pli_filename
test.execute()
if test.vlt:
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 43)
test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 38)
test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1)
test.passes()

View File

@ -4,7 +4,7 @@
... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest
... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message.
t/t_unopt_combo.v:23:25: Example path: t.b
t/t_unopt_combo.v:124:4: Example path: ALWAYS
t/t_unopt_combo.v:137:14: Example path: ASSIGNW
t/t_unopt_combo.v:24:25: Example path: t.c
t/t_unopt_combo.v:81:4: Example path: ALWAYS
t/t_unopt_combo.v:23:25: Example path: t.b