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"
55 Value gt =
builder.createOrFold<arith::CmpIOp>(
loc, arith::CmpIPredicate::ugt,
57 return builder.createOrFold<arith::SelectOp>(
loc, gt, t, e);
61 Value gt =
builder.createOrFold<arith::CmpIOp>(
loc, arith::CmpIPredicate::ugt,
63 return builder.createOrFold<arith::SelectOp>(
loc, gt, t, e);
68 builder.createOrFold<arith::CmpIOp>(
loc, arith::CmpIPredicate::ult, a, b);
69 return builder.createOrFold<arith::SelectOp>(
loc, lt, t, e);
77 const Twine &message) {
78 if (
auto c = getConstantIntValue(cond)) {
81 return emitError(loc) << message;
83 cf::AssertOp::create(builder, loc, cond, builder.getStringAttr(message));
88 root->walk<WalkOrder::PostOrder, ReverseIterator>([](Operation *op) {
89 if (isa<arith::ArithDialect>(op->getDialect()) && isOpTriviallyDead(op))
105 Value cst(uint64_t c) {
106 return arith::ConstantOp::create(b, loc, b.getI64IntegerAttr((int64_t)c));
108 Value add(Value x, Value y) {
109 return b.createOrFold<arith::AddIOp>(loc, x, y);
111 Value sub(Value x, uint64_t c) {
112 return b.createOrFold<arith::SubIOp>(loc, x, cst(c));
114 Value mul(Value x, Value y) {
115 return b.createOrFold<arith::MulIOp>(loc, x, y);
117 Value both(Value x, Value y) {
118 return b.createOrFold<arith::AndIOp>(loc, x, y);
120 Value either(Value x, Value y) {
121 return b.createOrFold<arith::OrIOp>(loc, x, y);
123 Value cmp(arith::CmpIPredicate pred, Value x, Value y) {
124 return b.createOrFold<arith::CmpIOp>(loc, pred, x, y);
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));
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);
136 LogicalResult check(Value cond,
const Twine &message) {
143Value
getAsI64(OpBuilder &builder, Location loc, OpFoldResult ofr) {
144 Type i64 = builder.getI64Type();
145 if (
auto c = getConstantIntValue(ofr))
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();
153 return Value(arith::ExtUIOp::create(builder, loc, i64, v));
155 GuardBuilder g{builder, loc};
156 Value max = arith::ConstantOp::create(
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")))
162 return Value(arith::TruncIOp::create(builder, loc, i64, v));
171Value
getAsValue(OpBuilder &builder, Location loc, OpFoldResult ofr,
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);
185 val = arith::ExtUIOp::create(builder, loc, intType, val);
191 ArrayRef<OpFoldResult> elementOffsets,
192 ArrayRef<OpFoldResult> strides, int64_t elemWidthBytes,
193 int64_t baseByteOffset, uint32_t granuleBytes) {
194 auto i32ty = builder.getIntegerType(32);
198 bool allConst = llvm::all_of(elementOffsets,
200 return getConstantIntValue(o).has_value();
202 llvm::all_of(strides, [](OpFoldResult s) {
203 return getConstantIntValue(s).has_value();
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;
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)));
223 GuardBuilder g{builder, loc};
224 Value acc = g.cst(0);
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);
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);
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)),
247 "a runtime DMA offset is not " + Twine(granuleBytes) +
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) {
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,
271 return emitError(loc) <<
"DMA transfer starts past the end of its host "
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));
288 for (
auto [o, s] : llvm::zip(offsets, offsetStrides)) {
289 Value ov =
getAsI64(builder, loc, o);
290 Value sv =
getAsI64(builder, loc, s);
295 for (
auto [sz, st] : llvm::zip(sizes, strides)) {
296 Value szv =
getAsI64(builder, loc, sz);
297 Value stv =
getAsI64(builder, loc, st);
300 addTerm(g.sub(szv, 1), stv);
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");
308 ArrayRef<std::tuple<Value, uint32_t, uint32_t>> fields) {
309 auto i32ty = builder.getIntegerType(32);
311 arith::ConstantOp::create(builder, loc, IntegerAttr::get(i32ty, 0));
312 for (
auto &[val, mask, shift] : fields) {
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);
320 auto shiftConst = arith::ConstantOp::create(
321 builder, loc, IntegerAttr::get(i32ty, shift));
322 masked = builder.createOrFold<arith::ShLIOp>(loc, masked, shiftConst);
324 result = builder.createOrFold<arith::OrIOp>(loc, result, masked);
331 int tileRow, OpFoldResult bdId) {
332 auto i32ty = builder.getIntegerType(32);
333 if (
auto c = getConstantIntValue(bdId))
336 tileCol, tileRow, *c)));
339 uint64_t addrForId0 = targetModel.
getDmaBdAddress(tileCol, tileRow, 0);
342 Value bdIdVal =
getAsValue(builder, loc, bdId, i32ty);
343 return arith::AddIOp::create(
345 arith::MulIOp::create(builder, loc, bdIdVal,
354 Value hwS[4], hwT[4];
358 bool isLinear =
false;
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,
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) {
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);
387 auto knownContiguous = [&]() ->
bool {
388 auto s0 = cst(stridesRev[0]);
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)
397 auto d2sz = cst(sizesRev[2]);
398 auto d2st = cst(stridesRev[2]);
399 if (!d2sz || *d2sz != 1) {
401 (sz0 && d1sz) ? std::optional<int64_t>(*sz0 * *d1sz) : std::nullopt;
402 if (!prod01 || !d2st || *d2st != *prod01)
411 out.isLinear = knownLinear() ||
412 (tm.isShimNOCTile(tileCol, tileRow) && knownContiguous());
413 bool isLinear = out.isLinear;
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])
426 uint64_t ew = elemWidth;
427 uint64_t wrapMax = (1ULL << tm.getDmaBdWrapBits(tileCol, tileRow)) - 1;
428 uint64_t maxLen = tm.getDmaBdMaxLen(tileCol, tileRow);
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};
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) +
"]");
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)))
446 Value pureRepeat = g.eq(inT[3], 0);
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) +
"]")))
452 for (
int i = 0; i < 4; i++) {
453 if (i < 3 && (isLinear || (i == 0 && ew > gran)))
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")))
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 " +
471 "-byte elements (the DMA moves whole " +
472 Twine(gran / 8) +
"-byte granules)")))
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)");
486 if (failed(checkMultiple(inS[0],
"d0 size")))
488 for (
int i = 1; i < 4; i++)
489 if (failed(checkMultiple(inT[i], i == 3 ? Twine(
"iteration stride")
490 :
"d" + Twine(i) +
" stride")))
495 Value hwS[4], hwT[4];
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);
511 lenFits = g.both(g.ule(d0d1, maxLen), lenFits);
512 if (failed(g.check(lenFits,
"a runtime DMA transfer exceeds the " +
514 "-granule BD buffer_length")))
518 Value lenV =
getAsI64(builder, loc, lenElems);
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")) ||
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)"))))
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")))
540 auto asI32 = [&](Value v) {
541 return builder.createOrFold<arith::TruncIOp>(loc, i32ty, v);
543 for (
int i = 0; i < 4; i++) {
544 out.hwS[i] = asI32(hwS[i]);
545 out.hwT[i] = asI32(hwT[i]);
547 out.bufLen = asI32(bufLen);
552 out.iterSizeField = asI32(policy.selectGT0(inT[3], hwS[3], policy.cst(0)));
557 if (
auto outerConst = getConstantIntValue(sizesRev[3])) {
558 int64_t r = *outerConst > 1 ? *outerConst - 1 : 0;
560 arith::ConstantOp::create(builder, loc, IntegerAttr::get(i32ty, r));
562 out.repeatCount = out.hwS[3];
568void packBdWords(OpBuilder &builder, Location loc,
569 const AIE::AIETargetModel &tm,
const AIE::DmaBdLayout &layout,
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) {
578 constWords[field.word] |= field.place(value);
580 auto add = [&](
const AIE::DmaBdField &field, Value value) {
582 runtimeWords[field.word].push_back({value, field.mask(), field.shift});
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())
592 set(layout.axcache, axcache);
593 if (f.next_bd_id_val)
594 add(layout.nextBd, f.next_bd_id_val);
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);
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);
608 set(layout.lockAcqValue, f.lock_acq_val);
609 set(layout.lockAcqId, f.lock_acq_id);
611 add(layout.bufferLength, e.bufLen);
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]);
619 add(layout.iterationSize, e.iterSizeField);
620 add(layout.iterationStride, e.hwT[3]);
623 for (
auto [constWord, fields] : llvm::zip(constWords, runtimeWords)) {
624 if (fields.empty()) {
628 auto [value, mask, shift] = fields.front();
629 if (constWord == 0 && fields.size() == 1 && mask == 0xFFFFFFFF &&
631 wordsOut.push_back(value);
636 word = builder.createOrFold<arith::OrIOp>(
638 wordsOut.push_back(word);
648 ArrayRef<OpFoldResult> mixedStrides, uint64_t elemWidth,
649 uint32_t burstLength, uint32_t axcache, OpFoldResult lenElems,
650 Value &repeatCountOut, SmallVectorImpl<Value> &wordsOut) {
652 if (failed(encodeBdCommon(builder, loc, targetModel, tileCol, tileRow,
653 mixedSizes, mixedStrides, elemWidth, lenElems, e)))
655 repeatCountOut = e.repeatCount;
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,
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)
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])
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
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.