MLIR-AIE 1.4.5.dev
BdLowering.cpp
Go to the documentation of this file.
1//===- BdLowering.cpp -------------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
9
13
14#include "mlir/Dialect/Arith/IR/Arith.h"
15#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
16#include "mlir/Dialect/Utils/StaticValueUtils.h"
17#include "mlir/IR/Iterators.h"
18#include "llvm/Support/MathExtras.h"
19
20#include <limits>
21#include <numeric>
22
23using namespace mlir;
24
25namespace xilinx::AIEX {
26
27//===----------------------------------------------------------------------===//
28// SsaStridePolicy: arith-emitting mirror of ConstStridePolicy.
29//
30// Arithmetic is i64, like ConstStridePolicy. Operands arrive zero-extended, so
31// the comparisons are unsigned; a negative runtime value reads as a huge one,
32// which the host-side guards in encodeBdCommon reject before the encoded
33// fields are used.
34//===----------------------------------------------------------------------===//
35
36Value SsaStridePolicy::cst(int64_t c) const {
37 return arith::ConstantOp::create(builder, loc, builder.getI64IntegerAttr(c));
38}
39
40Value SsaStridePolicy::mul(Value v, int64_t c) const {
41 return builder.createOrFold<arith::MulIOp>(loc, v, cst(c));
42}
43
44Value SsaStridePolicy::div(Value v, int64_t c) const {
45 // Hardware granularity scaling is exact (verifier/guards enforce
46 // divisibility).
47 return builder.createOrFold<arith::DivUIOp>(loc, v, cst(c));
48}
49
50Value SsaStridePolicy::sub(Value v, int64_t c) const {
51 return builder.createOrFold<arith::SubIOp>(loc, v, cst(c));
52}
53
54Value SsaStridePolicy::selectGT1(Value cond, Value t, Value e) const {
55 Value gt = builder.createOrFold<arith::CmpIOp>(loc, arith::CmpIPredicate::ugt,
56 cond, cst(1));
57 return builder.createOrFold<arith::SelectOp>(loc, gt, t, e);
58}
59
60Value SsaStridePolicy::selectGT0(Value cond, Value t, Value e) const {
61 Value gt = builder.createOrFold<arith::CmpIOp>(loc, arith::CmpIPredicate::ugt,
62 cond, cst(0));
63 return builder.createOrFold<arith::SelectOp>(loc, gt, t, e);
64}
65
66Value SsaStridePolicy::selectLt(Value a, Value b, Value t, Value e) const {
67 Value lt =
68 builder.createOrFold<arith::CmpIOp>(loc, arith::CmpIPredicate::ult, a, b);
69 return builder.createOrFold<arith::SelectOp>(loc, lt, t, e);
70}
71
72//===----------------------------------------------------------------------===//
73// Host-side guards.
74//===----------------------------------------------------------------------===//
75
76LogicalResult emitRuntimeCheck(OpBuilder &builder, Location loc, Value cond,
77 const Twine &message) {
78 if (auto c = getConstantIntValue(cond)) {
79 if (*c)
80 return success();
81 return emitError(loc) << message;
82 }
83 cf::AssertOp::create(builder, loc, cond, builder.getStringAttr(message));
84 return success();
85}
86
87void eraseDeadArith(Operation *root) {
88 root->walk<WalkOrder::PostOrder, ReverseIterator>([](Operation *op) {
89 if (isa<arith::ArithDialect>(op->getDialect()) && isOpTriviallyDead(op))
90 op->erase();
91 });
92}
93
94namespace {
95
96// Folding unsigned i64 arithmetic for building guard conditions: over
97// constant operands a condition folds to a constant, which emitRuntimeCheck
98// drops (true) or reports at compile time (false).
99// Arguments that build ops go in separate statements: C++ leaves their
100// evaluation order, and so the emitted op order, to the compiler.
101struct GuardBuilder {
102 OpBuilder &b;
103 Location loc;
104
105 Value cst(uint64_t c) {
106 return arith::ConstantOp::create(b, loc, b.getI64IntegerAttr((int64_t)c));
107 }
108 Value add(Value x, Value y) {
109 return b.createOrFold<arith::AddIOp>(loc, x, y);
110 }
111 Value sub(Value x, uint64_t c) {
112 return b.createOrFold<arith::SubIOp>(loc, x, cst(c));
113 }
114 Value mul(Value x, Value y) {
115 return b.createOrFold<arith::MulIOp>(loc, x, y);
116 }
117 Value both(Value x, Value y) {
118 return b.createOrFold<arith::AndIOp>(loc, x, y);
119 }
120 Value either(Value x, Value y) {
121 return b.createOrFold<arith::OrIOp>(loc, x, y);
122 }
123 Value cmp(arith::CmpIPredicate pred, Value x, Value y) {
124 return b.createOrFold<arith::CmpIOp>(loc, pred, x, y);
125 }
126 Value eq(Value x, Value y) { return cmp(arith::CmpIPredicate::eq, x, y); }
127 Value eq(Value x, uint64_t c) { return eq(x, cst(c)); }
128 Value ule(Value x, uint64_t c) {
129 return cmp(arith::CmpIPredicate::ule, x, cst(c));
130 }
131 // 1 <= x <= hi as one unsigned compare: x - 1 wraps 0 to UINT64_MAX.
132 Value inRange1(Value x, uint64_t hi) { return ule(sub(x, 1), hi - 1); }
133 Value multipleOf(Value x, uint64_t d) {
134 return eq(b.createOrFold<arith::RemUIOp>(loc, x, cst(d)), 0);
135 }
136 LogicalResult check(Value cond, const Twine &message) {
137 return emitRuntimeCheck(b, loc, cond, message);
138 }
139};
140
141} // namespace
142
143Value getAsI64(OpBuilder &builder, Location loc, OpFoldResult ofr) {
144 Type i64 = builder.getI64Type();
145 if (auto c = getConstantIntValue(ofr))
146 return Value(
147 arith::ConstantOp::create(builder, loc, IntegerAttr::get(i64, *c)));
148 Value v = cast<Value>(ofr);
149 if (v.getType().isIndex())
150 return Value(arith::IndexCastUIOp::create(builder, loc, i64, v));
151 unsigned bits = v.getType().getIntOrFloatBitWidth();
152 if (bits < 64)
153 return Value(arith::ExtUIOp::create(builder, loc, i64, v));
154 if (bits > 64) {
155 GuardBuilder g{builder, loc};
156 Value max = arith::ConstantOp::create(
157 builder, loc,
158 IntegerAttr::get(v.getType(), APInt::getMaxValue(64).zext(bits)));
159 if (failed(g.check(g.cmp(arith::CmpIPredicate::ule, v, max),
160 "a runtime DMA operand does not fit in 64 bits")))
161 return {};
162 return Value(arith::TruncIOp::create(builder, loc, i64, v));
163 }
164 return v;
165}
166
167//===----------------------------------------------------------------------===//
168// Layout helpers.
169//===----------------------------------------------------------------------===//
170
171Value getAsValue(OpBuilder &builder, Location loc, OpFoldResult ofr,
172 Type intType) {
173 if (auto constVal = getConstantIntValue(ofr))
174 return arith::ConstantOp::create(builder, loc,
175 IntegerAttr::get(intType, *constVal));
176 Value val = cast<Value>(ofr);
177 if (val.getType() != intType) {
178 if (val.getType().isIndex())
179 return arith::IndexCastUIOp::create(builder, loc, intType, val);
180 unsigned valBits = val.getType().getIntOrFloatBitWidth();
181 unsigned tgtBits = intType.getIntOrFloatBitWidth();
182 if (valBits > tgtBits)
183 val = arith::TruncIOp::create(builder, loc, intType, val);
184 else
185 val = arith::ExtUIOp::create(builder, loc, intType, val);
186 }
187 return val;
188}
189
190Value buildArgPlusValue(OpBuilder &builder, Location loc,
191 ArrayRef<OpFoldResult> elementOffsets,
192 ArrayRef<OpFoldResult> strides, int64_t elemWidthBytes,
193 int64_t baseByteOffset, uint32_t granuleBytes) {
194 auto i32ty = builder.getIntegerType(32);
195
196 // Fast path: everything constant -> fold to one arith.constant, matching the
197 // static lowering byte-for-byte.
198 bool allConst = llvm::all_of(elementOffsets,
199 [](OpFoldResult o) {
200 return getConstantIntValue(o).has_value();
201 }) &&
202 llvm::all_of(strides, [](OpFoldResult s) {
203 return getConstantIntValue(s).has_value();
204 });
205 if (allConst) {
206 int64_t bytes = baseByteOffset;
207 for (auto [o, s] : llvm::zip(elementOffsets, strides)) {
208 auto oc = getConstantIntValue(o);
209 auto sc = getConstantIntValue(s);
210 assert(oc && sc && "allConst already verified these are constant");
211 bytes += (*oc) * (*sc) * elemWidthBytes;
212 }
213 if (bytes > std::numeric_limits<uint32_t>::max() || bytes < 0)
214 return Value(arith::ConstantOp::create(
215 builder, loc, IntegerAttr::get(builder.getIntegerType(64), bytes)));
216 return Value(arith::ConstantOp::create(builder, loc,
217 IntegerAttr::get(i32ty, bytes)));
218 }
219
220 // Runtime path, in i64: sum(offset[i] * stride[i]) * elemWidthBytes + base.
221 // The range is the bounds guard's job (guardWithinHostBuffer); the patch
222 // itself only needs the granule alignment the static verifier checks.
223 GuardBuilder g{builder, loc};
224 Value acc = g.cst(0);
225 // A byte offset every term is a multiple of; when it is a whole granule the
226 // alignment guard is statically true.
227 uint64_t knownMultiple = (uint64_t)baseByteOffset;
228 for (auto [o, s] : llvm::zip(elementOffsets, strides)) {
229 Value ov = getAsI64(builder, loc, o);
230 Value sv = getAsI64(builder, loc, s);
231 if (!ov || !sv)
232 return {};
233 acc = g.add(acc, g.mul(ov, sv));
234 uint64_t factor = (uint64_t)elemWidthBytes;
235 for (OpFoldResult f : {o, s})
236 if (auto c = getConstantIntValue(f))
237 factor *= (uint64_t)*c;
238 knownMultiple = std::gcd(knownMultiple, factor);
239 }
240 acc = g.mul(acc, g.cst(elemWidthBytes));
241 if (baseByteOffset != 0)
242 acc = g.add(acc, g.cst(baseByteOffset));
243 if (granuleBytes > 1 && knownMultiple % granuleBytes != 0 &&
244 failed(g.check(g.eq(builder.createOrFold<arith::AndIOp>(
245 loc, acc, g.cst(granuleBytes - 1)),
246 0),
247 "a runtime DMA offset is not " + Twine(granuleBytes) +
248 "-byte aligned")))
249 return {};
250 return acc;
251}
252
254 OpBuilder &builder, Location loc, BaseMemRefType hostBufferType,
255 int64_t baseByteOffset, int64_t elemWidthBytes,
256 ArrayRef<OpFoldResult> offsets, ArrayRef<OpFoldResult> offsetStrides,
257 ArrayRef<OpFoldResult> sizes, ArrayRef<OpFoldResult> strides) {
258 // Elements addressable past the base offset. Capped at 2^32 so every term
259 // below is a product of two values under 2^32 and no sum can wrap 64 bits.
260 constexpr uint64_t kMaxElems = 1ULL << 32;
261 uint64_t cap = kMaxElems;
262 if (hostBufferType.hasStaticShape()) {
263 int64_t bytes = hostBufferType.getNumElements() *
264 (int64_t)hostBufferType.getElementTypeBitWidth() / 8;
265 cap = bytes > baseByteOffset
266 ? std::min<uint64_t>((bytes - baseByteOffset) / elemWidthBytes,
267 kMaxElems)
268 : 0;
269 }
270 if (cap == 0)
271 return emitError(loc) << "DMA transfer starts past the end of its host "
272 "buffer";
273
274 // The furthest element touched is sum(offset_k * offsetStride_k) +
275 // sum((size_i - 1) * stride_i); it must be below `cap`. A term is in range
276 // only if both factors are (a zero factor makes the other irrelevant), which
277 // also keeps the product from wrapping.
278 GuardBuilder g{builder, loc};
279 Value ok, sum = g.cst(0);
280 auto addTerm = [&](Value c, Value t) {
281 Value cZero = g.eq(c, 0);
282 Value tFits = g.either(cZero, g.ule(t, cap - 1));
283 Value tZero = g.eq(t, 0);
284 Value inRange = g.both(tFits, g.either(tZero, g.ule(c, cap - 1)));
285 ok = ok ? g.both(ok, inRange) : inRange;
286 sum = g.add(sum, g.mul(c, t));
287 };
288 for (auto [o, s] : llvm::zip(offsets, offsetStrides)) {
289 Value ov = getAsI64(builder, loc, o);
290 Value sv = getAsI64(builder, loc, s);
291 if (!ov || !sv)
292 return failure();
293 addTerm(ov, sv);
294 }
295 for (auto [sz, st] : llvm::zip(sizes, strides)) {
296 Value szv = getAsI64(builder, loc, sz);
297 Value stv = getAsI64(builder, loc, st);
298 if (!szv || !stv)
299 return failure();
300 addTerm(g.sub(szv, 1), stv);
301 }
302 Value cond = ok ? g.both(ok, g.ule(sum, cap - 1)) : g.ule(sum, cap - 1);
303 return g.check(cond, "a runtime DMA access runs past the end of its " +
304 Twine(cap) + "-element host buffer");
305}
306
307Value buildBdWord(OpBuilder &builder, Location loc,
308 ArrayRef<std::tuple<Value, uint32_t, uint32_t>> fields) {
309 auto i32ty = builder.getIntegerType(32);
310 Value result =
311 arith::ConstantOp::create(builder, loc, IntegerAttr::get(i32ty, 0));
312 for (auto &[val, mask, shift] : fields) {
313 Value masked = val;
314 if (mask != 0xFFFFFFFF) {
315 auto maskConst = arith::ConstantOp::create(
316 builder, loc, IntegerAttr::get(i32ty, (int64_t)(int32_t)mask));
317 masked = builder.createOrFold<arith::AndIOp>(loc, masked, maskConst);
318 }
319 if (shift > 0) {
320 auto shiftConst = arith::ConstantOp::create(
321 builder, loc, IntegerAttr::get(i32ty, shift));
322 masked = builder.createOrFold<arith::ShLIOp>(loc, masked, shiftConst);
323 }
324 result = builder.createOrFold<arith::OrIOp>(loc, result, masked);
325 }
326 return result;
327}
328
329Value getBdRegisterBase(OpBuilder &builder, Location loc,
330 const AIE::AIETargetModel &targetModel, int tileCol,
331 int tileRow, OpFoldResult bdId) {
332 auto i32ty = builder.getIntegerType(32);
333 if (auto c = getConstantIntValue(bdId))
334 return createConstantI32(builder, loc,
335 static_cast<uint32_t>(targetModel.getDmaBdAddress(
336 tileCol, tileRow, *c)));
337 // Runtime bd_id: base + bd_id * bdStride, with base/stride from the (linear)
338 // target-model address function.
339 uint64_t addrForId0 = targetModel.getDmaBdAddress(tileCol, tileRow, 0);
340 uint64_t bdStride =
341 targetModel.getDmaBdAddress(tileCol, tileRow, 1) - addrForId0;
342 Value bdIdVal = getAsValue(builder, loc, bdId, i32ty);
343 return arith::AddIOp::create(
344 builder, loc, createConstantI32(builder, loc, addrForId0),
345 arith::MulIOp::create(builder, loc, bdIdVal,
346 createConstantI32(builder, loc, bdStride)));
347}
348
349namespace {
350
351// Arrays are innermost-first: [d0, d1, d2, iter]. Every value is i32, ready to
352// pack into its BD field.
353struct EncodedBd {
354 Value hwS[4], hwT[4]; // granule-scaled wraps and -1-biased steps
355 Value bufLen; // buffer_length, in address-gen granules
356 Value iterSizeField; // iteration_size, zeroed for a pure repeat
357 Value repeatCount; // outer-dim repeat for the caller's queue push
358 bool isLinear = false; // d0/d1/d2 folded into buffer_length
359};
360
361LogicalResult encodeBdCommon(OpBuilder &builder, Location loc,
362 const AIE::AIETargetModel &tm, int tileCol,
363 int tileRow, ArrayRef<OpFoldResult> mixedSizes,
364 ArrayRef<OpFoldResult> mixedStrides,
365 uint64_t elemWidth, OpFoldResult lenElems,
366 EncodedBd &out) {
367 auto i32ty = builder.getIntegerType(32);
368 uint32_t gran = tm.getAddressGenGranularity();
369 SmallVector<OpFoldResult, 4> sizesRev(llvm::reverse(mixedSizes));
370 SmallVector<OpFoldResult, 4> stridesRev(llvm::reverse(mixedStrides));
371 auto cst = [&](OpFoldResult v) { return getConstantIntValue(v); };
372 auto constEq = [&](OpFoldResult v, int64_t c) {
373 auto k = cst(v);
374 return k && *k == c;
375 };
376 // A transfer with no data layout transformation at all (mirrors
377 // AIEX::isLinearTransfer). Canonicalization zeroes size-1 strides before this
378 // runs, hence the stride == 0 tests.
379 auto knownLinear = [&]() {
380 return constEq(sizesRev[1], 1) && constEq(sizesRev[2], 1) &&
381 constEq(stridesRev[0], 1) && constEq(stridesRev[1], 0) &&
382 constEq(stridesRev[2], 0);
383 };
384 // A contiguous row-major scan: each outer stride is the product of the inner
385 // sizes (mirrors AIEX::isContiguousTransfer). A runtime operand needed for
386 // the test falls back to ND mode.
387 auto knownContiguous = [&]() -> bool {
388 auto s0 = cst(stridesRev[0]);
389 if (!s0 || *s0 != 1)
390 return false;
391 auto sz0 = cst(sizesRev[0]);
392 auto d1sz = cst(sizesRev[1]);
393 auto d1st = cst(stridesRev[1]);
394 if (!d1sz || *d1sz != 1)
395 if (!sz0 || !d1st || *d1st != *sz0)
396 return false;
397 auto d2sz = cst(sizesRev[2]);
398 auto d2st = cst(stridesRev[2]);
399 if (!d2sz || *d2sz != 1) {
400 auto prod01 =
401 (sz0 && d1sz) ? std::optional<int64_t>(*sz0 * *d1sz) : std::nullopt;
402 if (!prod01 || !d2st || *d2st != *prod01)
403 return false;
404 }
405 return true;
406 };
407 // Folding d0/d1/d2 into buffer_length dodges the 10-bit d0 wrap, but only a
408 // shim NOC tile's 32-bit length field can absorb a merely-contiguous scan
409 // (17 bits on a mem tile, 14 on a core tile). The static path draws the line
410 // in the same place, in AIEDMATasksToNPU.cpp's treatAsLinear.
411 out.isLinear = knownLinear() ||
412 (tm.isShimNOCTile(tileCol, tileRow) && knownContiguous());
413 bool isLinear = out.isLinear;
414
415 Value inS[4], inT[4];
416 for (int i = 0; i < 4; i++) {
417 inS[i] = getAsI64(builder, loc, sizesRev[i]);
418 inT[i] = getAsI64(builder, loc, stridesRev[i]);
419 if (!inS[i] || !inT[i])
420 return failure();
421 }
422
423 // Host-side guards: every size and stride must land in its BD field, checked
424 // in the element domain before any scaling can wrap. Over constant operands
425 // they fold away (the verifiers already checked those) or fail here.
426 uint64_t ew = elemWidth;
427 uint64_t wrapMax = (1ULL << tm.getDmaBdWrapBits(tileCol, tileRow)) - 1;
428 uint64_t maxLen = tm.getDmaBdMaxLen(tileCol, tileRow);
429 uint64_t maxStride =
430 (1ULL << tm.getDmaBdStepBits(tileCol, tileRow)) * gran / ew;
431 uint64_t maxIterations = 1ULL << tm.getDmaBdIterBits(tileCol, tileRow);
432 uint64_t maxRepeats = tm.getMaxRepeatCount() + 1;
433 GuardBuilder g{builder, loc};
434 uint64_t sizeMax[4];
435 auto checkSize = [&](int i, uint64_t hi) {
436 sizeMax[i] = getConstantIntValue(sizesRev[i]).value_or(hi);
437 return g.check(g.inRange1(inS[i], hi),
438 (i == 3 ? Twine("a runtime DMA repeat count")
439 : "a runtime DMA d" + Twine(i) + " size") +
440 " must be in [1:" + Twine(hi) + "]");
441 };
442 if (failed(checkSize(0, (isLinear ? maxLen : wrapMax) * gran / ew)) ||
443 failed(checkSize(1, isLinear ? maxLen : wrapMax)) ||
444 failed(checkSize(2, maxLen)) || failed(checkSize(3, maxRepeats)))
445 return failure();
446 Value pureRepeat = g.eq(inT[3], 0);
447 if (failed(g.check(
448 g.either(pureRepeat, g.ule(g.sub(inS[3], 1), maxIterations - 1)),
449 "a runtime DMA iteration count must be in [1:" +
450 Twine(maxIterations) + "]")))
451 return failure();
452 for (int i = 0; i < 4; i++) {
453 if (i < 3 && (isLinear || (i == 0 && ew > gran)))
454 continue;
455 Value inRange =
456 i == 3 ? g.ule(inT[3], maxStride) : g.inRange1(inT[i], maxStride);
457 if (failed(g.check(g.either(g.ule(inS[i], 1), inRange),
458 (i == 3 ? Twine("a runtime DMA iteration stride")
459 : "a runtime DMA d" + Twine(i) + " stride") +
460 " must be in [" + Twine(i == 3 ? 0 : 1) + ":" +
461 Twine(maxStride) + "] when its size > 1")))
462 return failure();
463 }
464
465 // A non-word element's innermost stride must be 1 (see verifyStridesWraps).
466 if (ew != gran) {
467 Value d0Unit = g.eq(inT[0], 1);
468 if (failed(g.check(g.either(g.ule(inS[0], 1), d0Unit),
469 "a runtime DMA d0 stride must be 1 for " +
470 Twine(ew / 8) +
471 "-byte elements (the DMA moves whole " +
472 Twine(gran / 8) + "-byte granules)")))
473 return failure();
474 }
475
476 // A size or stride whose byte extent is not a whole number of granules is
477 // unrealizable (mirrors verifyStridesWraps).
478 int64_t divisor = bdGranuleDivisor(elemWidth, gran);
479 if (divisor > 1) {
480 auto checkMultiple = [&](Value v, const Twine &what) {
481 return g.check(g.multipleOf(v, divisor),
482 "a runtime DMA " + what + " must be a multiple of " +
483 Twine(divisor) + " elements (whole " +
484 Twine(gran / 8) + "-byte granules)");
485 };
486 if (failed(checkMultiple(inS[0], "d0 size")))
487 return failure();
488 for (int i = 1; i < 4; i++)
489 if (failed(checkMultiple(inT[i], i == 3 ? Twine("iteration stride")
490 : "d" + Twine(i) + " stride")))
491 return failure();
492 }
493
494 // Compute the hardware sizes/strides via the shared encoder.
495 Value hwS[4], hwT[4];
496 SsaStridePolicy policy(builder, loc);
497 encodeHardwareStridesWraps(policy, elemWidth, gran, inS, inT, hwS, hwT);
498
499 // buffer_length (word[0]) is the d0*d1*d2 extent in granules. Every size is
500 // already bounded by 2^32, so the d0*d1 product cannot wrap, and the final
501 // product only counts once d0*d1 is in range. The guard is skipped when the
502 // size bounds alone keep it in range. A dma_task's explicit `len` must agree
503 // with it.
504 Value d0d1 = g.mul(hwS[0], inS[1]);
505 Value bufLen = g.mul(d0d1, inS[2]);
506 uint64_t lenMax = llvm::SaturatingMultiply(
507 llvm::SaturatingMultiply(sizeMax[0] * ew / gran, sizeMax[1]), sizeMax[2]);
508 if (lenMax > maxLen) {
509 Value lenFits = g.ule(bufLen, maxLen);
510 if (bufLen != d0d1)
511 lenFits = g.both(g.ule(d0d1, maxLen), lenFits);
512 if (failed(g.check(lenFits, "a runtime DMA transfer exceeds the " +
513 Twine(maxLen) +
514 "-granule BD buffer_length")))
515 return failure();
516 }
517 if (lenElems) {
518 Value lenV = getAsI64(builder, loc, lenElems);
519 if (!lenV)
520 return failure();
521 if (failed(g.check(g.inRange1(lenV, maxLen * gran / ew),
522 "a runtime DMA length must be in [1:" +
523 Twine(maxLen * gran / ew) + "] elements")) ||
524 (divisor > 1 &&
525 failed(g.check(g.multipleOf(lenV, divisor),
526 "a runtime DMA length must be a multiple of " +
527 Twine(divisor) + " elements (whole " +
528 Twine(gran / 8) + "-byte granules)"))))
529 return failure();
530 Value lenGranules =
531 ew >= gran
532 ? g.mul(lenV, g.cst(ew / gran))
533 : builder.createOrFold<arith::DivUIOp>(loc, lenV, g.cst(gran / ew));
534 if (failed(g.check(g.eq(lenGranules, bufLen),
535 "a runtime DMA length must equal the d0*d1*d2 extent "
536 "of its dimensions")))
537 return failure();
538 }
539
540 auto asI32 = [&](Value v) {
541 return builder.createOrFold<arith::TruncIOp>(loc, i32ty, v);
542 };
543 for (int i = 0; i < 4; i++) {
544 out.hwS[i] = asI32(hwS[i]);
545 out.hwT[i] = asI32(hwT[i]);
546 }
547 out.bufLen = asI32(bufLen);
548
549 // iteration_size: a zero outer stride is a pure repeat (carried by
550 // repeat_count), so the field must be 0 like AIEDmaToNpu; gate it on the
551 // stride while leaving hwS[3] for repeatCount. hwT[3] already collapses to 0.
552 out.iterSizeField = asI32(policy.selectGT0(inT[3], hwS[3], policy.cst(0)));
553
554 // repeat_count for the queue push is the biased outer size (N > 1 ? N - 1 :
555 // 0), matching the static path. A constant folds so the static push_queue
556 // lowering can consume it; a runtime size yields the SSA hwS[3].
557 if (auto outerConst = getConstantIntValue(sizesRev[3])) {
558 int64_t r = *outerConst > 1 ? *outerConst - 1 : 0;
559 out.repeatCount =
560 arith::ConstantOp::create(builder, loc, IntegerAttr::get(i32ty, r));
561 } else {
562 out.repeatCount = out.hwS[3];
563 }
564 return success();
565}
566
567// dynamic-matches-static-words.mlir holds this to WriteBdToBlockWritePattern.
568void packBdWords(OpBuilder &builder, Location loc,
569 const AIE::AIETargetModel &tm, const AIE::DmaBdLayout &layout,
570 const BdTemplateFields &f, const EncodedBd &e,
571 uint32_t burstLength, uint32_t axcache,
572 SmallVectorImpl<Value> &wordsOut) {
573 using Field = std::tuple<Value, uint32_t, uint32_t>;
574 SmallVector<uint32_t, 8> constWords(layout.numWords, 0);
575 SmallVector<SmallVector<Field, 3>, 8> runtimeWords(layout.numWords);
576 auto set = [&](const AIE::DmaBdField &field, uint64_t value) {
577 if (field.exists())
578 constWords[field.word] |= field.place(value);
579 };
580 auto add = [&](const AIE::DmaBdField &field, Value value) {
581 if (field.exists())
582 runtimeWords[field.word].push_back({value, field.mask(), field.shift});
583 };
584
585 set(layout.enablePacket, f.enable_packet);
586 set(layout.packetType, f.packet_type);
587 set(layout.packetId, f.packet_id);
588 set(layout.outOfOrderId, f.out_of_order_id);
589 set(layout.iterationCurrent, f.iteration_current);
590 if (layout.burstLength.exists())
591 set(layout.burstLength, AIE::getShimBurstLengthEncoding(tm, burstLength));
592 set(layout.axcache, axcache);
593 if (f.next_bd_id_val)
594 add(layout.nextBd, f.next_bd_id_val);
595 else
596 set(layout.nextBd, f.next_bd_id);
597 set(layout.useNextBd, f.use_next_bd);
598 set(layout.validBd, 1);
599 if (f.lock_rel_val_val)
600 add(layout.lockRelValue, f.lock_rel_val_val);
601 else
602 set(layout.lockRelValue, f.lock_rel_val);
603 set(layout.lockRelId, f.lock_rel_id);
604 set(layout.lockAcqEnable, f.lock_acq_enable);
605 if (f.lock_acq_val_val)
606 add(layout.lockAcqValue, f.lock_acq_val_val);
607 else
608 set(layout.lockAcqValue, f.lock_acq_val);
609 set(layout.lockAcqId, f.lock_acq_id);
610
611 add(layout.bufferLength, e.bufLen);
612 if (!e.isLinear) {
613 add(layout.d0Size, e.hwS[0]);
614 add(layout.d0Stride, e.hwT[0]);
615 add(layout.d1Size, e.hwS[1]);
616 add(layout.d1Stride, e.hwT[1]);
617 add(layout.d2Stride, e.hwT[2]);
618 }
619 add(layout.iterationSize, e.iterSizeField);
620 add(layout.iterationStride, e.hwT[3]);
621
622 wordsOut.clear();
623 for (auto [constWord, fields] : llvm::zip(constWords, runtimeWords)) {
624 if (fields.empty()) {
625 wordsOut.push_back(createConstantI32(builder, loc, constWord));
626 continue;
627 }
628 auto [value, mask, shift] = fields.front();
629 if (constWord == 0 && fields.size() == 1 && mask == 0xFFFFFFFF &&
630 shift == 0) {
631 wordsOut.push_back(value);
632 continue;
633 }
634 Value word = buildBdWord(builder, loc, fields);
635 if (constWord != 0)
636 word = builder.createOrFold<arith::OrIOp>(
637 loc, createConstantI32(builder, loc, constWord), word);
638 wordsOut.push_back(word);
639 }
640}
641
642} // namespace
643
644LogicalResult
645buildBdWords(OpBuilder &builder, Location loc,
646 const AIE::AIETargetModel &targetModel, int tileCol, int tileRow,
647 const BdTemplateFields &f, ArrayRef<OpFoldResult> mixedSizes,
648 ArrayRef<OpFoldResult> mixedStrides, uint64_t elemWidth,
649 uint32_t burstLength, uint32_t axcache, OpFoldResult lenElems,
650 Value &repeatCountOut, SmallVectorImpl<Value> &wordsOut) {
651 EncodedBd e;
652 if (failed(encodeBdCommon(builder, loc, targetModel, tileCol, tileRow,
653 mixedSizes, mixedStrides, elemWidth, lenElems, e)))
654 return failure();
655 repeatCountOut = e.repeatCount;
656
657 const AIE::DmaBdLayout *layout = targetModel.getDmaBdLayout(tileCol, tileRow);
658 assert(layout && "buildBdWords called for a tile with no DMA BD layout "
659 "(rejected by the caller)");
660 packBdWords(builder, loc, targetModel, *layout, f, e, burstLength, axcache,
661 wordsOut);
662 return success();
663}
664
665} // namespace xilinx::AIEX
virtual const DmaBdLayout * getDmaBdLayout(AIETileType tileType) const
Return the register layout of a buffer descriptor on the given tile type, or null if the target has n...
virtual uint64_t getDmaBdAddress(int col, int row, uint32_t bd_id, int channel=-1, AIE::DMAChannelDir direction=AIE::DMAChannelDir::MM2S) const =0
Return the array address of the dma buffer descriptor for the given col, row, buffer descriptor id,...
int64_t bdGranuleDivisor(uint64_t elemWidth, uint32_t addressGranularity)
Definition BdLowering.h:58
mlir::Value buildBdWord(mlir::OpBuilder &builder, mlir::Location loc, llvm::ArrayRef< std::tuple< mlir::Value, uint32_t, uint32_t > > fields)
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::Value buildArgPlusValue(mlir::OpBuilder &builder, mlir::Location loc, llvm::ArrayRef< mlir::OpFoldResult > elementOffsets, llvm::ArrayRef< mlir::OpFoldResult > strides, int64_t elemWidthBytes, int64_t baseByteOffset, uint32_t granuleBytes)
mlir::Value getAsValue(mlir::OpBuilder &builder, mlir::Location loc, mlir::OpFoldResult ofr, mlir::Type intType)
mlir::LogicalResult guardWithinHostBuffer(mlir::OpBuilder &builder, mlir::Location loc, mlir::BaseMemRefType hostBufferType, int64_t baseByteOffset, int64_t elemWidthBytes, llvm::ArrayRef< mlir::OpFoldResult > offsets, llvm::ArrayRef< mlir::OpFoldResult > offsetStrides, llvm::ArrayRef< mlir::OpFoldResult > sizes, llvm::ArrayRef< mlir::OpFoldResult > strides)
mlir::LogicalResult buildBdWords(mlir::OpBuilder &builder, mlir::Location loc, const xilinx::AIE::AIETargetModel &targetModel, int tileCol, int tileRow, const BdTemplateFields &fields, llvm::ArrayRef< mlir::OpFoldResult > mixedSizes, llvm::ArrayRef< mlir::OpFoldResult > mixedStrides, uint64_t elemWidth, uint32_t burstLength, uint32_t axcache, mlir::OpFoldResult lenElems, mlir::Value &repeatCountOut, llvm::SmallVectorImpl< mlir::Value > &wordsOut)
mlir::Value getAsI64(mlir::OpBuilder &builder, mlir::Location loc, mlir::OpFoldResult ofr)
void eraseDeadArith(mlir::Operation *root)
mlir::Value getBdRegisterBase(mlir::OpBuilder &builder, mlir::Location loc, const xilinx::AIE::AIETargetModel &targetModel, int tileCol, int tileRow, mlir::OpFoldResult bdId)
mlir::LogicalResult emitRuntimeCheck(mlir::OpBuilder &builder, mlir::Location loc, mlir::Value cond, const llvm::Twine &message)
mlir::Value createConstantI32(mlir::OpBuilder &builder, mlir::Location loc, uint32_t value)
uint32_t getShimBurstLengthEncoding(const AIE::AIETargetModel &tm, uint32_t burstLength)
V selectGT1(V cond, V t, V e) const
V div(V v, int64_t c) const
V sub(V v, int64_t c) const
mlir::OpBuilder & builder
Definition BdLowering.h:211
V selectGT0(V cond, V t, V e) const
V mul(V v, int64_t c) const
V selectLt(V a, V b, V t, V e) const
The register layout of one tile type's DMA buffer descriptor.