diff --git a/src/V3DfgContext.h b/src/V3DfgContext.h index 16b427521..97c6e9659 100644 --- a/src/V3DfgContext.h +++ b/src/V3DfgContext.h @@ -216,6 +216,27 @@ private: addStat("temporaries introduced", m_temporariesIntroduced); } }; +class V3DfgRemoveUnobservableContext final : public V3DfgSubContext { + // Only V3DfgContext can create an instance + friend class V3DfgContext; + +public: + // STATE + VDouble0 m_varsRemoved; // Number of variables removed from the Dfg + VDouble0 m_varsDeleted; // Number of variables removed from the Dfg and the Ast + VDouble0 m_logicRemoved; // Number of logic blocks removed from the Dfg + VDouble0 m_logicDeleted; // Number of logic blocks removed from the Dfg and the Ast + +private: + V3DfgRemoveUnobservableContext(V3DfgContext& ctx, const std::string& label) + : V3DfgSubContext{ctx, label, "RemoveUnobservable"} {} + ~V3DfgRemoveUnobservableContext() { + addStat("variables removed", m_varsRemoved); + addStat("variables deleted", m_varsDeleted); + addStat("logic removed", m_logicRemoved); + addStat("logic deleted", m_logicDeleted); + } +}; class V3DfgSynthesisContext final : public V3DfgSubContext { // Only V3DfgContext can create an instance friend class V3DfgContext; @@ -366,6 +387,7 @@ public: V3DfgPeepholeContext m_peepholeContext{*this, m_label}; V3DfgPushDownSelsContext m_pushDownSelsContext{*this, m_label}; V3DfgRegularizeContext m_regularizeContext{*this, m_label}; + V3DfgRemoveUnobservableContext m_removeUnobservableContext{*this, m_label}; V3DfgSynthesisContext m_synthContext{*this, m_label}; // Node pattern collector diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index 6cd92c743..78a21deb6 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -314,6 +314,10 @@ class DataflowOptimize final { // Dump the initial graph for debugging if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "dfg-in"); + // Remove unobservable variabels and logic that drives only such variables + V3DfgPasses::removeUnobservable(dfg, m_ctx); + if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "pruned"); + // Synthesize DfgLogic vertices V3DfgPasses::synthesize(dfg, m_ctx); if (dumpDfgLevel() >= 8) dfg.dumpDotFilePrefixed(m_ctx.prefix() + "synth"); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 3a9613531..dff67fc6d 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -25,6 +25,82 @@ VL_DEFINE_DEBUG_FUNCTIONS; +void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) { + V3DfgRemoveUnobservableContext& ctx = dfgCtx.m_removeUnobservableContext; + + // Enqueue all DfgLogic vertices to work list + DfgWorklist workList{dfg}; + for (DfgVertex& vtx : dfg.opVertices()) { + if (vtx.is()) workList.push_front(vtx); + } + + // Remove all logic that only drives unobservable variables + workList.foreach([&](DfgVertex& vtx) { + DfgLogic* const logicp = vtx.as(); + // Check all variables driven by this logic are removable + bool used = logicp->foreachSink([&](DfgVertex& snk) { + DfgUnresolved* const uVtxp = snk.as(); + DfgVertexVar* const vVtxp = uVtxp->firtsSinkp()->as(); + if (vVtxp->hasSinks()) return true; + if (vVtxp->isObserved()) return true; + return false; + }); + // If some are used, the logic must stay in the Ast + if (used) return; + // If impure, it must stay in the Ast + if (!logicp->isPure()) return; + // Enqueue logic driving the inputs of this logic we are about to delete + logicp->foreachSource([&](DfgVertex& src) { + DfgVertexVar* const varp = src.as(); + if (!varp->srcp()) return false; + varp->srcp()->as()->foreachSource([&](DfgVertex& driver) { + workList.push_front(*driver.as()); + return false; + }); + return false; + }); + // Delete this logic both from the Dfg and the Ast + AstNode* const nodep = logicp->nodep(); + VL_DO_DANGLING(logicp->unlinkDelete(dfg), logicp); + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + ++ctx.m_logicDeleted; + }); + + // Remove unobservable variables + for (DfgVertexVar* const vVtxp : dfg.varVertices().unlinkable()) { + if (vVtxp->hasSinks()) continue; + if (vVtxp->isObserved()) continue; + DfgVertex* const srcp = vVtxp->srcp(); // Must be a DfgUnresolved or nullptr + AstNode* const varp = vVtxp->nodep(); + // Can delete the Ast variable too if it has no other references + const bool delAst = (!srcp || !srcp->nInputs()) // + && !vVtxp->hasExtWrRefs() // + && !vVtxp->hasModWrRefs(); + VL_DO_DANGLING(vVtxp->unlinkDelete(dfg), vVtxp); + if (srcp) VL_DO_DANGLING(srcp->unlinkDelete(dfg), srcp); + if (delAst) { + VL_DO_DANGLING(varp->unlinkFrBack()->deleteTree(), varp); + ++ctx.m_varsDeleted; + } else { + ++ctx.m_varsRemoved; + } + } + + // Finally remove logic from the Dfg if it drives no variables in the graph. + // These should only be those with side effects. + for (DfgVertex* const vtxp : dfg.opVertices().unlinkable()) { + if (!vtxp->is()) continue; + if (vtxp->hasSinks()) continue; + // Input variables will be read in Ast code, mark as such + vtxp->foreachSource([](DfgVertex& src) { + src.as()->setHasModRdRefs(); + return false; + }); + VL_DO_DANGLING(vtxp->unlinkDelete(dfg), vtxp); + ++ctx.m_logicRemoved; + } +} + void V3DfgPasses::inlineVars(DfgGraph& dfg) { for (DfgVertexVar& vtx : dfg.varVertices()) { // Nothing to inline it into diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index bb79b1d4c..cd184022f 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -38,6 +38,9 @@ std::unique_ptr astToDfg(AstModule&, V3DfgContext&) VL_MT_DISABLED; // Same as above, but for the entire netlist, after V3Scope std::unique_ptr astToDfg(AstNetlist&, V3DfgContext&) VL_MT_DISABLED; +// Remove unobservable variabels and logic that drives only such variables +void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; + // Synthesize DfgLogic vertices into primitive operations. // Removes all DfgLogic (even those that were not synthesized). void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index 940afb2d6..7a6cc0da6 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -465,6 +465,7 @@ class DfgLogic final : public DfgVertexVariadic { bool m_selectedForSynthesis = false; // Logic selected for synthesis bool m_nonSynthesizable = false; // Logic is not synthesizeable (by DfgSynthesis) bool m_reverted = false; // Logic was synthesized (in part if non synthesizable) then reverted + mutable uint8_t m_cachedPure = 0; // Cached purity of the logic public: DfgLogic(DfgGraph& dfg, AstAlways* nodep, AstScope* scopep, std::unique_ptr cfgp) @@ -493,6 +494,16 @@ public: void setNonSynthesizable() { m_nonSynthesizable = true; } bool reverted() const { return m_reverted; } void setReverted() { m_reverted = true; } + // Logic has no side-effect, just computes its output variables based on its input variables + bool isPure() const { + if (!m_cachedPure) { + // This is a sledgehamer, but AstNodeStmts don't compute their 'purity' properly, + // not that 'purity' makes sense for statements... We don't call this often and cached. + const bool pure = m_nodep->forall([](AstNode* nodep) { return nodep->isPure(); }); + m_cachedPure = static_cast(pure) | 0x2; + } + return m_cachedPure & 0x01; + } }; class DfgUnresolved final : public DfgVertexVariadic { diff --git a/test_regress/t/t_dfg_push_sel.py b/test_regress/t/t_dfg_push_sel.py index 281112afa..5e23ff9b3 100755 --- a/test_regress/t/t_dfg_push_sel.py +++ b/test_regress/t/t_dfg_push_sel.py @@ -19,7 +19,7 @@ test.execute() if test.vlt: test.file_grep(test.stats, r'Optimizations, DFG scoped PushDownSels, sels pushed down\s+(\d+)', - 50) + 49) test.file_grep(test.stats, r'Optimizations, DFG scoped PushDownSels, would be cyclic\s+(\d+)', 1) diff --git a/test_regress/t/t_opt_const_dfg.py b/test_regress/t/t_opt_const_dfg.py index 88bf99d4f..eb5626311 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+)', 42) + test.file_grep(test.stats, r'Optimizations, Const bit op reduction\s+(\d+)', 43) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 1) test.passes()