MLIR-AIE 1.4.6.dev
AIEXDialect.cpp
Go to the documentation of this file.
1//===- AIEXDialect.cpp ------------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2019-2022 Xilinx, Inc.
4// Copyright (C) 2022-2026 Advanced Micro Devices, Inc.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
12
13#include "mlir/Dialect/Arith/IR/Arith.h"
14#include "mlir/Dialect/Func/IR/FuncOps.h"
15#include "mlir/IR/DialectImplementation.h"
16#include "mlir/IR/Matchers.h"
17#include "mlir/IR/Operation.h"
18#include "mlir/IR/RegionGraphTraits.h"
19#include "mlir/IR/SymbolTable.h"
20#include "mlir/IR/TypeUtilities.h"
21#include "mlir/Interfaces/DataLayoutInterfaces.h"
22#include "mlir/Interfaces/FoldInterfaces.h"
23#include "mlir/Transforms/InliningUtils.h"
24#include "llvm/ADT/SCCIterator.h"
25#include "llvm/ADT/STLExtras.h"
26
27#include "llvm/ADT/DenseMap.h"
28#include "llvm/ADT/TypeSwitch.h"
29#include "llvm/Support/CheckedArithmetic.h"
30#include "llvm/Support/MathExtras.h"
31#include "llvm/Support/TypeSize.h"
32
33#include <cstdint>
34#include <limits>
35#include <numeric>
36
37using namespace mlir;
38using namespace xilinx;
39
40#include "aie/Dialect/AIEX/IR/AIEXDialect.cpp.inc"
41
42#include "aie/Dialect/AIEX/IR/AIEXEnums.cpp.inc"
43
44#include "aie/Dialect/AIEX/IR/AIEXInterfaces.cpp.inc"
45
46#define GET_TYPEDEF_CLASSES
47#include "aie/Dialect/AIEX/IR/AIEXTypes.cpp.inc"
48
49#define GET_ATTRDEF_CLASSES
50#include "aie/Dialect/AIEX/IR/AIEXAttrDefs.cpp.inc"
51
52namespace xilinx::AIEX {
53
54// FIXME: use Tablegen'd dialect class
55void AIEXDialect::initialize() {
56 addOperations<
57#define GET_OP_LIST
58#include "aie/Dialect/AIEX/IR/AIEX.cpp.inc"
59 >();
60 addTypes<
61#define GET_TYPEDEF_LIST
62#include "aie/Dialect/AIEX/IR/AIEXTypes.cpp.inc"
63 >();
64 addAttributes<
65#define GET_ATTRDEF_LIST
66#include "aie/Dialect/AIEX/IR/AIEXAttrDefs.cpp.inc"
67 >();
68}
69
70} // namespace xilinx::AIEX
71
74
75#define GET_OP_CLASSES
76#include "aie/Dialect/AIEX/IR/AIEX.cpp.inc"
77
78/* Return the correct values to write to the hardware registers to configure
79 strides and wraps given the input user-facing strides and wraps.
80
81 In the IR, we express strides in units of element data type, but the hardware
82 requires it in units of address granularity. Address granularity currently is
83 4 bytes for all hardware.
84
85
86 User-facing strides/wraps relate to hardware as follows:
87
88 - By default, stride 0 and size 1 is assumed if unspecified.
89 - If only N strides/wraps are defined, those define the lowest N dimensions.
90
91 inputStride[3] == iteration_stride / elemSizeFac + 1
92 inputWrap[3] == iteration_size + 1
93 Highest-dimension stride/wrap is iteration count / iteration stride.
94 inputStride[2] == d2_stride / elemSizeFac + 1
95 Note: d2_size is not specified in hardware as it is
96 implicit from the total buffer transfer length
97 inputStride[1] == d1_stride / elemSizeFac + 1
98 inputSize[1] == d1_size
99 inputStride[0] == d0_stride / elemSizeFac + 1
100 inputSize[0] == d0_size / elemSizeFac
101
102 where elemSizeFac == bufferElementSize / addressGranularity
103 where bufferElementSize == size in bytes of elements in buffer,
104 e.g. 4 for int32
105 where addressGranularity == transfer granularity in hardware, which is
106 4 bytes for all current hardware
107
108 Note: strides are expressed offset by one from user input strides, because the
109 hardware does not support a 0 stride (repeat).
110 */
112 mlir::Operation *op,
113 mlir::BaseMemRefType referencedBufType,
114 llvm::SmallVector<int64_t, 4> inputSizes,
115 llvm::SmallVector<int64_t, 4> inputStrides,
116 llvm::SmallVector<int64_t, 4> &sizes,
117 llvm::SmallVector<int64_t, 4> &strides) {
118 assert(inputSizes.size() == inputStrides.size());
119 assert(sizes.size() == 4);
120 assert(strides.size() == 4);
121
122 DataLayout dataLayout = DataLayout::closest(op);
123 auto elemWidth =
124 dataLayout.getTypeSizeInBits(referencedBufType.getElementType());
125 auto addressGranularity = targetModel.getAddressGenGranularity();
126
127 // Output strides and sizes are default-initialized to 0
128 llvm::fill(sizes, 0);
129 llvm::fill(strides, 0);
130
131 if (inputSizes[0] == 0) {
132 // Illegal input, this won't transfer anything at all.
133 // Leave it to the verification functions to complain to the user.
134 return;
135 }
136
137 ConstStridePolicy policy;
138 int64_t inS[4] = {inputSizes[0], inputSizes[1], inputSizes[2], inputSizes[3]};
139 int64_t inT[4] = {inputStrides[0], inputStrides[1], inputStrides[2],
140 inputStrides[3]};
141 int64_t outS[4], outT[4];
142 encodeHardwareStridesWraps(policy, elemWidth, addressGranularity, inS, inT,
143 outS, outT);
144 for (int i = 0; i < 4; i++) {
145 sizes[i] = outS[i];
146 strides[i] = outT[i];
147 }
148}
149
150mlir::LogicalResult AIEX::verifyStridesWraps(
151 mlir::Operation *forOp,
152 llvm::function_ref<mlir::InFlightDiagnostic()> emitError,
153 mlir::BaseMemRefType referencedBufType, int tileCol, int tileRow,
154 llvm::SmallVector<int64_t, 4> inputSizes,
155 llvm::SmallVector<int64_t, 4> inputStrides,
156 llvm::SmallVector<int64_t, 4> hardwareSizes,
157 llvm::SmallVector<int64_t, 4> hardwareStrides,
158 bool skipTransformationChecks) {
159 const auto &targetModel = AIE::getTargetModel(forOp);
160 auto addressGranularity = targetModel.getAddressGenGranularity();
161 DataLayout dataLayout = DataLayout::closest(forOp);
162 auto elemWidth =
163 dataLayout.getTypeSizeInBits(referencedBufType.getElementType());
164
165 // ShimPLTiles have no ShimDMA, so BD field widths are meaningless here.
166 if (!targetModel.isCoreTile(tileCol, tileRow) &&
167 !targetModel.isMemTile(tileCol, tileRow) &&
168 !targetModel.isShimNOCTile(tileCol, tileRow))
169 return emitError() << "Unsupported tile type at (" << tileCol << ", "
170 << tileRow << ") Must be ShimNOC, Mem or Core.";
171
172 uint32_t wrap_bits = targetModel.getDmaBdWrapBits(tileCol, tileRow);
173 uint32_t step_bits = targetModel.getDmaBdStepBits(tileCol, tileRow);
174
175 for (int i = 0; i < 4; i++) {
176 if (inputSizes[i] <= 0) {
177 return emitError() << "Size " << i << " must be a positive integer.";
178 }
179 }
180
181 if (!isConstMultipleOfGranule(inputSizes[0], elemWidth, addressGranularity)) {
182 std::stringstream msg;
183 msg << "Transfer sizes must be multiples of " << (addressGranularity / 8)
184 << " bytes. " << inputSizes[0] << " elements at " << (elemWidth / 8)
185 << " bytes each equal " << (inputSizes[0] * elemWidth / 8)
186 << " bytes, which is not divisible by " << (addressGranularity / 8)
187 << ". ";
188 return emitError() << msg.str();
189 }
190
191 for (int i = 0; i < 3; i++) {
192 if (inputSizes[i] > 1 && inputStrides[i] < 1) {
193 // If inputSize[i] == 1, anything is allowable in the stride, since that
194 // stride will never be applied. For any larger size, we must verify that
195 // the stride is positive.
196 return emitError() << "Stride " << i << " must be a positive integer.";
197 }
198 }
199 // A value of zero is allowable for the fourth-dimension stride
200 // (this indicates an interation stride for the repeat of 0)
201 if (inputSizes[3] > 1 && inputStrides[3] < 0) {
202 return emitError() << "Stride 3 must be a non-negative integer.";
203 }
204
205 for (int i = 0; i < 4; i++) {
206 // strides[0] == 1 is ok iff the transfer size is a multiple of
207 // addressGranularity, which is checked below
208 if (i == 0 && inputStrides[i] == 1)
209 continue;
210 if (!isConstMultipleOfGranule(inputStrides[i], elemWidth,
211 addressGranularity)) {
212 std::stringstream msg;
213 msg << "Stride " << i << " is " << inputStrides[i] << " elements * "
214 << (elemWidth / 8) << " bytes = " << (inputStrides[i] * elemWidth / 8)
215 << " bytes, which is not divisible by " << (addressGranularity / 8)
216 << ". ";
217 return emitError() << msg.str();
218 }
219 }
220 // The innermost dimension steps whole granules, so for elements of another
221 // width it can only be contiguous.
222 if (elemWidth != addressGranularity && inputSizes[0] > 1 &&
223 inputStrides[0] != 1)
224 return emitError() << "Stride 0 is " << inputStrides[0]
225 << " elements, but must be 1 for " << (elemWidth / 8)
226 << "-byte elements: the DMA moves whole "
227 << (addressGranularity / 8) << "-byte words.";
228
229 auto outOfRange = [&](const char *what, int dim, int64_t written,
230 int64_t encoded, int64_t lo, int64_t hi) {
231 return emitError() << what << " " << dim << " is " << written
232 << " (encoded as " << encoded << "), which exceeds the ["
233 << lo << ":" << hi << "] range.";
234 };
235
236 int64_t maxWrap = (1 << wrap_bits) - 1;
237 int64_t maxStep = 1 << step_bits;
238
239 for (int dim : {0, 1}) {
240 if (!skipTransformationChecks && hardwareSizes[dim] > maxWrap)
241 return outOfRange("Size", dim, inputSizes[dim], hardwareSizes[dim], 0,
242 maxWrap);
243 }
244 // A zero d3 stride is a pure repeat: the lowerings leave the iteration fields
245 // 0 and carry the count on the queue push, so the repeat limit applies.
246 bool pureRepeat = inputStrides[3] == 0;
247 int64_t maxCount =
248 pureRepeat ? targetModel.getMaxRepeatCount() + 1
249 : 1LL << targetModel.getDmaBdIterBits(tileCol, tileRow);
250 if (inputSizes[3] > maxCount)
251 return emitError() << (pureRepeat ? "repeat count " : "iteration count ")
252 << inputSizes[3] << " exceeds the [1:" << maxCount
253 << "] range.";
254 for (int dim : {0, 1, 2}) {
255 if (hardwareStrides[dim] > maxStep - 1)
256 return outOfRange("Stride", dim, inputStrides[dim], hardwareStrides[dim],
257 1, maxStep);
258 }
259 // strides[3] exceeding the range is ok iff the sizes[3] is one, which is
260 // checked below
261 if (hardwareStrides[3] > maxStep - 1 && hardwareSizes[3] > 0)
262 return outOfRange("Stride", 3, inputStrides[3], hardwareStrides[3], 1,
263 maxStep);
264
265 return success();
266}
267
268//===----------------------------------------------------------------------===//
269// UseTokenOp
270//===----------------------------------------------------------------------===//
271
272// A field flagged as a register must name one of r0..r23. The bound cannot be
273// an attribute constraint, because the same field holds a full 32-bit
274// immediate when unflagged.
275LogicalResult AIEX::CertWrite32DOp::verify() {
276 if (getAddressIsReg() && getAddress() > 23)
277 return emitOpError("has address_is_reg, so address ")
278 << getAddress() << " must be a register index in [0, 23]";
279 if (getValueIsReg() && getValue() > 23)
280 return emitOpError("has value_is_reg, so value ")
281 << getValue() << " must be a register index in [0, 23]";
282 return success();
283}
284
285LogicalResult AIEX::UseTokenOp::verify() {
286 auto *parentOp = (*this)->getParentOp();
287 if (isa<func::FuncOp>(parentOp) || isa<AIE::CoreOp>(parentOp) ||
288 isa<AIE::MemOp>(parentOp) || isa<AIE::ShimDMAOp>(parentOp))
289 return success();
290 return failure();
291}
292
293//===----------------------------------------------------------------------===//
294// MulticastOp
295//===----------------------------------------------------------------------===//
296
297LogicalResult AIEX::MulticastOp::verify() {
298 Region &body = getPorts();
299 assert(getOperation()->getNumRegions());
300 assert(!body.empty());
301 for (auto &ops : body.front())
302 if (!isa<MultiDestOp, AIE::EndOp>(ops))
303 return ops.emitOpError("cannot be contained in a Multicast op");
304
305 return success();
306}
307
308//===----------------------------------------------------------------------===//
309// BroadcastPacketOp
310//===----------------------------------------------------------------------===//
311
312LogicalResult AIEX::BroadcastPacketOp::verify() {
313 Region &body = getPorts();
314 assert(getOperation()->getNumRegions());
315 assert(!body.empty());
316 for (auto &ops : body.front())
317 if (!isa<BPIDOp, AIE::EndOp>(ops))
318 return ops.emitOpError("cannot be contained in a BroadcastPacket op");
319
320 return success();
321}
322
323//===----------------------------------------------------------------------===//
324// NpuDmaMemcpyNdOp
325//===----------------------------------------------------------------------===//
326
327/* Calculates the offset value to be written to the
328 */
329uint32_t AIEX::NpuDmaMemcpyNdOp::getAxcacheOrDefault() {
330 return getAxcache().value_or(AIE::getTargetModel(*this).getDefaultAxCache());
331}
332
333int64_t AIEX::NpuDmaMemcpyNdOp::getOffsetInBytes() {
334 llvm::SmallVector<int64_t, 4> offsets =
335 llvm::map_to_vector(llvm::reverse(getMixedOffsets()), [](OpFoldResult s) {
336 return getConstantIntValue(s).value();
337 });
338 auto strides = llvm::to_vector<4>(llvm::reverse(getMixedStrides()));
339 size_t offset = 0;
340 size_t R = offsets.size();
341 size_t el_bit_width = getElementTypeBitwidth();
342 assert(el_bit_width % 8 == 0 &&
343 "Expected Memref element bitwidth to be multiple of 8.");
344 size_t S = el_bit_width / 8;
345 // A dimension only contributes to the byte offset when its (constant) offset
346 // is non-zero; a runtime stride paired with a zero offset is fine and must
347 // not be forced to a constant. The verifier requires any stride multiplied by
348 // a non-zero offset to be constant.
349 for (size_t i = 0; i < R; i++) {
350 if (offsets[i] == 0)
351 continue;
352 auto strideConst = getConstantIntValue(strides[i]);
353 assert(strideConst &&
354 "verifier requires a stride paired with a non-zero offset to be "
355 "constant");
356 offset += offsets[i] * (*strideConst) * S;
357 }
358 return offset;
359}
360
361// Returns true when sizes/strides describe a plain contiguous transfer with
362// no data layout transformation (d1/d2 sizes == 1, d0 stride == 1).
363// d3 (repeat) is intentionally excluded.
364bool AIEX::isLinearTransfer(llvm::ArrayRef<int64_t> sizes,
365 llvm::ArrayRef<int64_t> strides) {
366 return sizes[1] == 1 && sizes[2] == 1 && strides[0] == 1 && strides[1] == 0 &&
367 strides[2] == 0;
368}
369
370// Returns true when sizes/strides (innermost-first) describe a contiguous
371// row-major scan: innermost stride == 1 and each outer stride equals the
372// product of all inner sizes. The repeat dimension (index 3) is excluded.
373// Size-1 dimensions are allowed to carry any stride value because that stride
374// is never applied during the transfer (the loop runs only once).
375// This is the vector-form counterpart of AIE::isContiguousBDTransfer.
376bool AIEX::isContiguousTransfer(llvm::ArrayRef<int64_t> sizes,
377 llvm::ArrayRef<int64_t> strides) {
378 if (strides[0] != 1)
379 return false;
380 if (sizes[1] > 1 && strides[1] != sizes[0])
381 return false;
382 if (sizes[2] > 1 && strides[2] != sizes[0] * sizes[1])
383 return false;
384 return true;
385}
386
387bool AIEX::isLinearModeTransfer(const AIE::AIETargetModel &targetModel, int col,
388 int row, llvm::ArrayRef<int64_t> sizes,
389 llvm::ArrayRef<int64_t> strides) {
390 return isLinearTransfer(sizes, strides) ||
391 (targetModel.isShimNOCTile(col, row) &&
392 isContiguousTransfer(sizes, strides));
393}
394
395// dma_memcpy_nd transfers of the form [*, 1, 1, len][*, 0, 0, 1] do not
396// specify any data layout transformation, but simply express a contiguous
397// transfer of `len`. The 4th dimension is excluded because a repeat count
398// is still compatible with a linear transfer.
399bool AIEX::NpuDmaMemcpyNdOp::isLinearTransferWithoutTransformation() {
400 llvm::SmallVector<int64_t, 4> inputSizes =
401 llvm::map_to_vector(llvm::reverse(getMixedSizes()), [](OpFoldResult s) {
402 return getConstantIntValue(s).value();
403 });
404 llvm::SmallVector<int64_t, 4> inputStrides =
405 llvm::map_to_vector(llvm::reverse(getMixedStrides()), [](OpFoldResult s) {
406 return getConstantIntValue(s).value();
407 });
408 return isLinearTransfer(inputSizes, inputStrides);
409}
410
411// Canonicalization pattern: rewrite a contiguous row-major access pattern to
412// the canonical linear form [s3, 1, 1, N][st3, 0, 0, 1].
413//
414// Using outermost-first index notation (matching the IR syntax), a 4D access
415// [s3, s2, s1, s0][st3, st2, st1, st0] is a contiguous linear scan when:
416// st0 == 1
417// s1 == 1 || st1 == s0 (stride irrelevant when size is 1)
418// s2 == 1 || st2 == s0 * s1
419// yielding a total of N = s0 * s1 * s2 contiguous elements. The repeat
420// dimension s3 / stride st3 is unchanged by the fold.
421//
422// Note: this pattern applies only to NpuDmaMemcpyNdOp. The analogous
423// dimensionsToStream / dimensionsFromStreamPerConsumer attributes on
424// ObjectFifoCreateOp are not canonicalized here; they are lowered separately
425// by the ObjectFifo stateful transform pass.
426//
427// This fold is always semantically valid and never introduces new hardware
428// limit violations: in the resulting linear form, isLinearTransferWithout-
429// Transformation() returns true, so verifyStridesWraps() skips the 10-bit
430// d0 wrap-size check. The hardware uses a wider buffer_length register in
431// linear mode (32-bit on shim tiles, 17-bit on mem tiles, 14-bit on core
432// tiles), so N can be much larger than the 10-bit ND wrap limit.
433namespace {
434struct LinearizeContiguousTransfer
435 : public mlir::OpRewritePattern<AIEX::NpuDmaMemcpyNdOp> {
436 using OpRewritePattern::OpRewritePattern;
437
438 mlir::LogicalResult
439 matchAndRewrite(AIEX::NpuDmaMemcpyNdOp op,
440 mlir::PatternRewriter &rewriter) const override {
441 // Only constant sizes/strides/offsets can be analysed statically.
442 if (!llvm::all_of(op.getMixedSizes(), [](mlir::OpFoldResult s) {
443 return mlir::getConstantIntValue(s).has_value();
444 }))
445 return mlir::failure();
446 if (!llvm::all_of(op.getMixedStrides(), [](mlir::OpFoldResult s) {
447 return mlir::getConstantIntValue(s).has_value();
448 }))
449 return mlir::failure();
450 if (!llvm::all_of(op.getMixedOffsets(), [](mlir::OpFoldResult s) {
451 return mlir::getConstantIntValue(s).has_value();
452 }))
453 return mlir::failure();
454
455 // Skip ops that are already in canonical linear form.
456 if (op.isLinearTransferWithoutTransformation())
457 return mlir::failure();
458
459 // getMixedSizes/Strides/Offsets return outermost-first; reverse to
460 // innermost-first so index 0 = d0 (innermost) and index 3 = repeat.
461 llvm::SmallVector<int64_t, 4> sizes = llvm::map_to_vector(
462 llvm::reverse(op.getMixedSizes()), [](mlir::OpFoldResult s) {
463 return mlir::getConstantIntValue(s).value();
464 });
465 llvm::SmallVector<int64_t, 4> strides = llvm::map_to_vector(
466 llvm::reverse(op.getMixedStrides()), [](mlir::OpFoldResult s) {
467 return mlir::getConstantIntValue(s).value();
468 });
469 llvm::SmallVector<int64_t, 4> offsets = llvm::map_to_vector(
470 llvm::reverse(op.getMixedOffsets()), [](mlir::OpFoldResult s) {
471 return mlir::getConstantIntValue(s).value();
472 });
473
474 // Require a contiguous row-major scan.
475 if (!AIEX::isContiguousTransfer(sizes, strides))
476 return mlir::failure();
477
478 // Fold d0/d1/d2 into one linear count; keep the repeat dimension intact.
479 // Build directly in outermost-first order for the replacement op.
480 int64_t N = sizes[0] * sizes[1] * sizes[2];
481 llvm::SmallVector<int64_t, 4> newSizesOuter = {sizes[3], 1, 1, N};
482 llvm::SmallVector<int64_t, 4> newStridesOuter = {strides[3], 0, 0, 1};
483
484 // getOffsetInBytes() computes: sum(offsets[i] * strides[i] * elemSize).
485 // After folding, the intermediate strides become 0, so any non-zero offset
486 // in those dimensions would silently contribute 0 bytes. Preserve the
487 // correct start address by collapsing the innermost three offset/stride
488 // pairs into a single linear element index at d0.
489 int64_t linearOffset = offsets[0] * strides[0] + offsets[1] * strides[1] +
490 offsets[2] * strides[2];
491 llvm::SmallVector<int64_t, 4> newOffsetsOuter = {offsets[3], 0, 0,
492 linearOffset};
493
494 rewriter.replaceOpWithNewOp<AIEX::NpuDmaMemcpyNdOp>(
495 op, op, newOffsetsOuter, newSizesOuter, newStridesOuter);
496 return mlir::success();
497 }
498};
499} // namespace
500
501void AIEX::NpuDmaMemcpyNdOp::getCanonicalizationPatterns(
502 mlir::RewritePatternSet &results, mlir::MLIRContext *context) {
503 results.add<LinearizeContiguousTransfer>(context);
504}
505
506// Helper method to check if a requested burst length is supported by the target
507// model. Returns an error message if the burst length is not supported or an
508// empty option otherwise.
509static std::optional<std::string>
510checkBurstLength(const xilinx::AIE::AIETargetModel &targetModel,
511 uint32_t requestedBurstLength) {
512 if (requestedBurstLength != 0) {
513 auto bel = targetModel.getShimBurstEncodingsAndLengths();
514 auto pair = llvm::find_if(bel, [=](const std::pair<uint32_t, uint32_t> &p) {
515 return p.second == requestedBurstLength;
516 });
517
518 if (pair == bel.end()) {
519 std::string errorMessage =
520 "Requested burst length is not supported by the target. "
521 "Supported burst lengths:";
522
523 errorMessage =
524 std::accumulate(bel.begin(), bel.end(), errorMessage,
525 [](const std::string &a, auto b) {
526 return a + " " + std::to_string(b.second);
527 });
528
529 return errorMessage;
530 }
531 }
532
533 return std::nullopt;
534}
535
536// Verify the supported scope for a dma_memcpy_nd carrying runtime (SSA)
537// offsets/sizes/strides, hard-erroring on any statically-provable violation.
538// The scope here is exactly what the dynamic BD encoder (AIEDmaToNpu.cpp
539// lowerDynamic) can lower; runtime values are never silently masked.
540LogicalResult AIEX::NpuDmaMemcpyNdOp::verifyDynamicSizesStrides(
541 const AIE::AIETargetModel &targetModel, mlir::BaseMemRefType buffer,
542 AIE::ShimDMAAllocationOp allocOp) {
543 // Shim NOC only.
544 if (!allocOp)
545 return emitOpError(
546 "runtime sizes/strides require a shim_dma_allocation to resolve the "
547 "tile; none found.");
548 AIE::TileOp tile = allocOp.getTileOp();
549 // Coordinate-dependent checks must wait until logical tiles are placed.
550 if (!tile)
551 return success();
552 if (!targetModel.isShimNOCTile(tile.getCol(), tile.getRow()))
553 return emitOpError(
554 "runtime sizes/strides are only supported for shim NOC tile DMAs.");
555
556 // No zero-padding with runtime sizes/strides.
557 if (getD0ZeroBefore() || getD1ZeroBefore() || getD2ZeroBefore() ||
558 getD0ZeroAfter() || getD1ZeroAfter() || getD2ZeroAfter())
559 return emitOpError(
560 "zero padding is not supported with runtime sizes/strides.");
561
562 // Per-field runtime values are allowed: any individual size/stride may be an
563 // SSA value while others stay constant. Only the runtime-dependent fields
564 // need runtime handling; the encoder produces the same word for a constant
565 // operand either way, so the static ≡ dynamic byte-identity holds field by
566 // field.
567 auto sizes = getMixedSizes();
568 auto strides = getMixedStrides();
569
570 // The innermost stride may be runtime like any other dimension: the encoder
571 // resolves its collapse-to-zero case with a select, and its realizability
572 // (unit stride, or granule-aligned) is enforced below for constants and by a
573 // host-side check (buildBdWords) for runtime values.
574
575 // (The memref must also trace to a runtime-sequence block argument through
576 // static subview/cast offsets; that structural check, with the same clean
577 // diagnostic, is enforced by the lowering in AIEDmaToNpu.cpp, which already
578 // owns the trace utility. It is not duplicated here to keep the dialect
579 // verifier free of the analysis-layer dependency.)
580
581 // A constant size must fit its hardware wrap field (an out-of-range constant
582 // is a hard error, never a silent truncation); runtime sizes are guarded at
583 // lowering. Bounds are checked on the ENCODED value, matching
584 // verifyStridesWraps: d0 wrap scaled to granules (size * elemWidth / gran),
585 // iteration wrap biased by -1, d1 a raw element count. Sizes outermost-first.
586 DataLayout dataLayout = DataLayout::closest(getOperation());
587 uint64_t elemWidth = dataLayout.getTypeSizeInBits(buffer.getElementType());
588 uint32_t gran = targetModel.getAddressGenGranularity();
589 llvm::SmallVector<mlir::OpFoldResult, 4> sizesRev(llvm::reverse(sizes));
590 auto checkSize = [&](mlir::OpFoldResult ofr, int64_t hwVal, int64_t hi,
591 llvm::StringRef what) -> LogicalResult {
592 if (!getConstantIntValue(ofr))
593 return success();
594 if (hwVal < 0 || hwVal > hi)
595 return emitOpError(what) << " hardware value " << hwVal
596 << " exceeds hardware range [0:" << hi << "].";
597 return success();
598 };
599 auto hwSize = [&](mlir::OpFoldResult ofr) {
600 return getConstantIntValue(ofr).value_or(0);
601 };
602 int64_t d0Hw = (int64_t)(hwSize(sizesRev[0]) * elemWidth / gran);
603 int64_t d1Hw = hwSize(sizesRev[1]);
604 int64_t iterRaw = hwSize(sizesRev[3]);
605 int64_t iterHw = iterRaw > 1 ? iterRaw - 1 : 0;
606 std::optional<int64_t> outerStride = getConstantIntValue(strides.front());
607 bool pureRepeat = outerStride && *outerStride == 0;
608 AIE::AIETileType tileType =
609 targetModel.getTileType(tile.getCol(), tile.getRow());
610 int64_t wrapMax = (1LL << targetModel.getDmaBdWrapBits(tileType)) - 1;
611 int64_t maxCount = pureRepeat ? targetModel.getMaxRepeatCount() + 1
612 : 1LL << targetModel.getDmaBdIterBits(tileType);
613 if (failed(checkSize(sizesRev[0], d0Hw, wrapMax, "d0 size")) ||
614 failed(checkSize(sizesRev[1], d1Hw, wrapMax, "d1 size")) ||
615 failed(checkSize(sizesRev[3], iterHw, maxCount - 1,
616 pureRepeat ? "repeat count" : "iteration size")))
617 return failure();
618
619 // Realizability of the CONSTANT size/stride operands (divisibility +
620 // positivity, innermost-first). Runtime operands get a host-side check at
621 // lowering time. Shared with the dma_task path.
622 llvm::SmallVector<mlir::OpFoldResult, 4> stridesRev(llvm::reverse(strides));
623 if (failed(verifyConstBdRealizability(getOperation(), sizesRev, stridesRev,
624 elemWidth, gran)))
625 return failure();
626
627 // Runtime fields are checked at dispatch instead; see buildBdWords.
628
629 auto errorMessage = checkBurstLength(targetModel, getBurstLength());
630 if (errorMessage.has_value())
631 return emitOpError(errorMessage.value());
632
633 return success();
634}
635
636LogicalResult AIEX::NpuDmaMemcpyNdOp::verify() {
637 BaseMemRefType buffer = getMemref().getType();
638 const auto &targetModel = AIE::getTargetModel(*this);
639 auto addressGranularity = targetModel.getAddressGenGranularity();
640
641 if (getOffsetParameterAttr() || getOffsetStateTableIdxAttr()) {
642 uint64_t elemBitWidth = buffer.getElementTypeBitWidth();
643 if (elemBitWidth == 0 || (elemBitWidth % 8) != 0)
644 return emitOpError("offset_parameter requires a whole-byte element type");
645 }
646
647 if (getElementTypeBitwidth() > addressGranularity) {
648 return emitOpError("Maximum element bit width allowed is ")
649 << addressGranularity << "bits. ";
650 }
651 if (buffer.hasStaticShape() &&
652 (buffer.getNumElements() * getElementTypeBitwidth()) <
653 addressGranularity) {
654 return emitOpError("Minimum data transfer size required is ")
655 << addressGranularity << "bits. ";
656 }
657 bool allStridesConstant = llvm::all_of(getMixedStrides(), [](OpFoldResult s) {
658 return getConstantIntValue(s).has_value();
659 });
660 bool allSizesConstant = llvm::all_of(getMixedSizes(), [](OpFoldResult s) {
661 return getConstantIntValue(s).has_value();
662 });
663 bool allOffsetsConstant = llvm::all_of(getMixedOffsets(), [](OpFoldResult s) {
664 return getConstantIntValue(s).has_value();
665 });
666
667 bool hasLengthParameter =
668 getLengthParameterAttr() || getLengthStateTableIdxAttr();
669 // Runtime offsets, sizes and strides lower only through EmitC, which has no
670 // scratchpad.
671 if (hasLengthParameter &&
672 (!allStridesConstant || !allSizesConstant || !allOffsetsConstant))
673 return emitOpError(
674 "length_parameter requires constant offsets, sizes and strides");
675
676 // The dynamic scope check needs the allocation resolved by verifySymbolUses.
677 if (!allStridesConstant || !allSizesConstant || !allOffsetsConstant)
678 return success();
679
680 llvm::SmallVector<int64_t, 4> inputSizes =
681 llvm::map_to_vector(llvm::reverse(getMixedSizes()), [](OpFoldResult s) {
682 return getConstantIntValue(s).value();
683 });
684 llvm::SmallVector<int64_t, 4> inputStrides =
685 llvm::map_to_vector(llvm::reverse(getMixedStrides()), [](OpFoldResult s) {
686 return getConstantIntValue(s).value();
687 });
688 int64_t offset = getOffsetInBytes();
689
690 auto errorMessage = checkBurstLength(targetModel, getBurstLength());
691 if (errorMessage.has_value()) {
692 return emitOpError(errorMessage.value());
693 }
694
695 if (hasLengthParameter) {
697 *this, getLengthUnit(), buffer,
698 inputSizes[0] * inputSizes[1] * inputSizes[2],
699 AIEX::isContiguousTransfer(inputSizes, inputStrides),
700 llvm::ArrayRef<int64_t>(inputSizes).take_front(3))))
701 return failure();
702 }
703
704 // AIE1 does not use the NPU alignment and packet-header checks.
705 if (targetModel.getTargetArch() == AIE::AIEArch::AIE1)
706 return success();
707
708 if (offset % 4 != 0) {
709 return emitOpError("Offset must be 4-byte-aligned.");
710 }
711
712 // packet header
713 if (auto packetInfo = getPacket()) {
714 if (failed(AIE::verifyAssignedPacket(*this, *packetInfo)))
715 return failure();
716 if (packetInfo->getPktType() > 7)
717 return emitOpError("Packet type field can only hold 3 bits.");
718 if (packetInfo->assignedId() > 31)
719 return emitOpError("Packet ID field can only hold 5 bits.");
720 }
721
722 return success();
723}
724
725// The verifier-owned collection shares the device symbol table across memcpy
726// checks without retaining allocation pointers between verification calls.
727LogicalResult
728AIEX::NpuDmaMemcpyNdOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
729 BaseMemRefType buffer = getMemref().getType();
730 const auto &targetModel = AIE::getTargetModel(*this);
731 bool allStridesConstant = llvm::all_of(getMixedStrides(), [](OpFoldResult s) {
732 return getConstantIntValue(s).has_value();
733 });
734 bool allSizesConstant = llvm::all_of(getMixedSizes(), [](OpFoldResult s) {
735 return getConstantIntValue(s).has_value();
736 });
737 bool allOffsetsConstant = llvm::all_of(getMixedOffsets(), [](OpFoldResult s) {
738 return getConstantIntValue(s).has_value();
739 });
740 bool isDynamic =
741 !allStridesConstant || !allSizesConstant || !allOffsetsConstant;
742
743 bool hasLengthParameter =
744 getLengthParameterAttr() || getLengthStateTableIdxAttr();
745 bool isAIE1 = targetModel.getTargetArch() == AIE::AIEArch::AIE1;
746 AIE::DeviceOp dev = getOperation()->getParentOfType<AIE::DeviceOp>();
747 if (!dev && isAIE1 && !isDynamic && !hasLengthParameter)
748 return success();
749 auto allocOp = symbolTable.lookupSymbolIn<AIE::ShimDMAAllocationOp>(
750 dev, getMetadata().getRootReference());
751 if (isDynamic)
752 return verifyDynamicSizesStrides(targetModel, buffer, allocOp);
753
754 // Static transfers can precede allocation lowering, so defer tile checks
755 // until metadata names a shim DMA allocation.
756 if (!allocOp)
757 return success();
758 auto tile = allocOp.getTileOp();
759 // Defer coordinate-dependent checks until logical tiles are placed.
760 if (!tile) {
761 if (isAIE1 && hasLengthParameter)
762 return emitOpError("length_parameter is only supported on AIE2 and "
763 "AIE2P devices");
764 return success();
765 }
766 int col = tile.getCol();
767 int row = tile.getRow();
768 if (hasLengthParameter &&
769 failed(AIE::verifyLengthParameterTile(*this, targetModel, col, row)))
770 return failure();
771
772 llvm::SmallVector<int64_t, 4> inputSizes =
773 llvm::map_to_vector(llvm::reverse(getMixedSizes()), [](OpFoldResult s) {
774 return getConstantIntValue(s).value();
775 });
776 llvm::SmallVector<int64_t, 4> inputStrides =
777 llvm::map_to_vector(llvm::reverse(getMixedStrides()), [](OpFoldResult s) {
778 return getConstantIntValue(s).value();
779 });
780 llvm::SmallVector<int64_t, 4> hardwareSizes(4);
781 llvm::SmallVector<int64_t, 4> hardwareStrides(4);
782 getHardwareStridesWraps(targetModel, getOperation(), buffer, inputSizes,
783 inputStrides, hardwareSizes, hardwareStrides);
784
785 // dma_memcpy_nd transfers of the form [1, 1, 1, len][0, 0, 0, 1] do not
786 // specify any data layout transformation, but simply express a contiguous
787 // transfer of `len`. For backwards compatibility, we allow this to proceed
788 // even if it exceeds the maximum stride/wrap size of any one dimension,
789 // and simply do not lower any data layout transformations, since there is
790 // no other way to express this at the dma_memcpy_nd interface otherwise.
791 // A contiguous row-major ND access is also exempt from the ND wrap-size
792 // limit: aie-dma-to-npu lowers it to linear mode (d0_size=d1_size=0),
793 // and LinearizeContiguousTransfer canonicalizes it to explicit linear form.
794 bool skipTransformationChecks = AIEX::isLinearModeTransfer(
795 targetModel, col, row, inputSizes, inputStrides);
796 // An oversized non-contiguous pattern that aie-decompose-large-dma-bd can
797 // split into hardware-legal sub-transfers is also allowed to verify: the
798 // pass rewrites it before BD lowering. Truly undecomposable patterns (e.g.
799 // oversized strides) still fail below. isDecomposableNdDmaPattern
800 // suppresses its own diagnostics, so no stray error is emitted for the
801 // accepted case.
802 llvm::SmallVector<int64_t, 4> inputOffsets =
803 llvm::map_to_vector(llvm::reverse(getMixedOffsets()), [](OpFoldResult s) {
804 return getConstantIntValue(s).value();
805 });
806 // aie-decompose-large-dma-bd leaves a transfer with a runtime length
807 // alone, so it must be legal as written.
808 // AIE1 patterns must fit directly, even when the decomposition pass is
809 // scheduled: verification must not rely on it to legalize AIE1 transfers.
810 if (!isAIE1 && !hasLengthParameter &&
811 AIEX::isDecomposableNdDmaPattern(getOperation(), buffer, targetModel, col,
812 row, inputOffsets, inputSizes,
813 inputStrides)) {
814 // ok: will be decomposed before lowering
815 } else if (failed(verifyStridesWraps(
816 *this, [&] { return emitOpError(); }, buffer, col, row,
817 inputSizes, inputStrides, hardwareSizes, hardwareStrides,
818 skipTransformationChecks))) {
819 return failure();
820 }
821
822 return success();
823}
824
825//===----------------------------------------------------------------------===//
826// NpuDmaWaitOp
827//===----------------------------------------------------------------------===//
828
829LogicalResult AIEX::NpuDmaWaitOp::verify() {
830 AIE::DeviceOp dev = (*this)->getParentOfType<AIE::DeviceOp>();
831 // Some passes (e.g. aie-standard-lowering) use aiex ops outside a DeviceOp,
832 // so we can't expect the device to always exist.
833 if (dev && !dev.lookupSymbol(getSymbol()))
834 return emitOpError("couldn't find symbol in parent device");
835 return success();
836}
837
838//===----------------------------------------------------------------------===//
839// NpuPushQueueOp
840//===----------------------------------------------------------------------===//
841
842LogicalResult AIEX::NpuPushQueueOp::verify() {
843 const auto &targetModel = AIE::getTargetModel(*this);
844 auto numBds = targetModel.getNumBDs(getColumn(), getRow());
845 // bd_id and repeat_count are SSA operands; range-check them only when they
846 // are compile-time constants. A runtime repeat_count is checked on the host
847 // when lowered (AIEDmaToNpu); a runtime bd_id comes from the tile's BD pool.
848 if (std::optional<uint32_t> bdId = getConstantIntOperand(getBdId());
849 bdId && *bdId >= numBds)
850 return emitOpError("BD ID exceeds the maximum ID.");
851 uint32_t maxRepeat = targetModel.getMaxRepeatCount();
852 if (std::optional<uint64_t> repeatCount =
853 getConstantInt64Operand(getRepeatCount());
854 repeatCount && *repeatCount > maxRepeat)
855 return emitOpError("Repeat count exceeds the [0:")
856 << maxRepeat << "] range.";
857 if (getRepeatStateTableIdxAttr()) {
858 AIE::AIEArch arch = targetModel.getTargetArch();
859 if (arch != AIE::AIEArch::AIE2 && arch != AIE::AIEArch::AIE2p)
860 return emitOpError("repeat_parameter is only supported on AIE2 and "
861 "AIE2P devices");
862 if (!targetModel.isShimNOCTile(getColumn(), getRow()) &&
863 !targetModel.isMemTile(getColumn(), getRow()))
864 return emitOpError("repeat_parameter is only supported on shim NOC "
865 "and mem tiles, got tile (")
866 << getColumn() << ", " << getRow() << ")";
867 if (getConstantInt64Operand(getRepeatCount()) != uint64_t{0})
868 return emitOpError("takes its repeat count from the scratchpad, so its "
869 "repeat_count operand must be 0");
870 std::optional<uint32_t> bdId = getConstantIntOperand(getBdId());
871 if (!bdId)
872 return emitOpError("takes its repeat count from the scratchpad, so its "
873 "bd_id must be a constant");
874 if (*bdId % 4 != 0)
875 return emitOpError("takes its repeat count from the scratchpad, whose "
876 "update clears the low two bits of the queue word, "
877 "so its bd_id must be a multiple of 4, got ")
878 << *bdId;
879 }
880 return success();
881}
882
883//===----------------------------------------------------------------------===//
884// NpuWriteBdOp
885//===----------------------------------------------------------------------===//
886
887LogicalResult AIEX::NpuWriteBdOp::verify() {
888 const auto &targetModel = AIE::getTargetModel(*this);
889 auto numBds = targetModel.getNumBDs(getColumn(), getRow());
890 bool isLinearTransfer =
891 (getD0Size() >= 1) && (getD1Size() == 1) && (getIterationSize() == 0);
892 if (getBdId() >= numBds)
893 return emitOpError("BD ID exceeds the maximum ID.");
894 if (getPacketId() > 31)
895 return emitOpError("Packet ID exceeds the maximum supported by 5 bits.");
896 if (getPacketType() > 7)
897 return emitOpError("Packet Type exceeds the maximum supported by 3 bits.");
898 int64_t oooId = getOutOfOrderId();
899 if (oooId < 0 ||
900 static_cast<uint64_t>(oooId) > targetModel.getMaxOutOfOrderId())
901 return emitOpError("out_of_order_id must be in [0, ")
902 << targetModel.getMaxOutOfOrderId() << "].";
903 if (oooId != 0 && getEnablePacket() == 0)
904 return emitOpError("out_of_order_id requires a packet-enabled BD");
905
906 // Every value on this op is already the hardware-encoded field value (wrap
907 // fields unbiased, stepsize/iteration fields biased actual-1), so the
908 // legal encoded range for a B-bit field is simply [0, 2^B - 1].
909 AIE::AIETileType tileType = targetModel.getTileType(getColumn(), getRow());
910 uint32_t wrapBits = targetModel.getDmaBdWrapBits(tileType);
911 uint32_t stepBits = targetModel.getDmaBdStepBits(tileType);
912 uint32_t iterBits = targetModel.getDmaBdIterBits(tileType);
913 uint32_t maxWrap = wrapBits > 0 ? (1u << wrapBits) - 1 : 0;
914 uint32_t maxStep = stepBits > 0 ? (1u << stepBits) - 1 : 0;
915 uint32_t maxIter = iterBits > 0 ? (1u << iterBits) - 1 : 0;
916
917 if (!isLinearTransfer && getD0Size() > maxWrap)
918 return emitOpError() << "D0 Size exceeds the [0:" << maxWrap << "] range.";
919 if (getD0Stride() > maxStep)
920 return emitOpError() << "D0 Stride exceeds the [0:" << maxStep
921 << "] range.";
922 if (getD1Size() > maxWrap)
923 return emitOpError() << "D1 Size exceeds the [0:" << maxWrap << "] range.";
924 if (getD1Stride() > maxStep)
925 return emitOpError() << "D1 Stride exceeds the [0:" << maxStep
926 << "] range.";
927 if (getD2Stride() > maxStep)
928 return emitOpError() << "D2 Stride exceeds the [0:" << maxStep
929 << "] range.";
930 if (getIterationSize() > maxIter)
931 return emitOpError() << "Iteration Size exceeds the [0:" << maxIter
932 << "] range.";
933 if (static_cast<uint32_t>(getIterationStride()) > maxStep)
934 return emitOpError() << "Iteration Stride exceeds the [0:" << maxStep
935 << "] range.";
936 // buffer_length is the full 32-bit word on a shim NOC tile but only 17 bits
937 // on a mem tile and 14 on a core tile, where the packer masks it.
938 uint64_t maxLen = targetModel.getDmaBdMaxLen(tileType);
939 if (getBufferLength() > maxLen)
940 return emitOpError() << "Buffer length exceeds the [0:" << maxLen
941 << "] range.";
942 if (targetModel.isShimNOCTile(getColumn(), getRow()) && getD2Size() != 0)
943 return emitOpError("ShimTile only supports 3 dimensions of sizes.");
944 if (targetModel.isShimNOCTile(getColumn(), getRow()) &&
945 (getD0ZeroBefore() != 0 || getD0ZeroAfter() != 0 ||
946 getD1ZeroBefore() != 0 || getD1ZeroAfter() != 0 ||
947 getD2ZeroBefore() != 0 || getD2ZeroAfter() != 0))
948 return emitOpError("ShimTile doesn't support zero padding.");
949
950 // Pad field widths (AIE2P ArchSpec Table 3-34, in 32-bit words): D0 is a
951 // 6-bit field (max 63), D1 a 5-bit field (max 31), D2 a 4-bit field (max
952 // 15).
953 //
954 // Placement is a deliberate compromise. DMABDOp::verify() would be the
955 // better home for this check, since its diagnostic would point at the
956 // user's own `aie.dma_bd`. But DMABDOp::verify() early-returns for BDs
957 // nested inside `aiex.dma_configure_task`, and the only case with
958 // hardware evidence for this defect is on that task path -- a check
959 // placed only in DMABDOp::verify() would miss it. NpuWriteBdOp is where
960 // both the static and task runtime paths converge, so putting it here
961 // covers both, at the cost of the diagnostic pointing at this
962 // compiler-generated op rather than the user-written BD.
963 //
964 // TODO: migrate this check to DMABDOp::verify() once BD iteration becomes
965 // an explicit `aie.dma_bd` field and that early-return can be removed.
966 if (getD0ZeroBefore() > 0x3F || getD0ZeroAfter() > 0x3F)
967 return emitOpError("D0 pad_before/pad_after exceeds the [0:63] range.");
968 if (getD1ZeroBefore() > 0x1F || getD1ZeroAfter() > 0x1F)
969 return emitOpError("D1 pad_before/pad_after exceeds the [0:31] range.");
970 if (getD2ZeroBefore() > 0xF || getD2ZeroAfter() > 0xF)
971 return emitOpError("D2 pad_before/pad_after exceeds the [0:15] range.");
972
973 if (!targetModel.isShimNOCTile(getColumn(), getRow()) &&
974 getBurstLength() != 0)
975 return emitOpError("Only ShimTiles support burst length.");
976 auto errorMessage = checkBurstLength(targetModel, getBurstLength());
977 if (errorMessage.has_value()) {
978 return emitOpError(errorMessage.value());
979 }
980 if (!targetModel.isShimNOCTile(getColumn(), getRow()) && getAxcache())
981 return emitOpError("Only ShimTiles support AxCACHE configuration.");
982
983 return success();
984}
985
986uint32_t AIEX::NpuWriteBdOp::getAxcacheOrDefault() {
987 return getAxcache().value_or(AIE::getTargetModel(*this).getDefaultAxCache());
988}
989
990std::optional<uint32_t> AIEX::getConstantIntOperand(mlir::Value v) {
991 if (std::optional<uint64_t> cst = getConstantInt64Operand(v))
992 return static_cast<uint32_t>(*cst);
993 return std::nullopt;
994}
995
996// Widthwise counterpart of getConstantIntOperand; see createConstantArgPlus.
997std::optional<uint64_t> AIEX::getConstantInt64Operand(mlir::Value v) {
998 // An arith.constant is read in place: the generic match folds the op, which
999 // dominates translating a sequence of a million writes.
1000 if (auto constant = v.getDefiningOp<arith::ConstantOp>())
1001 if (auto attr = dyn_cast<IntegerAttr>(constant.getValue()))
1002 return attr.getValue().getZExtValue();
1003 mlir::APInt cst;
1004 if (!mlir::matchPattern(v, mlir::m_ConstantInt(&cst)))
1005 return std::nullopt;
1006 return cst.getZExtValue();
1007}
1008
1009mlir::Value AIEX::createConstantI32(mlir::OpBuilder &builder,
1010 mlir::Location loc, uint32_t value) {
1011 return arith::ConstantOp::create(
1012 builder, loc, builder.getI32IntegerAttr(static_cast<int32_t>(value)));
1013}
1014
1015mlir::Value AIEX::createConstantArgPlus(mlir::OpBuilder &builder,
1016 mlir::Location loc, uint64_t value) {
1017 if (value <= std::numeric_limits<uint32_t>::max())
1018 return createConstantI32(builder, loc, static_cast<uint32_t>(value));
1019 return arith::ConstantOp::create(
1020 builder, loc,
1021 IntegerAttr::get(builder.getIntegerType(64),
1022 static_cast<int64_t>(value)));
1023}
1024
1025//===----------------------------------------------------------------------===//
1026// Buffer-relative runtime writes
1027//===----------------------------------------------------------------------===//
1028
1029// DataLayout's default queries abort for types without a builtin layout or a
1030// type interface; a legal memref element (such as another memref) need not have
1031// either. Check component types before querying composite builtin layouts too.
1032static bool hasQueryableStorageLayout(Type type) {
1033 if (type.isIntOrFloat() || isa<IndexType, DataLayoutTypeInterface>(type))
1034 return true;
1035 if (auto vector = dyn_cast<VectorType>(type))
1036 return hasQueryableStorageLayout(vector.getElementType());
1037 if (auto complex = dyn_cast<ComplexType>(type))
1038 return hasQueryableStorageLayout(complex.getElementType());
1039 return false;
1040}
1041
1042// Match BufferOp's allocation convention without an unchecked shape product or
1043// a fixed-size conversion of scalable element storage.
1044static std::optional<uint64_t>
1045getStaticMemRefStorageBytes(MemRefType type, const DataLayout &layout) {
1046 if (!type || !type.hasStaticShape() ||
1047 !hasQueryableStorageLayout(type.getElementType()))
1048 return std::nullopt;
1049 if (auto vector = dyn_cast<VectorType>(type.getElementType())) {
1050 // The default vector layout uses a signed getNumElements(), then unsigned
1051 // innermost-dimension padding and bit products. Guard that query itself.
1052 if (vector.isScalable() || vector.getRank() == 0)
1053 return std::nullopt;
1054 int64_t elements = 1;
1055 for (int64_t dim : vector.getShape()) {
1056 auto product = llvm::checkedMul(elements, dim);
1057 if (!product)
1058 return std::nullopt;
1059 elements = *product;
1060 }
1061 uint64_t inner = vector.getShape().back();
1062 auto padded = llvm::checkedMulUnsigned(
1063 static_cast<uint64_t>(elements) / inner, llvm::PowerOf2Ceil(inner));
1064 llvm::TypeSize bits = layout.getTypeSizeInBits(vector.getElementType());
1065 if (!padded || bits.isScalable() ||
1066 !llvm::checkedMulUnsigned(*padded, bits.getFixedValue()))
1067 return std::nullopt;
1068 }
1069 llvm::TypeSize elementBytes = layout.getTypeSize(type.getElementType());
1070 if (elementBytes.isScalable())
1071 return std::nullopt;
1072 if (llvm::is_contained(type.getShape(), int64_t{0}))
1073 return 0;
1074 uint64_t bytes = elementBytes.getFixedValue();
1075 for (int64_t dim : type.getShape()) {
1076 auto product = llvm::checkedMulUnsigned(bytes, static_cast<uint64_t>(dim));
1077 if (!product)
1078 return std::nullopt;
1079 bytes = *product;
1080 }
1081 return bytes;
1082}
1083
1084template <typename OpTy>
1085static LogicalResult verifyBufferWriteScope(OpTy op) {
1086 if (op.getBuffer() && !op->template getParentOfType<AIE::DeviceOp>())
1087 return op.emitOpError("must be inside a device when buffer is present");
1088 return success();
1089}
1090
1091template <typename OpTy>
1092static LogicalResult
1093verifyBufferWrite(OpTy op, std::optional<uint32_t> wordOffset,
1094 uint64_t writeBytes, AIE::NamedOpTableCollection &names) {
1095 auto bufferAttr = op.getBufferAttr();
1096 if (!bufferAttr)
1097 return success();
1098 if (!wordOffset)
1099 return op.emitOpError("address must be constant when buffer is present");
1100 auto device = op->template getParentOfType<AIE::DeviceOp>();
1101 if (!device)
1102 return op.emitOpError("must be inside a device when buffer is present");
1103 // The device's region verifier owns the index and has verified all child
1104 // invariants before dispatching these graph-dependent checks.
1105 auto buffer = names.lookupIn<AIE::BufferOp>(device, bufferAttr.getAttr());
1106 if (!buffer)
1107 return op.emitOpError()
1108 << "buffer '" << bufferAttr.getValue() << "' not found in device";
1109 auto bufferBytes = getStaticMemRefStorageBytes(buffer.getType(),
1110 DataLayout::closest(buffer));
1111 if (!bufferBytes)
1112 return op.emitOpError(
1113 "referenced buffer must have a static, fixed-size storage extent "
1114 "representable in 64 bits");
1115
1116 uint64_t offsetBytes = uint64_t{*wordOffset} * sizeof(uint32_t);
1117 auto end = llvm::checkedAddUnsigned(offsetBytes, writeBytes);
1118 if (!end)
1119 return op.emitOpError("buffer-relative write range overflows 64-bit bytes");
1120 if (*end > *bufferBytes)
1121 return op.emitOpError() << "write range [" << offsetBytes << ", " << *end
1122 << ") exceeds buffer @" << bufferAttr.getValue()
1123 << " size " << *bufferBytes << " bytes";
1124
1125 auto tile = buffer.getTileLike();
1126 const auto &targetModel = device.getTargetModel();
1127 uint64_t memoryBytes = tile.isMemTile() ? targetModel.getMemTileSize()
1128 : targetModel.getLocalMemorySize();
1129 uint64_t start = offsetBytes;
1130 if (auto base = buffer.getAddress()) {
1131 if (*base < 0)
1132 return op.emitOpError("referenced buffer address must be nonnegative");
1133 start += static_cast<uint64_t>(*base);
1134 if (start % sizeof(uint32_t) != 0)
1135 return op.emitOpError() << "resolved buffer write address " << start
1136 << " is not 4-byte aligned";
1137 }
1138 // An unassigned base can only move the interval upward. Check this necessary
1139 // bound now, and the resolved interval when address assignment runs.
1140 auto memoryEnd = llvm::checkedAddUnsigned(start, writeBytes);
1141 if (!memoryEnd)
1142 return op.emitOpError("resolved buffer write range overflows 64-bit bytes");
1143 if (*memoryEnd > memoryBytes)
1144 return op.emitOpError()
1145 << "write range [" << start << ", " << *memoryEnd
1146 << ") exceeds tile data memory size " << memoryBytes << " bytes";
1147 return success();
1148}
1149
1150//===----------------------------------------------------------------------===//
1151// NpuWrite32Op
1152//===----------------------------------------------------------------------===//
1153
1154LogicalResult AIEX::NpuWrite32Op::verify() {
1155 return verifyBufferWriteScope(*this);
1156}
1157
1158LogicalResult
1159AIEX::NpuWrite32Op::verifyNamedOpUses(AIE::NamedOpTableCollection &names) {
1160 if (!getBuffer())
1161 return success();
1162 return verifyBufferWrite(*this, getConstantIntOperand(getAddress()),
1163 sizeof(uint32_t), names);
1164}
1165
1166template <typename T>
1167static std::optional<uint32_t>
1168getAbsoluteAddress(T *op, uint32_t addressOffset,
1169 const AIE::NamedOpTable *names) {
1170 AIE::DeviceOp device =
1171 op->getOperation()->template getParentOfType<AIE::DeviceOp>();
1172 if (!device) {
1173 op->emitError("Must be inside a device.");
1174 return std::nullopt;
1175 }
1176 const AIE::AIETargetModel &tm = device.getTargetModel();
1177
1178 uint32_t address = 0;
1179
1180 // If blockwrite references a buffer, the given address is understood to be
1181 // relative to the buffer's start address.
1182 if (auto bufferSym = op->getBuffer()) {
1183 AIE::BufferOp buffer =
1184 names ? names->lookup<AIE::BufferOp>(op->getBufferAttr().getAttr())
1185 : AIE::lookupNamedOpIn<AIE::BufferOp>(device, *bufferSym);
1186 if (!buffer) {
1187 op->emitError() << "buffer '" << *bufferSym << "' not found in device";
1188 return std::nullopt;
1189 }
1190
1191 auto bufferAddress = buffer.getAddress();
1192 if (!bufferAddress) {
1193 mlir::InFlightDiagnostic err =
1194 op->emitError("referenced buffer must have address assigned");
1195 err.attachNote(buffer.getLoc()) << "This buffer must have an address.";
1196 return std::nullopt;
1197 }
1198
1199 uint32_t col = buffer.getTileOp().getCol();
1200 uint32_t row = buffer.getTileOp().getRow();
1201 address = static_cast<uint32_t>(*bufferAddress) +
1202 addressOffset * sizeof(uint32_t);
1203 address = ((col & 0xff) << tm.getColumnShift()) |
1204 ((row & 0xff) << tm.getRowShift()) | (address & 0xfffff);
1205 } else { // otherwise, the given address is absolute
1206 address = addressOffset;
1207 std::optional<uint32_t> col = op->getColumn();
1208 std::optional<uint32_t> row = op->getRow();
1209 if (col && row) {
1210 // If col and row are set, only the lower 20 bits of the address are
1211 // used, and col and row dictate the upper bits (ignored)
1212 address = ((*col & 0xff) << tm.getColumnShift()) |
1213 ((*row & 0xff) << tm.getRowShift()) | (address & 0xfffff);
1214 }
1215 }
1216
1217 return address;
1218}
1219
1220std::optional<uint32_t>
1221AIEX::NpuWrite32Op::getAbsoluteAddress(const AIE::NamedOpTable *names) {
1222 std::optional<uint32_t> addressOffset = getConstantIntOperand(getAddress());
1223 if (!addressOffset)
1224 return std::nullopt;
1225 return ::getAbsoluteAddress(this, *addressOffset, names);
1226}
1227
1228//===----------------------------------------------------------------------===//
1229// NpuAddressPatchOp
1230//===----------------------------------------------------------------------===//
1231
1232LogicalResult AIEX::NpuAddressPatchOp::verify() {
1233 // A runtime register address (addr_val) is meaningful only on the EmitC (C++
1234 // TXN) target; the constant `addr` still carries the fallback / static value.
1235 // The static binary target checks for the operand and diagnoses it there.
1236 if (getAddrVal() && !getAddrVal().getType().isInteger(32))
1237 return emitOpError("addr_val must be an i32 value");
1238 if (getArgIdx().has_value() == static_cast<bool>(getBuffer()))
1239 return emitOpError("must name the host buffer either by the 'arg_idx' "
1240 "attribute or by the 'buffer' operand, not both and not "
1241 "neither");
1242 return success();
1243}
1244
1245//===----------------------------------------------------------------------===//
1246// NpuUpdateFromScratchpadOp
1247//===----------------------------------------------------------------------===//
1248
1249std::optional<uint32_t> AIEX::NpuUpdateFromScratchpadOp::getAbsoluteAddress(
1250 const AIE::NamedOpTable *names) {
1251 return ::getAbsoluteAddress(this, getAddress(), names);
1252}
1253
1254LogicalResult AIEX::NpuUpdateFromScratchpadOp::verify() {
1255 // StateTable has at most 32 entries (32-bit words).
1256 constexpr uint32_t kMaxStateTableEntries = 32;
1257 if (getStateTableIdx() >= kMaxStateTableEntries)
1258 return emitOpError("state_table_idx ")
1259 << static_cast<uint32_t>(getStateTableIdx())
1260 << " exceeds maximum StateTable index ("
1261 << (kMaxStateTableEntries - 1) << ").";
1262
1263 // NpuCreateScratchpadOp::verify() bounds the state_table_idx.
1264 return success();
1265}
1266
1267//===----------------------------------------------------------------------===//
1268// NpuCreateScratchpadOp
1269//===----------------------------------------------------------------------===//
1270
1271LogicalResult AIEX::NpuCreateScratchpadOp::verify() {
1272 // Only usage_type == 0 is currently supported by firmware.
1273 if (getUsageType() != 0) {
1274 return emitOpError("usage_type must be 0 (got ")
1275 << static_cast<uint32_t>(getUsageType())
1276 << "); other layouts are not supported.";
1277 }
1278
1279 // The StateTable layout (usage_type 0) has a max of 32 32-bit-word entries,
1280 // i.e. a maximum total scratchpad size of 128 bytes.
1281 constexpr uint32_t kMaxScratchpadSizeBytes = 128;
1282 if (getSize() == 0) {
1283 return emitOpError("size must be greater than 0.");
1284 }
1285 if (getSize() % 4 != 0) {
1286 return emitOpError("size (")
1287 << getSize() << ") must be a multiple of 4 bytes.";
1288 }
1289 if (getSize() > kMaxScratchpadSizeBytes) {
1290 return emitOpError("size (")
1291 << getSize() << " bytes) exceeds maximum scratchpad size of "
1292 << kMaxScratchpadSizeBytes << " bytes.";
1293 }
1294
1295 // Scratchpads are far rarer than the updates they bound, and an op verifier
1296 // runs after every pass, so the block scan belongs on this side.
1297 uint32_t numEntries = getSize() / 4;
1298 if (Block *block = (*this)->getBlock()) {
1299 for (auto updateOp : block->getOps<AIEX::NpuUpdateFromScratchpadOp>()) {
1300 if (updateOp.getStateTableIdx() < numEntries)
1301 continue;
1302 return updateOp.emitOpError("state_table_idx ")
1303 << static_cast<uint32_t>(updateOp.getStateTableIdx())
1304 << " is out of bounds for scratchpad of size " << getSize()
1305 << " bytes (" << numEntries << " entries) created by "
1306 << (*this)->getName() << ".";
1307 }
1308 }
1309
1310 // At most one create_scratchpad may appear per runtime sequence. Walk the
1311 // parent RuntimeSequenceOp to check; only report from the duplicate (i.e.
1312 // the op that is NOT the first occurrence) to avoid emitting the same error
1313 // twice.
1314 auto runtimeSeq = getOperation()->getParentOfType<AIE::RuntimeSequenceOp>();
1315 if (!runtimeSeq) {
1316 return success();
1317 }
1318
1319 NpuCreateScratchpadOp firstSeen;
1320 runtimeSeq.walk([&](NpuCreateScratchpadOp op) {
1321 firstSeen = op;
1322 return WalkResult::interrupt();
1323 });
1324 if (firstSeen != *this) {
1325 InFlightDiagnostic diag =
1326 emitOpError("only one 'aiex.npu.create_scratchpad' is allowed per "
1327 "runtime sequence");
1328 diag.attachNote(firstSeen.getLoc())
1329 << "previous 'aiex.npu.create_scratchpad' here";
1330 return diag;
1331 }
1332
1333 return success();
1334}
1335
1336//===----------------------------------------------------------------------===//
1337// NpuMaskWrite32Op
1338//===----------------------------------------------------------------------===//
1339
1340LogicalResult AIEX::NpuMaskWrite32Op::verify() {
1341 return verifyBufferWriteScope(*this);
1342}
1343
1344LogicalResult
1345AIEX::NpuMaskWrite32Op::verifyNamedOpUses(AIE::NamedOpTableCollection &names) {
1346 if (!getBuffer())
1347 return success();
1348 return verifyBufferWrite(*this, getConstantIntOperand(getAddress()),
1349 sizeof(uint32_t), names);
1350}
1351
1352std::optional<uint32_t>
1353AIEX::NpuMaskWrite32Op::getAbsoluteAddress(const AIE::NamedOpTable *names) {
1354 std::optional<uint32_t> addressOffset = getConstantIntOperand(getAddress());
1355 if (!addressOffset)
1356 return std::nullopt;
1357 return ::getAbsoluteAddress(this, *addressOffset, names);
1358}
1359
1360//===----------------------------------------------------------------------===//
1361// NpuMaskPollOp
1362//===----------------------------------------------------------------------===//
1363
1364LogicalResult AIEX::NpuMaskPollOp::verify() {
1365 return verifyBufferWriteScope(*this);
1366}
1367
1368LogicalResult
1369AIEX::NpuMaskPollOp::verifyNamedOpUses(AIE::NamedOpTableCollection &names) {
1370 if (!getBuffer())
1371 return success();
1372 return verifyBufferWrite(*this, getConstantIntOperand(getAddress()),
1373 sizeof(uint32_t), names);
1374}
1375
1376std::optional<uint32_t>
1377AIEX::NpuMaskPollOp::getAbsoluteAddress(const AIE::NamedOpTable *names) {
1378 std::optional<uint32_t> addressOffset = getConstantIntOperand(getAddress());
1379 if (!addressOffset)
1380 return std::nullopt;
1381 return ::getAbsoluteAddress(this, *addressOffset, names);
1382}
1383
1384//===----------------------------------------------------------------------===//
1385// NpuBlockWriteOp
1386//===----------------------------------------------------------------------===//
1387
1388LogicalResult AIEX::NpuBlockWriteOp::verify() {
1389 return verifyBufferWriteScope(*this);
1390}
1391
1392LogicalResult
1393AIEX::NpuBlockWriteOp::verifyNamedOpUses(AIE::NamedOpTableCollection &names) {
1394 if (!getBuffer())
1395 return success();
1396 auto type = dyn_cast<MemRefType>(getData().getType());
1397 DataLayout layout = DataLayout::closest(*this);
1398 auto bytes = getStaticMemRefStorageBytes(type, layout);
1399 if (!bytes)
1400 return emitOpError("payload must have a static, fixed-size storage extent "
1401 "representable in 64 bits when buffer is present");
1402 llvm::TypeSize elementBits = layout.getTypeSizeInBits(type.getElementType());
1403 if (elementBits.isScalable() || elementBits.getFixedValue() != 32)
1404 return emitOpError(
1405 "payload must have 32-bit elements when buffer is present");
1406 return verifyBufferWrite(*this, getAddress(), *bytes, names);
1407}
1408
1409std::optional<uint32_t>
1410AIEX::NpuBlockWriteOp::getAbsoluteAddress(const AIE::NamedOpTable *names) {
1411 return ::getAbsoluteAddress(this, getAddress(), names);
1412}
1413
1414DenseIntElementsAttr AIEX::NpuBlockWriteOp::getDataWords() {
1415 Value memref = this->getData();
1416 DataLayout dataLayout = DataLayout::closest(*this);
1417 int64_t width = dataLayout.getTypeSizeInBits(
1418 cast<MemRefType>(memref.getType()).getElementType());
1419 if (width != 32) {
1420 emitWarning("Only 32-bit data type is supported for now");
1421 return nullptr;
1422 }
1423
1424 memref::GetGlobalOp getGlobal = memref.getDefiningOp<memref::GetGlobalOp>();
1425 if (!getGlobal) {
1426 emitError("Only MemRefs from memref.get_global are supported");
1427 return nullptr;
1428 }
1429
1430 auto global = dyn_cast_if_present<memref::GlobalOp>(
1431 (*this)->getParentOfType<AIE::DeviceOp>().lookupSymbol(
1432 getGlobal.getName()));
1433 if (!global) {
1434 emitError("Global symbol not found");
1435 return nullptr;
1436 }
1437
1438 auto initVal = global.getInitialValue();
1439 if (!initVal) {
1440 emitError("Global symbol has no initial value");
1441 return nullptr;
1442 }
1443
1444 auto data = dyn_cast<DenseIntElementsAttr>(*initVal);
1445 if (!data) {
1446 emitError("Global symbol initial value is not a dense int array");
1447 return nullptr;
1448 }
1449
1450 return data;
1451}
1452
1453//===----------------------------------------------------------------------===//
1454// DMAConfigureTaskOp
1455//===----------------------------------------------------------------------===//
1456
1457AIE::DMABDOp AIEX::DMAConfigureTaskOp::getFirstBd() {
1458 Region &body = getBody();
1459 if (body.empty()) {
1460 return nullptr;
1461 }
1462 auto bd_ops = body.front().getOps<AIE::DMABDOp>();
1463 if (bd_ops.empty() && body.front().getNumSuccessors() == 1) {
1464 // Allow the first block to be empty and point to the entry point of the
1465 // chain. This allows for specifying cyclying BD chains (infinite loops)
1466 // within the constraints of MLIR syntax.
1467 Block &chain_entry = *body.front().getSuccessor(0);
1468 bd_ops = chain_entry.getOps<AIE::DMABDOp>();
1469 }
1470 if (bd_ops.empty()) {
1471 return nullptr;
1472 }
1473 return *bd_ops.begin();
1474}
1475
1476LogicalResult
1477AIEX::DMAConfigureTaskOp::canonicalize(AIEX::DMAConfigureTaskOp op,
1478 PatternRewriter &rewriter) {
1479 // Remove blocks that contain nothing but a terminator
1480 Region &body = op.getBody();
1481 bool did_rewrite = false;
1482 for (auto &block : body) {
1483 if (block.empty()) {
1484 continue;
1485 }
1486 auto ops_it = block.without_terminator();
1487 if (std::distance(ops_it.begin(), ops_it.end()) == 0) {
1488 rewriter.eraseOp(block.getTerminator());
1489 did_rewrite = true;
1490 }
1491 }
1492 if (did_rewrite) {
1493 return success();
1494 }
1495 return failure();
1496}
1497
1498// Enforce the per-BD ND access-pattern limit for BDs nested inside a
1499// runtime-sequence DMA task. The AIE::DMABDOp verifier skips these BDs (their
1500// parent is a DMA task op, not a *DMAOp), so this is the only check of the BD
1501// dimension count on the runtime-sequence path.
1502//
1503// Every AIE2/AIE2P DMA BD register file carries getBDMaxDims ND address
1504// dimensions (D0..) plus one separate iteration/repeat dimension: a core/shim
1505// BD has D0..D2 + iteration, a MemTile BD has D0..D3 + iteration. On this path
1506// aiex.shim_dma_single_bd_task hoists the leading tap dimension into that
1507// iteration register, so a shim/core BD may carry one dimension beyond its ND
1508// access limit (3 + 1). A MemTile is not given the +1: AIEDMATasksToNPU maps
1509// the 4th task dimension onto the iteration register for every tile type and
1510// caps the total at 4, which the MemTile's 4 ND dimensions already reach. Both
1511// branches therefore land on the same uniform 4-dimension cap enforced later by
1512// AIEDMATasksToNPU.
1513//
1514// A BD whose sizes and strides are all constant may give more: every dimension
1515// past the third is an iteration dimension, and aie-decompose-large-dma-bd
1516// splits them off into descriptors of 4. With a runtime value among them it
1517// cannot, so the cap applies here.
1518//
1519// A BD's `iteration` attribute claims that iteration register itself, so its
1520// sizes/strides must leave it free: one dimension fewer, with no splitting.
1521static LogicalResult
1522verifyTaskBDDimensions(const AIE::AIETargetModel &targetModel, int col, int row,
1523 Region &body) {
1524 size_t maxNDims = targetModel.getBDMaxDims(col, row);
1525 if (!targetModel.isMemTile(col, row))
1526 ++maxNDims; // leading dim is hoisted into the iteration/repeat register
1527 LogicalResult result = success();
1528 body.walk([&](AIE::DMABDOp bd) {
1529 // The BD's own verifier skips it here, so nothing has yet established that
1530 // its mixed sizes/strides lists are safe to read.
1531 if (failed(bd.verifyMixedSizesAndStrides())) {
1532 result = failure();
1533 return;
1534 }
1535 if (failed(
1536 AIE::verifyDMABDIteration(bd, targetModel.getTileType(col, row)))) {
1537 result = failure();
1538 return;
1539 }
1540 size_t numDims = bd.getMixedSizes().size();
1541 if (bd.getIteration()) {
1542 if (numDims + 1 > maxNDims) {
1543 bd.emitOpError() << "Cannot give more than "
1544 << std::to_string(maxNDims - 1)
1545 << " dimensions for step sizes and wraps alongside "
1546 "the iteration attribute on this tile (got "
1547 << std::to_string(numDims) << " dimensions).";
1548 result = failure();
1549 }
1550 return;
1551 }
1552 auto isConstant = [](OpFoldResult v) {
1553 return getConstantIntValue(v).has_value();
1554 };
1555 if (numDims > maxNDims && llvm::all_of(bd.getMixedSizes(), isConstant) &&
1556 llvm::all_of(bd.getMixedStrides(), isConstant))
1557 return;
1558 if (numDims > maxNDims) {
1559 bd.emitOpError() << "Cannot give more than " << std::to_string(maxNDims)
1560 << " dimensions for step sizes and wraps on this tile "
1561 "(got "
1562 << std::to_string(numDims) << " dimensions).";
1563 result = failure();
1564 }
1565 });
1566 return result;
1567}
1568
1569LogicalResult AIEX::DMAConfigureTaskOp::verify() {
1570 const AIE::AIETargetModel &targetModel = AIE::getTargetModel(getOperation());
1571 // Skip the per-BD dimension check on an unplaced (logical) tile: the ND limit
1572 // is a function of the tile's placed coordinates, which are not yet known.
1573 // The verifier runs again on the concrete tile once placement resolves it.
1574 std::optional<int> col = getTileLike().tryGetCol();
1575 std::optional<int> row = getTileLike().tryGetRow();
1576 if (col && row &&
1577 failed(verifyTaskBDDimensions(targetModel, *col, *row, getBody())))
1578 return failure();
1579 Region &body = getBody();
1580 // This is a layering violation on the DMABDOps, but they are never verified
1581 // otherwise Because DMAConfigureTaskOps are not yet merged into the AIE
1582 // dialect. The normal DMABDOp verify operation will skip over any BD inside
1583 // a DMAConfigureTaskOp
1584 if (failed(AIE::verifyAssignedPacket(*this, getPacketAttr())))
1585 return failure();
1586 LogicalResult result = success();
1587 bool taskHasPacket = getPacket().has_value();
1588 llvm::SmallVector<AIE::DMABDOp> bds;
1589 for (auto &block : body) {
1590 if (block.empty()) {
1591 continue;
1592 }
1593 if (block.hasNoPredecessors() && !block.isEntryBlock()) {
1594 auto error = block.getTerminator()->emitError(
1595 "Block ending in this terminator does not form a chain with "
1596 "entry block.");
1597 return failure();
1598 }
1599
1600 const AIE::AIETargetModel &targetModel =
1601 AIE::getTargetModel(getOperation());
1602
1603 block.walk([&](AIE::DMABDOp bd) {
1604 if (bd.getBurstLength() != 0 &&
1605 !targetModel.isShimNOCTile(getTileID().col, getTileID().row)) {
1606 bd.emitOpError("Burst length is only supported in Shim NOC tiles that "
1607 "are connected to the memory-mapped NOC.");
1608 result = failure();
1609 }
1610 // DMABDOp::verify skips task BDs, so validate their packet header and
1611 // out_of_order_id here too.
1612 if (failed(AIE::verifyAssignedPacket(
1613 bd, bd.getPacket().value_or(AIE::PacketInfoAttr()))) ||
1614 failed(AIE::verifyDMABDOutOfOrderId(bd, taskHasPacket)) ||
1615 failed(bd.verifyTransferInBounds()))
1616 result = failure();
1617 bds.push_back(bd);
1618 });
1619 }
1620 if (getOutOfOrder()) {
1621 // Out-of-order mode rejects task completion token (bits are aliased).
1622 if (getIssueToken()) {
1623 auto err =
1624 emitOpError("out_of_order channel cannot issue a completion token");
1625 err.attachNote() << "set issue_token = false on this out-of-order task";
1626 result = failure();
1627 }
1629 getOperation(), getDirection(), getOutOfOrder(),
1630 llvm::ArrayRef<AIE::DMABDOp>(bds),
1631 /*packetEnabledByContext=*/taskHasPacket)))
1632 result = failure();
1633 }
1634 return result;
1635}
1636
1637LogicalResult AIEX::DMAStartTaskOp::verify() {
1638 if (getRepeatCountAttr() && getRepeatCountVal())
1639 return emitOpError(
1640 "takes repeat_count or a runtime repeat count, not both");
1641 if (IntegerAttr rc = getRepeatCountAttr(); rc && rc.getInt() < 0)
1642 return emitOpError("repeat_count must be non-negative, got ")
1643 << rc.getInt();
1644 if (hasRepeatParameter() && (getRepeatCountAttr() || getRepeatCountVal()))
1645 return emitOpError("takes a repeat_parameter or a repeat count, not both");
1646 if (getRepeatParameterAttr() && getRepeatStateTableIdxAttr())
1647 return emitOpError("takes repeat_parameter or repeat_state_table_idx, "
1648 "not both");
1649 return success();
1650}
1651
1652// A BD's static length in bytes: the explicit or implicit (whole-buffer)
1653// length, scaled by the element width. Nullopt for a runtime length, or one a
1654// scratchpad parameter lengthens at run time (AIEOps.td's "Runtime length").
1655static std::optional<int64_t> staticBdLenBytes(AIE::DMABDOp bd) {
1656 if (bd.getLengthParameterAttr() || bd.getLengthStateTableIdxAttr())
1657 return std::nullopt;
1658 int64_t elems;
1659 if (std::optional<int32_t> len = bd.getConstantLen()) {
1660 // Zero-extended, as DMABDOp::getLenInBytes() reads the length.
1661 elems = static_cast<uint32_t>(*len);
1662 } else {
1663 // With runtime sizes/strides, an omitted len is their inner extent
1664 // (AIEDMATasksToNPU), not the whole buffer.
1665 if (bd.hasLen() || !bd.getSizes().empty() || !bd.getStrides().empty())
1666 return std::nullopt;
1667 auto buffer = dyn_cast<MemRefType>(bd.getBuffer().getType());
1668 if (!buffer || !buffer.hasStaticShape())
1669 return std::nullopt;
1670 elems = buffer.getNumElements();
1671 }
1672 return llvm::checkedMul(
1673 elems, static_cast<int64_t>(bd.getBufferElementTypeWidthInBytes()));
1674}
1675
1676// Bytes a task streams per dispatch, or nullopt when any extent, repeat or
1677// start count is not static, or the total overflows.
1678static std::optional<int64_t>
1679staticBytesStreamed(AIEX::DMAConfigureTaskForOp task) {
1680 int64_t starts = 0;
1681 for (Operation *user : task.getResult().getUsers()) {
1682 auto start = dyn_cast<AIEX::DMAStartTaskOp>(user);
1683 if (!start) {
1684 if (isa<AIEX::DMAAwaitTaskOp, AIEX::DMAFreeTaskOp>(user))
1685 continue;
1686 // The task value is forwarded (a branch operand, a call, ...), so its
1687 // starts elsewhere can't be counted.
1688 return std::nullopt;
1689 }
1690 // A start outside the task's own block may run under a loop or a
1691 // mutually-exclusive branch, either of which this static count can't see.
1692 if (start->getBlock() != task->getBlock() || start.getRepeatCountAttr() ||
1693 start.getRepeatCountVal() || start.hasRepeatParameter())
1694 return std::nullopt;
1695 ++starts;
1696 }
1697 if (starts == 0)
1698 return 0;
1699 std::optional<int64_t> repeat =
1700 getConstantIntValue(task.getRepeatCountValue());
1701 if (!repeat || *repeat < 0)
1702 return std::nullopt;
1703
1704 Region &body = task.getBody();
1705 if (body.empty())
1706 return std::nullopt;
1707 // The queue starts the chain at the entry block, so only blocks reachable
1708 // from it stream. A cycle runs indefinitely, and a next_bd_id continues the
1709 // chain outside the body.
1710 std::optional<int64_t> moved = 0;
1711 for (auto scc = llvm::scc_begin(&body.front()); !scc.isAtEnd(); ++scc) {
1712 if (scc.hasCycle())
1713 return std::nullopt;
1714 for (AIE::DMABDOp bd : scc->front()->getOps<AIE::DMABDOp>()) {
1715 std::optional<int64_t> len = staticBdLenBytes(bd);
1716 if (!len || bd.getIteration() || bd.getNextBdId())
1717 return std::nullopt;
1718 moved = llvm::checkedAdd(*moved, *len);
1719 if (!moved)
1720 return std::nullopt;
1721 }
1722 }
1723 std::optional<int64_t> runs = llvm::checkedAdd(*repeat, int64_t{1});
1724 if (runs)
1725 runs = llvm::checkedMul(*runs, starts);
1726 return runs ? llvm::checkedMul(*moved, *runs) : std::nullopt;
1727}
1728
1729namespace {
1730// A far end's cyclic chain of BDs over one object: each segment's BD, in
1731// order, sent `copies` times in a row.
1732struct BdChain {
1733 SmallVector<int64_t> segmentBytes;
1734 int64_t copies;
1735 int64_t objectBytes;
1736
1737 // Nullopt when the object's bytes overflow or a segment is empty.
1738 static std::optional<BdChain> get(ArrayRef<int64_t> segmentBytes,
1739 int64_t copies) {
1740 if (segmentBytes.size() == 1)
1741 copies = 1;
1742 std::optional<int64_t> objectBytes = 0;
1743 for (int64_t bytes : segmentBytes) {
1744 std::optional<int64_t> run = llvm::checkedMul(bytes, copies);
1745 if (bytes <= 0 || copies <= 0 || !run)
1746 return std::nullopt;
1747 objectBytes = llvm::checkedAdd(*objectBytes, *run);
1748 if (!objectBytes)
1749 return std::nullopt;
1750 }
1751 if (segmentBytes.empty())
1752 return std::nullopt;
1753 return BdChain{SmallVector<int64_t>(segmentBytes), copies, *objectBytes};
1754 }
1755
1756 // True when `bytes` through the chain stop where one of its BDs ends.
1757 bool endsOnBd(int64_t bytes) const {
1758 int64_t rest = bytes % objectBytes;
1759 int64_t start = 0;
1760 for (int64_t segment : segmentBytes) {
1761 if (rest < start + segment * copies)
1762 return (rest - start) % segment == 0;
1763 start += segment * copies;
1764 }
1765 return true;
1766 }
1767
1768 bool operator==(const BdChain &other) const {
1769 return segmentBytes == other.segmentBytes && copies == other.copies;
1770 }
1771};
1772} // namespace
1773
1774// The far end of a shim transfer on an objectFIFO receives (or sends) whole
1775// objects through BDs sized by the object type, or by its link pool's
1776// segments, so transfers ending mid-BD never complete it and the fifo hangs.
1777// Several transfers may share an object (concatenating host buffers into it, or
1778// splitting it across them), so it is the runtime sequence's static total
1779// through the fifo that must stop at a BD boundary.
1780static LogicalResult
1781verifyTaskCoversWholeObjects(AIEX::DMAConfigureTaskForOp task,
1782 AIE::ObjectFifoCreateOp fifo) {
1783 if (fifo.getPadDimensions() || fifo.usesStream())
1784 return success();
1785 std::optional<int64_t> moved = staticBytesStreamed(task);
1786 if (!moved || *moved == 0)
1787 return success();
1788 DataLayout layout = DataLayout::closest(task);
1789 // Bytes an object holds, or nullopt when that overflows.
1790 auto objectBytesOf = [&](Type fifoType) -> std::optional<int64_t> {
1791 MemRefType elem = cast<AIE::AIEObjectFifoType>(fifoType).getElementType();
1792 return llvm::checkedMul(
1793 elem.getNumElements(),
1794 static_cast<int64_t>(layout.getTypeSize(elem.getElementType())));
1795 };
1796 // A linked fifo's far end on the link's shared tile is a chain of BDs over
1797 // the link's pool, one per pool segment, sized by the pool owner whatever
1798 // this fifo's own (or consumer) type. A join's or distribute's combined end
1799 // steps through every segment in order, each small end through its own, and
1800 // a plain link's single segment is the whole pool object. The link's
1801 // repeat_count sends each of its output's segment BDs that many times in a
1802 // row. Every link the fifo meets has an end the transfer must complete,
1803 // one per tile when links on several tiles read a broadcast.
1804 SmallVector<Value> linkTiles;
1805 SmallVector<BdChain> chains;
1806 if (AIE::DeviceOp device = fifo->getParentOfType<AIE::DeviceOp>())
1807 for (AIE::ObjectFifoLinkOp link : device.getOps<AIE::ObjectFifoLinkOp>()) {
1808 std::vector<AIE::ObjectFifoCreateOp> ins = link.getInputObjectFifos();
1809 std::vector<AIE::ObjectFifoCreateOp> outs = link.getOutputObjectFifos();
1810 if (!llvm::is_contained(ins, fifo) && !llvm::is_contained(outs, fifo))
1811 continue;
1812 std::optional<Value> sharedTile = link.getOptionalSharedTile();
1813 if (ins.empty() || outs.empty() || !sharedTile)
1814 return success();
1815 linkTiles.push_back(*sharedTile);
1816 AIE::ObjectFifoCreateOp owner = link.getPoolOwner();
1817 MemRefType pool =
1818 cast<AIE::AIEObjectFifoType>(owner.getElemType()).getElementType();
1819 auto width =
1820 static_cast<int64_t>(layout.getTypeSize(pool.getElementType()));
1821 SmallVector<std::pair<int64_t, int64_t>> segments =
1822 link.getPoolSegments();
1823 if (fifo != owner && (link.isJoin() || link.isDistribute())) {
1824 std::vector<AIE::ObjectFifoCreateOp> &many = link.isJoin() ? ins : outs;
1825 segments = {segments[llvm::find(many, fifo) - many.begin()]};
1826 }
1827 SmallVector<int64_t> segmentBytes;
1828 for (auto [offset, size] : segments) {
1829 std::optional<int64_t> bytes = llvm::checkedMul(size, width);
1830 if (!bytes)
1831 break;
1832 segmentBytes.push_back(*bytes);
1833 }
1834 int64_t copies = llvm::is_contained(outs, fifo)
1835 ? link.getRepeatCount().value_or(1)
1836 : 1;
1837 std::optional<BdChain> chain;
1838 if (segmentBytes.size() == segments.size())
1839 chain = BdChain::get(segmentBytes, copies);
1840 if (chain && !llvm::is_contained(chains, *chain))
1841 chains.push_back(*chain);
1842 }
1843 // Every far-end BD chain this transfer must leave at a BD boundary: the
1844 // producer's, for a drain, and each consumer's other than the shim's own. A
1845 // broadcast stalls on its slowest consumer, so a drain bounds what the
1846 // others receive too. An end off every link tile is one BD over its own
1847 // object.
1848 auto addChain = [&](Value tile, Type fifoType) {
1849 if (llvm::is_contained(linkTiles, tile))
1850 return;
1851 std::optional<BdChain> chain;
1852 if (std::optional<int64_t> bytes = objectBytesOf(fifoType))
1853 chain = BdChain::get({*bytes}, 1);
1854 if (chain && !llvm::is_contained(chains, *chain))
1855 chains.push_back(*chain);
1856 };
1857 auto producer =
1858 dyn_cast_or_null<AIE::TileLike>(fifo.getProducerTile().getDefiningOp());
1859 if (!producer)
1860 return success();
1861 if (!producer.isShimTile())
1862 addChain(fifo.getProducerTile(), fifo.getElemType());
1863 for (Value consumer : fifo.getConsumerTiles()) {
1864 auto tile = dyn_cast_or_null<AIE::TileLike>(consumer.getDefiningOp());
1865 if (!tile || !tile.isShimTile())
1866 addChain(consumer, fifo.getConsumerElemTypeOrDefault());
1867 }
1868 if (chains.empty())
1869 return success();
1870
1871 // Transfers in a loop or branch have no static count to sum.
1872 auto sequence = task->getParentOfType<AIE::RuntimeSequenceOp>();
1873 if (!sequence || task->getBlock() != &sequence.getBody().front())
1874 return success();
1875 // A dma_channel_reset_for re-arms the fifo's far ends and drops a partly
1876 // filled object. It doesn't block, so which bytes land before it isn't
1877 // static, and the transfers around it can't be summed.
1878 if (llvm::any_of(task->getBlock()->getOps<AIEX::DmaChannelResetForOp>(),
1879 [&](AIEX::DmaChannelResetForOp reset) {
1880 return reset.getObjfifoAttr() == task.getAlloc();
1881 }))
1882 return success();
1883 // Segment boundaries don't add up the way whole objects do, so the
1884 // sequence's total is checked whatever this transfer moves, once: by the
1885 // first transfer through the fifo.
1886 for (Operation *prev = task->getPrevNode(); prev; prev = prev->getPrevNode())
1887 if (auto other = dyn_cast<AIEX::DMAConfigureTaskForOp>(prev);
1888 other && other.getAlloc() == task.getAlloc() &&
1889 staticBytesStreamed(other) != 0)
1890 return success();
1891 std::optional<int64_t> total = 0;
1892 int64_t sharing = 0;
1893 WalkResult walk = sequence.walk([&](Operation *op) {
1894 // A dma_memcpy_nd or BD chain through the fifo also shares its objects;
1895 // its extent is not summed here, so the sequence's total is unknown.
1896 if (auto memcpy = dyn_cast<AIEX::NpuDmaMemcpyNdOp>(op))
1897 return memcpy.getMetadata() == task.getAlloc() ? WalkResult::interrupt()
1898 : WalkResult::advance();
1899 if (auto chain = dyn_cast<AIEX::DMAStartBdChainForOp>(op))
1900 return chain.getAllocAttr() == task.getAlloc() ? WalkResult::interrupt()
1901 : WalkResult::advance();
1902 auto other = dyn_cast<AIEX::DMAConfigureTaskForOp>(op);
1903 if (!other || other.getAlloc() != task.getAlloc())
1904 return WalkResult::advance();
1905 std::optional<int64_t> bytes = staticBytesStreamed(other);
1906 // A task that is never started streams nothing, wherever it is.
1907 if (bytes == 0)
1908 return WalkResult::advance();
1909 if (!bytes || other->getBlock() != task->getBlock())
1910 return WalkResult::interrupt();
1911 total = llvm::checkedAdd(*total, *bytes);
1912 if (!total)
1913 return WalkResult::interrupt();
1914 ++sharing;
1915 return WalkResult::advance();
1916 });
1917 if (walk.wasInterrupted())
1918 return success();
1919 const BdChain *partial = llvm::find_if(
1920 chains, [&](const BdChain &chain) { return !chain.endsOnBd(*total); });
1921 if (partial == chains.end())
1922 return success();
1923
1924 InFlightDiagnostic error =
1925 task.emitOpError() << "moves " << *moved << " bytes through objectFIFO @"
1926 << fifo.getSymName() << ", which";
1927 if (sharing > 1)
1928 error << ", with this runtime sequence's other transfers through it ("
1929 << *total << " bytes in all),";
1930 error << " is not a whole number of its " << partial->objectBytes
1931 << "-byte objects";
1932 if (partial->segmentBytes.size() == 1)
1933 return error << "; the far end's buffer descriptor is sized by the "
1934 "object, so a partial one never completes";
1935 std::string segments;
1936 llvm::raw_string_ostream os(segments);
1937 llvm::interleaveComma(partial->segmentBytes, os);
1938 error << " and doesn't end on a segment boundary (segments of " << segments
1939 << " bytes";
1940 if (partial->copies > 1)
1941 error << ", each sent " << partial->copies << " times in a row";
1942 return error << "); the far end's buffer descriptors are sized by the "
1943 "segments, so a partial one never completes";
1944}
1945
1946// Resolving the allocation symbol through the collection keeps the lookup off
1947// the device's linear symbol scan, which a per-op verifier repeats after every
1948// pass.
1949LogicalResult AIEX::DMAConfigureTaskForOp::verifySymbolUses(
1950 SymbolTableCollection &symbolTable) {
1951 AIE::DeviceOp dev = getOperation()->getParentOfType<AIE::DeviceOp>();
1952 if (!dev)
1953 return success();
1954 Operation *target =
1955 symbolTable.lookupSymbolIn(dev, getAlloc().getRootReference());
1956 if (!target)
1957 return emitOpError() << getAlloc() << " does not name a symbol";
1958 // The tile is known once the name is a shim DMA allocation or a route
1959 // endpoint, so the per-BD dimension limit can be enforced before the task
1960 // becomes a concrete dma_configure_task. Before the objectFIFO lowering
1961 // the name is the fifo's own, and the check waits.
1962 Value tileValue;
1963 LogicalResult named =
1964 TypeSwitch<Operation *, LogicalResult>(target)
1965 .Case([&](AIE::ShimDMAAllocationOp alloc) {
1966 tileValue = alloc.getTile();
1967 return success();
1968 })
1969 .Case([&](AIE::RouteEndpointOp endpoint) -> LogicalResult {
1970 tileValue = endpoint.getTile();
1971 if (endpoint.getBundle() == AIE::WireBundle::DMA)
1972 return success();
1973 return emitOpError() << getAlloc() << " names a "
1974 << stringifyWireBundle(endpoint.getBundle())
1975 << " port, not a DMA channel";
1976 })
1977 .Case([&](AIE::ObjectFifoCreateOp fifo) {
1978 return verifyTaskCoversWholeObjects(*this, fifo);
1979 })
1980 .Default([&](Operation *) -> LogicalResult {
1981 return emitOpError() << getAlloc()
1982 << " must name an aie.shim_dma_allocation, an "
1983 "aie.route_endpoint or an aie.objectfifo";
1984 });
1985 if (failed(named))
1986 return failure();
1987 // An allocation or endpoint on an unplaced (logical) tile waits for
1988 // placement to substitute a real one.
1989 auto tile = tileValue ? tileValue.getDefiningOp<AIE::TileOp>() : nullptr;
1990 if (!tile)
1991 return success();
1992 const AIE::AIETargetModel &targetModel = AIE::getTargetModel(getOperation());
1993 return verifyTaskBDDimensions(targetModel, tile.getCol(), tile.getRow(),
1994 getBody());
1995}
1996
1997//===----------------------------------------------------------------------===//
1998// DMAStartBdChainOp
1999//===----------------------------------------------------------------------===//
2000
2001AIE::BDChainOp AIEX::DMAStartBdChainOp::getBDChainOp() {
2002 AIE::DeviceOp device = (*this)->getParentOfType<AIE::DeviceOp>();
2003 AIE::BDChainOp chain = device.lookupSymbol<AIE::BDChainOp>(getSymbol());
2004 return chain;
2005}
2006
2007LogicalResult AIEX::DMAStartBdChainOp::verify() {
2008 AIE::BDChainOp chain = getBDChainOp();
2009 if (!chain) {
2010 return emitOpError("symbol does not reference valid BD chain");
2011 }
2012
2013 auto actualArgTypes = getArgs().getTypes();
2014 auto expectedArgTypes = chain.getRegion().getArgumentTypes();
2015 if (actualArgTypes.size() != expectedArgTypes.size()) {
2016 return emitOpError("Number of arguments mismatches.");
2017 }
2018 for (unsigned i = 0, n = expectedArgTypes.size(); i < n; i++) {
2019 if (actualArgTypes[i] != expectedArgTypes[i]) {
2020 return emitOpError("Argument ") << (i + 1) << " types mismatch: "
2021 << "expected " << expectedArgTypes[i]
2022 << " but got " << actualArgTypes[i];
2023 }
2024 }
2025 return success();
2026}
2027
2028//===----------------------------------------------------------------------===//
2029// NpuControlPacketOp
2030//===----------------------------------------------------------------------===//
2031
2032uint32_t AIEX::NpuControlPacketOp::getRowFromAddr() {
2033 const auto &targetModel = AIE::getTargetModel(*this);
2034 uint32_t addr = getAddress();
2035 uint32_t rowInt = (addr >> targetModel.getRowShift()) & 0x1f;
2036 return rowInt;
2037}
2038
2039uint32_t AIEX::NpuControlPacketOp::getColumnFromAddr() {
2040 const auto &targetModel = AIE::getTargetModel(*this);
2041 uint32_t addr = getAddress();
2042 uint32_t colInt = (addr >> targetModel.getColumnShift()) & 0x1f;
2043 return colInt;
2044}
2045
2046//===----------------------------------------------------------------------===//
2047// SetLockOp
2048//===----------------------------------------------------------------------===//
2049
2050LogicalResult AIEX::SetLockOp::verify() {
2051 const auto &targetModel = AIE::getTargetModel(*this);
2052
2053 if (targetModel.getTargetArch() == AIE::AIEArch::AIE1)
2054 return emitOpError("SetLockOp is not supported on AIE1.");
2055
2056 if (std::optional<int64_t> value = getConstantIntValue(getValue())) {
2057 if (*value < 0)
2058 return emitOpError("Lock value must be non-negative");
2059 if (*value > targetModel.getMaxLockValue())
2060 return emitOpError("Lock value exceeds the maximum value of " +
2061 std::to_string(targetModel.getMaxLockValue()));
2062 }
2063
2064 auto lockOp = getLockOp();
2065 auto lockIDOpt = getLockOp().getLockID();
2066 // Note that the lockID may not be assigned initially, so lets wait until it
2067 // is to verify the lockID dependent conditions
2068 if (!lockIDOpt) {
2069 return success();
2070 }
2071 // A logical tile with a coordinate still unpinned waits for the
2072 // post-placement re-verify for its range and address checks, as
2073 // UsesAreAccessible's do.
2074 std::optional<int> col = lockOp.getTileLike().tryGetCol();
2075 std::optional<int> row = lockOp.getTileLike().tryGetRow();
2076 if (!col || !row)
2077 return success();
2078
2079 uint32_t lockID = lockOp.getLockIDValue();
2080
2081 if (lockID >= targetModel.getNumLocks(*col, *row)) {
2082 return emitOpError("Lock ID out of range for given tile. Max ID: " +
2083 std::to_string(targetModel.getNumLocks(*col, *row) - 1));
2084 }
2085
2086 if (!targetModel.getLocalLockAddress(lockID, AIE::TileID{*col, *row})) {
2087 return emitOpError("Invalid lock ID and tile combination when trying to "
2088 "retrieve the local lock address.");
2089 }
2090
2091 return success();
2092}
2093
2094//===----------------------------------------------------------------------===//
2095// DMABdPoolPopOp / DMABdPoolPushOp
2096//===----------------------------------------------------------------------===//
2097
2098static LogicalResult verifyBdPoolPartition(Operation *op, int col, int row,
2099 uint32_t begin, uint32_t end) {
2100 uint32_t numBds = AIE::getTargetModel(op).getNumBDs(col, row);
2101 if (begin >= end || end > numBds)
2102 return op->emitOpError()
2103 << "partition [" << begin << ", " << end
2104 << ") is not a nonempty range of the " << numBds
2105 << " buffer descriptors on tile (" << col << ", " << row << ")";
2106 return success();
2107}
2108
2109LogicalResult AIEX::DMABdPoolPopOp::verify() {
2110 return verifyBdPoolPartition(*this, getColumn(), getRow(), getBdBegin(),
2111 getBdEnd());
2112}
2113
2114LogicalResult AIEX::DMABdPoolPushOp::verify() {
2115 return verifyBdPoolPartition(*this, getColumn(), getRow(), getBdBegin(),
2116 getBdEnd());
2117}
2118
2119//===----------------------------------------------------------------------===//
2120// DmaChannelResetOp
2121//===----------------------------------------------------------------------===//
2122
2123LogicalResult AIEX::DmaChannelResetOp::verify() {
2124 const auto &targetModel = AIE::getTargetModel(*this);
2125
2126 auto tile = dyn_cast_or_null<AIE::TileOp>(getTile().getDefiningOp());
2127 if (!tile)
2128 return emitOpError() << "tile operand must be produced by an aie.tile op";
2129 int col = tile.getCol();
2130 int row = tile.getRow();
2131 // Only core and mem tiles have a per-channel DMA reset bit. The shim NOC DMA
2132 // control register has no reset field (aie-rt's Aie2PShimDmaChProp sets
2133 // Reset.Mask = 0); bit 1 there is PAUSE_MEM, not RESET. Rejecting shim keeps
2134 // the op honest about what it can lower, matching aie-rt's own
2135 // XAie_DmaChannelReset, which errors on SHIMNOC/SHIMPL tiles.
2136 if (!targetModel.isCoreTile(col, row) && !targetModel.isMemTile(col, row))
2137 return emitOpError() << "tile (" << col << ", " << row
2138 << ") has no DMA channel reset (only core and mem "
2139 "tiles do; shim NOC DMA has no reset bit)";
2140
2141 // Number of DMA channels on this tile in this direction. Mirrors
2142 // TileOp::getNumSource/DestConnections(WireBundle::DMA): the switchbox
2143 // direction is reversed relative to the DMA direction.
2144 uint32_t numChannels = getDirection() == AIE::DMAChannelDir::S2MM
2145 ? targetModel.getNumDestSwitchboxConnections(
2146 col, row, AIE::WireBundle::DMA)
2148 col, row, AIE::WireBundle::DMA);
2149 if (getChannel() >= numChannels)
2150 return emitOpError() << "channel " << getChannel()
2151 << " out of range for this tile and direction (tile "
2152 "has "
2153 << numChannels << " DMA channel(s) in this direction)";
2154
2155 return success();
2156}
2157
2158LogicalResult AIEX::DmaChannelResetForOp::verify() {
2159 // Deferring verifier, in the shape of DMAConfigureTaskForOp::verify: the
2160 // referenced symbol is only resolvable once the objectFIFO lowering has run.
2161 // It names an aie.objectfifo, then that fifo's shim endpoint while the
2162 // lowering is in flight, and finally its aie.objectfifo_rearm_binding. If
2163 // none is resolvable yet, defer -- a later pass will resolve it.
2164 AIE::DeviceOp dev = getOperation()->getParentOfType<AIE::DeviceOp>();
2165 if (!dev)
2166 return success();
2167 // The resident re-arm relies on the aie2p behavior that a DMA channel has no
2168 // enable bit, so the only way to restart it is a START_QUEUE push. AIE1 DMA
2169 // channels have an enable bit and are armed differently, so the trio this op
2170 // lowers to would not re-arm them correctly.
2171 if (AIE::getTargetModel(*this).getTargetArch() == AIE::AIEArch::AIE1)
2172 return emitOpError() << "is not supported on AIE1 devices (the resident "
2173 "re-arm relies on the aie2p start-queue push)";
2174 Operation *target = dev.lookupSymbol(getObjfifo());
2175 if (!target)
2176 return success(); // symbol resolved during a later pass; defer the check
2177 if (!isa<AIE::ObjectFifoCreateOp, AIE::RouteEndpoint,
2178 AIE::ObjectFifoRearmBindingOp>(target))
2179 return emitOpError() << "'" << getObjfifo()
2180 << "' must reference an aie.objectfifo (or its "
2181 "aie.objectfifo_rearm_binding)";
2182 return success();
2183}
2184
2185//===----------------------------------------------------------------------===//
2186// CoreResetOp
2187//===----------------------------------------------------------------------===//
2188
2189LogicalResult AIEX::CoreResetOp::verify() {
2190 const auto &targetModel = AIE::getTargetModel(*this);
2191
2192 // The op lowers to an NPU control-packet write; the runtime sequence has no
2193 // meaning on AIE1. Reject it explicitly, as SetLockOp does.
2194 if (targetModel.getTargetArch() == AIE::AIEArch::AIE1)
2195 return emitOpError("aiex.core_reset is not supported on AIE1.");
2196
2197 auto tile = dyn_cast_or_null<AIE::TileOp>(getTile().getDefiningOp());
2198 if (!tile)
2199 return emitOpError() << "tile operand must be produced by an aie.tile op";
2200 int col = tile.getCol();
2201 int row = tile.getRow();
2202 // The tile coordinates are bounded by aie.tile's own verifier, so this op
2203 // does not re-check them for range.
2204
2205 // Only core tiles have a CORE_CONTROL register with a reset bit. Mem and shim
2206 // tiles have no compute core, so there is nothing valid to lower to. This
2207 // matches aie-rt's XAie_CoreReset, which errors on any tile that is not an
2208 // AIE (core) tile.
2209 if (!targetModel.isCoreTile(col, row))
2210 return emitOpError() << "tile (" << col << ", " << row
2211 << ") has no core to reset (only core tiles have a "
2212 "CORE_CONTROL register)";
2213
2214 return success();
2215}
2216
2217//===----------------------------------------------------------------------===//
2218// BlockFloatingPointType
2219//===----------------------------------------------------------------------===//
2220uint64_t AIEX::BlockFloatType::getTotalSizeInBits() const {
2221 return getBlockSize() * getMantissaBits() + getExponentBits() +
2222 getSubtileShiftBits();
2223}
2224
2225llvm::TypeSize AIEX::BlockFloatType::getTypeSizeInBits(
2226 const mlir::DataLayout &dataLayout,
2227 mlir::DataLayoutEntryListRef params) const {
2228 return llvm::TypeSize::getFixed(getTotalSizeInBits());
2229}
2230
2231uint64_t AIEX::BlockFloatType::getABIAlignment(
2232 const mlir::DataLayout &dataLayout,
2233 mlir::DataLayoutEntryListRef params) const {
2234 // For the purposes of the data movement operations, we want all types to be
2235 // packed <=> ABI alignment is 1.
2236 return 1;
2237}
2238
2239std::optional<AIEX::BlockFloatType::BlockFormat>
2240AIEX::BlockFloatType::getBlockFormat(StringRef blockType) {
2241 static const llvm::StringMap<AIEX::BlockFloatType::BlockFormat>
2242 blockFormatsMap = {
2243 {"v8bfp16ebs8", {8, 8, 8, 0}},
2244 {"v16bfp16ebs16", {16, 8, 8, 0}},
2245 };
2246
2247 auto it = blockFormatsMap.find(blockType);
2248 if (it != blockFormatsMap.end()) {
2249 return it->second;
2250 }
2251
2252 return std::nullopt;
2253}
2254
2255LogicalResult
2256AIEX::BlockFloatType::verify(function_ref<InFlightDiagnostic()> emitError,
2257 StringRef block_type) {
2258 if (!getBlockFormat(block_type))
2259 return emitError() << "Invalid block type: " << block_type
2260 << ". Known types are: v8bfp16ebs8, v16bfp16ebs16.";
2261
2262 return success();
2263}
2264
2265//===----------------------------------------------------------------------===//
2266// ConfigureOp
2267//===----------------------------------------------------------------------===//
2268
2269AIE::DeviceOp AIEX::ConfigureOp::getReferencedDeviceOp() {
2270 ModuleOp moduleOp = this->getOperation()->getParentOfType<ModuleOp>();
2271 if (!moduleOp) {
2272 emitError("aiex.configure must be inside of a module");
2273 return nullptr;
2274 }
2275 Operation *maybeReferencedDevice =
2276 SymbolTable::lookupSymbolIn(moduleOp.getOperation(), getSymbolAttr());
2277 if (!maybeReferencedDevice) {
2278 emitError("No such device: '") << getSymbolAttr() << "'";
2279 return nullptr;
2280 }
2281 AIE::DeviceOp referencedDevice =
2282 llvm::dyn_cast<AIE::DeviceOp>(maybeReferencedDevice);
2283 if (!referencedDevice) {
2284 emitError("Not a device: '") << getSymbolAttr() << "'";
2285 return nullptr;
2286 }
2287 return referencedDevice;
2288}
2289
2290//===----------------------------------------------------------------------===//
2291// ReadScratchpadParameterOp
2292//===----------------------------------------------------------------------===//
2293
2294LogicalResult AIEX::ReadScratchpadParameterOp::verify() {
2295 auto device = (*this)->getParentOfType<AIE::DeviceOp>();
2296 if (!device) {
2297 return emitOpError("must be inside an aie.device");
2298 }
2299 if (!(*this)->getParentOfType<AIE::CoreOp>()) {
2300 return emitOpError("must be inside an aie.core");
2301 }
2302 auto moduleOp = (*this)->getParentOfType<ModuleOp>();
2303 if (!moduleOp ||
2304 !moduleOp.lookupSymbol<AIEX::ScratchpadParameterOp>(getParameter())) {
2305 return emitOpError("references unknown parameter '")
2306 << getParameter()
2307 << "' (aiex.scratchpad_parameter ops are declared at module scope)";
2308 }
2309 if (getResult().getType().isF32()) {
2310 return emitOpError(
2311 "f32 parameters are not supported: the scratchpad encoding zeroes "
2312 "the top 2 bits, which clobbers the sign bit and top exponent bit "
2313 "of an f32. Use bf16 or an integer type up to i32 instead.");
2314 }
2315 return success();
2316}
2317
2318LogicalResult AIEX::WaitScratchpadParametersOp::verify() {
2319 if (!(*this)->getParentOfType<AIE::CoreOp>()) {
2320 return emitOpError("must be inside an aie.core");
2321 }
2322 return success();
2323}
2324
2325LogicalResult AIEX::ConfigureOp::verify() {
2326 AIE::DeviceOp parentDev = getOperation()->getParentOfType<AIE::DeviceOp>();
2327 AIE::DeviceOp referencedDev = getReferencedDeviceOp();
2328 if (!referencedDev) {
2329 return failure();
2330 }
2331 if (parentDev.getDevice() != referencedDev.getDevice()) {
2332 emitError("Device types do not match: '")
2333 << AIE::stringifyAIEDevice(parentDev.getDevice()) << "' vs. '"
2334 << AIE::stringifyAIEDevice(referencedDev.getDevice()) << "'";
2335 return failure();
2336 }
2337 return success();
2338}
2339
2340//===----------------------------------------------------------------------===//
2341// RunOp
2342//===----------------------------------------------------------------------===//
2343
2344AIE::DeviceOp AIEX::RunOp::getCalleeDeviceOp() {
2345 AIEX::ConfigureOp configureOp =
2346 getOperation()->getParentOfType<AIEX::ConfigureOp>();
2347 if (!configureOp) {
2348 return nullptr;
2349 }
2350 AIE::DeviceOp referencedDevice = configureOp.getReferencedDeviceOp();
2351 return referencedDevice;
2352}
2353
2354AIE::RuntimeSequenceOp
2355AIEX::RunOp::getCalleeRuntimeSequenceOp(AIE::NamedOpTableCollection *tables) {
2356 AIEX::ConfigureOp configureOp =
2357 getOperation()->getParentOfType<AIEX::ConfigureOp>();
2358 if (!configureOp) {
2359 return nullptr;
2360 }
2361 AIE::DeviceOp referencedDevice = configureOp.getReferencedDeviceOp();
2362 if (!referencedDevice) {
2363 return nullptr;
2364 }
2365
2366 StringAttr name = getRuntimeSequenceSymbolAttr().getAttr();
2367 if (!tables) {
2368 return AIE::lookupNamedOpIn<AIE::RuntimeSequenceOp>(referencedDevice, name);
2369 }
2370 return tables->lookupIn<AIE::RuntimeSequenceOp>(referencedDevice, name);
2371}
2372
2373//===----------------------------------------------------------------------===//
2374// NpuLoadPdiOp
2375//===----------------------------------------------------------------------===//
2376
2377LogicalResult AIEX::NpuLoadPdiOp::canonicalize(AIEX::NpuLoadPdiOp op,
2378 PatternRewriter &rewriter) {
2379 // Check for back-to-back identical load_pdi ops and remove duplicates
2380 Operation *nextOp = op->getNextNode();
2381 if (!nextOp)
2382 return failure();
2383
2384 // Check if next op is also a NpuLoadPdiOp
2385 auto nextLoadPdi = dyn_cast<AIEX::NpuLoadPdiOp>(nextOp);
2386 if (!nextLoadPdi)
2387 return failure();
2388
2389 // Check if they are identical (all attributes match)
2390 if (op.getDeviceRefAttr() == nextLoadPdi.getDeviceRefAttr() &&
2391 op.getId() == nextLoadPdi.getId() &&
2392 op.getSize() == nextLoadPdi.getSize() &&
2393 op.getAddress() == nextLoadPdi.getAddress()) {
2394 // Erase the first one, keeping the second
2395 rewriter.eraseOp(op);
2396 return success();
2397 }
2398
2399 return failure();
2400}
2401
2402//===----------------------------------------------------------------------===//
2403// NpuWriteConfigOp
2404//===----------------------------------------------------------------------===//
2405
2406static bool writesConfig(Operation *op) {
2407 return isa<AIEX::NpuLoadPdiOp, AIEX::NpuWriteConfigOp,
2408 AIEX::NpuWriteTeardownOp>(op);
2409}
2410
2411// Visits the load_pdi, write_config and write_teardown ops before `op` in its
2412// block, nearest first, then those after it from the block's end, as the host
2413// runs the sequence again on each dispatch, then `op` itself. Returns the first
2414// for which `visit` returns true.
2415static Operation *findConfigOpBefore(Operation *op,
2416 function_ref<bool(Operation *)> visit) {
2417 for (Operation *prev = op->getPrevNode(); prev; prev = prev->getPrevNode())
2418 if (writesConfig(prev) && visit(prev))
2419 return prev;
2420 for (Operation &next : llvm::reverse(*op->getBlock())) {
2421 if (&next == op)
2422 break;
2423 if (writesConfig(&next) && visit(&next))
2424 return &next;
2425 }
2426 return visit(op) ? op : nullptr;
2427}
2428
2429// The load_pdi that resets the array for `op`, or the write_teardown that
2430// leaves it so, or null.
2431static Operation *precedingReset(Operation *op) {
2432 return findConfigOpBefore(op, [&](Operation *prev) {
2433 return prev != op &&
2434 isa<AIEX::NpuLoadPdiOp, AIEX::NpuWriteTeardownOp>(prev);
2435 });
2436}
2437
2438static bool loadsSamePdi(AIEX::NpuLoadPdiOp a, AIEX::NpuLoadPdiOp b) {
2439 return a.getDeviceRefAttr() == b.getDeviceRefAttr() &&
2440 a.getId() == b.getId() && a.getSize() == b.getSize() &&
2441 a.getAddress() == b.getAddress();
2442}
2443
2444LogicalResult AIEX::NpuWriteConfigOp::verify() {
2445 Operation *found = precedingReset(*this);
2446 if (!found)
2447 return emitOpError("must follow the load_pdi that resets the array");
2448 auto reset = dyn_cast<AIEX::NpuLoadPdiOp>(found);
2449 if (!reset)
2450 return success();
2451 // The firmware skips the load if it loaded the same PDI last. That leaves
2452 // the array as a reset does only if every configuration since is torn down.
2453 bool tornDown = true;
2454 Operation *later = reset;
2455 auto before =
2456 cast<AIEX::NpuLoadPdiOp>(findConfigOpBefore(reset, [&](Operation *op) {
2457 if (isa<AIEX::NpuWriteConfigOp>(op) &&
2458 !isa<AIEX::NpuWriteTeardownOp>(later))
2459 tornDown = false;
2460 later = op;
2461 return isa<AIEX::NpuLoadPdiOp>(op);
2462 }));
2463 if (!loadsSamePdi(before, reset) || tornDown)
2464 return success();
2465 InFlightDiagnostic diag = emitOpError(
2466 "follows a load_pdi the firmware skips, as it loads the PDI loaded "
2467 "just before it, so the array is not reset");
2468 diag.attachNote(before.getLoc()) << "the PDI is loaded here";
2469 return diag;
2470}
2471
2472LogicalResult
2473AIEX::NpuLoadPdiOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
2474 FlatSymbolRefAttr ref = getDeviceRefAttr();
2475 if (!ref)
2476 return success();
2477 auto device = (*this)->getParentOfType<AIE::DeviceOp>();
2478 Operation *scope = device ? device->getParentOp()
2479 : SymbolTable::getNearestSymbolTable(*this);
2480 if (!isa_and_present<AIE::DeviceOp>(symbolTable.lookupSymbolIn(scope, ref)))
2481 return emitOpError() << ref << " is not a device";
2482 return success();
2483}
2484
2485LogicalResult
2486AIEX::NpuWriteConfigOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
2487 Operation *scope = (*this)->getParentOfType<AIE::DeviceOp>()->getParentOp();
2488 if (!isa_and_present<AIE::DeviceOp>(
2489 symbolTable.lookupSymbolIn(scope, getDeviceRefAttr())))
2490 return emitOpError() << getDeviceRefAttr() << " is not a device";
2491 auto reset = dyn_cast_or_null<AIEX::NpuLoadPdiOp>(precedingReset(*this));
2492 FlatSymbolRefAttr resetRef = reset ? reset.getDeviceRefAttr() : nullptr;
2493 auto empty = resetRef ? dyn_cast_or_null<AIE::DeviceOp>(
2494 symbolTable.lookupSymbolIn(scope, resetRef))
2495 : nullptr;
2496 // The transaction conversion gives every device a `@configure` sequence; an
2497 // empty one still configures nothing.
2498 if (reset &&
2499 (!empty ||
2500 !llvm::all_of(empty.getBody()->without_terminator(), [](Operation &op) {
2501 return isa<AIE::RuntimeSequenceOp>(op) &&
2502 llvm::all_of(op.getRegion(0),
2503 [](Block &block) { return block.empty(); });
2504 })))
2505 return emitOpError("must follow a load_pdi of an empty device, which "
2506 "resets the array");
2507 return success();
2508}
2509
2510//===----------------------------------------------------------------------===//
2511// NpuWriteTeardownOp
2512//===----------------------------------------------------------------------===//
2513
2514LogicalResult AIEX::NpuWriteTeardownOp::verify() {
2515 Operation *prev = (*this)->getPrevNode();
2516 while (prev && !writesConfig(prev))
2517 prev = prev->getPrevNode();
2518 auto config = dyn_cast_or_null<AIEX::NpuWriteConfigOp>(prev);
2519 if (!config || config.getDeviceRefAttr() != getDeviceRefAttr())
2520 return emitOpError("must follow the write_config of ")
2521 << getDeviceRefAttr() << ", with no load_pdi between";
2522 return success();
2523}
2524
2525LogicalResult
2526AIEX::NpuWriteTeardownOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
2527 Operation *scope = (*this)->getParentOfType<AIE::DeviceOp>()->getParentOp();
2528 auto device = dyn_cast_or_null<AIE::DeviceOp>(
2529 symbolTable.lookupSymbolIn(scope, getDeviceRefAttr()));
2530 if (!device)
2531 return emitOpError() << getDeviceRefAttr() << " is not a device";
2532 Operation *shimStart = AIEX::findShimDmaStart(device);
2533 if (!shimStart)
2534 return success();
2535 InFlightDiagnostic diag =
2536 emitOpError() << getDeviceRefAttr()
2537 << " starts a shim DMA channel, which teardown cannot stop";
2538 diag.attachNote(shimStart->getLoc()) << "started here";
2539 return diag;
2540}
2541
2542Operation *AIEX::findShimDmaStart(AIE::DeviceOp device) {
2543 Operation *found = nullptr;
2544 device.walk([&](Operation *op) {
2545 if (!isa<AIE::DMAStartOp, AIE::DMAOp>(op) ||
2546 !isa<AIE::ShimDMAOp>(op->getParentOp()))
2547 return WalkResult::advance();
2548 found = op;
2549 return WalkResult::interrupt();
2550 });
2551 return found;
2552}
bool isCoreTile(int col, int row) const
Return true if the given tile is a Core tile.
virtual uint64_t getDmaBdMaxLen(AIETileType tileType) const =0
Return the maximum transfer length, measured in address-generation granules (see getAddressGenGranula...
virtual AIEArch getTargetArch() const =0
Return the target architecture.
bool isMemTile(int col, int row) const
Return true if the given tile is a Mem tile.
virtual uint32_t getDmaBdWrapBits(AIETileType tileType) const =0
Return the bit width of the wrap (size) field in a DMA buffer descriptor for the given tile type.
virtual uint32_t getLocalMemorySize() const =0
Return the size (in bytes) of the local data memory of a core.
virtual uint32_t getNumLocks(AIETileType tileType) const =0
Return the number of lock objects for a given tile type.
virtual uint32_t getDmaBdIterBits(AIETileType tileType) const =0
Return the bit width of the iteration (repeat) wrap/stride fields in a DMA buffer descriptor for the ...
virtual uint32_t getBDMaxDims(AIETileType tileType) const =0
Return the maximum number of addressing dimensions (wraps/strides) a single buffer descriptor support...
virtual std::vector< std::pair< uint32_t, uint32_t > > getShimBurstEncodingsAndLengths() const =0
virtual uint32_t getDmaBdStepBits(AIETileType tileType) const =0
Return the bit width of the step (stride) field in a DMA buffer descriptor for the given tile type.
virtual std::optional< uint32_t > getLocalLockAddress(uint32_t lockId, TileID tile) const =0
virtual AIETileType getTileType(int col, int row) const =0
Return the tile type for the given tile coordinates.
virtual uint32_t getMaxRepeatCount() const =0
Return the largest DMA task repeat count (unsupported = 0).
bool isShimNOCTile(int col, int row) const
Return true if the given tile is a ShimNOC tile.
virtual uint32_t getNumBDs(AIETileType tileType) const =0
Return the number of buffer descriptors for a given tile type.
virtual uint32_t getMaxLockValue() const =0
Return the maximum value that can be stored in a lock register.
virtual uint32_t getMaxOutOfOrderId() const =0
Return the largest out-of-order BD id (unsupported = 0).
virtual uint32_t getColumnShift() const =0
virtual uint32_t getNumDestSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a switchbox.
virtual uint32_t getRowShift() const =0
virtual uint32_t getAddressGenGranularity() const =0
Return the data bus width of the device.
virtual uint32_t getMemTileSize() const =0
Return the size (in bytes) of a MemTile.
virtual uint32_t getNumSourceSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a switchbox.
OpTy lookupIn(mlir::Operation *symbolTableOp, mlir::StringAttr name)
Definition AIEDialect.h:179
mlir::Operation * lookup(mlir::StringAttr name) const
Definition AIEDialect.h:153
const V & get(const std::map< K, V > &pa, K k)
Definition d_ary_heap.h:70
mlir::Operation * findShimDmaStart(AIE::DeviceOp device)
bool isContiguousTransfer(llvm::ArrayRef< int64_t > sizes, llvm::ArrayRef< int64_t > strides)
void encodeHardwareStridesWraps(Policy &p, uint64_t elemWidth, uint32_t addressGranularity, typename Policy::V inputSizes[4], typename Policy::V inputStrides[4], typename Policy::V sizes[4], typename Policy::V strides[4])
Definition BdLowering.h:145
mlir::LogicalResult verifyStridesWraps(mlir::Operation *forOp, llvm::function_ref< mlir::InFlightDiagnostic()> emitError, mlir::BaseMemRefType referencedBufType, int tileCol, int tileRow, llvm::SmallVector< int64_t, 4 > inputSizes, llvm::SmallVector< int64_t, 4 > inputStrides, llvm::SmallVector< int64_t, 4 > hardwareSizes, llvm::SmallVector< int64_t, 4 > hardwareStrides, bool skipTransformationChecks=false)
std::optional< uint64_t > getConstantInt64Operand(mlir::Value v)
bool isDecomposableNdDmaPattern(mlir::Operation *forOp, mlir::BaseMemRefType referencedBufType, const xilinx::AIE::AIETargetModel &targetModel, int tileCol, int tileRow, llvm::ArrayRef< int64_t > offsetsInnermostFirst, llvm::ArrayRef< int64_t > sizesInnermostFirst, llvm::ArrayRef< int64_t > stridesInnermostFirst)
True when the pattern is fully constant, non-contiguous, currently illegal, and decomposeNdDmaPattern...
std::optional< uint32_t > getConstantIntOperand(mlir::Value v)
mlir::Value createConstantArgPlus(mlir::OpBuilder &builder, mlir::Location loc, uint64_t value)
void getHardwareStridesWraps(const AIE::AIETargetModel &targetModel, mlir::Operation *op, mlir::BaseMemRefType referencedBufType, llvm::SmallVector< int64_t, 4 > inputSizes, llvm::SmallVector< int64_t, 4 > inputStrides, llvm::SmallVector< int64_t, 4 > &sizes, llvm::SmallVector< int64_t, 4 > &strides)
bool isLinearModeTransfer(const AIE::AIETargetModel &targetModel, int col, int row, llvm::ArrayRef< int64_t > sizes, llvm::ArrayRef< int64_t > strides)
bool isConstMultipleOfGranule(int64_t value, uint64_t elemWidth, uint32_t addressGranularity)
Definition BdLowering.h:65
bool isLinearTransfer(llvm::ArrayRef< int64_t > sizes, llvm::ArrayRef< int64_t > strides)
mlir::LogicalResult verifyConstBdRealizability(mlir::Operation *op, llvm::ArrayRef< mlir::OpFoldResult > sizes, llvm::ArrayRef< mlir::OpFoldResult > strides, uint64_t elemWidth, uint32_t gran)
Definition BdLowering.h:77
mlir::Value createConstantI32(mlir::OpBuilder &builder, mlir::Location loc, uint32_t value)
void printTypedDynamicIndexList(mlir::OpAsmPrinter &printer, mlir::Operation *op, mlir::OperandRange values, llvm::ArrayRef< int64_t > integers, mlir::TypeRange types)
mlir::LogicalResult verifyLengthParameterTile(mlir::Operation *op, const AIETargetModel &targetModel, int col, int row)
mlir::LogicalResult verifyAssignedPacket(mlir::Operation *op, PacketInfoAttr packet, llvm::StringRef what="packet")
mlir::ParseResult parseTypedDynamicIndexList(mlir::OpAsmParser &parser, llvm::SmallVectorImpl< mlir::OpAsmParser::UnresolvedOperand > &values, mlir::DenseI64ArrayAttr &integers, llvm::SmallVectorImpl< mlir::Type > &types)
mlir::Operation * lookupNamedOpIn(mlir::Operation *symbolTableOp, mlir::StringAttr name)
mlir::LogicalResult verifyDMABDOutOfOrderId(DMABDOp bd, bool packetEnabledByContext=false)
mlir::LogicalResult verifyOutOfOrderChannel(mlir::Operation *op, DMAChannelDir dir, bool outOfOrder, llvm::ArrayRef< DMABDOp > bds, bool packetEnabledByContext=false)
mlir::LogicalResult verifyLengthParameter(mlir::Operation *op, std::optional< int64_t > lengthUnit, mlir::BaseMemRefType buffer, std::optional< int64_t > lenElems, bool contiguous, llvm::ArrayRef< int64_t > innerSizes)
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
const AIETargetModel & getTargetModel(mlir::Operation *op)
mlir::LogicalResult verifyDMABDIteration(DMABDOp bd, AIETileType tileType)