// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Reorder statements within always blocks // // 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 // //************************************************************************* // V3Reorder transformations: // // reorderAll() reorders statements within individual blocks // to avoid delay vars when possible. It no longer splits always blocks. // // The scoreboard tracks data deps as follows: // // ALWAYS // ASSIGN ({var} <= {cons}) // Record as generating var_DLY (independent of use of var), consumers // ASSIGN ({var} = {cons} // Record generator and consumer // Any var that is only consumed can be ignored. // Then we split into separate ALWAYS blocks. // // The scoreboard includes innards of if/else nodes also. Splitting is no // longer limited to top-level statements, we can split within if-else // blocks. We want to be able to split this: // // The optional reorder routine can optimize this: // NODEASSIGN/NODEIF/WHILE // S1: ASSIGN {v1} <= 0. // Duplicate of below // S2: ASSIGN {v1} <= {v0} // S3: IF (..., // X1: ASSIGN {v2} <= {v1} // X2: ASSIGN {v3} <= {v2} // We'd like to swap S2 and S3, and X1 and X2. // // Create a graph in split assignment order. // v3 -breakable-> v3Dly --> X2 --> v2 -brk-> v2Dly -> X1 -> v1 // Likewise on each "upper" statement vertex // v3Dly & v2Dly -> S3 -> v1 & v2 // v1 -brk-> v1Dly -> S2 -> v0 // v1Dly -> S1 -> {empty} // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Reorder.h" #include "V3Graph.h" #include "V3Stats.h" #include #include #include VL_DEFINE_DEBUG_FUNCTIONS; namespace { //###################################################################### // Support classes class SplitNodeVertex VL_NOT_FINAL : public V3GraphVertex { VL_RTTI_IMPL(SplitNodeVertex, V3GraphVertex) AstNode* const m_nodep; protected: SplitNodeVertex(V3Graph* graphp, AstNode* nodep) : V3GraphVertex{graphp} , m_nodep{nodep} {} ~SplitNodeVertex() override = default; // ACCESSORS // Do not make accessor for nodep(), It may change due to // reordering a lower block, but we don't repair it string name() const override { return cvtToHex(m_nodep) + ' ' + m_nodep->prettyTypeName(); } FileLine* fileline() const override { return nodep()->fileline(); } public: virtual AstNode* nodep() const { return m_nodep; } }; class SplitPliVertex final : public SplitNodeVertex { VL_RTTI_IMPL(SplitPliVertex, SplitNodeVertex) public: explicit SplitPliVertex(V3Graph* graphp, AstNode* nodep) : SplitNodeVertex{graphp, nodep} {} ~SplitPliVertex() override = default; string name() const override VL_MT_STABLE { return "*PLI*"; } string dotColor() const override { return "green"; } }; class SplitLogicVertex final : public SplitNodeVertex { VL_RTTI_IMPL(SplitLogicVertex, SplitNodeVertex) public: SplitLogicVertex(V3Graph* graphp, AstNode* nodep) : SplitNodeVertex{graphp, nodep} {} ~SplitLogicVertex() override = default; string dotColor() const override { return "yellow"; } }; class SplitVarStdVertex final : public SplitNodeVertex { VL_RTTI_IMPL(SplitVarStdVertex, SplitNodeVertex) public: SplitVarStdVertex(V3Graph* graphp, AstNode* nodep) : SplitNodeVertex{graphp, nodep} {} ~SplitVarStdVertex() override = default; string dotColor() const override { return "skyblue"; } }; class SplitVarPostVertex final : public SplitNodeVertex { VL_RTTI_IMPL(SplitVarPostVertex, SplitNodeVertex) public: SplitVarPostVertex(V3Graph* graphp, AstNode* nodep) : SplitNodeVertex{graphp, nodep} {} ~SplitVarPostVertex() override = default; string name() const override { return "POST "s + SplitNodeVertex::name(); } string dotColor() const override { return "CadetBlue"; } }; //###################################################################### // Edge types class SplitEdge VL_NOT_FINAL : public V3GraphEdge { VL_RTTI_IMPL(SplitEdge, V3GraphEdge) uint32_t m_ignoreInStep = 0; // Step number that if set to, causes this edge to be ignored static uint32_t s_stepNum; // Global step number protected: static constexpr int WEIGHT_NORMAL = 10; SplitEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight, bool cutable = CUTABLE) : V3GraphEdge{graphp, fromp, top, weight, cutable} {} ~SplitEdge() override = default; public: // Iterator for graph functions static void incrementStep() { ++s_stepNum; } bool ignoreThisStep() const { return m_ignoreInStep == s_stepNum; } void setIgnoreThisStep() { m_ignoreInStep = s_stepNum; } virtual bool followScoreboard() const = 0; static bool followScoreboard(const V3GraphEdge* edgep) { const SplitEdge* const oedgep = static_cast(edgep); if (oedgep->ignoreThisStep()) return false; return oedgep->followScoreboard(); } static bool followCyclic(const V3GraphEdge* edgep) { const SplitEdge* const oedgep = static_cast(edgep); return (!oedgep->ignoreThisStep()); } string dotStyle() const override { return ignoreThisStep() ? "dotted" : V3GraphEdge::dotStyle(); } }; uint32_t SplitEdge::s_stepNum = 0; class SplitPostEdge final : public SplitEdge { VL_RTTI_IMPL(SplitPostEdge, SplitEdge) public: SplitPostEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {} ~SplitPostEdge() override = default; bool followScoreboard() const override { return false; } string dotColor() const override { return "khaki"; } }; class SplitLVEdge final : public SplitEdge { VL_RTTI_IMPL(SplitLVEdge, SplitEdge) public: SplitLVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {} ~SplitLVEdge() override = default; bool followScoreboard() const override { return true; } string dotColor() const override { return "yellowGreen"; } }; class SplitRVEdge final : public SplitEdge { VL_RTTI_IMPL(SplitRVEdge, SplitEdge) public: SplitRVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {} ~SplitRVEdge() override = default; bool followScoreboard() const override { return true; } string dotColor() const override { return "green"; } }; class SplitScorebdEdge final : public SplitEdge { VL_RTTI_IMPL(SplitScorebdEdge, SplitEdge) public: SplitScorebdEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL} {} ~SplitScorebdEdge() override = default; bool followScoreboard() const override { return true; } string dotColor() const override { return "blue"; } }; class SplitStrictEdge final : public SplitEdge { VL_RTTI_IMPL(SplitStrictEdge, SplitEdge) // A strict order, based on the original statement order in the graph // The only non-cutable edge type public: SplitStrictEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : SplitEdge{graphp, fromp, top, WEIGHT_NORMAL, NOT_CUTABLE} {} ~SplitStrictEdge() override = default; bool followScoreboard() const override { return true; } string dotColor() const override { return "blue"; } }; //###################################################################### // Split class functions class SplitReorderBaseVisitor VL_NOT_FINAL : public VNVisitor { // NODE STATE // AstVarScope::user1p -> Var SplitNodeVertex* for usage var, 0=not set yet // AstVarScope::user2p -> Var SplitNodeVertex* for delayed assignment var, 0=not set yet // Ast*::user3p -> Statement SplitLogicVertex* (temporary only) // Ast*::user4 -> Current ordering number (reorderBlock usage) const VNUser1InUse m_inuser1; const VNUser2InUse m_inuser2; const VNUser3InUse m_inuser3; const VNUser4InUse m_inuser4; protected: // STATE string m_noReorderWhy; // Reason we can't reorder std::vector m_stmtStackps; // Current statements being tracked SplitPliVertex* m_pliVertexp; // Element specifying PLI ordering V3Graph m_graph; // Scoreboard of var usages/dependencies bool m_inDly; // Inside ASSIGNDLY // CONSTRUCTORS public: SplitReorderBaseVisitor() { scoreboardClear(); } ~SplitReorderBaseVisitor() override = default; // METHODS protected: void scoreboardClear() { // VV***** We reset user1p() and user2p on each block!!! m_inDly = false; m_graph.clear(); m_stmtStackps.clear(); m_pliVertexp = nullptr; m_noReorderWhy = ""; AstNode::user1ClearTree(); AstNode::user2ClearTree(); AstNode::user3ClearTree(); AstNode::user4ClearTree(); } private: void scoreboardPli(AstNode* nodep) { // Order all PLI statements with other PLI statements // This ensures $display's and such remain in proper order // We don't prevent splitting out other non-pli statements, however. if (!m_pliVertexp) { m_pliVertexp = new SplitPliVertex{&m_graph, nodep}; // m_graph.clear() will delete it } for (const auto& vtxp : m_stmtStackps) { // Both ways... new SplitScorebdEdge{&m_graph, vtxp, m_pliVertexp}; new SplitScorebdEdge{&m_graph, m_pliVertexp, vtxp}; } } void scoreboardPushStmt(AstNode* nodep) { // UINFO(9, " push " << nodep); SplitLogicVertex* const vertexp = new SplitLogicVertex{&m_graph, nodep}; m_stmtStackps.push_back(vertexp); UASSERT_OBJ(!nodep->user3p(), nodep, "user3p should not be used; cleared in processBlock"); nodep->user3p(vertexp); } void scoreboardPopStmt() { // UINFO(9, " pop"); UASSERT(!m_stmtStackps.empty(), "Stack underflow"); m_stmtStackps.pop_back(); } protected: void scanBlock(AstNode* nodep) { // Iterate across current block, making the scoreboard for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) { scoreboardPushStmt(nextp); iterate(nextp); scoreboardPopStmt(); } } void pruneDepsOnInputs() { for (V3GraphVertex& vertex : m_graph.vertices()) { if (vertex.outEmpty() && vertex.is()) { if (debug() >= 9) { const SplitVarStdVertex& sVtx = static_cast(vertex); UINFO(0, "Will prune deps on var " << sVtx.nodep()); sVtx.nodep()->dumpTree("- "); } for (V3GraphEdge& edge : vertex.inEdges()) { SplitEdge& oedge = static_cast(edge); oedge.setIgnoreThisStep(); } } } } virtual void makeRvalueEdges(SplitVarStdVertex* vstdp) = 0; // VISITORS void visit(AstAlways* nodep) override = 0; void visit(AstNodeIf* nodep) override = 0; // We don't do AstLoop, due to the standard question of what is before vs. after void visit(AstExprStmt* nodep) override { VL_RESTORER(m_inDly); m_inDly = false; iterateChildren(nodep); } void visit(AstAssignDly* nodep) override { UINFO(4, " ASSIGNDLY " << nodep); iterate(nodep->rhsp()); VL_RESTORER(m_inDly); m_inDly = true; iterate(nodep->lhsp()); } void visit(AstVarRef* nodep) override { if (!m_stmtStackps.empty()) { AstVarScope* const vscp = nodep->varScopep(); UASSERT_OBJ(vscp, nodep, "Not linked"); if (!nodep->varp()->isConst()) { // Constant lookups can be ignored // --- // NOTE: Formerly at this location we would avoid // splitting or reordering if the variable is public. // // However, it should be perfectly safe to split an // always block containing a public variable. // Neither operation should perturb PLI's view of // the variable. // // Former code: // // if (nodep->varp()->isSigPublic()) { // // Public signals shouldn't be changed, // // pli code might be messing with them // scoreboardPli(nodep); // } // --- // Create vertexes for variable if (!vscp->user1p()) { SplitVarStdVertex* const vstdp = new SplitVarStdVertex{&m_graph, vscp}; vscp->user1p(vstdp); } SplitVarStdVertex* const vstdp = reinterpret_cast(vscp->user1p()); // SPEEDUP: We add duplicate edges, that should be fixed if (m_inDly && nodep->access().isWriteOrRW()) { UINFO(4, " VARREFDLY: " << nodep); // Delayed variable is different from non-delayed variable if (!vscp->user2p()) { SplitVarPostVertex* const vpostp = new SplitVarPostVertex{&m_graph, vscp}; vscp->user2p(vpostp); new SplitPostEdge{&m_graph, vstdp, vpostp}; } SplitVarPostVertex* const vpostp = reinterpret_cast(vscp->user2p()); // Add edges for (SplitLogicVertex* vxp : m_stmtStackps) { new SplitLVEdge{&m_graph, vpostp, vxp}; } } else { // Nondelayed assignment if (nodep->access().isWriteOrRW()) { // Non-delay; need to maintain existing ordering // with all consumers of the signal UINFO(4, " VARREFLV: " << nodep); for (SplitLogicVertex* ivxp : m_stmtStackps) { new SplitLVEdge{&m_graph, vstdp, ivxp}; } } else { UINFO(4, " VARREF: " << nodep); makeRvalueEdges(vstdp); } } } } } void visit(AstJumpGo* nodep) override { // Jumps will disable reordering at all levels // This is overly pessimistic; we could treat jumps as barriers, and // reorder everything between jumps/labels, however jumps are rare // in always, so the performance gain probably isn't worth the work. UINFO(9, " NoReordering " << nodep); m_noReorderWhy = "JumpGo"; iterateChildren(nodep); } //-------------------- // Default void visit(AstNode* nodep) override { // **** SPECIAL default type that sets PLI_ORDERING if (!m_stmtStackps.empty() && !nodep->isPure()) { UINFO(9, " NotSplittable " << nodep); scoreboardPli(nodep); } if (nodep->isTimingControl()) { UINFO(9, " NoReordering " << nodep); m_noReorderWhy = "TimingControl"; } iterateChildren(nodep); } private: VL_UNCOPYABLE(SplitReorderBaseVisitor); }; class ReorderVisitor final : public SplitReorderBaseVisitor { // CONSTRUCTORS public: explicit ReorderVisitor(AstNetlist* nodep) { iterate(nodep); } ~ReorderVisitor() override = default; // METHODS protected: void makeRvalueEdges(SplitVarStdVertex* vstdp) override { for (SplitLogicVertex* vxp : m_stmtStackps) new SplitRVEdge{&m_graph, vxp, vstdp}; } void cleanupBlockGraph(AstNode* nodep) { // Transform the graph into what we need UINFO(5, "ReorderBlock " << nodep); m_graph.removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue); if (dumpGraphLevel() >= 9) m_graph.dumpDotFilePrefixed("reorderg_nodup", false); // Mark all the logic for this step // Vertex::m_user begin: true indicates logic for this step m_graph.userClearVertices(); for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) { SplitLogicVertex* const vvertexp = reinterpret_cast(nextp->user3p()); vvertexp->user(true); } // If a var vertex has only inputs, it's a input-only node, // and can be ignored for coloring **this block only** SplitEdge::incrementStep(); pruneDepsOnInputs(); // For reordering this single block only, mark all logic // vertexes not involved with this step as unimportant for (V3GraphVertex& vertex : m_graph.vertices()) { if (!vertex.user()) { if (vertex.is()) { for (V3GraphEdge& edge : vertex.inEdges()) { SplitEdge& oedge = static_cast(edge); oedge.setIgnoreThisStep(); } for (V3GraphEdge& edge : vertex.outEdges()) { SplitEdge& oedge = static_cast(edge); oedge.setIgnoreThisStep(); } } } } // Weak coloring to determine what needs to remain in order // This follows all step-relevant edges excluding PostEdges, which are done later m_graph.weaklyConnected(&SplitEdge::followScoreboard); // Add hard orderings between all nodes of same color, in the order they appeared std::unordered_map lastOfColor; for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) { SplitLogicVertex* const vvertexp = reinterpret_cast(nextp->user3p()); const uint32_t color = vvertexp->color(); UASSERT_OBJ(color, nextp, "No node color assigned"); if (lastOfColor[color]) { new SplitStrictEdge{&m_graph, lastOfColor[color], vvertexp}; } lastOfColor[color] = vvertexp; } // And a real ordering to get the statements into something reasonable // We don't care if there's cutable violations here... // Non-cutable violations should be impossible; as those edges are program-order if (dumpGraphLevel() >= 9) m_graph.dumpDotFilePrefixed("splitg_preo", false); m_graph.acyclic(&SplitEdge::followCyclic); m_graph.rank(&SplitEdge::followCyclic); // Or order(), but that's more expensive if (dumpGraphLevel() >= 9) m_graph.dumpDotFilePrefixed("splitg_opt", false); } void reorderBlock(AstNode* nodep) { // Reorder statements in the completed graph // Map the rank numbers into nodes they associate with std::multimap rankMap; int currOrder = 0; // Existing sequence number of assignment for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) { const SplitLogicVertex* const vvertexp = reinterpret_cast(nextp->user3p()); rankMap.emplace(vvertexp->rank(), nextp); nextp->user4(++currOrder); // Record current ordering } // Is the current ordering OK? bool leaveAlone = true; int newOrder = 0; // New sequence number of assignment for (auto it = rankMap.cbegin(); it != rankMap.cend(); ++it) { const AstNode* const nextp = it->second; if (++newOrder != nextp->user4()) leaveAlone = false; } if (leaveAlone) { UINFO(6, " No changes"); } else { VNRelinker replaceHandle; // Where to add the list AstNode* newListp = nullptr; for (auto it = rankMap.cbegin(); it != rankMap.cend(); ++it) { AstNode* const nextp = it->second; UINFO(6, " New order: " << nextp); if (nextp == nodep) { nodep->unlinkFrBack(&replaceHandle); } else { nextp->unlinkFrBack(); } if (newListp) { newListp = newListp->addNext(nextp); } else { newListp = nextp; } } replaceHandle.relink(newListp); } } void processBlock(AstNode* nodep) { if (!nodep) return; // Empty lists are ignorable // Pass the first node in a list of block items, we'll process them // Check there's >= 2 sub statements, else nothing to analyze // Save recursion state AstNode* firstp = nodep; // We may reorder, and nodep is no longer first. void* const oldBlockUser3 = nodep->user3p(); // May be overloaded in below loop, save it nodep->user3p(nullptr); UASSERT_OBJ(nodep->firstAbovep(), nodep, "Node passed is in next list; should have processed all list at once"); // Process it if (!nodep->nextp()) { // Just one, so can't reorder. Just look for more blocks/statements. iterate(nodep); } else { UINFO(9, " processBlock " << nodep); // Process block and followers scanBlock(nodep); if (m_noReorderWhy != "") { // Jump or something nasty UINFO(9, " NoReorderBlock because " << m_noReorderWhy); } else { // Reorder statements in this block cleanupBlockGraph(nodep); reorderBlock(nodep); // Delete old vertexes and edges only applying to this block // First, walk back to first in list while (firstp->backp()->nextp() == firstp) firstp = firstp->backp(); for (AstNode* nextp = firstp; nextp; nextp = nextp->nextp()) { SplitLogicVertex* const vvertexp = reinterpret_cast(nextp->user3p()); vvertexp->unlinkDelete(&m_graph); } } } // Again, nodep may no longer be first. firstp->user3p(oldBlockUser3); } void visit(AstAlways* nodep) override { UINFO(4, " ALW " << nodep); UINFOTREE(9, nodep, "", "alwIn:"); scoreboardClear(); processBlock(nodep->stmtsp()); UINFOTREE(9, nodep, "", "alwOut"); } void visit(AstNodeIf* nodep) override { UINFO(4, " IF " << nodep); iterateAndNextNull(nodep->condp()); processBlock(nodep->thensp()); processBlock(nodep->elsesp()); } private: VL_UNCOPYABLE(ReorderVisitor); }; } // namespace //###################################################################### // V3Reorder class functions void V3Reorder::reorderAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { ReorderVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("reorder", 0, dumpTreeEitherLevel() >= 3); }