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<DmaQueueModel::ChannelKey>
79 otherTokenChannel(Operation *op) {
80 if (
auto push = dyn_cast<NpuPushQueueOp>(op)) {
81 if (push.getIssueToken())
83 static_cast<int>(push.getRow()),
84 static_cast<int>(push.getDirection()),
85 static_cast<int>(push.getChannel())};
86 }
else if (
auto copy = dyn_cast<NpuDmaMemcpyNdOp>(op)) {
87 auto allocation = AIE::ShimDMAAllocationOp::getForSymbol(
88 op->getParentOfType<AIE::DeviceOp>(),
89 copy.getMetadata().getRootReference());
91 (copy.getIssueToken() ||
92 allocation.getChannelDir() == AIE::DMAChannelDir::S2MM)) {
93 if (AIE::TileOp tile = allocation.getTileOp())
95 allocation.getChannelIndex());
101 static std::optional<DmaQueueModel::ChannelKey>
102 otherAwaitChannel(Operation *op) {
103 if (
auto sync = dyn_cast<NpuSyncOp>(op))
105 if (
auto wait = dyn_cast<NpuDmaWaitOp>(op)) {
106 auto allocation = AIE::ShimDMAAllocationOp::getForSymbol(
107 op->getParentOfType<AIE::DeviceOp>(), wait.getSymbol());
109 if (AIE::TileOp tile = allocation.getTileOp())
111 allocation.getChannelIndex());
126 llvm::DenseMap<Operation *,
127 llvm::DenseMap<AIE::TileID, SmallVector<uint32_t>>>
132 void seedFromStaticBds(AIE::DeviceOp device, AIE::TileOp tile,
133 BdIdGenerator &gen) {
134 auto [it, inserted] = staticBdIds.try_emplace(device);
136 for (AIE::DmaBody program : device.getOps<AIE::DmaBody>())
137 llvm::append_range(it->second[program.getTileID()],
139 for (uint32_t
id : it->second.lookup(tile.getTileID()))
144 BdIdGenerator &getGeneratorForTile(AIE::TileOp tile) {
145 auto it = gens.find(tile);
146 if (it == gens.end()) {
147 AIE::DeviceOp device = tile->getParentOfType<AIE::DeviceOp>();
148 it = gens.insert({tile, BdIdGenerator(tile.getCol(), tile.getRow(),
149 device.getTargetModel())})
151 seedFromStaticBds(device, tile, it->second);
152 if (loadedDevice && loadedDevice != device)
153 seedFromStaticBds(loadedDevice, tile, it->second);
159 llvm::DenseMap<Operation *,
160 std::map<DmaQueueModel::ChannelKey, AIE::DMAStartOp>>
163 std::set<DmaQueueModel::ChannelKey> resetChannels;
165 std::map<DmaQueueModel::ChannelKey, AIE::DMAStartOp> &
166 loopingStartsOf(AIE::DeviceOp device) {
167 auto [it, inserted] = loopingStarts.try_emplace(device);
168 auto &starts = it->second;
171 for (AIE::DmaBody program : device.getOps<AIE::DmaBody>()) {
173 program->walk([&](AIE::DMAStartOp start) {
174 if (start.getEndpoint())
176 llvm::SmallPtrSet<Block *, 8> visited;
177 Block *bd = start.getDest();
178 while (bd && visited.insert(bd).second)
179 bd = bd->getNumSuccessors() == 1 ? bd->getSuccessor(0) :
nullptr;
181 starts.try_emplace({tile.col, tile.row,
182 static_cast<int>(start.getChannelDir()),
183 start.getChannelIndex()},
193 LogicalResult checkNotBehindLoop(DMAStartTaskOp start,
194 DMAConfigureTaskOp cfg) {
198 if (resetChannels.count(key))
201 loopingStartsOf(loadedDevice ? loadedDevice : getOperation());
202 auto it = starts.find(key);
203 if (it == starts.end())
205 InFlightDiagnostic diag =
206 start.emitOpError(
"starts a task on a channel whose static DMA "
207 "program loops forever, so the task never runs; "
208 "end that program's BD chain, or reset the channel "
209 "with aiex.dma_channel_reset first");
210 diag.attachNote(it->second.getLoc()) <<
"the program starts here";
217 AIE::DeviceOp loadedDevice;
219 NpuLoadPdiOp opaqueLoad;
221 llvm::SmallPtrSet<Operation *, 16> unloaded;
225 void noteLoadPdi(NpuLoadPdiOp load) {
227 if (FlatSymbolRefAttr ref = load.getDeviceRefAttr())
228 if (Operation *parent = getOperation()->getParentOp())
230 SymbolTable::lookupNearestSymbolFrom<AIE::DeviceOp>(parent, ref);
231 opaqueLoad = loadedDevice ? NpuLoadPdiOp() : load;
232 resetChannels.clear();
233 for (
auto &[tile, tasks] : liveByTile)
234 for (DMAConfigureTaskOp task : tasks)
235 unloaded.insert(task);
238 pendingAwaitReleases.clear();
239 freedInFlight.clear();
245 LogicalResult rejectRuntimeControlFlow(AIE::RuntimeSequenceOp seq) {
246 WalkResult wr = seq.walk([&](Operation *op) -> WalkResult {
247 if (isa<scf::ForOp, scf::IfOp, scf::WhileOp>(op)) {
249 "Runtime-valued control flow in a runtime sequence is not "
250 "supported by this static BD-ID allocation pass. Either pass "
251 "only constant-valued predicates to scf.for and scf.if so "
252 "`aie-unroll-runtime-sequence-loops` can unroll/fold them, or "
253 "use the dynamic EmitC path instead.");
254 return WalkResult::interrupt();
256 return WalkResult::advance();
258 return failure(wr.wasInterrupted());
272 LogicalResult verifyChannelUsage(AIE::RuntimeSequenceOp seq) {
275 const AIETargetModel &tm =
278 std::map<ChannelKey, int> avail;
281 WalkResult wr = seq.walk([&](Operation *op) -> WalkResult {
282 if (
auto start = dyn_cast<DMAStartTaskOp>(op)) {
283 DMAConfigureTaskOp cfg = start.getTaskOp();
285 return WalkResult::advance();
286 ChannelKey key = channelOf(cfg);
287 AIE::TileOp tile = cfg.getTileOp();
289 tile.getCol(), tile.getRow(), cfg.getChannel(), cfg.getDirection());
292 bool issuesToken = start.getPushIssueToken(cfg);
293 queue.
push(key, issuesToken);
296 }
else if (
auto await = dyn_cast<DMAAwaitTaskOp>(op)) {
297 DMAConfigureTaskOp cfg = await.getTaskOp();
300 if (!cfg || !cfg.getIssueToken())
301 return WalkResult::advance();
302 ChannelKey key = channelOf(cfg);
303 int &tokens = avail[key];
306 "awaits a task-completion token on a channel where no "
307 "outstanding token is guaranteed to have been produced; the "
308 "runtime sequence would block here forever. Ensure a prior "
309 "issue_token dma_start_task on the same tile, direction and "
310 "channel reaches this await");
311 return WalkResult::interrupt();
315 }
else if (
auto key = otherAwaitChannel(op)) {
318 avail[*key] = std::max(0, avail[*key] - 1);
320 }
else if (
auto key = otherTokenChannel(op)) {
322 queue.
push(*key,
true);
324 return WalkResult::advance();
326 return failure(wr.wasInterrupted());
329 LogicalResult validate(AIE::RuntimeSequenceOp seq) {
330 WalkResult unplaced = seq.walk([](DMAConfigureTaskOp cfg) {
331 if (cfg.getTile().getDefiningOp<AIE::TileOp>())
332 return WalkResult::advance();
333 cfg.emitOpError(
"Cannot assign buffer descriptor ids to a DMA task "
334 "whose tile is not placed; run placement first.");
335 return WalkResult::interrupt();
337 if (unplaced.wasInterrupted())
341 if (failed(rejectRuntimeControlFlow(seq)))
343 if (failed(verifyChannelUsage(seq)))
348 LogicalResult allocateConfigure(DMAConfigureTaskOp op) {
349 AIE::TileOp tile = op.getTileOp();
350 BdIdGenerator &gen = getGeneratorForTile(tile);
352 const AIETargetModel &targetModel =
356 WalkResult result = op.walk<WalkOrder::PreOrder>([&](AIE::DMABDOp bd_op) {
357 if (bd_op.getBdId().has_value()) {
359 bd_op.getBdId().value(),
361 bd_op.emitOpError(
"Buffer descriptor ID ")
362 << bd_op.getBdId().value() <<
" cannot be submitted on channel "
363 << op.getChannel() <<
" of tile (" << tile.getCol() <<
","
365 <<
"), which partitions its buffer descriptors by channel "
366 "parity: an even channel reaches only the low half of the "
367 "ids and an odd channel only the high half.";
368 return WalkResult::interrupt();
371 op.emitOpError(
"Specified buffer descriptor ID ")
372 << bd_op.getBdId().value()
373 <<
" is already in use. Release the earlier task first: "
374 "aiex.dma_await_task waits for hardware completion before its "
375 "BDs are reusable. aiex.dma_free_task also releases them but "
376 "does NOT wait, so it is only safe when some other "
377 "synchronization already guarantees that task has finished "
378 "(see programming_guide/section-2/section-2d/DMATasks.md).";
379 return WalkResult::interrupt();
381 checkReallocation(tile, bd_op.getBdId().value(), bd_op);
384 return WalkResult::advance();
386 if (result.wasInterrupted())
391 AIE::DMABDOp alignedHead =
392 llvm::any_of(op->getUsers(),
393 [](Operation *user) {
394 auto start = dyn_cast<DMAStartTaskOp>(user);
395 return start && start.hasRepeatParameter();
401 result = op.walk<WalkOrder::PreOrder>([&](AIE::DMABDOp bd_op) {
402 if (bd_op.getBdId().has_value())
403 return WalkResult::advance();
406 "needs a buffer descriptor ID, but the aiex.npu.load_pdi "
407 "before it does not name a device in this module, so the "
408 "compiler cannot tell which ids the loaded PDI's static "
409 "DMAs use. Name the loaded device with device_ref = "
410 "@<device>, or give this aie.dma_bd a bd_id that the PDI "
412 .attachNote(opaqueLoad.getLoc())
413 <<
"the PDI is loaded here";
414 return WalkResult::interrupt();
419 uint32_t alignment = bd_op == alignedHead ? 4 : 1;
420 std::optional<int32_t> next_id = gen.
nextBdId(op.getChannel(), alignment);
421 while (!next_id && reclaimBds && succeeded(reclaimFor(op)))
422 next_id = gen.
nextBdId(op.getChannel(), alignment);
426 <<
"Too many simultaneously active buffer descriptors on tile ("
427 << tile.getCol() <<
"," << tile.getRow()
428 <<
"), which supports up to "
431 <<
". Emit an aiex.dma_await_task to free BDs for reuse; it "
432 "waits for hardware completion, so the recycled ids are no "
433 "longer in flight. aiex.dma_free_task also recycles ids but "
434 "does NOT wait for completion -- using it before the task "
435 "has finished is a race -- so reach for it only when some "
436 "other synchronization already guarantees completion (see "
437 "programming_guide/section-2/section-2d/DMATasks.md).";
439 diag <<
" The compiler could not take any back either: every "
440 "task holding one is not started yet, is started again "
441 "later, or runs on a channel whose status it cannot "
444 diag <<
" Or let the compiler poll for finished tasks and take "
445 "their ids (reclaim-bds, aiecc --reclaim-runtime-bds), "
446 "provided no task depends on a push issued after it.";
447 return WalkResult::interrupt();
449 checkReallocation(tile, *next_id, bd_op);
450 bd_op.setBdId(next_id);
451 return WalkResult::advance();
453 if (result.wasInterrupted())
456 liveByTile[tile].push_back(op);
464 DMAConfigureTaskOp task;
473 struct ChannelPushes {
474 SmallVector<StartedTask, 8> tasks;
477 std::map<DmaQueueModel::ChannelKey, ChannelPushes> startedOnChannel;
478 uint64_t pushCount = 0;
481 llvm::DenseMap<Operation *, unsigned> startCount;
482 llvm::DenseMap<Operation *, unsigned> runningCount;
486 startedOnChannel[key].tasks.push_back({task, issuesToken, pushCount++});
489 ++runningCount[task];
493 llvm::SmallPtrSet<Operation *, 16> knownComplete;
497 llvm::SmallPtrSet<Operation *, 16> pollProven;
499 llvm::SmallPtrSet<Operation *, 16> reclaimed;
501 llvm::SmallPtrSet<Operation *, 16> releasedTasks;
503 llvm::DenseMap<AIE::TileOp, SmallVector<DMAConfigureTaskOp, 16>> liveByTile;
506 struct ReleasedTask {
507 DMAConfigureTaskOp configure;
510 std::map<std::pair<int, int>, std::map<uint32_t, ReleasedTask>> freedInFlight;
521 ChannelPushes &channel = startedOnChannel[key];
522 auto &started = channel.tasks;
523 auto *it = llvm::find_if(
524 started, [](
const StartedTask &s) {
return s.issuesToken; });
525 if (it == started.end())
527 size_t retired = std::distance(started.begin(), it) + 1;
528 for (
size_t i = 0; i < retired; ++i)
529 if (DMAConfigureTaskOp task = started[i].task) {
531 if (i >= channel.finished)
532 --runningCount[task];
534 for (
size_t i = 0; i < retired; ++i)
535 if (DMAConfigureTaskOp task = started[i].task)
536 if (startCount.lookup(task) == 0)
537 knownComplete.insert(task);
538 started.erase(started.begin(), std::next(it));
539 channel.finished -= std::min(channel.finished, retired);
545 ChannelPushes &channel = startedOnChannel[key];
546 if (channel.tasks.size() <= unfinished)
548 for (
size_t done = channel.tasks.size() - unfinished;
549 channel.finished < done; ++channel.finished)
550 if (DMAConfigureTaskOp task = channel.tasks[channel.finished].task)
551 if (--runningCount[task] == 0)
552 pollProven.insert(task);
559 void notePoll(NpuMaskPollOp poll) {
560 std::optional<uint32_t> address = poll.getAbsoluteAddress();
563 const AIETargetModel &tm =
567 if (!address || !mask || !value || !field)
569 for (
const auto &[key, channel] : startedOnChannel) {
571 static_cast<AIE::DMAChannelDir
>(key[2])) !=
574 if (idle && (*mask & idle) == idle && (*value & idle) == 0)
575 return noteDrained(key, 0);
576 unsigned shift = llvm::countr_zero(field);
578 for (uint32_t size = 0; size <= field >> shift; ++size)
579 if (((size << shift) & *mask & field) == (*value & *mask & field))
581 return noteDrained(key, bound + 1);
588 void noteFreedInFlight(DMAConfigureTaskOp cfg, Operation *freeOp) {
589 if (!startCount.lookup(cfg))
591 AIE::TileOp tile = cfg.getTileOp();
592 auto &ids = freedInFlight[{tile.getCol(), tile.getRow()}];
593 cfg.walk([&](AIE::DMABDOp bd) {
594 if (bd.getBdId().has_value())
595 ids[bd.getBdId().value()] = {cfg, freeOp};
600 void checkReallocation(AIE::TileOp tile, uint32_t
id, AIE::DMABDOp bd) {
601 if (!warnUnsafeBdReuse)
603 auto tileIt = freedInFlight.find({tile.getCol(), tile.getRow()});
604 if (tileIt == freedInFlight.end())
606 auto idIt = tileIt->second.find(
id);
607 if (idIt == tileIt->second.end())
609 ReleasedTask released = idIt->second;
610 tileIt->second.erase(idIt);
611 if (knownComplete.contains(released.configure) ||
612 pollProven.contains(released.configure))
616 <<
"reuses buffer descriptor ID " <<
id <<
" on tile (" << tile.getCol()
617 <<
"," << tile.getRow()
618 <<
") after it was released by an aiex.dma_free_task that had no "
619 "completion guarantee, so the DMA it belonged to may still be "
620 "running and this reprograms it underneath. Consume the outstanding "
621 "tokens on the same tile, direction and channel through a task "
622 "queued at or after this transfer -- each await consumes the oldest "
623 "token, regardless of the task it names";
624 diag.attachNote(released.freeOp->getLoc()) <<
"released here";
632 llvm::SmallPtrSet<Operation *, 8> awaitedConfigures;
634 llvm::SmallPtrSet<Operation *, 8> pendingAwaitReleases;
640 LogicalResult recycle(DMAConfigureTaskOp task_op, Operation *freeOp,
642 pendingAwaitReleases.erase(task_op);
645 if (awaitedConfigures.contains(task_op) || reclaimed.contains(task_op) ||
646 unloaded.contains(task_op))
648 BdIdGenerator &gen = getGeneratorForTile(task_op.getTileOp());
649 WalkResult result = task_op.walk<WalkOrder::PreOrder>([&](AIE::DMABDOp bd) {
650 if (!bd.getBdId().has_value()) {
651 bd.emitOpError(
"Free called on BD chain with unassigned IDs.");
652 return WalkResult::interrupt();
657 freeOp->emitOpError(
"frees buffer descriptor ID ")
658 << bd.getBdId().value()
659 <<
", which is not currently in use; it was already completed by "
661 "earlier aiex.dma_free_task or aiex.dma_await_task";
662 return WalkResult::interrupt();
664 return WalkResult::advance();
666 if (result.wasInterrupted())
668 releasedTasks.insert(task_op);
670 awaitedConfigures.insert(task_op);
672 noteFreedInFlight(task_op, freeOp);
679 LogicalResult recycleTask(Value task, Operation *op,
bool isAwait) {
680 auto cfg = dyn_cast_or_null<DMAConfigureTaskOp>(task.getDefiningOp());
682 auto err = op->emitOpError(
683 "does not reference a valid configure_task operation.");
684 if (Operation *def = task.getDefiningOp()) {
685 if (isa<DMAStartBdChainOp>(def))
686 err.attachNote(def->getLoc())
687 <<
"Lower this operation first using the "
688 "--aie-materialize-bd-chains pass.";
689 if (isa<DMAConfigureTaskForOp>(def))
690 err.attachNote(def->getLoc())
691 <<
"Lower this operation first using the "
692 "--aie-substitute-shim-dma-allocations pass.";
697 return recycle(cfg, op,
false);
698 pendingAwaitReleases.insert(cfg);
699 return recycleCompletedTasks(op);
702 LogicalResult recycleCompletedTasks(Operation *op) {
703 SmallVector<Operation *, 8> pending(pendingAwaitReleases.begin(),
704 pendingAwaitReleases.end());
705 for (Operation *pendingOp : pending) {
706 auto pendingCfg = cast<DMAConfigureTaskOp>(pendingOp);
709 if (!knownComplete.contains(pendingCfg))
712 if (llvm::any_of(pendingCfg.getResult().getUsers(), [&](Operation *user) {
713 return isa<DMAStartTaskOp>(user) &&
714 user->getBlock() == op->getBlock() &&
715 op->isBeforeInBlock(user);
718 if (failed(recycle(pendingCfg, op,
true)))
724 static bool isStartedAfter(DMAConfigureTaskOp task, Operation *op) {
725 return llvm::any_of(task.getResult().getUsers(), [&](Operation *user) {
726 return isa<DMAStartTaskOp>(user) && user->getBlock() == op->getBlock() &&
727 op->isBeforeInBlock(user);
740 LogicalResult reclaimFor(DMAConfigureTaskOp op) {
741 AIE::TileOp tile = op.getTileOp();
742 const AIETargetModel &tm =
744 auto isCandidate = [&](DMAConfigureTaskOp task) {
745 if (releasedTasks.contains(task) || isStartedAfter(task, op))
748 task.walk([&](AIE::DMABDOp bd) {
749 usable |= bd.getBdId() &&
751 *bd.getBdId(), op.getChannel());
755 auto take = [&](DMAConfigureTaskOp task) {
756 BdIdGenerator &gen = getGeneratorForTile(tile);
757 task.walk([&](AIE::DMABDOp bd) { gen.
freeBdId(*bd.getBdId()); });
758 reclaimed.insert(task);
759 releasedTasks.insert(task);
760 pendingAwaitReleases.erase(task);
763 ArrayRef<DMAConfigureTaskOp> live = liveByTile[tile];
764 for (DMAConfigureTaskOp task : live)
765 if ((knownComplete.contains(task) || pollProven.contains(task)) &&
773 DMAConfigureTaskOp victim;
774 size_t victimQueuedBehind = 0;
775 uint64_t victimOrder = 0;
776 for (DMAConfigureTaskOp task : live) {
777 if (!isCandidate(task))
781 static_cast<AIE::DMAChannelDir
>(key[2])))
783 const ChannelPushes &channel = startedOnChannel[key];
784 ArrayRef<StartedTask> started = channel.tasks;
786 llvm::find_if(llvm::reverse(started),
787 [&](
const StartedTask &s) {
return s.task == task; });
788 size_t queuedBehind = std::distance(started.rbegin(), last);
790 if (last == started.rend() ||
791 started.size() - 1 - queuedBehind < channel.finished)
793 if (queuedBehind == 0 ? !idle : !field)
796 if (victim && std::make_pair(queuedBehind == 0, last->order) >=
797 std::make_pair(victimQueuedBehind == 0, victimOrder))
800 victimQueuedBehind = queuedBehind;
801 victimOrder = last->order;
808 key[0], key[1], key[3],
static_cast<AIE::DMAChannelDir
>(key[2]));
811 uint32_t mask = idle;
812 size_t unfinished = 0;
813 if (victimQueuedBehind > 0) {
814 unsigned shift = llvm::countr_zero(field);
816 std::min<uint32_t>(llvm::bit_floor(victimQueuedBehind),
817 llvm::bit_floor((field >> shift) + 1) / 2) -
819 mask = field & ~(bound << shift);
820 unfinished = bound + 1;
823 auto cst = [&](uint32_t v) {
824 return arith::ConstantOp::create(b, op.getLoc(), b.getI32Type(),
825 b.getI32IntegerAttr(v))
828 Value maskValue = cst(mask);
829 Value compareValue = cst(0);
830 Value statusValue = cst(*status);
831 auto poll = NpuMaskPollOp::create(b, op.getLoc(), statusValue, compareValue,
835 SmallVector<int32_t> ids;
836 victim.walk([&](AIE::DMABDOp bd) { ids.push_back(*bd.getBdId()); });
839 auto remark = mlir::emitRemark(poll.getLoc())
840 <<
"tile (" << tile.getCol() <<
"," << tile.getRow()
841 <<
") has no free buffer descriptor id, so this poll waits "
842 << (victimQueuedBehind > 0
843 ?
"until the queue behind an earlier task is short "
844 "enough to prove it finished"
845 :
"until the channel of an earlier task is idle")
846 <<
" and takes back its ids ";
847 llvm::interleaveComma(ids, remark);
848 remark.attachNote(victim.getLoc()) <<
"the task it waits for";
849 noteDrained(key, unfinished);
860 void resetPerSequenceState() {
862 awaitedConfigures.clear();
863 pendingAwaitReleases.clear();
864 startedOnChannel.clear();
867 runningCount.clear();
868 knownComplete.clear();
871 releasedTasks.clear();
873 freedInFlight.clear();
877 resetChannels.clear();
880 void runOnOperation()
override {
881 AIE::DeviceOp device = getOperation();
883 loopingStarts.clear();
885 WalkResult wr = device.walk([&](AIE::RuntimeSequenceOp seq) -> WalkResult {
890 bool dynamicPool =
false;
891 bool hasTasks =
false;
892 seq.walk([&](Operation *op) {
893 dynamicPool |= isa<DMABdPoolPopOp>(op);
895 isa<DMAConfigureTaskOp, DMAConfigureTaskForOp, DMAStartBdChainOp,
896 DMAStartTaskOp, DMAAwaitTaskOp, DMAFreeTaskOp>(op);
899 if (dynamicPool || !hasTasks)
900 return WalkResult::advance();
902 if (failed(validate(seq)))
903 return WalkResult::interrupt();
904 resetPerSequenceState();
911 SmallVector<DMAFreeTaskOp> frees;
912 WalkResult r = seq.walk([&](Operation *op) -> WalkResult {
913 if (
auto cfg = dyn_cast<DMAConfigureTaskOp>(op)) {
914 if (failed(allocateConfigure(cfg)))
915 return WalkResult::interrupt();
916 }
else if (
auto start = dyn_cast<DMAStartTaskOp>(op)) {
917 if (DMAConfigureTaskOp cfg = start.getTaskOp()) {
918 if (unloaded.contains(cfg)) {
920 "starts a task configured before an aiex.npu.load_pdi, "
921 "which reconfigured the array; its buffer descriptor ids "
922 "may now belong to the loaded device's static DMAs. "
923 "Configure the task after the load_pdi instead.");
924 return WalkResult::interrupt();
930 if (releasedTasks.contains(cfg)) {
932 "starts a task whose buffer descriptors an earlier "
933 "aiex.dma_free_task released; their ids may already be "
934 "reprogrammed for another task. Free the task after its "
935 "last start instead.");
936 return WalkResult::interrupt();
938 if (failed(checkNotBehindLoop(start, cfg)))
939 return WalkResult::interrupt();
940 knownComplete.erase(cfg);
941 pollProven.erase(cfg);
942 notePush(channelOf(cfg), cfg, start.getPushIssueToken(cfg));
944 }
else if (
auto load = dyn_cast<NpuLoadPdiOp>(op)) {
946 }
else if (
auto reset = dyn_cast<DmaChannelResetOp>(op)) {
948 reset.getTileOp(), reset.getDirection(), reset.getChannel()));
949 }
else if (
auto poll = dyn_cast<NpuMaskPollOp>(op)) {
951 }
else if (
auto await = dyn_cast<DMAAwaitTaskOp>(op)) {
952 if (DMAConfigureTaskOp cfg = await.getTaskOp())
953 if (cfg.getIssueToken())
954 noteAwaited(channelOf(cfg));
955 if (failed(recycleTask(await.getTask(), await,
true)))
956 return WalkResult::interrupt();
957 }
else if (
auto key = otherAwaitChannel(op)) {
959 if (failed(recycleCompletedTasks(op)))
960 return WalkResult::interrupt();
961 }
else if (
auto freeOp = dyn_cast<DMAFreeTaskOp>(op)) {
962 if (failed(recycleTask(freeOp.getTask(), freeOp,
false)))
963 return WalkResult::interrupt();
964 frees.push_back(freeOp);
965 }
else if (
auto key = otherTokenChannel(op)) {
966 notePush(*key, DMAConfigureTaskOp{},
true);
968 return WalkResult::advance();
970 if (r.wasInterrupted())
971 return WalkResult::interrupt();
972 for (DMAFreeTaskOp freeOp : frees)
974 return WalkResult::advance();
976 if (wr.wasInterrupted())
977 return signalPassFailure();
983std::unique_ptr<OperationPass<AIE::DeviceOp>>
985 return std::make_unique<AIEAssignRuntimeSequenceBDIDsPass>();
988std::unique_ptr<OperationPass<AIE::DeviceOp>>
990 const AIEAssignRuntimeSequenceBDIDsOptions &options) {
991 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)