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 @@ - - + +