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"
36#define DEBUG_TYPE "aie-create-pathfinder-flows"
39#define GEN_PASS_DEF_AIEROUTEPATHFINDERFLOWS
40#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
47 std::map<PhysPort, SmallVector<std::tuple<int, int, int>, 2>>;
52 std::map<PathEndPoint, std::set<int>>
ids;
57 const int numArbiters = d.getTargetModel().getNumArbiters();
58 AIEDialect::PriorityRouteAttrHelper priority(d.getContext());
60 for (SwitchboxOp box : d.getOps<SwitchboxOp>())
61 for (PacketRulesOp port : box.getOps<PacketRulesOp>())
62 for (PacketRuleOp rule : port.getRules().front().getOps<PacketRuleOp>())
63 if (priority.isAttrPresent(port) || priority.isAttrPresent(rule)) {
64 auto amsel = cast<AMSelOp>(rule.getAmsel().getDefiningOp());
65 rules[{box.getTileOp().getTileID(),
66 {port.getSourceBundle(), port.sourceIndex()}}]
68 {rule.maskInt(), rule.valueInt(),
69 amsel.arbiterIndex() + amsel.getMselValue() * numArbiters});
80static constexpr llvm::StringLiteral sharedReceiverCycleReason =
81 "Packet flows into receivers they share can deadlock holding arbiters "
82 "across switchboxes, and no routing found avoids it: ";
83static constexpr llvm::StringLiteral allowDeadlockProneHint =
84 " Set allow-deadlock-prone (aiecc --allow-deadlock-prone-routing) to "
101struct AIEPathfinderPass
102 : xilinx::AIE::impl::AIERoutePathfinderFlowsBase<AIEPathfinderPass> {
104 xilinx::AIE::impl::AIERoutePathfinderFlowsBase<AIEPathfinderPass>;
105 AIEPathfinderPass() =
default;
106 AIEPathfinderPass(
const AIERoutePathfinderFlowsOptions &options)
108 std::vector<std::pair<TileID, Port>> avoidedPorts;
109 void runOnOperation()
override;
113 LogicalResult runOnPacketFlow(DeviceOp d, OpBuilder &builder,
116 const PacketPlan &plan,
bool warn);
120 bool circuitSwitchHops,
121 bool prioritize =
true);
126struct ConvertFlowsToInterconnect : OpConversionPattern<FlowOp> {
127 using OpConversionPattern::OpConversionPattern;
130 ConvertFlowsToInterconnect(MLIRContext *context, DeviceOp &d,
132 : OpConversionPattern(context, benefit), device(d), analyzer(a) {}
134 void addConnection(ConversionPatternRewriter &rewriter,
136 Interconnect op, FlowOp flowOp, WireBundle inBundle,
137 int inIndex, WireBundle outBundle,
int outIndex)
const {
139 Region &r = op.getConnections();
140 Block &b = r.front();
141 auto point = rewriter.saveInsertionPoint();
142 rewriter.setInsertionPoint(b.getTerminator());
144 ConnectOp::create(rewriter, flowOp.getLoc(), inBundle, inIndex, outBundle,
147 rewriter.restoreInsertionPoint(point);
149 LLVM_DEBUG(llvm::dbgs()
150 <<
"\t\taddConnection() (" << op.colIndex() <<
","
151 << op.rowIndex() <<
") " << stringifyWireBundle(inBundle)
152 << inIndex <<
" -> " << stringifyWireBundle(outBundle)
153 << outIndex <<
"\n");
157 matchAndRewrite(FlowOp flowOp, OpAdaptor adaptor,
158 ConversionPatternRewriter &rewriter)
const override {
159 Operation *Op = flowOp.getOperation();
160 DeviceOp d = flowOp->getParentOfType<DeviceOp>();
162 flowOp->emitOpError(
"This operation must be contained within a device");
165 rewriter.setInsertionPoint(d.getBody()->getTerminator());
167 auto srcTile = cast<TileOp>(flowOp.getSource().getDefiningOp());
168 TileID srcCoords = {srcTile.colIndex(), srcTile.rowIndex()};
169 auto srcBundle = flowOp.getSourceBundle();
170 auto srcChannel = flowOp.getSourceChannel();
171 Port srcPort = {srcBundle, srcChannel};
174 auto dstTile = cast<TileOp>(flowOp.getDest().getDefiningOp());
175 llvm::dbgs() <<
"\n\t---Begin rewrite() for flowOp: (" << srcCoords.col
176 <<
", " << srcCoords.row <<
")"
177 << stringifyWireBundle(srcBundle) << srcChannel <<
" -> ("
178 << dstTile.colIndex() <<
", " << dstTile.rowIndex() <<
")"
179 << stringifyWireBundle(flowOp.getDestBundle())
180 << flowOp.getDestChannel() <<
"\n\t";
185 TileID srcSbId = {srcCoords.col, srcCoords.row};
192 LLVM_DEBUG(llvm::dbgs() <<
"Flow already processed!\n");
193 rewriter.eraseOp(Op);
198 for (
const auto &[tileId, setting] : settings) {
199 int col = tileId.col;
200 int row = tileId.row;
201 SwitchboxOp swOp = analyzer.
getSwitchbox(rewriter, col, row);
202 int shimCh = srcChannel;
203 bool isShim = analyzer.
getTile(rewriter, tileId).isShimNOCorPLTile();
206 bool srcUsesShimMux = srcBundle == WireBundle::DMA ||
207 srcBundle == WireBundle::PLIO ||
208 srcBundle == WireBundle::NOC;
209 if (isShim && tileId == srcSbId && srcUsesShimMux) {
212 ShimMuxOp shimMuxOp = analyzer.
getShimMux(rewriter, col);
213 addConnection(rewriter, cast<Interconnect>(shimMuxOp.getOperation()),
214 flowOp, srcBundle, srcChannel, WireBundle::North, shimCh);
216 assert(setting.srcs.size() == setting.dsts.size());
217 for (
size_t i = 0; i < setting.srcs.size(); i++) {
218 Port src = setting.srcs[i];
219 Port dest = setting.dsts[i];
222 if (isShim && tileId == srcSbId && srcUsesShimMux && src == srcPort)
223 src = {WireBundle::South, shimCh};
224 if (isShim && (dest.bundle == WireBundle::DMA ||
225 dest.bundle == WireBundle::PLIO ||
226 dest.bundle == WireBundle::NOC)) {
229 ShimMuxOp shimMuxOp = analyzer.
getShimMux(rewriter, col);
230 addConnection(rewriter, cast<Interconnect>(shimMuxOp.getOperation()),
231 flowOp, WireBundle::North, destCh, dest.bundle,
233 dest = {WireBundle::South, destCh};
235 addConnection(rewriter, cast<Interconnect>(swOp.getOperation()), flowOp,
236 src.bundle, src.channel, dest.bundle, dest.channel);
239 LLVM_DEBUG(llvm::dbgs() << tileId <<
": " << setting <<
" | " <<
"\n");
242 LLVM_DEBUG(llvm::dbgs()
243 <<
"\n\t\tFinished adding ConnectOps to implement flowOp.\n");
246 rewriter.eraseOp(Op);
253LogicalResult AIEPathfinderPass::runOnFlow(DeviceOp d,
257 ConversionTarget target(getContext());
258 target.addLegalOp<TileOp>();
259 target.addLegalOp<ConnectOp>();
260 target.addLegalOp<SwitchboxOp>();
261 target.addLegalOp<ShimMuxOp>();
262 target.addLegalOp<EndOp>();
264 RewritePatternSet patterns(&getContext());
265 patterns.insert<ConvertFlowsToInterconnect>(d.getContext(), d, analyzer);
266 if (failed(applyPartialConversion(d, target, std::move(patterns))))
272static std::optional<std::pair<TileID, Port>> linkedInput(
TileID tile,
274 switch (out.bundle) {
275 case WireBundle::East:
276 return std::pair{
TileID{tile.col + 1, tile.row},
277 Port{WireBundle::West, out.channel}};
278 case WireBundle::West:
279 return std::pair{
TileID{tile.col - 1, tile.row},
280 Port{WireBundle::East, out.channel}};
281 case WireBundle::North:
282 return std::pair{
TileID{tile.col, tile.row + 1},
283 Port{WireBundle::South, out.channel}};
284 case WireBundle::South:
285 return std::pair{
TileID{tile.col, tile.row - 1},
286 Port{WireBundle::North, out.channel}};
296 if (tile == finalTile && out == finalPort)
298 std::optional<std::pair<TileID, Port>> next = linkedInput(tile, out);
301 TileID neighbour = next->first;
302 Port in = next->second;
303 auto setting = settings.find(neighbour);
304 if (setting == settings.end())
307 llvm::zip(setting->second.srcs, setting->second.dsts), [&](
auto link) {
308 auto [src, dest] = link;
310 findPathToDest(settings, neighbour, dest, finalTile, finalPort);
317template <
typename Node,
typename Successors>
318static std::map<Node, Node>
319breadthFirstParents(Node start, Successors successors,
320 std::optional<Node> goal = std::nullopt) {
321 std::map<Node, Node> parent{{start, start}};
322 std::deque<Node> work{start};
323 while (!work.empty() && !(goal && parent.count(*goal))) {
324 Node u = work.front();
326 for (Node v : successors(u))
327 if (parent.try_emplace(v, u).second)
338 llvm::SmallBitVector cov;
346static void bnbMinCover(ArrayRef<CoverCube> cubes,
347 const llvm::SmallBitVector &uncovered,
348 SmallVectorImpl<int> &sel,
int &bestSize,
349 SmallVectorImpl<int> &bestSel) {
352 if (uncovered.none()) {
353 if (
static_cast<int>(sel.size()) < bestSize) {
354 bestSize =
static_cast<int>(sel.size());
355 bestSel.assign(sel.begin(), sel.end());
361 if (
static_cast<int>(sel.size()) + 1 >= bestSize) {
367 int pickCount =
static_cast<int>(cubes.size()) + 1;
369 for (
int id = uncovered.find_first();
id != -1;
370 id = uncovered.find_next(
id)) {
373 for (
const CoverCube &cb : cubes) {
374 if (cb.cov.test(
id)) {
386 for (
int i = 0; i < static_cast<int>(cubes.size()); ++i) {
387 if (!cubes[i].cov.test(pick)) {
392 llvm::SmallBitVector next = uncovered;
393 next.reset(cubes[i].cov);
394 bnbMinCover(cubes, next, sel, bestSize, bestSel);
399static bool cubesIntersect(std::pair<int, int> a, std::pair<int, int> b) {
400 return ((a.second ^ b.second) & a.first & b.first) == 0;
415static SmallVector<std::pair<int, int>>
416computeSubcubeCover(
const SmallVector<int, 4> &matchIds,
417 ArrayRef<std::pair<int, int>> avoidCubes,
int idBits) {
420 const int numIds = 1 << idBits;
421 const int idMask = numIds - 1;
423 auto hitsAvoid = [&](
int mask,
int value) {
424 return llvm::any_of(avoidCubes, [&](std::pair<int, int> c) {
425 return cubesIntersect({mask, value}, c);
429 llvm::SmallBitVector matchMask(numIds);
430 for (
int id : matchIds) {
435 SmallVector<CoverCube> primes;
436 for (
int mask = 0; mask < numIds; ++mask) {
437 for (
int value = 0; value < numIds; ++value) {
439 if ((value & mask) != value) {
442 if (hitsAvoid(mask, value)) {
447 llvm::SmallBitVector cov(numIds);
448 for (
int id : matchIds) {
449 if ((
id & mask) == value) {
461 for (
int b = 0; b < idBits; ++b) {
462 if (!((mask >> b) & 1)) {
466 if (!hitsAvoid(mask & ~(1 << b), value & ~(1 << b))) {
473 primes.push_back({mask, value, std::move(cov)});
479 SmallVector<int> sel, bestSel;
480 int bestSize =
static_cast<int>(matchIds.size()) + 1;
482 bnbMinCover(primes, matchMask, sel, bestSize, bestSel);
484 SmallVector<std::pair<int, int>> chosen;
485 for (
int i : bestSel) {
486 chosen.push_back({primes[i].mask, primes[i].value});
489 if (chosen.empty()) {
491 for (
int id : matchIds) {
492 chosen.push_back({idMask,
id});
506 SmallVector<SmallVector<int, 4>, 4> assigned(chosen.size());
507 for (
int id : matchIds) {
508 for (
int c = 0; c < static_cast<int>(chosen.size()); ++c) {
509 if ((
id & chosen[c].first) == chosen[c].second) {
510 assigned[c].push_back(
id);
516 SmallVector<std::pair<int, int>> cover;
517 for (
const SmallVector<int, 4> &ids : assigned) {
523 for (
int i = 0; i < idBits; ++i) {
524 int bit = (ids.front() >> i) & 1;
526 if (((
id >> i) & 1) != bit) {
532 cover.push_back({mask, ids.front() & mask});
541static SmallVector<std::pair<int, int>>
542groupRules(ArrayRef<std::pair<int, int>> stated, ArrayRef<int> derived,
543 ArrayRef<std::pair<int, int>> avoid,
int idBits) {
544 SmallVector<std::pair<int, int>> rules(stated);
547 SmallVector<std::pair<int, int>> derivedAvoid(avoid);
548 derivedAvoid.append(stated.begin(), stated.end());
549 llvm::append_range(rules, computeSubcubeCover(SmallVector<int, 4>(derived),
550 derivedAvoid, idBits));
556 ArrayRef<std::pair<int, int>> stated;
557 ArrayRef<int> derived;
558 bool priority =
false;
569static uint64_t cubeIds(
int mask,
int value,
int idBits) {
571 for (
int id = 0;
id < (1 << idBits); ++id)
572 if ((
id & mask) == (value & mask))
573 ids |= uint64_t(1) << id;
580static std::optional<SmallVector<PortRule>>
581orderedRules(ArrayRef<GroupClaims> groups,
582 ArrayRef<std::pair<int, int>> existing,
int idBits,
586 const int idMask = (1 << idBits) - 1;
587 SmallVector<uint64_t> claims;
588 for (
const GroupClaims &group : groups) {
590 for (
auto [mask, value] : group.stated)
591 ids |= cubeIds(mask, value, idBits);
592 for (
int id : group.derived)
593 ids |= uint64_t(1) << id;
594 claims.push_back(ids);
596 uint64_t claimed = 0;
597 for (uint64_t ids : claims)
600 for (
auto [mask, value] : existing)
601 taken |= cubeIds(mask, value, idBits);
604 SmallVector<uint64_t> cubes;
605 for (
int mask = 0; mask <= idMask; ++mask)
606 for (
int value = 0; value <= idMask; ++value)
607 if ((value & mask) == value)
608 cubes.push_back(cubeIds(mask, value, idBits));
613 SmallVector<PortRule> rules;
614 std::set<std::pair<uint64_t, size_t>> dead;
615 auto search = [&](
auto &self, uint64_t taken,
size_t left) ->
bool {
616 uint64_t open = claimed & ~taken;
618 llvm::count_if(claims, [&](uint64_t ids) {
return ids & open; });
621 if (openGroups > left || dead.count({taken, left}))
623 SmallVector<std::pair<uint64_t, size_t>> moves;
624 for (uint64_t cube : cubes)
625 for (
auto [g, ids] : llvm::enumerate(claims))
626 if ((cube & open) && (cube & open & ~ids) == 0 &&
627 !llvm::is_contained(moves, std::pair{cube & open, g}))
628 moves.push_back({cube & open, g});
629 llvm::stable_sort(moves, [](
auto a,
auto b) {
630 return llvm::popcount(a.first) > llvm::popcount(b.first);
632 SmallVector<std::pair<uint64_t, size_t>> tried;
633 for (
auto [own, g] : moves) {
634 if (llvm::any_of(tried, [&, own = own, g = g](
auto t) {
635 return t.second == g && (t.first & own) == own;
638 tried.push_back({own, g});
639 int all = idMask, any = 0;
640 for (
int id = 0;
id <= idMask; ++id)
641 if ((own >>
id) & 1) {
645 int mask = idMask & ~(all ^ any);
646 rules.push_back({mask, all, g});
647 if (self(self, taken | own, left - 1))
651 dead.insert({taken, left});
654 for (
size_t length = 1; length <= budget; ++length)
655 if (search(search, taken, length))
663static SmallVector<PortRule> portRules(ArrayRef<GroupClaims> groups,
664 ArrayRef<std::pair<int, int>> existing,
665 int idBits,
size_t slots) {
666 const int idMask = (1 << idBits) - 1;
667 SmallVector<PortRule> rules;
668 for (
auto [g, group] : llvm::enumerate(groups)) {
669 SmallVector<std::pair<int, int>> avoid(existing);
670 for (
auto [o, other] : llvm::enumerate(groups)) {
673 avoid.append(other.stated.begin(), other.stated.end());
674 for (
int id : other.derived)
675 avoid.push_back({idMask,
id});
677 for (
auto [mask, value] :
678 groupRules(group.stated, group.derived, avoid, idBits))
679 rules.push_back({mask, value, g});
681 if (existing.size() + rules.size() <= slots || existing.size() >= slots)
683 if (std::optional<SmallVector<PortRule>> ordered =
684 orderedRules(groups, existing, idBits, slots - existing.size()))
689static SmallVector<PortRule>
690slavePortRules(ArrayRef<GroupClaims> groups,
691 ArrayRef<std::pair<int, int>> existing,
int idBits,
693 SmallVector<std::pair<int, int>> taken(existing);
694 SmallVector<PortRule> rules;
695 for (
bool priority : {
true,
false}) {
696 SmallVector<size_t, 4> members;
697 SmallVector<GroupClaims> claims;
698 for (
auto [g, group] : llvm::enumerate(groups))
699 if (group.priority == priority) {
700 members.push_back(g);
701 claims.push_back(group);
705 for (
const PortRule &r : portRules(claims, taken, idBits, slots))
706 rules.push_back({r.mask, r.value, members[r.group]});
707 for (
const PortRule &r : rules)
708 if (groups[r.group].priority == priority)
709 taken.push_back({r.mask, r.value});
720 SmallVector<Port, 4> masters;
727 std::map<std::pair<Port, int>,
int> slaveAmsels;
728 std::map<int, SmallVector<Port, 4>> amselMasters;
733template <
typename SlaveFlows>
734llvm::EquivalenceClasses<Port> tiedMasters(SlaveFlows &&flows) {
735 llvm::EquivalenceClasses<Port> tied;
736 for (
const SlaveFlow &f : flows)
737 for (
Port m : f.masters)
738 tied.unionSets(f.masters.front(), m);
751std::optional<ArbiterPlan>
752planArbiters(
const AIETargetModel &targetModel, ArrayRef<SlaveFlow> flows,
753 llvm::function_ref<
bool(
size_t,
size_t)> conflict,
754 llvm::function_ref<
bool(
size_t,
int)> excluded,
755 const std::set<int> &reservedAmsels,
756 SmallVectorImpl<std::pair<size_t, size_t>> &blocking) {
759 auto amselOf = [&](
int arbiter,
int msel) {
760 return arbiter + msel * numArbiters;
763 llvm::EquivalenceClasses<Port> tied = tiedMasters(flows);
766 SmallVector<size_t, 4> flows;
767 SmallVector<SmallVector<Port, 4>, 2> masterSets;
768 bool isPriority =
false;
770 std::vector<Unit> units;
771 std::map<Port, size_t> unitOf;
772 SmallVector<size_t, 8> flowUnit;
773 for (
auto [i, f] : llvm::enumerate(flows)) {
774 auto [it, inserted] =
775 unitOf.try_emplace(tied.getLeaderValue(f.masters.front()), 0);
777 it->second = units.size();
778 units.emplace_back();
780 Unit &unit = units[it->second];
781 flowUnit.push_back(it->second);
782 unit.flows.push_back(i);
783 unit.isPriority |= f.isPriority;
784 if (!llvm::is_contained(unit.masterSets, f.masters))
785 unit.masterSets.push_back(f.masters);
787 for (Unit &unit : units) {
788 auto *priorityEnd = std::stable_partition(
789 unit.masterSets.begin(), unit.masterSets.end(),
790 [&](
const SmallVector<Port, 4> &masters) {
791 return llvm::any_of(unit.flows, [&](size_t f) {
792 return flows[f].isPriority && flows[f].masters == masters;
795 std::sort(unit.masterSets.begin(), priorityEnd);
798 auto clash = [&](
size_t a,
size_t b) {
799 return flows[a].slave != flows[b].slave && conflict(a, b);
801 for (
const Unit &unit : units)
802 for (
auto [k, a] : llvm::enumerate(unit.flows))
803 for (
size_t b : llvm::drop_begin(unit.flows, k + 1))
805 blocking.push_back({a, b});
806 if (!blocking.empty())
809 std::vector<llvm::SmallDenseSet<size_t, 4>> neighbors(units.size());
810 SmallVector<std::pair<size_t, size_t>, 4> crossPairs;
811 for (
size_t a = 0; a < flows.size(); a++)
812 for (
size_t b = a + 1; b < flows.size(); b++)
813 if (flowUnit[a] != flowUnit[b] && clash(a, b)) {
814 neighbors[flowUnit[a]].insert(flowUnit[b]);
815 neighbors[flowUnit[b]].insert(flowUnit[a]);
816 crossPairs.push_back({a, b});
819 SmallVector<SmallVector<int, 4>, 6> freeMsels(numArbiters);
820 SmallVector<SmallVector<size_t, 4>, 6> excludedUnits(numArbiters);
821 for (
int a = 0; a < numArbiters; a++) {
822 for (
int m = 0; m < numMselsPerArbiter; m++)
823 if (!reservedAmsels.count(amselOf(a, m)))
824 freeMsels[a].push_back(m);
825 for (
size_t f = 0; f < flows.size(); f++)
826 if (excluded(f, a) && !llvm::is_contained(excludedUnits[a], flowUnit[f]))
827 excludedUnits[a].push_back(flowUnit[f]);
830 for (
const SmallVector<int, 4> &free : freeMsels)
831 mostFree = std::max(mostFree, free.size());
832 if (llvm::any_of(units, [&](
const Unit &unit) {
833 return unit.masterSets.size() > mostFree;
838 SmallVector<size_t, 8> order(units.size());
839 std::iota(order.begin(), order.end(), 0);
840 llvm::stable_sort(order, [&](
size_t a,
size_t b) {
841 return std::make_pair(neighbors[a].size(), units[a].masterSets.size()) >
842 std::make_pair(neighbors[b].size(), units[b].masterSets.size());
845 SmallVector<int, 8> arbiterOf(units.size(), -1);
846 SmallVector<size_t, 6> load(numArbiters, 0);
848 auto place = [&](
auto &self,
size_t depth) ->
bool {
849 if (depth == order.size())
852 LLVM_DEBUG(llvm::dbgs() <<
"Arbiter search gave up after "
856 size_t u = order[depth];
857 SmallVector<int, 6> candidates(numArbiters);
858 std::iota(candidates.begin(), candidates.end(), 0);
859 if (units[u].isPriority)
860 std::reverse(candidates.begin(), candidates.end());
862 llvm::stable_sort(candidates, [&](
int a,
int b) {
863 return load[a] + numMselsPerArbiter - freeMsels[a].size() <
864 load[b] + numMselsPerArbiter - freeMsels[b].size();
866 std::set<std::pair<size_t, SmallVector<size_t, 4>>> triedEmpty;
867 for (
int a : candidates) {
868 if (load[a] + units[u].masterSets.size() > freeMsels[a].size())
870 if (llvm::is_contained(excludedUnits[a], u) ||
871 llvm::any_of(neighbors[u],
872 [&](
size_t v) {
return arbiterOf[v] == a; }))
877 !triedEmpty.insert({freeMsels[a].size(), excludedUnits[a]}).second)
880 load[a] += units[u].masterSets.size();
881 if (self(self, depth + 1))
883 load[a] -= units[u].masterSets.size();
888 if (!place(place, 0)) {
889 blocking.append(crossPairs.begin(), crossPairs.end());
893 SmallVector<size_t, 8> mselOrder(units.size());
894 std::iota(mselOrder.begin(), mselOrder.end(), 0);
895 llvm::stable_sort(mselOrder, [&](
size_t a,
size_t b) {
896 if (!units[a].isPriority || !units[b].isPriority)
897 return units[a].isPriority > units[b].isPriority;
898 return units[a].masterSets.front() < units[b].masterSets.front();
901 SmallVector<size_t, 6> low(numArbiters, 0), high(numArbiters, 0);
902 for (
size_t u : mselOrder) {
903 const Unit &unit = units[u];
904 int a = arbiterOf[u];
905 std::map<SmallVector<Port, 4>,
int> setAmsel;
906 for (
const SmallVector<Port, 4> &masters : unit.masterSets) {
907 int msel = unit.isPriority
908 ? freeMsels[a][freeMsels[a].size() - 1 - high[a]++]
909 : freeMsels[a][low[a]++];
910 setAmsel[masters] = amselOf(a, msel);
911 plan.amselMasters[amselOf(a, msel)] = masters;
913 for (
size_t f : unit.flows)
914 plan.slaveAmsels[{flows[f].slave, flows[f].id}] =
915 setAmsel.at(flows[f].masters);
921SmallVector<TileID> cutTiles(
const AIETargetModel &targetModel,
TileID src,
923 auto neighbors = [&](
TileID t) {
924 SmallVector<TileID, 4> next;
925 for (
auto [bundle, dc, dr] : {std::tuple{WireBundle::North, 0, 1},
926 {WireBundle::South, 0, -1},
927 {WireBundle::East, 1, 0},
928 {WireBundle::West, -1, 0}}) {
929 TileID n{t.col + dc, t.row + dr};
930 if (n.col >= 0 && n.col < targetModel.
columns() && n.row >= 0 &&
931 n.row < targetModel.
rows() &&
937 auto reach = [&](std::optional<TileID> avoid) {
938 return breadthFirstParents(
941 SmallVector<TileID, 4> next = neighbors(t);
942 llvm::erase(next, avoid);
947 std::map<TileID, TileID> via = reach(std::nullopt);
950 SmallVector<TileID> interior;
951 for (
TileID t = via.at(dst); t != src; t = via.at(t))
952 interior.push_back(t);
953 llvm::erase_if(interior, [&](
TileID t) {
return reach(t).count(dst) > 0; });
959struct PrioritizedPackets {
960 std::set<std::tuple<TileID, Port, int>> packets;
961 bool contains(
const RoutedStream &s)
const {
966PrioritizedPackets prioritizedPackets(DeviceOp device) {
967 PrioritizedPackets prioritized;
968 for (PacketFlowOp flow : device.getOps<PacketFlowOp>())
969 if (flow.getPriorityRoute().value_or(
false))
970 for (
auto src : flow.getPorts().getOps<PacketSourceOp>())
971 prioritized.packets.insert(
972 {cast<TileOp>(src.getTile().getDefiningOp()).getTileID(),
973 src.port(), flow.IDInt()});
980SmallVector<size_t, 8> largestApartSet(
const StreamConflicts &conflicts,
981 ArrayRef<size_t> candidates,
983 SmallVector<size_t, 8> clique, best;
985 auto grow = [&](
auto &self, ArrayRef<size_t> cands) ->
void {
986 if (clique.size() > best.size())
988 for (
auto [k, s] : llvm::enumerate(cands)) {
990 clique.size() + cands.size() - k <= best.size())
992 SmallVector<size_t, 8> next;
993 for (
size_t t : cands.drop_front(k + 1))
1001 grow(grow, candidates);
1008std::map<TileID, std::set<int>> configuredAmsels(DeviceOp device) {
1009 const int numArbiters = device.getTargetModel().getNumArbiters();
1010 std::map<TileID, std::set<int>> amsels;
1011 for (
auto swboxOp : device.getOps<SwitchboxOp>())
1012 for (
auto amselOp : swboxOp.getConnections().getOps<AMSelOp>())
1013 amsels[swboxOp.getTileOp().getTileID()].insert(
1014 amselOp.arbiterIndex() + amselOp.getMselValue() * numArbiters);
1026std::optional<std::string>
1027tooFewArbiters(DeviceOp device,
const StreamConflicts &conflicts,
1028 llvm::function_ref<
bool(
TileID)> pinsHops,
1029 const PrioritizedPackets &prioritized) {
1030 const AIETargetModel &targetModel = device.getTargetModel();
1033 std::map<TileID, SmallVector<size_t, 8>> pinned;
1037 pinned[s.dst.tile].push_back(i);
1038 if (s.src.tile == s.dst.tile)
1040 bool circuitless = prioritized.contains(s);
1041 if (circuitless || pinsHops(s.src.tile))
1042 pinned[s.src.tile].push_back(i);
1043 for (
TileID t : cutTiles(targetModel, s.src.tile, s.dst.tile))
1044 if (circuitless || pinsHops(t))
1045 pinned[t].push_back(i);
1047 std::map<TileID, std::set<int>> reserved = configuredAmsels(device);
1049 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
1050 for (
const auto &[tileId, candidates] : pinned) {
1051 const std::set<int> &taken = reserved[tileId];
1053 for (
int a = 0; a < numArbiters; a++)
1054 free += llvm::any_of(llvm::seq(numMselsPerArbiter), [&](
int m) {
1055 return !taken.count(a + m * numArbiters);
1058 std::set<std::pair<TileID, Port>> srcs, dsts;
1059 for (
size_t s : candidates) {
1060 srcs.insert({streams[s].src.tile, streams[s].src.port});
1061 dsts.insert({streams[s].dst.tile, streams[s].dst.port});
1063 if (std::min(srcs.size(), dsts.size()) <= free)
1065 SmallVector<size_t, 8> best = largestApartSet(conflicts, candidates, free);
1066 if (best.size() <= free)
1070 llvm::raw_string_ostream os(reason);
1071 if (best.size() == 1) {
1072 os <<
"at tile (" << tileId.col <<
", " << tileId.row <<
"), "
1074 <<
" takes an arbiter whatever the routing, but the switchbox has "
1078 os <<
"at tile (" << tileId.col <<
", " << tileId.row <<
"), no two of ";
1080 best, os, [&](
size_t s) { os <<
describeStream(streams[s]); },
", ");
1081 os <<
" can share an arbiter, and each takes one there whatever the "
1082 "routing, but the switchbox has "
1083 << free <<
" free. For example, " << conflicts.
explain(best[0], best[1]);
1086 return std::nullopt;
1093 std::optional<Port> slave;
1099std::pair<std::map<TileID, SmallVector<Leaving, 8>>, std::set<TileID>>
1100leavingPinnedTrees(
const StreamConflicts &conflicts,
1102 const PrioritizedPackets &prioritized) {
1104 std::map<PathEndPoint, std::map<PathEndPoint, std::optional<PathEndPoint>>>
1106 for (
const auto &[src, hops] : pinnedTrees)
1107 for (
const TreeHop &hop : hops) {
1108 auto [it, first] = preds[src].try_emplace(hop.to, hop.from);
1109 if (!first && it->second && !(*it->second == hop.from))
1112 std::map<TileID, SmallVector<Leaving, 8>> leaving;
1113 std::set<TileID> onTrees;
1118 auto tree = prioritized.contains(s) ? preds.find({s.src.tile, s.src.port})
1120 SmallVector<std::pair<Port, PathEndPoint>, 8> hops;
1121 PathEndPoint at{s.dst.tile, s.dst.port}, src{s.src.tile, s.src.port};
1123 tree != preds.end() && !(at == src) && n <= tree->second.size(); n++) {
1124 auto pred = tree->second.find(at);
1125 if (pred == tree->second.end() || !pred->second)
1127 if (pred->second->coords == at.coords)
1128 hops.push_back({pred->second->port, at});
1132 leaving[s.dst.tile].push_back({i, std::nullopt, s.dst.port});
1135 for (
auto [slave, hop] : hops) {
1136 leaving[hop.coords].push_back({i, slave, hop.port});
1137 onTrees.insert(hop.coords);
1140 return {std::move(leaving), std::move(onTrees)};
1153std::string explainSharedArbiter(
TileID tileId,
size_t a,
size_t b,
1154 ArrayRef<ArbiterLink> chain,
1155 ArrayRef<Leaving> here,
1156 const StreamConflicts &conflicts) {
1157 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
1159 llvm::raw_string_ostream os(reason);
1160 os <<
"at tile (" << tileId.col <<
", " << tileId.row
1161 <<
"), the routes prioritized flows (priority_route) keep put "
1164 for (
auto [k, link] : llvm::enumerate(chain))
1165 os << (k == 0 ?
"" :
", which")
1166 << (link.rule ?
" takes one packet rule on " :
" leaves by ")
1169 os <<
", and a master port or packet rule takes one arbiter. "
1177std::optional<std::string> sharedArbiterAt(
TileID tileId,
1178 ArrayRef<Leaving> here,
1179 const StreamConflicts &conflicts) {
1180 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
1185 std::map<size_t, SmallVector<ArbiterLink, 4>> with;
1186 for (
auto [x, l] : llvm::enumerate(here))
1187 for (
auto [y, m] : llvm::enumerate(here)) {
1190 if (l.master == m.master)
1191 with[x].push_back({y, l.master,
false});
1192 else if (l.slave && m.slave && *l.slave == *m.slave &&
1193 streams[l.stream].packetID == streams[m.stream].packetID)
1194 with[x].push_back({y, *l.slave,
true});
1196 for (
size_t x : llvm::make_first_range(with)) {
1198 std::map<size_t, size_t> via = breadthFirstParents(x, [&](
size_t u) {
1199 return llvm::map_range(
1200 with.at(u), [](
const ArbiterLink &link) { return link.visit; });
1202 size_t a = here[x].stream;
1203 for (
size_t y : llvm::make_first_range(via)) {
1204 size_t b = here[y].stream;
1207 SmallVector<ArbiterLink, 4> chain;
1208 for (
size_t v = y; v != x; v = via.at(v))
1210 *llvm::find_if(with.at(via.at(v)), [&](
const ArbiterLink &link) {
1211 return link.visit == v;
1213 std::reverse(chain.begin(), chain.end());
1214 return explainSharedArbiter(tileId, a, b, chain, here, conflicts);
1217 return std::nullopt;
1224llvm::Error unroutableArbiters(DeviceOp device,
1225 const StreamConflicts &conflicts,
1226 llvm::function_ref<
bool(
TileID)> pinsHops,
1228 PrioritizedPackets prioritized = prioritizedPackets(device);
1229 if (std::optional<std::string> reason =
1230 tooFewArbiters(device, conflicts, pinsHops, prioritized))
1231 return llvm::make_error<RoutingFailure>(std::move(*reason));
1232 if (pinnedTrees.empty())
1233 return llvm::Error::success();
1234 auto [leaving, onTrees] =
1235 leavingPinnedTrees(conflicts, pinnedTrees, prioritized);
1236 for (
TileID tileId : onTrees)
1237 if (std::optional<std::string> reason =
1238 sharedArbiterAt(tileId, leaving[tileId], conflicts))
1239 return llvm::make_error<PinnedRoutingFailure>(std::move(*reason));
1240 return llvm::Error::success();
1244static void getOrCreateConnect(OpBuilder &builder, ShimMuxOp shimMux,
1245 Location loc, WireBundle srcBundle,
int srcCh,
1246 WireBundle destBundle,
int destCh) {
1247 for (
auto connect : shimMux.getConnections().getOps<ConnectOp>())
1248 if (connect.getSourceBundle() == srcBundle &&
1249 connect.getSourceChannel() == srcCh &&
1250 connect.getDestBundle() == destBundle &&
1251 connect.getDestChannel() == destCh)
1253 ConnectOp::create(builder, loc, srcBundle, srcCh, destBundle, destCh);
1256static PathEndPoint sourceOf(FlowOp flow) {
1257 auto tile = cast<TileOp>(flow.getSource().getDefiningOp());
1258 return PathEndPoint{tile.getTileID(),
1259 {flow.getSourceBundle(), flow.getSourceChannel()}};
1265static void lowerShimDMAPorts(DeviceOp device, OpBuilder &builder,
1266 DynamicTileAnalysis &analyzer) {
1267 for (
auto switchbox : device.getOps<SwitchboxOp>()) {
1268 TileOp tileOp = switchbox.getTileOp();
1269 if (!tileOp.isShimNOCTile())
1271 auto connectInMux = [&](Location loc,
Port src,
Port dst) {
1272 builder.setInsertionPointAfter(tileOp);
1273 ShimMuxOp shimMux = analyzer.
getShimMux(builder, tileOp.colIndex());
1274 builder.setInsertionPointToStart(&shimMux.getConnections().front());
1275 getOrCreateConnect(builder, shimMux, loc, src.bundle, src.channel,
1276 dst.bundle, dst.channel);
1278 for (Operation &op : switchbox.getConnections().front()) {
1279 if (
auto rules = dyn_cast<PacketRulesOp>(op);
1280 rules && rules.getSourceBundle() == WireBundle::DMA) {
1282 connectInMux(rules.getLoc(), rules.sourcePort(),
1283 {WireBundle::North, ch});
1284 rules.setSourceBundle(WireBundle::South);
1285 rules.setSourceChannel(ch);
1287 if (
auto masterSet = dyn_cast<MasterSetOp>(op);
1288 masterSet && masterSet.getDestBundle() == WireBundle::DMA) {
1290 connectInMux(masterSet.getLoc(), {WireBundle::North, ch},
1291 masterSet.destPort());
1292 masterSet.setDestBundle(WireBundle::South);
1293 masterSet.setDestChannel(ch);
1303 LogicalResult emit(DeviceOp device, OpBuilder &builder,
1304 DynamicTileAnalysis &analyzer)
const;
1305 SmallVector<std::pair<int, int>, 2>
1306 statedCubes(
const std::pair<PhysPort, int> &slaveFlow,
int idMask)
const;
1310 std::optional<HoldCycle> sharedReceiverCycle;
1314 std::map<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>> packetFlows;
1315 SmallVector<std::pair<PhysPort, int>, 4> slavePorts;
1318 std::map<std::pair<PhysPort, int>, SmallVector<Location, 1>> slaveFlowLocs;
1320 DenseMap<Location, unsigned> flowLocOrder;
1322 Location locOf(ArrayRef<std::pair<PhysPort, int>> slaveFlows,
1323 Location fallback)
const;
1325 DenseMap<PhysPort, BoolAttr> keepPktHeaderAttr;
1328 DenseSet<std::pair<PhysPort, int>> prioritizedFlows;
1329 DenseSet<std::pair<PhysPort, int>> markedFlows;
1330 DenseSet<PhysPort> priorityMasterPorts;
1336 std::map<std::pair<PhysPort, int>, std::set<int>> pinnedMasks;
1347 std::map<TileID, SmallVector<Connect, 4>> circuitHops;
1348 std::map<TileID, ArbiterPlan> plans;
1353struct PacketFlowRouting : PacketPlan {
1354 using FlowKey = std::pair<Port, int>;
1356 PacketFlowRouting(DeviceOp device,
const Routing &routing,
1357 const StreamConflicts &conflicts,
1358 const PinnedRoutes &pinned,
bool routeCircuit,
1359 bool circuitSwitchHops,
bool prioritize)
1360 : device(device), routing(routing), conflicts(conflicts), pinned(pinned),
1361 prioritize(prioritize), routeCircuit(routeCircuit),
1362 circuitSwitchHops(circuitSwitchHops),
1364 numArbiters(targetModel.getNumArbiters()),
1365 numMselsPerArbiter(targetModel.getNumMselsPerArbiter()),
1366 maxPacketId(targetModel.getMaxPacketId()),
1367 idBits(llvm::Log2_32_Ceil(maxPacketId + 1)), idMask((1 << idBits) - 1) {
1370 llvm::Expected<PacketPlan> plan();
1371 void collectFlows();
1372 void findCircuitHops();
1373 void collectSlaveFlows();
1376 std::optional<std::string> breakHoldCycles();
1377 RoutingFaults faults()
const;
1380 std::optional<int>
id = std::nullopt);
1381 std::optional<std::pair<size_t, size_t>>
1382 conflictingStreams(
TileID tileId,
const SlaveFlow &a,
const SlaveFlow &b);
1383 std::optional<ArbiterPlan>
1384 planTile(
TileID tileId, SmallVectorImpl<std::pair<size_t, size_t>> &blocking);
1385 void moveFlow(
TileID tileId,
FlowKey key,
bool priority =
false);
1387 void splitFlow(
TileID tileId,
FlowKey key, ArrayRef<FlowKey> partners);
1390 const Routing &routing;
1391 const StreamConflicts &conflicts;
1392 const PinnedRoutes &pinned;
1396 bool circuitSwitchHops;
1397 const AIETargetModel &targetModel;
1398 const int numArbiters;
1399 const int numMselsPerArbiter;
1400 const uint32_t maxPacketId;
1409 PathEndPoint src, dst;
1411 bool operator<(
const StreamEnds &rhs)
const {
1412 return std::tie(src, dst,
id) < std::tie(rhs.src, rhs.dst, rhs.id);
1415 std::map<StreamEnds, SmallVector<size_t, 1>> packetStreamIndex;
1416 DenseMap<std::pair<PhysPort, int>, SmallVector<size_t, 2>> slaveFlowStreams;
1420 std::map<std::pair<PhysPort, int>, std::map<PathEndPoint, std::set<Port>>>
1424 std::vector<SmallVector<StreamHop, 8>> routes;
1426 std::set<PathEndPoint> circuitSources;
1430 std::map<TileID, SmallVector<std::pair<Connect, int>, 8>> switchboxes;
1434 std::map<TileID, std::set<int>> reservedAmsels;
1438 std::map<TileID, std::map<FlowKey, SlaveFlow>> tileSlaveFlows;
1439 std::map<TileID, SmallVector<SlaveFlow, 8>> tileFlows;
1442 std::set<std::tuple<TileID, FlowKey, FlowKey>> apart;
1443 std::set<std::tuple<TileID, FlowKey, int>> offArbiter;
1444 std::set<std::pair<TileID, Connect>> hazardConnections;
1445 std::set<std::pair<TileID, Connect>> priorityConnections;
1450 std::set<TreeSplit> hazardSplits;
1451 std::set<std::pair<PathEndPoint, PathEndPoint>> hazardApart;
1452 std::set<std::pair<PathEndPoint, PathEndPoint>> hazardTogether;
1453 std::set<PathEndPoint> hazardUntrunk;
1454 std::set<TileID> crowdedTiles;
1455 std::optional<std::string> planFailure;
1456 std::optional<std::pair<Location, std::string>> incomplete;
1460void PacketFlowRouting::collectFlows() {
1461 for (
auto [i, s] : llvm::enumerate(conflicts.
getStreams()))
1463 packetStreamIndex[{{s.src.tile, s.src.port},
1464 {s.dst.tile, s.dst.port},
1467 for (
const RoutedStream &s : conflicts.
getStreams())
1468 routes.push_back(s.hops);
1471 for (FlowOp flow : device.getOps<FlowOp>())
1472 circuitSources.insert(sourceOf(flow));
1474 for (PacketFlowOp pktFlowOp : device.getOps<PacketFlowOp>()) {
1475 Region &r = pktFlowOp.getPorts();
1476 Block &b = r.front();
1477 int flowID = pktFlowOp.IDInt();
1478 SmallVector<std::pair<TileID, Port>, 4> sources;
1481 for (Operation &Op : b.getOperations()) {
1482 if (
auto pktSource = dyn_cast<PacketSourceOp>(Op)) {
1483 auto srcTile = cast<TileOp>(pktSource.getTile().getDefiningOp());
1485 {{srcTile.colIndex(), srcTile.rowIndex()}, pktSource.port()});
1490 for (Operation &Op : b.getOperations()) {
1491 auto pktDest = dyn_cast<PacketDestOp>(Op);
1494 auto destTile = cast<TileOp>(pktDest.getTile().getDefiningOp());
1495 Port destPort = pktDest.port();
1496 TileID destCoords = {destTile.colIndex(), destTile.rowIndex()};
1498 auto keep = pktFlowOp.getKeepPktHeader();
1499 keepPktHeaderAttr[{destTile.getTileID(), destPort}] =
1500 keep ? BoolAttr::get(Op.getContext(), *keep) : nullptr;
1502 for (
auto &[srcCoords, srcPort] : sources) {
1503 TileID srcSB = {srcCoords.col, srcCoords.row};
1504 PathEndPoint srcPoint = {srcSB, srcPort};
1505 if (circuitSources.count(srcPoint))
1510 SmallVector<size_t, 1> flowStreams;
1511 if (
auto stream = packetStreamIndex.find(
1512 {srcPoint, {destCoords, destPort}, flowID});
1513 stream != packetStreamIndex.end()) {
1514 int mask =
static_cast<int>(pktFlowOp.getMask().value_or(~0));
1515 for (
size_t i : stream->second)
1517 conflicts.
getStreams()[i].packetMask == mask)
1518 flowStreams.push_back(i);
1519 if (flowStreams.empty())
1520 flowStreams.push_back(stream->second.front());
1522 for (
size_t stream : flowStreams) {
1523 SmallVector<StreamHop, 8> &hops = routes[stream];
1526 size_t connections = 0;
1527 for (
const auto &[_, setting] : settings)
1528 connections += setting.srcs.size();
1529 std::optional<std::pair<TileID, Port>> at{{srcSB, srcPort}};
1530 while (at && hops.size() <= connections) {
1531 auto [tile, input] = *at;
1532 hops.push_back({tile, input, std::nullopt});
1534 auto setting = settings.find(tile);
1535 if (setting == settings.end())
1537 for (
auto [src, dest] :
1538 llvm::zip(setting->second.srcs, setting->second.dsts))
1539 if (src == input && !(tile == destCoords && dest == destPort) &&
1540 findPathToDest(settings, tile, dest, destCoords, destPort)) {
1541 at = linkedInput(tile, dest);
1547 bool srcRouted =
false;
1549 for (
const auto &[curr, setting] : settings) {
1550 assert(setting.srcs.size() == setting.dsts.size());
1551 TileID currTile = {curr.col, curr.row};
1552 for (
size_t i = 0; i < setting.srcs.size(); i++) {
1553 Port src = setting.srcs[i];
1554 Port dest = setting.dsts[i];
1556 if (!findPathToDest(settings, currTile, dest, destCoords, destPort))
1558 if (currTile == srcSB && src.bundle == srcPort.bundle &&
1559 src.channel == srcPort.channel)
1561 Connect connect = {{src.bundle, src.channel},
1562 {dest.bundle, dest.channel}};
1564 switchboxes[currTile].begin(), switchboxes[currTile].end(),
1565 std::pair{connect, flowID}) == switchboxes[currTile].end())
1566 switchboxes[currTile].push_back({connect, flowID});
1569 PhysPort slavePort = {currTile, {src.bundle, src.channel}};
1570 std::pair<PhysPort, int> slaveFlow = {slavePort, flowID};
1571 for (
size_t stream : flowStreams)
1572 if (!llvm::is_contained(slaveFlowStreams[slaveFlow], stream))
1573 slaveFlowStreams[slaveFlow].push_back(stream);
1574 slaveFlowSources[slaveFlow][srcPoint].insert(dest);
1575 if (!llvm::is_contained(slaveFlowLocs[slaveFlow],
1576 pktFlowOp.getLoc()))
1577 slaveFlowLocs[slaveFlow].push_back(pktFlowOp.getLoc());
1578 flowLocOrder.try_emplace(pktFlowOp.getLoc(), flowLocOrder.size());
1579 if (std::optional<uint8_t> mask = pktFlowOp.getMask()) {
1580 pinnedMasks[{{currTile, {src.bundle, src.channel}}, flowID}]
1583 if (pktFlowOp.getPriorityRoute().value_or(
false)) {
1584 markedFlows.insert(slaveFlow);
1585 if (prioritize || pinned.trees.count(srcPoint)) {
1586 prioritizedFlows.insert(slaveFlow);
1591 if (!srcRouted && !incomplete)
1595 " could not be routed to destination " +
1597 "; the pathfinder produced an incomplete routing for this "
1604Location PacketPlan::locOf(ArrayRef<std::pair<PhysPort, int>> slaveFlows,
1605 Location fallback)
const {
1606 SmallVector<Location, 2> locs;
1607 for (
const auto &flow : slaveFlows)
1608 if (
auto found = slaveFlowLocs.find(flow); found != slaveFlowLocs.end())
1609 for (Location loc : found->second)
1610 if (!llvm::is_contained(locs, loc))
1611 locs.push_back(loc);
1614 llvm::sort(locs, [&](Location a, Location b) {
1615 return flowLocOrder.lookup(a) < flowLocOrder.lookup(b);
1617 return FusedLoc::get(fallback.getContext(), locs);
1623SmallVector<std::pair<int, int>, 2>
1624PacketPlan::statedCubes(
const std::pair<PhysPort, int> &slaveFlow,
1626 SmallVector<std::pair<int, int>, 2> cubes;
1627 auto it = pinnedMasks.find(slaveFlow);
1628 if (it != pinnedMasks.end())
1629 for (
int mask : it->second)
1631 cubes.push_back({mask, slaveFlow.second});
1635const SwitchSettings &PacketFlowRouting::settingsOf(
const PathEndPoint &src,
1636 std::optional<int>
id) {
1638 if (
auto own = routing.
idSettings.find({src, *id});
1641 auto it = routing.
settings.find(src);
1642 return it == routing.
settings.end() ? noSettings : it->second;
1645void PacketFlowRouting::findCircuitHops() {
1648 reservedAmsels = configuredAmsels(device);
1651 std::set<PhysPort> claimedSlaves, claimedMasters;
1652 for (
auto swbox : device.getOps<SwitchboxOp>()) {
1653 TileID tileId = swbox.getTileOp().getTileID();
1654 Region &ops = swbox.getConnections();
1655 for (
auto connect : ops.getOps<ConnectOp>()) {
1656 claimedSlaves.insert({tileId, connect.sourcePort()});
1657 claimedMasters.insert({tileId, connect.destPort()});
1659 for (
auto rules : ops.getOps<PacketRulesOp>())
1660 claimedSlaves.insert({tileId, rules.sourcePort()});
1661 for (
auto masterSet : ops.getOps<MasterSetOp>())
1662 claimedMasters.insert({tileId, masterSet.destPort()});
1666 for (FlowOp flow : device.getOps<FlowOp>())
1667 for (
const auto &[tileId, setting] : settingsOf(sourceOf(flow))) {
1668 for (
Port p : setting.srcs)
1669 claimedSlaves.insert({tileId, p});
1670 for (
Port p : setting.dsts)
1671 claimedMasters.insert({tileId, p});
1673 for (
auto &[tileId, connects] : switchboxes) {
1674 if (!circuitSwitchHops ||
1677 std::set<Port> masters;
1678 for (
const auto &[conn, flowID] : connects)
1679 masters.insert(conn.dst);
1680 SmallVector<Port, 8> slaves;
1681 for (
const auto &[conn, flowID] : connects)
1682 if (!llvm::is_contained(slaves, conn.src))
1683 slaves.push_back(conn.src);
1684 const std::set<int> &reserved = reservedAmsels[tileId];
1685 size_t freeArbiters = llvm::count_if(llvm::seq(0, numArbiters), [&](
int a) {
1686 return llvm::none_of(llvm::seq(0, numMselsPerArbiter), [&](
int m) {
1687 return reserved.count(a + m * numArbiters);
1690 for (
Port slave : slaves) {
1691 if (masters.size() <= freeArbiters)
1693 std::set<Port> reached;
1694 std::map<int, std::set<Port>> reachedByID;
1695 for (
const auto &[conn, flowID] : connects)
1696 if (conn.src == slave) {
1697 reached.insert(conn.dst);
1698 reachedByID[flowID].insert(conn.dst);
1701 !claimedSlaves.count({tileId, slave}) &&
1702 llvm::all_of(reached,
1703 [&, tileId = tileId](
Port m) {
1705 !claimedMasters.count({tileId, m});
1709 [&](
const auto &ids) {
return ids.second == reached; }) &&
1710 llvm::all_of(connects, [&, tileId = tileId](
const auto &entry) {
1711 const auto &[other, otherID] = entry;
1712 if (!reached.count(other.dst))
1714 return other.src == slave &&
1715 !prioritizedFlows.contains({{tileId, slave}, otherID});
1719 for (
Port m : reached) {
1720 circuitHops[tileId].push_back({slave, m});
1723 llvm::erase_if(connects, [&](
const auto &entry) {
1724 return entry.first.src == slave;
1730void PacketFlowRouting::collectSlaveFlows() {
1731 LLVM_DEBUG(llvm::dbgs() <<
"Check switchboxes\n");
1733 for (
const auto &[tileId, connects] : switchboxes) {
1734 LLVM_DEBUG(llvm::dbgs() <<
"***switchbox*** " << tileId.col <<
" "
1735 << tileId.row <<
'\n');
1736 for (
const auto &[conn, flowID] : connects) {
1737 Port sourcePort = conn.src;
1738 Port destPort = conn.dst;
1740 std::make_pair(std::make_pair(tileId, sourcePort), flowID);
1741 packetFlows[sourceFlow].push_back({tileId, destPort});
1742 if (markedFlows.contains(sourceFlow))
1743 priorityMasterPorts.insert({tileId, destPort});
1744 slavePorts.push_back(sourceFlow);
1745 LLVM_DEBUG(llvm::dbgs() <<
"flowID " << flowID <<
':'
1746 << stringifyWireBundle(sourcePort.bundle) <<
" "
1747 << sourcePort.channel <<
" -> "
1748 << stringifyWireBundle(destPort.bundle) <<
" "
1749 << destPort.channel <<
"\n");
1753 for (
const auto &[flow, dests] : packetFlows) {
1754 SlaveFlow &f = tileSlaveFlows[flow.first.first]
1755 .try_emplace({flow.first.second, flow.second},
1756 SlaveFlow{flow.first.second,
1759 prioritizedFlows.contains(flow)})
1762 if (!llvm::is_contained(f.masters, dest.second))
1763 f.masters.push_back(dest.second);
1764 llvm::sort(f.masters);
1766 for (
const auto &[tileId, byFlow] : tileSlaveFlows)
1767 for (
const auto &[key, f] : byFlow)
1768 tileFlows[tileId].push_back(f);
1771std::optional<std::pair<size_t, size_t>>
1772PacketFlowRouting::conflictingStreams(
TileID tileId,
const SlaveFlow &a,
1773 const SlaveFlow &b) {
1774 auto as = slaveFlowStreams.find({{tileId, a.slave}, a.id});
1775 auto bs = slaveFlowStreams.find({{tileId, b.slave}, b.id});
1776 if (as == slaveFlowStreams.end() || bs == slaveFlowStreams.end())
1777 return std::nullopt;
1778 for (
size_t s : as->second)
1779 for (
size_t t : bs->second)
1781 return std::pair{s, t};
1782 return std::nullopt;
1785std::optional<ArbiterPlan> PacketFlowRouting::planTile(
1786 TileID tileId, SmallVectorImpl<std::pair<size_t, size_t>> &blocking) {
1787 ArrayRef<SlaveFlow> flows = tileFlows.at(tileId);
1788 auto key = [&](
size_t f) {
return FlowKey{flows[f].slave, flows[f].id}; };
1789 auto conflict = [&](
size_t a,
size_t b) {
1791 {tileId, std::min(key(a), key(b)), std::max(key(a), key(b))}) ||
1792 conflictingStreams(tileId, flows[a], flows[b]);
1794 auto excluded = [&](
size_t f,
int arbiter) {
1795 return offArbiter.count({tileId, key(f), arbiter}) > 0;
1797 SmallVector<size_t, 8> priority, others;
1798 for (
auto [i, f] : llvm::enumerate(flows))
1799 (f.isPriority ? priority : others).push_back(i);
1800 if (priority.empty() || (others.empty() && pinned.rules.empty()))
1801 return planArbiters(targetModel, flows, conflict, excluded,
1802 reservedAmsels[tileId], blocking);
1804 auto subset = [&](ArrayRef<size_t> members) {
1805 SmallVector<SlaveFlow, 8> sub;
1806 for (
size_t m : members)
1807 sub.push_back(flows[m]);
1810 auto keepPinned = [&]() -> std::optional<ArbiterPlan> {
1812 for (
size_t f : priority) {
1813 Port slave = flows[f].slave;
1814 if (slave.bundle == WireBundle::DMA &&
1817 auto rules = pinned.rules.find({tileId, slave});
1818 const std::tuple<int, int, int> *rule =
nullptr;
1819 if (rules != pinned.rules.end())
1820 rule = llvm::find_if(rules->second, [&](
const auto &r) {
1821 auto [mask, value, amsel] = r;
1822 return (flows[f].id & mask) == (value & mask);
1824 if (!rule || rule == rules->second.end())
1825 return std::nullopt;
1826 int amsel = std::get<2>(*rule);
1827 if (excluded(f, amsel % numArbiters) ||
1828 reservedAmsels[tileId].count(amsel) ||
1829 kept.amselMasters.try_emplace(amsel, flows[f].masters)
1830 .first->second != flows[f].masters)
1831 return std::nullopt;
1832 kept.slaveAmsels[key(f)] = amsel;
1834 for (
auto [k, a] : llvm::enumerate(priority))
1835 for (
size_t b : llvm::drop_begin(priority, k + 1))
1836 if (kept.slaveAmsels.at(key(a)) % numArbiters ==
1837 kept.slaveAmsels.at(key(b)) % numArbiters &&
1838 flows[a].slave != flows[b].slave && conflict(a, b)) {
1839 return std::nullopt;
1843 SmallVector<std::pair<size_t, size_t>, 4> stuck;
1844 std::optional<ArbiterPlan> plan;
1845 if (!pinned.rules.empty())
1846 plan = keepPinned();
1848 plan = planArbiters(
1849 targetModel, subset(priority),
1850 [&](
size_t a,
size_t b) {
return conflict(priority[a], priority[b]); },
1851 [&](
size_t f,
int arbiter) {
return excluded(priority[f], arbiter); },
1852 reservedAmsels[tileId], stuck);
1854 for (
auto [a, b] : stuck)
1855 blocking.push_back({priority[a], priority[b]});
1856 return std::nullopt;
1862 auto around = [&](ArbiterPlan plan, ArrayRef<size_t> planned,
1863 ArrayRef<size_t> pending,
1864 SmallVectorImpl<std::pair<size_t, size_t>> &blocking)
1865 -> std::optional<ArbiterPlan> {
1866 auto arbiterOf = [&](
size_t f) {
1867 return plan.slaveAmsels.at(key(f)) % numArbiters;
1869 SmallVector<size_t, 8> placed(planned), rest;
1870 for (
size_t f : pending) {
1871 auto same = llvm::find_if(plan.amselMasters, [&](
const auto &entry) {
1872 return entry.second == flows[f].masters;
1874 if (same == plan.amselMasters.end()) {
1878 for (
size_t g : placed)
1879 if (arbiterOf(g) == same->first % numArbiters &&
1880 flows[g].slave != flows[f].slave && conflict(f, g))
1881 blocking.push_back({g, f});
1882 if (excluded(f, same->first % numArbiters))
1883 blocking.push_back({planned.front(), f});
1884 plan.slaveAmsels[key(f)] = same->first;
1885 placed.push_back(f);
1887 if (!blocking.empty())
1888 return std::nullopt;
1894 llvm::EquivalenceClasses<Port> tied = tiedMasters(flows);
1895 std::map<Port, std::set<int>> tiedArbiters;
1896 std::set<int> reserved = reservedAmsels[tileId];
1897 for (
const auto &[amsel, masters] : plan.amselMasters) {
1898 reserved.insert(amsel);
1899 tiedArbiters[tied.getLeaderValue(masters.front())].insert(amsel %
1902 SmallVector<std::pair<size_t, size_t>, 4> stuck;
1903 std::optional<ArbiterPlan> rested = planArbiters(
1904 targetModel, subset(rest),
1905 [&](
size_t a,
size_t b) {
return conflict(rest[a], rest[b]); },
1906 [&](
size_t f,
int arbiter) {
1907 auto arbiters = tiedArbiters.find(
1908 tied.getLeaderValue(flows[rest[f]].masters.front()));
1909 return excluded(rest[f], arbiter) ||
1910 (arbiters != tiedArbiters.end() &&
1911 (arbiters->second.size() > 1 ||
1912 !arbiters->second.count(arbiter))) ||
1913 llvm::any_of(placed, [&](
size_t g) {
1914 return arbiterOf(g) == arbiter &&
1915 flows[g].slave != flows[rest[f]].slave &&
1916 conflict(rest[f], g);
1921 for (
auto [a, b] : stuck)
1922 blocking.push_back({rest[a], rest[b]});
1923 for (
size_t f : rest)
1924 for (
size_t g : placed)
1925 if (flows[g].slave != flows[f].slave && conflict(f, g))
1926 blocking.push_back({g, f});
1927 return std::nullopt;
1929 plan.slaveAmsels.insert(rested->slaveAmsels.begin(),
1930 rested->slaveAmsels.end());
1931 plan.amselMasters.insert(rested->amselMasters.begin(),
1932 rested->amselMasters.end());
1935 SmallVector<std::pair<size_t, size_t>, 4> first;
1936 if (std::optional<ArbiterPlan> placed =
1937 around(*plan, priority, others, first))
1940 llvm::EquivalenceClasses<Port> tied = tiedMasters(flows);
1941 SmallVector<size_t, 8> joint(priority), rest;
1942 for (
size_t f : others) {
1943 bool tiedToPriority = llvm::any_of(priority, [&](
size_t g) {
1944 return tied.isEquivalent(flows[g].masters.front(),
1945 flows[f].masters.front());
1947 (tiedToPriority ? joint : rest).push_back(f);
1950 std::optional<ArbiterPlan> together;
1951 if (joint.size() > priority.size())
1952 together = planArbiters(
1953 targetModel, subset(joint),
1954 [&](
size_t a,
size_t b) {
return conflict(joint[a], joint[b]); },
1955 [&](
size_t f,
int arbiter) {
return excluded(joint[f], arbiter); },
1956 reservedAmsels[tileId], stuck);
1958 blocking.append(first.begin(), first.end());
1959 return std::nullopt;
1961 return around(*together, joint, rest, blocking);
1964void PacketFlowRouting::moveFlow(
TileID tileId,
FlowKey key,
bool priority) {
1965 auto move = [&](
const Connect &c) {
1966 hazardConnections.insert({tileId, c});
1968 priorityConnections.insert({tileId, c});
1970 auto flows = tileSlaveFlows.find(tileId);
1971 if (flows != tileSlaveFlows.end())
1972 if (
auto f = flows->second.find(key); f != flows->second.end())
1973 for (
Port m : f->second.masters)
1974 move({key.first, m});
1975 for (
const Connect &hop : circuitHops[tileId])
1976 if (hop.src == key.first)
1982void PacketFlowRouting::moveUnit(
TileID tileId,
FlowKey key) {
1983 moveFlow(tileId, key);
1984 auto flows = tileSlaveFlows.find(tileId);
1985 if (flows == tileSlaveFlows.end())
1987 auto self = flows->second.find(key);
1988 if (self == flows->second.end())
1990 llvm::EquivalenceClasses<Port> tied =
1991 tiedMasters(llvm::make_second_range(flows->second));
1992 for (
const auto &[other, f] : flows->second)
1993 if (f.masters.size() > 1 &&
1994 tied.isEquivalent(f.masters.front(), self->second.masters.front()))
1995 moveFlow(tileId, other);
1998void PacketFlowRouting::splitFlow(
TileID tileId,
FlowKey key,
1999 ArrayRef<FlowKey> partners) {
2000 auto flows = tileSlaveFlows.find(tileId);
2001 if (flows == tileSlaveFlows.end())
2003 auto self = flows->second.find(key);
2004 if (self == flows->second.end())
2006 auto onlySource = [&](
FlowKey k) -> std::optional<PathEndPoint> {
2007 auto sources = slaveFlowSources.find({{tileId, k.first}, k.second});
2008 if (sources == slaveFlowSources.end() || sources->second.size() != 1)
2009 return std::nullopt;
2010 return sources->second.begin()->first;
2012 std::optional<PathEndPoint> src = onlySource(key);
2015 auto sharesMaster = [&](
const auto &f,
const std::set<Port> &ports) {
2016 return llvm::any_of(f.masters, [&](
Port m) { return ports.count(m); });
2018 std::set<Port> own(self->second.masters.begin(), self->second.masters.end());
2019 std::set<FlowKey> siblings;
2020 for (
const auto &[other, f] : flows->second)
2021 if (other != key && other.first == key.first && sharesMaster(f, own) &&
2022 onlySource(other) == src)
2023 siblings.insert(other);
2026 auto tied = [&](
FlowKey partner,
bool skipSiblings) {
2027 llvm::EquivalenceClasses<Port> classes =
2028 tiedMasters(llvm::make_filter_range(
2029 llvm::make_second_range(flows->second), [&](
const SlaveFlow &f) {
2030 FlowKey other{f.slave, f.id};
2031 return other != key && !(skipSiblings && siblings.count(other));
2033 auto unit = classes.members(flows->second.at(partner).masters.front());
2034 return std::set<Port>(unit.begin(), unit.end());
2036 auto split = [&](
FlowKey other,
2037 std::optional<PathEndPoint> apart = std::nullopt) {
2038 int a = key.second, b = other.second;
2039 if (!apart && a > b)
2041 hazardSplits.insert({*src, tileId, a, b, apart});
2043 for (
FlowKey partner : partners) {
2044 if (partner == key || !flows->second.count(partner))
2046 if (siblings.count(partner)) {
2050 std::set<Port> partnerUnit = tied(partner,
true);
2051 if (sharesMaster(self->second, partnerUnit))
2055 bool unjoined =
false;
2056 for (
FlowKey other : siblings)
2057 for (
Port m : flows->second.find(other)->second.masters)
2059 hazardConnections.insert({tileId, {other.first, m}});
2064 std::set<Port> unit = tied(partner,
false);
2065 for (
FlowKey other : siblings) {
2066 const SlaveFlow &f = flows->second.find(other)->second;
2067 if (!sharesMaster(f, unit))
2072 SmallVector<PathEndPoint, 2> apart;
2073 for (
Port m : f.masters)
2074 if (partnerUnit.count(m))
2075 apart.push_back({tileId, m});
2076 if (PathEndPoint{tileId, key.first} == *src || apart.empty() ||
2077 !llvm::all_of(apart, [&](
const PathEndPoint &p) {
2078 return packetStreamIndex.count({*src, p, other.second});
2083 for (
const PathEndPoint &p : apart)
2091llvm::Expected<PacketPlan> PacketFlowRouting::plan() {
2094 collectSlaveFlows();
2098 planFailure = breakHoldCycles();
2100 return llvm::make_error<RoutingFailure>(std::move(*planFailure), faults());
2101 return PacketPlan(std::move(*
this));
2104RoutingFaults PacketFlowRouting::faults()
const {
2105 RoutingFaults faults;
2106 faults.
connections.assign(hazardConnections.begin(), hazardConnections.end());
2107 faults.
splits.assign(hazardSplits.begin(), hazardSplits.end());
2108 faults.
crowded.assign(crowdedTiles.begin(), crowdedTiles.end());
2109 faults.
apart.assign(hazardApart.begin(), hazardApart.end());
2110 faults.
together.assign(hazardTogether.begin(), hazardTogether.end());
2111 faults.
untrunk.assign(hazardUntrunk.begin(), hazardUntrunk.end());
2113 hazardSplits.empty() && crowdedTiles.empty() &&
2114 hazardApart.empty() && hazardUntrunk.empty() &&
2115 hazardConnections == priorityConnections;
2119void PacketFlowRouting::checkRules() {
2122 std::map<PhysPort, SmallVector<std::pair<int, int>>> existingCubes;
2123 for (
auto swbox : device.getOps<SwitchboxOp>())
2124 for (
auto rules : swbox.getConnections().getOps<PacketRulesOp>())
2125 for (
auto rule : rules.getRules().getOps<PacketRuleOp>())
2126 existingCubes[{swbox.getTileOp().getTileID(), rules.sourcePort()}]
2127 .push_back({rule.maskInt(), rule.valueInt()});
2129 SmallVector<std::pair<int, int>, 4> stated;
2130 SmallVector<int, 4> derived;
2132 for (
const auto &[slaveFlow, sources] : slaveFlowSources) {
2133 const auto &[first, firstMasters] = *sources.begin();
2134 auto other = llvm::find_if(
2135 sources, [&, &firstMasters = firstMasters](
const auto &source) {
2136 return source.second != firstMasters;
2138 if (other == sources.end())
2140 const auto &[slavePort, id] = slaveFlow;
2141 TileID tileId = slavePort.first;
2142 moveFlow(tileId, {slavePort.second,
id});
2145 const PathEndPoint &second = other->first;
2146 planFailure = llvm::formatv(
2147 "at tile ({0}, {1}), packets with id {2} from {3} and {4} enter on {5} "
2148 "and leave by different ports; a switchbox routes on the id alone, so "
2149 "each source's packets would also go where the other's do.",
2154 for (
const auto &[tileId, byFlow] : tileSlaveFlows) {
2156 [&, tileId = tileId, &byFlow = byFlow](
2157 Port slave, std::optional<std::pair<PathEndPoint, Port>> without,
2159 SmallVector<SmallVector<Port, 4>> *groupMasters =
nullptr) {
2160 std::map<std::pair<bool, SmallVector<Port, 4>>, RuleGroup> groups;
2161 for (
const auto &[key, f] : byFlow) {
2162 if (f.slave != slave || (priorityOnly && !f.isPriority))
2164 SmallVector<Port, 4> masters = f.masters;
2165 auto sources = slaveFlowSources.find({{tileId, slave}, f.id});
2166 if (without && sources != slaveFlowSources.end()) {
2167 std::set<Port> kept;
2168 for (
const auto &[src, ports] : sources->second)
2169 for (
Port m : ports)
2170 if (!(src == without->first) || m != without->second)
2172 masters.assign(kept.begin(), kept.end());
2174 if (masters.empty())
2176 RuleGroup &group = groups[{f.isPriority, masters}];
2177 auto cubes = statedCubes({{tileId, f.slave}, f.id}, idMask);
2179 group.derived.push_back(f.id);
2180 for (
auto cube : cubes)
2181 if (!llvm::is_contained(group.stated, cube))
2182 group.stated.push_back(cube);
2184 SmallVector<GroupClaims> claims;
2185 for (
const auto &[masters, group] : groups) {
2186 claims.push_back({group.stated, group.derived, masters.first});
2188 groupMasters->push_back(masters.second);
2190 return slavePortRules(claims, existingCubes[{tileId, slave}], idBits,
2194 [&, tileId = tileId](
2196 std::optional<std::pair<PathEndPoint, Port>> without = {}) {
2197 return existingCubes[{tileId, slave}].size() +
2198 newRules(slave, without,
false).size();
2203 auto splitRules = [&, tileId = tileId, &byFlow = byFlow](
Port slave) {
2204 std::map<PathEndPoint, std::map<PathEndPoint, std::set<int>>> dstIds;
2205 for (
const auto &[key, f] : byFlow) {
2206 if (f.slave != slave)
2208 auto streams = slaveFlowStreams.find({{tileId, slave}, f.id});
2209 if (streams == slaveFlowStreams.end())
2211 for (
size_t i : streams->second) {
2212 const RoutedStream &s = conflicts.
getStreams()[i];
2217 std::optional<std::pair<PathEndPoint, PathEndPoint>> best;
2218 size_t fewest = rulesNeeded(slave);
2219 for (
const auto &[src, dsts] : dstIds)
2220 for (
const auto &[dst, ids] : dsts)
2221 if (dst.coords == tileId && dsts.size() > 1)
2222 if (
size_t n = rulesNeeded(slave, {{src, dst.port}}); n < fewest) {
2228 const auto &[src, apart] = *best;
2229 const std::set<int> &apartIds = dstIds[src][apart];
2230 for (
const auto &[dst, ids] : dstIds[src]) {
2231 auto carries = [&, &src = src, &dst = dst](
int id) {
2232 return packetStreamIndex.count({src, dst,
id}) > 0;
2234 auto b = llvm::find_if_not(apartIds, carries);
2235 if (!(dst == apart) && b != apartIds.end())
2236 hazardSplits.insert({src, tileId, *ids.begin(), *b, apart});
2243 auto claim = [&, tileId = tileId](
const SlaveFlow &f) {
2244 auto cubes = statedCubes({{tileId, f.slave}, f.id}, idMask);
2246 cubes.push_back({idMask, f.id});
2249 for (
auto a = byFlow.begin(); a != byFlow.end(); ++a)
2250 for (
auto b = std::next(a); b != byFlow.end(); ++b) {
2251 const SlaveFlow &fa = a->second, &fb = b->second;
2252 if (fa.slave != fb.slave || fa.masters == fb.masters)
2254 std::optional<std::pair<std::pair<int, int>, std::pair<int, int>>>
2256 for (
auto own : claim(fa))
2257 for (
auto other : claim(fb))
2258 if (!overlap && cubesIntersect(own, other))
2259 overlap = {own, other};
2262 auto [own, other] = *overlap;
2263 moveFlow(tileId, a->first);
2264 moveFlow(tileId, b->first);
2267 int witness = (own.second & own.first) |
2268 (other.second & other.first & ~own.first);
2269 planFailure = llvm::formatv(
2270 "at tile ({0}, {1}), packet flows through {2} claim rule (mask "
2271 "0x{3:X-}, id 0x{4:X-}) and rule (mask 0x{5:X-}, id 0x{6:X-}), "
2272 "which both match id 0x{7:X-}; widen one mask to carry both, or "
2273 "route them apart.",
2274 tileId.col, tileId.row,
describePort(fa.slave), own.first,
2275 own.second, other.first, other.second, witness);
2277 std::set<Port> slaves;
2278 for (
const auto &[key, f] : byFlow)
2279 slaves.insert(f.slave);
2280 std::set<Port> priorityMasters;
2281 std::set<SmallVector<Port, 4>> prioritySets;
2282 for (
const auto &[key, f] : byFlow)
2283 if (f.isPriority && !pinned.rules.empty()) {
2284 priorityMasters.insert(f.masters.begin(), f.masters.end());
2285 prioritySets.insert(f.masters);
2287 for (
const auto &[key, f] : byFlow) {
2288 const Port *shared = llvm::find_if(
2289 f.masters, [&](
Port m) { return priorityMasters.count(m); });
2290 if (f.isPriority || shared == f.masters.end() ||
2291 prioritySets.count(f.masters))
2293 moveFlow(tileId, key,
true);
2295 planFailure = llvm::formatv(
2296 "at tile ({0}, {1}), packets with id {2} entering on {3} leave by "
2297 "{4}, a master port of the prioritized flows, but not by one of "
2298 "their fixed master sets.",
2302 for (
Port slave : pinned.rules.empty() ? std::set<Port>{} : slaves) {
2303 SmallVector<SmallVector<Port, 4>> ruleMasters;
2304 SmallVector<PortRule> priority = newRules(slave, {},
true, &ruleMasters);
2305 for (
const auto &[key, f] : byFlow) {
2306 if (f.slave != slave || f.isPriority)
2308 for (
auto own : claim(f)) {
2310 llvm::find_if(priority, [&, &f = f](
const PortRule &r) {
2311 return cubesIntersect(own, {r.mask, r.value}) &&
2312 ruleMasters[r.group] != f.masters;
2314 if (rule == priority.end())
2316 moveFlow(tileId, key);
2318 planFailure = llvm::formatv(
2319 "at tile ({0}, {1}), the packet rule (mask 0x{2:X-}, id "
2320 "0x{3:X-}) of the prioritized flows on "
2321 "{4} also matches packet id 0x{5:X-}, and priority routing "
2322 "keeps their rules; use an id the rule does not match, "
2323 "or route the flow apart.",
2324 tileId.col, tileId.row, rule->mask, rule->value,
2330 for (
Port slave : slaves) {
2331 size_t needed = rulesNeeded(slave);
2334 for (
const auto &[key, f] : byFlow)
2335 if (f.slave == slave)
2336 moveFlow(tileId, key);
2337 if (!splitRules(slave))
2338 crowdedTiles.insert(tileId);
2341 planFailure = llvm::formatv(
2342 "at tile ({0}, {1}), the packet flows entering on {2} need {3} "
2343 "packet rules, and a slave port holds {4}.",
2350void PacketFlowRouting::planTiles() {
2351 for (
const auto &[tileId, flows] : tileFlows) {
2352 SmallVector<std::pair<size_t, size_t>, 4> blocking;
2353 if (std::optional<ArbiterPlan> plan = planTile(tileId, blocking)) {
2354 plans[tileId] = std::move(*plan);
2358 llvm::dbgs() <<
"No arbiter plan at tile (" << tileId.col <<
", "
2359 << tileId.row <<
"):\n";
2360 for (
const SlaveFlow &f : flows) {
2361 llvm::dbgs() <<
" " <<
describePort(f.slave) <<
" id " << f.id
2363 for (
Port m : f.masters)
2365 llvm::dbgs() <<
'\n';
2367 for (
auto [a, b] : blocking)
2368 llvm::dbgs() <<
" blocking " << a <<
' ' << b <<
'\n';
2371 llvm::raw_string_ostream os(reason);
2372 if (blocking.empty()) {
2375 for (
const SlaveFlow &f : flows)
2376 for (
const SlaveFlow &g : flows)
2377 if (f.slave == g.slave && f.id < g.id && f.masters != g.masters &&
2380 [&](
Port m) { return llvm::is_contained(g.masters, m); })) {
2381 FlowKey keys[] = {{f.slave, f.id}, {g.slave, g.id}};
2382 splitFlow(tileId, keys[0], keys);
2384 for (
const SlaveFlow &f : flows)
2385 moveFlow(tileId, {f.slave, f.id});
2386 crowdedTiles.insert(tileId);
2389 os <<
"at tile (" << tileId.col <<
", " << tileId.row
2390 <<
"), the packet flows need more arbiter msels than the switchbox "
2392 planFailure = std::move(reason);
2395 for (
auto [a, b] : blocking) {
2396 FlowKey keys[] = {{flows[a].slave, flows[a].id},
2397 {flows[b].slave, flows[b].id}};
2399 moveUnit(tileId, key);
2400 splitFlow(tileId, key, keys);
2405 std::optional<std::pair<size_t, size_t>> pair = conflictingStreams(
2406 tileId, flows[blocking.front().first], flows[blocking.front().second]);
2407 assert(pair && apart.empty() && offArbiter.empty() &&
2408 "before the hold-cycle search, only stream conflicts block a plan");
2409 auto [s, t] = *pair;
2412 <<
" can deadlock if they share an arbiter, and no routing found keeps "
2413 "them apart (last tried: tile ("
2414 << tileId.col <<
", " << tileId.row <<
")). " << conflicts.
explain(s, t);
2415 planFailure = std::move(reason);
2419std::optional<std::string> PacketFlowRouting::breakHoldCycles() {
2424 auto arbitrate = [&]() {
2428 for (StreamHop &hop : routes[s]) {
2429 hop.arbiter.reset();
2430 auto plan = plans.find(hop.tile);
2431 if (plan == plans.end())
2433 auto amsel = plan->second.slaveAmsels.find(
2434 {hop.input, *conflicts.
getStreams()[s].packetID});
2435 if (amsel != plan->second.slaveAmsels.end())
2436 hop.arbiter = amsel->second % numArbiters;
2440 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
2444 SmallVector<HoldCycle, 2> cycles;
2446 bool forcedWaits =
false, definite =
false;
2447 auto search = [&](
auto &self) ->
bool {
2449 std::optional<HoldCycle> cycle =
2450 conflicts.
holdCycle(routes, forcedWaits, definite);
2453 if (!cycle && definite)
2457 cycles.push_back(*cycle);
2458 LLVM_DEBUG(llvm::dbgs()
2459 <<
"Hold cycle: " << conflicts.
explain(*cycle) <<
'\n');
2460 for (
const HoldCycle::Step &step : cycle->steps) {
2461 if (step.
wait != HoldCycle::Wait::Arbiter || step.
forced)
2465 bool sharerNew = step.
sharer < numRequested;
2466 bool holderNew = step.
holding < numRequested;
2467 std::optional<std::tuple<TileID, FlowKey, FlowKey>> pair;
2468 std::optional<std::tuple<TileID, FlowKey, int>> off;
2469 if (sharerNew && holderNew)
2470 pair = {step.
tile, std::min(sharer, holder), std::max(sharer, holder)};
2471 else if (sharerNew || holderNew)
2472 off = {step.
tile, sharerNew ? sharer : holder, step.
arbiter};
2477 if ((pair && !apart.insert(*pair).second) ||
2478 (off && !offArbiter.insert(*off).second))
2481 ArbiterPlan saved = plans.at(step.
tile);
2482 SmallVector<std::pair<size_t, size_t>, 4> blocking;
2483 if (std::optional<ArbiterPlan> plan = planTile(step.
tile, blocking)) {
2484 plans[step.
tile] = std::move(*plan);
2487 plans[step.
tile] = std::move(saved);
2492 offArbiter.erase(*off);
2500 for (
const HoldCycle &cycle : cycles)
2501 for (
const HoldCycle::Step &step : cycle.steps) {
2502 if (step.
wait == HoldCycle::Wait::Arbiter && step.
forced &&
2503 step.
sharer < numRequested && step.
holding < numRequested &&
2504 streams[step.
sharer].packetID && streams[step.
holding].packetID) {
2505 PathEndPoint a{streams[step.
sharer].src.tile,
2506 streams[step.
sharer].src.port},
2507 b{streams[step.
holding].src.tile, streams[step.
holding].src.port};
2509 hazardTogether.insert(std::minmax(a, b));
2511 if (step.
wait != HoldCycle::Wait::Drain && !step.
forced) {
2512 SmallVector<FlowKey, 3> keys;
2516 if (std::optional<int>
id = streams[s].packetID;
2517 s < numRequested && id)
2518 keys.push_back({input, *id});
2520 moveUnit(step.
tile, key);
2521 splitFlow(step.
tile, key, keys);
2526 if (behind >= numRequested || step.
holding >= numRequested ||
2527 !streams[behind].packetID || !streams[step.
holding].packetID)
2529 PathEndPoint a{streams[behind].src.tile, streams[behind].src.port},
2530 b{streams[step.
holding].src.tile, streams[step.
holding].src.port};
2533 hazardApart.insert(std::minmax(a, b));
2534 else if (step.
wait == HoldCycle::Wait::Arbiter)
2535 hazardUntrunk.insert(a);
2539 if (!search(search)) {
2541 return "packet flows can deadlock holding arbiters across "
2542 "switchboxes, and no arbiter assignment found avoids it. " +
2543 conflicts.
explain(cycles.front());
2552 if (!search(search)) {
2553 LLVM_DEBUG(llvm::dbgs() <<
"Hold cycle at a shared receiver: "
2554 << conflicts.
explain(cycles.front()) <<
'\n');
2558 if (!search(search)) {
2560 sharedReceiverCycle = cycles.front();
2563 return std::nullopt;
2566LogicalResult PacketPlan::emit(DeviceOp device, OpBuilder &builder,
2567 DynamicTileAnalysis &analyzer)
const {
2568 const AIETargetModel &targetModel = device.getTargetModel();
2572 const int idBits = llvm::Log2_32_Ceil(maxPacketId + 1);
2573 const int idMask = (1 << idBits) - 1;
2574 AIEDialect::PriorityRouteAttrHelper priority(builder.getContext());
2575 UnitAttr unit = builder.getUnitAttr();
2583 std::map<std::pair<TileID, int>, SmallVector<Port, 4>> masterAMSels;
2584 DenseMap<std::pair<PhysPort, int>,
int> slaveAMSels;
2585 for (
const auto &[tileId, plan] : plans) {
2586 for (
const auto &[amsel, masters] : plan.amselMasters)
2587 masterAMSels[{tileId, amsel}].append(masters.begin(), masters.end());
2588 for (
const auto &[flow, amsel] : plan.slaveAmsels)
2589 slaveAMSels[{{tileId, flow.first}, flow.second}] = amsel;
2594 std::map<PhysPort, SmallVector<int, 4>> mastersets;
2595 for (
const auto &[tileAmsel, ports] : masterAMSels)
2596 for (
Port port : ports)
2597 mastersets[{tileAmsel.first, port}].push_back(tileAmsel.second);
2600 llvm::dbgs() <<
"CHECK mastersets\n";
2601 for (
const auto &[master, values] : mastersets) {
2602 llvm::dbgs() <<
"master " << master.first <<
" "
2603 << stringifyWireBundle(master.second.bundle) <<
" : "
2604 << master.second.channel <<
'\n';
2605 for (
int value : values)
2606 llvm::dbgs() <<
"amsel: " << value <<
'\n';
2612 SmallVector<SmallVector<std::pair<PhysPort, int>, 4>, 4> slaveGroups;
2613 SmallVector<std::pair<PhysPort, int>, 4> workList(slavePorts);
2614 while (!workList.empty()) {
2615 std::pair<PhysPort, int> slave = workList.pop_back_val();
2616 const SmallVector<PhysPort, 4> &dests = packetFlows.at(slave);
2617 auto *group = llvm::find_if(slaveGroups, [&](
const auto &candidate) {
2618 const SmallVector<PhysPort, 4> &groupDests =
2619 packetFlows.at(candidate.front());
2620 return candidate.front().first.second == slave.first.second &&
2621 prioritizedFlows.contains(candidate.front()) ==
2622 prioritizedFlows.contains(slave) &&
2623 groupDests.size() == dests.size() &&
2624 llvm::all_of(dests, [&](
const PhysPort &dest) {
2625 return llvm::is_contained(groupDests, dest);
2628 if (group != slaveGroups.end())
2629 group->push_back(slave);
2631 slaveGroups.push_back({slave});
2636 SmallVector<SmallVector<std::pair<int, int>, 4>, 4> statedRules(
2637 slaveGroups.size());
2638 SmallVector<SmallVector<int, 4>, 4> derivedIds(slaveGroups.size());
2639 for (
size_t gi = 0; gi < slaveGroups.size(); ++gi) {
2640 for (
auto member : slaveGroups[gi]) {
2641 auto cubes = statedCubes(member, idMask);
2642 if (cubes.empty()) {
2643 derivedIds[gi].push_back(member.second);
2646 for (
auto cube : cubes)
2647 if (!llvm::is_contained(statedRules[gi], cube))
2648 statedRules[gi].push_back(cube);
2653 [[maybe_unused]]
auto claimsOf = [&](
size_t gi) {
2654 SmallVector<std::pair<int, int>, 8> claims(statedRules[gi].begin(),
2655 statedRules[gi].end());
2656 for (
int id : derivedIds[gi])
2657 claims.push_back({idMask,
id});
2665 std::map<TileID, Operation *> tiles;
2666 for (
auto tileOp : device.getOps<TileOp>())
2667 tiles[tileOp.getTileID()] = tileOp;
2668 for (
const PhysPort &master : llvm::make_first_range(mastersets))
2669 tiles.try_emplace(master.first, analyzer.
getTile(builder, master.first));
2671 for (Operation *tileOp : llvm::make_second_range(tiles)) {
2672 TileOp tile = cast<TileOp>(tileOp);
2673 TileID tileId = tile.getTileID();
2674 Location tileLoc = tile.getLoc();
2677 builder.setInsertionPointAfter(tileOp);
2679 analyzer.
getSwitchbox(builder, tile.colIndex(), tile.rowIndex());
2680 SwitchboxOp::ensureTerminator(swbox.getConnections(), builder, tileLoc);
2681 Block &b = swbox.getConnections().front();
2682 builder.setInsertionPoint(b.getTerminator());
2684 if (
auto hops = circuitHops.find(tileId); hops != circuitHops.end())
2685 for (
const Connect &hop : hops->second) {
2686 SmallVector<std::pair<PhysPort, int>, 4> passing;
2687 for (
const auto &flow : llvm::make_first_range(slaveFlowLocs))
2688 if (flow.first ==
PhysPort{tileId, hop.src})
2689 passing.push_back(flow);
2690 ConnectOp::create(builder, locOf(passing, tileLoc), hop.src.bundle,
2691 hop.src.channel, hop.dst.bundle, hop.dst.channel);
2695 auto tileMastersets =
2696 llvm::make_filter_range(mastersets, [&](
const auto &entry) {
2697 return entry.first.first == tileId;
2699 llvm::SmallBitVector amselNeeded(numMselsPerArbiter * numArbiters);
2700 for (
const auto &[master, amsels] : tileMastersets)
2701 for (
int amsel : amsels)
2702 amselNeeded.set(amsel);
2703 std::map<int, AMSelOp> amselOps;
2704 for (
int msel = 0; msel < numMselsPerArbiter; msel++)
2705 for (
int arbiter = 0; arbiter < numArbiters; arbiter++)
2706 if (
int amsel = arbiter + msel * numArbiters; amselNeeded.test(amsel))
2707 amselOps[amsel] = AMSelOp::create(builder, tileLoc, arbiter, msel);
2708 for (
const auto &[master, msels] : tileMastersets) {
2709 SmallVector<Value, 4> amsels;
2710 for (
int msel : msels)
2711 amsels.push_back(amselOps.at(msel));
2712 SmallVector<std::pair<PhysPort, int>, 4> feeding;
2713 for (
const auto &[flow, dests] : packetFlows)
2714 if (llvm::is_contained(dests, master))
2715 feeding.push_back(flow);
2716 auto msOp = MasterSetOp::create(
2717 builder, locOf(feeding, tileLoc), builder.getIndexType(),
2718 master.second.bundle, master.second.channel, amsels,
2719 keepPktHeaderAttr.lookup(master));
2720 if (priorityMasterPorts.contains(master))
2721 priority.setAttr(msOp, unit);
2725 DenseMap<Port, PacketRulesOp> slaveRules;
2726 DenseMap<Port, SmallVector<PacketRuleOp>> existingRules;
2728 SmallVector<size_t, 4> groups;
2729 SmallVector<PortRule> rules;
2732 std::map<Port, PortPlan> portPlans;
2733 for (
auto rulesOp : b.getOps<PacketRulesOp>()) {
2734 slaveRules[rulesOp.sourcePort()] = rulesOp;
2735 llvm::append_range(existingRules[rulesOp.sourcePort()],
2736 rulesOp.getRules().getOps<PacketRuleOp>());
2738 for (
size_t gi = 0; gi < slaveGroups.size(); ++gi) {
2739 const auto &group = slaveGroups[gi];
2740 builder.setInsertionPoint(b.getTerminator());
2742 auto port = group.front().first;
2743 if (tileId != port.first)
2746 Port slave = port.second;
2748 SmallVector<int, 4> matchIds =
2749 llvm::to_vector<4>(llvm::make_second_range(group));
2750 for (
int id : matchIds)
2751 if (
id >
static_cast<int>(maxPacketId)) {
2752 SmallVector<std::pair<PhysPort, int>> offending;
2753 for (
const auto &flow : group)
2754 if (flow.second ==
id)
2755 offending.push_back(flow);
2756 return mlir::emitError(locOf(offending, tileLoc))
2757 <<
"packet id " <<
id <<
" exceeds the maximum of "
2762 for (PacketRuleOp rule : existingRules.lookup(slave))
2763 for (
int id : matchIds)
2764 if ((
id & rule.maskInt()) == rule.valueInt()) {
2765 rule->emitOpError(
"can lead to false packet id match for id ")
2766 <<
id <<
", which is not supposed to pass through this port.";
2767 rule->emitRemark(
"Please consider changing all uses of packet id ")
2768 <<
id <<
" to avoid deadlock.";
2774 assert(llvm::all_of(
2775 llvm::seq<size_t>(0, slaveGroups.size()),
2777 return oi == gi || slaveGroups[oi].front().first != port ||
2778 llvm::none_of(claimsOf(gi), [&](auto own) {
2779 return llvm::any_of(claimsOf(oi), [&](auto other) {
2780 return cubesIntersect(own, other);
2784 "groups on one slave port claim the same id");
2788 auto [planIt, fresh] = portPlans.try_emplace(slave);
2789 PortPlan &plan = planIt->second;
2791 SmallVector<GroupClaims> claims;
2792 for (
size_t oi = 0; oi < slaveGroups.size(); ++oi) {
2793 if (slaveGroups[oi].front().first != port)
2795 plan.groups.push_back(oi);
2797 {statedRules[oi], derivedIds[oi],
2798 prioritizedFlows.contains(slaveGroups[oi].front())});
2800 SmallVector<std::pair<int, int>> existing;
2801 for (PacketRuleOp rule : existingRules.lookup(slave))
2802 existing.push_back({rule.maskInt(), rule.valueInt()});
2803 plan.rules = slavePortRules(claims, existing, idBits,
2806 llvm::dbgs() <<
"packet rules " <<
describePort(slave) <<
":";
2807 for (
const PortRule &r : plan.rules)
2808 llvm::dbgs() <<
" rule(" << r.mask <<
", " << r.value
2809 <<
") -> group " << plan.groups[r.group];
2810 llvm::dbgs() <<
'\n';
2816 llvm::seq(0, idMask + 1),
2818 bool own = llvm::is_contained(derivedIds[gi],
id) ||
2819 llvm::any_of(statedRules[gi], [&](
auto c) {
2820 return (
id & c.first) == (c.second & c.first);
2823 llvm::find_if(plan.rules, [&](
const PortRule &r) {
2824 return (id & r.mask) == r.value;
2827 (first != plan.rules.end() &&
2829 slaveGroups[plan.groups[first->group]].front()) ==
2830 slaveAMSels.at(slaveGroups[gi].front()));
2832 "packet rules send a claimed id elsewhere");
2834 size_t last = plan.emitted;
2835 for (
size_t r = plan.emitted; r < plan.rules.size(); ++r)
2836 if (plan.groups[plan.rules[r].group] == gi)
2839 PacketRulesOp packetrules = slaveRules.lookup(slave);
2841 packetrules = PacketRulesOp::create(builder, locOf(group, tileLoc),
2842 slave.bundle, slave.channel);
2843 PacketRulesOp::ensureTerminator(packetrules.getRules(), builder,
2845 slaveRules[slave] = packetrules;
2848 packetrules->moveBefore(b.getTerminator());
2849 if (Location more = locOf(group, tileLoc); more != tileLoc)
2850 packetrules->setLoc(FusedLoc::get(builder.getContext(),
2851 {packetrules->getLoc(), more}));
2854 Block &rules = packetrules.getRules().front();
2856 assert(llvm::range_size(rules.getOps<PacketRuleOp>()) +
2857 (last - plan.emitted) <=
2859 "checkRules lets a slave port take more rules than it holds");
2861 builder.setInsertionPoint(rules.getTerminator());
2862 for (; plan.emitted < last; ++plan.emitted) {
2863 const PortRule &r = plan.rules[plan.emitted];
2864 const auto &ruleGroup = slaveGroups[plan.groups[r.group]];
2865 auto rule = PacketRuleOp::create(
2866 builder, locOf(ruleGroup, tileLoc), r.mask, r.value,
2867 amselOps.at(slaveAMSels.at(ruleGroup.front())));
2868 if (llvm::all_of(ruleGroup, [&](
const auto &member) {
2869 return markedFlows.contains(member);
2871 priority.setAttr(rule, unit);
2875 for (
auto rulesOp : b.getOps<PacketRulesOp>()) {
2876 auto rules = rulesOp.getRules().front().getOps<PacketRuleOp>();
2877 if (rules.empty() || !llvm::all_of(rules, [&](PacketRuleOp rule) {
2878 return priority.isAttrPresent(rule);
2881 priority.setAttr(rulesOp, unit);
2882 for (
auto rule : rules)
2883 priority.removeAttr(rule);
2889LogicalResult AIEPathfinderPass::runOnPacketFlow(
2890 DeviceOp device, OpBuilder &builder, DynamicTileAnalysis &analyzer,
2891 const StreamConflicts &conflicts,
const PacketPlan &plan,
bool warn) {
2892 if (warn && plan.sharedReceiverCycle)
2893 emitWarning(device.getLoc(), sharedReceiverCycleReason)
2894 << conflicts.
explain(*plan.sharedReceiverCycle);
2895 if (failed(plan.emit(device, builder, analyzer)))
2897 lowerShimDMAPorts(device, builder, analyzer);
2898 for (PacketFlowOp flow :
2899 llvm::make_early_inc_range(device.getOps<PacketFlowOp>()))
2905static DeviceOp cloneInScratch(DeviceOp d, OwningOpRef<ModuleOp> &scratch) {
2906 scratch = ModuleOp::create(d.getLoc());
2907 if (Operation *parent = d->getParentOp())
2908 (*scratch)->setAttrs(parent->getAttrDictionary());
2909 DeviceOp copy = d.clone();
2910 scratch->push_back(copy);
2918static void unmuxShimDMAPacketPorts(DeviceOp device) {
2919 for (
auto shimMux : device.getOps<ShimMuxOp>()) {
2920 auto hasConnect = [&](
Port src,
Port dst) {
2921 return llvm::any_of(shimMux.getConnections().getOps<ConnectOp>(),
2923 return c.sourcePort() == src && c.destPort() == dst;
2926 for (
auto switchbox : device.getOps<SwitchboxOp>()) {
2927 if (switchbox.getTileOp() != shimMux.getTileOp())
2929 for (
auto rules : switchbox.getConnections().getOps<PacketRulesOp>())
2930 for (
int ch : {0, 1}) {
2931 Port dma{WireBundle::DMA, ch};
2933 if (rules.sourcePort() ==
Port{WireBundle::South, north.channel} &&
2934 hasConnect(dma, north)) {
2935 rules.setSourceBundle(WireBundle::DMA);
2936 rules.setSourceChannel(ch);
2940 for (
auto masterSet : switchbox.getConnections().getOps<MasterSetOp>())
2941 for (
int ch : {0, 1}) {
2942 Port dma{WireBundle::DMA, ch};
2944 if (masterSet.destPort() ==
Port{WireBundle::South, north.channel} &&
2945 hasConnect(north, dma)) {
2946 masterSet.setDestBundle(WireBundle::DMA);
2947 masterSet.setDestChannel(ch);
2955llvm::Expected<PacketPlan> AIEPathfinderPass::route(
2956 DeviceOp d, DynamicTileAnalysis &analyzer,
const StreamConflicts &conflicts,
2957 const PinnedRoutes &pinned,
bool circuitSwitchHops,
bool prioritize) {
2961 std::map<PathEndPoint, SmallVector<size_t, 4>> streamsFrom;
2962 PacketConstraints constraints;
2966 if (clRoutePacket && !d.getOps<PacketFlowOp>().empty()) {
2967 const AIETargetModel &targetModel = d.getTargetModel();
2968 if (llvm::Error err = unroutableArbiters(
2971 return !circuitSwitchHops ||
2975 return std::move(err);
2978 streamsFrom[{s.src.tile, s.src.port}].push_back(i);
2981 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
2982 SmallVector<size_t, 16> crossing;
2983 std::set<std::pair<TileID, Port>> srcs, dsts;
2985 if (s.packetID && low(s.src.tile) == up && low(s.dst.tile) != up) {
2986 crossing.push_back(i);
2987 srcs.insert({s.src.tile, s.src.port});
2988 dsts.insert({s.dst.tile, s.dst.port});
2992 size_t most = std::min(srcs.size(), dsts.size());
2993 SmallVector<size_t, 8> best =
2995 ? largestApartSet(conflicts, crossing, limit)
2996 : SmallVector<size_t, 8>(
2998 crossing.begin() + std::min<size_t>(1, crossing.size()));
2999 for (
size_t s : best)
3001 if (best.size() > 1)
3002 apart.
why = conflicts.
explain(best[0], best[1]);
3005 constraints.
conflict = [&](
const PathEndPoint &a,
const std::set<int> &aIds,
3006 const PathEndPoint &b,
3007 const std::set<int> &bIds) {
3008 auto as = streamsFrom.find(a), bs = streamsFrom.find(b);
3009 if (as == streamsFrom.end() || bs == streamsFrom.end())
3011 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
3012 for (
size_t s : as->second)
3013 for (
size_t t : bs->second)
3014 if (aIds.count(*streams[s].packetID) &&
3015 bIds.count(*streams[t].packetID) && conflicts.
conflict(s, t))
3019 constraints.
check = [&](
const Routing &routing) -> llvm::Error {
3020 PacketFlowRouting check(d, routing, conflicts, pinned, clRouteCircuit,
3021 circuitSwitchHops, prioritize);
3022 llvm::Expected<PacketPlan> plan = check.plan();
3024 return plan.takeError();
3025 RoutingFaults all = check.faults(), faults;
3029 if (!plan->sharedReceiverCycle ||
3032 return llvm::Error::success();
3033 return llvm::make_error<DeadlockProneRouting>(
3034 conflicts.
explain(*plan->sharedReceiverCycle), std::move(faults));
3041 return std::move(routed);
3043 return PacketPlan();
3044 PacketFlowRouting found(d, analyzer.
routing, conflicts, pinned,
3045 clRouteCircuit, circuitSwitchHops, prioritize);
3046 llvm::Expected<PacketPlan> plan = found.plan();
3047 if (found.incomplete) {
3048 llvm::consumeError(plan.takeError());
3049 return llvm::make_error<RoutingFailure>(std::move(found.incomplete->second),
3051 found.incomplete->first);
3055 if (!plan || clAllowDeadlockProne)
3057 const std::optional<HoldCycle> &cycle = plan->sharedReceiverCycle;
3060 return llvm::make_error<DeadlockProneRouting>(
3061 (llvm::Twine(sharedReceiverCycleReason) + conflicts.
explain(*cycle) +
3062 allowDeadlockProneHint)
3066static LogicalResult checkFixedFlowEndpoints(DeviceOp device) {
3067 DenseMap<PhysPort, Operation *> inputs, outputs;
3068 for (SwitchboxOp box : device.getOps<SwitchboxOp>()) {
3069 TileID tile = box.getTileOp().getTileID();
3070 for (ConnectOp connect : box.getOps<ConnectOp>()) {
3071 inputs[{tile, connect.sourcePort()}] = connect;
3072 outputs[{tile, connect.destPort()}] = connect;
3074 for (MasterSetOp master : box.getOps<MasterSetOp>())
3075 outputs[{tile, master.destPort()}] = master;
3076 for (PacketRulesOp rules : box.getOps<PacketRulesOp>())
3077 inputs[{tile, rules.sourcePort()}] = rules;
3079 AIEDialect::IsCtrlPktOverlayAttrHelper overlay(device.getContext());
3080 auto check = [&](Operation *flow, Value value,
Port port,
3081 bool source) -> LogicalResult {
3082 auto tile = cast<TileOp>(value.getDefiningOp());
3083 auto &reservations = source ? inputs : outputs;
3084 Operation *reserved = reservations.lookup({tile.getTileID(), port});
3085 if (!reserved && tile.isShimNOCorPLTile() &&
3086 llvm::is_contained({WireBundle::DMA, WireBundle::NOC, WireBundle::PLIO},
3088 Port south{WireBundle::South,
3090 reserved = reservations.lookup({tile.getTileID(), south});
3092 auto packet = dyn_cast<PacketFlowOp>(flow);
3093 if (!reserved || (packet && isa<PacketRulesOp>(reserved)))
3095 bool fromOverlay = overlay.isAttrPresent(reserved);
3096 if (
auto rules = dyn_cast<PacketRulesOp>(reserved))
3097 fromOverlay |= llvm::any_of(
3098 rules.getRules().getOps<PacketRuleOp>(),
3099 [&](PacketRuleOp rule) { return overlay.isAttrPresent(rule); });
3100 auto diag = emitError(flow->getLoc());
3101 diag <<
"Unable to find a legal routing: ";
3103 diag <<
"packet flow " << packet.IDInt();
3105 diag <<
"circuit flow";
3106 diag << (source ?
" starts at " :
" ends at ")
3108 << (source ?
"input" :
"output") <<
" port is reserved by ";
3110 diag <<
"the control packet overlay";
3112 diag <<
"a fixed " << reserved->getName();
3113 diag.attachNote(reserved->getLoc())
3114 << (source ?
"input" :
"output") <<
" port reserved here by "
3115 << reserved->getName();
3118 for (PacketFlowOp flow : device.getOps<PacketFlowOp>()) {
3119 for (PacketSourceOp source : flow.getPorts().getOps<PacketSourceOp>())
3120 if (failed(check(flow, source.getTile(), source.port(),
true)))
3122 for (PacketDestOp dest : flow.getPorts().getOps<PacketDestOp>())
3123 if (failed(check(flow, dest.getTile(), dest.port(),
false)))
3126 for (FlowOp flow : device.getOps<FlowOp>())
3127 if (failed(check(flow, flow.getSource(),
3128 {flow.getSourceBundle(), flow.getSourceChannel()},
3130 failed(check(flow, flow.getDest(),
3131 {flow.getDestBundle(), flow.getDestChannel()},
false)))
3136void AIEPathfinderPass::runOnOperation() {
3139 LLVM_DEBUG(llvm::dbgs() <<
"---Begin AIEPathfinderPass---\n");
3141 DeviceOp d = getOperation();
3143 return signalPassFailure();
3144 OpBuilder builder = OpBuilder::atBlockTerminator(d.getBody());
3146 unmuxShimDMAPacketPorts(d);
3147 if (failed(checkFixedFlowEndpoints(d)))
3148 return signalPassFailure();
3150 StreamConflicts conflicts(d);
3151 if (
auto pairs = conflicts.
unavoidable(); !pairs.empty()) {
3152 InFlightDiagnostic diag =
3153 (clAllowDeadlockProne ? emitWarning(d.getLoc()) : emitError(d.getLoc()))
3154 <<
"Flows can deadlock however they are routed: "
3155 << conflicts.explain(pairs[0].first, pairs[0].second);
3156 if (pairs.size() > 1)
3157 diag <<
" So can " << pairs.size() - 1 <<
" other pair"
3158 << (pairs.size() > 2 ?
"s" :
"") <<
" of flows.";
3159 if (!clAllowDeadlockProne) {
3160 diag << allowDeadlockProneHint;
3161 return signalPassFailure();
3164 DynamicTileAnalysis analyzer;
3170 auto sourcesOf = [](PacketFlowOp flow) {
3171 return llvm::map_range(
3172 flow.getPorts().getOps<PacketSourceOp>(), [](PacketSourceOp src) {
3173 return PathEndPoint{
3174 cast<TileOp>(src.getTile().getDefiningOp()).getTileID(),
3178 std::set<PathEndPoint> prioritized;
3179 std::set<std::pair<PathEndPoint, int>> prioritizedIds;
3180 for (PacketFlowOp flow : d.getOps<PacketFlowOp>())
3181 if (flow.getPriorityRoute().value_or(false))
3182 for (PathEndPoint src : sourcesOf(flow)) {
3183 prioritized.insert(src);
3184 prioritizedIds.insert({src, flow.IDInt()});
3186 auto isPrioritized = [&](PacketFlowOp flow) {
3187 return llvm::any_of(sourcesOf(flow), [&](
const PathEndPoint &src) {
3188 return prioritizedIds.count({src, flow.IDInt()}) > 0;
3191 auto routesAlone = isPrioritized;
3192 bool pinned =
false;
3193 PinnedRoutes priority;
3194 bool othersToRoute = !d.getOps<FlowOp>().empty() ||
3195 !llvm::all_of(d.getOps<PacketFlowOp>(), routesAlone);
3200 if (clRoutePacket && !prioritized.empty() && othersToRoute) {
3201 OwningOpRef<ModuleOp> scratch;
3202 DeviceOp alone = cloneInScratch(d, scratch);
3203 SmallVector<Operation *> others;
3204 for (Operation &op : alone.getBody()->without_terminator()) {
3205 auto flow = dyn_cast<PacketFlowOp>(op);
3206 if (flow ? !routesAlone(flow) : isa<FlowOp>(op))
3207 others.push_back(&op);
3209 for (Operation *op : others)
3210 op->dropAllReferences();
3211 for (Operation *op : others)
3213 std::map<PathEndPoint, std::set<int>> aloneIds;
3214 for (PacketFlowOp flow : alone.getOps<PacketFlowOp>())
3215 for (
auto src : flow.getPorts().getOps<PacketSourceOp>()) {
3216 auto tile = cast<TileOp>(src.getTile().getDefiningOp());
3217 aloneIds[{{tile.colIndex(), tile.rowIndex()}, src.port()}].insert(
3220 DynamicTileAnalysis aloneAnalyzer;
3222 StreamConflicts aloneConflicts(alone);
3223 llvm::Expected<PacketPlan> alonePlan =
3224 route(alone, aloneAnalyzer, aloneConflicts, {},
3227 llvm::consumeError(alonePlan.takeError());
3231 OpBuilder aloneBuilder = OpBuilder::atBlockTerminator(alone.getBody());
3232 if (failed(runOnPacketFlow(alone, aloneBuilder, aloneAnalyzer,
3233 aloneConflicts, *alonePlan,
false)))
3234 return signalPassFailure();
3236 std::move(aloneIds), priorityRules(alone)};
3244 auto routeRelaxing = [&](DeviceOp dev, DynamicTileAnalysis &an,
3245 const StreamConflicts &c,
const PinnedRoutes &pin,
3247 true) -> llvm::Expected<PacketPlan> {
3248 llvm::Expected<PacketPlan> plan =
3249 route(dev, an, c, pin, clCircuitSwitchHops, prioritize);
3252 llvm::Error first = plan.takeError();
3253 while (an.pathfinder.relax()) {
3254 plan = route(dev, an, c, pin, clCircuitSwitchHops, prioritize);
3256 llvm::consumeError(std::move(first));
3259 llvm::Error err = plan.takeError();
3260 if (err.isA<DeadlockProneRouting>() && !first.isA<DeadlockProneRouting>())
3261 std::swap(first, err);
3262 llvm::consumeError(std::move(err));
3264 return std::move(first);
3266 llvm::Expected<PacketPlan> plan =
3267 routeRelaxing(d, analyzer, conflicts, priority);
3268 if (!plan && !prioritized.empty()) {
3269 llvm::Error err = plan.takeError();
3271 DynamicTileAnalysis freeAnalyzer;
3272 plan = routeRelaxing(d, freeAnalyzer, conflicts, priority,
3275 llvm::consumeError(std::move(err));
3276 analyzer = std::move(freeAnalyzer);
3278 llvm::Error freeErr = plan.takeError();
3279 if (freeErr.isA<DeadlockProneRouting>() ||
3280 !err.isA<DeadlockProneRouting>())
3281 std::swap(err, freeErr);
3282 llvm::consumeError(std::move(freeErr));
3283 plan = std::move(err);
3288 llvm::handleAllErrors(plan.takeError(), [&](RoutingFailure &f) {
3291 if (pinned && !f.reason.empty() &&
3292 !f.isA(PinnedRoutingFailure::classID())) {
3293 OwningOpRef<ModuleOp> scratch;
3294 DeviceOp free = cloneInScratch(d, scratch);
3295 DynamicTileAnalysis freeAnalyzer;
3296 StreamConflicts freeConflicts(free);
3297 llvm::Expected<PacketPlan> freePlan = routeRelaxing(
3298 free, freeAnalyzer, freeConflicts, {}, false);
3300 llvm::consumeError(freePlan.takeError());
3302 f.reason = describePrioritized(llvm::to_vector(llvm::map_range(
3304 [](const PathEndPoint &src) {
3305 return describeTilePort(src.coords, src.port);
3307 ", and the other flows route only if it moves. Around "
3312 emitError(f.
loc.value_or(d.getLoc())) << f.message();
3314 signalPassFailure();
3317 for (
auto flowOp : d.getOps<FlowOp>()) {
3318 if (!flowOp.getVias().empty()) {
3319 flowOp.emitOpError(
"cannot be routed while it carries via waypoints; run "
3320 "--aie-split-flow-vias first to lower the vias into "
3321 "switchbox connections");
3322 signalPassFailure();
3326 for (
auto flowOp : d.getOps<PacketFlowOp>()) {
3327 if (!flowOp.getVias().empty()) {
3328 flowOp.emitOpError(
"cannot be routed while it carries via waypoints; run "
3329 "--aie-split-flow-vias first to lower the vias into "
3330 "switchbox connections");
3331 signalPassFailure();
3336 if (clRouteCircuit && failed(runOnFlow(d, analyzer))) {
3337 signalPassFailure();
3340 if (clRoutePacket && failed(runOnPacketFlow(d, builder, analyzer, conflicts,
3342 signalPassFailure();
3348 builder.setInsertionPoint(d.getBody()->getTerminator());
3349 llvm::DenseSet<std::tuple<Value, int, Value, int>> existingWires;
3352 auto recordWire = [&](Value source,
int sourceBundle, Value dest,
3355 !existingWires.contains({source, sourceBundle, dest, destBundle}) &&
3356 !existingWires.contains({dest, destBundle, source, sourceBundle});
3357 existingWires.insert({source, sourceBundle, dest, destBundle});
3358 existingWires.insert({dest, destBundle, source, sourceBundle});
3361 for (
auto wire : d.getOps<WireOp>()) {
3362 recordWire(wire.getSource(),
static_cast<int>(wire.getSourceBundle()),
3363 wire.getDest(),
static_cast<int>(wire.getDestBundle()));
3365 auto wire = [&](Location loc, Value source, WireBundle sourceBundle,
3366 Value dest, WireBundle destBundle) {
3367 if (recordWire(source,
static_cast<int>(sourceBundle), dest,
3368 static_cast<int>(destBundle))) {
3369 WireOp::create(builder, loc, source, sourceBundle, dest, destBundle);
3372 for (
int col = 0; col <= analyzer.
getMaxCol(); col++) {
3373 for (
int row = 0; row <= analyzer.
getMaxRow(); row++) {
3374 TileOp tile = analyzer.
lookupTile({col, row});
3378 Location loc = tile.getLoc();
3382 wire(loc, westsw, WireBundle::East, sw, WireBundle::West);
3385 wire(loc, tile, WireBundle::Core, sw, WireBundle::Core);
3387 wire(loc, tile, WireBundle::DMA, sw, WireBundle::DMA);
3391 wire(loc, southsw, WireBundle::North, sw, WireBundle::South);
3392 }
else if (tile.isShimNOCTile()) {
3396 sw, WireBundle::South);
3399 wire(loc, tile, WireBundle::DMA, shimsw, WireBundle::DMA);
3407 return std::make_unique<AIEPathfinderPass>();
3410std::unique_ptr<OperationPass<DeviceOp>>
3412 return std::make_unique<AIEPathfinderPass>(options);
3415std::unique_ptr<OperationPass<DeviceOp>>
3417 ArrayRef<std::pair<TileID, Port>> avoidedPorts) {
3418 auto pass = std::make_unique<AIEPathfinderPass>(options);
3419 pass->avoidedPorts.assign(avoidedPorts.begin(), avoidedPorts.end());
constexpr int maxSearchSteps
std::pair< TileID, Port > PhysPort
constexpr int maxHoldCycleReplans
std::map< PhysPort, SmallVector< std::tuple< int, int, int >, 2 > > PriorityRules
(mask, value, amsel) by slave port, in slot order.
std::tuple< int, int, int, int, int, int, int, int, int, int > FlowKey
virtual uint32_t getNumMselsPerArbiter() const =0
Return the number of master select values per stream-switch arbiter.
virtual uint32_t getNumArbiters() const =0
Return the number of arbiters per stream switch.
bool isShimNOCorPLTile(int col, int row) const
Return true if the given tile is either a ShimNOC or ShimPL tile.
virtual int rows() const =0
Return the number of rows in the device.
bool isShimNOCTile(int col, int row) const
Return true if the given tile is a ShimNOC tile.
virtual uint32_t getMaxPacketId() const =0
Return the largest packet id the stream switch can route.
virtual int columns() const =0
Return the number of columns in the device.
virtual uint32_t getNumDestSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a switchbox.
virtual uint32_t getNumSlaveSlots() const =0
Return the number of packet-rule slots per stream-switch slave port.
ShimMuxOp getShimMux(mlir::OpBuilder &builder, int col)
SwitchboxOp getSwitchbox(mlir::OpBuilder &builder, int col, int row)
SwitchboxOp lookupSwitchbox(TileID tile) const
TileOp lookupTile(TileID tile) const
TileOp getTile(mlir::OpBuilder &builder, int col, int row)
llvm::Error runAnalysis(DeviceOp &device)
std::set< PathEndPoint > processedFlows
ShimMuxOp lookupShimMux(int col) const
std::vector< std::pair< TileID, Port > > avoidedPorts
void setPacketConstraints(PacketConstraints c)
std::optional< mlir::Location > loc
The streams a device asks for or already routes, and which pairs of them must not share an arbiter or...
llvm::ArrayRef< RoutedStream > getRequestedStreams() const
The streams the flow ops ask for, which come first in getStreams().
llvm::SmallVector< std::pair< size_t, size_t > > unavoidable() const
The requested streams where the first can hold up the second however they are routed,...
bool mustSeparate(size_t s, size_t t) const
Whether a routing that puts packet streams s and t on one arbiter anywhere has a hold cycle: they con...
llvm::ArrayRef< RoutedStream > getStreams() const
bool conflict(size_t s, size_t t) const
Whether s and t can deadlock if they share an arbiter or a link.
std::optional< HoldCycle > holdCycle(llvm::ArrayRef< llvm::SmallVector< StreamHop, 8 > > routes, bool forcedWaits=false, bool definite=false) const
A cycle of waits the packet streams can deadlock in when routed along routes, indexed like getStreams...
std::string explain(size_t s, size_t t) const
Why s and t conflict.
int shimMuxChannelTo(Port dst)
struct Connect { Port src; Port dst; bool operator==(const Connect &rhs) const { return std::tie(src, dst)==std::tie(rhs.src, rhs.dst); } bool operator!=(const Connect &rhs) const { return !(*this==rhs);} bool operator<(const Connect &rhs) const { return std::tie(src, dst)< std::tie(rhs.src, rhs.dst); } } Connect
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)
std::string describeStream(const RoutedStream &stream)
Names the stream by its endpoints and packet id, e.g.
const AIETargetModel & getTargetModel(mlir::Operation *op)
mlir::LogicalResult verifyDMAChannelsResolved(DeviceOp device)
int shimMuxChannelFrom(Port src)
std::string describeTilePort(TileID tile, Port port)
Names a tile port the way diagnostics do, e.g. "(0, 2) DMA:1".
struct Port { WireBundle bundle; int channel; bool operator==(const Port &rhs) const { return std::tie(bundle, channel)==std::tie(rhs.bundle, rhs.channel); } bool operator!=(const Port &rhs) const { return !(*this==rhs);} bool operator<(const Port &rhs) const { return std::tie(bundle, channel)< std::tie(rhs.bundle, rhs.channel); } friend std::ostream &operator<<(std::ostream &os, const Port &port) { os<< "("; os<< stringifyWireBundle(port.bundle).str(); os<< ": "<< std::to_string(port.channel)<< ")"; return os; } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Port &port) { os<< to_string(port); return os; } } Port
std::map< TileID, SwitchSetting > SwitchSettings
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEPathfinderPass()
std::string describePort(Port port)
Names a port the way diagnostics do, e.g. "DMA:1".
std::map< PathEndPoint, std::vector< TreeHop > > PacketTrees
std::map< PathEndPoint, std::set< int > > ids
bool forced
The wait holds however the arbiters are planned, as sharer and holding go into a receiver they share ...
std::map< PathEndPoint, std::set< int > > pinnedIds
PacketsApartAcross apartAcross
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