// -*- 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 "V3EmitV.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& vtx) { vtx.unlinkDelete(*this); }); } std::unique_ptr DfgGraph::clone() const { const bool scoped = !modulep(); DfgGraph* const clonep = new DfgGraph{modulep(), name()}; // Map from original vertex to clone std::unordered_map 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* cp = nullptr; switch (vtx.type()) { case VDfgType::VarArray: { if (scoped) { cp = new DfgVarArray{*clonep, vp->varScopep()}; } else { cp = new DfgVarArray{*clonep, vp->varp()}; } vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::VarPacked: { 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; } } if (AstNode* const tmpForp = vp->tmpForp()) cp->tmpForp(tmpForp); } // Clone operation vertices for (const DfgVertex& vtx : m_opVertices) { switch (vtx.type()) { #include "V3Dfg__gen_clone_cases.h" // From ./astgen case VDfgType::Sel: { DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtype()}; cp->lsb(vtx.as()->lsb()); vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::UnitArray: { DfgUnitArray* const cp = new DfgUnitArray{*clonep, vtx.fileline(), vtx.dtype()}; vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::Mux: { DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtype()}; vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::SpliceArray: { DfgSpliceArray* const cp = new DfgSpliceArray{*clonep, vtx.fileline(), vtx.dtype()}; vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::SplicePacked: { DfgSplicePacked* const cp = new DfgSplicePacked{*clonep, vtx.fileline(), vtx.dtype()}; vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::Logic: { vtx.v3fatalSrc("DfgLogic cannot be cloned"); VL_UNREACHABLE; break; } case VDfgType::Unresolved: { vtx.v3fatalSrc("DfgUnresolved cannot be cloned"); VL_UNREACHABLE; 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) { DfgVertexVar* const cp = vtxp2clonep.at(&vtx)->as(); if (const DfgVertex* const srcp = vtx.srcp()) cp->srcp(vtxp2clonep.at(srcp)); if (const DfgVertex* const defp = vtx.defaultp()) cp->defaultp(vtxp2clonep.at(defp)); } // Hook up inputs of cloned operation vertices for (const DfgVertex& vtx : m_opVertices) { if (vtx.is()) { switch (vtx.type()) { case VDfgType::SpliceArray: case VDfgType::SplicePacked: { const DfgVertexSplice* const vp = vtx.as(); DfgVertexSplice* const cp = vtxp2clonep.at(vp)->as(); vp->foreachDriver([&](const DfgVertex& src, uint32_t lo, FileLine* flp) { cp->addDriver(vtxp2clonep.at(&src), lo, flp); return false; }); break; } default: { vtx.v3fatalSrc("Unhandled DfgVertexVariadic sub type: " + vtx.typeName()); VL_UNREACHABLE; break; } } } else { DfgVertex* const cp = vtxp2clonep.at(&vtx); for (size_t i = 0; i < vtx.nInputs(); ++i) { cp->inputp(i, vtxp2clonep.at(vtx.inputp(i))); } } } return std::unique_ptr{clonep}; } void DfgGraph::mergeGraphs(std::vector>&& 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& 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()) { 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->nodep()->user2p(vtxp); vtxp->m_userGeneration = 0; #ifdef VL_DEBUG vtxp->m_dfgp = this; #endif } m_varVertices.splice(m_varVertices.end(), otherp->m_varVertices); // 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 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) { 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, dtype.astDtypep()}; 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 (dtype.isArray()) 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 (dtype.isArray()) 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 DfgVertexVar* const varVtxp = vtx.cast()) { const AstNode* const nodep = varVtxp->nodep(); const AstVar* const varp = varVtxp->varp(); os << toDotId(vtx); // Begin attributes os << " ["; // Begin 'label' os << "label=\""; // Name os << nodep->prettyName(); // Address os << '\n' << cvtToHex(varVtxp); // Original variable, if any if (const AstNode* const tmpForp = varVtxp->tmpForp()) { if (tmpForp != nodep) os << "\ntemporary for: " << tmpForp->prettyName(); } // Type and fanout os << '\n'; varVtxp->dtype().astDtypep()->dumpSmall(os); os << " / F" << varVtxp->fanout(); // End 'label' os << '"'; // Shape if (varVtxp->is()) { os << ", shape=box"; } else if (varVtxp->is()) { os << ", shape=box3d"; } else { varVtxp->v3fatalSrc("Unhandled DfgVertexVar sub-type"); } // Color if (varp->direction() == VDirection::INPUT) { os << ", style=filled, fillcolor=chartreuse2"; // Green } else if (varp->direction() == VDirection::OUTPUT) { os << ", style=filled, fillcolor=cyan2"; // Cyan } else if (varp->direction() == VDirection::INOUT) { os << ", style=filled, fillcolor=darkorchid2"; // Purple } else if (varVtxp->hasExtRefs()) { os << ", style=filled, fillcolor=firebrick2"; // Red } else if (varVtxp->hasModRefs()) { os << ", style=filled, fillcolor=darkorange1"; // Orange } else if (varVtxp->hasDfgRefs()) { os << ", style=filled, fillcolor=gold2"; // Yellow } else if (varVtxp->tmpForp()) { os << ", style=filled, fillcolor=gray95"; } // End attributes os << "]\n"; return; } if (const DfgConst* const constVtxp = vtx.cast()) { 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()) { 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() || vtx.is() || vtx.is()) { 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()) { os << toDotId(vtx); std::stringstream ss; V3EmitV::debugVerilogForTree(logicp->nodep(), ss); std::string str = 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" << cvtToHex(&vtx); 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 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 emitted; // Emit all vertices associated with a DfgLogic forEachVertex([&](const DfgVertex& vtx) { const DfgLogic* const logicp = vtx.cast(); 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 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 p) const { const std::unique_ptr 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 p) const { std::string filename = name(); if (!label.empty()) filename += "-" + label; dumpDotFile(v3Global.debugFilename(filename) + ".dot", label, p); } template static std::unique_ptr> dfgGraphCollectCone(const std::vector& vtxps) { // Work queue for traversal starting from all the seed vertices std::vector queue = vtxps; // Set of already visited vertices std::unique_ptr> resp{ new std::unordered_set{}}; // 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> DfgGraph::sourceCone(const std::vector& vtxps) const { return dfgGraphCollectCone(vtxps); } std::unique_ptr> DfgGraph::sinkCone(const std::vector& vtxps) const { return dfgGraphCollectCone(vtxps); } //------------------------------------------------------------------------------ // DfgVertex DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataType& dt) : m_filelinep{flp} , m_dtype{dt} , m_type{type} { dfg.addVertex(*this); } 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()) 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(); if (!varp) return false; // First variable found if (!resp) { resp = varp; return false; } // Prefer those variables that must be kept anyway if (resp->hasExtRefs() != varp->hasExtRefs()) { if (!resp->hasExtRefs()) resp = varp; return false; } if (resp->hasModWrRefs() != varp->hasModWrRefs()) { if (!resp->hasModWrRefs()) resp = varp; return false; } if (resp->hasDfgRefs() != varp->hasDfgRefs()) { if (!resp->hasDfgRefs()) resp = varp; return false; } // Prefer those that already have module references if (resp->hasModRdRefs() != varp->hasModRdRefs()) { if (!resp->hasModRdRefs()) resp = varp; return false; } // Prefer real variabels over temporaries 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->nodep()->name().compare(varp->nodep()->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()) { return varp->varScopep()->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->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(1); // Find scope based on sources, falling back on the root scope AstScope* const rootp = v3Global.rootp()->topScopep()->scopep(); AstScope* foundp = nullptr; foreachSource([&](DfgVertex& src) { AstScope* const scp = src.scopep(cache, true); if (scp != rootp) { foundp = scp; return true; } return false; }); resultr = foundp ? foundp : rootp; } // Die on a graph circular through operation vertices UASSERT_OBJ(resultr != reinterpret_cast(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; } //------------------------------------------------------------------------------ // DfgVisitor #include "V3Dfg__gen_visitor_defns.h" // From ./astgen