MLIR-AIE 1.4.5.dev
AIEObjectFifoAllocate.cpp
Go to the documentation of this file.
1//===- AIEObjectFifoAllocate.cpp --------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
17
18#include "mlir/IR/Attributes.h"
19#include "mlir/Interfaces/DataLayoutInterfaces.h"
20#include "mlir/Pass/Pass.h"
21#include "llvm/ADT/MapVector.h"
22#include "llvm/ADT/StringMap.h"
23#include "llvm/Support/Debug.h"
24
25#include <set>
26
27#define DEBUG_TYPE "aie-objectfifo-allocate"
28
29using namespace mlir;
30using namespace xilinx;
31using namespace xilinx::AIE;
32
33namespace xilinx::AIE {
34#define GEN_PASS_DEF_AIEOBJECTFIFOALLOCATE
35#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
36} // namespace xilinx::AIE
37
38namespace {
39
40struct AIEObjectFifoAllocatePass
41 : public xilinx::AIE::impl::AIEObjectFifoAllocateBase<
42 AIEObjectFifoAllocatePass> {
43 using Base::Base;
44
45 DeviceOp device;
46 OpBuilder builder{static_cast<MLIRContext *>(nullptr)};
47 /// The last buffer or lock this pass placed on each tile.
48 DenseMap<Value, Operation *> lastPlaced;
49 /// Pools some endpoint writes into.
50 DenseSet<Operation *> filledPools;
51 /// The `aiex.dma_channel_reset_for` ops naming each fifo.
52 llvm::StringMap<SmallVector<Operation *>> rearmUsers;
53 /// Flows already lowered. A route endpoint reads its direction off the
54 /// flow naming it, so these outlive the walk that replaces them.
55 SmallVector<Operation *> loweredFlows;
56 /// The flows each route endpoint is the source of, by its symbol name,
57 /// gathered before any flow is lowered.
58 DenseMap<StringAttr, SmallVector<RouteOp>> sourcedFlows;
59 llvm::MapVector<ObjectFifoDmaEndpointOp, llvm::StringMap<PacketInfoAttr>>
60 dispatchHeaders;
61 /// Ends that take another end's channel, by the end they take it from:
62 /// shim ends never in flight together, and ends taking turns.
63 llvm::MapVector<Operation *, Operation *> channelSharers;
64 DenseMap<Value, SmallVector<int64_t>> plannedMemory;
65 DenseMap<Operation *, SmallVector<Value>> bufferPlacements;
66 DenseMap<Operation *, int> channelAssignments;
67 DenseMap<Operation *, Value> localPools;
68 DenseMap<Operation *, SmallVector<Value>> poolUsers;
69 DenseMap<Operation *, SmallVector<Operation *>> lockUsers;
70 DenseMap<Operation *, Value> lockPlacements;
71 /// What runs on each channel the design programs itself, by the
72 /// `aie.route_endpoint` naming it: dma_starts in its tile's DMA program and
73 /// tasks the runtime sequence configures for it.
74 DenseMap<Operation *, SmallVector<DMAStartOp>> endpointStarts;
75 DenseMap<Operation *, SmallVector<AIEX::DMAConfigureTaskForOp>> endpointTasks;
76 /// BDs the chains DMA programs start by index hold on each channel of a
77 /// placed tile, by (col, row, channel). Channels are chosen from these and
78 /// the endpoints' own chains alone, so an image built for one runtime
79 /// sequence serves another's instruction stream.
80 DenseMap<std::tuple<int, int, int>, int64_t> fixedBDs;
81 /// The largest task the runtime sequence configures on each channel by
82 /// index, by (col, row, channel).
83 DenseMap<std::tuple<int, int, int>, int64_t> taskBDs;
84 /// The BDs every channel the last assignment handed out needs at once,
85 /// runtime tasks included.
86 DenseMap<std::tuple<int, int, int>, int64_t> channelDemand;
87 /// Where each tile, pool, endpoint and route falls in an order taken from
88 /// the design's structure alone, which settles the choices op order would.
89 DenseMap<Operation *, unsigned> structuralRank;
90 RouteEndpoint channelFailure;
91 /// The pool the buffer search could not place with the fewest left
92 /// unplaced, i.e. where it got furthest.
93 ObjectFifoPoolOp bufferFailure;
94 size_t unplacedAtFailure = 0;
95 /// Neighbors spillTargets created to weigh a spill. The ones no buffer or
96 /// lock ends up on are erased once allocation is done.
97 SmallVector<TileOp> spillTiles;
98 Value lockFailure;
99 Operation *lockAccessFailure = nullptr;
100
101 bool sameTile(Value a, Value b) {
102 if (a == b)
103 return true;
104 auto first = cast<TileLike>(a.getDefiningOp());
105 auto second = cast<TileLike>(b.getDefiningOp());
106 auto ac = first.tryGetCol(), ar = first.tryGetRow();
107 auto bc = second.tryGetCol(), br = second.tryGetRow();
108 return ac && ar && bc && br && ac == bc && ar == br;
109 }
110
111 int64_t memoryUsed(Value tile, int64_t extraSize = 0, int extraCount = 0) {
112 SmallVector<BufferAllocation> layout;
113 int64_t alignment =
114 device.getTargetModel().getMemTileLoadStoreBusWidth() / 8;
115 // Generated buffers are inserted immediately after their tile, before
116 // existing buffers. Preserve that order for equal-sized aligned/unaligned
117 // buffers, including coordinate-equivalent tile references.
118 for (Operation &op : *device.getBody()) {
119 if (auto placed = dyn_cast<TileLike>(op);
120 placed && sameTile(tile, placed->getResult(0))) {
121 Value value = placed->getResult(0);
122 for (int64_t size : plannedMemory[value])
123 layout.push_back({size, alignment, std::nullopt});
124 if (value == tile)
125 for (int i = 0; i < extraCount; ++i)
126 layout.push_back({extraSize, alignment, std::nullopt});
127 } else if (auto buffer = dyn_cast<BufferOp>(op);
128 buffer && sameTile(tile, buffer.getTile())) {
129 layout.push_back({buffer.getAllocationSize(),
130 buffer.getAligned() ? alignment : 1,
131 buffer.getAddress()});
132 }
133 }
134 return assignSequentialBufferAddresses(layout);
135 }
136
137 LogicalResult collectFixedMemory() {
138 plannedMemory.clear();
139 for (auto tile : device.getOps<TileLike>()) {
140 if (!tile.isMemTile())
141 continue;
142 int64_t bytes = memoryUsed(tile->getResult(0));
143 int64_t capacity = device.getTargetModel().getMemTileSize();
144 if (bytes > capacity)
145 return tile->emitOpError("existing buffers require ")
146 << bytes << " bytes, exceeding MemTile capacity of " << capacity
147 << " bytes";
148 }
149 return success();
150 }
151
152 bool canAccess(Value user, Value memory, bool localOnly = false) {
153 if (user == memory)
154 return true;
155 auto userTile = dyn_cast<TileLike>(user.getDefiningOp());
156 auto memoryTile = dyn_cast<TileLike>(memory.getDefiningOp());
157 if (!userTile || !memoryTile)
158 return false;
159 const auto &target = device.getTargetModel();
160 auto positions = [&](TileLike tile) {
161 SmallVector<TileID> compatible;
162 auto col = tile.tryGetCol(), row = tile.tryGetRow();
163 for (int c = 0; c < target.columns(); ++c) {
164 if (col && c != *col)
165 continue;
166 for (int r = 0; r < target.rows(); ++r) {
167 if ((!row || r == *row) &&
168 target.getTileType(c, r) == tile.getTileType())
169 compatible.push_back({c, r});
170 }
171 }
172 return compatible;
173 };
174 // Before placement, defer only accesses that some compatible physical
175 // positions could satisfy. Known coordinates can already disprove affinity.
176 auto userPositions = positions(userTile);
177 auto memoryPositions = positions(memoryTile);
178 for (TileID u : userPositions)
179 for (TileID m : memoryPositions) {
180 if (localOnly && u != m)
181 continue;
182 auto shared = target.getSharedMemory(u, m);
183 if (shared == AIETargetModel::SharedMemory::Second ||
184 shared == AIETargetModel::SharedMemory::Either)
185 return true;
186 }
187 return false;
188 }
189
190 bool canPlace(ObjectFifoPoolOp pool, Value tile, int64_t sizeBytes,
191 int count = 1) {
192 if (cast<TileLike>(tile.getDefiningOp()).isMemTile() &&
193 memoryUsed(tile, sizeBytes, count) >
194 device.getTargetModel().getMemTileSize())
195 return false;
196 return llvm::all_of(poolUsers[pool],
197 [&](Value user) { return canAccess(user, tile); });
198 }
199
200 Value lockPlacement(ObjectFifoPoolOp pool, Operation *group) {
201 if (Value placed = lockPlacements.lookup(group))
202 return placed;
203 Value local = localPools.lookup(pool);
204 return local ? local : pool.getTile();
205 }
206
207 /// A contiguous pool's objects are one buffer; others have one per object.
208 static int bufferCount(ObjectFifoPoolOp pool) {
209 return pool.getContiguous() ? 1 : pool.getDepth();
210 }
211 static int64_t bufferBytes(ObjectFifoPoolOp pool) {
212 return pool.getObjectSizeInBytes() * pool.getObjectsPerBuffer();
213 }
214
215 /// Reserve mandatory-local objects before any other pool can spill into
216 /// their memory. Existing buffers are fixed and counted by identity, not by
217 /// how many pools or endpoints reference them.
218 LogicalResult planBuffers(ArrayRef<ObjectFifoPoolOp> pools) {
219 plannedMemory.clear();
220 bufferPlacements.clear();
221 bufferFailure = nullptr;
222 for (auto pool : pools) {
223 if (!localPools.contains(pool))
224 continue;
225 Value tile = localPools.lookup(pool);
226 int count = pool.getBuffers() ? 0 : bufferCount(pool);
227 int64_t size = bufferBytes(pool);
228 if (!canAccess(pool.getTile(), tile) ||
229 !canPlace(pool, tile, size, count)) {
230 bufferFailure = pool;
231 return failure();
232 }
233 plannedMemory[tile].append(count, size);
234 }
235 SmallVector<ObjectFifoPoolOp> generated;
236 for (auto pool : pools) {
237 if (pool.getBuffers()) {
238 for (auto buffer : pool.getBufferOps()) {
239 Value tile = buffer.getBufferTile();
240 if (tile && ((localPools.contains(pool) &&
241 !sameTile(tile, localPools.lookup(pool))) ||
242 !llvm::all_of(poolUsers[pool], [&](Value user) {
243 return canAccess(user, tile);
244 }))) {
245 bufferFailure = pool;
246 return failure();
247 }
248 bufferPlacements[pool].push_back(tile);
249 }
250 continue;
251 }
252 if (pool.getTileLike().isShimTile())
253 continue;
254 if (localPools.contains(pool))
255 bufferPlacements[pool].append(bufferCount(pool),
256 localPools.lookup(pool));
257 else
258 generated.append(bufferCount(pool), pool);
259 }
260 int retries = kSpillRetries;
261 return success(placeBuffers(generated, retries));
262 }
263
264 /// The first layout tried is the greedy one. When a buffer fits nowhere,
265 /// an earlier buffer may have spilled to the one neighbor it could use, or
266 /// stayed home where a later buffer that only home can hold needed the
267 /// room, so earlier choices are revisited, latest first, a bounded number of
268 /// times.
269 static constexpr int kSpillRetries = 256;
270
271 bool placeBuffers(ArrayRef<ObjectFifoPoolOp> buffers, int &retries) {
272 if (buffers.empty())
273 return true;
274 ObjectFifoPoolOp pool = buffers.front();
275 int64_t size = bufferBytes(pool);
276 Value homeTile = pool.getTileLike()->getResult(0);
277 bool fitsHome = canPlace(pool, homeTile, size);
278 SmallVector<Value> candidates;
279 if (fitsHome)
280 candidates.push_back(homeTile);
281 else
282 candidates = spillTargets(pool, size);
283 if (candidates.empty() &&
284 (!bufferFailure || buffers.size() < unplacedAtFailure)) {
285 bufferFailure = pool;
286 unplacedAtFailure = buffers.size();
287 }
288 auto tryTile = [&](Value tile) {
289 plannedMemory[tile].push_back(size);
290 bufferPlacements[pool].push_back(tile);
291 if (placeBuffers(buffers.drop_front(), retries))
292 return true;
293 plannedMemory[tile].pop_back();
294 bufferPlacements[pool].pop_back();
295 return false;
296 };
297 for (auto [index, tile] : llvm::enumerate(candidates)) {
298 if (index > 0 && retries-- <= 0)
299 return false;
300 if (tryTile(tile))
301 return true;
302 }
303 if (!fitsHome)
304 return false;
305 // Spill targets are only looked up once home has failed, so a layout that
306 // keeps every buffer home creates no neighbor tiles.
307 for (Value tile : spillTargets(pool, size)) {
308 if (retries-- <= 0)
309 return false;
310 if (tryTile(tile))
311 return true;
312 }
313 return false;
314 }
315
316 /// FIXME: choosing which tile a buffer lives on is the buffer allocator's
317 /// job, not this pass's. Tracking used memory here to make that choice
318 /// breaks the separation of concerns; --aie-assign-buffer-addresses should
319 /// instead be free to move buffers between tiles that share a memory module.
320 ///
321 /// A MemTile buffer that does not fit at home spills to a neighbor reachable
322 /// by its DMAs, preferring the emptier one so adjacent MemTiles
323 /// keep room for their own spills. Which tiles neighbor an unplaced one is
324 /// not yet known, so its buffers stay at home.
325 SmallVector<Value> spillTargets(ObjectFifoPoolOp pool, int64_t sizeBytes) {
326 TileLike home = pool.getTileLike();
327 auto &target = device.getTargetModel();
328 if (!home.isMemTile())
329 return {};
330
331 auto homeOp = dyn_cast<TileOp>(home.getOperation());
332 if (!homeOp) {
333 return {};
334 }
335
336 SmallVector<TileOp> neighbors;
337 for (int col : {homeOp.getCol() - 1, homeOp.getCol() + 1}) {
338 if (col < 0 || col >= target.columns()) {
339 continue;
340 }
341 int row = homeOp.getRow();
342 bool declared = llvm::any_of(device.getOps<TileOp>(), [&](TileOp t) {
343 return t.getCol() == col && t.getRow() == row;
344 });
345 TileOp neighbor =
346 TileOp::getOrCreate(builder, device, col, row, pool.getLoc());
347 if (!declared)
348 spillTiles.push_back(neighbor);
349 using SharedMemory = AIETargetModel::SharedMemory;
350 SharedMemory shared = sharedMemory(homeOp, neighbor);
351 if (shared == SharedMemory::Second || shared == SharedMemory::Either) {
352 neighbors.push_back(neighbor);
353 }
354 }
355 llvm::stable_sort(neighbors, [&](TileOp a, TileOp b) {
356 return memoryUsed(a.getResult()) < memoryUsed(b.getResult());
357 });
358 SmallVector<Value> fitting;
359 for (TileOp neighbor : neighbors) {
360 if (canPlace(pool, neighbor.getResult(), sizeBytes)) {
361 fitting.push_back(neighbor.getResult());
362 }
363 }
364 return fitting;
365 }
366
367 /// Buffers and locks sit directly below the tile whose memory holds them,
368 /// after whatever this pass has already put there.
369 void setInsertionPointOn(Value tile) {
370 Operation *after = lastPlaced.lookup(tile);
371 if (!after) {
372 after = tile.getDefiningOp();
373 }
374 if (after) {
375 builder.setInsertionPointAfter(after);
376 } else {
377 builder.setInsertionPointToStart(device.getBody());
378 }
379 }
380
381 void allocateBuffers(ObjectFifoPoolOp pool) {
382 if (pool.getBuffers()) {
383 return;
384 }
385 TileLike home = pool.getTileLike();
386 if (home.isShimTile()) {
387 return;
388 }
389
390 ArrayRef<Attribute> initValues;
391 if (auto initAttr = pool.getInitValues()) {
392 initValues = initAttr->getValue();
393 }
394 int filled = initValues.size();
395 StringRef base = pool.getBaseName();
396
397 SmallVector<Attribute> names;
398 MemRefType type = pool.getElemType();
399 ElementsAttr zeros;
400 if (pool.getContiguous()) {
401 type =
402 MemRefType::get({pool.getObjectsPerBuffer() * type.getNumElements()},
403 type.getElementType());
404 if (pool.getZeroObjects())
405 zeros = DenseElementsAttr::get(
406 RankedTensorType::get(type.getShape(), type.getElementType()),
407 builder.getZeroAttr(type.getElementType()));
408 }
409 // Initial contents may fill only the first objects of a deeper pool.
410 for (int i = 0; i < bufferCount(pool); i++) {
411 ElementsAttr init =
412 i < filled ? cast<ElementsAttr>(initValues[i]) : zeros;
413 std::string name = (base + "_buff_" + std::to_string(i)).str();
414 Value placement = bufferPlacements[pool][i];
415 setInsertionPointOn(placement);
416 lastPlaced[placement] = BufferOp::create(
417 builder, pool.getLoc(), type, placement, builder.getStringAttr(name),
418 /*address=*/nullptr, init,
419 /*mem_bank=*/nullptr, /*core_data=*/nullptr);
420 names.push_back(FlatSymbolRefAttr::get(builder.getContext(), name));
421 }
422 pool.setBuffersAttr(builder.getArrayAttr(names));
423 }
424
425 LockOp createLock(ObjectFifoPoolOp pool, Value tile, StringRef name,
426 int value) {
427 setInsertionPointOn(tile);
428 auto lock = LockOp::create(builder, pool.getLoc(), tile, value);
429 lastPlaced[tile] = lock;
430 lock->setAttr(SymbolTable::getSymbolAttrName(),
431 builder.getStringAttr(name));
432 return lock;
433 }
434
435 bool needsLocks(ObjectFifoPoolOp pool) {
436 if (pool.getDisableSynchronization()) {
437 return false;
438 }
439
440 int depth = pool.getDepth();
441 auto initValues = pool.getInitValues();
442 int filled = initValues ? initValues->size() : 0;
443
444 // A pool that starts full and is never refilled holds constants: its
445 // readers have nothing to wait for. Locks would also let it be read only
446 // as many times as they count, so a second launch would hang, unless a
447 // `dma_channel_reset_for` re-arms them each launch.
448 std::optional<StringRef> fifo = pool.getFifoName();
449 return filled != depth || filled <= 0 || filledPools.contains(pool) ||
450 (fifo && rearmUsers.contains(*fifo));
451 }
452
453 LogicalResult planLocks(ArrayRef<ObjectFifoPoolOp> pools) {
454 lockFailure = {};
455 lockAccessFailure = nullptr;
456 lockPlacements.clear();
457 DenseMap<std::pair<int, int>, int64_t> used;
458 auto reserve = [&](Value tile, int64_t count) {
459 auto like = cast<TileLike>(tile.getDefiningOp());
460 auto col = like.tryGetCol(), row = like.tryGetRow();
461 if (!col || !row)
462 return success();
463 if (used[{*col, *row}] + count >
464 device.getTargetModel().getNumLocks(*col, *row)) {
465 lockFailure = tile;
466 return failure();
467 }
468 used[{*col, *row}] += count;
469 return success();
470 };
471 for (auto lock : device.getOps<LockOp>())
472 if (failed(reserve(lock.getTile(), 1)))
473 return failure();
474 // Neighbors created to weigh a spill have no rank and come last.
475 SmallVector<TileLike> tiles(device.getOps<TileLike>());
476 llvm::stable_sort(tiles, [&](TileLike a, TileLike b) {
477 auto aRank = structuralRank.find(a), bRank = structuralRank.find(b);
478 if ((aRank == structuralRank.end()) != (bRank == structuralRank.end()))
479 return bRank == structuralRank.end();
480 return aRank != structuralRank.end() && aRank->second < bRank->second;
481 });
482 auto place = [&](ObjectFifoPoolOp pool, Operation *group, int count) {
483 SmallVector<Value> candidates{lockPlacement(pool, group)};
484 if (!localPools.contains(pool)) {
485 llvm::append_range(candidates, bufferPlacements[pool]);
486 for (Operation *user : lockUsers[group])
487 candidates.push_back(user->getOperand(0));
488 for (auto tile : tiles)
489 candidates.push_back(tile->getResult(0));
490 }
491 bool reachable = false;
492 for (Value tile : candidates) {
493 if (!tile || !llvm::all_of(lockUsers[group], [&](Operation *user) {
494 return canAccessLocks(user, tile);
495 }))
496 continue;
497 reachable = true;
498 if (succeeded(reserve(tile, count))) {
499 lockPlacements[group] = tile;
500 lockFailure = {};
501 return success();
502 }
503 }
504 if (!reachable && !lockUsers[group].empty()) {
505 lockFailure = {};
506 lockAccessFailure = lockUsers[group].front();
507 }
508 return failure();
509 };
510 for (auto pool : pools) {
511 if (!needsLocks(pool))
512 continue;
513 if (device.getTargetModel().getTargetArch() == AIEArch::AIE1) {
514 if (!pool.getLocks() && failed(place(pool, pool, pool.getDepth())))
515 return failure();
516 } else {
517 for (auto segment : pool.getSegmentOps())
518 if (!(segment.getProduceLock() && segment.getConsumeLock()) &&
519 failed(place(pool, segment, 2)))
520 return failure();
521 }
522 }
523 auto check = [&](auto endpoint) {
524 for (Value tile : endpointLockTiles(endpoint)) {
525 if (!canAccessLocks(endpoint, tile)) {
526 lockAccessFailure = endpoint;
527 return failure();
528 }
529 }
530 return success();
531 };
532 for (auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>())
533 if (failed(check(endpoint)))
534 return failure();
535 for (auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>())
536 if (failed(check(endpoint)))
537 return failure();
538 return success();
539 }
540
541 /// AIE1 guards each buffer with one binary lock that rotates with it; AIE2
542 /// gives each segment a counting pair, the producer's lock counting free
543 /// objects and the consumer's counting full ones.
544 void allocateLocks(ObjectFifoPoolOp pool) {
545 if (!needsLocks(pool))
546 return;
547 StringRef base = pool.getBaseName();
548 int depth = pool.getDepth();
549 auto initValues = pool.getInitValues();
550 int filled = initValues ? initValues->size() : 0;
551 int repeat = pool.getRepeatCount().value_or(1);
552
553 if (device.getTargetModel().getTargetArch() == AIEArch::AIE1) {
554 if (pool.getLocks()) {
555 return;
556 }
557 SmallVector<Attribute> names;
558 for (int i = 0; i < depth; i++) {
559 std::string name = (base + "_lock_" + std::to_string(i)).str();
560 createLock(pool, lockPlacement(pool, pool), name, i < filled ? 1 : 0);
561 names.push_back(FlatSymbolRefAttr::get(builder.getContext(), name));
562 }
563 pool.setLocksAttr(builder.getArrayAttr(names));
564 return;
565 }
566
567 for (auto [index, segment] : llvm::enumerate(pool.getSegmentOps())) {
568 if (segment.getProduceLock() && segment.getConsumeLock()) {
569 continue;
570 }
571 std::string produce =
572 (base + "_prod_lock_" + std::to_string(index)).str();
573 std::string consume =
574 (base + "_cons_lock_" + std::to_string(index)).str();
575 Value tile = lockPlacement(pool, segment);
576 createLock(pool, tile, produce, (depth - filled) * repeat);
577 createLock(pool, tile, consume, filled * repeat);
578 segment.setProduceLockAttr(
579 FlatSymbolRefAttr::get(builder.getContext(), produce));
580 segment.setConsumeLockAttr(
581 FlatSymbolRefAttr::get(builder.getContext(), consume));
582 }
583 }
584
585 bool canAccessLocks(Operation *endpoint, Value tile) {
586 Value user = endpoint->getOperand(0);
587 // Compute-tile DMA engines, unlike cores, only use their own lock module.
588 if (isa<ObjectFifoDmaEndpointOp>(endpoint) &&
589 !cast<TileLike>(user.getDefiningOp()).isMemTile())
590 return canAccess(user, tile, /*localOnly=*/true);
591 return canAccess(user, tile);
592 }
593
594 template <typename EndpointOp>
595 void collectLockUsers(EndpointOp endpoint) {
596 auto pool = endpoint.getPoolOp();
597 if (device.getTargetModel().getTargetArch() == AIEArch::AIE1) {
598 lockUsers[pool].push_back(endpoint);
599 return;
600 }
601 for (auto segment : endpoint.getSelectedSegments())
602 lockUsers[segment].push_back(endpoint);
603 }
604
605 /// Predict the locks core and DMA lowering will use, including hand-written
606 /// locks and those allocation will create. Only selected segments matter.
607 template <typename EndpointOp>
608 SmallVector<Value> endpointLockTiles(EndpointOp endpoint) {
609 SmallVector<Value> tiles;
610 auto pool = endpoint.getPoolOp();
611 if (pool.getDepth() == 0)
612 return tiles;
613 if (device.getTargetModel().getTargetArch() == AIEArch::AIE1) {
614 if (needsLocks(pool) && !pool.getLocks())
615 tiles.push_back(lockPlacement(pool, pool));
616 else
617 for (auto lock : pool.getLockOps())
618 tiles.push_back(lock.getTile());
619 return tiles;
620 }
621 for (auto segment : endpoint.getSelectedSegments()) {
622 if (needsLocks(pool) &&
623 !(segment.getProduceLock() && segment.getConsumeLock())) {
624 tiles.push_back(lockPlacement(pool, segment));
625 continue;
626 }
627 for (auto name :
628 {segment.getProduceLockAttr(), segment.getConsumeLockAttr()})
629 if (name)
630 if (auto lock = lookupNamedOp<LockOp>(device, name.getAttr()))
631 tiles.push_back(lock.getTile());
632 }
633 return tiles;
634 }
635
636 /// Both buffers and locks must be local to use the local-only channels.
637 /// Distinct unresolved tiles may be neighbors after placement, so they
638 /// cannot be assumed local when assigning a placed endpoint's channels.
639 bool reachesAdjacentTile(RouteEndpoint endpoint) {
640 if (auto route = dyn_cast<RouteEndpointOp>(endpoint.getOperation()))
641 return programReachesAdjacentTile(route);
642 auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation());
643 if (!dma || !dma.getTileLike().isMemTile()) {
644 return false;
645 }
646 ObjectFifoPoolOp pool = dma.getPoolOp();
647 auto remote = [&](Value tile) {
648 return tile && !sameTile(tile, endpoint.getTile());
649 };
650 return pool && (llvm::any_of(bufferPlacements[pool], remote) ||
651 llvm::any_of(endpointLockTiles(dma), remote));
652 }
653
654 TileLike tileOf(RouteEndpoint endpoint) {
655 return dyn_cast<TileLike>(endpoint.getTile().getDefiningOp());
656 }
657
658 /// A DMA channel the design programs itself, by naming this endpoint from a
659 /// dma_start or a runtime task, rather than one an objectFIFO lowers to.
660 static bool isProgramOwned(RouteEndpoint endpoint) {
661 auto route = dyn_cast<RouteEndpointOp>(endpoint.getOperation());
662 return route && route.getBundle() == WireBundle::DMA &&
663 !route.getTileLike().isShimTile();
664 }
665
666 /// Blocks of the BD chain `start` begins, following next_bd.
667 static SmallVector<Block *> chainOf(DMAStartOp start) {
668 SmallVector<Block *> chain;
669 SmallPtrSet<Block *, 8> seen;
670 SmallVector<Block *> work{start.getDest()};
671 while (!work.empty()) {
672 Block *block = work.pop_back_val();
673 if (!seen.insert(block).second || block->getOps<DMABDOp>().empty())
674 continue;
675 chain.push_back(block);
676 if (auto next = dyn_cast<NextBDOp>(block->getTerminator()))
677 work.push_back(next.getDest());
678 }
679 return chain;
680 }
681
682 static int64_t countBDs(DMAStartOp start) {
683 int64_t bds = 0;
684 for (Block *block : chainOf(start))
685 bds += llvm::range_size(block->getOps<DMABDOp>());
686 return bds;
687 }
688
689 static int64_t countBDs(Region &taskBody) {
690 int64_t bds = 0;
691 taskBody.walk([&](DMABDOp) { bds++; });
692 return bds;
693 }
694
695 /// BDs a program-owned endpoint's static chains hold; they stay configured.
696 int64_t endpointBDs(Operation *endpoint) {
697 int64_t bds = 0;
698 for (DMAStartOp start : endpointStarts.lookup(endpoint))
699 bds += countBDs(start);
700 return bds;
701 }
702
703 /// BDs the largest runtime task on a program-owned endpoint needs.
704 int64_t endpointTaskBDs(Operation *endpoint) {
705 int64_t largest = 0;
706 for (auto task : endpointTasks.lookup(endpoint))
707 largest = std::max(largest, countBDs(task.getBody()));
708 return largest;
709 }
710
711 /// Whether a program-owned MemTile channel's BDs touch a neighbor's buffers
712 /// or locks, which only the lower channels can.
713 bool programReachesAdjacentTile(RouteEndpointOp endpoint) {
714 if (!endpoint.getTileLike().isMemTile())
715 return false;
716 auto remote = [&](Operation *op) {
717 Value tile;
718 if (auto bd = dyn_cast<DMABDOp>(op)) {
719 if (auto buffer =
720 dyn_cast_or_null<BufferOp>(bd.getBuffer().getDefiningOp()))
721 tile = buffer.getTile();
722 } else if (auto use = dyn_cast<UseLockOp>(op)) {
723 if (auto lock = dyn_cast_or_null<LockOp>(use.getLock().getDefiningOp()))
724 tile = lock.getTile();
725 }
726 return tile && !sameTile(tile, endpoint.getTile());
727 };
728 for (DMAStartOp start : endpointStarts.lookup(endpoint))
729 for (Block *block : chainOf(start))
730 if (llvm::any_of(block->getOperations(),
731 [&](Operation &op) { return remote(&op); }))
732 return true;
733 for (auto task : endpointTasks.lookup(endpoint)) {
734 bool reaches = false;
735 task.getBody().walk([&](Operation *op) { reaches |= remote(op); });
736 if (reaches)
737 return true;
738 }
739 return false;
740 }
741
742 /// Find what programs each endpoint-named channel, and the BDs already spoken
743 /// for on channels named by index.
744 void collectChannelPrograms() {
745 endpointStarts.clear();
746 endpointTasks.clear();
747 fixedBDs.clear();
748 taskBDs.clear();
749 auto key = [](Value tile,
750 int channel) -> std::optional<std::tuple<int, int, int>> {
751 auto placed = cast<TileLike>(tile.getDefiningOp());
752 auto col = placed.tryGetCol(), row = placed.tryGetRow();
753 if (!col || !row)
754 return std::nullopt;
755 return std::make_tuple(*col, *row, channel);
756 };
757 for (auto program : device.getOps<DmaBody>())
758 for (Block &block : program.getDmaBody())
759 for (auto start : block.getOps<DMAStartOp>()) {
760 if (auto endpoint = start.getEndpointOp()) {
761 endpointStarts[endpoint].push_back(start);
762 } else if (auto at =
763 key(program.getTile(), start.getChannelIndex())) {
764 fixedBDs[*at] += countBDs(start);
765 }
766 }
767 device.walk([&](Operation *op) {
768 if (auto task = dyn_cast<AIEX::DMAConfigureTaskForOp>(op)) {
769 if (auto endpoint = dyn_cast_or_null<RouteEndpointOp>(
770 SymbolTable::lookupNearestSymbolFrom(
771 device, task.getAlloc().getRootReference())))
772 endpointTasks[endpoint].push_back(task);
773 } else if (auto task = dyn_cast<AIEX::DMAConfigureTaskOp>(op)) {
774 if (auto at = key(task.getTile(), task.getChannel())) {
775 int64_t &largest = taskBDs[*at];
776 largest = std::max(largest, countBDs(task.getBody()));
777 }
778 }
779 });
780 }
781
782 /// Rank tiles, pools, endpoints and routes by their attributes and then, a
783 /// round at a time until no class splits, by the ranks of what they connect
784 /// to. Symbol names and op order play no part, so a permuted or renamed
785 /// design ranks alike, and ops still tied cannot be told apart.
786 void rankStructurally() {
787 StructuralGraph graph;
788 // A placed tile is one node however many ops name it.
789 DenseMap<std::pair<int, int>, Operation *> placed;
790 DenseMap<Operation *, Operation *> tileNode;
791 for (auto tile : device.getOps<TileLike>()) {
792 auto col = tile.tryGetCol(), row = tile.tryGetRow();
793 if (!col || !row) {
794 graph.add(tileNode[tile] = tile);
795 continue;
796 }
797 auto [it, first] = placed.try_emplace({*col, *row}, tile);
798 tileNode[tile] = it->second;
799 if (first) {
800 graph.nodes.push_back(tile);
801 llvm::raw_string_ostream(graph.features[tile])
802 << "tile " << *col << ' ' << *row << ' '
803 << stringifyAIETileType(tile.getTileType());
804 }
805 }
806 auto link = [&](Operation *from, Operation *to, StringRef label) {
807 if (Operation *tile = tileNode.lookup(to))
808 to = tile;
809 graph.link(from, to, label);
810 };
811 for (auto pool : device.getOps<ObjectFifoPoolOp>()) {
812 llvm::raw_string_ostream os(graph.add(pool));
813 for (auto segment : pool.getSegmentOps())
814 os << " [" << segment.getOffset() << ", " << segment.getSize() << "]";
815 link(pool, pool.getTile().getDefiningOp(), "tile");
816 }
817 for (auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>()) {
818 llvm::raw_string_ostream os(graph.add(endpoint));
819 for (auto segment : endpoint.getSelectedSegments())
820 os << " @" << segment.getOffset();
821 link(endpoint, endpoint.getTile().getDefiningOp(), "tile");
822 link(endpoint, endpoint.getPoolOp(), "pool");
823 }
824 for (auto endpoint : device.getOps<RouteEndpoint>()) {
825 llvm::raw_string_ostream os(graph.add(endpoint));
826 if (auto dma =
827 dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation())) {
828 for (auto segment : dma.getSelectedSegments())
829 os << " @" << segment.getOffset();
830 link(endpoint, dma.getPoolOp(), "pool");
831 }
832 link(endpoint, endpoint.getTile().getDefiningOp(), "tile");
833 }
834 for (auto route : device.getOps<RouteOp>()) {
835 graph.add(route);
836 for (StringRef source : route.getSourceNames())
837 link(route, lookupEndpoint(source), "source");
838 for (StringRef dest : route.getDestinationNames())
839 link(route, lookupEndpoint(dest), "dest");
840 }
841 DenseMap<Operation *, unsigned> rank = graph.rank();
842 for (auto [tile, node] : tileNode)
843 rank[tile] = rank[node];
844 structuralRank = std::move(rank);
845 }
846
847 /// BDs `channel` of the tile at (`col`, `row`) can use that `demand` leaves
848 /// over, counting every channel whose BD ids overlap its own. Negative when
849 /// overcommitted.
850 int64_t bdRoom(TileLike tile, int col, int row, int channel,
851 const DenseMap<std::tuple<int, int, int>, int64_t> &demand) {
852 const AIETargetModel &target = device.getTargetModel();
853 uint32_t numBDs = target.getNumBDs(col, row);
854 int numChannels = std::max(tile.getNumSourceConnections(WireBundle::DMA),
855 tile.getNumDestConnections(WireBundle::DMA));
856 auto sharesBDs = [&](int other) {
857 for (uint32_t bd = 0; bd < numBDs; ++bd)
858 if (target.isBdChannelAccessible(col, row, bd, channel) &&
859 target.isBdChannelAccessible(col, row, bd, other))
860 return true;
861 return false;
862 };
863 int64_t room = target.getNumBDsForChannel(col, row, channel);
864 for (int other = 0; other < numChannels; ++other)
865 if (sharesBDs(other))
866 if (auto it = demand.find({col, row, other}); it != demand.end())
867 room -= it->second;
868 return room;
869 }
870
871 /// On a tile whose BD ids are split between channels, the free channel whose
872 /// BDs have the most room left once every channel sharing them has its
873 /// demand; ties, and tiles without such a split, go to the lowest index.
874 /// Falls back to first-free where coordinates are unknown.
875 int chooseChannel(DMAChannelAnalysis &channels, RouteEndpoint endpoint,
876 const DenseMap<std::tuple<int, int, int>, int64_t> &demand,
877 bool adjacent) {
878 TileLike tile = tileOf(endpoint);
879 DMAChannelDir dir = endpoint.getRouteDirection();
880 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
881 if (!col || !row)
882 return channels.getDMAChannelIndex(tile, dir, adjacent,
883 endpoint.getOperation());
884 int best = -1;
885 int64_t bestRoom = 0;
886 int limit = DMAChannelAnalysis::getDMAChannelLimit(tile, dir, adjacent);
887 for (int channel = 0; channel < limit; ++channel) {
888 if (!channels.isChannelFree(tile, dir, channel))
889 continue;
890 int64_t room = bdRoom(tile, *col, *row, channel, demand);
891 if (best < 0 || room > bestRoom) {
892 best = channel;
893 bestRoom = room;
894 }
895 }
896 if (best < 0)
897 return -1;
898 return channels.reservePinnedChannel(tile, dir, best,
899 endpoint.getOperation());
900 }
901
902 void noteChannelOwner(InFlightDiagnostic &diag, Operation *owner,
903 std::optional<int> channel = std::nullopt) {
904 auto &note = diag.attachNote(owner->getLoc());
905 if (auto endpoint = dyn_cast<RouteEndpoint>(owner)) {
906 note << "DMA endpoint @" << cast<SymbolOpInterface>(owner).getName();
907 if (auto fifo = owner->getAttrOfType<StringAttr>("fifoName"))
908 note << " for ObjectFifo @" << fifo.getValue();
909 if (reachesAdjacentTile(endpoint))
910 note << " requires adjacent MemTile access";
911 if (channel)
912 note << "; occupies channel " << *channel;
913 } else if (channel) {
914 note << "pre-existing " << owner->getName() << " reserves DMA channel "
915 << *channel;
916 }
917 }
918
919 // No allocation can move a stream port the design names, so a clash fails
920 // the pass before the search rather than surfacing inside it.
921 LogicalResult checkStreamPorts() {
922 DMAChannelAnalysis channels(device);
923 DenseSet<Operation *> packetEndpoints;
924 for (auto flow : device.getOps<RouteOp>()) {
925 if (!flow.getPacket())
926 continue;
927 for (StringRef source : flow.getSourceNames())
928 packetEndpoints.insert(lookupEndpoint(source).getOperation());
929 for (StringRef dest : flow.getDestinationNames())
930 packetEndpoints.insert(lookupEndpoint(dest).getOperation());
931 }
932 bool clash = false;
933 for (auto endpoint : device.getOps<RouteEndpoint>()) {
934 if (endpoint.getRouteBundle() != WireBundle::Core)
935 continue;
936 std::optional<int> channel = endpoint.getRouteChannel();
937 if (!channel)
938 return endpoint->emitOpError("a stream port names its own channel");
939 clash |= failed(channels.checkAIEStreamIndex(
940 tileOf(endpoint), {endpoint.getRouteDirection(), *channel},
941 endpoint.getOperation(),
942 packetEndpoints.contains(endpoint.getOperation())));
943 }
944 return failure(clash);
945 }
946
947 LogicalResult assignChannels(DMAChannelAnalysis &channels,
948 bool diagnose = true) {
949 channelAssignments.clear();
950 channelFailure = nullptr;
951 SmallVector<RouteEndpoint> pending;
952 // BDs per placed (col, row, channel) as channels are handed out, for
953 // choosing among a tile's channels by the BD ids they can use.
954 DenseMap<std::tuple<int, int, int>, int64_t> demand = fixedBDs;
955 auto noteDemand = [&](RouteEndpoint endpoint, int channel) {
956 TileLike tile = tileOf(endpoint);
957 auto col = tile.tryGetCol(), row = tile.tryGetRow();
958 if (!col || !row)
959 return;
960 int64_t bds = 0;
961 if (auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation()))
962 bds = dma.getNumBDs();
963 else if (isProgramOwned(endpoint))
964 bds = endpointBDs(endpoint.getOperation());
965 demand[{*col, *row, channel}] += bds;
966 };
967 for (auto endpoint : device.getOps<RouteEndpoint>()) {
968 DMAChannelDir dir = endpoint.getRouteDirection();
969 std::optional<int> channel = endpoint.getRouteChannel();
970 // A core's stream port is named by the design, not drawn from the tile's
971 // DMA channels; checkStreamPorts has claimed it.
972 if (endpoint.getRouteBundle() == WireBundle::Core ||
973 channelSharers.count(endpoint.getOperation()))
974 continue;
975
976 if (channel) {
977 if (reachesAdjacentTile(endpoint) &&
978 *channel >= DMAChannelAnalysis::getDMAChannelLimit(tileOf(endpoint),
979 dir, true)) {
980 channelFailure = endpoint;
981 if (!diagnose)
982 return failure();
983 return endpoint->emitOpError("pinned ")
984 << stringifyDMAChannelDir(dir) << " DMA channel " << *channel
985 << " cannot access adjacent MemTile buffers or locks";
986 }
987 if (channels.reservePinnedChannel(tileOf(endpoint), dir, *channel,
988 endpoint.getOperation()) < 0) {
989 if (!diagnose)
990 return failure();
991 auto diag = endpoint->emitOpError("pinned ");
992 diag << stringifyDMAChannelDir(dir) << " DMA channel " << *channel
993 << " is out of range or already in use on this tile";
994 if (Operation *owner =
995 channels.getDMAChannelOwner(tileOf(endpoint), dir, *channel))
996 noteChannelOwner(diag, owner, channel);
997 return failure();
998 }
999 noteDemand(endpoint, *channel);
1000 continue;
1001 }
1002 pending.push_back(endpoint);
1003 }
1004
1005 // Endpoints reaching a spilled buffer draw from a restricted channel
1006 // range, so they are served before the unrestricted ones. Channels the
1007 // design programs itself come after the objectFIFOs', whose BDs they are
1008 // then chosen around, the most demanding first. The structural rank
1009 // settles the rest, so the order ops were written in never does.
1010 DenseMap<Operation *, int64_t> programBDs;
1011 for (auto endpoint : pending)
1012 if (isProgramOwned(endpoint))
1013 programBDs[endpoint.getOperation()] =
1014 endpointBDs(endpoint.getOperation());
1015 llvm::stable_sort(pending, [&](RouteEndpoint a, RouteEndpoint b) {
1016 bool aAdjacent = reachesAdjacentTile(a);
1017 bool bAdjacent = reachesAdjacentTile(b);
1018 if (aAdjacent != bAdjacent)
1019 return aAdjacent;
1020 bool aProgram = isProgramOwned(a), bProgram = isProgramOwned(b);
1021 if (aProgram != bProgram)
1022 return bProgram;
1023 int64_t aBDs = programBDs.lookup(a.getOperation());
1024 int64_t bBDs = programBDs.lookup(b.getOperation());
1025 if (aBDs != bBDs)
1026 return aBDs > bBDs;
1027 return structuralRank.lookup(a.getOperation()) <
1028 structuralRank.lookup(b.getOperation());
1029 });
1030
1031 for (auto endpoint : pending) {
1032 DMAChannelDir dir = endpoint.getRouteDirection();
1033 TileLike tile = tileOf(endpoint);
1034 bool adjacent = reachesAdjacentTile(endpoint);
1035 // An objectFIFO end takes the first free channel, unless that channel's
1036 // BD ids have too few left for it: then the one with the most left.
1037 bool roomy = true;
1038 auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(endpoint.getOperation());
1039 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
1040 if (dma && col && row) {
1041 int limit = DMAChannelAnalysis::getDMAChannelLimit(tile, dir, adjacent);
1042 for (int free = 0; free < limit; ++free) {
1043 if (!channels.isChannelFree(tile, dir, free))
1044 continue;
1045 roomy = bdRoom(tile, *col, *row, free, demand) >= dma.getNumBDs();
1046 break;
1047 }
1048 }
1049 int channel = isProgramOwned(endpoint) || !roomy
1050 ? chooseChannel(channels, endpoint, demand, adjacent)
1051 : channels.getDMAChannelIndex(tile, dir, adjacent,
1052 endpoint.getOperation());
1053 if (channel < 0) {
1054 channelFailure = endpoint;
1055 if (!diagnose)
1056 return failure();
1057 int capacity =
1058 DMAChannelAnalysis::getDMAChannelLimit(tile, dir, adjacent);
1059 auto diag =
1060 tile.emitOpError(dir == DMAChannelDir::MM2S
1061 ? "number of output DMA channel exceeded!"
1062 : "number of input DMA channel exceeded!");
1063 diag << " requires at least " << capacity + 1 << " "
1064 << stringifyDMAChannelDir(dir) << " channels, but capacity is "
1065 << capacity;
1066 if (adjacent) {
1067 diag << " for adjacent MemTile access";
1068 }
1069 for (int i = 0; i < capacity; ++i)
1070 if (Operation *owner = channels.getDMAChannelOwner(tile, dir, i))
1071 noteChannelOwner(diag, owner, i);
1072 noteChannelOwner(diag, endpoint.getOperation());
1073 return failure();
1074 }
1075 channelAssignments[endpoint.getOperation()] = channel;
1076 noteDemand(endpoint, channel);
1077 }
1078 for (auto [sharer, owner] : channelSharers)
1079 channelAssignments[sharer] = channelAssignments.lookup(owner);
1080 // Runtime tasks did not steer the choice, but they hold BDs while they
1081 // run, which the overcommit warning counts.
1082 for (auto &[at, bds] : taskBDs)
1083 demand[at] += bds;
1084 for (auto endpoint : device.getOps<RouteEndpoint>()) {
1085 TileLike tile = tileOf(endpoint);
1086 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
1087 std::optional<int> channel = endpoint.getRouteChannel();
1088 if (auto it = channelAssignments.find(endpoint.getOperation());
1089 it != channelAssignments.end())
1090 channel = it->second;
1091 if (isProgramOwned(endpoint) && channel && col && row)
1092 demand[{*col, *row, *channel}] +=
1093 endpointTaskBDs(endpoint.getOperation());
1094 }
1095 channelDemand = std::move(demand);
1096 return success();
1097 }
1098
1099 /// Gives one more shim end of `exhausted` the channel of ends it is never in
1100 /// flight with, in the order ShimTransferSpans groups them, so only as many
1101 /// ends share as the tile needs. Packet headers tell a shared channel's ends
1102 /// apart.
1103 bool planShimSharing(TileLike exhausted, DMAChannelDir dir) {
1104 ShimTransferSpans spans(
1105 device,
1106 [&](StringAttr symbol) -> std::optional<ShimTransferSpans::End> {
1107 auto endpoint = SymbolTable::lookupNearestSymbolFrom<RouteEndpointOp>(
1108 device, symbol);
1109 if (!endpoint || !endpoint.getFifoName())
1110 return std::nullopt;
1111 return ShimTransferSpans::End{endpoint.getFifoNameAttr(),
1112 endpoint.getRouteDirection() ==
1113 DMAChannelDir::MM2S};
1114 });
1115 SmallVector<Operation *> candidates;
1116 SmallVector<StringAttr> fifos;
1117 for (auto endpoint : device.getOps<RouteEndpointOp>()) {
1118 if (endpoint.getBundle() != WireBundle::DMA ||
1119 endpoint.getChannelIndex() || !endpoint.getFifoName() ||
1120 endpoint.getTile() != exhausted->getResult(0) ||
1121 endpoint.getRouteDirection() != dir)
1122 continue;
1123 candidates.push_back(endpoint);
1124 fifos.push_back(endpoint.getFifoNameAttr());
1125 }
1126 for (auto &members : spans.groups(fifos))
1127 for (unsigned sharer : llvm::drop_begin(members))
1128 if (channelSharers
1129 .insert({candidates[sharer], candidates[members.front()]})
1130 .second)
1131 return true;
1132 return false;
1133 }
1134
1135 /// A MemTile whose endpoints need more buffer descriptors than it has
1136 /// walks its deepest plain pools with one descriptor a side instead, their
1137 /// objects side by side in one buffer and counted by one lock pair: the
1138 /// deepest first, until its descriptors fit.
1139 /// Whether one iterating descriptor a side could walk `pool`: a plain mem
1140 /// tile pool of one segment, deeper than one object and no deeper than a
1141 /// lock counts, whose ends neither dispatch nor take turns, but those in
1142 /// `regrouped`, whose turns are being chosen again.
1143 bool canWalkWhole(ObjectFifoPoolOp pool,
1144 ArrayRef<ObjectFifoDmaEndpointOp> regrouped = {}) {
1145 const AIETargetModel &target = device.getTargetModel();
1146 TileLike tile =
1147 pool ? dyn_cast<TileLike>(pool.getTile().getDefiningOp()) : TileLike();
1148 return tile && tile.isMemTile() && !pool.getContiguous() &&
1149 pool.getSegmentOps().size() == 1 && !pool.getRepeatCount() &&
1150 !pool.getBuffers() && pool.getDepth() > 1 &&
1151 pool.getDepth() <= static_cast<int>(target.getMaxLockValue()) &&
1152 llvm::all_of(device.getOps<ObjectFifoDmaEndpointOp>(),
1153 [&](ObjectFifoDmaEndpointOp end) {
1154 return end.getPoolOp() != pool ||
1155 (!end.getDispatch() &&
1156 (!end.getTurns() ||
1157 llvm::is_contained(regrouped, end)));
1158 });
1159 }
1160
1161 void walkDeepPoolsWhole() {
1162 const AIETargetModel &target = device.getTargetModel();
1163 llvm::MapVector<Value, SmallVector<ObjectFifoDmaEndpointOp>> byTile;
1164 for (auto dma : device.getOps<ObjectFifoDmaEndpointOp>())
1165 if (dma.getTileLike() && dma.getTileLike().isMemTile())
1166 byTile[dma.getTile()].push_back(dma);
1167 for (SmallVector<ObjectFifoDmaEndpointOp> &ends :
1168 llvm::make_second_range(byTile)) {
1169 // A mem tile's BD ids are split between its even and odd channels, and
1170 // channels go to ends in an order of their own, each to the half with
1171 // room. That cannot fail while every end fits beside all the others in
1172 // either half: the ends' BDs and the largest of them within the BDs
1173 // plus one.
1174 TileLike tileLike = ends.front().getTileLike();
1175 std::optional<int> col = tileLike.tryGetCol(), row = tileLike.tryGetRow();
1176 int64_t numBDs = target.getNumBDs(AIETileType::MemTile);
1177 bool halved =
1178 col && row && target.getNumBDsForChannel(*col, *row, 0) < numBDs;
1179 auto fits = [&] {
1180 int64_t bds = 0, largest = 0;
1181 for (ObjectFifoDmaEndpointOp end : ends) {
1182 bds += end.getNumBDs();
1183 largest = std::max(largest, end.getNumBDs());
1184 }
1185 return halved ? bds + largest <= numBDs + 1 : bds <= numBDs;
1186 };
1187 SmallVector<ObjectFifoPoolOp> pools;
1188 for (ObjectFifoDmaEndpointOp end : ends) {
1189 ObjectFifoPoolOp pool = end.getPoolOp();
1190 if (!llvm::is_contained(pools, pool) && canWalkWhole(pool))
1191 pools.push_back(pool);
1192 }
1193 llvm::stable_sort(pools, [&](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
1194 if (a.getDepth() != b.getDepth())
1195 return a.getDepth() > b.getDepth();
1196 return structuralRank.lookup(a) < structuralRank.lookup(b);
1197 });
1198 for (ObjectFifoPoolOp pool : pools) {
1199 if (fits())
1200 break;
1201 pool.setContiguous(true);
1202 }
1203 }
1204 }
1205
1206 /// The other end of the one route naming `endpoint`, when that route is a
1207 /// circuit between two objectFIFO DMA endpoints.
1208 ObjectFifoDmaEndpointOp circuitPeer(ObjectFifoDmaEndpointOp endpoint) {
1209 ObjectFifoDmaEndpointOp peer;
1210 StringRef name = endpoint.getSymName();
1211 for (RouteOp route : device.getOps<RouteOp>()) {
1212 SmallVector<StringRef> sources = llvm::to_vector(route.getSourceNames());
1213 SmallVector<StringRef> dests =
1214 llvm::to_vector(route.getDestinationNames());
1215 bool source = llvm::is_contained(sources, name);
1216 if (!source && !llvm::is_contained(dests, name))
1217 continue;
1218 if (peer || route.getPacket() || sources.size() != 1 || dests.size() != 1)
1219 return {};
1220 RouteEndpoint other = lookupEndpoint(source ? dests[0] : sources[0]);
1221 peer = other ? dyn_cast<ObjectFifoDmaEndpointOp>(other.getOperation())
1222 : ObjectFifoDmaEndpointOp();
1223 if (!peer)
1224 return {};
1225 }
1226 return peer;
1227 }
1228
1229 /// The one core endpoint working through `endpoint`'s pool from the other
1230 /// side.
1231 ObjectFifoCoreEndpointOp coreEndOf(ObjectFifoDmaEndpointOp endpoint) {
1232 ObjectFifoCoreEndpointOp found;
1233 for (auto core : device.getOps<ObjectFifoCoreEndpointOp>()) {
1234 if (core.getPool() != endpoint.getPool())
1235 continue;
1236 if (found || core.drains() == endpoint.drains())
1237 return {};
1238 found = core;
1239 }
1240 return found;
1241 }
1242
1243 static bool canTakeTurns(ObjectFifoDmaEndpointOp endpoint) {
1244 return endpoint && !endpoint.getChannelIndex() && !endpoint.getDispatch() &&
1245 !endpoint.getPacketAttr() && !endpoint.getIterCount() &&
1246 endpoint.getRepeat() == 1 && !endpoint.walksStream();
1247 }
1248
1249 /// Whether a chain moving `turns` over `ends` can walk each contiguous pool
1250 /// with one descriptor per turn of its unrolled passes, each stepping over
1251 /// the others' objects: they must divide the pool's objects.
1252 static bool contiguousWalkersFit(ArrayRef<ObjectFifoDmaEndpointOp> ends,
1253 ArrayRef<unsigned> turns) {
1254 SmallVector<std::pair<int64_t, int64_t>> counts;
1255 for (auto [i, constEnd] : llvm::enumerate(ends)) {
1256 ObjectFifoDmaEndpointOp end = constEnd;
1257 counts.push_back({llvm::count(turns, i), end.getPoolOp().getContiguous()
1258 ? 0
1259 : end.getNumDescriptors()});
1260 }
1261 int64_t passes = turnPasses(counts);
1262 for (auto [i, constEnd] : llvm::enumerate(ends)) {
1263 ObjectFifoDmaEndpointOp end = constEnd;
1264 if (end.getPoolOp().getContiguous() &&
1265 (end.walksStream() ||
1266 end.getPoolOp().getDepth() % (counts[i].first * passes)))
1267 return false;
1268 }
1269 return true;
1270 }
1271
1272 /// Gives `ends` one channel, the k-th object belonging to `ends[order[k]]`.
1273 void takeTurns(MutableArrayRef<ObjectFifoDmaEndpointOp> ends,
1274 ArrayRef<unsigned> order) {
1275 SmallVector<Attribute> names;
1276 for (unsigned i : order)
1277 names.push_back(FlatSymbolRefAttr::get(ends[i].getSymNameAttr()));
1278 ArrayAttr turns = builder.getArrayAttr(names);
1279 Operation *owner = ends[order.front()];
1280 channelSharers.erase(owner);
1281 for (ObjectFifoDmaEndpointOp end : ends) {
1282 end.setTurnsAttr(turns);
1283 if (end != owner)
1284 channelSharers[end] = owner;
1285 }
1286 }
1287
1288 /// Gives one more end of `exhausted` the channel of others whose objects
1289 /// the same core needs in a fixed order, read off its program, so only as
1290 /// many ends share as the tile needs. A circuit carries one stream, so the
1291 /// ends at the routes' other ends take the same turns.
1292 bool planTurnSharing(TileLike exhausted, DMAChannelDir dir) {
1293 // `coreEndOf`/`circuitPeer` each rescan the whole device; neither's
1294 // domain (core endpoints, routes) changes within this function (only
1295 // pool `contiguous` flags and endpoints' `turns` attrs do), so caching
1296 // them once here turns every lookup below from O(device size) into O(1).
1297 DenseMap<Operation *, ObjectFifoCoreEndpointOp> coreEndCache;
1298 DenseMap<Operation *, ObjectFifoDmaEndpointOp> circuitPeerCache;
1299 for (auto dma : device.getOps<ObjectFifoDmaEndpointOp>()) {
1300 coreEndCache[dma] = coreEndOf(dma);
1301 circuitPeerCache[dma] = circuitPeer(dma);
1302 }
1303 auto cachedCoreEnd = [&](ObjectFifoDmaEndpointOp dma) {
1304 return coreEndCache.lookup(dma);
1305 };
1306 auto cachedCircuitPeer = [&](ObjectFifoDmaEndpointOp dma) {
1307 return circuitPeerCache.lookup(dma);
1308 };
1309 SmallVector<ObjectFifoDmaEndpointOp> candidates;
1310 for (auto dma : device.getOps<ObjectFifoDmaEndpointOp>())
1311 if (sameTile(dma.getTile(), exhausted->getResult(0)) &&
1312 dma.getRouteDirection() == dir && cachedCoreEnd(dma) &&
1313 canTakeTurns(dma.getTurns() ? turnEndpoints(dma).front() : dma) &&
1314 canTakeTurns(cachedCircuitPeer(dma)))
1315 candidates.push_back(dma);
1316 llvm::stable_sort(candidates, [&](Operation *a, Operation *b) {
1317 return structuralRank.lookup(a) < structuralRank.lookup(b);
1318 });
1319 LLVM_DEBUG({
1320 llvm::dbgs() << "turns on " << exhausted->getLoc() << ": "
1321 << candidates.size() << " candidate ends\n";
1322 for (auto dma : device.getOps<ObjectFifoDmaEndpointOp>())
1323 if (sameTile(dma.getTile(), exhausted->getResult(0)) &&
1324 dma.getRouteDirection() == dir)
1325 llvm::dbgs() << " " << dma.getSymName() << ": core end "
1326 << bool(cachedCoreEnd(dma)) << ", takes turns "
1327 << canTakeTurns(dma) << ", peer takes turns "
1328 << canTakeTurns(cachedCircuitPeer(dma)) << "\n";
1329 });
1330 auto groupOf = [](ObjectFifoDmaEndpointOp end) {
1331 SmallVector<ObjectFifoDmaEndpointOp> group = turnEndpoints(end);
1332 return group.empty() ? SmallVector<ObjectFifoDmaEndpointOp>{end} : group;
1333 };
1334 for (auto [i, a] : llvm::enumerate(candidates)) {
1335 for (ObjectFifoDmaEndpointOp b : llvm::drop_begin(candidates, i + 1)) {
1336 SmallVector<ObjectFifoDmaEndpointOp> group = groupOf(a);
1337 if (llvm::is_contained(group, b))
1338 continue;
1339 llvm::append_range(group, groupOf(b));
1340 SmallVector<ObjectFifoCoreEndpointOp> cores;
1341 SmallVector<ObjectFifoDmaEndpointOp> peers;
1342 SmallVector<int64_t> depths;
1343 for (ObjectFifoDmaEndpointOp end : group) {
1344 cores.push_back(cachedCoreEnd(end));
1345 peers.push_back(cachedCircuitPeer(end));
1346 depths.push_back(end.getPoolOp().getDepth());
1347 }
1348 if (llvm::any_of(peers, [&](ObjectFifoDmaEndpointOp peer) {
1349 return !sameTile(peer.getTile(), peers.front().getTile()) ||
1350 llvm::any_of(turnEndpoints(peer), [&](auto other) {
1351 return !llvm::is_contained(peers, other);
1352 });
1353 })) {
1354 LLVM_DEBUG(llvm::dbgs()
1355 << " " << a.getSymName() << " + " << b.getSymName()
1356 << ": senders on different tiles\n");
1357 continue;
1358 }
1359 CoreTurns turns;
1360 std::string reason;
1361 if (succeeded(turns.read(cores, reason)) && !turns.keepsUp(depths))
1362 reason = "would stall";
1363 // Over plain pools the chain unrolls to whole passes over each; past
1364 // the peers' channel BDs, walking them whole takes one a turn.
1365 bool overflows = false;
1366 if (reason.empty()) {
1367 SmallVector<std::pair<int64_t, int64_t>> counts;
1368 for (auto [i, constPeer] : llvm::enumerate(peers)) {
1369 ObjectFifoDmaEndpointOp peer = constPeer;
1370 counts.push_back({llvm::count(turns.turns(), i),
1371 peer.getPoolOp().getContiguous()
1372 ? 0
1373 : peer.getNumDescriptors()});
1374 }
1375 TileLike tile = peers.front().getTileLike();
1376 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
1377 overflows =
1378 col && row &&
1379 turnPasses(counts) * static_cast<int64_t>(turns.turns().size()) >
1380 device.getTargetModel().getNumBDsForChannel(*col, *row, 0);
1381 }
1382 // Beside a pool walked whole, the others' descriptors must come round
1383 // each pass, which walking them whole as well lines up.
1384 // A pool whose other end already takes turns may be walked whole too,
1385 // where those turns still line up.
1386 SmallVector<ObjectFifoPoolOp> walked;
1387 if (reason.empty() &&
1388 (overflows || !contiguousWalkersFit(peers, turns.turns())))
1389 for (ObjectFifoDmaEndpointOp peer : peers) {
1390 ObjectFifoPoolOp pool = peer.getPoolOp();
1391 SmallVector<ObjectFifoDmaEndpointOp> others;
1392 for (auto end : device.getOps<ObjectFifoDmaEndpointOp>())
1393 if (end.getPoolOp() == pool && end.getTurns() &&
1394 !llvm::is_contained(peers, end))
1395 others.push_back(end);
1396 SmallVector<ObjectFifoDmaEndpointOp> ignored(peers);
1397 llvm::append_range(ignored, others);
1398 if (!canWalkWhole(pool, ignored)) {
1399 LLVM_DEBUG(llvm::dbgs() << " " << pool.getSymName()
1400 << " cannot be walked whole\n");
1401 continue;
1402 }
1403 pool.setContiguous(true);
1404 bool linedUp =
1405 llvm::all_of(others, [&](ObjectFifoDmaEndpointOp end) {
1406 SmallVector<ObjectFifoDmaEndpointOp> ends =
1407 turnEndpoints(end);
1408 SmallVector<unsigned> order;
1409 for (auto name :
1410 end.getTurnsAttr().getAsRange<FlatSymbolRefAttr>())
1411 order.push_back(
1412 llvm::find_if(ends,
1413 [&](ObjectFifoDmaEndpointOp e) {
1414 return e.getSymNameAttr() ==
1415 name.getAttr();
1416 }) -
1417 ends.begin());
1418 return contiguousWalkersFit(ends, order);
1419 });
1420 LLVM_DEBUG(
1421 llvm::dbgs()
1422 << " " << pool.getSymName() << " walked whole"
1423 << (linedUp ? "" : ", but its other turns would not line up")
1424 << "\n");
1425 if (linedUp)
1426 walked.push_back(pool);
1427 else
1428 pool.setContiguous(false);
1429 }
1430 if (reason.empty() && (!contiguousWalkersFit(group, turns.turns()) ||
1431 !contiguousWalkersFit(peers, turns.turns()))) {
1432 reason = "a contiguous pool's objects do not divide among its turns";
1433 for (ObjectFifoPoolOp pool : walked)
1434 pool.setContiguous(false);
1435 }
1436 if (!reason.empty()) {
1437 LLVM_DEBUG(llvm::dbgs() << " " << a.getSymName() << " + "
1438 << b.getSymName() << ": " << reason << "\n");
1439 continue;
1440 }
1441 takeTurns(group, turns.turns());
1442 takeTurns(peers, turns.turns());
1443 return true;
1444 }
1445 }
1446 return false;
1447 }
1448
1449 /// A route reaching or leaving a shared shim channel carries a packet
1450 /// header, its id distinct from those of the routes it shares the channel
1451 /// with. Other shared channels reuse the same ids: the router keeps flows
1452 /// with one id apart wherever their destinations differ.
1453 LogicalResult headShimSharingRoutes() {
1454 DenseMap<Operation *, Operation *> channelOf;
1455 for (auto [sharer, owner] : channelSharers) {
1456 if (!tileOf(cast<RouteEndpoint>(owner)).isShimTile())
1457 continue;
1458 channelOf[sharer] = owner;
1459 channelOf[owner] = owner;
1460 }
1461 auto channelsOf = [&](RouteOp route) {
1462 SmallVector<Operation *> channels;
1463 auto note = [&](StringRef name) {
1464 if (auto endpoint = lookupEndpoint(name);
1465 endpoint && channelOf.count(endpoint.getOperation()))
1466 channels.push_back(channelOf.lookup(endpoint.getOperation()));
1467 };
1468 llvm::for_each(route.getSourceNames(), note);
1469 llvm::for_each(route.getDestinationNames(), note);
1470 return channels;
1471 };
1472 DenseMap<Operation *, DenseSet<int>> idsOn;
1473 for (auto route : device.getOps<RouteOp>())
1474 if (PacketInfoAttr packet = route.getPacketAttr();
1475 packet && packet.isAssigned())
1476 for (Operation *channel : channelsOf(route))
1477 idsOn[channel].insert(packet.assignedId());
1478 PacketIdSpace ids(device);
1479 SmallVector<int> reused;
1480 for (auto route : device.getOps<RouteOp>()) {
1481 SmallVector<Operation *> channels = channelsOf(route);
1482 if (route.getPacket() || channels.empty())
1483 continue;
1484 auto freeOnAll = [&](int id) {
1485 return llvm::none_of(channels, [&](Operation *channel) {
1486 return idsOn[channel].contains(id);
1487 });
1488 };
1489 std::optional<int> id;
1490 if (auto *it = llvm::find_if(reused, freeOnAll); it != reused.end())
1491 id = *it;
1492 else if ((id = ids.takeLowestFrom()))
1493 reused.push_back(*id);
1494 else
1495 return route.emitOpError("shares a shim channel, but no packet id is "
1496 "left to tell its transfers apart");
1497 for (Operation *channel : channels)
1498 idsOn[channel].insert(*id);
1499 route.setPacketAttr(PacketInfoAttr::get(builder.getContext(), 0,
1500 static_cast<uint16_t>(*id)));
1501 }
1502 return success();
1503 }
1504
1505 /// Channels are chosen by BD room but never refused for lack of it: a task
1506 /// that frees its BDs before the next starts lets demand exceed the BDs.
1507 /// Otherwise BD id assignment fails, so say where.
1508 void warnOvercommittedBDs() {
1509 const AIETargetModel &target = device.getTargetModel();
1510 for (auto endpoint : device.getOps<RouteEndpoint>()) {
1511 auto it = channelAssignments.find(endpoint.getOperation());
1512 TileLike tile = tileOf(endpoint);
1513 std::optional<int> col = tile.tryGetCol(), row = tile.tryGetRow();
1514 if (it == channelAssignments.end() || !isProgramOwned(endpoint) || !col ||
1515 !row)
1516 continue;
1517 int channel = it->second;
1518 int64_t room = bdRoom(tile, *col, *row, channel, channelDemand);
1519 if (room >= 0)
1520 continue;
1521 int64_t numBDs = target.getNumBDsForChannel(*col, *row, channel);
1522 endpoint->emitWarning("channel ")
1523 << channel << " can use " << numBDs
1524 << " BDs, but it and the channels sharing them need " << numBDs - room
1525 << " at once; BD id assignment fails unless tasks free their BDs "
1526 "before others start";
1527 }
1528 }
1529
1530 /// Keep a successful largest-first allocation unchanged. On channel
1531 /// exhaustion, try keeping an affected pool's resources local, cheapest
1532 /// first. Existing buffers and locks never move. Each DMA accesses every
1533 /// object in its pool, even when it selects only one segment. Reserving that
1534 /// pool once can therefore free several restricted channels. This is a
1535 /// bounded repair of the greedy buffer placement, not an exhaustive solver
1536 /// for tile placement or memory packing.
1537 LogicalResult planAllocation(ArrayRef<ObjectFifoPoolOp> pools,
1538 std::set<std::vector<unsigned>> &tried) {
1539 std::vector<unsigned> key;
1540 for (auto [index, pool] : llvm::enumerate(pools)) {
1541 if (auto tile = localPools.lookup(pool)) {
1542 auto placed = cast<TileLike>(tile.getDefiningOp());
1543 auto col = placed.tryGetCol(), row = placed.tryGetRow();
1544 assert(col && row && "locality repair requires resolved coordinates");
1545 key.push_back(index);
1546 key.push_back(*col);
1547 key.push_back(*row);
1548 }
1549 }
1550 if (tried.size() >= 64 || !tried.insert(key).second ||
1551 failed(planBuffers(pools)) || failed(planLocks(pools)))
1552 return failure();
1553 DMAChannelAnalysis channels(device);
1554 if (succeeded(assignChannels(channels, /*diagnose=*/false)))
1555 return success();
1556 if (!channelFailure || !tileOf(channelFailure).isMemTile() ||
1557 !tileOf(channelFailure).tryGetCol() ||
1558 !tileOf(channelFailure).tryGetRow())
1559 return failure();
1560
1561 Value localTile = channelFailure.getTile();
1562 SmallVector<ObjectFifoPoolOp> candidates;
1563 for (auto dma : device.getOps<ObjectFifoDmaEndpointOp>()) {
1564 auto endpoint = cast<RouteEndpoint>(dma.getOperation());
1565 auto pool = dma.getPoolOp();
1566 if (!pool || localPools.contains(pool) ||
1567 !sameTile(endpoint.getTile(), channelFailure.getTile()) ||
1568 endpoint.getRouteDirection() != channelFailure.getRouteDirection() ||
1569 (channelFailure.getRouteChannel() && endpoint != channelFailure) ||
1570 (endpoint.getRouteChannel() && endpoint != channelFailure) ||
1571 !reachesAdjacentTile(endpoint) ||
1572 llvm::is_contained(candidates, pool))
1573 continue;
1574 candidates.push_back(pool);
1575 }
1576 llvm::stable_sort(candidates, [&](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
1577 int64_t aBytes =
1578 a.getBuffers() ? 0 : a.getObjectSizeInBytes() * a.getDepth();
1579 int64_t bBytes =
1580 b.getBuffers() ? 0 : b.getObjectSizeInBytes() * b.getDepth();
1581 if (aBytes != bBytes)
1582 return aBytes < bBytes;
1583 return structuralRank.lookup(a) < structuralRank.lookup(b);
1584 });
1585 for (auto pool : candidates) {
1586 localPools[pool] = localTile;
1587 if (succeeded(planAllocation(pools, tried)))
1588 return success();
1589 localPools.erase(pool);
1590 }
1591 return failure();
1592 }
1593
1594 /// A packet-switched flow shares the stream with others, so every buffer
1595 /// descriptor the source emits has to carry the packet header.
1596 /// One packet flow carries every source of the route: each stamps the same
1597 /// header, and their packets share the destination channels.
1598 LogicalResult lowerPacketFlow(RouteOp flow, ArrayRef<RouteEndpoint> sources,
1599 PacketInfoAttr header) {
1600 int packetID = header.assignedId();
1601 for (auto [name, endpoint] : llvm::zip_equal(
1602 flow.getSources().getAsRange<FlatSymbolRefAttr>(), sources)) {
1603 RouteEndpoint source = endpoint;
1604 // Written out in turn order after the last route.
1605 if (auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(*source);
1606 dma && dma.getDispatch()) {
1607 dispatchHeaders[dma][*flow.getDestinationNames().begin()] = header;
1608 continue;
1609 }
1610 if (sourcedFlows.at(name.getAttr()).size() == 1)
1611 source.setRoutePacket(header);
1612 }
1613
1614 builder.setInsertionPoint(flow);
1615 auto packetFlow = PacketFlowOp::create(
1616 builder, flow.getLoc(),
1617 builder.getIntegerAttr(builder.getI8Type(), packetID),
1618 flow.getKeepPktHeader() ? builder.getBoolAttr(true) : nullptr, nullptr);
1619 OpBuilder::InsertionGuard g(builder);
1620 Block &ports = packetFlow.getRegion().emplaceBlock();
1621 builder.setInsertionPointToStart(&ports);
1622 EndOp::create(builder, flow.getLoc());
1623
1624 builder.setInsertionPointToStart(&ports);
1625 for (RouteEndpoint source : sources) {
1626 PacketSourceOp::create(builder, flow.getLoc(), source.getTile(),
1627 source.getRouteBundle(), channelOf(source));
1628 }
1629 for (StringRef destName : flow.getDestinationNames()) {
1630 auto dest = lookupEndpoint(destName);
1631 PacketDestOp::create(builder, flow.getLoc(), dest.getTile(),
1632 dest.getRouteBundle(), channelOf(dest));
1633 }
1634 return success();
1635 }
1636
1637 /// Assigned before flows are lowered, so every endpoint has one by now.
1638 int channelOf(RouteEndpoint endpoint) {
1639 std::optional<int> channel = endpoint.getRouteChannel();
1640 assert(channel && "channels are assigned before flows are lowered");
1641 return *channel;
1642 }
1643
1644 RouteEndpoint lookupEndpoint(StringRef name) {
1645 return dyn_cast_or_null<RouteEndpoint>(SymbolTable::lookupNearestSymbolFrom(
1646 device, StringAttr::get(device.getContext(), name)));
1647 }
1648
1649 LogicalResult lowerFlows() {
1650 DenseSet<std::tuple<Value, int, Value, int>> circuits;
1651 for (RouteOp flow : device.getOps<RouteOp>())
1652 for (auto name : flow.getSources().getAsRange<FlatSymbolRefAttr>())
1653 sourcedFlows[name.getAttr()].push_back(flow);
1654 for (auto flow : device.getOps<RouteOp>()) {
1655 SmallVector<RouteEndpoint> sources;
1656 for (StringRef name : flow.getSourceNames()) {
1657 sources.push_back(lookupEndpoint(name));
1658 }
1659 loweredFlows.push_back(flow);
1660
1661 // Which switching a route gets was settled upstream of this pass: the
1662 // header is on the route, and --aie-assign-packet-ids gave it its id.
1663 if (std::optional<PacketInfoAttr> packet = flow.getPacket()) {
1664 if (!packet->isAssigned()) {
1665 return flow.emitOpError("has a packet header with no pkt_id; run "
1666 "--aie-assign-packet-ids before this pass");
1667 }
1668 if (failed(lowerPacketFlow(flow, sources, *packet))) {
1669 return failure();
1670 }
1671 continue;
1672 }
1673
1674 // The verifier keeps a circuit route to one source.
1675 RouteEndpoint source = sources.front();
1676
1677 int sourceChannel = channelOf(source);
1678 builder.setInsertionPoint(flow);
1679 for (StringRef destName : flow.getDestinationNames()) {
1680 auto dest = lookupEndpoint(destName);
1681 // Ends taking turns share one circuit for all their routes.
1682 if (!circuits
1683 .insert({source.getTile(), sourceChannel, dest.getTile(),
1684 channelOf(dest)})
1685 .second)
1686 continue;
1687 FlowOp::create(builder, flow.getLoc(), source.getTile(),
1688 source.getRouteBundle(), sourceChannel, dest.getTile(),
1689 dest.getRouteBundle(), channelOf(dest));
1690 }
1691 }
1692 for (auto &[endpoint, byDest] : dispatchHeaders) {
1693 SmallVector<Attribute> headers;
1694 for (StringRef dest :
1695 endpoint.getDispatchAttr().getAsValueRange<FlatSymbolRefAttr>())
1696 headers.push_back(byDest.lookup(dest));
1697 endpoint.setDispatchPacketsAttr(builder.getArrayAttr(headers));
1698 }
1699 return success();
1700 }
1701
1702 void collectRearmUsers() {
1703 device.walk([&](Operation *op) {
1704 if (op->getName().getStringRef() != "aiex.dma_channel_reset_for") {
1705 return;
1706 }
1707 auto sym = op->getAttrOfType<FlatSymbolRefAttr>("objfifo");
1708 if (!sym) {
1709 return;
1710 }
1711 // Split may already have pointed this at the fifo's shim endpoint.
1712 StringRef name = sym.getValue();
1713 if (auto endpoint = lookupEndpoint(sym.getValue())) {
1714 if (auto fifoName = endpoint.getFifoName()) {
1715 name = *fifoName;
1716 }
1717 }
1718 rearmUsers[name].push_back(op);
1719 });
1720 }
1721
1722 /// An `aiex.dma_channel_reset_for` outlives the fifo it names, so record the
1723 /// channels and locks it has to re-arm and point it at that record. Shim
1724 /// endpoints are left out: the host re-pushes those itself.
1725 LogicalResult bindRearmTargets() {
1726 if (rearmUsers.empty()) {
1727 return success();
1728 }
1729
1730 builder.setInsertionPoint(device.getBody()->getTerminator());
1731 for (auto &[fifoName, users] : rearmUsers) {
1732 SmallVector<Value> channelTiles, lockValues;
1733 SmallVector<int32_t> channelDirs, channelIndices, lockInits;
1734
1735 for (auto endpoint : device.getOps<RouteEndpoint>()) {
1736 std::optional<int> channel = endpoint.getRouteChannel();
1737 if (endpoint.getFifoName() != fifoName ||
1738 tileOf(endpoint).isShimTile() || !channel) {
1739 continue;
1740 }
1741 channelTiles.push_back(endpoint.getTile());
1742 channelDirs.push_back(
1743 static_cast<int32_t>(endpoint.getRouteDirection()));
1744 channelIndices.push_back(*channel);
1745 }
1746 for (auto pool : device.getOps<ObjectFifoPoolOp>()) {
1747 if (pool.getFifoName() != fifoName || pool.getTileLike().isShimTile()) {
1748 continue;
1749 }
1750 for (LockOp lock : pool.getLockOps()) {
1751 lockValues.push_back(lock.getResult());
1752 lockInits.push_back(lock.getInit().value_or(0));
1753 }
1754 }
1755
1756 if (channelTiles.empty() && lockValues.empty()) {
1757 for (Operation *user : users) {
1758 user->emitOpError() << "objectFIFO '" << fifoName
1759 << "' has no resident core/mem DMA channels or "
1760 "locks to re-arm";
1761 }
1762 return failure();
1763 }
1764
1765 std::string name = (fifoName + "_rearm").str();
1766 for (unsigned suffix = 0; lookupNamedOpIn(device, StringRef(name));
1767 suffix++) {
1768 name = (fifoName + "_rearm_" + std::to_string(suffix)).str();
1769 }
1770
1771 // head_bd_ids and repeat_counts are filled in by --aie-assign-bd-ids.
1772 ObjectFifoRearmBindingOp::create(
1773 builder, device.getLoc(), builder.getStringAttr(name),
1774 ValueRange(channelTiles), ValueRange(lockValues),
1775 builder.getDenseI32ArrayAttr(channelDirs),
1776 builder.getDenseI32ArrayAttr(channelIndices),
1777 builder.getDenseI32ArrayAttr(lockInits),
1778 /*head_bd_ids=*/DenseI32ArrayAttr(),
1779 /*repeat_counts=*/DenseI32ArrayAttr());
1780 auto target = FlatSymbolRefAttr::get(builder.getContext(), name);
1781 for (Operation *user : users) {
1782 user->setAttr("objfifo", target);
1783 }
1784 }
1785 return success();
1786 }
1787
1788 /// Give each dma_start naming an endpoint the channel it was assigned.
1789 LogicalResult resolveStart(DMAStartOp start, RouteEndpointOp endpoint,
1790 ArrayRef<RouteOp> flows, ArrayRef<int> ids,
1791 int channel) {
1792 DMAChannelDir dir = endpoint.getRouteDirection();
1793 if (start.getChannelDir() != dir)
1794 return start.emitOpError("starts ")
1795 << stringifyDMAChannelDir(start.getChannelDir()) << " on "
1796 << start.getEndpoint() << ", but its route makes it "
1797 << stringifyDMAChannelDir(dir);
1798 // Only a runtime task's BDs are given the packet header, so a DMA
1799 // program states it in each BD; --aie-assign-packet-ids made it pin one.
1800 bool packet = !flows.empty() && RouteOp(flows.front()).getPacket();
1801 if (packet) {
1802 SmallVector<DMABDOp> bds;
1803 for (Block *block : chainOf(start))
1804 llvm::append_range(bds, block->getOps<DMABDOp>());
1805 if (failed(checkHeaders(endpoint, ids, bds)))
1806 return failure();
1807 }
1808 start.setChannelAttr(builder.getI32IntegerAttr(channel));
1809 return success();
1810 }
1811
1812 /// A packet source that no one header is stamped on needs each of `bds` to
1813 /// state the header of one of its flows, whose pinned ids are `ids`.
1814 LogicalResult checkHeaders(RouteEndpointOp endpoint, ArrayRef<int> ids,
1815 ArrayRef<DMABDOp> bds) {
1816 for (DMABDOp bd : bds) {
1817 std::optional<int> id;
1818 if (auto packet = bd.getPacket())
1819 id = packet->getPktId();
1820 else if (auto ops = bd->getBlock()->getOps<DMABDPACKETOp>(); !ops.empty())
1821 id = (*ops.begin()).getPacketId();
1822 if (id && llvm::is_contained(ids, *id))
1823 continue;
1824 return bd.emitOpError("sends on @")
1825 << endpoint.getSymName()
1826 << " without the packet header of one of its flows, ids " << ids;
1827 }
1828 return success();
1829 }
1830
1831 /// Each runtime task or transfer that sends on a shared source must state
1832 /// one of its flows' headers; a DMA program's start is resolveStart's, and
1833 /// other users send nothing.
1834 LogicalResult checkSharedSourceUsers(RouteEndpointOp endpoint,
1835 ArrayRef<int> ids) {
1836 auto uses = SymbolTable::getSymbolUses(endpoint, device);
1837 if (!uses)
1838 return success();
1839 for (const SymbolTable::SymbolUse &use : *uses) {
1840 Operation *user = use.getUser();
1841 if (auto task = dyn_cast<AIEX::DMAConfigureTaskForOp>(user)) {
1842 SmallVector<DMABDOp> bds;
1843 task.getBody().walk([&](DMABDOp bd) { bds.push_back(bd); });
1844 if (failed(checkHeaders(endpoint, ids, bds)))
1845 return failure();
1846 continue;
1847 }
1848 if (auto start = dyn_cast<AIEX::DMAStartBdChainForOp>(user)) {
1849 auto chain = device.lookupSymbol<BDChainOp>(start.getSymbol());
1850 if (!chain)
1851 return start.emitOpError("symbol does not reference valid BD chain");
1852 SmallVector<DMABDOp> bds;
1853 chain.getBody().walk([&](DMABDOp bd) { bds.push_back(bd); });
1854 if (failed(checkHeaders(endpoint, ids, bds)))
1855 return failure();
1856 continue;
1857 }
1858 auto memcpy = dyn_cast<AIEX::NpuDmaMemcpyNdOp>(user);
1859 if (!memcpy)
1860 continue;
1861 std::optional<PacketInfoAttr> packet = memcpy.getPacket();
1862 if (packet && llvm::is_contained(ids, packet->getPktId()))
1863 continue;
1864 return memcpy.emitOpError("sends on @")
1865 << endpoint.getSymName()
1866 << ", a source several packet routes share, which has no one "
1867 "header to stamp; it needs a packet header of its own, one of "
1868 "ids "
1869 << ids;
1870 }
1871 return success();
1872 }
1873
1874 /// A runtime task naming an endpoint is configured on the endpoint's tile
1875 /// and channel directly.
1876 void resolveTask(AIEX::DMAConfigureTaskForOp task, RouteEndpointOp endpoint,
1877 int channel) {
1878 builder.setInsertionPoint(task);
1879 auto configured = AIEX::DMAConfigureTaskOp::create(
1880 builder, task.getLoc(), builder.getIndexType(), endpoint.getTile(),
1881 DMAChannelDirAttr::get(builder.getContext(),
1882 endpoint.getRouteDirection()),
1883 builder.getI32IntegerAttr(channel),
1884 builder.getBoolAttr(task.getIssueToken()),
1885 builder.getI32IntegerAttr(task.getRepeatCount()),
1886 task.getRepeatCountVal(), endpoint.getPacketAttr());
1887 task.getResult().replaceAllUsesWith(configured.getResult());
1888 configured.getBody().takeBody(task.getBody());
1889 task.erase();
1890 }
1891
1892 /// Rewrite the programs and tasks naming an endpoint to its channel while
1893 /// the route giving its direction is still there. A shim end with a fifo
1894 /// name keeps its tasks, which emitShimAllocations points at the allocation.
1895 LogicalResult resolveEndpointUses() {
1896 for (auto endpoint : device.getOps<RouteEndpointOp>()) {
1897 auto starts = endpointStarts.lookup(endpoint);
1898 auto tasks = endpointTasks.lookup(endpoint);
1899 auto sourced = sourcedFlows.find(endpoint.getSymNameAttr());
1900 ArrayRef<RouteOp> flows;
1901 if (sourced != sourcedFlows.end())
1902 flows = sourced->second;
1903 SmallVector<int> ids;
1904 for (RouteOp flow : flows)
1905 if (std::optional<PacketInfoAttr> packet = flow.getPacket();
1906 packet && packet->isAssigned())
1907 ids.push_back(packet->assignedId());
1908 bool shared = flows.size() > 1;
1909 if (shared && failed(checkSharedSourceUsers(endpoint, ids)))
1910 return failure();
1911 if (starts.empty() && tasks.empty())
1912 continue;
1913 std::optional<int> channel = endpoint.getRouteChannel();
1914 assert(channel && "assignChannels gives every routed endpoint one");
1915 for (DMAStartOp start : starts)
1916 if (failed(resolveStart(start, endpoint, flows, ids, *channel)))
1917 return failure();
1918 if (endpoint.getTileLike().isShimTile() && endpoint.getFifoName())
1919 continue;
1920 for (auto task : tasks)
1921 resolveTask(task, endpoint, *channel);
1922 }
1923 return success();
1924 }
1925
1926 /// A shim endpoint has no memory of its own, so the runtime needs its channel
1927 /// spelled out under the name the sequence refers to.
1928 void emitShimAllocations() {
1929 builder.setInsertionPoint(device.getBody()->getTerminator());
1930 // A fifo's first shim end takes the name the runtime sequence was pointed
1931 // at. Any further shim end of the same fifo, as when a core broadcasts to
1932 // two shim tiles, is a different tile and channel and needs a record of
1933 // its own; sharing the first would send the runtime to the wrong tile.
1934 llvm::StringMap<unsigned> shimEndsSeen;
1935 for (auto endpoint : device.getOps<RouteEndpoint>()) {
1936 std::optional<StringRef> fifoName = endpoint.getFifoName();
1937 std::optional<int> channel = endpoint.getRouteChannel();
1938 if (!tileOf(endpoint).isShimTile() || !fifoName || !channel) {
1939 continue;
1940 }
1941 std::string name = (*fifoName + "_shim_alloc").str();
1942 unsigned ordinal = shimEndsSeen[*fifoName]++;
1943 bool create = true;
1944 if (ordinal == 0) {
1945 // A record under this name may already be there, declared by hand.
1946 create = !SymbolTable::lookupNearestSymbolFrom<ShimDMAAllocationOp>(
1947 device, builder.getStringAttr(name));
1948 } else {
1949 for (unsigned suffix = ordinal - 1; device.lookupSymbol(name);
1950 suffix++) {
1951 name = (*fifoName + "_shim_alloc_" + std::to_string(suffix)).str();
1952 }
1953 }
1954 if (create) {
1955 ShimDMAAllocationOp::create(
1956 builder, endpoint.getLoc(), builder.getStringAttr(name),
1957 endpoint.getTile(),
1958 DMAChannelDirAttr::get(builder.getContext(),
1959 endpoint.getRouteDirection()),
1960 builder.getI64IntegerAttr(*channel),
1961 builder.getBoolAttr(endpoint.getRouteBundle() == WireBundle::PLIO),
1962 endpoint->getAttrOfType<PacketInfoAttr>("packet"));
1963 }
1964 // The runtime sequence reaches the fifo through this record.
1965 (void)SymbolTable::replaceAllSymbolUses(
1966 endpoint, builder.getStringAttr(name), device);
1967 }
1968 }
1969
1970 /// Alternative packing order: prioritize DMA demand within each home tile,
1971 /// but merge those lists by object size to avoid globally prioritizing small
1972 /// pools over large objects on unrelated tiles. This is only a heuristic;
1973 /// the same buffer, lock and channel planner validates both orders.
1974 SmallVector<ObjectFifoPoolOp>
1975 demandOrderedPools(ArrayRef<ObjectFifoPoolOp> pools) {
1976 SmallVector<ObjectFifoPoolOp> ordered(pools);
1977 SmallVector<size_t> memTileSlots;
1978 SmallVector<Value> memTileHomes;
1979 DenseMap<Value, SmallVector<ObjectFifoPoolOp>> memTilePools;
1980 DenseMap<Operation *, std::pair<int, int>> poolChannelDemand;
1981 for (auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>()) {
1982 ObjectFifoPoolOp pool = endpoint.getPoolOp();
1983 if (!sameTile(endpoint.getTile(), pool.getTile()))
1984 continue;
1985 auto &demand = poolChannelDemand[pool];
1986 if (endpoint.getRouteDirection() == DMAChannelDir::S2MM)
1987 ++demand.first;
1988 else
1989 ++demand.second;
1990 }
1991 for (auto [index, pool] : llvm::enumerate(ordered)) {
1992 if (pool.getTileLike().isMemTile()) {
1993 memTileSlots.push_back(index);
1994 Value home = pool.getTile();
1995 for (Value known : memTileHomes)
1996 if (sameTile(home, known)) {
1997 home = known;
1998 break;
1999 }
2000 if (!memTilePools.count(home))
2001 memTileHomes.push_back(home);
2002 memTilePools[home].push_back(pool);
2003 }
2004 }
2005 DenseMap<Value, size_t> nextPool;
2006 auto demand = [&](ObjectFifoPoolOp pool) {
2007 auto [input, output] = poolChannelDemand.lookup(pool.getOperation());
2008 return std::max(input, output);
2009 };
2010 for (Value home : memTileHomes) {
2011 auto &tilePools = memTilePools[home];
2012 llvm::stable_sort(tilePools, [&](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
2013 if (demand(a) != demand(b))
2014 return demand(a) > demand(b);
2015 return a.getObjectSizeInBytes() > b.getObjectSizeInBytes();
2016 });
2017 }
2018 for (size_t slot : memTileSlots) {
2019 Value bestHome;
2020 ObjectFifoPoolOp bestPool;
2021 for (Value home : memTileHomes) {
2022 auto &tilePools = memTilePools[home];
2023 size_t index = nextPool[home];
2024 if (index >= tilePools.size())
2025 continue;
2026 ObjectFifoPoolOp pool = tilePools[index];
2027 if (!bestPool ||
2028 pool.getObjectSizeInBytes() > bestPool.getObjectSizeInBytes() ||
2029 (pool.getObjectSizeInBytes() == bestPool.getObjectSizeInBytes() &&
2030 demand(pool) > demand(bestPool))) {
2031 bestHome = home;
2032 bestPool = pool;
2033 }
2034 }
2035 ordered[slot] = bestPool;
2036 ++nextPool[bestHome];
2037 }
2038 return ordered;
2039 }
2040
2041 void runOnOperation() override {
2042 device = getOperation();
2043 builder = OpBuilder(device.getContext());
2044 // One pass instance serves every device in the module, and none of this
2045 // state means anything outside the device it was gathered from.
2046 lastPlaced.clear();
2047 filledPools.clear();
2048 rearmUsers.clear();
2049 loweredFlows.clear();
2050 sourcedFlows.clear();
2051 localPools.clear();
2052 poolUsers.clear();
2053 lockUsers.clear();
2054 lockPlacements.clear();
2055 spillTiles.clear();
2056
2057 if (failed(collectFixedMemory()) || failed(checkStreamPorts()))
2058 return signalPassFailure();
2059 collectChannelPrograms();
2060 rankStructurally();
2061 walkDeepPoolsWhole();
2062
2063 // Preserve successful largest-first allocations, including their locality
2064 // repairs. Only try demand ordering when that search fails.
2065 SmallVector<ObjectFifoPoolOp> pools(device.getOps<ObjectFifoPoolOp>());
2066 llvm::stable_sort(pools, [&](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
2067 return structuralRank.lookup(a) < structuralRank.lookup(b);
2068 });
2069 SmallVector<size_t> memTileSlots;
2070 SmallVector<ObjectFifoPoolOp> memTilePools;
2071 for (auto [index, pool] : llvm::enumerate(pools)) {
2072 if (pool.getTileLike().isMemTile()) {
2073 memTileSlots.push_back(index);
2074 memTilePools.push_back(pool);
2075 }
2076 }
2077 llvm::stable_sort(memTilePools, [](ObjectFifoPoolOp a, ObjectFifoPoolOp b) {
2078 return a.getObjectSizeInBytes() > b.getObjectSizeInBytes();
2079 });
2080 for (auto [slot, pool] : llvm::zip(memTileSlots, memTilePools)) {
2081 pools[slot] = pool;
2082 }
2083
2084 collectRearmUsers();
2085 for (auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>()) {
2086 poolUsers[endpoint.getPoolOp()].push_back(endpoint.getTile());
2087 collectLockUsers(endpoint);
2088 if (!endpoint.drains()) {
2089 filledPools.insert(endpoint.getPoolOp());
2090 }
2091 }
2092 for (auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>()) {
2093 poolUsers[endpoint.getPoolOp()].push_back(endpoint.getTile());
2094 collectLockUsers(endpoint);
2095 if (!endpoint.drains()) {
2096 filledPools.insert(endpoint.getPoolOp());
2097 }
2098 }
2099 for (auto &[group, users] : lockUsers)
2100 llvm::stable_sort(users, [&](Operation *a, Operation *b) {
2101 return structuralRank.lookup(a) < structuralRank.lookup(b);
2102 });
2103
2104 std::set<std::vector<unsigned>> tried;
2105 LogicalResult allocated = planAllocation(pools, tried);
2106 if (failed(allocated)) {
2107 auto demandOrdered = demandOrderedPools(pools);
2108 if (!llvm::equal(pools, demandOrdered)) {
2109 // Memoized locality sets are specific to a packing order.
2110 localPools.clear();
2111 tried.clear();
2112 allocated = planAllocation(demandOrdered, tried);
2113 }
2114 }
2115 // Out of channels, ends that never need theirs at once take turns on one,
2116 // a tile and direction at a time: on a shim tile, ends never in flight
2117 // together; elsewhere, ends whose objects one core needs in a fixed order.
2118 while (failed(allocated) && channelFailure &&
2119 (tileOf(channelFailure).isShimTile()
2120 ? planShimSharing(tileOf(channelFailure),
2121 channelFailure.getRouteDirection())
2122 : planTurnSharing(tileOf(channelFailure),
2123 channelFailure.getRouteDirection()))) {
2124 localPools.clear();
2125 tried.clear();
2126 allocated = planAllocation(pools, tried);
2127 }
2128 if (succeeded(allocated) && !channelSharers.empty() &&
2129 failed(headShimSharingRoutes()))
2130 return signalPassFailure();
2131 if (failed(allocated)) {
2132 localPools.clear();
2133 if (failed(planBuffers(pools))) {
2134 bufferFailure.emitOpError(
2135 "could not place buffers in accessible memory with available "
2136 "capacity");
2137 } else if (failed(planLocks(pools))) {
2138 if (lockAccessFailure)
2139 lockAccessFailure->emitOpError("cannot access pool locks");
2140 else
2141 lockFailure.getDefiningOp()->emitOpError(
2142 "could not place locks within tile lock capacity");
2143 } else {
2144 DMAChannelAnalysis channels(device);
2145 (void)assignChannels(channels);
2146 device.emitRemark(
2147 "could not find a spill-aware allocation with size-first and "
2148 "demand-first packing and bounded local-pool retries; tile "
2149 "placement and buffer packing remain greedy");
2150 }
2151 return signalPassFailure();
2152 }
2153 for (ObjectFifoPoolOp pool : pools) {
2154 allocateBuffers(pool);
2155 allocateLocks(pool);
2156 }
2157 for (TileOp tile : spillTiles)
2158 if (tile->use_empty())
2159 tile->erase();
2160 for (auto endpoint : device.getOps<RouteEndpoint>())
2161 if (auto it = channelAssignments.find(endpoint.getOperation());
2162 it != channelAssignments.end())
2163 endpoint.setRouteChannel(it->second);
2164 warnOvercommittedBDs();
2165
2166 if (failed(bindRearmTargets())) {
2167 return signalPassFailure();
2168 }
2169 if (failed(lowerFlows()) || failed(resolveEndpointUses())) {
2170 return signalPassFailure();
2171 }
2172 emitShimAllocations();
2173 for (Operation *flow : loweredFlows) {
2174 flow->erase();
2175 }
2176 }
2177};
2178
2179} // namespace
2180
2181std::unique_ptr<OperationPass<DeviceOp>>
2183 return std::make_unique<AIEObjectFifoAllocatePass>();
2184}
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 this channel, or else the flow routing it; null if neither.
bool isChannelFree(TileLike tile, DMAChannelDir dir, int channel)
Whether first-free assignment could hand out channel: nothing reserves it and no explicit flow routes...
int reservePinnedChannel(TileLike tile, DMAChannelDir dir, int channel, mlir::Operation *owner=nullptr)
Claim channel for (tile, dir) so first-free assignment cannot take it.
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...
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.