MLIR-AIE 1.4.5.dev
AIELowerScratchpadParameters.cpp
Go to the documentation of this file.
1//===- AIELowerScratchpadParameters.cpp - Lower scratchpad parameter ops --===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
12
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"
24
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"
29
30#include <array>
31#include <numeric>
32
33using namespace mlir;
34using namespace xilinx;
35using namespace xilinx::AIE;
36using namespace xilinx::AIEX;
37
38namespace xilinx::AIEX {
39#define GEN_PASS_DEF_AIELOWERSCRATCHPADPARAMETERS
40#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
41} // namespace xilinx::AIEX
42
43namespace {
44
45/// Returns true iff this core needs a parameter-sync lock. Reads the explicit
46/// attribute if present; otherwise defaults to true iff the core body reads a
47/// scratchpad parameter or waits for them.
48static bool shouldEmitParameterSyncPreamble(CoreOp coreOp) {
49 if (auto attr = coreOp.getEmitParameterSyncPreambleAttr()) {
50 return attr.getValue();
51 }
52 WalkResult found = coreOp.getBody().walk([&](Operation *op) {
53 return isa<ReadScratchpadParameterOp, WaitScratchpadParametersOp>(op)
54 ? WalkResult::interrupt()
55 : WalkResult::advance();
56 });
57 return found.wasInterrupted();
58}
59
60// Warned here, not at NPU lowering, which inlines a sequence at every call
61// site and so would warn once per call.
62static void warnIfRuntimeOffsetMayRound(Operation *op, Type bufType) {
63 uint32_t elemBytes =
64 llvm::cast<BaseMemRefType>(bufType).getElementTypeBitWidth() / 8;
65 if (elemBytes == 0 || elemBytes % 4 == 0) {
66 return;
67 }
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";
75}
76
77/// A DMA with runtime offset `o` and length `l` touches elements
78/// [offset + o, last + o + lengthStep * l].
79struct TransferExtent {
80 int64_t bufferElems;
81 int64_t offset;
82 int64_t last;
83 int64_t lengthStep;
84};
85
86/// `sizes`/`strides` are innermost-first and empty for a linear BD.
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())
93 return std::nullopt;
94 TransferExtent extent{buffer.getNumElements(), offset, offset, 0};
95 if (!lengthUnit) {
96 if (sizes.empty())
97 extent.last += lenElems - 1;
98 for (auto [size, stride] : llvm::zip(sizes, strides))
99 extent.last += (size - 1) * stride;
100 return extent;
101 }
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];
107 if (linear || AIEX::isContiguousTransfer(sizes, strides)) {
108 extent.last += lenElems - 1;
109 extent.lengthStep = *lengthUnit;
110 return extent;
111 }
112 AIE::placeRuntimeLengthDimension(sizes, strides);
113 int64_t rowElems = sizes[0] * sizes[1];
114 // A static length of 0 counts -1 steps, so that the first added unit's
115 // steps start at 0.
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];
119 return extent;
120}
121
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)
132 return std::nullopt;
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);
138}
139
140/// The range of a BD's runtime offset, if it is known to stay within the
141/// buffer. An offset computed from a runtime-sequence loop's induction variable
142/// is only folded once the loop is unrolled, after this pass.
143static std::optional<std::pair<int64_t, int64_t>>
144getOffsetRange(Value offset, int64_t bufferElems, DataFlowSolver *solver) {
145 if (!solver)
146 return std::nullopt;
147 auto *state = solver->lookupState<dataflow::IntegerValueRangeLattice>(offset);
148 if (!state || state->getValue().isUninitialized())
149 return std::nullopt;
150 const ConstantIntRanges &range = state->getValue().getValue();
151 // The offset may be wider than 64 bits; an extremum that does not fit is
152 // outside the buffer anyway.
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)
156 return std::nullopt;
157 return std::make_pair(*lo, *hi);
158}
159
160/// With a runtime offset, the extent starts at its smallest value and ends
161/// at its largest.
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()) {
170 if (auto range =
171 getOffsetRange(op.getOffset(), buffer.getNumElements(), solver)) {
172 offsetMin = range->first;
173 offset = range->second;
174 }
175 }
176 }
177 std::optional<int64_t> len;
178 if (op.hasLen())
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)
187 return std::nullopt;
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);
192 if (extent)
193 extent->offset = *offsetMin;
194 return extent;
195}
196
197/// The largest length parameter value that keeps the shim BD's Buffer_Length,
198/// the static length plus that many units in 32-bit words, within its field.
199static int64_t getMaxLengthSteps(Operation *op, int64_t lenBytes,
200 int64_t unitBytes) {
201 int64_t maxWords =
202 getTargetModel(op).getDmaBdMaxLen(AIETileType::ShimNOCTile);
203 return (maxWords - lenBytes / 4) / (unitBytes / 4);
204}
205
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)
211 return std::nullopt;
212 int64_t elemBytes = op.getElementTypeBitwidth() / 8;
213 return getMaxLengthSteps(op,
214 (*sizes)[1] * (*sizes)[2] * (*sizes)[3] * elemBytes,
215 *lengthUnit * elemBytes);
216}
217
218static std::optional<int64_t> getMaxLengthSteps(AIE::DMABDOp op) {
219 std::optional<int64_t> lengthUnit = op.getLengthUnit();
220 if (!lengthUnit)
221 return std::nullopt;
222 return getMaxLengthSteps(op, op.getLenInBytes(),
223 *lengthUnit * op.getBufferElementTypeWidthInBytes());
224}
225
226/// Returns true iff the parameter-sync preamble (create_scratchpad + set_lock)
227/// should be emitted into this sequence. Reads the explicit attribute if
228/// present; otherwise defaults to true iff the runtime sequence does not
229/// already contain a `aiex.sync_scratchpad_parameters_from_host` marker and
230/// either a core of the device waits on a sync lock (`coresWait`) or the device
231/// uses a scratchpad parameter from a core, a DMA BD or a task start.
232static bool shouldEmitParameterSyncPreamble(RuntimeSequenceOp seqOp,
233 bool coresWait) {
234 if (auto attr = seqOp.getEmitParameterSyncPreambleAttr()) {
235 return attr.getValue();
236 }
237 bool hasManualSync = false;
238 seqOp.walk([&](SyncScratchpadParametersFromHostOp) { hasManualSync = true; });
239 if (hasManualSync) {
240 return false;
241 }
242 if (coresWait) {
243 return true;
244 }
245 auto device = seqOp->getParentOfType<DeviceOp>();
246 if (!device) {
247 return false;
248 }
249 bool found = false;
250 device.walk([&](Operation *op) {
251 if (found) {
252 return;
253 }
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")) {
261 found = true;
262 }
263 });
264 return found;
265}
266
267// The command processor's state table: one entry per parameter a command reads.
268constexpr unsigned kMaxStateTableEntries = 32;
269
270/// The parameters each runtime sequence no `aiex.run` calls reads, in walk
271/// order: those its transfers and task starts use, directly or through the
272/// sequences it runs, and the core parameters of every device it reaches.
273/// Each such sequence is a command, which a host runs with a scratchpad of
274/// its own. The `scratchpad_parameters` of a command a previous run lowered
275/// come first, since that run dropped the uses they were found from.
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());
281 });
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);
296 }
297 });
298 });
299 llvm::MapVector<RuntimeSequenceOp, llvm::SetVector<StringRef>> commands;
300 moduleOp.walk([&](RuntimeSequenceOp seq) {
301 if (called.contains(seq))
302 return;
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());
307 // In call order: a sequence, then those it runs.
308 llvm::SetVector<RuntimeSequenceOp> reached;
309 reached.insert(seq);
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));
315 }
316 });
317 return commands;
318}
319
320/// Gives parameters state-table slots such that two a command reads together
321/// differ: a colouring of the graph whose cliques are the commands. The search
322/// is exact, so whether a module fits does not depend on the order its
323/// parameters are declared in. It goes in DSatur order (next the parameter
324/// whose conflicts hold the most distinct slots, then the most conflicts,
325/// then the first declared), lowest slot first, so its first descent is the
326/// DSatur greedy colouring, and it backtracks only when that does not fit.
327class SlotColoring {
328public:
329 enum class Result { Found, Infeasible, GaveUp };
330
331 explicit SlotColoring(unsigned numParams)
332 : conflicts(numParams), slots(numParams), wanted(numParams, false),
333 neighbourSlots(numParams), saturation(numParams, 0) {}
334
335 void addConflict(unsigned a, unsigned b) {
336 if (a == b || llvm::is_contained(conflicts[a], b))
337 return;
338 conflicts[a].push_back(b);
339 conflicts[b].push_back(a);
340 }
341
342 void want(unsigned param) { wanted[param] = true; }
343
344 void pin(unsigned param, unsigned slot) {
345 wanted[param] = false;
346 assign(param, slot);
347 maxSlot = std::max<int>(maxSlot, slot);
348 }
349
350 Result solve(unsigned stepLimit) {
351 steps = stepLimit;
352 return search();
353 }
354
355 std::optional<unsigned> slot(unsigned param) const { return slots[param]; }
356
357 /// The parameter the first descent found no slot for, after a failed solve.
358 unsigned stuck() const { return firstStuck; }
359
360private:
361 SmallVector<SmallVector<unsigned>> conflicts;
362 SmallVector<std::optional<unsigned>> slots;
363 SmallVector<bool> wanted;
364 // How many conflicts hold each slot, and how many slots that is.
365 SmallVector<std::array<unsigned, kMaxStateTableEntries>> neighbourSlots;
366 SmallVector<unsigned> saturation;
367 int maxSlot = -1;
368 unsigned steps = 0;
369 bool deadEnd = false;
370 unsigned firstStuck = 0;
371
372 void assign(unsigned param, unsigned slot) {
373 slots[param] = slot;
374 for (unsigned other : conflicts[param])
375 if (neighbourSlots[other][slot]++ == 0)
376 saturation[other]++;
377 }
378
379 void unassign(unsigned param, unsigned slot) {
380 slots[param].reset();
381 for (unsigned other : conflicts[param])
382 if (--neighbourSlots[other][slot] == 0)
383 saturation[other]--;
384 }
385
386 Result search() {
387 std::optional<unsigned> next;
388 for (unsigned p = 0; p < slots.size(); p++) {
389 if (!wanted[p] || slots[p])
390 continue;
391 if (!next || saturation[p] > saturation[*next] ||
392 (saturation[p] == saturation[*next] &&
393 conflicts[p].size() > conflicts[*next].size()))
394 next = p;
395 }
396 if (!next)
397 return Result::Found;
398 // Every slot above the highest held is alike, so only the first is tried.
399 unsigned limit = std::min<unsigned>(maxSlot + 2, kMaxStateTableEntries);
400 for (unsigned s = 0; s < limit; s++) {
401 if (neighbourSlots[*next][s] != 0)
402 continue;
403 if (deadEnd && steps-- == 0)
404 return Result::GaveUp;
405 int previousMax = maxSlot;
406 maxSlot = std::max<int>(maxSlot, s);
407 assign(*next, s);
408 Result result = search();
409 if (result != Result::Infeasible)
410 return result;
411 unassign(*next, s);
412 maxSlot = previousMax;
413 }
414 if (!deadEnd) {
415 deadEnd = true;
416 firstStuck = *next;
417 }
418 return Result::Infeasible;
419 }
420};
421
422// Slots the search tries after its first dead end before giving up.
423constexpr unsigned kMaxSlotSearchSteps = 1 << 17;
424
425struct AIELowerScratchpadParametersPass
426 : public xilinx::AIEX::impl::AIELowerScratchpadParametersBase<
427 AIELowerScratchpadParametersPass> {
428 using AIELowerScratchpadParametersBase::AIELowerScratchpadParametersBase;
429
430 // For each read_scratchpad_parameter of a unique parameter, create a 2xi32
431 // buffer and store a reference to it on the ReadScratchpadParameterOp as
432 // the `buffer` attribute.
433 void allocateBuffers(DeviceOp device, OpBuilder &builder) {
434 MLIRContext *ctx = device.getContext();
435 unsigned uniquingCounter = 0;
436 AIE::NamedOpTable names(device);
437
438 DenseMap<std::pair<StringRef, Operation *>, BufferOp> seen;
439
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());
445
446 if (seen.count(key)) {
447 readOp.setBufferAttr(
448 FlatSymbolRefAttr::get(ctx, *seen[key].getSymName()));
449 return;
450 }
451
452 builder.setInsertionPointAfter(tile);
453 // Buffer must be 8 bytes: update_from_scratchpad always writes a 48-bit
454 // value across two 32-bit registers at [RegOff] and [RegOff+4]. The
455 // firmware masks Reg[0] with 0xFFFFFFFC (lower 2 bits forced to 0)
456 // because it was designed for 4-byte-aligned DMA BD addresses. The host
457 // library's `ParameterScratchpad::write` left-shifts by 2 before
458 // writing to the scratchpad, and the core right-shifts by 2 after
459 // loading. Note that this limits effective parameter values to 30
460 // bits.
461 auto bufType = MemRefType::get({2}, builder.getI32Type());
462 std::string prefix =
463 ("__param_" + paramName + "_" + std::to_string(tile.getCol()) + "_" +
464 std::to_string(tile.getRow()) + "_")
465 .str();
466 std::string bufName = names.uniqueName(prefix, uniquingCounter);
467 auto buf =
468 BufferOp::create(builder, readOp.getLoc(), bufType, tile,
469 builder.getStringAttr(bufName), /*address=*/nullptr,
470 /*initial_value=*/nullptr, /*mem_bank=*/nullptr);
471 seen[key] = buf;
472
473 readOp.setBufferAttr(
474 FlatSymbolRefAttr::get(ctx, *seen[key].getSymName()));
475 });
476 }
477
478 // Lower each read_scratchpad_parameter to: load from buffer[0], shift right
479 // by 2, and cast to the result type. The buffer to use comes from the
480 // `buffer` attribute set by allocateBuffers().
481 void lowerReadParameters(DeviceOp device, OpBuilder &builder) {
482 SmallVector<ReadScratchpadParameterOp> readOps;
483 device.walk([&](ReadScratchpadParameterOp op) { readOps.push_back(op); });
484
485 for (auto readOp : readOps) {
486 FlatSymbolRefAttr bufRef = readOp.getBufferAttr();
487 auto buf = AIE::lookupNamedOpIn<BufferOp>(device, bufRef.getAttr());
488
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);
495
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,
500 decoded);
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,
505 masked);
506 }
507
508 readOp.getResult().replaceAllUsesWith(result);
509 readOp.erase();
510 }
511 }
512
513 // For each core in `device` with shouldEmitParameterSyncPreamble()==true,
514 // create an aie.lock (no lockID; AIEAssignLockIDs will assign one later) and
515 // acquire it: in place of each aiex.wait_scratchpad_parameters if the core
516 // has any, else once at the top of the core body. Appends the lock values
517 // created (one per qualifying core, in walk order) to `syncLocks`. Fails if
518 // a core with waits reads a parameter no wait dominates, or no wait inside
519 // the outermost loop around the read, or if a core without the preamble
520 // waits.
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();
529 });
530 }
531
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) {
537 // A loop serving several dispatches is the outermost one around the
538 // read; a wait outside it passes once, on the first.
539 Operation *outerLoop = nullptr;
540 for (Operation *op = read->getParentOp(); op != coreOp;
541 op = op->getParentOp())
542 if (isa<LoopLikeOpInterface>(op))
543 outerLoop = op;
544 bool dominated = false;
545 for (WaitScratchpadParametersOp wait : waits) {
546 if (!dominance.properlyDominates(wait, read))
547 continue;
548 dominated = true;
549 if (!outerLoop || outerLoop->isProperAncestor(wait))
550 return WalkResult::advance();
551 }
552 if (dominated)
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";
558 else
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();
564 });
565 if (unordered.wasInterrupted())
566 return WalkResult::interrupt();
567 }
568
569 TileOp tile = coreOp.getTileOp();
570 builder.setInsertionPointAfter(tile);
571 auto lockOp = LockOp::create(
572 builder, coreOp.getLoc(), builder.getIndexType(), tile.getResult(),
573 /*lockID=*/IntegerAttr{}, builder.getI32IntegerAttr(0),
574 /*sym_name=*/StringAttr{});
575 syncLocks.push_back(lockOp.getResult());
576
577 if (waits.empty()) {
578 builder.setInsertionPointToStart(&coreOp.getBody().front());
579 UseLockOp::create(builder, coreOp.getLoc(), lockOp.getResult(),
580 LockAction::Acquire, 1);
581 }
582 for (WaitScratchpadParametersOp wait : waits) {
583 builder.setInsertionPoint(wait);
584 UseLockOp::create(builder, wait.getLoc(), lockOp.getResult(),
585 LockAction::AcquireGreaterEqual, 1);
586 wait.erase();
587 }
588
589 // Mark as done so the pass is idempotent.
590 coreOp.setEmitParameterSyncPreambleAttr(builder.getBoolAttr(false));
591 return WalkResult::advance();
592 });
593 return failure(result.wasInterrupted());
594 }
595
596 // For each runtime sequence in `device` with shouldEmitParameterSyncPreamble
597 // ()==true, insert a marker SyncScratchpadParametersFromHostOp after the
598 // last top-level load_pdi of the sequence body, or at its start if there is
599 // none.
600 LogicalResult emitSequencePreambles(DeviceOp device, OpBuilder &builder,
601 bool coresWait) {
602 WalkResult result = device.walk([&](RuntimeSequenceOp seqOp) {
603 if (!shouldEmitParameterSyncPreamble(seqOp, coresWait)) {
604 return WalkResult::advance();
605 }
606
607 Region &region = seqOp.getBody();
608 if (region.empty())
609 region.emplaceBlock();
610 Block &body = region.front();
611 builder.setInsertionPointToStart(&body);
612 // Loading a PDI resets the core buffers and locks the sync writes.
613 for (NpuLoadPdiOp loadPdi : body.getOps<NpuLoadPdiOp>())
614 builder.setInsertionPointAfter(loadPdi);
615 for (Operation &op :
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())) {
628 inner->emitOpError(
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();
634 }
635 return WalkResult::advance();
636 });
637 if (early.wasInterrupted())
638 return WalkResult::interrupt();
639 }
640 SyncScratchpadParametersFromHostOp::create(builder, seqOp.getLoc());
641
642 // Mark as done so the pass is idempotent.
643 seqOp.setEmitParameterSyncPreambleAttr(builder.getBoolAttr(false));
644 return WalkResult::advance();
645 });
646 return failure(result.wasInterrupted());
647 }
648
649 // Lower a sync_scratchpad_parameters_from_host marker op in place to its
650 // component ops, using the parameter information for the device that contains
651 // it:
652 // 1. npu.create_scratchpad(scratchpadSize)
653 // 2. For each (state_idx, buffer_ref) in paramEntries: npu.write32(0) +
654 // npu.update_from_scratchpad
655 // 3. set_lock(lock, 1) for each lock in syncLocks
656 void lowerSyncParametersOp(
657 SyncScratchpadParametersFromHostOp syncOp, uint32_t scratchpadSize,
658 const SmallVector<std::pair<uint8_t, FlatSymbolRefAttr>> &paramEntries,
659 const SmallVector<Value> &syncLocks) {
660 OpBuilder builder(syncOp);
661 Location loc = syncOp.getLoc();
662
663 // A module with no parameters has nothing to stage, and size 0 is invalid.
664 if (scratchpadSize > 0)
665 NpuCreateScratchpadOp::create(builder, loc, scratchpadSize);
666
667 for (auto &[stateIdx, bufRef] : paramEntries) {
668 // Zero the destination before the additive UpdateScratchpad.
669 NpuWrite32Op::create(builder, loc,
670 /*address=*/createConstantI32(builder, loc, 0),
671 /*value=*/createConstantI32(builder, loc, 0), bufRef,
672 /*column=*/nullptr, /*row=*/nullptr);
673 NpuUpdateFromScratchpadOp::create(
674 builder, loc, stateIdx, StateTableFunc::Incr,
675 /*func_arg=*/static_cast<uint32_t>(0),
676 /*address=*/static_cast<uint32_t>(0), bufRef,
677 /*column=*/nullptr, /*row=*/nullptr);
678 }
679
680 for (Value lock : syncLocks) {
681 SetLockOp::create(builder, loc, lock, createConstantI32(builder, loc, 1));
682 }
683
684 syncOp.erase();
685 }
686
687 // Lower every sync_scratchpad_parameters_from_host op in `device` to its
688 // component ops in place, using parameter info for this device only.
689 void lowerSyncParametersOps(
690 DeviceOp device, uint32_t scratchpadSize,
691 const SmallVector<std::pair<uint8_t, FlatSymbolRefAttr>> &paramEntries,
692 const SmallVector<Value> &syncLocks) {
693 SmallVector<SyncScratchpadParametersFromHostOp> syncOps;
694 device.walk(
695 [&](SyncScratchpadParametersFromHostOp op) { syncOps.push_back(op); });
696 for (SyncScratchpadParametersFromHostOp syncOp : syncOps) {
697 lowerSyncParametersOp(syncOp, scratchpadSize, paramEntries, syncLocks);
698 }
699 }
700
701 // Emit a single params.txt for the whole module to `outputParamsFile`.
702 LogicalResult emitParamsFile() {
703 if (outputParamsFile.empty())
704 return success();
705
706 std::error_code ec;
707 llvm::raw_fd_ostream out(outputParamsFile, ec);
708 if (ec) {
709 return emitError(UnknownLoc::get(&getContext()),
710 "failed to open params output file '")
711 << outputParamsFile << "': " << ec.message();
712 }
713
714 emitScratchpadParamsFile(getOperation(), out);
715 return success();
716 }
717
718 void runOnOperation() override {
719 ModuleOp moduleOp = getOperation();
720 OpBuilder builder(&getContext());
721
722 // Ranges are only needed for a parameterized BD with a runtime offset.
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();
729 });
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)))
736 solver.reset();
737 }
738
739 // Step 1: collect every parameter in the module, and the ones each
740 // command reads.
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)
747 continue;
748 InFlightDiagnostic diag =
749 seq.emitError("runtime sequence '")
750 << scratchpadCommandName(seq) << "' reads " << reads.size()
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();
758 }
759
760 // Step 2: determine each parameter's kind from its usage, erroring on
761 // mixed use. A parameter is "core" if any aiex.read_scratchpad_parameter
762 // references it; "addr" if any DMA op references it via offset_parameter.
763 // If both, emit an error. A DMA length_parameter works with either kind,
764 // so it takes the kind of the parameter's other uses, and "addr" if it is
765 // the only use; the lowering compensates in the update's multiplier.
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;
771 });
772 auto markDmaUses = [&](FlatSymbolRefAttr offsetRef,
773 FlatSymbolRefAttr lengthRef) {
774 if (offsetRef)
775 usedAsAddr[offsetRef.getValue()] = true;
776 if (lengthRef)
777 usedAsLength[lengthRef.getValue()] = true;
778 };
779 moduleOp.walk([&](NpuDmaMemcpyNdOp op) {
780 markDmaUses(op.getOffsetParameterAttr(), op.getLengthParameterAttr());
781 });
782 moduleOp.walk([&](AIE::DMABDOp op) {
783 markDmaUses(op.getOffsetParameterAttr(), op.getLengthParameterAttr());
784 });
785 // A repeat count is written shifted into the task queue's repeat field,
786 // an encoding no other use shares.
787 DenseMap<StringRef, bool> usedAsRepeat;
788 moduleOp.walk([&](DMAStartTaskOp op) {
789 if (FlatSymbolRefAttr ref = op.getRepeatParameterAttr())
790 usedAsRepeat[ref.getValue()] = true;
791 });
792
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 '")
801 << name
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();
806 }
807 p.setKindAttr(ScratchpadParameterKindAttr::get(
808 &getContext(), ScratchpadParameterKind::Repeat));
809 continue;
810 }
811 // Step 3b drops the symbol uses, so a lowered parameter keeps its kind.
812 if (!core && !length && !addr && p.getKindAttr())
813 continue;
814 if (core && addr) {
815 p.emitError("parameter '")
816 << name
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();
821 }
822 p.setKindAttr(ScratchpadParameterKindAttr::get(
823 &getContext(), addr || (length && !core)
824 ? ScratchpadParameterKind::Addr
825 : ScratchpadParameterKind::Core));
826 }
827
828 // Step 3: colour the slots, keeping those a previous run assigned.
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);
842 coloring.want(a);
843 for (unsigned b : llvm::drop_begin(read, i + 1))
844 coloring.addConflict(a, b);
845 std::optional<uint8_t> slot = allParams[a].getStateTableIdx();
846 if (!slot)
847 continue;
848 auto [it, inserted] = pinnedBy.try_emplace(*slot, a);
849 if (!inserted) {
850 allParams[a].emitError("parameter '")
851 << allParams[a].getSymName() << "' and another parameter "
852 << "command '" << scratchpadCommandName(seq)
853 << "' reads both have state-table entry " << *slot;
854 return signalPassFailure();
855 }
856 }
857 }
858 for (auto [i, p] : llvm::enumerate(allParams)) {
859 std::optional<uint8_t> slot = p.getStateTableIdx();
860 if (!slot)
861 continue;
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();
867 }
868 coloring.pin(i, *slot);
869 }
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 '")
874 << p.getSymName()
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 "
879 "distinct entries";
880 else
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())
886 << "read by command '" << scratchpadCommandName(seq) << "'";
887 return signalPassFailure();
888 }
889 unsigned numSlots = 0;
890 for (auto [i, p] : llvm::enumerate(allParams)) {
891 std::optional<unsigned> slot = coloring.slot(i);
892 if (!slot)
893 continue;
894 numSlots = std::max(numSlots, *slot + 1);
895 p.setStateTableIdxAttr(builder.getIntegerAttr(
896 builder.getIntegerType(8, /*isSigned=*/false), *slot));
897 }
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));
903 }
904
905 // Step 3b: rewrite DMA `offset_parameter` / `length_parameter` symbol
906 // references to plain `offset_state_table_idx` / `length_state_table_idx`
907 // integer attributes, so downstream `aie`-dialect passes do not need to
908 // resolve `aiex.scratchpad_parameter` symbols.
909 auto rewriteParam =
910 [&](Operation *op, FlatSymbolRefAttr ref, StringRef attrName,
911 StringRef idxAttrName) -> FailureOr<ScratchpadParameterOp> {
912 auto paramOp =
913 moduleOp.lookupSymbol<ScratchpadParameterOp>(ref.getAttr());
914 if (!paramOp) {
915 op->emitOpError() << attrName << " '" << ref.getValue()
916 << "' not found. Declare it at module scope with "
917 "aiex.scratchpad_parameter.";
918 return failure();
919 }
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.";
925 return failure();
926 }
927 std::optional<uint8_t> stateIdx = paramOp.getStateTableIdx();
928 if (!stateIdx) {
929 op->emitOpError() << attrName << " '" << ref.getValue()
930 << "' has no state-table entry: no command, a "
931 "runtime sequence no aiex.run calls, reaches "
932 "this op";
933 return failure();
934 }
935 op->setAttr(
936 idxAttrName,
937 builder.getIntegerAttr(builder.getIntegerType(8, /*isSigned=*/false),
938 *stateIdx));
939 op->removeAttr(attrName);
940 return paramOp;
941 };
942 // The values of each DMA offset or length parameter that keep every
943 // transfer using it within its buffer, judged with the transfer's other
944 // parameter at 0.
945 DenseMap<StringRef, std::pair<int64_t, int64_t>> bounds;
946 // (length, offset, length step) -> max of offset + length step * length.
947 llvm::MapVector<std::tuple<StringRef, StringRef, int64_t>, int64_t>
948 jointBounds;
949 auto rewriteParams =
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;
956 if (offsetRef) {
957 if (failed(rewriteParam(op, offsetRef, "offset_parameter",
958 "offset_state_table_idx")))
959 return failure();
960 warnIfRuntimeOffsetMayRound(op, bufType);
961 auto &[lo, hi] =
962 bounds.try_emplace(offsetRef.getValue(), INT32_MIN, INT32_MAX)
963 .first->second;
964 if (extent) {
965 lo = std::max(lo, -extent->offset);
966 hi = std::min(hi, llvm::divideFloorSigned(
967 room, 1 + (joint ? extent->lengthStep : 0)));
968 }
969 }
970 if (lengthRef) {
971 FailureOr<ScratchpadParameterOp> lengthParam = rewriteParam(
972 op, lengthRef, "length_parameter", "length_state_table_idx");
973 if (failed(lengthParam))
974 return failure();
975 auto &[lo, hi] =
976 bounds.try_emplace(lengthRef.getValue(), INT32_MIN, INT32_MAX)
977 .first->second;
978 lo = std::max<int64_t>(lo, 0);
979 if (maxLengthSteps)
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);
984 }
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},
990 room);
991 it->second = std::min(it->second, room);
992 }
993 }
994 for (FlatSymbolRefAttr ref : {offsetRef, lengthRef}) {
995 if (!ref)
996 continue;
997 auto [lo, hi] = bounds.lookup(ref.getValue());
998 if (lo > hi)
999 return op->emitOpError("no value of parameter '")
1000 << ref.getValue()
1001 << "' keeps every transfer using it within its buffer";
1002 }
1003 return success();
1004 };
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()
1014 : std::nullopt),
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;
1023 if (lengthRef)
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();
1030 }
1031 return WalkResult::advance();
1032 });
1033 if (rewriteResult.wasInterrupted()) {
1034 return signalPassFailure();
1035 }
1036 // A repeat parameter is bounded by its task's own repeat count, the
1037 // largest the start may push.
1038 WalkResult repeatResult = moduleOp.walk([&](DMAStartTaskOp op) {
1039 FlatSymbolRefAttr ref = op.getRepeatParameterAttr();
1040 if (!ref)
1041 return WalkResult::advance();
1042 auto task = op.getTask().getDefiningOp<DMAConfigureTaskLike>();
1043 if (!task) {
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();
1048 }
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();
1053 }
1054 if (failed(rewriteParam(op, ref, "repeat_parameter",
1055 "repeat_state_table_idx")))
1056 return WalkResult::interrupt();
1057 auto &[lo, hi] =
1058 bounds.try_emplace(ref.getValue(), 0, INT32_MAX).first->second;
1059 hi = std::min<int64_t>(hi, task.getRepeatCount());
1060 return WalkResult::advance();
1061 });
1062 if (repeatResult.wasInterrupted())
1063 return signalPassFailure();
1064 for (ScratchpadParameterOp p : allParams) {
1065 auto it = bounds.find(p.getSymName());
1066 if (it == bounds.end())
1067 continue;
1068 p.setMinValueAttr(builder.getI32IntegerAttr(it->second.first));
1069 p.setMaxValueAttr(builder.getI32IntegerAttr(it->second.second));
1070 }
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,
1076 max));
1077 }
1078 for (ScratchpadParameterOp p : allParams) {
1079 auto *it = jointBoundAttrs.find(p.getSymName());
1080 if (it != jointBoundAttrs.end())
1081 p.setJointBoundsAttr(builder.getArrayAttr(it->second));
1082 }
1083
1084 // Step 4: per-device lowering.
1085 SmallVector<DeviceOp> devices;
1086 moduleOp.walk([&](DeviceOp d) { devices.push_back(d); });
1087 for (auto d : devices) {
1088 allocateBuffers(d, builder);
1089
1090 // Collect unique (stateIdx, bufferRef) pairs for core-kind parameters in
1091 // this device. Buffer attrs are set by allocateBuffers() above.
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) {
1097 return;
1098 }
1099 auto paramOp =
1100 moduleOp.lookupSymbol<ScratchpadParameterOp>(readOp.getParameter());
1101 // No command reaches a core whose parameter has no slot, so no sync
1102 // writes it.
1103 if (std::optional<uint8_t> stateIdx = paramOp.getStateTableIdx())
1104 paramEntries.push_back({*stateIdx, bufRef});
1105 });
1106
1107 // Emit lock + use_lock preambles in cores, then insert a marker
1108 // SyncScratchpadParametersFromHostOp at the start of each qualifying
1109 // runtime sequence. Immediately lower every
1110 // sync_scratchpad_parameters_from_host op in the device (both the
1111 // preamble-inserted ones and any user-written ones) using this device's
1112 // parameter info.
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);
1119
1120 lowerReadParameters(d, builder);
1121 }
1122
1123 // Step 5: emit the single params.txt for the module.
1124 if (failed(emitParamsFile())) {
1125 return signalPassFailure();
1126 }
1127 }
1128};
1129
1130} // namespace
1131
1132std::unique_ptr<OperationPass<ModuleOp>>
1134 return std::make_unique<AIELowerScratchpadParametersPass>();
1135}
1136
1137std::unique_ptr<OperationPass<ModuleOp>>
1139 AIELowerScratchpadParametersOptions options) {
1140 return std::make_unique<AIELowerScratchpadParametersPass>(std::move(options));
1141}
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)
Definition AIEUtils.cpp:254
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)