From 197f11044ee1065f60ffbaf17190c29b8cbf3a3a Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 1 Feb 2026 05:09:40 +0000 Subject: [PATCH] Internals: Split reorder transfrom from V3Split to V3Reorder (#6976) --- src/CMakeLists.txt | 2 + src/Makefile_obj.in | 1 + src/V3Reorder.cpp | 607 ++++++++++++++++++++++++++++++++++++++++++++ src/V3Reorder.h | 32 +++ src/V3Split.cpp | 217 +--------------- src/V3Split.h | 3 +- src/Verilator.cpp | 5 +- 7 files changed, 654 insertions(+), 213 deletions(-) create mode 100644 src/V3Reorder.cpp create mode 100644 src/V3Reorder.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b64ddd13..de37df5db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -159,6 +159,7 @@ set(HEADERS V3RandSequence.h V3Randomize.h V3Reloop.h + V3Reorder.h V3Rtti.h V3Sampled.h V3Sched.h @@ -325,6 +326,7 @@ set(COMMON_SOURCES V3RandSequence.cpp V3Randomize.cpp V3Reloop.cpp + V3Reorder.cpp V3Sampled.cpp V3Sched.cpp V3SchedAcyclic.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index c03334e37..ad6f2387f 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -313,6 +313,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3RandSequence.o \ V3Randomize.o \ V3Reloop.o \ + V3Reorder.o \ V3Sampled.o \ V3Sched.o \ V3SchedAcyclic.o \ diff --git a/src/V3Reorder.cpp b/src/V3Reorder.cpp new file mode 100644 index 000000000..c4bc643a7 --- /dev/null +++ b/src/V3Reorder.cpp @@ -0,0 +1,607 @@ +// -*- 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); +} diff --git a/src/V3Reorder.h b/src/V3Reorder.h new file mode 100644 index 000000000..8583fc6e6 --- /dev/null +++ b/src/V3Reorder.h @@ -0,0 +1,32 @@ +// -*- 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 +// +//************************************************************************* + +#ifndef VERILATOR_V3REORDER_H_ +#define VERILATOR_V3REORDER_H_ + +#include "config_build.h" +#include "verilatedos.h" + +class AstNetlist; + +//============================================================================ + +class V3Reorder final { +public: + static void reorderAll(AstNetlist* nodep) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/V3Split.cpp b/src/V3Split.cpp index bef5759ad..c3c835501 100644 --- a/src/V3Split.cpp +++ b/src/V3Split.cpp @@ -13,17 +13,12 @@ // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* -// V3Split implements two separate transformations: -// splitAlwaysAll() splits large always blocks into smaller always blocks +// V3Split transformation: +// +// splitAll() splits large always blocks into smaller always blocks // when possible (but does not change the order of statements relative // to one another.) // -// splitReorderAll() reorders statements within individual blocks -// to avoid delay vars when possible. It no longer splits always blocks. -// -// Both use a common base class, and common graph-building code to reflect -// data dependencies within an always block (the "scoreboard".) -// // The scoreboard tracks data deps as follows: // // ALWAYS @@ -56,25 +51,6 @@ // better. Later modules (V3Gate, V3Order) run faster if they aren't // handling enormous blocks with long lists of inputs and outputs. // -// Furthermore, 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} -// Multiple assignments to the same variable must remain in order -// -// Also vars must not be "public" and we also scoreboard nodep->isPure() -// //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT @@ -90,6 +66,8 @@ VL_DEFINE_DEBUG_FUNCTIONS; +namespace { + //###################################################################### // Support classes @@ -444,182 +422,6 @@ 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); -}; - using ColorSet = std::unordered_set; using AlwaysVec = std::vector; @@ -1002,15 +804,12 @@ private: VL_UNCOPYABLE(SplitVisitor); }; +} //namespace + //###################################################################### // Split class functions -void V3Split::splitReorderAll(AstNetlist* nodep) { - UINFO(2, __FUNCTION__ << ":"); - { ReorderVisitor{nodep}; } // Destruct before checking - V3Global::dumpCheckGlobalTree("reorder", 0, dumpTreeEitherLevel() >= 3); -} -void V3Split::splitAlwaysAll(AstNetlist* nodep) { +void V3Split::splitAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { SplitVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("split", 0, dumpTreeEitherLevel() >= 3); diff --git a/src/V3Split.h b/src/V3Split.h index 13eb98193..6212d8c55 100644 --- a/src/V3Split.h +++ b/src/V3Split.h @@ -26,8 +26,7 @@ class AstNetlist; class V3Split final { public: - static void splitReorderAll(AstNetlist* nodep) VL_MT_DISABLED; - static void splitAlwaysAll(AstNetlist* nodep) VL_MT_DISABLED; + static void splitAll(AstNetlist* nodep) VL_MT_DISABLED; }; #endif // Guard diff --git a/src/Verilator.cpp b/src/Verilator.cpp index b6b4a506f..50a4dcf0f 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -86,6 +86,7 @@ #include "V3RandSequence.h" #include "V3Randomize.h" #include "V3Reloop.h" +#include "V3Reorder.h" #include "V3Sampled.h" #include "V3Sched.h" #include "V3Scope.h" @@ -401,7 +402,7 @@ static void process() { V3Active::activeAll(v3Global.rootp()); // Split single ALWAYS blocks into multiple blocks for better ordering chances - if (v3Global.opt.fSplit()) V3Split::splitAlwaysAll(v3Global.rootp()); + if (v3Global.opt.fSplit()) V3Split::splitAll(v3Global.rootp()); V3SplitAs::splitAsAll(v3Global.rootp()); // Create tracing sample points, before we start eliminating signals @@ -435,7 +436,7 @@ static void process() { V3Dead::deadifyAllScoped(v3Global.rootp()); // Reorder assignments in pipelined blocks - if (v3Global.opt.fReorder()) V3Split::splitReorderAll(v3Global.rootp()); + if (v3Global.opt.fReorder()) V3Reorder::reorderAll(v3Global.rootp()); if (v3Global.opt.timing().isSetTrue()) { // Convert AST for timing if requested