MLIR-AIE 1.4.5.dev
AIEPathFinder.cpp
Go to the documentation of this file.
1//===- AIEPathFinder.cpp ----------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2021-2022 Xilinx, Inc.
4// Copyright (C) 2022-2026 Advanced Micro Devices, Inc.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
11#include "d_ary_heap.h"
12
13#include "llvm/ADT/MapVector.h"
14#include "llvm/ADT/STLExtras.h"
15#include "llvm/ADT/SetVector.h"
16#include "llvm/Support/Debug.h"
17#include "llvm/Support/FormatVariadic.h"
18
19#include <iterator>
20#include <limits>
21
22using namespace mlir;
23using namespace xilinx;
24using namespace xilinx::AIE;
25
26#define DEBUG_TYPE "aie-pathfinder"
27
28namespace {
29// A connection's demand, the cost Dijkstra weighs it by, is
30// (demandBase + overCapacityCoeff * iterations it was over capacity) *
31// (demandBase + usedCapacityCoeff * streams using it).
32constexpr double overCapacityCoeff = 0.1;
33constexpr double usedCapacityCoeff = 0.02;
34constexpr double demandBase = 1.0;
35// A full connection's demand grows by this factor, or a prioritized flow's
36// by priorityDemandCoeff, each time another stream takes it.
37constexpr double demandCoeff = 1.1;
38constexpr double priorityDemandCoeff =
39 static_cast<double>(std::numeric_limits<int>::max());
40constexpr int maxCircuitStreamCapacity = 1;
41constexpr int maxPacketStreamCapacity = 32;
42// History added to a connection each time the routing check rejects it.
43constexpr int routingCheckPenalty = 5;
44// Iterations spent steering away from a usable routing the check rejects.
45constexpr int maxSteers = 16;
46// Routings in a row the check rejects only for onlyOverlayMasters before
47// routing gives up: the penalties move such a flow between the ports of the
48// switchbox it has to leave by.
49constexpr int maxOverlayFaults = 16;
50// Times the routing check may fault where two trees meet before they stop
51// meeting; each fault moves the meeting elsewhere.
52constexpr int maxMeetFaults = 4;
53// Cost added to a hop that shares an arbiter unit with a flow to avoid.
54constexpr double conflictSharePenalty = 4;
55// Channels per direction packet streams leave a capped tile by (see
56// Pathfinder::relax). A heuristic: fewer channels mean fewer master
57// sets per tile, but more flows on each.
58constexpr int packetFanoutCap = 2;
59// A multicast's next destination may branch off any hop its tree already
60// takes, starting at this cost per hop back to the source: enough of a
61// discount to share hops, while still preferring the shortest path to each
62// destination.
63constexpr double treeSeedFactor = 0.9;
64// A destination's branch is rerouted only when that saves more than this.
65constexpr double rerouteMinSaving = 1e-6;
66// Iterations without fewer overused channels before the circuit trees that
67// overuse one route first (see findPaths).
68constexpr int stallIterations = 50;
69// Iterations of a stall that route the losing circuit trees first, without
70// fewer overused channels, before the attempt gives up.
71constexpr int maxReorderedStall = 300;
72// Iterations without fewer overused channels before an attempt that has never
73// fit the fabric gives up. In the nightly's designs, none that routed took
74// over 275.
75constexpr int maxOverusedStall = 400;
76// How much faster a stalled routing's overused channels that only circuit
77// trees take accrue history, so the trees spread out sooner.
78constexpr int stallHistory = 4;
79} // namespace
80
82 for (Cell &c : cells)
83 c.demand = (demandBase + overCapacityCoeff * c.overCapacity) *
84 (demandBase + usedCapacityCoeff * c.usedCapacity);
85}
86
88 if (c.usedCapacity >= maxCircuitStreamCapacity)
89 c.demand *= c.isPriority ? priorityDemandCoeff : demandCoeff;
90}
91
92char RoutingFailure::ID = 0;
95
96void RoutingFailure::log(llvm::raw_ostream &os) const {
97 os << "Unable to find a legal routing";
98 if (!reason.empty())
99 os << ": " << reason;
100}
101
102llvm::Error DynamicTileAnalysis::runAnalysis(DeviceOp &device) {
103 LLVM_DEBUG(llvm::dbgs() << "\t---Begin DynamicTileAnalysis Constructor---\n");
104 // find the maxCol and maxRow
105 maxCol = device.getTargetModel().columns();
106 maxRow = device.getTargetModel().rows();
107
108 pathfinder.initialize(maxCol, maxRow, device.getTargetModel());
109
110 // For each flow (circuit + packet) in the device, add it to pathfinder. Each
111 // source can map to multiple different destinations (fanout). Control packet
112 // flows to be routed (as prioritized routings). Then followed by normal
113 // packet flows.
114 for (PacketFlowOp pktFlowOp : device.getOps<PacketFlowOp>()) {
115 Region &r = pktFlowOp.getPorts();
116 Block &b = r.front();
117 SmallVector<std::pair<TileID, Port>, 4> sources;
118 // Pass 1: collect all sources (order-independent; supports fan-in).
119 for (Operation &Op : b.getOperations()) {
120 if (auto pktSource = dyn_cast<PacketSourceOp>(Op)) {
121 auto srcTile = cast<TileOp>(pktSource.getTile().getDefiningOp());
122 sources.push_back(
123 {{srcTile.colIndex(), srcTile.rowIndex()}, pktSource.port()});
124 }
125 }
126
127 bool priorityFlow = pktFlowOp.getPriorityRoute().value_or(false);
128 // Pass 2: add a flow from every source to every destination so
129 // fan-in topologies are routed (not just the last source).
130 for (Operation &Op : b.getOperations()) {
131 if (auto pktDest = dyn_cast<PacketDestOp>(Op)) {
132 auto dstTile = cast<TileOp>(pktDest.getTile().getDefiningOp());
133 Port dstPort = pktDest.port();
134 TileID dstCoords = {dstTile.colIndex(), dstTile.rowIndex()};
135 for (auto &[srcCoords, srcPort] : sources) {
136 LLVM_DEBUG(llvm::dbgs()
137 << "\tAdding Packet Flow: (" << srcCoords.col << ", "
138 << srcCoords.row << ")"
139 << stringifyWireBundle(srcPort.bundle) << srcPort.channel
140 << " -> (" << dstCoords.col << ", " << dstCoords.row << ")"
141 << stringifyWireBundle(dstPort.bundle) << dstPort.channel
142 << "\n");
143 pathfinder.addFlow(srcCoords, srcPort, dstCoords, dstPort,
144 pktFlowOp.IDInt(), priorityFlow,
145 pktFlowOp.getLoc());
146 }
147 }
148 }
149 }
150
151 // Add circuit flows.
152 for (FlowOp flowOp : device.getOps<FlowOp>()) {
153 TileOp srcTile = cast<TileOp>(flowOp.getSource().getDefiningOp());
154 TileOp dstTile = cast<TileOp>(flowOp.getDest().getDefiningOp());
155 TileID srcCoords = {srcTile.colIndex(), srcTile.rowIndex()};
156 TileID dstCoords = {dstTile.colIndex(), dstTile.rowIndex()};
157 Port srcPort = {flowOp.getSourceBundle(), flowOp.getSourceChannel()};
158 Port dstPort = {flowOp.getDestBundle(), flowOp.getDestChannel()};
159 LLVM_DEBUG(llvm::dbgs()
160 << "\tAdding Flow: (" << srcCoords.col << ", " << srcCoords.row
161 << ")" << stringifyWireBundle(srcPort.bundle) << srcPort.channel
162 << " -> (" << dstCoords.col << ", " << dstCoords.row << ")"
163 << stringifyWireBundle(dstPort.bundle) << dstPort.channel
164 << "\n");
165 pathfinder.addFlow(srcCoords, srcPort, dstCoords, dstPort,
166 /*packetId=*/std::nullopt, /*isPriorityFlow=*/false,
167 flowOp.getLoc());
168 }
169
170 // Canonicalize all flows after both packet and circuit flows are collected.
171 pathfinder.sortFlows();
172
173 // A reload configures only the switchboxes the overlay reaches.
174 if (device.getHasCtrlPktOverlay()) {
175 llvm::DenseSet<TileID> reached;
176 for (PacketFlowOp pktFlowOp : device.getOps<PacketFlowOp>()) {
177 if (!pktFlowOp.getPriorityRoute().value_or(false))
178 continue;
179 for (auto pktDest : pktFlowOp.getPorts().getOps<PacketDestOp>())
180 if (pktDest.getBundle() == WireBundle::TileControl)
181 reached.insert(
182 cast<TileOp>(pktDest.getTile().getDefiningOp()).getTileID());
183 }
184 if (!reached.empty())
185 for (int row = 0; row <= maxRow; row++)
186 for (int col = 0; col <= maxCol; col++)
187 if (!reached.contains({col, row}))
188 pathfinder.excludeTile({col, row});
189 }
190
191 // add existing connections so Pathfinder knows which resources are
192 // available search all existing SwitchBoxOps for exising connections
193 for (SwitchboxOp switchboxOp : device.getOps<SwitchboxOp>()) {
194 if (failed(pathfinder.addFixedConnection(switchboxOp)))
195 return llvm::make_error<RoutingFailure>(
196 llvm::formatv("cannot add the fixed connections of the switchbox "
197 "at tile ({0}, {1})",
198 switchboxOp.colIndex(), switchboxOp.rowIndex()),
199 RoutingFaults{}, switchboxOp.getLoc());
200 }
201
202 // all flows are now populated, call the congestion-aware pathfinder
203 // algorithm
204 // check whether the pathfinder algorithm creates a legal routing
205 llvm::Expected<Routing> found = pathfinder.findPaths(maxIterations);
206 if (!found)
207 return found.takeError();
208 routing = std::move(*found);
209
210 // fill in coords to TileOps, SwitchboxOps, and ShimMuxOps
211 coordToTile.clear();
212 coordToSwitchbox.clear();
213 coordToShimMux.clear();
214 for (auto tileOp : device.getOps<TileOp>()) {
215 [[maybe_unused]] bool fresh =
216 coordToTile.try_emplace(tileOp.getTileID(), tileOp).second;
217 assert(fresh);
218 }
219 for (auto switchboxOp : device.getOps<SwitchboxOp>()) {
220 [[maybe_unused]] bool fresh =
221 coordToSwitchbox
222 .try_emplace({switchboxOp.colIndex(), switchboxOp.rowIndex()},
223 switchboxOp)
224 .second;
225 assert(fresh);
226 }
227 for (auto shimmuxOp : device.getOps<ShimMuxOp>()) {
228 [[maybe_unused]] bool fresh =
229 coordToShimMux
230 .try_emplace({shimmuxOp.colIndex(), shimmuxOp.rowIndex()},
231 shimmuxOp)
232 .second;
233 assert(fresh);
234 }
235
236 LLVM_DEBUG(llvm::dbgs() << "\t---End DynamicTileAnalysis Constructor---\n");
237 return llvm::Error::success();
238}
239
240TileOp DynamicTileAnalysis::getTile(OpBuilder &builder, int col, int row) {
241 TileOp &tileOp = coordToTile[{col, row}];
242 if (!tileOp)
243 tileOp = TileOp::create(
244 builder,
245 cast<DeviceOp>(builder.getInsertionBlock()->getParentOp()).getLoc(),
246 col, row);
247 return tileOp;
248}
249
250TileOp DynamicTileAnalysis::getTile(OpBuilder &builder, const TileID &tileId) {
251 return getTile(builder, tileId.col, tileId.row);
252}
253
254SwitchboxOp DynamicTileAnalysis::getSwitchbox(OpBuilder &builder, int col,
255 int row) {
256 assert(col >= 0);
257 assert(row >= 0);
258 if (SwitchboxOp switchboxOp = lookupSwitchbox({col, row}))
259 return switchboxOp;
260 auto tileOp = getTile(builder, col, row);
261 auto switchboxOp = SwitchboxOp::create(builder, tileOp.getLoc(), tileOp);
262 SwitchboxOp::ensureTerminator(switchboxOp.getConnections(), builder,
263 tileOp.getLoc());
264 coordToSwitchbox[{col, row}] = switchboxOp;
265 return switchboxOp;
266}
267
268ShimMuxOp DynamicTileAnalysis::getShimMux(OpBuilder &builder, int col) {
269 assert(col >= 0);
270 if (ShimMuxOp shimMuxOp = lookupShimMux(col))
271 return shimMuxOp;
272 auto tileOp = getTile(builder, col, 0);
273 assert(tileOp.isShimNOCorPLTile());
274 auto shimMuxOp = ShimMuxOp::create(builder, tileOp.getLoc(), tileOp);
275 ShimMuxOp::ensureTerminator(shimMuxOp.getConnections(), builder,
276 tileOp.getLoc());
277 coordToShimMux[{col, 0}] = shimMuxOp;
278 return shimMuxOp;
279}
280
281void Pathfinder::initialize(int maxCol, int maxRow,
282 const AIETargetModel &targetModel) {
283 // Reset the graph and flows so a Pathfinder can be reused across analyses
284 // and devices; the dense-graph cache must be rebuilt for the new topology.
285 // The relax state (shareChannels, idsApart, meetApart, relaxStep,
286 // cappedTiles, crowdedTiles) survives, so the ladder advances across
287 // attempts.
288 graph.clear();
289 flows.clear();
290 packetIdsTo.clear();
291 priorityIds.clear();
292 flowLocs.clear();
293 graphBuilt = false;
294 columns = maxCol;
295 rows = maxRow;
296 nodeIds.clear();
297 nodes.clear();
298 adjacency.clear();
299 wireEdgesBegin.clear();
300 distance.clear();
301 indexInHeap.clear();
302 colors.clear();
303 preds.clear();
304 predEdge.clear();
305
306 std::map<WireBundle, int> maxChannels;
307 auto intraconnect = [&](int col, int row) {
308 TileID coords = {col, row};
309 SwitchboxConnect &sb =
310 graph.try_emplace({coords, coords}, coords).first->second;
311
312 for (int i = 0, e = getMaxEnumValForWireBundle() + 1; i < e; ++i) {
313 WireBundle bundle = symbolizeWireBundle(i).value();
314 // get all ports into current switchbox
315 int channels =
316 targetModel.getNumSourceSwitchboxConnections(col, row, bundle);
317 if (channels == 0 && targetModel.isShimNOCorPLTile(col, row)) {
318 // wordaround for shimMux
319 channels = targetModel.getNumSourceShimMuxConnections(col, row, bundle);
320 }
321 for (int channel = 0; channel < channels; channel++) {
322 sb.srcPorts.push_back(Port{bundle, channel});
323 }
324 // get all ports out of current switchbox
325 channels = targetModel.getNumDestSwitchboxConnections(col, row, bundle);
326 if (channels == 0 && targetModel.isShimNOCorPLTile(col, row)) {
327 // wordaround for shimMux
328 channels = targetModel.getNumDestShimMuxConnections(col, row, bundle);
329 }
330 for (int channel = 0; channel < channels; channel++) {
331 sb.dstPorts.push_back(Port{bundle, channel});
332 }
333 maxChannels[bundle] = channels;
334 }
335 sb.resize();
336 // A shim's mux reaches its DMA, NOC and PLIO ports from any port.
337 auto isMuxed = [&](Port p) {
338 return targetModel.isShimNOCorPLTile(col, row) &&
339 llvm::is_contained(
340 {WireBundle::DMA, WireBundle::NOC, WireBundle::PLIO},
341 p.bundle);
342 };
343 for (auto [i, pIn] : llvm::enumerate(sb.srcPorts))
344 for (auto [j, pOut] : llvm::enumerate(sb.dstPorts))
345 sb.at(i, j).available =
346 targetModel.isLegalTileConnection(col, row, pIn.bundle, pIn.channel,
347 pOut.bundle, pOut.channel) ||
348 isMuxed(pIn) || isMuxed(pOut);
349 };
350
351 auto interconnect = [&](int col, int row, int targetCol, int targetRow,
352 WireBundle srcBundle, WireBundle dstBundle) {
353 TileID src = {col, row}, dst = {targetCol, targetRow};
354 SwitchboxConnect &sb =
355 graph.try_emplace({src, dst}, src, dst).first->second;
356 for (int channel = 0; channel < maxChannels[srcBundle]; channel++) {
357 sb.srcPorts.push_back(Port{srcBundle, channel});
358 sb.dstPorts.push_back(Port{dstBundle, channel});
359 }
360 sb.resize();
361 for (size_t i = 0; i < sb.srcPorts.size(); i++)
362 sb.at(i, i).available = true;
363 };
364
365 for (int row = 0; row <= maxRow; row++) {
366 for (int col = 0; col <= maxCol; col++) {
367 maxChannels.clear();
368 // connections within the same switchbox
369 intraconnect(col, row);
370
371 // connections between switchboxes
372 if (row > 0) {
373 // from south to north
374 interconnect(col, row, col, row - 1, WireBundle::South,
375 WireBundle::North);
376 }
377 if (row < maxRow) {
378 // from north to south
379 interconnect(col, row, col, row + 1, WireBundle::North,
380 WireBundle::South);
381 }
382 if (col > 0) {
383 // from east to west
384 interconnect(col, row, col - 1, row, WireBundle::West,
385 WireBundle::East);
386 }
387 if (col < maxCol) {
388 // from west to east
389 interconnect(col, row, col + 1, row, WireBundle::East,
390 WireBundle::West);
391 }
392 }
393 }
394}
395
396// Add a flow from src to dst can have an arbitrary number of dst locations
397// due to fanout.
398void Pathfinder::addFlow(TileID srcCoords, Port srcPort, TileID dstCoords,
399 Port dstPort, std::optional<int> packetId,
400 bool isPriorityFlow,
401 std::optional<mlir::Location> loc) {
402 isPriorityFlow &= constraints.prioritize ||
403 constraints.pinned.count({srcCoords, srcPort}) > 0;
404 if (loc)
405 flowLocs[{srcCoords, srcPort}].try_emplace({dstCoords, dstPort}, *loc);
406 if (packetId) {
407 auto &ids = packetIdsTo[{{srcCoords, srcPort}, {dstCoords, dstPort}}];
408 if (!llvm::is_contained(ids, *packetId))
409 ids.push_back(*packetId);
410 if (isPriorityFlow)
411 priorityIds[{srcCoords, srcPort}].insert(*packetId);
412 }
413 // A source has one flow, with all its destinations.
414 PathEndPoint src{srcCoords, srcPort}, dst{dstCoords, dstPort};
415 auto flow = llvm::find_if(flows, [&](const Flow &f) { return f.src == src; });
416 if (flow == flows.end()) {
417 // sortFlows assigns the packet groups.
418 flows.push_back(
419 Flow{packetId ? 0 : -1, isPriorityFlow, src, {dst}, packetId});
420 } else if (isPriorityFlow) {
421 flow->isPriorityFlow = true;
422 flow->dsts.insert(flow->dsts.begin(), dst);
423 } else {
424 flow->dsts.push_back(dst);
425 }
426}
427
428// Where the flow from `src` to `dst`, or to any destination if `dst` is null,
429// was declared.
430std::optional<mlir::Location>
431Pathfinder::flowLoc(const PathEndPoint &src, const PathEndPoint *dst) const {
432 auto locs = flowLocs.find(src);
433 if (locs == flowLocs.end())
434 return std::nullopt;
435 auto it = dst ? locs->second.find(*dst) : locs->second.begin();
436 if (it == locs->second.end())
437 return std::nullopt;
438 return it->second;
439}
440
441bool Pathfinder::shareAllChannels() {
442 shareChannels = true;
443 return llvm::any_of(flows, [](const Flow &f) { return f.packetGroupId > 0; });
444}
445
446bool Pathfinder::capCrowdedFanOut() {
447 size_t capped = cappedTiles.size();
448 cappedTiles.insert(crowdedTiles.begin(), crowdedTiles.end());
449 return cappedTiles.size() > capped;
450}
451
452std::map<PathEndPoint, std::set<int>> Pathfinder::idsBySource() const {
453 std::map<PathEndPoint, std::set<int>> ids;
454 for (const auto &[ends, sent] : packetIdsTo)
455 ids[ends.first].insert(sent.begin(), sent.end());
456 return ids;
457}
458
459bool Pathfinder::routeIdsApart() {
460 if (idsApart)
461 return false;
462 idsApart = true;
463 shareChannels = false;
464 cappedTiles.clear();
465 return llvm::any_of(idsBySource(),
466 [](const auto &s) { return s.second.size() > 1; });
467}
468
469bool Pathfinder::splitSharedIds() {
470 if (splitShared)
471 return false;
472 splitShared = true;
473 shareChannels = false;
474 cappedTiles.clear();
475 std::map<PathEndPoint, std::set<int>> ids = idsBySource();
476 for (const auto &[src, srcIds] : ids)
477 for (const auto &[other, otherIds] : ids) {
478 const std::set<int> &theirs = otherIds;
479 if (!(src == other) &&
480 llvm::any_of(srcIds, [&](int id) { return theirs.count(id); }) &&
481 llvm::any_of(srcIds, [&](int id) { return !theirs.count(id); }))
482 return true;
483 }
484 return false;
485}
486
487// The steps before it bent the routing around the meetings, so the ladder
488// starts over without them.
489bool Pathfinder::stopMeeting() {
490 if (meetApart || !treesMet)
491 return false;
492 meetApart = true;
493 shareChannels = idsApart = splitShared = false;
494 cappedTiles.clear();
495 relaxStep = 0;
496 return true;
497}
498
499namespace {
500enum class RelaxStep {
501 ShareChannels,
502 CapCrowdedTiles,
503 RouteIdsApart,
504 SplitSharedIds,
505 StopMeeting
506};
507// Stopping meetings comes last, and runs the ladder once more: what it routes
508// can hold arbiters in a cycle through the receivers trees share.
509constexpr RelaxStep relaxLadder[] = {
510 RelaxStep::ShareChannels, RelaxStep::CapCrowdedTiles,
511 RelaxStep::RouteIdsApart, RelaxStep::ShareChannels,
512 RelaxStep::CapCrowdedTiles, RelaxStep::SplitSharedIds,
513 RelaxStep::ShareChannels, RelaxStep::CapCrowdedTiles,
514 RelaxStep::StopMeeting};
515} // namespace
516
518 if (!packetsFailed) {
519 LLVM_DEBUG(llvm::dbgs() << "No packet stream crosses an overused link\n");
520 return false;
521 }
522 while (relaxStep < std::size(relaxLadder)) {
523 switch (relaxLadder[relaxStep++]) {
524 case RelaxStep::ShareChannels:
525 if (shareAllChannels()) {
526 LLVM_DEBUG(llvm::dbgs() << "Relax: share channels\n");
527 return true;
528 }
529 break;
530 case RelaxStep::CapCrowdedTiles:
531 if (capCrowdedFanOut()) {
532 LLVM_DEBUG(llvm::dbgs() << "Relax: cap crowded tiles\n");
533 return true;
534 }
535 break;
536 case RelaxStep::RouteIdsApart:
537 if (routeIdsApart()) {
538 LLVM_DEBUG(llvm::dbgs() << "Relax: route ids apart\n");
539 return true;
540 }
541 // With one id per source, the steps up to the last would only repeat.
542 relaxStep = std::size(relaxLadder) - 1;
543 break;
544 case RelaxStep::SplitSharedIds:
545 if (splitSharedIds()) {
546 LLVM_DEBUG(llvm::dbgs() << "Relax: split shared ids\n");
547 return true;
548 }
549 relaxStep = std::size(relaxLadder) - 1;
550 break;
551 case RelaxStep::StopMeeting:
552 if (stopMeeting()) {
553 LLVM_DEBUG(llvm::dbgs() << "Relax: stop meeting\n");
554 return true;
555 }
556 break;
557 }
558 }
559 return false;
560}
561
562// Sort flows to (1) get deterministic routing, and (2) perform routings on
563// prioritized flows before others, for routing consistency on those flows.
565 for (auto &flow : flows)
566 llvm::sort(flow.dsts);
567
568 // The groups shareAllChannels merges, whatever order the flows were
569 // added in: packet flows to a common destination are in one.
570 llvm::IntEqClasses groups(flows.size());
571 std::map<PathEndPoint, unsigned> firstTo;
572 for (auto [k, flow] : llvm::enumerate(flows)) {
573 if (flow.packetGroupId < 0)
574 continue;
575 for (const PathEndPoint &dst : flow.dsts) {
576 auto [it, fresh] = firstTo.try_emplace(dst, k);
577 if (!fresh)
578 groups.join(k, it->second);
579 }
580 }
581 llvm::DenseMap<unsigned, int> groupOf;
582 for (auto [k, flow] : llvm::enumerate(flows))
583 if (flow.packetGroupId >= 0)
584 flow.packetGroupId =
585 shareChannels
586 ? 0
587 : groupOf.try_emplace(groups.findLeader(k), groupOf.size())
588 .first->second;
589
590 auto flowRank = [](const Flow &flow) {
591 if (flow.isPriorityFlow)
592 return 0;
593 if (flow.packetGroupId >= 0)
594 return 1;
595 return 2;
596 };
597 llvm::sort(flows, [&](const Flow &lhs, const Flow &rhs) {
598 return std::make_pair(flowRank(lhs), lhs.src) <
599 std::make_pair(flowRank(rhs), rhs.src);
600 });
601}
602
604 // DMA0 -> N3, DMA1 -> N7; NOC0/1 -> N2/3, NOC2/3 -> N6/7.
605 if (src.bundle == WireBundle::DMA)
606 return src.channel == 0 ? 3 : 7;
607 if (src.bundle == WireBundle::NOC)
608 return src.channel >= 2 ? src.channel + 4 : src.channel + 2;
609 return src.channel;
610}
611
613 // N2 -> DMA0, N3 -> DMA1; N2-5 -> NOC0-3.
614 if (dst.bundle == WireBundle::DMA)
615 return dst.channel == 0 ? 2 : 3;
616 if (dst.bundle == WireBundle::NOC)
617 return dst.channel + 2;
618 return dst.channel;
619}
620
621LogicalResult Pathfinder::addFixedConnection(SwitchboxOp switchboxOp) {
622 int col = switchboxOp.colIndex();
623 int row = switchboxOp.rowIndex();
624 TileID coords = {col, row};
625 auto &sb = graph[std::make_pair(coords, coords)];
626 // Validate every connect against the original connectivity before reserving
627 // any resource, so a broadcast (one source fanning out to several dests) does
628 // not invalidate its own sibling connects mid-scan. A destination port may be
629 // driven only once; a source port may legally recur across a broadcast.
630 llvm::SmallVector<std::pair<int, int>, 8> reserved;
631 llvm::SmallDenseSet<int, 8> claimedDsts;
632 for (ConnectOp connectOp : switchboxOp.getOps<ConnectOp>()) {
633 int srcIdx = sb.srcIndex(connectOp.sourcePort());
634 int dstIdx = sb.dstIndex(connectOp.destPort());
635 // Reject an illegal pair (absent from the switchbox model) or a second
636 // driver on the same output port; a repeated source port is a broadcast.
637 if (srcIdx < 0 || dstIdx < 0 || !sb.at(srcIdx, dstIdx).available ||
638 !claimedDsts.insert(dstIdx).second) {
639 return failure();
640 }
641 reserved.emplace_back(srcIdx, dstIdx);
642 }
643 // A pre-placed packet-switched output (an aie.masterset) also monopolizes its
644 // destination port. That stream-switch output is already configured for
645 // packet switching, so no circuit stream may drive it, and the router cannot
646 // emit a second masterset on the same port for another packet flow. The
647 // circuit pathfinder does not read packet ops, so without this reservation it
648 // may route a flow onto an output the masterset owns and produce two ops
649 // driving one destination.
650 llvm::SmallVector<int, 8> reservedMasterDsts;
651 for (MasterSetOp masterSetOp : switchboxOp.getOps<MasterSetOp>()) {
652 int dstIdx = sb.dstIndex(masterSetOp.destPort());
653 // Reject an output port absent from the switchbox model or already driven
654 // by a circuit connect or another masterset.
655 if (dstIdx < 0 || !claimedDsts.insert(dstIdx).second) {
656 return failure();
657 }
658 reservedMasterDsts.push_back(dstIdx);
659 }
660 // A circuit-switched ConnectOp monopolizes both its source port (the stream
661 // switch input) and its destination port (the output): no other stream may
662 // inject on that input, and the output can carry only this one stream.
663 // Reserving the whole column also reserves the outgoing wire, since that wire
664 // is reachable only by driving this output port.
665 for (auto [srcIdx, dstIdx] : reserved) {
666 for (size_t j = 0; j < sb.dstPorts.size(); j++)
667 sb.at(srcIdx, j).available = false;
668 for (size_t i = 0; i < sb.srcPorts.size(); i++)
669 sb.at(i, dstIdx).available = false;
670 }
671 // A masterset fixes only its output port. Its inputs arrive through arbiters
672 // that packet flows share, so the source rows stay free.
673 for (int dstIdx : reservedMasterDsts)
674 for (size_t i = 0; i < sb.srcPorts.size(); i++)
675 sb.at(i, dstIdx).available = false;
676 // A fixed op on a shim's South channel claims the port the shim mux carries
677 // on it (see shimMuxChannelFrom).
678 if (switchboxOp.getTileOp().isShimNOCorPLTile()) {
679 auto isMuxed = [](Port p) {
680 return llvm::is_contained(
681 {WireBundle::DMA, WireBundle::NOC, WireBundle::PLIO}, p.bundle);
682 };
683 llvm::SmallDenseSet<int, 8> southDsts, southSrcs;
684 for (int dstIdx : claimedDsts)
685 if (sb.dstPorts[dstIdx].bundle == WireBundle::South)
686 southDsts.insert(sb.dstPorts[dstIdx].channel);
687 for (auto [srcIdx, dstIdx] : reserved)
688 if (sb.srcPorts[srcIdx].bundle == WireBundle::South)
689 southSrcs.insert(sb.srcPorts[srcIdx].channel);
690 for (size_t j = 0; j < sb.dstPorts.size(); j++)
691 if (isMuxed(sb.dstPorts[j]) &&
692 southDsts.count(shimMuxChannelTo(sb.dstPorts[j])))
693 for (size_t i = 0; i < sb.srcPorts.size(); i++)
694 sb.at(i, j).available = false;
695 for (size_t i = 0; i < sb.srcPorts.size(); i++)
696 if (isMuxed(sb.srcPorts[i]) &&
697 southSrcs.count(shimMuxChannelFrom(sb.srcPorts[i])))
698 for (size_t j = 0; j < sb.dstPorts.size(); j++)
699 sb.at(i, j).available = false;
700 }
701 for (PacketRulesOp rulesOp : switchboxOp.getOps<PacketRulesOp>()) {
702 int srcIdx = sb.srcIndex(rulesOp.sourcePort());
703 if (srcIdx < 0)
704 return failure();
705 sb.packetOnlySrc.set(srcIdx);
706 }
707 return success();
708}
709
711 auto it = graph.find({coords, coords});
712 if (it == graph.end())
713 return;
714 SwitchboxConnect &sb = it->second;
715 for (size_t i = 0; i < sb.srcPorts.size(); i++)
716 for (size_t j = 0; j < sb.dstPorts.size(); j++)
717 sb.at(i, j).available = false;
718}
719
720static constexpr double INF = std::numeric_limits<double>::max();
721
722namespace {
723enum Color : int8_t { WHITE = 0, GRAY = 1, BLACK = 2 };
724} // namespace
725
726int Pathfinder::getOrAddNodeId(const PathEndPoint &pep) {
727 auto it = nodeIds.find(pep);
728 if (it != nodeIds.end())
729 return it->second;
730 int id = static_cast<int>(nodes.size());
731 nodeIds[pep] = id;
732 nodes.push_back(pep);
733 return id;
734}
735
736// Build the dense integer node numbering and per-node adjacency once. The graph
737// topology is fixed across congestion iterations (only the demand weights
738// change), so this is computed a single time and the edges carry live pointers
739// into `graph` for demand lookups. A node's crossbar edges come first, then its
740// wire edges, each sorted by the PathEndPoint they reach, so Dijkstra breaks
741// ties the same way every run.
742void Pathfinder::buildRoutingGraph() {
743 // Seed the dense node set with all flow endpoints (the only nodes Dijkstra is
744 // ever started from or traced back to). Remaining nodes are discovered as
745 // edge destinations below.
746 for (auto &f : flows) {
747 getOrAddNodeId(f.src);
748 for (auto &d : f.dsts)
749 getOrAddNodeId(d);
750 }
751
752 // Process nodes by growing index; getOrAddNodeId() may append new nodes as
753 // edge destinations are discovered, so re-read nodes.size() each iteration.
754 for (size_t id = 0; id < nodes.size(); id++) {
755 PathEndPoint src = nodes[id];
756 // The ports the crossbar connects this one to, and the neighbours' ports
757 // its wire reaches.
758 std::vector<PathEndPoint> dests;
759 auto intraIt = graph.find({src.coords, src.coords});
760 if (intraIt != graph.end()) {
761 auto &sb = intraIt->second;
762 for (auto [i, pIn] : llvm::enumerate(sb.srcPorts))
763 for (auto [j, pOut] : llvm::enumerate(sb.dstPorts))
764 if (pIn == src.port && sb.at(i, j).available)
765 dests.emplace_back(src.coords, pOut);
766 }
767 std::pair<TileID, Port> neighbors[] = {
768 {{src.coords.col, src.coords.row - 1},
769 {WireBundle::North, src.port.channel}},
770 {{src.coords.col - 1, src.coords.row},
771 {WireBundle::East, src.port.channel}},
772 {{src.coords.col, src.coords.row + 1},
773 {WireBundle::South, src.port.channel}},
774 {{src.coords.col + 1, src.coords.row},
775 {WireBundle::West, src.port.channel}}};
776 for (const auto &[neighborCoords, neighborPort] : neighbors) {
777 auto nIt = graph.find({src.coords, neighborCoords});
778 if (nIt != graph.end() &&
779 src.port.bundle == getConnectingBundle(neighborPort.bundle) &&
780 llvm::is_contained(nIt->second.dstPorts, neighborPort))
781 dests.emplace_back(neighborCoords, neighborPort);
782 }
783 llvm::sort(dests);
784
785 std::vector<Edge> edges;
786 edges.reserve(dests.size());
787 for (auto &dest : dests) {
788 auto &sb = graph.at({src.coords, dest.coords});
789 int i = sb.srcIndex(src.port);
790 int j = sb.dstIndex(dest.port);
791 assert(i >= 0 && j >= 0);
792 int destId = getOrAddNodeId(dest);
793 edges.push_back(Edge{destId, &sb, i, j});
794 }
795 auto wires =
796 std::stable_partition(edges.begin(), edges.end(), [](const Edge &e) {
797 return e.sb->srcCoords == e.sb->dstCoords;
798 });
799 if (wireEdgesBegin.size() < nodes.size())
800 wireEdgesBegin.resize(nodes.size());
801 wireEdgesBegin[id] = wires - edges.begin();
802 // getOrAddNodeId above may have reallocated `adjacency` via index growth in
803 // later iterations, but we only assign this node's edges now.
804 if (adjacency.size() < nodes.size())
805 adjacency.resize(nodes.size());
806 adjacency[id] = std::move(edges);
807 }
808 size_t n = nodes.size();
809 if (adjacency.size() < n)
810 adjacency.resize(n);
811 wireEdgesBegin.resize(n);
812
813 // Size the reusable Dijkstra scratch buffers. These are indexed by state id,
814 // i.e. two entries per node -- one per side of the port.
815 distance.assign(2 * n, INF);
816 indexInHeap.assign(2 * n, 0);
817 colors.assign(2 * n, WHITE);
818 preds.assign(2 * n, -1);
819 predEdge.assign(2 * n, Edge{-1, nullptr, 0, 0});
820 graphBuilt = true;
821}
822
823double Pathfinder::edgeWeight(const Edge &e, std::optional<int> packetId,
824 const llvm::BitVector *avoid,
825 const llvm::BitVector *avoidBranch) const {
826 const SwitchboxConnect::Cell &cell = e.sb->at(e.i, e.j);
827 double w = cell.demand;
828 // Sharing it would take a second stream's capacity, which the demand
829 // only shows once the group is routed.
830 if (packetId && cell.packetFlowCount > 0 && cell.packetIds.count(*packetId))
831 w *= demandCoeff;
832 if (e.sb->srcCoords == e.sb->dstCoords)
833 for (const llvm::BitVector *flows : {avoid, avoidBranch})
834 if (flows && llvm::any_of(e.sb->unitFlows(e.j),
835 [&](int f) { return flows->test(f); }))
836 w += conflictSharePenalty;
837 return w;
838}
839
840// Dijkstra over the dense graph from the states in `seeds`, searching states
841// (node, PortSide) rather than bare nodes. Fills the `preds` and `predEdge`
842// scratch buffers, both indexed by state id.
843void Pathfinder::dijkstraShortestPaths(
844 ArrayRef<int> seeds, ArrayRef<double> seedCosts,
845 std::optional<int> packetId, const llvm::BitVector *avoid,
846 const llvm::DenseMap<int, llvm::BitVector> *branchAvoid,
847 const llvm::DenseSet<int> *stops, ArrayRef<int> targets,
848 llvm::function_ref<bool(int, int)> mayCross) {
849 llvm::fill(distance, INF);
850 llvm::fill(colors, static_cast<int8_t>(WHITE));
851 llvm::fill(preds, -1);
852 llvm::fill(indexInHeap, uint64_t{0});
853
854 using MutableQueue = d_ary_heap_indirect<
855 /*Value=*/int, /*Arity=*/4,
856 /*IndexInHeapPropertyMap=*/std::vector<uint64_t> &,
857 /*DistanceMap=*/std::vector<double> &,
858 /*Compare=*/std::less<>>;
859 MutableQueue Q(distance, indexInHeap);
860
861 for (auto [seed, cost] : llvm::zip_equal(seeds, seedCosts)) {
862 distance[seed] = cost;
863 colors[seed] = GRAY;
864 Q.push(seed);
865 }
866 // A settled state's distance and predecessor never change again.
867 SmallVector<int, 8> unsettled(targets);
868 while (!Q.empty()) {
869 int s = Q.top();
870 Q.pop();
871 if (llvm::is_contained(unsettled, s)) {
872 llvm::erase(unsettled, s);
873 if (unsettled.empty())
874 break;
875 }
876 if (stops && stops->count(s)) {
877 colors[s] = BLACK;
878 continue;
879 }
880 // In takes crossbar edges and lands on the Out side of the port it picks;
881 // Out takes the wire to the neighbour and lands on that tile's In side. Any
882 // other pairing would either turn the stream around inside a switchbox or
883 // ride a wire the crossbar was never set to drive.
884 const bool sIsOut = (s & 1) == Out;
885 const llvm::BitVector *avoidBranch = nullptr;
886 if (branchAvoid)
887 if (auto it = branchAvoid->find(s); it != branchAvoid->end())
888 avoidBranch = &it->second;
889 ArrayRef<Edge> out = adjacency[stateNode(s)];
890 size_t wires = wireEdgesBegin[stateNode(s)];
891 const bool isIntra = !sIsOut;
892 for (const Edge &e :
893 sIsOut ? out.drop_front(wires) : out.take_front(wires)) {
894 if ((isIntra && !packetId && e.sb->packetOnlySrc[e.i]) ||
895 (isIntra && packetId && e.sb->circuitOnlyDst[e.j]))
896 continue;
897 int dst = stateId(e.dst, isIntra ? Out : In);
898 if (colors[dst] == BLACK || (isIntra && mayCross && !mayCross(s, dst)))
899 continue;
900 double w = edgeWeight(e, packetId, avoid, avoidBranch);
901 if (distance[s] + w >= distance[dst])
902 continue;
903 distance[dst] = distance[s] + w;
904 preds[dst] = s;
905 predEdge[dst] = e;
906 if (colors[dst] == WHITE) {
907 colors[dst] = GRAY;
908 Q.push(dst);
909 } else {
910 Q.update(dst);
911 }
912 }
913 colors[s] = BLACK;
914 }
915}
916
917// What findPaths keeps from one flow and iteration to the next.
919 explicit RouteState(const Pathfinder &pf);
920
921 int groupOf(const Flow &f) const;
922 SmallVector<int, 4> idsTo(int flow, int dstState) const;
923 void relateParts();
924 int splitPart(int flow, const std::set<int> &ids);
925 bool isPinned(const Flow &f) const;
926
929 // Stamp-based "processed" set (avoids O(n) clears per flow).
930 std::vector<uint32_t> processedStamp;
931 uint32_t curStamp = 0;
932 // The flows to route: one per source, less the packets split off into parts
933 // of their own (see below), which route as flows from the same source.
934 std::vector<Flow> parts;
935 std::map<PathEndPoint, SmallVector<int, 2>> partsOf;
936 // group flows based on packetGroupId; pinned trees go in first, so the
937 // flows routed around them see them.
938 llvm::MapVector<int, SmallVector<int, 8>> groupedFlows;
939 // Packet flows that conflict, by the ids each carries or by
940 // RoutingFaults::apart.
941 std::vector<llvm::BitVector> conflicting;
942 std::set<std::pair<PathEndPoint, PathEndPoint>> apartSources;
943 std::set<std::pair<PathEndPoint, PathEndPoint>> togetherSources;
944 // The packet ids each flow carries, in all and to each destination.
945 std::vector<std::set<int>> flowIds;
946 std::vector<SmallVector<int, 4>> sameId;
947 // Each packet flow's tree as routed so far this iteration: the state each
948 // hop reaches, from which state and by which edge; and its destinations.
949 std::vector<llvm::DenseMap<int, std::pair<int, Edge>>> treeOf;
950 // Each circuit flow's hops this iteration, which treeOf keeps for packet
951 // flows only.
952 std::vector<SmallVector<Edge, 8>> circuitHops;
953 std::vector<SmallVector<int, 4>> treeDsts;
954 // The hops each flow takes only by joining another flow's tree, by the flow
955 // it joined. A join the routing check faults is not made again.
956 std::vector<SmallVector<std::pair<int, Edge>, 8>> joinedHops;
957 std::set<std::pair<int, int>> noJoin;
958 // Where the routing check split a flow's tree: the tile it branches at, the
959 // ids to branch apart there, and the state of the destination that reaches
960 // the tile apart, if one does.
961 struct IdSplit {
963 int a, b;
964 std::optional<int> apart;
965 bool operator<(const IdSplit &other) const {
966 return std::tie(at, a, b, apart) <
967 std::tie(other.at, other.a, other.b, other.apart);
968 }
969 };
970 std::vector<std::set<IdSplit>> splitsOf;
971 // A split one tree cannot make, since a destination takes both ids, moves
972 // the second id to a part of its own. Parts of a source split apart at a
973 // tile leave it by different master ports: the part routed later keeps off
974 // those the other's tree takes there.
975 std::vector<std::set<std::pair<TileID, int>>> partSplits;
976 // Flows that found no one switchbox to meet a tree routed before them at,
977 // by that tree's flow; each such pair routes the other way round once.
978 std::set<std::pair<int, int>> unmet, reordered;
979 // A tree branching for two destinations at its source switchbox may leave
980 // no slave port there to meet it by. Each of an unmet pair then reaches the
981 // destinations it shares with the other by one master port out of it.
982 std::set<std::pair<int, int>> trunked;
983 // Sources whose trunk took their tree back through a switchbox it holds an
984 // arbiter in (RoutingFaults::untrunk), which branch freely from then on.
985 std::set<PathEndPoint> untrunked;
986 // The flows that met a tree this iteration, by its flow, and how often the
987 // routing check faulted a meeting of each pair.
988 std::set<std::pair<int, int>> met;
989 std::map<std::pair<int, int>, int> meetFaults;
990 // Joined pairs the routing check faulted along with hops neither joined,
991 // which were left to the penalties on those hops once.
992 std::set<std::pair<int, int>> spared;
993 // A control-packet reload keeps the master sets the prioritized flows' trees
994 // leave each switchbox by, so another flow leaving by one of those master
995 // ports leaves by all of one such set and no others.
996 llvm::DenseSet<int> overlayMasters;
997 std::vector<SmallVector<int, 4>> overlaySets;
998 bool mayLeave(ArrayRef<int> masters, int next) const;
999 bool reorder(int later, int earlier, bool again = false);
1000};
1001
1003 : pf(pf), processedStamp(2 * pf.nodes.size(), 0), parts(pf.flows),
1004 conflicting(parts.size(), llvm::BitVector(parts.size())),
1005 flowIds(parts.size()), treeOf(parts.size()), circuitHops(parts.size()),
1006 treeDsts(parts.size()), joinedHops(parts.size()), splitsOf(parts.size()),
1007 partSplits(parts.size()) {
1008 for (auto [k, f] : llvm::enumerate(parts))
1009 partsOf[f.src].push_back(k);
1010 for (auto [k, f] : llvm::enumerate(parts))
1011 groupedFlows[groupOf(f)].push_back(k);
1012 for (const auto &[ends, ids] : pf.packetIdsTo)
1013 flowIds[partsOf.at(ends.first).front()].insert(ids.begin(), ids.end());
1014 relateParts();
1015 // A pinned source's other packets follow the pinned tree where they go
1016 // everywhere it goes, so they leave each switchbox by its master sets, and
1017 // otherwise route as a part of their own.
1018 for (size_t k = 0, n = parts.size(); k < n; k++) {
1019 if (!isPinned(parts[k]))
1020 continue;
1021 const PathEndPoint &src = parts[k].src;
1022 const std::set<int> &prioritized = pf.priorityIds.at(src);
1023 std::set<PathEndPoint> treeDsts;
1024 std::map<int, std::set<PathEndPoint>> dstsOf;
1025 for (const auto &[ends, ids] : pf.packetIdsTo)
1026 if (ends.first == src)
1027 for (int id : ids)
1028 (prioritized.count(id) ? treeDsts : dstsOf[id]).insert(ends.second);
1029 std::set<int> others;
1030 for (const auto &[id, dsts] : dstsOf)
1031 if (!prioritized.count(id) && dsts != treeDsts)
1032 others.insert(id);
1033 if (!others.empty())
1034 splitPart(k, others);
1035 }
1036 for (const auto &[src, tree] : pf.constraints.pinned) {
1037 std::map<PathEndPoint, SmallVector<int, 4>> sets;
1038 for (const TreeHop &h : tree) {
1039 auto to = pf.nodeIds.find(h.to);
1040 if (h.from.coords == h.to.coords && to != pf.nodeIds.end())
1041 sets[h.from].push_back(stateId(to->second, Out));
1042 }
1043 for (auto &[_, set] : sets) {
1044 llvm::sort(set);
1045 overlayMasters.insert(set.begin(), set.end());
1046 overlaySets.push_back(std::move(set));
1047 }
1048 }
1049}
1050
1051// Route `later` before `earlier` from the next iteration on, unless the two
1052// were reordered before and this is not `again`.
1053bool Pathfinder::RouteState::reorder(int later, int earlier, bool again) {
1054 if (!again && (reordered.count({earlier, later}) ||
1055 !reordered.insert({later, earlier}).second))
1056 return false;
1057 SmallVector<int, 8> &group = groupedFlows[groupOf(parts[later])];
1058 if (!llvm::is_contained(group, earlier))
1059 return false;
1060 group.erase(llvm::find(group, later));
1061 group.insert(llvm::find(group, earlier), later);
1062 return true;
1063}
1064
1065// Whether a tree whose packets leave a switchbox by `masters` from one slave
1066// port may leave it by `next` too; see overlayMasters.
1067bool Pathfinder::RouteState::mayLeave(ArrayRef<int> masters, int next) const {
1068 if (!overlayMasters.count(next) &&
1069 llvm::none_of(masters, [&](int m) { return overlayMasters.count(m); }))
1070 return true;
1071 return llvm::any_of(overlaySets, [&](const SmallVector<int, 4> &set) {
1072 return llvm::is_contained(set, next) && llvm::all_of(masters, [&](int m) {
1073 return llvm::is_contained(set, m);
1074 });
1075 });
1076}
1077
1079 return f.isPriorityFlow && pf.constraints.pinned.count(f.src);
1080}
1081
1083 return isPinned(f) ? std::numeric_limits<int>::min() : f.packetGroupId;
1084}
1085
1086SmallVector<int, 4> Pathfinder::RouteState::idsTo(int flow,
1087 int dstState) const {
1088 SmallVector<int, 4> ids;
1089 auto it =
1090 pf.packetIdsTo.find({parts[flow].src, pf.nodes[stateNode(dstState)]});
1091 if (it != pf.packetIdsTo.end())
1092 for (int id : it->second)
1093 if (flowIds[flow].count(id))
1094 ids.push_back(id);
1095 return ids;
1096}
1097
1099 sameId.assign(parts.size(), {});
1100 conflicting.assign(parts.size(), llvm::BitVector(parts.size()));
1101 for (size_t a = 0; a < parts.size(); a++)
1102 for (size_t b = 0; b < parts.size(); b++) {
1103 // Trees with an id in common join where they meet, so are never apart.
1104 bool same = llvm::any_of(flowIds[a],
1105 [&](int id) { return flowIds[b].count(id); });
1106 if (a != b && same)
1107 sameId[a].push_back(b);
1108 if (a < b && parts[a].packetId && parts[b].packetId &&
1109 ((!same &&
1110 apartSources.count(std::minmax(parts[a].src, parts[b].src))) ||
1111 (pf.constraints.conflict &&
1112 pf.constraints.conflict(parts[a].src, flowIds[a], parts[b].src,
1113 flowIds[b])))) {
1114 conflicting[a].set(b);
1115 conflicting[b].set(a);
1116 }
1117 }
1118}
1119
1120int Pathfinder::RouteState::splitPart(int flow, const std::set<int> &ids) {
1121 int part = parts.size();
1122 Flow f = parts[flow];
1123 LLVM_DEBUG({
1124 llvm::dbgs() << "\t\tRouting ids";
1125 for (int id : ids)
1126 llvm::dbgs() << ' ' << id;
1127 llvm::dbgs() << " from " << describeTilePort(f.src.coords, f.src.port)
1128 << " apart\n";
1129 });
1130 for (int id : ids)
1131 flowIds[flow].erase(id);
1132 flowIds.push_back(ids);
1133 auto carries = [&](int k, const PathEndPoint &p) {
1134 auto it = pf.packetIdsTo.find({f.src, p});
1135 return it != pf.packetIdsTo.end() &&
1136 llvm::any_of(it->second, [&](int i) { return flowIds[k].count(i); });
1137 };
1138 llvm::erase_if(f.dsts,
1139 [&](const PathEndPoint &p) { return !carries(part, p); });
1140 llvm::erase_if(parts[flow].dsts,
1141 [&](const PathEndPoint &p) { return !carries(flow, p); });
1142 f.packetId = *ids.begin();
1143 std::optional<int> &flowId = parts[flow].packetId;
1144 if (flowId && ids.count(*flowId))
1145 flowId = *flowIds[flow].begin();
1146 auto prioritized = [&](const std::set<int> &carried) {
1147 auto prio = pf.priorityIds.find(f.src);
1148 return prio != pf.priorityIds.end() && llvm::any_of(carried, [&](int id) {
1149 return prio->second.count(id);
1150 });
1151 };
1152 f.isPriorityFlow = prioritized(ids);
1153 parts[flow].isPriorityFlow = prioritized(flowIds[flow]);
1154 parts.push_back(f);
1155 partsOf[f.src].push_back(part);
1156 groupedFlows[groupOf(f)].push_back(part);
1157 relateParts();
1158 treeOf.emplace_back();
1159 circuitHops.emplace_back();
1160 treeDsts.emplace_back();
1161 joinedHops.emplace_back();
1162 for (auto [a, b] : std::set<std::pair<int, int>>(noJoin))
1163 if (a == flow || b == flow)
1164 noJoin.insert({a == flow ? part : a, b == flow ? part : b});
1165 splitsOf.push_back(splitsOf[flow]);
1166 partSplits.push_back(partSplits[flow]);
1167 for (auto [at, other] : partSplits[flow])
1168 partSplits[other].insert({at, part});
1169 for (auto [at, a, b, apart] : splitsOf[flow])
1170 if (ids.count(a) != ids.count(b) &&
1171 flowIds[flow].count(ids.count(a) ? b : a)) {
1172 partSplits[flow].insert({at, part});
1173 partSplits[part].insert({at, flow});
1174 }
1175 return part;
1176}
1177
1178// A link with a channel no flow takes and a free master port driving it
1179// was overused only by the way the flows went; its flows could spread.
1180bool Pathfinder::hasRoom(const SwitchboxConnect &sb) const {
1181 if (sb.srcCoords == sb.dstCoords)
1182 return true;
1183 auto xbar = graph.find({sb.srcCoords, sb.srcCoords});
1184 for (size_t i = 0; i < sb.srcPorts.size(); i++)
1185 for (size_t j = 0; j < sb.dstPorts.size(); j++) {
1186 const SwitchboxConnect::Cell &c = sb.at(i, j);
1187 if (!c.available || c.usedCapacity > 0)
1188 continue;
1189 if (xbar == graph.end())
1190 return true;
1191 const SwitchboxConnect &x = xbar->second;
1192 int k = x.dstIndex(sb.srcPorts[i]);
1193 if (k < 0)
1194 return true;
1195 for (size_t row = 0; row < x.srcPorts.size(); row++)
1196 if (x.at(row, k).available)
1197 return true;
1198 }
1199 return false;
1200}
1201
1202llvm::Error Pathfinder::explainNoRouting(const RouteState &st) const {
1203 // A prioritized flow keeps the route it takes alone, so the others
1204 // may have had to fit around it.
1205 auto overused = [](const SwitchboxConnect &sb) {
1206 return sb.srcCoords != sb.dstCoords &&
1207 llvm::any_of(sb.cells, [](const SwitchboxConnect::Cell &c) {
1208 return c.usedCapacity > maxCircuitStreamCapacity;
1209 });
1210 };
1211 auto link = [](const SwitchboxConnect &sb) -> std::string {
1212 return llvm::formatv("from tile ({0}, {1}) to ({2}, {3})", sb.srcCoords.col,
1213 sb.srcCoords.row, sb.dstCoords.col, sb.dstCoords.row);
1214 };
1215 const Flow *prioritized = nullptr;
1216 llvm::SetVector<const SwitchboxConnect *> held;
1217 for (auto [k, f] : llvm::enumerate(st.parts)) {
1218 if (!st.isPinned(f))
1219 continue;
1220 prioritized = prioritized ? prioritized : &f;
1221 for (const auto &[_, hop] : st.treeOf[k]) {
1222 if (overused(*hop.second.sb)) {
1223 return llvm::make_error<PinnedRoutingFailure>(
1224 describePrioritized(describeTilePort(f.src.coords, f.src.port)) +
1225 ", and it holds a channel " + link(*hop.second.sb) +
1226 " the other flows need.");
1227 }
1228 if (hop.second.sb->srcCoords != hop.second.sb->dstCoords)
1229 held.insert(hop.second.sb);
1230 }
1231 }
1232 if (prioritized && llvm::any_of(graph, [&](const auto &entry) {
1233 return overused(entry.second);
1234 })) {
1235 return llvm::make_error<PinnedRoutingFailure>(
1237 describeTilePort(prioritized->src.coords, prioritized->src.port)) +
1238 ", and the router found no routing for the other flows around the "
1239 "channels it holds " +
1240 joinNames(llvm::to_vector(
1241 llvm::map_range(llvm::make_pointee_range(held), link))) +
1242 ".");
1243 }
1244 // The routing check says more than the overuse it led to.
1245 if (!checkReason.empty())
1246 return llvm::make_error<RoutingFailure>(checkReason);
1247 // Name the channel the last iteration overused that was overused in the
1248 // most iterations, on a link with no room if there is one, and the flows
1249 // the last one routed through it.
1250 const SwitchboxConnect *worst = nullptr;
1251 int worstI = 0, worstJ = 0;
1252 std::pair<bool, int> worstRank{false, 0};
1253 for (const auto &[_, sb] : graph) {
1254 std::optional<bool> roomless;
1255 for (size_t i = 0; i < sb.srcPorts.size(); i++)
1256 for (size_t j = 0; j < sb.dstPorts.size(); j++) {
1257 if (sb.at(i, j).usedCapacity <= maxCircuitStreamCapacity)
1258 continue;
1259 if (!roomless)
1260 roomless = !hasRoom(sb);
1261 std::pair<bool, int> rank{*roomless, sb.at(i, j).overCapacity};
1262 if (rank > worstRank) {
1263 worst = &sb;
1264 worstI = i;
1265 worstJ = j;
1266 worstRank = rank;
1267 }
1268 }
1269 }
1270 if (!worst)
1271 return llvm::make_error<RoutingFailure>("");
1272 bool crossbar = worst->srcCoords == worst->dstCoords;
1273 std::vector<std::string> users;
1274 for (const auto &[src, settings] : st.routing.settings) {
1275 auto it = settings.find(worst->srcCoords);
1276 if (it == settings.end())
1277 continue;
1278 const SwitchSetting &s = it->second;
1279 bool uses = false;
1280 for (size_t k = 0; k < s.dsts.size() && !uses; k++)
1281 uses = crossbar
1282 ? k < s.srcs.size() && s.srcs[k] == worst->srcPorts[worstI] &&
1283 s.dsts[k] == worst->dstPorts[worstJ]
1284 : llvm::is_contained(worst->srcPorts, s.dsts[k]);
1285 if (uses)
1286 users.push_back(describeTilePort(src.coords, src.port));
1287 }
1288 std::string where =
1289 crossbar ? llvm::formatv("the connection from {0} to {1} at tile ({2}, "
1290 "{3})",
1291 describePort(worst->srcPorts[worstI]),
1292 describePort(worst->dstPorts[worstJ]),
1293 worst->srcCoords.col, worst->srcCoords.row)
1294 .str()
1295 : "the links " + link(*worst);
1296 if (users.empty())
1297 return llvm::make_error<RoutingFailure>(
1298 "the router found no routing that fits " + where + ".");
1299 return llvm::make_error<RoutingFailure>(
1300 "the flows from " + joinNames(users) + " need " + where +
1301 ", and the router found no routing that fits them.");
1302}
1303
1304llvm::Error Pathfinder::cutOverflow() const {
1305 // Each circuit tree crossing a boundary needs a channel of its own, and the
1306 // packet flows one more, or one each for streams no two of which may share
1307 // an arbiter, since a channel's master port has one. Channels fixed
1308 // connections hold are not the router's to use, so this rejects only what
1309 // it could not route anyway.
1310 auto onDevice = [&](TileID t) { return t.col < columns && t.row < rows; };
1311 std::map<std::tuple<bool, int, bool>, size_t> channels;
1312 for (const auto &[ends, sb] : graph) {
1313 const auto &[a, b] = ends;
1314 auto xbar = graph.find({a, a});
1315 if (a == b || !onDevice(a) || !onDevice(b) || xbar == graph.end())
1316 continue;
1317 bool rows = a.col == b.col;
1318 int below = rows ? std::min(a.row, b.row) : std::min(a.col, b.col);
1319 bool up = rows ? b.row > a.row : b.col > a.col;
1320 const SwitchboxConnect &x = xbar->second;
1321 // A boundary with no available channel at all (every one is a fixed
1322 // connection's) still needs an entry, so flows that must cross it are
1323 // not skipped as if the boundary did not exist.
1324 channels[{rows, below, up}] += 0;
1325 for (Port port : sb.srcPorts) {
1326 int k = x.dstIndex(port);
1327 if (k >= 0 &&
1328 llvm::any_of(llvm::seq<size_t>(0, x.srcPorts.size()),
1329 [&](size_t i) { return x.at(i, k).available; }))
1330 channels[{rows, below, up}]++;
1331 }
1332 }
1333 for (const auto &[cut, available] : channels) {
1334 bool rows = std::get<0>(cut), up = std::get<2>(cut);
1335 int below = std::get<1>(cut);
1336 auto low = [&](TileID t) { return (rows ? t.row : t.col) <= below; };
1337 std::vector<std::string> circuits;
1338 bool packets = false;
1339 for (const Flow &f : flows) {
1340 if (low(f.src.coords) != up ||
1341 llvm::none_of(f.dsts, [&](const PathEndPoint &d) {
1342 return low(d.coords) != up;
1343 }))
1344 continue;
1345 if (f.packetId)
1346 packets = true;
1347 else
1348 circuits.push_back(describeTilePort(f.src.coords, f.src.port));
1349 }
1350 PacketsApart apart;
1351 if (packets && constraints.apartAcross && available > circuits.size())
1352 apart = constraints.apartAcross(low, up, available - circuits.size());
1353 size_t needed =
1354 circuits.size() + std::max<size_t>(packets, apart.flows.size());
1355 LLVM_DEBUG(llvm::dbgs() << "Cut " << (rows ? "row " : "column ") << below
1356 << (up ? " up" : " down") << ": " << needed
1357 << " of " << available << " channels\n");
1358 if (needed <= available)
1359 continue;
1360 int from = up ? below : below + 1, to = up ? below + 1 : below;
1361 auto channel = [](size_t n) { return n == 1 ? "channel" : "channels"; };
1362 std::string who =
1363 circuits.empty() ? "" : "the circuit flows from " + joinNames(circuits);
1364 if (apart.flows.size() > 1)
1365 who += (who.empty() ? "" : ", and ") + joinNames(apart.flows) +
1366 ", no two of which can share an arbiter,";
1367 else if (packets)
1368 who =
1369 circuits.empty() ? "the packet flows" : who + ", with packet flows,";
1370 std::string why =
1371 apart.flows.size() > 1 ? " For example, " + apart.why : "";
1372 return llvm::make_error<RoutingFailure>(llvm::formatv(
1373 "{0} cross from {1} {2} to {1} {3}, which takes {4} {5} across, and "
1374 "it has {6} {7}.{8}",
1375 who, rows ? "row" : "column", from, to, needed, channel(needed),
1376 available, channel(available), why));
1377 }
1378 return llvm::Error::success();
1379}
1380
1381// One flow's tree, grown by routePart.
1384
1385 // A destination port is driven by its switchbox, so it is reached on
1386 // the Out side.
1387 int dstState(const PathEndPoint &p) const {
1388 return stateId(pf.nodeIds.at(p), Out);
1389 }
1390 bool isPending(int state) const;
1391 void reach(ArrayRef<int> dsts);
1392 bool joinable(int at, ArrayRef<int> dsts) const;
1393 void findJoins();
1394 void meet();
1395 void meet(int owner);
1396 void search(const llvm::DenseSet<int> &drop, ArrayRef<int> targets,
1397 const llvm::DenseSet<int> &off = {});
1398 bool trace(int state);
1399 llvm::DenseSet<int> splitOff(int dst) const;
1400 llvm::DenseSet<int> trunkOff(int dst) const;
1401 llvm::Error placePinned();
1402 llvm::Error grow();
1403 void reroute();
1404 void claim();
1405 bool join(int state, int predId, const Edge &e);
1406 void joinTrees();
1407 void record();
1408
1411 int flow;
1412 const Flow &part;
1413 const llvm::BitVector *avoid;
1414 // The route the flow takes alone, if it is pinned to it.
1415 const std::vector<TreeHop> *pinned = nullptr;
1417 // The states the tree reaches, and how many hops from the source each is.
1418 SmallVector<int, 16> tree, treeHops;
1419 SmallVector<int, 16> seeds;
1420 SmallVector<double, 16> seedCosts;
1421 // The tree's hops, by the state each reaches, from which state and by
1422 // which edge, in the order they were traced. They take effect once the
1423 // tree is final.
1424 llvm::MapVector<int, std::pair<int, Edge>> planned;
1425 llvm::DenseMap<int, int> children;
1426 // A branch off a port the tree already crosses joins that port's unit
1427 // (see planArbiters), so it avoids the flows conflicting with any flow
1428 // on those arbiters too.
1429 llvm::DenseMap<int, llvm::BitVector> branchAvoid;
1430 SmallVector<PathEndPoint, 4> pending;
1431 bool toSelf = false;
1432 // The destination to reach first, if not the nearest.
1433 std::optional<PathEndPoint> first;
1434 // A switchbox routes on the id alone, so packets that reach a master
1435 // port another source's tree takes an id they share by go wherever
1436 // that id goes from there. The flow may join the tree there only if
1437 // it still has to reach each of those destinations, with just the ids
1438 // they share.
1439 llvm::DenseMap<int, SmallVector<int, 4>> joins;
1440 llvm::DenseMap<int, SmallVector<int, 2>> joinOwners;
1441 llvm::DenseSet<int> stops, unjoinable;
1442 // The destinations the flow still has to reach that each other source's
1443 // tree reaches with the ids they share, less the trees it may not join.
1444 llvm::MapVector<int, SmallVector<int, 4>> sharedDsts;
1445 llvm::DenseSet<int> disagree, foreign;
1446 // The destinations the flow reaches where it meets another tree.
1447 SmallVector<int, 4> met;
1448 // The master ports the source's other parts take at the tiles the flow
1449 // was split apart from them at.
1450 llvm::DenseSet<int> partOff;
1451 // The destinations the flow reaches by one master port out of its source
1452 // switchbox (see RouteState::trunked).
1453 llvm::DenseSet<int> trunkDsts;
1454 SmallVector<int, 4> reached;
1455 SmallVector<std::pair<int, const SmallVector<int, 4> *>, 2> joinedAt;
1456 SmallVector<std::pair<int, std::pair<int, Edge>>, 8> pinnedJoins;
1457 SmallVector<int, 4> pinnedJoinDsts;
1458};
1459
1461 : pf(pf), st(st), flow(flow), part(st.parts[flow]),
1462 avoid(part.packetId ? &st.conflicting[flow] : nullptr) {
1463 if (st.isPinned(part))
1464 pinned = &pf.constraints.pinned.at(part.src);
1465 // The flow source port feeds into its switchbox, so the tree starts
1466 // on its In side and the first edge taken is necessarily a crossbar
1467 // hop.
1468 int srcId = pf.nodeIds.at(part.src);
1469 tree.push_back(stateId(srcId, In));
1470 treeHops.push_back(0);
1471 st.processedStamp[tree.front()] = ++st.curStamp;
1472 for (const PathEndPoint &endPoint : part.dsts) {
1473 // Route to self: the port is both ends. Where its switchbox cannot
1474 // connect it to itself (Core to Core), the stream has to leave and
1475 // come back, which Dijkstra finds from the In side to the Out side.
1476 // A tree the flow joins may reach the port for it instead. A port in
1477 // overlayMasters is reached like any other destination, so mayLeave
1478 // still holds.
1479 int self = stateId(srcId, Out);
1480 if (endPoint == part.src && !st.overlayMasters.count(self) &&
1481 llvm::any_of(pf.adjacency[srcId],
1482 [&](const Edge &e) { return e.dst == srcId; })) {
1483 toSelf = true;
1484 continue;
1485 }
1486 pending.push_back(endPoint);
1487 }
1488 findJoins();
1489 for (auto it = st.trunked.lower_bound({flow, 0});
1490 !st.untrunked.count(part.src) && it != st.trunked.end() &&
1491 it->first == flow;
1492 ++it) {
1493 SmallVector<int, 4> shared;
1494 for (const PathEndPoint &dst : st.parts[it->second].dsts)
1495 if (llvm::is_contained(part.dsts, dst))
1496 shared.push_back(dstState(dst));
1497 if (shared.size() > 1)
1498 trunkDsts.insert(shared.begin(), shared.end());
1499 }
1500 for (auto [at, other] : st.partSplits[flow])
1501 for (const auto &[state, _] : st.treeOf[other])
1502 if ((state & 1) == Out && pf.nodes[stateNode(state)].coords == at)
1503 partOff.insert(state);
1504}
1505
1507 return (toSelf && state == dstState(part.src)) ||
1508 llvm::any_of(pending, [&](const PathEndPoint &p) {
1509 return dstState(p) == state;
1510 });
1511}
1512
1513void Pathfinder::TreeBuilder::reach(ArrayRef<int> dsts) {
1514 llvm::erase_if(pending, [&](const PathEndPoint &p) {
1515 return llvm::is_contained(dsts, dstState(p));
1516 });
1517 if (llvm::is_contained(dsts, dstState(part.src)))
1518 toSelf = false;
1519}
1520
1521bool Pathfinder::TreeBuilder::joinable(int at, ArrayRef<int> dsts) const {
1522 return !unjoinable.count(at) &&
1523 llvm::all_of(dsts, [&](int dst) { return isPending(dst); });
1524}
1525
1527 auto shared = [&](ArrayRef<int> ids, int other) {
1528 std::set<int> common;
1529 for (int id : ids)
1530 if (st.flowIds[other].count(id))
1531 common.insert(id);
1532 return common;
1533 };
1534 for (int other : st.sameId[flow]) {
1535 // A tree reaching its own source port takes no hop for it.
1536 const Flow &o = st.parts[other];
1537 int self = dstState(o.src);
1538 if (!st.treeDsts[other].empty() && llvm::is_contained(o.dsts, o.src) &&
1539 isPending(self) && !shared(st.idsTo(other, self), flow).empty())
1540 sharedDsts[other].push_back(self);
1541 for (int dst : st.treeDsts[other]) {
1542 std::set<int> common = shared(st.idsTo(other, dst), flow);
1543 if (common.empty())
1544 continue;
1545 // Joining shares the other flow's arbiters below the join with the
1546 // packets it takes there. A prioritized flow's tree is pinned, so it
1547 // joins only trees pinned with it. A destination of the other flow alone
1548 // keeps the flow off the hops toward it, as no join there is joinable.
1549 bool agree =
1550 (!isPending(dst) || common == shared(st.idsTo(flow, dst), other)) &&
1551 !(pf.constraints.conflict &&
1552 pf.constraints.conflict(part.src, common, o.src,
1553 st.flowIds[other])) &&
1554 !st.noJoin.count({flow, other}) &&
1555 (!part.isPriorityFlow || st.parts[other].isPriorityFlow);
1556 if (!agree)
1557 disagree.insert(other);
1558 else if (isPending(dst))
1559 sharedDsts[other].push_back(dst);
1560 for (auto it = st.treeOf[other].find(dst); it != st.treeOf[other].end();
1561 it = st.treeOf[other].find(it->second.first)) {
1562 int up = it->second.first;
1563 if ((up & 1) != Out)
1564 continue;
1565 stops.insert(up);
1566 if (!agree)
1567 unjoinable.insert(up);
1568 if (!llvm::is_contained(joins[up], dst))
1569 joins[up].push_back(dst);
1570 if (!llvm::is_contained(joinOwners[up], other))
1571 joinOwners[up].push_back(other);
1572 }
1573 }
1574 }
1575 // A tree with other ids reaching two of the flow's destinations is met the
1576 // same way, the flow taking its hops below the meeting with its own ids.
1577 // Not for a destination a tree with one of its ids also reaches, which the
1578 // flow has to meet on the way there instead.
1579 if (!part.packetId || part.isPriorityFlow)
1580 return;
1581 llvm::DenseSet<int> sameIdDsts;
1582 for (int other : st.sameId[flow])
1583 for (const PathEndPoint &dst : st.parts[other].dsts)
1584 if (!shared(st.idsTo(other, dstState(dst)), flow).empty())
1585 sameIdDsts.insert(dstState(dst));
1586 for (auto [other, o] : llvm::enumerate(st.parts)) {
1587 int k = other;
1588 if (k == flow || o.src == part.src || !o.packetId || o.isPriorityFlow ||
1589 st.treeDsts[k].empty() || llvm::is_contained(st.sameId[flow], k) ||
1590 st.conflicting[flow].test(k) || st.noJoin.count({flow, k}))
1591 continue;
1592 SmallVector<int, 4> dsts;
1593 for (int dst : st.treeDsts[k])
1594 if (isPending(dst) && dst != dstState(part.src) &&
1595 !sameIdDsts.count(dst) && st.treeOf[k].count(dst))
1596 dsts.push_back(dst);
1597 if (dsts.size() > 1) {
1598 sharedDsts[k] = std::move(dsts);
1599 foreign.insert(k);
1600 }
1601 }
1602}
1603
1604// Trees from two sources wait on each other wherever they meet (see HoldCycle),
1605// so two trees meeting at two switchboxes can each hold one arbiter and wait at
1606// the other. The flow meets each tree it shares destinations with at one
1607// switchbox, those it shares the most with first, on that tree's way from its
1608// source to where it branches for them, by a slave port that takes each master
1609// port the tree takes there toward them. Only a packet going to two of them can
1610// hold the arbiter where the trees meet for one and wait where they meet for
1611// the other, so two trees meet only when each sends some id to two shared
1612// destinations.
1614 if (pf.meetApart)
1615 return;
1616 auto spans = [&](int k, ArrayRef<int> dsts) {
1617 return llvm::any_of(st.flowIds[k], [&](int id) {
1618 return llvm::count_if(dsts, [&](int dst) {
1619 return llvm::is_contained(st.idsTo(k, dst), id);
1620 }) > 1;
1621 });
1622 };
1623 auto mustMeet = [&](int other) {
1624 return spans(flow, sharedDsts[other]) && spans(other, sharedDsts[other]);
1625 };
1626 SmallVector<int, 4> owners;
1627 for (int other : llvm::make_first_range(sharedDsts))
1628 if (!disagree.count(other) && mustMeet(other))
1629 owners.push_back(other);
1630 llvm::stable_sort(owners, [&](int a, int b) {
1631 return sharedDsts[a].size() > sharedDsts[b].size();
1632 });
1633 for (int owner : owners) {
1634 llvm::erase_if(sharedDsts[owner], [&](int dst) { return !isPending(dst); });
1635 if (mustMeet(owner))
1636 meet(owner);
1637 }
1638}
1639
1641 st.unmet.insert({flow, owner});
1642 pf.treesMet = true;
1643 bool other = foreign.count(owner);
1644 const auto &t = st.treeOf[owner];
1645 const Flow &o = st.parts[owner];
1646 int root = stateId(pf.nodeIds.at(o.src), In);
1647 // The owner's states from its source down to each shared destination.
1648 SmallVector<SmallVector<int, 16>, 4> paths;
1649 for (int dst : sharedDsts[owner]) {
1650 SmallVector<int, 16> &path = paths.emplace_back(1, dst);
1651 for (auto it = t.find(dst); it != t.end(); it = t.find(it->second.first))
1652 path.push_back(it->second.first);
1653 if (path.size() == 1)
1654 path.push_back(root);
1655 if (path.back() != root)
1656 return;
1657 std::reverse(path.begin(), path.end());
1658 }
1659 size_t trunk = 0;
1660 while (llvm::all_of(paths, [&](const auto &path) {
1661 return trunk + 1 < path.size() && path[trunk] == paths[0][trunk];
1662 }))
1663 trunk++;
1664 struct Meeting {
1665 int in;
1666 SmallVector<std::pair<int, Edge>, 4> hops;
1667 double cost;
1668 size_t at;
1669 };
1670 SmallVector<Meeting, 8> meetings;
1671 SmallVector<int, 8> targets;
1672 // Below a meeting with other ids the flow takes the owner's hops itself.
1673 auto below = [&](size_t k) {
1674 llvm::MapVector<int, std::pair<int, Edge>> hops;
1675 for (const auto &path : paths)
1676 for (size_t l = k + 1; l + 1 < path.size(); l++)
1677 hops.insert({path[l + 1], t.find(path[l + 1])->second});
1678 return hops;
1679 };
1680 for (size_t k = 0; k < trunk; k++) {
1681 int at = paths[0][k];
1682 if ((at & 1) != In)
1683 continue;
1684 SetVector<int> next;
1685 for (const auto &path : paths)
1686 next.insert(path[k + 1]);
1687 if (!other && llvm::any_of(next, [&](int s) {
1688 return !isPending(s) && !(joins.count(s) && joinable(s, joins[s]));
1689 }))
1690 continue;
1691 auto taken = [&](int s) {
1692 return st.processedStamp[s] == st.curStamp || stops.count(s) ||
1693 partOff.count(s);
1694 };
1695 if (other && (llvm::any_of(next, taken) ||
1696 llvm::any_of(below(k), [&](const auto &hop) {
1697 const Edge &e = hop.second.second;
1698 return taken(hop.first) ||
1699 (e.sb->srcCoords == e.sb->dstCoords &&
1700 e.sb->circuitOnlyDst[e.j]);
1701 })))
1702 continue;
1703 SmallVector<int, 4> leaving;
1704 if (llvm::any_of(next, [&](int s) {
1705 bool ok = st.mayLeave(leaving, s);
1706 leaving.push_back(s);
1707 return !ok;
1708 }))
1709 continue;
1710 TileID tile = pf.nodes[stateNode(at)].coords;
1711 auto sb = pf.graph.find({tile, tile});
1712 if (sb == pf.graph.end())
1713 continue;
1714 for (Port port : sb->second.srcPorts) {
1715 auto node = pf.nodeIds.find({tile, port});
1716 if (node == pf.nodeIds.end())
1717 continue;
1718 int in = stateId(node->second, In);
1719 if (in == at || t.count(in))
1720 continue;
1721 Meeting m{in, {}, 0, k};
1722 for (int s : next)
1723 for (const Edge &e : pf.adjacency[node->second])
1724 if (e.dst == stateNode(s) && e.sb == &sb->second &&
1725 !(part.packetId && e.sb->circuitOnlyDst[e.j])) {
1726 m.hops.push_back({s, e});
1727 m.cost += pf.edgeWeight(e, part.packetId, avoid, nullptr);
1728 break;
1729 }
1730 if (m.hops.size() == next.size()) {
1731 meetings.push_back(std::move(m));
1732 targets.push_back(in);
1733 }
1734 }
1735 }
1736 if (meetings.empty())
1737 return;
1738 llvm::DenseSet<int> off;
1739 if (other)
1740 for (const auto &[s, _] : below(0))
1741 off.insert(s);
1742 search({}, targets, off);
1743 const Meeting *best = nullptr;
1744 for (const Meeting &m : meetings)
1745 if (pf.distance[m.in] < INF &&
1746 (!best ||
1747 pf.distance[m.in] + m.cost < pf.distance[best->in] + best->cost))
1748 best = &m;
1749 if (!best || !trace(best->in))
1750 return;
1751 st.unmet.erase({flow, owner});
1752 st.met.insert({flow, owner});
1753 int hops = treeHops[llvm::find(tree, best->in) - tree.begin()] + 1;
1754 auto add = [&](int s, int from, const Edge &e, int depth) {
1755 planned.insert({s, {from, e}});
1756 ++children[from];
1757 st.processedStamp[s] = st.curStamp;
1758 tree.push_back(s);
1759 treeHops.push_back(depth);
1760 };
1761 for (const auto &[s, e] : best->hops) {
1762 add(s, best->in, e, hops);
1763 if (other) {
1764 st.joinedHops[flow].push_back({owner, e});
1765 continue;
1766 }
1767 if (isPending(s)) {
1768 met.push_back(s);
1769 reach(s);
1770 continue;
1771 }
1772 const SmallVector<int, 4> &dsts = joins[s];
1773 reach(dsts);
1774 joinedAt.push_back({s, &dsts});
1775 ++children[s];
1776 }
1777 if (!other)
1778 return;
1779 for (const auto &[s, hop] : below(best->at)) {
1780 const auto &[from, e] = hop;
1781 add(s, from, e, treeHops[llvm::find(tree, from) - tree.begin()] + 1);
1782 st.joinedHops[flow].push_back({owner, e});
1783 }
1784 for (int dst : sharedDsts[owner]) {
1785 met.push_back(dst);
1786 reach(dst);
1787 }
1788}
1789
1790// Dijkstra from the tree, less the states in `drop`, and not through
1791// those in `off`, until the states in `targets` are settled.
1792void Pathfinder::TreeBuilder::search(const llvm::DenseSet<int> &drop,
1793 ArrayRef<int> targets,
1794 const llvm::DenseSet<int> &off) {
1795 branchAvoid.clear();
1796 llvm::DenseMap<int, llvm::BitVector> onArbiter;
1797 if (avoid)
1798 for (const auto &[state, hop] : planned) {
1799 const auto &[from, e] = hop;
1800 if (e.sb->srcCoords != e.sb->dstCoords || drop.count(state))
1801 continue;
1802 llvm::BitVector &away =
1803 branchAvoid.try_emplace(from, st.conflicting.size()).first->second;
1804 llvm::BitVector &on =
1805 onArbiter.try_emplace(from, st.conflicting.size()).first->second;
1806 for (int other : e.sb->unitFlows(e.j)) {
1807 on.set(other);
1808 if (other != flow)
1809 away |= st.conflicting[other];
1810 }
1811 }
1812 // A flow sharing an arbiter with itself is no conflict, and flows
1813 // already on those arbiters share them whatever the branch does.
1814 for (auto &[from, away] : branchAvoid) {
1815 away.reset(onArbiter.find(from)->second);
1816 away.reset(flow);
1817 }
1818 seeds.clear();
1819 seedCosts.clear();
1820 for (auto [state, hops] : llvm::zip_equal(tree, treeHops))
1821 if (!drop.count(state) && !off.count(state)) {
1822 seeds.push_back(state);
1823 seedCosts.push_back(treeSeedFactor * hops);
1824 }
1825 llvm::DenseSet<int> blocked;
1826 if (!off.empty()) {
1827 blocked = stops;
1828 blocked.insert(off.begin(), off.end());
1829 }
1830 llvm::DenseMap<int, SmallVector<int, 4>> masters;
1831 bool limited = !st.overlayMasters.empty();
1832 if (limited)
1833 for (const auto &[state, hop] : planned)
1834 if ((hop.first & 1) == In && !drop.count(state))
1835 masters[hop.first].push_back(state);
1836 auto mayCross = [&](int from, int to) {
1837 auto it = masters.find(from);
1838 return st.mayLeave(it == masters.end() ? ArrayRef<int>{} : it->second, to);
1839 };
1840 pf.dijkstraShortestPaths(
1841 seeds, seedCosts, part.packetId, avoid, &branchAvoid,
1842 off.empty() ? &stops : &blocked, targets,
1843 limited ? llvm::function_ref<bool(int, int)>(mayCross) : nullptr);
1844}
1845
1846// Trace the path Dijkstra found to `state` back to the tree.
1848 size_t grown = tree.size();
1849 while (st.processedStamp[state] != st.curStamp) {
1850 // If Dijkstra never reached this node it has no predecessor; the
1851 // destination is unroutable under the current demand.
1852 int pred = pf.preds[state];
1853 if (pred < 0)
1854 return false;
1855 planned.insert({state, {pred, pf.predEdge[state]}});
1856 ++children[pred];
1857 st.processedStamp[state] = st.curStamp;
1858 tree.push_back(state);
1859 state = pred;
1860 }
1861 // The new hops were traced from the destination back to the tree.
1862 int hops = treeHops[llvm::find(tree, state) - tree.begin()] +
1863 static_cast<int>(tree.size() - grown);
1864 while (treeHops.size() < tree.size())
1865 treeHops.push_back(hops--);
1866 return true;
1867}
1868
1869// The path to `dst` stays off the hops another destination's path
1870// takes below where the check split the two apart, and off the
1871// slave port it takes there too if one of them has to reach the tile
1872// on its own.
1873llvm::DenseSet<int> Pathfinder::TreeBuilder::splitOff(int dst) const {
1874 llvm::DenseSet<int> off = partOff;
1875 auto carries = [&](int d, int id) {
1876 return llvm::is_contained(st.idsTo(flow, d), id);
1877 };
1878 auto only = [&](int d, int id, int other) {
1879 return carries(d, id) && !carries(d, other);
1880 };
1881 for (auto [at, a, b, apart] : st.splitsOf[flow])
1882 for (int other : reached) {
1883 bool splits = !apart ? (only(dst, a, b) && only(other, b, a)) ||
1884 (only(dst, b, a) && only(other, a, b))
1885 : (only(dst, a, b) && other == *apart) ||
1886 (dst == *apart && only(other, a, b));
1887 if (!splits)
1888 continue;
1889 SmallVector<int, 8> below;
1890 int s = other;
1891 auto entersAt = [&, tile = at](int state) {
1892 return (state & 1) == In && pf.nodes[stateNode(state)].coords == tile;
1893 };
1894 for (auto *hop = planned.find(s); !entersAt(s) && hop != planned.end();
1895 hop = planned.find(s)) {
1896 below.push_back(s);
1897 s = hop->second.first;
1898 }
1899 if (!entersAt(s))
1900 continue;
1901 off.insert(below.begin(), below.end());
1902 if (apart)
1903 off.insert(s);
1904 }
1905 return off;
1906}
1907
1908// The path to a trunked destination leaves the tree below the master port
1909// the others it has reached leave the source switchbox by.
1910llvm::DenseSet<int> Pathfinder::TreeBuilder::trunkOff(int dst) const {
1911 llvm::DenseSet<int> off;
1912 if (!trunkDsts.count(dst))
1913 return off;
1914 int root = tree.front();
1915 auto exitOf = [&](int s) {
1916 for (const auto *it = planned.find(s);
1917 it != planned.end() && it->second.first != root; it = planned.find(s))
1918 s = it->second.first;
1919 return s;
1920 };
1921 const auto *other = llvm::find_if(reached, [&](int d) {
1922 return d != dst && trunkDsts.count(d) && planned.count(d);
1923 });
1924 if (other == reached.end())
1925 return off;
1926 int exit = exitOf(*other);
1927 for (int s : tree)
1928 if (s == root || exitOf(s) != exit)
1929 off.insert(s);
1930 return off;
1931}
1932
1933// Lay the tree along the route the flow takes alone.
1935 for (const auto &[from, to, joined] : *pinned) {
1936 if (toSelf && from == to)
1937 continue;
1938 bool intra = from.coords == to.coords;
1939 auto fromId = pf.nodeIds.find(from), toId = pf.nodeIds.find(to);
1940 const Edge *e = nullptr;
1941 if (fromId != pf.nodeIds.end() && toId != pf.nodeIds.end())
1942 for (const Edge &out : pf.adjacency[fromId->second])
1943 if (out.dst == toId->second &&
1944 (out.sb->srcCoords == out.sb->dstCoords) == intra)
1945 e = &out;
1946 if (!e)
1947 return llvm::make_error<RoutingFailure>(
1948 "the route packet flows from " +
1949 describeTilePort(part.src.coords, part.src.port) +
1950 " take alone does not fit this design: it goes from " +
1951 describeTilePort(from.coords, from.port) + " to " +
1952 describeTilePort(to.coords, to.port) +
1953 ", which the design's own switchboxes leave no connection for.",
1954 RoutingFaults{}, pf.flowLoc(part.src, nullptr));
1955 std::pair<int, std::pair<int, Edge>> hop{
1956 stateId(toId->second, intra ? Out : In),
1957 {stateId(fromId->second, intra ? In : Out), *e}};
1958 if (joined)
1959 pinnedJoins.push_back(hop);
1960 else
1961 planned.insert(hop);
1962 }
1963 int self = dstState(part.src);
1964 if (toSelf && llvm::is_contained(llvm::make_first_range(pinnedJoins), self)) {
1965 toSelf = false;
1966 pinnedJoinDsts.push_back(self);
1967 }
1968 for (const PathEndPoint &p : pending)
1969 (planned.count(dstState(p)) ? reached : pinnedJoinDsts)
1970 .push_back(dstState(p));
1971 pending.clear();
1972 return llvm::Error::success();
1973}
1974
1975// Grow the tree one destination at a time: Dijkstra, given the current
1976// demand, from everything the tree reaches so far to the next destination,
1977// whose path is then traced back to the tree. Growing from the tree rather
1978// than the source lets destinations share hops; see treeSeedFactor for what
1979// a branch off the tree costs.
1981 // A path to another tree can leave the destination to reach first no way
1982 // to it, so the tree meets the others once it reaches it.
1983 bool metTrees = !first || !llvm::is_contained(pending, *first);
1984 if (metTrees)
1985 meet();
1986 while (!pending.empty()) {
1987 SmallVector<int, 8> targets;
1988 for (const PathEndPoint &p : pending)
1989 targets.push_back(dstState(p));
1990 for (const auto &[at, dsts] : joins)
1991 if (joinable(at, dsts))
1992 targets.push_back(at);
1993 search({}, targets);
1994 // The nearest destination joins the tree next, or the nearest join
1995 // brings every destination below it.
1996 auto *nearest =
1997 first && llvm::is_contained(pending, *first)
1998 ? llvm::find(pending, *first)
1999 : llvm::min_element(
2000 pending, [&](const PathEndPoint &a, const PathEndPoint &b) {
2001 return pf.distance[dstState(a)] < pf.distance[dstState(b)];
2002 });
2003 PathEndPoint endPoint = *nearest;
2004 int currId = dstState(endPoint);
2005 llvm::DenseSet<int> off = splitOff(currId);
2006 llvm::DenseSet<int> trunk = trunkOff(currId);
2007 off.insert(trunk.begin(), trunk.end());
2008 if (!off.empty()) {
2009 search({}, targets, off);
2010 if (pf.distance[currId] == INF)
2011 search({}, targets);
2012 }
2013 const SmallVector<int, 4> *joined = nullptr;
2014 for (const auto &[at, dsts] : joins)
2015 if (pf.distance[at] < pf.distance[currId] && joinable(at, dsts)) {
2016 currId = at;
2017 joined = &dsts;
2018 }
2019 if (joined) {
2020 reach(*joined);
2021 joinedAt.push_back({currId, joined});
2022 // The tree it joins goes on below.
2023 ++children[currId];
2024 } else {
2025 pending.erase(nearest);
2026 reached.push_back(currId);
2027 }
2028 if (!trace(currId))
2029 return llvm::make_error<RoutingFailure>(
2030 "no path leads from " +
2031 describeTilePort(part.src.coords, part.src.port) + " to " +
2032 describeTilePort(endPoint.coords, endPoint.port) +
2033 " through the connections the switchboxes allow and existing "
2034 "routing leaves free.",
2035 RoutingFaults{}, pf.flowLoc(part.src, &endPoint));
2036 if (!metTrees) {
2037 metTrees = true;
2038 meet();
2039 }
2040 }
2041 return llvm::Error::success();
2042}
2043
2044// A destination's path was chosen before the tree reached the later
2045// ones, so reroute each destination's own branch from the rest of the
2046// tree where that is cheaper.
2048 for (int dst : reached) {
2049 llvm::DenseSet<int> branch;
2050 int top = dst;
2051 for (int below = 0; top != tree.front() && children.lookup(top) == below;
2052 below = 1) {
2053 branch.insert(top);
2054 top = planned.find(top)->second.first;
2055 }
2056 if (branch.empty())
2057 continue;
2058 llvm::DenseSet<int> off = splitOff(dst), trunk = trunkOff(dst);
2059 off.insert(trunk.begin(), trunk.end());
2060 search(branch, dst, off);
2061 double cost =
2062 treeSeedFactor * treeHops[llvm::find(tree, top) - tree.begin()];
2063 for (int s = dst; s != top;) {
2064 const auto &[from, e] = planned.find(s)->second;
2065 auto away = branchAvoid.find(from);
2066 cost +=
2067 pf.edgeWeight(e, part.packetId, avoid,
2068 away == branchAvoid.end() ? nullptr : &away->second);
2069 s = from;
2070 }
2071 if (pf.distance[dst] + rerouteMinSaving >= cost)
2072 continue;
2073 for (int s : branch) {
2074 st.processedStamp[s] = 0;
2075 planned.erase(s);
2076 children.erase(s);
2077 }
2078 --children[top];
2079 for (size_t k = tree.size(); k-- > 0;)
2080 if (branch.count(tree[k])) {
2081 tree.erase(tree.begin() + k);
2082 treeHops.erase(treeHops.begin() + k);
2083 }
2084 [[maybe_unused]] bool traced = trace(dst);
2085 assert(traced && "a rerouted branch reaches its destination");
2086 }
2087}
2088
2089// Take the channels the tree's hops cross.
2091 if (toSelf) {
2092 switchSettings[part.src.coords].srcs.push_back(part.src.port);
2093 switchSettings[part.src.coords].dsts.push_back(part.src.port);
2094 if (part.packetId)
2095 st.routing.packetTrees[part.src].push_back({part.src, part.src, false});
2096 }
2097 const std::optional<int> &packetId = part.packetId;
2098 int packetGroupId = part.packetGroupId;
2099 llvm::DenseMap<int, std::pair<SwitchboxConnect *, int>> branchPort;
2100 for (const auto &[currId, hop] : planned) {
2101 const auto &[predId, e] = hop;
2102 const PathEndPoint &curr = pf.nodes[stateNode(currId)];
2103 const PathEndPoint &pred = pf.nodes[stateNode(predId)];
2104 SwitchboxConnect &sb = *e.sb;
2105 int i = e.i;
2106 int j = e.j;
2107 SwitchboxConnect::Cell &cell = sb.at(i, j);
2108 if (packetId)
2109 st.treeOf[flow].try_emplace(currId, predId, e);
2110 else
2111 st.circuitHops[flow].push_back(e);
2112 cell.isPriority |= part.isPriorityFlow;
2113 // Two trees with the same id may merge onto one only where nothing fans out
2114 // after, or where both take the id to the same destinations, so a merged id
2115 // never fans back out to one tree's destinations alone. The flow's own tree
2116 // branching at a port never merges back.
2117 // packetGroupId only becomes >= 0 when packetId has a value (see
2118 // Pathfinder::addFlow), so the dereferences below are safe; the
2119 // checker just can't correlate the two across this loop's back edge.
2120 // NOLINTBEGIN(bugprone-unchecked-optional-access)
2121 auto seen =
2122 packetId ? cell.packetIds.find(*packetId) : cell.packetIds.end();
2123 auto mergeable = [&] {
2124 if (!isDirectional(sb.dstPorts[j].bundle))
2125 return true;
2126 auto reached = [&](const Flow &f) {
2127 std::set<PathEndPoint> dsts;
2128 for (const PathEndPoint &dst : f.dsts)
2129 if (auto ids = pf.packetIdsTo.find({f.src, dst});
2130 ids != pf.packetIdsTo.end() &&
2131 llvm::is_contained(ids->second, *packetId))
2132 dsts.insert(dst);
2133 return dsts;
2134 };
2135 return reached(part) == reached(st.parts[seen->second]);
2136 };
2137 bool sameGroupUnseen =
2138 packetGroupId >= 0 && packetId.has_value() &&
2139 (cell.packetGroupId == -1 || cell.packetGroupId == packetGroupId) &&
2140 (seen == cell.packetIds.end() || seen->second == flow || mergeable());
2141 if (sameGroupUnseen) {
2142 int packetIdValue = *packetId;
2143 // NOLINTEND(bugprone-unchecked-optional-access)
2144 auto claim = [&](SwitchboxConnect::Cell &c) {
2145 c.packetGroupId = packetGroupId;
2146 c.packetIds.try_emplace(packetIdValue, flow);
2147 };
2148 for (size_t k = 0; k < sb.srcPorts.size(); k++)
2149 claim(sb.at(k, j));
2150 for (size_t l = 0; l < sb.dstPorts.size(); l++)
2151 if (l != static_cast<size_t>(j))
2152 claim(sb.at(i, l));
2153 // maximum packet stream sharing per channel
2154 if (++cell.packetFlowCount >= maxPacketStreamCapacity) {
2155 cell.packetFlowCount = 0;
2156 cell.usedCapacity++;
2157 }
2158 } else {
2159 cell.usedCapacity++;
2160 }
2161 // if at capacity, bump demand to discourage using this Channel
2162 // this means the order matters!
2164 if (pred.coords == curr.coords) {
2165 switchSettings[pred.coords].srcs.push_back(pred.port);
2166 switchSettings[curr.coords].dsts.push_back(curr.port);
2167 if (packetId) {
2168 sb.addToUnit(j, flow);
2169 auto [it, first] = branchPort.try_emplace(predId, &sb, j);
2170 if (!first)
2171 sb.joinUnits(it->second.second, j);
2172 }
2173 }
2174 }
2175 if (packetId) {
2176 st.treeDsts[flow].append(reached.begin(), reached.end());
2177 st.treeDsts[flow].append(met.begin(), met.end());
2178 for (const auto &[currId, hop] : planned)
2179 st.routing.packetTrees[part.src].push_back(
2180 {pf.nodes[stateNode(hop.first)], pf.nodes[stateNode(currId)], false});
2181 }
2182}
2183
2184// Below a join the flow's packets follow the tree they joined.
2185bool Pathfinder::TreeBuilder::join(int state, int predId, const Edge &e) {
2186 if (!st.treeOf[flow].try_emplace(state, predId, e).second)
2187 return false;
2188 const PathEndPoint &pred = pf.nodes[stateNode(predId)];
2189 const PathEndPoint &curr = pf.nodes[stateNode(state)];
2190 st.routing.packetTrees[part.src].push_back({pred, curr, true});
2191 if (pred.coords == curr.coords) {
2192 switchSettings[pred.coords].srcs.push_back(pred.port);
2193 switchSettings[curr.coords].dsts.push_back(curr.port);
2194 e.sb->addToUnit(e.j, flow);
2195 }
2196 return true;
2197}
2198
2200 for (const auto &[joinAt, joined] : joinedAt) {
2201 for (int other : joinOwners[joinAt]) {
2202 st.joinedHops[flow].push_back(
2203 {other, planned.find(joinAt)->second.second});
2204 for (int dst : *joined) {
2205 const auto &t = st.treeOf[other];
2206 SmallVector<std::pair<int, std::pair<int, Edge>>, 8> path;
2207 auto it = t.find(dst);
2208 for (; it != t.end() && it->first != joinAt;
2209 it = t.find(it->second.first))
2210 path.push_back(*it);
2211 if (it == t.end())
2212 continue;
2213 for (const auto &[state, hop] : path)
2214 if (join(state, hop.first, hop.second))
2215 st.joinedHops[flow].push_back({other, hop.second});
2216 }
2217 }
2218 st.treeDsts[flow].append(joined->begin(), joined->end());
2219 }
2220 for (const auto &[state, hop] : pinnedJoins)
2221 join(state, hop.first, hop.second);
2222 st.treeDsts[flow].append(pinnedJoinDsts.begin(), pinnedJoinDsts.end());
2223}
2224
2225// Add the tree to the routing.
2227 const PathEndPoint &src = part.src;
2228 if (st.partsOf.at(src).size() == 1) {
2229 st.routing.settings[src] = switchSettings;
2230 return;
2231 }
2232 for (int id : st.flowIds[flow])
2233 st.routing.idSettings[{src, id}] = switchSettings;
2234 for (const auto &[tile, setting] : switchSettings) {
2235 SwitchSetting &all = st.routing.settings[src][tile];
2236 for (auto [in, out] : llvm::zip(setting.srcs, setting.dsts))
2237 if (!llvm::is_contained(llvm::zip(all.srcs, all.dsts),
2238 std::make_tuple(in, out))) {
2239 all.srcs.push_back(in);
2240 all.dsts.push_back(out);
2241 }
2242 }
2243}
2244
2245llvm::Error Pathfinder::routePart(RouteState &st, int flow) {
2246 std::optional<TreeBuilder> tree;
2247 tree.emplace(*this, st, flow);
2248 llvm::Error err = tree->pinned ? tree->placePinned() : tree->grow();
2249 // Kept to the prioritized flows' master sets, the branch a tree takes first
2250 // can leave it no way to another destination; each is then tried first.
2251 if (err && !tree->pinned && !st.overlayMasters.empty())
2252 for (const PathEndPoint &dst : st.parts[flow].dsts) {
2253 for (auto *pairs : {&st.unmet, &st.met})
2254 pairs->erase(pairs->lower_bound({flow, 0}),
2255 pairs->lower_bound({flow + 1, 0}));
2256 st.joinedHops[flow].clear();
2257 tree.emplace(*this, st, flow);
2258 tree->first = dst;
2259 if (llvm::Error retry = tree->grow()) {
2260 llvm::consumeError(std::move(retry));
2261 continue;
2262 }
2263 llvm::consumeError(std::move(err));
2264 break;
2265 }
2266 if (err)
2267 return err;
2268 if (!tree->pinned && tree->reached.size() + tree->joinedAt.size() > 1)
2269 tree->reroute();
2270 tree->claim();
2271 tree->joinTrees();
2272 tree->record();
2273 return llvm::Error::success();
2274}
2275
2276int Pathfinder::applyRoutingFaults(RouteState &st,
2277 const RoutingFaults &faults) {
2278 int illegalEdges = 0;
2279 for (const TreeSplit &split : faults.splits) {
2280 auto src = st.partsOf.find(split.src);
2281 if (src == st.partsOf.end())
2282 continue;
2283 auto partWith = [&](int id) {
2284 for (int k : src->second)
2285 if (st.flowIds[k].count(id))
2286 return k;
2287 return -1;
2288 };
2289 int flow = partWith(split.a), other = partWith(split.b);
2290 if (flow < 0 || other < 0)
2291 continue;
2292 std::optional<int> apart;
2293 if (split.apart) {
2294 auto it = nodeIds.find(*split.apart);
2295 if (it == nodeIds.end())
2296 continue;
2297 apart = stateId(it->second, Out);
2298 }
2299 // Whether a destination taking both ids is reached through the tile.
2300 auto inseparable = [&] {
2301 const Flow &f = st.parts[flow];
2302 for (const PathEndPoint &dst : f.dsts) {
2303 auto ids = packetIdsTo.find({f.src, dst});
2304 if (ids == packetIdsTo.end() ||
2305 !llvm::is_contained(ids->second, split.a) ||
2306 !llvm::is_contained(ids->second, split.b))
2307 continue;
2308 const auto &tree = st.treeOf[flow];
2309 auto it = tree.find(stateId(nodeIds.at(dst), Out));
2310 if (it == tree.end() && dst == f.src && dst.coords == split.at)
2311 return true;
2312 for (; it != tree.end(); it = tree.find(it->second.first)) {
2313 int up = it->second.first;
2314 if ((up & 1) == In && nodes[stateNode(up)].coords == split.at)
2315 return true;
2316 }
2317 }
2318 return false;
2319 };
2320 if (idsApart && flow == other && !split.apart &&
2321 !st.parts[flow].isPriorityFlow && inseparable())
2322 other = st.splitPart(flow, {split.b});
2323 if (flow != other) {
2324 if (st.partSplits[flow].insert({split.at, other}).second)
2325 illegalEdges++;
2326 st.partSplits[other].insert({split.at, flow});
2327 } else if (st.splitsOf[flow]
2328 .insert({split.at, split.a, split.b, apart})
2329 .second) {
2330 LLVM_DEBUG(llvm::dbgs()
2331 << "Split ids " << split.a << " and " << split.b << " from "
2332 << describeTilePort(split.src.coords, split.src.port)
2333 << " at tile (" << split.at.col << ", " << split.at.row
2334 << ")\n");
2335 illegalEdges++;
2336 }
2337 }
2338 crowdedTiles.insert(faults.crowded.begin(), faults.crowded.end());
2339 // Trees with an id in common join where they meet, so the ids a source
2340 // sends that the other does not route apart as a part of their own.
2341 auto splitOwnIds = [&](const std::pair<PathEndPoint, PathEndPoint> &pair) {
2342 for (auto [src, other] : {pair, std::pair{pair.second, pair.first}}) {
2343 auto srcParts = st.partsOf.find(src);
2344 auto otherParts = st.partsOf.find(other);
2345 if (srcParts == st.partsOf.end() || otherParts == st.partsOf.end())
2346 continue;
2347 std::set<int> otherIds;
2348 for (int k : otherParts->second)
2349 otherIds.insert(st.flowIds[k].begin(), st.flowIds[k].end());
2350 for (int k : SmallVector<int, 2>(srcParts->second)) {
2351 std::set<int> own;
2352 for (int id : st.flowIds[k])
2353 if (!otherIds.count(id))
2354 own.insert(id);
2355 if (!st.parts[k].isPriorityFlow && !own.empty() &&
2356 own.size() < st.flowIds[k].size())
2357 st.splitPart(k, own);
2358 }
2359 }
2360 };
2361 bool related = false;
2362 for (const auto &pair : faults.apart) {
2363 if (!st.apartSources.insert(pair).second)
2364 continue;
2365 related = true;
2366 LLVM_DEBUG(llvm::dbgs()
2367 << "Route apart: "
2368 << describeTilePort(pair.first.coords, pair.first.port)
2369 << " and "
2370 << describeTilePort(pair.second.coords, pair.second.port)
2371 << "\n");
2372 if (splitShared)
2373 splitOwnIds(pair);
2374 }
2375 for (const auto &pair : faults.together) {
2376 if (!st.togetherSources.insert(pair).second)
2377 continue;
2378 related = true;
2379 LLVM_DEBUG(llvm::dbgs()
2380 << "Route together: "
2381 << describeTilePort(pair.first.coords, pair.first.port)
2382 << " and "
2383 << describeTilePort(pair.second.coords, pair.second.port)
2384 << "\n");
2385 splitOwnIds(pair);
2386 }
2387 if (related) {
2388 illegalEdges++;
2389 st.relateParts();
2390 }
2391 for (const PathEndPoint &src : faults.untrunk)
2392 if (st.untrunked.insert(src).second) {
2393 LLVM_DEBUG(llvm::dbgs()
2394 << "Untrunk: " << describeTilePort(src.coords, src.port)
2395 << "\n");
2396 illegalEdges++;
2397 }
2398 std::set<std::pair<int, int>> faulted;
2399 bool unjoined = false;
2400 for (const auto &[tile, conn] : faults.connections) {
2401 illegalEdges++;
2402 auto it = graph.find({tile, tile});
2403 if (it == graph.end())
2404 continue;
2405 SwitchboxConnect &sb = it->second;
2406 int i = sb.srcIndex(conn.src);
2407 int j = sb.dstIndex(conn.dst);
2408 if (i < 0 || j < 0)
2409 continue;
2410 sb.at(i, j).overCapacity += routingCheckPenalty;
2411 bool joined = false;
2412 for (auto [flow, hops] : llvm::enumerate(st.joinedHops))
2413 for (const auto &[other, e] : hops)
2414 if (e.sb == &sb && sb.srcPorts[e.i] == conn.src &&
2415 sb.dstPorts[e.j] == conn.dst) {
2416 faulted.insert({static_cast<int>(flow), other});
2417 joined = true;
2418 }
2419 unjoined |= !joined;
2420 }
2421 // Moving the hops neither joined may be enough, so a joined pair is first
2422 // left to their penalties.
2423 for (auto [flow, other] : faulted)
2424 if (unjoined && st.spared.insert({flow, other}).second)
2425 continue;
2426 else if (st.met.count({flow, other}) &&
2427 ++st.meetFaults[{std::min(flow, other), std::max(flow, other)}] <=
2428 maxMeetFaults)
2429 st.reorder(flow, other, /*again=*/true);
2430 else
2431 st.noJoin.insert({flow, other});
2432 return illegalEdges;
2433}
2434
2435// Perform congestion-aware routing for all flows which have been added.
2436// Use Dijkstra's shortest path to find routes, and use "demand" as the
2437// weights. If the routing finds too much congestion, update the demand
2438// weights and repeat the process until a valid solution is found. Returns
2439// the switchbox settings for all flows, or a RoutingFailure if no legal
2440// routing is found after maxIterations.
2441llvm::Expected<Routing> Pathfinder::findPaths(const int maxIterations) {
2442 LLVM_DEBUG(llvm::dbgs() << "\t---Begin Pathfinder::findPaths---\n");
2443 checkReason.clear();
2444 packetsFailed = true;
2445 // Build the dense routing graph once; topology is invariant across
2446 // iterations.
2447 if (!graphBuilt)
2448 buildRoutingGraph();
2449 if (llvm::Error err = cutOverflow()) {
2450 packetsFailed = false;
2451 return std::move(err);
2452 }
2453 crowdedTiles.clear();
2454 // initialize all Channel histories to 0
2455 for (auto &[_, sb] : graph) {
2456 if (sb.srcCoords == sb.dstCoords)
2457 for (auto [j, port] : llvm::enumerate(sb.dstPorts))
2458 sb.circuitOnlyDst[j] = cappedTiles.contains(sb.srcCoords) &&
2459 port.channel >= packetFanoutCap &&
2460 isDirectional(port.bundle);
2461 for (SwitchboxConnect::Cell &c : sb.cells) {
2462 c.usedCapacity = 0;
2463 c.overCapacity = 0;
2464 c.isPriority = false;
2465 }
2466 }
2467
2468 RouteState st(*this);
2469
2470 int iterationCount = -1;
2471 int illegalEdges = 0;
2472 std::optional<Routing> usable;
2473 int usableAt = 0;
2474 int fewestIllegal = std::numeric_limits<int>::max(), sinceFewest = 0;
2475 // Iterations of the current stall that moved circuit trees first.
2476 int reorderedStall = 0;
2477 int overlayFaults = 0;
2478 do {
2479 if (usable && (iterationCount + 1 >= maxIterations ||
2480 iterationCount - usableAt >= maxSteers)) {
2481 LLVM_DEBUG(llvm::dbgs() << "\t\tKeeping the usable routing\n");
2482 return std::move(*usable);
2483 }
2484 // if reach maxIterations, throw an error since no routing can be found;
2485 // so too once the overuse stalls for good, or routing the losing circuit
2486 // trees first has stopped helping
2487 if (++iterationCount >= maxIterations ||
2488 (fewestIllegal > 0 && sinceFewest >= maxOverusedStall) ||
2489 reorderedStall >= maxReorderedStall) {
2490 LLVM_DEBUG(llvm::dbgs()
2491 << "\t\tPathfinder: giving up after " << iterationCount
2492 << " iterations ("
2493 << (iterationCount >= maxIterations ? "the iteration limit"
2494 : reorderedStall >= maxReorderedStall
2495 ? "circuit trees routed first stopped helping"
2496 : "the overuse stalled")
2497 << ")...unable to find routing for flows.\n");
2498 packetsFailed =
2499 !checkReason.empty() || llvm::any_of(graph, [](const auto &entry) {
2500 ArrayRef<SwitchboxConnect::Cell> cells = entry.second.cells;
2501 return llvm::any_of(cells,
2502 [](const SwitchboxConnect::Cell &c) {
2503 return c.packetGroupId >= 0;
2504 }) &&
2505 llvm::any_of(cells, [](const SwitchboxConnect::Cell &c) {
2506 return c.usedCapacity > maxCircuitStreamCapacity;
2507 });
2508 });
2509 return explainNoRouting(st);
2510 }
2511
2512 LLVM_DEBUG(llvm::dbgs() << "\t\t---Begin findPaths iteration #"
2513 << iterationCount << "---\n");
2514 // update demand at the beginning of each iteration
2515 for (auto &[_, sb] : graph) {
2516 sb.updateDemand();
2517 }
2518
2519 // "rip up" all routes
2520 illegalEdges = 0;
2521 st.routing = {};
2522 for (auto &[_, sb] : graph) {
2523 for (SwitchboxConnect::Cell &c : sb.cells) {
2524 c.usedCapacity = 0;
2525 c.packetFlowCount = 0;
2526 c.packetGroupId = -1;
2527 c.packetIds.clear();
2528 }
2529 sb.resetUnits();
2530 }
2531 for (auto &tree : st.treeOf)
2532 tree.clear();
2533 for (auto &hops : st.circuitHops)
2534 hops.clear();
2535 for (auto &dsts : st.treeDsts)
2536 dsts.clear();
2537 for (auto &hops : st.joinedHops)
2538 hops.clear();
2539 st.unmet.clear();
2540 st.met.clear();
2541
2542 // for each flow, find the shortest path from source to destination
2543 // update used_capacity for the path between them
2544
2545 for (const auto &[_, group] : st.groupedFlows) {
2546 for (int flow : group)
2547 if (llvm::Error err = routePart(st, flow))
2548 return std::move(err);
2549 for (auto &[_, sb] : graph) {
2550 for (SwitchboxConnect::Cell &c : sb.cells) {
2551 // fix used capacity for packet flows
2552 if (c.packetFlowCount > 0) {
2553 c.packetFlowCount = 0;
2554 c.usedCapacity++;
2555 }
2557 }
2558 }
2559 }
2560
2561 // The tree routed first may branch where none from elsewhere can meet it
2562 // at one switchbox, though it could meet a tree routed before it, or one
2563 // that leaves its source switchbox by one master port for them.
2564 for (auto [later, earlier] : st.unmet) {
2565 bool steered = st.reorder(later, earlier);
2566 steered |= st.trunked.insert({later, earlier}).second;
2567 steered |= st.trunked.insert({earlier, later}).second;
2568 if (steered)
2569 illegalEdges++;
2570 }
2571
2572 for (auto &[_, sb] : graph) {
2573 for (size_t i = 0; i < sb.srcPorts.size(); i++) {
2574 for (size_t j = 0; j < sb.dstPorts.size(); j++) {
2575 SwitchboxConnect::Cell &c = sb.at(i, j);
2576 // check that every channel does not exceed max capacity
2577 if (c.usedCapacity > maxCircuitStreamCapacity) {
2578 c.overCapacity +=
2579 sinceFewest >= stallIterations && c.packetGroupId < 0
2580 ? stallHistory
2581 : 1;
2582 illegalEdges++;
2583 LLVM_DEBUG(llvm::dbgs()
2584 << "\t\t\tToo much capacity on (" << sb.srcCoords.col
2585 << "," << sb.srcCoords.row << ") "
2586 << sb.srcPorts[i].bundle << sb.srcPorts[i].channel
2587 << " -> (" << sb.dstCoords.col << "," << sb.dstCoords.row
2588 << ") " << sb.dstPorts[j].bundle
2589 << sb.dstPorts[j].channel << ", used_capacity = "
2590 << c.usedCapacity << ", demand = " << c.demand
2591 << ", over_capacity_count = " << c.overCapacity << "\n");
2592 }
2593 }
2594 }
2595 }
2596
2597 // Once the overuse stalls, circuit trees that overuse a circuit-only
2598 // channel route first, so the same tree stops losing it. Packet-shared
2599 // overuse is left to the packet steering.
2600 if (illegalEdges < fewestIllegal) {
2601 fewestIllegal = illegalEdges;
2602 sinceFewest = reorderedStall = 0;
2603 } else {
2604 sinceFewest++;
2605 }
2606 if (illegalEdges && sinceFewest >= stallIterations) {
2607 auto losing = [&](int flow) {
2608 return !st.parts[flow].packetId &&
2609 llvm::any_of(st.circuitHops[flow], [](const Edge &e) {
2610 const SwitchboxConnect::Cell &c = e.sb->at(e.i, e.j);
2611 return c.usedCapacity > maxCircuitStreamCapacity &&
2612 c.packetGroupId < 0;
2613 });
2614 };
2615 bool moved = false;
2616 for (auto &[_, group] : st.groupedFlows) {
2617 moved |= llvm::any_of(group, losing);
2618 std::stable_partition(group.begin(), group.end(), losing);
2619 }
2620 reorderedStall += moved;
2621 }
2622 // Each stall period without a legal routing, the packet trees drop their
2623 // id splits: the routing that faulted them may be gone, and the routing
2624 // check adds back any still needed.
2625 if (sinceFewest >= stallIterations && sinceFewest % stallIterations == 0) {
2626 LLVM_DEBUG(llvm::dbgs() << "Drop the id splits after " << sinceFewest
2627 << " stalled iterations\n");
2628 for (auto &splits : st.splitsOf)
2629 splits.clear();
2630 }
2631
2632 // A routing that fits the fabric can still be one the caller cannot use;
2633 // steer away from the connections it names as if they were overused. One
2634 // it rejects naming nothing to move is as far as steering gets, leaving
2635 // the usable routing found, if any, or the rejection.
2636 bool stuck = false;
2637 llvm::Error unmoved =
2638 illegalEdges != 0 || !constraints.check
2639 ? llvm::Error::success()
2640 : llvm::handleErrors(
2641 constraints.check(st.routing),
2642 [&](std::unique_ptr<RoutingFailure> rejected) -> llvm::Error {
2643 LLVM_DEBUG(llvm::dbgs() << "Routing rejected: "
2644 << rejected->reason << '\n');
2645 illegalEdges += applyRoutingFaults(st, rejected->faults);
2646 overlayFaults = rejected->faults.onlyOverlayMasters
2647 ? overlayFaults + 1
2648 : 0;
2649 stuck = illegalEdges == 0;
2650 if (rejected->isA(DeadlockProneRouting::classID())) {
2651 if (!usable) {
2652 usable = st.routing;
2653 usableAt = iterationCount;
2654 }
2655 return llvm::Error::success();
2656 }
2657 if (stuck)
2658 return llvm::Error(std::move(rejected));
2659 checkReason = std::move(rejected->reason);
2660 return llvm::Error::success();
2661 });
2662
2663 LLVM_DEBUG({
2664 for (const auto &[src, settings] : st.routing.settings)
2665 llvm::dbgs() << "\t\t\tFlow starting at (" << src.coords.col << ","
2666 << src.coords.row << "):\t" << settings;
2667 // total path length, across switchboxes
2668 int totalPathLength = 0;
2669 for (const auto &[_, sb] : graph)
2670 if (sb.srcCoords != sb.dstCoords)
2671 for (const SwitchboxConnect::Cell &c : sb.cells)
2672 totalPathLength += c.usedCapacity;
2673 llvm::dbgs() << "\t\t---End findPaths iteration #" << iterationCount
2674 << " , illegal edges count = " << illegalEdges
2675 << ", total path length = " << totalPathLength << "---\n";
2676 });
2677 if (stuck && usable) {
2678 llvm::consumeError(std::move(unmoved));
2679 return std::move(*usable);
2680 }
2681 if (unmoved)
2682 return std::move(unmoved);
2683 if (overlayFaults >= maxOverlayFaults) {
2684 LLVM_DEBUG(llvm::dbgs() << "\t\tPathfinder: the routing check rejects "
2685 "only master sets of the prioritized flows ("
2686 << overlayFaults << " routings in a row)\n");
2687 if (usable)
2688 return std::move(*usable);
2689 packetsFailed = false;
2690 return explainNoRouting(st);
2691 }
2692 // continue iterations until a legal routing is found
2693 } while (illegalEdges > 0);
2694
2695 LLVM_DEBUG(llvm::dbgs() << "\t---End Pathfinder::findPaths---\n");
2696 return std::move(st.routing);
2697}
virtual uint32_t getNumSourceShimMuxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a shimmux.
virtual bool isLegalTileConnection(int col, int row, WireBundle srcBundle, int srcChan, WireBundle dstBundle, int dstChan) const =0
bool isShimNOCorPLTile(int col, int row) const
Return true if the given tile is either a ShimNOC or ShimPL tile.
virtual uint32_t getNumDestShimMuxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a shimmux.
virtual uint32_t getNumDestSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a switchbox.
virtual uint32_t getNumSourceSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a switchbox.
ShimMuxOp getShimMux(mlir::OpBuilder &builder, int col)
SwitchboxOp getSwitchbox(mlir::OpBuilder &builder, int col, int row)
SwitchboxOp lookupSwitchbox(TileID tile) const
TileOp getTile(mlir::OpBuilder &builder, int col, int row)
llvm::Error runAnalysis(DeviceOp &device)
ShimMuxOp lookupShimMux(int col) const
llvm::Expected< Routing > findPaths(int maxIterations)
void excludeTile(TileID coords)
void addFlow(TileID srcCoords, Port srcPort, TileID dstCoords, Port dstPort, std::optional< int > packetId, bool isPriorityFlow, std::optional< mlir::Location > loc=std::nullopt)
void initialize(int maxCol, int maxRow, const AIETargetModel &targetModel)
mlir::LogicalResult addFixedConnection(SwitchboxOp switchboxOp)
void log(llvm::raw_ostream &os) const override
std::string joinNames(llvm::ArrayRef< std::string > names, size_t shown=4)
Lists names as "A, B and C", the first shown of them followed by "N more" if there are more.
int shimMuxChannelTo(Port dst)
std::string describePrioritized(llvm::ArrayRef< std::string > sources)
Says that the packet flows from sources keep the route they take alone, as a control-packet reload of...
struct TileID { friend std::ostream &operator<<(std::ostream &os, const TileID &s) { os<< "TileID("<< s.col<< ", "<< s.row<< ")"; return os; } friend std::string to_string(const TileID &s) { std::ostringstream ss; ss<< s; return ss.str(); } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const TileID &s) { os<< to_string(s); return os; } inline bool operator<(const TileID &rhs) const { return std::tie(col, row)< std::tie(rhs.col, rhs.row); } bool operator==(const TileID &rhs) const { return std::tie(col, row)==std::tie(rhs.col, rhs.row); } bool operator!=(const TileID &rhs) const { return !(*this==rhs);} int col, row;} TileID
bool isDirectional(WireBundle bundle)
Definition AIEDialect.h:447
int shimMuxChannelFrom(Port src)
std::string describeTilePort(TileID tile, Port port)
Names a tile port the way diagnostics do, e.g. "(0, 2) DMA:1".
struct Port { WireBundle bundle; int channel; bool operator==(const Port &rhs) const { return std::tie(bundle, channel)==std::tie(rhs.bundle, rhs.channel); } bool operator!=(const Port &rhs) const { return !(*this==rhs);} bool operator<(const Port &rhs) const { return std::tie(bundle, channel)< std::tie(rhs.bundle, rhs.channel); } friend std::ostream &operator<<(std::ostream &os, const Port &port) { os<< "("; os<< stringifyWireBundle(port.bundle).str(); os<< ": "<< std::to_string(port.channel)<< ")"; return os; } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Port &port) { os<< to_string(port); return os; } } Port
Definition AIEDialect.h:265
std::map< TileID, SwitchSetting > SwitchSettings
std::string describePort(Port port)
Names a port the way diagnostics do, e.g. "DMA:1".
WireBundle getConnectingBundle(WireBundle dir)
bool operator<(const IdSplit &other) const
std::set< std::pair< int, int > > noJoin
std::set< PathEndPoint > untrunked
std::vector< llvm::DenseMap< int, std::pair< int, Edge > > > treeOf
std::map< std::pair< int, int >, int > meetFaults
std::vector< SmallVector< Edge, 8 > > circuitHops
std::set< std::pair< int, int > > reordered
std::vector< std::set< std::pair< TileID, int > > > partSplits
std::vector< uint32_t > processedStamp
std::vector< SmallVector< std::pair< int, Edge >, 8 > > joinedHops
std::set< std::pair< int, int > > spared
std::vector< llvm::BitVector > conflicting
std::vector< SmallVector< int, 4 > > treeDsts
std::vector< std::set< IdSplit > > splitsOf
std::set< std::pair< int, int > > met
std::set< std::pair< PathEndPoint, PathEndPoint > > togetherSources
bool reorder(int later, int earlier, bool again=false)
std::vector< SmallVector< int, 4 > > overlaySets
std::vector< std::set< int > > flowIds
llvm::MapVector< int, SmallVector< int, 8 > > groupedFlows
bool isPinned(const Flow &f) const
std::set< std::pair< PathEndPoint, PathEndPoint > > apartSources
std::set< std::pair< int, int > > unmet
bool mayLeave(ArrayRef< int > masters, int next) const
std::set< std::pair< int, int > > trunked
int splitPart(int flow, const std::set< int > &ids)
std::vector< SmallVector< int, 4 > > sameId
SmallVector< int, 4 > idsTo(int flow, int dstState) const
std::map< PathEndPoint, SmallVector< int, 2 > > partsOf
TreeBuilder(Pathfinder &pf, RouteState &st, int flow)
llvm::MapVector< int, SmallVector< int, 4 > > sharedDsts
SmallVector< PathEndPoint, 4 > pending
llvm::DenseSet< int > trunkOff(int dst) const
void search(const llvm::DenseSet< int > &drop, ArrayRef< int > targets, const llvm::DenseSet< int > &off={})
const std::vector< TreeHop > * pinned
SmallVector< double, 16 > seedCosts
std::optional< PathEndPoint > first
SmallVector< std::pair< int, std::pair< int, Edge > >, 8 > pinnedJoins
int dstState(const PathEndPoint &p) const
llvm::MapVector< int, std::pair< int, Edge > > planned
llvm::DenseMap< int, SmallVector< int, 2 > > joinOwners
llvm::DenseMap< int, llvm::BitVector > branchAvoid
bool joinable(int at, ArrayRef< int > dsts) const
SmallVector< std::pair< int, const SmallVector< int, 4 > * >, 2 > joinedAt
bool join(int state, int predId, const Edge &e)
llvm::DenseSet< int > splitOff(int dst) const
llvm::DenseMap< int, SmallVector< int, 4 > > joins
llvm::DenseMap< int, int > children
std::vector< PathEndPoint > dsts
std::optional< int > packetId
std::vector< std::string > flows
std::vector< std::pair< TileID, Connect > > connections
std::vector< PathEndPoint > untrunk
std::vector< std::pair< PathEndPoint, PathEndPoint > > together
std::vector< TreeSplit > splits
std::vector< TileID > crowded
std::vector< std::pair< PathEndPoint, PathEndPoint > > apart
std::map< PathEndPoint, SwitchSettings > settings
std::vector< Port > dsts
std::vector< Port > srcs
llvm::SmallDenseMap< int, int, 2 > packetIds
std::vector< Port > dstPorts
std::vector< Port > srcPorts
static void bumpDemand(Cell &c)
void addToUnit(int j, int flow)
Cell & at(size_t i, size_t j)
std::vector< Cell > cells
std::optional< PathEndPoint > apart