12#include "mlir/Dialect/Utils/StaticValueUtils.h"
13#include "mlir/IR/Matchers.h"
14#include "mlir/Interfaces/ControlFlowInterfaces.h"
15#include "mlir/Interfaces/LoopLikeInterface.h"
17#include "llvm/ADT/DenseSet.h"
18#include "llvm/ADT/GraphTraits.h"
19#include "llvm/ADT/SCCIterator.h"
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/ADT/ScopeExit.h"
22#include "llvm/ADT/SetVector.h"
23#include "llvm/ADT/SparseBitVector.h"
24#include "llvm/Support/Debug.h"
25#include "llvm/Support/FormatVariadic.h"
32#define DEBUG_TYPE "aie-stream-dependency"
40std::optional<TileID> tileOf(Value tile) {
41 if (
auto tileOp = dyn_cast_or_null<TileOp>(tile.getDefiningOp()))
42 return tileOp.getTileID();
46std::optional<DmaChannelProgram> makeProgram(Operation *op, DeviceOp device) {
47 auto parentTile = [](Operation *op) -> std::optional<TileID> {
48 if (
auto element = op->getParentOfType<TileElement>())
49 return tileOf(element.getTile());
52 if (
auto start = dyn_cast<DMAStartOp>(op)) {
53 std::optional<TileID> tile = parentTile(op);
57 op, {*tile, start.getChannelDir(), start.getChannelIndex()}};
58 p.
passes += start.getRepeatCount();
59 llvm::SmallPtrSet<Block *, 8> seen;
60 for (Block *b = start.getDest(); b;) {
61 if (!seen.insert(b).second) {
67 auto next = dyn_cast<NextBDOp>(b->getTerminator());
68 b = next ? next.getDest() :
nullptr;
72 if (
auto dma = dyn_cast<DMAOp>(op)) {
73 std::optional<TileID> tile = parentTile(op);
77 {*tile, dma.getChannelDir(), dma.getChannelIndex()}};
78 p.
passes += dma.getRepeatCount();
79 for (Region &r : dma.getBds())
81 p.
loops = dma.getLoop();
84 if (
auto task = dyn_cast<AIEX::DMAConfigureTaskOp>(op)) {
85 std::optional<TileID> tile = tileOf(task.getTile());
89 op, {*tile, task.getDirection(),
static_cast<int>(task.getChannel())}};
90 p.
regions.push_back(&task.getBody());
91 p.
passes += task.getRepeatCount();
94 if (
auto task = dyn_cast<AIEX::DMAConfigureTaskForOp>(op)) {
95 auto alloc = ShimDMAAllocationOp::getForSymbol(
96 device, task.getAlloc().getRootReference());
99 std::optional<TileID> tile = tileOf(alloc.getTile());
103 {*tile, alloc.getChannelDir(),
104 static_cast<int>(alloc.getChannelIndex())}};
105 p.
regions.push_back(&task.getBody());
106 p.
passes += task.getRepeatCount();
114SmallVector<DmaChannelProgram> collectDmaPrograms(DeviceOp device) {
115 SmallVector<DmaChannelProgram> programs;
116 device.walk([&](Operation *op) {
117 if (isa<DMAStartOp, DMAOp, AIEX::DMAConfigureTaskOp,
118 AIEX::DMAConfigureTaskForOp>(op))
119 if (std::optional<DmaChannelProgram> p = makeProgram(op, device))
120 programs.push_back(std::move(*p));
125template <
typename OpT>
127 llvm::function_ref<
void(OpT)> fn) {
128 for (Block *b : p.
bds)
129 for (
auto op : b->getOps<OpT>())
132 r->walk([&](OpT op) { fn(op); });
135std::optional<TileDMAChannel> channelOfSymbol(DeviceOp device,
137 auto alloc = ShimDMAAllocationOp::getForSymbol(device, symbol);
140 std::optional<TileID> tile = tileOf(alloc.getTile());
144 static_cast<int>(alloc.getChannelIndex())};
150SmallVector<Operation *> taskCreators(Value task) {
151 SmallVector<Operation *> creators;
152 llvm::SmallPtrSet<Value, 8> seen;
153 SmallVector<Value> worklist{task};
154 while (!worklist.empty()) {
155 Value v = worklist.pop_back_val();
156 if (!seen.insert(v).second)
158 Operation *op = v.getDefiningOp();
159 if (isa_and_nonnull<AIEX::DMAConfigureTaskOp, AIEX::DMAConfigureTaskForOp,
160 AIEX::DMAStartBdChainOp, AIEX::DMAStartBdChainForOp>(
162 creators.push_back(op);
166 op ? op : cast<BlockArgument>(v).getOwner()->getParentOp();
167 auto branch = dyn_cast_or_null<RegionBranchOpInterface>(owner);
170 RegionBranchInverseSuccessorMapping mapping;
171 branch.getSuccessorInputOperandMapping(mapping);
172 auto it = mapping.find(v);
173 if (it == mapping.end())
175 for (OpOperand *operand : it->second)
176 worklist.push_back(operand->get());
182SmallVector<TileDMAChannel> channelsOfTask(DeviceOp device, Value task) {
183 SmallVector<TileDMAChannel> keys;
184 for (Operation *op : taskCreators(task)) {
185 std::optional<TileDMAChannel> key;
186 auto onTile = [&](Value tileValue, DMAChannelDir dir,
int channel) {
187 if (std::optional<TileID> tile = tileOf(tileValue))
190 if (
auto configure = dyn_cast<AIEX::DMAConfigureTaskOp>(op))
191 onTile(configure.getTile(), configure.getDirection(),
192 configure.getChannel());
193 else if (
auto chain = dyn_cast<AIEX::DMAStartBdChainOp>(op))
194 onTile(chain.getTile(), chain.getDirection(), chain.getChannel());
195 else if (
auto configure = dyn_cast<AIEX::DMAConfigureTaskForOp>(op))
196 key = channelOfSymbol(device, configure.getAlloc().getRootReference());
197 else if (
auto chain = dyn_cast<AIEX::DMAStartBdChainForOp>(op))
198 key = channelOfSymbol(device, chain.getAlloc());
201 if (!llvm::is_contained(keys, *key))
202 keys.push_back(*key);
210void walkLoopsTwice(Region ®ion, function_ref<
void(Operation *)> fn) {
211 for (Operation &op : region.getOps()) {
213 auto branch = dyn_cast<RegionBranchOpInterface>(op);
214 int passes = branch && branch.hasLoop() ? 2 : 1;
215 for (
int pass = 0; pass < passes; pass++)
216 for (Region &r : op.getRegions())
217 walkLoopsTwice(r, fn);
222std::map<TileDMAChannel, std::set<int>> collectSentPacketIDs(DeviceOp device) {
223 std::map<TileDMAChannel, std::set<int>> ids;
225 if (p.
dma.
dir != DMAChannelDir::MM2S)
227 forEachInProgram<DMABDOp>(p, [&](DMABDOp bd) {
228 if (std::optional<PacketInfoAttr> packet = bd.getPacket())
229 ids[p.
dma].insert(packet->assignedId());
232 for (
auto alloc : device.getOps<ShimDMAAllocationOp>()) {
233 std::optional<PacketInfoAttr> packet = alloc.getPacket();
234 std::optional<TileID> tile = tileOf(alloc.getTile());
236 ids[{*tile, alloc.getChannelDir(),
237 static_cast<int>(alloc.getChannelIndex())}]
238 .insert(packet->assignedId());
240 device.walk([&](AIEX::NpuDmaMemcpyNdOp memcpy) {
241 std::optional<PacketInfoAttr> packet = memcpy.getPacket();
244 if (std::optional<TileDMAChannel> key =
245 channelOfSymbol(device, memcpy.getMetadata().getRootReference()))
246 ids[*key].insert(packet->assignedId());
253 explicit StreamTracer(DeviceOp device)
255 for (
auto sb : device.getOps<SwitchboxOp>())
256 switchboxes[sb.getTileOp().getTileID()] = sb;
257 for (
auto mux : device.getOps<ShimMuxOp>())
258 shimMuxes[mux.getTileOp().getTileID()] = mux;
259 sentIDs = collectSentPacketIDs(device);
262 std::vector<RoutedStream> trace() {
263 for (
auto &[tile, mux] : shimMuxes)
264 for (
Port p : inputPorts(mux.getConnections()))
265 if (p.bundle != WireBundle::North)
266 traceFrom({tile, p}, mux, p);
267 for (
auto &[tile, sb] : switchboxes)
268 for (
Port p : inputPorts(sb.getConnections()))
270 traceFrom({tile, p}, sb, p);
271 return std::move(streams);
276 Interconnect interconnect;
280 static SmallVector<Port> inputPorts(Region &connections) {
281 SmallVector<Port> ports;
282 for (Operation &op : connections.front()) {
283 std::optional<Port> p;
284 if (
auto connect = dyn_cast<ConnectOp>(op))
285 p = connect.sourcePort();
286 else if (
auto rules = dyn_cast<PacketRulesOp>(op))
287 p = rules.sourcePort();
288 if (p && !llvm::is_contained(ports, *p))
295 std::optional<std::set<int>> ids;
297 if (src.
port.bundle == WireBundle::DMA) {
298 auto it = sentIDs.find({src.
tile, DMAChannelDir::MM2S, src.
port.channel});
299 if (it != sentIDs.end())
303 step(src, {interconnect, input}, std::nullopt);
307 step(src, {interconnect, input}, id);
312 std::variant<Hop, StreamEndpoint> follow(Interconnect from,
Port out) {
313 TileID here{from.colIndex(), from.rowIndex()};
314 if (isa<ShimMuxOp>(from)) {
315 if (out.bundle == WireBundle::North)
316 if (
auto sb = switchboxes.find(here); sb != switchboxes.end())
317 return Hop{sb->second, {WireBundle::South, out.channel}};
322 if (out.bundle == WireBundle::South)
323 if (
auto mux = shimMuxes.find(here); mux != shimMuxes.end())
324 return Hop{mux->second, {WireBundle::North, out.channel}};
326 WireBundle in = WireBundle::South;
327 switch (out.bundle) {
328 case WireBundle::North:
330 in = WireBundle::South;
332 case WireBundle::South:
334 in = WireBundle::North;
336 case WireBundle::East:
338 in = WireBundle::West;
342 in = WireBundle::East;
345 if (
auto sb = switchboxes.find(next); sb != switchboxes.end())
346 return Hop{sb->second, {in, out.channel}};
353 std::pair<Operation *, Port> input{hop.interconnect, hop.input};
354 if (!onPath.insert(input).second)
356 llvm::scope_exit leave([&] { onPath.erase(input); });
357 auto sb = dyn_cast<SwitchboxOp>(hop.interconnect.getOperation());
358 Block &b = hop.interconnect.getConnections().front();
360 auto next = [&](
Port out, std::optional<int> nextID,
363 path.push_back({sb.getTileOp().getTileID(), hop.input, arbiter});
364 llvm::scope_exit backtrack([&] {
368 std::variant<Hop, StreamEndpoint> to = follow(hop.interconnect, out);
369 if (
auto *endpoint = std::get_if<StreamEndpoint>(&to))
370 streams.push_back({src, *endpoint, nextID,
keepsPktHeader, path});
372 step(src, std::get<Hop>(to), nextID);
375 for (
auto connect : b.getOps<ConnectOp>())
376 if (connect.sourcePort() == hop.input)
377 next(connect.destPort(),
id, std::nullopt);
379 for (
auto rules : b.getOps<PacketRulesOp>()) {
380 if (rules.sourcePort() != hop.input)
382 auto route = [&](PacketRuleOp rule,
int ruleID) {
383 std::optional<int> arbiter;
384 if (
auto amsel = rule.getAmsel().getDefiningOp<AMSelOp>())
385 arbiter = amsel.arbiterIndex();
386 for (
auto masterSet : b.getOps<MasterSetOp>())
387 if (llvm::is_contained(masterSet.getAmsels(), rule.getAmsel()))
388 next(masterSet.destPort(), ruleID, arbiter,
389 masterSet.keepsPktHeader());
391 for (
int packetID =
id.value_or(0); packetID <=
id.value_or(maxPacketID);
393 for (
auto rule : rules.getRules().front().getOps<PacketRuleOp>())
394 if ((packetID & rule.maskInt()) ==
395 (rule.valueInt() & rule.maskInt())) {
396 route(rule, packetID);
402 std::map<TileID, SwitchboxOp> switchboxes;
403 std::map<TileID, ShimMuxOp> shimMuxes;
404 std::map<TileDMAChannel, std::set<int>> sentIDs;
405 std::vector<RoutedStream> streams;
408 llvm::DenseSet<std::pair<Operation *, Port>> onPath;
409 SmallVector<StreamHop, 8> path;
416 return StreamTracer(device).trace();
420 std::vector<RoutedStream> streams;
421 for (
auto flow : device.getOps<FlowOp>()) {
422 std::optional<TileID> srcTile = tileOf(flow.getSource());
423 std::optional<TileID> dstTile = tileOf(flow.getDest());
424 if (srcTile && dstTile)
426 {{*srcTile, {flow.getSourceBundle(), flow.sourceIndex()}},
427 {*dstTile, {flow.getDestBundle(), flow.destIndex()}},
432 DenseMap<std::pair<TileID, Port>, std::optional<bool>> keepAt;
433 for (
auto flow : device.getOps<PacketFlowOp>())
434 for (
auto dst : flow.getPorts().front().getOps<PacketDestOp>())
435 if (std::optional<TileID> dstTile = tileOf(dst.getTile()))
436 keepAt[{*dstTile, dst.port()}] = flow.getKeepPktHeader();
437 for (
auto flow : device.getOps<PacketFlowOp>()) {
438 Block &b = flow.getPorts().front();
439 for (
auto src : b.getOps<PacketSourceOp>()) {
440 std::optional<TileID> srcTile = tileOf(src.getTile());
443 for (
auto dst : b.getOps<PacketDestOp>())
444 if (std::optional<TileID> dstTile = tileOf(dst.getTile()))
446 {{*srcTile, src.port()},
447 {*dstTile, dst.port()},
450 keepAt.lookup({*dstTile, dst.port()})),
452 static_cast<int>(flow.getMask().value_or(~0))});
459 ArrayRef<RoutedStream> streams)
460 : device(device), streams(streams) {
462 programs[p.dma].push_back(std::move(p));
463 for (
const auto &[dma, channelPrograms] : programs)
465 forEachInProgram<UseLockOp>(
466 p, [&](UseLockOp use) { lockUseProgram[use] = &p; });
467 device.walk([&](AIEX::NpuDmaMemcpyNdOp memcpy) {
468 if (std::optional<TileDMAChannel> dma =
469 channelOfSymbol(device, memcpy.getMetadata().getRootReference()))
470 memcpys[*dma].push_back(memcpy);
475static SmallVector<Block *> chainBlocks(
const DmaChannelProgram &p) {
476 SmallVector<Block *> sequence(p.
bds);
478 sequence.push_back(&r->front());
485static size_t blockAt(
const DmaChannelProgram &p,
size_t size, uint64_t step) {
489 return start + (step - start) % (size - start);
493static std::optional<int64_t> lockAmount(UseLockOp use) {
495 if (!use.getLock().getDefiningOp<LockOp>() ||
496 !matchPattern(use.getValue(), m_ConstantInt(&amount)))
498 return amount.getSExtValue();
502static std::optional<uint64_t> lockTokens(UseLockOp use) {
503 std::optional<int64_t> n = lockAmount(use);
511std::optional<uint64_t>
513 if (stream.
src.
port.bundle != WireBundle::DMA)
517 constexpr uint64_t pktHeaderBytes = 4;
518 auto headerBytes = [&](std::optional<PacketInfoAttr> packet) -> uint64_t {
521 auto bytesOf = [&](DMABDOp bd) -> uint64_t {
522 if (!stream.
carries(bd.getPacket()))
524 return bd.getLenInBytes() + headerBytes(bd.getPacket());
528 if (
auto it = programs.find(dma); it != programs.end()) {
530 bool carried =
false;
532 forEachInProgram<DMABDOp>(p, [&](DMABDOp bd) {
533 if (!stream.
carries(bd.getPacket()))
536 bytes += bytesOf(bd);
542 std::optional<uint64_t> looped = loopedVolume(p, bytesOf);
549 if (isa<AIEX::DMAConfigureTaskOp, AIEX::DMAConfigureTaskForOp>(p.
op)) {
550 auto task = dyn_cast<AIEX::DMAConfigureTaskOp>(p.
op);
551 auto taskFor = dyn_cast<AIEX::DMAConfigureTaskForOp>(p.
op);
552 if ((task && task.getRepeatCountVal()) ||
553 (taskFor && taskFor.getRepeatCountVal()))
556 for (Operation *user : p.
op->getUsers()) {
557 if (isa<AIEX::DMAAwaitTaskOp, AIEX::DMAFreeTaskOp>(user))
561 auto start = dyn_cast<AIEX::DMAStartTaskOp>(user);
562 if (!start || start.hasRepeatParameter())
564 if (user->getParentOfType<LoopLikeOpInterface>())
569 total += bytes * runs;
572 if (
auto it = memcpys.find(dma); it != memcpys.end()) {
573 for (Operation *op : it->second) {
574 auto memcpy = cast<AIEX::NpuDmaMemcpyNdOp>(op);
575 std::optional<PacketInfoAttr> packet = memcpy.getPacket();
577 packet = ShimDMAAllocationOp::getForSymbol(
578 device, memcpy.getMetadata().getRootReference())
582 auto type = dyn_cast<BaseMemRefType>(memcpy.getMemref().getType());
583 if (memcpy->getParentOfType<LoopLikeOpInterface>() || !type ||
584 !type.getElementType().isIntOrFloat())
586 std::optional<SmallVector<int64_t>> sizes =
587 getConstantIntValues(memcpy.getMixedSizes());
590 uint64_t elements = 1;
591 for (int64_t n : *sizes)
595 uint64_t runs = sizes->front();
597 total += elements * type.getElementTypeBitWidth() / 8 +
598 runs * headerBytes(packet);
606std::optional<uint64_t> StreamVolumeAnalysis::loopedVolume(
608 function_ref<uint64_t(DMABDOp)> bytesOf)
const {
610 if (!walkLoop(program, [&](DMABDOp bd) { bytes += bytesOf(bd); }))
616 function_ref<
void(DMABDOp)> visit)
const {
619 !visiting.insert(&p).second)
621 llvm::scope_exit done([&] { visiting.erase(&p); });
622 SmallVector<Block *> sequence = chainBlocks(p);
623 if (sequence.empty())
625 DenseMap<Operation *, uint64_t> tokens;
626 for (
int step = 0; step <
maxBDSteps; step++) {
627 for (Operation &bdOp : *sequence[blockAt(p, sequence.size(), step)]) {
628 if (
auto use = dyn_cast<UseLockOp>(bdOp)) {
629 auto lock = use.getLock().getDefiningOp<LockOp>();
630 std::optional<uint64_t> n = lockTokens(use);
631 if (!n || (!use.release() && !use.acquireGE()))
633 auto it = tokens.find(lock);
634 if (it == tokens.end()) {
635 std::optional<uint64_t> others = tokensFromOthers(lock, &p);
638 it = tokens.try_emplace(lock, lock.getInit().value_or(0) + *others)
643 else if (it->second < *n)
647 }
else if (
auto bd = dyn_cast<DMABDOp>(bdOp)) {
656static bool onceInCore(Operation *op) {
657 for (Operation *parent = op->getParentOp(); parent;
658 op = parent, parent = parent->getParentOp()) {
659 if (!op->getParentRegion()->hasOneBlock() ||
660 isa<LoopLikeOpInterface>(parent))
662 if (isa<CoreOp>(parent))
668std::optional<uint64_t>
669StreamVolumeAnalysis::tokensFromOthers(LockOp lock,
670 const DmaChannelProgram *self)
const {
671 llvm::SetVector<const DmaChannelProgram *> releasers;
673 for (Operation *user : lock->getUsers()) {
674 auto use = dyn_cast<UseLockOp>(user);
679 auto owner = lockUseProgram.find(use);
680 if (owner == lockUseProgram.end()) {
681 std::optional<uint64_t> n = lockTokens(use);
682 if (!onceInCore(use) || !n)
687 if (owner->second != self)
688 releasers.insert(owner->second);
690 for (
const DmaChannelProgram *program : releasers) {
691 std::optional<uint64_t> n = releasesOver(*program, lock);
702 if (first.
src != then.
src)
708 if (first.
src.
port.bundle != WireBundle::DMA)
712 auto it = programs.find(dma);
713 if (it == programs.end() || it->second.size() != 1 ||
714 !isa<DMAStartOp, DMAOp>(it->second.front().op) || memcpys.count(dma))
717 bool sent =
false, after =
false;
718 auto visit = [&](DMABDOp bd) {
719 after |= sent && then.
carries(bd.getPacket());
720 sent |= first.
carries(bd.getPacket());
723 if (!walkLoop(p, visit))
727 SmallVector<Block *> sequence = chainBlocks(p);
728 for (uint64_t pass = 0; pass < p.
passes && !after; pass++)
729 for (Block *b : sequence)
730 for (DMABDOp bd : b->getOps<DMABDOp>())
735std::optional<uint64_t>
738 if (!isa<DMAStartOp, DMAOp>(p.
op))
740 SmallVector<Block *> sequence = chainBlocks(p);
742 uint64_t perPass = 0;
743 for (Block *b : sequence)
744 for (
auto use : b->getOps<UseLockOp>())
745 if (use.release() && use.getLock().getDefiningOp() == lock) {
746 std::optional<uint64_t> n = lockTokens(use);
751 return perPass * p.
passes;
754 if (p.
dma.
dir != DMAChannelDir::S2MM || sequence.empty())
757 uint64_t received = 0;
758 for (
const RoutedStream &s : streams) {
759 if (s.dst != endpoint)
761 std::optional<uint64_t> bytes = sendVolume(s);
766 uint64_t filled = 0, tokens = 0;
767 for (
int step = 0; step <
maxBDSteps; step++) {
768 for (Operation &bdOp : *sequence[blockAt(p, sequence.size(), step)]) {
769 if (
auto bd = dyn_cast<DMABDOp>(bdOp)) {
770 if (filled + bd.getLenInBytes() > received)
772 filled += bd.getLenInBytes();
773 }
else if (
auto use = dyn_cast<UseLockOp>(bdOp)) {
774 if (!use.release() || use.getLock().getDefiningOp() != lock)
776 std::optional<uint64_t> n = lockTokens(use);
786static std::optional<uint64_t> programCapacity(
const DmaChannelProgram &p,
788 if (!isa<DMAStartOp, DMAOp>(p.
op))
792 SmallVector<Block *> sequence = chainBlocks(p);
793 if (sequence.empty())
798 DenseMap<Operation *, int64_t> lockValues;
802 std::optional<DenseMap<Operation *, int64_t>> cycleLocks;
803 uint64_t cycleBytes = 0;
805 for (uint64_t step = 0;; step++) {
806 if (!p.
loops && step >= p.
passes * sequence.size())
808 size_t i = blockAt(p, sequence.size(), step);
810 if (cycleLocks && cycleLocks->size() == lockValues.size() &&
811 llvm::all_of(lockValues, [&](
const auto &lv) {
812 int64_t before = cycleLocks->at(lv.first);
813 return stateLocks ? lv.second == before : lv.second >= before;
818 (p.
passes - step / sequence.size()) * (bytes - cycleBytes);
820 cycleLocks = lockValues;
824 if (step >=
static_cast<uint64_t
>(
maxBDSteps))
826 for (Operation &bdOp : *sequence[i]) {
827 if (
auto use = dyn_cast<UseLockOp>(bdOp)) {
828 std::optional<int64_t> n = lockAmount(use);
831 auto lock = use.getLock().getDefiningOp<LockOp>();
833 lockValues.try_emplace(lock, lock.getInit().value_or(0))
838 else if (value != *n)
840 }
else if (use.release()) {
842 }
else if (!use.acquireGE() || value < *n) {
847 }
else if (
auto bd = dyn_cast<DMABDOp>(bdOp)) {
848 bytes += bd.getLenInBytes();
854std::optional<uint64_t>
856 if (endpoint.
port.bundle != WireBundle::DMA)
858 auto it = programs.find(
859 {endpoint.
tile, DMAChannelDir::S2MM, endpoint.
port.channel});
860 if (it == programs.end())
862 std::optional<uint64_t> capacity;
864 if (std::optional<uint64_t> c = programCapacity(p, device))
865 capacity = std::min(*c, capacity.value_or(*c));
870 ArrayRef<RoutedStream> streams)
const {
876 if (s.dst != endpoint)
878 std::optional<uint64_t> bytes =
sendVolume(s);
883 return sent > *capacity;
887 ArrayRef<RoutedStream> streams,
889 std::set<TileDMAChannel> neverFull;
891 if (s.dst.port.bundle == WireBundle::DMA &&
892 !volumes.
canFill(s.dst, streams))
893 neverFull.insert({s.dst.tile, DMAChannelDir::S2MM, s.dst.port.channel});
894 auto waitsOnLocks = [&](
unsigned agent) {
895 const Agent &a = agents[agent];
900 DenseMap<Operation *, llvm::SetVector<unsigned>> acquirers, releasers;
901 auto noteLock = [&](UseLockOp use,
unsigned agent) {
902 Operation *lock = use.getLock().getDefiningOp();
905 if (use.getAction() == LockAction::Release)
906 releasers[lock].insert(agent);
908 acquirers[lock].insert(agent);
913 for (
auto core : device.getOps<CoreOp>()) {
914 unsigned agent = getOrCreate(
Agent::core(core.getTileOp().getTileID()));
915 modeled.insert(agent);
916 core.walk([&](UseLockOp use) { noteLock(use, agent); });
920 modeled.insert(agent);
921 forEachInProgram<UseLockOp>(p,
922 [&](UseLockOp use) { noteLock(use, agent); });
926 for (
auto lock : device.getOps<LockOp>()) {
927 auto waiting = acquirers.find(lock), it = releasers.find(lock);
928 if (waiting == acquirers.end() || it == releasers.end())
930 for (
unsigned p : waiting->second)
931 for (
unsigned q : it->second)
932 if (p != q && waitsOnLocks(p))
933 addEdge(p, q, EdgeKind::Lock);
937 std::optional<unsigned> from, to;
938 if (std::optional<Agent> agent =
Agent::at(s.src,
true))
939 from = getOrCreate(*agent);
940 if (std::optional<Agent> agent =
Agent::at(s.dst,
false))
941 to = getOrCreate(*agent);
942 if (!from || !to || *from == *to)
944 addEdge(*from, *to, EdgeKind::Stream,
true);
945 addEdge(*to, *from, EdgeKind::Stream,
false,
true);
953 unsigned numStreamAgents = agents.size();
954 for (
auto sequence : device.getOps<RuntimeSequenceOp>()) {
955 auto bySymbol = [&](SymbolRefAttr symbol) -> SmallVector<TileDMAChannel> {
956 if (std::optional<TileDMAChannel> key =
957 channelOfSymbol(device, symbol.getRootReference()))
962 [&](Operation *op) -> std::optional<SmallVector<TileDMAChannel>> {
963 if (
auto memcpy = dyn_cast<AIEX::NpuDmaMemcpyNdOp>(op))
964 return bySymbol(memcpy.getMetadata());
965 if (
auto start = dyn_cast<AIEX::DMAStartTaskOp>(op))
966 return channelsOfTask(device, start.getTask());
967 if (isa<AIEX::DMAStartBdChainOp, AIEX::DMAStartBdChainForOp>(op))
968 return channelsOfTask(device, op->getResult(0));
969 if (
auto push = dyn_cast<AIEX::NpuPushQueueOp>(op))
970 return SmallVector<TileDMAChannel>{
971 {{
static_cast<int>(push.getColumn()),
972 static_cast<int>(push.getRow())},
974 static_cast<int>(push.getChannel())}};
975 if (isa<AIEX::NpuWrite32Op, AIEX::NpuMaskWrite32Op, AIEX::NpuBlockWriteOp,
976 AIEX::NpuWriteConfigOp, AIEX::NpuWriteTeardownOp>(op))
977 return SmallVector<TileDMAChannel>{};
981 [&](Operation *op) -> std::optional<SmallVector<TileDMAChannel>> {
982 if (
auto dmaWait = dyn_cast<AIEX::NpuDmaWaitOp>(op))
983 return bySymbol(dmaWait.getSymbolAttr());
984 if (
auto await = dyn_cast<AIEX::DMAAwaitTaskOp>(op))
985 return channelsOfTask(device, await.getTask());
986 if (
auto sync = dyn_cast<AIEX::NpuSyncOp>(op)) {
987 std::optional<int64_t> col = getConstantIntValue(sync.getColumn()),
988 row = getConstantIntValue(sync.getRow()),
989 dir = getConstantIntValue(sync.getDirection()),
990 channel = getConstantIntValue(sync.getChannel()),
991 cols = getConstantIntValue(sync.getColumnNum()),
992 rows = getConstantIntValue(sync.getRowNum());
993 if (!col || !row || !dir || !channel || !cols || !rows)
994 return SmallVector<TileDMAChannel>{};
995 SmallVector<TileDMAChannel> keys;
996 for (int64_t c = *col; c < *col + *cols; c++)
997 for (int64_t r = *row; r < *row + *rows; r++)
998 keys.push_back({{static_cast<int>(c), static_cast<int>(r)},
999 *dir ? DMAChannelDir::MM2S : DMAChannelDir::S2MM,
1000 static_cast<int>(*channel)});
1003 if (isa<AIEX::NpuMaskPollOp>(op))
1004 return SmallVector<TileDMAChannel>{};
1005 return std::nullopt;
1007 llvm::SetVector<TileDMAChannel, SmallVector<TileDMAChannel>,
1008 std::set<TileDMAChannel>>
1010 sequence.walk([&](Operation *op) {
1011 if (std::optional<SmallVector<TileDMAChannel>> keys = issued(op))
1012 issuedKeys.insert(keys->begin(), keys->end());
1015 llvm::SetVector<unsigned> waited;
1016 walkLoopsTwice(sequence.getBody(), [&](Operation *op) {
1017 if (std::optional<SmallVector<TileDMAChannel>> keys = issued(op)) {
1018 for (const TileDMAChannel &key : *keys) {
1019 unsigned agent = getOrCreate(Agent::channel(key));
1020 modeled.insert(agent);
1021 for (unsigned w : waited)
1023 addEdge(agent, w, EdgeKind::Host);
1026 for (unsigned a = 0; a < numStreamAgents; a++)
1027 if (agents[a].kind == Agent::Kind::Channel)
1028 for (unsigned w : waited)
1030 addEdge(a, w, EdgeKind::Host);
1031 }
else if (std::optional<SmallVector<TileDMAChannel>> keys =
1034 keys->empty() ? issuedKeys.getArrayRef() : ArrayRef(*keys)) {
1036 for (
unsigned a = 0; a < numStreamAgents; a++)
1038 agents[a].dma.tile.col == key.tile.col)
1049 for (Agent &a : agents)
1050 a.onShim = a.kind == Agent::Kind::Channel &&
1052 for (
unsigned a = 0; a < agents.size(); a++) {
1053 if (modeled.contains(a) || agents[a].kind == Agent::Kind::Controller ||
1056 for (
unsigned b = 0; b < agents.size(); b++)
1057 if (b != a && agents[b].kind != Agent::Kind::Controller &&
1058 (agents[b].dma.tile == agents[a].dma.tile ||
1059 (agents[a].onShim && agents[b].onShim)))
1060 addEdge(a, b, EdgeKind::Lock);
1064 for (
unsigned a = 0; a < agents.size(); a++)
1065 for (
const Edge &e : edges[a])
1066 llvm::dbgs() <<
"Wait: " << describe(a) <<
" on " << describe(e.to)
1067 << (e.kind == EdgeKind::Lock ?
" (lock)"
1068 : e.kind == EdgeKind::Stream ?
" (stream)"
1070 << (e.kind == EdgeKind::Lock && !modeled.contains(a)
1077std::optional<StreamWaitGraph::Agent>
1079 if (endpoint.
port.bundle == WireBundle::Core)
1081 if (endpoint.
port.bundle == WireBundle::TileControl && sending)
1083 if (endpoint.
port.bundle == WireBundle::DMA)
1085 sending ? DMAChannelDir::MM2S : DMAChannelDir::S2MM,
1086 endpoint.
port.channel});
1089 return std::nullopt;
1092unsigned StreamWaitGraph::getOrCreate(
const Agent &agent) {
1093 auto [it, inserted] =
1094 agentIDs.try_emplace({agent.
kind, agent.
dma}, agents.size());
1096 agents.push_back(agent);
1097 edges.emplace_back();
1102std::optional<unsigned> StreamWaitGraph::lookup(
const Agent &agent)
const {
1103 auto it = agentIDs.find({agent.kind, agent.dma});
1104 if (it == agentIDs.end())
1105 return std::nullopt;
1109void StreamWaitGraph::addEdge(
unsigned from,
unsigned to, EdgeKind kind,
1110 bool sends,
bool receives) {
1111 for (Edge &e : edges[from]) {
1112 if (e.to == to && e.kind == kind) {
1114 e.receives |= receives;
1118 edges[from].push_back({to, kind, sends, receives});
1122 bool sending)
const {
1123 if (std::optional<Agent> agent =
Agent::at(endpoint, sending))
1124 return lookup(*agent);
1125 return std::nullopt;
1131 SmallVector<unsigned> drainers;
1132 for (
const Edge &e : edges[agent])
1133 if (e.kind != EdgeKind::Stream)
1134 drainers.push_back(e.to);
1138SmallVector<unsigned>
1140 ArrayRef<unsigned> avoid)
const {
1153 llvm::SparseBitVector<> took;
1154 std::optional<size_t> parent;
1156 std::vector<State> states;
1157 DenseMap<unsigned, SmallVector<size_t>> seen;
1158 std::deque<size_t> worklist;
1159 auto visit = [&](State next) {
1160 for (
size_t i : seen.lookup(next.agent)) {
1161 const State &s = states[i];
1162 if ((next.taking || !s.taking) && next.took.contains(s.took))
1165 seen[next.agent].push_back(states.size());
1166 worklist.push_back(states.size());
1167 states.push_back(std::move(next));
1169 for (
unsigned a : from)
1170 if (!llvm::is_contained(avoid, a))
1171 visit({a,
false, {}, std::nullopt});
1172 while (!worklist.empty()) {
1173 size_t index = worklist.front();
1174 worklist.pop_front();
1175 unsigned a = states[index].agent;
1176 if (llvm::is_contained(targets, a)) {
1177 SmallVector<unsigned> chain;
1178 for (std::optional<size_t> at = index; at; at = states[*at].parent)
1179 chain.push_back(states[*at].agent);
1180 std::reverse(chain.begin(), chain.end());
1183 for (
const Edge &e : edges[a]) {
1184 const State &state = states[index];
1185 if (state.taking && e.receives && !e.sends &&
1188 if (llvm::is_contained(avoid, e.to) || state.took.test(e.to))
1190 bool takes = e.kind == EdgeKind::Stream && e.sends &&
1192 llvm::SparseBitVector<> took = state.took;
1195 visit({e.to, takes, std::move(took), index});
1202 const Agent &a = agents[id];
1204 llvm::raw_string_ostream os(s);
1209 os <<
"TileControl";
1216 ArrayRef<RoutedStream> streams)
1217 : streams(streams), volumes(device, streams),
1218 graph(device, streams, volumes), stalls(streams.size()),
1219 silence(streams.size()) {}
1221bool StreamDeadlockAnalysis::canStall(
size_t f)
const {
1222 std::optional<bool> &stall = stalls[f];
1224 stall = volumes.
canFill(streams[f].dst, streams);
1228SmallVector<unsigned> StreamDeadlockAnalysis::blockingChain(
size_t f,
1231 std::optional<unsigned> fDst = graph.
agentAt(fs.
dst,
false);
1234 SmallVector<unsigned> own;
1235 if (std::optional<unsigned> fSrc = graph.
agentAt(fs.
src,
true))
1236 own.push_back(*fSrc);
1237 own.push_back(*fDst);
1238 SmallVector<unsigned> drainers = graph.
drainersOf(*fDst);
1239 SmallVector<unsigned> avoid, targets;
1240 for (
unsigned a : own)
1241 if (!llvm::is_contained(drainers, a))
1243 for (std::optional<unsigned> a :
1245 if (a && !llvm::is_contained(own, *a))
1246 targets.push_back(*a);
1247 if (targets.empty())
1249 return graph.waitChain(drainers, targets, avoid);
1253 if (
auto it = blocks.find({f, g}); it != blocks.end())
1255 return blocks[{f, g}] = canStall(f) && !
silent(f) && !
silent(g) &&
1256 volumes.maySendAfter(streams[f], streams[g]) !=
1257 std::optional(
false) &&
1258 !blockingChain(f, g).empty();
1262 std::optional<bool> &quiet = silence[f];
1264 quiet = volumes.sendVolume(streams[f]) == std::optional<uint64_t>(0);
1270 SmallVector<unsigned> chain = blockingChain(f, g);
1272 llvm::raw_string_ostream os(s);
1273 os <<
describeStream(fs) <<
" can fill its receiver, and draining that "
1275 for (
auto [i, a] : llvm::enumerate(chain))
1276 os << (i ?
", then " :
"") << graph.describe(a);
1277 os << (graph.agentAt(gs.dst,
false) == chain.back() ?
", which receives "
1288 SmallVector<std::string> assumed;
1290 if (other.dst == fs.
dst && !volumes.sendVolume(other)) {
1292 " carries is unknown, so it is assumed to overrun its "
1296 if (fs.
src == gs.src && !volumes.maySendAfter(fs, gs))
1297 assumed.push_back(
"Both come from " +
1299 ", and the order it sends in is not modeled.");
1300 SmallVector<unsigned> chain = blockingChain(f, g);
1301 std::optional<unsigned> receiver = graph.agentAt(fs.
dst,
false);
1302 assert(receiver &&
"a stream that can block has a receiver");
1303 SmallVector<unsigned> waiters{*receiver};
1304 waiters.append(chain.begin(), std::prev(chain.end()));
1305 for (
auto [i, a] : llvm::enumerate(waiters))
1306 if (!graph.isModeled(a) &&
1307 !llvm::is_contained(ArrayRef(waiters).take_front(i), a))
1309 "Nothing in the design programs " + graph.describe(a) +
1310 ", so it is assumed to wait on anything on its tile" +
1311 (graph.getAgent(a).onShim ?
" or on another shim tile." :
"."));
1317 llvm::raw_string_ostream os(s);
1318 os << (stream.
packetID ?
"packet flow " :
"flow ")
1322 os <<
" (id " << *stream.
packetID <<
")";
1328 numRequested(streams.size()) {
1330 streams.push_back(std::move(s));
1331 std::map<TreeKey, size_t> treeIDs;
1332 for (
size_t i = 0; i < streams.size(); i++) {
1333 size_t tree = treeMembers.size();
1334 if (std::optional<TreeKey> key = treeKey(i))
1335 tree = treeIDs.try_emplace(*key, tree).first->second;
1336 if (tree == treeMembers.size())
1337 treeMembers.emplace_back();
1338 treeMembers[tree].push_back(i);
1339 treeOf.push_back(tree);
1341 waits.resize(treeMembers.size());
1344std::optional<StreamConflicts::TreeKey>
1345StreamConflicts::treeKey(
size_t s)
const {
1348 return std::nullopt;
1355 analysis.emplace(device, streams);
1359bool StreamConflicts::blocks(
size_t s,
size_t t)
const {
1360 const RoutedStream &a = streams[s], &b = streams[t];
1361 if (a.
src == b.src || a.
dst == b.dst)
1363 return getAnalysis().canBlock(s, t);
1368bool StreamConflicts::related(
size_t s,
size_t t)
const {
1369 if (streams[s].src == streams[t].src)
1371 for (
size_t m : treeMembers[treeOf[s]])
1372 for (
size_t n : treeMembers[treeOf[t]])
1373 if (streams[m].dst == streams[n].dst)
1379 return !related(s, t) && (blocks(s, t) || blocks(t, s));
1384const DenseMap<size_t, std::pair<size_t, size_t>> &
1385StreamConflicts::waitsFrom(
size_t a)
const {
1386 std::optional<DenseMap<size_t, std::pair<size_t, size_t>>> &cached = waits[a];
1389 DenseMap<size_t, std::pair<size_t, size_t>> &reached = cached.emplace();
1390 std::deque<size_t> work{a};
1391 while (!work.empty()) {
1392 size_t u = work.front();
1394 for (
size_t g = 0; g < treeMembers.size(); g++) {
1395 if (g == a || reached.contains(g) ||
1396 !streams[treeMembers[g].front()].packetID)
1398 auto by = [&]() -> std::optional<std::pair<size_t, size_t>> {
1399 for (
size_t x : treeMembers[u])
1400 for (
size_t m : treeMembers[g])
1402 return std::pair{x, m};
1403 return std::nullopt;
1417 return blocks(s, t) || blocks(t, s) ||
1418 waitsFrom(treeOf[s]).contains(treeOf[t]) ||
1419 waitsFrom(treeOf[t]).contains(treeOf[s]);
1428 if (!waitsFrom(treeOf[s]).contains(treeOf[t]))
1430 SmallVector<std::string> steps;
1431 for (
size_t g = treeOf[t]; g != treeOf[s];) {
1432 auto [x, m] = waitsFrom(treeOf[s]).at(g);
1436 std::reverse(steps.begin(), steps.end());
1437 return llvm::join(steps,
" ");
1441 SmallVector<std::pair<size_t, size_t>> pairs;
1443 for (
size_t s = 0; s < numRequested; s++)
1444 for (
size_t t = 0; t < numRequested; t++) {
1445 if (s == t || !(streams[s].packetID || streams[t].packetID) ||
1446 !related(s, t) || !a.
canBlock(s, t))
1449 pairs.push_back({s, t});
1451 LLVM_DEBUG(llvm::dbgs() <<
"Unavoidable only by assumption: "
1462 std::optional<HoldCycle::Step> step;
1465 std::optional<std::pair<TileID, int>> merged = std::nullopt;
1470 llvm::function_ref<ArrayRef<WaitEdge>(
size_t)> successors;
1473using WaitNode = std::pair<const WaitGraph *, size_t>;
1478struct GraphTraits<const WaitGraph *> {
1487 return map_iterator(n.first->successors(n.second).begin(),
ToNode{n.first});
1490 return map_iterator(n.first->successors(n.second).end(),
ToNode{n.first});
1495std::optional<HoldCycle>
1497 bool forcedWaits,
bool definite)
const {
1500 SmallVector<size_t, 2> members;
1501 SmallVector<std::pair<TileID, Port>, 8> hops;
1502 SmallVector<int, 8> parent;
1503 SmallVector<std::optional<int>, 8> arbiter;
1506 std::vector<Tree> trees;
1507 std::map<TreeKey, size_t> treeIDs;
1508 for (
size_t i = 0; i < streams.size(); i++) {
1509 std::optional<TreeKey> key = treeKey(i);
1510 if (!key || getAnalysis().silent(i))
1512 auto [it, inserted] = treeIDs.try_emplace(*key, trees.size());
1514 trees.emplace_back();
1515 Tree &tree = trees[it->second];
1516 tree.members.push_back(i);
1518 for (
const StreamHop &hop : routes[i]) {
1519 std::pair<TileID, Port> key{hop.tile, hop.input};
1520 int h = llvm::find(tree.hops, key) - tree.hops.begin();
1521 if (h ==
static_cast<int>(tree.hops.size())) {
1522 tree.hops.push_back(key);
1523 tree.parent.push_back(prev);
1524 tree.arbiter.push_back(hop.arbiter);
1535 auto sameSource = [&](
size_t a,
size_t b) {
1536 return streams[trees[a].members.front()].src ==
1537 streams[trees[b].members.front()].src;
1539 auto forced = [&](
size_t a,
int ha,
size_t b,
int hb,
TileID tile) {
1540 Port ina = trees[a].hops[ha].second, inb = trees[b].hops[hb].second;
1541 auto into = [&](
size_t m,
Port in) {
1542 return streams[m].dst.tile == tile && !routes[m].empty() &&
1543 routes[m].back().tile == tile && routes[m].back().input == in;
1545 for (
size_t m : trees[a].members)
1546 for (
size_t n : trees[b].members)
1547 if (streams[m].dst == streams[n].dst && into(m, ina) && into(n, inb))
1549 if (streams[trees[a].members.front()].packetID !=
1550 streams[trees[b].members.front()].packetID)
1552 for (
auto above : llvm::enumerate(trees[a].parent))
1553 if (above.value() == ha &&
1554 llvm::any_of(llvm::enumerate(trees[b].parent), [&](
auto below) {
1555 return below.value() == hb &&
1556 trees[b].hops[below.index()] == trees[a].hops[above.index()];
1565 enum class Kind { Hop, Receiver, Anywhere, Past };
1571 std::vector<Node> nodes;
1572 for (
size_t t = 0; t < trees.size(); t++) {
1573 Tree &tree = trees[t];
1574 tree.base = nodes.size();
1575 for (
size_t h = 0; h < tree.hops.size(); h++)
1576 nodes.push_back({t, Kind::Hop, h});
1577 for (
size_t m = 0; m < tree.members.size(); m++)
1578 nodes.push_back({t, Kind::Receiver, m});
1579 nodes.push_back({t, Kind::Anywhere, 0});
1580 for (
size_t h = 0; h < tree.hops.size(); h++)
1581 nodes.push_back({t, Kind::Past, h});
1583 auto receiverNode = [&](
size_t t,
size_t m) {
1584 return trees[t].base + trees[t].hops.size() + m;
1586 auto anywhereNode = [&](
size_t t) {
1587 return receiverNode(t, trees[t].members.size());
1589 auto pastNode = [&](
size_t t,
size_t h) {
return anywhereNode(t) + 1 + h; };
1591 std::map<std::pair<TileID, Port>, SmallVector<std::pair<size_t, size_t>, 4>>
1593 std::map<TileID, SmallVector<std::pair<size_t, size_t>, 8>> passing;
1594 for (
size_t t = 0; t < trees.size(); t++)
1595 for (
size_t h = 0; h < trees[t].hops.size(); h++) {
1596 entering[trees[t].hops[h]].push_back({t, h});
1597 passing[trees[t].hops[h].first].push_back({t, h});
1602 using Grant = std::pair<TileID, int>;
1603 auto mergedAt = [&](
size_t a,
int ha,
size_t b,
1604 int hb) -> std::optional<Grant> {
1606 int pa = trees[a].parent[ha], pb = trees[b].parent[hb];
1607 if (pa < 0 || pb < 0)
1608 return std::nullopt;
1609 if (trees[a].hops[pa] != trees[b].hops[pb]) {
1610 std::optional<int> arbiter = trees[a].arbiter[pa];
1611 if (trees[a].hops[pa].first != trees[b].hops[pb].first || !arbiter ||
1612 arbiter != trees[b].arbiter[pb])
1613 return std::nullopt;
1614 return Grant{trees[a].hops[pa].first, *arbiter};
1621 using Edge = WaitEdge;
1622 SmallVector<size_t> roots;
1623 std::vector<std::optional<SmallVector<Edge, 4>>> edges(nodes.size() + 1);
1624 auto successors = [&](
size_t n) -> ArrayRef<Edge> {
1627 SmallVector<Edge, 4> out;
1628 if (n == nodes.size()) {
1629 for (
size_t root : roots)
1630 out.push_back({root, std::nullopt});
1631 return *(edges[n] = std::move(out));
1633 const Node &node = nodes[n];
1634 const Tree &u = trees[node.tree];
1635 switch (node.kind) {
1637 auto [tile, input] = u.hops[node.index];
1638 size_t waiting = u.members.front();
1639 for (
auto [t, ht] : entering.at({tile, input})) {
1640 if (t != node.tree && sameSource(node.tree, t))
1642 size_t sharer = trees[t].members.front();
1643 bool sameId = streams[waiting].packetID == streams[sharer].packetID;
1644 if (t != node.tree && (!sameId || forcedWaits))
1648 tile, input, input, -1, sameId},
1649 mergedAt(node.tree, node.index, t, ht)});
1650 std::optional<int> arbiter = trees[t].arbiter[ht];
1655 for (
auto [v, hv] : passing.at(tile)) {
1656 Port holderInput = trees[v].hops[hv].second;
1657 bool self = v == node.tree && t == node.tree;
1658 if ((!self && (v == node.tree || v == t || sameSource(t, v))) ||
1659 holderInput == input || trees[v].arbiter[hv] != arbiter)
1661 bool isForced = !self && forced(t, ht, v, hv, tile);
1662 if (isForced && !forcedWaits)
1667 trees[v].members.front(), tile, input,
1668 holderInput, *arbiter, isForced}});
1673 case Kind::Receiver: {
1675 size_t s = u.members[node.index];
1676 for (
size_t g = 0; g < trees.size(); g++) {
1677 if (g == node.tree || llvm::any_of(trees[g].members, [&](
size_t m) {
1678 return streams[m].dst == streams[s].dst;
1681 for (
size_t m : trees[g].members)
1683 (!definite || getAnalysis().assumptions(s, m).empty())) {
1684 out.push_back({anywhereNode(g),
1692 case Kind::Anywhere:
1694 llvm::SmallDenseSet<size_t, 8> behind;
1695 if (node.kind == Kind::Past)
1696 for (
int h = node.index; h >= 0; h = u.parent[h])
1698 for (
size_t h = 0; h < u.hops.size(); h++)
1699 if (!behind.contains(h))
1700 out.push_back({u.base + h, std::nullopt});
1701 for (
size_t m = 0; m < u.members.size(); m++)
1702 out.push_back({receiverNode(node.tree, m), std::nullopt});
1706 return *(edges[n] = std::move(out));
1711 auto counts = [&](
const Edge &e) {
1714 return e.step->waiting < numRequested || e.step->sharer < numRequested ||
1715 e.step->holding < numRequested;
1717 for (
size_t n = 0; n < nodes.size(); n++)
1718 if (nodes[n].kind == Kind::Hop && llvm::any_of(successors(n), counts))
1721 const WaitGraph graph{successors, nodes.size()};
1722 std::vector<int> component(nodes.size() + 1, -1);
1724 for (
auto scc = llvm::scc_begin(&graph); !scc.isAtEnd(); ++scc, ++components)
1725 for (WaitNode n : *scc)
1726 component[n.second] = components;
1738 std::optional<size_t> fixed;
1739 SmallVector<size_t, 2> excluded;
1741 using Constraints = std::map<Grant, Holders>;
1742 auto allowed = [](
const Edge &edge,
const Constraints &c) {
1745 auto it = c.find({edge.step->tile, edge.step->arbiter});
1748 size_t holder = edge.step->holding;
1749 return it->second.fixed ? *it->second.fixed == holder
1750 : !llvm::is_contained(it->second.excluded, holder);
1753 [&](
size_t x,
const Edge &e,
1754 const Constraints &c) -> std::optional<SmallVector<const Edge *>> {
1756 return std::nullopt;
1759 auto ordered = [&](
const Edge &edge) {
1760 return !edge.step || edge.merged == e.merged;
1762 auto state = [](
size_t node,
bool waited) {
return 2 * node + waited; };
1763 size_t start = state(e.to, !e.merged), end = state(x,
true);
1764 DenseMap<size_t, std::pair<size_t, const Edge *>> via;
1765 via[start] = {start,
nullptr};
1766 std::deque<size_t> worklist{start};
1767 while (!via.contains(end)) {
1768 if (worklist.empty())
1769 return std::nullopt;
1770 size_t n = worklist.front();
1771 worklist.pop_front();
1772 for (
const Edge &next : successors(n / 2)) {
1773 size_t to = state(next.to, n % 2 || !ordered(next));
1774 if (component[next.to] == component[x] && allowed(next, c) &&
1775 via.try_emplace(to, n, &next).second)
1776 worklist.push_back(to);
1779 SmallVector<const Edge *> path;
1780 for (
size_t n = end; n != start; n = via.at(n).first)
1781 path.push_back(via.at(n).second);
1783 std::reverse(path.begin(), path.end());
1786 auto toCycle = [](ArrayRef<const Edge *> path) {
1788 for (
const Edge *edge : path)
1790 cycle.
steps.push_back(*edge->step);
1793 constexpr int maxWalkSearches = 1024;
1794 for (
size_t x : roots)
1795 for (
const Edge &e : successors(x)) {
1796 if (!counts(e) || component[e.to] != component[x])
1798 std::optional<SmallVector<const Edge *>> first;
1799 SmallVector<Constraints> pending{{}};
1801 for (; !pending.empty(); search++) {
1802 if (search == maxWalkSearches) {
1803 assert(first &&
"only a walk found adds pending constraints");
1804 LLVM_DEBUG(llvm::dbgs() <<
"Hold cycle search gave up after "
1805 << search <<
" walks\n");
1806 return toCycle(*first);
1808 Constraints c = pending.pop_back_val();
1809 std::optional<SmallVector<const Edge *>> path = closeWalk(x, e, c);
1814 std::map<Grant, size_t> held;
1815 std::optional<std::pair<Grant, size_t>> clash;
1816 for (
const Edge *edge : *path) {
1819 auto [it, inserted] = held.try_emplace(
1820 {edge->step->tile, edge->step->arbiter}, edge->step->holding);
1821 if (!inserted && it->second != edge->step->holding) {
1827 LLVM_DEBUG(llvm::dbgs() <<
"Hold cycle search closed a walk after "
1828 << search + 1 <<
" walks\n");
1829 return toCycle(*path);
1831 Constraints fix = c, exclude = c;
1832 fix[clash->first].fixed = clash->second;
1833 exclude[clash->first].excluded.push_back(clash->second);
1834 pending.push_back(std::move(exclude));
1835 pending.push_back(std::move(fix));
1837 LLVM_DEBUG(llvm::dbgs() <<
"Hold cycle search closed no walk after "
1838 << search <<
" walks\n");
1840 return std::nullopt;
1845 llvm::raw_string_ostream os(s);
1846 for (
auto [i, step] : llvm::enumerate(cycle.
steps)) {
1849 switch (step.wait) {
1851 os <<
describeStream(streams[step.waiting]) <<
" can queue behind "
1853 <<
describePort(step.sharerInput) <<
" into tile (" << step.tile.col
1854 <<
", " << step.tile.row <<
").";
1857 os <<
describeStream(streams[step.holding]) <<
" can hold arbiter "
1858 << step.arbiter <<
" at tile (" << step.tile.col <<
", "
1859 << step.tile.row <<
") that " <<
describeStream(streams[step.sharer])
1861 if (step.waiting != step.sharer)
1863 <<
" can queue behind it";
1867 os << getAnalysis().explainBlock(step.waiting, step.holding);
virtual AIEArch getTargetArch() const =0
Return the target architecture.
bool isShimNOCorPLTile(int col, int row) const
Return true if the given tile is either a ShimNOC or ShimPL tile.
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,...
StreamConflicts(DeviceOp device)
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...
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.
Which streams can deadlock against each other if they share an arbiter or a link.
bool canBlock(size_t f, size_t g) const
Whether stream f, stalled at its receiver, can keep stream g from ever arriving: f carries more than ...
std::string explainBlock(size_t f, size_t g) const
Why f can block g: the chain of waits from f's receiver to g, and which links of it are assumed for l...
bool silent(size_t f) const
Whether stream f carries nothing, so it neither holds nor waits.
llvm::SmallVector< std::string > assumptions(size_t f, size_t g) const
What explainBlock(f, g) takes on trust rather than reads off the design, one sentence each.
StreamDeadlockAnalysis(DeviceOp device, llvm::ArrayRef< RoutedStream > streams)
streams must outlive the analysis.
How much data a stream carries over a run and how much a receiver takes in before it has to wait on a...
StreamVolumeAnalysis(DeviceOp device, llvm::ArrayRef< RoutedStream > streams)
std::optional< uint64_t > sendVolume(const RoutedStream &stream) const
Bytes the stream's source sends with the stream's packet id, headers included where the receiver keep...
std::optional< uint64_t > receiveCapacity(const StreamEndpoint &endpoint) const
Bytes the receiver at endpoint accepts before it waits on another agent, or nullopt when it never doe...
bool canFill(const StreamEndpoint &endpoint, llvm::ArrayRef< RoutedStream > streams) const
Whether streams can send the receiver at endpoint more than it takes in before it waits on another ag...
std::optional< bool > maySendAfter(const RoutedStream &first, const RoutedStream &then) const
Whether the source both streams come from can send a packet of then after one of first; nullopt if it...
std::optional< unsigned > agentAt(const StreamEndpoint &endpoint, bool sending) const
The agent pushing data into (sending) or pulling data out of the fabric at endpoint,...
std::string describe(unsigned id) const
llvm::SmallVector< unsigned > waitChain(llvm::ArrayRef< unsigned > from, llvm::ArrayRef< unsigned > targets, llvm::ArrayRef< unsigned > avoid) const
A shortest chain of waits from an agent in from to one in targets that avoids avoid,...
llvm::SmallVector< unsigned > drainersOf(unsigned agent) const
Agents whose progress frees room in agent for more incoming data: a core frees itself,...
StreamWaitGraph(DeviceOp device, llvm::ArrayRef< RoutedStream > streams, const StreamVolumeAnalysis &volumes)
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)
std::vector< RoutedStream > requestedStreams(DeviceOp device)
The streams the aie.flow and aie.packet_flow ops ask for, one per source, destination and packet id,...
std::vector< RoutedStream > traceRoutedStreams(DeviceOp device)
Follows every stream from its source tile port through the configured switchboxes,...
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::string describePort(Port port)
Names a port the way diagnostics do, e.g. "DMA:1".
NodeRef operator()(const WaitEdge &e) const
static ChildIteratorType child_begin(NodeRef n)
static NodeRef getEntryNode(const WaitGraph *g)
mapped_iterator< const WaitEdge *, ToNode > ChildIteratorType
static ChildIteratorType child_end(NodeRef n)
The BD chain a DMA channel runs, as one aie.dma_start, aie.dma or runtime task programs it.
llvm::SmallVector< mlir::Region * > regions
uint64_t passes
Times one start of the channel runs the chain: its repeat count plus one.
bool loops
Whether the BD chain runs forever.
size_t loopStart
The first of bds a looping chain returns to once it has run them all.
llvm::SmallVector< mlir::Block * > bds
A cycle of waits packet streams can deadlock in, given their routes.
llvm::SmallVector< Step, 4 > steps
@ Drain
waiting fills its receiver, and draining that waits on holding.
@ Link
waiting queues behind holding on the port both enter tile by.
@ Arbiter
waiting, or sharer it queues behind, needs the arbiter holding holds at tile; sharer enters by sharer...
One stream from a source tile port to a destination tile port, recovered from the physical switchbox ...
bool carries(std::optional< PacketInfoAttr > packet) const
Whether what a BD sends with header packet, or with none, goes on this stream.
int packetMask
The stream carries every id that agrees with packetID on these bits.
bool keepsPktHeader
The destination stores each packet's header along with its payload.
std::optional< int > packetID
The packet id carried, if any.
A tile port at the edge of the stream fabric.
A switchbox a stream passes: the port it enters by and, where it is packet switched there,...
A core, a DMA channel, or a tile's controller.
static Agent core(TileID tile)
static std::optional< Agent > at(const StreamEndpoint &endpoint, bool sending)
Each agent at a stream endpoint: the core, DMA channel or controller pushing data into (sending) or p...
static Agent channel(const TileDMAChannel &dma)
static Agent controller(TileID tile)
One DMA channel of one tile.