From 47bce4157dacd3b177a20308fde8f70e3b0ca929 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 23 Sep 2022 16:46:22 +0100 Subject: [PATCH] Introduce DFG based combinational logic optimizer (#3527) Added a new data-flow graph (DFG) based combinational logic optimizer. The capabilities of this covers a combination of V3Const and V3Gate, but is also more capable of transforming combinational logic into simplified forms and more. This entail adding a new internal representation, `DfgGraph`, and appropriate `astToDfg` and `dfgToAst` conversion functions. The graph represents some of the combinational equations (~continuous assignments) in a module, and for the duration of the DFG passes, it takes over the role of AstModule. A bulk of the Dfg vertices represent expressions. These vertex classes, and the corresponding conversions to/from AST are mostly auto-generated by astgen, together with a DfgVVisitor that can be used for dynamic dispatch based on vertex (operation) types. The resulting combinational logic graph (a `DfgGraph`) is then optimized in various ways. Currently we perform common sub-expression elimination, variable inlining, and some specific peephole optimizations, but there is scope for more optimizations in the future using the same representation. The optimizer is run directly before and after inlining. The pre inline pass can operate on smaller graphs and hence converges faster, but still has a chance of substantially reducing the size of the logic on some designs, making inlining both faster and less memory intensive. The post inline pass can then optimize across the inlined module boundaries. No optimization is performed across a module boundary. For debugging purposes, each peephole optimization can be disabled individually via the -fno-dfg-peepnole- option, where is one of the optimizations listed in V3DfgPeephole.h, for example -fno-dfg-peephole-remove-not-not. The peephole patterns currently implemented were mostly picked based on the design that inspired this work, and on that design the optimizations yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As you can imagine not having to haul around redundant combinational networks in the rest of the compilation pipeline also helps with memory consumption, and up to 30% peak memory usage of Verilator was observed on the same design. Gains on other arbitrary designs are smaller (and can be improved by analyzing those designs). For example OpenTitan gains between 1-15% speedup depending on build type. --- bin/verilator | 4 +- docs/guide/exe_verilator.rst | 31 + docs/internals.rst | 36 + src/Makefile_obj.in | 6 + src/V3Dfg.cpp | 535 +++++++++++ src/V3Dfg.h | 726 ++++++++++++++ src/V3DfgAstToDfg.cpp | 267 ++++++ src/V3DfgDfgToAst.cpp | 314 +++++++ src/V3DfgOptimizer.cpp | 292 ++++++ src/V3DfgOptimizer.h | 35 + src/V3DfgPasses.cpp | 213 +++++ src/V3DfgPasses.h | 113 +++ src/V3DfgPeephole.cpp | 1144 +++++++++++++++++++++++ src/V3DfgPeephole.h | 126 +++ src/V3Error.h | 1 + src/V3Hash.h | 5 + src/V3List.h | 3 + src/V3OptionParser.cpp | 4 + src/V3OptionParser.h | 5 +- src/V3Options.cpp | 15 + src/V3Options.h | 10 + src/Verilator.cpp | 16 + src/astgen | 147 +++ test_regress/t/t_altera_lpm.v | 1 + test_regress/t/t_cdc_async_bad.out | 8 +- test_regress/t/t_cellarray.pl | 2 +- test_regress/t/t_const_opt.pl | 3 +- test_regress/t/t_dfg_circular.pl | 18 + test_regress/t/t_dfg_circular.v | 19 + test_regress/t/t_dfg_peephole.cpp | 37 + test_regress/t/t_dfg_peephole.pl | 126 +++ test_regress/t/t_dfg_peephole.v | 227 +++++ test_regress/t/t_dfg_unhandled.pl | 20 + test_regress/t/t_dfg_unhandled.v | 16 + test_regress/t/t_dump_dfg.pl | 21 + test_regress/t/t_flag_expand_limit.pl | 2 +- test_regress/t/t_gate_chained.pl | 2 +- test_regress/t/t_gate_ormux.pl | 2 +- test_regress/t/t_inst_tree_inl1_pub0.pl | 2 +- test_regress/t/t_inst_tree_inl1_pub1.pl | 2 +- test_regress/t/t_math_wide_bad.out | 8 +- test_regress/t/t_order_wireloop.pl | 2 +- test_regress/t/t_var_escape.out | 214 ++--- test_regress/t/t_xml_first.out | 4 +- test_regress/t/t_xml_flat.out | 4 +- 45 files changed, 4653 insertions(+), 135 deletions(-) create mode 100644 src/V3Dfg.cpp create mode 100644 src/V3Dfg.h create mode 100644 src/V3DfgAstToDfg.cpp create mode 100644 src/V3DfgDfgToAst.cpp create mode 100644 src/V3DfgOptimizer.cpp create mode 100644 src/V3DfgOptimizer.h create mode 100644 src/V3DfgPasses.cpp create mode 100644 src/V3DfgPasses.h create mode 100644 src/V3DfgPeephole.cpp create mode 100644 src/V3DfgPeephole.h create mode 100755 test_regress/t/t_dfg_circular.pl create mode 100644 test_regress/t/t_dfg_circular.v create mode 100644 test_regress/t/t_dfg_peephole.cpp create mode 100755 test_regress/t/t_dfg_peephole.pl create mode 100644 test_regress/t/t_dfg_peephole.v create mode 100755 test_regress/t/t_dfg_unhandled.pl create mode 100644 test_regress/t/t_dfg_unhandled.v create mode 100755 test_regress/t/t_dump_dfg.pl diff --git a/bin/verilator b/bin/verilator index 0e2a882a9..247b4aae9 100755 --- a/bin/verilator +++ b/bin/verilator @@ -309,10 +309,12 @@ detailed descriptions of these arguments. +define+= Set preprocessor define --dpi-hdr-only Only produce the DPI header file --dump-defines Show preprocessor defines with -E - --dump-graph Enable dumping V3Graphs to .dot + --dump-dfg Enable dumping DfgGraphs to .dot files + --dump-graph Enable dumping V3Graphs to .dot files --dump-tree Enable dumping Ast .tree files --dump-tree-addrids Use short identifiers instead of addresses --dump- Enable dumping everything in source file + --dumpi-dfg Enable dumping DfgGraphs to .dot files at level --dumpi-graph Enable dumping V3Graphs to .dot files at level --dumpi-tree Enable dumping Ast .tree files at level --dumpi- Enable dumping everything in source file at level diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 14878f869..aff59dd5b 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -355,6 +355,11 @@ Summary: touch foo.v ; verilator -E --dump-defines foo.v +.. option:: --dump-dfg + + Rarely needed. Enable dumping DfgGraph .dot debug files with dumping + level 3. + .. option:: --dump-graph Rarely needed. Enable dumping V3Graph .dot debug files with dumping @@ -384,6 +389,11 @@ Summary: Rarely needed - for developer use. Enable all dumping in the given source file at level 3. +.. option:: --dumpi-dfg + + Rarely needed - for developer use. Set internal DfgGraph dumping level + globally to the specified value. + .. option:: --dumpi-graph Rarely needed - for developer use. Set internal V3Graph dumping level @@ -482,6 +492,27 @@ Summary: .. option:: -fno-dedup +.. option:: -fno-dfg + + Disable all use of the DFG based combinational logic optimizer. + Alias for :vlopt:`-fno-dfg-pre-inline` and :vlopt:`-fno-dfg-post-inline`. + +.. option:: -fno-dfg-peephole + + Disable the DFG peephole optimizer. + +.. option:: -fno-dfg-peephole- + + Disable individula DFG peephole optimizer pattern. + +.. option:: -fno-dfg-pre-inline + + Do not apply the DFG optimizer before inlining. + +.. option:: -fno-dfg-post-inline + + Do not apply the DFG optimizer after inlining. + .. option:: -fno-expand .. option:: -fno-gate diff --git a/docs/internals.rst b/docs/internals.rst index d575bccec..842c27d17 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -184,6 +184,34 @@ A number of predefined derived algorithm classes and access methods are provided and documented in ``V3GraphAlg.cpp``. +``DfgGraph`` +^^^^^^^^^^^^^ + +The data-flow graph based combinational logic optimizer (DFG optimizer) +converts an ``AstModule`` into a ``DfgGraph``. The graph represents the +combinational equations (~continuous assignments) in the module, and for the +duration of the DFG passes, it takes over the role of the represented +``AstModule``. The ``DfgGraph`` keeps holds of the represented ``AstModule``, +and the ``AstModule`` retains all other logic that is not representable as a +data-flow graph. At the end of optimization, the combinational logic +represented by the ``DfgGraph`` is converted back into AST form and is +re-inserted into the corresponding ``AstModule``. The ``DfgGraph`` is distinct +from ``V3Graph`` for efficiency and other desirable properties which make +writing DFG passes easier. + + +``DfgVertex`` +^^^^^^^^^^^^^ + +The ``DfgGraph`` represents combinational logic equations as a graph of +``DfgVertex`` vertices. Each sub-class of ``DfgVertex`` corresponds to an +expression (a sub-class of ``AstNodeMath``), a constanat, or a variable +reference. LValues and RValues referencing the same storage location are +represented by the same ``DfgVertex``. Consumers of such vertices read as the +LValue, writers of such vertices write the RValue. The bulk of the final +``DfgVertex`` sub-classes are generated by ``astgen`` from the corresponding +``AstNode`` definitions. + Scheduling ---------- @@ -1067,6 +1095,7 @@ given ````. For list type children, the getter is ````, and instead of the setter, there an ``add`` method is generated that appends new nodes (or lists of nodes) to the child list. + ``alias op`` operand alias directives """""""""""""""""""""""""""""""""""""""" @@ -1080,6 +1109,13 @@ super-class of the current node. Example: ``@astgen alias op1 := condp`` +Generating ``DfgVertex`` sub-classes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Most of the ``DfgVertex`` sub-classes are generated by ``astgen``, from the +definitions of the corresponding ``AstNode`` vertices. + + Additional features of ``astgen`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index e435abf36..a6430e10c 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -180,6 +180,12 @@ RAW_OBJS = \ V3Depth.o \ V3DepthBlock.o \ V3Descope.o \ + V3Dfg.o \ + V3DfgAstToDfg.o \ + V3DfgDfgToAst.o \ + V3DfgOptimizer.o \ + V3DfgPasses.o \ + V3DfgPeephole.o \ V3DupFinder.o \ V3Timing.o \ V3EmitCBase.o \ diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp new file mode 100644 index 000000000..24658be08 --- /dev/null +++ b/src/V3Dfg.cpp @@ -0,0 +1,535 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Dfg.h" + +#include "V3File.h" + +#include +#include + +//------------------------------------------------------------------------------ +// DfgGraph +//------------------------------------------------------------------------------ + +DfgGraph::DfgGraph(AstModule& module, const string& name) + : m_modulep{&module} + , m_name{name} {} + +DfgGraph::~DfgGraph() { + forEachVertex([](DfgVertex& vtxp) { delete &vtxp; }); +} + +void DfgGraph::addGraph(DfgGraph& other) { + other.forEachVertex([&](DfgVertex& vtx) { + other.removeVertex(vtx); + this->addVertex(vtx); + }); +} + +bool DfgGraph::sortTopologically(bool reverse) { + // Vertices in reverse topological order + std::vector order; + + // Markings for algorithm + enum class Mark : uint8_t { Scheduled, OnPath, Finished }; + std::unordered_map marks; + + // Stack of nodes in depth first search. The second element of the pair is true if the vertex + // is on the current DFS path, and false if it's only scheduled for visitation. + std::vector> stack; + + // Schedule vertex for visitation + const auto scheudle = [&](DfgVertex& vtx) { + // Nothing to do if already finished + if (marks.emplace(&vtx, Mark::Scheduled).first->second == Mark::Finished) return; + // Otherwise scheule for visitation + stack.emplace_back(&vtx, false); + }; + + // For each vertex (direct loop, so we can return early) + for (DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) { + // Initiate DFS from this vertex + scheudle(*vtxp); + while (!stack.empty()) { + // Pick up stack top + const auto pair = stack.back(); + stack.pop_back(); + DfgVertex* const currp = pair.first; + const bool onPath = pair.second; + Mark& mark = marks.at(currp); + + if (onPath) { // Popped node on path + // Mark it as done + UASSERT_OBJ(mark == Mark::OnPath, currp, "DFS got lost"); + mark = Mark::Finished; + // Add to order + order.push_back(currp); + } else { // Otherwise node was scheduled for visitation, so visit it + // If already finished, then nothing to do + if (mark == Mark::Finished) continue; + // If already on path, then not a DAG + if (mark == Mark::OnPath) return false; + // Push to path and mark as such + mark = Mark::OnPath; + stack.emplace_back(currp, true); + // Schedule children + currp->forEachSink(scheudle); + } + } + } + + // Move given vertex to end of vertex list + const auto reinsert = [this](DfgVertex& vtx) { + // Remove from current location + removeVertex(vtx); + // 'addVertex' appends to the end of the vertex list, so can do this in one loop + addVertex(vtx); + }; + + // Remember 'order' is in reverse topological order + if (!reverse) { + for (DfgVertex* vtxp : vlstd::reverse_view(order)) reinsert(*vtxp); + } else { + for (DfgVertex* vtxp : order) reinsert(*vtxp); + } + + // Done + return true; +} + +std::vector> DfgGraph::splitIntoComponents() { + size_t componentNumber = 0; + std::unordered_map vertex2component; + + forEachVertex([&](const DfgVertex& vtx) { + // If already assigned this vertex to a component, then continue + if (vertex2component.count(&vtx)) return; + + // Work queue for depth first traversal starting from this vertex + std::vector queue{&vtx}; + + // Depth first traversal + while (!queue.empty()) { + // Pop next work item + const DfgVertex& item = *queue.back(); + queue.pop_back(); + + // Mark vertex as belonging to current component (if it's not marked yet) + const bool isFirstEncounter = vertex2component.emplace(&item, componentNumber).second; + + // If we have already visited this vertex during the traversal, then move on. + if (!isFirstEncounter) continue; + + // Enqueue all sources and sinks of this vertex. + item.forEachSource([&](const DfgVertex& src) { queue.push_back(&src); }); + item.forEachSink([&](const DfgVertex& dst) { queue.push_back(&dst); }); + } + + // Done with this component + ++componentNumber; + }); + + // Create the component graphs + std::vector> results{componentNumber}; + + for (size_t i = 0; i < componentNumber; ++i) { + results[i].reset(new DfgGraph{*m_modulep, name() + "-component-" + cvtToStr(i)}); + } + + // Move all vertices under the corresponding component graphs + forEachVertex([&](DfgVertex& vtx) { + this->removeVertex(vtx); + results[vertex2component[&vtx]]->addVertex(vtx); + }); + + UASSERT(size() == 0, "'this' DfgGraph should have been emptied"); + + return results; +} + +void DfgGraph::runToFixedPoint(std::function f) { + bool changed; + const auto apply = [&](DfgVertex& vtx) -> void { + if (f(vtx)) changed = true; + }; + while (true) { + // Do one pass over the graph. + changed = false; + forEachVertex(apply); + if (!changed) break; + // Do another pass in the opposite direction. Alternating directions reduces + // the pathological complexity with left/right leaning trees. + changed = false; + forEachVertexInReverse(apply); + if (!changed) break; + } +} + +static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; } + +// Dump one DfgVertex in Graphviz format +static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { + os << toDotId(vtx); + if (const DfgVar* const varVtxp = vtx.cast()) { + AstVar* const varp = varVtxp->varp(); + os << " [label=\"" << varp->name() << "\nW" << varVtxp->width() << " / F" + << varVtxp->fanout() << '"'; + if (varp->isIO()) { + if (varp->direction() == VDirection::INPUT) { + os << ", shape=house, orientation=270"; + } else if (varp->direction() == VDirection::OUTPUT) { + os << ", shape=house, orientation=90"; + } else { + os << ", shape=star"; + } + } else if (varVtxp->hasExtRefs()) { + os << ", shape=box, style=diagonals,filled, fillcolor=red"; + } else if (varVtxp->hasModRefs()) { + os << ", shape=box, style=diagonals"; + } else { + os << ", shape=box"; + } + os << "]"; + } else if (const DfgConst* const constVtxp = vtx.cast()) { + const V3Number& num = constVtxp->constp()->num(); + os << " [label=\""; + if (num.width() <= 32 && !num.isSigned()) { + const bool feedsSel = !constVtxp->findSink([](const DfgVertex& vtx) { // + return !vtx.is(); + }); + if (feedsSel) { + os << num.toUInt(); + } else { + os << constVtxp->width() << "'d" << num.toUInt() << "\n"; + os << constVtxp->width() << "'h" << std::hex << num.toUInt() << std::dec; + } + } else { + os << num.ascii(); + } + os << '"'; + os << ", shape=plain"; + os << "]"; + } else { + os << " [label=\"" << vtx.typeName() << "\nW" << vtx.width() << " / F" << vtx.fanout() + << '"'; + if (vtx.hasMultipleSinks()) + os << ", shape=doublecircle"; + else + os << ", shape=circle"; + os << "]"; + } + os << endl; +} + +// Dump one DfgEdge in Graphviz format +static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, const string& headlabel) { + os << toDotId(*edge.sourcep()) << " -> " << toDotId(*edge.sinkp()); + if (!headlabel.empty()) os << " [headlabel=\"" << headlabel << "\"]"; + os << endl; +} + +// Dump one DfgVertex and all of its source DfgEdges in Graphviz format +static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) { + dumpDotVertex(os, vtx); + vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { // + if (edge.sourcep()) { + string headLabel; + if (vtx.arity() > 1) headLabel = std::toupper(vtx.srcName(idx)[0]); + dumpDotEdge(os, edge, headLabel); + } + }); +} + +void DfgGraph::dumpDot(std::ostream& os, const string& label) const { + // Header + os << "digraph dfg {" << endl; + os << "graph [label=\"" << name(); + if (!label.empty()) os << "-" << label; + os << "\", labelloc=t, labeljust=l]" << endl; + os << "graph [rankdir=LR]" << endl; + + // Emit all vertices + forEachVertex([&](const DfgVertex& vtx) { dumpDotVertexAndSourceEdges(os, vtx); }); + + // Footer + os << "}" << endl; +} + +void DfgGraph::dumpDotFile(const string& fileName, const string& label) const { + // This generates a file used by graphviz, https://www.graphviz.org + // "hardcoded" parameters: + const std::unique_ptr os{V3File::new_ofstream(fileName)}; + if (os->fail()) v3fatal("Cannot write to file: " << fileName); + dumpDot(*os.get(), label); + os->close(); +} + +void DfgGraph::dumpDotFilePrefixed(const string& label) const { + string fileName = name(); + if (!label.empty()) fileName += "-" + label; + dumpDotFile(v3Global.debugFilename(fileName) + ".dot", label); +} + +// Dump upstream logic cone starting from given vertex +static void dumpDotUpstreamConeFromVertex(std::ostream& os, const DfgVertex& vtx) { + // Work queue for depth first traversal starting from this vertex + std::vector queue{&vtx}; + + // Set of already visited vertices + std::unordered_set visited; + + // Depth first traversal + while (!queue.empty()) { + // Pop next work item + const DfgVertex* const itemp = queue.back(); + queue.pop_back(); + + // Mark vertex as visited + const bool isFirstEncounter = visited.insert(itemp).second; + + // If we have already visited this vertex during the traversal, then move on. + if (!isFirstEncounter) continue; + + // Enqueue all sources of this vertex. + itemp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); }); + + // Emit this vertex and all of its source edges + dumpDotVertexAndSourceEdges(os, *itemp); + } + + // Emit all DfgVar vertices that have external references driven by this vertex + vtx.forEachSink([&](const DfgVertex& dst) { + if (const DfgVar* const varVtxp = dst.cast()) { + if (varVtxp->hasRefs()) dumpDotVertexAndSourceEdges(os, dst); + } + }); +} + +// LCOV_EXCL_START // Debug function for developer use only +void DfgGraph::dumpDotUpstreamCone(const string& fileName, const DfgVertex& vtx, + const string& name) const { + // Open output file + const std::unique_ptr os{V3File::new_ofstream(fileName)}; + if (os->fail()) v3fatal("Cannot write to file: " << fileName); + + // Header + *os << "digraph dfg {" << endl; + if (!name.empty()) *os << "graph [label=\"" << name << "\", labelloc=t, labeljust=l]" << endl; + *os << "graph [rankdir=LR]" << endl; + + // Dump the cone + dumpDotUpstreamConeFromVertex(*os, vtx); + + // Footer + *os << "}" << endl; + + // Done + os->close(); +} +// LCOV_EXCL_STOP + +void DfgGraph::dumpDotAllVarConesPrefixed(const string& label) const { + const string prefix = label.empty() ? name() + "-cone-" : name() + "-" + label + "-cone-"; + forEachVertex([&](const DfgVertex& vtx) { + // Check if this vertex drives a variable referenced outside the DFG. + const DfgVar* const sinkp = vtx.findSink([](const DfgVar& sink) { // + return sink.hasRefs(); + }); + + // We only dump cones driving an externally referenced variable + if (!sinkp) return; + + // Open output file + const string coneName{prefix + sinkp->varp()->name()}; + const string fileName{v3Global.debugFilename(coneName) + ".dot"}; + const std::unique_ptr os{V3File::new_ofstream(fileName)}; + if (os->fail()) v3fatal("Cannot write to file: " << fileName); + + // Header + *os << "digraph dfg {" << endl; + *os << "graph [label=\"" << coneName << "\", labelloc=t, labeljust=l]" << endl; + *os << "graph [rankdir=LR]" << endl; + + // Dump this cone + dumpDotUpstreamConeFromVertex(*os, vtx); + + // Footer + *os << "}" << endl; + + // Done with this logic cone + os->close(); + }); +} + +//------------------------------------------------------------------------------ +// DfgEdge +//------------------------------------------------------------------------------ + +void DfgEdge::unlinkSource() { + if (!m_sourcep) return; +#ifdef VL_DEBUG + { + DfgEdge* sinkp = m_sourcep->m_sinksp; + while (sinkp) { + if (sinkp == this) break; + sinkp = sinkp->m_nextp; + } + UASSERT(sinkp, "'m_sourcep' does not have this edge as sink"); + } +#endif + // Relink pointers of predecessor and successor + if (m_prevp) m_prevp->m_nextp = m_nextp; + if (m_nextp) m_nextp->m_prevp = m_prevp; + // If head of list in source, update source's head pointer + if (m_sourcep->m_sinksp == this) m_sourcep->m_sinksp = m_nextp; + // Mark source as unconnected + m_sourcep = nullptr; + // Clear links. This is not strictly necessary, but might catch bugs. + m_prevp = nullptr; + m_nextp = nullptr; +} + +void DfgEdge::relinkSource(DfgVertex* newSourcep) { + // Unlink current source, if any + unlinkSource(); + // Link new source + m_sourcep = newSourcep; + // Prepend to sink list in source + m_nextp = newSourcep->m_sinksp; + if (m_nextp) m_nextp->m_prevp = this; + newSourcep->m_sinksp = this; +} + +//------------------------------------------------------------------------------ +// DfgVertex +//------------------------------------------------------------------------------ + +DfgVertex::DfgVertex(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type) + : m_filelinep{flp} + , m_dtypep{dtypep} + , m_type{type} { + dfg.addVertex(*this); +} + +bool DfgVertex::selfEquals(const DfgVertex& that) const { + return this->m_type == that.m_type && this->dtypep() == that.dtypep(); +} + +V3Hash DfgVertex::selfHash() const { return V3Hash{m_type} + width(); } + +bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const { + if (this == &that) return true; + if (!this->selfEquals(that)) return false; + + const auto key = (this < &that) ? EqualsCache::key_type{this, &that} // + : EqualsCache::key_type{&that, this}; + const auto pair = cache.emplace(key, true); + bool& result = pair.first->second; + if (pair.second) { + auto thisPair = this->sourceEdges(); + const DfgEdge* const thisSrcEdgesp = thisPair.first; + const size_t thisArity = thisPair.second; + auto thatPair = that.sourceEdges(); + const DfgEdge* const thatSrcEdgesp = thatPair.first; + const size_t thatArity = thatPair.second; + UASSERT_OBJ(thisArity == thatArity, this, "Same type vertices must have same arity!"); + for (size_t i = 0; i < thisArity; ++i) { + const DfgVertex* const thisSrcVtxp = thisSrcEdgesp[i].m_sourcep; + const DfgVertex* const thatSrcVtxp = thatSrcEdgesp[i].m_sourcep; + if (thisSrcVtxp == thatSrcVtxp) continue; + if (!thisSrcVtxp || !thatSrcVtxp || !thisSrcVtxp->equals(*thatSrcVtxp, cache)) { + result = false; + break; + } + } + } + return result; +} + +V3Hash DfgVertex::hash(HashCache& cache) const { + const auto pair = cache.emplace(this, V3Hash{}); + V3Hash& result = pair.first->second; + if (pair.second) { + result += selfHash(); + forEachSource([&result, &cache](const DfgVertex& src) { result += src.hash(cache); }); + } + return result; +} + +uint32_t DfgVertex::fanout() const { + uint32_t result = 0; + forEachSinkEdge([&](const DfgEdge&) { ++result; }); + return result; +} + +void DfgVertex::unlinkDelete(DfgGraph& dfg) { + // Unlink source edges + forEachSourceEdge([](DfgEdge& edge, size_t) { edge.unlinkSource(); }); + // Unlink sink edges + forEachSinkEdge([](DfgEdge& edge) { edge.unlinkSource(); }); + // Remove from graph + dfg.removeVertex(*this); + // Delete + delete this; +} + +void DfgVertex::replaceWith(DfgVertex* newSorucep) { + while (m_sinksp) m_sinksp->relinkSource(newSorucep); +} + +//------------------------------------------------------------------------------ +// Vertex classes +//------------------------------------------------------------------------------ + +// DfgVar ---------- +void DfgVar::accept(DfgVisitor& visitor) { visitor.visit(this); } + +bool DfgVar::selfEquals(const DfgVertex& that) const { + if (const DfgVar* otherp = that.cast()) return varp() == otherp->varp(); + return false; +} + +V3Hash DfgVar::selfHash() const { return V3Hasher::uncachedHash(m_varp); } + +// DfgConst ---------- +void DfgConst::accept(DfgVisitor& visitor) { visitor.visit(this); } + +bool DfgConst::selfEquals(const DfgVertex& that) const { + if (const DfgConst* otherp = that.cast()) { + return constp()->sameTree(otherp->constp()); + } + return false; +} + +V3Hash DfgConst::selfHash() const { return V3Hasher::uncachedHash(m_constp); } + +//------------------------------------------------------------------------------ +// DfgVisitor +//------------------------------------------------------------------------------ + +void DfgVisitor::visit(DfgVar* vtxp) { visit(static_cast(vtxp)); } + +void DfgVisitor::visit(DfgConst* vtxp) { visit(static_cast(vtxp)); } + +//------------------------------------------------------------------------------ +// 'astgen' generated definitions +//------------------------------------------------------------------------------ + +#include "V3Dfg__gen_definitions.h" diff --git a/src/V3Dfg.h b/src/V3Dfg.h new file mode 100644 index 000000000..94fcac4ed --- /dev/null +++ b/src/V3Dfg.h @@ -0,0 +1,726 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// This is a data-flow graph based representation of combinational logic, +// the main difference from a V3Graph is that DfgVertex owns the storage +// of it's input edges (operands/sources/arguments), and can access each +// input edge directly by indexing, making modifications more efficient +// than the linked list based structures used by V3Graph. +// +// A bulk of the DfgVertex sub-types are generated by astgen, and are +// analogous to the correspondign AstNode sub-types. +// +// See also the internals documentation docs/internals.rst +// +//************************************************************************* + +#ifndef VERILATOR_V3DFG_H_ +#define VERILATOR_V3DFG_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Error.h" +#include "V3Hash.h" +#include "V3Hasher.h" +#include "V3List.h" + +#include +#include +#include +#include + +class DfgVertex; +class DfgEdge; +class DfgVisitor; + +//------------------------------------------------------------------------------ + +// Specialization of std::hash for a std::pair for use below +template <> +struct std::hash> final { + size_t operator()(const std::pair& item) const { + const size_t a = reinterpret_cast(item.first); + const size_t b = reinterpret_cast(item.second); + constexpr size_t halfWidth = 8 * sizeof(b) / 2; + return a ^ ((b << halfWidth) | (b >> halfWidth)); + } +}; + +//------------------------------------------------------------------------------ +// Dataflow graph +//------------------------------------------------------------------------------ + +class DfgGraph final { + friend class DfgVertex; + + // MEMBERS + size_t m_size = 0; // Number of vertices in the graph + V3List m_vertices; // The vertices in the graph + // Parent of the graph (i.e.: the module containing the logic represented by this graph). + AstModule* const m_modulep; + const string m_name; // Name of graph (for debugging) + +public: + // CONSTRUCTOR + explicit DfgGraph(AstModule& module, const string& name = ""); + ~DfgGraph(); + VL_UNCOPYABLE(DfgGraph); + + // METHODS +private: + // Add DfgVertex to this graph (assumes not yet contained). + inline void addVertex(DfgVertex& vtx); + // Remove DfgVertex form this graph (assumes it is contained). + inline void removeVertex(DfgVertex& vtx); + +public: + // Number of vertices in this graph + size_t size() const { return m_size; } + + // Parent module + AstModule* modulep() const { return m_modulep; } + + // Name of this graph + const string& name() const { return m_name; } + + // Calls given function 'f' for each vertex in the graph. It is safe to manipulate any vertices + // in the graph, or to delete/unlink the vertex passed to 'f' during iteration. It is however + // not safe to delete/unlink any vertex in the same graph other than the one passed to 'f'. + inline void forEachVertex(std::function f); + + // 'const' variant of 'forEachVertex'. No mutation allowed. + inline void forEachVertex(std::function f) const; + + // Same as 'forEachVertex' but iterates in reverse order. + inline void forEachVertexInReverse(std::function f); + + // Returns first vertex of type 'Vertex' that satisfies the given predicate 'p', + // or nullptr if no such vertex exists in the graph. + template + inline Vertex* findVertex(std::function p) const; + + // Add contents of other graph to this graph. Leaves other graph empty. + void addGraph(DfgGraph& other); + + // Topologically sort the list of vertices in this graph (such that 'forEachVertex' will + // iterate in topological order), or reverse topologically if the passed boolean argument is + // true. Returns true on success (the graph is acyclic and a topological order exists), false + // if the graph is cyclic. If the graph is cyclic, the vertex ordering is not modified. + bool sortTopologically(bool reverse = false); + + // Split this graph into individual components (unique sub-graphs with no edges between them). + // Leaves 'this' graph empty. + std::vector> splitIntoComponents(); + + // Apply the given function to all vertices in the graph. The function return value indicates + // that a change has been made to the graph. Repeat until no changes reported. + void runToFixedPoint(std::function f); + + // Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of + // the graph which is included in the output. + void dumpDot(std::ostream& os, const string& label = "") const; + // Dump graph in Graphviz format into a new file with the given 'fileName'. 'label' is added to + // the name of the graph which is included in the output. + void dumpDotFile(const string& fileName, const string& label = "") const; + // Dump graph in Graphviz format into a new automatically numbered debug file. 'label' is + // added to the name of the graph, which is included in the file name and the output. + void dumpDotFilePrefixed(const string& label = "") const; + // Dump upstream (source) logic cone starting from given vertex into a file with the given + // 'fileName'. 'name' is the name of the graph, which is included in the output. + void dumpDotUpstreamCone(const string& fileName, const DfgVertex& vtx, + const string& name = "") const; + // Dump all individual logic cones driving external variables in Graphviz format into separate + // new automatically numbered debug files. 'label' is added to the name of the graph, which is + // included in the file names and the output. This is useful for very large graphs that are + // otherwise difficult to browse visually due to their size. + void dumpDotAllVarConesPrefixed(const string& label = "") const; +}; + +//------------------------------------------------------------------------------ +// Dataflow graph edge +//------------------------------------------------------------------------------ + +class DfgEdge final { + friend class DfgVertex; + template + friend class DfgVertexWithArity; + + DfgEdge* m_nextp = nullptr; // Next edge in sink list + DfgEdge* m_prevp = nullptr; // Previous edge in sink list + DfgVertex* m_sourcep = nullptr; // The source vertex driving this edge + DfgVertex* const m_sinkp; // The sink vertex. The sink owns the edge, so immutable + + explicit DfgEdge(DfgVertex* sinkp) // The sink vertices own the edges, hence private + : m_sinkp{sinkp} {} + +public: + // The source (driver) of this edge + DfgVertex* sourcep() const { return m_sourcep; } + // The sink (consumer) of this edge + DfgVertex* sinkp() const { return m_sinkp; } + // Remove driver of this edge + void unlinkSource(); + // Relink this edge to be driven from the given new source vertex + void relinkSource(DfgVertex* newSourcep); +}; + +//------------------------------------------------------------------------------ +// Dataflow graph vertex +//------------------------------------------------------------------------------ + +// Reuse the generated type constants +using DfgType = VNType; + +// Base data flow graph vertex +class DfgVertex VL_NOT_FINAL { + friend class DfgGraph; + friend class DfgEdge; + friend class DfgVisitor; + + // STATE + V3ListEnt m_verticesEnt; // V3List handle of this vertex, kept under the DfgGraph +protected: + DfgEdge* m_sinksp = nullptr; // List of sinks of this vertex + FileLine* const m_filelinep; // Source location + AstNodeDType* m_dtypep = nullptr; // Data type of the result of this vertex + const DfgType m_type; + + // CONSTRUCTOR + DfgVertex(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type); + +public: + virtual ~DfgVertex() = default; + + // METHODS +private: + // Visitor accept method + virtual void accept(DfgVisitor& v) = 0; + + // Part of Vertex equality only dependent on this vertex + virtual bool selfEquals(const DfgVertex& that) const; + + // Part of Vertex hash only dependent on this vertex + virtual V3Hash selfHash() const; + +public: + // Returns true if an AstNode with the given 'dtype' can be represented as a DfgVertex + static bool isSupportedDType(const AstNodeDType* dtypep) { + // Conservatively only support bit-vector like basic types and packed arrays of the same + dtypep = dtypep->skipRefp(); + if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) { + return typep->keyword().isIntNumeric(); + } + if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) { + return isSupportedDType(typep->subDTypep()); + } + return false; + } + + // Return data type used to represent any packed value of the given 'width'. All packed types + // of a given width use the same canonical data type, as the only interesting information is + // the total width. + static AstNodeDType* dtypeForWidth(uint32_t width) { + return v3Global.rootp()->typeTablep()->findLogicDType(width, width, VSigning::UNSIGNED); + } + + // 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");); + // Currently all supported types are packed, so this is simple + return dtypeForWidth(nodep->width()); + } + + // Source location + FileLine* fileline() const { return m_filelinep; } + // The data type of the result of the nodes + AstNodeDType* dtypep() const { return m_dtypep; } + + // Width of result + uint32_t width() const { + // Everything supported is packed now, so we can just do this: + return dtypep()->width(); + } + + // Cache type for 'equals' below + using EqualsCache = std::unordered_map, bool>; + + // Vertex equality (based on this vertex and all upstream vertices feeding into this vertex). + // Returns true, if the vertices can be substituted for each other without changing the + // semantics of the logic. The 'cache' argument is used to store results to avoid repeat + // evaluations, but it requires that the upstream sources of the compared vertices do not + // change between invocations. + bool equals(const DfgVertex& that, EqualsCache& cache) const; + + // Uncached version of 'equals' + bool equals(const DfgVertex& that) const { + EqualsCache cache; // Still cache recursive calls within this invocation + return equals(that, cache); + } + + // Cache type for 'hash' below + using HashCache = std::unordered_map; + + // Hash of vertex (depends on this vertex and all upstream vertices feeding into this vertex). + // The 'cache' argument is used to store results to avoid repeat evaluations, but it requires + // that the upstream sources of the vertex do not change between invocations. + V3Hash hash(HashCache& cache) const; + + // Uncached version of 'hash' + V3Hash hash() const { + HashCache cache; // Still cache recursive calls within this invocation + return hash(cache); + } + + // Source edges of this vertex + virtual std::pair sourceEdges() { return {nullptr, 0}; } + + // Source edges of this vertex + virtual std::pair sourceEdges() const { return {nullptr, 0}; } + + // Arity (number of sources) of this vertex + size_t arity() const { return sourceEdges().second; } + + // Predicate: has 1 or more sinks + bool hasSinks() const { return m_sinksp != nullptr; } + + // Predicate: has 2 or more sinks + bool hasMultipleSinks() const { return m_sinksp && m_sinksp->m_nextp; } + + // Fanout (number of sinks) of this vertex (expensive to compute) + uint32_t fanout() const; + + // Unlink from container (graph or builder), then delete this vertex + void unlinkDelete(DfgGraph& dfg); + + // Relink all sinks to be driven from the given new source + void replaceWith(DfgVertex* newSourcep); + + // Calls given function 'f' for each source vertex of this vertex + // Unconnected source edges are not iterated. + inline void forEachSource(std::function f) const; + + // Calls given function 'f' for each source edge of this vertex. Also passes source index. + inline void forEachSourceEdge(std::function f); + + // Calls given function 'f' for each source edge of this vertex. Also passes source index. + inline void forEachSourceEdge(std::function f) const; + + // Calls given function 'f' for each sink vertex of this vertex + inline void forEachSink(std::function f); + + // Calls given function 'f' for each sink vertex of this vertex + inline void forEachSink(std::function f) const; + + // Calls given function 'f' for each sink edge of this vertex. + // Unlinking/deleting the given sink during iteration is safe, but not other sinks of this + // vertex. + inline void forEachSinkEdge(std::function f); + + // Calls given function 'f' for each sink edge of this vertex. + inline void forEachSinkEdge(std::function f) const; + + // Returns first sink vertex of type 'Vertex' which satisfies the given predicate 'p', + // or nullptr if no such sink vertex exists + template + inline Vertex* findSink(std::function p) const; + + // Returns first sink vertex of type 'Vertex', or nullptr if no such sink vertex exists. + // This is a special case of 'findSink' above with the predicate always true. + template + inline Vertex* findSink() const; + + // Is this a DfgConst that is all zeroes + inline bool isZero() const; + + // Is this a DfgConst that is all ones + inline bool isOnes() const; + + // Methods that allow DfgVertex to participate in error reporting/messaging + void v3errorEnd(std::ostringstream& str) const { m_filelinep->v3errorEnd(str); } + void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN { + m_filelinep->v3errorEndFatal(str); + } + string warnContextPrimary() const { return fileline()->warnContextPrimary(); } + string warnContextSecondary() const { return fileline()->warnContextSecondary(); } + string warnMore() const { return fileline()->warnMore(); } + string warnOther() const { return fileline()->warnOther(); } + + // Subtype test + template + bool is() const { + static_assert(std::is_base_of::value, "'T' must be a subtype of DfgVertex"); + return m_type == T::dfgType(); + } + + // Ensure subtype, then cast to that type + template + T* as() { + UASSERT_OBJ(is(), this, + "DfgVertex is not of expected type, but instead has type '" << typeName() + << "'"); + return static_cast(this); + } + template + const T* as() const { + UASSERT_OBJ(is(), this, + "DfgVertex is not of expected type, but instead has type '" << typeName() + << "'"); + return static_cast(this); + } + + // Cast to subtype, or null if different + template + T* cast() { + return is() ? static_cast(this) : nullptr; + } + template + const T* cast() const { + return is() ? static_cast(this) : nullptr; + } + + // Human-readable vertex type as string for debugging + const string typeName() const { return m_type.ascii(); } + + // Human-readable name for source operand with given index for debugging + virtual const string srcName(size_t idx) const = 0; +}; + +// DfgVertices are, well ... DfgVertices +template <> +constexpr bool DfgVertex::is() const { + return true; +} +template <> +constexpr DfgVertex* DfgVertex::as() { + return this; +} +template <> +constexpr const DfgVertex* DfgVertex::as() const { + return this; +} +template <> +constexpr DfgVertex* DfgVertex::cast() { + return this; +} +template <> +constexpr const DfgVertex* DfgVertex::cast() const { + return this; +} + +template +class DfgVertexWithArity VL_NOT_FINAL : public DfgVertex { + static_assert(1 <= Arity && Arity <= 4, "Arity must be between 1 and 4 inclusive"); + + // Uninitialized storage for source edges + typename std::aligned_storage::type + m_sourceEdges; + + constexpr DfgEdge& sourceEdge(size_t index) { + return reinterpret_cast(&m_sourceEdges)[index]; + } + constexpr const DfgEdge& sourceEdge(size_t index) const { + return reinterpret_cast(&m_sourceEdges)[index]; + } + +protected: + DfgVertexWithArity(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type) + : DfgVertex{dfg, flp, dtypep, type} { + // Initialize source edges + for (size_t i = 0; i < Arity; ++i) new (&sourceEdge(i)) DfgEdge{this}; + } + + virtual ~DfgVertexWithArity() = default; + +public: + std::pair sourceEdges() override { // + return {&sourceEdge(0), Arity}; + } + std::pair sourceEdges() const override { + return {&sourceEdge(0), Arity}; + } + + template + DfgVertex* source() const { + static_assert(Index < Arity, "Source index out of range"); + return sourceEdge(Index).m_sourcep; + } + + template + void relinkSource(DfgVertex* newSourcep) { + static_assert(Index < Arity, "Source index out of range"); + UASSERT_OBJ(sourceEdge(Index).m_sinkp == this, this, "Inconsistent"); + sourceEdge(Index).relinkSource(newSourcep); + } + + // Named source getter/setter for unary vertices + template + typename std::enable_if::type srcp() const { + static_assert(A == Arity, "Should not be changed"); + return source<0>(); + } + template + typename std::enable_if::type srcp(DfgVertex* vtxp) { + static_assert(A == Arity, "Should not be changed"); + relinkSource<0>(vtxp); + } + + // Named source getter/setter for binary vertices + template + typename std::enable_if::type lhsp() const { + static_assert(A == Arity, "Should not be changed"); + return source<0>(); + } + template + typename std::enable_if::type lhsp(DfgVertex* vtxp) { + static_assert(A == Arity, "Should not be changed"); + relinkSource<0>(vtxp); + } + + template + typename std::enable_if::type rhsp() const { + static_assert(A == Arity, "Should not be changed"); + return source<1>(); + } + template + typename std::enable_if::type rhsp(DfgVertex* vtxp) { + static_assert(A == Arity, "Should not be changed"); + relinkSource<1>(vtxp); + } +}; + +//------------------------------------------------------------------------------ +// Vertex classes +//------------------------------------------------------------------------------ + +class DfgVar final : public DfgVertexWithArity<1> { + friend class DfgVertex; + friend class DfgVisitor; + + AstVar* const m_varp; // The AstVar associated with this vertex (not owned by this vertex) + FileLine* m_assignmentFlp; // The FileLine of the original assignment driving this var + 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 + + void accept(DfgVisitor& visitor) override; + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + static constexpr DfgType dfgType() { return DfgType::atVar; }; + +public: + DfgVar(DfgGraph& dfg, AstVar* varp) + : DfgVertexWithArity<1>{dfg, varp->fileline(), dtypeFor(varp), dfgType()} + , m_varp{varp} {} + + AstVar* varp() const { return m_varp; } + FileLine* assignmentFileline() const { return m_assignmentFlp; } + void assignmentFileline(FileLine* flp) { m_assignmentFlp = flp; } + bool hasModRefs() const { return m_hasModRefs; } + void setHasModRefs() { m_hasModRefs = true; } + bool hasExtRefs() const { return m_hasExtRefs; } + void setHasExtRefs() { m_hasExtRefs = true; } + bool hasRefs() const { return m_hasModRefs || m_hasExtRefs; } + + DfgVertex* driverp() const { return srcp(); } + void driverp(DfgVertex* vtxp) { srcp(vtxp); } + + // Variable cannot be removed, even if redundant in the DfgGraph (might be used externally) + bool keep() const { + // Keep if referenced outside this module + if (hasExtRefs()) return true; + // Keep if traced + if (v3Global.opt.trace() && varp()->isTrace()) return true; + // Keep if public + if (varp()->isSigPublic()) return true; + // Otherwise it can be removed + return false; + } + + const string srcName(size_t) const override { return "driverp"; } +}; + +class DfgConst final : public DfgVertex { + friend class DfgVertex; + friend class DfgVisitor; + + AstConst* const m_constp; // The AstConst associated with this vertex (owned by this vertex) + + void accept(DfgVisitor& visitor) override; + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + static constexpr DfgType dfgType() { return DfgType::atConst; }; + +public: + DfgConst(DfgGraph& dfg, AstConst* constp) + : DfgVertex{dfg, constp->fileline(), dtypeFor(constp), dfgType()} + , m_constp{constp} {} + + ~DfgConst() { VL_DO_DANGLING(m_constp->deleteTree(), m_constp); } + + AstConst* constp() const { return m_constp; } + V3Number& num() const { return m_constp->num(); } + + uint32_t toU32() const { return num().toUInt(); } + int32_t toI32() const { return num().toSInt(); } + + bool isZero() const { return num().isEqZero(); } + bool isOnes() const { return num().isEqAllOnes(width()); } + + const string srcName(size_t) const override { // LCOV_EXCL_START + VL_UNREACHABLE; + return ""; + } // LCOV_EXCL_STOP +}; + +// The rest of the DfgVertex subclasses are generated by 'astgen' from AstNodeMath nodes +#include "V3Dfg__gen_vertex_classes.h" + +//------------------------------------------------------------------------------ +// Dfg vertex visitor +//------------------------------------------------------------------------------ + +class DfgVisitor VL_NOT_FINAL { +public: + // Dispatch to most specific 'visit' method on 'vtxp' + void iterate(DfgVertex* vtxp) { vtxp->accept(*this); } + + virtual void visit(DfgVar* vtxp); + virtual void visit(DfgConst* vtxp); +#include "V3Dfg__gen_visitor_decls.h" +}; + +//------------------------------------------------------------------------------ +// Inline method definitions +//------------------------------------------------------------------------------ + +void DfgGraph::addVertex(DfgVertex& vtx) { + ++m_size; + vtx.m_verticesEnt.pushBack(m_vertices, &vtx); +} + +void DfgGraph::removeVertex(DfgVertex& vtx) { + --m_size; + vtx.m_verticesEnt.unlink(m_vertices, &vtx); +} + +void DfgGraph::forEachVertex(std::function f) { + for (DfgVertex *vtxp = m_vertices.begin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->m_verticesEnt.nextp(); + f(*vtxp); + } +} + +void DfgGraph::forEachVertex(std::function f) const { + for (const DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) { + f(*vtxp); + } +} + +void DfgGraph::forEachVertexInReverse(std::function f) { + for (DfgVertex *vtxp = m_vertices.rbegin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->m_verticesEnt.prevp(); + f(*vtxp); + } +} + +template +Vertex* DfgGraph::findVertex(std::function p) const { + static_assert(std::is_base_of::value, + "'Vertex' must be subclass of 'DfgVertex'"); + for (DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) { + if (Vertex* const vvtxp = vtxp->cast()) { + if (p(*vvtxp)) return vvtxp; + } + } + return nullptr; +} + +void DfgVertex::forEachSource(std::function f) const { + const auto pair = sourceEdges(); + const DfgEdge* const edgesp = pair.first; + const size_t arity = pair.second; + for (size_t i = 0; i < arity; ++i) { + if (DfgVertex* const sourcep = edgesp[i].m_sourcep) f(*sourcep); + } +} + +void DfgVertex::forEachSink(std::function f) { + for (const DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) f(*edgep->m_sinkp); +} + +void DfgVertex::forEachSink(std::function f) const { + for (const DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) f(*edgep->m_sinkp); +} + +void DfgVertex::forEachSourceEdge(std::function f) { + const auto pair = sourceEdges(); + DfgEdge* const edgesp = pair.first; + const size_t arity = pair.second; + for (size_t i = 0; i < arity; ++i) f(edgesp[i], i); +} + +void DfgVertex::forEachSourceEdge(std::function f) const { + const auto pair = sourceEdges(); + const DfgEdge* const edgesp = pair.first; + const size_t arity = pair.second; + for (size_t i = 0; i < arity; ++i) f(edgesp[i], i); +} + +void DfgVertex::forEachSinkEdge(std::function f) { + for (DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) { + nextp = edgep->m_nextp; + f(*edgep); + } +} + +void DfgVertex::forEachSinkEdge(std::function f) const { + for (DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) { + nextp = edgep->m_nextp; + f(*edgep); + } +} + +template +Vertex* DfgVertex::findSink(std::function p) const { + static_assert(std::is_base_of::value, + "'Vertex' must be subclass of 'DfgVertex'"); + for (DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) { + if (Vertex* const sinkp = edgep->m_sinkp->cast()) { + if (p(*sinkp)) return sinkp; + } + } + return nullptr; +} + +template +Vertex* DfgVertex::findSink() const { + static_assert(!std::is_same::value, + "'Vertex' must be proper subclass of 'DfgVertex'"); + return findSink([](const Vertex&) { return true; }); +} + +bool DfgVertex::isZero() const { + if (const DfgConst* const constp = cast()) return constp->isZero(); + return false; +} + +bool DfgVertex::isOnes() const { + if (const DfgConst* const constp = cast()) return constp->isOnes(); + return false; +} + +#endif diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp new file mode 100644 index 000000000..bae5e7a33 --- /dev/null +++ b/src/V3DfgAstToDfg.cpp @@ -0,0 +1,267 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Convert AstModule to DfgGraph +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Convert and AstModule to a DfgGraph. We proceed by visiting convertable logic blocks (e.g.: +// AstAssignW of appropriate type and with no delays), recursively constructing DfgVertex instances +// for the expressions that compose the subject logic block. If all expressions in the current +// logic block can be converted, then we delete the logic block (now represented in the DfgGraph), +// and connect the corresponding DfgVertex instances appropriately. If some of the expressions were +// not convertible in the current logic block, we revert (delete) the DfgVertex instances created +// for the logic block, and leave the logic block in the AstModule. Any variable reference from +// non-converted logic blocks (or other constructs under the AstModule) are marked as being +// referenced in the AstModule, which is relevant for later optimization. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3Error.h" +#include "V3Global.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +namespace { + +// Create a DfgVertex out of a AstNodeMath. For most AstNodeMath subtypes, this can be done +// automatically. For the few special cases, we provide specializations below +template +Vertex* makeVertex(const AstForDfg* nodep, DfgGraph& dfg) { + return new Vertex{dfg, nodep->fileline(), DfgVertex::dtypeFor(nodep)}; +} + +//====================================================================== +// Currently unhandled nodes +// LCOV_EXCL_START +// AstCCast changes width, but should not exists where DFG optimization is currently invoked +template <> +DfgCCast* makeVertex(const AstCCast*, DfgGraph&) { + return nullptr; +} +// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway +template <> +DfgAtoN* makeVertex(const AstAtoN*, DfgGraph&) { + return nullptr; +} +// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway +template <> +DfgCompareNN* makeVertex(const AstCompareNN*, DfgGraph&) { + return nullptr; +} +// Unhandled in DfgToAst, but also operates on unpacked arrays which we don't optimize anyway +template <> +DfgSliceSel* makeVertex(const AstSliceSel*, DfgGraph&) { + return nullptr; +} +// LCOV_EXCL_STOP + +} // namespace + +class AstToDfgVisitor final : public VNVisitor { + // NODE STATE + + // AstNode::user1p // DfgVertex for this AstNode + const VNUser1InUse m_user1InUse; + + // STATE + + DfgGraph* const m_dfgp; // The graph being built + V3DfgOptimizationContext& m_ctx; // The optimization context for stats + bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit' + std::vector m_uncommittedVertices; // Vertices that we might decide to revert + + // METHODS + 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; + getNet(refp->varp())->setHasModRefs(); + }); + } + + void commitVertices() { m_uncommittedVertices.clear(); } + + void revertUncommittedVertices() { + for (DfgVertex* const vtxp : m_uncommittedVertices) vtxp->unlinkDelete(*m_dfgp); + m_uncommittedVertices.clear(); + } + + DfgVar* getNet(AstVar* varp) { + if (!varp->user1p()) { + // Note DfgVar vertices are not added to m_uncommittedVertices, because we want to + // hold onto them via AstVar::user1p, and the AstVar which might be referenced via + // multiple AstVarRef instances, so we will never revert a DfgVar once created. This + // means we can end up with DfgVar vertices in the graph which have no connections at + // all (which is fine for later processing). + varp->user1p(new DfgVar{*m_dfgp, varp}); + } + return varp->user1u().to(); + } + + DfgVertex* getVertex(AstNode* nodep) { + DfgVertex* vtxp = nodep->user1u().to(); + UASSERT_OBJ(vtxp, nodep, "Missing Dfg vertex"); + return vtxp; + } + + // Returns true if the expression cannot (or should not) be represented by DFG + bool unhandled(AstNodeMath* nodep) { + // Short-circuiting if something was already unhandled + if (!m_foundUnhandled) { + // Impure nodes cannot be represented + if (!nodep->isPure()) { + m_foundUnhandled = true; + ++m_ctx.m_nonRepImpure; + } + // Check node has supported dtype + if (!DfgVertex::isSupportedDType(nodep->dtypep())) { + m_foundUnhandled = true; + ++m_ctx.m_nonRepDType; + } + } + return m_foundUnhandled; + } + + // VISITORS + void visit(AstNode* nodep) override { + // Conservatively treat this node as unhandled + m_foundUnhandled = true; + ++m_ctx.m_nonRepUnknown; + markReferenced(nodep); + } + void visit(AstCell* nodep) override { markReferenced(nodep); } + void visit(AstNodeProcedure* nodep) override { markReferenced(nodep); } + void visit(AstVar* nodep) override { + // No need to (and in fact cannot) handle variables with unsupported dtypes + if (!DfgVertex::isSupportedDType(nodep->dtypep())) return; + // Mark ports as having external references + if (nodep->isIO()) getNet(nodep)->setHasExtRefs(); + // Mark variables that are the target of a hierarchical reference + // (these flags were set up in DataflowPrepVisitor) + if (nodep->user2()) getNet(nodep)->setHasExtRefs(); + } + + void visit(AstAssignW* nodep) override { + // Cannot handle assignment with timing control yet + if (nodep->timingControlp()) { + markReferenced(nodep); + ++m_ctx.m_nonRepTiming; + return; + } + + // Cannot handle mismatched widths. Mismatched assignments should have been fixed up in + // earlier passes anyway, so this should never be hit, but being paranoid just in case. + if (nodep->lhsp()->width() != nodep->rhsp()->width()) { // LCOV_EXCL_START + markReferenced(nodep); + ++m_ctx.m_nonRepWidth; + return; + } // LCOV_EXCL_START + + // Simple assignment with whole variable on left-hand side + if (AstVarRef* const vrefp = VN_CAST(nodep->lhsp(), VarRef)) { + UASSERT_OBJ(m_uncommittedVertices.empty(), nodep, "Should not nest"); + + // Build DFG vertices representing the two sides + { + m_foundUnhandled = false; + iterate(vrefp); + iterate(nodep->rhsp()); + // If this assignment contains an AstNode not representable by a DfgVertex, + // then revert the graph. + if (m_foundUnhandled) { + revertUncommittedVertices(); + markReferenced(nodep); + return; + } + } + + // Connect the vertices representing the 2 sides + DfgVar* const lVtxp = getVertex(vrefp)->as(); + DfgVertex* const rVtxp = getVertex(nodep->rhsp()); + lVtxp->driverp(rVtxp); + lVtxp->assignmentFileline(nodep->fileline()); + commitVertices(); + + // Remove assignment from Ast. Now represented by the Dfg. + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + + // + ++m_ctx.m_representable; + return; + } + + // TODO: handle complex left-hand sides + markReferenced(nodep); + ++m_ctx.m_nonRepLhs; + } + + void visit(AstVarRef* nodep) override { + UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + + if (nodep->access().isRW() // Cannot represent read-write references + || nodep->varp()->isIfaceRef() // Cannot handle interface references + || nodep->varp()->delayp() // Cannot handle delayed variables + || nodep->classOrPackagep() // Cannot represent cross module references + ) { + 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; + ++m_ctx.m_nonRepVarRef; + return; + } + + nodep->user1p(getNet(nodep->varp())); + } + + void visit(AstConst* nodep) override { + UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + DfgVertex* const vtxp = new DfgConst{*m_dfgp, nodep->cloneTree(false)}; + m_uncommittedVertices.push_back(vtxp); + nodep->user1p(vtxp); + } + + // The rest of the 'visit' methods are generated by 'astgen' +#include "V3Dfg__gen_ast_to_dfg.h" + + // CONSTRUCTOR + explicit AstToDfgVisitor(AstModule& module, V3DfgOptimizationContext& ctx) + : m_dfgp{new DfgGraph{module, module.name()}} + , m_ctx{ctx} { + // Build the DFG + iterateChildren(&module); + UASSERT_OBJ(m_uncommittedVertices.empty(), &module, "Uncommitted vertices remain"); + } + +public: + static DfgGraph* apply(AstModule& module, V3DfgOptimizationContext& ctx) { + return AstToDfgVisitor{module, ctx}.m_dfgp; + } +}; + +DfgGraph* V3DfgPasses::astToDfg(AstModule& module, V3DfgOptimizationContext& ctx) { + return AstToDfgVisitor::apply(module, ctx); +} diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp new file mode 100644 index 000000000..6084ba5be --- /dev/null +++ b/src/V3DfgDfgToAst.cpp @@ -0,0 +1,314 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Convert DfgGraph to AstModule +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Convert DfgGraph back to AstModule. We recursively construct AstNodeMath expressions for each +// DfgVertex which represents a storage location (e.g.: DfgVar), or has multiple sinks without +// driving a storage location (and hence needs a temporary variable to duplication). The recursion +// stops when we reach a DfgVertex representing a storage location (e.g.: DfgVar), or a vertex that +// that has multiple sinks (as these nodes will have a [potentially new temporary] corresponding +// storage location). Redundant variables (those whose source vertex drives multiple variables) are +// eliminated when possible. Vertices driving multiple variables are rendered once, driving an +// arbitrarily (but deterministically) chosen canonical variable, and the corresponding redundant +// variables are assigned from the canonical variable. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3UniqueNames.h" + +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +namespace { + +// Create an AstNodeMath out of a DfgVertex. For most AstNodeMath subtypes, this can be done +// automatically. For the few special cases, we provide specializations below +template +Node* makeNode(const DfgForAst* vtxp, Ops... ops) { + Node* const nodep = new Node{vtxp->fileline(), ops...}; + UASSERT_OBJ(nodep->width() == static_cast(vtxp->width()), vtxp, + "Incorrect width in AstNode created from DfgVertex " + << vtxp->typeName() << ": " << nodep->width() << " vs " << vtxp->width()); + return nodep; +} + +//====================================================================== +// Vertices needing special conversion + +template <> +AstExtend* makeNode( // + const DfgExtend* vtxp, AstNodeMath* op1) { + return new AstExtend{vtxp->fileline(), op1, static_cast(vtxp->width())}; +} + +template <> +AstExtendS* makeNode( // + const DfgExtendS* vtxp, AstNodeMath* op1) { + return new AstExtendS{vtxp->fileline(), op1, static_cast(vtxp->width())}; +} + +template <> +AstShiftL* makeNode( // + const DfgShiftL* vtxp, AstNodeMath* op1, AstNodeMath* op2) { + return new AstShiftL{vtxp->fileline(), op1, op2, static_cast(vtxp->width())}; +} + +template <> +AstShiftR* makeNode( // + const DfgShiftR* vtxp, AstNodeMath* op1, AstNodeMath* op2) { + return new AstShiftR{vtxp->fileline(), op1, op2, static_cast(vtxp->width())}; +} + +template <> +AstShiftRS* makeNode( // + const DfgShiftRS* vtxp, AstNodeMath* op1, AstNodeMath* op2) { + return new AstShiftRS{vtxp->fileline(), op1, op2, static_cast(vtxp->width())}; +} + +//====================================================================== +// Currently unhandled nodes - see corresponding AstToDfg functions +// LCOV_EXCL_START +template <> +AstCCast* makeNode(const DfgCCast* vtxp, AstNodeMath*) { + vtxp->v3fatal("not implemented"); +} +template <> +AstAtoN* makeNode(const DfgAtoN* vtxp, AstNodeMath*) { + vtxp->v3fatal("not implemented"); +} +template <> +AstCompareNN* makeNode(const DfgCompareNN* vtxp, + AstNodeMath*, AstNodeMath*) { + vtxp->v3fatal("not implemented"); +} +template <> +AstSliceSel* makeNode( + const DfgSliceSel* vtxp, AstNodeMath*, AstNodeMath*, AstNodeMath*) { + vtxp->v3fatal("not implemented"); +} +// LCOV_EXCL_STOP + +} // namespace + +class DfgToAstVisitor final : DfgVisitor { + // STATE + + AstModule* const m_modp; // The parent/result module + V3DfgOptimizationContext& m_ctx; // The optimization context for stats + AstNodeMath* 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 + DfgVertex::HashCache m_hashCache; // For caching hashes + + // METHODS + + // Given a DfgVar, return the canonical AstVar that can be used for this DfgVar. + // Also builds the m_canonVars map as a side effect. + AstVar* getCanonicalVar(const DfgVar* vtxp) { + // Variable only read by DFG + if (!vtxp->driverp()) return vtxp->varp(); + + // Look up map + const auto it = m_canonVars.find(vtxp->varp()); + if (it != m_canonVars.end()) return it->second; + + // Not known yet, compute it (for all vars from the same driver) + std::vector varps; + vtxp->driverp()->forEachSink([&](const DfgVertex& vtx) { + if (const DfgVar* const varVtxp = vtx.cast()) varps.push_back(varVtxp); + }); + UASSERT_OBJ(!varps.empty(), vtxp, "The input vtxp->varp() is always available"); + std::stable_sort(varps.begin(), varps.end(), [](const DfgVar* ap, const DfgVar* bp) { + if (ap->hasExtRefs() != bp->hasExtRefs()) return ap->hasExtRefs(); + const FileLine& aFl = *(ap->fileline()); + const FileLine& bFl = *(bp->fileline()); + if (const int cmp = aFl.operatorCompare(bFl)) return cmp < 0; + return ap->varp()->name() < bp->varp()->name(); + }); + AstVar* const canonVarp = varps.front()->varp(); + + // Add results to map + for (const DfgVar* const varp : varps) m_canonVars.emplace(varp->varp(), canonVarp); + + // Return it + return canonVarp; + } + + // Given a DfgVertex, return an AstVar that will hold the value of the given DfgVertex once we + // are done with converting this Dfg into Ast form. + AstVar* getResultVar(const DfgVertex* vtxp) { + const auto pair = m_resultVars.emplace(vtxp, nullptr); + AstVar*& varp = pair.first->second; + if (pair.second) { + // If this vertex is a DfgVar, then we know the variable. If this node is not a DfgVar, + // then first we try to find a DfgVar driven by this node, and use that, otherwise we + // create a temporary + if (const DfgVar* const thisDfgVarp = vtxp->cast()) { + // This is a DfgVar + varp = getCanonicalVar(thisDfgVarp); + } else if (const DfgVar* const sinkDfgVarp = vtxp->findSink()) { + // We found a DfgVar driven by this node + varp = getCanonicalVar(sinkDfgVarp); + } else { + // No DfgVar driven by this node. Create a temporary. + const string name = m_tmpNames.get(vtxp->hash(m_hashCache).toString()); + // Note: It is ok for these temporary variables to be always unsigned. They are + // read only by other expressions within the graph and all expressions interpret + // their operands based on the expression type, not the operand type. + AstNodeDType* const dtypep = v3Global.rootp()->findBitDType( + vtxp->width(), vtxp->width(), VSigning::UNSIGNED); + varp = new AstVar{vtxp->fileline(), VVarType::MODULETEMP, name, dtypep}; + // Add temporary AstVar to containing module + m_modp->addStmtsp(varp); + } + // Add to map + } + return varp; + } + + AstNodeMath* convertDfgVertexToAstNodeMath(DfgVertex* vtxp) { + UASSERT_OBJ(!m_resultp, vtxp, "Result already computed"); + iterate(vtxp); + UASSERT_OBJ(m_resultp, vtxp, "Missing result"); + AstNodeMath* const resultp = m_resultp; + m_resultp = nullptr; + return resultp; + } + + AstNodeMath* convertSource(DfgVertex* vtxp) { + if (vtxp->hasMultipleSinks()) { + // Vertices with multiple sinks need a temporary variable, just return a reference + return new AstVarRef{vtxp->fileline(), getResultVar(vtxp), VAccess::READ}; + } else { + // Vertex with single sink is simply recursively converted + UASSERT_OBJ(vtxp->hasSinks(), vtxp, "Must have one sink: " << vtxp->typeName()); + return convertDfgVertexToAstNodeMath(vtxp); + } + } + + // VISITORS + void visit(DfgVertex* vtxp) override { // LCOV_EXCL_START + vtxp->v3fatal("Unhandled DfgVertex: " << vtxp->typeName()); + } // LCOV_EXCL_STOP + + void visit(DfgVar* vtxp) override { + m_resultp = new AstVarRef{vtxp->fileline(), getCanonicalVar(vtxp), VAccess::READ}; + } + + void visit(DfgConst* vtxp) override { // + m_resultp = vtxp->constp()->cloneTree(false); + } + + // The rest of the 'visit' methods are generated by 'astgen' +#include "V3Dfg__gen_dfg_to_ast.h" + + // Constructor + explicit DfgToAstVisitor(DfgGraph& dfg, V3DfgOptimizationContext& ctx) + : m_modp{dfg.modulep()} + , m_ctx{ctx} { + // We can eliminate some variables completely + std::vector redundantVarps; + + // Render the logic + dfg.forEachVertex([&](DfgVertex& vtx) { + // Compute the AstNodeMath expression representing this DfgVertex + AstNodeMath* rhsp = nullptr; + AstNodeMath* lhsp = nullptr; + FileLine* assignmentFlp = nullptr; + if (const DfgVar* const dfgVarp = vtx.cast()) { + // DfgVar instances (these might be driving the given AstVar variable) + // If there is no driver (i.e.: this DfgVar is an input to the Dfg), then nothing + // to do + if (!dfgVarp->driverp()) return; + // The driver of this DfgVar might drive multiple variables. Only emit one + // assignment from the driver to an arbitrarily chosen canonical variable, and + // assign the other variables from that canonical variable + AstVar* const canonVarp = getCanonicalVar(dfgVarp); + if (canonVarp == dfgVarp->varp()) { + // This is the canonical variable, so render the driver + rhsp = convertDfgVertexToAstNodeMath(dfgVarp->driverp()); + } else if (dfgVarp->keep()) { + // Not the canonical variable but it must be kept, just assign from the + // canonical variable. + rhsp = new AstVarRef{canonVarp->fileline(), canonVarp, VAccess::READ}; + } else { + // Not a canonical var, and it can be removed. We will replace all references + // to it with the canonical variable, and hence this can be removed. + redundantVarps.push_back(dfgVarp->varp()); + ++m_ctx.m_replacedVars; + return; + } + // The Lhs is the variable driven by this DfgVar + lhsp = new AstVarRef{vtx.fileline(), dfgVarp->varp(), VAccess::WRITE}; + // Set location to the location of the original assignment to this variable + assignmentFlp = dfgVarp->assignmentFileline(); + } else if (vtx.hasMultipleSinks() && !vtx.findSink()) { + // DfgVertex that has multiple sinks, but does not drive a DfgVar (needs temporary) + // Just render the logic + rhsp = convertDfgVertexToAstNodeMath(&vtx); + // The lhs is a temporary + lhsp = new AstVarRef{vtx.fileline(), getResultVar(&vtx), VAccess::WRITE}; + // Render vertex + assignmentFlp = vtx.fileline(); + // Stats + ++m_ctx.m_intermediateVars; + } else { + // Every other DfgVertex will be inlined by 'convertDfgVertexToAstNodeMath' as an + // AstNodeMath at use, and hence need not be converted. + return; + } + // Add assignment of the value to the variable + m_modp->addStmtsp(new AstAssignW{assignmentFlp, lhsp, rhsp}); + ++m_ctx.m_resultEquations; + }); + + // Remap all references to point to the canonical variables, if one exists + VNDeleter deleter; + m_modp->foreach([&](AstVarRef* refp) { + // Any variable that is written outside the DFG will have itself as the canonical + // var, so need not be replaced, furthermore, if a variable is traced, we don't + // want to update the write ref we just created above, so we only replace read only + // references. + if (!refp->access().isReadOnly()) return; + const auto it = m_canonVars.find(refp->varp()); + if (it == m_canonVars.end()) return; + if (it->second == refp->varp()) return; + refp->replaceWith(new AstVarRef{refp->fileline(), it->second, refp->access()}); + deleter.pushDeletep(refp); + }); + + // Remove redundant variables + for (AstVar* const varp : redundantVarps) varp->unlinkFrBack()->deleteTree(); + } + +public: + static AstModule* apply(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { + return DfgToAstVisitor{dfg, ctx}.m_modp; + } +}; + +AstModule* V3DfgPasses::dfgToAst(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { + return DfgToAstVisitor::apply(dfg, ctx); +} diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp new file mode 100644 index 000000000..d4491b5e0 --- /dev/null +++ b/src/V3DfgOptimizer.cpp @@ -0,0 +1,292 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Dataflow based optimization of combinational logic +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// High level entry points from Ast world to the DFG optimizer. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3DfgOptimizer.h" + +#include "V3Ast.h" +#include "V3AstUserAllocator.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3Error.h" +#include "V3Global.h" +#include "V3Graph.h" +#include "V3UniqueNames.h" + +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +// Extract more combinational logic equations from procedures for better optimization opportunities +class DataflowExtractVisitor final : public VNVisitor { + // NODE STATE + // AstVar::user3 -> bool: Flag indicating variable is subject of force or release + // statement AstVar::user4 -> bool: Flag indicating variable is combinationally driven + // AstNodeModule::user4 -> Extraction candidates (via m_extractionCandidates) + const VNUser3InUse m_user3InUse; + const VNUser4InUse m_user4InUse; + + // Expressions considered for extraction as separate assignment to gain more opportunities for + // optimization, together with the list of variables they read. + using Candidates = std::vector>>; + + // Expressions considered for extraction. All the candidates are pure expressions. + AstUser4Allocator m_extractionCandidates; + + // STATE + AstNodeModule* m_modp = nullptr; // The module being visited + Candidates* m_candidatesp = nullptr; + bool m_impure = false; // True if the visited tree has a side effect + bool m_inForceReleaseLhs = false; // Iterating LHS of force/release + // List of AstVar nodes read by the visited tree. 'vector' rather than 'set' as duplicates are + // somewhat unlikely and we can handle them later. + std::vector m_readVars; + + // METHODS + + // Node considered for extraction as a combinational equation. Trace variable usage/purity. + void iterateExtractionCandidate(AstNode* nodep) { + UASSERT_OBJ(!VN_IS(nodep->backp(), NodeMath), nodep, + "Should not try to extract nested expressions (only root expressions)"); + + // Simple VarRefs should not be extracted, as they only yield trivial assignments. + // Similarly, don't extract anything if no candidate map is set up (for non-modules). + // We still need to visit them though, to mark hierarchical references. + if (VN_IS(nodep, NodeVarRef) || !m_candidatesp) { + iterate(nodep); + return; + } + + // Don't extract plain constants + if (VN_IS(nodep, Const)) return; + + // Candidates can't nest, so no need for VL_RESTORER, just initialize iteration state + m_impure = false; + m_readVars.clear(); + + // Trace variable usage + iterate(nodep); + + // We only extract pure expressions + if (m_impure) return; + + // Do not extract expressions without any variable references + if (m_readVars.empty()) return; + + // Add to candidate list + m_candidatesp->emplace_back(VN_AS(nodep, NodeMath), std::move(m_readVars)); + } + + // VISIT methods + + void visit(AstNetlist* nodep) override { + // Analyse the whole design + iterateChildrenConst(nodep); + + // Replace candidate expressions only reading combinationally driven signals with variables + V3UniqueNames names{"_VdfgExtracted__"}; + for (AstNodeModule* modp = nodep->modulesp(); modp; + modp = VN_AS(modp->nextp(), NodeModule)) { + // Only extract from proper modules + if (!VN_IS(modp, Module)) continue; + + for (const auto& pair : m_extractionCandidates(modp)) { + AstNodeMath* const nodep = pair.first; + + // Do not extract expressions without any variable references + if (pair.second.empty()) continue; + + // Check if all variables read by this expression are driven combinationally, + // and move on if not. Also don't extract it if one of the variables is subject + // to a force/release, as releasing nets must have immediate effect, but adding + // extra combinational logic can change semantics (see t_force_release_net*). + { + bool hasBadVar = false; + for (const AstVar* const readVarp : pair.second) { + // variable is target of force/release or not combinationally driven + if (readVarp->user3() || !readVarp->user4()) { + hasBadVar = true; + break; + } + } + if (hasBadVar) continue; + } + + // Create temporary variable + FileLine* const flp = nodep->fileline(); + const string name = names.get(nodep); + AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, nodep->dtypep()}; + varp->trace(false); + modp->addStmtsp(varp); + + // Replace expression with temporary variable + nodep->replaceWith(new AstVarRef{flp, varp, VAccess::READ}); + + // Add assignment driving temporary variable + modp->addStmtsp( + new AstAssignW{flp, new AstVarRef{flp, varp, VAccess::WRITE}, nodep}); + } + } + } + + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_modp); + m_modp = nodep; + iterateChildrenConst(nodep); + } + + void visit(AstAlways* nodep) override { + VL_RESTORER(m_candidatesp); + // Only extract from combinational logic under proper modules + const bool isComb = !nodep->sensesp() + && (nodep->keyword() == VAlwaysKwd::ALWAYS + || nodep->keyword() == VAlwaysKwd::ALWAYS_COMB + || nodep->keyword() == VAlwaysKwd::ALWAYS_LATCH); + m_candidatesp + = isComb && VN_IS(m_modp, Module) ? &m_extractionCandidates(m_modp) : nullptr; + iterateChildrenConst(nodep); + } + + void visit(AstAssignW* nodep) override { + // Mark LHS variable as combinationally driven + if (AstVarRef* const vrefp = VN_CAST(nodep->lhsp(), VarRef)) vrefp->varp()->user4(true); + // + iterateChildrenConst(nodep); + } + + void visit(AstAssign* nodep) override { + iterateExtractionCandidate(nodep->rhsp()); + iterate(nodep->lhsp()); + } + + void visit(AstAssignDly* nodep) override { + iterateExtractionCandidate(nodep->rhsp()); + iterate(nodep->lhsp()); + } + + void visit(AstIf* nodep) override { + iterateExtractionCandidate(nodep->condp()); + iterateAndNextConstNull(nodep->thensp()); + iterateAndNextConstNull(nodep->elsesp()); + } + + void visit(AstAssignForce* nodep) override { + iterate(nodep->rhsp()); + UASSERT_OBJ(!m_inForceReleaseLhs, nodep, "Should not nest"); + m_inForceReleaseLhs = true; + iterate(nodep->lhsp()); + m_inForceReleaseLhs = false; + } + + void visit(AstRelease* nodep) override { + UASSERT_OBJ(!m_inForceReleaseLhs, nodep, "Should not nest"); + m_inForceReleaseLhs = true; + iterate(nodep->lhsp()); + m_inForceReleaseLhs = false; + } + + void visit(AstNodeMath* nodep) override { iterateChildrenConst(nodep); } + + void visit(AstNodeVarRef* nodep) override { + if (nodep->access().isWriteOrRW()) { + // If it writes a variable, mark as impure + m_impure = true; + // Mark target of force/release + if (m_inForceReleaseLhs) nodep->varp()->user3(true); + } else { + // Otherwise, add read reference + m_readVars.push_back(nodep->varp()); + } + } + + void visit(AstNode* nodep) override { + // Conservatively assume unhandled nodes are impure. This covers all AstNodeFTaskRef + // as AstNodeFTaskRef are sadly not AstNodeMath. + m_impure = true; + // Still need to gather all references/force/release, etc. + iterateChildrenConst(nodep); + } + + // CONSTRUCTOR + explicit DataflowExtractVisitor(AstNetlist* netlistp) { iterate(netlistp); } + +public: + static void apply(AstNetlist* netlistp) { DataflowExtractVisitor{netlistp}; } +}; + +void V3DfgOptimizer::extract(AstNetlist* netlistp) { + UINFO(2, __FUNCTION__ << ": " << endl); + // Extract more optimization candidates + DataflowExtractVisitor::apply(netlistp); + V3Global::dumpCheckGlobalTree("dfg-extract", 0, dumpTree() >= 3); +} + +void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) { + UINFO(2, __FUNCTION__ << ": " << endl); + + // NODE STATE + // AstVar::user1 -> Used by V3DfgPasses::astToDfg + // AstVar::user2 -> bool: Flag indicating referenced by AstVarXRef + const VNUser2InUse user2InUse; + + // 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; + + UINFO(3, "Applying DFG optimization to module'" << modp->name() << "'" << endl); + ++ctx.m_modules; + + // Build the DFG of this module + const std::unique_ptr dfg{V3DfgPasses::astToDfg(*modp, ctx)}; + if (dumpDfg() >= 9) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-input"); + + // Split the DFG into independent components + const std::vector>& components = dfg->splitIntoComponents(); + + // For each component + for (auto& component : components) { + // Reverse topologically sort the component + const bool acyclic = component->sortTopologically(/* reverse: */ true); + // Optimize the component (iff it is not cyclic) + if (VL_LIKELY(acyclic)) { + V3DfgPasses::optimize(*component, ctx); + } else if (dumpDfg() >= 7) { + component->dumpDotFilePrefixed(ctx.prefix() + "cyclic"); + } + // Add back under the main DFG (we will convert back in one go) + dfg->addGraph(*component); + } + + // Convert back to Ast + if (dumpDfg() >= 9) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-optimized"); + AstModule* const resultModp = V3DfgPasses::dfgToAst(*dfg, ctx); + UASSERT_OBJ(resultModp == modp, modp, "Should be the same module"); + } + V3Global::dumpCheckGlobalTree("dfg-optimize", 0, dumpTree() >= 3); +} diff --git a/src/V3DfgOptimizer.h b/src/V3DfgOptimizer.h new file mode 100644 index 000000000..5377dcd23 --- /dev/null +++ b/src/V3DfgOptimizer.h @@ -0,0 +1,35 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Dataflow based optimization of combinational logic +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3DFGOPTIMIZER_H_ +#define VERILATOR_V3DFGOPTIMIZER_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" + +//============================================================================ + +namespace V3DfgOptimizer { +// Extract further logic blocks from the design for additional optimization opportunities +void extract(AstNetlist*); + +// Optimize the design +void optimize(AstNetlist*, const string& label); +} // namespace V3DfgOptimizer + +#endif // Guard diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp new file mode 100644 index 000000000..a2c998025 --- /dev/null +++ b/src/V3DfgPasses.cpp @@ -0,0 +1,213 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Implementations of simple passes over DfgGraph +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "config_build.h" + +#include "V3DfgPasses.h" + +#include "V3Dfg.h" +#include "V3Global.h" +#include "V3String.h" + +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +V3DfgCseContext::~V3DfgCseContext() { + V3Stats::addStat("Optimizations, DFG " + m_label + " CSE, expressions eliminated", + m_eliminated); +} + +DfgRemoveVarsContext::~DfgRemoveVarsContext() { + V3Stats::addStat("Optimizations, DFG " + m_label + " Remove vars, variables removed", + m_removed); +} + +static std::string getPrefix(const std::string& label) { + if (label.empty()) return ""; + std::string str = VString::removeWhitespace(label); + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { // + return c == ' ' ? '-' : std::tolower(c); + }); + str += "-"; + return str; +} + +V3DfgOptimizationContext::V3DfgOptimizationContext(const std::string& label) + : m_label{label} + , m_prefix{getPrefix(label)} {} + +V3DfgOptimizationContext::~V3DfgOptimizationContext() { + const string prefix = "Optimizations, DFG " + m_label + " "; + V3Stats::addStat(prefix + "General, modules", m_modules); + V3Stats::addStat(prefix + "Ast2Dfg, representable", m_representable); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (dtype)", m_nonRepDType); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (impure)", m_nonRepImpure); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (timing)", m_nonRepTiming); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (lhs)", m_nonRepLhs); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (node)", m_nonRepNode); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (unknown)", m_nonRepUnknown); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (var ref)", m_nonRepVarRef); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (width)", m_nonRepWidth); + V3Stats::addStat(prefix + "Dfg2Ast, intermediate variables", m_intermediateVars); + V3Stats::addStat(prefix + "Dfg2Ast, replaced variables", m_replacedVars); + V3Stats::addStat(prefix + "Dfg2Ast, result equations", m_resultEquations); +} + +// 'Inline' DfgVar nodes with known drivers +void V3DfgPasses::inlineVars(DfgGraph& dfg) { + dfg.forEachVertex([](DfgVertex& vtx) { + // For each DfgVar that has a known driver + if (DfgVar* const varVtxp = vtx.cast()) { + if (DfgVertex* const driverp = varVtxp->driverp()) { + // Make consumers of the DfgVar consume the driver directly + varVtxp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); }); + } + } + }); +} + +// Common subexpression elimination +void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) { + DfgVertex::HashCache hashCache; + DfgVertex::EqualsCache equalsCache; + std::unordered_multimap verticesWithEqualHashes; + + // In reverse, as the graph is sometimes in reverse topological order already + dfg.forEachVertexInReverse([&](DfgVertex& vtx) { + // Don't merge constants + if (vtx.is()) return; + // For everything else... + const V3Hash hash = vtx.hash(hashCache); + auto pair = verticesWithEqualHashes.equal_range(hash); + for (auto it = pair.first, end = pair.second; it != end; ++it) { + DfgVertex* const candidatep = it->second; + if (candidatep->equals(vtx, equalsCache)) { + ++ctx.m_eliminated; + vtx.replaceWith(candidatep); + vtx.unlinkDelete(dfg); + return; + } + } + verticesWithEqualHashes.emplace(hash, &vtx); + }); +} + +void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { + dfg.forEachVertex([&](DfgVertex& vtx) { + // We can eliminate certain redundant DfgVar vertices + DfgVar* const varp = vtx.cast(); + if (!varp) return; + + // Can't remove if it has consumers + if (varp->hasSinks()) return; + + // Can't remove if read in the module and driven here (i.e.: it's an output of the DFG) + if (varp->hasModRefs() && varp->driverp()) return; + + // Can't remove if referenced externally, or other special reasons + if (varp->keep()) return; + + // If the driver of this variable has multiple non-variable sinks, then we would need + // a temporary when rendering the graph. Instead of introducing a temporary, keep the + // first variable that is driven by that driver + if (DfgVertex* const driverp = varp->driverp()) { + unsigned nonVarSinks = 0; + const DfgVar* firstSinkVarp = nullptr; + const bool keepFirst = driverp->findSink([&](const DfgVertex& sink) { + if (const DfgVar* const sinkVarp = sink.cast()) { + if (!firstSinkVarp) firstSinkVarp = sinkVarp; + } else { + ++nonVarSinks; + } + // We can stop as soon as we found the first var, and 2 non-var sinks + return firstSinkVarp && nonVarSinks >= 2; + }); + // Keep this DfgVar if needed + if (keepFirst && firstSinkVarp == varp) return; + } + + // OK, we can delete this DfgVar! + ++ctx.m_removed; + + // If not referenced outside the DFG, then also delete the referenced AstVar, + // as it is now unused. + if (!varp->hasRefs()) varp->varp()->unlinkFrBack()->deleteTree(); + + // Unlink and delete vertex + vtx.unlinkDelete(dfg); + }); +} + +void V3DfgPasses::removeUnused(DfgGraph& dfg) { + const auto processVertex = [&](DfgVertex& vtx) { + // Keep variables + if (vtx.is()) return false; + // Keep if it has sinks + if (vtx.hasSinks()) return false; + // Unlink and delete vertex + vtx.unlinkDelete(dfg); + return true; + }; + + dfg.runToFixedPoint(processVertex); +} + +void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { + // There is absolutely nothing useful we can do with a graph of size 2 or less + if (dfg.size() <= 2) return; + + // We consider a DFG trivial if it contains no more than 1 non-variable, non-constant vertex + unsigned excitingVertices = 0; + const bool isTrivial = !dfg.findVertex([&](const DfgVertex& vtx) { // + if (vtx.is()) return false; + if (vtx.is()) return false; + return ++excitingVertices >= 2; + }); + + int passNumber = 0; + + const auto apply = [&](int dumpLevel, const string name, std::function pass) { + pass(); + if (dumpDfg() >= dumpLevel) { + const string strippedName = VString::removeWhitespace(name); + const string label + = ctx.prefix() + "pass-" + cvtToStr(passNumber) + "-" + strippedName; + dfg.dumpDotFilePrefixed(label); + } + ++passNumber; + }; + + if (!isTrivial) { + // Optimize non-trivial graph + if (dumpDfg() >= 8) { dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "input"); } + apply(3, "input ", [&]() {}); + apply(4, "inlineVars ", [&]() { inlineVars(dfg); }); + apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext0); }); + if (v3Global.opt.fDfgPeephole()) { + apply(4, "peephole ", [&]() { peephole(dfg, ctx.m_peepholeContext); }); + } + apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); + apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext1); }); + apply(3, "optimized ", [&]() { removeUnused(dfg); }); + if (dumpDfg() >= 8) { dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized"); } + } else { + // We can still eliminate redundancies from trivial graphs + apply(5, "trivial-input ", [&]() {}); + apply(6, "trivial-inlineVars ", [&]() { inlineVars(dfg); }); + apply(5, "trivial-optimized ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); + } +} diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h new file mode 100644 index 000000000..20adfd201 --- /dev/null +++ b/src/V3DfgPasses.h @@ -0,0 +1,113 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Passes over DfgGraph +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3DFGPASSES_H_ +#define VERILATOR_V3DFGPASSES_H_ + +#include "config_build.h" + +#include "V3DfgPeephole.h" + +class AstModule; +class DfgGraph; + +//=========================================================================== +// Various context objects hold data that need to persist across invocations +// of a DFG pass. + +class V3DfgCseContext final { + const std::string m_label; // Label to apply to stats + +public: + VDouble0 m_eliminated; // Number of common sub-expressions eliminated + V3DfgCseContext(const std::string& label) + : m_label{label} {} + ~V3DfgCseContext(); +}; + +class DfgRemoveVarsContext final { + const std::string m_label; // Label to apply to stats + +public: + VDouble0 m_removed; // Number of redundant variables removed + DfgRemoveVarsContext(const std::string& label) + : m_label{label} {} + ~DfgRemoveVarsContext(); +}; + +class V3DfgOptimizationContext final { + const std::string m_label; // Label to add to stats, etc. + const std::string m_prefix; // Prefix to add to file dumps (derived from label) + +public: + VDouble0 m_modules; // Number of modules optimized + VDouble0 m_representable; // Number of combinational equations representable + VDouble0 m_nonRepDType; // Equations non-representable due to data type + VDouble0 m_nonRepImpure; // Equations non-representable due to impure node + VDouble0 m_nonRepTiming; // Equations non-representable due to timing control + VDouble0 m_nonRepLhs; // Equations non-representable due to lhs + VDouble0 m_nonRepNode; // Equations non-representable due to node type + VDouble0 m_nonRepUnknown; // Equations non-representable due to unknown node + VDouble0 m_nonRepVarRef; // Equations non-representable due to variable reference + VDouble0 m_nonRepWidth; // Equations non-representable due to width mismatch + VDouble0 m_intermediateVars; // Number of intermediate variables introduced + VDouble0 m_replacedVars; // Number of variables replaced + VDouble0 m_resultEquations; // Number of result combinational equations + + V3DfgCseContext m_cseContext0{m_label + " 1st"}; + V3DfgCseContext m_cseContext1{m_label + " 2nd"}; + V3DfgPeepholeContext m_peepholeContext{m_label}; + DfgRemoveVarsContext m_removeVarsContext{m_label}; + V3DfgOptimizationContext(const std::string& label); + ~V3DfgOptimizationContext(); + + const std::string& prefix() const { return m_prefix; } +}; + +namespace V3DfgPasses { +//=========================================================================== +// Top level entry points +//=========================================================================== + +// Construct a DfGGraph representing the combinational logic in the given AstModule. The logic +// that is represented by the graph is removed from the given AstModule. Returns the +// constructed DfgGraph. +DfgGraph* astToDfg(AstModule&, V3DfgOptimizationContext&); + +// Optimize the given DfgGraph +void optimize(DfgGraph&, V3DfgOptimizationContext&); + +// Convert DfgGraph back into Ast, and insert converted graph back into its parent module. +// Returns the parent module. +AstModule* dfgToAst(DfgGraph&, V3DfgOptimizationContext&); + +//=========================================================================== +// Intermediate/internal operations +//=========================================================================== + +// Inline variables +void inlineVars(DfgGraph&); +// Common subexpression elimination +void cse(DfgGraph&, V3DfgCseContext&); +// Peephole optimizations +void peephole(DfgGraph&, V3DfgPeepholeContext&); +// Remove redundant variables +void removeVars(DfgGraph&, DfgRemoveVarsContext&); +// Remove unused nodes +void removeUnused(DfgGraph&); +} // namespace V3DfgPasses + +#endif diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp new file mode 100644 index 000000000..c27607e21 --- /dev/null +++ b/src/V3DfgPeephole.cpp @@ -0,0 +1,1144 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Peephole optimizations over DfgGraph +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// A pattern-matching based optimizer for DfgGraph. This is in some aspects similar to V3Const, but +// more powerful in that it does not care about ordering combinational statement. This is also less +// broadly applicable than V3Const, as it does not apply to procedural statements with sequential +// execution semantics. +// +//************************************************************************* + +#include "config_build.h" + +#include "V3DfgPeephole.h" + +#include "V3Ast.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3Stats.h" + +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +V3DfgPeepholeContext::V3DfgPeepholeContext(const std::string& label) + : m_label{label} { + const auto checkEnabled = [this](VDfgPeepholePattern id) { + string str{id.ascii()}; + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { // + return c == '_' ? '-' : std::tolower(c); + }); + m_enabled[id] = v3Global.opt.fDfgPeepholeEnabled(str); + }; +#define OPTIMIZATION_CHECK_ENABLED(id, name) checkEnabled(VDfgPeepholePattern::id); + FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_CHECK_ENABLED) +#undef OPTIMIZATION_CHECK_ENABLED +} + +V3DfgPeepholeContext::~V3DfgPeepholeContext() { + const auto emitStat = [this](VDfgPeepholePattern id) { + string str{id.ascii()}; + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { // + return c == '_' ? ' ' : std::tolower(c); + }); + V3Stats::addStat("Optimizations, DFG " + m_label + " Peephole, " + str, m_count[id]); + }; +#define OPTIMIZATION_EMIT_STATS(id, name) emitStat(VDfgPeepholePattern::id); + FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_EMIT_STATS) +#undef OPTIMIZATION_EMIT_STATS +} + +class V3DfgPeephole final : public DfgVisitor { + + // STATE + DfgGraph& m_dfg; // The DfgGraph being visited + V3DfgPeepholeContext& m_ctx; // The config structure + bool m_changed = false; // Changed a vertex + AstNodeDType* const m_bitDType = DfgVertex::dtypeForWidth(1); // Common, so grab it up front + +#define APPLYING(id) if (checkApplying(VDfgPeepholePattern::id)) + + // METHODS + bool checkApplying(VDfgPeepholePattern id) { + if (!m_ctx.m_enabled[id]) return false; + UINFO(9, "Applying DFG patten " << id.ascii() << endl); + ++m_ctx.m_count[id]; + m_changed = true; + return true; + } + + // Shorthand + static AstNodeDType* dtypeForWidth(uint32_t width) { return DfgVertex::dtypeForWidth(width); } + + // Create a new DfgConst vertex with the given width and value + DfgConst* makeConst(FileLine* flp, uint32_t width, uint32_t value) { + const int widthInt = static_cast(width); + return new DfgConst{m_dfg, new AstConst{flp, AstConst::WidthedValue{}, widthInt, value}}; + } + + // Create a new 32-bit DfgConst vertex + DfgConst* makeI32(FileLine* flp, uint32_t value) { return makeConst(flp, 32, value); } + + // Create a new DfgConst vertex with the given width and value zero + DfgConst* makeZero(FileLine* flp, uint32_t width) { return makeConst(flp, width, 0); } + + // Transformations that apply to all commutative binary vertices + void commutativeBinary(DfgVertexWithArity<2>* vtxp) { + DfgVertex* const lhsp = vtxp->source<0>(); + DfgVertex* const rhsp = vtxp->source<1>(); + // Ensure Const is on left-hand side to simplify other patterns + if (lhsp->is()) return; + if (rhsp->is()) { + APPLYING(SWAP_CONST_IN_COMMUTATIVE_BINARY) { + vtxp->lhsp(rhsp); + vtxp->rhsp(lhsp); + return; + } + } + // Ensure Not is on the left-hand side to simplify other patterns + if (lhsp->is()) return; + if (rhsp->is()) { + APPLYING(SWAP_NOT_IN_COMMUTATIVE_BINARY) { + vtxp->lhsp(rhsp); + vtxp->rhsp(lhsp); + return; + } + } + // 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(); + if (lVarp->name() > rVarp->name()) { + APPLYING(SWAP_VAR_IN_COMMUTATIVE_BINARY) { + vtxp->lhsp(rhsp); + vtxp->rhsp(lhsp); + return; + } + } + } + } + + // Bitwise operation with one side Const, and the other side a Concat + template + bool tryPushBitwiseOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { + UASSERT_OBJ(constp->width() == concatp->width(), vtxp, "Mismatched widths"); + + FileLine* const flp = vtxp->fileline(); + + // If at least one of the sides of the Concat constant, or width 1 (i.e.: can be + // further simplified), then push the Vertex past the Concat + if (concatp->lhsp()->is() || concatp->rhsp()->is() // + || concatp->lhsp()->width() == 1 || concatp->rhsp()->width() == 1) { + APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) { + const uint32_t width = concatp->width(); + AstNodeDType* const lDtypep = concatp->lhsp()->dtypep(); + AstNodeDType* const rDtypep = concatp->rhsp()->dtypep(); + const uint32_t lWidth = lDtypep->width(); + const uint32_t rWidth = rDtypep->width(); + + // The new Lhs vertex + Vertex* const newLhsp = new Vertex{m_dfg, flp, lDtypep}; + DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth); + newLhsConstp->num().opSel(constp->num(), width - 1, rWidth); + newLhsp->lhsp(newLhsConstp); + newLhsp->rhsp(concatp->lhsp()); + + // The new Rhs vertex + Vertex* const newRhsp = new Vertex{m_dfg, flp, rDtypep}; + DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth); + newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0); + newRhsp->lhsp(newRhsConstp); + newRhsp->rhsp(concatp->rhsp()); + + // The replacement Concat vertex + DfgConcat* const newConcat + = new DfgConcat{m_dfg, concatp->fileline(), concatp->dtypep()}; + newConcat->lhsp(newLhsp); + newConcat->rhsp(newRhsp); + + // Replace this vertex + vtxp->replaceWith(newConcat); + return true; + } + } + return false; + } + + template + bool tryPushCompareOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { + UASSERT_OBJ(constp->width() == concatp->width(), vtxp, "Mismatched widths"); + + FileLine* const flp = vtxp->fileline(); + + // If at least one of the sides of the Concat is constant, then push the Vertex past the + // Concat + if (concatp->lhsp()->is() || concatp->rhsp()->is()) { + APPLYING(PUSH_COMPARE_OP_THROUGH_CONCAT) { + const uint32_t width = concatp->width(); + const uint32_t lWidth = concatp->lhsp()->width(); + const uint32_t rWidth = concatp->rhsp()->width(); + + // The new Lhs vertex + Vertex* const newLhsp = new Vertex{m_dfg, flp, m_bitDType}; + DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth); + newLhsConstp->num().opSel(constp->num(), width - 1, rWidth); + newLhsp->lhsp(newLhsConstp); + newLhsp->rhsp(concatp->lhsp()); + + // The new Rhs vertex + Vertex* const newRhsp = new Vertex{m_dfg, flp, m_bitDType}; + DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth); + newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0); + newRhsp->lhsp(newRhsConstp); + newRhsp->rhsp(concatp->rhsp()); + + // The replacement Vertex + DfgVertexWithArity<2>* const replacementp + = std::is_same::value + ? new DfgAnd{m_dfg, concatp->fileline(), m_bitDType} + : nullptr; + UASSERT_OBJ(replacementp, vtxp, + "Unhandled vertex type in 'tryPushCompareOpThroughConcat': " + << vtxp->typeName()); + replacementp->relinkSource<0>(newLhsp); + replacementp->relinkSource<1>(newRhsp); + + // Replace this vertex + vtxp->replaceWith(replacementp); + return true; + } + } + return false; + } + + template + void optimizeReduction(Vertex* vtxp) { + static_assert(std::is_same::value + || std::is_same::value + || std::is_same::value, + "Invalid 'Vertex' type for this method"); + + DfgVertex* const srcp = vtxp->srcp(); + + // Reduction of 1-bit value -- Currently unreachable as V3Const can remove these, but + // in the future they will be created during this optimization. + if (srcp->width() == 1) { // LCOV_EXCL_START + APPLYING(REMOVE_WIDTH_ONE_REDUCTION) { + vtxp->replaceWith(srcp); + return; + } + } // LCOV_EXCL_STOP + + if (DfgCond* const condp = srcp->cast()) { + if (condp->thenp()->is() || condp->elsep()->is()) { + APPLYING(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) { + // The new 'then' vertex + Vertex* const newThenp = new Vertex{m_dfg, vtxp->fileline(), m_bitDType}; + newThenp->srcp(condp->thenp()); + + // The new 'else' vertex + Vertex* const newElsep = new Vertex{m_dfg, vtxp->fileline(), m_bitDType}; + newElsep->srcp(condp->elsep()); + + // The replacement Cond vertex + DfgCond* const newCondp = new DfgCond{m_dfg, condp->fileline(), m_bitDType}; + newCondp->condp(condp->condp()); + newCondp->thenp(newThenp); + newCondp->elsep(newElsep); + + // Replace this vertex + vtxp->replaceWith(newCondp); + return; + } + } + } + + if (DfgConst* const constp = srcp->cast()) { + APPLYING(REPLACE_REDUCTION_OF_CONST) { + DfgConst* const replacementp = makeZero(vtxp->fileline(), 1); + if (std::is_same::value) { + replacementp->num().opRedAnd(constp->num()); + } else if (std::is_same::value) { + replacementp->num().opRedOr(constp->num()); + } else { + replacementp->num().opRedXor(constp->num()); + } + vtxp->replaceWith(replacementp); + return; + } + } + } + + void optimizeShiftRHS(DfgVertexWithArity<2>* vtxp) { + if (const DfgConcat* const concatp = vtxp->rhsp()->cast()) { + if (concatp->lhsp()->isZero()) { // Drop redundant zero extension + APPLYING(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) { // + vtxp->rhsp(concatp->rhsp()); + } + } + } + } + + // VISIT methods + + void visit(DfgVertex*) override {} + + void visit(DfgExtend* vtxp) override { + const uint32_t extension = vtxp->width() - vtxp->srcp()->width(); + UASSERT_OBJ(extension > 0, vtxp, "Useless Extend"); + + FileLine* const flp = vtxp->fileline(); + + // Convert Extend into Concat with zeros. This simplifies other patterns as they only need + // to handle Concat, which is more generic, and don't need special cases for Extend. + APPLYING(REPLACE_EXTEND) { + DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(makeZero(flp, extension)); + replacementp->rhsp(vtxp->srcp()); + vtxp->replaceWith(replacementp); + } + } + + void visit(DfgNot* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->srcp()->width(), vtxp, + "Mismatched width: " << vtxp->width() << " != " << vtxp->srcp()->width()); + + // Not of Cond + if (DfgCond* const condp = vtxp->srcp()->cast()) { + // If at least one of the branches are a constant, push the Not past the Cond + if (condp->thenp()->is() || condp->elsep()->is()) { + APPLYING(PUSH_NOT_THROUGH_COND) { + // The new 'then' vertex + DfgNot* const newThenp = new DfgNot{m_dfg, vtxp->fileline(), vtxp->dtypep()}; + newThenp->srcp(condp->thenp()); + + // The new 'else' vertex + DfgNot* const newElsep = new DfgNot{m_dfg, vtxp->fileline(), vtxp->dtypep()}; + newElsep->srcp(condp->elsep()); + + // The replacement Cond vertex + DfgCond* const newCondp + = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; + newCondp->condp(condp->condp()); + newCondp->thenp(newThenp); + newCondp->elsep(newElsep); + + // Replace this vertex + vtxp->replaceWith(newCondp); + return; + } + } + } + + // Not of Not + if (DfgNot* const notp = vtxp->srcp()->cast()) { + UASSERT_OBJ(vtxp->width() == notp->srcp()->width(), vtxp, "Width mismatch"); + APPLYING(REMOVE_NOT_NOT) { + vtxp->replaceWith(notp->srcp()); + return; + } + } + + // Not of Neq + if (DfgNeq* const neqp = vtxp->srcp()->cast()) { + APPLYING(REPLACE_NOT_NEQ) { + DfgEq* const replacementp = new DfgEq{m_dfg, neqp->fileline(), vtxp->dtypep()}; + replacementp->lhsp(neqp->lhsp()); + replacementp->rhsp(neqp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + + // Not of Const + if (DfgConst* const constp = vtxp->srcp()->cast()) { + APPLYING(REPLACE_NOT_OF_CONST) { + DfgConst* const replacementp = makeZero(vtxp->fileline(), vtxp->width()); + replacementp->num().opNot(constp->num()); + vtxp->replaceWith(replacementp); + return; + } + } + } + + void visit(DfgAnd* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + commutativeBinary(vtxp); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + FileLine* const flp = vtxp->fileline(); + + // Bubble pushing + if (lhsp->is() && rhsp->is()) { + APPLYING(REPLACE_AND_OF_NOT_AND_NOT) { + DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; + orp->lhsp(lhsp->as()->srcp()); + orp->rhsp(rhsp->as()->srcp()); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(orp); + vtxp->replaceWith(notp); + return; + } + } + + if (DfgConst* const lhsConstp = lhsp->cast()) { + if (DfgConst* const rhsConstp = rhsp->cast()) { + APPLYING(REPLACE_AND_OF_CONST_AND_CONST) { + DfgConst* const replacementp = makeZero(flp, vtxp->width()); + replacementp->num().opAnd(lhsConstp->num(), rhsConstp->num()); + vtxp->replaceWith(replacementp); + return; + } + } + + if (lhsConstp->isZero()) { + APPLYING(REPLACE_AND_WITH_ZERO) { + vtxp->replaceWith(lhsConstp); + return; + } + } + + if (lhsConstp->isOnes()) { + APPLYING(REMOVE_AND_WITH_ONES) { + vtxp->replaceWith(rhsp); + return; + } + } + + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + } + } + + if (DfgNot* const lhsNotp = lhsp->cast()) { + // ~A & A is all zeroes + if (lhsNotp->srcp() == rhsp) { + APPLYING(REPLACE_CONTRADICTORY_AND) { + DfgConst* const replacementp = makeZero(flp, vtxp->width()); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + + void visit(DfgOr* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + commutativeBinary(vtxp); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + FileLine* const flp = vtxp->fileline(); + + // Bubble pushing + if (DfgNot* const lhsNotp = lhsp->cast()) { + if (DfgNot* const rhsNotp = rhsp->cast()) { + APPLYING(REPLACE_OR_OF_NOT_AND_NOT) { + DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; + andp->lhsp(lhsNotp->srcp()); + andp->rhsp(rhsNotp->srcp()); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(andp); + vtxp->replaceWith(notp); + return; + } + } + if (DfgNeq* const rhsNeqp = rhsp->cast()) { + APPLYING(REPLACE_OR_OF_NOT_AND_NEQ) { + DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; + andp->lhsp(lhsNotp->srcp()); + DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()}; + newRhsp->lhsp(rhsNeqp->lhsp()); + newRhsp->rhsp(rhsNeqp->rhsp()); + andp->rhsp(newRhsp); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(andp); + vtxp->replaceWith(notp); + return; + } + } + } + + if (DfgConcat* const lhsConcatp = lhsp->cast()) { + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (lhsConcatp->lhsp()->width() == rhsConcatp->lhsp()->width()) { + if (lhsConcatp->lhsp()->isZero() && rhsConcatp->rhsp()->isZero()) { + APPLYING(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) { + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(rhsConcatp->lhsp()); + replacementp->rhsp(lhsConcatp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + if (lhsConcatp->rhsp()->isZero() && rhsConcatp->lhsp()->isZero()) { + APPLYING(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) { + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lhsConcatp->lhsp()); + replacementp->rhsp(rhsConcatp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + + if (DfgConst* const lhsConstp = lhsp->cast()) { + if (DfgConst* const rhsConstp = rhsp->cast()) { + APPLYING(REPLACE_OR_OF_CONST_AND_CONST) { + DfgConst* const replacementp = makeZero(flp, vtxp->width()); + replacementp->num().opOr(lhsConstp->num(), rhsConstp->num()); + vtxp->replaceWith(replacementp); + return; + } + } + + if (lhsConstp->isZero()) { + APPLYING(REMOVE_OR_WITH_ZERO) { + vtxp->replaceWith(rhsp); + return; + } + } + + if (lhsConstp->isOnes()) { + APPLYING(REPLACE_OR_WITH_ONES) { + vtxp->replaceWith(lhsp); + return; + } + } + + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + } + } + + if (DfgNot* const lhsNotp = lhsp->cast()) { + // ~A | A is all ones + if (lhsNotp->srcp() == rhsp) { + APPLYING(REPLACE_TAUTOLOGICAL_OR) { + DfgConst* const replacementp = makeZero(flp, vtxp->width()); + replacementp->num().setAllBits1(); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + + void visit(DfgXor* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + commutativeBinary(vtxp); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + if (DfgConst* const lhsConstp = lhsp->cast()) { + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + } + } + } + + void visit(DfgAdd* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + commutativeBinary(vtxp); + } + + void visit(DfgSub* vtxp) override { + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + UASSERT_OBJ(lhsp->width() == rhsp->width(), vtxp, "Width mismatch"); + UASSERT_OBJ(lhsp->width() == vtxp->width(), vtxp, "Width mismatch"); + if (DfgConst* const rConstp = rhsp->cast()) { + if (rConstp->isZero()) { + APPLYING(REMOVE_SUB_ZERO) { + vtxp->replaceWith(lhsp); + return; + } + } + if (vtxp->width() == 1 && rConstp->toU32() == 1) { + APPLYING(REPLACE_SUB_WITH_NOT) { + DfgNot* const replacementp = new DfgNot{m_dfg, vtxp->fileline(), m_bitDType}; + replacementp->srcp(lhsp); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + + void visit(DfgShiftL* vtxp) override { optimizeShiftRHS(vtxp); } + void visit(DfgShiftR* vtxp) override { optimizeShiftRHS(vtxp); } + void visit(DfgShiftRS* vtxp) override { optimizeShiftRHS(vtxp); } + + void visit(DfgEq* vtxp) override { + commutativeBinary(vtxp); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + if (DfgConst* const lhsConstp = lhsp->cast()) { + if (DfgConst* const rhsConstp = rhsp->cast()) { + APPLYING(REPLACE_EQ_OF_CONST_AND_CONST) { + DfgConst* const replacementp = makeZero(vtxp->fileline(), 1); + if (lhsConstp->constp()->sameTree(rhsConstp->constp())) { + replacementp->num().setLong(1); + } + vtxp->replaceWith(replacementp); + return; + } + } + + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (tryPushCompareOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + } + } + } + + void visit(DfgSel* vtxp) override { + DfgVertex* const fromp = vtxp->fromp(); + DfgConst* const lsbp = vtxp->lsbp()->cast(); + DfgConst* const widthp = vtxp->widthp()->cast(); + if (!lsbp || !widthp) return; + + FileLine* const flp = vtxp->fileline(); + + UASSERT_OBJ(lsbp->toI32() >= 0, vtxp, "Negative LSB in Sel"); + + const uint32_t lsb = lsbp->toU32(); + const uint32_t width = widthp->toU32(); + const uint32_t msb = lsb + width - 1; + + UASSERT_OBJ(width == vtxp->width(), vtxp, "Incorrect Sel width"); + + // Full width select, replace with the source. + if (fromp->width() == width) { + UASSERT_OBJ(lsb == 0, fromp, "OOPS"); + APPLYING(REMOVE_FULL_WIDTH_SEL) { + vtxp->replaceWith(fromp); + return; + } + } + + // Sel from Concat + if (DfgConcat* const concatp = fromp->cast()) { + DfgVertex* const lhsp = concatp->lhsp(); + DfgVertex* const rhsp = concatp->rhsp(); + + if (msb < rhsp->width()) { + // If the select is entirely from rhs, then replace with sel from rhs + APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { // + vtxp->fromp(rhsp); + } + } else if (lsb >= rhsp->width()) { + // If the select is entirely from the lhs, then replace with sel from lhs + APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) { + vtxp->fromp(lhsp); + vtxp->lsbp(makeI32(flp, lsb - rhsp->width())); + } + } else if (lsb == 0 || msb == concatp->width() - 1 // + || lhsp->is() || rhsp->is()) { + // If the select straddles both sides, but at least one of the sides is wholly + // selected, or at least one of the sides is a Const, then push the Sel past + // the Concat + APPLYING(PUSH_SEL_THROUGH_CONCAT) { + const uint32_t rSelWidth = rhsp->width() - lsb; + const uint32_t lSelWidth = width - rSelWidth; + + // The new Lhs vertex + DfgSel* const newLhsp = new DfgSel{m_dfg, flp, dtypeForWidth(lSelWidth)}; + newLhsp->fromp(lhsp); + newLhsp->lsbp(makeI32(lsbp->fileline(), 0)); + newLhsp->widthp(makeI32(widthp->fileline(), lSelWidth)); + + // The new Rhs vertex + DfgSel* const newRhsp = new DfgSel{m_dfg, flp, dtypeForWidth(rSelWidth)}; + newRhsp->fromp(rhsp); + newRhsp->lsbp(makeI32(lsbp->fileline(), lsb)); + newRhsp->widthp(makeI32(widthp->fileline(), rSelWidth)); + + // The replacement Concat vertex + DfgConcat* const newConcat + = new DfgConcat{m_dfg, concatp->fileline(), vtxp->dtypep()}; + newConcat->lhsp(newLhsp); + newConcat->rhsp(newRhsp); + + // Replace this vertex + vtxp->replaceWith(newConcat); + return; + } + } + } + + if (DfgReplicate* const repp = fromp->cast()) { + // If the Sel is wholly into the source of the Replicate, push the Sel through the + // Replicate and apply it directly to the source of the Replicate. + const uint32_t srcWidth = repp->srcp()->width(); + if (width <= srcWidth) { + const uint32_t newLsb = lsb % srcWidth; + if (newLsb + width <= srcWidth) { + APPLYING(PUSH_SEL_THROUGH_REPLICATE) { + vtxp->fromp(repp->srcp()); + vtxp->lsbp(makeI32(flp, newLsb)); + } + } + } + } + + // Sel from Not + if (DfgNot* const notp = fromp->cast()) { + // Replace "Sel from Not" with "Not of Sel" + if (!notp->hasMultipleSinks()) { + UASSERT_OBJ(notp->srcp()->width() == notp->width(), notp, "Mismatched widths"); + APPLYING(PUSH_SEL_THROUGH_NOT) { + // Make Sel select from source of Not + vtxp->fromp(notp->srcp()); + // Add Not after Sel + DfgNot* const replacementp + = new DfgNot{m_dfg, notp->fileline(), vtxp->dtypep()}; + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + } + } + } + + // Sel from Sel + if (DfgSel* const selp = fromp->cast()) { + UASSERT_OBJ(widthp->toU32() <= selp->width(), vtxp, "Out of bound Sel"); + if (DfgConst* const sourceLsbp = selp->lsbp()->cast()) { + UASSERT_OBJ(sourceLsbp->toI32() >= 0, selp, "negative"); + UASSERT_OBJ(selp->widthp()->as()->toU32() >= widthp->toU32(), selp, + "negative"); + APPLYING(REPLACE_SEL_FROM_SEL) { + // Make this Sel select from the source of the source Sel + vtxp->fromp(selp->fromp()); + // Adjust LSB + vtxp->lsbp(makeI32(flp, lsb + sourceLsbp->toU32())); + } + } + } + + // Sel from Cond + if (DfgCond* const condp = fromp->cast()) { + // If at least one of the branches are a constant, push the select past the cond + if (condp->thenp()->is() || condp->elsep()->is()) { + APPLYING(PUSH_SEL_THROUGH_COND) { + // The new 'then' vertex + DfgSel* const newThenp = new DfgSel{m_dfg, flp, vtxp->dtypep()}; + newThenp->fromp(condp->thenp()); + newThenp->lsbp(makeI32(lsbp->fileline(), lsb)); + newThenp->widthp(makeI32(widthp->fileline(), width)); + + // The new 'else' vertex + DfgSel* const newElsep = new DfgSel{m_dfg, flp, vtxp->dtypep()}; + newElsep->fromp(condp->elsep()); + newElsep->lsbp(makeI32(lsbp->fileline(), lsb)); + newElsep->widthp(makeI32(widthp->fileline(), width)); + + // The replacement Cond vertex + DfgCond* const newCondp + = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; + newCondp->condp(condp->condp()); + newCondp->thenp(newThenp); + newCondp->elsep(newElsep); + + // Replace this vertex + vtxp->replaceWith(newCondp); + return; + } + } + } + + // Sel from ShiftL + if (DfgShiftL* const shiftLp = fromp->cast()) { + // If selecting bottom bits of left shift, push the Sel before the shift + if (lsb == 0) { + UASSERT_OBJ(shiftLp->lhsp()->width() >= width, vtxp, "input of shift narrow"); + APPLYING(PUSH_SEL_THROUGH_SHIFTL) { + vtxp->fromp(shiftLp->lhsp()); + DfgShiftL* const newShiftLp + = new DfgShiftL{m_dfg, shiftLp->fileline(), vtxp->dtypep()}; + vtxp->replaceWith(newShiftLp); + newShiftLp->lhsp(vtxp); + newShiftLp->rhsp(shiftLp->rhsp()); + } + } + } + + // Sel from Const + if (DfgConst* const constp = fromp->cast()) { + APPLYING(REPLACE_SEL_FROM_CONST) { + DfgConst* const replacementp = makeZero(flp, width); + replacementp->num().opSel(constp->num(), msb, lsb); + vtxp->replaceWith(replacementp); + return; + } + } + } + + void visit(DfgRedOr* vtxp) override { optimizeReduction(vtxp); } + void visit(DfgRedAnd* vtxp) override { optimizeReduction(vtxp); } + void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); } + + void visit(DfgConcat* vtxp) override { + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + UASSERT_OBJ(vtxp->width() == lhsp->width() + rhsp->width(), vtxp, + "Incorrect Concat width: " << vtxp->width() << " != " << lhsp->width() << " + " + << rhsp->width()); + + FileLine* const flp = vtxp->fileline(); + + { + const auto joinConsts + = [this](DfgConst* lConstp, DfgConst* rConstp, FileLine* flp) -> DfgConst* { + DfgConst* const newConstp = makeZero(flp, lConstp->width() + rConstp->width()); + newConstp->num().opSelInto(rConstp->num(), 0, rConstp->width()); + newConstp->num().opSelInto(lConstp->num(), rConstp->width(), lConstp->width()); + return newConstp; + }; + + DfgConst* const lConstp = lhsp->cast(); + DfgConst* const rConstp = rhsp->cast(); + + if (lConstp && rConstp) { + APPLYING(REPLACE_CONCAT_OF_CONSTS) { + vtxp->replaceWith(joinConsts(lConstp, rConstp, flp)); + return; + } + } + + if (lConstp) { + if (DfgConcat* const rConcatp = rhsp->cast()) { + if (DfgConst* const rlConstp = rConcatp->lhsp()->cast()) { + APPLYING(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS) { + DfgConst* const joinedConstp = joinConsts(lConstp, rlConstp, flp); + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(joinedConstp); + replacementp->rhsp(rConcatp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + } + + if (lConstp->isZero()) { + if (DfgSel* const rSelp = rhsp->cast()) { + if (DfgConst* const rSelLsbConstp = rSelp->lsbp()->cast()) { + if (vtxp->width() == rSelp->fromp()->width() + && rSelLsbConstp->toU32() == lConstp->width()) { + const uint32_t rSelWidth + = rSelp->widthp()->as()->toU32(); + UASSERT_OBJ(lConstp->width() + rSelWidth == vtxp->width(), vtxp, + "Inconsistent"); + APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) { + DfgShiftR* const replacementp + = new DfgShiftR{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(rSelp->fromp()); + replacementp->rhsp(makeI32(flp, lConstp->width())); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + } + + if (rConstp) { + if (DfgConcat* const lConcatp = lhsp->cast()) { + if (DfgConst* const lrConstp = lConcatp->rhsp()->cast()) { + APPLYING(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) { + DfgConst* const joinedConstp = joinConsts(lrConstp, rConstp, flp); + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lConcatp->lhsp()); + replacementp->rhsp(joinedConstp); + vtxp->replaceWith(replacementp); + return; + } + } + } + + if (rConstp->isZero()) { + if (DfgSel* const lSelp = lhsp->cast()) { + if (DfgConst* const lSelLsbConstp = lSelp->lsbp()->cast()) { + if (vtxp->width() == lSelp->fromp()->width() + && lSelLsbConstp->toU32() == 0) { + const uint32_t lSelWidth + = lSelp->widthp()->as()->toU32(); + UASSERT_OBJ(lSelWidth + rConstp->width() == vtxp->width(), vtxp, + "Inconsistent"); + APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) { + DfgShiftL* const replacementp + = new DfgShiftL{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lSelp->fromp()); + replacementp->rhsp(makeI32(flp, rConstp->width())); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + } + } + + { + DfgNot* const lNot = lhsp->cast(); + DfgNot* const rNot = rhsp->cast(); + if (lNot && rNot) { + APPLYING(PUSH_CONCAT_THROUGH_NOTS) { + vtxp->lhsp(lNot->srcp()); + vtxp->rhsp(rNot->srcp()); + DfgNot* const replacementp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + return; + } + } + } + + { + const auto joinSels = [this](DfgSel* lSelp, DfgSel* rSelp, FileLine* flp) -> DfgSel* { + DfgConst* const lLsbp = lSelp->lsbp()->cast(); + DfgConst* const lWidthp = lSelp->widthp()->cast(); + DfgConst* const rLsbp = rSelp->lsbp()->cast(); + DfgConst* const rWidthp = rSelp->widthp()->cast(); + if (lLsbp && lWidthp && rLsbp && rWidthp) { + if (lSelp->fromp()->equals(*rSelp->fromp())) { + if (lLsbp->toU32() == rLsbp->toU32() + rWidthp->toU32()) { + // Two consecutive Sels, make a single Sel. + const uint32_t width = lWidthp->toU32() + rWidthp->toU32(); + AstNodeDType* const dtypep = dtypeForWidth(width); + DfgSel* const joinedSelp = new DfgSel{m_dfg, flp, dtypep}; + joinedSelp->fromp(rSelp->fromp()); + joinedSelp->lsbp(rSelp->lsbp()); + joinedSelp->widthp(makeI32(flp, width)); + return joinedSelp; + } + } + } + return nullptr; + }; + + DfgSel* const lSelp = lhsp->cast(); + DfgSel* const rSelp = rhsp->cast(); + if (lSelp && rSelp) { + if (DfgSel* const jointSelp = joinSels(lSelp, rSelp, flp)) { + APPLYING(REMOVE_CONCAT_OF_ADJOINING_SELS) { + vtxp->replaceWith(jointSelp); + return; + } + } + } + if (lSelp) { + if (DfgConcat* const rConcatp = rhsp->cast()) { + if (DfgSel* const rlSelp = rConcatp->lhsp()->cast()) { + if (DfgSel* const jointSelp = joinSels(lSelp, rlSelp, flp)) { + APPLYING(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) { + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(jointSelp); + replacementp->rhsp(rConcatp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + if (rSelp) { + if (DfgConcat* const lConcatp = lhsp->cast()) { + if (DfgSel* const lrlSelp = lConcatp->rhsp()->cast()) { + if (DfgSel* const jointSelp = joinSels(lrlSelp, rSelp, flp)) { + APPLYING(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) { + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lConcatp->lhsp()); + replacementp->rhsp(jointSelp); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + } + } + + void visit(DfgCond* vtxp) override { + DfgVertex* const condp = vtxp->condp(); + DfgVertex* const thenp = vtxp->thenp(); + DfgVertex* const elsep = vtxp->elsep(); + UASSERT_OBJ(vtxp->width() == thenp->width(), vtxp, "Width mismatch"); + UASSERT_OBJ(vtxp->width() == elsep->width(), vtxp, "Width mismatch"); + + if (condp->width() != 1) return; + + FileLine* const flp = vtxp->fileline(); + + if (condp->isOnes()) { + APPLYING(REMOVE_COND_WITH_TRUE_CONDITION) { + vtxp->replaceWith(thenp); + return; + } + } + + if (condp->isZero()) { + APPLYING(REMOVE_COND_WITH_FALSE_CONDITION) { + vtxp->replaceWith(elsep); + return; + } + } + + if (DfgNot* const condNotp = condp->cast()) { + APPLYING(SWAP_COND_WITH_NOT_CONDITION) { + vtxp->condp(condNotp->srcp()); + vtxp->thenp(elsep); + vtxp->elsep(thenp); + visit(vtxp); + return; + } + } + + if (DfgNeq* const condNeqp = condp->cast()) { + APPLYING(SWAP_COND_WITH_NEQ_CONDITION) { + DfgEq* const newCondp = new DfgEq{m_dfg, condp->fileline(), condp->dtypep()}; + newCondp->lhsp(condNeqp->lhsp()); + newCondp->rhsp(condNeqp->rhsp()); + vtxp->condp(newCondp); + vtxp->thenp(elsep); + vtxp->elsep(thenp); + visit(vtxp); + return; + } + } + + if (DfgNot* const thenNotp = thenp->cast()) { + if (DfgNot* const elseNotp = elsep->cast()) { + APPLYING(PULL_NOTS_THROUGH_COND) { + DfgNot* const replacementp + = new DfgNot{m_dfg, thenp->fileline(), vtxp->dtypep()}; + vtxp->thenp(thenNotp->srcp()); + vtxp->elsep(elseNotp->srcp()); + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + return; + } + } + } + + if (vtxp->width() == 1) { + AstNodeDType* const dtypep = vtxp->dtypep(); + if (thenp->isZero()) { // a ? 0 : b becomes ~a & b + APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) { + DfgAnd* const repalcementp = new DfgAnd{m_dfg, flp, dtypep}; + DfgNot* const notp = new DfgNot{m_dfg, flp, dtypep}; + notp->srcp(condp); + repalcementp->lhsp(notp); + repalcementp->rhsp(elsep); + vtxp->replaceWith(repalcementp); + return; + } + } + if (thenp->isOnes()) { // a ? 1 : b becomes a | b + APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) { + DfgOr* const repalcementp = new DfgOr{m_dfg, flp, dtypep}; + repalcementp->lhsp(condp); + repalcementp->rhsp(elsep); + vtxp->replaceWith(repalcementp); + return; + } + } + if (elsep->isZero()) { // a ? b : 0 becomes a & b + APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) { + DfgAnd* const repalcementp = new DfgAnd{m_dfg, flp, dtypep}; + repalcementp->lhsp(condp); + repalcementp->rhsp(thenp); + vtxp->replaceWith(repalcementp); + return; + } + } + if (elsep->isOnes()) { // a ? b : 1 becomes ~a | b + APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) { + DfgOr* const repalcementp = new DfgOr{m_dfg, flp, dtypep}; + DfgNot* const notp = new DfgNot{m_dfg, flp, dtypep}; + notp->srcp(condp); + repalcementp->lhsp(notp); + repalcementp->rhsp(thenp); + vtxp->replaceWith(repalcementp); + return; + } + } + } + } + +#undef APPLYING + + // Process one vertex. Return true if graph changed + bool processVertex(DfgVertex& vtx) { + // Keep DfgVars in this pass, we will remove them later if they become redundant + // Note: We want to keep the original variables for non-var vertices that drive multiple + // sinks (otherwise we would need to introduce a temporary, it is better for debugging to + // keep the original variable name, if one is available), so we can't remove redundant + // variables here. + if (vtx.is()) return false; + + // If it has no sinks (unused), we can remove it + if (!vtx.hasSinks()) { + vtx.unlinkDelete(m_dfg); + return true; + } + + // Transform node + m_changed = false; + iterate(&vtx); + if (!vtx.hasSinks()) vtx.unlinkDelete(m_dfg); // If it became unused, we can remove it + return m_changed; + } + + V3DfgPeephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx) + : m_dfg{dfg} + , m_ctx{ctx} {} + +public: + static void apply(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { + V3DfgPeephole visitor{dfg, ctx}; + dfg.runToFixedPoint([&](DfgVertex& vtx) { return visitor.processVertex(vtx); }); + } +}; + +void V3DfgPasses::peephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { + V3DfgPeephole::apply(dfg, ctx); +} diff --git a/src/V3DfgPeephole.h b/src/V3DfgPeephole.h new file mode 100644 index 000000000..1836341db --- /dev/null +++ b/src/V3DfgPeephole.h @@ -0,0 +1,126 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Peephole optimizations over DfgGraph +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3DFGPEEPHOLE_H_ +#define VERILATOR_V3DFGPEEPHOLE_H_ + +#include "config_build.h" + +#include + +#define _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, arg) macro(arg, #arg) + +// clang-format off +#define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_CONST_IN_COMMUTATIVE_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_NOT_IN_COMMUTATIVE_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_VAR_IN_COMMUTATIVE_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMPARE_OP_THROUGH_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_WIDTH_ONE_REDUCTION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_REDUCTION_OF_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EXTEND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_NOT_THROUGH_COND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_NOT_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_NEQ) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_OF_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_NOT_AND_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_WITH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_AND_WITH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONTRADICTORY_AND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NEQ) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_OR_WITH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_WITH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_TAUTOLOGICAL_OR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SUB_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SUB_WITH_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EQ_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_FULL_WIDTH_SEL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_RHS_OF_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_LHS_OF_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REPLICATE) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_SEL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_COND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SHIFTL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_OF_CONSTS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_CONCAT_THROUGH_NOTS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_CONCAT_OF_ADJOINING_SELS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_COND_WITH_FALSE_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_COND_WITH_TRUE_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NEQ_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ONES) + +// clang-format on + +class VDfgPeepholePattern final { +public: + enum en : unsigned { +#define OPTIMIZATION_ID(id, name) id, + FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_ID) +#undef OPTIMIZATION_ID + _ENUM_END + }; + enum en m_e; + const char* ascii() const { + static const char* const names[] = { +#define OPTIMIZATION_NAME(id, name) name, + FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_NAME) +#undef OPTIMIZATION_NAME + "_ENUM_END" // + }; + return names[m_e]; + } + + // cppcheck-suppress noExplicitConstructor + VDfgPeepholePattern(en _e) + : m_e{_e} {} + operator en() const { return m_e; } +}; + +struct V3DfgPeepholeContext final { + const std::string m_label; // Label to apply to stats + + // Enable flags for each optimization + bool m_enabled[VDfgPeepholePattern::_ENUM_END]; + // Count of applications for each optimization (for statistics) + VDouble0 m_count[VDfgPeepholePattern::_ENUM_END]; + + V3DfgPeepholeContext(const std::string& label); + ~V3DfgPeepholeContext(); +}; + +#endif diff --git a/src/V3Error.h b/src/V3Error.h index 209936898..d5be7e005 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -458,6 +458,7 @@ inline void v3errorEndFatal(std::ostringstream& sstr) { #define VL_DEFINE_DEBUG_FUNCTIONS \ VL_DEFINE_DEBUG(); /* Define 'int debug()' */ \ VL_DEFINE_DUMP(); /* Define 'int dump()' */ \ + VL_DEFINE_DUMP(Dfg); /* Define 'int dumpDfg()' */ \ VL_DEFINE_DUMP(Graph); /* Define 'int dumpGraph()' */ \ VL_DEFINE_DUMP(Tree); /* Define 'int dumpTree()' */ \ static_assert(true, "") diff --git a/src/V3Hash.h b/src/V3Hash.h index c5dd1b631..3df97c079 100644 --- a/src/V3Hash.h +++ b/src/V3Hash.h @@ -69,4 +69,9 @@ public: std::ostream& operator<<(std::ostream& os, const V3Hash& rhs); +template <> +struct std::hash { + std::size_t operator()(const V3Hash& h) const noexcept { return h.value(); } +}; + #endif // Guard diff --git a/src/V3List.h b/src/V3List.h index 883db89af..83ef4de17 100644 --- a/src/V3List.h +++ b/src/V3List.h @@ -44,6 +44,8 @@ public: // METHODS T begin() const { return m_headp; } T end() const { return nullptr; } + T rbegin() const { return m_tailp; } + T rend() const { return nullptr; } bool empty() const { return m_headp == nullptr; } void reset() { // clear() without walking the list m_headp = nullptr; @@ -78,6 +80,7 @@ public: #endif } T nextp() const { return m_nextp; } + T prevp() const { return m_prevp; } // METHODS void pushBack(V3List& listr, T newp) { // "this" must be a element inside of *newp diff --git a/src/V3OptionParser.cpp b/src/V3OptionParser.cpp index 1a1f7d84a..cc019b33d 100644 --- a/src/V3OptionParser.cpp +++ b/src/V3OptionParser.cpp @@ -55,6 +55,7 @@ struct V3OptionParser::Impl { template class ActionOnOff; // "-opt" and "-no-opt" for bool-ish class ActionCbCall; // Callback without argument for "-opt" + class ActionCbFOnOff; // Callback for "-fopt" and "-fno-opt" class ActionCbOnOff; // Callback for "-opt" and "-no-opt" template class ActionCbVal; // Callback for "-opt val" @@ -108,6 +109,8 @@ V3OPTION_PARSER_DEF_ACT_CLASS(ActionOnOff, VOptionBool, m_valp->setTrueOrFalse(! } V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbCall, void(void), m_cb(), en::NONE); +V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbFOnOff, void(bool), m_cb(!hasPrefixFNo(optp)), + en::FONOFF); V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbOnOff, void(bool), m_cb(!hasPrefixNo(optp)), en::ONOFF); template <> V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbVal, void(int), m_cb(std::atoi(argp)), en::VALUE); @@ -238,6 +241,7 @@ V3OPTION_PARSER_DEF_OP(OnOff, bool*, ActionOnOff) V3OPTION_PARSER_DEF_OP(OnOff, VOptionBool*, ActionOnOff) #endif V3OPTION_PARSER_DEF_OP(CbCall, Impl::ActionCbCall::CbType, ActionCbCall) +V3OPTION_PARSER_DEF_OP(CbFOnOff, Impl::ActionCbFOnOff::CbType, ActionCbFOnOff) V3OPTION_PARSER_DEF_OP(CbOnOff, Impl::ActionCbOnOff::CbType, ActionCbOnOff) V3OPTION_PARSER_DEF_OP(CbVal, Impl::ActionCbVal::CbType, ActionCbVal) V3OPTION_PARSER_DEF_OP(CbVal, Impl::ActionCbVal::CbType, ActionCbVal) diff --git a/src/V3OptionParser.h b/src/V3OptionParser.h index 2c5dbf6fb..23da89245 100644 --- a/src/V3OptionParser.h +++ b/src/V3OptionParser.h @@ -109,7 +109,8 @@ public: struct Set {}; // For ActionSet struct CbCall {}; // For ActionCbCall - struct CbOnOff {}; // For ActionOnOff of ActionFOnOff + struct CbFOnOff {}; // For ActionCbFOnOff + struct CbOnOff {}; // For ActionCbOnOff struct CbPartialMatch {}; // For ActionCbPartialMatch struct CbPartialMatchVal {}; // For ActionCbPartialMatchVal struct CbVal {}; // For ActionCbVal @@ -134,6 +135,7 @@ public: #endif ActionIfs& operator()(const char* optp, CbCall, std::function) const; + ActionIfs& operator()(const char* optp, CbFOnOff, std::function) const; ActionIfs& operator()(const char* optp, CbOnOff, std::function) const; ActionIfs& operator()(const char* optp, CbVal, std::function) const; ActionIfs& operator()(const char* optp, CbVal, std::function) const; @@ -153,6 +155,7 @@ public: const auto FOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::FOnOff{}; \ const auto OnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::OnOff{}; \ const auto CbCall VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbCall{}; \ + const auto CbFOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbFOnOff{}; \ const auto CbOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbOnOff{}; \ const auto CbPartialMatch VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbPartialMatch{}; \ const auto CbPartialMatchVal VL_ATTR_UNUSED \ diff --git a/src/V3Options.cpp b/src/V3Options.cpp index c662cc59a..3fd6efe76 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1123,6 +1123,19 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char DECL_OPTION("-fconst", FOnOff, &m_fConst); DECL_OPTION("-fconst-bit-op-tree", FOnOff, &m_fConstBitOpTree); DECL_OPTION("-fdedup", FOnOff, &m_fDedupe); + DECL_OPTION("-fdfg", CbFOnOff, [this](bool flag) { + m_fDfgPreInline = flag; + m_fDfgPostInline = flag; + }); + DECL_OPTION("-fdfg-peephole", FOnOff, &m_fDfgPeephole); + DECL_OPTION("-fdfg-peephole-", CbPartialMatch, [this](const char* optp) { // + m_fDfgPeepholeDisabled.erase(optp); + }); + DECL_OPTION("-fno-dfg-peephole-", CbPartialMatch, [this](const char* optp) { // + m_fDfgPeepholeDisabled.emplace(optp); + }); + DECL_OPTION("-fdfg-pre-inline", FOnOff, &m_fDfgPreInline); + DECL_OPTION("-fdfg-post-inline", FOnOff, &m_fDfgPostInline); DECL_OPTION("-fexpand", FOnOff, &m_fExpand); DECL_OPTION("-fgate", FOnOff, &m_fGate); DECL_OPTION("-finline", FOnOff, &m_fInline); @@ -1854,6 +1867,8 @@ void V3Options::optimize(int level) { m_fConst = flag; m_fConstBitOpTree = flag; m_fDedupe = flag; + m_fDfgPreInline = flag; + m_fDfgPostInline = flag; m_fExpand = flag; m_fGate = flag; m_fInline = flag; diff --git a/src/V3Options.h b/src/V3Options.h index bc27f0b2b..d4e80a38f 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -214,6 +214,7 @@ private: DebugLevelMap m_dumpLevel; // argument: --dumpi- std::map m_parameters; // Parameters std::map m_hierBlocks; // main switch: --hierarchical-block + V3StringSet m_fDfgPeepholeDisabled; // argument: -f[no-]dfg-peephole- bool m_preprocOnly = false; // main switch: -E bool m_makePhony = false; // main switch: -MP @@ -350,6 +351,9 @@ private: bool m_fConst; // main switch: -fno-const: constant folding bool m_fConstBitOpTree; // main switch: -fno-const-bit-op-tree constant bit op tree bool m_fDedupe; // main switch: -fno-dedupe: logic deduplication + 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_fExpand; // main switch: -fno-expand: expansion of C macros bool m_fGate; // main switch: -fno-gate: gate wire elimination bool m_fInline; // main switch: -fno-inline: module inlining @@ -592,6 +596,12 @@ public: bool fConst() const { return m_fConst; } bool fConstBitOpTree() const { return m_fConstBitOpTree; } bool fDedupe() const { return m_fDedupe; } + bool fDfgPeephole() const { return m_fDfgPeephole; } + bool fDfgPreInline() const { return m_fDfgPreInline; } + bool fDfgPostInline() const { return m_fDfgPostInline; } + bool fDfgPeepholeEnabled(const std::string& name) const { + return !m_fDfgPeepholeDisabled.count(name); + } bool fExpand() const { return m_fExpand; } bool fGate() const { return m_fGate; } bool fInline() const { return m_fInline; } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index eedc41193..2bd4798a7 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -40,6 +40,7 @@ #include "V3Depth.h" #include "V3DepthBlock.h" #include "V3Descope.h" +#include "V3DfgOptimizer.h" #include "V3EmitC.h" #include "V3EmitCMain.h" #include "V3EmitCMake.h" @@ -235,6 +236,16 @@ static void process() { v3Global.constRemoveXs(true); } + if (v3Global.opt.fDfgPreInline() || v3Global.opt.fDfgPostInline()) { + // If doing DFG optimization, extract some additional candidates + V3DfgOptimizer::extract(v3Global.rootp()); + } + + if (v3Global.opt.fDfgPreInline()) { + // Pre inline DFG optimization + V3DfgOptimizer::optimize(v3Global.rootp(), " pre inline"); + } + if (!(v3Global.opt.xmlOnly() && !v3Global.opt.flatten())) { // Module inlining // Cannot remove dead variables after this, as alias information for final @@ -245,6 +256,11 @@ static void process() { } } + if (v3Global.opt.fDfgPostInline()) { + // Post inline DFG optimization + V3DfgOptimizer::optimize(v3Global.rootp(), "post inline"); + } + // --PRE-FLAT OPTIMIZATIONS------------------ // Initial const/dead to reduce work for ordering code diff --git a/src/astgen b/src/astgen index c1d571ecd..183ff5837 100755 --- a/src/astgen +++ b/src/astgen @@ -174,6 +174,7 @@ class Node: Nodes = {} SortedNodes = None +DfgVertices = None ClassRefs = {} Stages = {} @@ -987,6 +988,142 @@ def write_op_checks(filename): ''') +def write_dfg_vertex_classes(filename): + with open_file(filename) as fh: + fh.write("\n") + for node in DfgVertices: + fh.write("class Dfg{} final : public DfgVertexWithArity<{}> {{\n". + format(node.name, node.arity)) + fh.write(" friend class DfgVertex;\n") + fh.write(" friend class DfgVisitor;\n") + fh.write(" void accept(DfgVisitor& visitor) override;\n") + fh.write( + " static constexpr DfgType dfgType() {{ return DfgType::at{t}; }};\n" + .format(t=node.name)) + fh.write("public:\n") + fh.write( + " Dfg{t}(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep) : DfgVertexWithArity<{a}>{{dfg, flp, dtypep, dfgType()}} {{}}\n" + .format(t=node.name, a=node.arity)) + # Accessors + operandNames = tuple( + node.getOp(n)[0] for n in range(1, node.arity + 1)) + assert not operandNames or len(operandNames) == node.arity + for i, n in enumerate(operandNames): + fh.write( + " DfgVertex* {n}() const {{ return source<{i}>(); }}\n". + format(n=n, i=i)) + for i, n in enumerate(operandNames): + fh.write( + " void {n}(DfgVertex* vtxp) {{ relinkSource<{i}>(vtxp); }}\n" + .format(n=n, i=i)) + if operandNames: + names = ", ".join(map(lambda _: '"' + _ + '"', operandNames)) + fh.write( + " const string srcName(size_t idx) const override {\n") + fh.write( + " static const char* names[{a}] = {{ {ns} }};\n". + format(a=node.arity, ns=names)) + fh.write(" return names[idx];\n") + fh.write(" }\n") + fh.write("};\n") + fh.write("\n") + fh.write("\n") + + fh.write("\n\ntemplate\n") + fh.write("struct DfgForAstImpl;\n\n") + for node in DfgVertices: + fh.write("template <>\n") + fh.write( + "struct DfgForAstImpl {{\n".format(name=node.name)) + fh.write(" using type = Dfg{name};\n".format(name=node.name)) + fh.write("};\n") + fh.write("\ntemplate\n") + fh.write("using DfgForAst = typename DfgForAstImpl::type;\n") + + fh.write("\n\ntemplate\n") + fh.write("struct AstForDfgImpl;\n\n") + for node in DfgVertices: + fh.write("template <>\n") + fh.write( + "struct AstForDfgImpl {{\n".format(name=node.name)) + fh.write(" using type = Ast{name};\n".format(name=node.name)) + fh.write("};\n") + fh.write("\ntemplate\n") + fh.write("using AstForDfg = typename AstForDfgImpl::type;\n") + + +def write_dfg_visitor_decls(filename): + with open_file(filename) as fh: + fh.write("\n") + fh.write("virtual void visit(DfgVertex*) = 0;\n") + for node in DfgVertices: + fh.write("virtual void visit(Dfg{}*);\n".format(node.name)) + + +def write_dfg_definitions(filename): + with open_file(filename) as fh: + fh.write("\n") + for node in DfgVertices: + fh.write( + "void Dfg{}::accept(DfgVisitor& visitor) {{ visitor.visit(this); }}\n" + .format(node.name)) + fh.write("\n") + for node in DfgVertices: + fh.write( + "void DfgVisitor::visit(Dfg{}* vtxp) {{ visit(static_cast(vtxp)); }}\n" + .format(node.name)) + + +def write_dfg_ast_to_dfg(filename): + with open_file(filename) as fh: + fh.write("\n") + for node in DfgVertices: + fh.write( + "void visit(Ast{t}* nodep) override {{\n".format(t=node.name)) + fh.write( + ' UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");\n' + ) + fh.write(" if (unhandled(nodep)) return;\n") + fh.write( + " Dfg{t}* const vtxp = makeVertex(nodep, *m_dfgp);\n" + .format(t=node.name)) + fh.write(" if (!vtxp) {\n") + fh.write(" m_foundUnhandled = true;\n") + fh.write(" ++m_ctx.m_nonRepNode;\n") + fh.write(" return;\n") + fh.write(" }\n\n") + fh.write(" m_uncommittedVertices.push_back(vtxp);\n") + for i in range(node.arity): + fh.write(" iterate(nodep->op{j}p());\n".format(j=i + 1)) + fh.write(" if (m_foundUnhandled) return;\n") + fh.write( + ' UASSERT_OBJ(nodep->op{j}p()->user1p(), nodep, "Child {j} missing Dfg vertex");\n' + .format(j=i + 1)) + fh.write( + " vtxp->relinkSource<{i}>(nodep->op{j}p()->user1u().to());\n\n" + .format(i=i, j=i + 1)) + fh.write(" nodep->user1p(vtxp);\n") + fh.write("}\n") + + +def write_dfg_dfg_to_ast(filename): + with open_file(filename) as fh: + fh.write("\n") + for node in DfgVertices: + fh.write( + "void visit(Dfg{t}* vtxp) override {{\n".format(t=node.name)) + for i in range(node.arity): + fh.write( + " AstNodeMath* const op{j}p = convertSource(vtxp->source<{i}>());\n" + .format(i=i, j=i + 1)) + fh.write( + " m_resultp = makeNode(vtxp".format(t=node.name)) + for i in range(node.arity): + fh.write(", op{j}p".format(j=i + 1)) + fh.write(");\n") + fh.write("}\n") + + ###################################################################### # main @@ -1041,6 +1178,11 @@ for node in SortedNodes: "%Error: Non-final AstNode subclasses must be named AstNode*: Ast" + node.name) +DfgBases = (Nodes["NodeUniop"], Nodes["NodeBiop"], Nodes["NodeTriop"]) +DfgVertices = tuple( + node for node in SortedNodes + if node.isLeaf and any(node.isSubClassOf(base) for base in DfgBases)) + # Check ordering of node definitions files = tuple(sorted(set(_.file for _ in SortedNodes))) @@ -1089,6 +1231,11 @@ if Args.classes: write_yystype("V3Ast__gen_yystype.h") write_macros("V3Ast__gen_macros.h") write_op_checks("V3Ast__gen_op_checks.h") + write_dfg_vertex_classes("V3Dfg__gen_vertex_classes.h") + write_dfg_visitor_decls("V3Dfg__gen_visitor_decls.h") + write_dfg_definitions("V3Dfg__gen_definitions.h") + write_dfg_ast_to_dfg("V3Dfg__gen_ast_to_dfg.h") + write_dfg_dfg_to_ast("V3Dfg__gen_dfg_to_ast.h") for cpt in Args.infiles: if not re.search(r'.cpp$', cpt): diff --git a/test_regress/t/t_altera_lpm.v b/test_regress/t/t_altera_lpm.v index 44676b6dc..0a3671e49 100644 --- a/test_regress/t/t_altera_lpm.v +++ b/test_regress/t/t_altera_lpm.v @@ -46,6 +46,7 @@ //END_MODULE_NAME-------------------------------------------------------------- //See also: https://github.com/twosigma/verilator_support +// verilator lint_off BLKANDNBLK // verilator lint_off COMBDLY // verilator lint_off INITIALDLY // verilator lint_off MULTIDRIVEN diff --git a/test_regress/t/t_cdc_async_bad.out b/test_regress/t/t_cdc_async_bad.out index 697a80fe0..3d767fbe4 100644 --- a/test_regress/t/t_cdc_async_bad.out +++ b/test_regress/t/t_cdc_async_bad.out @@ -1,14 +1,14 @@ %Warning-DEPRECATED: Option --cdc is deprecated and is planned for removal ... For warning description see https://verilator.org/warn/DEPRECATED?v=latest ... Use "/* verilator lint_off DEPRECATED */" and lint_on around source to disable this message. -%Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:28:21: Logic in path that feeds async reset, via signal: 't.rst2_bad_n' - 28 | wire rst2_bad_n = rst0_n | rst1_n; - | ^ -%Warning-CDCRSTLOGIC: See details in obj_vlt/t_cdc_async_bad/Vt_cdc_async_bad__cdc.txt %Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:53:21: Logic in path that feeds async reset, via signal: 't.rst6a_bad_n' 53 | wire rst6a_bad_n = rst6_bad_n ^ $c1("0"); | ^ +%Warning-CDCRSTLOGIC: See details in obj_vlt/t_cdc_async_bad/Vt_cdc_async_bad__cdc.txt %Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:54:21: Logic in path that feeds async reset, via signal: 't.rst6b_bad_n' 54 | wire rst6b_bad_n = rst6_bad_n ^ $c1("1"); | ^ +%Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:28:21: Logic in path that feeds async reset, via signal: 't.rst2_bad_n' + 28 | wire rst2_bad_n = rst0_n | rst1_n; + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_cellarray.pl b/test_regress/t/t_cellarray.pl index 710539001..fa5c2725e 100755 --- a/test_regress/t/t_cellarray.pl +++ b/test_regress/t/t_cellarray.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - v_flags2 => ["--stats"], + v_flags2 => ["--stats -fno-dfg"], ); execute( diff --git a/test_regress/t/t_const_opt.pl b/test_regress/t/t_const_opt.pl index 36f064cb4..1ee302ca7 100755 --- a/test_regress/t/t_const_opt.pl +++ b/test_regress/t/t_const_opt.pl @@ -11,7 +11,8 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-UNOPTTHREADS", "--stats", "$Self->{t_dir}/$Self->{name}.cpp"], + verilator_flags2 => ["-Wno-UNOPTTHREADS", "-fno-dfg", + "--stats", "$Self->{t_dir}/$Self->{name}.cpp"], ); execute( diff --git a/test_regress/t/t_dfg_circular.pl b/test_regress/t/t_dfg_circular.pl new file mode 100755 index 000000000..a4e59f8b5 --- /dev/null +++ b/test_regress/t/t_dfg_circular.pl @@ -0,0 +1,18 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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 + +scenarios(vlt => 1); + +compile( + verilator_flags2 => ["--dumpi-dfg 9"] + ); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_circular.v b/test_regress/t/t_dfg_circular.v new file mode 100644 index 000000000..5eb9a7049 --- /dev/null +++ b/test_regress/t/t_dfg_circular.v @@ -0,0 +1,19 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + wire a; + wire b; + + assign a = b + 1'b1; + assign b = a + 1'b1; + +endmodule diff --git a/test_regress/t/t_dfg_peephole.cpp b/test_regress/t/t_dfg_peephole.cpp new file mode 100644 index 000000000..cd2c25318 --- /dev/null +++ b/test_regress/t/t_dfg_peephole.cpp @@ -0,0 +1,37 @@ +// +// DESCRIPTION: Verilator: DFG optimzier equivalence testing +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 +// + +#include +#include + +#include +#include +#include + +int main(int, char**) { + // Create contexts + VerilatedContext ctx; + + // Create models + Vref ref{&ctx}; + Vopt opt{&ctx}; + + ref.clk = 0; + opt.clk = 0; + + while (!ctx.gotFinish()) { + ref.eval(); + opt.eval(); +#include "checks.h" + // increment time + ctx.timeInc(1); + + ref.clk = !ref.clk; + opt.clk = !opt.clk; + } +} diff --git a/test_regress/t/t_dfg_peephole.pl b/test_regress/t/t_dfg_peephole.pl new file mode 100755 index 000000000..ded92d5f5 --- /dev/null +++ b/test_regress/t/t_dfg_peephole.pl @@ -0,0 +1,126 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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 + +scenarios(vlt_all => 1); + +$Self->{sim_time} = 2000000; + +# Compile un-optimized +compile( + verilator_flags2 => ["--stats", "--build", "-fno-dfg", "+define+REF", + "-Mdir", "$Self->{obj_dir}/obj_ref", "--prefix", "Vref"], + verilator_make_gmake => 0, + verilator_make_cmake => 0 + ); + +# Generate the equivalence checks +my $rdFile = "$Self->{obj_dir}/obj_ref/Vref.h"; +my $wrFile = "$Self->{obj_dir}/checks.h"; +my $rfh = IO::File->new("<$rdFile") or error("$! $rdFile"); +my $wfh = IO::File->new(">$wrFile") or error("$! $wrFile"); +while (defined(my $line = $rfh->getline)) { + next if $line !~ /.*\b(dfg_[A-Z_]*)\b/; + my $signal = $1; + print $wfh "if (ref.$signal != opt.$signal) {\n"; + print $wfh " std::cout << \"Mismatched $signal\" << std::endl;\n"; + print $wfh " std::cout << \"Ref: 0x\" << std::hex << (ref.$signal + 0) << std::endl;\n"; + print $wfh " std::cout << \"Opt: 0x\" << std::hex << (opt.$signal + 0) << std::endl;\n"; + print $wfh " std::exit(1);\n"; + print $wfh "}\n"; +} +close $rdFile; +close $wrFile; + +# Compile optimized - also builds executable +compile( + verilator_flags2 => ["--stats", "--build", "--exe", + "-Mdir", "$Self->{obj_dir}/obj_opt", "--prefix", "Vopt", + "-CFLAGS \"-I .. -I ../obj_ref\"", + "../obj_ref/Vref__ALL.a", + "../../t/$Self->{name}.cpp"], + verilator_make_gmake => 0, + verilator_make_cmake => 0 + ); + +# Execute test to check equivalence +execute( + executable => "$Self->{obj_dir}/obj_opt/Vopt", + check_finished => 1, + ); + +sub check { + my $name = shift; + $name = lc $name; + $name =~ s/_/ /g; + file_grep("$Self->{obj_dir}/obj_opt/Vopt__stats.txt", qr/DFG\s+(pre|post) inline Peephole, ${name}\s+([1-9]\d*)/i); +} + +# Check optimizations +check("SWAP_CONST_IN_COMMUTATIVE_BINARY"); +check("SWAP_NOT_IN_COMMUTATIVE_BINARY"); +check("SWAP_VAR_IN_COMMUTATIVE_BINARY"); +check("PUSH_BITWISE_OP_THROUGH_CONCAT"); +check("PUSH_COMPARE_OP_THROUGH_CONCAT"); +#check("REMOVE_WIDTH_ONE_REDUCTION"); V3Const eats this +check("PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH"); +check("REPLACE_REDUCTION_OF_CONST"); +check("REPLACE_EXTEND"); +check("PUSH_NOT_THROUGH_COND"); +check("REMOVE_NOT_NOT"); +check("REPLACE_NOT_NEQ"); +check("REPLACE_NOT_OF_CONST"); +check("REPLACE_AND_OF_NOT_AND_NOT"); +check("REPLACE_AND_OF_CONST_AND_CONST"); +check("REPLACE_AND_WITH_ZERO"); +check("REMOVE_AND_WITH_ONES"); +check("REPLACE_CONTRADICTORY_AND"); +check("REPLACE_OR_OF_NOT_AND_NOT"); +check("REPLACE_OR_OF_NOT_AND_NEQ"); +check("REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO"); +check("REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS"); +check("REPLACE_OR_OF_CONST_AND_CONST"); +check("REMOVE_OR_WITH_ZERO"); +check("REPLACE_OR_WITH_ONES"); +check("REPLACE_TAUTOLOGICAL_OR"); +check("REMOVE_SUB_ZERO"); +check("REPLACE_SUB_WITH_NOT"); +check("REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT"); +check("REPLACE_EQ_OF_CONST_AND_CONST"); +check("REMOVE_FULL_WIDTH_SEL"); +check("REMOVE_SEL_FROM_RHS_OF_CONCAT"); +check("REMOVE_SEL_FROM_LHS_OF_CONCAT"); +check("PUSH_SEL_THROUGH_CONCAT"); +check("PUSH_SEL_THROUGH_REPLICATE"); +check("PUSH_SEL_THROUGH_NOT"); +check("REPLACE_SEL_FROM_SEL"); +check("PUSH_SEL_THROUGH_COND"); +check("PUSH_SEL_THROUGH_SHIFTL"); +check("REPLACE_SEL_FROM_CONST"); +check("REPLACE_CONCAT_OF_CONSTS"); +check("REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS"); +check("REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS"); +check("REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR"); +check("REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL"); +check("PUSH_CONCAT_THROUGH_NOTS"); +check("REMOVE_CONCAT_OF_ADJOINING_SELS"); +check("REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS"); +check("REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS"); +check("REMOVE_COND_WITH_FALSE_CONDITION"); +check("REMOVE_COND_WITH_TRUE_CONDITION"); +check("SWAP_COND_WITH_NOT_CONDITION"); +check("SWAP_COND_WITH_NEQ_CONDITION"); +check("PULL_NOTS_THROUGH_COND"); +check("REPLACE_COND_WITH_THEN_BRANCH_ZERO"); +check("REPLACE_COND_WITH_THEN_BRANCH_ONES"); +check("REPLACE_COND_WITH_ELSE_BRANCH_ZERO"); +check("REPLACE_COND_WITH_ELSE_BRANCH_ONES"); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v new file mode 100644 index 000000000..e5609c9c4 --- /dev/null +++ b/test_regress/t/t_dfg_peephole.v @@ -0,0 +1,227 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +`define STRINGIFY(x) `"x`" + +`define signal(name, expr) wire [$bits(expr)-1:0] dfg_``name = expr; + + +module t (/*AUTOARG*/ + // Outputs + dfg_SWAP_CONST_IN_COMMUTATIVE_BINARY, + dfg_SWAP_NOT_IN_COMMUTATIVE_BINARY, + dfg_SWAP_VAR_IN_COMMUTATIVE_BINARY, + dfg_PUSH_BITWISE_OP_THROUGH_CONCAT, + dfg_PUSH_BITWISE_OP_THROUGH_CONCAT_2, + dfg_PUSH_COMPARE_OP_THROUGH_CONCAT, dfg_REMOVE_WIDTH_ONE_REDUCTION, + dfg_PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH, + dfg_REPLACE_REDUCTION_OF_CONST_AND, + dfg_REPLACE_REDUCTION_OF_CONST_OR, + dfg_REPLACE_REDUCTION_OF_CONST_XOR, dfg_REPLACE_EXTEND, + dfg_PUSH_NOT_THROUGH_COND, dfg_REMOVE_NOT_NOT, dfg_REPLACE_NOT_NEQ, + dfg_REPLACE_NOT_OF_CONST, dfg_REPLACE_AND_OF_NOT_AND_NOT, + dfg_REPLACE_AND_OF_CONST_AND_CONST, dfg_REPLACE_AND_WITH_ZERO, + dfg_REMOVE_AND_WITH_ONES, dfg_REPLACE_CONTRADICTORY_AND, + dfg_REPLACE_OR_OF_NOT_AND_NOT, dfg_REPLACE_OR_OF_NOT_AND_NEQ, + dfg_REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, + dfg_REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, + dfg_REPLACE_OR_OF_CONST_AND_CONST, dfg_REMOVE_OR_WITH_ZERO, + dfg_REPLACE_OR_WITH_ONES, dfg_REPLACE_TAUTOLOGICAL_OR, + dfg_REMOVE_SUB_ZERO, dfg_REPLACE_SUB_WITH_NOT, + dfg_REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT, + dfg_REPLACE_EQ_OF_CONST_AND_CONST, dfg_REMOVE_FULL_WIDTH_SEL, + dfg_REMOVE_SEL_FROM_RHS_OF_CONCAT, + dfg_REMOVE_SEL_FROM_LHS_OF_CONCAT, dfg_PUSH_SEL_THROUGH_CONCAT, + dfg_PUSH_SEL_THROUGH_REPLICATE, dfg_REPLACE_SEL_FROM_CONST, + dfg_REPLACE_CONCAT_OF_CONSTS, + dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS, + dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS, + dfg_REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, + dfg_REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, + dfg_PUSH_CONCAT_THROUGH_NOTS, dfg_REMOVE_CONCAT_OF_ADJOINING_SELS, + dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, + dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, + dfg_REMOVE_COND_WITH_FALSE_CONDITION, + dfg_REMOVE_COND_WITH_TRUE_CONDITION, + dfg_SWAP_COND_WITH_NOT_CONDITION, dfg_SWAP_COND_WITH_NEQ_CONDITION, + dfg_PULL_NOTS_THROUGH_COND, dfg_REPLACE_COND_WITH_THEN_BRANCH_ZERO, + dfg_REPLACE_COND_WITH_THEN_BRANCH_ONES, + dfg_REPLACE_COND_WITH_ELSE_BRANCH_ZERO, + dfg_REPLACE_COND_WITH_ELSE_BRANCH_ONES, dfg_PUSH_SEL_THROUGH_COND, + dfg_PUSH_SEL_THROUGH_SHIFTL, dfg_REPLACE_SEL_FROM_SEL, + // Inputs + clk + ); + input clk; + + // Sadly verilog-mode cannot look in macros so need to define these + // separately + output dfg_SWAP_CONST_IN_COMMUTATIVE_BINARY; + output dfg_SWAP_NOT_IN_COMMUTATIVE_BINARY; + output dfg_SWAP_VAR_IN_COMMUTATIVE_BINARY; + output dfg_PUSH_BITWISE_OP_THROUGH_CONCAT; + output dfg_PUSH_BITWISE_OP_THROUGH_CONCAT_2; + output dfg_PUSH_COMPARE_OP_THROUGH_CONCAT; + output dfg_REMOVE_WIDTH_ONE_REDUCTION; + output dfg_PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH; + output dfg_REPLACE_REDUCTION_OF_CONST_AND; + output dfg_REPLACE_REDUCTION_OF_CONST_OR; + output dfg_REPLACE_REDUCTION_OF_CONST_XOR; + output dfg_REPLACE_EXTEND; + output dfg_PUSH_NOT_THROUGH_COND; + output dfg_REMOVE_NOT_NOT; + output dfg_REPLACE_NOT_NEQ; + output dfg_REPLACE_NOT_OF_CONST; + output dfg_REPLACE_AND_OF_NOT_AND_NOT; + output dfg_REPLACE_AND_OF_CONST_AND_CONST; + output dfg_REPLACE_AND_WITH_ZERO; + output dfg_REMOVE_AND_WITH_ONES; + output dfg_REPLACE_CONTRADICTORY_AND; + output dfg_REPLACE_OR_OF_NOT_AND_NOT; + output dfg_REPLACE_OR_OF_NOT_AND_NEQ; + output dfg_REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO; + output dfg_REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS; + output dfg_REPLACE_OR_OF_CONST_AND_CONST; + output dfg_REMOVE_OR_WITH_ZERO; + output dfg_REPLACE_OR_WITH_ONES; + output dfg_REPLACE_TAUTOLOGICAL_OR; + output dfg_REMOVE_SUB_ZERO; + output dfg_REPLACE_SUB_WITH_NOT; + output dfg_REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT; + output dfg_REPLACE_EQ_OF_CONST_AND_CONST; + output dfg_REMOVE_FULL_WIDTH_SEL; + output dfg_REMOVE_SEL_FROM_RHS_OF_CONCAT; + output dfg_REMOVE_SEL_FROM_LHS_OF_CONCAT; + output dfg_PUSH_SEL_THROUGH_CONCAT; + output dfg_PUSH_SEL_THROUGH_REPLICATE; + output dfg_REPLACE_SEL_FROM_CONST; + output dfg_REPLACE_CONCAT_OF_CONSTS; + output dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS; + output dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS; + output dfg_REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR; + output dfg_REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL; + output dfg_PUSH_CONCAT_THROUGH_NOTS; + output dfg_REMOVE_CONCAT_OF_ADJOINING_SELS; + output dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS; + output dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS; + output dfg_REMOVE_COND_WITH_FALSE_CONDITION; + output dfg_REMOVE_COND_WITH_TRUE_CONDITION; + output dfg_SWAP_COND_WITH_NOT_CONDITION; + output dfg_SWAP_COND_WITH_NEQ_CONDITION; + output dfg_PULL_NOTS_THROUGH_COND; + output dfg_REPLACE_COND_WITH_THEN_BRANCH_ZERO; + output dfg_REPLACE_COND_WITH_THEN_BRANCH_ONES; + output dfg_REPLACE_COND_WITH_ELSE_BRANCH_ZERO; + output dfg_REPLACE_COND_WITH_ELSE_BRANCH_ONES; + output dfg_PUSH_SEL_THROUGH_COND; + output dfg_PUSH_SEL_THROUGH_SHIFTL; + output dfg_REPLACE_SEL_FROM_SEL; + + integer cyc = 0; + + reg [63:0] crc = 64'h5aef0c8d_d70a4497; + reg [63:0] rcr; + wire logic [127:0] rcr_crc = {rcr, crc}; + wire logic [127:0] crc_rep = {2{crc}}; + wire logic [63:0] const_a; + wire logic [63:0] const_b; + + always @ (posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; + rcr <= ~crc; + +`ifdef REF + if (cyc >= 100_000) begin + $write("*-* All Finished *-*\n"); + $finish; + end +`endif + end + + // 64'0 but don't tell V3Const +`define ZERO (const_a & ~const_a) + // 64'1 but don't tell V3Const +`define ONES (const_a | ~const_a) + // x, but in a way only DFG understands +`define DFG(x) ((|`ONES) ? (x) : (~x)) + + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, crc + const_a); + `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, crc + ~crc); + `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rcr + crc); + `signal(PUSH_BITWISE_OP_THROUGH_CONCAT, 32'h12345678 ^ {8'h0, crc[23:0]}); + `signal(PUSH_BITWISE_OP_THROUGH_CONCAT_2, 32'h12345678 ^ {rcr[7:0], crc[23:0]}); + `signal(PUSH_COMPARE_OP_THROUGH_CONCAT, 4'b1011 == {2'b10, crc[1:0]}); + `signal(REMOVE_WIDTH_ONE_REDUCTION, &`DFG(crc[0])); + `signal(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH, |(crc[32] ? crc[3:0] : 4'h0)); + `signal(REPLACE_REDUCTION_OF_CONST_AND, &const_a); + `signal(REPLACE_REDUCTION_OF_CONST_OR, |const_a); + `signal(REPLACE_REDUCTION_OF_CONST_XOR, ^const_a); + `signal(REPLACE_EXTEND, 4'(crc[0])); + `signal(PUSH_NOT_THROUGH_COND, ~(crc[0] ? crc[4:0] : 5'hb)); + `signal(REMOVE_NOT_NOT, ~`DFG(~`DFG(crc))); + `signal(REPLACE_NOT_NEQ, ~`DFG(crc != rcr)); + `signal(REPLACE_NOT_OF_CONST, ~4'd0); + `signal(REPLACE_AND_OF_NOT_AND_NOT, ~crc[0] & ~rcr[0]); + `signal(REPLACE_AND_OF_CONST_AND_CONST, const_a & const_b); + `signal(REPLACE_AND_WITH_ZERO, `ZERO & crc); + `signal(REMOVE_AND_WITH_ONES, `ONES & crc); + `signal(REPLACE_CONTRADICTORY_AND, crc & ~crc); + `signal(REPLACE_OR_OF_NOT_AND_NOT, ~crc[0] | ~rcr[0]); + `signal(REPLACE_OR_OF_NOT_AND_NEQ, ~crc[0] | (rcr != 64'd2)); + `signal(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, {2'd0, crc[1:0]} | {rcr[1:0], 2'd0}); + `signal(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, {crc[1:0], 2'd0} | {2'd0, rcr[1:0]}); + `signal(REPLACE_OR_OF_CONST_AND_CONST, const_a | const_b); + `signal(REMOVE_OR_WITH_ZERO, `ZERO | crc); + `signal(REPLACE_OR_WITH_ONES, `ONES | crc); + `signal(REPLACE_TAUTOLOGICAL_OR, crc | ~crc); + `signal(REMOVE_SUB_ZERO, crc - `ZERO); + `signal(REPLACE_SUB_WITH_NOT, crc[0] - 1'b1); + `signal(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT, crc << {2'b0, crc[2:0]}); + `signal(REPLACE_EQ_OF_CONST_AND_CONST, 4'd0 == 4'd1); + `signal(REMOVE_FULL_WIDTH_SEL, crc[63:0]); + `signal(REMOVE_SEL_FROM_RHS_OF_CONCAT, rcr_crc[63:0]); + `signal(REMOVE_SEL_FROM_LHS_OF_CONCAT, rcr_crc[127:64]); + `signal(PUSH_SEL_THROUGH_CONCAT, rcr_crc[120:0]); + `signal(PUSH_SEL_THROUGH_REPLICATE, crc_rep[0]); + `signal(REPLACE_SEL_FROM_CONST, const_a[2]); + `signal(REPLACE_CONCAT_OF_CONSTS, {const_a, const_b}); + `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS, {`DFG({crc, const_a}), const_b}); + `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS, {const_a, `DFG({const_b, crc})}); + `signal(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, {62'd0, crc[63:62]}); + `signal(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, {crc[1:0], 62'd0}); + `signal(PUSH_CONCAT_THROUGH_NOTS, {~crc, ~rcr} ); + `signal(REMOVE_CONCAT_OF_ADJOINING_SELS, {`DFG(crc[10:3]), `DFG(crc[2:1])}); + `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, {crc[10:3], {crc[2:1], rcr}}); + `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, {`DFG({rcr, crc[10:3]}), crc[2:1]}); + `signal(REMOVE_COND_WITH_FALSE_CONDITION, &`ZERO ? crc : rcr); + `signal(REMOVE_COND_WITH_TRUE_CONDITION, |`ONES ? crc : rcr); + `signal(SWAP_COND_WITH_NOT_CONDITION, (~crc[0] & |`ONES) ? crc : rcr); + `signal(SWAP_COND_WITH_NEQ_CONDITION, rcr != crc ? crc : rcr); + `signal(PULL_NOTS_THROUGH_COND, crc[0] ? ~crc[4:0] : ~rcr[4:0]); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ZERO, crc[0] ? |`ZERO : crc[1]); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ONES, crc[0] ? |`ONES : crc[1]); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ZERO, crc[0] ? crc[1] : |`ZERO); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ONES, crc[0] ? crc[1] : |`ONES); + + assign const_a = (crc | ~crc) & 64'h0123456789abcdef; + assign const_b = ~(crc & ~crc) & 64'h98badefc10325647; + + // Some selects need extra temporaries + wire [63:0] sel_from_cond = crc[0] ? crc : const_a; + wire [63:0] sel_from_shiftl = crc << 10; + wire [31:0] sel_from_sel = crc[10+:32]; + + `signal(PUSH_SEL_THROUGH_COND, sel_from_cond[2]); + `signal(PUSH_SEL_THROUGH_SHIFTL, sel_from_shiftl[20:0]); + `signal(REPLACE_SEL_FROM_SEL, sel_from_sel[4:3]); + + // Sel from not requires the operand to have a sinle sink, so can't use + // the chekc due to the raw expression referencing the operand + wire [63:0] sel_from_not_tmp = ~(crc >> rcr[2:0] << crc[3:0]); + wire sel_from_not = sel_from_not_tmp[2]; + always @(posedge clk) if ($c(0)) $display(sel_from_not); // Do not remove signal +endmodule diff --git a/test_regress/t/t_dfg_unhandled.pl b/test_regress/t/t_dfg_unhandled.pl new file mode 100755 index 000000000..6364d038e --- /dev/null +++ b/test_regress/t/t_dfg_unhandled.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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 + +scenarios(vlt => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +file_grep($Self->{stats}, qr/Optimizations, DFG pre inline Ast2Dfg, non-representable \(impure\)\s+(\d+)/i, 1); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_unhandled.v b/test_regress/t/t_dfg_unhandled.v new file mode 100644 index 000000000..f01edfeff --- /dev/null +++ b/test_regress/t/t_dfg_unhandled.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire clk, + output wire [31:0] o0 + ); + + int file; + + assign o0 = $fgetc(file); // Impure + +endmodule diff --git a/test_regress/t/t_dump_dfg.pl b/test_regress/t/t_dump_dfg.pl new file mode 100755 index 000000000..2164a894c --- /dev/null +++ b/test_regress/t/t_dump_dfg.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Geza Lore. 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 + +scenarios(vlt => 1); + +# For code coverage of graph dumping, so does not matter much what the input is +top_filename("t/t_bench_mux4k.v"); + +compile( + verilator_flags2 => ["--dump-dfg", "--dumpi-dfg 9"], + ); + +ok(1); +1; diff --git a/test_regress/t/t_flag_expand_limit.pl b/test_regress/t/t_flag_expand_limit.pl index 6270f76ca..60ff7ed44 100755 --- a/test_regress/t/t_flag_expand_limit.pl +++ b/test_regress/t/t_flag_expand_limit.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); compile( - verilator_flags2 => ['--expand-limit 1 --stats'], + verilator_flags2 => ['--expand-limit 1 --stats -fno-dfg'], ); file_grep($Self->{stats}, qr/Optimizations, expand limited\s+(\d+)/i, 4); diff --git a/test_regress/t/t_gate_chained.pl b/test_regress/t/t_gate_chained.pl index ac9dd39d1..ebaff169c 100755 --- a/test_regress/t/t_gate_chained.pl +++ b/test_regress/t/t_gate_chained.pl @@ -46,7 +46,7 @@ gen($Self->{top_filename}); compile( verilator_flags2 => ["--stats --x-assign fast --x-initial fast", - "-Wno-UNOPTTHREADS"], + "-Wno-UNOPTTHREADS -fno-dfg"], ); execute( diff --git a/test_regress/t/t_gate_ormux.pl b/test_regress/t/t_gate_ormux.pl index 02476bffd..3f5118673 100755 --- a/test_regress/t/t_gate_ormux.pl +++ b/test_regress/t/t_gate_ormux.pl @@ -15,7 +15,7 @@ $Self->{sim_time} = $Self->{cycles} * 10 + 1000; compile( v_flags2 => ["+define+SIM_CYCLES=$Self->{cycles}",], - verilator_flags2 => ["-Wno-UNOPTTHREADS", "--stats"], + verilator_flags2 => ["-Wno-UNOPTTHREADS", "--stats", "-fno-dfg"], ); if ($Self->{vlt}) { diff --git a/test_regress/t/t_inst_tree_inl1_pub0.pl b/test_regress/t/t_inst_tree_inl1_pub0.pl index 9e8e50970..b8f738b3e 100755 --- a/test_regress/t/t_inst_tree_inl1_pub0.pl +++ b/test_regress/t/t_inst_tree_inl1_pub0.pl @@ -14,7 +14,7 @@ top_filename("t/t_inst_tree.v"); my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml"; compile( - v_flags2 => ["$Self->{t_dir}/t_inst_tree_inl1_pub0.vlt"], + v_flags2 => ["-fno-dfg-post-inline", "$Self->{t_dir}/t_inst_tree_inl1_pub0.vlt"], ); if ($Self->{vlt_all}) { diff --git a/test_regress/t/t_inst_tree_inl1_pub1.pl b/test_regress/t/t_inst_tree_inl1_pub1.pl index 2c8ff9ac9..4240d8339 100755 --- a/test_regress/t/t_inst_tree_inl1_pub1.pl +++ b/test_regress/t/t_inst_tree_inl1_pub1.pl @@ -14,7 +14,7 @@ top_filename("t/t_inst_tree.v"); my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml"; compile( - v_flags2 => ["t/$Self->{name}.vlt", + v_flags2 => ["-fno-dfg-post-inline", "t/$Self->{name}.vlt", $Self->wno_unopthreads_for_few_cores()] ); diff --git a/test_regress/t/t_math_wide_bad.out b/test_regress/t/t_math_wide_bad.out index ec2179c13..b3c120d82 100644 --- a/test_regress/t/t_math_wide_bad.out +++ b/test_regress/t/t_math_wide_bad.out @@ -1,10 +1,10 @@ -%Error-UNSUPPORTED: t/t_math_wide_bad.v:22:18: Unsupported: operator POWSS operator of 576 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h - 22 | assign z2 = a ** 3; - | ^~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_math_wide_bad.v:23:15: Unsupported: operator ISTORD operator of 64 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h 23 | assign r = real'(a); | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_math_wide_bad.v:22:18: Unsupported: operator POWSS operator of 576 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h + 22 | assign z2 = a ** 3; + | ^~ %Error-UNSUPPORTED: t/t_math_wide_bad.v:21:17: Unsupported: operator MULS operator of 576 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h 21 | assign z = a * b; | ^ diff --git a/test_regress/t/t_order_wireloop.pl b/test_regress/t/t_order_wireloop.pl index 9eef881b4..41a50e716 100755 --- a/test_regress/t/t_order_wireloop.pl +++ b/test_regress/t/t_order_wireloop.pl @@ -16,7 +16,7 @@ compile( # However we no longer gate optimize this # Can't use expect_filename here as unstable output expect => -'%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'t.foo\' +'%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'bar\' ', ); diff --git a/test_regress/t/t_var_escape.out b/test_regress/t/t_var_escape.out index a4bcf2e13..3cf2f0549 100644 --- a/test_regress/t/t_var_escape.out +++ b/test_regress/t/t_var_escape.out @@ -1,32 +1,31 @@ $version Generated by VerilatedVcd $end -$date Tue Jul 24 18:44:43 2012 - $end +$date Thu Sep 22 13:02:07 2022 $end $timescale 1ps $end $scope module top $end - $var wire 1 * 9num $end - $var wire 1 + bra[ket]slash/dash-colon:9backslash\done $end - $var wire 1 ' clk $end - $var wire 1 ) double__underscore $end - $var wire 1 ( escaped_normal $end + $var wire 1 & 9num $end + $var wire 1 ' bra[ket]slash/dash-colon:9backslash\done $end + $var wire 1 # clk $end + $var wire 1 % double__underscore $end + $var wire 1 $ escaped_normal $end $scope module t $end - $var wire 1 * 9num $end - $var wire 32 & a0.cyc [31:0] $end - $var wire 1 + bra[ket]slash/dash-colon:9backslash\done $end + $var wire 1 $ 9num $end + $var wire 32 * a0.cyc [31:0] $end + $var wire 1 $ bra[ket]slash/dash-colon:9backslash\done $end $var wire 1 $ check:alias $end - $var wire 1 % check;alias $end + $var wire 1 ) check;alias $end $var wire 1 $ check_alias $end - $var wire 1 ' clk $end - $var wire 32 # cyc [31:0] $end - $var wire 1 ) double__underscore $end - $var wire 1 ( escaped_normal $end - $var wire 32 & other.cyc [31:0] $end + $var wire 1 # clk $end + $var wire 32 ( cyc [31:0] $end + $var wire 1 $ double__underscore $end + $var wire 1 $ escaped_normal $end + $var wire 32 * other.cyc [31:0] $end $var wire 1 $ wire $end $scope module a0 $end - $var wire 32 # cyc [31:0] $end + $var wire 32 ( cyc [31:0] $end $upscope $end $scope module mod.with_dot $end - $var wire 32 # cyc [31:0] $end + $var wire 32 ( cyc [31:0] $end $upscope $end $upscope $end $upscope $end @@ -34,130 +33,119 @@ $enddefinitions $end #0 +0# 1$ -0% -b11111111111111111111111111111110 & -b00000000000000000000000000000001 # -0' -1( -1) -1* -1+ +1% +1& +1' +b00000000000000000000000000000001 ( +0) +b11111111111111111111111111111110 * #10 +1# 0$ -1% -b11111111111111111111111111111101 & -b00000000000000000000000000000010 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000000010 ( +1) +b11111111111111111111111111111101 * #15 -0' +0# #20 +1# 1$ -0% -b11111111111111111111111111111100 & -b00000000000000000000000000000011 # +1% +1& 1' -1( -1) -1* -1+ +b00000000000000000000000000000011 ( +0) +b11111111111111111111111111111100 * #25 -0' +0# #30 +1# 0$ -1% -b11111111111111111111111111111011 & -b00000000000000000000000000000100 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000000100 ( +1) +b11111111111111111111111111111011 * #35 -0' +0# #40 +1# 1$ -0% -b11111111111111111111111111111010 & -b00000000000000000000000000000101 # +1% +1& 1' -1( -1) -1* -1+ +b00000000000000000000000000000101 ( +0) +b11111111111111111111111111111010 * #45 -0' +0# #50 +1# 0$ -1% -b11111111111111111111111111111001 & -b00000000000000000000000000000110 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000000110 ( +1) +b11111111111111111111111111111001 * #55 -0' +0# #60 +1# 1$ -0% -b11111111111111111111111111111000 & -b00000000000000000000000000000111 # +1% +1& 1' -1( -1) -1* -1+ +b00000000000000000000000000000111 ( +0) +b11111111111111111111111111111000 * #65 -0' +0# #70 +1# 0$ -1% -b11111111111111111111111111110111 & -b00000000000000000000000000001000 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000001000 ( +1) +b11111111111111111111111111110111 * #75 -0' +0# #80 +1# 1$ -0% -b11111111111111111111111111110110 & -b00000000000000000000000000001001 # -1' -1( -1) -1* -1+ -#85 -0' -#90 -0$ 1% -b11111111111111111111111111110101 & -b00000000000000000000000000001010 # +1& 1' -0( +b00000000000000000000000000001001 ( 0) -0* -0+ -#95 -0' -#100 -1$ +b11111111111111111111111111110110 * +#85 +0# +#90 +1# +0$ 0% -b11111111111111111111111111110100 & -b00000000000000000000000000001011 # -1' -1( +0& +0' +b00000000000000000000000000001010 ( 1) -1* -1+ +b11111111111111111111111111110101 * +#95 +0# +#100 +1# +1$ +1% +1& +1' +b00000000000000000000000000001011 ( +0) +b11111111111111111111111111110100 * diff --git a/test_regress/t/t_xml_first.out b/test_regress/t/t_xml_first.out index 11cff436a..1056865dc 100644 --- a/test_regress/t/t_xml_first.out +++ b/test_regress/t/t_xml_first.out @@ -50,8 +50,8 @@ - - + + diff --git a/test_regress/t/t_xml_flat.out b/test_regress/t/t_xml_flat.out index bc2e4f9b8..d7ec257c2 100644 --- a/test_regress/t/t_xml_flat.out +++ b/test_regress/t/t_xml_flat.out @@ -99,8 +99,8 @@ - - + +