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>>;
53static bool reloadConfigures(PacketFlowOp flow) {
54 auto control = [](
auto end) {
55 return end.getBundle() == WireBundle::TileControl;
57 return flow->getParentOfType<DeviceOp>().getHasCtrlPktOverlay() &&
58 llvm::none_of(flow.getPorts().getOps<PacketSourceOp>(), control) &&
59 llvm::none_of(flow.getPorts().getOps<PacketDestOp>(), control);
69 const int numArbiters = d.getTargetModel().getNumArbiters();
70 AIEDialect::IsCtrlPktOverlayAttrHelper overlay(d.getContext());
72 for (SwitchboxOp box : d.getOps<SwitchboxOp>())
73 for (PacketRulesOp port : box.getOps<PacketRulesOp>())
74 for (PacketRuleOp rule : port.getRules().front().getOps<PacketRuleOp>())
75 if (overlay.isAttrPresent(port) || overlay.isAttrPresent(rule)) {
76 auto amsel = cast<AMSelOp>(rule.getAmsel().getDefiningOp());
77 rules[{box.getTileOp().getTileID(),
78 {port.getSourceBundle(), port.sourceIndex()}}]
80 {rule.maskInt(), rule.valueInt(),
81 amsel.arbiterIndex() + amsel.getMselValue() * numArbiters});
92static constexpr llvm::StringLiteral sharedReceiverCycleReason =
93 "Packet flows into receivers they share can deadlock holding arbiters "
94 "across switchboxes, and no routing found avoids it: ";
95static constexpr llvm::StringLiteral allowDeadlockProneHint =
96 " Set allow-deadlock-prone (aiecc --allow-deadlock-prone-routing) to "
113struct AIEPathfinderPass
114 : xilinx::AIE::impl::AIERoutePathfinderFlowsBase<AIEPathfinderPass> {
116 xilinx::AIE::impl::AIERoutePathfinderFlowsBase<AIEPathfinderPass>;
117 AIEPathfinderPass() =
default;
118 AIEPathfinderPass(
const AIERoutePathfinderFlowsOptions &options)
120 void runOnOperation()
override;
124 LogicalResult runOnPacketFlow(DeviceOp d, OpBuilder &builder,
127 const PacketPlan &plan,
bool warn);
135 bool circuitSwitchHops,
136 bool prioritize =
true);
141struct ConvertFlowsToInterconnect : OpConversionPattern<FlowOp> {
142 using OpConversionPattern::OpConversionPattern;
145 ConvertFlowsToInterconnect(MLIRContext *context, DeviceOp &d,
147 : OpConversionPattern(context, benefit), device(d), analyzer(a) {}
149 void addConnection(ConversionPatternRewriter &rewriter,
151 Interconnect op, FlowOp flowOp, WireBundle inBundle,
152 int inIndex, WireBundle outBundle,
int outIndex)
const {
154 Region &r = op.getConnections();
155 Block &b = r.front();
156 auto point = rewriter.saveInsertionPoint();
157 rewriter.setInsertionPoint(b.getTerminator());
159 ConnectOp::create(rewriter, flowOp.getLoc(), inBundle, inIndex, outBundle,
162 rewriter.restoreInsertionPoint(point);
164 LLVM_DEBUG(llvm::dbgs()
165 <<
"\t\taddConnection() (" << op.colIndex() <<
","
166 << op.rowIndex() <<
") " << stringifyWireBundle(inBundle)
167 << inIndex <<
" -> " << stringifyWireBundle(outBundle)
168 << outIndex <<
"\n");
172 matchAndRewrite(FlowOp flowOp, OpAdaptor adaptor,
173 ConversionPatternRewriter &rewriter)
const override {
174 Operation *Op = flowOp.getOperation();
175 DeviceOp d = flowOp->getParentOfType<DeviceOp>();
177 flowOp->emitOpError(
"This operation must be contained within a device");
180 rewriter.setInsertionPoint(d.getBody()->getTerminator());
182 auto srcTile = cast<TileOp>(flowOp.getSource().getDefiningOp());
183 TileID srcCoords = {srcTile.colIndex(), srcTile.rowIndex()};
184 auto srcBundle = flowOp.getSourceBundle();
185 auto srcChannel = flowOp.getSourceChannel();
186 Port srcPort = {srcBundle, srcChannel};
189 auto dstTile = cast<TileOp>(flowOp.getDest().getDefiningOp());
190 llvm::dbgs() <<
"\n\t---Begin rewrite() for flowOp: (" << srcCoords.col
191 <<
", " << srcCoords.row <<
")"
192 << stringifyWireBundle(srcBundle) << srcChannel <<
" -> ("
193 << dstTile.colIndex() <<
", " << dstTile.rowIndex() <<
")"
194 << stringifyWireBundle(flowOp.getDestBundle())
195 << flowOp.getDestChannel() <<
"\n\t";
200 TileID srcSbId = {srcCoords.col, srcCoords.row};
207 LLVM_DEBUG(llvm::dbgs() <<
"Flow already processed!\n");
208 rewriter.eraseOp(Op);
213 for (
const auto &[tileId, setting] : settings) {
214 int col = tileId.col;
215 int row = tileId.row;
216 SwitchboxOp swOp = analyzer.
getSwitchbox(rewriter, col, row);
217 int shimCh = srcChannel;
218 bool isShim = analyzer.
getTile(rewriter, tileId).isShimNOCorPLTile();
221 bool srcUsesShimMux = srcBundle == WireBundle::DMA ||
222 srcBundle == WireBundle::PLIO ||
223 srcBundle == WireBundle::NOC;
224 if (isShim && tileId == srcSbId && srcUsesShimMux) {
227 ShimMuxOp shimMuxOp = analyzer.
getShimMux(rewriter, col);
228 addConnection(rewriter, cast<Interconnect>(shimMuxOp.getOperation()),
229 flowOp, srcBundle, srcChannel, WireBundle::North, shimCh);
231 assert(setting.srcs.size() == setting.dsts.size());
232 for (
size_t i = 0; i < setting.srcs.size(); i++) {
233 Port src = setting.srcs[i];
234 Port dest = setting.dsts[i];
237 if (isShim && tileId == srcSbId && srcUsesShimMux && src == srcPort)
238 src = {WireBundle::South, shimCh};
239 if (isShim && (dest.bundle == WireBundle::DMA ||
240 dest.bundle == WireBundle::PLIO ||
241 dest.bundle == WireBundle::NOC)) {
244 ShimMuxOp shimMuxOp = analyzer.
getShimMux(rewriter, col);
245 addConnection(rewriter, cast<Interconnect>(shimMuxOp.getOperation()),
246 flowOp, WireBundle::North, destCh, dest.bundle,
248 dest = {WireBundle::South, destCh};
250 addConnection(rewriter, cast<Interconnect>(swOp.getOperation()), flowOp,
251 src.bundle, src.channel, dest.bundle, dest.channel);
254 LLVM_DEBUG(llvm::dbgs() << tileId <<
": " << setting <<
" | " <<
"\n");
257 LLVM_DEBUG(llvm::dbgs()
258 <<
"\n\t\tFinished adding ConnectOps to implement flowOp.\n");
261 rewriter.eraseOp(Op);
268LogicalResult AIEPathfinderPass::runOnFlow(DeviceOp d,
272 ConversionTarget target(getContext());
273 target.addLegalOp<TileOp>();
274 target.addLegalOp<ConnectOp>();
275 target.addLegalOp<SwitchboxOp>();
276 target.addLegalOp<ShimMuxOp>();
277 target.addLegalOp<EndOp>();
279 RewritePatternSet patterns(&getContext());
280 patterns.insert<ConvertFlowsToInterconnect>(d.getContext(), d, analyzer);
281 if (failed(applyPartialConversion(d, target, std::move(patterns))))
287static std::optional<std::pair<TileID, Port>> linkedInput(
TileID tile,
289 switch (out.bundle) {
290 case WireBundle::East:
291 return std::pair{
TileID{tile.col + 1, tile.row},
292 Port{WireBundle::West, out.channel}};
293 case WireBundle::West:
294 return std::pair{
TileID{tile.col - 1, tile.row},
295 Port{WireBundle::East, out.channel}};
296 case WireBundle::North:
297 return std::pair{
TileID{tile.col, tile.row + 1},
298 Port{WireBundle::South, out.channel}};
299 case WireBundle::South:
300 return std::pair{
TileID{tile.col, tile.row - 1},
301 Port{WireBundle::North, out.channel}};
311 if (tile == finalTile && out == finalPort)
313 std::optional<std::pair<TileID, Port>> next = linkedInput(tile, out);
316 TileID neighbour = next->first;
317 Port in = next->second;
318 auto setting = settings.find(neighbour);
319 if (setting == settings.end())
322 llvm::zip(setting->second.srcs, setting->second.dsts), [&](
auto link) {
323 auto [src, dest] = link;
325 findPathToDest(settings, neighbour, dest, finalTile, finalPort);
332template <
typename Node,
typename Successors>
333static std::map<Node, Node>
334breadthFirstParents(Node start, Successors successors,
335 std::optional<Node> goal = std::nullopt) {
336 std::map<Node, Node> parent{{start, start}};
337 std::deque<Node> work{start};
338 while (!work.empty() && !(goal && parent.count(*goal))) {
339 Node u = work.front();
341 for (Node v : successors(u))
342 if (parent.try_emplace(v, u).second)
353 llvm::SmallBitVector cov;
361static void bnbMinCover(ArrayRef<CoverCube> cubes,
362 const llvm::SmallBitVector &uncovered,
363 SmallVectorImpl<int> &sel,
int &bestSize,
364 SmallVectorImpl<int> &bestSel) {
367 if (uncovered.none()) {
368 if (
static_cast<int>(sel.size()) < bestSize) {
369 bestSize =
static_cast<int>(sel.size());
370 bestSel.assign(sel.begin(), sel.end());
376 if (
static_cast<int>(sel.size()) + 1 >= bestSize) {
382 int pickCount =
static_cast<int>(cubes.size()) + 1;
384 for (
int id = uncovered.find_first();
id != -1;
385 id = uncovered.find_next(
id)) {
388 for (
const CoverCube &cb : cubes) {
389 if (cb.cov.test(
id)) {
401 for (
int i = 0; i < static_cast<int>(cubes.size()); ++i) {
402 if (!cubes[i].cov.test(pick)) {
407 llvm::SmallBitVector next = uncovered;
408 next.reset(cubes[i].cov);
409 bnbMinCover(cubes, next, sel, bestSize, bestSel);
414static bool cubesIntersect(std::pair<int, int> a, std::pair<int, int> b) {
415 return ((a.second ^ b.second) & a.first & b.first) == 0;
430static SmallVector<std::pair<int, int>>
431computeSubcubeCover(
const SmallVector<int, 4> &matchIds,
432 ArrayRef<std::pair<int, int>> avoidCubes,
int idBits) {
435 const int numIds = 1 << idBits;
436 const int idMask = numIds - 1;
438 auto hitsAvoid = [&](
int mask,
int value) {
439 return llvm::any_of(avoidCubes, [&](std::pair<int, int> c) {
440 return cubesIntersect({mask, value}, c);
444 llvm::SmallBitVector matchMask(numIds);
445 for (
int id : matchIds) {
450 SmallVector<CoverCube> primes;
451 for (
int mask = 0; mask < numIds; ++mask) {
452 for (
int value = 0; value < numIds; ++value) {
454 if ((value & mask) != value) {
457 if (hitsAvoid(mask, value)) {
462 llvm::SmallBitVector cov(numIds);
463 for (
int id : matchIds) {
464 if ((
id & mask) == value) {
476 for (
int b = 0; b < idBits; ++b) {
477 if (!((mask >> b) & 1)) {
481 if (!hitsAvoid(mask & ~(1 << b), value & ~(1 << b))) {
488 primes.push_back({mask, value, std::move(cov)});
494 SmallVector<int> sel, bestSel;
495 int bestSize =
static_cast<int>(matchIds.size()) + 1;
497 bnbMinCover(primes, matchMask, sel, bestSize, bestSel);
499 SmallVector<std::pair<int, int>> chosen;
500 for (
int i : bestSel) {
501 chosen.push_back({primes[i].mask, primes[i].value});
504 if (chosen.empty()) {
506 for (
int id : matchIds) {
507 chosen.push_back({idMask,
id});
521 SmallVector<SmallVector<int, 4>, 4> assigned(chosen.size());
522 for (
int id : matchIds) {
523 for (
int c = 0; c < static_cast<int>(chosen.size()); ++c) {
524 if ((
id & chosen[c].first) == chosen[c].second) {
525 assigned[c].push_back(
id);
531 SmallVector<std::pair<int, int>> cover;
532 for (
const SmallVector<int, 4> &ids : assigned) {
538 for (
int i = 0; i < idBits; ++i) {
539 int bit = (ids.front() >> i) & 1;
541 if (((
id >> i) & 1) != bit) {
547 cover.push_back({mask, ids.front() & mask});
556static SmallVector<std::pair<int, int>>
557groupRules(ArrayRef<std::pair<int, int>> stated, ArrayRef<int> derived,
558 ArrayRef<std::pair<int, int>> avoid,
int idBits) {
559 SmallVector<std::pair<int, int>> rules(stated);
562 SmallVector<std::pair<int, int>> derivedAvoid(avoid);
563 derivedAvoid.append(stated.begin(), stated.end());
564 llvm::append_range(rules, computeSubcubeCover(SmallVector<int, 4>(derived),
565 derivedAvoid, idBits));
574 ArrayRef<std::pair<int, int>> stated;
575 ArrayRef<int> derived;
576 bool overlay =
false;
587static uint64_t cubeIds(
int mask,
int value,
int idBits) {
589 for (
int id = 0;
id < (1 << idBits); ++id)
590 if ((
id & mask) == (value & mask))
591 ids |= uint64_t(1) << id;
598static std::optional<SmallVector<PortRule>>
599orderedRules(ArrayRef<GroupClaims> groups,
600 ArrayRef<std::pair<int, int>> existing,
int idBits,
604 const int idMask = (1 << idBits) - 1;
605 SmallVector<uint64_t> claims;
606 for (
const GroupClaims &group : groups) {
608 for (
auto [mask, value] : group.stated)
609 ids |= cubeIds(mask, value, idBits);
610 for (
int id : group.derived)
611 ids |= uint64_t(1) << id;
612 claims.push_back(ids);
614 uint64_t claimed = 0;
615 for (uint64_t ids : claims)
618 for (
auto [mask, value] : existing)
619 taken |= cubeIds(mask, value, idBits);
622 SmallVector<uint64_t> cubes;
623 for (
int mask = 0; mask <= idMask; ++mask)
624 for (
int value = 0; value <= idMask; ++value)
625 if ((value & mask) == value)
626 cubes.push_back(cubeIds(mask, value, idBits));
631 SmallVector<PortRule> rules;
632 std::set<std::pair<uint64_t, size_t>> dead;
633 auto search = [&](
auto &self, uint64_t taken,
size_t left) ->
bool {
634 uint64_t open = claimed & ~taken;
636 llvm::count_if(claims, [&](uint64_t ids) {
return ids & open; });
639 if (openGroups > left || dead.count({taken, left}))
641 SmallVector<std::pair<uint64_t, size_t>> moves;
642 for (uint64_t cube : cubes)
643 for (
auto [g, ids] : llvm::enumerate(claims))
644 if ((cube & open) && (cube & open & ~ids) == 0 &&
645 !llvm::is_contained(moves, std::pair{cube & open, g}))
646 moves.push_back({cube & open, g});
647 llvm::stable_sort(moves, [](
auto a,
auto b) {
648 return llvm::popcount(a.first) > llvm::popcount(b.first);
650 SmallVector<std::pair<uint64_t, size_t>> tried;
651 for (
auto [own, g] : moves) {
652 if (llvm::any_of(tried, [&, own = own, g = g](
auto t) {
653 return t.second == g && (t.first & own) == own;
656 tried.push_back({own, g});
657 int all = idMask, any = 0;
658 for (
int id = 0;
id <= idMask; ++id)
659 if ((own >>
id) & 1) {
663 int mask = idMask & ~(all ^ any);
664 rules.push_back({mask, all, g});
665 if (self(self, taken | own, left - 1))
669 dead.insert({taken, left});
672 for (
size_t length = 1; length <= budget; ++length)
673 if (search(search, taken, length))
681static SmallVector<PortRule> portRules(ArrayRef<GroupClaims> groups,
682 ArrayRef<std::pair<int, int>> existing,
683 int idBits,
size_t slots) {
684 const int idMask = (1 << idBits) - 1;
685 SmallVector<PortRule> rules;
686 for (
auto [g, group] : llvm::enumerate(groups)) {
687 SmallVector<std::pair<int, int>> avoid(existing);
688 for (
auto [o, other] : llvm::enumerate(groups)) {
691 avoid.append(other.stated.begin(), other.stated.end());
692 for (
int id : other.derived)
693 avoid.push_back({idMask,
id});
695 for (
auto [mask, value] :
696 groupRules(group.stated, group.derived, avoid, idBits))
697 rules.push_back({mask, value, g});
699 if (existing.size() + rules.size() <= slots || existing.size() >= slots)
701 if (std::optional<SmallVector<PortRule>> ordered =
702 orderedRules(groups, existing, idBits, slots - existing.size()))
710static SmallVector<PortRule>
711slavePortRules(ArrayRef<GroupClaims> groups,
712 ArrayRef<std::pair<int, int>> existing,
int idBits,
714 SmallVector<std::pair<int, int>> taken(existing);
715 SmallVector<PortRule> rules;
716 for (
bool overlay : {
true,
false}) {
717 SmallVector<size_t, 4> members;
718 SmallVector<GroupClaims> claims;
719 for (
auto [g, group] : llvm::enumerate(groups))
720 if (group.overlay == overlay) {
721 members.push_back(g);
722 claims.push_back(group);
726 for (
const PortRule &r : portRules(claims, taken, idBits, slots))
727 rules.push_back({r.mask, r.value, members[r.group]});
728 for (
const PortRule &r : rules)
729 if (groups[r.group].overlay == overlay)
730 taken.push_back({r.mask, r.value});
741 SmallVector<Port, 4> masters;
748 std::map<std::pair<Port, int>,
int> slaveAmsels;
749 std::map<int, SmallVector<Port, 4>> amselMasters;
754template <
typename SlaveFlows>
755llvm::EquivalenceClasses<Port> tiedMasters(SlaveFlows &&flows) {
756 llvm::EquivalenceClasses<Port> tied;
757 for (
const SlaveFlow &f : flows)
758 for (
Port m : f.masters)
759 tied.unionSets(f.masters.front(), m);
772std::optional<ArbiterPlan>
773planArbiters(
const AIETargetModel &targetModel, ArrayRef<SlaveFlow> flows,
774 llvm::function_ref<
bool(
size_t,
size_t)> conflict,
775 llvm::function_ref<
bool(
size_t,
int)> excluded,
776 const std::set<int> &reservedAmsels,
777 SmallVectorImpl<std::pair<size_t, size_t>> &blocking) {
780 auto amselOf = [&](
int arbiter,
int msel) {
781 return arbiter + msel * numArbiters;
784 llvm::EquivalenceClasses<Port> tied = tiedMasters(flows);
787 SmallVector<size_t, 4> flows;
788 SmallVector<SmallVector<Port, 4>, 2> masterSets;
789 bool isCtrlPkt =
false;
791 std::vector<Unit> units;
792 std::map<Port, size_t> unitOf;
793 SmallVector<size_t, 8> flowUnit;
794 for (
auto [i, f] : llvm::enumerate(flows)) {
795 auto [it, inserted] =
796 unitOf.try_emplace(tied.getLeaderValue(f.masters.front()), 0);
798 it->second = units.size();
799 units.emplace_back();
801 Unit &unit = units[it->second];
802 flowUnit.push_back(it->second);
803 unit.flows.push_back(i);
804 unit.isCtrlPkt |= f.isCtrlPkt;
805 if (!llvm::is_contained(unit.masterSets, f.masters))
806 unit.masterSets.push_back(f.masters);
808 for (Unit &unit : units) {
809 auto *ctrlEnd = std::stable_partition(
810 unit.masterSets.begin(), unit.masterSets.end(),
811 [&](
const SmallVector<Port, 4> &masters) {
812 return llvm::any_of(unit.flows, [&](size_t f) {
813 return flows[f].isCtrlPkt && flows[f].masters == masters;
816 std::sort(unit.masterSets.begin(), ctrlEnd);
819 auto clash = [&](
size_t a,
size_t b) {
820 return flows[a].slave != flows[b].slave && conflict(a, b);
822 for (
const Unit &unit : units)
823 for (
auto [k, a] : llvm::enumerate(unit.flows))
824 for (
size_t b : llvm::drop_begin(unit.flows, k + 1))
826 blocking.push_back({a, b});
827 if (!blocking.empty())
830 std::vector<llvm::SmallDenseSet<size_t, 4>> neighbors(units.size());
831 SmallVector<std::pair<size_t, size_t>, 4> crossPairs;
832 for (
size_t a = 0; a < flows.size(); a++)
833 for (
size_t b = a + 1; b < flows.size(); b++)
834 if (flowUnit[a] != flowUnit[b] && clash(a, b)) {
835 neighbors[flowUnit[a]].insert(flowUnit[b]);
836 neighbors[flowUnit[b]].insert(flowUnit[a]);
837 crossPairs.push_back({a, b});
840 SmallVector<SmallVector<int, 4>, 6> freeMsels(numArbiters);
841 SmallVector<SmallVector<size_t, 4>, 6> excludedUnits(numArbiters);
842 for (
int a = 0; a < numArbiters; a++) {
843 for (
int m = 0; m < numMselsPerArbiter; m++)
844 if (!reservedAmsels.count(amselOf(a, m)))
845 freeMsels[a].push_back(m);
846 for (
size_t f = 0; f < flows.size(); f++)
847 if (excluded(f, a) && !llvm::is_contained(excludedUnits[a], flowUnit[f]))
848 excludedUnits[a].push_back(flowUnit[f]);
851 for (
const SmallVector<int, 4> &free : freeMsels)
852 mostFree = std::max(mostFree, free.size());
853 if (llvm::any_of(units, [&](
const Unit &unit) {
854 return unit.masterSets.size() > mostFree;
859 SmallVector<size_t, 8> order(units.size());
860 std::iota(order.begin(), order.end(), 0);
861 llvm::stable_sort(order, [&](
size_t a,
size_t b) {
862 return std::make_pair(neighbors[a].size(), units[a].masterSets.size()) >
863 std::make_pair(neighbors[b].size(), units[b].masterSets.size());
866 SmallVector<int, 8> arbiterOf(units.size(), -1);
867 SmallVector<size_t, 6> load(numArbiters, 0);
869 auto place = [&](
auto &self,
size_t depth) ->
bool {
870 if (depth == order.size())
873 LLVM_DEBUG(llvm::dbgs() <<
"Arbiter search gave up after "
877 size_t u = order[depth];
878 SmallVector<int, 6> candidates(numArbiters);
879 std::iota(candidates.begin(), candidates.end(), 0);
880 if (units[u].isCtrlPkt)
881 std::reverse(candidates.begin(), candidates.end());
883 llvm::stable_sort(candidates, [&](
int a,
int b) {
884 return load[a] + numMselsPerArbiter - freeMsels[a].size() <
885 load[b] + numMselsPerArbiter - freeMsels[b].size();
887 std::set<std::pair<size_t, SmallVector<size_t, 4>>> triedEmpty;
888 for (
int a : candidates) {
889 if (load[a] + units[u].masterSets.size() > freeMsels[a].size())
891 if (llvm::is_contained(excludedUnits[a], u) ||
892 llvm::any_of(neighbors[u],
893 [&](
size_t v) {
return arbiterOf[v] == a; }))
898 !triedEmpty.insert({freeMsels[a].size(), excludedUnits[a]}).second)
901 load[a] += units[u].masterSets.size();
902 if (self(self, depth + 1))
904 load[a] -= units[u].masterSets.size();
909 if (!place(place, 0)) {
910 blocking.append(crossPairs.begin(), crossPairs.end());
917 SmallVector<size_t, 8> mselOrder(units.size());
918 std::iota(mselOrder.begin(), mselOrder.end(), 0);
919 llvm::stable_sort(mselOrder, [&](
size_t a,
size_t b) {
920 if (!units[a].isCtrlPkt || !units[b].isCtrlPkt)
921 return units[a].isCtrlPkt > units[b].isCtrlPkt;
922 return units[a].masterSets.front() < units[b].masterSets.front();
925 SmallVector<size_t, 6> low(numArbiters, 0), high(numArbiters, 0);
926 for (
size_t u : mselOrder) {
927 const Unit &unit = units[u];
928 int a = arbiterOf[u];
929 std::map<SmallVector<Port, 4>,
int> setAmsel;
930 for (
const SmallVector<Port, 4> &masters : unit.masterSets) {
931 int msel = unit.isCtrlPkt
932 ? freeMsels[a][freeMsels[a].size() - 1 - high[a]++]
933 : freeMsels[a][low[a]++];
934 setAmsel[masters] = amselOf(a, msel);
935 plan.amselMasters[amselOf(a, msel)] = masters;
937 for (
size_t f : unit.flows)
938 plan.slaveAmsels[{flows[f].slave, flows[f].id}] =
939 setAmsel.at(flows[f].masters);
945SmallVector<TileID> cutTiles(
const AIETargetModel &targetModel,
TileID src,
947 auto neighbors = [&](
TileID t) {
948 SmallVector<TileID, 4> next;
949 for (
auto [bundle, dc, dr] : {std::tuple{WireBundle::North, 0, 1},
950 {WireBundle::South, 0, -1},
951 {WireBundle::East, 1, 0},
952 {WireBundle::West, -1, 0}}) {
953 TileID n{t.col + dc, t.row + dr};
954 if (n.col >= 0 && n.col < targetModel.
columns() && n.row >= 0 &&
955 n.row < targetModel.
rows() &&
961 auto reach = [&](std::optional<TileID> avoid) {
962 return breadthFirstParents(
965 SmallVector<TileID, 4> next = neighbors(t);
966 llvm::erase(next, avoid);
971 std::map<TileID, TileID> via = reach(std::nullopt);
974 SmallVector<TileID> interior;
975 for (
TileID t = via.at(dst); t != src; t = via.at(t))
976 interior.push_back(t);
977 llvm::erase_if(interior, [&](
TileID t) {
return reach(t).count(dst) > 0; });
983struct PrioritizedPackets {
984 std::set<std::tuple<TileID, Port, int>> packets;
985 bool contains(
const RoutedStream &s)
const {
990PrioritizedPackets prioritizedPackets(DeviceOp device) {
991 PrioritizedPackets prioritized;
992 for (PacketFlowOp flow : device.getOps<PacketFlowOp>())
993 if (flow.getPriorityRoute().value_or(
false))
994 for (
auto src : flow.getPorts().getOps<PacketSourceOp>())
995 prioritized.packets.insert(
996 {cast<TileOp>(src.getTile().getDefiningOp()).getTileID(),
997 src.port(), flow.IDInt()});
1004SmallVector<size_t, 8> largestApartSet(
const StreamConflicts &conflicts,
1005 ArrayRef<size_t> candidates,
1007 SmallVector<size_t, 8> clique, best;
1009 auto grow = [&](
auto &self, ArrayRef<size_t> cands) ->
void {
1010 if (clique.size() > best.size())
1012 for (
auto [k, s] : llvm::enumerate(cands)) {
1014 clique.size() + cands.size() - k <= best.size())
1016 SmallVector<size_t, 8> next;
1017 for (
size_t t : cands.drop_front(k + 1))
1020 clique.push_back(s);
1025 grow(grow, candidates);
1032std::map<TileID, std::set<int>> configuredAmsels(DeviceOp device) {
1033 const int numArbiters = device.getTargetModel().getNumArbiters();
1034 std::map<TileID, std::set<int>> amsels;
1035 for (
auto swboxOp : device.getOps<SwitchboxOp>())
1036 for (
auto amselOp : swboxOp.getConnections().getOps<AMSelOp>())
1037 amsels[swboxOp.getTileOp().getTileID()].insert(
1038 amselOp.arbiterIndex() + amselOp.getMselValue() * numArbiters);
1050std::optional<std::string>
1051tooFewArbiters(DeviceOp device,
const StreamConflicts &conflicts,
1052 llvm::function_ref<
bool(
TileID)> pinsHops,
1053 const PrioritizedPackets &prioritized) {
1054 const AIETargetModel &targetModel = device.getTargetModel();
1057 std::map<TileID, SmallVector<size_t, 8>> pinned;
1061 pinned[s.dst.tile].push_back(i);
1062 if (s.src.tile == s.dst.tile)
1064 bool circuitless = prioritized.contains(s);
1065 if (circuitless || pinsHops(s.src.tile))
1066 pinned[s.src.tile].push_back(i);
1067 for (
TileID t : cutTiles(targetModel, s.src.tile, s.dst.tile))
1068 if (circuitless || pinsHops(t))
1069 pinned[t].push_back(i);
1071 std::map<TileID, std::set<int>> reserved = configuredAmsels(device);
1073 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
1074 for (
const auto &[tileId, candidates] : pinned) {
1075 const std::set<int> &taken = reserved[tileId];
1077 for (
int a = 0; a < numArbiters; a++)
1078 free += llvm::any_of(llvm::seq(numMselsPerArbiter), [&](
int m) {
1079 return !taken.count(a + m * numArbiters);
1082 std::set<std::pair<TileID, Port>> srcs, dsts;
1083 for (
size_t s : candidates) {
1084 srcs.insert({streams[s].src.tile, streams[s].src.port});
1085 dsts.insert({streams[s].dst.tile, streams[s].dst.port});
1087 if (std::min(srcs.size(), dsts.size()) <= free)
1089 SmallVector<size_t, 8> best = largestApartSet(conflicts, candidates, free);
1090 if (best.size() <= free)
1094 llvm::raw_string_ostream os(reason);
1095 if (best.size() == 1) {
1096 os <<
"at tile (" << tileId.col <<
", " << tileId.row <<
"), "
1098 <<
" takes an arbiter whatever the routing, but the switchbox has "
1102 os <<
"at tile (" << tileId.col <<
", " << tileId.row <<
"), no two of ";
1104 best, os, [&](
size_t s) { os <<
describeStream(streams[s]); },
", ");
1105 os <<
" can share an arbiter, and each takes one there whatever the "
1106 "routing, but the switchbox has "
1107 << free <<
" free. For example, " << conflicts.
explain(best[0], best[1]);
1110 return std::nullopt;
1117 std::optional<Port> slave;
1123std::pair<std::map<TileID, SmallVector<Leaving, 8>>, std::set<TileID>>
1124leavingPinnedTrees(
const StreamConflicts &conflicts,
1126 const PrioritizedPackets &prioritized) {
1128 std::map<PathEndPoint, std::map<PathEndPoint, std::optional<PathEndPoint>>>
1130 for (
const auto &[src, hops] : pinnedTrees)
1131 for (
const TreeHop &hop : hops) {
1132 auto [it, first] = preds[src].try_emplace(hop.to, hop.from);
1133 if (!first && it->second && !(*it->second == hop.from))
1136 std::map<TileID, SmallVector<Leaving, 8>> leaving;
1137 std::set<TileID> onTrees;
1142 auto tree = prioritized.contains(s) ? preds.find({s.src.tile, s.src.port})
1144 SmallVector<std::pair<Port, PathEndPoint>, 8> hops;
1145 PathEndPoint at{s.dst.tile, s.dst.port}, src{s.src.tile, s.src.port};
1147 tree != preds.end() && !(at == src) && n <= tree->second.size(); n++) {
1148 auto pred = tree->second.find(at);
1149 if (pred == tree->second.end() || !pred->second)
1151 if (pred->second->coords == at.coords)
1152 hops.push_back({pred->second->port, at});
1156 leaving[s.dst.tile].push_back({i, std::nullopt, s.dst.port});
1159 for (
auto [slave, hop] : hops) {
1160 leaving[hop.coords].push_back({i, slave, hop.port});
1161 onTrees.insert(hop.coords);
1164 return {std::move(leaving), std::move(onTrees)};
1177std::string explainSharedArbiter(
TileID tileId,
size_t a,
size_t b,
1178 ArrayRef<ArbiterLink> chain,
1179 ArrayRef<Leaving> here,
1180 const StreamConflicts &conflicts) {
1181 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
1183 llvm::raw_string_ostream os(reason);
1184 os <<
"at tile (" << tileId.col <<
", " << tileId.row
1185 <<
"), the routes prioritized flows (priority_route) keep put "
1188 for (
auto [k, link] : llvm::enumerate(chain))
1189 os << (k == 0 ?
"" :
", which")
1190 << (link.rule ?
" takes one packet rule on " :
" leaves by ")
1193 os <<
", and a master port or packet rule takes one arbiter. "
1201std::optional<std::string> sharedArbiterAt(
TileID tileId,
1202 ArrayRef<Leaving> here,
1203 const StreamConflicts &conflicts) {
1204 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
1209 std::map<size_t, SmallVector<ArbiterLink, 4>> with;
1210 for (
auto [x, l] : llvm::enumerate(here))
1211 for (
auto [y, m] : llvm::enumerate(here)) {
1214 if (l.master == m.master)
1215 with[x].push_back({y, l.master,
false});
1216 else if (l.slave && m.slave && *l.slave == *m.slave &&
1217 streams[l.stream].packetID == streams[m.stream].packetID)
1218 with[x].push_back({y, *l.slave,
true});
1220 for (
size_t x : llvm::make_first_range(with)) {
1222 std::map<size_t, size_t> via = breadthFirstParents(x, [&](
size_t u) {
1223 return llvm::map_range(
1224 with.at(u), [](
const ArbiterLink &link) { return link.visit; });
1226 size_t a = here[x].stream;
1227 for (
size_t y : llvm::make_first_range(via)) {
1228 size_t b = here[y].stream;
1231 SmallVector<ArbiterLink, 4> chain;
1232 for (
size_t v = y; v != x; v = via.at(v))
1234 *llvm::find_if(with.at(via.at(v)), [&](
const ArbiterLink &link) {
1235 return link.visit == v;
1237 std::reverse(chain.begin(), chain.end());
1238 return explainSharedArbiter(tileId, a, b, chain, here, conflicts);
1241 return std::nullopt;
1248llvm::Error unroutableArbiters(DeviceOp device,
1249 const StreamConflicts &conflicts,
1250 llvm::function_ref<
bool(
TileID)> pinsHops,
1252 PrioritizedPackets prioritized = prioritizedPackets(device);
1253 if (std::optional<std::string> reason =
1254 tooFewArbiters(device, conflicts, pinsHops, prioritized))
1255 return llvm::make_error<RoutingFailure>(std::move(*reason));
1256 if (pinnedTrees.empty())
1257 return llvm::Error::success();
1258 auto [leaving, onTrees] =
1259 leavingPinnedTrees(conflicts, pinnedTrees, prioritized);
1260 for (
TileID tileId : onTrees)
1261 if (std::optional<std::string> reason =
1262 sharedArbiterAt(tileId, leaving[tileId], conflicts))
1263 return llvm::make_error<PinnedRoutingFailure>(std::move(*reason));
1264 return llvm::Error::success();
1268static void getOrCreateConnect(OpBuilder &builder, ShimMuxOp shimMux,
1269 Location loc, WireBundle srcBundle,
int srcCh,
1270 WireBundle destBundle,
int destCh) {
1271 for (
auto connect : shimMux.getConnections().getOps<ConnectOp>())
1272 if (connect.getSourceBundle() == srcBundle &&
1273 connect.getSourceChannel() == srcCh &&
1274 connect.getDestBundle() == destBundle &&
1275 connect.getDestChannel() == destCh)
1277 ConnectOp::create(builder, loc, srcBundle, srcCh, destBundle, destCh);
1280static PathEndPoint sourceOf(FlowOp flow) {
1281 auto tile = cast<TileOp>(flow.getSource().getDefiningOp());
1282 return PathEndPoint{tile.getTileID(),
1283 {flow.getSourceBundle(), flow.getSourceChannel()}};
1289static void lowerShimDMAPorts(DeviceOp device, OpBuilder &builder,
1290 DynamicTileAnalysis &analyzer) {
1291 for (
auto switchbox : device.getOps<SwitchboxOp>()) {
1292 TileOp tileOp = switchbox.getTileOp();
1293 if (!tileOp.isShimNOCTile())
1295 auto connectInMux = [&](
Port src,
Port dst) {
1296 builder.setInsertionPointAfter(tileOp);
1297 ShimMuxOp shimMux = analyzer.
getShimMux(builder, tileOp.colIndex());
1298 builder.setInsertionPointToStart(&shimMux.getConnections().front());
1299 getOrCreateConnect(builder, shimMux, tileOp.getLoc(), src.bundle,
1300 src.channel, dst.bundle, dst.channel);
1302 for (Operation &op : switchbox.getConnections().front()) {
1303 if (
auto rules = dyn_cast<PacketRulesOp>(op);
1304 rules && rules.getSourceBundle() == WireBundle::DMA) {
1306 connectInMux(rules.sourcePort(), {WireBundle::North, ch});
1307 rules.setSourceBundle(WireBundle::South);
1308 rules.setSourceChannel(ch);
1310 if (
auto masterSet = dyn_cast<MasterSetOp>(op);
1311 masterSet && masterSet.getDestBundle() == WireBundle::DMA) {
1313 connectInMux({WireBundle::North, ch}, masterSet.destPort());
1314 masterSet.setDestBundle(WireBundle::South);
1315 masterSet.setDestChannel(ch);
1325 LogicalResult emit(DeviceOp device, OpBuilder &builder,
1326 DynamicTileAnalysis &analyzer)
const;
1327 SmallVector<std::pair<int, int>, 2>
1328 statedCubes(
const std::pair<PhysPort, int> &slaveFlow,
int idMask)
const;
1332 std::optional<HoldCycle> sharedReceiverCycle;
1336 std::map<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>> packetFlows;
1337 SmallVector<std::pair<PhysPort, int>, 4> slavePorts;
1339 DenseMap<PhysPort, BoolAttr> keepPktHeaderAttr;
1342 DenseSet<std::pair<PhysPort, int>> prioritizedFlows;
1345 DenseSet<std::pair<PhysPort, int>> overlayFlows;
1349 DenseSet<std::pair<PhysPort, int>> markedFlows;
1351 DenseSet<PhysPort> ctrlPktOverlayMasterPorts;
1355 DenseSet<PhysPort> prioritizedSourcePorts;
1361 std::map<std::pair<PhysPort, int>, std::set<int>> pinnedMasks;
1372 std::map<TileID, SmallVector<Connect, 4>> circuitHops;
1373 std::map<TileID, ArbiterPlan> plans;
1378struct PacketFlowRouting : PacketPlan {
1379 using FlowKey = std::pair<Port, int>;
1381 PacketFlowRouting(DeviceOp device,
const Routing &routing,
1382 const StreamConflicts &conflicts,
1383 const PinnedOverlay &pinned,
bool routeCircuit,
1384 bool circuitSwitchHops,
bool prioritize)
1385 : device(device), routing(routing), conflicts(conflicts), pinned(pinned),
1386 prioritize(prioritize),
1387 keepOverlay(!pinned.rules.empty() && device.getHasCtrlPktOverlay()),
1388 routeCircuit(routeCircuit), circuitSwitchHops(circuitSwitchHops),
1390 numArbiters(targetModel.getNumArbiters()),
1391 numMselsPerArbiter(targetModel.getNumMselsPerArbiter()),
1392 maxPacketId(targetModel.getMaxPacketId()),
1393 idBits(llvm::Log2_32_Ceil(maxPacketId + 1)), idMask((1 << idBits) - 1) {
1396 llvm::Expected<PacketPlan> plan();
1397 void collectFlows();
1398 void findCircuitHops();
1399 void collectSlaveFlows();
1402 std::optional<std::string> breakHoldCycles();
1403 RoutingFaults faults()
const;
1406 std::optional<int>
id = std::nullopt);
1407 std::optional<std::pair<size_t, size_t>>
1408 conflictingStreams(
TileID tileId,
const SlaveFlow &a,
const SlaveFlow &b);
1409 std::optional<ArbiterPlan>
1410 planTile(
TileID tileId, SmallVectorImpl<std::pair<size_t, size_t>> &blocking);
1411 void moveFlow(
TileID tileId,
FlowKey key,
bool overlay =
false);
1413 void splitFlow(
TileID tileId,
FlowKey key, ArrayRef<FlowKey> partners);
1416 const Routing &routing;
1417 const StreamConflicts &conflicts;
1418 const PinnedOverlay &pinned;
1424 std::map<TileID, std::string> unkeptOverlay;
1426 bool circuitSwitchHops;
1427 const AIETargetModel &targetModel;
1428 const int numArbiters;
1429 const int numMselsPerArbiter;
1430 const uint32_t maxPacketId;
1439 PathEndPoint src, dst;
1441 bool operator<(
const StreamEnds &rhs)
const {
1442 return std::tie(src, dst,
id) < std::tie(rhs.src, rhs.dst, rhs.id);
1445 std::map<StreamEnds, SmallVector<size_t, 1>> packetStreamIndex;
1446 DenseMap<std::pair<PhysPort, int>, SmallVector<size_t, 2>> slaveFlowStreams;
1450 std::map<std::pair<PhysPort, int>, std::map<PathEndPoint, std::set<Port>>>
1454 std::vector<SmallVector<StreamHop, 8>> routes;
1456 std::set<PathEndPoint> circuitSources;
1460 std::map<TileID, SmallVector<std::pair<Connect, int>, 8>> switchboxes;
1464 std::map<TileID, std::set<int>> reservedAmsels;
1468 std::map<TileID, std::map<FlowKey, SlaveFlow>> tileSlaveFlows;
1469 std::map<TileID, SmallVector<SlaveFlow, 8>> tileFlows;
1472 std::set<std::tuple<TileID, FlowKey, FlowKey>> apart;
1473 std::set<std::tuple<TileID, FlowKey, int>> offArbiter;
1474 std::set<std::pair<TileID, Connect>> hazardConnections;
1476 std::set<std::pair<TileID, Connect>> overlayConnections;
1481 std::set<TreeSplit> hazardSplits;
1482 std::set<std::pair<PathEndPoint, PathEndPoint>> hazardApart;
1483 std::set<std::pair<PathEndPoint, PathEndPoint>> hazardTogether;
1484 std::set<PathEndPoint> hazardUntrunk;
1485 std::set<TileID> crowdedTiles;
1486 std::optional<std::string> planFailure;
1488 bool overlayFailure =
false;
1491 std::optional<std::pair<Location, std::string>> incomplete;
1495void PacketFlowRouting::collectFlows() {
1496 for (
auto [i, s] : llvm::enumerate(conflicts.
getStreams()))
1498 packetStreamIndex[{{s.src.tile, s.src.port},
1499 {s.dst.tile, s.dst.port},
1502 for (
const RoutedStream &s : conflicts.
getStreams())
1503 routes.push_back(s.hops);
1506 for (FlowOp flow : device.getOps<FlowOp>())
1507 circuitSources.insert(sourceOf(flow));
1509 for (PacketFlowOp pktFlowOp : device.getOps<PacketFlowOp>()) {
1510 Region &r = pktFlowOp.getPorts();
1511 Block &b = r.front();
1512 int flowID = pktFlowOp.IDInt();
1513 SmallVector<std::pair<TileID, Port>, 4> sources;
1516 for (Operation &Op : b.getOperations()) {
1517 if (
auto pktSource = dyn_cast<PacketSourceOp>(Op)) {
1518 auto srcTile = cast<TileOp>(pktSource.getTile().getDefiningOp());
1520 {{srcTile.colIndex(), srcTile.rowIndex()}, pktSource.port()});
1525 for (Operation &Op : b.getOperations()) {
1526 auto pktDest = dyn_cast<PacketDestOp>(Op);
1529 auto destTile = cast<TileOp>(pktDest.getTile().getDefiningOp());
1530 Port destPort = pktDest.port();
1531 TileID destCoords = {destTile.colIndex(), destTile.rowIndex()};
1533 auto keep = pktFlowOp.getKeepPktHeader();
1534 keepPktHeaderAttr[{destTile.getTileID(), destPort}] =
1535 keep ? BoolAttr::get(Op.getContext(), *keep) : nullptr;
1537 for (
auto &[srcCoords, srcPort] : sources) {
1538 TileID srcSB = {srcCoords.col, srcCoords.row};
1539 PathEndPoint srcPoint = {srcSB, srcPort};
1540 if (circuitSources.count(srcPoint))
1545 SmallVector<size_t, 1> flowStreams;
1546 if (
auto stream = packetStreamIndex.find(
1547 {srcPoint, {destCoords, destPort}, flowID});
1548 stream != packetStreamIndex.end()) {
1549 int mask =
static_cast<int>(pktFlowOp.getMask().value_or(~0));
1550 for (
size_t i : stream->second)
1552 conflicts.
getStreams()[i].packetMask == mask)
1553 flowStreams.push_back(i);
1554 if (flowStreams.empty())
1555 flowStreams.push_back(stream->second.front());
1557 for (
size_t stream : flowStreams) {
1558 SmallVector<StreamHop, 8> &hops = routes[stream];
1561 size_t connections = 0;
1562 for (
const auto &[_, setting] : settings)
1563 connections += setting.srcs.size();
1564 std::optional<std::pair<TileID, Port>> at{{srcSB, srcPort}};
1565 while (at && hops.size() <= connections) {
1566 auto [tile, input] = *at;
1567 hops.push_back({tile, input, std::nullopt});
1569 auto setting = settings.find(tile);
1570 if (setting == settings.end())
1572 for (
auto [src, dest] :
1573 llvm::zip(setting->second.srcs, setting->second.dsts))
1574 if (src == input && !(tile == destCoords && dest == destPort) &&
1575 findPathToDest(settings, tile, dest, destCoords, destPort)) {
1576 at = linkedInput(tile, dest);
1582 bool srcRouted =
false;
1584 for (
const auto &[curr, setting] : settings) {
1585 assert(setting.srcs.size() == setting.dsts.size());
1586 TileID currTile = {curr.col, curr.row};
1587 for (
size_t i = 0; i < setting.srcs.size(); i++) {
1588 Port src = setting.srcs[i];
1589 Port dest = setting.dsts[i];
1591 if (!findPathToDest(settings, currTile, dest, destCoords, destPort))
1593 if (currTile == srcSB && src.bundle == srcPort.bundle &&
1594 src.channel == srcPort.channel)
1596 Connect connect = {{src.bundle, src.channel},
1597 {dest.bundle, dest.channel}};
1599 switchboxes[currTile].begin(), switchboxes[currTile].end(),
1600 std::pair{connect, flowID}) == switchboxes[currTile].end())
1601 switchboxes[currTile].push_back({connect, flowID});
1604 PhysPort slavePort = {currTile, {src.bundle, src.channel}};
1605 std::pair<PhysPort, int> slaveFlow = {slavePort, flowID};
1606 for (
size_t stream : flowStreams)
1607 if (!llvm::is_contained(slaveFlowStreams[slaveFlow], stream))
1608 slaveFlowStreams[slaveFlow].push_back(stream);
1609 slaveFlowSources[slaveFlow][srcPoint].insert(dest);
1610 if (std::optional<uint8_t> mask = pktFlowOp.getMask()) {
1611 pinnedMasks[{{currTile, {src.bundle, src.channel}}, flowID}]
1614 if (pktFlowOp.getPriorityRoute().value_or(
false)) {
1615 bool overlay = !reloadConfigures(pktFlowOp);
1617 markedFlows.insert(slaveFlow);
1618 if (prioritize || pinned.trees.count(srcPoint)) {
1619 prioritizedFlows.insert(slaveFlow);
1621 overlayFlows.insert(slaveFlow);
1622 if (slavePort ==
PhysPort{srcSB, srcPort})
1623 prioritizedSourcePorts.insert(slavePort);
1628 if (!srcRouted && !incomplete)
1632 " could not be routed to destination " +
1634 "; the pathfinder produced an incomplete routing for this "
1644SmallVector<std::pair<int, int>, 2>
1645PacketPlan::statedCubes(
const std::pair<PhysPort, int> &slaveFlow,
1647 SmallVector<std::pair<int, int>, 2> cubes;
1648 auto it = pinnedMasks.find(slaveFlow);
1649 if (it != pinnedMasks.end())
1650 for (
int mask : it->second)
1652 cubes.push_back({mask, slaveFlow.second});
1656const SwitchSettings &PacketFlowRouting::settingsOf(
const PathEndPoint &src,
1657 std::optional<int>
id) {
1659 if (
auto own = routing.
idSettings.find({src, *id});
1662 auto it = routing.
settings.find(src);
1663 return it == routing.
settings.end() ? noSettings : it->second;
1666void PacketFlowRouting::findCircuitHops() {
1669 reservedAmsels = configuredAmsels(device);
1672 std::set<PhysPort> claimedSlaves, claimedMasters;
1673 for (
auto swbox : device.getOps<SwitchboxOp>()) {
1674 TileID tileId = swbox.getTileOp().getTileID();
1675 Region &ops = swbox.getConnections();
1676 for (
auto connect : ops.getOps<ConnectOp>()) {
1677 claimedSlaves.insert({tileId, connect.sourcePort()});
1678 claimedMasters.insert({tileId, connect.destPort()});
1680 for (
auto rules : ops.getOps<PacketRulesOp>())
1681 claimedSlaves.insert({tileId, rules.sourcePort()});
1682 for (
auto masterSet : ops.getOps<MasterSetOp>())
1683 claimedMasters.insert({tileId, masterSet.destPort()});
1687 for (FlowOp flow : device.getOps<FlowOp>())
1688 for (
const auto &[tileId, setting] : settingsOf(sourceOf(flow))) {
1689 for (
Port p : setting.srcs)
1690 claimedSlaves.insert({tileId, p});
1691 for (
Port p : setting.dsts)
1692 claimedMasters.insert({tileId, p});
1694 for (
auto &[tileId, connects] : switchboxes) {
1695 if (!circuitSwitchHops ||
1698 std::set<Port> masters;
1699 for (
const auto &[conn, flowID] : connects)
1700 masters.insert(conn.dst);
1701 SmallVector<Port, 8> slaves;
1702 for (
const auto &[conn, flowID] : connects)
1703 if (!llvm::is_contained(slaves, conn.src))
1704 slaves.push_back(conn.src);
1705 const std::set<int> &reserved = reservedAmsels[tileId];
1706 size_t freeArbiters = llvm::count_if(llvm::seq(0, numArbiters), [&](
int a) {
1707 return llvm::none_of(llvm::seq(0, numMselsPerArbiter), [&](
int m) {
1708 return reserved.count(a + m * numArbiters);
1711 for (
Port slave : slaves) {
1712 if (masters.size() <= freeArbiters)
1714 std::set<Port> reached;
1715 std::map<int, std::set<Port>> reachedByID;
1716 for (
const auto &[conn, flowID] : connects)
1717 if (conn.src == slave) {
1718 reached.insert(conn.dst);
1719 reachedByID[flowID].insert(conn.dst);
1722 !claimedSlaves.count({tileId, slave}) &&
1723 llvm::all_of(reached,
1724 [&, tileId = tileId](
Port m) {
1726 !claimedMasters.count({tileId, m});
1730 [&](
const auto &ids) {
return ids.second == reached; }) &&
1731 llvm::all_of(connects, [&, tileId = tileId](
const auto &entry) {
1732 const auto &[other, otherID] = entry;
1733 if (!reached.count(other.dst))
1735 return other.src == slave &&
1736 !prioritizedFlows.contains({{tileId, slave}, otherID});
1740 for (
Port m : reached) {
1741 circuitHops[tileId].push_back({slave, m});
1744 llvm::erase_if(connects, [&](
const auto &entry) {
1745 return entry.first.src == slave;
1751void PacketFlowRouting::collectSlaveFlows() {
1752 LLVM_DEBUG(llvm::dbgs() <<
"Check switchboxes\n");
1754 for (
const auto &[tileId, connects] : switchboxes) {
1755 LLVM_DEBUG(llvm::dbgs() <<
"***switchbox*** " << tileId.col <<
" "
1756 << tileId.row <<
'\n');
1757 for (
const auto &[conn, flowID] : connects) {
1758 Port sourcePort = conn.src;
1759 Port destPort = conn.dst;
1761 std::make_pair(std::make_pair(tileId, sourcePort), flowID);
1762 packetFlows[sourceFlow].push_back({tileId, destPort});
1763 if (markedFlows.contains(sourceFlow))
1764 ctrlPktOverlayMasterPorts.insert({tileId, destPort});
1765 slavePorts.push_back(sourceFlow);
1766 LLVM_DEBUG(llvm::dbgs() <<
"flowID " << flowID <<
':'
1767 << stringifyWireBundle(sourcePort.bundle) <<
" "
1768 << sourcePort.channel <<
" -> "
1769 << stringifyWireBundle(destPort.bundle) <<
" "
1770 << destPort.channel <<
"\n");
1774 for (
const auto &[flow, dests] : packetFlows) {
1775 SlaveFlow &f = tileSlaveFlows[flow.first.first]
1776 .try_emplace({flow.first.second, flow.second},
1777 SlaveFlow{flow.first.second,
1780 overlayFlows.contains(flow)})
1783 if (!llvm::is_contained(f.masters, dest.second))
1784 f.masters.push_back(dest.second);
1785 llvm::sort(f.masters);
1787 for (
const auto &[tileId, byFlow] : tileSlaveFlows)
1788 for (
const auto &[key, f] : byFlow)
1789 tileFlows[tileId].push_back(f);
1792std::optional<std::pair<size_t, size_t>>
1793PacketFlowRouting::conflictingStreams(
TileID tileId,
const SlaveFlow &a,
1794 const SlaveFlow &b) {
1795 auto as = slaveFlowStreams.find({{tileId, a.slave}, a.id});
1796 auto bs = slaveFlowStreams.find({{tileId, b.slave}, b.id});
1797 if (as == slaveFlowStreams.end() || bs == slaveFlowStreams.end())
1798 return std::nullopt;
1799 for (
size_t s : as->second)
1800 for (
size_t t : bs->second)
1802 return std::pair{s, t};
1803 return std::nullopt;
1806std::optional<ArbiterPlan> PacketFlowRouting::planTile(
1807 TileID tileId, SmallVectorImpl<std::pair<size_t, size_t>> &blocking) {
1808 ArrayRef<SlaveFlow> flows = tileFlows.at(tileId);
1809 auto key = [&](
size_t f) {
return FlowKey{flows[f].slave, flows[f].id}; };
1810 auto conflict = [&](
size_t a,
size_t b) {
1812 {tileId, std::min(key(a), key(b)), std::max(key(a), key(b))}) ||
1813 conflictingStreams(tileId, flows[a], flows[b]);
1815 auto excluded = [&](
size_t f,
int arbiter) {
1816 return offArbiter.count({tileId, key(f), arbiter}) > 0;
1818 SmallVector<size_t, 8> overlay, others;
1819 for (
auto [i, f] : llvm::enumerate(flows))
1820 (f.isCtrlPkt ? overlay : others).push_back(i);
1821 if (overlay.empty() || (others.empty() && pinned.rules.empty()))
1822 return planArbiters(targetModel, flows, conflict, excluded,
1823 reservedAmsels[tileId], blocking);
1825 auto subset = [&](ArrayRef<size_t> members) {
1826 SmallVector<SlaveFlow, 8> sub;
1827 for (
size_t m : members)
1828 sub.push_back(flows[m]);
1834 auto keepPinned = [&]() -> std::optional<ArbiterPlan> {
1836 llvm::raw_string_ostream os(reason);
1837 auto unkept = [&](
size_t f) -> llvm::raw_ostream & {
1838 return os <<
"packet flow " << flows[f].id <<
" into "
1842 for (
size_t f : overlay) {
1844 Port slave = flows[f].slave;
1845 if (slave.bundle == WireBundle::DMA &&
1848 auto rules = pinned.rules.find({tileId, slave});
1849 const std::tuple<int, int, int> *rule =
nullptr;
1850 if (rules != pinned.rules.end())
1851 rule = llvm::find_if(rules->second, [&](
const auto &r) {
1852 auto [mask, value, amsel] = r;
1853 return (flows[f].id & mask) == (value & mask);
1855 if (!rule || rule == rules->second.end()) {
1856 unkept(f) <<
"takes no packet rule there alone.";
1857 }
else if (
int amsel = std::get<2>(*rule);
1858 excluded(f, amsel % numArbiters)) {
1859 unkept(f) <<
"takes arbiter " << amsel % numArbiters
1860 <<
" alone, and here can deadlock holding it.";
1861 }
else if (reservedAmsels[tileId].count(amsel)) {
1862 unkept(f) <<
"takes amsel<" << amsel % numArbiters <<
"> ("
1863 << amsel / numArbiters
1864 <<
") alone, and the design's own switchbox takes it.";
1865 }
else if (kept.amselMasters.try_emplace(amsel, flows[f].masters)
1866 .first->second != flows[f].masters) {
1867 unkept(f) <<
"takes amsel<" << amsel % numArbiters <<
"> ("
1868 << amsel / numArbiters
1869 <<
") alone, to other master ports than here.";
1871 kept.slaveAmsels[key(f)] = amsel;
1874 unkeptOverlay[tileId] = std::move(reason);
1875 return std::nullopt;
1877 for (
auto [k, a] : llvm::enumerate(overlay))
1878 for (
size_t b : llvm::drop_begin(overlay, k + 1))
1879 if (kept.slaveAmsels.at(key(a)) % numArbiters ==
1880 kept.slaveAmsels.at(key(b)) % numArbiters &&
1881 flows[a].slave != flows[b].slave && conflict(a, b)) {
1882 unkept(a) <<
"and packet flow " << flows[b].id <<
" into "
1884 <<
" share an arbiter alone, and here can deadlock on it.";
1885 unkeptOverlay[tileId] = std::move(reason);
1886 return std::nullopt;
1890 SmallVector<std::pair<size_t, size_t>, 4> stuck;
1891 std::optional<ArbiterPlan> plan;
1892 if (!pinned.rules.empty())
1893 plan = keepPinned();
1894 if (!plan && keepOverlay)
1895 return std::nullopt;
1897 plan = planArbiters(
1898 targetModel, subset(overlay),
1899 [&](
size_t a,
size_t b) {
return conflict(overlay[a], overlay[b]); },
1900 [&](
size_t f,
int arbiter) {
return excluded(overlay[f], arbiter); },
1901 reservedAmsels[tileId], stuck);
1903 for (
auto [a, b] : stuck)
1904 blocking.push_back({overlay[a], overlay[b]});
1905 return std::nullopt;
1913 auto around = [&](ArbiterPlan plan, ArrayRef<size_t> planned,
1914 ArrayRef<size_t> pending,
1915 SmallVectorImpl<std::pair<size_t, size_t>> &blocking)
1916 -> std::optional<ArbiterPlan> {
1917 auto arbiterOf = [&](
size_t f) {
1918 return plan.slaveAmsels.at(key(f)) % numArbiters;
1920 SmallVector<size_t, 8> placed(planned), rest;
1921 for (
size_t f : pending) {
1922 auto same = llvm::find_if(plan.amselMasters, [&](
const auto &entry) {
1923 return entry.second == flows[f].masters;
1925 if (same == plan.amselMasters.end()) {
1929 for (
size_t g : placed)
1930 if (arbiterOf(g) == same->first % numArbiters &&
1931 flows[g].slave != flows[f].slave && conflict(f, g))
1932 blocking.push_back({g, f});
1933 if (excluded(f, same->first % numArbiters))
1934 blocking.push_back({planned.front(), f});
1935 plan.slaveAmsels[key(f)] = same->first;
1936 placed.push_back(f);
1938 if (!blocking.empty())
1939 return std::nullopt;
1945 llvm::EquivalenceClasses<Port> tied = tiedMasters(flows);
1946 std::map<Port, std::set<int>> tiedArbiters;
1947 std::set<int> reserved = reservedAmsels[tileId];
1948 for (
const auto &[amsel, masters] : plan.amselMasters) {
1949 reserved.insert(amsel);
1950 tiedArbiters[tied.getLeaderValue(masters.front())].insert(amsel %
1953 SmallVector<std::pair<size_t, size_t>, 4> stuck;
1954 std::optional<ArbiterPlan> rested = planArbiters(
1955 targetModel, subset(rest),
1956 [&](
size_t a,
size_t b) {
return conflict(rest[a], rest[b]); },
1957 [&](
size_t f,
int arbiter) {
1958 auto arbiters = tiedArbiters.find(
1959 tied.getLeaderValue(flows[rest[f]].masters.front()));
1960 return excluded(rest[f], arbiter) ||
1961 (arbiters != tiedArbiters.end() &&
1962 (arbiters->second.size() > 1 ||
1963 !arbiters->second.count(arbiter))) ||
1964 llvm::any_of(placed, [&](
size_t g) {
1965 return arbiterOf(g) == arbiter &&
1966 flows[g].slave != flows[rest[f]].slave &&
1967 conflict(rest[f], g);
1972 for (
auto [a, b] : stuck)
1973 blocking.push_back({rest[a], rest[b]});
1974 for (
size_t f : rest)
1975 for (
size_t g : placed)
1976 if (flows[g].slave != flows[f].slave && conflict(f, g))
1977 blocking.push_back({g, f});
1978 return std::nullopt;
1980 plan.slaveAmsels.insert(rested->slaveAmsels.begin(),
1981 rested->slaveAmsels.end());
1982 plan.amselMasters.insert(rested->amselMasters.begin(),
1983 rested->amselMasters.end());
1986 SmallVector<std::pair<size_t, size_t>, 4> first;
1987 if (std::optional<ArbiterPlan> placed = around(*plan, overlay, others, first))
1990 blocking.append(first.begin(), first.end());
1991 return std::nullopt;
1996 llvm::EquivalenceClasses<Port> tied = tiedMasters(flows);
1997 SmallVector<size_t, 8> joint(overlay), rest;
1998 for (
size_t f : others) {
1999 bool tiedToOverlay = llvm::any_of(overlay, [&](
size_t g) {
2000 return tied.isEquivalent(flows[g].masters.front(),
2001 flows[f].masters.front());
2003 (tiedToOverlay ? joint : rest).push_back(f);
2006 std::optional<ArbiterPlan> together;
2007 if (joint.size() > overlay.size())
2008 together = planArbiters(
2009 targetModel, subset(joint),
2010 [&](
size_t a,
size_t b) {
return conflict(joint[a], joint[b]); },
2011 [&](
size_t f,
int arbiter) {
return excluded(joint[f], arbiter); },
2012 reservedAmsels[tileId], stuck);
2014 blocking.append(first.begin(), first.end());
2015 return std::nullopt;
2017 return around(*together, joint, rest, blocking);
2020void PacketFlowRouting::moveFlow(
TileID tileId,
FlowKey key,
bool overlay) {
2021 auto move = [&](
const Connect &c) {
2022 hazardConnections.insert({tileId, c});
2024 overlayConnections.insert({tileId, c});
2026 auto flows = tileSlaveFlows.find(tileId);
2027 if (flows != tileSlaveFlows.end())
2028 if (
auto f = flows->second.find(key); f != flows->second.end())
2029 for (
Port m : f->second.masters)
2030 move({key.first, m});
2031 for (
const Connect &hop : circuitHops[tileId])
2032 if (hop.src == key.first)
2038void PacketFlowRouting::moveUnit(
TileID tileId,
FlowKey key) {
2039 moveFlow(tileId, key);
2040 auto flows = tileSlaveFlows.find(tileId);
2041 if (flows == tileSlaveFlows.end())
2043 auto self = flows->second.find(key);
2044 if (self == flows->second.end())
2046 llvm::EquivalenceClasses<Port> tied =
2047 tiedMasters(llvm::make_second_range(flows->second));
2048 for (
const auto &[other, f] : flows->second)
2049 if (f.masters.size() > 1 &&
2050 tied.isEquivalent(f.masters.front(), self->second.masters.front()))
2051 moveFlow(tileId, other);
2054void PacketFlowRouting::splitFlow(
TileID tileId,
FlowKey key,
2055 ArrayRef<FlowKey> partners) {
2056 auto flows = tileSlaveFlows.find(tileId);
2057 if (flows == tileSlaveFlows.end())
2059 auto self = flows->second.find(key);
2060 if (self == flows->second.end())
2062 auto onlySource = [&](
FlowKey k) -> std::optional<PathEndPoint> {
2063 auto sources = slaveFlowSources.find({{tileId, k.first}, k.second});
2064 if (sources == slaveFlowSources.end() || sources->second.size() != 1)
2065 return std::nullopt;
2066 return sources->second.begin()->first;
2068 std::optional<PathEndPoint> src = onlySource(key);
2071 auto sharesMaster = [&](
const auto &f,
const std::set<Port> &ports) {
2072 return llvm::any_of(f.masters, [&](
Port m) { return ports.count(m); });
2074 std::set<Port> own(self->second.masters.begin(), self->second.masters.end());
2075 std::set<FlowKey> siblings;
2076 for (
const auto &[other, f] : flows->second)
2077 if (other != key && other.first == key.first && sharesMaster(f, own) &&
2078 onlySource(other) == src)
2079 siblings.insert(other);
2082 auto tied = [&](
FlowKey partner,
bool skipSiblings) {
2083 llvm::EquivalenceClasses<Port> classes =
2084 tiedMasters(llvm::make_filter_range(
2085 llvm::make_second_range(flows->second), [&](
const SlaveFlow &f) {
2086 FlowKey other{f.slave, f.id};
2087 return other != key && !(skipSiblings && siblings.count(other));
2089 auto unit = classes.members(flows->second.at(partner).masters.front());
2090 return std::set<Port>(unit.begin(), unit.end());
2092 auto split = [&](
FlowKey other,
2093 std::optional<PathEndPoint> apart = std::nullopt) {
2094 int a = key.second, b = other.second;
2095 if (!apart && a > b)
2097 hazardSplits.insert({*src, tileId, a, b, apart});
2099 for (
FlowKey partner : partners) {
2100 if (partner == key || !flows->second.count(partner))
2102 if (siblings.count(partner)) {
2106 std::set<Port> partnerUnit = tied(partner,
true);
2107 if (sharesMaster(self->second, partnerUnit))
2111 bool unjoined =
false;
2112 for (
FlowKey other : siblings)
2113 for (
Port m : flows->second.find(other)->second.masters)
2115 hazardConnections.insert({tileId, {other.first, m}});
2120 std::set<Port> unit = tied(partner,
false);
2121 for (
FlowKey other : siblings) {
2122 const SlaveFlow &f = flows->second.find(other)->second;
2123 if (!sharesMaster(f, unit))
2128 SmallVector<PathEndPoint, 2> apart;
2129 for (
Port m : f.masters)
2130 if (partnerUnit.count(m))
2131 apart.push_back({tileId, m});
2132 if (PathEndPoint{tileId, key.first} == *src || apart.empty() ||
2133 !llvm::all_of(apart, [&](
const PathEndPoint &p) {
2134 return packetStreamIndex.count({*src, p, other.second});
2139 for (
const PathEndPoint &p : apart)
2147llvm::Expected<PacketPlan> PacketFlowRouting::plan() {
2150 collectSlaveFlows();
2154 planFailure = breakHoldCycles();
2155 if (planFailure && overlayFailure)
2156 return llvm::make_error<PinnedRoutingFailure>(std::move(*planFailure),
2159 return llvm::make_error<RoutingFailure>(std::move(*planFailure), faults());
2160 return PacketPlan(std::move(*
this));
2163RoutingFaults PacketFlowRouting::faults()
const {
2164 RoutingFaults faults;
2165 faults.
connections.assign(hazardConnections.begin(), hazardConnections.end());
2166 faults.
splits.assign(hazardSplits.begin(), hazardSplits.end());
2167 faults.
crowded.assign(crowdedTiles.begin(), crowdedTiles.end());
2168 faults.
apart.assign(hazardApart.begin(), hazardApart.end());
2169 faults.
together.assign(hazardTogether.begin(), hazardTogether.end());
2170 faults.
untrunk.assign(hazardUntrunk.begin(), hazardUntrunk.end());
2172 hazardSplits.empty() && crowdedTiles.empty() &&
2173 hazardApart.empty() && hazardUntrunk.empty() &&
2174 hazardConnections == overlayConnections;
2178void PacketFlowRouting::checkRules() {
2181 std::map<PhysPort, SmallVector<std::pair<int, int>>> existingCubes;
2182 for (
auto swbox : device.getOps<SwitchboxOp>())
2183 for (
auto rules : swbox.getConnections().getOps<PacketRulesOp>())
2184 for (
auto rule : rules.getRules().getOps<PacketRuleOp>())
2185 existingCubes[{swbox.getTileOp().getTileID(), rules.sourcePort()}]
2186 .push_back({rule.maskInt(), rule.valueInt()});
2188 SmallVector<std::pair<int, int>, 4> stated;
2189 SmallVector<int, 4> derived;
2191 for (
const auto &[slaveFlow, sources] : slaveFlowSources) {
2192 const auto &[first, firstMasters] = *sources.begin();
2193 auto other = llvm::find_if(
2194 sources, [&, &firstMasters = firstMasters](
const auto &source) {
2195 return source.second != firstMasters;
2197 if (other == sources.end())
2199 const auto &[slavePort, id] = slaveFlow;
2200 TileID tileId = slavePort.first;
2201 moveFlow(tileId, {slavePort.second,
id});
2204 const PathEndPoint &second = other->first;
2205 planFailure = llvm::formatv(
2206 "at tile ({0}, {1}), packets with id {2} from {3} and {4} enter on {5} "
2207 "and leave by different ports; a switchbox routes on the id alone, so "
2208 "each source's packets would also go where the other's do.",
2213 for (
const auto &[tileId, byFlow] : tileSlaveFlows) {
2218 [&, tileId = tileId, &byFlow = byFlow](
2219 Port slave, std::optional<std::pair<PathEndPoint, Port>> without,
2221 SmallVector<SmallVector<Port, 4>> *groupMasters =
nullptr) {
2222 std::map<std::pair<bool, SmallVector<Port, 4>>, RuleGroup> groups;
2223 for (
const auto &[key, f] : byFlow) {
2224 if (f.slave != slave || (overlayOnly && !f.isCtrlPkt))
2226 SmallVector<Port, 4> masters = f.masters;
2227 auto sources = slaveFlowSources.find({{tileId, slave}, f.id});
2228 if (without && sources != slaveFlowSources.end()) {
2229 std::set<Port> kept;
2230 for (
const auto &[src, ports] : sources->second)
2231 for (
Port m : ports)
2232 if (!(src == without->first) || m != without->second)
2234 masters.assign(kept.begin(), kept.end());
2236 if (masters.empty())
2238 RuleGroup &group = groups[{f.isCtrlPkt, masters}];
2239 auto cubes = statedCubes({{tileId, f.slave}, f.id}, idMask);
2241 group.derived.push_back(f.id);
2242 for (
auto cube : cubes)
2243 if (!llvm::is_contained(group.stated, cube))
2244 group.stated.push_back(cube);
2246 SmallVector<GroupClaims> claims;
2247 for (
const auto &[masters, group] : groups) {
2248 claims.push_back({group.stated, group.derived, masters.first});
2250 groupMasters->push_back(masters.second);
2252 return slavePortRules(claims, existingCubes[{tileId, slave}], idBits,
2256 [&, tileId = tileId](
2258 std::optional<std::pair<PathEndPoint, Port>> without = {}) {
2259 return existingCubes[{tileId, slave}].size() +
2260 newRules(slave, without,
false).size();
2265 auto splitRules = [&, tileId = tileId, &byFlow = byFlow](
Port slave) {
2266 std::map<PathEndPoint, std::map<PathEndPoint, std::set<int>>> dstIds;
2267 for (
const auto &[key, f] : byFlow) {
2268 if (f.slave != slave)
2270 auto streams = slaveFlowStreams.find({{tileId, slave}, f.id});
2271 if (streams == slaveFlowStreams.end())
2273 for (
size_t i : streams->second) {
2274 const RoutedStream &s = conflicts.
getStreams()[i];
2279 std::optional<std::pair<PathEndPoint, PathEndPoint>> best;
2280 size_t fewest = rulesNeeded(slave);
2281 for (
const auto &[src, dsts] : dstIds)
2282 for (
const auto &[dst, ids] : dsts)
2283 if (dst.coords == tileId && dsts.size() > 1)
2284 if (
size_t n = rulesNeeded(slave, {{src, dst.port}}); n < fewest) {
2290 const auto &[src, apart] = *best;
2291 const std::set<int> &apartIds = dstIds[src][apart];
2292 for (
const auto &[dst, ids] : dstIds[src]) {
2293 auto carries = [&, &src = src, &dst = dst](
int id) {
2294 return packetStreamIndex.count({src, dst,
id}) > 0;
2296 auto b = llvm::find_if_not(apartIds, carries);
2297 if (!(dst == apart) && b != apartIds.end())
2298 hazardSplits.insert({src, tileId, *ids.begin(), *b, apart});
2305 auto claim = [&, tileId = tileId](
const SlaveFlow &f) {
2306 auto cubes = statedCubes({{tileId, f.slave}, f.id}, idMask);
2308 cubes.push_back({idMask, f.id});
2311 for (
auto a = byFlow.begin(); a != byFlow.end(); ++a)
2312 for (
auto b = std::next(a); b != byFlow.end(); ++b) {
2313 const SlaveFlow &fa = a->second, &fb = b->second;
2314 if (fa.slave != fb.slave || fa.masters == fb.masters)
2316 std::optional<std::pair<std::pair<int, int>, std::pair<int, int>>>
2318 for (
auto own : claim(fa))
2319 for (
auto other : claim(fb))
2320 if (!overlap && cubesIntersect(own, other))
2321 overlap = {own, other};
2324 auto [own, other] = *overlap;
2325 moveFlow(tileId, a->first);
2326 moveFlow(tileId, b->first);
2329 int witness = (own.second & own.first) |
2330 (other.second & other.first & ~own.first);
2331 planFailure = llvm::formatv(
2332 "at tile ({0}, {1}), packet flows through {2} claim rule (mask "
2333 "0x{3:X-}, id 0x{4:X-}) and rule (mask 0x{5:X-}, id 0x{6:X-}), "
2334 "which both match id 0x{7:X-}; widen one mask to carry both, or "
2335 "route them apart.",
2336 tileId.col, tileId.row,
describePort(fa.slave), own.first,
2337 own.second, other.first, other.second, witness);
2339 std::set<Port> slaves;
2340 for (
const auto &[key, f] : byFlow)
2341 slaves.insert(f.slave);
2345 std::set<Port> overlayMasters;
2346 std::set<SmallVector<Port, 4>> overlaySets;
2347 for (
const auto &[key, f] : byFlow)
2348 if (f.isCtrlPkt && !pinned.rules.empty()) {
2349 overlayMasters.insert(f.masters.begin(), f.masters.end());
2350 overlaySets.insert(f.masters);
2352 for (
const auto &[key, f] : byFlow) {
2353 const Port *shared = llvm::find_if(
2354 f.masters, [&](
Port m) { return overlayMasters.count(m); });
2355 if (f.isCtrlPkt || shared == f.masters.end() ||
2356 overlaySets.count(f.masters))
2358 moveFlow(tileId, key,
true);
2360 planFailure = llvm::formatv(
2361 "at tile ({0}, {1}), packets with id {2} entering on {3} leave by "
2362 "{4}, a master port of the prioritized flows (the control "
2363 "overlay), but not by one of their master sets, which a "
2364 "control-packet reload keeps.",
2370 for (
Port slave : pinned.rules.empty() ? std::set<Port>{} : slaves) {
2371 SmallVector<SmallVector<Port, 4>> ruleMasters;
2372 SmallVector<PortRule> overlay = newRules(slave, {},
true, &ruleMasters);
2373 for (
const auto &[key, f] : byFlow) {
2374 if (f.slave != slave || f.isCtrlPkt)
2376 for (
auto own : claim(f)) {
2378 llvm::find_if(overlay, [&, &f = f](
const PortRule &r) {
2379 return cubesIntersect(own, {r.mask, r.value}) &&
2380 ruleMasters[r.group] != f.masters;
2382 if (rule == overlay.end())
2384 moveFlow(tileId, key);
2386 planFailure = llvm::formatv(
2387 "at tile ({0}, {1}), the packet rule (mask 0x{2:X-}, id "
2388 "0x{3:X-}) of the prioritized flows (the control overlay) on "
2389 "{4} also matches packet id 0x{5:X-}, and a control-packet "
2390 "reload keeps their rules; use an id the rule does not match, "
2391 "or route the flow apart.",
2392 tileId.col, tileId.row, rule->mask, rule->value,
2398 for (
Port slave : slaves) {
2399 size_t needed = rulesNeeded(slave);
2402 for (
const auto &[key, f] : byFlow)
2403 if (f.slave == slave)
2404 moveFlow(tileId, key);
2405 if (!splitRules(slave))
2406 crowdedTiles.insert(tileId);
2409 planFailure = llvm::formatv(
2410 "at tile ({0}, {1}), the packet flows entering on {2} need {3} "
2411 "packet rules, and a slave port holds {4}.",
2418void PacketFlowRouting::planTiles() {
2419 for (
const auto &[tileId, flows] : tileFlows) {
2420 SmallVector<std::pair<size_t, size_t>, 4> blocking;
2421 if (std::optional<ArbiterPlan> plan = planTile(tileId, blocking)) {
2422 plans[tileId] = std::move(*plan);
2426 llvm::dbgs() <<
"No arbiter plan at tile (" << tileId.col <<
", "
2427 << tileId.row <<
"):\n";
2428 for (
const SlaveFlow &f : flows) {
2429 llvm::dbgs() <<
" " <<
describePort(f.slave) <<
" id " << f.id
2431 for (
Port m : f.masters)
2433 llvm::dbgs() <<
'\n';
2435 for (
auto [a, b] : blocking)
2436 llvm::dbgs() <<
" blocking " << a <<
' ' << b <<
'\n';
2439 llvm::raw_string_ostream os(reason);
2440 if (
auto unkept = unkeptOverlay.find(tileId);
2441 keepOverlay && unkept != unkeptOverlay.end()) {
2443 overlayFailure =
true;
2444 planFailure =
"the control overlay keeps the packet rules it takes "
2445 "alone, as in @ctrl_pkt_overlay, since a control-packet "
2446 "reload (has_ctrl_pkt_overlay) installs them first, but "
2448 std::to_string(tileId.col) +
", " +
2449 std::to_string(tileId.row) +
"), " + unkept->second;
2453 if (blocking.empty()) {
2456 for (
const SlaveFlow &f : flows)
2457 for (
const SlaveFlow &g : flows)
2458 if (f.slave == g.slave && f.id < g.id && f.masters != g.masters &&
2461 [&](
Port m) { return llvm::is_contained(g.masters, m); })) {
2462 FlowKey keys[] = {{f.slave, f.id}, {g.slave, g.id}};
2463 splitFlow(tileId, keys[0], keys);
2465 for (
const SlaveFlow &f : flows)
2466 moveFlow(tileId, {f.slave, f.id});
2467 crowdedTiles.insert(tileId);
2470 os <<
"at tile (" << tileId.col <<
", " << tileId.row
2471 <<
"), the packet flows need more arbiter msels than the switchbox "
2473 planFailure = std::move(reason);
2476 for (
auto [a, b] : blocking) {
2477 FlowKey keys[] = {{flows[a].slave, flows[a].id},
2478 {flows[b].slave, flows[b].id}};
2480 moveUnit(tileId, key);
2481 splitFlow(tileId, key, keys);
2486 std::optional<std::pair<size_t, size_t>> pair = conflictingStreams(
2487 tileId, flows[blocking.front().first], flows[blocking.front().second]);
2488 assert(pair && apart.empty() && offArbiter.empty() &&
2489 "before the hold-cycle search, only stream conflicts block a plan");
2490 auto [s, t] = *pair;
2493 <<
" can deadlock if they share an arbiter, and no routing found keeps "
2494 "them apart (last tried: tile ("
2495 << tileId.col <<
", " << tileId.row <<
")). " << conflicts.
explain(s, t);
2496 if (keepOverlay && (flows[blocking.front().first].isCtrlPkt ||
2497 flows[blocking.front().second].isCtrlPkt))
2498 os <<
" The control overlay keeps the arbiters it takes alone, as in "
2499 "@ctrl_pkt_overlay, since a control-packet reload "
2500 "(has_ctrl_pkt_overlay) installs them first.";
2501 planFailure = std::move(reason);
2505std::optional<std::string> PacketFlowRouting::breakHoldCycles() {
2510 auto arbitrate = [&]() {
2514 for (StreamHop &hop : routes[s]) {
2515 hop.arbiter.reset();
2516 auto plan = plans.find(hop.tile);
2517 if (plan == plans.end())
2519 auto amsel = plan->second.slaveAmsels.find(
2520 {hop.input, *conflicts.
getStreams()[s].packetID});
2521 if (amsel != plan->second.slaveAmsels.end())
2522 hop.arbiter = amsel->second % numArbiters;
2526 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
2530 SmallVector<HoldCycle, 2> cycles;
2532 bool forcedWaits =
false, definite =
false;
2533 auto search = [&](
auto &self) ->
bool {
2535 std::optional<HoldCycle> cycle =
2536 conflicts.
holdCycle(routes, forcedWaits, definite);
2539 if (!cycle && definite)
2543 cycles.push_back(*cycle);
2544 LLVM_DEBUG(llvm::dbgs()
2545 <<
"Hold cycle: " << conflicts.
explain(*cycle) <<
'\n');
2546 for (
const HoldCycle::Step &step : cycle->steps) {
2547 if (step.
wait != HoldCycle::Wait::Arbiter || step.
forced)
2551 bool sharerNew = step.
sharer < numRequested;
2552 bool holderNew = step.
holding < numRequested;
2553 std::optional<std::tuple<TileID, FlowKey, FlowKey>> pair;
2554 std::optional<std::tuple<TileID, FlowKey, int>> off;
2555 if (sharerNew && holderNew)
2556 pair = {step.
tile, std::min(sharer, holder), std::max(sharer, holder)};
2557 else if (sharerNew || holderNew)
2558 off = {step.
tile, sharerNew ? sharer : holder, step.
arbiter};
2563 if ((pair && !apart.insert(*pair).second) ||
2564 (off && !offArbiter.insert(*off).second))
2567 ArbiterPlan saved = plans.at(step.
tile);
2568 SmallVector<std::pair<size_t, size_t>, 4> blocking;
2569 if (std::optional<ArbiterPlan> plan = planTile(step.
tile, blocking)) {
2570 plans[step.
tile] = std::move(*plan);
2573 plans[step.
tile] = std::move(saved);
2578 offArbiter.erase(*off);
2586 for (
const HoldCycle &cycle : cycles)
2587 for (
const HoldCycle::Step &step : cycle.steps) {
2588 if (step.
wait == HoldCycle::Wait::Arbiter && step.
forced &&
2589 step.
sharer < numRequested && step.
holding < numRequested &&
2590 streams[step.
sharer].packetID && streams[step.
holding].packetID) {
2591 PathEndPoint a{streams[step.
sharer].src.tile,
2592 streams[step.
sharer].src.port},
2593 b{streams[step.
holding].src.tile, streams[step.
holding].src.port};
2595 hazardTogether.insert(std::minmax(a, b));
2597 if (step.
wait != HoldCycle::Wait::Drain && !step.
forced) {
2598 SmallVector<FlowKey, 3> keys;
2602 if (std::optional<int>
id = streams[s].packetID;
2603 s < numRequested && id)
2604 keys.push_back({input, *id});
2606 moveUnit(step.
tile, key);
2607 splitFlow(step.
tile, key, keys);
2612 if (behind >= numRequested || step.
holding >= numRequested ||
2613 !streams[behind].packetID || !streams[step.
holding].packetID)
2615 PathEndPoint a{streams[behind].src.tile, streams[behind].src.port},
2616 b{streams[step.
holding].src.tile, streams[step.
holding].src.port};
2619 hazardApart.insert(std::minmax(a, b));
2620 else if (step.
wait == HoldCycle::Wait::Arbiter)
2621 hazardUntrunk.insert(a);
2625 if (!search(search)) {
2627 return "packet flows can deadlock holding arbiters across "
2628 "switchboxes, and no arbiter assignment found avoids it. " +
2629 conflicts.
explain(cycles.front());
2638 if (!search(search)) {
2639 LLVM_DEBUG(llvm::dbgs() <<
"Hold cycle at a shared receiver: "
2640 << conflicts.
explain(cycles.front()) <<
'\n');
2644 if (!search(search)) {
2646 sharedReceiverCycle = cycles.front();
2649 return std::nullopt;
2652LogicalResult PacketPlan::emit(DeviceOp device, OpBuilder &builder,
2653 DynamicTileAnalysis &analyzer)
const {
2654 const AIETargetModel &targetModel = device.getTargetModel();
2658 const int idBits = llvm::Log2_32_Ceil(maxPacketId + 1);
2659 const int idMask = (1 << idBits) - 1;
2660 AIEDialect::IsCtrlPktOverlayAttrHelper overlay(builder.getContext());
2661 AIEDialect::PriorityRouteAttrHelper priorityRoute(builder.getContext());
2662 UnitAttr unit = builder.getUnitAttr();
2670 std::map<std::pair<TileID, int>, SmallVector<Port, 4>> masterAMSels;
2671 DenseMap<std::pair<PhysPort, int>,
int> slaveAMSels;
2672 for (
const auto &[tileId, plan] : plans) {
2673 for (
const auto &[amsel, masters] : plan.amselMasters)
2674 masterAMSels[{tileId, amsel}].append(masters.begin(), masters.end());
2675 for (
const auto &[flow, amsel] : plan.slaveAmsels)
2676 slaveAMSels[{{tileId, flow.first}, flow.second}] = amsel;
2681 std::map<PhysPort, SmallVector<int, 4>> mastersets;
2682 for (
const auto &[tileAmsel, ports] : masterAMSels)
2683 for (
Port port : ports)
2684 mastersets[{tileAmsel.first, port}].push_back(tileAmsel.second);
2687 llvm::dbgs() <<
"CHECK mastersets\n";
2688 for (
const auto &[master, values] : mastersets) {
2689 llvm::dbgs() <<
"master " << master.first <<
" "
2690 << stringifyWireBundle(master.second.bundle) <<
" : "
2691 << master.second.channel <<
'\n';
2692 for (
int value : values)
2693 llvm::dbgs() <<
"amsel: " << value <<
'\n';
2699 SmallVector<SmallVector<std::pair<PhysPort, int>, 4>, 4> slaveGroups;
2700 SmallVector<std::pair<PhysPort, int>, 4> workList(slavePorts);
2701 while (!workList.empty()) {
2702 std::pair<PhysPort, int> slave = workList.pop_back_val();
2703 const SmallVector<PhysPort, 4> &dests = packetFlows.at(slave);
2704 auto *group = llvm::find_if(slaveGroups, [&](
const auto &candidate) {
2705 const SmallVector<PhysPort, 4> &groupDests =
2706 packetFlows.at(candidate.front());
2707 return candidate.front().first.second == slave.first.second &&
2708 overlayFlows.contains(candidate.front()) ==
2709 overlayFlows.contains(slave) &&
2710 groupDests.size() == dests.size() &&
2711 llvm::all_of(dests, [&](
const PhysPort &dest) {
2712 return llvm::is_contained(groupDests, dest);
2715 if (group != slaveGroups.end())
2716 group->push_back(slave);
2718 slaveGroups.push_back({slave});
2723 SmallVector<SmallVector<std::pair<int, int>, 4>, 4> statedRules(
2724 slaveGroups.size());
2725 SmallVector<SmallVector<int, 4>, 4> derivedIds(slaveGroups.size());
2726 for (
size_t gi = 0; gi < slaveGroups.size(); ++gi) {
2727 for (
auto member : slaveGroups[gi]) {
2728 auto cubes = statedCubes(member, idMask);
2729 if (cubes.empty()) {
2730 derivedIds[gi].push_back(member.second);
2733 for (
auto cube : cubes)
2734 if (!llvm::is_contained(statedRules[gi], cube))
2735 statedRules[gi].push_back(cube);
2740 [[maybe_unused]]
auto claimsOf = [&](
size_t gi) {
2741 SmallVector<std::pair<int, int>, 8> claims(statedRules[gi].begin(),
2742 statedRules[gi].end());
2743 for (
int id : derivedIds[gi])
2744 claims.push_back({idMask,
id});
2752 std::map<TileID, Operation *> tiles;
2753 for (
auto tileOp : device.getOps<TileOp>())
2754 tiles[tileOp.getTileID()] = tileOp;
2755 for (
const PhysPort &master : llvm::make_first_range(mastersets))
2756 tiles.try_emplace(master.first, analyzer.
getTile(builder, master.first));
2758 for (Operation *tileOp : llvm::make_second_range(tiles)) {
2759 TileOp tile = cast<TileOp>(tileOp);
2760 TileID tileId = tile.getTileID();
2761 Location tileLoc = tile.getLoc();
2764 builder.setInsertionPointAfter(tileOp);
2766 analyzer.
getSwitchbox(builder, tile.colIndex(), tile.rowIndex());
2767 SwitchboxOp::ensureTerminator(swbox.getConnections(), builder, tileLoc);
2768 Block &b = swbox.getConnections().front();
2769 builder.setInsertionPoint(b.getTerminator());
2771 if (
auto hops = circuitHops.find(tileId); hops != circuitHops.end())
2772 for (
const Connect &hop : hops->second)
2773 ConnectOp::create(builder, tileLoc, hop.src.bundle, hop.src.channel,
2774 hop.dst.bundle, hop.dst.channel);
2777 auto tileMastersets =
2778 llvm::make_filter_range(mastersets, [&](
const auto &entry) {
2779 return entry.first.first == tileId;
2781 llvm::SmallBitVector amselNeeded(numMselsPerArbiter * numArbiters);
2782 for (
const auto &[master, amsels] : tileMastersets)
2783 for (
int amsel : amsels)
2784 amselNeeded.set(amsel);
2785 std::map<int, AMSelOp> amselOps;
2786 for (
int msel = 0; msel < numMselsPerArbiter; msel++)
2787 for (
int arbiter = 0; arbiter < numArbiters; arbiter++)
2788 if (
int amsel = arbiter + msel * numArbiters; amselNeeded.test(amsel))
2789 amselOps[amsel] = AMSelOp::create(builder, tileLoc, arbiter, msel);
2790 for (
const auto &[master, msels] : tileMastersets) {
2791 SmallVector<Value, 4> amsels;
2792 for (
int msel : msels)
2793 amsels.push_back(amselOps.at(msel));
2794 auto msOp = MasterSetOp::create(
2795 builder, tileLoc, builder.getIndexType(), master.second.bundle,
2796 master.second.channel, amsels, keepPktHeaderAttr.lookup(master));
2797 if (ctrlPktOverlayMasterPorts.contains(master))
2798 overlay.setAttr(msOp, unit);
2802 DenseMap<Port, PacketRulesOp> slaveRules;
2803 DenseMap<Port, SmallVector<PacketRuleOp>> existingRules;
2805 SmallVector<size_t, 4> groups;
2806 SmallVector<PortRule> rules;
2809 std::map<Port, PortPlan> portPlans;
2810 for (
auto rulesOp : b.getOps<PacketRulesOp>()) {
2811 slaveRules[rulesOp.sourcePort()] = rulesOp;
2812 llvm::append_range(existingRules[rulesOp.sourcePort()],
2813 rulesOp.getRules().getOps<PacketRuleOp>());
2815 for (
size_t gi = 0; gi < slaveGroups.size(); ++gi) {
2816 const auto &group = slaveGroups[gi];
2817 builder.setInsertionPoint(b.getTerminator());
2819 auto port = group.front().first;
2820 if (tileId != port.first)
2823 Port slave = port.second;
2825 SmallVector<int, 4> matchIds =
2826 llvm::to_vector<4>(llvm::make_second_range(group));
2827 for (
int id : matchIds)
2828 if (
id >
static_cast<int>(maxPacketId))
2829 return mlir::emitError(tileLoc)
2830 <<
"packet id " <<
id <<
" exceeds the maximum of "
2834 for (PacketRuleOp rule : existingRules.lookup(slave))
2835 for (
int id : matchIds)
2836 if ((
id & rule.maskInt()) == rule.valueInt()) {
2837 rule->emitOpError(
"can lead to false packet id match for id ")
2838 <<
id <<
", which is not supposed to pass through this port.";
2839 rule->emitRemark(
"Please consider changing all uses of packet id ")
2840 <<
id <<
" to avoid deadlock.";
2846 assert(llvm::all_of(
2847 llvm::seq<size_t>(0, slaveGroups.size()),
2849 return oi == gi || slaveGroups[oi].front().first != port ||
2850 llvm::none_of(claimsOf(gi), [&](auto own) {
2851 return llvm::any_of(claimsOf(oi), [&](auto other) {
2852 return cubesIntersect(own, other);
2856 "groups on one slave port claim the same id");
2860 auto [planIt, fresh] = portPlans.try_emplace(slave);
2861 PortPlan &plan = planIt->second;
2863 SmallVector<GroupClaims> claims;
2864 for (
size_t oi = 0; oi < slaveGroups.size(); ++oi) {
2865 if (slaveGroups[oi].front().first != port)
2867 plan.groups.push_back(oi);
2868 claims.push_back({statedRules[oi], derivedIds[oi],
2869 overlayFlows.contains(slaveGroups[oi].front())});
2871 SmallVector<std::pair<int, int>> existing;
2872 for (PacketRuleOp rule : existingRules.lookup(slave))
2873 existing.push_back({rule.maskInt(), rule.valueInt()});
2874 plan.rules = slavePortRules(claims, existing, idBits,
2877 llvm::dbgs() <<
"packet rules " <<
describePort(slave) <<
":";
2878 for (
const PortRule &r : plan.rules)
2879 llvm::dbgs() <<
" rule(" << r.mask <<
", " << r.value
2880 <<
") -> group " << plan.groups[r.group];
2881 llvm::dbgs() <<
'\n';
2889 llvm::seq(0, idMask + 1),
2891 bool own = llvm::is_contained(derivedIds[gi],
id) ||
2892 llvm::any_of(statedRules[gi], [&](
auto c) {
2893 return (
id & c.first) == (c.second & c.first);
2896 llvm::find_if(plan.rules, [&](
const PortRule &r) {
2897 return (id & r.mask) == r.value;
2900 (first != plan.rules.end() &&
2902 slaveGroups[plan.groups[first->group]].front()) ==
2903 slaveAMSels.at(slaveGroups[gi].front()));
2905 "packet rules send a claimed id elsewhere");
2907 size_t last = plan.emitted;
2908 for (
size_t r = plan.emitted; r < plan.rules.size(); ++r)
2909 if (plan.groups[plan.rules[r].group] == gi)
2912 PacketRulesOp packetrules = slaveRules.lookup(slave);
2914 packetrules = PacketRulesOp::create(builder, tileLoc, slave.bundle,
2916 PacketRulesOp::ensureTerminator(packetrules.getRules(), builder,
2918 slaveRules[slave] = packetrules;
2921 packetrules->moveBefore(b.getTerminator());
2924 Block &rules = packetrules.getRules().front();
2926 assert(llvm::range_size(rules.getOps<PacketRuleOp>()) +
2927 (last - plan.emitted) <=
2929 "checkRules lets a slave port take more rules than it holds");
2931 builder.setInsertionPoint(rules.getTerminator());
2932 for (; plan.emitted < last; ++plan.emitted) {
2933 const PortRule &r = plan.rules[plan.emitted];
2934 const auto &ruleGroup = slaveGroups[plan.groups[r.group]];
2935 auto rule = PacketRuleOp::create(
2936 builder, tileLoc, r.mask, r.value,
2937 amselOps.at(slaveAMSels.at(ruleGroup.front())));
2938 if (llvm::all_of(ruleGroup, [&](
const auto &member) {
2939 return markedFlows.contains(member);
2941 overlay.setAttr(rule, unit);
2942 if (prioritizedSourcePorts.contains(port) &&
2943 llvm::any_of(ruleGroup, [&](
const auto &member) {
2944 return prioritizedFlows.contains(member);
2946 priorityRoute.setAttr(rule, unit);
2952 for (
auto rulesOp : b.getOps<PacketRulesOp>()) {
2953 auto rules = rulesOp.getRules().getOps<PacketRuleOp>();
2954 bool blockTagged = overlay.isAttrPresent(rulesOp);
2955 auto tagged = [&](PacketRuleOp rule) {
2956 return blockTagged || overlay.isAttrPresent(rule);
2958 bool all = llvm::all_of(rules, tagged);
2959 for (PacketRuleOp rule : rules) {
2960 if (!all && tagged(rule))
2961 overlay.setAttr(rule, unit);
2963 rule->removeDiscardableAttr(overlay.getName());
2965 if (all && !rules.empty())
2966 overlay.setAttr(rulesOp, unit);
2968 rulesOp->removeDiscardableAttr(overlay.getName());
2974LogicalResult AIEPathfinderPass::runOnPacketFlow(
2975 DeviceOp device, OpBuilder &builder, DynamicTileAnalysis &analyzer,
2976 const StreamConflicts &conflicts,
const PacketPlan &plan,
bool warn) {
2977 if (warn && plan.sharedReceiverCycle)
2978 emitWarning(device.getLoc(), sharedReceiverCycleReason)
2979 << conflicts.
explain(*plan.sharedReceiverCycle);
2980 if (failed(plan.emit(device, builder, analyzer)))
2982 lowerShimDMAPorts(device, builder, analyzer);
2983 for (PacketFlowOp flow :
2984 llvm::make_early_inc_range(device.getOps<PacketFlowOp>()))
2990static DeviceOp cloneInScratch(DeviceOp d, OwningOpRef<ModuleOp> &scratch) {
2991 scratch = ModuleOp::create(d.getLoc());
2992 if (Operation *parent = d->getParentOp())
2993 (*scratch)->setAttrs(parent->getAttrDictionary());
2994 DeviceOp copy = d.clone();
2995 scratch->push_back(copy);
3003static void unmuxShimDMAPacketPorts(DeviceOp device) {
3004 for (
auto shimMux : device.getOps<ShimMuxOp>()) {
3005 auto hasConnect = [&](
Port src,
Port dst) {
3006 return llvm::any_of(shimMux.getConnections().getOps<ConnectOp>(),
3008 return c.sourcePort() == src && c.destPort() == dst;
3011 for (
auto switchbox : device.getOps<SwitchboxOp>()) {
3012 if (switchbox.getTileOp() != shimMux.getTileOp())
3014 for (
auto rules : switchbox.getConnections().getOps<PacketRulesOp>())
3015 for (
int ch : {0, 1}) {
3016 Port dma{WireBundle::DMA, ch};
3018 if (rules.sourcePort() ==
Port{WireBundle::South, north.channel} &&
3019 hasConnect(dma, north)) {
3020 rules.setSourceBundle(WireBundle::DMA);
3021 rules.setSourceChannel(ch);
3025 for (
auto masterSet : switchbox.getConnections().getOps<MasterSetOp>())
3026 for (
int ch : {0, 1}) {
3027 Port dma{WireBundle::DMA, ch};
3029 if (masterSet.destPort() ==
Port{WireBundle::South, north.channel} &&
3030 hasConnect(north, dma)) {
3031 masterSet.setDestBundle(WireBundle::DMA);
3032 masterSet.setDestChannel(ch);
3040llvm::Expected<PacketPlan> AIEPathfinderPass::route(
3041 DeviceOp d, DynamicTileAnalysis &analyzer,
const StreamConflicts &conflicts,
3042 const PinnedOverlay &pinned,
bool circuitSwitchHops,
bool prioritize) {
3046 std::map<PathEndPoint, SmallVector<size_t, 4>> streamsFrom;
3047 PacketConstraints constraints;
3050 if (clRoutePacket && !d.getOps<PacketFlowOp>().empty()) {
3051 const AIETargetModel &targetModel = d.getTargetModel();
3052 if (llvm::Error err = unroutableArbiters(
3055 return !circuitSwitchHops ||
3059 return std::move(err);
3062 streamsFrom[{s.src.tile, s.src.port}].push_back(i);
3065 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
3066 SmallVector<size_t, 16> crossing;
3067 std::set<std::pair<TileID, Port>> srcs, dsts;
3069 if (s.packetID && low(s.src.tile) == up && low(s.dst.tile) != up) {
3070 crossing.push_back(i);
3071 srcs.insert({s.src.tile, s.src.port});
3072 dsts.insert({s.dst.tile, s.dst.port});
3076 size_t most = std::min(srcs.size(), dsts.size());
3077 SmallVector<size_t, 8> best =
3079 ? largestApartSet(conflicts, crossing, limit)
3080 : SmallVector<size_t, 8>(
3082 crossing.begin() + std::min<size_t>(1, crossing.size()));
3083 for (
size_t s : best)
3085 if (best.size() > 1)
3086 apart.
why = conflicts.
explain(best[0], best[1]);
3089 constraints.
conflict = [&](
const PathEndPoint &a,
const std::set<int> &aIds,
3090 const PathEndPoint &b,
3091 const std::set<int> &bIds) {
3092 auto as = streamsFrom.find(a), bs = streamsFrom.find(b);
3093 if (as == streamsFrom.end() || bs == streamsFrom.end())
3095 ArrayRef<RoutedStream> streams = conflicts.
getStreams();
3096 for (
size_t s : as->second)
3097 for (
size_t t : bs->second)
3098 if (aIds.count(*streams[s].packetID) &&
3099 bIds.count(*streams[t].packetID) && conflicts.
conflict(s, t))
3103 constraints.
check = [&](
const Routing &routing) -> llvm::Error {
3104 PacketFlowRouting check(d, routing, conflicts, pinned, clRouteCircuit,
3105 circuitSwitchHops, prioritize);
3106 llvm::Expected<PacketPlan> plan = check.plan();
3108 return plan.takeError();
3109 RoutingFaults all = check.faults(), faults;
3113 if (!plan->sharedReceiverCycle ||
3116 return llvm::Error::success();
3117 return llvm::make_error<DeadlockProneRouting>(
3118 conflicts.
explain(*plan->sharedReceiverCycle), std::move(faults));
3125 return std::move(routed);
3127 return PacketPlan();
3128 PacketFlowRouting found(d, analyzer.
routing, conflicts, pinned,
3129 clRouteCircuit, circuitSwitchHops, prioritize);
3130 llvm::Expected<PacketPlan> plan = found.plan();
3131 if (found.incomplete) {
3132 llvm::consumeError(plan.takeError());
3133 return llvm::make_error<RoutingFailure>(std::move(found.incomplete->second),
3135 found.incomplete->first);
3139 if (!plan || clAllowDeadlockProne)
3141 const std::optional<HoldCycle> &cycle = plan->sharedReceiverCycle;
3144 return llvm::make_error<DeadlockProneRouting>(
3145 (llvm::Twine(sharedReceiverCycleReason) + conflicts.
explain(*cycle) +
3146 allowDeadlockProneHint)
3150void AIEPathfinderPass::runOnOperation() {
3153 LLVM_DEBUG(llvm::dbgs() <<
"---Begin AIEPathfinderPass---\n");
3155 DeviceOp d = getOperation();
3157 return signalPassFailure();
3158 OpBuilder builder = OpBuilder::atBlockTerminator(d.getBody());
3160 unmuxShimDMAPacketPorts(d);
3162 StreamConflicts conflicts(d);
3163 if (
auto pairs = conflicts.
unavoidable(); !pairs.empty()) {
3164 InFlightDiagnostic diag =
3165 (clAllowDeadlockProne ? emitWarning(d.getLoc()) : emitError(d.getLoc()))
3166 <<
"Flows can deadlock however they are routed: "
3167 << conflicts.explain(pairs[0].first, pairs[0].second);
3168 if (pairs.size() > 1)
3169 diag <<
" So can " << pairs.size() - 1 <<
" other pair"
3170 << (pairs.size() > 2 ?
"s" :
"") <<
" of flows.";
3171 if (!clAllowDeadlockProne) {
3172 diag << allowDeadlockProneHint;
3173 return signalPassFailure();
3176 DynamicTileAnalysis analyzer;
3182 auto sourcesOf = [](PacketFlowOp flow) {
3183 return llvm::map_range(
3184 flow.getPorts().getOps<PacketSourceOp>(), [](PacketSourceOp src) {
3185 return PathEndPoint{
3186 cast<TileOp>(src.getTile().getDefiningOp()).getTileID(),
3190 std::set<PathEndPoint> prioritized;
3191 std::set<std::pair<PathEndPoint, int>> prioritizedIds;
3192 for (PacketFlowOp flow : d.getOps<PacketFlowOp>())
3193 if (flow.getPriorityRoute().value_or(false))
3194 for (PathEndPoint src : sourcesOf(flow)) {
3195 prioritized.insert(src);
3196 prioritizedIds.insert({src, flow.IDInt()});
3198 auto isPrioritized = [&](PacketFlowOp flow) {
3199 return llvm::any_of(sourcesOf(flow), [&](
const PathEndPoint &src) {
3200 return prioritizedIds.count({src, flow.IDInt()}) > 0;
3205 bool reload = d.getHasCtrlPktOverlay();
3206 std::map<PathEndPoint, std::pair<PacketFlowOp, PacketFlowOp>> lastTo;
3207 for (PacketFlowOp flow : d.getOps<PacketFlowOp>())
3208 for (PacketDestOp dst : flow.getPorts().getOps<PacketDestOp>()) {
3209 PathEndPoint port{cast<TileOp>(dst.getTile().getDefiningOp()).getTileID(),
3211 auto &[prio, last] = lastTo[port];
3213 if (reload && isPrioritized(flow) && !reloadConfigures(flow))
3216 for (
const auto &[dst, flows] : lastTo) {
3217 auto [prio, last] = flows;
3222 last.emitError() <<
"packet flow " << last.IDInt()
3223 <<
" is the last to end at "
3225 <<
", so it sets whether packets keep their header "
3226 "there, but the prioritized flows (the control "
3227 "overlay) ending there set it otherwise, and a "
3228 "control-packet reload keeps their setting.";
3229 return signalPassFailure();
3235 bool pinned =
false;
3236 PinnedOverlay overlay;
3237 if (clRoutePacket && !prioritized.empty() &&
3238 (!d.getOps<FlowOp>().empty() ||
3239 !llvm::all_of(d.getOps<PacketFlowOp>(), isPrioritized))) {
3240 OwningOpRef<ModuleOp> scratch;
3244 DeviceOp alone = cloneInScratch(d, scratch);
3245 SmallVector<Operation *> others;
3246 for (Operation &op : alone.getBody()->without_terminator()) {
3247 auto flow = dyn_cast<PacketFlowOp>(op);
3248 if (flow ? !isPrioritized(flow) || reloadConfigures(flow)
3249 : isa<FlowOp>(op) || (reload && !isa<TileOp>(op)))
3250 others.push_back(&op);
3252 for (Operation *op : others)
3253 op->dropAllReferences();
3254 for (Operation *op : others)
3256 DynamicTileAnalysis aloneAnalyzer;
3257 StreamConflicts aloneConflicts(alone);
3258 llvm::Expected<PacketPlan> alonePlan =
3259 route(alone, aloneAnalyzer, aloneConflicts, {},
3263 llvm::handleAllErrors(alonePlan.takeError(), [&](RoutingFailure &f) {
3264 f.reason =
"prioritized packet flows (priority_route) in a design a "
3265 "control-packet reload configures (has_ctrl_pkt_overlay) "
3266 "keep the route they take alone, as in @ctrl_pkt_overlay, "
3267 "and alone they have none" +
3268 (f.reason.empty() ?
"." :
": " + f.reason);
3269 emitError(f.loc.value_or(d.getLoc())) << f.message();
3271 return signalPassFailure();
3273 llvm::consumeError(alonePlan.takeError());
3277 OpBuilder aloneBuilder = OpBuilder::atBlockTerminator(alone.getBody());
3278 if (failed(runOnPacketFlow(alone, aloneBuilder, aloneAnalyzer,
3279 aloneConflicts, *alonePlan,
false)))
3280 return signalPassFailure();
3282 PinnedOverlay{aloneAnalyzer.routing.packetTrees, overlayRules(alone)};
3290 auto routeRelaxing = [&](DeviceOp dev, DynamicTileAnalysis &an,
3291 const StreamConflicts &c,
const PinnedOverlay &pin,
3293 true) -> llvm::Expected<PacketPlan> {
3294 llvm::Expected<PacketPlan> plan =
3295 route(dev, an, c, pin, clCircuitSwitchHops, prioritize);
3298 llvm::Error first = plan.takeError();
3299 while (an.pathfinder.relax()) {
3300 plan = route(dev, an, c, pin, clCircuitSwitchHops, prioritize);
3302 llvm::consumeError(std::move(first));
3305 llvm::Error err = plan.takeError();
3306 if (err.isA<DeadlockProneRouting>() && !first.isA<DeadlockProneRouting>())
3307 std::swap(first, err);
3308 llvm::consumeError(std::move(err));
3310 return std::move(first);
3312 llvm::Expected<PacketPlan> plan =
3313 routeRelaxing(d, analyzer, conflicts, overlay);
3318 if (!plan && !prioritized.empty()) {
3319 llvm::Error err = plan.takeError();
3322 DynamicTileAnalysis freeAnalyzer;
3323 plan = routeRelaxing(d, freeAnalyzer, conflicts, overlay,
3326 llvm::consumeError(std::move(err));
3327 analyzer = std::move(freeAnalyzer);
3329 llvm::Error freeErr = plan.takeError();
3330 if (!reload && (freeErr.isA<DeadlockProneRouting>() ||
3331 !err.isA<DeadlockProneRouting>()))
3332 std::swap(err, freeErr);
3333 llvm::consumeError(std::move(freeErr));
3334 plan = std::move(err);
3339 llvm::handleAllErrors(plan.takeError(), [&](RoutingFailure &f) {
3342 if (pinned && !f.reason.empty() &&
3343 !f.isA(PinnedRoutingFailure::classID())) {
3344 OwningOpRef<ModuleOp> scratch;
3345 DeviceOp free = cloneInScratch(d, scratch);
3346 DynamicTileAnalysis freeAnalyzer;
3347 StreamConflicts freeConflicts(free);
3348 llvm::Expected<PacketPlan> freePlan = routeRelaxing(
3349 free, freeAnalyzer, freeConflicts, {}, false);
3351 llvm::consumeError(freePlan.takeError());
3353 f.reason = describePrioritized(llvm::to_vector(llvm::map_range(
3355 [](const PathEndPoint &src) {
3356 return describeTilePort(src.coords, src.port);
3358 ", and the other flows route only if it moves. Around "
3363 emitError(f.
loc.value_or(d.getLoc())) << f.message();
3365 signalPassFailure();
3368 for (
auto flowOp : d.getOps<FlowOp>()) {
3369 if (!flowOp.getVias().empty()) {
3370 flowOp.emitOpError(
"cannot be routed while it carries via waypoints; run "
3371 "--aie-split-flow-vias first to lower the vias into "
3372 "switchbox connections");
3373 signalPassFailure();
3377 for (
auto flowOp : d.getOps<PacketFlowOp>()) {
3378 if (!flowOp.getVias().empty()) {
3379 flowOp.emitOpError(
"cannot be routed while it carries via waypoints; run "
3380 "--aie-split-flow-vias first to lower the vias into "
3381 "switchbox connections");
3382 signalPassFailure();
3387 if (clRouteCircuit && failed(runOnFlow(d, analyzer))) {
3388 signalPassFailure();
3391 if (clRoutePacket && failed(runOnPacketFlow(d, builder, analyzer, conflicts,
3393 signalPassFailure();
3396 if (pinned && reload) {
3398 for (
const auto &[port, portRules] : overlay.
rules)
3399 rules.try_emplace(port);
3400 for (
const auto &[port, portRules] : rules)
3401 if (portRules != overlay.
rules[port]) {
3402 emitError(d.getLoc())
3403 <<
"the control overlay takes other packet rules at "
3405 <<
" than it takes alone, as in @ctrl_pkt_overlay, which a "
3406 "control-packet reload (has_ctrl_pkt_overlay) installs first.";
3407 signalPassFailure();
3415 builder.setInsertionPoint(d.getBody()->getTerminator());
3416 llvm::DenseSet<std::tuple<Value, int, Value, int>> existingWires;
3419 auto recordWire = [&](Value source,
int sourceBundle, Value dest,
3422 !existingWires.contains({source, sourceBundle, dest, destBundle}) &&
3423 !existingWires.contains({dest, destBundle, source, sourceBundle});
3424 existingWires.insert({source, sourceBundle, dest, destBundle});
3425 existingWires.insert({dest, destBundle, source, sourceBundle});
3428 for (
auto wire : d.getOps<WireOp>()) {
3429 recordWire(wire.getSource(),
static_cast<int>(wire.getSourceBundle()),
3430 wire.getDest(),
static_cast<int>(wire.getDestBundle()));
3432 auto wire = [&](Location loc, Value source, WireBundle sourceBundle,
3433 Value dest, WireBundle destBundle) {
3434 if (recordWire(source,
static_cast<int>(sourceBundle), dest,
3435 static_cast<int>(destBundle))) {
3436 WireOp::create(builder, loc, source, sourceBundle, dest, destBundle);
3439 for (
int col = 0; col <= analyzer.
getMaxCol(); col++) {
3440 for (
int row = 0; row <= analyzer.
getMaxRow(); row++) {
3441 TileOp tile = analyzer.
lookupTile({col, row});
3445 Location loc = tile.getLoc();
3449 wire(loc, westsw, WireBundle::East, sw, WireBundle::West);
3452 wire(loc, tile, WireBundle::Core, sw, WireBundle::Core);
3454 wire(loc, tile, WireBundle::DMA, sw, WireBundle::DMA);
3458 wire(loc, southsw, WireBundle::North, sw, WireBundle::South);
3459 }
else if (tile.isShimNOCTile()) {
3463 sw, WireBundle::South);
3466 wire(loc, tile, WireBundle::DMA, shimsw, WireBundle::DMA);
3474 return std::make_unique<AIEPathfinderPass>();
3477std::unique_ptr<OperationPass<DeviceOp>>
3479 return std::make_unique<AIEPathfinderPass>(options);
constexpr int maxSearchSteps
std::pair< TileID, Port > PhysPort
constexpr int maxHoldCycleReplans
std::map< PhysPort, SmallVector< std::tuple< int, int, int >, 2 > > OverlayRules
(mask, value, amsel) by slave port, in slot order.
std::tuple< int, int, int, int, int, int, int, int, int, int > FlowKey
virtual uint32_t getNumMselsPerArbiter() const =0
Return the number of master select values per stream-switch arbiter.
virtual uint32_t getNumArbiters() const =0
Return the number of arbiters per stream switch.
bool isShimNOCorPLTile(int col, int row) const
Return true if the given tile is either a ShimNOC or ShimPL tile.
virtual int rows() const =0
Return the number of rows in the device.
bool isShimNOCTile(int col, int row) const
Return true if the given tile is a ShimNOC tile.
virtual uint32_t getMaxPacketId() const =0
Return the largest packet id the stream switch can route.
virtual int columns() const =0
Return the number of columns in the device.
virtual uint32_t getNumDestSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a switchbox.
virtual uint32_t getNumSlaveSlots() const =0
Return the number of packet-rule slots per stream-switch slave port.
ShimMuxOp getShimMux(mlir::OpBuilder &builder, int col)
SwitchboxOp getSwitchbox(mlir::OpBuilder &builder, int col, int row)
SwitchboxOp lookupSwitchbox(TileID tile) const
TileOp lookupTile(TileID tile) const
TileOp getTile(mlir::OpBuilder &builder, int col, int row)
llvm::Error runAnalysis(DeviceOp &device)
std::set< PathEndPoint > processedFlows
ShimMuxOp lookupShimMux(int col) const
void setPacketConstraints(PacketConstraints c)
std::optional< mlir::Location > loc
The streams a device asks for or already routes, and which pairs of them must not share an arbiter or...
llvm::ArrayRef< RoutedStream > getRequestedStreams() const
The streams the flow ops ask for, which come first in getStreams().
llvm::SmallVector< std::pair< size_t, size_t > > unavoidable() const
The requested streams where the first can hold up the second however they are routed,...
bool mustSeparate(size_t s, size_t t) const
Whether a routing that puts packet streams s and t on one arbiter anywhere has a hold cycle: they con...
llvm::ArrayRef< RoutedStream > getStreams() const
bool conflict(size_t s, size_t t) const
Whether s and t can deadlock if they share an arbiter or a link.
std::optional< HoldCycle > holdCycle(llvm::ArrayRef< llvm::SmallVector< StreamHop, 8 > > routes, bool forcedWaits=false, bool definite=false) const
A cycle of waits the packet streams can deadlock in when routed along routes, indexed like getStreams...
std::string explain(size_t s, size_t t) const
Why s and t conflict.
int shimMuxChannelTo(Port dst)
struct Connect { Port src; Port dst; bool operator==(const Connect &rhs) const { return std::tie(src, dst)==std::tie(rhs.src, rhs.dst); } bool operator!=(const Connect &rhs) const { return !(*this==rhs);} bool operator<(const Connect &rhs) const { return std::tie(src, dst)< std::tie(rhs.src, rhs.dst); } } Connect
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)
bool keepsPktHeader(TileID tile, Port master, std::optional< bool > keep)
mlir::LogicalResult verifyDMAChannelsResolved(DeviceOp device)
int shimMuxChannelFrom(Port src)
std::string describeTilePort(TileID tile, Port port)
Names a tile port the way diagnostics do, e.g. "(0, 2) DMA:1".
struct Port { WireBundle bundle; int channel; bool operator==(const Port &rhs) const { return std::tie(bundle, channel)==std::tie(rhs.bundle, rhs.channel); } bool operator!=(const Port &rhs) const { return !(*this==rhs);} bool operator<(const Port &rhs) const { return std::tie(bundle, channel)< std::tie(rhs.bundle, rhs.channel); } friend std::ostream &operator<<(std::ostream &os, const Port &port) { os<< "("; os<< stringifyWireBundle(port.bundle).str(); os<< ": "<< std::to_string(port.channel)<< ")"; return os; } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Port &port) { os<< to_string(port); return os; } } Port
std::map< TileID, SwitchSetting > SwitchSettings
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEPathfinderPass()
std::string describePort(Port port)
Names a port the way diagnostics do, e.g. "DMA:1".
std::map< PathEndPoint, std::vector< TreeHop > > PacketTrees
What the prioritized flows take routed alone (see has_ctrl_pkt_overlay).
bool forced
The wait holds however the arbiters are planned, as sharer and holding go into a receiver they share ...
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