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