// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Convert AstModule to DfgGraph // // 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 // //************************************************************************* // // Convert and AstModule (before V3Scope), or the entire AstNetlist // (after V3Scope) to an initial DfgGraph composed onlyof DfgLogic, // DfgUnresolved and DfgVertexVar vertices. This will later be synthesized // into primitive operations by V3DfgPasses::synthesize. // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Cfg.h" #include "V3Const.h" #include "V3Dfg.h" #include "V3DfgPasses.h" VL_DEFINE_DEBUG_FUNCTIONS; class AstToDfgAddAstRefs final : public VNVisitorConst { // STATE DfgGraph& m_dfg; // The graph being processed // Function to get the DfgVertexVar for a AstVarScope const std::function m_getVarVertex; bool m_inSenItem = false; // Inside an AstSenItem bool m_inLoop = false; // Inside an AstLoop // VISITORS void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } void visit(AstSenItem* nodep) override { VL_RESTORER(m_inSenItem); m_inSenItem = true; iterateChildrenConst(nodep); } void visit(AstLoop* nodep) override { VL_RESTORER(m_inLoop); m_inLoop = true; iterateChildrenConst(nodep); } void visit(AstVarRef* nodep) override { // Disguised hierarchical reference handled as external reference, ignore if (nodep->classOrPackagep()) return; // If target is not supported, ignore AstVarScope* const vscp = nodep->varScopep(); if (!V3Dfg::isSupported(vscp)) return; // V3Dfg::isSupported should reject vars with READWRITE references UASSERT_OBJ(!nodep->access().isRW(), nodep, "AstVarScope with READWRITE ref not rejected"); // Get target variable vergtex, ignore if not given one DfgVertexVar* const varp = m_getVarVertex(vscp); if (!varp) return; // Create Ast reference vertices if (nodep->access().isReadOnly()) { DfgAstRd* const astp = new DfgAstRd{m_dfg, nodep, m_inSenItem, m_inLoop}; astp->srcp(varp); return; } // Mark as written from non-DFG logic DfgVertexVar::setHasModWrRefs(vscp); } AstToDfgAddAstRefs(DfgGraph& dfg, AstNode* nodep, std::function getVarVertex) : m_dfg{dfg} , m_getVarVertex{getVarVertex} { iterateConst(nodep); } public: static void apply(DfgGraph& dfg, AstNode* nodep, std::function getVarVertex) { AstToDfgAddAstRefs{dfg, nodep, getVarVertex}; } }; void V3DfgPasses::addAstRefs(DfgGraph& dfg, AstNode* nodep, std::function getVarVertex) { AstToDfgAddAstRefs::apply(dfg, nodep, getVarVertex); } class AstToDfgVisitor final : public VNVisitor { // NODE STATE // AstVarScope::user2() -> DfgVertexVar* : the corresponding variable vertex // AstVarScope::user3() -> bool : Already gathered - used fine grained below const VNUser2InUse m_user2InUse; // STATE DfgGraph& m_dfg; // The graph being built V3DfgAstToDfgContext& m_ctx; // The context for stats AstScope* m_scopep = nullptr; // The current scope, iff T_Scoped // METHODS // Mark variables referenced under node void markReferenced(AstNode* nodep) { V3DfgPasses::addAstRefs(m_dfg, nodep, [this](AstVarScope* vscp) { // return getVarVertex(vscp); }); } DfgVertexVar* getVarVertex(AstVarScope* varp) { if (!varp->user2p()) { // TODO: fix this up when removing the different flavours of DfgVar const AstNodeDType* const dtypep = varp->dtypep()->skipRefp(); DfgVertexVar* const vtxp = VN_IS(dtypep, UnpackArrayDType) ? static_cast(new DfgVarArray{m_dfg, varp}) : static_cast(new DfgVarPacked{m_dfg, varp}); varp->user2p(vtxp); } return varp->user2u().template to(); } // Gather variables written by the given logic node. // Return nullptr if any are not supported. std::unique_ptr> gatherWritten(const AstNode* nodep) { const VNUser3InUse user3InUse; std::unique_ptr> resp{new std::vector{}}; const bool abort = nodep->exists([&](const AstVarRef* refp) -> bool { if (refp->access().isReadOnly()) return false; AstVarScope* const vscp = refp->varScopep(); if (!V3Dfg::isSupported(vscp)) return true; if (!vscp->user3SetOnce()) resp->emplace_back(getVarVertex(vscp)); return false; }); if (abort) { ++m_ctx.m_nonRepVar; return nullptr; } return resp; } // Gather variables read by the given logic node. // Return nullptr if any are not supported. std::unique_ptr> gatherRead(const AstNode* nodep) { const VNUser3InUse user3InUse; std::unique_ptr> resp{new std::vector{}}; const bool abort = nodep->exists([&](const AstVarRef* refp) -> bool { if (refp->access().isWriteOnly()) return false; AstVarScope* const vscp = refp->varScopep(); if (!V3Dfg::isSupported(vscp)) return true; if (!vscp->user3SetOnce()) resp->emplace_back(getVarVertex(vscp)); return false; }); if (abort) { ++m_ctx.m_nonRepVar; return nullptr; } return resp; } // Gather variables live in to the given CFG. // Return nullptr if any are not supported. std::unique_ptr> gatherLive(const CfgGraph& cfg) { // Run analysis std::unique_ptr> varps = V3Cfg::liveVarScopes(cfg); if (!varps) { ++m_ctx.m_nonRepLive; return nullptr; } // Convert to vertics const VNUser3InUse user3InUse; std::unique_ptr> resp{new std::vector{}}; resp->reserve(varps->size()); for (AstVarScope* const varp : *varps) { if (!V3Dfg::isSupported(varp)) { ++m_ctx.m_nonRepVar; return nullptr; } UASSERT_OBJ(!varp->user3SetOnce(), varp, "Live variables should be unique"); resp->emplace_back(getVarVertex(varp)); } return resp; } // Connect inputs and outputs of a DfgLogic void connect(DfgLogic& vtx, const std::vector& iVarps, const std::vector& oVarps) { // Connect inputs for (DfgVertexVar* const iVarp : iVarps) vtx.addInput(iVarp); // Connect outputs for (DfgVertexVar* const oVarp : oVarps) { if (!oVarp->srcp()) oVarp->srcp(new DfgUnresolved{m_dfg, oVarp}); oVarp->srcp()->as()->addDriver(&vtx); } } // Convert AstAlways to DfgLogic, return true if successful. bool convert(AstAlways* nodep) { const VAlwaysKwd kwd = nodep->keyword(); if (kwd == VAlwaysKwd::CONT_ASSIGN) { // TODO: simplify once CFG analysis can handle arrays if (const AstAssignW* const ap = VN_CAST(nodep->stmtsp(), AssignW)) { if (ap->nextp()) return false; // Cannot handle assignment with timing control if (ap->timingControlp()) return false; // Potentially convertible block ++m_ctx.m_inputs; // Gather written variables, give up if any are not supported const std::unique_ptr> oVarpsp = gatherWritten(ap); if (!oVarpsp) return false; // Gather read variables, give up if any are not supported const std::unique_ptr> iVarpsp = gatherRead(ap); if (!iVarpsp) return false; // Create the DfgLogic DfgLogic* const logicp = new DfgLogic{m_dfg, nodep, m_scopep, nullptr}; // Connect it up connect(*logicp, *iVarpsp, *oVarpsp); // Done ++m_ctx.m_representable; return true; } } // Can only handle combinational logic if (nodep->sentreep()) return false; if (kwd != VAlwaysKwd::ALWAYS // && kwd != VAlwaysKwd::ALWAYS_COMB // && kwd != VAlwaysKwd::CONT_ASSIGN) { return false; } // Potentially convertible block ++m_ctx.m_inputs; // Attempt to build CFG of AstAlways, give up if failed std::unique_ptr cfgp = CfgGraph::build(nodep->stmtsp()); if (!cfgp) { ++m_ctx.m_nonRepCfg; return false; } // Gather written variables, give up if any are not supported const std::unique_ptr> oVarpsp = gatherWritten(nodep); if (!oVarpsp) return false; // Gather read variables, give up if any are not supported const std::unique_ptr> iVarpsp = gatherLive(*cfgp); if (!iVarpsp) return false; // Create the DfgLogic DfgLogic* const logicp = new DfgLogic{m_dfg, nodep, m_scopep, std::move(cfgp)}; // Connect it up connect(*logicp, *iVarpsp, *oVarpsp); // Done ++m_ctx.m_representable; return true; } // VISITORS // Unhandled node void visit(AstNode* nodep) override { markReferenced(nodep); } // Containers to descend through to find logic constructs void visit(AstNetlist* nodep) override { iterateAndNextNull(nodep->modulesp()); } void visit(AstModule* nodep) override { iterateAndNextNull(nodep->stmtsp()); } void visit(AstIface* nodep) override { if (!nodep->hasVirtualRef()) { iterateAndNextNull(nodep->stmtsp()); } else { markReferenced(nodep); } } void visit(AstTopScope* nodep) override { iterate(nodep->scopep()); } void visit(AstScope* nodep) override { VL_RESTORER(m_scopep); m_scopep = nodep; iterateChildren(nodep); } void visit(AstActive* nodep) override { if (nodep->hasCombo()) { iterateChildren(nodep); } else { markReferenced(nodep); } } // Non-representable constructs void visit(AstCell* nodep) override { markReferenced(nodep); } void visit(AstNodeProcedure* nodep) override { markReferenced(nodep); } // Potentially representable constructs void visit(AstAlways* nodep) override { if (!convert(nodep)) markReferenced(nodep); } // CONSTRUCTOR AstToDfgVisitor(DfgGraph& dfg, AstNetlist& root, V3DfgAstToDfgContext& ctx) : m_dfg{dfg} , m_ctx{ctx} { iterate(&root); } VL_UNCOPYABLE(AstToDfgVisitor); VL_UNMOVABLE(AstToDfgVisitor); public: static void apply(DfgGraph& dfg, AstNetlist& root, V3DfgAstToDfgContext& ctx) { // Convert all logic under 'root' AstToDfgVisitor{dfg, root, ctx}; // Remove unread and undriven variables (created when something failed to convert) for (DfgVertexVar* const varp : dfg.varVertices().unlinkable()) { if (varp->srcp()) continue; const bool keep = varp->foreachSink([&](DfgVertex& d) { return !d.is(); }); if (!keep) { while (varp->hasSinks()) varp->firtsSinkp()->unlinkDelete(dfg); VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); } } } }; std::unique_ptr V3DfgPasses::astToDfg(AstNetlist& netlist, V3DfgContext& ctx) { DfgGraph* const dfgp = new DfgGraph{"netlist"}; AstToDfgVisitor::apply(*dfgp, netlist, ctx.m_ast2DfgContext); return std::unique_ptr{dfgp}; }