mirror of
https://github.com/verilator/verilator.git
synced 2026-10-07 02:23:19 +02:00
Internals: Refactor MT scheduling (#8012)
Prep for fixing test added in #7913. This is a large scale no functional change refactor, however, MT output is perturbed as tied scores will be broken differently due to ordering changes (still deterministic). Split multi-threaded scheduling out of the monolithic V3OrderParallel.cpp, into relatively independent parts, simplify the data structures, and drop redundant or unused code. New translation units: - V3OrderMTaskGraph.h/.cpp: OrderMTaskGraph, the graph of LogicMTask vertices and MTaskEdge edges. LogicMTask and MTaskEdge no longer depend on the coarsening algorithm's merge candidate types; per-algorithm auxiliary data is attached externally via the vertex and edge user pointers. - V3OrderMTaskFixHazards.cpp: data hazard fixup, was FixDataHazards. - V3OrderMTaskContraction.cpp: graph coarsening, was Partitioner together with PropagateCp and the merge candidate types. - V3OrderParallel.cpp: now just the partitioning driver and ExecMTask graph construction. Data structure changes: - Delete V3Scoreboard.h/.cpp. The generic template had a single user, now a file-local MergeCandidateScoreboard in V3OrderMTaskContraction.cpp. - Merge candidates are now MergeCandidate/SiblingMC/EdgeMC, distinguished by a bit in the candidate id rather than by a vtable, and allocated by the scoreboard, which owns their lifetime. This removes the multiple inheritance previously used by MTaskEdge. Move `hashGraphDebug` which prints the hash of a graph's shape for debugging to generic `V3Graph::hashGraphDebug`. Removed (can be added back later): - Unnecesasry self tests that force special data stucture requirements. - Per stage --stats output under --debug. (Final figures still reported.) - Various debug dumps
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: OrderMTask graph construction
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
||||
|
||||
#include "V3OrderMTaskGraph.h"
|
||||
|
||||
#include "V3InstrCount.h"
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
//######################################################################
|
||||
// OrderMTaskGraph
|
||||
|
||||
OrderMTaskGraph::OrderMTaskGraph(OrderMoveGraph& moveGraph)
|
||||
: m_moveGraph{moveGraph}
|
||||
, m_entryp{new LogicMTask{*this, nullptr}}
|
||||
, m_exitp{new LogicMTask{*this, nullptr}} {}
|
||||
|
||||
uint64_t OrderMTaskGraph::totalCost() const {
|
||||
uint64_t cost = 0;
|
||||
for (const V3GraphVertex& vtx : vertices()) cost += static_cast<const LogicMTask&>(vtx).cost();
|
||||
return cost;
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// LogicMTask
|
||||
|
||||
uint32_t LogicMTask::s_nextId = 1; // Start at 1, so that 0 indicates no mtask.
|
||||
|
||||
LogicMTask::LogicMTask(OrderMTaskGraph& graph, OrderMoveVertex* mVtxp)
|
||||
: V3GraphVertex{&graph} {
|
||||
UASSERT(s_nextId < 0xFFFFFFFFUL, "Too many LogicMTask instances");
|
||||
if (!mVtxp) return;
|
||||
m_mVertices.linkBack(mVtxp);
|
||||
if (const OrderLogicVertex* const olvp = mVtxp->logicp()) {
|
||||
m_cost += V3InstrCount::count(olvp->nodep(), true);
|
||||
}
|
||||
}
|
||||
|
||||
//######################################################################
|
||||
// OrderMTaskGraphBuilder
|
||||
|
||||
class OrderMTaskGraphBuilder final {
|
||||
// NODE STATE
|
||||
// Used by V3InstrCount::count within the LogicMTask constructor only
|
||||
const VNUser1InUse m_user1InUse;
|
||||
|
||||
// MEMBERS
|
||||
OrderMTaskGraph& m_mtaskGraph; // Output OrderMTaskGraph
|
||||
|
||||
// METHODS
|
||||
|
||||
// Predicate function to determine what OrderMoveVertex to bypass when constructing the MTask
|
||||
// graph. The OrderMoveGraph is a bipartite graph of:
|
||||
// - 1. OrderMoveVertex instances containing logic via OrderLogicVertex
|
||||
// (OrderMoveVertex::logicp() != nullptr)
|
||||
// - 2. OrderMoveVertex instances containing an (OrderVarVertex, domain) pair
|
||||
// The goal is to order the logic vertices. The second type of variable/domain vertices only
|
||||
// carry dependencies and are eventually discarded. In order to reduce the working set size,
|
||||
// we 'bypass' and not create LogicMTask vertices for some variable vertices, and instead add
|
||||
// the transitive dependencies directly, but only if adding the transitive edges directly does
|
||||
// not require more dependency edges than keeping the intermediate vertex. That is, we bypass a
|
||||
// variable vertex if fanIn * fanOut <= fanIn + fanOut. This is true if fanIn or fanOut are 1,
|
||||
// or if they are both 2. This can significantly reduce the initial size of OrderMTaskGraph.
|
||||
static bool bypassOk(OrderMoveVertex* mvtxp) {
|
||||
// Need to keep all logic vertices
|
||||
if (mvtxp->logicp()) return false;
|
||||
// Count fan-in, up to 3
|
||||
unsigned fanIn = 0;
|
||||
auto& inEdges = mvtxp->inEdges();
|
||||
for (auto it = inEdges.begin(); it != inEdges.end(); ++it) {
|
||||
if (++fanIn == 3) break;
|
||||
}
|
||||
// If fanIn no more than one, bypass
|
||||
if (fanIn <= 1) return true;
|
||||
// Count fan-out, up to 3
|
||||
unsigned fanOut = 0;
|
||||
auto& outEdges = mvtxp->outEdges();
|
||||
for (auto it = outEdges.begin(); it != outEdges.end(); ++it) {
|
||||
if (++fanOut == 3) break;
|
||||
}
|
||||
// If fan-out no more than one, bypass
|
||||
if (fanOut <= 1) return true;
|
||||
// They can only be (2, 2), (2, 3), (3, 2), (3, 3) at this point, bypass if (2, 2)
|
||||
return fanIn + fanOut == 4;
|
||||
}
|
||||
|
||||
// Add an edge to the graph, if there is not already an edge between the two vertices.
|
||||
void addEdge(LogicMTask& src, LogicMTask& dst) {
|
||||
UASSERT_OBJ(&src != &dst, &src, "Should not create self-edges");
|
||||
if (src.hasRelativeMTask(&dst)) return; // Don't create redundant edges.
|
||||
new MTaskEdge{&m_mtaskGraph, &src, &dst, 1};
|
||||
}
|
||||
|
||||
// CONSTRUCTORS
|
||||
explicit OrderMTaskGraphBuilder(OrderMTaskGraph& mtaskGraph)
|
||||
: m_mtaskGraph{mtaskGraph} {
|
||||
|
||||
// Create the LogicMTasks for each OrderMoveVertex
|
||||
for (V3GraphVertex& vtx : mtaskGraph.moveGraph().vertices()) {
|
||||
OrderMoveVertex& mVtx = static_cast<OrderMoveVertex&>(vtx);
|
||||
if (bypassOk(&mVtx)) {
|
||||
mVtx.userp(nullptr); // Set to nullptr to mark as bypassed
|
||||
} else {
|
||||
mVtx.userp(new LogicMTask{mtaskGraph, &mVtx}); // Create vertex and set userp
|
||||
}
|
||||
}
|
||||
|
||||
LogicMTask& entry = *mtaskGraph.entryp();
|
||||
LogicMTask& exit = *mtaskGraph.exitp();
|
||||
|
||||
// Create the MTask dependency edges based on the OrderMoveGraph dependencies
|
||||
for (V3GraphVertex& vtx : mtaskGraph.vertices()) {
|
||||
LogicMTask& mtask = static_cast<LogicMTask&>(vtx);
|
||||
|
||||
// Entry and exit vertices handled separately
|
||||
if (VL_UNLIKELY((&mtask == &entry) || (&mtask == &exit))) continue;
|
||||
|
||||
OrderMoveVertex::List& vertexList = mtask.vertexList();
|
||||
// At this point, there should only be one OrderMoveVertex per LogicMTask
|
||||
UASSERT_OBJ(vertexList.hasSingleElement(), &mtask, "Multiple OrderMoveVertex");
|
||||
OrderMoveVertex* const mVtxp = vertexList.frontp();
|
||||
UASSERT_OBJ(mVtxp->userp(), &mtask, "Bypassed OrderMoveVertex should not have MTask");
|
||||
|
||||
// Iterate downstream direct dependents
|
||||
for (const V3GraphEdge& dEdge : mVtxp->outEdges()) {
|
||||
V3GraphVertex* const top = dEdge.top();
|
||||
|
||||
// If the opposite end of the edge is not a bypassed vertex, add direct dependency
|
||||
if (LogicMTask* const otherp = static_cast<LogicMTask*>(top->userp())) {
|
||||
addEdge(mtask, *otherp);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The opposite end of the edge is a bypassed vertex, add transitive dependencies
|
||||
for (const V3GraphEdge& tEdge : top->outEdges()) {
|
||||
LogicMTask* const transp = static_cast<LogicMTask*>(tEdge.top()->userp());
|
||||
// The Move graph is bipartite (logic <-> var), and logic is never
|
||||
// bypassed, hence 'transp' must be non-nullptr.
|
||||
UASSERT_OBJ(transp, mVtxp, "This cannot be a bypassed vertex");
|
||||
addEdge(mtask, *transp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create Dependencies to/from the entry/exit vertices, so all vertices are
|
||||
// reachable from the entry point and flow to the exit point.
|
||||
for (V3GraphVertex& vtx : mtaskGraph.vertices()) {
|
||||
LogicMTask& mtask = static_cast<LogicMTask&>(vtx);
|
||||
if (VL_UNLIKELY((&mtask == &entry) || (&mtask == &exit))) continue;
|
||||
// Add the entry/exit edges if not otherwise connected
|
||||
if (mtask.inEmpty()) addEdge(entry, mtask);
|
||||
if (mtask.outEmpty()) addEdge(mtask, exit);
|
||||
}
|
||||
}
|
||||
~OrderMTaskGraphBuilder() = default;
|
||||
VL_UNCOPYABLE(OrderMTaskGraphBuilder);
|
||||
VL_UNMOVABLE(OrderMTaskGraphBuilder);
|
||||
|
||||
public:
|
||||
static void apply(OrderMTaskGraph& mtaskGraph) { OrderMTaskGraphBuilder{mtaskGraph}; }
|
||||
};
|
||||
|
||||
std::unique_ptr<OrderMTaskGraph> OrderMTaskGraph::build(OrderMoveGraph& moveGraph) {
|
||||
std::unique_ptr<OrderMTaskGraph> resp{new OrderMTaskGraph{moveGraph}};
|
||||
OrderMTaskGraphBuilder::apply(*resp);
|
||||
return resp;
|
||||
}
|
||||
Reference in New Issue
Block a user