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