MLIR-AIE 1.4.6.dev
AIECreatePathFindFlows.cpp
Go to the documentation of this file.
1//===- AIECreatePathfindFlows.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
14
15#include "mlir/IR/PatternMatch.h"
16#include "mlir/Pass/Pass.h"
17#include "mlir/Transforms/DialectConversion.h"
18#include "llvm/ADT/DenseSet.h"
19#include "llvm/ADT/EquivalenceClasses.h"
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/ADT/SmallBitVector.h"
22#include "llvm/Support/Debug.h"
23#include "llvm/Support/MathExtras.h"
24
25#include <algorithm>
26#include <cstdint>
27#include <deque>
28#include <numeric>
29#include <optional>
30#include <set>
31
32using namespace mlir;
33using namespace xilinx;
34using namespace xilinx::AIE;
35
36#define DEBUG_TYPE "aie-create-pathfinder-flows"
37
38namespace xilinx::AIE {
39#define GEN_PASS_DEF_AIEROUTEPATHFINDERFLOWS
40#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
41} // namespace xilinx::AIE
42
43using PhysPort = std::pair<TileID, Port>;
44
45/// (mask, value, amsel) by slave port, in slot order.
47 std::map<PhysPort, SmallVector<std::tuple<int, int, int>, 2>>;
48
51 // The packet ids each tree carries.
52 std::map<PathEndPoint, std::set<int>> ids;
54};
55
56static PriorityRules priorityRules(DeviceOp d) {
57 const int numArbiters = d.getTargetModel().getNumArbiters();
58 AIEDialect::PriorityRouteAttrHelper priority(d.getContext());
59 PriorityRules rules;
60 for (SwitchboxOp box : d.getOps<SwitchboxOp>())
61 for (PacketRulesOp port : box.getOps<PacketRulesOp>())
62 for (PacketRuleOp rule : port.getRules().front().getOps<PacketRuleOp>())
63 if (priority.isAttrPresent(port) || priority.isAttrPresent(rule)) {
64 auto amsel = cast<AMSelOp>(rule.getAmsel().getDefiningOp());
65 rules[{box.getTileOp().getTileID(),
66 {port.getSourceBundle(), port.sourceIndex()}}]
67 .push_back(
68 {rule.maskInt(), rule.valueInt(),
69 amsel.arbiterIndex() + amsel.getMselValue() * numArbiters});
70 }
71 return rules;
72}
73
74// The backtracking searches for an arbiter plan and for a set of streams that
75// cannot share an arbiter are exact up to this many steps.
76constexpr int maxSearchSteps = 100000;
77// The hold-cycle search replans a tile at most this many times.
78constexpr int maxHoldCycleReplans = 256;
79
80static constexpr llvm::StringLiteral sharedReceiverCycleReason =
81 "Packet flows into receivers they share can deadlock holding arbiters "
82 "across switchboxes, and no routing found avoids it: ";
83static constexpr llvm::StringLiteral allowDeadlockProneHint =
84 " Set allow-deadlock-prone (aiecc --allow-deadlock-prone-routing) to "
85 "route them anyway.";
86
87namespace {
88struct PacketPlan;
89
90/// \brief Routes flows in a device by lowering them to stream-switch
91/// configurations.
92///
93/// Overall flow:
94/// 1. Report flows that can deadlock however they are routed.
95/// 2. Route the prioritized packet flows alone and pin their trees.
96/// 3. Route every flow with Pathfinder, planning the arbiters and packet rules
97/// on each routing found and steering off those that cannot be used,
98/// relaxing the packet constraints if no routing is found.
99/// 4. Lower the circuit flows to connects, and the packet flows as planned.
100/// 5. Add the wires between switchboxes and tiles.
101struct AIEPathfinderPass
102 : xilinx::AIE::impl::AIERoutePathfinderFlowsBase<AIEPathfinderPass> {
103 using Base =
104 xilinx::AIE::impl::AIERoutePathfinderFlowsBase<AIEPathfinderPass>;
105 AIEPathfinderPass() = default;
106 AIEPathfinderPass(const AIERoutePathfinderFlowsOptions &options)
107 : Base(options) {}
108 std::vector<std::pair<TileID, Port>> avoidedPorts;
109 void runOnOperation() override;
110 LogicalResult runOnFlow(DeviceOp d, DynamicTileAnalysis &analyzer);
111 /// Lowers the packet flows as `plan` says, on the routing `analyzer` found,
112 /// warning of a hold cycle left through receivers flows share if `warn`.
113 LogicalResult runOnPacketFlow(DeviceOp d, OpBuilder &builder,
114 DynamicTileAnalysis &analyzer,
115 const StreamConflicts &conflicts,
116 const PacketPlan &plan, bool warn);
117 llvm::Expected<PacketPlan> route(DeviceOp d, DynamicTileAnalysis &analyzer,
118 const StreamConflicts &conflicts,
119 const PinnedRoutes &pinned,
120 bool circuitSwitchHops,
121 bool prioritize = true);
122};
123
124// allocates channels between switchboxes ( but does not assign them)
125// instantiates shim-muxes AND allocates channels ( no need to rip these up in )
126struct ConvertFlowsToInterconnect : OpConversionPattern<FlowOp> {
127 using OpConversionPattern::OpConversionPattern;
128 DeviceOp &device;
129 DynamicTileAnalysis &analyzer;
130 ConvertFlowsToInterconnect(MLIRContext *context, DeviceOp &d,
131 DynamicTileAnalysis &a, PatternBenefit benefit = 1)
132 : OpConversionPattern(context, benefit), device(d), analyzer(a) {}
133
134 void addConnection(ConversionPatternRewriter &rewriter,
135 // could be a shim-mux or a switchbox.
136 Interconnect op, FlowOp flowOp, WireBundle inBundle,
137 int inIndex, WireBundle outBundle, int outIndex) const {
138
139 Region &r = op.getConnections();
140 Block &b = r.front();
141 auto point = rewriter.saveInsertionPoint();
142 rewriter.setInsertionPoint(b.getTerminator());
143
144 ConnectOp::create(rewriter, flowOp.getLoc(), inBundle, inIndex, outBundle,
145 outIndex);
146
147 rewriter.restoreInsertionPoint(point);
148
149 LLVM_DEBUG(llvm::dbgs()
150 << "\t\taddConnection() (" << op.colIndex() << ","
151 << op.rowIndex() << ") " << stringifyWireBundle(inBundle)
152 << inIndex << " -> " << stringifyWireBundle(outBundle)
153 << outIndex << "\n");
154 }
155
156 mlir::LogicalResult
157 matchAndRewrite(FlowOp flowOp, OpAdaptor adaptor,
158 ConversionPatternRewriter &rewriter) const override {
159 Operation *Op = flowOp.getOperation();
160 DeviceOp d = flowOp->getParentOfType<DeviceOp>();
161 if (!d) {
162 flowOp->emitOpError("This operation must be contained within a device");
163 return failure();
164 }
165 rewriter.setInsertionPoint(d.getBody()->getTerminator());
166
167 auto srcTile = cast<TileOp>(flowOp.getSource().getDefiningOp());
168 TileID srcCoords = {srcTile.colIndex(), srcTile.rowIndex()};
169 auto srcBundle = flowOp.getSourceBundle();
170 auto srcChannel = flowOp.getSourceChannel();
171 Port srcPort = {srcBundle, srcChannel};
172
173 LLVM_DEBUG({
174 auto dstTile = cast<TileOp>(flowOp.getDest().getDefiningOp());
175 llvm::dbgs() << "\n\t---Begin rewrite() for flowOp: (" << srcCoords.col
176 << ", " << srcCoords.row << ")"
177 << stringifyWireBundle(srcBundle) << srcChannel << " -> ("
178 << dstTile.colIndex() << ", " << dstTile.rowIndex() << ")"
179 << stringifyWireBundle(flowOp.getDestBundle())
180 << flowOp.getDestChannel() << "\n\t";
181 });
182
183 // if the flow (aka "net") for this FlowOp hasn't been processed yet,
184 // add all switchbox connections to implement the flow
185 TileID srcSbId = {srcCoords.col, srcCoords.row};
186 PathEndPoint srcPoint = {srcSbId, srcPort};
187 if (analyzer.processedFlows.count(srcPoint)) {
188 // This FlowOp is a broadcast sibling of a flow whose route was already
189 // materialized (the analyzer merges all destinations sharing a source
190 // into one net, so the first sibling emitted connections for every
191 // destination). Erase it and report success so the erase is committed.
192 LLVM_DEBUG(llvm::dbgs() << "Flow already processed!\n");
193 rewriter.eraseOp(Op);
194 return success();
195 }
196 const SwitchSettings &settings = analyzer.routing.settings.at(srcPoint);
197 // add connections for all the Switchboxes in SwitchSettings
198 for (const auto &[tileId, setting] : settings) {
199 int col = tileId.col;
200 int row = tileId.row;
201 SwitchboxOp swOp = analyzer.getSwitchbox(rewriter, col, row);
202 int shimCh = srcChannel;
203 bool isShim = analyzer.getTile(rewriter, tileId).isShimNOCorPLTile();
204
205 // TODO: must reserve N3, N7, S2, S3 for DMA connections
206 bool srcUsesShimMux = srcBundle == WireBundle::DMA ||
207 srcBundle == WireBundle::PLIO ||
208 srcBundle == WireBundle::NOC;
209 if (isShim && tileId == srcSbId && srcUsesShimMux) {
210
211 shimCh = shimMuxChannelFrom(srcPort);
212 ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, col);
213 addConnection(rewriter, cast<Interconnect>(shimMuxOp.getOperation()),
214 flowOp, srcBundle, srcChannel, WireBundle::North, shimCh);
215 }
216 assert(setting.srcs.size() == setting.dsts.size());
217 for (size_t i = 0; i < setting.srcs.size(); i++) {
218 Port src = setting.srcs[i];
219 Port dest = setting.dsts[i];
220
221 // A flow can start and end at one shim (see shimMuxChannelFrom).
222 if (isShim && tileId == srcSbId && srcUsesShimMux && src == srcPort)
223 src = {WireBundle::South, shimCh};
224 if (isShim && (dest.bundle == WireBundle::DMA ||
225 dest.bundle == WireBundle::PLIO ||
226 dest.bundle == WireBundle::NOC)) {
227 int destCh = shimMuxChannelTo(dest);
228
229 ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, col);
230 addConnection(rewriter, cast<Interconnect>(shimMuxOp.getOperation()),
231 flowOp, WireBundle::North, destCh, dest.bundle,
232 dest.channel);
233 dest = {WireBundle::South, destCh};
234 }
235 addConnection(rewriter, cast<Interconnect>(swOp.getOperation()), flowOp,
236 src.bundle, src.channel, dest.bundle, dest.channel);
237 }
238
239 LLVM_DEBUG(llvm::dbgs() << tileId << ": " << setting << " | " << "\n");
240 }
241
242 LLVM_DEBUG(llvm::dbgs()
243 << "\n\t\tFinished adding ConnectOps to implement flowOp.\n");
244
245 analyzer.processedFlows.insert(srcPoint);
246 rewriter.eraseOp(Op);
247 return success();
248 }
249};
250
251} // namespace
252
253LogicalResult AIEPathfinderPass::runOnFlow(DeviceOp d,
254 DynamicTileAnalysis &analyzer) {
255 // Apply rewrite rule to switchboxes to add assignments to every 'connect'
256 // operation inside
257 ConversionTarget target(getContext());
258 target.addLegalOp<TileOp>();
259 target.addLegalOp<ConnectOp>();
260 target.addLegalOp<SwitchboxOp>();
261 target.addLegalOp<ShimMuxOp>();
262 target.addLegalOp<EndOp>();
263
264 RewritePatternSet patterns(&getContext());
265 patterns.insert<ConvertFlowsToInterconnect>(d.getContext(), d, analyzer);
266 if (failed(applyPartialConversion(d, target, std::move(patterns))))
267 return failure();
268 return success();
269}
270
271/// The switchbox a master port feeds, and the slave port it feeds there.
272static std::optional<std::pair<TileID, Port>> linkedInput(TileID tile,
273 Port out) {
274 switch (out.bundle) {
275 case WireBundle::East:
276 return std::pair{TileID{tile.col + 1, tile.row},
277 Port{WireBundle::West, out.channel}};
278 case WireBundle::West:
279 return std::pair{TileID{tile.col - 1, tile.row},
280 Port{WireBundle::East, out.channel}};
281 case WireBundle::North:
282 return std::pair{TileID{tile.col, tile.row + 1},
283 Port{WireBundle::South, out.channel}};
284 case WireBundle::South:
285 return std::pair{TileID{tile.col, tile.row - 1},
286 Port{WireBundle::North, out.channel}};
287 default:
288 return std::nullopt;
289 }
290}
291
292/// Whether the connection out of `out` at `tile` leads, through `settings`, to
293/// `finalPort` at `finalTile`.
294static bool findPathToDest(const SwitchSettings &settings, TileID tile,
295 Port out, TileID finalTile, Port finalPort) {
296 if (tile == finalTile && out == finalPort)
297 return true;
298 std::optional<std::pair<TileID, Port>> next = linkedInput(tile, out);
299 if (!next)
300 return false;
301 TileID neighbour = next->first;
302 Port in = next->second;
303 auto setting = settings.find(neighbour);
304 if (setting == settings.end())
305 return false;
306 return llvm::any_of(
307 llvm::zip(setting->second.srcs, setting->second.dsts), [&](auto link) {
308 auto [src, dest] = link;
309 return src == in &&
310 findPathToDest(settings, neighbour, dest, finalTile, finalPort);
311 });
312}
313
314/// Every node `successors` reaches from `start`, with the node a breadth-first
315/// search first reaches it from; `start` maps to itself. Stops once `goal` is
316/// reached, if given.
317template <typename Node, typename Successors>
318static std::map<Node, Node>
319breadthFirstParents(Node start, Successors successors,
320 std::optional<Node> goal = std::nullopt) {
321 std::map<Node, Node> parent{{start, start}};
322 std::deque<Node> work{start};
323 while (!work.empty() && !(goal && parent.count(*goal))) {
324 Node u = work.front();
325 work.pop_front();
326 for (Node v : successors(u))
327 if (parent.try_emplace(v, u).second)
328 work.push_back(v);
329 }
330 return parent;
331}
332
333namespace {
334struct CoverCube {
335 int mask;
336 int value;
337 // cov[i] is set iff this cube matches match id i.
338 llvm::SmallBitVector cov;
339};
340} // namespace
341
342// Minimum set cover by branch-and-bound: find the fewest `cubes` whose coverage
343// (cube.cov) unions to every match id still set in `uncovered`. `sel` is the
344// current partial pick; `bestSel`/`bestSize` hold the smallest full cover so
345// far and are updated in place.
346static void bnbMinCover(ArrayRef<CoverCube> cubes,
347 const llvm::SmallBitVector &uncovered,
348 SmallVectorImpl<int> &sel, int &bestSize,
349 SmallVectorImpl<int> &bestSel) {
350
351 // base case
352 if (uncovered.none()) {
353 if (static_cast<int>(sel.size()) < bestSize) {
354 bestSize = static_cast<int>(sel.size());
355 bestSel.assign(sel.begin(), sel.end());
356 }
357 return;
358 }
359
360 // bound: adding a cube would only tie the best
361 if (static_cast<int>(sel.size()) + 1 >= bestSize) {
362 return;
363 }
364
365 // most constrained variable: find the least-covered, uncovered matchId
366 int pick = -1;
367 int pickCount = static_cast<int>(cubes.size()) + 1;
368
369 for (int id = uncovered.find_first(); id != -1;
370 id = uncovered.find_next(id)) {
371 int c = 0;
372
373 for (const CoverCube &cb : cubes) {
374 if (cb.cov.test(id)) {
375 c++;
376 }
377 }
378
379 if (c < pickCount) {
380 pickCount = c;
381 pick = id;
382 }
383 }
384
385 // branch: try each cube that covers the chosen id, recursing on the rest.
386 for (int i = 0; i < static_cast<int>(cubes.size()); ++i) {
387 if (!cubes[i].cov.test(pick)) {
388 continue;
389 }
390
391 sel.push_back(i);
392 llvm::SmallBitVector next = uncovered;
393 next.reset(cubes[i].cov); // next &= ~cov
394 bnbMinCover(cubes, next, sel, bestSize, bestSel);
395 sel.pop_back();
396 }
397}
398
399static bool cubesIntersect(std::pair<int, int> a, std::pair<int, int> b) {
400 return ((a.second ^ b.second) & a.first & b.first) == 0;
401}
402
403// Cover `matchIds` (a group's packet ids) with the fewest (mask, value) rules
404// that match every specified id and no cube in `avoidCubes` (what the other
405// groups on the same slave port claim). A rule matches id x iff
406// (x & mask) == value; ids no cube claims are don't-cares, free to over-claim.
407//
408// Based on Quine-McCluskey:
409// 1. Enumerate prime implicants: maximal cubes (a cube is one (mask, value))
410// that match >=1 matchIds and no avoid cube.
411// 2. Pick the fewest of those cubes that cover all match ids (bnbMinCover).
412// 3. Tighten each chosen cube to the smallest enclosing cube of the ids it
413// took, so it claims no more don't-cares than necessary; the one-cube case
414// reduces to the old common-bits mask.
415static SmallVector<std::pair<int, int>>
416computeSubcubeCover(const SmallVector<int, 4> &matchIds,
417 ArrayRef<std::pair<int, int>> avoidCubes, int idBits) {
418
419 // id space and full mask, sized from the target's packet-id width.
420 const int numIds = 1 << idBits;
421 const int idMask = numIds - 1;
422
423 auto hitsAvoid = [&](int mask, int value) {
424 return llvm::any_of(avoidCubes, [&](std::pair<int, int> c) {
425 return cubesIntersect({mask, value}, c);
426 });
427 };
428
429 llvm::SmallBitVector matchMask(numIds);
430 for (int id : matchIds) {
431 matchMask.set(id);
432 }
433
434 // phase 1: enumerate prime implicants. small (3^idBits), so brute force.
435 SmallVector<CoverCube> primes;
436 for (int mask = 0; mask < numIds; ++mask) {
437 for (int value = 0; value < numIds; ++value) {
438 // valid cube := value covered by mask, avoids properly
439 if ((value & mask) != value) {
440 continue;
441 }
442 if (hitsAvoid(mask, value)) {
443 continue;
444 }
445
446 // record covering
447 llvm::SmallBitVector cov(numIds);
448 for (int id : matchIds) {
449 if ((id & mask) == value) {
450 cov.set(id);
451 }
452 }
453
454 if (cov.none()) {
455 continue;
456 }
457
458 // prime = maximal, i.e., no checked mask bit can be dropped without
459 // covering an avoidId.
460 bool isPrime = true;
461 for (int b = 0; b < idBits; ++b) {
462 if (!((mask >> b) & 1)) {
463 continue;
464 }
465
466 if (!hitsAvoid(mask & ~(1 << b), value & ~(1 << b))) {
467 isPrime = false;
468 break;
469 }
470 }
471
472 if (isPrime) {
473 primes.push_back({mask, value, std::move(cov)});
474 }
475 }
476 }
477
478 // phase 2: pick the fewest primes that cover every match id (bnb).
479 SmallVector<int> sel, bestSel;
480 int bestSize = static_cast<int>(matchIds.size()) + 1;
481
482 bnbMinCover(primes, matchMask, sel, bestSize, bestSel);
483
484 SmallVector<std::pair<int, int>> chosen;
485 for (int i : bestSel) {
486 chosen.push_back({primes[i].mask, primes[i].value});
487 }
488
489 if (chosen.empty()) {
490 // unreachable: a single-id cube always covers
491 for (int id : matchIds) {
492 chosen.push_back({idMask, id});
493 }
494 }
495
496 // phase 3: tighten each chosen cube to match only the ids it took.
497 // maximal -> over-claim don't-cares -> more mask bits narrow the cube.
498 //
499 // e.g., {2,4} avoid {0}:
500 // prime (mask 00010, value 00010): 2 & 00010 = 00010 == value -> hit
501 // 6 & 00010 = 00010 == value -> also hit
502 // tight (mask 11111, value 00010): 2 & 11111 = 00010 == value -> hit
503 // 6 & 11111 = 00110 != value -> dropped
504 // 0 & 11111 = 00000 != value -> avoided
505
506 SmallVector<SmallVector<int, 4>, 4> assigned(chosen.size());
507 for (int id : matchIds) {
508 for (int c = 0; c < static_cast<int>(chosen.size()); ++c) {
509 if ((id & chosen[c].first) == chosen[c].second) {
510 assigned[c].push_back(id);
511 break;
512 }
513 }
514 }
515
516 SmallVector<std::pair<int, int>> cover;
517 for (const SmallVector<int, 4> &ids : assigned) {
518 if (ids.empty()) {
519 continue;
520 }
521
522 int mask = idMask;
523 for (int i = 0; i < idBits; ++i) {
524 int bit = (ids.front() >> i) & 1;
525 for (int id : ids) {
526 if (((id >> i) & 1) != bit) {
527 mask &= ~(1 << i);
528 break;
529 }
530 }
531 }
532 cover.push_back({mask, ids.front() & mask});
533 }
534
535 return cover;
536}
537
538// A group's rules (see GroupClaims): the stated ones, then a cover of the
539// derived ids that avoids them and `avoid`, whatever else claims ids on the
540// port.
541static SmallVector<std::pair<int, int>>
542groupRules(ArrayRef<std::pair<int, int>> stated, ArrayRef<int> derived,
543 ArrayRef<std::pair<int, int>> avoid, int idBits) {
544 SmallVector<std::pair<int, int>> rules(stated);
545 if (derived.empty())
546 return rules;
547 SmallVector<std::pair<int, int>> derivedAvoid(avoid);
548 derivedAvoid.append(stated.begin(), stated.end());
549 llvm::append_range(rules, computeSubcubeCover(SmallVector<int, 4>(derived),
550 derivedAvoid, idBits));
551 return rules;
552}
553
554namespace {
555struct GroupClaims {
556 ArrayRef<std::pair<int, int>> stated;
557 ArrayRef<int> derived;
558 bool priority = false;
559};
560
561/// A packet rule of a slave port, and the group it selects.
562struct PortRule {
563 int mask;
564 int value;
565 size_t group;
566};
567} // namespace
568
569static uint64_t cubeIds(int mask, int value, int idBits) {
570 uint64_t ids = 0;
571 for (int id = 0; id < (1 << idBits); ++id)
572 if ((id & mask) == (value & mask))
573 ids |= uint64_t(1) << id;
574 return ids;
575}
576
577// The fewest rules, at most `budget`, that send every id a group claims to
578// that group, where a packet takes the first rule it matches. A rule may then
579// claim ids of another group that an earlier rule already takes.
580static std::optional<SmallVector<PortRule>>
581orderedRules(ArrayRef<GroupClaims> groups,
582 ArrayRef<std::pair<int, int>> existing, int idBits,
583 size_t budget) {
584 if (idBits > 6)
585 return std::nullopt;
586 const int idMask = (1 << idBits) - 1;
587 SmallVector<uint64_t> claims;
588 for (const GroupClaims &group : groups) {
589 uint64_t ids = 0;
590 for (auto [mask, value] : group.stated)
591 ids |= cubeIds(mask, value, idBits);
592 for (int id : group.derived)
593 ids |= uint64_t(1) << id;
594 claims.push_back(ids);
595 }
596 uint64_t claimed = 0;
597 for (uint64_t ids : claims)
598 claimed |= ids;
599 uint64_t taken = 0;
600 for (auto [mask, value] : existing)
601 taken |= cubeIds(mask, value, idBits);
602 if (taken & claimed)
603 return std::nullopt;
604 SmallVector<uint64_t> cubes;
605 for (int mask = 0; mask <= idMask; ++mask)
606 for (int value = 0; value <= idMask; ++value)
607 if ((value & mask) == value)
608 cubes.push_back(cubeIds(mask, value, idBits));
609
610 // A rule is worth only the claimed ids it newly takes, all of one group, so
611 // the tightest cube around them serves, and fewer ids of the same group
612 // never serve better.
613 SmallVector<PortRule> rules;
614 std::set<std::pair<uint64_t, size_t>> dead;
615 auto search = [&](auto &self, uint64_t taken, size_t left) -> bool {
616 uint64_t open = claimed & ~taken;
617 size_t openGroups =
618 llvm::count_if(claims, [&](uint64_t ids) { return ids & open; });
619 if (openGroups == 0)
620 return true;
621 if (openGroups > left || dead.count({taken, left}))
622 return false;
623 SmallVector<std::pair<uint64_t, size_t>> moves;
624 for (uint64_t cube : cubes)
625 for (auto [g, ids] : llvm::enumerate(claims))
626 if ((cube & open) && (cube & open & ~ids) == 0 &&
627 !llvm::is_contained(moves, std::pair{cube & open, g}))
628 moves.push_back({cube & open, g});
629 llvm::stable_sort(moves, [](auto a, auto b) {
630 return llvm::popcount(a.first) > llvm::popcount(b.first);
631 });
632 SmallVector<std::pair<uint64_t, size_t>> tried;
633 for (auto [own, g] : moves) {
634 if (llvm::any_of(tried, [&, own = own, g = g](auto t) {
635 return t.second == g && (t.first & own) == own;
636 }))
637 continue;
638 tried.push_back({own, g});
639 int all = idMask, any = 0;
640 for (int id = 0; id <= idMask; ++id)
641 if ((own >> id) & 1) {
642 all &= id;
643 any |= id;
644 }
645 int mask = idMask & ~(all ^ any);
646 rules.push_back({mask, all, g});
647 if (self(self, taken | own, left - 1))
648 return true;
649 rules.pop_back();
650 }
651 dead.insert({taken, left});
652 return false;
653 };
654 for (size_t length = 1; length <= budget; ++length)
655 if (search(search, taken, length))
656 return rules;
657 return std::nullopt;
658}
659
660// The rules of a slave port, in slot order after the `existing` ones. Each
661// group takes its own cover, which claims no id another group claims, unless
662// those outnumber the free slots and first-match order fits them.
663static SmallVector<PortRule> portRules(ArrayRef<GroupClaims> groups,
664 ArrayRef<std::pair<int, int>> existing,
665 int idBits, size_t slots) {
666 const int idMask = (1 << idBits) - 1;
667 SmallVector<PortRule> rules;
668 for (auto [g, group] : llvm::enumerate(groups)) {
669 SmallVector<std::pair<int, int>> avoid(existing);
670 for (auto [o, other] : llvm::enumerate(groups)) {
671 if (o == g)
672 continue;
673 avoid.append(other.stated.begin(), other.stated.end());
674 for (int id : other.derived)
675 avoid.push_back({idMask, id});
676 }
677 for (auto [mask, value] :
678 groupRules(group.stated, group.derived, avoid, idBits))
679 rules.push_back({mask, value, g});
680 }
681 if (existing.size() + rules.size() <= slots || existing.size() >= slots)
682 return rules;
683 if (std::optional<SmallVector<PortRule>> ordered =
684 orderedRules(groups, existing, idBits, slots - existing.size()))
685 return *ordered;
686 return rules;
687}
688
689static SmallVector<PortRule>
690slavePortRules(ArrayRef<GroupClaims> groups,
691 ArrayRef<std::pair<int, int>> existing, int idBits,
692 size_t slots) {
693 SmallVector<std::pair<int, int>> taken(existing);
694 SmallVector<PortRule> rules;
695 for (bool priority : {true, false}) {
696 SmallVector<size_t, 4> members;
697 SmallVector<GroupClaims> claims;
698 for (auto [g, group] : llvm::enumerate(groups))
699 if (group.priority == priority) {
700 members.push_back(g);
701 claims.push_back(group);
702 }
703 if (claims.empty())
704 continue;
705 for (const PortRule &r : portRules(claims, taken, idBits, slots))
706 rules.push_back({r.mask, r.value, members[r.group]});
707 for (const PortRule &r : rules)
708 if (groups[r.group].priority == priority)
709 taken.push_back({r.mask, r.value});
710 }
711 return rules;
712}
713
714namespace {
715/// Packets with one id entering a switchbox on one slave port, and the master
716/// ports they leave by.
717struct SlaveFlow {
718 Port slave;
719 int id;
720 SmallVector<Port, 4> masters;
721 bool isPriority;
722};
723
724/// The amsel each slave flow takes, and the master ports each amsel selects
725/// (see planArbiters).
726struct ArbiterPlan {
727 std::map<std::pair<Port, int>, int> slaveAmsels;
728 std::map<int, SmallVector<Port, 4>> amselMasters;
729};
730
731/// The master ports `flows` tie to one arbiter: a slave flow's arbiter takes
732/// every master port it leaves by (see planArbiters).
733template <typename SlaveFlows>
734llvm::EquivalenceClasses<Port> tiedMasters(SlaveFlows &&flows) {
735 llvm::EquivalenceClasses<Port> tied;
736 for (const SlaveFlow &f : flows)
737 for (Port m : f.masters)
738 tied.unionSets(f.masters.front(), m);
739 return tied;
740}
741
742/// Chooses arbiters for a switchbox's slave flows so that no two flows that
743/// can deadlock (`conflict`) and enter on different slave ports share one.
744/// A master port is tied to one arbiter, so every master port a slave flow
745/// leaves by takes that flow's arbiter; the master ports linked that way form
746/// a unit, and each master set in a unit takes an msel. A flow never takes
747/// an arbiter `excluded` rules out for it. Units are colored onto arbiters by
748/// backtracking, which is exact up to a step budget. On failure, fills
749/// `blocking` with the conflicting pairs that stood in the way; it stays empty
750/// when the master sets alone outnumber the free msels.
751std::optional<ArbiterPlan>
752planArbiters(const AIETargetModel &targetModel, ArrayRef<SlaveFlow> flows,
753 llvm::function_ref<bool(size_t, size_t)> conflict,
754 llvm::function_ref<bool(size_t, int)> excluded,
755 const std::set<int> &reservedAmsels,
756 SmallVectorImpl<std::pair<size_t, size_t>> &blocking) {
757 const int numArbiters = targetModel.getNumArbiters();
758 const int numMselsPerArbiter = targetModel.getNumMselsPerArbiter();
759 auto amselOf = [&](int arbiter, int msel) {
760 return arbiter + msel * numArbiters;
761 };
762
763 llvm::EquivalenceClasses<Port> tied = tiedMasters(flows);
764
765 struct Unit {
766 SmallVector<size_t, 4> flows;
767 SmallVector<SmallVector<Port, 4>, 2> masterSets;
768 bool isPriority = false;
769 };
770 std::vector<Unit> units;
771 std::map<Port, size_t> unitOf;
772 SmallVector<size_t, 8> flowUnit;
773 for (auto [i, f] : llvm::enumerate(flows)) {
774 auto [it, inserted] =
775 unitOf.try_emplace(tied.getLeaderValue(f.masters.front()), 0);
776 if (inserted) {
777 it->second = units.size();
778 units.emplace_back();
779 }
780 Unit &unit = units[it->second];
781 flowUnit.push_back(it->second);
782 unit.flows.push_back(i);
783 unit.isPriority |= f.isPriority;
784 if (!llvm::is_contained(unit.masterSets, f.masters))
785 unit.masterSets.push_back(f.masters);
786 }
787 for (Unit &unit : units) {
788 auto *priorityEnd = std::stable_partition(
789 unit.masterSets.begin(), unit.masterSets.end(),
790 [&](const SmallVector<Port, 4> &masters) {
791 return llvm::any_of(unit.flows, [&](size_t f) {
792 return flows[f].isPriority && flows[f].masters == masters;
793 });
794 });
795 std::sort(unit.masterSets.begin(), priorityEnd);
796 }
797
798 auto clash = [&](size_t a, size_t b) {
799 return flows[a].slave != flows[b].slave && conflict(a, b);
800 };
801 for (const Unit &unit : units)
802 for (auto [k, a] : llvm::enumerate(unit.flows))
803 for (size_t b : llvm::drop_begin(unit.flows, k + 1))
804 if (clash(a, b))
805 blocking.push_back({a, b});
806 if (!blocking.empty())
807 return std::nullopt;
808
809 std::vector<llvm::SmallDenseSet<size_t, 4>> neighbors(units.size());
810 SmallVector<std::pair<size_t, size_t>, 4> crossPairs;
811 for (size_t a = 0; a < flows.size(); a++)
812 for (size_t b = a + 1; b < flows.size(); b++)
813 if (flowUnit[a] != flowUnit[b] && clash(a, b)) {
814 neighbors[flowUnit[a]].insert(flowUnit[b]);
815 neighbors[flowUnit[b]].insert(flowUnit[a]);
816 crossPairs.push_back({a, b});
817 }
818
819 SmallVector<SmallVector<int, 4>, 6> freeMsels(numArbiters);
820 SmallVector<SmallVector<size_t, 4>, 6> excludedUnits(numArbiters);
821 for (int a = 0; a < numArbiters; a++) {
822 for (int m = 0; m < numMselsPerArbiter; m++)
823 if (!reservedAmsels.count(amselOf(a, m)))
824 freeMsels[a].push_back(m);
825 for (size_t f = 0; f < flows.size(); f++)
826 if (excluded(f, a) && !llvm::is_contained(excludedUnits[a], flowUnit[f]))
827 excludedUnits[a].push_back(flowUnit[f]);
828 }
829 size_t mostFree = 0;
830 for (const SmallVector<int, 4> &free : freeMsels)
831 mostFree = std::max(mostFree, free.size());
832 if (llvm::any_of(units, [&](const Unit &unit) {
833 return unit.masterSets.size() > mostFree;
834 }))
835 return std::nullopt;
836
837 // Most constrained first.
838 SmallVector<size_t, 8> order(units.size());
839 std::iota(order.begin(), order.end(), 0);
840 llvm::stable_sort(order, [&](size_t a, size_t b) {
841 return std::make_pair(neighbors[a].size(), units[a].masterSets.size()) >
842 std::make_pair(neighbors[b].size(), units[b].masterSets.size());
843 });
844
845 SmallVector<int, 8> arbiterOf(units.size(), -1);
846 SmallVector<size_t, 6> load(numArbiters, 0);
847 int steps = 0;
848 auto place = [&](auto &self, size_t depth) -> bool {
849 if (depth == order.size())
850 return true;
851 if (++steps > maxSearchSteps) {
852 LLVM_DEBUG(llvm::dbgs() << "Arbiter search gave up after "
853 << maxSearchSteps << " steps\n");
854 return false;
855 }
856 size_t u = order[depth];
857 SmallVector<int, 6> candidates(numArbiters);
858 std::iota(candidates.begin(), candidates.end(), 0);
859 if (units[u].isPriority)
860 std::reverse(candidates.begin(), candidates.end());
861 else
862 llvm::stable_sort(candidates, [&](int a, int b) {
863 return load[a] + numMselsPerArbiter - freeMsels[a].size() <
864 load[b] + numMselsPerArbiter - freeMsels[b].size();
865 });
866 std::set<std::pair<size_t, SmallVector<size_t, 4>>> triedEmpty;
867 for (int a : candidates) {
868 if (load[a] + units[u].masterSets.size() > freeMsels[a].size())
869 continue;
870 if (llvm::is_contained(excludedUnits[a], u) ||
871 llvm::any_of(neighbors[u],
872 [&](size_t v) { return arbiterOf[v] == a; }))
873 continue;
874 // Empty arbiters with as many free msels, ruled out for the same units,
875 // are interchangeable.
876 if (load[a] == 0 &&
877 !triedEmpty.insert({freeMsels[a].size(), excludedUnits[a]}).second)
878 continue;
879 arbiterOf[u] = a;
880 load[a] += units[u].masterSets.size();
881 if (self(self, depth + 1))
882 return true;
883 load[a] -= units[u].masterSets.size();
884 arbiterOf[u] = -1;
885 }
886 return false;
887 };
888 if (!place(place, 0)) {
889 blocking.append(crossPairs.begin(), crossPairs.end());
890 return std::nullopt;
891 }
892
893 SmallVector<size_t, 8> mselOrder(units.size());
894 std::iota(mselOrder.begin(), mselOrder.end(), 0);
895 llvm::stable_sort(mselOrder, [&](size_t a, size_t b) {
896 if (!units[a].isPriority || !units[b].isPriority)
897 return units[a].isPriority > units[b].isPriority;
898 return units[a].masterSets.front() < units[b].masterSets.front();
899 });
900 ArbiterPlan plan;
901 SmallVector<size_t, 6> low(numArbiters, 0), high(numArbiters, 0);
902 for (size_t u : mselOrder) {
903 const Unit &unit = units[u];
904 int a = arbiterOf[u];
905 std::map<SmallVector<Port, 4>, int> setAmsel;
906 for (const SmallVector<Port, 4> &masters : unit.masterSets) {
907 int msel = unit.isPriority
908 ? freeMsels[a][freeMsels[a].size() - 1 - high[a]++]
909 : freeMsels[a][low[a]++];
910 setAmsel[masters] = amselOf(a, msel);
911 plan.amselMasters[amselOf(a, msel)] = masters;
912 }
913 for (size_t f : unit.flows)
914 plan.slaveAmsels[{flows[f].slave, flows[f].id}] =
915 setAmsel.at(flows[f].masters);
916 }
917 return plan;
918}
919
920/// The tiles other than `src` and `dst` that every route between them passes.
921SmallVector<TileID> cutTiles(const AIETargetModel &targetModel, TileID src,
922 TileID dst) {
923 auto neighbors = [&](TileID t) {
924 SmallVector<TileID, 4> next;
925 for (auto [bundle, dc, dr] : {std::tuple{WireBundle::North, 0, 1},
926 {WireBundle::South, 0, -1},
927 {WireBundle::East, 1, 0},
928 {WireBundle::West, -1, 0}}) {
929 TileID n{t.col + dc, t.row + dr};
930 if (n.col >= 0 && n.col < targetModel.columns() && n.row >= 0 &&
931 n.row < targetModel.rows() &&
932 targetModel.getNumDestSwitchboxConnections(t.col, t.row, bundle) > 0)
933 next.push_back(n);
934 }
935 return next;
936 };
937 auto reach = [&](std::optional<TileID> avoid) {
938 return breadthFirstParents(
939 src,
940 [&](TileID t) {
941 SmallVector<TileID, 4> next = neighbors(t);
942 llvm::erase(next, avoid);
943 return next;
944 },
945 std::optional{dst});
946 };
947 std::map<TileID, TileID> via = reach(std::nullopt);
948 if (!via.count(dst))
949 return {};
950 SmallVector<TileID> interior;
951 for (TileID t = via.at(dst); t != src; t = via.at(t))
952 interior.push_back(t);
953 llvm::erase_if(interior, [&](TileID t) { return reach(t).count(dst) > 0; });
954 return interior;
955}
956
957/// The tile, port and id of each packet a prioritized flow (priority_route)
958/// sends.
959struct PrioritizedPackets {
960 std::set<std::tuple<TileID, Port, int>> packets;
961 bool contains(const RoutedStream &s) const {
962 return s.packetID && packets.count({s.src.tile, s.src.port, *s.packetID});
963 }
964};
965
966PrioritizedPackets prioritizedPackets(DeviceOp device) {
967 PrioritizedPackets prioritized;
968 for (PacketFlowOp flow : device.getOps<PacketFlowOp>())
969 if (flow.getPriorityRoute().value_or(false))
970 for (auto src : flow.getPorts().getOps<PacketSourceOp>())
971 prioritized.packets.insert(
972 {cast<TileOp>(src.getTile().getDefiningOp()).getTileID(),
973 src.port(), flow.IDInt()});
974 return prioritized;
975}
976
977/// The largest set of `candidates` that must be kept apart pairwise
978/// (StreamConflicts::mustSeparate). The search stops once it finds one larger
979/// than `limit` or runs out of steps.
980SmallVector<size_t, 8> largestApartSet(const StreamConflicts &conflicts,
981 ArrayRef<size_t> candidates,
982 size_t limit) {
983 SmallVector<size_t, 8> clique, best;
984 int steps = 0;
985 auto grow = [&](auto &self, ArrayRef<size_t> cands) -> void {
986 if (clique.size() > best.size())
987 best = clique;
988 for (auto [k, s] : llvm::enumerate(cands)) {
989 if (best.size() > limit || ++steps > maxSearchSteps ||
990 clique.size() + cands.size() - k <= best.size())
991 return;
992 SmallVector<size_t, 8> next;
993 for (size_t t : cands.drop_front(k + 1))
994 if (conflicts.mustSeparate(s, t))
995 next.push_back(t);
996 clique.push_back(s);
997 self(self, next);
998 clique.pop_back();
999 }
1000 };
1001 grow(grow, candidates);
1002 return best;
1003}
1004
1005/// The amsels, as arbiter + msel * arbiters, the input's switchboxes already
1006/// configure, by tile. An amsel no master set uses still has rules steering
1007/// packets to it.
1008std::map<TileID, std::set<int>> configuredAmsels(DeviceOp device) {
1009 const int numArbiters = device.getTargetModel().getNumArbiters();
1010 std::map<TileID, std::set<int>> amsels;
1011 for (auto swboxOp : device.getOps<SwitchboxOp>())
1012 for (auto amselOp : swboxOp.getConnections().getOps<AMSelOp>())
1013 amsels[swboxOp.getTileOp().getTileID()].insert(
1014 amselOp.arbiterIndex() + amselOp.getMselValue() * numArbiters);
1015 return amsels;
1016}
1017
1018/// Packet streams take an arbiter at the tile they end at whatever the
1019/// routing, and where `pinsHops` says hops cannot be circuit switched, or the
1020/// stream is prioritized, at the tile they start at and every tile each of
1021/// their routes passes too. Two that must be kept apart pass any tile on
1022/// different slave ports -- sharing one means they merged, unsafely, upstream
1023/// -- so a set of them that must be kept apart pairwise needs an arbiter
1024/// apiece. Says why no routing can work if some tile has such a set larger
1025/// than its free arbiters.
1026std::optional<std::string>
1027tooFewArbiters(DeviceOp device, const StreamConflicts &conflicts,
1028 llvm::function_ref<bool(TileID)> pinsHops,
1029 const PrioritizedPackets &prioritized) {
1030 const AIETargetModel &targetModel = device.getTargetModel();
1031 const int numArbiters = targetModel.getNumArbiters();
1032 const int numMselsPerArbiter = targetModel.getNumMselsPerArbiter();
1033 std::map<TileID, SmallVector<size_t, 8>> pinned;
1034 for (auto [i, s] : llvm::enumerate(conflicts.getRequestedStreams())) {
1035 if (!s.packetID)
1036 continue;
1037 pinned[s.dst.tile].push_back(i);
1038 if (s.src.tile == s.dst.tile)
1039 continue;
1040 bool circuitless = prioritized.contains(s);
1041 if (circuitless || pinsHops(s.src.tile))
1042 pinned[s.src.tile].push_back(i);
1043 for (TileID t : cutTiles(targetModel, s.src.tile, s.dst.tile))
1044 if (circuitless || pinsHops(t))
1045 pinned[t].push_back(i);
1046 }
1047 std::map<TileID, std::set<int>> reserved = configuredAmsels(device);
1048
1049 ArrayRef<RoutedStream> streams = conflicts.getStreams();
1050 for (const auto &[tileId, candidates] : pinned) {
1051 const std::set<int> &taken = reserved[tileId];
1052 size_t free = 0;
1053 for (int a = 0; a < numArbiters; a++)
1054 free += llvm::any_of(llvm::seq(numMselsPerArbiter), [&](int m) {
1055 return !taken.count(a + m * numArbiters);
1056 });
1057 // Streams that must be kept apart have distinct sources and destinations.
1058 std::set<std::pair<TileID, Port>> srcs, dsts;
1059 for (size_t s : candidates) {
1060 srcs.insert({streams[s].src.tile, streams[s].src.port});
1061 dsts.insert({streams[s].dst.tile, streams[s].dst.port});
1062 }
1063 if (std::min(srcs.size(), dsts.size()) <= free)
1064 continue;
1065 SmallVector<size_t, 8> best = largestApartSet(conflicts, candidates, free);
1066 if (best.size() <= free)
1067 continue;
1068
1069 std::string reason;
1070 llvm::raw_string_ostream os(reason);
1071 if (best.size() == 1) {
1072 os << "at tile (" << tileId.col << ", " << tileId.row << "), "
1073 << describeStream(streams[best[0]])
1074 << " takes an arbiter whatever the routing, but the switchbox has "
1075 "none free";
1076 return reason;
1077 }
1078 os << "at tile (" << tileId.col << ", " << tileId.row << "), no two of ";
1079 llvm::interleave(
1080 best, os, [&](size_t s) { os << describeStream(streams[s]); }, ", ");
1081 os << " can share an arbiter, and each takes one there whatever the "
1082 "routing, but the switchbox has "
1083 << free << " free. For example, " << conflicts.explain(best[0], best[1]);
1084 return reason;
1085 }
1086 return std::nullopt;
1087}
1088
1089/// Where a stream leaves a tile it is known to pass: by which master port from
1090/// which slave port, if known.
1091struct Leaving {
1092 size_t stream;
1093 std::optional<Port> slave;
1094 Port master;
1095};
1096
1097/// Where each stream leaves each tile on the trees in `pinnedTrees`, and the
1098/// tiles those trees pass.
1099std::pair<std::map<TileID, SmallVector<Leaving, 8>>, std::set<TileID>>
1100leavingPinnedTrees(const StreamConflicts &conflicts,
1101 const PacketTrees &pinnedTrees,
1102 const PrioritizedPackets &prioritized) {
1103 // Each hop of a pinned tree by the hop before it, unset where two lead to it.
1104 std::map<PathEndPoint, std::map<PathEndPoint, std::optional<PathEndPoint>>>
1105 preds;
1106 for (const auto &[src, hops] : pinnedTrees)
1107 for (const TreeHop &hop : hops) {
1108 auto [it, first] = preds[src].try_emplace(hop.to, hop.from);
1109 if (!first && it->second && !(*it->second == hop.from))
1110 it->second.reset();
1111 }
1112 std::map<TileID, SmallVector<Leaving, 8>> leaving;
1113 std::set<TileID> onTrees;
1114 for (auto [i, s] : llvm::enumerate(conflicts.getRequestedStreams())) {
1115 if (!s.packetID)
1116 continue;
1117 // Only the prioritized packets of a source take its tree.
1118 auto tree = prioritized.contains(s) ? preds.find({s.src.tile, s.src.port})
1119 : preds.end();
1120 SmallVector<std::pair<Port, PathEndPoint>, 8> hops;
1121 PathEndPoint at{s.dst.tile, s.dst.port}, src{s.src.tile, s.src.port};
1122 for (size_t n = 0;
1123 tree != preds.end() && !(at == src) && n <= tree->second.size(); n++) {
1124 auto pred = tree->second.find(at);
1125 if (pred == tree->second.end() || !pred->second)
1126 break;
1127 if (pred->second->coords == at.coords)
1128 hops.push_back({pred->second->port, at});
1129 at = *pred->second;
1130 }
1131 if (!(at == src)) {
1132 leaving[s.dst.tile].push_back({i, std::nullopt, s.dst.port});
1133 continue;
1134 }
1135 for (auto [slave, hop] : hops) {
1136 leaving[hop.coords].push_back({i, slave, hop.port});
1137 onTrees.insert(hop.coords);
1138 }
1139 }
1140 return {std::move(leaving), std::move(onTrees)};
1141}
1142
1143/// A visit of `here` on one arbiter with another: the other visit, and the
1144/// master port they leave by or the slave port they share a packet rule on.
1145struct ArbiterLink {
1146 size_t visit;
1147 Port port;
1148 bool rule;
1149};
1150
1151/// Says why `a` and `b`, which must be kept apart, end up on one arbiter at
1152/// `tileId` through the visits in `chain`.
1153std::string explainSharedArbiter(TileID tileId, size_t a, size_t b,
1154 ArrayRef<ArbiterLink> chain,
1155 ArrayRef<Leaving> here,
1156 const StreamConflicts &conflicts) {
1157 ArrayRef<RoutedStream> streams = conflicts.getStreams();
1158 std::string reason;
1159 llvm::raw_string_ostream os(reason);
1160 os << "at tile (" << tileId.col << ", " << tileId.row
1161 << "), the routes prioritized flows (priority_route) keep put "
1162 << describeStream(streams[a]) << " and " << describeStream(streams[b])
1163 << " on one arbiter: " << describeStream(streams[a]);
1164 for (auto [k, link] : llvm::enumerate(chain))
1165 os << (k == 0 ? "" : ", which")
1166 << (link.rule ? " takes one packet rule on " : " leaves by ")
1167 << describePort(link.port) << " with "
1168 << describeStream(streams[here[link.visit].stream]);
1169 os << ", and a master port or packet rule takes one arbiter. "
1170 << conflicts.explain(a, b);
1171 return reason;
1172}
1173
1174/// Says why no routing can work if two streams that must be kept apart leave
1175/// `tileId` by master ports that streams leaving by one each join, which puts
1176/// them on one arbiter.
1177std::optional<std::string> sharedArbiterAt(TileID tileId,
1178 ArrayRef<Leaving> here,
1179 const StreamConflicts &conflicts) {
1180 ArrayRef<RoutedStream> streams = conflicts.getStreams();
1181 // A stream packet switched at a master port takes its one arbiter, and
1182 // packets with one id on a slave port take one packet rule's arbiter.
1183 // The visits on one arbiter with each. A stream passing a tile twice takes
1184 // an arbiter on each visit, so the nodes are visits.
1185 std::map<size_t, SmallVector<ArbiterLink, 4>> with;
1186 for (auto [x, l] : llvm::enumerate(here))
1187 for (auto [y, m] : llvm::enumerate(here)) {
1188 if (x == y)
1189 continue;
1190 if (l.master == m.master)
1191 with[x].push_back({y, l.master, false});
1192 else if (l.slave && m.slave && *l.slave == *m.slave &&
1193 streams[l.stream].packetID == streams[m.stream].packetID)
1194 with[x].push_back({y, *l.slave, true});
1195 }
1196 for (size_t x : llvm::make_first_range(with)) {
1197 // The chain of visits that joins `x` to each visit it reaches.
1198 std::map<size_t, size_t> via = breadthFirstParents(x, [&](size_t u) {
1199 return llvm::map_range(
1200 with.at(u), [](const ArbiterLink &link) { return link.visit; });
1201 });
1202 size_t a = here[x].stream;
1203 for (size_t y : llvm::make_first_range(via)) {
1204 size_t b = here[y].stream;
1205 if (b <= a || !conflicts.mustSeparate(a, b))
1206 continue;
1207 SmallVector<ArbiterLink, 4> chain;
1208 for (size_t v = y; v != x; v = via.at(v))
1209 chain.push_back(
1210 *llvm::find_if(with.at(via.at(v)), [&](const ArbiterLink &link) {
1211 return link.visit == v;
1212 }));
1213 std::reverse(chain.begin(), chain.end());
1214 return explainSharedArbiter(tileId, a, b, chain, here, conflicts);
1215 }
1216 }
1217 return std::nullopt;
1218}
1219
1220/// A RoutingFailure if no routing can work because the arbiters packet streams
1221/// take whatever the routing do not fit: too few free ones at a tile, or, as a
1222/// PinnedRoutingFailure, two streams that must be kept apart put on one by the
1223/// trees in `pinnedTrees`, which prioritized sources keep.
1224llvm::Error unroutableArbiters(DeviceOp device,
1225 const StreamConflicts &conflicts,
1226 llvm::function_ref<bool(TileID)> pinsHops,
1227 const PacketTrees &pinnedTrees) {
1228 PrioritizedPackets prioritized = prioritizedPackets(device);
1229 if (std::optional<std::string> reason =
1230 tooFewArbiters(device, conflicts, pinsHops, prioritized))
1231 return llvm::make_error<RoutingFailure>(std::move(*reason));
1232 if (pinnedTrees.empty())
1233 return llvm::Error::success();
1234 auto [leaving, onTrees] =
1235 leavingPinnedTrees(conflicts, pinnedTrees, prioritized);
1236 for (TileID tileId : onTrees)
1237 if (std::optional<std::string> reason =
1238 sharedArbiterAt(tileId, leaving[tileId], conflicts))
1239 return llvm::make_error<PinnedRoutingFailure>(std::move(*reason));
1240 return llvm::Error::success();
1241}
1242} // namespace
1243
1244static void getOrCreateConnect(OpBuilder &builder, ShimMuxOp shimMux,
1245 Location loc, WireBundle srcBundle, int srcCh,
1246 WireBundle destBundle, int destCh) {
1247 for (auto connect : shimMux.getConnections().getOps<ConnectOp>())
1248 if (connect.getSourceBundle() == srcBundle &&
1249 connect.getSourceChannel() == srcCh &&
1250 connect.getDestBundle() == destBundle &&
1251 connect.getDestChannel() == destCh)
1252 return;
1253 ConnectOp::create(builder, loc, srcBundle, srcCh, destBundle, destCh);
1254}
1255
1256static PathEndPoint sourceOf(FlowOp flow) {
1257 auto tile = cast<TileOp>(flow.getSource().getDefiningOp());
1258 return PathEndPoint{tile.getTileID(),
1259 {flow.getSourceBundle(), flow.getSourceChannel()}};
1260}
1261
1262// The router names a shim DMA by its own port; move the rules and master sets
1263// that use one behind the shim mux, onto the South channel it reaches the
1264// switchbox by (see shimMuxChannelFrom).
1265static void lowerShimDMAPorts(DeviceOp device, OpBuilder &builder,
1266 DynamicTileAnalysis &analyzer) {
1267 for (auto switchbox : device.getOps<SwitchboxOp>()) {
1268 TileOp tileOp = switchbox.getTileOp();
1269 if (!tileOp.isShimNOCTile())
1270 continue;
1271 auto connectInMux = [&](Location loc, Port src, Port dst) {
1272 builder.setInsertionPointAfter(tileOp);
1273 ShimMuxOp shimMux = analyzer.getShimMux(builder, tileOp.colIndex());
1274 builder.setInsertionPointToStart(&shimMux.getConnections().front());
1275 getOrCreateConnect(builder, shimMux, loc, src.bundle, src.channel,
1276 dst.bundle, dst.channel);
1277 };
1278 for (Operation &op : switchbox.getConnections().front()) {
1279 if (auto rules = dyn_cast<PacketRulesOp>(op);
1280 rules && rules.getSourceBundle() == WireBundle::DMA) {
1281 int ch = shimMuxChannelFrom(rules.sourcePort());
1282 connectInMux(rules.getLoc(), rules.sourcePort(),
1283 {WireBundle::North, ch});
1284 rules.setSourceBundle(WireBundle::South);
1285 rules.setSourceChannel(ch);
1286 }
1287 if (auto masterSet = dyn_cast<MasterSetOp>(op);
1288 masterSet && masterSet.getDestBundle() == WireBundle::DMA) {
1289 int ch = shimMuxChannelTo(masterSet.destPort());
1290 connectInMux(masterSet.getLoc(), {WireBundle::North, ch},
1291 masterSet.destPort());
1292 masterSet.setDestBundle(WireBundle::South);
1293 masterSet.setDestChannel(ch);
1294 }
1295 }
1296 }
1297}
1298
1299namespace {
1300// How runOnPacketFlow lowers the packet flows on one routing: the connections
1301// the routing lays down and the arbiters planned for them.
1302struct PacketPlan {
1303 LogicalResult emit(DeviceOp device, OpBuilder &builder,
1304 DynamicTileAnalysis &analyzer) const;
1305 SmallVector<std::pair<int, int>, 2>
1306 statedCubes(const std::pair<PhysPort, int> &slaveFlow, int idMask) const;
1307
1308 // A hold cycle through waits at receivers the trees share that no arbiter
1309 // assignment found breaks.
1310 std::optional<HoldCycle> sharedReceiverCycle;
1311
1312 // The master ports packets with each flow ID leave by, keyed by the slave
1313 // port they enter.
1314 std::map<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>> packetFlows;
1315 SmallVector<std::pair<PhysPort, int>, 4> slavePorts;
1316 // Where the packet flows entering each slave port with each ID were
1317 // written, so the routing they become points back at them.
1318 std::map<std::pair<PhysPort, int>, SmallVector<Location, 1>> slaveFlowLocs;
1319 // The packet flows' locations in the order the flows were written.
1320 DenseMap<Location, unsigned> flowLocOrder;
1321 // The flows behind `slaveFlows`, fused, or `fallback` if none.
1322 Location locOf(ArrayRef<std::pair<PhysPort, int>> slaveFlows,
1323 Location fallback) const;
1324 // Flag to keep packet header at packet flow destination
1325 DenseMap<PhysPort, BoolAttr> keepPktHeaderAttr;
1326 // The slave ports and IDs that carry prioritized packets. A switchbox routes
1327 // on the ID alone, so a flow sharing both with a priority flow is one too.
1328 DenseSet<std::pair<PhysPort, int>> prioritizedFlows;
1329 DenseSet<std::pair<PhysPort, int>> markedFlows;
1330 DenseSet<PhysPort> priorityMasterPorts;
1331
1332 // Packet-rule masks the flows state, keyed by the slave port the stream
1333 // enters and the flow ID. One ID may reach a port under two masks, so the
1334 // ID alone does not identify the claim, and flows sharing both may each
1335 // state a mask of their own, which the port then claims together.
1336 std::map<std::pair<PhysPort, int>, std::set<int>> pinnedMasks;
1337
1338 // A switchbox with more packet master ports than free arbiters has to put
1339 // two master ports on one arbiter, and an arbiter holds its grant until
1340 // tlast, so one stalled packet then holds up an unrelated flow. A hop that
1341 // alone uses both its ports needs no arbiter: a circuit connection passes the
1342 // header and tlast through, and the next switchbox routes the packet as
1343 // before. The last hop stays packet-switched, since it drops the header.
1344 // A circuit may broadcast, so a slave port qualifies with all the master
1345 // ports it reaches, provided it alone feeds them and every packet goes to
1346 // all of them.
1347 std::map<TileID, SmallVector<Connect, 4>> circuitHops;
1348 std::map<TileID, ArbiterPlan> plans;
1349};
1350
1351// Plans the packet flows on a routing, leaving the IR alone. Where no plan
1352// fits, the faults say which connections are in the way.
1353struct PacketFlowRouting : PacketPlan {
1354 using FlowKey = std::pair<Port, int>;
1355
1356 PacketFlowRouting(DeviceOp device, const Routing &routing,
1357 const StreamConflicts &conflicts,
1358 const PinnedRoutes &pinned, bool routeCircuit,
1359 bool circuitSwitchHops, bool prioritize)
1360 : device(device), routing(routing), conflicts(conflicts), pinned(pinned),
1361 prioritize(prioritize), routeCircuit(routeCircuit),
1362 circuitSwitchHops(circuitSwitchHops),
1363 targetModel(device.getTargetModel()),
1364 numArbiters(targetModel.getNumArbiters()),
1365 numMselsPerArbiter(targetModel.getNumMselsPerArbiter()),
1366 maxPacketId(targetModel.getMaxPacketId()),
1367 idBits(llvm::Log2_32_Ceil(maxPacketId + 1)), idMask((1 << idBits) - 1) {
1368 }
1369
1370 llvm::Expected<PacketPlan> plan();
1371 void collectFlows();
1372 void findCircuitHops();
1373 void collectSlaveFlows();
1374 void checkRules();
1375 void planTiles();
1376 std::optional<std::string> breakHoldCycles();
1377 RoutingFaults faults() const;
1378
1379 const SwitchSettings &settingsOf(const PathEndPoint &src,
1380 std::optional<int> id = std::nullopt);
1381 std::optional<std::pair<size_t, size_t>>
1382 conflictingStreams(TileID tileId, const SlaveFlow &a, const SlaveFlow &b);
1383 std::optional<ArbiterPlan>
1384 planTile(TileID tileId, SmallVectorImpl<std::pair<size_t, size_t>> &blocking);
1385 void moveFlow(TileID tileId, FlowKey key, bool priority = false);
1386 void moveUnit(TileID tileId, FlowKey key);
1387 void splitFlow(TileID tileId, FlowKey key, ArrayRef<FlowKey> partners);
1388
1389 DeviceOp device;
1390 const Routing &routing;
1391 const StreamConflicts &conflicts;
1392 const PinnedRoutes &pinned;
1393 // Whether prioritized flows not pinned are prioritized here.
1394 bool prioritize;
1395 bool routeCircuit;
1396 bool circuitSwitchHops;
1397 const AIETargetModel &targetModel;
1398 const int numArbiters;
1399 const int numMselsPerArbiter;
1400 const uint32_t maxPacketId;
1401 const int idBits;
1402 const int idMask;
1403
1404 // The streams each slave flow carries, for asking whether two flows can
1405 // deadlock on an arbiter. Flows with one source, destination and id may
1406 // state different masks, so carry different ids, and each is a stream of
1407 // its own.
1408 struct StreamEnds {
1409 PathEndPoint src, dst;
1410 int id;
1411 bool operator<(const StreamEnds &rhs) const {
1412 return std::tie(src, dst, id) < std::tie(rhs.src, rhs.dst, rhs.id);
1413 }
1414 };
1415 std::map<StreamEnds, SmallVector<size_t, 1>> packetStreamIndex;
1416 DenseMap<std::pair<PhysPort, int>, SmallVector<size_t, 2>> slaveFlowStreams;
1417 // Each source's part of a SlaveFlow (see SlaveFlow). A switchbox routes on
1418 // the id alone, so sources sharing an id there go everywhere any of them
1419 // does.
1420 std::map<std::pair<PhysPort, int>, std::map<PathEndPoint, std::set<Port>>>
1421 slaveFlowSources;
1422 // Every stream's hops, source first; the requested ones as this routing
1423 // lays them, the rest as the design already does.
1424 std::vector<SmallVector<StreamHop, 8>> routes;
1425 // Sources routed as circuits, which runOnFlow lowers.
1426 std::set<PathEndPoint> circuitSources;
1427 const SwitchSettings noSettings;
1428
1429 // The logical model of all the switchboxes.
1430 std::map<TileID, SmallVector<std::pair<Connect, int>, 8>> switchboxes;
1431 // <arbiter, msel> slots (per tile) that packet-switch configuration in the
1432 // input IR already occupies. Kept apart from masterAMSels so the allocator
1433 // run does not re-emit those ops.
1434 std::map<TileID, std::set<int>> reservedAmsels;
1435 // Each switchbox has its arbiters planned as a whole (see planArbiters).
1436 // Where that is impossible the routing is unusable, and the connections of
1437 // the flows in the way are what the router has to move.
1438 std::map<TileID, std::map<FlowKey, SlaveFlow>> tileSlaveFlows;
1439 std::map<TileID, SmallVector<SlaveFlow, 8>> tileFlows;
1440 // What the search below learns: flows to keep on different arbiters, and
1441 // flows to keep off an arbiter that flows already in the design take.
1442 std::set<std::tuple<TileID, FlowKey, FlowKey>> apart;
1443 std::set<std::tuple<TileID, FlowKey, int>> offArbiter;
1444 std::set<std::pair<TileID, Connect>> hazardConnections;
1445 std::set<std::pair<TileID, Connect>> priorityConnections;
1446 // The TreeSplits (see TreeSplit) the routing check asks for. `key` branches
1447 // from the flows of its source that tie it to one of `partners`, where
1448 // nothing else does. The next routing may reach the tile by another slave
1449 // port, so the split holds for the tile.
1450 std::set<TreeSplit> hazardSplits;
1451 std::set<std::pair<PathEndPoint, PathEndPoint>> hazardApart;
1452 std::set<std::pair<PathEndPoint, PathEndPoint>> hazardTogether;
1453 std::set<PathEndPoint> hazardUntrunk;
1454 std::set<TileID> crowdedTiles;
1455 std::optional<std::string> planFailure;
1456 std::optional<std::pair<Location, std::string>> incomplete;
1457};
1458} // namespace
1459
1460void PacketFlowRouting::collectFlows() {
1461 for (auto [i, s] : llvm::enumerate(conflicts.getStreams()))
1462 if (s.packetID)
1463 packetStreamIndex[{{s.src.tile, s.src.port},
1464 {s.dst.tile, s.dst.port},
1465 *s.packetID}]
1466 .push_back(i);
1467 for (const RoutedStream &s : conflicts.getStreams())
1468 routes.push_back(s.hops);
1469
1470 if (routeCircuit)
1471 for (FlowOp flow : device.getOps<FlowOp>())
1472 circuitSources.insert(sourceOf(flow));
1473
1474 for (PacketFlowOp pktFlowOp : device.getOps<PacketFlowOp>()) {
1475 Region &r = pktFlowOp.getPorts();
1476 Block &b = r.front();
1477 int flowID = pktFlowOp.IDInt();
1478 SmallVector<std::pair<TileID, Port>, 4> sources;
1479
1480 // Pass 1: collect all sources (order-independent; supports fan-in).
1481 for (Operation &Op : b.getOperations()) {
1482 if (auto pktSource = dyn_cast<PacketSourceOp>(Op)) {
1483 auto srcTile = cast<TileOp>(pktSource.getTile().getDefiningOp());
1484 sources.push_back(
1485 {{srcTile.colIndex(), srcTile.rowIndex()}, pktSource.port()});
1486 }
1487 }
1488 // Pass 2: lower each (source, destination) pair so fan-in flows lay
1489 // down switchbox connections for every source, not just the last one.
1490 for (Operation &Op : b.getOperations()) {
1491 auto pktDest = dyn_cast<PacketDestOp>(Op);
1492 if (!pktDest)
1493 continue;
1494 auto destTile = cast<TileOp>(pktDest.getTile().getDefiningOp());
1495 Port destPort = pktDest.port();
1496 TileID destCoords = {destTile.colIndex(), destTile.rowIndex()};
1497 // Assign "keep_pkt_header flag"
1498 auto keep = pktFlowOp.getKeepPktHeader();
1499 keepPktHeaderAttr[{destTile.getTileID(), destPort}] =
1500 keep ? BoolAttr::get(Op.getContext(), *keep) : nullptr;
1501
1502 for (auto &[srcCoords, srcPort] : sources) {
1503 TileID srcSB = {srcCoords.col, srcCoords.row};
1504 PathEndPoint srcPoint = {srcSB, srcPort};
1505 if (circuitSources.count(srcPoint))
1506 continue;
1507 const SwitchSettings &settings = settingsOf(srcPoint, flowID);
1508 // The requested streams this flow asks for here; with none, the
1509 // stream the design already routes.
1510 SmallVector<size_t, 1> flowStreams;
1511 if (auto stream = packetStreamIndex.find(
1512 {srcPoint, {destCoords, destPort}, flowID});
1513 stream != packetStreamIndex.end()) {
1514 int mask = static_cast<int>(pktFlowOp.getMask().value_or(~0));
1515 for (size_t i : stream->second)
1516 if (i < conflicts.getRequestedStreams().size() &&
1517 conflicts.getStreams()[i].packetMask == mask)
1518 flowStreams.push_back(i);
1519 if (flowStreams.empty())
1520 flowStreams.push_back(stream->second.front());
1521 }
1522 for (size_t stream : flowStreams) {
1523 SmallVector<StreamHop, 8> &hops = routes[stream];
1524 hops.clear();
1525 // A route may pass a switchbox more than once, but no connection.
1526 size_t connections = 0;
1527 for (const auto &[_, setting] : settings)
1528 connections += setting.srcs.size();
1529 std::optional<std::pair<TileID, Port>> at{{srcSB, srcPort}};
1530 while (at && hops.size() <= connections) {
1531 auto [tile, input] = *at;
1532 hops.push_back({tile, input, std::nullopt});
1533 at.reset();
1534 auto setting = settings.find(tile);
1535 if (setting == settings.end())
1536 break;
1537 for (auto [src, dest] :
1538 llvm::zip(setting->second.srcs, setting->second.dsts))
1539 if (src == input && !(tile == destCoords && dest == destPort) &&
1540 findPathToDest(settings, tile, dest, destCoords, destPort)) {
1541 at = linkedInput(tile, dest);
1542 break;
1543 }
1544 }
1545 }
1546 // Track whether the source's own switch connection survives routing.
1547 bool srcRouted = false;
1548 // add connections for all the Switchboxes in SwitchSettings
1549 for (const auto &[curr, setting] : settings) {
1550 assert(setting.srcs.size() == setting.dsts.size());
1551 TileID currTile = {curr.col, curr.row};
1552 for (size_t i = 0; i < setting.srcs.size(); i++) {
1553 Port src = setting.srcs[i];
1554 Port dest = setting.dsts[i];
1555 // reject false broadcast
1556 if (!findPathToDest(settings, currTile, dest, destCoords, destPort))
1557 continue;
1558 if (currTile == srcSB && src.bundle == srcPort.bundle &&
1559 src.channel == srcPort.channel)
1560 srcRouted = true;
1561 Connect connect = {{src.bundle, src.channel},
1562 {dest.bundle, dest.channel}};
1563 if (std::find(
1564 switchboxes[currTile].begin(), switchboxes[currTile].end(),
1565 std::pair{connect, flowID}) == switchboxes[currTile].end())
1566 switchboxes[currTile].push_back({connect, flowID});
1567 // Keyed by slave port as well as ID, since IDs are reused across
1568 // unrelated flows.
1569 PhysPort slavePort = {currTile, {src.bundle, src.channel}};
1570 std::pair<PhysPort, int> slaveFlow = {slavePort, flowID};
1571 for (size_t stream : flowStreams)
1572 if (!llvm::is_contained(slaveFlowStreams[slaveFlow], stream))
1573 slaveFlowStreams[slaveFlow].push_back(stream);
1574 slaveFlowSources[slaveFlow][srcPoint].insert(dest);
1575 if (!llvm::is_contained(slaveFlowLocs[slaveFlow],
1576 pktFlowOp.getLoc()))
1577 slaveFlowLocs[slaveFlow].push_back(pktFlowOp.getLoc());
1578 flowLocOrder.try_emplace(pktFlowOp.getLoc(), flowLocOrder.size());
1579 if (std::optional<uint8_t> mask = pktFlowOp.getMask()) {
1580 pinnedMasks[{{currTile, {src.bundle, src.channel}}, flowID}]
1581 .insert(*mask);
1582 }
1583 if (pktFlowOp.getPriorityRoute().value_or(false)) {
1584 markedFlows.insert(slaveFlow);
1585 if (prioritize || pinned.trees.count(srcPoint)) {
1586 prioritizedFlows.insert(slaveFlow);
1587 }
1588 }
1589 }
1590 }
1591 if (!srcRouted && !incomplete)
1592 incomplete = {
1593 pktFlowOp.getLoc(),
1594 "packet flow source " + describeTilePort(srcCoords, srcPort) +
1595 " could not be routed to destination " +
1596 describeTilePort(destCoords, destPort) +
1597 "; the pathfinder produced an incomplete routing for this "
1598 "placement."};
1599 }
1600 }
1601 }
1602}
1603
1604Location PacketPlan::locOf(ArrayRef<std::pair<PhysPort, int>> slaveFlows,
1605 Location fallback) const {
1606 SmallVector<Location, 2> locs;
1607 for (const auto &flow : slaveFlows)
1608 if (auto found = slaveFlowLocs.find(flow); found != slaveFlowLocs.end())
1609 for (Location loc : found->second)
1610 if (!llvm::is_contained(locs, loc))
1611 locs.push_back(loc);
1612 if (locs.empty())
1613 return fallback;
1614 llvm::sort(locs, [&](Location a, Location b) {
1615 return flowLocOrder.lookup(a) < flowLocOrder.lookup(b);
1616 });
1617 return FusedLoc::get(fallback.getContext(), locs);
1618}
1619
1620// The rules the flows through `slaveFlow` state. A full-width mask selects the
1621// id alone, which the cover states just as well, so only a wider claim becomes
1622// a rule of its own.
1623SmallVector<std::pair<int, int>, 2>
1624PacketPlan::statedCubes(const std::pair<PhysPort, int> &slaveFlow,
1625 int idMask) const {
1626 SmallVector<std::pair<int, int>, 2> cubes;
1627 auto it = pinnedMasks.find(slaveFlow);
1628 if (it != pinnedMasks.end())
1629 for (int mask : it->second)
1630 if (mask != idMask)
1631 cubes.push_back({mask, slaveFlow.second});
1632 return cubes;
1633}
1634
1635const SwitchSettings &PacketFlowRouting::settingsOf(const PathEndPoint &src,
1636 std::optional<int> id) {
1637 if (id)
1638 if (auto own = routing.idSettings.find({src, *id});
1639 own != routing.idSettings.end())
1640 return own->second;
1641 auto it = routing.settings.find(src);
1642 return it == routing.settings.end() ? noSettings : it->second;
1643}
1644
1645void PacketFlowRouting::findCircuitHops() {
1646 // Seed the reserved set from the packet-switch configuration the switchboxes
1647 // already carry, so this run allocates around it instead of over it.
1648 reservedAmsels = configuredAmsels(device);
1649
1650 // Ports the input's own aie.switchbox ops already drive, circuit or packet.
1651 std::set<PhysPort> claimedSlaves, claimedMasters;
1652 for (auto swbox : device.getOps<SwitchboxOp>()) {
1653 TileID tileId = swbox.getTileOp().getTileID();
1654 Region &ops = swbox.getConnections();
1655 for (auto connect : ops.getOps<ConnectOp>()) {
1656 claimedSlaves.insert({tileId, connect.sourcePort()});
1657 claimedMasters.insert({tileId, connect.destPort()});
1658 }
1659 for (auto rules : ops.getOps<PacketRulesOp>())
1660 claimedSlaves.insert({tileId, rules.sourcePort()});
1661 for (auto masterSet : ops.getOps<MasterSetOp>())
1662 claimedMasters.insert({tileId, masterSet.destPort()});
1663 }
1664 // Circuits not yet lowered claim their ports all the same.
1665 if (routeCircuit)
1666 for (FlowOp flow : device.getOps<FlowOp>())
1667 for (const auto &[tileId, setting] : settingsOf(sourceOf(flow))) {
1668 for (Port p : setting.srcs)
1669 claimedSlaves.insert({tileId, p});
1670 for (Port p : setting.dsts)
1671 claimedMasters.insert({tileId, p});
1672 }
1673 for (auto &[tileId, connects] : switchboxes) {
1674 if (!circuitSwitchHops ||
1675 targetModel.isShimNOCorPLTile(tileId.col, tileId.row))
1676 continue;
1677 std::set<Port> masters;
1678 for (const auto &[conn, flowID] : connects)
1679 masters.insert(conn.dst);
1680 SmallVector<Port, 8> slaves;
1681 for (const auto &[conn, flowID] : connects)
1682 if (!llvm::is_contained(slaves, conn.src))
1683 slaves.push_back(conn.src);
1684 const std::set<int> &reserved = reservedAmsels[tileId];
1685 size_t freeArbiters = llvm::count_if(llvm::seq(0, numArbiters), [&](int a) {
1686 return llvm::none_of(llvm::seq(0, numMselsPerArbiter), [&](int m) {
1687 return reserved.count(a + m * numArbiters);
1688 });
1689 });
1690 for (Port slave : slaves) {
1691 if (masters.size() <= freeArbiters)
1692 break;
1693 std::set<Port> reached;
1694 std::map<int, std::set<Port>> reachedByID;
1695 for (const auto &[conn, flowID] : connects)
1696 if (conn.src == slave) {
1697 reached.insert(conn.dst);
1698 reachedByID[flowID].insert(conn.dst);
1699 }
1700 bool exclusive =
1701 !claimedSlaves.count({tileId, slave}) &&
1702 llvm::all_of(reached,
1703 [&, tileId = tileId](Port m) {
1704 return isDirectional(m.bundle) &&
1705 !claimedMasters.count({tileId, m});
1706 }) &&
1707 llvm::all_of(
1708 reachedByID,
1709 [&](const auto &ids) { return ids.second == reached; }) &&
1710 llvm::all_of(connects, [&, tileId = tileId](const auto &entry) {
1711 const auto &[other, otherID] = entry;
1712 if (!reached.count(other.dst))
1713 return true;
1714 return other.src == slave &&
1715 !prioritizedFlows.contains({{tileId, slave}, otherID});
1716 });
1717 if (!exclusive)
1718 continue;
1719 for (Port m : reached) {
1720 circuitHops[tileId].push_back({slave, m});
1721 masters.erase(m);
1722 }
1723 llvm::erase_if(connects, [&](const auto &entry) {
1724 return entry.first.src == slave;
1725 });
1726 }
1727 }
1728}
1729
1730void PacketFlowRouting::collectSlaveFlows() {
1731 LLVM_DEBUG(llvm::dbgs() << "Check switchboxes\n");
1732
1733 for (const auto &[tileId, connects] : switchboxes) {
1734 LLVM_DEBUG(llvm::dbgs() << "***switchbox*** " << tileId.col << " "
1735 << tileId.row << '\n');
1736 for (const auto &[conn, flowID] : connects) {
1737 Port sourcePort = conn.src;
1738 Port destPort = conn.dst;
1739 auto sourceFlow =
1740 std::make_pair(std::make_pair(tileId, sourcePort), flowID);
1741 packetFlows[sourceFlow].push_back({tileId, destPort});
1742 if (markedFlows.contains(sourceFlow))
1743 priorityMasterPorts.insert({tileId, destPort});
1744 slavePorts.push_back(sourceFlow);
1745 LLVM_DEBUG(llvm::dbgs() << "flowID " << flowID << ':'
1746 << stringifyWireBundle(sourcePort.bundle) << " "
1747 << sourcePort.channel << " -> "
1748 << stringifyWireBundle(destPort.bundle) << " "
1749 << destPort.channel << "\n");
1750 }
1751 }
1752
1753 for (const auto &[flow, dests] : packetFlows) {
1754 SlaveFlow &f = tileSlaveFlows[flow.first.first]
1755 .try_emplace({flow.first.second, flow.second},
1756 SlaveFlow{flow.first.second,
1757 flow.second,
1758 {},
1759 prioritizedFlows.contains(flow)})
1760 .first->second;
1761 for (const PhysPort &dest : dests)
1762 if (!llvm::is_contained(f.masters, dest.second))
1763 f.masters.push_back(dest.second);
1764 llvm::sort(f.masters);
1765 }
1766 for (const auto &[tileId, byFlow] : tileSlaveFlows)
1767 for (const auto &[key, f] : byFlow)
1768 tileFlows[tileId].push_back(f);
1769}
1770
1771std::optional<std::pair<size_t, size_t>>
1772PacketFlowRouting::conflictingStreams(TileID tileId, const SlaveFlow &a,
1773 const SlaveFlow &b) {
1774 auto as = slaveFlowStreams.find({{tileId, a.slave}, a.id});
1775 auto bs = slaveFlowStreams.find({{tileId, b.slave}, b.id});
1776 if (as == slaveFlowStreams.end() || bs == slaveFlowStreams.end())
1777 return std::nullopt;
1778 for (size_t s : as->second)
1779 for (size_t t : bs->second)
1780 if (conflicts.conflict(s, t))
1781 return std::pair{s, t};
1782 return std::nullopt;
1783}
1784
1785std::optional<ArbiterPlan> PacketFlowRouting::planTile(
1786 TileID tileId, SmallVectorImpl<std::pair<size_t, size_t>> &blocking) {
1787 ArrayRef<SlaveFlow> flows = tileFlows.at(tileId);
1788 auto key = [&](size_t f) { return FlowKey{flows[f].slave, flows[f].id}; };
1789 auto conflict = [&](size_t a, size_t b) {
1790 return apart.count(
1791 {tileId, std::min(key(a), key(b)), std::max(key(a), key(b))}) ||
1792 conflictingStreams(tileId, flows[a], flows[b]);
1793 };
1794 auto excluded = [&](size_t f, int arbiter) {
1795 return offArbiter.count({tileId, key(f), arbiter}) > 0;
1796 };
1797 SmallVector<size_t, 8> priority, others;
1798 for (auto [i, f] : llvm::enumerate(flows))
1799 (f.isPriority ? priority : others).push_back(i);
1800 if (priority.empty() || (others.empty() && pinned.rules.empty()))
1801 return planArbiters(targetModel, flows, conflict, excluded,
1802 reservedAmsels[tileId], blocking);
1803
1804 auto subset = [&](ArrayRef<size_t> members) {
1805 SmallVector<SlaveFlow, 8> sub;
1806 for (size_t m : members)
1807 sub.push_back(flows[m]);
1808 return sub;
1809 };
1810 auto keepPinned = [&]() -> std::optional<ArbiterPlan> {
1811 ArbiterPlan kept;
1812 for (size_t f : priority) {
1813 Port slave = flows[f].slave;
1814 if (slave.bundle == WireBundle::DMA &&
1815 targetModel.isShimNOCTile(tileId.col, tileId.row))
1816 slave = {WireBundle::South, shimMuxChannelFrom(slave)};
1817 auto rules = pinned.rules.find({tileId, slave});
1818 const std::tuple<int, int, int> *rule = nullptr;
1819 if (rules != pinned.rules.end())
1820 rule = llvm::find_if(rules->second, [&](const auto &r) {
1821 auto [mask, value, amsel] = r;
1822 return (flows[f].id & mask) == (value & mask);
1823 });
1824 if (!rule || rule == rules->second.end())
1825 return std::nullopt;
1826 int amsel = std::get<2>(*rule);
1827 if (excluded(f, amsel % numArbiters) ||
1828 reservedAmsels[tileId].count(amsel) ||
1829 kept.amselMasters.try_emplace(amsel, flows[f].masters)
1830 .first->second != flows[f].masters)
1831 return std::nullopt;
1832 kept.slaveAmsels[key(f)] = amsel;
1833 }
1834 for (auto [k, a] : llvm::enumerate(priority))
1835 for (size_t b : llvm::drop_begin(priority, k + 1))
1836 if (kept.slaveAmsels.at(key(a)) % numArbiters ==
1837 kept.slaveAmsels.at(key(b)) % numArbiters &&
1838 flows[a].slave != flows[b].slave && conflict(a, b)) {
1839 return std::nullopt;
1840 }
1841 return kept;
1842 };
1843 SmallVector<std::pair<size_t, size_t>, 4> stuck;
1844 std::optional<ArbiterPlan> plan;
1845 if (!pinned.rules.empty())
1846 plan = keepPinned();
1847 if (!plan) {
1848 plan = planArbiters(
1849 targetModel, subset(priority),
1850 [&](size_t a, size_t b) { return conflict(priority[a], priority[b]); },
1851 [&](size_t f, int arbiter) { return excluded(priority[f], arbiter); },
1852 reservedAmsels[tileId], stuck);
1853 if (!plan) {
1854 for (auto [a, b] : stuck)
1855 blocking.push_back({priority[a], priority[b]});
1856 return std::nullopt;
1857 }
1858 }
1859 if (others.empty())
1860 return plan;
1861
1862 auto around = [&](ArbiterPlan plan, ArrayRef<size_t> planned,
1863 ArrayRef<size_t> pending,
1864 SmallVectorImpl<std::pair<size_t, size_t>> &blocking)
1865 -> std::optional<ArbiterPlan> {
1866 auto arbiterOf = [&](size_t f) {
1867 return plan.slaveAmsels.at(key(f)) % numArbiters;
1868 };
1869 SmallVector<size_t, 8> placed(planned), rest;
1870 for (size_t f : pending) {
1871 auto same = llvm::find_if(plan.amselMasters, [&](const auto &entry) {
1872 return entry.second == flows[f].masters;
1873 });
1874 if (same == plan.amselMasters.end()) {
1875 rest.push_back(f);
1876 continue;
1877 }
1878 for (size_t g : placed)
1879 if (arbiterOf(g) == same->first % numArbiters &&
1880 flows[g].slave != flows[f].slave && conflict(f, g))
1881 blocking.push_back({g, f});
1882 if (excluded(f, same->first % numArbiters))
1883 blocking.push_back({planned.front(), f});
1884 plan.slaveAmsels[key(f)] = same->first;
1885 placed.push_back(f);
1886 }
1887 if (!blocking.empty())
1888 return std::nullopt;
1889 if (rest.empty())
1890 return plan;
1891
1892 // A master port is tied to one arbiter, so a flow leaving by a master port
1893 // the plan has tied takes that arbiter.
1894 llvm::EquivalenceClasses<Port> tied = tiedMasters(flows);
1895 std::map<Port, std::set<int>> tiedArbiters;
1896 std::set<int> reserved = reservedAmsels[tileId];
1897 for (const auto &[amsel, masters] : plan.amselMasters) {
1898 reserved.insert(amsel);
1899 tiedArbiters[tied.getLeaderValue(masters.front())].insert(amsel %
1900 numArbiters);
1901 }
1902 SmallVector<std::pair<size_t, size_t>, 4> stuck;
1903 std::optional<ArbiterPlan> rested = planArbiters(
1904 targetModel, subset(rest),
1905 [&](size_t a, size_t b) { return conflict(rest[a], rest[b]); },
1906 [&](size_t f, int arbiter) {
1907 auto arbiters = tiedArbiters.find(
1908 tied.getLeaderValue(flows[rest[f]].masters.front()));
1909 return excluded(rest[f], arbiter) ||
1910 (arbiters != tiedArbiters.end() &&
1911 (arbiters->second.size() > 1 ||
1912 !arbiters->second.count(arbiter))) ||
1913 llvm::any_of(placed, [&](size_t g) {
1914 return arbiterOf(g) == arbiter &&
1915 flows[g].slave != flows[rest[f]].slave &&
1916 conflict(rest[f], g);
1917 });
1918 },
1919 reserved, stuck);
1920 if (!rested) {
1921 for (auto [a, b] : stuck)
1922 blocking.push_back({rest[a], rest[b]});
1923 for (size_t f : rest)
1924 for (size_t g : placed)
1925 if (flows[g].slave != flows[f].slave && conflict(f, g))
1926 blocking.push_back({g, f});
1927 return std::nullopt;
1928 }
1929 plan.slaveAmsels.insert(rested->slaveAmsels.begin(),
1930 rested->slaveAmsels.end());
1931 plan.amselMasters.insert(rested->amselMasters.begin(),
1932 rested->amselMasters.end());
1933 return plan;
1934 };
1935 SmallVector<std::pair<size_t, size_t>, 4> first;
1936 if (std::optional<ArbiterPlan> placed =
1937 around(*plan, priority, others, first))
1938 return placed;
1939
1940 llvm::EquivalenceClasses<Port> tied = tiedMasters(flows);
1941 SmallVector<size_t, 8> joint(priority), rest;
1942 for (size_t f : others) {
1943 bool tiedToPriority = llvm::any_of(priority, [&](size_t g) {
1944 return tied.isEquivalent(flows[g].masters.front(),
1945 flows[f].masters.front());
1946 });
1947 (tiedToPriority ? joint : rest).push_back(f);
1948 }
1949 stuck.clear();
1950 std::optional<ArbiterPlan> together;
1951 if (joint.size() > priority.size())
1952 together = planArbiters(
1953 targetModel, subset(joint),
1954 [&](size_t a, size_t b) { return conflict(joint[a], joint[b]); },
1955 [&](size_t f, int arbiter) { return excluded(joint[f], arbiter); },
1956 reservedAmsels[tileId], stuck);
1957 if (!together) {
1958 blocking.append(first.begin(), first.end());
1959 return std::nullopt;
1960 }
1961 return around(*together, joint, rest, blocking);
1962}
1963
1964void PacketFlowRouting::moveFlow(TileID tileId, FlowKey key, bool priority) {
1965 auto move = [&](const Connect &c) {
1966 hazardConnections.insert({tileId, c});
1967 if (priority)
1968 priorityConnections.insert({tileId, c});
1969 };
1970 auto flows = tileSlaveFlows.find(tileId);
1971 if (flows != tileSlaveFlows.end())
1972 if (auto f = flows->second.find(key); f != flows->second.end())
1973 for (Port m : f->second.masters)
1974 move({key.first, m});
1975 for (const Connect &hop : circuitHops[tileId])
1976 if (hop.src == key.first)
1977 move(hop);
1978}
1979
1980// Moves `key` with its whole unit (see planArbiters): a flow's arbiter is
1981// fixed by every flow reaching its master ports too.
1982void PacketFlowRouting::moveUnit(TileID tileId, FlowKey key) {
1983 moveFlow(tileId, key);
1984 auto flows = tileSlaveFlows.find(tileId);
1985 if (flows == tileSlaveFlows.end())
1986 return;
1987 auto self = flows->second.find(key);
1988 if (self == flows->second.end())
1989 return;
1990 llvm::EquivalenceClasses<Port> tied =
1991 tiedMasters(llvm::make_second_range(flows->second));
1992 for (const auto &[other, f] : flows->second)
1993 if (f.masters.size() > 1 &&
1994 tied.isEquivalent(f.masters.front(), self->second.masters.front()))
1995 moveFlow(tileId, other);
1996}
1997
1998void PacketFlowRouting::splitFlow(TileID tileId, FlowKey key,
1999 ArrayRef<FlowKey> partners) {
2000 auto flows = tileSlaveFlows.find(tileId);
2001 if (flows == tileSlaveFlows.end())
2002 return;
2003 auto self = flows->second.find(key);
2004 if (self == flows->second.end())
2005 return;
2006 auto onlySource = [&](FlowKey k) -> std::optional<PathEndPoint> {
2007 auto sources = slaveFlowSources.find({{tileId, k.first}, k.second});
2008 if (sources == slaveFlowSources.end() || sources->second.size() != 1)
2009 return std::nullopt;
2010 return sources->second.begin()->first;
2011 };
2012 std::optional<PathEndPoint> src = onlySource(key);
2013 if (!src)
2014 return;
2015 auto sharesMaster = [&](const auto &f, const std::set<Port> &ports) {
2016 return llvm::any_of(f.masters, [&](Port m) { return ports.count(m); });
2017 };
2018 std::set<Port> own(self->second.masters.begin(), self->second.masters.end());
2019 std::set<FlowKey> siblings;
2020 for (const auto &[other, f] : flows->second)
2021 if (other != key && other.first == key.first && sharesMaster(f, own) &&
2022 onlySource(other) == src)
2023 siblings.insert(other);
2024 // The masters tied to `partner`'s arbiter, not through `key`, nor through
2025 // its siblings if `skipSiblings`.
2026 auto tied = [&](FlowKey partner, bool skipSiblings) {
2027 llvm::EquivalenceClasses<Port> classes =
2028 tiedMasters(llvm::make_filter_range(
2029 llvm::make_second_range(flows->second), [&](const SlaveFlow &f) {
2030 FlowKey other{f.slave, f.id};
2031 return other != key && !(skipSiblings && siblings.count(other));
2032 }));
2033 auto unit = classes.members(flows->second.at(partner).masters.front());
2034 return std::set<Port>(unit.begin(), unit.end());
2035 };
2036 auto split = [&](FlowKey other,
2037 std::optional<PathEndPoint> apart = std::nullopt) {
2038 int a = key.second, b = other.second;
2039 if (!apart && a > b)
2040 std::swap(a, b);
2041 hazardSplits.insert({*src, tileId, a, b, apart});
2042 };
2043 for (FlowKey partner : partners) {
2044 if (partner == key || !flows->second.count(partner))
2045 continue;
2046 if (siblings.count(partner)) {
2047 split(partner);
2048 continue;
2049 }
2050 std::set<Port> partnerUnit = tied(partner, true);
2051 if (sharesMaster(self->second, partnerUnit))
2052 continue;
2053 // A sibling sharing a routing port with the partner's tree ties `key` to
2054 // its arbiter; leaving that port undoes the tie, so move off before split.
2055 bool unjoined = false;
2056 for (FlowKey other : siblings)
2057 for (Port m : flows->second.find(other)->second.masters)
2058 if (partnerUnit.count(m) && isDirectional(m.bundle)) {
2059 hazardConnections.insert({tileId, {other.first, m}});
2060 unjoined = true;
2061 }
2062 if (unjoined)
2063 continue;
2064 std::set<Port> unit = tied(partner, false);
2065 for (FlowKey other : siblings) {
2066 const SlaveFlow &f = flows->second.find(other)->second;
2067 if (!sharesMaster(f, unit))
2068 continue;
2069 // Where the sibling only ties `key` to the partner's arbiter by
2070 // destinations on this tile, those can reach it by a slave port of
2071 // their own, and `key` keeps sharing the rest of the tree.
2072 SmallVector<PathEndPoint, 2> apart;
2073 for (Port m : f.masters)
2074 if (partnerUnit.count(m))
2075 apart.push_back({tileId, m});
2076 if (PathEndPoint{tileId, key.first} == *src || apart.empty() ||
2077 !llvm::all_of(apart, [&](const PathEndPoint &p) {
2078 return packetStreamIndex.count({*src, p, other.second});
2079 })) {
2080 split(other);
2081 continue;
2082 }
2083 for (const PathEndPoint &p : apart)
2084 split(other, p);
2085 }
2086 }
2087}
2088
2089// Plans the arbiters of every switchbox on the routing. Where that fails, the
2090// RoutingFailure names the connections in the way.
2091llvm::Expected<PacketPlan> PacketFlowRouting::plan() {
2092 collectFlows();
2093 findCircuitHops();
2094 collectSlaveFlows();
2095 checkRules();
2096 planTiles();
2097 if (!planFailure)
2098 planFailure = breakHoldCycles();
2099 if (planFailure)
2100 return llvm::make_error<RoutingFailure>(std::move(*planFailure), faults());
2101 return PacketPlan(std::move(*this));
2102}
2103
2104RoutingFaults PacketFlowRouting::faults() const {
2105 RoutingFaults faults;
2106 faults.connections.assign(hazardConnections.begin(), hazardConnections.end());
2107 faults.splits.assign(hazardSplits.begin(), hazardSplits.end());
2108 faults.crowded.assign(crowdedTiles.begin(), crowdedTiles.end());
2109 faults.apart.assign(hazardApart.begin(), hazardApart.end());
2110 faults.together.assign(hazardTogether.begin(), hazardTogether.end());
2111 faults.untrunk.assign(hazardUntrunk.begin(), hazardUntrunk.end());
2112 faults.onlyPriorityMasters = !priorityConnections.empty() &&
2113 hazardSplits.empty() && crowdedTiles.empty() &&
2114 hazardApart.empty() && hazardUntrunk.empty() &&
2115 hazardConnections == priorityConnections;
2116 return faults;
2117}
2118
2119void PacketFlowRouting::checkRules() {
2120 // A slave port holds a few packet rules, and how many its flows need depends
2121 // on which ids leave by the same master ports.
2122 std::map<PhysPort, SmallVector<std::pair<int, int>>> existingCubes;
2123 for (auto swbox : device.getOps<SwitchboxOp>())
2124 for (auto rules : swbox.getConnections().getOps<PacketRulesOp>())
2125 for (auto rule : rules.getRules().getOps<PacketRuleOp>())
2126 existingCubes[{swbox.getTileOp().getTileID(), rules.sourcePort()}]
2127 .push_back({rule.maskInt(), rule.valueInt()});
2128 struct RuleGroup {
2129 SmallVector<std::pair<int, int>, 4> stated;
2130 SmallVector<int, 4> derived;
2131 };
2132 for (const auto &[slaveFlow, sources] : slaveFlowSources) {
2133 const auto &[first, firstMasters] = *sources.begin();
2134 auto other = llvm::find_if(
2135 sources, [&, &firstMasters = firstMasters](const auto &source) {
2136 return source.second != firstMasters;
2137 });
2138 if (other == sources.end())
2139 continue;
2140 const auto &[slavePort, id] = slaveFlow;
2141 TileID tileId = slavePort.first;
2142 moveFlow(tileId, {slavePort.second, id});
2143 if (planFailure)
2144 continue;
2145 const PathEndPoint &second = other->first;
2146 planFailure = llvm::formatv(
2147 "at tile ({0}, {1}), packets with id {2} from {3} and {4} enter on {5} "
2148 "and leave by different ports; a switchbox routes on the id alone, so "
2149 "each source's packets would also go where the other's do.",
2150 tileId.col, tileId.row, id, describeTilePort(first.coords, first.port),
2151 describeTilePort(second.coords, second.port),
2152 describePort(slavePort.second));
2153 }
2154 for (const auto &[tileId, byFlow] : tileSlaveFlows) {
2155 auto newRules =
2156 [&, tileId = tileId, &byFlow = byFlow](
2157 Port slave, std::optional<std::pair<PathEndPoint, Port>> without,
2158 bool priorityOnly,
2159 SmallVector<SmallVector<Port, 4>> *groupMasters = nullptr) {
2160 std::map<std::pair<bool, SmallVector<Port, 4>>, RuleGroup> groups;
2161 for (const auto &[key, f] : byFlow) {
2162 if (f.slave != slave || (priorityOnly && !f.isPriority))
2163 continue;
2164 SmallVector<Port, 4> masters = f.masters;
2165 auto sources = slaveFlowSources.find({{tileId, slave}, f.id});
2166 if (without && sources != slaveFlowSources.end()) {
2167 std::set<Port> kept;
2168 for (const auto &[src, ports] : sources->second)
2169 for (Port m : ports)
2170 if (!(src == without->first) || m != without->second)
2171 kept.insert(m);
2172 masters.assign(kept.begin(), kept.end());
2173 }
2174 if (masters.empty())
2175 continue;
2176 RuleGroup &group = groups[{f.isPriority, masters}];
2177 auto cubes = statedCubes({{tileId, f.slave}, f.id}, idMask);
2178 if (cubes.empty())
2179 group.derived.push_back(f.id);
2180 for (auto cube : cubes)
2181 if (!llvm::is_contained(group.stated, cube))
2182 group.stated.push_back(cube);
2183 }
2184 SmallVector<GroupClaims> claims;
2185 for (const auto &[masters, group] : groups) {
2186 claims.push_back({group.stated, group.derived, masters.first});
2187 if (groupMasters)
2188 groupMasters->push_back(masters.second);
2189 }
2190 return slavePortRules(claims, existingCubes[{tileId, slave}], idBits,
2191 targetModel.getNumSlaveSlots());
2192 };
2193 auto rulesNeeded =
2194 [&, tileId = tileId](
2195 Port slave,
2196 std::optional<std::pair<PathEndPoint, Port>> without = {}) {
2197 return existingCubes[{tileId, slave}].size() +
2198 newRules(slave, without, false).size();
2199 };
2200 // Packets for a destination on the tile that reach it by a slave port of
2201 // their own take their rules with them. The source's tree splits apart the
2202 // destination that frees the most rules on `slave`, if one frees any.
2203 auto splitRules = [&, tileId = tileId, &byFlow = byFlow](Port slave) {
2204 std::map<PathEndPoint, std::map<PathEndPoint, std::set<int>>> dstIds;
2205 for (const auto &[key, f] : byFlow) {
2206 if (f.slave != slave)
2207 continue;
2208 auto streams = slaveFlowStreams.find({{tileId, slave}, f.id});
2209 if (streams == slaveFlowStreams.end())
2210 continue;
2211 for (size_t i : streams->second) {
2212 const RoutedStream &s = conflicts.getStreams()[i];
2213 dstIds[{s.src.tile, s.src.port}][{s.dst.tile, s.dst.port}].insert(
2214 f.id);
2215 }
2216 }
2217 std::optional<std::pair<PathEndPoint, PathEndPoint>> best;
2218 size_t fewest = rulesNeeded(slave);
2219 for (const auto &[src, dsts] : dstIds)
2220 for (const auto &[dst, ids] : dsts)
2221 if (dst.coords == tileId && dsts.size() > 1)
2222 if (size_t n = rulesNeeded(slave, {{src, dst.port}}); n < fewest) {
2223 fewest = n;
2224 best = {src, dst};
2225 }
2226 if (!best)
2227 return false;
2228 const auto &[src, apart] = *best;
2229 const std::set<int> &apartIds = dstIds[src][apart];
2230 for (const auto &[dst, ids] : dstIds[src]) {
2231 auto carries = [&, &src = src, &dst = dst](int id) {
2232 return packetStreamIndex.count({src, dst, id}) > 0;
2233 };
2234 auto b = llvm::find_if_not(apartIds, carries);
2235 if (!(dst == apart) && b != apartIds.end())
2236 hazardSplits.insert({src, tileId, *ids.begin(), *b, apart});
2237 }
2238 return true;
2239 };
2240 // Flows on one slave port that leave by different master ports must not
2241 // claim a common id, and a mask written on an aie.packet_flow claims every
2242 // id it matches.
2243 auto claim = [&, tileId = tileId](const SlaveFlow &f) {
2244 auto cubes = statedCubes({{tileId, f.slave}, f.id}, idMask);
2245 if (cubes.empty())
2246 cubes.push_back({idMask, f.id});
2247 return cubes;
2248 };
2249 for (auto a = byFlow.begin(); a != byFlow.end(); ++a)
2250 for (auto b = std::next(a); b != byFlow.end(); ++b) {
2251 const SlaveFlow &fa = a->second, &fb = b->second;
2252 if (fa.slave != fb.slave || fa.masters == fb.masters)
2253 continue;
2254 std::optional<std::pair<std::pair<int, int>, std::pair<int, int>>>
2255 overlap;
2256 for (auto own : claim(fa))
2257 for (auto other : claim(fb))
2258 if (!overlap && cubesIntersect(own, other))
2259 overlap = {own, other};
2260 if (!overlap)
2261 continue;
2262 auto [own, other] = *overlap;
2263 moveFlow(tileId, a->first);
2264 moveFlow(tileId, b->first);
2265 if (planFailure)
2266 continue;
2267 int witness = (own.second & own.first) |
2268 (other.second & other.first & ~own.first);
2269 planFailure = llvm::formatv(
2270 "at tile ({0}, {1}), packet flows through {2} claim rule (mask "
2271 "0x{3:X-}, id 0x{4:X-}) and rule (mask 0x{5:X-}, id 0x{6:X-}), "
2272 "which both match id 0x{7:X-}; widen one mask to carry both, or "
2273 "route them apart.",
2274 tileId.col, tileId.row, describePort(fa.slave), own.first,
2275 own.second, other.first, other.second, witness);
2276 }
2277 std::set<Port> slaves;
2278 for (const auto &[key, f] : byFlow)
2279 slaves.insert(f.slave);
2280 std::set<Port> priorityMasters;
2281 std::set<SmallVector<Port, 4>> prioritySets;
2282 for (const auto &[key, f] : byFlow)
2283 if (f.isPriority && !pinned.rules.empty()) {
2284 priorityMasters.insert(f.masters.begin(), f.masters.end());
2285 prioritySets.insert(f.masters);
2286 }
2287 for (const auto &[key, f] : byFlow) {
2288 const Port *shared = llvm::find_if(
2289 f.masters, [&](Port m) { return priorityMasters.count(m); });
2290 if (f.isPriority || shared == f.masters.end() ||
2291 prioritySets.count(f.masters))
2292 continue;
2293 moveFlow(tileId, key, /*priority=*/true);
2294 if (!planFailure)
2295 planFailure = llvm::formatv(
2296 "at tile ({0}, {1}), packets with id {2} entering on {3} leave by "
2297 "{4}, a master port of the prioritized flows, but not by one of "
2298 "their fixed master sets.",
2299 tileId.col, tileId.row, f.id, describePort(f.slave),
2300 describePort(*shared));
2301 }
2302 for (Port slave : pinned.rules.empty() ? std::set<Port>{} : slaves) {
2303 SmallVector<SmallVector<Port, 4>> ruleMasters;
2304 SmallVector<PortRule> priority = newRules(slave, {}, true, &ruleMasters);
2305 for (const auto &[key, f] : byFlow) {
2306 if (f.slave != slave || f.isPriority)
2307 continue;
2308 for (auto own : claim(f)) {
2309 const auto *rule =
2310 llvm::find_if(priority, [&, &f = f](const PortRule &r) {
2311 return cubesIntersect(own, {r.mask, r.value}) &&
2312 ruleMasters[r.group] != f.masters;
2313 });
2314 if (rule == priority.end())
2315 continue;
2316 moveFlow(tileId, key);
2317 if (!planFailure)
2318 planFailure = llvm::formatv(
2319 "at tile ({0}, {1}), the packet rule (mask 0x{2:X-}, id "
2320 "0x{3:X-}) of the prioritized flows on "
2321 "{4} also matches packet id 0x{5:X-}, and priority routing "
2322 "keeps their rules; use an id the rule does not match, "
2323 "or route the flow apart.",
2324 tileId.col, tileId.row, rule->mask, rule->value,
2325 describePort(slave), f.id);
2326 break;
2327 }
2328 }
2329 }
2330 for (Port slave : slaves) {
2331 size_t needed = rulesNeeded(slave);
2332 if (needed <= targetModel.getNumSlaveSlots())
2333 continue;
2334 for (const auto &[key, f] : byFlow)
2335 if (f.slave == slave)
2336 moveFlow(tileId, key);
2337 if (!splitRules(slave))
2338 crowdedTiles.insert(tileId);
2339 if (planFailure)
2340 continue;
2341 planFailure = llvm::formatv(
2342 "at tile ({0}, {1}), the packet flows entering on {2} need {3} "
2343 "packet rules, and a slave port holds {4}.",
2344 tileId.col, tileId.row, describePort(slave), needed,
2345 targetModel.getNumSlaveSlots());
2346 }
2347 }
2348}
2349
2350void PacketFlowRouting::planTiles() {
2351 for (const auto &[tileId, flows] : tileFlows) {
2352 SmallVector<std::pair<size_t, size_t>, 4> blocking;
2353 if (std::optional<ArbiterPlan> plan = planTile(tileId, blocking)) {
2354 plans[tileId] = std::move(*plan);
2355 continue;
2356 }
2357 LLVM_DEBUG({
2358 llvm::dbgs() << "No arbiter plan at tile (" << tileId.col << ", "
2359 << tileId.row << "):\n";
2360 for (const SlaveFlow &f : flows) {
2361 llvm::dbgs() << " " << describePort(f.slave) << " id " << f.id
2362 << " ->";
2363 for (Port m : f.masters)
2364 llvm::dbgs() << ' ' << describePort(m);
2365 llvm::dbgs() << '\n';
2366 }
2367 for (auto [a, b] : blocking)
2368 llvm::dbgs() << " blocking " << a << ' ' << b << '\n';
2369 });
2370 std::string reason;
2371 llvm::raw_string_ostream os(reason);
2372 if (blocking.empty()) {
2373 // Ids a source sends out by master sets that overlap tie those sets to
2374 // one arbiter, so its tree branches them apart to free msels.
2375 for (const SlaveFlow &f : flows)
2376 for (const SlaveFlow &g : flows)
2377 if (f.slave == g.slave && f.id < g.id && f.masters != g.masters &&
2378 llvm::any_of(
2379 f.masters,
2380 [&](Port m) { return llvm::is_contained(g.masters, m); })) {
2381 FlowKey keys[] = {{f.slave, f.id}, {g.slave, g.id}};
2382 splitFlow(tileId, keys[0], keys);
2383 }
2384 for (const SlaveFlow &f : flows)
2385 moveFlow(tileId, {f.slave, f.id});
2386 crowdedTiles.insert(tileId);
2387 if (planFailure)
2388 continue;
2389 os << "at tile (" << tileId.col << ", " << tileId.row
2390 << "), the packet flows need more arbiter msels than the switchbox "
2391 "has free.";
2392 planFailure = std::move(reason);
2393 continue;
2394 }
2395 for (auto [a, b] : blocking) {
2396 FlowKey keys[] = {{flows[a].slave, flows[a].id},
2397 {flows[b].slave, flows[b].id}};
2398 for (FlowKey key : keys) {
2399 moveUnit(tileId, key);
2400 splitFlow(tileId, key, keys);
2401 }
2402 }
2403 if (planFailure)
2404 continue;
2405 std::optional<std::pair<size_t, size_t>> pair = conflictingStreams(
2406 tileId, flows[blocking.front().first], flows[blocking.front().second]);
2407 assert(pair && apart.empty() && offArbiter.empty() &&
2408 "before the hold-cycle search, only stream conflicts block a plan");
2409 auto [s, t] = *pair;
2410 os << describeStream(conflicts.getStreams()[s]) << " and "
2411 << describeStream(conflicts.getStreams()[t])
2412 << " can deadlock if they share an arbiter, and no routing found keeps "
2413 "them apart (last tried: tile ("
2414 << tileId.col << ", " << tileId.row << ")). " << conflicts.explain(s, t);
2415 planFailure = std::move(reason);
2416 }
2417}
2418
2419std::optional<std::string> PacketFlowRouting::breakHoldCycles() {
2420 // A packet holds every arbiter it has taken until its tail passes, so waits
2421 // chain from switchbox to switchbox, and planning each alone can close a
2422 // cycle of them. Breaking any one shared arbiter of such a cycle breaks it,
2423 // so the search tries each in turn, up to a budget.
2424 auto arbitrate = [&]() {
2425 for (size_t s = 0; s < conflicts.getRequestedStreams().size(); s++) {
2426 if (!conflicts.getStreams()[s].packetID)
2427 continue;
2428 for (StreamHop &hop : routes[s]) {
2429 hop.arbiter.reset();
2430 auto plan = plans.find(hop.tile);
2431 if (plan == plans.end())
2432 continue;
2433 auto amsel = plan->second.slaveAmsels.find(
2434 {hop.input, *conflicts.getStreams()[s].packetID});
2435 if (amsel != plan->second.slaveAmsels.end())
2436 hop.arbiter = amsel->second % numArbiters;
2437 }
2438 }
2439 };
2440 ArrayRef<RoutedStream> streams = conflicts.getStreams();
2441 size_t numRequested = conflicts.getRequestedStreams().size();
2442 // Every cycle the search meets: the routing moves the flows of each, as the
2443 // first may run only through flows it cannot move.
2444 SmallVector<HoldCycle, 2> cycles;
2445 int replans = 0;
2446 bool forcedWaits = false, definite = false;
2447 auto search = [&](auto &self) -> bool {
2448 arbitrate();
2449 std::optional<HoldCycle> cycle =
2450 conflicts.holdCycle(routes, forcedWaits, definite);
2451 // Waits `definite` drops still count where no receiver is shared, so a
2452 // replan must not close a cycle the first search broke.
2453 if (!cycle && definite)
2454 cycle = conflicts.holdCycle(routes);
2455 if (!cycle)
2456 return true;
2457 cycles.push_back(*cycle);
2458 LLVM_DEBUG(llvm::dbgs()
2459 << "Hold cycle: " << conflicts.explain(*cycle) << '\n');
2460 for (const HoldCycle::Step &step : cycle->steps) {
2461 if (step.wait != HoldCycle::Wait::Arbiter || step.forced)
2462 continue;
2463 FlowKey sharer{step.sharerInput, *streams[step.sharer].packetID};
2464 FlowKey holder{step.holderInput, *streams[step.holding].packetID};
2465 bool sharerNew = step.sharer < numRequested;
2466 bool holderNew = step.holding < numRequested;
2467 std::optional<std::tuple<TileID, FlowKey, FlowKey>> pair;
2468 std::optional<std::tuple<TileID, FlowKey, int>> off;
2469 if (sharerNew && holderNew)
2470 pair = {step.tile, std::min(sharer, holder), std::max(sharer, holder)};
2471 else if (sharerNew || holderNew)
2472 off = {step.tile, sharerNew ? sharer : holder, step.arbiter};
2473 else
2474 continue;
2475 if (replans == maxHoldCycleReplans)
2476 return false;
2477 if ((pair && !apart.insert(*pair).second) ||
2478 (off && !offArbiter.insert(*off).second))
2479 continue;
2480 replans++;
2481 ArbiterPlan saved = plans.at(step.tile);
2482 SmallVector<std::pair<size_t, size_t>, 4> blocking;
2483 if (std::optional<ArbiterPlan> plan = planTile(step.tile, blocking)) {
2484 plans[step.tile] = std::move(*plan);
2485 if (self(self))
2486 return true;
2487 plans[step.tile] = std::move(saved);
2488 }
2489 if (pair)
2490 apart.erase(*pair);
2491 if (off)
2492 offArbiter.erase(*off);
2493 }
2494 return false;
2495 };
2496 // The routing moves the flows of each cycle the search met, but for those
2497 // waiting at a receiver they share.
2498 auto move = [&] {
2499 arbitrate();
2500 for (const HoldCycle &cycle : cycles)
2501 for (const HoldCycle::Step &step : cycle.steps) {
2502 if (step.wait == HoldCycle::Wait::Arbiter && step.forced &&
2503 step.sharer < numRequested && step.holding < numRequested &&
2504 streams[step.sharer].packetID && streams[step.holding].packetID) {
2505 PathEndPoint a{streams[step.sharer].src.tile,
2506 streams[step.sharer].src.port},
2507 b{streams[step.holding].src.tile, streams[step.holding].src.port};
2508 if (!(a == b))
2509 hazardTogether.insert(std::minmax(a, b));
2510 }
2511 if (step.wait != HoldCycle::Wait::Drain && !step.forced) {
2512 SmallVector<FlowKey, 3> keys;
2513 for (auto [s, input] : {std::pair{step.waiting, step.sharerInput},
2514 std::pair{step.sharer, step.sharerInput},
2515 std::pair{step.holding, step.holderInput}})
2516 if (std::optional<int> id = streams[s].packetID;
2517 s < numRequested && id)
2518 keys.push_back({input, *id});
2519 for (FlowKey key : keys) {
2520 moveUnit(step.tile, key);
2521 splitFlow(step.tile, key, keys);
2522 }
2523 // On a link, `waiting` queues behind `holding` itself.
2524 size_t behind =
2525 step.wait == HoldCycle::Wait::Link ? step.waiting : step.sharer;
2526 if (behind >= numRequested || step.holding >= numRequested ||
2527 !streams[behind].packetID || !streams[step.holding].packetID)
2528 continue;
2529 PathEndPoint a{streams[behind].src.tile, streams[behind].src.port},
2530 b{streams[step.holding].src.tile, streams[step.holding].src.port};
2531 // holdCycle relates one source's trees only to themselves.
2532 if (!(a == b))
2533 hazardApart.insert(std::minmax(a, b));
2534 else if (step.wait == HoldCycle::Wait::Arbiter)
2535 hazardUntrunk.insert(a);
2536 }
2537 }
2538 };
2539 if (!search(search)) {
2540 move();
2541 return "packet flows can deadlock holding arbiters across "
2542 "switchboxes, and no arbiter assignment found avoids it. " +
2543 conflicts.explain(cycles.front());
2544 }
2545 // Trees into a receiver they share wait on each other at its master port
2546 // whatever the plan, so a cycle through such waits can only be broken
2547 // elsewhere along it. Where none of it can, the routing fails, unless some
2548 // plan has such cycles only by assumption.
2549 cycles.clear();
2550 replans = 0;
2551 forcedWaits = true;
2552 if (!search(search)) {
2553 LLVM_DEBUG(llvm::dbgs() << "Hold cycle at a shared receiver: "
2554 << conflicts.explain(cycles.front()) << '\n');
2555 cycles.clear();
2556 replans = 0;
2557 definite = true;
2558 if (!search(search)) {
2559 move();
2560 sharedReceiverCycle = cycles.front();
2561 }
2562 }
2563 return std::nullopt;
2564}
2565
2566LogicalResult PacketPlan::emit(DeviceOp device, OpBuilder &builder,
2567 DynamicTileAnalysis &analyzer) const {
2568 const AIETargetModel &targetModel = device.getTargetModel();
2569 const int numArbiters = targetModel.getNumArbiters();
2570 const int numMselsPerArbiter = targetModel.getNumMselsPerArbiter();
2571 const uint32_t maxPacketId = targetModel.getMaxPacketId();
2572 const int idBits = llvm::Log2_32_Ceil(maxPacketId + 1);
2573 const int idMask = (1 << idBits) - 1;
2574 AIEDialect::PriorityRouteAttrHelper priority(builder.getContext());
2575 UnitAttr unit = builder.getUnitAttr();
2576
2577 // A master port can only be associated with one arbiter, and each arbiter
2578 // has numMselsPerArbiter msels, so a tile has numArbiters x
2579 // numMselsPerArbiter "logical" arbiters.
2580 //
2581 // A map from Tile and master selectValue to the ports targetted by that
2582 // master select.
2583 std::map<std::pair<TileID, int>, SmallVector<Port, 4>> masterAMSels;
2584 DenseMap<std::pair<PhysPort, int>, int> slaveAMSels;
2585 for (const auto &[tileId, plan] : plans) {
2586 for (const auto &[amsel, masters] : plan.amselMasters)
2587 masterAMSels[{tileId, amsel}].append(masters.begin(), masters.end());
2588 for (const auto &[flow, amsel] : plan.slaveAmsels)
2589 slaveAMSels[{{tileId, flow.first}, flow.second}] = amsel;
2590 }
2591
2592 // Compute the master set IDs
2593 // A map from a switchbox output port to its associated amsel values
2594 std::map<PhysPort, SmallVector<int, 4>> mastersets;
2595 for (const auto &[tileAmsel, ports] : masterAMSels)
2596 for (Port port : ports)
2597 mastersets[{tileAmsel.first, port}].push_back(tileAmsel.second);
2598
2599 LLVM_DEBUG({
2600 llvm::dbgs() << "CHECK mastersets\n";
2601 for (const auto &[master, values] : mastersets) {
2602 llvm::dbgs() << "master " << master.first << " "
2603 << stringifyWireBundle(master.second.bundle) << " : "
2604 << master.second.channel << '\n';
2605 for (int value : values)
2606 llvm::dbgs() << "amsel: " << value << '\n';
2607 }
2608 });
2609
2610 // Prioritized flows group apart from the others; the ids of a group share its
2611 // packet rules.
2612 SmallVector<SmallVector<std::pair<PhysPort, int>, 4>, 4> slaveGroups;
2613 SmallVector<std::pair<PhysPort, int>, 4> workList(slavePorts);
2614 while (!workList.empty()) {
2615 std::pair<PhysPort, int> slave = workList.pop_back_val();
2616 const SmallVector<PhysPort, 4> &dests = packetFlows.at(slave);
2617 auto *group = llvm::find_if(slaveGroups, [&](const auto &candidate) {
2618 const SmallVector<PhysPort, 4> &groupDests =
2619 packetFlows.at(candidate.front());
2620 return candidate.front().first.second == slave.first.second &&
2621 prioritizedFlows.contains(candidate.front()) ==
2622 prioritizedFlows.contains(slave) &&
2623 groupDests.size() == dests.size() &&
2624 llvm::all_of(dests, [&](const PhysPort &dest) {
2625 return llvm::is_contained(groupDests, dest);
2626 });
2627 });
2628 if (group != slaveGroups.end())
2629 group->push_back(slave);
2630 else
2631 slaveGroups.push_back({slave});
2632 }
2633
2634 // What each group claims on its slave port, as cubes, split into the rules
2635 // its flows state and the ids left for the cover to describe.
2636 SmallVector<SmallVector<std::pair<int, int>, 4>, 4> statedRules(
2637 slaveGroups.size());
2638 SmallVector<SmallVector<int, 4>, 4> derivedIds(slaveGroups.size());
2639 for (size_t gi = 0; gi < slaveGroups.size(); ++gi) {
2640 for (auto member : slaveGroups[gi]) {
2641 auto cubes = statedCubes(member, idMask);
2642 if (cubes.empty()) {
2643 derivedIds[gi].push_back(member.second);
2644 continue;
2645 }
2646 for (auto cube : cubes)
2647 if (!llvm::is_contained(statedRules[gi], cube))
2648 statedRules[gi].push_back(cube);
2649 }
2650 }
2651
2652 // Everything a group claims, for the other groups on its port to avoid.
2653 [[maybe_unused]] auto claimsOf = [&](size_t gi) {
2654 SmallVector<std::pair<int, int>, 8> claims(statedRules[gi].begin(),
2655 statedRules[gi].end());
2656 for (int id : derivedIds[gi])
2657 claims.push_back({idMask, id});
2658 return claims;
2659 };
2660
2661 // Realize the routes in MLIR
2662
2663 // Update tiles map if any new tile op declaration is needed for constructing
2664 // the flow.
2665 std::map<TileID, Operation *> tiles;
2666 for (auto tileOp : device.getOps<TileOp>())
2667 tiles[tileOp.getTileID()] = tileOp;
2668 for (const PhysPort &master : llvm::make_first_range(mastersets))
2669 tiles.try_emplace(master.first, analyzer.getTile(builder, master.first));
2670
2671 for (Operation *tileOp : llvm::make_second_range(tiles)) {
2672 TileOp tile = cast<TileOp>(tileOp);
2673 TileID tileId = tile.getTileID();
2674 Location tileLoc = tile.getLoc();
2675
2676 // Create a switchbox for the routes and insert inside it.
2677 builder.setInsertionPointAfter(tileOp);
2678 SwitchboxOp swbox =
2679 analyzer.getSwitchbox(builder, tile.colIndex(), tile.rowIndex());
2680 SwitchboxOp::ensureTerminator(swbox.getConnections(), builder, tileLoc);
2681 Block &b = swbox.getConnections().front();
2682 builder.setInsertionPoint(b.getTerminator());
2683
2684 if (auto hops = circuitHops.find(tileId); hops != circuitHops.end())
2685 for (const Connect &hop : hops->second) {
2686 SmallVector<std::pair<PhysPort, int>, 4> passing;
2687 for (const auto &flow : llvm::make_first_range(slaveFlowLocs))
2688 if (flow.first == PhysPort{tileId, hop.src})
2689 passing.push_back(flow);
2690 ConnectOp::create(builder, locOf(passing, tileLoc), hop.src.bundle,
2691 hop.src.channel, hop.dst.bundle, hop.dst.channel);
2692 }
2693
2694 // The master sets of this tile, in port order, and the amsels they take.
2695 auto tileMastersets =
2696 llvm::make_filter_range(mastersets, [&](const auto &entry) {
2697 return entry.first.first == tileId;
2698 });
2699 llvm::SmallBitVector amselNeeded(numMselsPerArbiter * numArbiters);
2700 for (const auto &[master, amsels] : tileMastersets)
2701 for (int amsel : amsels)
2702 amselNeeded.set(amsel);
2703 std::map<int, AMSelOp> amselOps;
2704 for (int msel = 0; msel < numMselsPerArbiter; msel++)
2705 for (int arbiter = 0; arbiter < numArbiters; arbiter++)
2706 if (int amsel = arbiter + msel * numArbiters; amselNeeded.test(amsel))
2707 amselOps[amsel] = AMSelOp::create(builder, tileLoc, arbiter, msel);
2708 for (const auto &[master, msels] : tileMastersets) {
2709 SmallVector<Value, 4> amsels;
2710 for (int msel : msels)
2711 amsels.push_back(amselOps.at(msel));
2712 SmallVector<std::pair<PhysPort, int>, 4> feeding;
2713 for (const auto &[flow, dests] : packetFlows)
2714 if (llvm::is_contained(dests, master))
2715 feeding.push_back(flow);
2716 auto msOp = MasterSetOp::create(
2717 builder, locOf(feeding, tileLoc), builder.getIndexType(),
2718 master.second.bundle, master.second.channel, amsels,
2719 keepPktHeaderAttr.lookup(master));
2720 if (priorityMasterPorts.contains(master))
2721 priority.setAttr(msOp, unit);
2722 }
2723
2724 // Generate the packet rules, adding to any the switchbox already has.
2725 DenseMap<Port, PacketRulesOp> slaveRules;
2726 DenseMap<Port, SmallVector<PacketRuleOp>> existingRules;
2727 struct PortPlan {
2728 SmallVector<size_t, 4> groups;
2729 SmallVector<PortRule> rules;
2730 size_t emitted = 0;
2731 };
2732 std::map<Port, PortPlan> portPlans;
2733 for (auto rulesOp : b.getOps<PacketRulesOp>()) {
2734 slaveRules[rulesOp.sourcePort()] = rulesOp;
2735 llvm::append_range(existingRules[rulesOp.sourcePort()],
2736 rulesOp.getRules().getOps<PacketRuleOp>());
2737 }
2738 for (size_t gi = 0; gi < slaveGroups.size(); ++gi) {
2739 const auto &group = slaveGroups[gi];
2740 builder.setInsertionPoint(b.getTerminator());
2741
2742 auto port = group.front().first;
2743 if (tileId != port.first)
2744 continue;
2745
2746 Port slave = port.second;
2747
2748 SmallVector<int, 4> matchIds =
2749 llvm::to_vector<4>(llvm::make_second_range(group));
2750 for (int id : matchIds)
2751 if (id > static_cast<int>(maxPacketId)) {
2752 SmallVector<std::pair<PhysPort, int>> offending;
2753 for (const auto &flow : group)
2754 if (flow.second == id)
2755 offending.push_back(flow);
2756 return mlir::emitError(locOf(offending, tileLoc))
2757 << "packet id " << id << " exceeds the maximum of "
2758 << maxPacketId;
2759 }
2760
2761 // Rules the switchbox already has, e.g. hand-authored, match first.
2762 for (PacketRuleOp rule : existingRules.lookup(slave))
2763 for (int id : matchIds)
2764 if ((id & rule.maskInt()) == rule.valueInt()) {
2765 rule->emitOpError("can lead to false packet id match for id ")
2766 << id << ", which is not supposed to pass through this port.";
2767 rule->emitRemark("Please consider changing all uses of packet id ")
2768 << id << " to avoid deadlock.";
2769 return failure();
2770 }
2771
2772 // Groups on one slave port carry different destination sets, so
2773 // checkRules let no two of them claim the same id.
2774 assert(llvm::all_of(
2775 llvm::seq<size_t>(0, slaveGroups.size()),
2776 [&](size_t oi) {
2777 return oi == gi || slaveGroups[oi].front().first != port ||
2778 llvm::none_of(claimsOf(gi), [&](auto own) {
2779 return llvm::any_of(claimsOf(oi), [&](auto other) {
2780 return cubesIntersect(own, other);
2781 });
2782 });
2783 }) &&
2784 "groups on one slave port claim the same id");
2785
2786 // The rules of the slave port, planned at its first group, go out in
2787 // order: each group adds those up to its last.
2788 auto [planIt, fresh] = portPlans.try_emplace(slave);
2789 PortPlan &plan = planIt->second;
2790 if (fresh) {
2791 SmallVector<GroupClaims> claims;
2792 for (size_t oi = 0; oi < slaveGroups.size(); ++oi) {
2793 if (slaveGroups[oi].front().first != port)
2794 continue;
2795 plan.groups.push_back(oi);
2796 claims.push_back(
2797 {statedRules[oi], derivedIds[oi],
2798 prioritizedFlows.contains(slaveGroups[oi].front())});
2799 }
2800 SmallVector<std::pair<int, int>> existing;
2801 for (PacketRuleOp rule : existingRules.lookup(slave))
2802 existing.push_back({rule.maskInt(), rule.valueInt()});
2803 plan.rules = slavePortRules(claims, existing, idBits,
2804 targetModel.getNumSlaveSlots());
2805 LLVM_DEBUG({
2806 llvm::dbgs() << "packet rules " << describePort(slave) << ":";
2807 for (const PortRule &r : plan.rules)
2808 llvm::dbgs() << " rule(" << r.mask << ", " << r.value
2809 << ") -> group " << plan.groups[r.group];
2810 llvm::dbgs() << '\n';
2811 });
2812 }
2813
2814 assert(
2815 llvm::all_of(
2816 llvm::seq(0, idMask + 1),
2817 [&](int id) {
2818 bool own = llvm::is_contained(derivedIds[gi], id) ||
2819 llvm::any_of(statedRules[gi], [&](auto c) {
2820 return (id & c.first) == (c.second & c.first);
2821 });
2822 const auto *first =
2823 llvm::find_if(plan.rules, [&](const PortRule &r) {
2824 return (id & r.mask) == r.value;
2825 });
2826 return !own ||
2827 (first != plan.rules.end() &&
2828 slaveAMSels.at(
2829 slaveGroups[plan.groups[first->group]].front()) ==
2830 slaveAMSels.at(slaveGroups[gi].front()));
2831 }) &&
2832 "packet rules send a claimed id elsewhere");
2833
2834 size_t last = plan.emitted;
2835 for (size_t r = plan.emitted; r < plan.rules.size(); ++r)
2836 if (plan.groups[plan.rules[r].group] == gi)
2837 last = r + 1;
2838
2839 PacketRulesOp packetrules = slaveRules.lookup(slave);
2840 if (!packetrules) {
2841 packetrules = PacketRulesOp::create(builder, locOf(group, tileLoc),
2842 slave.bundle, slave.channel);
2843 PacketRulesOp::ensureTerminator(packetrules.getRules(), builder,
2844 tileLoc);
2845 slaveRules[slave] = packetrules;
2846 } else {
2847 // After the amsels its new rules use, and located at their flows too.
2848 packetrules->moveBefore(b.getTerminator());
2849 if (Location more = locOf(group, tileLoc); more != tileLoc)
2850 packetrules->setLoc(FusedLoc::get(builder.getContext(),
2851 {packetrules->getLoc(), more}));
2852 }
2853
2854 Block &rules = packetrules.getRules().front();
2855
2856 assert(llvm::range_size(rules.getOps<PacketRuleOp>()) +
2857 (last - plan.emitted) <=
2858 targetModel.getNumSlaveSlots() &&
2859 "checkRules lets a slave port take more rules than it holds");
2860
2861 builder.setInsertionPoint(rules.getTerminator());
2862 for (; plan.emitted < last; ++plan.emitted) {
2863 const PortRule &r = plan.rules[plan.emitted];
2864 const auto &ruleGroup = slaveGroups[plan.groups[r.group]];
2865 auto rule = PacketRuleOp::create(
2866 builder, locOf(ruleGroup, tileLoc), r.mask, r.value,
2867 amselOps.at(slaveAMSels.at(ruleGroup.front())));
2868 if (llvm::all_of(ruleGroup, [&](const auto &member) {
2869 return markedFlows.contains(member);
2870 }))
2871 priority.setAttr(rule, unit);
2872 }
2873 }
2874
2875 for (auto rulesOp : b.getOps<PacketRulesOp>()) {
2876 auto rules = rulesOp.getRules().front().getOps<PacketRuleOp>();
2877 if (rules.empty() || !llvm::all_of(rules, [&](PacketRuleOp rule) {
2878 return priority.isAttrPresent(rule);
2879 }))
2880 continue;
2881 priority.setAttr(rulesOp, unit);
2882 for (auto rule : rules)
2883 priority.removeAttr(rule);
2884 }
2885 }
2886 return success();
2887}
2888
2889LogicalResult AIEPathfinderPass::runOnPacketFlow(
2890 DeviceOp device, OpBuilder &builder, DynamicTileAnalysis &analyzer,
2891 const StreamConflicts &conflicts, const PacketPlan &plan, bool warn) {
2892 if (warn && plan.sharedReceiverCycle)
2893 emitWarning(device.getLoc(), sharedReceiverCycleReason)
2894 << conflicts.explain(*plan.sharedReceiverCycle);
2895 if (failed(plan.emit(device, builder, analyzer)))
2896 return failure();
2897 lowerShimDMAPorts(device, builder, analyzer);
2898 for (PacketFlowOp flow :
2899 llvm::make_early_inc_range(device.getOps<PacketFlowOp>()))
2900 flow.erase();
2901 return success();
2902}
2903
2904// A copy of `d` in a module like its own, as the analyses look up through it.
2905static DeviceOp cloneInScratch(DeviceOp d, OwningOpRef<ModuleOp> &scratch) {
2906 scratch = ModuleOp::create(d.getLoc());
2907 if (Operation *parent = d->getParentOp())
2908 (*scratch)->setAttrs(parent->getAttrDictionary());
2909 DeviceOp copy = d.clone();
2910 scratch->push_back(copy);
2911 return copy;
2912}
2913
2914// The router names a shim DMA by its own port, and the end of
2915// runOnPacketFlow moves it behind the shim mux onto a South channel. Rules and
2916// master sets a previous run left there are moved back first, so new flows on
2917// the same DMA channel share them.
2918static void unmuxShimDMAPacketPorts(DeviceOp device) {
2919 for (auto shimMux : device.getOps<ShimMuxOp>()) {
2920 auto hasConnect = [&](Port src, Port dst) {
2921 return llvm::any_of(shimMux.getConnections().getOps<ConnectOp>(),
2922 [&](ConnectOp c) {
2923 return c.sourcePort() == src && c.destPort() == dst;
2924 });
2925 };
2926 for (auto switchbox : device.getOps<SwitchboxOp>()) {
2927 if (switchbox.getTileOp() != shimMux.getTileOp())
2928 continue;
2929 for (auto rules : switchbox.getConnections().getOps<PacketRulesOp>())
2930 for (int ch : {0, 1}) {
2931 Port dma{WireBundle::DMA, ch};
2932 Port north{WireBundle::North, shimMuxChannelFrom(dma)};
2933 if (rules.sourcePort() == Port{WireBundle::South, north.channel} &&
2934 hasConnect(dma, north)) {
2935 rules.setSourceBundle(WireBundle::DMA);
2936 rules.setSourceChannel(ch);
2937 break;
2938 }
2939 }
2940 for (auto masterSet : switchbox.getConnections().getOps<MasterSetOp>())
2941 for (int ch : {0, 1}) {
2942 Port dma{WireBundle::DMA, ch};
2943 Port north{WireBundle::North, shimMuxChannelTo(dma)};
2944 if (masterSet.destPort() == Port{WireBundle::South, north.channel} &&
2945 hasConnect(north, dma)) {
2946 masterSet.setDestBundle(WireBundle::DMA);
2947 masterSet.setDestChannel(ch);
2948 break;
2949 }
2950 }
2951 }
2952 }
2953}
2954
2955llvm::Expected<PacketPlan> AIEPathfinderPass::route(
2956 DeviceOp d, DynamicTileAnalysis &analyzer, const StreamConflicts &conflicts,
2957 const PinnedRoutes &pinned, bool circuitSwitchHops, bool prioritize) {
2958 // Packet flows that can deadlock must not share an arbiter. Every routing
2959 // the router finds is checked by planning the arbiters on it, and one that
2960 // cannot be planned counts as illegal, so routing and allocation agree.
2961 std::map<PathEndPoint, SmallVector<size_t, 4>> streamsFrom;
2962 PacketConstraints constraints;
2963 constraints.pinned = pinned.trees;
2964 constraints.pinnedIds = pinned.ids;
2965 constraints.prioritize = prioritize;
2966 if (clRoutePacket && !d.getOps<PacketFlowOp>().empty()) {
2967 const AIETargetModel &targetModel = d.getTargetModel();
2968 if (llvm::Error err = unroutableArbiters(
2969 d, conflicts,
2970 [&](TileID tile) {
2971 return !circuitSwitchHops ||
2972 targetModel.isShimNOCorPLTile(tile.col, tile.row);
2973 },
2974 pinned.trees))
2975 return std::move(err);
2976 for (auto [i, s] : llvm::enumerate(conflicts.getRequestedStreams()))
2977 if (s.packetID)
2978 streamsFrom[{s.src.tile, s.src.port}].push_back(i);
2979 constraints.apartAcross = [&](llvm::function_ref<bool(TileID)> low, bool up,
2980 size_t limit) {
2981 ArrayRef<RoutedStream> streams = conflicts.getStreams();
2982 SmallVector<size_t, 16> crossing;
2983 std::set<std::pair<TileID, Port>> srcs, dsts;
2984 for (auto [i, s] : llvm::enumerate(conflicts.getRequestedStreams()))
2985 if (s.packetID && low(s.src.tile) == up && low(s.dst.tile) != up) {
2986 crossing.push_back(i);
2987 srcs.insert({s.src.tile, s.src.port});
2988 dsts.insert({s.dst.tile, s.dst.port});
2989 }
2990 PacketsApart apart;
2991 // Streams kept apart have distinct sources and destinations.
2992 size_t most = std::min(srcs.size(), dsts.size());
2993 SmallVector<size_t, 8> best =
2994 most > limit
2995 ? largestApartSet(conflicts, crossing, limit)
2996 : SmallVector<size_t, 8>(
2997 crossing.begin(),
2998 crossing.begin() + std::min<size_t>(1, crossing.size()));
2999 for (size_t s : best)
3000 apart.flows.push_back(describeStream(streams[s]));
3001 if (best.size() > 1)
3002 apart.why = conflicts.explain(best[0], best[1]);
3003 return apart;
3004 };
3005 constraints.conflict = [&](const PathEndPoint &a, const std::set<int> &aIds,
3006 const PathEndPoint &b,
3007 const std::set<int> &bIds) {
3008 auto as = streamsFrom.find(a), bs = streamsFrom.find(b);
3009 if (as == streamsFrom.end() || bs == streamsFrom.end())
3010 return false;
3011 ArrayRef<RoutedStream> streams = conflicts.getStreams();
3012 for (size_t s : as->second)
3013 for (size_t t : bs->second)
3014 if (aIds.count(*streams[s].packetID) &&
3015 bIds.count(*streams[t].packetID) && conflicts.conflict(s, t))
3016 return true;
3017 return false;
3018 };
3019 constraints.check = [&](const Routing &routing) -> llvm::Error {
3020 PacketFlowRouting check(d, routing, conflicts, pinned, clRouteCircuit,
3021 circuitSwitchHops, prioritize);
3022 llvm::Expected<PacketPlan> plan = check.plan();
3023 if (!plan)
3024 return plan.takeError();
3025 RoutingFaults all = check.faults(), faults;
3026 faults.connections = std::move(all.connections);
3027 faults.apart = std::move(all.apart);
3028 faults.together = std::move(all.together);
3029 if (!plan->sharedReceiverCycle ||
3030 (faults.connections.empty() && faults.apart.empty() &&
3031 faults.together.empty()))
3032 return llvm::Error::success();
3033 return llvm::make_error<DeadlockProneRouting>(
3034 conflicts.explain(*plan->sharedReceiverCycle), std::move(faults));
3035 };
3036 }
3037 analyzer.pathfinder.setPacketConstraints(std::move(constraints));
3038 llvm::Error routed = analyzer.runAnalysis(d);
3039 analyzer.pathfinder.setPacketConstraints({});
3040 if (routed)
3041 return std::move(routed);
3042 if (!clRoutePacket)
3043 return PacketPlan();
3044 PacketFlowRouting found(d, analyzer.routing, conflicts, pinned,
3045 clRouteCircuit, circuitSwitchHops, prioritize);
3046 llvm::Expected<PacketPlan> plan = found.plan();
3047 if (found.incomplete) {
3048 llvm::consumeError(plan.takeError());
3049 return llvm::make_error<RoutingFailure>(std::move(found.incomplete->second),
3050 RoutingFaults{},
3051 found.incomplete->first);
3052 }
3053 // The routing found may still leave a hold cycle through receivers flows
3054 // share, as the check lets one through where it names nothing to move.
3055 if (!plan || clAllowDeadlockProne)
3056 return plan;
3057 const std::optional<HoldCycle> &cycle = plan->sharedReceiverCycle;
3058 if (!cycle)
3059 return plan;
3060 return llvm::make_error<DeadlockProneRouting>(
3061 (llvm::Twine(sharedReceiverCycleReason) + conflicts.explain(*cycle) +
3062 allowDeadlockProneHint)
3063 .str());
3064}
3065
3066static LogicalResult checkFixedFlowEndpoints(DeviceOp device) {
3067 DenseMap<PhysPort, Operation *> inputs, outputs;
3068 for (SwitchboxOp box : device.getOps<SwitchboxOp>()) {
3069 TileID tile = box.getTileOp().getTileID();
3070 for (ConnectOp connect : box.getOps<ConnectOp>()) {
3071 inputs[{tile, connect.sourcePort()}] = connect;
3072 outputs[{tile, connect.destPort()}] = connect;
3073 }
3074 for (MasterSetOp master : box.getOps<MasterSetOp>())
3075 outputs[{tile, master.destPort()}] = master;
3076 for (PacketRulesOp rules : box.getOps<PacketRulesOp>())
3077 inputs[{tile, rules.sourcePort()}] = rules;
3078 }
3079 AIEDialect::IsCtrlPktOverlayAttrHelper overlay(device.getContext());
3080 auto check = [&](Operation *flow, Value value, Port port,
3081 bool source) -> LogicalResult {
3082 auto tile = cast<TileOp>(value.getDefiningOp());
3083 auto &reservations = source ? inputs : outputs;
3084 Operation *reserved = reservations.lookup({tile.getTileID(), port});
3085 if (!reserved && tile.isShimNOCorPLTile() &&
3086 llvm::is_contained({WireBundle::DMA, WireBundle::NOC, WireBundle::PLIO},
3087 port.bundle)) {
3088 Port south{WireBundle::South,
3089 source ? shimMuxChannelFrom(port) : shimMuxChannelTo(port)};
3090 reserved = reservations.lookup({tile.getTileID(), south});
3091 }
3092 auto packet = dyn_cast<PacketFlowOp>(flow);
3093 if (!reserved || (packet && isa<PacketRulesOp>(reserved)))
3094 return success();
3095 bool fromOverlay = overlay.isAttrPresent(reserved);
3096 if (auto rules = dyn_cast<PacketRulesOp>(reserved))
3097 fromOverlay |= llvm::any_of(
3098 rules.getRules().getOps<PacketRuleOp>(),
3099 [&](PacketRuleOp rule) { return overlay.isAttrPresent(rule); });
3100 auto diag = emitError(flow->getLoc());
3101 diag << "Unable to find a legal routing: ";
3102 if (packet)
3103 diag << "packet flow " << packet.IDInt();
3104 else
3105 diag << "circuit flow";
3106 diag << (source ? " starts at " : " ends at ")
3107 << describeTilePort(tile.getTileID(), port) << ", whose "
3108 << (source ? "input" : "output") << " port is reserved by ";
3109 if (fromOverlay)
3110 diag << "the control packet overlay";
3111 else
3112 diag << "a fixed " << reserved->getName();
3113 diag.attachNote(reserved->getLoc())
3114 << (source ? "input" : "output") << " port reserved here by "
3115 << reserved->getName();
3116 return failure();
3117 };
3118 for (PacketFlowOp flow : device.getOps<PacketFlowOp>()) {
3119 for (PacketSourceOp source : flow.getPorts().getOps<PacketSourceOp>())
3120 if (failed(check(flow, source.getTile(), source.port(), true)))
3121 return failure();
3122 for (PacketDestOp dest : flow.getPorts().getOps<PacketDestOp>())
3123 if (failed(check(flow, dest.getTile(), dest.port(), false)))
3124 return failure();
3125 }
3126 for (FlowOp flow : device.getOps<FlowOp>())
3127 if (failed(check(flow, flow.getSource(),
3128 {flow.getSourceBundle(), flow.getSourceChannel()},
3129 true)) ||
3130 failed(check(flow, flow.getDest(),
3131 {flow.getDestBundle(), flow.getDestChannel()}, false)))
3132 return failure();
3133 return success();
3134}
3135
3136void AIEPathfinderPass::runOnOperation() {
3137
3138 // create analysis pass with routing graph for entire device
3139 LLVM_DEBUG(llvm::dbgs() << "---Begin AIEPathfinderPass---\n");
3140
3141 DeviceOp d = getOperation();
3142 if (failed(verifyDMAChannelsResolved(d)))
3143 return signalPassFailure();
3144 OpBuilder builder = OpBuilder::atBlockTerminator(d.getBody());
3145 if (clRoutePacket)
3146 unmuxShimDMAPacketPorts(d);
3147 if (failed(checkFixedFlowEndpoints(d)))
3148 return signalPassFailure();
3149
3150 StreamConflicts conflicts(d);
3151 if (auto pairs = conflicts.unavoidable(); !pairs.empty()) {
3152 InFlightDiagnostic diag =
3153 (clAllowDeadlockProne ? emitWarning(d.getLoc()) : emitError(d.getLoc()))
3154 << "Flows can deadlock however they are routed: "
3155 << conflicts.explain(pairs[0].first, pairs[0].second);
3156 if (pairs.size() > 1)
3157 diag << " So can " << pairs.size() - 1 << " other pair"
3158 << (pairs.size() > 2 ? "s" : "") << " of flows.";
3159 if (!clAllowDeadlockProne) {
3160 diag << allowDeadlockProneHint;
3161 return signalPassFailure();
3162 }
3163 }
3164 DynamicTileAnalysis analyzer;
3165 // A prioritized flow keeps the route it takes alone, so route the
3166 // prioritized flows without the rest of the design first, and pin their
3167 // trees for the rest to route around. Packets sharing a source and id with
3168 // a prioritized flow go with it (see prioritizedFlows); other packets from
3169 // their sources route freely.
3170 auto sourcesOf = [](PacketFlowOp flow) {
3171 return llvm::map_range(
3172 flow.getPorts().getOps<PacketSourceOp>(), [](PacketSourceOp src) {
3173 return PathEndPoint{
3174 cast<TileOp>(src.getTile().getDefiningOp()).getTileID(),
3175 src.port()};
3176 });
3177 };
3178 std::set<PathEndPoint> prioritized;
3179 std::set<std::pair<PathEndPoint, int>> prioritizedIds;
3180 for (PacketFlowOp flow : d.getOps<PacketFlowOp>())
3181 if (flow.getPriorityRoute().value_or(false))
3182 for (PathEndPoint src : sourcesOf(flow)) {
3183 prioritized.insert(src);
3184 prioritizedIds.insert({src, flow.IDInt()});
3185 }
3186 auto isPrioritized = [&](PacketFlowOp flow) {
3187 return llvm::any_of(sourcesOf(flow), [&](const PathEndPoint &src) {
3188 return prioritizedIds.count({src, flow.IDInt()}) > 0;
3189 });
3190 };
3191 auto routesAlone = isPrioritized;
3192 bool pinned = false;
3193 PinnedRoutes priority;
3194 bool othersToRoute = !d.getOps<FlowOp>().empty() ||
3195 !llvm::all_of(d.getOps<PacketFlowOp>(), routesAlone);
3196 // With no other flows to route, this routing is the route-alone one, so it
3197 // keeps off the avoided ports too.
3198 if (!othersToRoute)
3199 analyzer.avoidedPorts = avoidedPorts;
3200 if (clRoutePacket && !prioritized.empty() && othersToRoute) {
3201 OwningOpRef<ModuleOp> scratch;
3202 DeviceOp alone = cloneInScratch(d, scratch);
3203 SmallVector<Operation *> others;
3204 for (Operation &op : alone.getBody()->without_terminator()) {
3205 auto flow = dyn_cast<PacketFlowOp>(op);
3206 if (flow ? !routesAlone(flow) : isa<FlowOp>(op))
3207 others.push_back(&op);
3208 }
3209 for (Operation *op : others)
3210 op->dropAllReferences();
3211 for (Operation *op : others)
3212 op->erase();
3213 std::map<PathEndPoint, std::set<int>> aloneIds;
3214 for (PacketFlowOp flow : alone.getOps<PacketFlowOp>())
3215 for (auto src : flow.getPorts().getOps<PacketSourceOp>()) {
3216 auto tile = cast<TileOp>(src.getTile().getDefiningOp());
3217 aloneIds[{{tile.colIndex(), tile.rowIndex()}, src.port()}].insert(
3218 flow.IDInt());
3219 }
3220 DynamicTileAnalysis aloneAnalyzer;
3221 aloneAnalyzer.avoidedPorts = avoidedPorts;
3222 StreamConflicts aloneConflicts(alone);
3223 llvm::Expected<PacketPlan> alonePlan =
3224 route(alone, aloneAnalyzer, aloneConflicts, {},
3225 /*circuitSwitchHops=*/false);
3226 if (!alonePlan) {
3227 llvm::consumeError(alonePlan.takeError());
3228 } else {
3229 // The design's own routing keeps these trees, so it warns of any hold
3230 // cycle they leave.
3231 OpBuilder aloneBuilder = OpBuilder::atBlockTerminator(alone.getBody());
3232 if (failed(runOnPacketFlow(alone, aloneBuilder, aloneAnalyzer,
3233 aloneConflicts, *alonePlan, /*warn=*/false)))
3234 return signalPassFailure();
3235 priority = PinnedRoutes{aloneAnalyzer.routing.packetTrees,
3236 std::move(aloneIds), priorityRules(alone)};
3237 pinned = true;
3238 }
3239 }
3240 // If routing fails, the router relaxes how it routes packet flows (see
3241 // Pathfinder::relax) and tries again. The error is the first attempt's, or
3242 // the first to find a routing allow-deadlock-prone would take, as that is
3243 // all that keeps the design from routing.
3244 auto routeRelaxing = [&](DeviceOp dev, DynamicTileAnalysis &an,
3245 const StreamConflicts &c, const PinnedRoutes &pin,
3246 bool prioritize =
3247 true) -> llvm::Expected<PacketPlan> {
3248 llvm::Expected<PacketPlan> plan =
3249 route(dev, an, c, pin, clCircuitSwitchHops, prioritize);
3250 if (plan)
3251 return plan;
3252 llvm::Error first = plan.takeError();
3253 while (an.pathfinder.relax()) {
3254 plan = route(dev, an, c, pin, clCircuitSwitchHops, prioritize);
3255 if (plan) {
3256 llvm::consumeError(std::move(first));
3257 return plan;
3258 }
3259 llvm::Error err = plan.takeError();
3260 if (err.isA<DeadlockProneRouting>() && !first.isA<DeadlockProneRouting>())
3261 std::swap(first, err);
3262 llvm::consumeError(std::move(err));
3263 }
3264 return std::move(first);
3265 };
3266 llvm::Expected<PacketPlan> plan =
3267 routeRelaxing(d, analyzer, conflicts, priority);
3268 if (!plan && !prioritized.empty()) {
3269 llvm::Error err = plan.takeError();
3270 priority = {};
3271 DynamicTileAnalysis freeAnalyzer;
3272 plan = routeRelaxing(d, freeAnalyzer, conflicts, priority,
3273 /*prioritize=*/false);
3274 if (plan) {
3275 llvm::consumeError(std::move(err));
3276 analyzer = std::move(freeAnalyzer);
3277 } else {
3278 llvm::Error freeErr = plan.takeError();
3279 if (freeErr.isA<DeadlockProneRouting>() ||
3280 !err.isA<DeadlockProneRouting>())
3281 std::swap(err, freeErr);
3282 llvm::consumeError(std::move(freeErr));
3283 plan = std::move(err);
3284 }
3285 pinned = false;
3286 }
3287 if (!plan) {
3288 llvm::handleAllErrors(plan.takeError(), [&](RoutingFailure &f) {
3289 // Say whether the pinned trees are what stands in the way: the design
3290 // routes if they may move.
3291 if (pinned && !f.reason.empty() &&
3292 !f.isA(PinnedRoutingFailure::classID())) {
3293 OwningOpRef<ModuleOp> scratch;
3294 DeviceOp free = cloneInScratch(d, scratch);
3295 DynamicTileAnalysis freeAnalyzer;
3296 StreamConflicts freeConflicts(free);
3297 llvm::Expected<PacketPlan> freePlan = routeRelaxing(
3298 free, freeAnalyzer, freeConflicts, {}, /*prioritize=*/false);
3299 if (!freePlan) {
3300 llvm::consumeError(freePlan.takeError());
3301 } else {
3302 f.reason = describePrioritized(llvm::to_vector(llvm::map_range(
3303 prioritized,
3304 [](const PathEndPoint &src) {
3305 return describeTilePort(src.coords, src.port);
3306 }))) +
3307 ", and the other flows route only if it moves. Around "
3308 "it, " +
3309 f.reason;
3310 }
3311 }
3312 emitError(f.loc.value_or(d.getLoc())) << f.message();
3313 });
3314 signalPassFailure();
3315 return;
3316 }
3317 for (auto flowOp : d.getOps<FlowOp>()) {
3318 if (!flowOp.getVias().empty()) {
3319 flowOp.emitOpError("cannot be routed while it carries via waypoints; run "
3320 "--aie-split-flow-vias first to lower the vias into "
3321 "switchbox connections");
3322 signalPassFailure();
3323 return;
3324 }
3325 }
3326 for (auto flowOp : d.getOps<PacketFlowOp>()) {
3327 if (!flowOp.getVias().empty()) {
3328 flowOp.emitOpError("cannot be routed while it carries via waypoints; run "
3329 "--aie-split-flow-vias first to lower the vias into "
3330 "switchbox connections");
3331 signalPassFailure();
3332 return;
3333 }
3334 }
3335
3336 if (clRouteCircuit && failed(runOnFlow(d, analyzer))) {
3337 signalPassFailure();
3338 return;
3339 }
3340 if (clRoutePacket && failed(runOnPacketFlow(d, builder, analyzer, conflicts,
3341 *plan, /*warn=*/true))) {
3342 signalPassFailure();
3343 return;
3344 }
3345 // Populate wires between switchboxes and tiles. Re-running the pass on
3346 // already-routed IR must not duplicate the wires a previous run emitted, so
3347 // key the wires already present and emit only the missing ones.
3348 builder.setInsertionPoint(d.getBody()->getTerminator());
3349 llvm::DenseSet<std::tuple<Value, int, Value, int>> existingWires;
3350 // aie.wire is a physical connection, so the IR may spell one adjacency in
3351 // either operand order.
3352 auto recordWire = [&](Value source, int sourceBundle, Value dest,
3353 int destBundle) {
3354 bool absent =
3355 !existingWires.contains({source, sourceBundle, dest, destBundle}) &&
3356 !existingWires.contains({dest, destBundle, source, sourceBundle});
3357 existingWires.insert({source, sourceBundle, dest, destBundle});
3358 existingWires.insert({dest, destBundle, source, sourceBundle});
3359 return absent;
3360 };
3361 for (auto wire : d.getOps<WireOp>()) {
3362 recordWire(wire.getSource(), static_cast<int>(wire.getSourceBundle()),
3363 wire.getDest(), static_cast<int>(wire.getDestBundle()));
3364 }
3365 auto wire = [&](Location loc, Value source, WireBundle sourceBundle,
3366 Value dest, WireBundle destBundle) {
3367 if (recordWire(source, static_cast<int>(sourceBundle), dest,
3368 static_cast<int>(destBundle))) {
3369 WireOp::create(builder, loc, source, sourceBundle, dest, destBundle);
3370 }
3371 };
3372 for (int col = 0; col <= analyzer.getMaxCol(); col++) {
3373 for (int row = 0; row <= analyzer.getMaxRow(); row++) {
3374 TileOp tile = analyzer.lookupTile({col, row});
3375 SwitchboxOp sw = analyzer.lookupSwitchbox({col, row});
3376 if (!tile || !sw)
3377 continue;
3378 Location loc = tile.getLoc();
3379 // connections east-west between stream switches
3380 if (col > 0)
3381 if (SwitchboxOp westsw = analyzer.lookupSwitchbox({col - 1, row}))
3382 wire(loc, westsw, WireBundle::East, sw, WireBundle::West);
3383 if (row > 0) {
3384 // connections between abstract 'core' of tile
3385 wire(loc, tile, WireBundle::Core, sw, WireBundle::Core);
3386 // connections between abstract 'dma' of tile
3387 wire(loc, tile, WireBundle::DMA, sw, WireBundle::DMA);
3388 // connections north-south inside array ( including connection to shim
3389 // row)
3390 if (SwitchboxOp southsw = analyzer.lookupSwitchbox({col, row - 1}))
3391 wire(loc, southsw, WireBundle::North, sw, WireBundle::South);
3392 } else if (tile.isShimNOCTile()) {
3393 if (ShimMuxOp shimsw = analyzer.lookupShimMux(col)) {
3394 wire(loc, shimsw,
3395 WireBundle::North, // Changed to connect into the north
3396 sw, WireBundle::South);
3397 // abstract 'DMA' connection on tile is attached to shim mux ( in
3398 // row 0 )
3399 wire(loc, tile, WireBundle::DMA, shimsw, WireBundle::DMA);
3400 }
3401 }
3402 }
3403 }
3404}
3405
3406std::unique_ptr<OperationPass<DeviceOp>> AIE::createAIEPathfinderPass() {
3407 return std::make_unique<AIEPathfinderPass>();
3408}
3409
3410std::unique_ptr<OperationPass<DeviceOp>>
3411AIE::createAIEPathfinderPass(const AIERoutePathfinderFlowsOptions &options) {
3412 return std::make_unique<AIEPathfinderPass>(options);
3413}
3414
3415std::unique_ptr<OperationPass<DeviceOp>>
3416AIE::createAIEPathfinderPass(const AIERoutePathfinderFlowsOptions &options,
3417 ArrayRef<std::pair<TileID, Port>> avoidedPorts) {
3418 auto pass = std::make_unique<AIEPathfinderPass>(options);
3419 pass->avoidedPorts.assign(avoidedPorts.begin(), avoidedPorts.end());
3420 return pass;
3421}
constexpr int maxSearchSteps
std::pair< TileID, Port > PhysPort
constexpr int maxHoldCycleReplans
std::map< PhysPort, SmallVector< std::tuple< int, int, int >, 2 > > PriorityRules
(mask, value, amsel) by slave port, in slot order.
std::tuple< int, int, int, int, int, int, int, int, int, int > FlowKey
virtual uint32_t getNumMselsPerArbiter() const =0
Return the number of master select values per stream-switch arbiter.
virtual uint32_t getNumArbiters() const =0
Return the number of arbiters per stream switch.
bool isShimNOCorPLTile(int col, int row) const
Return true if the given tile is either a ShimNOC or ShimPL tile.
virtual int rows() const =0
Return the number of rows in the device.
bool isShimNOCTile(int col, int row) const
Return true if the given tile is a ShimNOC tile.
virtual uint32_t getMaxPacketId() const =0
Return the largest packet id the stream switch can route.
virtual int columns() const =0
Return the number of columns in the device.
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 getNumSlaveSlots() const =0
Return the number of packet-rule slots per stream-switch slave port.
ShimMuxOp getShimMux(mlir::OpBuilder &builder, int col)
SwitchboxOp getSwitchbox(mlir::OpBuilder &builder, int col, int row)
SwitchboxOp lookupSwitchbox(TileID tile) const
TileOp lookupTile(TileID tile) const
TileOp getTile(mlir::OpBuilder &builder, int col, int row)
llvm::Error runAnalysis(DeviceOp &device)
std::set< PathEndPoint > processedFlows
ShimMuxOp lookupShimMux(int col) const
std::vector< std::pair< TileID, Port > > avoidedPorts
void setPacketConstraints(PacketConstraints c)
std::optional< mlir::Location > loc
The streams a device asks for or already routes, and which pairs of them must not share an arbiter or...
llvm::ArrayRef< RoutedStream > getRequestedStreams() const
The streams the flow ops ask for, which come first in getStreams().
llvm::SmallVector< std::pair< size_t, size_t > > unavoidable() const
The requested streams where the first can hold up the second however they are routed,...
bool mustSeparate(size_t s, size_t t) const
Whether a routing that puts packet streams s and t on one arbiter anywhere has a hold cycle: they con...
llvm::ArrayRef< RoutedStream > getStreams() const
bool conflict(size_t s, size_t t) const
Whether s and t can deadlock if they share an arbiter or a link.
std::optional< HoldCycle > holdCycle(llvm::ArrayRef< llvm::SmallVector< StreamHop, 8 > > routes, bool forcedWaits=false, bool definite=false) const
A cycle of waits the packet streams can deadlock in when routed along routes, indexed like getStreams...
std::string explain(size_t s, size_t t) const
Why s and t conflict.
int shimMuxChannelTo(Port dst)
struct Connect { Port src; Port dst; bool operator==(const Connect &rhs) const { return std::tie(src, dst)==std::tie(rhs.src, rhs.dst); } bool operator!=(const Connect &rhs) const { return !(*this==rhs);} bool operator<(const Connect &rhs) const { return std::tie(src, dst)< std::tie(rhs.src, rhs.dst); } } Connect
Definition AIEDialect.h:299
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
std::string describeStream(const RoutedStream &stream)
Names the stream by its endpoints and packet id, e.g.
const AIETargetModel & getTargetModel(mlir::Operation *op)
mlir::LogicalResult verifyDMAChannelsResolved(DeviceOp device)
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::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEPathfinderPass()
std::string describePort(Port port)
Names a port the way diagnostics do, e.g. "DMA:1".
std::map< PathEndPoint, std::vector< TreeHop > > PacketTrees
std::map< PathEndPoint, std::set< int > > ids
bool forced
The wait holds however the arbiters are planned, as sharer and holding go into a receiver they share ...
std::map< PathEndPoint, std::set< int > > pinnedIds
std::vector< std::string > flows
std::optional< int > packetID
The packet id carried, if any.
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::map< std::pair< PathEndPoint, int >, SwitchSettings > idSettings