// -*- 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 // shwdow variables use by non blocking assignments when possible. // For exmaple, the left side needs a shadow variable for 'b', the // right side does not: // Bad: Good: // b <= a; c <= b; // c <= b; b <= a; // // 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 "V3EmitV.h" #include "V3Graph.h" #include "V3Stats.h" #include #include #include VL_DEFINE_DEBUG_FUNCTIONS; namespace { //###################################################################### // Support classes class ReorderNodeVertex VL_NOT_FINAL : public V3GraphVertex { VL_RTTI_IMPL(ReorderNodeVertex, V3GraphVertex) AstNode* const m_nodep; protected: ReorderNodeVertex(V3Graph* graphp, AstNode* nodep) : V3GraphVertex{graphp} , m_nodep{nodep} {} ~ReorderNodeVertex() override = default; // ACCESSORS // Do not make accessor for nodep(), It may change due to // reordering a lower block, but we don't repair it std::string name() const override { std::string str = cvtToHex(m_nodep) + '\n'; if (AstVarScope* const vscp = VN_CAST(m_nodep, VarScope)) { str += vscp->prettyName(); } else { str += V3EmitV::debugVerilogForTree(m_nodep); str = VString::quoteBackslash(str); str = VString::quoteAny(str, '"', '\\'); str = VString::replaceSubstr(str, "\n", "\\l"); } return str; } FileLine* fileline() const override { return nodep()->fileline(); } std::string dotShape() const override { return VN_IS(m_nodep, VarScope) ? "ellipse" : "box"; } public: virtual AstNode* nodep() const { return m_nodep; } }; class ReorderImpureVertex final : public ReorderNodeVertex { VL_RTTI_IMPL(ReorderImpureVertex, ReorderNodeVertex) public: explicit ReorderImpureVertex(V3Graph* graphp, AstNode* nodep) : ReorderNodeVertex{graphp, nodep} {} ~ReorderImpureVertex() override = default; string name() const override VL_MT_STABLE { return "*IMPURE*"; } string dotColor() const override { return "red"; } }; class ReorderLogicVertex final : public ReorderNodeVertex { VL_RTTI_IMPL(ReorderLogicVertex, ReorderNodeVertex) public: ReorderLogicVertex(V3Graph* graphp, AstNode* nodep) : ReorderNodeVertex{graphp, nodep} {} ~ReorderLogicVertex() override = default; string dotColor() const override { return "black"; } }; class ReorderVarStdVertex final : public ReorderNodeVertex { VL_RTTI_IMPL(ReorderVarStdVertex, ReorderNodeVertex) public: ReorderVarStdVertex(V3Graph* graphp, AstVarScope* nodep) : ReorderNodeVertex{graphp, nodep} {} ~ReorderVarStdVertex() override = default; string dotColor() const override { return "blue"; } }; class ReorderVarPostVertex final : public ReorderNodeVertex { VL_RTTI_IMPL(ReorderVarPostVertex, ReorderNodeVertex) public: ReorderVarPostVertex(V3Graph* graphp, AstVarScope* nodep) : ReorderNodeVertex{graphp, nodep} {} ~ReorderVarPostVertex() override = default; string name() const override { return "POST "s + ReorderNodeVertex::name(); } string dotColor() const override { return "green"; } }; //###################################################################### // Edge types class ReorderEdge VL_NOT_FINAL : public V3GraphEdge { VL_RTTI_IMPL(ReorderEdge, 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; ReorderEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, bool cutable) : V3GraphEdge{graphp, fromp, top, WEIGHT_NORMAL, cutable} {} ~ReorderEdge() override = default; virtual bool followScoreboard() const = 0; std::string dotStyle() const override { return ignoreThisStep() ? "dotted" : V3GraphEdge::dotStyle(); } public: // Iterator for graph functions static void incrementStep() { ++s_stepNum; } bool ignoreThisStep() const { return m_ignoreInStep == s_stepNum; } void setIgnoreThisStep() { m_ignoreInStep = s_stepNum; } static bool followScoreboard(const V3GraphEdge* edgep) { const ReorderEdge& edge = *edgep->as(); return !edge.ignoreThisStep() && edge.followScoreboard(); } static bool followCyclic(const V3GraphEdge* edgep) { const ReorderEdge& edge = *edgep->as(); return !edge.ignoreThisStep(); } }; uint32_t ReorderEdge::s_stepNum = 0; class ReorderPostEdge final : public ReorderEdge { VL_RTTI_IMPL(ReorderPostEdge, ReorderEdge) public: ReorderPostEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : ReorderEdge{graphp, fromp, top, CUTABLE} {} ~ReorderPostEdge() override = default; bool followScoreboard() const override { return false; } string dotColor() const override { return "khaki"; } }; class ReorderLVEdge final : public ReorderEdge { VL_RTTI_IMPL(ReorderLVEdge, ReorderEdge) public: ReorderLVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : ReorderEdge{graphp, fromp, top, CUTABLE} {} ~ReorderLVEdge() override = default; bool followScoreboard() const override { return true; } string dotColor() const override { return "yellowGreen"; } }; class ReorderRVEdge final : public ReorderEdge { VL_RTTI_IMPL(ReorderRVEdge, ReorderEdge) public: ReorderRVEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : ReorderEdge{graphp, fromp, top, CUTABLE} {} ~ReorderRVEdge() override = default; bool followScoreboard() const override { return true; } string dotColor() const override { return "green"; } }; class ReorderScorebdEdge final : public ReorderEdge { VL_RTTI_IMPL(ReorderScorebdEdge, ReorderEdge) public: ReorderScorebdEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : ReorderEdge{graphp, fromp, top, CUTABLE} {} ~ReorderScorebdEdge() override = default; bool followScoreboard() const override { return true; } string dotColor() const override { return "blue"; } }; class ReorderStrictEdge final : public ReorderEdge { VL_RTTI_IMPL(ReorderStrictEdge, ReorderEdge) // A strict order, based on the original statement order in the graph // The only non-cutable edge type public: ReorderStrictEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) : ReorderEdge{graphp, fromp, top, NOT_CUTABLE} {} ~ReorderStrictEdge() override = default; bool followScoreboard() const override { return true; } string dotColor() const override { return "blue"; } }; //###################################################################### // Reorder class functions class ReorderVisitor final : public VNVisitor { // NODE STATE - Only under AstAlways // AstVarScope::user1p -> Var ReorderVarStdVertex* for usage var, 0=not set yet // AstVarScope::user2p -> Var ReorderVarPostVertex* for delayed assignment var, 0=not set yet // Ast*::user3p -> Statement ReorderLogicVertex* (temporary only) // Ast*::user4 -> Current ordering number (reorderBlock usage) // STATE V3Graph* m_graphp = nullptr; // Scoreboard of var usages/dependencies ReorderImpureVertex* m_impureVtxp = nullptr; // Element specifying PLI ordering bool m_inDly = false; // Inside ASSIGNDLY const char* m_noReorderWhy = nullptr; // Reason we can't reorder std::vector m_stmtStackps; // Current statements being tracked // METHODS void cleanupBlockGraph(AstNode* nodep) { // Transform the graph into what we need UINFO(5, "ReorderBlock " << nodep); // Simplify graph by removing redundant edges m_graphp->removeRedundantEdgesMax(&V3GraphEdge::followAlwaysTrue); if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("reorderg_nodup", false); // Mark all the logic for this step by setting Vertex::user() to true m_graphp->userClearVertices(); for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) { nextp->user3u().to()->user(true); } // New step ReorderEdge::incrementStep(); // If a var vertex has only inputs, it's a input-only node, // and can be ignored for coloring **this block only** for (V3GraphVertex& vtx : m_graphp->vertices()) { if (!vtx.outEmpty()) continue; if (!vtx.is()) continue; for (V3GraphEdge& edge : vtx.inEdges()) edge.as()->setIgnoreThisStep(); } // For reordering this single block only, mark all logic // vertexes not involved with this step as unimportant for (V3GraphVertex& vertex : m_graphp->vertices()) { if (vertex.user()) continue; if (!vertex.is()) continue; for (V3GraphEdge& edge : vertex.inEdges()) edge.as()->setIgnoreThisStep(); for (V3GraphEdge& edge : vertex.outEdges()) edge.as()->setIgnoreThisStep(); } // Weak coloring to determine what needs to remain in order // This follows all step-relevant edges excluding PostEdges, which are done later m_graphp->weaklyConnected(&ReorderEdge::followScoreboard); // Add hard orderings between all nodes of same color, in the order they appeared std::unordered_map lastOfColor; for (AstNode* currp = nodep; currp; currp = currp->nextp()) { ReorderLogicVertex* const vtxp = currp->user3u().to(); const uint32_t color = vtxp->color(); UASSERT_OBJ(color, currp, "No node color assigned"); if (lastOfColor[color]) new ReorderStrictEdge{m_graphp, lastOfColor[color], vtxp}; lastOfColor[color] = vtxp; } // 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_graphp->dumpDotFilePrefixed("reorderg_pre", false); m_graphp->acyclic(&ReorderEdge::followCyclic); m_graphp->rank(&ReorderEdge::followCyclic); // Or order(), but that's more expensive if (dumpGraphLevel() >= 9) m_graphp->dumpDotFilePrefixed("reorderg_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* currp = nodep; currp; currp = currp->nextp()) { const ReorderLogicVertex* const vtxp = currp->user3u().to(); rankMap.emplace(vtxp->rank(), currp); currp->user4(++currOrder); // Record current ordering } // Is the current ordering OK? bool leaveAlone = true; int newOrder = 0; // New sequence number of assignment for (const auto& item : rankMap) { const AstNode* const nextp = item.second; if (++newOrder != nextp->user4()) leaveAlone = false; } if (leaveAlone) { UINFO(6, " No changes"); return; } VNRelinker replaceHandle; // Where to add the list AstNode* newListp = nullptr; for (const auto& item : rankMap) { AstNode* const nextp = item.second; UINFO(6, " New order: " << nextp); nextp->unlinkFrBack(nextp == nodep ? &replaceHandle : nullptr); newListp = AstNode::addNext(newListp, nextp); } replaceHandle.relink(newListp); } void processBlock(AstNode* nodep) { if (m_noReorderWhy) return; // Empty lists are ignorable if (!nodep) return; UASSERT_OBJ(nodep->firstAbovep(), nodep, "Node passed is in not head of list"); UASSERT_OBJ(!nodep->user3p(), nodep, "Should not have a logic vertex"); // It nothing to reorder with, just iterate if (!nodep->nextp()) { iterate(nodep); return; } // Process it UINFO(9, " processBlock " << nodep); // Iterate across current block, making the scoreboard for (AstNode* currp = nodep; currp; currp = currp->nextp()) { // Create the logic vertex for this statement UASSERT_OBJ(!currp->user3p(), currp, "user3p should not be set"); ReorderLogicVertex* const vtxp = new ReorderLogicVertex{m_graphp, currp}; currp->user3p(vtxp); // Visit the statement - this can recursively reorder sub statements m_stmtStackps.push_back(vtxp); iterate(currp); m_stmtStackps.pop_back(); } if (m_noReorderWhy) { // Jump or something nasty UINFO(9, " NoReorderBlock because " << m_noReorderWhy); return; } // Reorder statements in this block cleanupBlockGraph(nodep); reorderBlock(nodep); // 'nodep' might no longer be the head of the list, rewind while (nodep->backp()->nextp() == nodep) nodep = nodep->backp(); // Delete vertexes and edges only applying to this block for (AstNode* currp = nodep; currp; currp = currp->nextp()) { currp->user3u().to()->unlinkDelete(m_graphp); currp->user3p(nullptr); } } // VISITORS void visit(AstAlways* nodep) override { UASSERT_OBJ(!m_graphp, nodep, "AstAlways should not nest"); VL_RESTORER(m_graphp); VL_RESTORER(m_impureVtxp); VL_RESTORER(m_inDly); VL_RESTORER(m_noReorderWhy); V3Graph graph; m_graphp = &graph; m_impureVtxp = nullptr; m_inDly = false; m_noReorderWhy = nullptr; const VNUser1InUse user1InUse; const VNUser2InUse user2InUse; const VNUser3InUse user3InUse; const VNUser4InUse user4InUse; UINFOTREE(9, nodep, "", "alwIn:"); processBlock(nodep->stmtsp()); UINFOTREE(9, nodep, "", "alwOut"); m_stmtStackps.clear(); } void visit(AstNodeIf* nodep) override { if (!m_graphp || m_noReorderWhy) return; iterateAndNextNull(nodep->condp()); processBlock(nodep->thensp()); processBlock(nodep->elsesp()); } void visit(AstJumpGo* nodep) override { if (!m_graphp || m_noReorderWhy) return; m_noReorderWhy = "JumpGo"; iterateChildren(nodep); } void visit(AstExprStmt* nodep) override { if (!m_graphp || m_noReorderWhy) return; VL_RESTORER(m_inDly); m_inDly = false; iterateChildren(nodep); } void visit(AstAssignDly* nodep) override { if (!m_graphp || m_noReorderWhy) return; iterate(nodep->rhsp()); VL_RESTORER(m_inDly); m_inDly = true; iterate(nodep->lhsp()); } void visit(AstVarRef* nodep) override { if (!m_graphp || m_noReorderWhy) return; if (m_stmtStackps.empty()) return; // Reads of constants can be ignored - TODO: This should be "constexpr", not run-time const if (nodep->varp()->isConst()) return; // SPEEDUP: We add duplicate edges, that should be fixed AstVarScope* const vscp = nodep->varScopep(); // Create vertexes for variable if (!vscp->user1p()) vscp->user1p(new ReorderVarStdVertex{m_graphp, vscp}); ReorderVarStdVertex* const vstdp = vscp->user1u().to(); // Variable is read if (nodep->access().isReadOnly()) { for (ReorderLogicVertex* const vtxp : m_stmtStackps) { new ReorderRVEdge{m_graphp, vtxp, vstdp}; } return; } // Variable is written, not NBA if (!m_inDly) { for (ReorderLogicVertex* const vtxp : m_stmtStackps) { new ReorderLVEdge{m_graphp, vstdp, vtxp}; } return; } // Variable is written by NBA if (!vscp->user2p()) { ReorderVarPostVertex* const vpostp = new ReorderVarPostVertex{m_graphp, vscp}; vscp->user2p(vpostp); new ReorderPostEdge{m_graphp, vstdp, vpostp}; } ReorderVarPostVertex* const vpostp = vscp->user2u().to(); for (ReorderLogicVertex* const vtxp : m_stmtStackps) { new ReorderLVEdge{m_graphp, vpostp, vtxp}; } } void visit(AstNode* nodep) override { // Outside AstAlways, just descend if (!m_graphp) { iterateChildren(nodep); return; } // Early exit if decided not to reorder if (m_noReorderWhy) return; // Timing control prevents reordering if (nodep->isTimingControl()) { m_noReorderWhy = "TimingControl"; return; } // Order all impure statements with other impure statements if (!nodep->isPure()) { if (!m_impureVtxp) m_impureVtxp = new ReorderImpureVertex{m_graphp, nodep}; // This edge is only used to find weakly connected components, so one edge is enough for (ReorderLogicVertex* const vtxp : m_stmtStackps) { new ReorderScorebdEdge{m_graphp, m_impureVtxp, vtxp}; } } iterateChildren(nodep); } // CONSTRUCTORS public: explicit ReorderVisitor(AstNetlist* nodep) { iterate(nodep); } ~ReorderVisitor() override = default; }; } // namespace //###################################################################### // V3Reorder class functions void V3Reorder::reorderAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { ReorderVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("reorder", 0, dumpTreeEitherLevel() >= 3); }