// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Block code ordering // // 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 // //************************************************************************* // // Class used to construct AstCFuncs from a sequence of OrderLogicVertex's // //************************************************************************* #ifndef VERILATOR_V3ORDERCFUNCEMITTER_H_ #define VERILATOR_V3ORDERCFUNCEMITTER_H_ #include "config_build.h" #include "verilatedos.h" #include "V3Ast.h" #include "V3Graph.h" #include "V3OrderGraph.h" #include "V3Sched.h" #include #include #include class V3OrderCFuncEmitter final { // Name component to add to function - must be unique const std::string m_tag; // True if creating slow functions const bool m_slow; // Whether to split functions const bool m_split = v3Global.opt.outputSplitCFuncs(); // Size of code emitted so in the current function - for splitting size_t m_size = 0; // Maximum size of code per function const size_t m_splitSize = []() -> size_t { // Don't want to split with procCFuncs if (v3Global.opt.profCFuncs()) return std::numeric_limits::max(); // Otherwise split at the specified size (- 1, so we can do >= comparison) if (const size_t limit = v3Global.opt.outputSplitCFuncs()) return limit - 1; return std::numeric_limits::max(); }(); // Current function being populated AstCFunc* m_funcp = nullptr; // Function ordinals to ensure unique names std::map, unsigned> m_funcNums; // The resulting ordered CFuncs with the trigger conditions needed to call them std::vector> m_result; // Create a unique name for a new function std::string cfuncName(FileLine* flp, AstScope* scopep, AstNodeModule* modp, AstSenTree* domainp) { std::string name = "_" + m_tag; name += domainp->isMulti() ? "_comb" : "_sequent"; name += "__" + scopep->nameDotless(); name += "__" + std::to_string(m_funcNums[{modp, name}]++); if (v3Global.opt.profCFuncs()) name += "__PROF__" + flp->profileFuncname(); return name; } public: // CONSTRUCTOR V3OrderCFuncEmitter(const std::string& tag, bool slow) : m_tag{tag} , m_slow{slow} {} VL_UNCOPYABLE(V3OrderCFuncEmitter); VL_UNMOVABLE(V3OrderCFuncEmitter); // Force the creation of a new function void forceNewFunction() { m_size = 0; m_funcp = nullptr; } // Retrieve list of statements which when executed will call the constructed functions AstNodeStmt* getStmts() { // The resulting list of statements we are constructing here AstNodeStmt* stmtsp = nullptr; // Current AstIf AstIf* ifp = nullptr; // Trigger conditoin of 'ifp' AstSenTree* pervSenTreep = nullptr; // Call each function under an AstIf that checks for the trigger condition for (const auto& pair : m_result) { AstCFunc* const cfuncp = pair.first; AstSenTree* senTreep = pair.second; // Create a new AstIf if the trigger is different if (senTreep != pervSenTreep) { pervSenTreep = senTreep; ifp = V3Sched::util::createIfFromSenTree(senTreep); stmtsp = AstNode::addNext(stmtsp, ifp); } // Call function when triggered AstCCall* const callp = new AstCCall{cfuncp->fileline(), cfuncp}; callp->dtypeSetVoid(); ifp->addThensp(callp->makeStmt()); } // Result is now spent, reset the emitter state m_result.clear(); forceNewFunction(); // Return the list of statement return stmtsp; } // Emit one logic vertex void emitLogic(const OrderLogicVertex* lVtxp) { // Sensitivity domain of logic we are emitting AstSenTree* const domainp = lVtxp->domainp(); // We are move the logic into a CFunc AstNode* const logicp = lVtxp->nodep(); // If the logic is a procedure, we need to do a few special things AstNodeProcedure* const procp = VN_CAST(logicp, NodeProcedure); // Some properties to consider const bool suspendable = procp && procp->isSuspendable(); const bool needProcess = procp && procp->needProcess(); // TODO: This is a bit muddy: 'initial forever @(posedge clk) begin ... end' is a fancy // way of saying always @(posedge clk), so it might be quite hot... // Also, if m_funcp is slow, but this one isn't we should force a new function const bool slow = m_slow && !(suspendable && VN_IS(procp, Always)); // Put suspendable processes into individual functions on their own if (suspendable) forceNewFunction(); // When profCFuncs, create a new function for each logic vertex if (v3Global.opt.profCFuncs()) forceNewFunction(); // If the new domain is different, force a new function as it needs to be called separately if (!m_result.empty() && m_result.back().second != domainp) forceNewFunction(); // Process procedures per statement, so we can split CFuncs within procedures. // Everything else is handled as a unit. AstNode* const headp = [&]() -> AstNode* { if (!procp) return logicp->unlinkFrBack(); // Not a procedure, handle as a unit AstNode* const stmtsp = procp->stmtsp(); UASSERT_OBJ(stmtsp, procp, "Empty process should have been deleted earlier"); stmtsp->unlinkFrBackWithNext(); // 'procp' is no longer needed but the OrderGraph still references // it (for debug dump). It will get deleted when deleting the // AstActives in V3Order. return stmtsp; }(); // Process each statement in the list starting at headp for (AstNode *currp = headp, *nextp; currp; currp = nextp) { nextp = currp->nextp(); // Unlink the current statement from the next statement (if any) if (nextp) nextp->unlinkFrBackWithNext(); // Split the function if too large, but don't split suspendable processes if (!suspendable && m_size >= m_splitSize) forceNewFunction(); // Create a new function if we don't have a current one if (!m_funcp) { UASSERT_OBJ(!m_size, currp, "Should have used forceNewFunction"); FileLine* const flp = currp->fileline(); AstScope* const scopep = lVtxp->scopep(); AstNodeModule* const modp = scopep->modp(); const std::string name = cfuncName(flp, scopep, modp, domainp); m_funcp = new AstCFunc{flp, name, scopep, suspendable ? "VlCoroutine" : ""}; if (needProcess) m_funcp->setNeedProcess(); m_funcp->isStatic(false); m_funcp->isLoose(true); m_funcp->slow(slow); scopep->addBlocksp(m_funcp); // Record function and sensitivity to call it with m_result.emplace_back(m_funcp, domainp); } // Add the code to the current function m_funcp->addStmtsp(currp); // If splitting, add in the size of the code we just added if (m_split) m_size += currp->nodeCount(); } // Put suspendable processes into individual functions on their own if (suspendable) forceNewFunction(); } }; #endif // Guard