45#include "mlir/Dialect/Arith/IR/Arith.h"
46#include "mlir/Dialect/SCF/IR/SCF.h"
47#include "mlir/Pass/Pass.h"
48#include "llvm/ADT/DenseMap.h"
49#include "llvm/ADT/SmallPtrSet.h"
56#define GEN_PASS_DEF_AIEASSIGNRUNTIMESEQUENCEBDIDS
57#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
66struct AIEAssignRuntimeSequenceBDIDsPass
67 : xilinx::AIEX::impl::AIEAssignRuntimeSequenceBDIDsBase<
68 AIEAssignRuntimeSequenceBDIDsPass> {
69 using Base = xilinx::AIEX::impl::AIEAssignRuntimeSequenceBDIDsBase<
70 AIEAssignRuntimeSequenceBDIDsPass>;
71 AIEAssignRuntimeSequenceBDIDsPass() =
default;
72 AIEAssignRuntimeSequenceBDIDsPass(
73 const AIEAssignRuntimeSequenceBDIDsOptions &options)
76 llvm::DenseMap<AIE::TileOp, BdIdGenerator> gens;
78 static std::optional<std::pair<DmaQueueModel::ChannelKey, bool>>
79 otherPush(Operation *op) {
80 if (
auto push = dyn_cast<NpuPushQueueOp>(op)) {
81 return std::make_pair(
83 static_cast<int>(push.getRow()),
84 static_cast<int>(push.getDirection()),
85 static_cast<int>(push.getChannel())},
86 push.getIssueToken());
88 if (
auto copy = dyn_cast<NpuDmaMemcpyNdOp>(op)) {
89 auto allocation = AIE::ShimDMAAllocationOp::getForSymbol(
90 op->getParentOfType<AIE::DeviceOp>(),
91 copy.getMetadata().getRootReference());
93 if (AIE::TileOp tile = allocation.getTileOp())
94 return std::make_pair(
96 allocation.getChannelIndex()),
97 copy.getIssueToken() ||
98 allocation.getChannelDir() == AIE::DMAChannelDir::S2MM);
103 static std::optional<DmaQueueModel::ChannelKey>
104 otherAwaitChannel(Operation *op) {
105 if (
auto sync = dyn_cast<NpuSyncOp>(op))
107 if (
auto wait = dyn_cast<NpuDmaWaitOp>(op)) {
108 auto allocation = AIE::ShimDMAAllocationOp::getForSymbol(
109 op->getParentOfType<AIE::DeviceOp>(), wait.getSymbol());
111 if (AIE::TileOp tile = allocation.getTileOp())
113 allocation.getChannelIndex());
128 llvm::DenseMap<Operation *,
129 llvm::DenseMap<AIE::TileID, SmallVector<uint32_t>>>
134 void seedFromStaticBds(AIE::DeviceOp device, AIE::TileOp tile,
135 BdIdGenerator &gen) {
136 auto [it, inserted] = staticBdIds.try_emplace(device);
138 for (AIE::DmaBody program : device.getOps<AIE::DmaBody>())
139 llvm::append_range(it->second[program.getTileID()],
141 for (uint32_t
id : it->second.lookup(tile.getTileID()))
146 BdIdGenerator &getGeneratorForTile(AIE::TileOp tile) {
147 auto it = gens.find(tile);
148 if (it == gens.end()) {
149 AIE::DeviceOp device = tile->getParentOfType<AIE::DeviceOp>();
150 it = gens.insert({tile, BdIdGenerator(tile.getCol(), tile.getRow(),
151 device.getTargetModel())})
153 seedFromStaticBds(device, tile, it->second);
154 if (loadedDevice && loadedDevice != device)
155 seedFromStaticBds(loadedDevice, tile, it->second);
161 llvm::DenseMap<Operation *,
162 std::map<DmaQueueModel::ChannelKey, AIE::DMAStartOp>>
165 std::set<DmaQueueModel::ChannelKey> resetChannels;
167 std::map<DmaQueueModel::ChannelKey, AIE::DMAStartOp> &
168 loopingStartsOf(AIE::DeviceOp device) {
169 auto [it, inserted] = loopingStarts.try_emplace(device);
170 auto &starts = it->second;
173 for (AIE::DmaBody program : device.getOps<AIE::DmaBody>()) {
175 program->walk([&](AIE::DMAStartOp start) {
176 if (start.getEndpoint())
178 llvm::SmallPtrSet<Block *, 8> visited;
179 Block *bd = start.getDest();
180 while (bd && visited.insert(bd).second)
181 bd = bd->getNumSuccessors() == 1 ? bd->getSuccessor(0) :
nullptr;
183 starts.try_emplace({tile.col, tile.row,
184 static_cast<int>(start.getChannelDir()),
185 start.getChannelIndex()},
195 LogicalResult checkNotBehindLoop(DMAStartTaskOp start,
196 DMAConfigureTaskOp cfg) {
200 if (resetChannels.count(key))
203 loopingStartsOf(loadedDevice ? loadedDevice : getOperation());
204 auto it = starts.find(key);
205 if (it == starts.end())
207 InFlightDiagnostic diag =
208 start.emitOpError(
"starts a task on a channel whose static DMA "
209 "program loops forever, so the task never runs; "
210 "end that program's BD chain, or reset the channel "
211 "with aiex.dma_channel_reset first");
212 diag.attachNote(it->second.getLoc()) <<
"the program starts here";
219 AIE::DeviceOp loadedDevice;
221 NpuLoadPdiOp opaqueLoad;
223 llvm::SmallPtrSet<Operation *, 16> unloaded;
227 void noteLoadPdi(NpuLoadPdiOp load) {
229 if (FlatSymbolRefAttr ref = load.getDeviceRefAttr())
230 if (Operation *parent = getOperation()->getParentOp())
232 SymbolTable::lookupNearestSymbolFrom<AIE::DeviceOp>(parent, ref);
233 opaqueLoad = loadedDevice ? NpuLoadPdiOp() : load;
234 resetChannels.clear();
235 for (
auto &[tile, tasks] : liveByTile)
236 for (DMAConfigureTaskOp task : tasks)
237 unloaded.insert(task);
240 pendingAwaitReleases.clear();
241 freedInFlight.clear();
247 LogicalResult rejectRuntimeControlFlow(AIE::RuntimeSequenceOp seq) {
248 WalkResult wr = seq.walk([&](Operation *op) -> WalkResult {
249 if (isa<scf::ForOp, scf::IfOp, scf::WhileOp>(op)) {
251 "Runtime-valued control flow in a runtime sequence is not "
252 "supported by this static BD-ID allocation pass. Either pass "
253 "only constant-valued predicates to scf.for and scf.if so "
254 "`aie-unroll-runtime-sequence-loops` can unroll/fold them, or "
255 "use the dynamic EmitC path instead.");
256 return WalkResult::interrupt();
258 return WalkResult::advance();
260 return failure(wr.wasInterrupted());
274 LogicalResult verifyChannelUsage(AIE::RuntimeSequenceOp seq) {
277 const AIETargetModel &tm =
280 std::map<ChannelKey, int> avail;
283 WalkResult wr = seq.walk([&](Operation *op) -> WalkResult {
284 if (
auto start = dyn_cast<DMAStartTaskOp>(op)) {
285 DMAConfigureTaskOp cfg = start.getTaskOp();
287 return WalkResult::advance();
288 ChannelKey key = channelOf(cfg);
289 AIE::TileOp tile = cfg.getTileOp();
291 tile.getCol(), tile.getRow(), cfg.getChannel(), cfg.getDirection());
294 bool issuesToken = start.getPushIssueToken(cfg);
295 queue.
push(key, issuesToken);
298 }
else if (
auto await = dyn_cast<DMAAwaitTaskOp>(op)) {
299 DMAConfigureTaskOp cfg = await.getTaskOp();
302 if (!cfg || !cfg.getIssueToken())
303 return WalkResult::advance();
304 ChannelKey key = channelOf(cfg);
305 int &tokens = avail[key];
308 "awaits a task-completion token on a channel where no "
309 "outstanding token is guaranteed to have been produced; the "
310 "runtime sequence would block here forever. Ensure a prior "
311 "issue_token dma_start_task on the same tile, direction and "
312 "channel reaches this await");
313 return WalkResult::interrupt();
317 }
else if (
auto key = otherAwaitChannel(op)) {
320 avail[*key] = std::max(0, avail[*key] - 1);
322 }
else if (
auto push = otherPush(op)) {
323 avail[push->first] += push->second;
324 queue.
push(push->first, push->second);
326 return WalkResult::advance();
328 return failure(wr.wasInterrupted());
331 LogicalResult validate(AIE::RuntimeSequenceOp seq) {
332 WalkResult unplaced = seq.walk([](DMAConfigureTaskOp cfg) {
333 if (cfg.getTile().getDefiningOp<AIE::TileOp>())
334 return WalkResult::advance();
335 cfg.emitOpError(
"Cannot assign buffer descriptor ids to a DMA task "
336 "whose tile is not placed; run placement first.");
337 return WalkResult::interrupt();
339 if (unplaced.wasInterrupted())
343 if (failed(rejectRuntimeControlFlow(seq)))
345 if (failed(verifyChannelUsage(seq)))
350 LogicalResult allocateConfigure(DMAConfigureTaskOp op) {
351 AIE::TileOp tile = op.getTileOp();
352 BdIdGenerator &gen = getGeneratorForTile(tile);
354 const AIETargetModel &targetModel =
358 WalkResult result = op.walk<WalkOrder::PreOrder>([&](AIE::DMABDOp bd_op) {
359 if (bd_op.getBdId().has_value()) {
361 bd_op.getBdId().value(),
363 bd_op.emitOpError(
"Buffer descriptor ID ")
364 << bd_op.getBdId().value() <<
" cannot be submitted on channel "
365 << op.getChannel() <<
" of tile (" << tile.getCol() <<
","
367 <<
"), which partitions its buffer descriptors by channel "
368 "parity: an even channel reaches only the low half of the "
369 "ids and an odd channel only the high half.";
370 return WalkResult::interrupt();
373 op.emitOpError(
"Specified buffer descriptor ID ")
374 << bd_op.getBdId().value()
375 <<
" is already in use. Release the earlier task first: "
376 "aiex.dma_await_task waits for hardware completion before its "
377 "BDs are reusable. aiex.dma_free_task also releases them but "
378 "does NOT wait, so it is only safe when some other "
379 "synchronization already guarantees that task has finished "
380 "(see programming_guide/section-2/section-2d/DMATasks.md).";
381 return WalkResult::interrupt();
383 checkReallocation(tile, bd_op.getBdId().value(), bd_op);
386 return WalkResult::advance();
388 if (result.wasInterrupted())
393 AIE::DMABDOp alignedHead =
394 llvm::any_of(op->getUsers(),
395 [](Operation *user) {
396 auto start = dyn_cast<DMAStartTaskOp>(user);
397 return start && start.hasRepeatParameter();
403 result = op.walk<WalkOrder::PreOrder>([&](AIE::DMABDOp bd_op) {
404 if (bd_op.getBdId().has_value())
405 return WalkResult::advance();
408 "needs a buffer descriptor ID, but the aiex.npu.load_pdi "
409 "before it does not name a device in this module, so the "
410 "compiler cannot tell which ids the loaded PDI's static "
411 "DMAs use. Name the loaded device with device_ref = "
412 "@<device>, or give this aie.dma_bd a bd_id that the PDI "
414 .attachNote(opaqueLoad.getLoc())
415 <<
"the PDI is loaded here";
416 return WalkResult::interrupt();
421 uint32_t alignment = bd_op == alignedHead ? 4 : 1;
422 std::optional<int32_t> next_id = gen.
nextBdId(op.getChannel(), alignment);
423 while (!next_id && reclaimBds && succeeded(reclaimFor(op)))
424 next_id = gen.
nextBdId(op.getChannel(), alignment);
428 <<
"Too many simultaneously active buffer descriptors on tile ("
429 << tile.getCol() <<
"," << tile.getRow()
430 <<
"), which supports up to "
433 <<
". Emit an aiex.dma_await_task to free BDs for reuse; it "
434 "waits for hardware completion, so the recycled ids are no "
435 "longer in flight. aiex.dma_free_task also recycles ids but "
436 "does NOT wait for completion -- using it before the task "
437 "has finished is a race -- so reach for it only when some "
438 "other synchronization already guarantees completion (see "
439 "programming_guide/section-2/section-2d/DMATasks.md).";
441 diag <<
" The compiler could not take any back either: every "
442 "task holding one is not started yet, is started again "
443 "later, or runs on a channel whose status it cannot "
446 diag <<
" Or let the compiler poll for finished tasks and take "
447 "their ids (reclaim-bds, aiecc --reclaim-runtime-bds), "
448 "provided no task depends on a push issued after it.";
449 return WalkResult::interrupt();
451 checkReallocation(tile, *next_id, bd_op);
452 bd_op.setBdId(next_id);
453 return WalkResult::advance();
455 if (result.wasInterrupted())
458 liveByTile[tile].push_back(op);
466 DMAConfigureTaskOp task;
476 struct ChannelPushes {
477 SmallVector<StartedTask, 8> tasks;
480 std::map<DmaQueueModel::ChannelKey, ChannelPushes> startedOnChannel;
481 uint64_t pushCount = 0;
484 llvm::DenseMap<Operation *, unsigned> startCount;
485 llvm::DenseMap<Operation *, unsigned> runningCount;
488 bool issuesToken, Operation *origin) {
489 startedOnChannel[key].tasks.push_back(
490 {task, origin, issuesToken, pushCount++});
493 ++runningCount[task];
497 llvm::SmallPtrSet<Operation *, 16> knownComplete;
501 llvm::SmallPtrSet<Operation *, 16> pollProven;
503 llvm::SmallPtrSet<Operation *, 16> reclaimed;
505 llvm::SmallPtrSet<Operation *, 16> releasedTasks;
507 llvm::DenseMap<AIE::TileOp, SmallVector<DMAConfigureTaskOp, 16>> liveByTile;
510 struct ReleasedTask {
511 DMAConfigureTaskOp configure;
514 std::map<std::pair<int, int>, std::map<uint32_t, ReleasedTask>> freedInFlight;
525 ChannelPushes &channel = startedOnChannel[key];
526 auto &started = channel.tasks;
527 auto *it = llvm::find_if(
528 started, [](
const StartedTask &s) {
return s.issuesToken; });
529 if (it == started.end())
531 size_t retired = std::distance(started.begin(), it) + 1;
532 for (
size_t i = 0; i < retired; ++i)
533 if (DMAConfigureTaskOp task = started[i].task) {
535 if (i >= channel.finished)
536 --runningCount[task];
538 for (
size_t i = 0; i < retired; ++i)
539 if (DMAConfigureTaskOp task = started[i].task)
540 if (startCount.lookup(task) == 0)
541 knownComplete.insert(task);
542 started.erase(started.begin(), std::next(it));
543 channel.finished -= std::min(channel.finished, retired);
549 ChannelPushes &channel = startedOnChannel[key];
550 if (channel.tasks.size() <= unfinished)
552 for (
size_t done = channel.tasks.size() - unfinished;
553 channel.finished < done; ++channel.finished)
554 if (DMAConfigureTaskOp task = channel.tasks[channel.finished].task)
555 if (--runningCount[task] == 0)
556 pollProven.insert(task);
563 void notePoll(NpuMaskPollOp poll) {
564 std::optional<uint32_t> address = poll.getAbsoluteAddress();
567 const AIETargetModel &tm =
571 if (!address || !mask || !value || !field)
573 for (
const auto &[key, channel] : startedOnChannel) {
575 static_cast<AIE::DMAChannelDir
>(key[2])) !=
578 if (idle && (*mask & idle) == idle && (*value & idle) == 0)
579 return noteDrained(key, 0);
580 unsigned shift = llvm::countr_zero(field);
582 for (uint32_t size = 0; size <= field >> shift; ++size)
583 if (((size << shift) & *mask & field) == (*value & *mask & field))
585 return noteDrained(key, bound + 1);
592 void noteFreedInFlight(DMAConfigureTaskOp cfg, Operation *freeOp) {
593 if (!startCount.lookup(cfg))
595 AIE::TileOp tile = cfg.getTileOp();
596 auto &ids = freedInFlight[{tile.getCol(), tile.getRow()}];
597 cfg.walk([&](AIE::DMABDOp bd) {
598 if (bd.getBdId().has_value())
599 ids[bd.getBdId().value()] = {cfg, freeOp};
604 void checkReallocation(AIE::TileOp tile, uint32_t
id, AIE::DMABDOp bd) {
605 if (!warnUnsafeBdReuse)
607 auto tileIt = freedInFlight.find({tile.getCol(), tile.getRow()});
608 if (tileIt == freedInFlight.end())
610 auto idIt = tileIt->second.find(
id);
611 if (idIt == tileIt->second.end())
613 ReleasedTask released = idIt->second;
614 tileIt->second.erase(idIt);
615 if (knownComplete.contains(released.configure) ||
616 pollProven.contains(released.configure))
621 mlir::emitWarning(bd.getLoc())
622 <<
"reuses buffer descriptor ID " <<
id <<
" on tile (" << tile.getCol()
623 <<
"," << tile.getRow()
624 <<
") after it was released by an aiex.dma_free_task that had no "
625 "completion guarantee, so the DMA it belonged to may still be "
626 "running and this reprograms it underneath. Consume the outstanding "
627 "tokens on the same tile, direction and channel through a task "
628 "queued at or after this transfer -- each await consumes the oldest "
629 "token, regardless of the task it names";
630 diag.attachNote(released.freeOp->getLoc()) <<
"released here";
638 llvm::SmallPtrSet<Operation *, 8> awaitedConfigures;
640 llvm::SmallPtrSet<Operation *, 8> pendingAwaitReleases;
646 LogicalResult recycle(DMAConfigureTaskOp task_op, Operation *freeOp,
648 pendingAwaitReleases.erase(task_op);
651 if (awaitedConfigures.contains(task_op) || reclaimed.contains(task_op) ||
652 unloaded.contains(task_op))
654 BdIdGenerator &gen = getGeneratorForTile(task_op.getTileOp());
655 WalkResult result = task_op.walk<WalkOrder::PreOrder>([&](AIE::DMABDOp bd) {
656 if (!bd.getBdId().has_value()) {
657 bd.emitOpError(
"Free called on BD chain with unassigned IDs.");
658 return WalkResult::interrupt();
663 freeOp->emitOpError(
"frees buffer descriptor ID ")
664 << bd.getBdId().value()
665 <<
", which is not currently in use; it was already completed by "
667 "earlier aiex.dma_free_task or aiex.dma_await_task";
668 return WalkResult::interrupt();
670 return WalkResult::advance();
672 if (result.wasInterrupted())
674 releasedTasks.insert(task_op);
676 awaitedConfigures.insert(task_op);
678 noteFreedInFlight(task_op, freeOp);
685 LogicalResult recycleTask(Value task, Operation *op,
bool isAwait) {
686 auto cfg = dyn_cast_or_null<DMAConfigureTaskOp>(task.getDefiningOp());
688 auto err = op->emitOpError(
689 "does not reference a valid configure_task operation.");
690 if (Operation *def = task.getDefiningOp()) {
691 if (isa<DMAStartBdChainOp>(def))
692 err.attachNote(def->getLoc())
693 <<
"Lower this operation first using the "
694 "--aie-materialize-bd-chains pass.";
695 if (isa<DMAConfigureTaskForOp>(def))
696 err.attachNote(def->getLoc())
697 <<
"Lower this operation first using the "
698 "--aie-substitute-shim-dma-allocations pass.";
703 return recycle(cfg, op,
false);
704 pendingAwaitReleases.insert(cfg);
705 return recycleCompletedTasks(op);
708 LogicalResult recycleCompletedTasks(Operation *op) {
709 SmallVector<Operation *, 8> pending(pendingAwaitReleases.begin(),
710 pendingAwaitReleases.end());
711 for (Operation *pendingOp : pending) {
712 auto pendingCfg = cast<DMAConfigureTaskOp>(pendingOp);
715 if (!knownComplete.contains(pendingCfg))
718 if (llvm::any_of(pendingCfg.getResult().getUsers(), [&](Operation *user) {
719 return isa<DMAStartTaskOp>(user) &&
720 user->getBlock() == op->getBlock() &&
721 op->isBeforeInBlock(user);
724 if (failed(recycle(pendingCfg, op,
true)))
730 static bool isStartedAfter(DMAConfigureTaskOp task, Operation *op) {
731 return llvm::any_of(task.getResult().getUsers(), [&](Operation *user) {
732 return isa<DMAStartTaskOp>(user) && user->getBlock() == op->getBlock() &&
733 op->isBeforeInBlock(user);
746 LogicalResult reclaimFor(DMAConfigureTaskOp op) {
747 AIE::TileOp tile = op.getTileOp();
748 const AIETargetModel &tm =
750 auto isCandidate = [&](DMAConfigureTaskOp task) {
751 if (releasedTasks.contains(task) || isStartedAfter(task, op))
754 task.walk([&](AIE::DMABDOp bd) {
755 usable |= bd.getBdId() &&
757 *bd.getBdId(), op.getChannel());
761 auto take = [&](DMAConfigureTaskOp task) {
762 BdIdGenerator &gen = getGeneratorForTile(tile);
763 task.walk([&](AIE::DMABDOp bd) { gen.
freeBdId(*bd.getBdId()); });
764 reclaimed.insert(task);
765 releasedTasks.insert(task);
766 pendingAwaitReleases.erase(task);
769 ArrayRef<DMAConfigureTaskOp> live = liveByTile[tile];
770 for (DMAConfigureTaskOp task : live)
771 if ((knownComplete.contains(task) || pollProven.contains(task)) &&
779 DMAConfigureTaskOp victim;
780 size_t victimQueuedBehind = 0;
781 uint64_t victimOrder = 0;
782 for (DMAConfigureTaskOp task : live) {
783 if (!isCandidate(task))
787 static_cast<AIE::DMAChannelDir
>(key[2])))
789 const ChannelPushes &channel = startedOnChannel[key];
790 ArrayRef<StartedTask> started = channel.tasks;
792 llvm::find_if(llvm::reverse(started),
793 [&](
const StartedTask &s) {
return s.task == task; });
794 size_t queuedBehind = std::distance(started.rbegin(), last);
796 if (last == started.rend() ||
797 started.size() - 1 - queuedBehind < channel.finished)
799 if (queuedBehind == 0 ? !idle : !field)
802 if (victim && std::make_pair(queuedBehind == 0, last->order) >=
803 std::make_pair(victimQueuedBehind == 0, victimOrder))
806 victimQueuedBehind = queuedBehind;
807 victimOrder = last->order;
814 key[0], key[1], key[3],
static_cast<AIE::DMAChannelDir
>(key[2]));
817 uint32_t mask = idle;
818 size_t unfinished = 0;
819 if (victimQueuedBehind > 0) {
820 unsigned shift = llvm::countr_zero(field);
822 std::min<uint32_t>(llvm::bit_floor(victimQueuedBehind),
823 llvm::bit_floor((field >> shift) + 1) / 2) -
825 mask = field & ~(bound << shift);
826 unfinished = bound + 1;
829 auto cst = [&](uint32_t v) {
830 return arith::ConstantOp::create(b, op.getLoc(), b.getI32Type(),
831 b.getI32IntegerAttr(v))
834 Value maskValue = cst(mask);
835 Value compareValue = cst(0);
836 Value statusValue = cst(*status);
837 auto poll = NpuMaskPollOp::create(b, op.getLoc(), statusValue, compareValue,
841 SmallVector<int32_t> ids;
842 victim.walk([&](AIE::DMABDOp bd) { ids.push_back(*bd.getBdId()); });
845 auto remark = mlir::emitRemark(poll.getLoc())
846 <<
"tile (" << tile.getCol() <<
"," << tile.getRow()
847 <<
") has no free buffer descriptor id, so this poll waits "
848 << (victimQueuedBehind > 0
849 ?
"until the queue behind an earlier task is short "
850 "enough to prove it finished"
851 :
"until the channel of an earlier task is idle")
852 <<
" and takes back its ids ";
853 llvm::interleaveComma(ids, remark);
854 remark.attachNote(victim.getLoc()) <<
"the task it waits for";
855 noteDrained(key, unfinished);
866 void resetPerSequenceState() {
868 awaitedConfigures.clear();
869 pendingAwaitReleases.clear();
870 startedOnChannel.clear();
873 runningCount.clear();
874 knownComplete.clear();
877 releasedTasks.clear();
879 freedInFlight.clear();
883 resetChannels.clear();
886 void warnLeaks(AIE::RuntimeSequenceOp sequence) {
887 if (!warnUnreleasedTransfers)
889 for (
const auto &[key, channel] : startedOnChannel) {
890 SmallVector<Operation *> outstanding;
891 for (
const StartedTask &push :
892 llvm::drop_begin(channel.tasks, channel.finished))
893 if (!push.task || (!releasedTasks.contains(push.task) &&
894 !unloaded.contains(push.task)))
895 outstanding.push_back(push.origin);
896 if (outstanding.empty())
898 auto diag = mlir::emitWarning(sequence.getLoc())
899 <<
"may exit with " << outstanding.size()
900 <<
" DMA transfer(s) neither awaited nor freed on tile ("
901 << key[0] <<
"," << key[1] <<
") "
902 << AIE::stringifyDMAChannelDir(
903 static_cast<AIE::DMAChannelDir
>(key[2]))
904 <<
" channel " << key[3]
905 <<
"; runtime sequences called by aiex.run must finish using "
906 "their channels before returning. Await completion, or "
907 "free tasks whose completion is guaranteed indirectly";
908 diag.attachNote(outstanding.front()->getLoc())
909 <<
"first outstanding transfer starts here";
913 void runOnOperation()
override {
914 AIE::DeviceOp device = getOperation();
916 loopingStarts.clear();
918 WalkResult wr = device.walk([&](AIE::RuntimeSequenceOp seq) -> WalkResult {
923 bool dynamicPool =
false;
924 bool hasTasks =
false;
925 bool hasControlFlow =
false;
926 seq.walk([&](Operation *op) {
927 dynamicPool |= isa<DMABdPoolPopOp>(op);
928 hasControlFlow |= isa<scf::ForOp, scf::IfOp, scf::WhileOp>(op);
930 isa<DMAConfigureTaskOp, DMAConfigureTaskForOp, DMAStartBdChainOp,
931 DMAStartTaskOp, DMAAwaitTaskOp, DMAFreeTaskOp>(op);
933 if (dynamicPool || (!hasTasks && hasControlFlow))
934 return WalkResult::advance();
936 if (hasTasks && failed(validate(seq)))
937 return WalkResult::interrupt();
938 resetPerSequenceState();
945 SmallVector<DMAFreeTaskOp> frees;
946 WalkResult r = seq.walk([&](Operation *op) -> WalkResult {
947 if (
auto cfg = dyn_cast<DMAConfigureTaskOp>(op)) {
948 if (failed(allocateConfigure(cfg)))
949 return WalkResult::interrupt();
950 }
else if (
auto start = dyn_cast<DMAStartTaskOp>(op)) {
951 if (DMAConfigureTaskOp cfg = start.getTaskOp()) {
952 if (unloaded.contains(cfg)) {
954 "starts a task configured before an aiex.npu.load_pdi, "
955 "which reconfigured the array; its buffer descriptor ids "
956 "may now belong to the loaded device's static DMAs. "
957 "Configure the task after the load_pdi instead.");
958 return WalkResult::interrupt();
964 if (releasedTasks.contains(cfg)) {
966 "starts a task whose buffer descriptors an earlier "
967 "aiex.dma_free_task released; their ids may already be "
968 "reprogrammed for another task. Free the task after its "
969 "last start instead.");
970 return WalkResult::interrupt();
972 if (failed(checkNotBehindLoop(start, cfg)))
973 return WalkResult::interrupt();
974 knownComplete.erase(cfg);
975 pollProven.erase(cfg);
976 notePush(channelOf(cfg), cfg, start.getPushIssueToken(cfg), start);
978 }
else if (
auto load = dyn_cast<NpuLoadPdiOp>(op)) {
980 }
else if (
auto reset = dyn_cast<DmaChannelResetOp>(op)) {
982 reset.getTileOp(), reset.getDirection(), reset.getChannel()));
983 }
else if (
auto poll = dyn_cast<NpuMaskPollOp>(op)) {
985 }
else if (
auto await = dyn_cast<DMAAwaitTaskOp>(op)) {
986 if (DMAConfigureTaskOp cfg = await.getTaskOp())
987 if (cfg.getIssueToken())
988 noteAwaited(channelOf(cfg));
989 if (failed(recycleTask(await.getTask(), await,
true)))
990 return WalkResult::interrupt();
991 }
else if (
auto key = otherAwaitChannel(op)) {
993 if (failed(recycleCompletedTasks(op)))
994 return WalkResult::interrupt();
995 }
else if (
auto freeOp = dyn_cast<DMAFreeTaskOp>(op)) {
996 if (failed(recycleTask(freeOp.getTask(), freeOp,
false)))
997 return WalkResult::interrupt();
998 frees.push_back(freeOp);
999 }
else if (
auto push = otherPush(op)) {
1000 notePush(push->first, DMAConfigureTaskOp{}, push->second, op);
1002 return WalkResult::advance();
1004 if (r.wasInterrupted())
1005 return WalkResult::interrupt();
1007 for (DMAFreeTaskOp freeOp : frees)
1009 return WalkResult::advance();
1011 if (wr.wasInterrupted())
1012 return signalPassFailure();
1018std::unique_ptr<OperationPass<AIE::DeviceOp>>
1020 return std::make_unique<AIEAssignRuntimeSequenceBDIDsPass>();
1023std::unique_ptr<OperationPass<AIE::DeviceOp>>
1025 const AIEAssignRuntimeSequenceBDIDsOptions &options) {
1026 return std::make_unique<AIEAssignRuntimeSequenceBDIDsPass>(options);
static void push(ChannelState &q, bool issuesToken)
Record a push.
static void awaitToken(ChannelState &q)
Retire what a task-completion-token await proves has drained.
std::array< int, 4 > ChannelKey
{col, row, direction, channel}.
static bool wouldOverflow(const ChannelState &q, uint32_t depth)
True when a push would land on a full queue.
static ChannelKey keyOf(AIE::TileOp tile, AIE::DMAChannelDir direction, int64_t channel)
virtual uint32_t getDmaChannelIdleMask() const
Return the bits of the getDmaStatusAddress() register that are all clear exactly when the channel has...
virtual uint32_t getDmaTaskQueueDepth() const =0
Return how many DMA tasks a single channel's task queue holds (unsupported = 0).
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 getDmaTaskQueueSizeMask() const
Return the mask selecting the live task-queue occupancy field within the getDmaStatusAddress() regist...
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,...
virtual std::optional< uint32_t > getDmaStatusAddress(int, int, int, AIE::DMAChannelDir) const
Return the address of the DMA status register for a channel, or nullopt when this target's layout has...
std::optional< DmaQueueModel::ChannelKey > syncChannelKey(NpuSyncOp sync)
The channel an aiex.npu.sync retires on, or nullopt when it cannot be pinned down.
void guardQueueOverflow(DmaQueueModel &queue, mlir::Operation *push, const AIE::AIETargetModel &tm, const DmaQueueModel::ChannelKey &key, uint32_t depth, bool enforce)
Handle a push on key that would land on a full queue: poll where one can be emitted,...
std::optional< uint32_t > getConstantIntOperand(mlir::Value v)
std::unique_ptr< mlir::OperationPass< AIE::DeviceOp > > createAIEAssignRuntimeSequenceBDIDsPass()
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
llvm::SmallVector< uint32_t > getAssignedBdIds(DmaBody program)
const AIETargetModel & getTargetModel(mlir::Operation *op)
bool bdIdAlreadyAssigned(uint32_t bdId)
void assignBdId(uint32_t bdId)
void freeBdId(uint32_t bdId)
std::optional< uint32_t > nextBdId(int channelIndex, uint32_t alignment=1)