diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32bebdc1f4e..a70f8b4e9f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -222,6 +222,7 @@ set(COMMON_SOURCES V3DfgPasses.cpp V3DfgPeephole.cpp V3DfgRegularize.cpp + V3DfgVectorize.cpp V3DupFinder.cpp V3Timing.cpp V3EmitCBase.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 232ff7b1538..5356835d2f6 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -235,6 +235,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3DfgPasses.o \ V3DfgPeephole.o \ V3DfgRegularize.o \ + V3DfgVectorize.o \ V3DupFinder.o \ V3EmitCMain.o \ V3EmitCMake.o \ diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index a54120e7bce..02936635f6c 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -55,14 +55,14 @@ void DfgGraph::addGraph(DfgGraph& other) { m_opVertices.splice(m_opVertices.end(), other.m_opVertices); } -static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; } +const std::string DfgVertex::toDotId() const { return '"' + cvtToHex(this) + '"'; } // Dump one DfgVertex in Graphviz format static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { if (const DfgVarPacked* const varVtxp = vtx.cast()) { AstVar* const varp = varVtxp->varp(); - os << toDotId(vtx); + os << vtx.toDotId(); os << " [label=\"" << varp->name() << "\nW" << varVtxp->width() << " / F" << varVtxp->fanout() << '"'; @@ -90,7 +90,7 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { if (const DfgVarArray* const arrVtxp = vtx.cast()) { AstVar* const varp = arrVtxp->varp(); const int elements = VN_AS(arrVtxp->dtypep(), UnpackArrayDType)->elementsConst(); - os << toDotId(vtx); + os << vtx.toDotId(); os << " [label=\"" << varp->name() << "[" << elements << "]\""; if (varp->direction() == VDirection::INPUT) { os << ", shape=box3d, style=filled, fillcolor=chartreuse2"; // Green @@ -116,7 +116,7 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { if (const DfgConst* const constVtxp = vtx.cast()) { const V3Number& num = constVtxp->num(); - os << toDotId(vtx); + os << vtx.toDotId(); os << " [label=\""; if (num.width() <= 32 && !num.isSigned()) { os << constVtxp->width() << "'d" << num.toUInt() << "\n"; @@ -133,7 +133,7 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { if (const DfgSel* const selVtxp = vtx.cast()) { const uint32_t lsb = selVtxp->lsb(); const uint32_t msb = lsb + selVtxp->width() - 1; - os << toDotId(vtx); + os << vtx.toDotId(); os << " [label=\"SEL\n_[" << msb << ":" << lsb << "]\nW" << vtx.width() << " / F" << vtx.fanout() << '"'; if (vtx.hasMultipleSinks()) { @@ -145,7 +145,7 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { return; } - os << toDotId(vtx); + os << vtx.toDotId(); os << " [label=\"" << vtx.typeName() << "\nW" << vtx.width() << " / F" << vtx.fanout() << '"'; if (vtx.hasMultipleSinks()) { os << ", shape=doublecircle"; @@ -157,18 +157,18 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { // 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()); + os << edge.sourcep()->toDotId() << " -> " << edge.sinkp()->toDotId(); if (!headlabel.empty()) os << " [headlabel=\"" << headlabel << "\"]"; os << "\n"; } // 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) { // +void DfgVertex::dumpDotVertexAndSourceEdges(std::ostream& os) const { + dumpDotVertex(os, *this); + forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { if (edge.sourcep()) { string headLabel; - if (vtx.arity() > 1 || vtx.is()) headLabel = vtx.srcName(idx); + if (arity() > 1 || is()) headLabel = srcName(idx); dumpDotEdge(os, edge, headLabel); } }); @@ -183,7 +183,7 @@ void DfgGraph::dumpDot(std::ostream& os, const string& label) const { os << "graph [rankdir=LR]\n"; // Emit all vertices - forEachVertex([&](const DfgVertex& vtx) { dumpDotVertexAndSourceEdges(os, vtx); }); + forEachVertex([&](const DfgVertex& vtx) { vtx.dumpDotVertexAndSourceEdges(os); }); // Footer os << "}\n"; @@ -228,13 +228,13 @@ static void dumpDotUpstreamConeFromVertex(std::ostream& os, const DfgVertex& vtx itemp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); }); // Emit this vertex and all of its source edges - dumpDotVertexAndSourceEdges(os, *itemp); + itemp->dumpDotVertexAndSourceEdges(os); } // Emit all DfgVarPacked vertices that have external references driven by this vertex vtx.forEachSink([&](const DfgVertex& dst) { if (const DfgVarPacked* const varVtxp = dst.cast()) { - if (varVtxp->hasNonLocalRefs()) dumpDotVertexAndSourceEdges(os, dst); + if (varVtxp->hasNonLocalRefs()) dst.dumpDotVertexAndSourceEdges(os); } }); } diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 0fe4036860b..e33ccdf9ab0 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -281,6 +281,11 @@ class DfgVertex VL_NOT_FINAL { // Fanout (number of sinks) of this vertex (expensive to compute) uint32_t fanout() const VL_MT_DISABLED; + // If this vertex has a single sink, return it, otherwise return nullptr + DfgVertex* singleSink() { + return m_sinksp && !m_sinksp->m_nextp ? m_sinksp->sinkp() : nullptr; + } + // Unlink from container (graph or builder), then delete this vertex void unlinkDelete(DfgGraph& dfg) VL_MT_DISABLED; @@ -399,6 +404,11 @@ class DfgVertex VL_NOT_FINAL { // Human-readable name for source operand with given index for debugging virtual const string srcName(size_t idx) const = 0; + + // Returns Graphviz id of this vertex. + const std::string toDotId() const; + // Dump this vertex and all it's source edges in Graphviz format to given stream 'os'. + void dumpDotVertexAndSourceEdges(std::ostream& os) const; }; //------------------------------------------------------------------------------ diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 355a85a4ffe..d7db43c9fb0 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -363,6 +363,10 @@ void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { } // Accumulate patterns for reporting if (v3Global.opt.stats()) ctx.m_patternStats.accumulate(dfg); + apply(4, "vectorize", [&]() { + vectorize(dfg); + removeUnused(dfg); + }); apply(4, "regularize", [&]() { regularize(dfg, ctx.m_regularizeContext); }); if (dumpDfgLevel() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized"); } diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index 6fe0d7d5461..04f9850ec8b 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -130,11 +130,12 @@ void inlineVars(DfgGraph&) VL_MT_DISABLED; void peephole(DfgGraph&, V3DfgPeepholeContext&) VL_MT_DISABLED; // Regularize graph. This must be run before converting back to Ast. void regularize(DfgGraph&, V3DfgRegularizeContext&) VL_MT_DISABLED; +// Vectorize compatible vertices +void vectorize(DfgGraph&); // Remove unused nodes void removeUnused(DfgGraph&) VL_MT_DISABLED; // Eliminate (remove or replace) redundant variables. Also removes resulting unused logic. void eliminateVars(DfgGraph&, V3DfgEliminateVarsContext&) VL_MT_DISABLED; - } // namespace V3DfgPasses #endif diff --git a/src/V3DfgVectorize.cpp b/src/V3DfgVectorize.cpp new file mode 100644 index 00000000000..f4ea5139d09 --- /dev/null +++ b/src/V3DfgVectorize.cpp @@ -0,0 +1,447 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Peephole optimizations over DfgGraph +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2024 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// TODO: description +// +//************************************************************************* + +#include "config_build.h" + +#include "V3Ast.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3File.h" +#include "V3Global.h" +#include "V3Stats.h" + +#include +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +class V3DfgVectorize final { + + static constexpr uint32_t UNASSIGNED = std::numeric_limits::max(); + + // TYPES + struct State final { + DfgVertex* lop = nullptr; // Lower bit index neighbour + DfgVertex* hip = nullptr; // Higher bit index neighbour + size_t rank = UNASSIGNED; + State() {} + + private: + VL_UNCOPYABLE(State); + }; + + // STATE + DfgGraph& m_dfg; // The DfgGraph being visited + std::deque m_sateAlloc; // Allocator for State structures + std::vector m_packs; // Vector of vertex packs to consider for vectorization + // Map from vertex of pack to vectorized vertex, and lsb offset + std::unordered_map> m_vtx2vec; + + // METHODS + State& state(DfgVertex* vtxp) { + State*& statep = vtxp->user(); + if (!statep) { + m_sateAlloc.emplace_back(); + statep = &m_sateAlloc.back(); + if (vtxp->arity() == 0) { + statep->rank = 0; + } else { + size_t max = 0; + vtxp->forEachSource([&](DfgVertex& src) { // + max = std::max(max, state(&src).rank); + }); + statep->rank = max + 1; + } + } + return *statep; + } + + bool canVectorize(DfgVertex* vtxp) { + return vtxp->is() || vtxp->is() || vtxp->is() || vtxp->is(); + } + + void constructRoots() { + // Map from vertex to all the DfgSel vertices that select from that vertex + std::unordered_map> selects; + selects.reserve(m_dfg.size()); + + // Gather root candidates + for (DfgVertex& vtx : m_dfg.opVertices()) { + // We only vectorize single use sub-expressions at the moment, to avoid lot of packing + if (DfgVertex* const sinkp = vtx.singleSink()) { + // Collect DfgSel vertices + if (DfgSel* const selp = vtx.cast()) { + selects[selp->fromp()].push_back(selp); + } + } + } + + // Construct initial packs + for (auto& pair : selects) { + std::vector& vec = pair.second; + if (vec.size() <= 1) continue; + std::stable_sort(vec.begin(), vec.end(), [](const DfgSel* ap, const DfgSel* bp) { // + return ap->lsb() < bp->lsb(); + }); + + DfgSel* prevp = vec[0]; + for (size_t i = 1; i < vec.size(); ++i) { + DfgSel* const currp = vec[i]; + State& prevState = state(prevp); + // If consecutive, and not yet part of a pack, then pack these together + if (!prevState.hip) { + if (currp->lsb() == prevp->msb() + 1) { + State& currState = state(currp); + if (!currState.lop) { + prevState.hip = currp; + currState.lop = prevp; + // If the previous vertex is the rightmost, add to the pack list + if (!prevState.lop) m_packs.push_back(prevp); + } + } + } + prevp = currp; + } + } + } + + bool pathExists(const DfgVertex* ap, const DfgVertex* bp) { + if (ap == bp) return true; + State& sa = state(const_cast(ap)); + State& sb = state(const_cast(bp)); + if (sa.rank == sb.rank) return false; + if (sa.rank > sb.rank) std::swap(ap, bp); + return ap->findSink([&](const DfgVertex& sink) { // + return pathExists(&sink, bp); + }); + } + + bool isPackStart(DfgVertex* vtxp) { + State& n = state(vtxp); + return n.hip && !n.lop; + } + + bool isPackMember(DfgVertex* vtxp) { + State& n = state(vtxp); + return n.hip || n.lop; + } + + void extendPacksThroughSinks() { + const size_t size = m_packs.size(); + // Just use the packs vector as the work list + std::vector workList = std::move(m_packs); + m_packs.reserve(workList.size()); + // Extend packs through sinks + while (!workList.empty()) { + // Pick up current work item + DfgVertex* const rightp = workList.back(); + workList.pop_back(); + + UASSERT_OBJ(isPackStart(rightp), rightp, "Not a pack start"); + + // Add pack to pack list + m_packs.push_back(rightp); + + // Process whole pack from right to left + for (DfgVertex *currp = rightp, *nextp; currp; currp = nextp) { + nextp = state(currp).hip; + if (!nextp) break; // End of pack + + DfgVertex* const sinkCurrp = currp->singleSink(); + if (!sinkCurrp) continue; + if (!canVectorize(sinkCurrp)) continue; + if (!sinkCurrp->singleSink()) continue; + + DfgVertex* const sinkNextp = nextp->singleSink(); + if (!sinkNextp) continue; + if (sinkNextp->type() != sinkCurrp->type()) continue; + if (!sinkNextp->singleSink()) continue; + if (isPackMember(sinkNextp)) continue; + + if (sinkNextp == sinkCurrp) continue; + + if (pathExists(sinkNextp, sinkCurrp)) continue; + + size_t indexCurr = 0; + sinkCurrp->findSourceEdge([&](const DfgEdge& edge, size_t index) { + indexCurr = index + 1; + return edge.sourcep() == currp; + }); + UASSERT_OBJ(indexCurr, currp, "Not an operand of it's sink?"); + + size_t indexNext = 0; + sinkNextp->findSourceEdge([&](const DfgEdge& edge, size_t index) { + indexNext = index + 1; + return edge.sourcep() == nextp; + }); + UASSERT_OBJ(indexNext, currp, "Not an operand of it's sink?"); + + if (indexCurr != indexNext) continue; + + State& sinkCurrState = state(sinkCurrp); + if (sinkCurrState.hip) continue; + + State& sinkNextState = state(sinkNextp); + if (sinkNextState.lop) continue; + + sinkCurrState.hip = sinkNextp; + sinkNextState.lop = sinkCurrp; + + if (!sinkCurrState.lop) workList.push_back(sinkCurrp); + } + } + } + + size_t packWidth(DfgVertex* vtxp) { + UASSERT_OBJ(isPackStart(vtxp), vtxp, "'vtxp' is not start of pack"); + uint32_t width = 0; + for (DfgVertex *currp = vtxp, *nextp; currp; currp = nextp) { + nextp = state(currp).hip; + width += currp->width(); + } + return width; + } + + std::pair vectorized(DfgVertex* vtxp) { + UASSERT_OBJ(isPackMember(vtxp), vtxp, "'vtxp' is not a member of a pack"); + auto emplaced = m_vtx2vec.emplace(std::piecewise_construct, // + std::forward_as_tuple(vtxp), // + std::forward_as_tuple(nullptr, 0)); + auto& pair = emplaced.first->second; + if (emplaced.second) { + // Rewind to head of pack and compute LSB as we go + uint32_t lsb = 0; + DfgVertex* headp = vtxp; + while (DfgVertex* const prevp = state(headp).lop) { + lsb += prevp->width(); + headp = prevp; + } + pair.second = lsb; + + if (lsb != 0) { + pair.first = vectorized(headp).first; + } else { + AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(packWidth(headp)); + if (headp->is()) { + pair.first = new DfgSel{m_dfg, headp->fileline(), dtypep}; + } else if (headp->is()) { + pair.first = new DfgAnd{m_dfg, headp->fileline(), dtypep}; + } else if (headp->is()) { + pair.first = new DfgOr{m_dfg, headp->fileline(), dtypep}; + } else if (headp->is()) { + pair.first = new DfgXor{m_dfg, headp->fileline(), dtypep}; + } else if (headp->is()) { + pair.first = new DfgNot{m_dfg, headp->fileline(), dtypep}; + } else { // LCOV_EXCL_START + headp->v3fatalSrc("Non-vectorizable DfgVertex: " << headp->type().ascii()); + } + } + } + return pair; + } + + template + DfgVertex* getInputPack(DfgVertexWithArity* vtxp) { + UASSERT_OBJ(isPackStart(vtxp), vtxp, "'vtxp' is not start of pack"); + std::vector terms; + uint32_t width = packWidth(vtxp); + const auto addTerm = [&](DfgVertex* termp) { + terms.push_back(termp); + width -= termp->width(); + }; + + do { + DfgVertex* const srcp = vtxp->template source(); + FileLine* const flp = srcp->fileline(); + uint32_t termWidth; + if (isPackStart(srcp)) { + const auto pair = vectorized(srcp); + DfgVertex* const vecp = pair.first; + UASSERT_OBJ(!pair.second, srcp, "Pack starting vertex should have offset 0"); + if (vecp->width() > width) { + termWidth = width; + DfgSel* const selp = new DfgSel{m_dfg, flp, DfgVertex::dtypeForWidth(width)}; + selp->fromp(vecp); + selp->lsb(0); + addTerm(selp); + } else { + termWidth = vecp->width(); + addTerm(vecp); + } + } else if (isPackMember(srcp)) { + const auto pair = vectorized(srcp); + DfgVertex* const vecp = pair.first; + UASSERT_OBJ(pair.second, srcp, "Mid pack vertex should have offset > 0"); + const uint32_t lsb = pair.second; + termWidth = std::min(vecp->width() - lsb, width); + DfgSel* const selp = new DfgSel{m_dfg, flp, DfgVertex::dtypeForWidth(termWidth)}; + selp->fromp(vecp); + selp->lsb(lsb); + addTerm(selp); + } else { + termWidth = srcp->width(); + addTerm(srcp); + } + // Skip over the pack members covered by this term + do { + UASSERT_OBJ(termWidth >= vtxp->width(), vtxp, "Should be at least same size"); + termWidth -= vtxp->width(); + vtxp = reinterpret_cast*>(state(vtxp).hip); + } while (termWidth); + } while (width); + + // Concatenate all the terms + DfgVertex* resultp = terms.back(); + terms.pop_back(); + while (!terms.empty()) { + DfgVertex* const termp = terms.back(); + terms.pop_back(); + const uint32_t partialWidth = resultp->width() + termp->width(); + AstNodeDType* const dtypep = DfgVertex::dtypeForWidth(partialWidth); + DfgConcat* const catp = new DfgConcat{m_dfg, termp->fileline(), dtypep}; + catp->rhsp(termp); + catp->lhsp(resultp); + resultp = catp; + } + + // Done + return resultp; + } + + void convertPacks() { + m_vtx2vec.reserve(m_dfg.size()); + + // Create and connect the vectorized vertices + for (DfgVertex* const rightp : m_packs) { + UASSERT_OBJ(isPackStart(rightp), rightp, "Not a pack start"); + if (DfgSel* const vtxp = rightp->cast()) { + DfgSel* const vecp = vectorized(vtxp).first->as(); + vecp->fromp(vtxp->fromp()); + vecp->lsb(vtxp->lsb()); + } else if (DfgVertexUnary* const vtxp = rightp->cast()) { + DfgVertexUnary* const vecp = vectorized(vtxp).first->as(); + vecp->srcp(getInputPack<0, 1>(vtxp)); + } else if (DfgVertexBinary* const vtxp = rightp->cast()) { + DfgVertexBinary* const vecp = vectorized(vtxp).first->as(); + vecp->lhsp(getInputPack<0, 2>(vtxp)); + vecp->rhsp(getInputPack<1, 2>(vtxp)); + } else { // LCOV_EXCL_START + rightp->v3fatalSrc("Non-vectorizable vertex: " << rightp->type().ascii()); + } // LCOV_EXCL_STOP + } + + dumpDot("combined"); + + // Now add the necessary unpacking + for (DfgVertex* const rightp : m_packs) { + // Grab the vectorized vertex + DfgVertex* const vecp = vectorized(rightp).first; + // Extract and substitute each sink from the vector + uint32_t lsb = 0; + for (DfgVertex *currp = rightp, *nextp; currp; currp = nextp) { + nextp = state(currp).hip; + DfgSel* unpackp = nullptr; + currp->forEachSinkEdge([&](DfgEdge& edge) { + // Pack inputs are converted above + if (isPackMember(edge.sinkp())) return; + + if (!unpackp) { + unpackp = new DfgSel{m_dfg, currp->fileline(), currp->dtypep()}; + unpackp->fromp(vecp); + unpackp->lsb(lsb); + } + edge.relinkSource(unpackp); + }); + lsb += currp->width(); + } + } + + dumpDot("unpacked"); + + // Finally delete the vectorized vertices + for (DfgVertex* const vtxp : m_packs) { + for (DfgVertex *currp = vtxp, *nextp; currp; currp = nextp) { + nextp = state(currp).hip; + currp->unlinkDelete(m_dfg); + } + } + m_packs.clear(); + } + + void dumpDot(const string& label) { + string fileName = m_dfg.name(); + if (!label.empty()) fileName += "-" + label; + fileName = v3Global.debugFilename(fileName) + ".dot"; + const std::unique_ptr osp{V3File::new_ofstream(fileName)}; + std::ofstream& os = *osp; + if (os.fail()) v3fatal("Cannot write to file: " << fileName); + + // Header + os << "digraph dfg {" << endl; + os << "graph [label=\"" << m_dfg.name(); + if (!label.empty()) os << "-" << label; + os << "\", labelloc=t, labeljust=l]" << endl; + os << "graph [rankdir=LR]" << endl; + + // Emit all packs as clusters + size_t i = 0; + for (DfgVertex* const rightp : m_packs) { + os << "subgraph cluster_" << i++ << " {" << endl; + os << "label=\"" << i << "\"" << endl; + os << "color=black" << endl; + for (DfgVertex* currp = rightp; currp; currp = state(currp).hip) { + os << currp->toDotId() << endl; + } + os << "}" << endl; + } + + // Emit all vertices + m_dfg.forEachVertex([&](const DfgVertex& vtx) { vtx.dumpDotVertexAndSourceEdges(os); }); + + // Footer + os << "}" << endl; + + os.close(); + } + + // CONSTRUCTOR + V3DfgVectorize(DfgGraph& dfg) + : m_dfg{dfg} { + // Stores pointer to State instance + const auto userDataInUse = dfg.userDataInUse(); + + constructRoots(); + if (m_packs.empty()) return; + dumpDot("roots"); + + extendPacksThroughSinks(); + dumpDot("extended"); + + convertPacks(); + dumpDot("converted"); + } + +public: + static void apply(DfgGraph& dfg) { V3DfgVectorize{dfg}; } +}; + +void V3DfgPasses::vectorize(DfgGraph& dfg) { V3DfgVectorize::apply(dfg); } diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index d33eb76d980..b1bb134bd14 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -163,6 +163,7 @@ class DfgSel final : public DfgVertexUnary { void fromp(DfgVertex* vtxp) { relinkSource<0>(vtxp); } uint32_t lsb() const { return m_lsb; } void lsb(uint32_t value) { m_lsb = value; } + uint32_t msb() const { return m_lsb + width() - 1; } const string srcName(size_t) const override { return "fromp"; } }; diff --git a/test_regress/t/t_dfg_vectorize.pl b/test_regress/t/t_dfg_vectorize.pl new file mode 100755 index 00000000000..45c4e033496 --- /dev/null +++ b/test_regress/t/t_dfg_vectorize.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 2024 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +compile( + verilator_flags2 => ["--stats", "-fno-const-before-dfg"], + ); + +#file_grep($Self->{stats}, qr/Optimizations, MergeCond merges\s+(\d+)/i, 9); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_vectorize.v b/test_regress/t/t_dfg_vectorize.v new file mode 100644 index 00000000000..733e877882a --- /dev/null +++ b/test_regress/t/t_dfg_vectorize.v @@ -0,0 +1,73 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module complete( + input wire [7:0] a, + input wire [7:0] b, + input wire [7:0] c, + input wire [7:0] d, + output wire [7:0] o +); + assign o[3] = a[3] | b[3] & ~c[3] ^ d[3]; + assign o[4] = a[4] | b[4] & ~c[4] ^ d[4]; + assign o[5] = a[5] | b[5] & ~c[5] ^ d[5]; + assign o[0] = a[0] | b[0] & ~c[0] ^ d[0]; + assign o[1] = a[1] | b[1] & ~c[1] ^ d[1]; + assign o[2] = a[2] | b[2] & ~c[2] ^ d[2]; + assign o[7] = a[7] | b[7] & ~c[7] ^ d[7]; + assign o[6] = a[6] | b[6] & ~c[6] ^ d[6]; +endmodule; + +module partial( + input wire [7:0] a, + input wire [7:0] b, + input wire [7:0] c, + input wire [7:0] d, + output wire [7:0] o +); + assign o[3] = 1'b0; + assign o[4] = a[4] | b[4] & ~c[4] ^ d[4]; + assign o[5] = a[5] | b[5] & ~c[5] ^ d[5]; + assign o[0] = a[0] | b[0] & ~c[0] ^ d[0]; + assign o[1] = a[1] | b[1] & ~c[1] ^ d[1]; + assign o[2] = a[2] | b[2] & ~c[2] ^ d[2]; + assign o[7] = a[7] | b[7] & ~c[7] ^ d[7]; + assign o[6] = a[6] | b[6] & ~c[6] ^ d[6]; +endmodule; + +module pack_in( + input wire [7:0] a, + input wire [7:0] b, + input wire [7:0] c, + input wire [7:0] d, + output wire [7:0] o +); + assign o[3] = 1'b0; + assign o[4] = a[4] | b[4] & ~c[4] ^ d[4]; + assign o[5] = a[5] | b[5] & ~c[5] ^ d[4]; + assign o[0] = a[0] | b[0] & ~c[0] ^ d[4]; + assign o[1] = a[1] | b[1] & ~c[1] ^ d[4]; + assign o[2] = a[2] | b[2] & ~c[2] ^ d[4]; + assign o[7] = a[7] | b[7] & ~c[7] ^ d[4]; + assign o[6] = a[6] | b[6] & ~c[6] ^ d[4]; +endmodule; + + +module t( + input wire [7:0] a, + input wire [7:0] b, + input wire [7:0] c, + input wire [7:0] d, + output wire [7:0] o_complete, + output wire [7:0] o_partial, + output wire [7:0] o_pack_in +); + + complete u_complete (.o(o_complete), .*); + partial u_partial (.o(o_partial), .*); + pack_in u_pack_in (.o(o_pack_in), .*); + +endmodule