13#include "mlir/Analysis/DataFlow/ConstantPropagationAnalysis.h"
14#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h"
15#include "mlir/Analysis/DataFlow/IntegerRangeAnalysis.h"
16#include "mlir/Analysis/DataFlowFramework.h"
17#include "mlir/Dialect/Arith/IR/Arith.h"
18#include "mlir/Dialect/MemRef/IR/MemRef.h"
19#include "mlir/Dialect/Utils/StaticValueUtils.h"
20#include "mlir/IR/Builders.h"
21#include "mlir/IR/Dominance.h"
22#include "mlir/Interfaces/LoopLikeInterface.h"
23#include "mlir/Pass/Pass.h"
25#include "llvm/ADT/MapVector.h"
26#include "llvm/ADT/STLExtras.h"
27#include "llvm/ADT/SetVector.h"
28#include "llvm/Support/raw_ostream.h"
39#define GEN_PASS_DEF_AIELOWERSCRATCHPADPARAMETERS
40#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
48static bool shouldEmitParameterSyncPreamble(CoreOp coreOp) {
49 if (
auto attr = coreOp.getEmitParameterSyncPreambleAttr()) {
50 return attr.getValue();
52 WalkResult found = coreOp.getBody().walk([&](Operation *op) {
53 return isa<ReadScratchpadParameterOp, WaitScratchpadParametersOp>(op)
54 ? WalkResult::interrupt()
55 : WalkResult::advance();
57 return found.wasInterrupted();
62static void warnIfRuntimeOffsetMayRound(Operation *op, Type bufType) {
64 llvm::cast<BaseMemRefType>(bufType).getElementTypeBitWidth() / 8;
65 if (elemBytes == 0 || elemBytes % 4 == 0) {
68 mlir::emitWarning(op->getLoc())
69 <<
"runtime offset parameter on a " << (elemBytes * 8)
70 <<
"-bit element type: the firmware masks the BD address register with "
71 "0xFFFFFFFC, so a value that is not a multiple of "
72 << (4 / std::gcd(4u, elemBytes))
73 <<
" elements is silently rounded down to the 4-byte boundary below "
74 "instead of being rejected";
79struct TransferExtent {
87static std::optional<TransferExtent>
88getTransferExtent(Type bufType, int64_t offset, int64_t lenElems,
89 SmallVector<int64_t> sizes, SmallVector<int64_t> strides,
90 std::optional<int64_t> lengthUnit) {
91 auto buffer = llvm::cast<BaseMemRefType>(bufType);
92 if (!buffer.hasStaticShape())
94 TransferExtent extent{buffer.getNumElements(), offset, offset, 0};
97 extent.last += lenElems - 1;
98 for (
auto [size, stride] : llvm::zip(sizes, strides))
99 extent.last += (size - 1) * stride;
102 bool linear = sizes.empty();
103 sizes.resize(std::max<size_t>(sizes.size(), 3), 1);
104 strides.resize(sizes.size(), 0);
105 for (
size_t i = 3; i < sizes.size(); ++i)
106 extent.last += (sizes[i] - 1) * strides[i];
108 extent.last += lenElems - 1;
109 extent.lengthStep = *lengthUnit;
113 int64_t rowElems = sizes[0] * sizes[1];
116 extent.last += (sizes[0] - 1) * strides[0] + (sizes[1] - 1) * strides[1] +
117 (llvm::divideCeilSigned(lenElems, rowElems) - 1) * strides[2];
118 extent.lengthStep = *lengthUnit / rowElems * strides[2];
122static std::optional<TransferExtent>
123getTransferExtent(NpuDmaMemcpyNdOp op, std::optional<int64_t> lengthUnit) {
124 std::optional<SmallVector<int64_t>> offsets =
125 getConstantIntValues(op.getMixedOffsets());
126 std::optional<SmallVector<int64_t>> sizes =
127 getConstantIntValues(op.getMixedSizes());
128 std::optional<SmallVector<int64_t>> strides =
129 getConstantIntValues(op.getMixedStrides());
130 int64_t elemBits = op.getElementTypeBitwidth();
131 if (!offsets || !sizes || !strides || elemBits % 8 != 0)
133 std::reverse(sizes->begin(), sizes->end());
134 std::reverse(strides->begin(), strides->end());
135 return getTransferExtent(
136 op.getMemref().getType(), op.getOffsetInBytes() / (elemBits / 8),
137 (*sizes)[0] * (*sizes)[1] * (*sizes)[2], *sizes, *strides, lengthUnit);
143static std::optional<std::pair<int64_t, int64_t>>
144getOffsetRange(Value offset, int64_t bufferElems, DataFlowSolver *solver) {
147 auto *state = solver->lookupState<dataflow::IntegerValueRangeLattice>(offset);
148 if (!state || state->getValue().isUninitialized())
150 const ConstantIntRanges &range = state->getValue().getValue();
153 std::optional<int64_t> lo = range.smin().trySExtValue();
154 std::optional<int64_t> hi = range.smax().trySExtValue();
155 if (!lo || !hi || *lo < 0 || *hi >= bufferElems)
157 return std::make_pair(*lo, *hi);
162static std::optional<TransferExtent>
163getTransferExtent(AIE::DMABDOp op, std::optional<int64_t> lengthUnit,
164 DataFlowSolver *solver) {
165 auto buffer = llvm::cast<BaseMemRefType>(op.getBuffer().getType());
166 std::optional<int64_t> offset = 0, offsetMin = 0;
167 if (op.hasOffset()) {
168 offset = offsetMin = op.getConstantOffset();
169 if (!offset && buffer.hasStaticShape()) {
171 getOffsetRange(op.getOffset(), buffer.getNumElements(), solver)) {
172 offsetMin = range->first;
173 offset = range->second;
177 std::optional<int64_t> len;
179 len = op.getConstantLen();
180 else if (buffer.hasStaticShape())
181 len = buffer.getNumElements();
182 std::optional<SmallVector<int64_t>> sizes =
183 getConstantIntValues(op.getMixedSizes());
184 std::optional<SmallVector<int64_t>> strides =
185 getConstantIntValues(op.getMixedStrides());
186 if (!offset || !len || !sizes || !strides)
188 std::reverse(sizes->begin(), sizes->end());
189 std::reverse(strides->begin(), strides->end());
190 std::optional<TransferExtent> extent =
191 getTransferExtent(buffer, *offset, *len, *sizes, *strides, lengthUnit);
193 extent->offset = *offsetMin;
199static int64_t getMaxLengthSteps(Operation *op, int64_t lenBytes,
203 return (maxWords - lenBytes / 4) / (unitBytes / 4);
206static std::optional<int64_t> getMaxLengthSteps(NpuDmaMemcpyNdOp op) {
207 std::optional<SmallVector<int64_t>> sizes =
208 getConstantIntValues(op.getMixedSizes());
209 std::optional<int64_t> lengthUnit = op.getLengthUnit();
210 if (!sizes || !lengthUnit)
212 int64_t elemBytes = op.getElementTypeBitwidth() / 8;
213 return getMaxLengthSteps(op,
214 (*sizes)[1] * (*sizes)[2] * (*sizes)[3] * elemBytes,
215 *lengthUnit * elemBytes);
218static std::optional<int64_t> getMaxLengthSteps(AIE::DMABDOp op) {
219 std::optional<int64_t> lengthUnit = op.getLengthUnit();
222 return getMaxLengthSteps(op, op.getLenInBytes(),
223 *lengthUnit * op.getBufferElementTypeWidthInBytes());
232static bool shouldEmitParameterSyncPreamble(RuntimeSequenceOp seqOp,
234 if (
auto attr = seqOp.getEmitParameterSyncPreambleAttr()) {
235 return attr.getValue();
237 bool hasManualSync =
false;
238 seqOp.walk([&](SyncScratchpadParametersFromHostOp) { hasManualSync =
true; });
245 auto device = seqOp->getParentOfType<DeviceOp>();
250 device.walk([&](Operation *op) {
254 if (llvm::isa<ReadScratchpadParameterOp>(op) ||
255 op->hasAttr(
"offset_parameter") ||
256 op->hasAttr(
"offset_state_table_idx") ||
257 op->hasAttr(
"length_parameter") ||
258 op->hasAttr(
"length_state_table_idx") ||
259 op->hasAttr(
"repeat_parameter") ||
260 op->hasAttr(
"repeat_state_table_idx")) {
268constexpr unsigned kMaxStateTableEntries = 32;
276static llvm::MapVector<RuntimeSequenceOp, llvm::SetVector<StringRef>>
277commandParameters(ModuleOp moduleOp) {
278 DenseMap<Operation *, llvm::SetVector<StringRef>> coreReads;
279 moduleOp.walk([&](ReadScratchpadParameterOp op) {
280 coreReads[op->getParentOfType<DeviceOp>()].insert(op.getParameter());
282 DenseMap<Operation *, llvm::SetVector<StringRef>> uses;
283 DenseMap<Operation *, SmallVector<RuntimeSequenceOp>> callees;
284 DenseSet<Operation *> called;
286 moduleOp.walk([&](RuntimeSequenceOp seq) {
287 seq.walk([&](Operation *op) {
288 for (StringRef attr :
289 {
"offset_parameter",
"length_parameter",
"repeat_parameter"})
290 if (
auto ref = op->getAttrOfType<FlatSymbolRefAttr>(attr))
291 uses[seq].insert(ref.getValue());
292 if (
auto run = dyn_cast<RunOp>(op))
293 if (RuntimeSequenceOp callee = run.getCalleeRuntimeSequenceOp(&names)) {
294 callees[seq].push_back(callee);
295 called.insert(callee);
299 llvm::MapVector<RuntimeSequenceOp, llvm::SetVector<StringRef>> commands;
300 moduleOp.walk([&](RuntimeSequenceOp seq) {
301 if (called.contains(seq))
303 llvm::SetVector<StringRef> &reads = commands[seq];
304 if (ArrayAttr lowered = seq.getScratchpadParametersAttr())
305 for (
auto ref : lowered.getAsRange<FlatSymbolRefAttr>())
306 reads.insert(ref.getValue());
308 llvm::SetVector<RuntimeSequenceOp> reached;
310 for (
unsigned i = 0; i < reached.size(); i++) {
311 RuntimeSequenceOp at = reached[i];
312 reads.insert_range(uses.lookup(at));
313 reads.insert_range(coreReads.lookup(at->getParentOfType<DeviceOp>()));
314 reached.insert_range(callees.lookup(at));
329 enum class Result { Found, Infeasible, GaveUp };
331 explicit SlotColoring(
unsigned numParams)
332 : conflicts(numParams), slots(numParams), wanted(numParams, false),
333 neighbourSlots(numParams), saturation(numParams, 0) {}
335 void addConflict(
unsigned a,
unsigned b) {
336 if (a == b || llvm::is_contained(conflicts[a], b))
338 conflicts[a].push_back(b);
339 conflicts[b].push_back(a);
342 void want(
unsigned param) { wanted[param] =
true; }
344 void pin(
unsigned param,
unsigned slot) {
345 wanted[param] =
false;
347 maxSlot = std::max<int>(maxSlot, slot);
350 Result solve(
unsigned stepLimit) {
355 std::optional<unsigned> slot(
unsigned param)
const {
return slots[param]; }
358 unsigned stuck()
const {
return firstStuck; }
361 SmallVector<SmallVector<unsigned>> conflicts;
362 SmallVector<std::optional<unsigned>> slots;
363 SmallVector<bool> wanted;
365 SmallVector<std::array<unsigned, kMaxStateTableEntries>> neighbourSlots;
366 SmallVector<unsigned> saturation;
369 bool deadEnd =
false;
370 unsigned firstStuck = 0;
372 void assign(
unsigned param,
unsigned slot) {
374 for (
unsigned other : conflicts[param])
375 if (neighbourSlots[other][slot]++ == 0)
379 void unassign(
unsigned param,
unsigned slot) {
380 slots[param].reset();
381 for (
unsigned other : conflicts[param])
382 if (--neighbourSlots[other][slot] == 0)
387 std::optional<unsigned> next;
388 for (
unsigned p = 0; p < slots.size(); p++) {
389 if (!wanted[p] || slots[p])
391 if (!next || saturation[p] > saturation[*next] ||
392 (saturation[p] == saturation[*next] &&
393 conflicts[p].size() > conflicts[*next].size()))
397 return Result::Found;
399 unsigned limit = std::min<unsigned>(maxSlot + 2, kMaxStateTableEntries);
400 for (
unsigned s = 0; s < limit; s++) {
401 if (neighbourSlots[*next][s] != 0)
403 if (deadEnd && steps-- == 0)
404 return Result::GaveUp;
405 int previousMax = maxSlot;
406 maxSlot = std::max<int>(maxSlot, s);
408 Result result = search();
409 if (result != Result::Infeasible)
412 maxSlot = previousMax;
418 return Result::Infeasible;
423constexpr unsigned kMaxSlotSearchSteps = 1 << 17;
425struct AIELowerScratchpadParametersPass
426 :
public xilinx::AIEX::impl::AIELowerScratchpadParametersBase<
427 AIELowerScratchpadParametersPass> {
428 using AIELowerScratchpadParametersBase::AIELowerScratchpadParametersBase;
433 void allocateBuffers(DeviceOp device, OpBuilder &builder) {
434 MLIRContext *ctx = device.getContext();
435 unsigned uniquingCounter = 0;
436 AIE::NamedOpTable names(device);
438 DenseMap<std::pair<StringRef, Operation *>, BufferOp> seen;
440 device.walk([&](ReadScratchpadParameterOp readOp) {
441 auto coreOp = readOp->getParentOfType<CoreOp>();
442 TileOp tile = coreOp.getTileOp();
443 StringRef paramName = readOp.getParameter();
444 auto key = std::make_pair(paramName, tile.getOperation());
446 if (seen.count(key)) {
447 readOp.setBufferAttr(
448 FlatSymbolRefAttr::get(ctx, *seen[key].getSymName()));
452 builder.setInsertionPointAfter(tile);
461 auto bufType = MemRefType::get({2}, builder.getI32Type());
463 (
"__param_" + paramName +
"_" + std::to_string(tile.getCol()) +
"_" +
464 std::to_string(tile.getRow()) +
"_")
466 std::string bufName = names.uniqueName(prefix, uniquingCounter);
468 BufferOp::create(builder, readOp.getLoc(), bufType, tile,
469 builder.getStringAttr(bufName),
nullptr,
473 readOp.setBufferAttr(
474 FlatSymbolRefAttr::get(ctx, *seen[key].getSymName()));
481 void lowerReadParameters(DeviceOp device, OpBuilder &builder) {
482 SmallVector<ReadScratchpadParameterOp> readOps;
483 device.walk([&](ReadScratchpadParameterOp op) { readOps.push_back(op); });
485 for (
auto readOp : readOps) {
486 FlatSymbolRefAttr bufRef = readOp.getBufferAttr();
489 builder.setInsertionPoint(readOp);
490 Value c0 = arith::ConstantIndexOp::create(builder, readOp.getLoc(), 0);
491 Value raw = memref::LoadOp::create(builder, readOp.getLoc(), buf, c0);
492 Value c2 = arith::ConstantOp::create(builder, readOp.getLoc(),
493 builder.getI32IntegerAttr(2));
494 Value decoded = arith::ShRUIOp::create(builder, readOp.getLoc(), raw, c2);
496 Type resultType = readOp.getResult().getType();
497 Value result = decoded;
498 if (resultType.isInteger() && resultType != builder.getI32Type()) {
499 result = arith::TruncIOp::create(builder, readOp.getLoc(), resultType,
501 }
else if (resultType.isBF16()) {
502 Value masked = arith::TruncIOp::create(builder, readOp.getLoc(),
503 builder.getI16Type(), decoded);
504 result = arith::BitcastOp::create(builder, readOp.getLoc(), resultType,
508 readOp.getResult().replaceAllUsesWith(result);
521 LogicalResult emitCorePreambles(DeviceOp device, OpBuilder &builder,
522 SmallVector<Value> &syncLocks) {
523 WalkResult result = device.walk([&](CoreOp coreOp) {
524 if (!shouldEmitParameterSyncPreamble(coreOp)) {
525 return coreOp.walk([](WaitScratchpadParametersOp wait) {
526 wait.emitOpError(
"waits in a core with emit_parameter_sync_preamble "
527 "= false, which gets no lock to wait on");
528 return WalkResult::interrupt();
532 SmallVector<WaitScratchpadParametersOp> waits;
533 coreOp.walk([&](WaitScratchpadParametersOp op) { waits.push_back(op); });
534 if (!waits.empty()) {
535 DominanceInfo dominance(coreOp);
536 WalkResult unordered = coreOp.walk([&](ReadScratchpadParameterOp read) {
539 Operation *outerLoop =
nullptr;
540 for (Operation *op = read->getParentOp(); op != coreOp;
541 op = op->getParentOp())
542 if (isa<LoopLikeOpInterface>(op))
544 bool dominated =
false;
545 for (WaitScratchpadParametersOp wait : waits) {
546 if (!dominance.properlyDominates(wait, read))
549 if (!outerLoop || outerLoop->isProperAncestor(wait))
550 return WalkResult::advance();
553 read.emitOpError(
"reads @")
554 << read.getParameter()
555 <<
" in a loop with no aiex.wait_scratchpad_parameters "
556 "inside it before the read, so after the loop's first "
557 "iteration it may read a previous dispatch's value";
559 read.emitOpError(
"reads @")
560 << read.getParameter()
561 <<
" where no aiex.wait_scratchpad_parameters of its core "
562 "dominates it, so it may read a previous dispatch's value";
563 return WalkResult::interrupt();
565 if (unordered.wasInterrupted())
566 return WalkResult::interrupt();
569 TileOp tile = coreOp.getTileOp();
570 builder.setInsertionPointAfter(tile);
571 auto lockOp = LockOp::create(
572 builder, coreOp.getLoc(), builder.getIndexType(), tile.getResult(),
573 IntegerAttr{}, builder.getI32IntegerAttr(0),
575 syncLocks.push_back(lockOp.getResult());
578 builder.setInsertionPointToStart(&coreOp.getBody().front());
579 UseLockOp::create(builder, coreOp.getLoc(), lockOp.getResult(),
580 LockAction::Acquire, 1);
582 for (WaitScratchpadParametersOp wait : waits) {
583 builder.setInsertionPoint(wait);
584 UseLockOp::create(builder, wait.getLoc(), lockOp.getResult(),
585 LockAction::AcquireGreaterEqual, 1);
590 coreOp.setEmitParameterSyncPreambleAttr(builder.getBoolAttr(
false));
591 return WalkResult::advance();
593 return failure(result.wasInterrupted());
600 LogicalResult emitSequencePreambles(DeviceOp device, OpBuilder &builder,
602 WalkResult result = device.walk([&](RuntimeSequenceOp seqOp) {
603 if (!shouldEmitParameterSyncPreamble(seqOp, coresWait)) {
604 return WalkResult::advance();
607 Region ®ion = seqOp.getBody();
609 region.emplaceBlock();
610 Block &body = region.front();
611 builder.setInsertionPointToStart(&body);
613 for (NpuLoadPdiOp loadPdi : body.getOps<NpuLoadPdiOp>())
614 builder.setInsertionPointAfter(loadPdi);
616 llvm::make_range(body.begin(), builder.getInsertionPoint())) {
617 WalkResult early = op.walk([&](Operation *inner) {
618 auto dmaOp = dyn_cast<NpuDmaMemcpyNdOp>(inner);
619 auto bdOp = dyn_cast<AIE::DMABDOp>(inner);
620 auto startOp = dyn_cast<DMAStartTaskOp>(inner);
621 auto pushOp = dyn_cast<NpuPushQueueOp>(inner);
622 if ((dmaOp && (dmaOp.getOffsetStateTableIdxAttr() ||
623 dmaOp.getLengthStateTableIdxAttr())) ||
624 (bdOp && (bdOp.getOffsetStateTableIdxAttr() ||
625 bdOp.getLengthStateTableIdxAttr())) ||
626 (startOp && startOp.getRepeatStateTableIdxAttr()) ||
627 (pushOp && pushOp.getRepeatStateTableIdxAttr())) {
629 "reads a scratchpad parameter before the last "
630 "aiex.npu.load_pdi of its runtime sequence, which is where "
631 "the scratchpad is created. Load the PDIs first, or place "
632 "aiex.sync_scratchpad_parameters_from_host before this op.");
633 return WalkResult::interrupt();
635 return WalkResult::advance();
637 if (early.wasInterrupted())
638 return WalkResult::interrupt();
640 SyncScratchpadParametersFromHostOp::create(builder, seqOp.getLoc());
643 seqOp.setEmitParameterSyncPreambleAttr(builder.getBoolAttr(
false));
644 return WalkResult::advance();
646 return failure(result.wasInterrupted());
656 void lowerSyncParametersOp(
657 SyncScratchpadParametersFromHostOp syncOp, uint32_t scratchpadSize,
658 const SmallVector<std::pair<uint8_t, FlatSymbolRefAttr>> ¶mEntries,
659 const SmallVector<Value> &syncLocks) {
660 OpBuilder builder(syncOp);
661 Location loc = syncOp.getLoc();
664 if (scratchpadSize > 0)
665 NpuCreateScratchpadOp::create(builder, loc, scratchpadSize);
667 for (
auto &[stateIdx, bufRef] : paramEntries) {
669 NpuWrite32Op::create(builder, loc,
673 NpuUpdateFromScratchpadOp::create(
674 builder, loc, stateIdx, StateTableFunc::Incr,
675 static_cast<uint32_t
>(0),
676 static_cast<uint32_t
>(0), bufRef,
680 for (Value lock : syncLocks) {
689 void lowerSyncParametersOps(
690 DeviceOp device, uint32_t scratchpadSize,
691 const SmallVector<std::pair<uint8_t, FlatSymbolRefAttr>> ¶mEntries,
692 const SmallVector<Value> &syncLocks) {
693 SmallVector<SyncScratchpadParametersFromHostOp> syncOps;
695 [&](SyncScratchpadParametersFromHostOp op) { syncOps.push_back(op); });
696 for (SyncScratchpadParametersFromHostOp syncOp : syncOps) {
697 lowerSyncParametersOp(syncOp, scratchpadSize, paramEntries, syncLocks);
702 LogicalResult emitParamsFile() {
703 if (outputParamsFile.empty())
707 llvm::raw_fd_ostream out(outputParamsFile, ec);
709 return emitError(UnknownLoc::get(&getContext()),
710 "failed to open params output file '")
711 << outputParamsFile <<
"': " << ec.message();
718 void runOnOperation()
override {
719 ModuleOp moduleOp = getOperation();
720 OpBuilder builder(&getContext());
723 std::unique_ptr<DataFlowSolver> solver;
724 WalkResult needRanges = moduleOp.walk([](AIE::DMABDOp bd) {
725 bool param = bd.getLengthParameterAttr() || bd.getOffsetParameterAttr();
726 return param && bd.getOffset() && !bd.getConstantOffset()
727 ? WalkResult::interrupt()
728 : WalkResult::advance();
730 if (needRanges.wasInterrupted()) {
731 solver = std::make_unique<DataFlowSolver>();
732 solver->load<dataflow::DeadCodeAnalysis>();
733 solver->load<dataflow::SparseConstantPropagation>();
734 solver->load<dataflow::IntegerRangeAnalysis>();
735 if (failed(solver->initializeAndRun(moduleOp)))
741 SmallVector<ScratchpadParameterOp> allParams;
742 moduleOp.walk([&](ScratchpadParameterOp p) { allParams.push_back(p); });
743 llvm::MapVector<RuntimeSequenceOp, llvm::SetVector<StringRef>> commands =
744 commandParameters(moduleOp);
745 for (
auto &[seq, reads] : commands) {
746 if (reads.size() <= kMaxStateTableEntries)
748 InFlightDiagnostic diag =
749 seq.emitError(
"runtime sequence '")
751 <<
" parameters but its scratchpad holds at most "
752 << kMaxStateTableEntries
753 <<
": each parameter a command reads takes one of the command "
754 "processor's state-table entries";
755 for (StringRef name : reads)
756 diag.attachNote(seq.getLoc()) <<
"parameter '" << name <<
"'";
757 return signalPassFailure();
766 DenseMap<StringRef, bool> usedAsCore;
767 DenseMap<StringRef, bool> usedAsLength;
768 DenseMap<StringRef, bool> usedAsAddr;
769 moduleOp.walk([&](ReadScratchpadParameterOp op) {
770 usedAsCore[op.getParameter()] =
true;
772 auto markDmaUses = [&](FlatSymbolRefAttr offsetRef,
773 FlatSymbolRefAttr lengthRef) {
775 usedAsAddr[offsetRef.getValue()] =
true;
777 usedAsLength[lengthRef.getValue()] =
true;
779 moduleOp.walk([&](NpuDmaMemcpyNdOp op) {
780 markDmaUses(op.getOffsetParameterAttr(), op.getLengthParameterAttr());
782 moduleOp.walk([&](AIE::DMABDOp op) {
783 markDmaUses(op.getOffsetParameterAttr(), op.getLengthParameterAttr());
787 DenseMap<StringRef, bool> usedAsRepeat;
788 moduleOp.walk([&](DMAStartTaskOp op) {
789 if (FlatSymbolRefAttr ref = op.getRepeatParameterAttr())
790 usedAsRepeat[ref.getValue()] =
true;
793 for (
auto p : allParams) {
794 StringRef name = p.getSymName();
795 bool core = usedAsCore.lookup(name);
796 bool length = usedAsLength.lookup(name);
797 bool addr = usedAsAddr.lookup(name);
798 if (usedAsRepeat.lookup(name)) {
799 if (core || length || addr) {
800 p.emitError(
"parameter '")
802 <<
"' is an aiex.dma_start_task repeat_parameter (repeat) and "
803 "also read by a core or used as a DMA offset or length; a "
804 "repeat parameter must have no other use";
805 return signalPassFailure();
807 p.setKindAttr(ScratchpadParameterKindAttr::get(
808 &getContext(), ScratchpadParameterKind::Repeat));
812 if (!core && !length && !addr && p.getKindAttr())
815 p.emitError(
"parameter '")
817 <<
"' is used both as an aiex.read_scratchpad_parameter source "
818 "(core) and as a DMA offset_parameter (addr); a parameter "
819 "must have a single kind";
820 return signalPassFailure();
822 p.setKindAttr(ScratchpadParameterKindAttr::get(
823 &getContext(), addr || (length && !core)
824 ? ScratchpadParameterKind::Addr
825 : ScratchpadParameterKind::Core));
829 DenseMap<StringRef, unsigned> paramIndex;
830 for (
auto [i, p] : llvm::enumerate(allParams))
831 paramIndex[p.getSymName()] = i;
832 SlotColoring coloring(allParams.size());
833 DenseMap<unsigned, SmallVector<RuntimeSequenceOp>> readers;
834 for (
auto &[seq, reads] : commands) {
835 SmallVector<unsigned> read;
836 for (StringRef name : reads)
837 if (
auto it = paramIndex.find(name); it != paramIndex.end())
838 read.push_back(it->second);
839 DenseMap<unsigned, unsigned> pinnedBy;
840 for (
auto [i, a] : llvm::enumerate(read)) {
841 readers[a].push_back(seq);
843 for (
unsigned b : llvm::drop_begin(read, i + 1))
844 coloring.addConflict(a, b);
845 std::optional<uint8_t> slot = allParams[a].getStateTableIdx();
848 auto [it, inserted] = pinnedBy.try_emplace(*slot, a);
850 allParams[a].emitError(
"parameter '")
851 << allParams[a].getSymName() <<
"' and another parameter "
853 <<
"' reads both have state-table entry " << *slot;
854 return signalPassFailure();
858 for (
auto [i, p] : llvm::enumerate(allParams)) {
859 std::optional<uint8_t> slot = p.getStateTableIdx();
862 if (*slot >= kMaxStateTableEntries) {
863 p.emitError(
"parameter '")
864 << p.getSymName() <<
"' has state-table entry " << *slot
865 <<
", past the " << kMaxStateTableEntries <<
" there are";
866 return signalPassFailure();
868 coloring.pin(i, *slot);
870 SlotColoring::Result colored = coloring.solve(kMaxSlotSearchSteps);
871 if (colored != SlotColoring::Result::Found) {
872 ScratchpadParameterOp p = allParams[coloring.stuck()];
873 InFlightDiagnostic diag = p.emitError(
"parameter '")
875 <<
"' finds no free state-table entry among "
876 << kMaxStateTableEntries <<
": ";
877 if (colored == SlotColoring::Result::Infeasible)
878 diag <<
"no assignment gives the parameters each command reads "
881 diag <<
"a search of " << kMaxSlotSearchSteps
882 <<
" steps found no assignment that gives the parameters each "
883 "command reads distinct entries";
884 for (RuntimeSequenceOp seq : readers.lookup(coloring.stuck()))
885 diag.attachNote(seq.getLoc())
887 return signalPassFailure();
889 unsigned numSlots = 0;
890 for (
auto [i, p] : llvm::enumerate(allParams)) {
891 std::optional<unsigned> slot = coloring.slot(i);
894 numSlots = std::max(numSlots, *slot + 1);
895 p.setStateTableIdxAttr(builder.getIntegerAttr(
896 builder.getIntegerType(8,
false), *slot));
898 for (
auto &[seq, reads] : commands) {
899 SmallVector<Attribute> refs;
900 for (StringRef name : reads)
901 refs.push_back(FlatSymbolRefAttr::get(&getContext(), name));
902 seq.setScratchpadParametersAttr(builder.getArrayAttr(refs));
910 [&](Operation *op, FlatSymbolRefAttr ref, StringRef attrName,
911 StringRef idxAttrName) -> FailureOr<ScratchpadParameterOp> {
913 moduleOp.lookupSymbol<ScratchpadParameterOp>(ref.getAttr());
915 op->emitOpError() << attrName <<
" '" << ref.getValue()
916 <<
"' not found. Declare it at module scope with "
917 "aiex.scratchpad_parameter.";
920 if (!paramOp.getType().isInteger(32)) {
921 auto err = op->emitOpError()
922 << attrName <<
" '" << ref.getValue()
923 <<
"' must have type i32, got " << paramOp.getType() <<
".";
924 err.attachNote(paramOp.getLoc()) <<
"Parameter declared here.";
927 std::optional<uint8_t> stateIdx = paramOp.getStateTableIdx();
929 op->emitOpError() << attrName <<
" '" << ref.getValue()
930 <<
"' has no state-table entry: no command, a "
931 "runtime sequence no aiex.run calls, reaches "
937 builder.getIntegerAttr(builder.getIntegerType(8,
false),
939 op->removeAttr(attrName);
945 DenseMap<StringRef, std::pair<int64_t, int64_t>> bounds;
947 llvm::MapVector<std::tuple<StringRef, StringRef, int64_t>, int64_t>
950 [&](Operation *op, FlatSymbolRefAttr offsetRef,
951 FlatSymbolRefAttr lengthRef, Type bufType,
952 std::optional<TransferExtent> extent,
953 std::optional<int64_t> maxLengthSteps) -> LogicalResult {
954 bool joint = offsetRef == lengthRef;
955 int64_t room = extent ? extent->bufferElems - 1 - extent->last : 0;
957 if (failed(rewriteParam(op, offsetRef,
"offset_parameter",
958 "offset_state_table_idx")))
960 warnIfRuntimeOffsetMayRound(op, bufType);
962 bounds.try_emplace(offsetRef.getValue(), INT32_MIN, INT32_MAX)
965 lo = std::max(lo, -extent->offset);
966 hi = std::min(hi, llvm::divideFloorSigned(
967 room, 1 + (joint ? extent->lengthStep : 0)));
971 FailureOr<ScratchpadParameterOp> lengthParam = rewriteParam(
972 op, lengthRef,
"length_parameter",
"length_state_table_idx");
973 if (failed(lengthParam))
976 bounds.try_emplace(lengthRef.getValue(), INT32_MIN, INT32_MAX)
978 lo = std::max<int64_t>(lo, 0);
980 hi = std::min(hi, *maxLengthSteps);
981 if (lengthParam->getKind() == ScratchpadParameterKind::Core) {
982 op->setAttr(
"length_core_encoded", builder.getUnitAttr());
983 hi = std::min<int64_t>(hi, (1 << 30) - 1);
985 if (extent && !joint && extent->lengthStep > 0)
986 hi = std::min(hi, llvm::divideFloorSigned(room, extent->lengthStep));
987 if (extent && offsetRef && !joint && extent->lengthStep > 0) {
988 auto [it, inserted] = jointBounds.try_emplace(
989 {lengthRef.getValue(), offsetRef.getValue(), extent->lengthStep},
991 it->second = std::min(it->second, room);
994 for (FlatSymbolRefAttr ref : {offsetRef, lengthRef}) {
997 auto [lo, hi] = bounds.lookup(ref.getValue());
999 return op->emitOpError(
"no value of parameter '")
1001 <<
"' keeps every transfer using it within its buffer";
1005 WalkResult rewriteResult = moduleOp.walk([&](Operation *op) {
1006 if (
auto dmaOp = dyn_cast<NpuDmaMemcpyNdOp>(op)) {
1007 FlatSymbolRefAttr offsetRef = dmaOp.getOffsetParameterAttr();
1008 FlatSymbolRefAttr lengthRef = dmaOp.getLengthParameterAttr();
1009 if (!offsetRef && !lengthRef)
1010 return WalkResult::advance();
1011 if (failed(rewriteParams(
1012 op, offsetRef, lengthRef, dmaOp.getMemref().getType(),
1013 getTransferExtent(dmaOp, lengthRef ? dmaOp.getLengthUnit()
1015 getMaxLengthSteps(dmaOp))))
1016 return WalkResult::interrupt();
1017 }
else if (
auto bdOp = dyn_cast<AIE::DMABDOp>(op)) {
1018 FlatSymbolRefAttr offsetRef = bdOp.getOffsetParameterAttr();
1019 FlatSymbolRefAttr lengthRef = bdOp.getLengthParameterAttr();
1020 if (!offsetRef && !lengthRef)
1021 return WalkResult::advance();
1022 std::optional<int64_t> lengthUnit;
1024 lengthUnit = bdOp.getLengthUnit();
1025 if (failed(rewriteParams(
1026 op, offsetRef, lengthRef, bdOp.getBuffer().getType(),
1027 getTransferExtent(bdOp, lengthUnit, solver.get()),
1028 getMaxLengthSteps(bdOp))))
1029 return WalkResult::interrupt();
1031 return WalkResult::advance();
1033 if (rewriteResult.wasInterrupted()) {
1034 return signalPassFailure();
1038 WalkResult repeatResult = moduleOp.walk([&](DMAStartTaskOp op) {
1039 FlatSymbolRefAttr ref = op.getRepeatParameterAttr();
1041 return WalkResult::advance();
1042 auto task = op.getTask().getDefiningOp<DMAConfigureTaskLike>();
1044 op.emitOpError(
"has a repeat_parameter, so it must start the task "
1045 "where it is configured: the task's repeat count "
1046 "bounds the parameter");
1047 return WalkResult::interrupt();
1049 if (task.getRepeatCountVal()) {
1050 op.emitOpError(
"has a repeat_parameter, which its task's repeat count "
1051 "bounds, but that is a runtime value");
1052 return WalkResult::interrupt();
1054 if (failed(rewriteParam(op, ref,
"repeat_parameter",
1055 "repeat_state_table_idx")))
1056 return WalkResult::interrupt();
1058 bounds.try_emplace(ref.getValue(), 0, INT32_MAX).first->second;
1059 hi = std::min<int64_t>(hi, task.getRepeatCount());
1060 return WalkResult::advance();
1062 if (repeatResult.wasInterrupted())
1063 return signalPassFailure();
1064 for (ScratchpadParameterOp p : allParams) {
1065 auto it = bounds.find(p.getSymName());
1066 if (it == bounds.end())
1068 p.setMinValueAttr(builder.getI32IntegerAttr(it->second.first));
1069 p.setMaxValueAttr(builder.getI32IntegerAttr(it->second.second));
1071 llvm::MapVector<StringRef, SmallVector<Attribute>> jointBoundAttrs;
1072 for (
auto [key, max] : jointBounds) {
1073 auto [length, offset, step] = key;
1074 jointBoundAttrs[length].push_back(JointBoundAttr::get(
1075 &getContext(), FlatSymbolRefAttr::get(&getContext(), offset), step,
1078 for (ScratchpadParameterOp p : allParams) {
1079 auto *it = jointBoundAttrs.find(p.getSymName());
1080 if (it != jointBoundAttrs.end())
1081 p.setJointBoundsAttr(builder.getArrayAttr(it->second));
1085 SmallVector<DeviceOp> devices;
1086 moduleOp.walk([&](DeviceOp d) { devices.push_back(d); });
1087 for (
auto d : devices) {
1088 allocateBuffers(d, builder);
1092 SmallVector<std::pair<uint8_t, FlatSymbolRefAttr>> paramEntries;
1093 DenseSet<StringRef> seenBufs;
1094 d.walk([&](ReadScratchpadParameterOp readOp) {
1095 FlatSymbolRefAttr bufRef = readOp.getBufferAttr();
1096 if (!bufRef || !seenBufs.insert(bufRef.getValue()).second) {
1100 moduleOp.lookupSymbol<ScratchpadParameterOp>(readOp.getParameter());
1103 if (std::optional<uint8_t> stateIdx = paramOp.getStateTableIdx())
1104 paramEntries.push_back({*stateIdx, bufRef});
1113 SmallVector<Value> syncLocks;
1114 if (failed(emitCorePreambles(d, builder, syncLocks)) ||
1115 failed(emitSequencePreambles(d, builder, !syncLocks.empty())))
1116 return signalPassFailure();
1117 uint32_t scratchpadSize = numSlots * 4;
1118 lowerSyncParametersOps(d, scratchpadSize, paramEntries, syncLocks);
1120 lowerReadParameters(d, builder);
1124 if (failed(emitParamsFile())) {
1125 return signalPassFailure();
1132std::unique_ptr<OperationPass<ModuleOp>>
1134 return std::make_unique<AIELowerScratchpadParametersPass>();
1137std::unique_ptr<OperationPass<ModuleOp>>
1139 AIELowerScratchpadParametersOptions options) {
1140 return std::make_unique<AIELowerScratchpadParametersPass>(std::move(options));
virtual uint64_t getDmaBdMaxLen(AIETileType tileType) const =0
Return the maximum transfer length, measured in address-generation granules (see getAddressGenGranula...
bool isContiguousTransfer(llvm::ArrayRef< int64_t > sizes, llvm::ArrayRef< int64_t > strides)
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createAIELowerScratchpadParametersPass()
std::string scratchpadCommandName(AIE::RuntimeSequenceOp seq)
void emitScratchpadParamsFile(mlir::ModuleOp moduleOp, llvm::raw_ostream &os)
mlir::Value createConstantI32(mlir::OpBuilder &builder, mlir::Location loc, uint32_t value)
mlir::Operation * lookupNamedOpIn(mlir::Operation *symbolTableOp, mlir::StringAttr name)
const AIETargetModel & getTargetModel(mlir::Operation *op)
void placeRuntimeLengthDimension(llvm::MutableArrayRef< int64_t > sizes, llvm::MutableArrayRef< int64_t > strides)