803 lines
29 KiB
C++
803 lines
29 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Copyright 2003-2025 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 "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "V3Dfg.h"
|
|
|
|
#include "V3File.h"
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// DfgGraph
|
|
//------------------------------------------------------------------------------
|
|
|
|
DfgGraph::DfgGraph(AstModule* modulep, const string& name)
|
|
: m_modulep{modulep}
|
|
, m_name{name} {}
|
|
|
|
DfgGraph::~DfgGraph() {
|
|
forEachVertex([](DfgVertex& vtxp) { delete &vtxp; });
|
|
}
|
|
|
|
std::unique_ptr<DfgGraph> DfgGraph::clone() const {
|
|
const bool scoped = !modulep();
|
|
|
|
DfgGraph* const clonep = new DfgGraph{modulep(), name()};
|
|
|
|
// Map from original vertex to clone
|
|
std::unordered_map<const DfgVertex*, DfgVertex*> vtxp2clonep(size() * 2);
|
|
|
|
// Clone constVertices
|
|
for (const DfgConst& vtx : m_constVertices) {
|
|
DfgConst* const cp = new DfgConst{*clonep, vtx.fileline(), vtx.num()};
|
|
vtxp2clonep.emplace(&vtx, cp);
|
|
}
|
|
// Clone variable vertices
|
|
for (const DfgVertexVar& vtx : m_varVertices) {
|
|
const DfgVertexVar* const vp = vtx.as<DfgVertexVar>();
|
|
DfgVertexVar* cp = nullptr;
|
|
|
|
switch (vtx.type()) {
|
|
case VDfgType::atVarArray: {
|
|
if (scoped) {
|
|
cp = new DfgVarArray{*clonep, vp->varScopep()};
|
|
} else {
|
|
cp = new DfgVarArray{*clonep, vp->varp()};
|
|
}
|
|
vtxp2clonep.emplace(&vtx, cp);
|
|
break;
|
|
}
|
|
case VDfgType::atVarPacked: {
|
|
if (scoped) {
|
|
cp = new DfgVarPacked{*clonep, vp->varScopep()};
|
|
} else {
|
|
cp = new DfgVarPacked{*clonep, vp->varp()};
|
|
}
|
|
vtxp2clonep.emplace(&vtx, cp);
|
|
break;
|
|
}
|
|
default: {
|
|
vtx.v3fatalSrc("Unhandled variable vertex type: " + vtx.typeName());
|
|
VL_UNREACHABLE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Clone operation vertices
|
|
for (const DfgVertex& vtx : m_opVertices) {
|
|
switch (vtx.type()) {
|
|
#include "V3Dfg__gen_clone_cases.h" // From ./astgen
|
|
case VDfgType::atSel: {
|
|
DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtypep()};
|
|
cp->lsb(vtx.as<DfgSel>()->lsb());
|
|
vtxp2clonep.emplace(&vtx, cp);
|
|
break;
|
|
}
|
|
case VDfgType::atMux: {
|
|
DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtypep()};
|
|
vtxp2clonep.emplace(&vtx, cp);
|
|
break;
|
|
}
|
|
case VDfgType::atSpliceArray: {
|
|
DfgSpliceArray* const cp = new DfgSpliceArray{*clonep, vtx.fileline(), vtx.dtypep()};
|
|
vtxp2clonep.emplace(&vtx, cp);
|
|
break;
|
|
}
|
|
case VDfgType::atSplicePacked: {
|
|
DfgSplicePacked* const cp = new DfgSplicePacked{*clonep, vtx.fileline(), vtx.dtypep()};
|
|
vtxp2clonep.emplace(&vtx, cp);
|
|
break;
|
|
}
|
|
default: {
|
|
vtx.v3fatalSrc("Unhandled operation vertex type: " + vtx.typeName());
|
|
VL_UNREACHABLE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
UASSERT(size() == clonep->size(), "Size of clone should be the same");
|
|
|
|
// Constants have no inputs
|
|
// Hook up inputs of cloned variables
|
|
for (const DfgVertexVar& vtx : m_varVertices) {
|
|
// All variable vertices are unary
|
|
if (DfgVertex* const srcp = vtx.srcp()) {
|
|
vtxp2clonep.at(&vtx)->as<DfgVertexVar>()->srcp(vtxp2clonep.at(srcp));
|
|
}
|
|
}
|
|
// Hook up inputs of cloned operation vertices
|
|
for (const DfgVertex& vtx : m_opVertices) {
|
|
if (vtx.is<DfgVertexVariadic>()) {
|
|
switch (vtx.type()) {
|
|
case VDfgType::atSpliceArray: {
|
|
const DfgSpliceArray* const vp = vtx.as<DfgSpliceArray>();
|
|
DfgSpliceArray* const cp = vtxp2clonep.at(vp)->as<DfgSpliceArray>();
|
|
vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) {
|
|
if (DfgVertex* const srcp = edge.sourcep()) {
|
|
cp->addDriver(vp->driverFileLine(i), //
|
|
vp->driverIndex(i), //
|
|
vtxp2clonep.at(srcp));
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
case VDfgType::atSplicePacked: {
|
|
const DfgSplicePacked* const vp = vtx.as<DfgSplicePacked>();
|
|
DfgSplicePacked* const cp = vtxp2clonep.at(vp)->as<DfgSplicePacked>();
|
|
vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) {
|
|
if (DfgVertex* const srcp = edge.sourcep()) {
|
|
cp->addDriver(vp->driverFileLine(i), //
|
|
vp->driverLsb(i), //
|
|
vtxp2clonep.at(srcp));
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
default: {
|
|
vtx.v3fatalSrc("Unhandled DfgVertexVariadic sub type: " + vtx.typeName());
|
|
VL_UNREACHABLE;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
DfgVertex* const cp = vtxp2clonep.at(&vtx);
|
|
const auto oSourceEdges = vtx.sourceEdges();
|
|
auto cSourceEdges = cp->sourceEdges();
|
|
UASSERT_OBJ(oSourceEdges.second == cSourceEdges.second, &vtx,
|
|
"Mismatched source count");
|
|
for (size_t i = 0; i < oSourceEdges.second; ++i) {
|
|
if (DfgVertex* const srcp = oSourceEdges.first[i].sourcep()) {
|
|
cSourceEdges.first[i].relinkSource(vtxp2clonep.at(srcp));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::unique_ptr<DfgGraph>{clonep};
|
|
}
|
|
|
|
void DfgGraph::mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) {
|
|
if (otherps.empty()) return;
|
|
|
|
// NODE STATE
|
|
// AstVar/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.nodep()->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->nodep()->user2u().to<DfgVertexVar*>()) {
|
|
DfgVertex* const srcp = vtxp->srcp();
|
|
UASSERT_OBJ(!srcp || !altp->srcp(), vtxp, "At most one alias should be driven");
|
|
vtxp->replaceWith(altp);
|
|
if (srcp) altp->srcp(srcp);
|
|
VL_DO_DANGLING(vtxp->unlinkDelete(*otherp), vtxp);
|
|
continue;
|
|
}
|
|
// Otherwise they will be moved
|
|
vtxp->nodep()->user2p(vtxp);
|
|
vtxp->m_userCnt = 0;
|
|
vtxp->m_graphp = this;
|
|
}
|
|
m_varVertices.splice(m_varVertices.end(), otherp->m_varVertices);
|
|
// Process constants
|
|
for (DfgConst& vtx : otherp->m_constVertices) {
|
|
vtx.m_userCnt = 0;
|
|
vtx.m_graphp = this;
|
|
}
|
|
m_constVertices.splice(m_constVertices.end(), otherp->m_constVertices);
|
|
// Process operations
|
|
for (DfgVertex& vtx : otherp->m_opVertices) {
|
|
vtx.m_userCnt = 0;
|
|
vtx.m_graphp = this;
|
|
}
|
|
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 name = 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 += '_' + name + '_' + std::to_string(s_multiplicity[name]++) + '_';
|
|
}
|
|
// Assemble the globally unique name
|
|
return "__Vdfg" + prefix + m_tmpNameStub + std::to_string(n);
|
|
}
|
|
|
|
DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name, AstNodeDType* dtypep,
|
|
AstScope* scopep) {
|
|
UASSERT_OBJ(!!scopep != !!modulep(), flp,
|
|
"makeNewVar scopep should only be provided for a scoped DfgGraph");
|
|
|
|
// Create AstVar
|
|
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep};
|
|
|
|
if (scopep) {
|
|
// 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 (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) return new DfgVarArray{*this, vscp};
|
|
return new DfgVarPacked{*this, vscp};
|
|
} else {
|
|
// Add AstVar to containing module
|
|
modulep()->addStmtsp(varp);
|
|
// Create and return the corresponding variable vertex
|
|
if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) return new DfgVarArray{*this, varp};
|
|
return new DfgVarPacked{*this, varp};
|
|
}
|
|
}
|
|
|
|
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 DfgVarPacked* const varVtxp = vtx.cast<DfgVarPacked>()) {
|
|
AstNode* const nodep = varVtxp->nodep();
|
|
AstVar* const varp = varVtxp->varp();
|
|
os << toDotId(vtx);
|
|
os << " [label=\"" << nodep->name() << '\n';
|
|
os << cvtToHex(varVtxp) << '\n';
|
|
varVtxp->dtypep()->dumpSmall(os);
|
|
os << " / F" << varVtxp->fanout() << '"';
|
|
|
|
if (varp->direction() == VDirection::INPUT) {
|
|
os << ", shape=box, style=filled, fillcolor=chartreuse2"; // Green
|
|
} else if (varp->direction() == VDirection::OUTPUT) {
|
|
os << ", shape=box, style=filled, fillcolor=cyan2"; // Cyan
|
|
} else if (varp->direction() == VDirection::INOUT) {
|
|
os << ", shape=box, style=filled, fillcolor=darkorchid2"; // Purple
|
|
} else if (varVtxp->hasExtRefs()) {
|
|
os << ", shape=box, style=filled, fillcolor=firebrick2"; // Red
|
|
} else if (varVtxp->hasModRefs()) {
|
|
os << ", shape=box, style=filled, fillcolor=darkorange1"; // Orange
|
|
} else if (varVtxp->hasDfgRefs()) {
|
|
os << ", shape=box, style=filled, fillcolor=gold2"; // Yellow
|
|
} else {
|
|
os << ", shape=box";
|
|
}
|
|
os << "]\n";
|
|
return;
|
|
}
|
|
|
|
if (const DfgVarArray* const arrVtxp = vtx.cast<DfgVarArray>()) {
|
|
AstNode* const nodep = arrVtxp->nodep();
|
|
AstVar* const varp = arrVtxp->varp();
|
|
os << toDotId(vtx);
|
|
os << " [label=\"" << nodep->name() << '\n';
|
|
os << cvtToHex(arrVtxp) << '\n';
|
|
arrVtxp->dtypep()->dumpSmall(os);
|
|
os << " / F" << arrVtxp->fanout() << '"';
|
|
if (varp->direction() == VDirection::INPUT) {
|
|
os << ", shape=box3d, style=filled, fillcolor=chartreuse2"; // Green
|
|
} else if (varp->direction() == VDirection::OUTPUT) {
|
|
os << ", shape=box3d, style=filled, fillcolor=cyan2"; // Cyan
|
|
} else if (varp->direction() == VDirection::INOUT) {
|
|
os << ", shape=box3d, style=filled, fillcolor=darkorchid2"; // Purple
|
|
} else if (arrVtxp->hasExtRefs()) {
|
|
os << ", shape=box3d, style=filled, fillcolor=firebrick2"; // Red
|
|
} else if (arrVtxp->hasModRefs()) {
|
|
os << ", shape=box3d, style=filled, fillcolor=darkorange1"; // Orange
|
|
} else if (arrVtxp->hasDfgRefs()) {
|
|
os << ", shape=box3d, style=filled, fillcolor=gold2"; // Yellow
|
|
} else {
|
|
os << ", shape=box3d";
|
|
}
|
|
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.dtypep()->dumpSmall(os);
|
|
os << " / F" << vtx.fanout() << '"';
|
|
if (vtx.hasMultipleSinks()) {
|
|
os << ", shape=doublecircle";
|
|
} else {
|
|
os << ", shape=circle";
|
|
}
|
|
os << "]\n";
|
|
return;
|
|
}
|
|
|
|
if (vtx.is<DfgVertexSplice>()) {
|
|
os << toDotId(vtx);
|
|
os << " [label=\"" << vtx.typeName() << '\n';
|
|
os << cvtToHex(&vtx) << '\n';
|
|
vtx.dtypep()->dumpSmall(os);
|
|
os << " / F" << vtx.fanout() << '"';
|
|
if (vtx.hasMultipleSinks()) {
|
|
os << ", shape=doubleoctagon";
|
|
} else {
|
|
os << ", shape=octagon";
|
|
}
|
|
os << "]\n";
|
|
return;
|
|
}
|
|
|
|
os << toDotId(vtx);
|
|
os << " [label=\"" << vtx.typeName() << '\n';
|
|
os << cvtToHex(&vtx) << '\n';
|
|
vtx.dtypep()->dumpSmall(os);
|
|
os << " / F" << vtx.fanout() << '"';
|
|
if (vtx.hasMultipleSinks()) {
|
|
os << ", shape=doublecircle";
|
|
} else {
|
|
os << ", shape=circle";
|
|
}
|
|
os << "]\n";
|
|
}
|
|
|
|
// Dump one DfgEdge in Graphviz format
|
|
static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, size_t idx) {
|
|
if (!edge.sourcep()) return;
|
|
const DfgVertex& sink = *edge.sinkp(); // sink is never nullptr
|
|
os << toDotId(*edge.sourcep()) << " -> " << toDotId(sink);
|
|
if (sink.arity() > 1 || sink.is<DfgVertexSplice>()) {
|
|
os << " [headlabel=\"" << sink.srcName(idx) << "\"]";
|
|
}
|
|
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 << "graph [label=\"" << name();
|
|
if (!label.empty()) os << "-" << label;
|
|
os << "\", labelloc=t, labeljust=l]\n";
|
|
os << "graph [rankdir=LR]\n";
|
|
|
|
if (!p) {
|
|
// Emit all vertices and edges
|
|
forEachVertex([&](const DfgVertex& vtx) {
|
|
dumpDotVertex(os, vtx);
|
|
vtx.forEachSourceEdge([&](const DfgEdge& e, size_t i) { dumpDotEdge(os, e, i); });
|
|
});
|
|
} else {
|
|
// Emit vertices that satify the predicate 'p'
|
|
forEachVertex([&](const DfgVertex& vtx) {
|
|
if (!p(vtx)) return;
|
|
dumpDotVertex(os, vtx);
|
|
vtx.forEachSourceEdge([&](const DfgEdge& e, size_t i) {
|
|
if (!e.sourcep() || !p(*e.sourcep())) return;
|
|
dumpDotEdge(os, e, i);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Footer
|
|
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);
|
|
}
|
|
|
|
// LCOV_EXCL_START // Debug functions for developer use only
|
|
void DfgGraph::dumpDotUpstreamCone(const std::string& fileName, const DfgVertex& vtx,
|
|
const std::string& name) const {
|
|
// Open output file
|
|
const std::unique_ptr<std::ofstream> os{V3File::new_ofstream(fileName)};
|
|
if (os->fail()) v3fatal("Can't write file: " << fileName);
|
|
|
|
// Header
|
|
*os << "digraph dfg {\n";
|
|
if (!name.empty()) *os << "graph [label=\"" << name << "\", labelloc=t, labeljust=l]\n";
|
|
*os << "graph [rankdir=LR]\n";
|
|
|
|
// Work queue for depth first traversal starting from this vertex
|
|
std::vector<const DfgVertex*> queue{&vtx};
|
|
|
|
// Set of already visited vertices
|
|
std::unordered_set<const DfgVertex*> visited;
|
|
|
|
// Depth first traversal
|
|
while (!queue.empty()) {
|
|
// Pop next work item
|
|
const DfgVertex* const vtxp = queue.back();
|
|
queue.pop_back();
|
|
|
|
// Mark vertex as visited
|
|
const bool isFirstEncounter = visited.insert(vtxp).second;
|
|
|
|
// If we have already visited this vertex during the traversal, then move on.
|
|
if (!isFirstEncounter) continue;
|
|
|
|
// Enqueue all sources of this vertex.
|
|
vtxp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); });
|
|
|
|
// Emit this vertex and all of its source edges
|
|
dumpDotVertex(*os, *vtxp);
|
|
vtxp->forEachSourceEdge([&](const DfgEdge& e, size_t i) { dumpDotEdge(*os, e, i); });
|
|
}
|
|
|
|
// Footer
|
|
*os << "}\n";
|
|
|
|
// Done
|
|
os->close();
|
|
}
|
|
// LCOV_EXCL_STOP
|
|
|
|
//------------------------------------------------------------------------------
|
|
// 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, VDfgType type, FileLine* flp, AstNodeDType* dtypep)
|
|
: m_filelinep{flp}
|
|
, m_dtypep{dtypep}
|
|
, m_type{type} {
|
|
dfg.addVertex(*this);
|
|
}
|
|
|
|
DfgVertex::~DfgVertex() {}
|
|
|
|
bool DfgVertex::selfEquals(const DfgVertex& that) const { return true; }
|
|
|
|
V3Hash DfgVertex::selfHash() const { return V3Hash{}; }
|
|
|
|
bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const {
|
|
// If same vertex, then equal
|
|
if (this == &that) return true;
|
|
|
|
// If different type, then not equal
|
|
if (this->type() != that.type()) return false;
|
|
|
|
// If different data type, then not equal
|
|
if (this->dtypep() != that.dtypep()) return false;
|
|
|
|
// If different number of inputs, then not equal
|
|
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;
|
|
if (thisArity != thatArity) return false;
|
|
|
|
// Check vertex specifics
|
|
if (!this->selfEquals(that)) return false;
|
|
|
|
// Check sources
|
|
const auto key = (this < &that) ? EqualsCache::key_type{this, &that} //
|
|
: EqualsCache::key_type{&that, this};
|
|
// Note: the recursive invocation can cause a re-hash but that will not invalidate references
|
|
uint8_t& result = cache[key];
|
|
if (!result) {
|
|
result = 2; // Assume equals
|
|
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 = 1; // Mark not equal
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result >> 1;
|
|
}
|
|
|
|
V3Hash DfgVertex::hash() {
|
|
V3Hash& result = user<V3Hash>();
|
|
if (!result.value()) {
|
|
V3Hash hash{selfHash()};
|
|
// Variables are defined by themselves, so there is no need to hash them further
|
|
// (especially the sources). This enables sound hashing of graphs circular only through
|
|
// variables, which we rely on.
|
|
if (!is<DfgVertexVar>()) {
|
|
hash += m_type;
|
|
if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep(), UnpackArrayDType)) {
|
|
hash += adtypep->elementsConst();
|
|
// TODO: maybe include sub-dtype, but not hugely important at the moment
|
|
} else {
|
|
hash += width();
|
|
}
|
|
const auto pair = sourceEdges();
|
|
const DfgEdge* const edgesp = pair.first;
|
|
const size_t arity = pair.second;
|
|
// Sources must always be connected in well-formed graphs
|
|
for (size_t i = 0; i < arity; ++i) hash += edgesp[i].m_sourcep->hash();
|
|
}
|
|
result = hash;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
uint32_t DfgVertex::fanout() const {
|
|
uint32_t result = 0;
|
|
forEachSinkEdge([&](const DfgEdge&) { ++result; });
|
|
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;
|
|
// First variable found
|
|
if (!resp) {
|
|
resp = varp;
|
|
return;
|
|
}
|
|
|
|
// Prefer those variables that must be kept anyway
|
|
if (resp->hasExtRefs() != varp->hasExtRefs()) {
|
|
if (!resp->hasExtRefs()) resp = varp;
|
|
return;
|
|
}
|
|
if (resp->hasModWrRefs() != varp->hasModWrRefs()) {
|
|
if (!resp->hasModWrRefs()) resp = varp;
|
|
return;
|
|
}
|
|
if (resp->hasDfgRefs() != varp->hasDfgRefs()) {
|
|
if (!resp->hasDfgRefs()) resp = varp;
|
|
return;
|
|
}
|
|
// Prefer those that already have module references
|
|
if (resp->hasModRdRefs() != varp->hasModRdRefs()) {
|
|
if (!resp->hasModRdRefs()) resp = varp;
|
|
return;
|
|
}
|
|
// 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;
|
|
}
|
|
// Prefer the one with the lexically smaller name
|
|
if (const int cmp = resp->nodep()->name().compare(varp->nodep()->name())) {
|
|
if (cmp > 0) resp = varp;
|
|
return;
|
|
}
|
|
// 'resp' and 'varp' are all the same, keep using the existing 'resp'
|
|
});
|
|
return resp;
|
|
}
|
|
|
|
AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED {
|
|
// If this is a variable, we are done
|
|
if (DfgVertexVar* const varp = this->cast<DfgVertexVar>()) return varp->varScopep()->scopep();
|
|
|
|
// Try the result var first if instructed (usully only in the recursive case)
|
|
if (tryResultVar) {
|
|
if (DfgVertexVar* const varp = this->getResultVar()) return varp->varScopep()->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
|
|
AstScope* const rootp = v3Global.rootp()->topScopep()->scopep();
|
|
AstScope* foundp = rootp;
|
|
const auto edges = sourceEdges();
|
|
for (size_t i = 0; i < edges.second; ++i) {
|
|
DfgEdge& edge = edges.first[i];
|
|
foundp = edge.sourcep()->scopep(cache, true);
|
|
if (foundp != rootp) break;
|
|
}
|
|
resultr = foundp;
|
|
}
|
|
|
|
// 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 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
|
|
//------------------------------------------------------------------------------
|
|
|
|
// DfgConst ----------
|
|
|
|
bool DfgConst::selfEquals(const DfgVertex& that) const {
|
|
return num().isCaseEq(that.as<DfgConst>()->num());
|
|
}
|
|
|
|
V3Hash DfgConst::selfHash() const { return num().toHash(); }
|
|
|
|
// DfgSel ----------
|
|
|
|
bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as<DfgSel>()->lsb(); }
|
|
|
|
V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; }
|
|
|
|
// DfgSpliceArray ----------
|
|
|
|
bool DfgSpliceArray::selfEquals(const DfgVertex& that) const {
|
|
const DfgSpliceArray* const thatp = that.as<DfgSpliceArray>();
|
|
const size_t arity = this->arity();
|
|
for (size_t i = 0; i < arity; ++i) {
|
|
if (driverIndex(i) != thatp->driverIndex(i)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
V3Hash DfgSpliceArray::selfHash() const {
|
|
V3Hash hash;
|
|
const size_t arity = this->arity();
|
|
for (size_t i = 0; i < arity; ++i) hash += driverIndex(i);
|
|
return hash;
|
|
}
|
|
|
|
// DfgSplicePacked ----------
|
|
|
|
bool DfgSplicePacked::selfEquals(const DfgVertex& that) const {
|
|
const DfgSplicePacked* const thatp = that.as<DfgSplicePacked>();
|
|
const size_t arity = this->arity();
|
|
for (size_t i = 0; i < arity; ++i) {
|
|
if (driverLsb(i) != thatp->driverLsb(i)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
V3Hash DfgSplicePacked::selfHash() const {
|
|
V3Hash hash;
|
|
const size_t arity = this->arity();
|
|
for (size_t i = 0; i < arity; ++i) hash += driverLsb(i);
|
|
return hash;
|
|
}
|
|
|
|
// DfgVertexVar ----------
|
|
|
|
bool DfgVertexVar::selfEquals(const DfgVertex& that) const {
|
|
UASSERT_OBJ(nodep()->type() == that.as<DfgVertexVar>()->nodep()->type(), this,
|
|
"Both DfgVertexVar should be scoped or unscoped");
|
|
UASSERT_OBJ(nodep() != that.as<DfgVertexVar>()->nodep(), this,
|
|
"There should only be one DfgVertexVar for a given AstVar or AstVarScope");
|
|
return false;
|
|
}
|
|
|
|
V3Hash DfgVertexVar::selfHash() const {
|
|
V3Hash hash;
|
|
hash += nodep()->name();
|
|
hash += varp()->varType();
|
|
return hash;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// DfgVisitor
|
|
//------------------------------------------------------------------------------
|
|
|
|
#include "V3Dfg__gen_visitor_defns.h" // From ./astgen
|