19#include "mlir/IR/Attributes.h"
20#include "mlir/Interfaces/DataLayoutInterfaces.h"
21#include "mlir/Pass/Pass.h"
22#include "llvm/ADT/EquivalenceClasses.h"
23#include "llvm/ADT/MapVector.h"
24#include "llvm/ADT/StringMap.h"
25#include "llvm/Support/Debug.h"
30#define DEBUG_TYPE "aie-objectfifo-allocate"
37#define GEN_PASS_DEF_AIEOBJECTFIFOALLOCATE
38#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
43struct AIEObjectFifoAllocatePass
44 :
public xilinx::AIE::impl::AIEObjectFifoAllocateBase<
45 AIEObjectFifoAllocatePass> {
49 OpBuilder builder{
static_cast<MLIRContext *
>(
nullptr)};
51 DenseMap<Value, Operation *> lastPlaced;
53 std::shared_ptr<PoolLocks> poolLocks;
56 SmallVector<Operation *> loweredFlows;
59 DenseMap<StringAttr, SmallVector<RouteOp>> sourcedFlows;
60 llvm::MapVector<ObjectFifoDmaEndpointOp, llvm::StringMap<PacketInfoAttr>>
64 llvm::MapVector<Operation *, Operation *> channelSharers;
65 DenseMap<Operation *, ShimDMAAllocationOp> allocationSharers;
66 DenseMap<Value, SmallVector<int64_t>> plannedMemory;
67 DenseMap<Operation *, SmallVector<Value>> bufferPlacements;
68 DenseMap<Operation *, int> channelAssignments;
69 DenseMap<Operation *, Value> localPools;
70 DenseMap<Operation *, SmallVector<Value>> poolUsers;
71 DenseMap<Operation *, SmallVector<Operation *>> lockUsers;
72 DenseMap<Operation *, Value> lockPlacements;
76 DenseMap<Operation *, SmallVector<DMAStartOp>> endpointStarts;
77 DenseMap<Operation *, SmallVector<AIEX::DMAConfigureTaskForOp>> endpointTasks;
82 DenseMap<std::tuple<int, int, int>, int64_t> fixedBDs;
85 DenseMap<std::tuple<int, int, int>, int64_t> taskBDs;
88 DenseMap<std::tuple<int, int, int>, int64_t> channelDemand;
91 DenseMap<Operation *, unsigned> structuralRank;
92 RouteEndpoint channelFailure;
95 ObjectFifoPoolOp bufferFailure;
96 size_t unplacedAtFailure = 0;
99 SmallVector<TileOp> spillTiles;
101 Operation *lockAccessFailure =
nullptr;
103 bool sameTile(Value a, Value b) {
106 auto first = cast<TileLike>(a.getDefiningOp());
107 auto second = cast<TileLike>(b.getDefiningOp());
108 auto ac = first.tryGetCol(), ar = first.tryGetRow();
109 auto bc = second.tryGetCol(), br = second.tryGetRow();
110 return ac && ar && bc && br && ac == bc && ar == br;
113 int64_t memoryUsed(Value tile, int64_t extraSize = 0,
int extraCount = 0) {
114 SmallVector<BufferAllocation> layout;
116 device.getTargetModel().getMemTileLoadStoreBusWidth() / 8;
120 for (Operation &op : *device.getBody()) {
121 if (
auto placed = dyn_cast<TileLike>(op);
122 placed && sameTile(tile, placed->getResult(0))) {
123 Value value = placed->getResult(0);
124 for (int64_t size : plannedMemory[value])
125 layout.push_back({size, alignment, std::nullopt});
127 for (
int i = 0; i < extraCount; ++i)
128 layout.push_back({extraSize, alignment, std::nullopt});
129 }
else if (
auto buffer = dyn_cast<BufferOp>(op);
130 buffer && sameTile(tile, buffer.getTile())) {
131 layout.push_back({buffer.getAllocationSize(),
132 buffer.getAligned() ? alignment : 1,
133 buffer.getAddress()});
139 LogicalResult collectFixedMemory() {
140 plannedMemory.clear();
141 for (
auto tile : device.getOps<TileLike>()) {
142 if (!tile.isMemTile())
144 int64_t bytes = memoryUsed(tile->getResult(0));
145 int64_t capacity = device.getTargetModel().getMemTileSize();
146 if (bytes > capacity)
147 return tile->emitOpError(
"existing buffers require ")
148 << bytes <<
" bytes, exceeding MemTile capacity of " << capacity
154 bool canAccess(Value user, Value memory,
bool localOnly =
false) {
157 auto userTile = dyn_cast<TileLike>(user.getDefiningOp());
158 auto memoryTile = dyn_cast<TileLike>(memory.getDefiningOp());
159 if (!userTile || !memoryTile)
161 const auto &target = device.getTargetModel();
162 auto positions = [&](TileLike tile) {
163 SmallVector<TileID> compatible;
164 auto col = tile.tryGetCol(), row = tile.tryGetRow();
165 for (
int c = 0; c < target.columns(); ++c) {
166 if (col && c != *col)
168 for (
int r = 0; r < target.rows(); ++r) {
169 if ((!row || r == *row) &&
170 target.getTileType(c, r) == tile.getTileType())
171 compatible.push_back({c, r});
178 auto userPositions = positions(userTile);
179 auto memoryPositions = positions(memoryTile);
180 for (
TileID u : userPositions)
181 for (
TileID m : memoryPositions) {
182 if (localOnly && u != m)
184 auto shared = target.getSharedMemory(u, m);
185 if (shared == AIETargetModel::SharedMemory::Second ||
186 shared == AIETargetModel::SharedMemory::Either)
192 bool canPlace(ObjectFifoPoolOp pool, Value tile, int64_t sizeBytes,
194 if (cast<TileLike>(tile.getDefiningOp()).isMemTile() &&
195 memoryUsed(tile, sizeBytes, count) >
196 device.getTargetModel().getMemTileSize())
198 return llvm::all_of(poolUsers[pool],
199 [&](Value user) {
return canAccess(user, tile); });
202 Value lockPlacement(ObjectFifoPoolOp pool, Operation *group) {
203 if (Value placed = lockPlacements.lookup(group))
205 Value local = localPools.lookup(pool);
206 return local ? local : pool.getTile();
210 static int bufferCount(ObjectFifoPoolOp pool) {
211 return pool.getContiguous() ? 1 : pool.getDepth();
213 static int64_t bufferBytes(ObjectFifoPoolOp pool) {
214 return pool.getObjectSizeInBytes() * pool.getObjectsPerBuffer();
220 LogicalResult planBuffers(ArrayRef<ObjectFifoPoolOp> pools) {
221 plannedMemory.clear();
222 bufferPlacements.clear();
223 bufferFailure =
nullptr;
224 for (
auto pool : pools) {
225 if (!localPools.contains(pool))
227 Value tile = localPools.lookup(pool);
228 int count = pool.getBuffers() ? 0 : bufferCount(pool);
229 int64_t size = bufferBytes(pool);
230 if (!canAccess(pool.getTile(), tile) ||
231 !canPlace(pool, tile, size, count)) {
232 bufferFailure = pool;
235 plannedMemory[tile].append(count, size);
237 SmallVector<ObjectFifoPoolOp> generated;
238 for (
auto pool : pools) {
239 if (pool.getBuffers()) {
240 for (
auto buffer : pool.getBufferOps()) {
241 Value tile = buffer.getBufferTile();
242 if (tile && ((localPools.contains(pool) &&
243 !sameTile(tile, localPools.lookup(pool))) ||
244 !llvm::all_of(poolUsers[pool], [&](Value user) {
245 return canAccess(user, tile);
247 bufferFailure = pool;
250 bufferPlacements[pool].push_back(tile);
254 if (pool.getTileLike().isShimTile())
256 if (localPools.contains(pool))
257 bufferPlacements[pool].append(bufferCount(pool),
258 localPools.lookup(pool));
260 generated.append(bufferCount(pool), pool);
262 int retries = kSpillRetries;
263 return success(placeBuffers(generated, retries));
271 static constexpr int kSpillRetries = 256;
273 bool placeBuffers(ArrayRef<ObjectFifoPoolOp> buffers,
int &retries) {
276 ObjectFifoPoolOp pool = buffers.front();
277 int64_t size = bufferBytes(pool);
278 Value homeTile = pool.getTileLike()->getResult(0);
279 bool fitsHome = canPlace(pool, homeTile, size);
280 SmallVector<Value> candidates;
282 candidates.push_back(homeTile);
284 candidates = spillTargets(pool, size);
285 if (candidates.empty() &&
286 (!bufferFailure || buffers.size() < unplacedAtFailure)) {
287 bufferFailure = pool;
288 unplacedAtFailure = buffers.size();
290 auto tryTile = [&](Value tile) {
291 plannedMemory[tile].push_back(size);
292 bufferPlacements[pool].push_back(tile);
293 if (placeBuffers(buffers.drop_front(), retries))
295 plannedMemory[tile].pop_back();
296 bufferPlacements[pool].pop_back();
299 for (
auto [index, tile] : llvm::enumerate(candidates)) {
300 if (index > 0 && retries-- <= 0)
309 for (Value tile : spillTargets(pool, size)) {
327 SmallVector<Value> spillTargets(ObjectFifoPoolOp pool, int64_t sizeBytes) {
328 TileLike home = pool.getTileLike();
329 auto &target = device.getTargetModel();
330 if (!home.isMemTile())
333 auto homeOp = dyn_cast<TileOp>(home.getOperation());
338 SmallVector<TileOp> neighbors;
339 for (
int col : {homeOp.getCol() - 1, homeOp.getCol() + 1}) {
340 if (col < 0 || col >= target.columns()) {
343 int row = homeOp.getRow();
344 bool declared = llvm::any_of(device.getOps<TileOp>(), [&](TileOp t) {
345 return t.getCol() == col && t.getRow() == row;
348 TileOp::getOrCreate(builder, device, col, row, pool.getLoc());
350 spillTiles.push_back(neighbor);
353 if (shared == SharedMemory::Second || shared == SharedMemory::Either) {
354 neighbors.push_back(neighbor);
357 llvm::stable_sort(neighbors, [&](TileOp a, TileOp b) {
358 return memoryUsed(a.getResult()) < memoryUsed(b.getResult());
360 SmallVector<Value> fitting;
361 for (TileOp neighbor : neighbors) {
362 if (canPlace(pool, neighbor.getResult(), sizeBytes)) {
363 fitting.push_back(neighbor.getResult());
371 void setInsertionPointOn(Value tile) {
372 Operation *after = lastPlaced.lookup(tile);
374 after = tile.getDefiningOp();
377 builder.setInsertionPointAfter(after);
379 builder.setInsertionPointToStart(device.getBody());
383 void allocateBuffers(ObjectFifoPoolOp pool) {
384 if (pool.getBuffers()) {
387 TileLike home = pool.getTileLike();
388 if (home.isShimTile()) {
392 ArrayRef<Attribute> initValues;
393 if (
auto initAttr = pool.getInitValues()) {
394 initValues = initAttr->getValue();
396 int filled = initValues.size();
397 StringRef base = pool.getBaseName();
399 SmallVector<Attribute> names;
400 MemRefType type = pool.getElemType();
402 if (pool.getContiguous()) {
404 MemRefType::get({pool.getObjectsPerBuffer() * type.getNumElements()},
405 type.getElementType());
406 if (pool.getZeroObjects())
407 zeros = DenseElementsAttr::get(
408 RankedTensorType::get(type.getShape(), type.getElementType()),
409 builder.getZeroAttr(type.getElementType()));
412 for (
int i = 0; i < bufferCount(pool); i++) {
414 i < filled ? cast<ElementsAttr>(initValues[i]) : zeros;
415 std::string name = (base +
"__buf" + std::to_string(i)).str();
416 Value placement = bufferPlacements[pool][i];
417 setInsertionPointOn(placement);
418 lastPlaced[placement] = BufferOp::create(
419 builder, pool.getLoc(), type, placement, builder.getStringAttr(name),
422 names.push_back(FlatSymbolRefAttr::get(builder.getContext(), name));
424 pool.setBuffersAttr(builder.getArrayAttr(names));
427 LockOp createLock(ObjectFifoPoolOp pool, Value tile, StringRef name,
429 setInsertionPointOn(tile);
430 auto lock = LockOp::create(builder, pool.getLoc(), tile, value);
431 lastPlaced[tile] = lock;
432 lock->setAttr(SymbolTable::getSymbolAttrName(),
433 builder.getStringAttr(name));
437 LogicalResult planLocks(ArrayRef<ObjectFifoPoolOp> pools) {
439 lockAccessFailure =
nullptr;
440 lockPlacements.clear();
441 DenseMap<std::pair<int, int>, int64_t> used;
442 auto reserve = [&](Value tile, int64_t count) {
443 auto like = cast<TileLike>(tile.getDefiningOp());
444 auto col = like.tryGetCol(), row = like.tryGetRow();
447 if (used[{*col, *row}] + count >
448 device.getTargetModel().getNumLocks(*col, *row)) {
452 used[{*col, *row}] += count;
455 for (
auto lock : device.getOps<LockOp>())
456 if (failed(reserve(lock.getTile(), 1)))
459 SmallVector<TileLike> tiles(device.getOps<TileLike>());
460 llvm::stable_sort(tiles, [&](TileLike a, TileLike b) {
461 auto aRank = structuralRank.find(a), bRank = structuralRank.find(b);
462 if ((aRank == structuralRank.end()) != (bRank == structuralRank.end()))
463 return bRank == structuralRank.end();
464 return aRank != structuralRank.end() && aRank->second < bRank->second;
466 auto place = [&](ObjectFifoPoolOp pool, Operation *group,
int count) {
467 SmallVector<Value> candidates{lockPlacement(pool, group)};
468 if (!localPools.contains(pool)) {
469 llvm::append_range(candidates, bufferPlacements[pool]);
470 for (Operation *user : lockUsers[group])
471 candidates.push_back(user->getOperand(0));
472 for (
auto tile : tiles)
473 candidates.push_back(tile->getResult(0));
475 bool reachable =
false;
476 for (Value tile : candidates) {
477 if (!tile || !llvm::all_of(lockUsers[group], [&](Operation *user) {
478 return canAccessLocks(user, tile);
482 if (succeeded(reserve(tile, count))) {
483 lockPlacements[group] = tile;
488 if (!reachable && !lockUsers[group].empty()) {
490 lockAccessFailure = lockUsers[group].front();
494 for (
auto pool : pools) {
495 if (!poolLocks->needsLocks(pool))
497 if (device.getTargetModel().getTargetArch() == AIEArch::AIE1) {
498 if (!pool.getLocks() && failed(place(pool, pool, pool.getDepth())))
501 for (
auto segment : pool.getSegmentOps())
502 if (!(segment.getProduceLock() && segment.getConsumeLock()) &&
503 failed(place(pool, segment, 2)))
507 auto check = [&](
auto endpoint) {
508 for (Value tile : endpointLockTiles(endpoint)) {
509 if (!canAccessLocks(endpoint, tile)) {
510 lockAccessFailure = endpoint;
516 for (
auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>())
517 if (failed(check(endpoint)))
519 for (
auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>())
520 if (failed(check(endpoint)))
528 void allocateLocks(ObjectFifoPoolOp pool) {
529 if (!poolLocks->needsLocks(pool))
531 StringRef base = pool.getBaseName();
532 int depth = pool.getDepth();
533 auto initValues = pool.getInitValues();
534 int filled = initValues ? initValues->size() : 0;
535 int repeat = pool.getRepeatCount().value_or(1);
537 if (device.getTargetModel().getTargetArch() == AIEArch::AIE1) {
538 if (pool.getLocks()) {
541 SmallVector<Attribute> names;
542 for (
int i = 0; i < depth; i++) {
543 std::string name = (base +
"__lock" + std::to_string(i)).str();
544 createLock(pool, lockPlacement(pool, pool), name, i < filled ? 1 : 0);
545 names.push_back(FlatSymbolRefAttr::get(builder.getContext(), name));
547 pool.setLocksAttr(builder.getArrayAttr(names));
551 for (
auto [index, segment] : llvm::enumerate(pool.getSegmentOps())) {
552 if (segment.getProduceLock() && segment.getConsumeLock()) {
555 std::string produce = (base +
"__prodlock" + std::to_string(index)).str();
556 std::string consume = (base +
"__conslock" + std::to_string(index)).str();
557 Value tile = lockPlacement(pool, segment);
558 createLock(pool, tile, produce, (depth - filled) * repeat);
559 createLock(pool, tile, consume, filled * repeat);
560 segment.setProduceLockAttr(
561 FlatSymbolRefAttr::get(builder.getContext(), produce));
562 segment.setConsumeLockAttr(
563 FlatSymbolRefAttr::get(builder.getContext(), consume));
567 bool canAccessLocks(Operation *endpoint, Value tile) {
568 Value user = endpoint->getOperand(0);
570 if (isa<ObjectFifoDmaEndpointOp>(endpoint) &&
571 !cast<TileLike>(user.getDefiningOp()).isMemTile())
572 return canAccess(user, tile,
true);
573 return canAccess(user, tile);
576 template <
typename Endpo
intOp>
577 void collectLockUsers(EndpointOp endpoint) {
578 auto pool = endpoint.getPoolOp();
579 if (device.getTargetModel().getTargetArch() == AIEArch::AIE1) {
580 lockUsers[pool].push_back(endpoint);
583 for (
auto segment : endpoint.getSelectedSegments())
584 lockUsers[segment].push_back(endpoint);
589 template <
typename Endpo
intOp>
590 SmallVector<Value> endpointLockTiles(EndpointOp endpoint) {
591 SmallVector<Value> tiles;
592 auto pool = endpoint.getPoolOp();
593 if (pool.getDepth() == 0)
595 if (device.getTargetModel().getTargetArch() == AIEArch::AIE1) {
596 if (poolLocks->needsLocks(pool) && !pool.getLocks())
597 tiles.push_back(lockPlacement(pool, pool));
599 for (
auto lock : pool.getLockOps())
600 tiles.push_back(lock.getTile());
603 for (
auto segment : endpoint.getSelectedSegments()) {
604 if (poolLocks->needsLocks(pool) &&
605 !(segment.getProduceLock() && segment.getConsumeLock())) {
606 tiles.push_back(lockPlacement(pool, segment));
610 {segment.getProduceLockAttr(), segment.getConsumeLockAttr()})
613 tiles.push_back(lock.getTile());
621 bool reachesAdjacentTile(RouteEndpoint endpoint) {
622 if (
auto route = dyn_cast<RouteEndpointOp>(endpoint.getOperation()))
623 return programReachesAdjacentTile(route);
624 auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation());
625 if (!dma || !dma.getTileLike().isMemTile()) {
628 ObjectFifoPoolOp pool = dma.getPoolOp();
629 auto remote = [&](Value tile) {
630 return tile && !sameTile(tile, endpoint.getTile());
632 return pool && (llvm::any_of(bufferPlacements[pool], remote) ||
633 llvm::any_of(endpointLockTiles(dma), remote));
636 TileLike tileOf(RouteEndpoint endpoint) {
637 return dyn_cast<TileLike>(endpoint.getTile().getDefiningOp());
642 static bool isProgramOwned(RouteEndpoint endpoint) {
643 auto route = dyn_cast<RouteEndpointOp>(endpoint.getOperation());
644 return route && route.getBundle() == WireBundle::DMA &&
645 !route.getTileLike().isShimTile();
649 static SmallVector<Block *> chainOf(DMAStartOp start) {
650 SmallVector<Block *> chain;
651 SmallPtrSet<Block *, 8> seen;
652 SmallVector<Block *> work{start.getDest()};
653 while (!work.empty()) {
654 Block *block = work.pop_back_val();
655 if (!seen.insert(block).second || block->getOps<DMABDOp>().empty())
657 chain.push_back(block);
658 if (
auto next = dyn_cast<NextBDOp>(block->getTerminator()))
659 work.push_back(next.getDest());
664 static int64_t countBDs(DMAStartOp start) {
666 for (Block *block : chainOf(start))
667 bds += llvm::range_size(block->getOps<DMABDOp>());
671 static int64_t countBDs(Region &taskBody) {
673 taskBody.walk([&](DMABDOp) { bds++; });
678 int64_t endpointBDs(Operation *endpoint) {
680 for (DMAStartOp start : endpointStarts.lookup(endpoint))
681 bds += countBDs(start);
686 int64_t endpointTaskBDs(Operation *endpoint) {
688 for (
auto task : endpointTasks.lookup(endpoint))
689 largest = std::max(largest, countBDs(task.getBody()));
695 bool programReachesAdjacentTile(RouteEndpointOp endpoint) {
696 if (!endpoint.getTileLike().isMemTile())
698 auto remote = [&](Operation *op) {
700 if (
auto bd = dyn_cast<DMABDOp>(op)) {
702 dyn_cast_or_null<BufferOp>(bd.getBuffer().getDefiningOp()))
703 tile = buffer.getTile();
704 }
else if (
auto use = dyn_cast<UseLockOp>(op)) {
705 if (
auto lock = dyn_cast_or_null<LockOp>(use.getLock().getDefiningOp()))
706 tile = lock.getTile();
708 return tile && !sameTile(tile, endpoint.getTile());
710 for (DMAStartOp start : endpointStarts.lookup(endpoint))
711 for (Block *block : chainOf(start))
712 if (llvm::any_of(block->getOperations(),
713 [&](Operation &op) { return remote(&op); }))
715 for (
auto task : endpointTasks.lookup(endpoint)) {
716 bool reaches =
false;
717 task.getBody().walk([&](Operation *op) { reaches |= remote(op); });
726 void collectChannelPrograms() {
727 endpointStarts.clear();
728 endpointTasks.clear();
731 auto key = [](Value tile,
732 int channel) -> std::optional<std::tuple<int, int, int>> {
733 auto placed = cast<TileLike>(tile.getDefiningOp());
734 auto col = placed.tryGetCol(), row = placed.tryGetRow();
737 return std::make_tuple(*col, *row, channel);
739 for (
auto program : device.getOps<DmaBody>())
740 for (Block &block : program.getDmaBody())
741 for (
auto start : block.getOps<DMAStartOp>()) {
742 if (
auto endpoint = start.getEndpointOp()) {
743 endpointStarts[endpoint].push_back(start);
745 key(program.getTile(), start.getChannelIndex())) {
746 fixedBDs[*at] += countBDs(start);
749 device.walk([&](Operation *op) {
750 if (
auto task = dyn_cast<AIEX::DMAConfigureTaskForOp>(op)) {
751 if (
auto endpoint = dyn_cast_or_null<RouteEndpointOp>(
752 SymbolTable::lookupNearestSymbolFrom(
753 device, task.getAlloc().getRootReference())))
754 endpointTasks[endpoint].push_back(task);
755 }
else if (
auto task = dyn_cast<AIEX::DMAConfigureTaskOp>(op)) {
756 if (
auto at = key(task.getTile(), task.getChannel())) {
757 int64_t &largest = taskBDs[*at];
758 largest = std::max(largest, countBDs(task.getBody()));
768 void rankStructurally() {
769 StructuralGraph graph;
771 DenseMap<std::pair<int, int>, Operation *> placed;
772 DenseMap<Operation *, Operation *> tileNode;
773 for (
auto tile : device.getOps<TileLike>()) {
774 auto col = tile.tryGetCol(), row = tile.tryGetRow();
776 graph.
add(tileNode[tile] = tile);
779 auto [it, first] = placed.try_emplace({*col, *row}, tile);
780 tileNode[tile] = it->second;
782 graph.
nodes.push_back(tile);
783 llvm::raw_string_ostream(graph.
features[tile])
784 <<
"tile " << *col <<
' ' << *row <<
' '
785 << stringifyAIETileType(tile.getTileType());
788 auto link = [&](Operation *from, Operation *to, StringRef label) {
789 if (Operation *tile = tileNode.lookup(to))
791 graph.
link(from, to, label);
793 for (
auto pool : device.getOps<ObjectFifoPoolOp>()) {
794 llvm::raw_string_ostream os(graph.
add(pool));
795 for (
auto segment : pool.getSegmentOps())
796 os <<
" [" << segment.getOffset() <<
", " << segment.getSize() <<
"]";
797 link(pool, pool.getTile().getDefiningOp(),
"tile");
799 for (
auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>()) {
800 llvm::raw_string_ostream os(graph.
add(endpoint));
801 for (
auto segment : endpoint.getSelectedSegments())
802 os <<
" @" << segment.getOffset();
803 link(endpoint, endpoint.getTile().getDefiningOp(),
"tile");
804 link(endpoint, endpoint.getPoolOp(),
"pool");
806 for (
auto endpoint : device.getOps<RouteEndpoint>()) {
807 llvm::raw_string_ostream os(graph.
add(endpoint));
809 dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation())) {
810 for (
auto segment : dma.getSelectedSegments())
811 os <<
" @" << segment.getOffset();
812 link(endpoint, dma.getPoolOp(),
"pool");
814 link(endpoint, endpoint.getTile().getDefiningOp(),
"tile");
816 for (
auto route : device.getOps<RouteOp>()) {
818 for (StringRef source : route.getSourceNames())
819 link(route, lookupEndpoint(source),
"source");
820 for (StringRef dest : route.getDestinationNames())
821 link(route, lookupEndpoint(dest),
"dest");
823 DenseMap<Operation *, unsigned> rank = graph.
rank();
824 for (
auto [tile, node] : tileNode)
825 rank[tile] = rank[node];
826 structuralRank = std::move(rank);
832 int64_t bdRoom(TileLike tile,
int col,
int row,
int channel,
833 const DenseMap<std::tuple<int, int, int>, int64_t> &demand) {
834 const AIETargetModel &target = device.getTargetModel();
835 uint32_t numBDs = target.
getNumBDs(col, row);
836 int numChannels = std::max(tile.getNumSourceConnections(WireBundle::DMA),
837 tile.getNumDestConnections(WireBundle::DMA));
838 auto sharesBDs = [&](
int other) {
839 for (uint32_t bd = 0; bd < numBDs; ++bd)
846 for (
int other = 0; other < numChannels; ++other)
847 if (sharesBDs(other))
848 if (
auto it = demand.find({col, row, other}); it != demand.end())
857 int chooseChannel(DMAChannelAnalysis &channels, RouteEndpoint endpoint,
858 const DenseMap<std::tuple<int, int, int>, int64_t> &demand,
860 TileLike tile = tileOf(endpoint);
861 DMAChannelDir dir = endpoint.getRouteDirection();
862 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
865 endpoint.getOperation());
867 int64_t bestRoom = 0;
869 for (
int channel = 0; channel < limit; ++channel) {
872 int64_t room = bdRoom(tile, *col, *row, channel, demand);
873 if (best < 0 || room > bestRoom) {
881 endpoint.getOperation());
884 void noteChannelOwner(InFlightDiagnostic &diag, Operation *owner,
885 std::optional<int> channel = std::nullopt) {
886 auto ¬e = diag.attachNote(owner->getLoc());
887 if (
auto endpoint = dyn_cast<RouteEndpoint>(owner)) {
888 note <<
"DMA endpoint @" << cast<SymbolOpInterface>(owner).getName();
889 if (
auto fifo = owner->getAttrOfType<StringAttr>(
"fifoName"))
890 note <<
" for ObjectFifo @" << fifo.getValue();
891 if (reachesAdjacentTile(endpoint))
892 note <<
" requires adjacent MemTile access";
894 note <<
"; occupies channel " << *channel;
895 }
else if (channel) {
896 note <<
"pre-existing " << owner->getName() <<
" reserves DMA channel "
902 DenseSet<Operation *> packetEndpoints() {
903 DenseSet<Operation *> endpoints;
904 for (
auto flow : device.getOps<RouteOp>()) {
905 if (!flow.getPacket())
907 for (StringRef source : flow.getSourceNames())
908 endpoints.insert(lookupEndpoint(source).getOperation());
909 for (StringRef dest : flow.getDestinationNames())
910 endpoints.insert(lookupEndpoint(dest).getOperation());
917 LogicalResult checkStreamPorts() {
918 DMAChannelAnalysis channels(device);
919 DenseSet<Operation *> packetEndpoints = this->packetEndpoints();
921 for (
auto endpoint : device.getOps<RouteEndpoint>()) {
922 if (endpoint.getRouteBundle() != WireBundle::Core)
924 std::optional<int> channel = endpoint.getRouteChannel();
926 return endpoint->emitOpError(
"a stream port names its own channel");
928 tileOf(endpoint), {endpoint.getRouteDirection(), *channel},
929 endpoint.getOperation(),
930 packetEndpoints.contains(endpoint.getOperation())));
932 return failure(clash);
937 bool sharesPinnedChannel(RouteEndpoint endpoint, DMAChannelDir dir) {
940 auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation());
941 if (dir == DMAChannelDir::S2MM)
942 return dma && dma.getOutOfOrder();
943 if (!tileOf(endpoint).isShimTile() &&
944 !(dma && dma.getReleaseStrategy() == ObjectFifoRelease::PushQueue))
946 bool hasPacketRoute =
false;
947 for (
auto flow : device.getOps<RouteOp>()) {
948 if (llvm::none_of(flow.getSourceNames(), [&](StringRef name) {
949 return lookupEndpoint(name).getOperation() ==
950 endpoint.getOperation();
953 if (!flow.getPacket())
955 hasPacketRoute =
true;
957 return hasPacketRoute;
960 LogicalResult assignChannels(DMAChannelAnalysis &channels,
961 bool diagnose =
true) {
962 channelAssignments.clear();
963 channelFailure =
nullptr;
964 SmallVector<RouteEndpoint> pending;
967 DenseMap<std::tuple<int, int, int>, int64_t> demand = fixedBDs;
968 auto noteDemand = [&](RouteEndpoint endpoint,
int channel) {
969 TileLike tile = tileOf(endpoint);
970 auto col = tile.tryGetCol(), row = tile.tryGetRow();
974 if (
auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation()))
975 bds = dma.getNumBDs();
976 else if (isProgramOwned(endpoint))
977 bds = endpointBDs(endpoint.getOperation());
978 demand[{*col, *row, channel}] += bds;
980 for (
auto endpoint : device.getOps<RouteEndpoint>()) {
981 DMAChannelDir dir = endpoint.getRouteDirection();
982 std::optional<int> channel = endpoint.getRouteChannel();
985 if (endpoint.getRouteBundle() == WireBundle::Core ||
986 allocationSharers.count(endpoint.getOperation()) ||
987 channelSharers.count(endpoint.getOperation()))
991 if (reachesAdjacentTile(endpoint) &&
994 channelFailure = endpoint;
997 return endpoint->emitOpError(
"pinned ")
998 << stringifyDMAChannelDir(dir) <<
" DMA channel " << *channel
999 <<
" cannot access adjacent MemTile buffers or locks";
1002 tileOf(endpoint), dir, *channel, endpoint.getOperation(),
1003 sharesPinnedChannel(endpoint, dir)) < 0) {
1006 auto diag = endpoint->emitOpError(
"pinned ");
1007 diag << stringifyDMAChannelDir(dir) <<
" DMA channel " << *channel
1008 <<
" is out of range or already in use on this tile";
1009 if (Operation *owner =
1011 noteChannelOwner(diag, owner, channel);
1014 noteDemand(endpoint, *channel);
1017 pending.push_back(endpoint);
1025 DenseMap<Operation *, int64_t> programBDs;
1026 for (
auto endpoint : pending)
1027 if (isProgramOwned(endpoint))
1028 programBDs[endpoint.getOperation()] =
1029 endpointBDs(endpoint.getOperation());
1030 llvm::stable_sort(pending, [&](RouteEndpoint a, RouteEndpoint b) {
1031 bool aAdjacent = reachesAdjacentTile(a);
1032 bool bAdjacent = reachesAdjacentTile(b);
1033 if (aAdjacent != bAdjacent)
1035 bool aProgram = isProgramOwned(a), bProgram = isProgramOwned(b);
1036 if (aProgram != bProgram)
1038 int64_t aBDs = programBDs.lookup(a.getOperation());
1039 int64_t bBDs = programBDs.lookup(b.getOperation());
1042 return structuralRank.lookup(a.getOperation()) <
1043 structuralRank.lookup(b.getOperation());
1046 for (
auto endpoint : pending) {
1047 DMAChannelDir dir = endpoint.getRouteDirection();
1048 TileLike tile = tileOf(endpoint);
1049 bool adjacent = reachesAdjacentTile(endpoint);
1053 auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation());
1054 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
1055 if (dma && col && row) {
1057 for (
int free = 0; free < limit; ++free) {
1060 roomy = bdRoom(tile, *col, *row, free, demand) >= dma.getNumBDs();
1064 int channel = isProgramOwned(endpoint) || !roomy
1065 ? chooseChannel(channels, endpoint, demand, adjacent)
1067 endpoint.getOperation());
1069 channelFailure = endpoint;
1075 tile.emitOpError(dir == DMAChannelDir::MM2S
1076 ?
"number of output DMA channel exceeded!"
1077 :
"number of input DMA channel exceeded!");
1078 diag <<
" requires at least " << capacity + 1 <<
" "
1079 << stringifyDMAChannelDir(dir) <<
" channels, but capacity is "
1082 diag <<
" for adjacent MemTile access";
1084 for (
int i = 0; i < capacity; ++i)
1086 noteChannelOwner(diag, owner, i);
1087 noteChannelOwner(diag, endpoint.getOperation());
1090 channelAssignments[endpoint.getOperation()] = channel;
1091 noteDemand(endpoint, channel);
1093 DenseSet<Operation *> claimedAllocations;
1094 for (
auto [endpoint, alloc] : allocationSharers) {
1095 auto end = cast<RouteEndpoint>(endpoint);
1096 int channel = alloc.getChannelIndex();
1097 if (claimedAllocations.insert(alloc).second &&
1099 channel, endpoint,
true) < 0) {
1100 channelFailure = end;
1103 auto diag = endpoint->emitOpError(
"cannot share ");
1104 diag << stringifyDMAChannelDir(alloc.getChannelDir()) <<
" DMA channel "
1105 << channel <<
": already in use or unavailable";
1107 tileOf(end), alloc.getChannelDir(), channel))
1108 noteChannelOwner(diag, owner, channel);
1111 channelAssignments[endpoint] = channel;
1112 noteDemand(end, channel);
1114 for (
auto [sharer, owner] : channelSharers)
1115 channelAssignments[sharer] = channelAssignments.lookup(owner);
1118 for (
auto &[at, bds] : taskBDs)
1120 for (
auto endpoint : device.getOps<RouteEndpoint>()) {
1121 TileLike tile = tileOf(endpoint);
1122 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
1123 std::optional<int> channel = endpoint.getRouteChannel();
1124 if (
auto it = channelAssignments.find(endpoint.getOperation());
1125 it != channelAssignments.end())
1126 channel = it->second;
1127 if (isProgramOwned(endpoint) && channel && col && row)
1128 demand[{*col, *row, *channel}] +=
1129 endpointTaskBDs(endpoint.getOperation());
1131 channelDemand = std::move(demand);
1139 bool planShimSharing(TileLike exhausted, DMAChannelDir dir) {
1140 ShimTransferSpans spans(
1142 [&](StringAttr symbol) -> std::optional<ShimTransferSpans::End> {
1143 auto endpoint = SymbolTable::lookupNearestSymbolFrom<RouteEndpointOp>(
1145 if (!endpoint || endpoint.getBundle() != WireBundle::DMA ||
1146 !endpoint.getTileLike().isShimTile())
1147 return std::nullopt;
1148 ShimTransferSpans::End end{
1149 endpoint.getFifoNameAttr() ? endpoint.getFifoNameAttr()
1150 : endpoint.getSymNameAttr(),
1151 endpoint.getRouteDirection() == DMAChannelDir::MM2S};
1154 auto canShare = [&](RouteEndpointOp a, RouteEndpointOp b) {
1156 a.getFifoNameAttr() ? a.getFifoNameAttr() : a.getSymNameAttr(),
1157 b.getFifoNameAttr() ? b.getFifoNameAttr() : b.getSymNameAttr());
1159 SmallVector<Operation *> candidates;
1160 SmallVector<StringAttr> fifos;
1161 for (
auto endpoint : device.getOps<RouteEndpointOp>()) {
1162 if (endpoint.getBundle() != WireBundle::DMA ||
1163 endpoint.getChannelIndex() ||
1164 allocationSharers.count(endpoint.getOperation()) ||
1165 endpoint.getTile() != exhausted->getResult(0) ||
1166 endpoint.getRouteDirection() != dir)
1168 candidates.push_back(endpoint);
1169 fifos.push_back(endpoint.getFifoNameAttr() ? endpoint.getFifoNameAttr()
1170 : endpoint.getSymNameAttr());
1172 for (
auto alloc : device.getOps<ShimDMAAllocationOp>()) {
1173 auto allowed = AIEDialect::ShareableWithAttrHelper(alloc->getContext())
1175 if (!allowed || alloc.getTile() != exhausted->getResult(0) ||
1176 alloc.getChannelDir() != dir)
1178 if (llvm::any_of(device.getOps<RouteEndpoint>(), [&](RouteEndpoint end) {
1179 return end.getRouteBundle() == WireBundle::DMA &&
1180 sameTile(end.getTile(), alloc.getTile()) &&
1181 end.getRouteDirection() == dir &&
1182 end.getRouteChannel() == alloc.getChannelIndex();
1185 for (Operation *candidate : candidates) {
1186 auto endpoint = cast<RouteEndpointOp>(candidate);
1187 if (allocationSharers.count(candidate) ||
1188 channelSharers.count(candidate) ||
1189 llvm::none_of(allowed, [&](Attribute attr) {
1190 auto symbol = dyn_cast<FlatSymbolRefAttr>(attr);
1191 return symbol && symbol.getValue() == endpoint.getSymName();
1194 if (llvm::any_of(allocationSharers, [&](
const auto &entry) {
1195 if (entry.second != alloc)
1197 auto other = cast<RouteEndpointOp>(entry.first);
1198 return !canShare(endpoint, other);
1201 allocationSharers[candidate] = alloc;
1205 SmallVector<unsigned> order;
1206 for (
auto &group : spans.groups(fifos))
1207 llvm::append_range(order, group);
1208 SmallVector<SmallVector<unsigned>> groups;
1209 for (
unsigned candidate : order) {
1210 auto *group = llvm::find_if(groups, [&](ArrayRef<unsigned> members) {
1211 return llvm::all_of(members, [&](
unsigned member) {
1212 return canShare(cast<RouteEndpointOp>(candidates[candidate]),
1213 cast<RouteEndpointOp>(candidates[member]));
1216 if (group == groups.end())
1217 groups.push_back({candidate});
1219 group->push_back(candidate);
1221 for (
auto &members : groups)
1222 for (
unsigned sharer : llvm::drop_begin(members))
1224 .insert({candidates[sharer], candidates[members.front()]})
1238 bool canWalkWhole(ObjectFifoPoolOp pool,
1239 ArrayRef<ObjectFifoDmaEndpointOp> regrouped = {}) {
1240 const AIETargetModel &target = device.getTargetModel();
1242 pool ? dyn_cast<TileLike>(pool.getTile().getDefiningOp()) : TileLike();
1243 return tile && tile.isMemTile() && !pool.getContiguous() &&
1244 pool.getSegmentOps().size() == 1 && !pool.getRepeatCount() &&
1245 !pool.getBuffers() && pool.getDepth() > 1 &&
1247 llvm::all_of(device.getOps<ObjectFifoDmaEndpointOp>(),
1248 [&](ObjectFifoDmaEndpointOp end) {
1249 return end.getPoolOp() != pool ||
1250 (!end.getOutOfOrder() &&
1251 end.getReleaseStrategy() !=
1252 ObjectFifoRelease::PushQueue &&
1253 !end.getDispatch() &&
1255 llvm::is_contained(regrouped, end)));
1259 void walkDeepPoolsWhole() {
1260 const AIETargetModel &target = device.getTargetModel();
1261 llvm::MapVector<Value, SmallVector<ObjectFifoDmaEndpointOp>> byTile;
1262 for (
auto dma : device.getOps<ObjectFifoDmaEndpointOp>())
1263 if (dma.getTileLike() && dma.getTileLike().isMemTile())
1264 byTile[dma.getTile()].push_back(dma);
1265 for (SmallVector<ObjectFifoDmaEndpointOp> &ends :
1266 llvm::make_second_range(byTile)) {
1272 TileLike tileLike = ends.front().getTileLike();
1273 std::optional<int> col = tileLike.tryGetCol(), row = tileLike.tryGetRow();
1274 int64_t numBDs = target.
getNumBDs(AIETileType::MemTile);
1278 int64_t bds = 0, largest = 0;
1279 for (ObjectFifoDmaEndpointOp end : ends) {
1280 bds += end.getNumBDs();
1281 largest = std::max(largest, end.getNumBDs());
1283 return halved ? bds + largest <= numBDs + 1 : bds <= numBDs;
1285 SmallVector<ObjectFifoPoolOp> pools;
1286 for (ObjectFifoDmaEndpointOp end : ends) {
1287 ObjectFifoPoolOp pool = end.getPoolOp();
1288 if (!llvm::is_contained(pools, pool) && canWalkWhole(pool))
1289 pools.push_back(pool);
1291 llvm::stable_sort(pools, [&](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
1292 if (a.getDepth() != b.getDepth())
1293 return a.getDepth() > b.getDepth();
1294 return structuralRank.lookup(a) < structuralRank.lookup(b);
1296 for (ObjectFifoPoolOp pool : pools) {
1299 pool.setContiguous(
true);
1306 ObjectFifoDmaEndpointOp circuitPeer(ObjectFifoDmaEndpointOp endpoint) {
1307 ObjectFifoDmaEndpointOp peer;
1308 StringRef name = endpoint.getSymName();
1309 for (RouteOp route : device.getOps<RouteOp>()) {
1310 SmallVector<StringRef> sources = llvm::to_vector(route.getSourceNames());
1311 SmallVector<StringRef> dests =
1312 llvm::to_vector(route.getDestinationNames());
1313 bool source = llvm::is_contained(sources, name);
1314 if (!source && !llvm::is_contained(dests, name))
1316 if (peer || route.getPacket() || sources.size() != 1 || dests.size() != 1)
1318 RouteEndpoint other = lookupEndpoint(source ? dests[0] : sources[0]);
1319 peer = other ? dyn_cast<ObjectFifoDmaEndpointOp>(other.getOperation())
1320 : ObjectFifoDmaEndpointOp();
1329 ObjectFifoCoreEndpointOp coreEndOf(ObjectFifoDmaEndpointOp endpoint) {
1330 ObjectFifoCoreEndpointOp found;
1331 for (
auto core : device.getOps<ObjectFifoCoreEndpointOp>()) {
1332 if (core.getPool() != endpoint.getPool())
1334 if (found || core.drains() == endpoint.drains())
1341 static bool canTakeTurns(ObjectFifoDmaEndpointOp endpoint) {
1343 endpoint.getReleaseStrategy() != ObjectFifoRelease::PushQueue &&
1344 !endpoint.getChannelIndex() && !endpoint.getDispatch() &&
1345 !endpoint.getPacketAttr() && !endpoint.getIterCount() &&
1346 endpoint.getRepeat() == 1 && !endpoint.walksStream();
1352 static bool contiguousWalkersFit(ArrayRef<ObjectFifoDmaEndpointOp> ends,
1353 ArrayRef<unsigned> turns) {
1354 SmallVector<std::pair<int64_t, int64_t>> counts;
1355 for (
auto [i, constEnd] : llvm::enumerate(ends)) {
1356 ObjectFifoDmaEndpointOp end = constEnd;
1357 counts.push_back({llvm::count(turns, i), end.getPoolOp().getContiguous()
1359 : end.getNumDescriptors()});
1362 for (
auto [i, constEnd] : llvm::enumerate(ends)) {
1363 ObjectFifoDmaEndpointOp end = constEnd;
1364 if (end.getPoolOp().getContiguous() &&
1365 (end.walksStream() ||
1366 end.getPoolOp().getDepth() % (counts[i].first * passes)))
1373 void takeTurns(MutableArrayRef<ObjectFifoDmaEndpointOp> ends,
1374 ArrayRef<unsigned> order) {
1375 SmallVector<Attribute> names;
1376 for (
unsigned i : order)
1377 names.push_back(FlatSymbolRefAttr::get(ends[i].getSymNameAttr()));
1378 ArrayAttr turns = builder.getArrayAttr(names);
1379 Operation *owner = ends[order.front()];
1380 channelSharers.erase(owner);
1381 for (ObjectFifoDmaEndpointOp end : ends) {
1382 end.setTurnsAttr(turns);
1384 channelSharers[end] = owner;
1392 bool planTurnSharing(TileLike exhausted, DMAChannelDir dir) {
1397 DenseMap<Operation *, ObjectFifoCoreEndpointOp> coreEndCache;
1398 DenseMap<Operation *, ObjectFifoDmaEndpointOp> circuitPeerCache;
1399 for (
auto dma : device.getOps<ObjectFifoDmaEndpointOp>()) {
1400 coreEndCache[dma] = coreEndOf(dma);
1401 circuitPeerCache[dma] = circuitPeer(dma);
1403 auto cachedCoreEnd = [&](ObjectFifoDmaEndpointOp dma) {
1404 return coreEndCache.lookup(dma);
1406 auto cachedCircuitPeer = [&](ObjectFifoDmaEndpointOp dma) {
1407 return circuitPeerCache.lookup(dma);
1409 SmallVector<ObjectFifoDmaEndpointOp> candidates;
1410 for (
auto dma : device.getOps<ObjectFifoDmaEndpointOp>())
1411 if (sameTile(dma.getTile(), exhausted->getResult(0)) &&
1412 dma.getRouteDirection() == dir && cachedCoreEnd(dma) &&
1413 canTakeTurns(dma.getTurns() ?
turnEndpoints(dma).front() : dma) &&
1414 canTakeTurns(cachedCircuitPeer(dma)))
1415 candidates.push_back(dma);
1416 llvm::stable_sort(candidates, [&](Operation *a, Operation *b) {
1417 return structuralRank.lookup(a) < structuralRank.lookup(b);
1420 llvm::dbgs() <<
"turns on " << exhausted->getLoc() <<
": "
1421 << candidates.size() <<
" candidate ends\n";
1422 for (
auto dma : device.getOps<ObjectFifoDmaEndpointOp>())
1423 if (sameTile(dma.getTile(), exhausted->getResult(0)) &&
1424 dma.getRouteDirection() == dir)
1425 llvm::dbgs() <<
" " << dma.getSymName() <<
": core end "
1426 << bool(cachedCoreEnd(dma)) <<
", takes turns "
1427 << canTakeTurns(dma) <<
", peer takes turns "
1428 << canTakeTurns(cachedCircuitPeer(dma)) <<
"\n";
1430 auto groupOf = [](ObjectFifoDmaEndpointOp end) {
1431 SmallVector<ObjectFifoDmaEndpointOp> group =
turnEndpoints(end);
1432 return group.empty() ? SmallVector<ObjectFifoDmaEndpointOp>{end} : group;
1434 for (
auto [i, a] : llvm::enumerate(candidates)) {
1435 for (ObjectFifoDmaEndpointOp b : llvm::drop_begin(candidates, i + 1)) {
1436 SmallVector<ObjectFifoDmaEndpointOp> group = groupOf(a);
1437 if (llvm::is_contained(group, b))
1439 llvm::append_range(group, groupOf(b));
1440 SmallVector<ObjectFifoCoreEndpointOp> cores;
1441 SmallVector<ObjectFifoDmaEndpointOp> peers;
1442 SmallVector<int64_t> depths;
1443 for (ObjectFifoDmaEndpointOp end : group) {
1444 cores.push_back(cachedCoreEnd(end));
1445 peers.push_back(cachedCircuitPeer(end));
1446 depths.push_back(end.getPoolOp().getDepth());
1448 if (llvm::any_of(peers, [&](ObjectFifoDmaEndpointOp peer) {
1449 return !sameTile(peer.getTile(), peers.front().getTile()) ||
1451 return !llvm::is_contained(peers, other);
1454 LLVM_DEBUG(llvm::dbgs()
1455 <<
" " << a.getSymName() <<
" + " << b.getSymName()
1456 <<
": senders on different tiles\n");
1461 if (succeeded(turns.
read(cores, reason)) && !turns.
keepsUp(depths))
1462 reason =
"would stall";
1465 bool overflows =
false;
1466 if (reason.empty()) {
1467 SmallVector<std::pair<int64_t, int64_t>> counts;
1468 for (
auto [i, constPeer] : llvm::enumerate(peers)) {
1469 ObjectFifoDmaEndpointOp peer = constPeer;
1470 counts.push_back({llvm::count(turns.
turns(), i),
1471 peer.getPoolOp().getContiguous()
1473 : peer.getNumDescriptors()});
1475 TileLike tile = peers.front().getTileLike();
1476 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
1480 device.getTargetModel().getNumBDsForChannel(*col, *row, 0);
1486 SmallVector<ObjectFifoPoolOp> walked;
1487 if (reason.empty() &&
1488 (overflows || !contiguousWalkersFit(peers, turns.
turns())))
1489 for (ObjectFifoDmaEndpointOp peer : peers) {
1490 ObjectFifoPoolOp pool = peer.getPoolOp();
1491 SmallVector<ObjectFifoDmaEndpointOp> others;
1492 for (
auto end : device.getOps<ObjectFifoDmaEndpointOp>())
1493 if (end.getPoolOp() == pool && end.getTurns() &&
1494 !llvm::is_contained(peers, end))
1495 others.push_back(end);
1496 SmallVector<ObjectFifoDmaEndpointOp> ignored(peers);
1497 llvm::append_range(ignored, others);
1498 if (!canWalkWhole(pool, ignored)) {
1499 LLVM_DEBUG(llvm::dbgs() <<
" " << pool.getSymName()
1500 <<
" cannot be walked whole\n");
1503 pool.setContiguous(
true);
1505 llvm::all_of(others, [&](ObjectFifoDmaEndpointOp end) {
1506 SmallVector<ObjectFifoDmaEndpointOp> ends =
1508 SmallVector<unsigned> order;
1510 end.getTurnsAttr().getAsRange<FlatSymbolRefAttr>())
1513 [&](ObjectFifoDmaEndpointOp e) {
1514 return e.getSymNameAttr() ==
1518 return contiguousWalkersFit(ends, order);
1522 <<
" " << pool.getSymName() <<
" walked whole"
1523 << (linedUp ?
"" :
", but its other turns would not line up")
1526 walked.push_back(pool);
1528 pool.setContiguous(
false);
1530 if (reason.empty() && (!contiguousWalkersFit(group, turns.
turns()) ||
1531 !contiguousWalkersFit(peers, turns.
turns()))) {
1532 reason =
"a contiguous pool's objects do not divide among its turns";
1533 for (ObjectFifoPoolOp pool : walked)
1534 pool.setContiguous(
false);
1536 if (!reason.empty()) {
1537 LLVM_DEBUG(llvm::dbgs() <<
" " << a.getSymName() <<
" + "
1538 << b.getSymName() <<
": " << reason <<
"\n");
1541 takeTurns(group, turns.
turns());
1542 takeTurns(peers, turns.
turns());
1553 LogicalResult headShimSharingRoutes() {
1554 DenseMap<Operation *, Operation *> channelOf;
1555 for (
auto [sharer, owner] : channelSharers) {
1556 if (!tileOf(cast<RouteEndpoint>(owner)).isShimTile())
1558 channelOf[sharer] = owner;
1559 channelOf[owner] = owner;
1561 for (
auto [endpoint, alloc] : allocationSharers)
1562 channelOf[endpoint] = alloc.getOperation();
1563 auto channelsOf = [&](RouteOp route) {
1564 SmallVector<Operation *> channels;
1565 auto note = [&](StringRef name) {
1566 if (
auto endpoint = lookupEndpoint(name);
1567 endpoint && channelOf.count(endpoint.getOperation()))
1568 channels.push_back(channelOf.lookup(endpoint.getOperation()));
1570 llvm::for_each(route.getSourceNames(), note);
1571 llvm::for_each(route.getDestinationNames(), note);
1574 DenseMap<Operation *, DenseSet<int>> idsOn;
1575 for (
auto route : device.getOps<RouteOp>())
1576 if (PacketInfoAttr packet = route.getPacketAttr();
1577 packet && packet.isAssigned())
1578 for (Operation *channel : channelsOf(route))
1579 idsOn[channel].insert(packet.assignedId());
1580 PacketIdSpace ids(device);
1581 SmallVector<int> reused;
1582 for (
auto route : device.getOps<RouteOp>()) {
1583 SmallVector<Operation *> channels = channelsOf(route);
1584 if (route.getPacket() || channels.empty())
1586 auto freeOnAll = [&](
int id) {
1587 return llvm::none_of(channels, [&](Operation *channel) {
1588 return idsOn[channel].contains(
id);
1591 std::optional<int> id;
1592 if (
auto *it = llvm::find_if(reused, freeOnAll); it != reused.end())
1595 reused.push_back(*
id);
1597 return route.emitOpError(
"shares a shim channel, but no packet id is "
1598 "left to tell its transfers apart");
1599 for (Operation *channel : channels)
1600 idsOn[channel].insert(*
id);
1601 route.setPacketAttr(PacketInfoAttr::get(builder.getContext(), 0,
1602 static_cast<uint16_t
>(*
id)));
1610 void warnOvercommittedBDs() {
1611 const AIETargetModel &target = device.getTargetModel();
1612 for (
auto endpoint : device.getOps<RouteEndpoint>()) {
1613 auto it = channelAssignments.find(endpoint.getOperation());
1614 TileLike tile = tileOf(endpoint);
1615 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
1616 if (it == channelAssignments.end() || !isProgramOwned(endpoint) || !col ||
1619 int channel = it->second;
1620 int64_t room = bdRoom(tile, *col, *row, channel, channelDemand);
1624 endpoint->emitWarning(
"channel ")
1625 << channel <<
" can use " << numBDs
1626 <<
" BDs, but it and the channels sharing them need " << numBDs - room
1627 <<
" at once; BD id assignment fails unless tasks free their BDs "
1628 "before others start";
1639 LogicalResult planAllocation(ArrayRef<ObjectFifoPoolOp> pools,
1640 std::set<std::vector<unsigned>> &tried) {
1641 std::vector<unsigned> key;
1642 for (
auto [index, pool] : llvm::enumerate(pools)) {
1643 if (
auto tile = localPools.lookup(pool)) {
1644 auto placed = cast<TileLike>(tile.getDefiningOp());
1645 auto col = placed.tryGetCol(), row = placed.tryGetRow();
1646 assert(col && row &&
"locality repair requires resolved coordinates");
1647 key.push_back(index);
1648 key.push_back(*col);
1649 key.push_back(*row);
1652 if (tried.size() >= 64 || !tried.insert(key).second ||
1653 failed(planBuffers(pools)) || failed(planLocks(pools)))
1655 DMAChannelAnalysis channels(device);
1656 if (succeeded(assignChannels(channels,
false)))
1658 if (!channelFailure || !tileOf(channelFailure).isMemTile() ||
1659 !tileOf(channelFailure).tryGetCol() ||
1660 !tileOf(channelFailure).tryGetRow())
1663 Value localTile = channelFailure.getTile();
1664 SmallVector<ObjectFifoPoolOp> candidates;
1665 for (
auto dma : device.getOps<ObjectFifoDmaEndpointOp>()) {
1666 auto endpoint = cast<RouteEndpoint>(dma.getOperation());
1667 auto pool = dma.getPoolOp();
1668 if (!pool || localPools.contains(pool) ||
1669 !sameTile(endpoint.getTile(), channelFailure.getTile()) ||
1670 endpoint.getRouteDirection() != channelFailure.getRouteDirection() ||
1671 (channelFailure.getRouteChannel() && endpoint != channelFailure) ||
1672 (endpoint.getRouteChannel() && endpoint != channelFailure) ||
1673 !reachesAdjacentTile(endpoint) ||
1674 llvm::is_contained(candidates, pool))
1676 candidates.push_back(pool);
1678 llvm::stable_sort(candidates, [&](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
1680 a.getBuffers() ? 0 : a.getObjectSizeInBytes() * a.getDepth();
1682 b.getBuffers() ? 0 : b.getObjectSizeInBytes() * b.getDepth();
1683 if (aBytes != bBytes)
1684 return aBytes < bBytes;
1685 return structuralRank.lookup(a) < structuralRank.lookup(b);
1687 for (
auto pool : candidates) {
1688 localPools[pool] = localTile;
1689 if (succeeded(planAllocation(pools, tried)))
1691 localPools.erase(pool);
1700 LogicalResult lowerPacketFlow(RouteOp flow, ArrayRef<RouteEndpoint> sources,
1701 PacketInfoAttr header) {
1702 int packetID = header.assignedId();
1703 for (
auto [name, endpoint] : llvm::zip_equal(
1704 flow.getSources().getAsRange<FlatSymbolRefAttr>(), sources)) {
1705 RouteEndpoint source = endpoint;
1707 if (
auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(*source);
1708 dma && dma.getDispatch()) {
1709 dispatchHeaders[dma][*flow.getDestinationNames().begin()] = header;
1712 if (sourcedFlows.at(name.getAttr()).size() == 1)
1713 source.setRoutePacket(header);
1718 bool keepHeader = flow.getKeepPktHeader();
1719 for (StringRef destName : flow.getDestinationNames())
1720 if (
auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(
1721 lookupEndpoint(destName).getOperation()))
1722 keepHeader |= dma.getOutOfOrder();
1724 builder.setInsertionPoint(flow);
1725 auto packetFlow = PacketFlowOp::create(
1726 builder, flow.getLoc(),
1727 builder.getIntegerAttr(builder.getI8Type(), packetID),
1728 keepHeader ? builder.getBoolAttr(
true) :
nullptr,
nullptr);
1729 OpBuilder::InsertionGuard g(builder);
1730 Block &ports = packetFlow.getRegion().emplaceBlock();
1731 builder.setInsertionPointToStart(&ports);
1732 EndOp::create(builder, flow.getLoc());
1734 builder.setInsertionPointToStart(&ports);
1735 for (RouteEndpoint source : sources) {
1736 PacketSourceOp::create(builder, flow.getLoc(), source.getTile(),
1737 source.getRouteBundle(), channelOf(source));
1739 for (StringRef destName : flow.getDestinationNames()) {
1740 auto dest = lookupEndpoint(destName);
1743 if (
auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(dest.getOperation()))
1744 if (dma.getOutOfOrder())
1745 dest.setRoutePacket(header);
1746 PacketDestOp::create(builder, flow.getLoc(), dest.getTile(),
1747 dest.getRouteBundle(), channelOf(dest));
1753 int channelOf(RouteEndpoint endpoint) {
1754 std::optional<int> channel = endpoint.getRouteChannel();
1755 assert(channel &&
"channels are assigned before flows are lowered");
1759 RouteEndpoint lookupEndpoint(StringRef name) {
1760 return dyn_cast_or_null<RouteEndpoint>(SymbolTable::lookupNearestSymbolFrom(
1761 device, StringAttr::get(device.getContext(), name)));
1764 LogicalResult lowerFlows() {
1767 DenseMap<std::tuple<Value, int, int>, Operation *> pushedPacketSources;
1768 for (RouteOp flow : device.getOps<RouteOp>()) {
1769 auto packet = flow.getPacket();
1770 if (!packet || !packet->isAssigned())
1772 for (StringRef name : flow.getSourceNames()) {
1773 auto source = dyn_cast<ObjectFifoDmaEndpointOp>(
1774 lookupEndpoint(name).getOperation());
1776 source.getReleaseStrategy() != ObjectFifoRelease::PushQueue)
1778 auto [it, inserted] = pushedPacketSources.try_emplace(
1779 std::make_tuple(source.getTile(), channelOf(source),
1780 packet->assignedId()),
1781 source.getOperation());
1782 if (!inserted && it->second != source.getOperation()) {
1783 auto diag = source.emitOpError(
1784 "core-pushed senders sharing an MM2S channel require distinct "
1786 diag.attachNote(it->second->getLoc()) <<
"other sender is here";
1791 DenseSet<std::tuple<Value, int, Value, int>> circuits;
1792 for (RouteOp flow : device.getOps<RouteOp>())
1793 for (
auto name : flow.getSources().getAsRange<FlatSymbolRefAttr>())
1794 sourcedFlows[name.getAttr()].push_back(flow);
1795 for (
auto flow : device.getOps<RouteOp>()) {
1796 SmallVector<RouteEndpoint> sources;
1797 for (StringRef name : flow.getSourceNames()) {
1798 sources.push_back(lookupEndpoint(name));
1800 loweredFlows.push_back(flow);
1804 if (std::optional<PacketInfoAttr> packet = flow.getPacket()) {
1805 if (!packet->isAssigned()) {
1806 return flow.emitOpError(
"has a packet header with no pkt_id; run "
1807 "--aie-assign-packet-ids before this pass");
1809 if (failed(lowerPacketFlow(flow, sources, *packet))) {
1816 RouteEndpoint source = sources.front();
1818 int sourceChannel = channelOf(source);
1819 builder.setInsertionPoint(flow);
1820 for (StringRef destName : flow.getDestinationNames()) {
1821 auto dest = lookupEndpoint(destName);
1824 .insert({source.getTile(), sourceChannel, dest.getTile(),
1828 FlowOp::create(builder, flow.getLoc(), source.getTile(),
1829 source.getRouteBundle(), sourceChannel, dest.getTile(),
1830 dest.getRouteBundle(), channelOf(dest));
1833 for (
auto &[endpoint, byDest] : dispatchHeaders) {
1834 SmallVector<Attribute> headers;
1835 for (StringRef dest :
1836 endpoint.getDispatchAttr().getAsValueRange<FlatSymbolRefAttr>())
1837 headers.push_back(byDest.lookup(dest));
1838 endpoint.setDispatchPacketsAttr(builder.getArrayAttr(headers));
1846 LogicalResult bindRearmTargets() {
1847 if (poolLocks->rearms().empty()) {
1851 builder.setInsertionPoint(device.getBody()->getTerminator());
1852 for (
auto &[fifoName, users] : poolLocks->rearms()) {
1853 SmallVector<Value> channelTiles, lockValues;
1854 SmallVector<int32_t> channelDirs, channelIndices, lockInits;
1856 SmallVector<Location> locs;
1858 for (
auto endpoint : device.getOps<RouteEndpoint>()) {
1859 std::optional<int> channel = endpoint.getRouteChannel();
1860 if (endpoint.getFifoName() != fifoName ||
1861 tileOf(endpoint).isShimTile() || !channel) {
1865 dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation());
1866 dma && dma.getReleaseStrategy() == ObjectFifoRelease::PushQueue) {
1867 for (AIEX::DmaChannelResetForOp user : users)
1869 "does not support core-pushed DMA channels; reset requires "
1870 "coordination with the issuing core and all shared senders");
1873 channelTiles.push_back(endpoint.getTile());
1874 channelDirs.push_back(
1875 static_cast<int32_t
>(endpoint.getRouteDirection()));
1876 channelIndices.push_back(*channel);
1877 locs.push_back(endpoint->getLoc());
1879 for (
auto pool : device.getOps<ObjectFifoPoolOp>()) {
1880 if (pool.getFifoName() != fifoName || pool.getTileLike().isShimTile()) {
1883 locs.push_back(pool.getLoc());
1884 for (LockOp lock : pool.getLockOps()) {
1885 lockValues.push_back(lock.getResult());
1886 lockInits.push_back(lock.getInit().value_or(0));
1890 if (channelTiles.empty() && lockValues.empty()) {
1891 for (AIEX::DmaChannelResetForOp user : users) {
1892 user.emitOpError() <<
"objectFIFO '" << fifoName
1893 <<
"' has no resident core/mem DMA channels or "
1899 std::string name = (fifoName +
"__rearm").str();
1902 name = (fifoName +
"__rearm" + std::to_string(suffix + 1)).str();
1906 ObjectFifoRearmBindingOp::create(
1907 builder, FusedLoc::get(builder.getContext(), locs),
1908 builder.getStringAttr(name), ValueRange(channelTiles),
1909 ValueRange(lockValues), builder.getDenseI32ArrayAttr(channelDirs),
1910 builder.getDenseI32ArrayAttr(channelIndices),
1911 builder.getDenseI32ArrayAttr(lockInits),
1912 DenseI32ArrayAttr(),
1913 DenseI32ArrayAttr());
1914 auto target = FlatSymbolRefAttr::get(builder.getContext(), name);
1915 for (AIEX::DmaChannelResetForOp user : users) {
1916 user.setObjfifoAttr(target);
1923 LogicalResult resolveStart(DMAStartOp start, RouteEndpointOp endpoint,
1924 ArrayRef<RouteOp> flows, ArrayRef<int> ids,
1926 DMAChannelDir dir = endpoint.getRouteDirection();
1927 if (start.getChannelDir() != dir)
1928 return start.emitOpError(
"starts ")
1929 << stringifyDMAChannelDir(start.getChannelDir()) <<
" on "
1930 << start.getEndpoint() <<
", but its route makes it "
1931 << stringifyDMAChannelDir(dir);
1934 bool packet = !flows.empty() && RouteOp(flows.front()).getPacket();
1936 SmallVector<DMABDOp> bds;
1937 for (Block *block : chainOf(start))
1938 llvm::append_range(bds, block->getOps<DMABDOp>());
1939 if (failed(checkHeaders(endpoint, ids, bds)))
1942 start.setChannelAttr(builder.getI32IntegerAttr(channel));
1948 LogicalResult checkHeaders(RouteEndpointOp endpoint, ArrayRef<int> ids,
1949 ArrayRef<DMABDOp> bds) {
1950 for (DMABDOp bd : bds) {
1951 std::optional<int> id;
1952 if (
auto packet = bd.getPacket())
1953 id = packet->getPktId();
1954 else if (
auto ops = bd->getBlock()->getOps<DMABDPACKETOp>(); !ops.empty())
1955 id = (*ops.begin()).getPacketId();
1956 if (
id && llvm::is_contained(ids, *
id))
1958 return bd.emitOpError(
"sends on @")
1959 << endpoint.getSymName()
1960 <<
" without the packet header of one of its flows, ids " << ids;
1968 LogicalResult checkSharedSourceUsers(RouteEndpointOp endpoint,
1969 ArrayRef<int> ids) {
1970 auto uses = SymbolTable::getSymbolUses(endpoint, device);
1973 for (
const SymbolTable::SymbolUse &use : *uses) {
1974 Operation *user = use.getUser();
1975 if (
auto task = dyn_cast<AIEX::DMAConfigureTaskForOp>(user)) {
1976 SmallVector<DMABDOp> bds;
1977 task.getBody().walk([&](DMABDOp bd) { bds.push_back(bd); });
1978 if (failed(checkHeaders(endpoint, ids, bds)))
1982 if (
auto start = dyn_cast<AIEX::DMAStartBdChainForOp>(user)) {
1983 auto chain = device.lookupSymbol<BDChainOp>(start.getSymbol());
1985 return start.emitOpError(
"symbol does not reference valid BD chain");
1986 SmallVector<DMABDOp> bds;
1987 chain.getBody().walk([&](DMABDOp bd) { bds.push_back(bd); });
1988 if (failed(checkHeaders(endpoint, ids, bds)))
1992 auto memcpy = dyn_cast<AIEX::NpuDmaMemcpyNdOp>(user);
1995 std::optional<PacketInfoAttr> packet = memcpy.getPacket();
1996 if (packet && llvm::is_contained(ids, packet->getPktId()))
1998 return memcpy.emitOpError(
"sends on @")
1999 << endpoint.getSymName()
2000 <<
", a source several packet routes share, which has no one "
2001 "header to stamp; it needs a packet header of its own, one of "
2010 void resolveTask(AIEX::DMAConfigureTaskForOp task, RouteEndpointOp endpoint,
2012 builder.setInsertionPoint(task);
2013 auto configured = AIEX::DMAConfigureTaskOp::create(
2014 builder, task.getLoc(), builder.getIndexType(), endpoint.getTile(),
2015 DMAChannelDirAttr::get(builder.getContext(),
2016 endpoint.getRouteDirection()),
2017 builder.getI32IntegerAttr(channel),
2018 builder.getBoolAttr(task.getIssueToken()),
2019 builder.getI32IntegerAttr(task.getRepeatCount()),
2020 task.getRepeatCountVal(), endpoint.getPacketAttr());
2021 task.getResult().replaceAllUsesWith(configured.getResult());
2022 configured.getBody().takeBody(task.getBody());
2029 LogicalResult resolveEndpointUses() {
2030 for (
auto endpoint : device.getOps<RouteEndpointOp>()) {
2031 auto starts = endpointStarts.lookup(endpoint);
2032 auto tasks = endpointTasks.lookup(endpoint);
2033 auto sourced = sourcedFlows.find(endpoint.getSymNameAttr());
2034 ArrayRef<RouteOp> flows;
2035 if (sourced != sourcedFlows.end())
2036 flows = sourced->second;
2037 SmallVector<int> ids;
2038 for (RouteOp flow : flows)
2039 if (std::optional<PacketInfoAttr> packet = flow.getPacket();
2040 packet && packet->isAssigned())
2041 ids.push_back(packet->assignedId());
2042 bool shared = flows.size() > 1;
2043 if (shared && failed(checkSharedSourceUsers(endpoint, ids)))
2045 if (starts.empty() && tasks.empty())
2047 std::optional<int> channel = endpoint.getRouteChannel();
2048 assert(channel &&
"assignChannels gives every routed endpoint one");
2049 for (DMAStartOp start : starts)
2050 if (failed(resolveStart(start, endpoint, flows, ids, *channel)))
2052 if (endpoint.getTileLike().isShimTile() && endpoint.getFifoName())
2054 for (
auto task : tasks)
2055 resolveTask(task, endpoint, *channel);
2062 void emitShimAllocations() {
2063 builder.setInsertionPoint(device.getBody()->getTerminator());
2068 llvm::StringMap<unsigned> shimEndsSeen;
2069 for (
auto endpoint : device.getOps<RouteEndpoint>()) {
2070 std::optional<StringRef> fifoName = endpoint.getFifoName();
2072 if (
auto named = dyn_cast<RouteEndpointOp>(endpoint.getOperation()))
2073 fifoName = named.getSymName();
2074 std::optional<int> channel = endpoint.getRouteChannel();
2075 if (!tileOf(endpoint).isShimTile() || !fifoName || !channel) {
2078 std::string name = (*fifoName +
"__shim_alloc").str();
2079 unsigned ordinal = shimEndsSeen[*fifoName]++;
2083 create = !SymbolTable::lookupNearestSymbolFrom<ShimDMAAllocationOp>(
2084 device, builder.getStringAttr(name));
2086 for (
unsigned suffix = ordinal - 1; device.lookupSymbol(name);
2089 (*fifoName +
"__shim_alloc" + std::to_string(suffix + 1)).str();
2093 ShimDMAAllocationOp::create(
2094 builder, endpoint.getLoc(), builder.getStringAttr(name),
2096 DMAChannelDirAttr::get(builder.getContext(),
2097 endpoint.getRouteDirection()),
2098 builder.getI64IntegerAttr(*channel),
2099 builder.getBoolAttr(endpoint.getRouteBundle() == WireBundle::PLIO),
2100 endpoint->getAttrOfType<PacketInfoAttr>(
"packet"));
2103 (void)SymbolTable::replaceAllSymbolUses(
2104 endpoint, builder.getStringAttr(name), device);
2112 SmallVector<ObjectFifoPoolOp>
2113 demandOrderedPools(ArrayRef<ObjectFifoPoolOp> pools) {
2114 SmallVector<ObjectFifoPoolOp> ordered(pools);
2115 SmallVector<size_t> memTileSlots;
2116 SmallVector<Value> memTileHomes;
2117 DenseMap<Value, SmallVector<ObjectFifoPoolOp>> memTilePools;
2118 DenseMap<Operation *, std::pair<int, int>> poolChannelDemand;
2119 for (
auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>()) {
2120 ObjectFifoPoolOp pool = endpoint.getPoolOp();
2121 if (!sameTile(endpoint.getTile(), pool.getTile()))
2123 auto &demand = poolChannelDemand[pool];
2124 if (endpoint.getRouteDirection() == DMAChannelDir::S2MM)
2129 for (
auto [index, pool] : llvm::enumerate(ordered)) {
2130 if (pool.getTileLike().isMemTile()) {
2131 memTileSlots.push_back(index);
2132 Value home = pool.getTile();
2133 for (Value known : memTileHomes)
2134 if (sameTile(home, known)) {
2138 if (!memTilePools.count(home))
2139 memTileHomes.push_back(home);
2140 memTilePools[home].push_back(pool);
2143 DenseMap<Value, size_t> nextPool;
2144 auto demand = [&](ObjectFifoPoolOp pool) {
2145 auto [input, output] = poolChannelDemand.lookup(pool.getOperation());
2146 return std::max(input, output);
2148 for (Value home : memTileHomes) {
2149 auto &tilePools = memTilePools[home];
2150 llvm::stable_sort(tilePools, [&](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
2151 if (demand(a) != demand(b))
2152 return demand(a) > demand(b);
2153 return a.getObjectSizeInBytes() > b.getObjectSizeInBytes();
2156 for (
size_t slot : memTileSlots) {
2158 ObjectFifoPoolOp bestPool;
2159 for (Value home : memTileHomes) {
2160 auto &tilePools = memTilePools[home];
2161 size_t index = nextPool[home];
2162 if (index >= tilePools.size())
2164 ObjectFifoPoolOp pool = tilePools[index];
2166 pool.getObjectSizeInBytes() > bestPool.getObjectSizeInBytes() ||
2167 (pool.getObjectSizeInBytes() == bestPool.getObjectSizeInBytes() &&
2168 demand(pool) > demand(bestPool))) {
2173 ordered[slot] = bestPool;
2174 ++nextPool[bestHome];
2179 void runOnOperation()
override {
2180 device = getOperation();
2181 builder = OpBuilder(device.getContext());
2184 channelSharers.clear();
2185 allocationSharers.clear();
2187 loweredFlows.clear();
2188 sourcedFlows.clear();
2192 lockPlacements.clear();
2194 dispatchHeaders.clear();
2195 channelSharers.clear();
2196 plannedMemory.clear();
2197 bufferPlacements.clear();
2198 channelAssignments.clear();
2199 endpointStarts.clear();
2200 endpointTasks.clear();
2203 channelDemand.clear();
2204 structuralRank.clear();
2205 channelFailure =
nullptr;
2206 bufferFailure =
nullptr;
2207 unplacedAtFailure = 0;
2208 lockFailure =
nullptr;
2209 lockAccessFailure =
nullptr;
2211 if (failed(collectFixedMemory()) || failed(checkStreamPorts()))
2212 return signalPassFailure();
2213 collectChannelPrograms();
2215 walkDeepPoolsWhole();
2219 SmallVector<ObjectFifoPoolOp> pools(device.getOps<ObjectFifoPoolOp>());
2220 llvm::stable_sort(pools, [&](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
2221 return structuralRank.lookup(a) < structuralRank.lookup(b);
2223 SmallVector<size_t> memTileSlots;
2224 SmallVector<ObjectFifoPoolOp> memTilePools;
2225 for (
auto [index, pool] : llvm::enumerate(pools)) {
2226 if (pool.getTileLike().isMemTile()) {
2227 memTileSlots.push_back(index);
2228 memTilePools.push_back(pool);
2231 llvm::stable_sort(memTilePools, [](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
2232 return a.getObjectSizeInBytes() > b.getObjectSizeInBytes();
2234 for (
auto [slot, pool] : llvm::zip(memTileSlots, memTilePools)) {
2238 poolLocks = std::make_shared<PoolLocks>(device);
2239 for (
auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>()) {
2240 poolUsers[endpoint.getPoolOp()].push_back(endpoint.getTile());
2241 collectLockUsers(endpoint);
2243 for (
auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>()) {
2244 poolUsers[endpoint.getPoolOp()].push_back(endpoint.getTile());
2245 collectLockUsers(endpoint);
2247 for (
auto &[group, users] : lockUsers)
2248 llvm::stable_sort(users, [&](Operation *a, Operation *b) {
2249 return structuralRank.lookup(a) < structuralRank.lookup(b);
2252 std::set<std::vector<unsigned>> tried;
2253 LogicalResult allocated = planAllocation(pools, tried);
2254 if (failed(allocated)) {
2255 auto demandOrdered = demandOrderedPools(pools);
2256 if (!llvm::equal(pools, demandOrdered)) {
2260 allocated = planAllocation(demandOrdered, tried);
2266 while (failed(allocated) && channelFailure &&
2267 (tileOf(channelFailure).isShimTile()
2268 ? planShimSharing(tileOf(channelFailure),
2269 channelFailure.getRouteDirection())
2270 : planTurnSharing(tileOf(channelFailure),
2271 channelFailure.getRouteDirection()))) {
2274 allocated = planAllocation(pools, tried);
2276 if (succeeded(allocated) &&
2277 (!channelSharers.empty() || !allocationSharers.empty()) &&
2278 failed(headShimSharingRoutes()))
2279 return signalPassFailure();
2280 if (failed(allocated)) {
2282 if (failed(planBuffers(pools))) {
2283 bufferFailure.emitOpError(
2284 "could not place buffers in accessible memory with available "
2286 }
else if (failed(planLocks(pools))) {
2287 if (lockAccessFailure)
2288 lockAccessFailure->emitOpError(
"cannot access pool locks");
2290 lockFailure.getDefiningOp()->emitOpError(
2291 "could not place locks within tile lock capacity");
2293 DMAChannelAnalysis channels(device);
2294 (void)assignChannels(channels);
2296 "could not find a spill-aware allocation with size-first and "
2297 "demand-first packing and bounded local-pool retries; tile "
2298 "placement and buffer packing remain greedy");
2300 return signalPassFailure();
2302 for (ObjectFifoPoolOp pool : pools) {
2303 allocateBuffers(pool);
2304 allocateLocks(pool);
2306 for (TileOp tile : spillTiles)
2307 if (tile->use_empty())
2309 for (
auto endpoint : device.getOps<RouteEndpoint>())
2310 if (
auto it = channelAssignments.find(endpoint.getOperation());
2311 it != channelAssignments.end())
2312 endpoint.setRouteChannel(it->second);
2313 warnOvercommittedBDs();
2318 llvm::EquivalenceClasses<Operation *> matchingPools;
2319 DenseMap<Operation *, int64_t> descriptorCounts;
2320 for (
auto flow : device.getOps<RouteOp>()) {
2321 if (flow.getSources().size() != 1)
2323 ObjectFifoPoolOp first;
2324 for (
auto destName : flow.getDestinationNames()) {
2325 RouteEndpoint receiver = lookupEndpoint(destName);
2328 ? dyn_cast<ObjectFifoDmaEndpointOp>(receiver.getOperation())
2330 if (!dst || !dst.getOutOfOrder())
2332 ObjectFifoPoolOp pool = dst.getPoolOp();
2335 matchingPools.unionSets(first.getOperation(), pool.getOperation());
2336 descriptorCounts[pool] = dst.getNumDescriptors();
2339 int32_t nextTag = 0;
2340 DenseMap<Operation *, DenseI32ArrayAttr> tagsOfGroup;
2341 for (ObjectFifoPoolOp pool : pools) {
2342 auto member = matchingPools.findLeader(pool.getOperation());
2343 if (member == matchingPools.member_end())
2345 Operation *leader = *member;
2346 auto &tags = tagsOfGroup[leader];
2348 SmallVector<int32_t> values;
2349 for (int64_t slot = 0; slot < descriptorCounts.lookup(pool); ++slot)
2350 values.push_back(nextTag++);
2351 tags = builder.getDenseI32ArrayAttr(values);
2353 pool.setOutOfOrderTagsAttr(tags);
2358 for (
auto flow : device.getOps<RouteOp>()) {
2359 if (flow.getSources().size() != 1)
2361 RouteEndpoint sender = lookupEndpoint(*flow.getSourceNames().begin());
2363 ? dyn_cast<ObjectFifoDmaEndpointOp>(sender.getOperation())
2365 if (!src || !src.getOutOfOrder())
2367 for (
auto destName : flow.getDestinationNames()) {
2368 RouteEndpoint receiver = lookupEndpoint(destName);
2371 ? dyn_cast<ObjectFifoDmaEndpointOp>(receiver.getOperation())
2373 if (dst && dst.getOutOfOrder()) {
2374 src.setOutOfOrderTagsAttr(dst.getPoolOp().getOutOfOrderTagsAttr());
2380 if (failed(bindRearmTargets())) {
2381 return signalPassFailure();
2383 if (failed(lowerFlows()) || failed(resolveEndpointUses())) {
2384 return signalPassFailure();
2386 emitShimAllocations();
2387 for (Operation *flow : loweredFlows) {
2395std::unique_ptr<OperationPass<DeviceOp>>
2397 return std::make_unique<AIEObjectFifoAllocatePass>();
virtual bool isBdChannelAccessible(int col, int row, uint32_t bd_id, int channel) const =0
Return true iff buffer descriptor bd_id on tile (col, row) can be submitted on channel channel.
virtual uint32_t getNumBDs(AIETileType tileType) const =0
Return the number of buffer descriptors for a given tile type.
virtual uint32_t getMaxLockValue() const =0
Return the maximum value that can be stored in a lock register.
uint32_t getNumBDsForChannel(int col, int row, int channel) const
Return the number of buffer descriptors accessible on channel channel for the tile at (col,...
SharedMemory
Which of two tiles' memory modules both of them can address.
mlir::LogicalResult read(llvm::ArrayRef< ObjectFifoCoreEndpointOp > ends, std::string &reason)
Reads the order off the program of the core working through ends, all draining (the channel fills the...
bool keepsUp(llvm::ArrayRef< int64_t > depths)
Whether a channel moving objects in turns() order forever, through pools of depths,...
llvm::ArrayRef< unsigned > turns() const
Indices into ends, one per object the channel moves in a period of the core's steady state.
int getDMAChannelIndex(TileLike tile, DMAChannelDir dir, bool requiresAdjacentTileAccessChannels, mlir::Operation *owner=nullptr)
Next free channel of tile in dir, or -1 when the tile has none left.
mlir::Operation * getDMAChannelOwner(TileLike tile, DMAChannelDir dir, int channel)
Operation reserving or routing this channel; null if none.
bool isChannelFree(TileLike tile, DMAChannelDir dir, int channel)
Whether first-free assignment could hand out channel: nothing reserves it and no explicit flow routes...
static int getDMAChannelLimit(TileLike tile, DMAChannelDir dir, bool requiresAdjacentTileAccessChannels)
Exclusive upper bound on channel indices eligible for this transfer.
mlir::LogicalResult checkAIEStreamIndex(TileLike tile, DMAChannel chan, mlir::Operation *user, bool packet=false)
Claim a raw stream port for user, reporting on it when the port is taken; packet users may share it w...
int reservePinnedChannel(TileLike tile, DMAChannelDir dir, int channel, mlir::Operation *owner=nullptr, bool shareable=false)
Claim channel for (tile, dir) so first-free assignment cannot take it.
std::optional< int > takeLowestFrom(int from=0)
Takes and returns the lowest free id at or above from, or nothing when the field is exhausted.
llvm::SmallVector< ObjectFifoDmaEndpointOp > turnEndpoints(ObjectFifoDmaEndpointOp end)
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEObjectFifoAllocatePass()
mlir::Operation * lookupNamedOpIn(mlir::Operation *symbolTableOp, mlir::StringAttr name)
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
int64_t turnPasses(llvm::ArrayRef< std::pair< int64_t, int64_t > > ends)
mlir::Operation * lookupNamedOp(mlir::Operation *from, mlir::StringAttr name)
int64_t assignSequentialBufferAddresses(llvm::MutableArrayRef< BufferAllocation > buffers, int64_t start=0)
The basic address allocator and resource planner share this layout: retain address pins,...
AIETargetModel::SharedMemory sharedMemory(TileOp a, TileOp b)
Which of a's and b's memory modules both tiles can address.
void link(mlir::Operation *from, mlir::Operation *to, llvm::StringRef label)
Link from to to under label, and to back to from.
llvm::SmallVector< mlir::Operation * > nodes
std::string & add(mlir::Operation *op)
Add op as a node whose features are its name and attributes, symbol names and references left out.
llvm::DenseMap< mlir::Operation *, unsigned > rank() const
Rank the nodes by their features and then, a round at a time until no class splits,...
llvm::DenseMap< mlir::Operation *, std::string > features
What each node is, before anything it links to is considered.