Files
verilator/src/V3Dfg.cpp
T
Geza Lore ccb4ab9fe3 Optimize Dfg vertex input edge storage (#8313)
Store input edges of fixed arity vertices inline in the vertex class.
This reduces heap allocations, memory fragmentation, and pointer chasing
and speeds up Dfg passes. The extra branch introduced in inputEdgep() is
well predictable and profiling shows branchless alternatives are a loss.
2026-09-18 18:19:53 +01:00

1037 lines
38 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Dfg.h"
#include "V3Ast.h"
#include "V3EmitV.h"
#include "V3File.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//------------------------------------------------------------------------------
// DfgGraph
DfgGraph::DfgGraph(const string& name)
: m_name{name} {}
DfgGraph::~DfgGraph() {
forEachVertex([&](DfgVertex& vtx) { vtx.unlinkDelete(*this); });
}
void DfgGraph::mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) {
if (otherps.empty()) return;
// NODE STATE
// AstVarScope::user2p() -> corresponding DfgVertexVar* in 'this' graph
const VNUser2InUse user2InUse;
// Set up Ast Variable -> DfgVertexVar map for 'this' graph
for (DfgVertexVar& vtx : m_varVertices) vtx.vscp()->user2p(&vtx);
// Merge in each of the other graphs
for (const std::unique_ptr<DfgGraph>& otherp : otherps) {
// Process variables
for (DfgVertexVar* const vtxp : otherp->m_varVertices.unlinkable()) {
// Variabels that are present in 'this', make them use the DfgVertexVar in 'this'.
if (DfgVertexVar* const altp = vtxp->vscp()->user2u().to<DfgVertexVar*>()) {
DfgVertex* const srcp = vtxp->srcp();
DfgVertex* const defaultp = vtxp->defaultp();
UASSERT_OBJ(!(srcp || defaultp) || (!altp->srcp() && !altp->defaultp()), vtxp,
"At most one alias should be driven");
vtxp->replaceWith(altp);
if (srcp) altp->srcp(srcp);
if (defaultp) altp->defaultp(defaultp);
VL_DO_DANGLING(vtxp->unlinkDelete(*otherp), vtxp);
continue;
}
// Otherwise they will be moved
vtxp->vscp()->user2p(vtxp);
vtxp->m_userGeneration = 0;
#ifdef VL_DEBUG
vtxp->m_dfgp = this;
#endif
}
m_varVertices.splice(m_varVertices.end(), otherp->m_varVertices);
// Process Ast references
for (DfgVertexAst* const vtxp : otherp->m_astVertices.unlinkable()) {
vtxp->m_userGeneration = 0;
#ifdef VL_DEBUG
vtxp->m_dfgp = this;
#endif
}
m_astVertices.splice(m_astVertices.end(), otherp->m_astVertices);
// Process constants
for (DfgConst& vtx : otherp->m_constVertices) {
vtx.m_userGeneration = 0;
#ifdef VL_DEBUG
vtx.m_dfgp = this;
#endif
}
m_constVertices.splice(m_constVertices.end(), otherp->m_constVertices);
// Process operations
for (DfgVertex& vtx : otherp->m_opVertices) {
vtx.m_userGeneration = 0;
#ifdef VL_DEBUG
vtx.m_dfgp = this;
#endif
}
m_opVertices.splice(m_opVertices.end(), otherp->m_opVertices);
// Update graph sizes
m_size += otherp->m_size;
otherp->m_size = 0;
}
}
std::string DfgGraph::makeUniqueName(const std::string& prefix, size_t n) {
// Construct the tmpNameStub if we have not done so yet
if (m_tmpNameStub.empty()) {
// Use the hash of the graph name (avoid long names and non-identifiers)
const std::string hash = V3Hash{m_name}.toString();
// We need to keep every variable globally unique, and graph hashed
// names might not be, so keep a static table to track multiplicity
static std::unordered_map<std::string, uint32_t> s_multiplicity;
m_tmpNameStub += '_' + hash + '_' + std::to_string(s_multiplicity[hash]++) + '_';
}
// Assemble the globally unique name
return "__Vdfg" + prefix + m_tmpNameStub + std::to_string(n);
}
DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name,
const DfgDataType& dtype, AstScope* scopep) {
// Create AstVar
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtype.astDtypep()};
// Add AstVar to the scope's module
scopep->modp()->addStmtsp(varp);
// Create AstVarScope
AstVarScope* const vscp = new AstVarScope{flp, scopep, varp};
// Add to scope
scopep->addVarsp(vscp);
// Create and return the corresponding variable vertex
if (dtype.isArray()) return new DfgVarArray{*this, vscp};
return new DfgVarPacked{*this, vscp};
}
static const std::string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; }
// Dump one DfgVertex in Graphviz format
static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
if (const DfgVertexVar* const varVtxp = vtx.cast<DfgVertexVar>()) {
const AstVarScope* const vscp = varVtxp->vscp();
os << toDotId(vtx);
// Begin attributes
os << " [";
// Begin 'label'
os << "label=\"";
// Name
os << vscp->prettyName();
// Address
os << '\n' << cvtToHex(varVtxp);
// Original variable, if any
if (const AstVarScope* const tmpForp = varVtxp->tmpForp()) {
if (tmpForp != vscp) os << "\ntemporary for: " << tmpForp->prettyName();
}
// Type and fanout
os << '\n';
varVtxp->dtype().astDtypep()->dumpSmall(os);
os << " / F" << varVtxp->fanout();
// Reference flags
os << " / ";
static const char* const rwmn[2][2] = {{"_", "W"}, {"R", "M"}};
os << rwmn[varVtxp->hasExtRdRefs()][varVtxp->hasExtWrRefs()];
os << rwmn[false][varVtxp->hasModWrRefs()];
os << (varVtxp->hasDfgRefs() ? "D" : "_");
// End 'label'
os << '"';
// Shape
if (varVtxp->is<DfgVarPacked>()) {
os << ", shape=box";
} else if (varVtxp->is<DfgVarArray>()) {
os << ", shape=box3d";
} else {
varVtxp->v3fatalSrc("Unhandled DfgVertexVar sub-type");
}
// Color
const char* const colorp = varVtxp->hasExtRefs() ? "firebrick2" // Red
: varVtxp->hasModWrRefs() ? "darkorange1" // Orange
: varVtxp->hasDfgRefs() ? "gold2" // Yellow
: varVtxp->tmpForp() ? "gray95" // Gray
: "white";
os << ", style=filled";
os << ", fillcolor=\"" << colorp << "\"";
// End attributes
os << "]\n";
return;
}
if (const DfgPrev* const prevVtxp = vtx.cast<DfgPrev>()) {
const AstVarScope* const vscp = prevVtxp->vscp();
os << toDotId(vtx);
// Begin attributes
os << " [";
// Begin 'label'
os << "label=\"";
// Name
os << vscp->prettyName();
// Address
os << '\n' << cvtToHex(prevVtxp);
// Type and fanout
os << '\n';
prevVtxp->dtype().astDtypep()->dumpSmall(os);
os << " / F" << prevVtxp->fanout();
// End 'label'
os << '"';
// Shape
if (prevVtxp->isPacked()) {
os << ", shape=box";
} else if (prevVtxp->isArray()) {
os << ", shape=box3d";
} else {
prevVtxp->v3fatalSrc("Unhandled variable type");
}
// Color
const char* const colorp = "mediumorchid1"; // Purple
os << ", style=filled";
os << ", fillcolor=\"" << colorp << "\"";
// End attributes
os << "]\n";
return;
}
if (const DfgConst* const constVtxp = vtx.cast<DfgConst>()) {
const V3Number& num = constVtxp->num();
os << toDotId(vtx);
os << " [label=\"";
if (num.width() <= 32 && !num.isSigned()) {
os << constVtxp->width() << "'d" << num.toUInt() << '\n';
os << constVtxp->width() << "'h" << std::hex << num.toUInt() << std::dec << '\n';
} else {
os << num.ascii() << '\n';
}
os << cvtToHex(constVtxp) << '\n';
os << '"';
os << ", shape=plain";
os << "]\n";
return;
}
if (const DfgSel* const selVtxp = vtx.cast<DfgSel>()) {
const uint32_t lsb = selVtxp->lsb();
const uint32_t msb = lsb + selVtxp->width() - 1;
os << toDotId(vtx);
os << " [label=\"SEL _[" << msb << ":" << lsb << "]\n";
os << cvtToHex(selVtxp) << '\n';
vtx.dtype().astDtypep()->dumpSmall(os);
os << " / F" << vtx.fanout() << '"';
if (vtx.hasMultipleSinks()) {
os << ", shape=doublecircle";
} else {
os << ", shape=circle";
}
os << "]\n";
return;
}
if (vtx.is<DfgVertexSplice>() || vtx.is<DfgUnitArray>() || vtx.is<DfgUnresolved>()) {
os << toDotId(vtx);
os << " [label=\"" << vtx.typeName() << '\n';
os << cvtToHex(&vtx) << '\n';
vtx.dtype().astDtypep()->dumpSmall(os);
os << " / F" << vtx.fanout() << '"';
if (vtx.hasMultipleSinks()) {
os << ", shape=doubleoctagon";
} else {
os << ", shape=octagon";
}
os << "]\n";
return;
}
if (const DfgLogic* const logicp = vtx.cast<DfgLogic>()) {
os << toDotId(vtx);
std::stringstream ss;
V3EmitV::debugVerilogForTree(logicp->nodep(), ss);
std::string str = "AstNode: " + cvtToHex(logicp->nodep()) + "\n" + ss.str();
str = VString::quoteBackslash(str);
str = VString::quoteAny(str, '"', '\\');
str = VString::replaceSubstr(str, "\n", "\\l");
const char* const colorp = !logicp->selectedForSynthesis() ? "#d0d0ff" // Pale Blue
: logicp->nonSynthesizable() ? "#ffd0d0" // Pale Red
: logicp->reverted() ? "#faffd0" // Pale Yellow
: "#d0ffd0"; // Pale Green
os << " [label=\"";
os << str;
os << '\n' << vtx.typeName() << '\n' << cvtToHex(&vtx);
os << "\"\n";
os << ", shape=box, style=\"rounded,filled\", nojustify=true";
os << ", fillcolor=\"" << colorp << "\"";
os << "]\n";
return;
}
if (const DfgVertexAst* const astVtxp = vtx.cast<DfgVertexAst>()) {
os << toDotId(vtx);
std::stringstream ss;
V3EmitV::debugVerilogForTree(astVtxp->exprp(), ss);
std::string str = "AstNode: " + cvtToHex(astVtxp->exprp()) + "\n" + ss.str() + "\n";
str = VString::quoteBackslash(str);
str = VString::quoteAny(str, '"', '\\');
str = VString::replaceSubstr(str, "\n", "\\l");
const DfgAstRd* const astRdVtxp = astVtxp->cast<DfgAstRd>();
const char* const colorp = astRdVtxp ? "#80ff80" // Green
: "#ffff80"; // Yellow
os << " [label=\"";
os << str;
os << '\n' << vtx.typeName() << '\n' << cvtToHex(&vtx) << '\n';
vtx.dtype().astDtypep()->dumpSmall(os);
os << " / ";
os << "_S"[astRdVtxp->inSenItem()];
os << "_L"[astRdVtxp->inLoop()];
os << "\"\n";
os << ", shape=box, style=\"rounded,filled\", nojustify=true";
os << ", fillcolor=\"" << colorp << "\"";
os << "]\n";
return;
}
os << toDotId(vtx);
os << " [label=\"" << vtx.typeName() << '\n';
os << cvtToHex(&vtx) << '\n';
vtx.dtype().astDtypep()->dumpSmall(os);
os << " / F" << vtx.fanout() << '"';
if (vtx.hasMultipleSinks()) {
os << ", shape=doublecircle";
} else {
os << ", shape=circle";
}
os << "]\n";
}
void DfgGraph::dumpDot(std::ostream& os, const std::string& label,
std::function<bool(const DfgVertex&)> p) const {
// This generates a graphviz dump, https://www.graphviz.org
// Header
os << "digraph dfg {\n";
os << "rankdir=LR\n";
// If predicate not given, dump everything
if (!p) p = [](const DfgVertex&) { return true; };
std::unordered_set<const DfgVertex*> emitted;
// Emit all vertices associated with a DfgLogic
forEachVertex([&](const DfgVertex& vtx) {
const DfgLogic* const logicp = vtx.cast<DfgLogic>();
if (!logicp) return;
if (logicp->synth().empty()) return;
if (!p(vtx)) return;
os << "subgraph cluster_" << cvtToHex(logicp) << " {\n";
dumpDotVertex(os, *logicp);
emitted.insert(logicp);
for (DfgVertex* const vtxp : logicp->synth()) {
if (!p(*vtxp)) continue;
dumpDotVertex(os, *vtxp);
emitted.insert(vtxp);
}
os << "}\n";
});
// Emit all remaining vertices
forEachVertex([&](const DfgVertex& vtx) {
if (emitted.count(&vtx)) return;
if (!p(vtx)) return;
dumpDotVertex(os, vtx);
});
// Emit all edges
forEachVertex([&](const DfgVertex& vtx) {
if (!p(vtx)) return;
for (size_t i = 0; i < vtx.nInputs(); ++i) {
DfgVertex* const srcp = vtx.inputp(i);
if (!srcp) continue;
if (!p(*srcp)) continue;
os << toDotId(*srcp) << " -> " << toDotId(vtx);
os << " [headlabel=\"" << vtx.srcName(i) << "\"]";
os << '\n';
}
});
// Footer
os << "label=\"" << name() + (label.empty() ? "" : "-" + label) << "\"\n";
os << "labelloc=t\n";
os << "labeljust=l\n";
os << "}\n";
}
std::string DfgGraph::dumpDotString(const std::string& label,
std::function<bool(const DfgVertex&)> p) const {
std::stringstream ss;
dumpDot(ss, label, p);
return ss.str();
}
void DfgGraph::dumpDotFile(const std::string& filename, const std::string& label,
std::function<bool(const DfgVertex&)> p) const {
const std::unique_ptr<std::ofstream> os{V3File::new_ofstream(filename)};
if (os->fail()) v3fatal("Can't write file: " << filename);
dumpDot(*os.get(), label, p);
os->close();
}
void DfgGraph::dumpDotFilePrefixed(const std::string& label,
std::function<bool(const DfgVertex&)> p) const {
std::string filename = name();
if (!label.empty()) filename += "-" + label;
dumpDotFile(v3Global.debugFilename(filename) + ".dot", label, p);
}
template <bool T_SinksNotSources>
static std::unique_ptr<std::unordered_set<const DfgVertex*>>
dfgGraphCollectCone(const std::vector<const DfgVertex*>& vtxps) {
// Work queue for traversal starting from all the seed vertices
std::vector<const DfgVertex*> queue = vtxps;
// Set of already visited vertices
std::unique_ptr<std::unordered_set<const DfgVertex*>> resp{
new std::unordered_set<const DfgVertex*>{}};
// Depth first traversal
while (!queue.empty()) {
// Pop next work item
const DfgVertex* const vtxp = queue.back();
queue.pop_back();
// Mark vertex as visited, move on if already visited
if (!resp->insert(vtxp).second) continue;
// Enqueue all siblings of this vertex.
if VL_CONSTEXPR_CXX17 (T_SinksNotSources) {
vtxp->foreachSink([&](const DfgVertex& sink) {
queue.push_back(&sink);
return false;
});
} else {
vtxp->foreachSource([&](const DfgVertex& src) {
queue.push_back(&src);
return false;
});
}
}
// Done
return resp;
}
std::unique_ptr<std::unordered_set<const DfgVertex*>>
DfgGraph::sourceCone(const std::vector<const DfgVertex*>& vtxps) const {
return dfgGraphCollectCone<false>(vtxps);
}
std::unique_ptr<std::unordered_set<const DfgVertex*>>
DfgGraph::sinkCone(const std::vector<const DfgVertex*>& vtxps) const {
return dfgGraphCollectCone<true>(vtxps);
}
std::unique_ptr<std::unordered_set<const DfgVertex*>>
DfgGraph::neighborhood(const std::vector<const DfgVertex*>& vtxps, size_t n) const {
// Neighborhood
std::vector<const DfgVertex*> vec = vtxps;
// Set of already visited vertices
std::unordered_set<const DfgVertex*> res{vec.begin(), vec.end()};
// Expand neihborhood by 'n' hops
size_t begin = 0;
size_t end = vec.size();
for (size_t hops = 1; hops <= n; ++hops) {
for (size_t i = begin; i < end; ++i) {
const DfgVertex* const vtxp = vec[i];
vtxp->foreachSink([&](const DfgVertex& dst) {
if (res.insert(&dst).second) vec.push_back(&dst);
return false;
});
vtxp->foreachSource([&](const DfgVertex& src) {
if (res.insert(&src).second) vec.push_back(&src);
return false;
});
}
begin = end;
end = vec.size();
}
// Move out the results
return std::make_unique<std::unordered_set<const DfgVertex*>>(std::move(res));
}
//------------------------------------------------------------------------------
// DfgVertex
DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataType& dt,
DfgEdge* inlineInputsp, uint32_t nInputs)
: m_inlineInputsp{inlineInputsp}
, m_nInputs{nInputs}
, m_filelinep{flp}
, m_dtype{dt}
, m_type{type} {
dfg.addVertex(*this);
}
bool DfgVertex::unsafe() const {
if (is<DfgMux>()) return true;
if (is<DfgArraySel>()) return !as<DfgArraySel>()->bitp()->is<DfgConst>();
return false;
}
void DfgVertex::typeCheck(const DfgGraph& dfg) const {
#define CHECK(cond, msg) \
UASSERT_OBJ(cond, this, \
"Dfg type error for vertex " << typeName() << " in " << dfg.name() << ": " \
<< msg);
switch (type()) {
case VDfgType::Const: {
CHECK(isPacked(), "Should be Packed type");
return;
}
case VDfgType::CReset: {
CHECK(isPacked() || isArray(), "Should be Packed or Array type");
return;
}
case VDfgType::AstRd: {
const DfgAstRd& v = *as<DfgAstRd>();
CHECK(v.isPacked() || v.isArray(), "Should be Packed or Array type");
return;
}
case VDfgType::VarArray:
case VDfgType::VarPacked: {
const DfgVertexVar& v = *as<DfgVertexVar>();
CHECK(!v.defaultp() || v.defaultp()->dtype() == v.dtype(), "'defaultp' should match");
CHECK(!v.srcp() || v.srcp()->dtype() == v.dtype(), "'srcp' should match");
return;
}
case VDfgType::Prev: {
CHECK(isPacked() || isArray(), "Should be Packed or Array type");
return;
}
case VDfgType::SpliceArray:
case VDfgType::SplicePacked: {
const DfgVertexSplice& v = *as<DfgVertexSplice>();
v.foreachDriver([&](const DfgVertex& src, uint32_t lo) {
CHECK(src.dtype() == DfgDataType::select(v.dtype(), lo, src.size()), "driver");
return false;
});
return;
}
case VDfgType::Logic: {
CHECK(dtype().isNull(), "Should be Null type");
return;
}
case VDfgType::Unresolved: {
CHECK(!dtype().isNull(), "Should not be Null type");
return;
}
case VDfgType::UnitArray: {
const DfgUnitArray& v = *as<DfgUnitArray>();
CHECK(v.isArray(), "Should be Array type");
CHECK(v.size() == 1, "Should be one element");
CHECK(v.srcp()->dtype() == v.dtype().elemDtype(), "Input should be the element type");
return;
}
case VDfgType::Sel: {
const DfgSel& v = *as<DfgSel>();
CHECK(v.isPacked(), "Should be Packed type");
CHECK(v.dtype() == DfgDataType::select(v.srcp()->dtype(), v.lsb(), v.size()), "sel");
return;
}
case VDfgType::MatchMasked: {
const DfgMatchMasked& v = *as<DfgMatchMasked>();
CHECK(v.isPacked(), "Should be Packed type");
CHECK(v.size() == 32U, "Should yield a 32-bit result");
CHECK(v.lhsp()->isPacked(), "Lhs should be packed");
CHECK(v.matchp()->isPacked(), "Match should be Packed type");
CHECK(v.matchp()->is<DfgVertexVar>(), "Match should be a variable");
return;
}
case VDfgType::Mux: {
const DfgMux& v = *as<DfgMux>();
CHECK(v.isPacked(), "Should be Packed type");
CHECK(v.fromp()->isPacked(), "Source operand should be Packed type");
CHECK(v.fromp()->size() >= v.size(), "Source operand should not be narrower");
CHECK(v.lsbp()->isPacked(), "Index should be Packed type");
return;
}
case VDfgType::ArraySel: {
const DfgArraySel& v = *as<DfgArraySel>();
CHECK(v.dtype() == v.fromp()->dtype().elemDtype(), "Element type should match");
CHECK(v.bitp()->isPacked(), "Index should be Packed type");
return;
}
case VDfgType::Add:
case VDfgType::And:
case VDfgType::Div:
case VDfgType::DivS:
case VDfgType::ModDiv:
case VDfgType::ModDivS:
case VDfgType::Mul:
case VDfgType::MulS:
case VDfgType::Or:
case VDfgType::Sub:
case VDfgType::Xor: {
CHECK(isPacked(), "Should be Packed type");
CHECK(inputp(0)->dtype() == dtype(), "LHS should be same type");
CHECK(inputp(1)->dtype() == dtype(), "RHS should be same type");
return;
}
case VDfgType::Negate:
case VDfgType::Not: {
CHECK(isPacked(), "Should be Packed type");
CHECK(inputp(0)->dtype() == dtype(), "Input should be same type");
return;
}
case VDfgType::ShiftL:
case VDfgType::ShiftR:
case VDfgType::ShiftRS: {
CHECK(isPacked(), "Should be Packed type");
CHECK(inputp(0)->dtype() == dtype(), "LHS should be same type");
CHECK(inputp(1)->isPacked(), "RHS should be Packed type");
return;
}
case VDfgType::Concat: {
const DfgConcat& v = *as<DfgConcat>();
CHECK(v.isPacked(), "Should be Packed type");
CHECK(v.lhsp()->isPacked(), "LHS should be Packed type");
CHECK(v.rhsp()->isPacked(), "RHS should be Packed type");
CHECK(v.size() == v.rhsp()->size() + v.lhsp()->size(), "Concat result mismatch");
return;
}
case VDfgType::Rep: {
const DfgRep& v = *as<DfgRep>();
CHECK(v.isPacked(), "Should be Packed type");
CHECK(v.srcp()->isPacked(), "'srcp' should be same type");
CHECK(v.size() % v.srcp()->size() == 0, "Not a replicate");
return;
}
case VDfgType::StreamL:
case VDfgType::StreamR: {
// TODO: model these without an explicit slice size which is always constant (?)
CHECK(isPacked(), "Should be Packed type");
CHECK(inputp(0)->dtype() == dtype(), "LHS should be same type");
CHECK(inputp(1)->isPacked(), "Slice size should be Packed type");
return;
}
case VDfgType::Eq:
case VDfgType::EqCase:
case VDfgType::EqWild:
case VDfgType::Neq:
case VDfgType::NeqCase:
case VDfgType::NeqWild:
case VDfgType::Gt:
case VDfgType::GtS:
case VDfgType::Gte:
case VDfgType::GteS:
case VDfgType::Lt:
case VDfgType::LtS:
case VDfgType::Lte:
case VDfgType::LteS: {
CHECK(dtype() == DfgDataType::packed(1), "Should be 1-bit");
CHECK(inputp(0)->dtype() == inputp(1)->dtype(), "Sides should match");
return;
}
case VDfgType::Extend:
case VDfgType::ExtendS: {
CHECK(isPacked(), "Should be Packed type");
CHECK(inputp(0)->isPacked(), "Operand should be same type");
CHECK(inputp(0)->size() < size(), "Operand should be narrower");
return;
}
case VDfgType::LogAnd:
case VDfgType::LogEq:
case VDfgType::LogIf:
case VDfgType::LogOr: {
CHECK(dtype() == DfgDataType::packed(1), "Should be 1-bit");
CHECK(inputp(0)->isPacked(), "LHS should be Packed type");
CHECK(inputp(1)->isPacked(), "RHS should be Packed type");
return;
}
case VDfgType::LogNot:
case VDfgType::RedAnd:
case VDfgType::RedOr:
case VDfgType::RedXor:
case VDfgType::OneHot:
case VDfgType::OneHot0: {
CHECK(dtype() == DfgDataType::packed(1), "Should be 1-bit");
CHECK(inputp(0)->isPacked(), "Operand should be Packed type");
return;
}
case VDfgType::Cond: {
const DfgCond& v = *as<DfgCond>();
CHECK(v.isPacked(), "Should be Packed type");
CHECK(v.condp()->isPacked(), "Condition should be Packed type");
CHECK(v.thenp()->dtype() == v.dtype(), "Then should be same type");
CHECK(v.elsep()->dtype() == v.dtype(), "Else should be same type");
return;
}
case VDfgType::CountOnes: {
const DfgCountOnes& v = *as<DfgCountOnes>();
CHECK(v.isPacked(), "Should be Packed type");
CHECK(v.srcp()->isPacked(), "Source should be Packed type");
CHECK(v.size() == 32U, "Should yield a 32-bit result");
return;
}
case VDfgType::Pow:
case VDfgType::PowSS:
case VDfgType::PowSU:
case VDfgType::PowUS: {
CHECK(isPacked(), "Should be Packed type");
CHECK(inputp(0)->dtype() == dtype(), "LHS should be same type");
CHECK(inputp(1)->isPacked(), "RHS should be Packed type");
return;
}
}
#undef CHECK
}
uint32_t DfgVertex::fanout() const {
uint32_t result = 0;
foreachSink([&](const DfgVertex&) {
++result;
return false;
});
return result;
}
DfgVertexVar* DfgVertex::getResultVar() {
// It's easy if the vertex is already a variable ...
if (DfgVertexVar* const varp = this->cast<DfgVertexVar>()) return varp;
// Inspect existing variables written by this vertex, and choose one
DfgVertexVar* resp = nullptr;
// cppcheck-has-bug-suppress constParameter
this->foreachSink([&resp](DfgVertex& sink) {
DfgVertexVar* const varp = sink.cast<DfgVertexVar>();
if (!varp) return false;
// Do not use it if value might differ from its drivers
if (varp->isVolatile()) return false;
// First variable found
if (!resp) {
resp = varp;
return false;
}
// Prefer those variables that must be kept anyway
if (resp->hasExtRdRefs() != varp->hasExtRdRefs()) {
if (!resp->hasExtRdRefs()) resp = varp;
return false;
}
if (resp->hasDfgRefs() != varp->hasDfgRefs()) {
if (!resp->hasDfgRefs()) resp = varp;
return false;
}
// Prefer real variables over temporaries
const bool resIsTemp = resp->vscp()->varp()->isTemp();
const bool varIsTemp = varp->vscp()->varp()->isTemp();
if (resIsTemp != varIsTemp) {
if (resIsTemp) resp = varp;
return false;
}
if (!resp->tmpForp() != !varp->tmpForp()) {
if (resp->tmpForp()) resp = varp;
return false;
}
// Prefer the earlier one in source order
const FileLine& oldFlp = *(resp->fileline());
const FileLine& newFlp = *(varp->fileline());
if (const int cmp = oldFlp.operatorCompare(newFlp)) {
if (cmp > 0) resp = varp;
return false;
}
// Prefer the one with the lexically smaller name
if (const int cmp = resp->vscp()->name().compare(varp->vscp()->name())) {
if (cmp > 0) resp = varp;
return false;
}
// 'resp' and 'varp' are all the same, keep using the existing 'resp'
return false;
});
return resp;
}
AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED {
// If this is a variable, we are done
if (const DfgVertexVar* const varp = this->cast<DfgVertexVar>()) {
return varp->vscp()->scopep();
}
// Try the result var first if instructed (usully only in the recursive case)
if (tryResultVar) {
if (const DfgVertexVar* const varp = this->getResultVar()) {
return varp->vscp()->scopep();
}
}
AstScope* const rootp = v3Global.rootp()->topScopep()->scopep();
AstScope* const constPoolp = v3Global.rootp()->constPoolp()->scopep();
// Note: the recursive invocation can cause a re-hash but that will not invalidate references
AstScope*& resultr = cache[this];
if (!resultr) {
// Mark to prevent infinite recursion on circular graphs - should never be called on such
resultr = reinterpret_cast<AstScope*>(1);
// Find scope based on sources, falling back on the root scope,
// also make sure it's not the constant pool scope, which is special.
AstScope* foundp = nullptr;
foreachSource([&](DfgVertex& src) {
AstScope* const scp = src.scopep(cache, true);
if (scp != rootp && scp != constPoolp) {
foundp = scp;
return true;
}
return false;
});
resultr = foundp ? foundp : rootp;
}
// Die on a graph circular through operation vertices
UASSERT_OBJ(resultr != reinterpret_cast<AstScope*>(1), this,
"DfgVertex::scopep called on graph with circular operations");
// Done
return resultr;
}
void DfgVertex::unlinkDelete(DfgGraph& dfg) {
// Unlink sink edges
while (!m_sinks.empty()) m_sinks.frontp()->unlinkSrcp();
// Remove from graph
dfg.removeVertex(*this);
// Delete - this will unlink sources
delete this;
}
//------------------------------------------------------------------------------
// DfgVertexVar
std::pair<DfgVertex*, uint32_t> DfgVertexVar::driverOfRange(uint32_t lo, uint32_t size) {
DfgVertex* const srcp = this->srcp();
// Not driven at all
if (!srcp) return {nullptr, 0};
// If volatile, can have other drivers
if (isVolatile()) return {nullptr, 0};
// Don't inline CReset
if (srcp->is<DfgCReset>()) return {nullptr, 0};
// If not driven via a splice, then it is driven whole, at the same offsets
DfgVertexSplice* const splicep = srcp->cast<DfgVertexSplice>();
if (!splicep) return {srcp, lo};
// Find the driver that covers the whole searched range, if there is a single one
const uint32_t hi = lo + size - 1;
DfgVertex* driverp = nullptr;
uint32_t driverLo = 0;
bool useDefault = defaultp();
splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLo) {
const uint32_t dHi = dLo + src.size() - 1;
// Note whether it overlaps the searched range, so the default cannot be used
if (dLo <= hi && lo <= dHi) useDefault = false;
// If it does not cover the whole searched range, move on
if (lo < dLo || dHi < hi) return false;
// Save the driver that covers the whole searched range
driverp = &src;
driverLo = dLo;
return true;
});
// If a single driver covers the searched range, it is the one
if (driverp) return {driverp, lo - driverLo};
// Otherwise the default driver is responsible for it, if nothing else overlaps it
if (useDefault) return {defaultp(), lo};
// Not driven by a single vertex
return {nullptr, 0};
}
//######################################################################
// Renders the canonical pattern S-expression for a single DfgVertex
class DfgPatternString final {
std::ostream& m_os;
std::map<std::string, std::string> m_internedConsts; // Interned constants
std::map<uint32_t, std::string> m_internedSelLsbs; // Interned lsb value for selects
std::map<uint32_t, std::string> m_internedWordWidths; // Interned widths
std::map<uint32_t, std::string> m_internedWideWidths; // Interned widths
std::map<const DfgVertex*, std::string> m_internedVertices; // Interned vertices
// Multiplicity and depth of vertices
std::map<const DfgVertex*, std::pair<uint32_t, uint32_t>> m_multiplicityAndDepth;
static std::string toLetters(size_t value, bool lowerCase = false) {
const char base = lowerCase ? 'a' : 'A';
std::string s;
do { s += static_cast<char>(base + value % 26); } while (value /= 26);
return s;
}
const std::string& internConst(const DfgConst& vtx) {
const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "");
if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
return pair.first->second;
}
const std::string& internSelLsb(uint32_t value) {
const auto pair = m_internedSelLsbs.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
return pair.first->second;
}
const std::string& internWordWidth(uint32_t value) {
const auto pair = m_internedWordWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
return pair.first->second;
}
const std::string& internWideWidth(uint32_t value) {
const auto pair = m_internedWideWidths.emplace(value, "");
if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
return pair.first->second;
}
const std::string& internVertex(const DfgVertex& vtx) {
const auto pair = m_internedVertices.emplace(&vtx, "");
if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
return pair.first->second;
}
void recordMultiplicityAndDepth(const DfgVertex& vtx, uint32_t depth) {
std::pair<uint32_t, uint32_t>& value = m_multiplicityAndDepth
.emplace(std::piecewise_construct, //
std::forward_as_tuple(&vtx), //
std::forward_as_tuple(0, depth))
.first->second;
value.first += 1;
value.second = std::max(value.second, depth);
if (!depth) return;
vtx.foreachSource([&](const DfgVertex& src) {
recordMultiplicityAndDepth(src, depth - 1);
return false;
});
}
// Render the vertx into ss, and return true if the recursion reached the given depth,
// meaning an S-expression with that nesting level has been rendered.
void render(const DfgVertex& vtx, uint32_t depth, bool isRoot = true) {
if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
// Base case 1: constant
if (constp->isZero()) {
m_os << "(CONST ZERO)";
} else if (constp->isOnes()) {
m_os << "(CONST ONES)";
} else {
m_os << "(CONST #" << internConst(*constp) << ')';
}
} else if (!isRoot && m_multiplicityAndDepth.at(&vtx).first > 1) {
// Base case 2: vertex appearing multiple times
m_os << internVertex(vtx);
} else if (!vtx.foreachSource([&](const DfgVertex&) { return true; })) {
// Base case 3: vertex with no inputs (input variable)
m_os << '(' << vtx.typeName() << ')';
} else if (depth == 0) {
// Base case 4: deep vertex (apperaing only once)
m_os << "_";
} else {
// Recursively print an S-expression for the vertex
m_os << '(';
// Name
m_os << vtx.typeName();
// Specials
if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
m_os << '@';
if (selp->lsb() == 0) {
m_os << '0';
} else {
m_os << internSelLsb(selp->lsb());
}
}
// Operands
vtx.foreachSource([&](const DfgVertex& src) {
m_os << ' ';
render(src, depth - 1, false);
return false;
});
// S-expression end
m_os << ')';
}
// Annotate type
m_os << ':';
if (!vtx.dtype().isPacked()) {
vtx.dtype().astDtypep()->dumpSmall(m_os);
} else {
const uint32_t width = vtx.size();
if (width == 1) {
m_os << '1';
} else if (width <= VL_QUADSIZE) {
m_os << internWordWidth(width);
} else {
m_os << internWideWidth(width);
}
}
// Mark it if it has multiple sinks
if (vtx.hasMultipleSinks()) m_os << '*';
}
public:
DfgPatternString(std::ostream& os, const DfgVertex& vtx, uint32_t depth)
: m_os{os} {
recordMultiplicityAndDepth(vtx, depth);
render(vtx, depth, false);
using Pair = std::pair<std::string, const DfgVertex*>;
std::vector<Pair> vertices;
for (const auto& pair : m_multiplicityAndDepth) {
if (pair.second.first == 1) continue;
vertices.emplace_back(internVertex(*pair.first), pair.first);
}
std::sort(vertices.begin(), vertices.end(), [](const Pair& a, const Pair& b) { //
return a.first < b.first;
});
for (const Pair& pair : vertices) {
m_os << " | " << pair.first << " is ";
render(*pair.second, m_multiplicityAndDepth.at(pair.second).second);
}
}
};
std::string DfgVertex::patternString(uint32_t depth) const {
std::ostringstream oss;
DfgPatternString{oss, *this, depth};
return oss.str();
}
//------------------------------------------------------------------------------
// DfgVisitor
#include "V3Dfg__gen_visitor_defns.h" // From ./astgen