// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Block code ordering // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2024 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 // //************************************************************************* // // Parallel code ordering // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Graph.h" #include "V3GraphStream.h" #include "V3List.h" #include "V3OrderCFuncEmitter.h" #include "V3OrderInternal.h" #include "V3OrderMoveGraphBuilder.h" #include "V3Partition.h" #include "V3PartitionGraph.h" #include #include VL_DEFINE_DEBUG_FUNCTIONS; class OrderMTaskMoveVertexMaker final : public V3OrderMoveGraphBuilder::MoveVertexMaker { V3Graph* m_pomGraphp; public: explicit OrderMTaskMoveVertexMaker(V3Graph* pomGraphp) : m_pomGraphp{pomGraphp} {} MTaskMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex* varVertexp, const AstSenTree* domainp) override { return new MTaskMoveVertex{m_pomGraphp, lvertexp, varVertexp, domainp}; } private: VL_UNCOPYABLE(OrderMTaskMoveVertexMaker); }; // Sort MTaskMoveVertex vertices by domain, then by scope, based on teh order they are encountered class OrderVerticesByDomainThenScope final { mutable uint64_t m_nextId = 0; // Next id to use mutable std::unordered_map m_id; // Map from ptr to id // Map a pointer into an id, for deterministic results uint64_t findId(const void* ptrp) const { const auto pair = m_id.emplace(ptrp, m_nextId); if (pair.second) ++m_nextId; return pair.first->second; } public: bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const { const MTaskMoveVertex* const l_vxp = lhsp->as(); const MTaskMoveVertex* const r_vxp = rhsp->as(); const uint64_t l_id = findId(l_vxp->domainp()); const uint64_t r_id = findId(r_vxp->domainp()); if (l_id != r_id) return l_id < r_id; return findId(l_vxp->scopep()) < findId(r_vxp->scopep()); } }; // Sort AbstractMTask vertices by their serial IDs. struct MTaskVxIdLessThan final { bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const { return lhsp->as()->id() < rhsp->as()->id(); } }; AstExecGraph* V3Order::createParallel(const OrderGraph& graph, const std::string& tag, const TrigToSenMap& trigToSen, bool slow) { // For nondeterminism debug: V3Partition::hashGraphDebug(&graph, "V3Order's m_graph"); // We already produced a graph of every var, input, and logic // block and all dependencies; this is 'm_graph'. // // Now, starting from m_graph, make a slightly-coarsened graph representing // only logic, and discarding edges we know we can ignore. // This is quite similar to the 'm_pomGraph' of the serial code gen: V3Graph logicGraph; { OrderMTaskMoveVertexMaker create_mtask_vertex(&logicGraph); V3OrderMoveGraphBuilder mtask_pmbg(&graph, &logicGraph, trigToSen, &create_mtask_vertex); mtask_pmbg.build(); } // Needed? We do this for m_pomGraph in serial mode, so do it here too: logicGraph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue); // Partition logicGraph into LogicMTask's. The partitioner will annotate // each vertex in logicGraph with a 'color' which is really an mtask ID // in this context. V3Partition partitioner{&graph, &logicGraph}; V3Graph mtasks; partitioner.go(&mtasks); // processMTask* routines schedule threaded execution struct MTaskState final { AstMTaskBody* m_mtaskBodyp = nullptr; std::list m_logics; ExecMTask* m_execMTaskp = nullptr; MTaskState() = default; }; std::unordered_map mtaskStates; // Iterate through the entire logicGraph. For each logic node, // attach it to a per-MTask ordered list of logic nodes. // This is the order we'll execute logic nodes within the MTask. // // MTasks may span scopes and domains, so sort by both here: GraphStream logicStream{&logicGraph}; while (const V3GraphVertex* const vtxp = logicStream.nextp()) { const MTaskMoveVertex* const movep = vtxp->as(); // Only care about logic vertices if (!movep->logicp()) continue; const unsigned mtaskId = movep->color(); UASSERT(mtaskId > 0, "Every MTaskMoveVertex should have an mtask assignment >0"); // Add this logic to the per-mtask order mtaskStates[mtaskId].m_logics.push_back(movep->logicp()); // Since we happen to be iterating over every logic node, // take this opportunity to annotate each AstVar with the id's // of mtasks that consume it and produce it. We'll use this // information in V3EmitC when we lay out var's in memory. const OrderLogicVertex* const logicp = movep->logicp(); for (const V3GraphEdge* edgep = logicp->inBeginp(); edgep; edgep = edgep->inNextp()) { const OrderVarVertex* const pre_varp = edgep->fromp()->cast(); if (!pre_varp) continue; AstVar* const varp = pre_varp->vscp()->varp(); // varp depends on logicp, so logicp produces varp, // and vice-versa below varp->addProducingMTaskId(mtaskId); } for (const V3GraphEdge* edgep = logicp->outBeginp(); edgep; edgep = edgep->outNextp()) { const OrderVarVertex* const post_varp = edgep->top()->cast(); if (!post_varp) continue; AstVar* const varp = post_varp->vscp()->varp(); varp->addConsumingMTaskId(mtaskId); } // TODO? We ignore IO vars here, so those will have empty mtask // signatures. But we could also give those mtask signatures. } // Create the AstExecGraph node which represents the execution // of the MTask graph. FileLine* const rootFlp = v3Global.rootp()->fileline(); AstExecGraph* const execGraphp = new AstExecGraph{rootFlp, tag}; // Create CFuncs and bodies for each MTask. V3OrderCFuncEmitter emitter{tag, slow}; GraphStream mtaskStream{&mtasks}; while (const V3GraphVertex* const vtxp = mtaskStream.nextp()) { const AbstractLogicMTask* const mtaskp = vtxp->as(); // Create a body for this mtask AstMTaskBody* const bodyp = new AstMTaskBody{rootFlp}; MTaskState& state = mtaskStates[mtaskp->id()]; state.m_mtaskBodyp = bodyp; // Emit functions with this MTaks's logic, and call them in the body. for (const OrderLogicVertex* lVtxp : state.m_logics) emitter.emitLogic(lVtxp); for (AstActive* const activep : emitter.getAndClearActiveps()) bodyp->addStmtsp(activep); // Translate the LogicMTask graph into the corresponding ExecMTask // graph, which will outlive V3Order and persist for the remainder // of verilator's processing. // - The LogicMTask graph points to MTaskMoveVertex's // and OrderLogicVertex's which are ephemeral to V3Order. // - The ExecMTask graph and the AstMTaskBody's produced here // persist until code generation time. V3Graph* const depGraphp = execGraphp->depGraphp(); state.m_execMTaskp = new ExecMTask{depGraphp, bodyp, mtaskp->id()}; // Cross-link each ExecMTask and MTaskBody // Q: Why even have two objects? // A: One is an AstNode, the other is a GraphVertex, // to combine them would involve multiple inheritance... state.m_mtaskBodyp->execMTaskp(state.m_execMTaskp); for (V3GraphEdge* inp = mtaskp->inBeginp(); inp; inp = inp->inNextp()) { const V3GraphVertex* fromVxp = inp->fromp(); const AbstractLogicMTask* const fromp = static_cast(fromVxp); const MTaskState& fromState = mtaskStates[fromp->id()]; new V3GraphEdge{depGraphp, fromState.m_execMTaskp, state.m_execMTaskp, 1}; } execGraphp->addMTaskBodiesp(bodyp); } return execGraphp; }