diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 78c54f463..1ec9a8f3b 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -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 diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 7d6dede3d..3d037be60 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -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()) { + 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()) { + 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 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()) 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()->varp(), this, - "There should only be one DfgVertexVar for a given AstVar"); + UASSERT_OBJ(nodep()->type() == that.as()->nodep()->type(), this, + "Both DfgVertexVar should be scoped or unscoped"); + UASSERT_OBJ(nodep() != that.as()->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; } diff --git a/src/V3Dfg.h b/src/V3Dfg.h index a9b495a4e..e4df1e06a 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -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; + + // 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 //------------------------------------------------------------------------------ diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index 7773d46ce..b0532ee95 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -69,6 +69,7 @@ DfgSliceSel* makeVertex(const AstSliceSel*, DfgGraph&) } // namespace +template class AstToDfgVisitor final : public VNVisitor { // NODE STATE @@ -87,6 +88,9 @@ class AstToDfgVisitor final : public VNVisitor { , m_lsb{lsb} {} }; + using RootType = std::conditional_t; + using VariableType = std::conditional_t; + // STATE DfgGraph* const m_dfgp; // The graph being built @@ -98,14 +102,33 @@ class AstToDfgVisitor final : public VNVisitor { std::vector 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(refp->varScopep()); + } else { + return reinterpret_cast(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(vp)->varp(); + } else { + return reinterpret_cast(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(); + return vp->user1u().template to(); } 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()->addDriver(flp, 0, vtxp); + getVertex(vrefp)->template as()->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()->addDriver(flp, lsbp->toUInt(), vtxp); + getVertex(vrefp)->template as()->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()->addDriver(flp, idxp->toUInt(), vtxp); + getVertex(vrefp)->template as()->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(varp->varScopep()) + : static_cast(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(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(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::apply(module, ctx); +} + +DfgGraph* V3DfgPasses::astToDfg(AstNetlist& netlist, V3DfgOptimizationContext& ctx) { + return AstToDfgVisitor::apply(netlist, ctx); } diff --git a/src/V3DfgDecomposition.cpp b/src/V3DfgDecomposition.cpp index a6b540763..cc9c3e76a 100644 --- a/src/V3DfgDecomposition.cpp +++ b/src/V3DfgDecomposition.cpp @@ -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()) { - 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()) { - 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 diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index 410d98e48..f62f3f3a3 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -125,20 +125,57 @@ AstSliceSel* makeNode class DfgToAstVisitor final : DfgVisitor { + // NODE STATE + + // AstScope::user1p // The combinational AstActive under this scope + const VNUser1InUse m_user1InUse; + + // TYPES + using VariableType = std::conditional_t; + // 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 m_resultVars; - // Map from an AstVar, to the canonical AstVar that can be substituted for that AstVar - std::unordered_map 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(vtxp->varScopep()); + } else { + return reinterpret_cast(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() @@ -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(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::apply(dfg, ctx); + } else { + DfgToAstVisitor::apply(dfg, ctx); + } } diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index cf3a2393e..64c03fb80 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -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>& cyclicComponents + = dfg.extractCyclicComponents("cyclic"); + + // Split the remaining acyclic DFG into [weakly] connected components + const std::vector>& 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 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 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 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>& cyclicComponents - = dfg->extractCyclicComponents("cyclic"); - - // Split the remaining acyclic DFG into [weakly] connected components - const std::vector>& 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); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 19a226f4a..63130699e 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -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(); + varp = dfg.makeNewVar(flp, name, idxDTypep, nullptr)->as(); 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* const varp + = dfg.makeNewVar(flp, name, tabDTypep, nullptr)->as(); 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 replacedVariables; + // List of variables (AstVar or AstVarScope) we are replacing + std::vector 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()) { // 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 diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index efb33a505..3893c5d67 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -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 diff --git a/src/V3DfgPatternStats.h b/src/V3DfgPatternStats.h index 6163c7505..d9597d00b 100644 --- a/src/V3DfgPatternStats.h +++ b/src/V3DfgPatternStats.h @@ -28,7 +28,7 @@ class V3DfgPatternStats final { static constexpr uint32_t MAX_PATTERN_DEPTH = 4; std::map m_internedConsts; // Interned constants - std::map m_internedVars; // Interned variables + std::map m_internedVars; // Interned variables std::map m_internedSelLsbs; // Interned lsb value for selects std::map m_internedWordWidths; // Interned widths std::map 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; } diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 5056e45b6..a1adf0af9 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -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() && rhsp->is()) { - AstVar* const lVarp = lhsp->as()->varp(); - AstVar* const rVarp = rhsp->as()->varp(); + AstNode* const lVarp = lhsp->as()->nodep(); + AstNode* const rVarp = rhsp->as()->nodep(); if (lVarp->name() > rVarp->name()) { APPLYING(SWAP_VAR_IN_COMMUTATIVE_BINARY) { Vertex* const replacementp = make(vtxp, rhsp, lhsp); diff --git a/src/V3DfgRegularize.cpp b/src/V3DfgRegularize.cpp index 0dd3e6da8..2b9c72b54 100644 --- a/src/V3DfgRegularize.cpp +++ b/src/V3DfgRegularize.cpp @@ -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(); + = m_dfg.makeNewVar(flp, name, vtx.dtypep(), scopep)->as(); ++m_nTmps; ++m_ctx.m_temporariesIntroduced; // Replace vertex with the variable and add back driver diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index d4375d6e7..833c3c869 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -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(m_varScopep) : static_cast(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 { diff --git a/src/V3Options.cpp b/src/V3Options.cpp index e0916c629..6c4f4fbe3 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -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; diff --git a/src/V3Options.h b/src/V3Options.h index 3ab444cee..e70fbe4ad 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -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); } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 315c2c271..2af5b973a 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -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()) { diff --git a/test_regress/t/t_dfg_peephole.py b/test_regress/t/t_dfg_peephole.py index dcb2cae87..0466a34e1 100755 --- a/test_regress/t/t_dfg_peephole.py +++ b/test_regress/t/t_dfg_peephole.py @@ -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 diff --git a/test_regress/t/t_dfg_stats_patterns_post_inline.py b/test_regress/t/t_dfg_stats_patterns_post_inline.py index 0813e38de..329b7557b 100755 --- a/test_regress/t/t_dfg_stats_patterns_post_inline.py +++ b/test_regress/t/t_dfg_stats_patterns_post_inline.py @@ -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) diff --git a/test_regress/t/t_dfg_stats_patterns_pre_inline.py b/test_regress/t/t_dfg_stats_patterns_pre_inline.py index 4fac195a5..bd1a3ca67 100755 --- a/test_regress/t/t_dfg_stats_patterns_pre_inline.py +++ b/test_regress/t/t_dfg_stats_patterns_pre_inline.py @@ -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) diff --git a/test_regress/t/t_dfg_stats_patterns_scoped.out b/test_regress/t/t_dfg_stats_patterns_scoped.out new file mode 100644 index 000000000..cf35ac494 --- /dev/null +++ b/test_regress/t/t_dfg_stats_patterns_scoped.out @@ -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 + diff --git a/test_regress/t/t_dfg_stats_patterns_scoped.py b/test_regress/t/t_dfg_stats_patterns_scoped.py new file mode 100755 index 000000000..afd635460 --- /dev/null +++ b/test_regress/t/t_dfg_stats_patterns_scoped.py @@ -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() diff --git a/test_regress/t/t_hier_block_chained.py b/test_regress/t/t_hier_block_chained.py index 4ed5f7ff7..553bf8ea4 100755 --- a/test_regress/t/t_hier_block_chained.py +++ b/test_regress/t/t_hier_block_chained.py @@ -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() diff --git a/test_regress/t/t_inst_tree_inl0_pub1.py b/test_regress/t/t_inst_tree_inl0_pub1.py index 3fa932f0e..4e35f9ef5 100755 --- a/test_regress/t/t_inst_tree_inl0_pub1.py +++ b/test_regress/t/t_inst_tree_inl0_pub1.py @@ -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) diff --git a/test_regress/t/t_inst_tree_inl1_pub0.py b/test_regress/t/t_inst_tree_inl1_pub0.py index a22a7e648..437358a66 100755 --- a/test_regress/t/t_inst_tree_inl1_pub0.py +++ b/test_regress/t/t_inst_tree_inl1_pub0.py @@ -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: diff --git a/test_regress/t/t_opt_const_dfg.py b/test_regress/t/t_opt_const_dfg.py index 6fd2ea9bc..45033fc3f 100755 --- a/test_regress/t/t_opt_const_dfg.py +++ b/test_regress/t/t_opt_const_dfg.py @@ -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() diff --git a/test_regress/t/t_unopt_combo_bad.out b/test_regress/t/t_unopt_combo_bad.out index 8cd58f59f..bee8bc003 100644 --- a/test_regress/t/t_unopt_combo_bad.out +++ b/test_regress/t/t_unopt_combo_bad.out @@ -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