MLIR-AIE 1.4.6.dev
AIEDialect.cpp
Go to the documentation of this file.
1//===- AIEDialect.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
10
11#include "mlir/Dialect/Arith/IR/Arith.h"
12#include "mlir/Dialect/Func/IR/FuncOps.h"
13#include "mlir/Dialect/MemRef/IR/MemRef.h"
14#include "mlir/Dialect/Utils/StaticValueUtils.h"
15#include "mlir/IR/DialectImplementation.h"
16#include "mlir/IR/Matchers.h"
17#include "mlir/IR/OpDefinition.h"
18#include "mlir/IR/SymbolTable.h"
19#include "mlir/Interfaces/FoldInterfaces.h"
20#include "mlir/Interfaces/ViewLikeInterface.h"
21#include "mlir/Transforms/InliningUtils.h"
22#include "llvm/ADT/STLExtras.h"
23
24#include "llvm/ADT/SmallPtrSet.h"
25#include "llvm/ADT/SmallSet.h"
26#include "llvm/ADT/TypeSwitch.h"
27#include "llvm/Support/CheckedArithmetic.h"
28#include "llvm/Support/MathExtras.h"
29
30#include <limits>
31#include <map>
32#include <numeric>
33#include <optional>
34#include <string>
35#include <tuple>
36#include <utility>
37#include <vector>
38
39using namespace mlir;
40using namespace xilinx::AIE;
41
42// Add TableGen'erated dialect definitions (including constructor)
43// We implement the initialize() function further below
44#include "aie/Dialect/AIE/IR/AIEDialect.cpp.inc"
45
46#define GET_TYPEDEF_CLASSES
47#include "aie/Dialect/AIE/IR/AIETypes.cpp.inc"
48
49namespace {
50
51struct AIEInlinerInterface : DialectInlinerInterface {
52 using DialectInlinerInterface::DialectInlinerInterface;
53 // We don't have any special restrictions on what can be inlined into
54 // destination regions. Always allow it.
55 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
56 IRMapping &valueMapping) const final {
57 return true;
58 }
59
60 // Operations in aie dialect are always legal to inline since they are
61 // pure.
62 bool isLegalToInline(Operation *op, Region *, bool wouldBeCloned,
63 IRMapping &) const final {
64 return true;
65 }
66
67 // Handle the given inlined terminator by replacing it with a new operation
68 // as necessary. Required when the inlined region has more than one block.
69 void handleTerminator(Operation *op, Block *newDest) const final {}
70
71 // Handle the given inlined terminator by replacing it with a new operation
72 // as necessary. Required when the region has only one block.
73 void handleTerminator(Operation *op, ValueRange valuesToRepl) const final {}
74};
75
76struct AIEDialectFoldInterface : DialectFoldInterface {
77 using DialectFoldInterface::DialectFoldInterface;
78
79 /// Registered hook to check if the given region, which is attached to an
80 /// operation that is *not* isolated from above, should be used when
81 /// materializing constants.
82 bool shouldMaterializeInto(Region *region) const final {
83 // Materialize constants into the op that "owns" them rather than letting
84 // them hoist up to the enclosing IsolatedFromAbove aie.device:
85 // - aie.core bodies are outlined into standalone funcs, so their
86 // constants must stay local for the func to be self-contained.
87 // - aie.runtime_sequence bodies carry constant operands (e.g. the scalar
88 // fields of npu.* ops). Hoisting them to the device body lets CSE merge
89 // them with a core's identical constants, which would leave the core
90 // referencing a device-level value that is erased when the core is
91 // outlined.
92 // - Make sure SSA values for aie.use_lock operands in
93 // aie.mem/aie.memtile_dma/aie.shim_dma bodies do not get
94 // hoisted.
95 return isa<CoreOp, RuntimeSequenceOp, MemOp, MemTileDMAOp, ShimDMAOp>(
96 region->getParentOp());
97 }
98};
99
100} // end anonymous namespace
101
102void AIEDialect::initialize() {
103 addTypes<
104#define GET_TYPEDEF_LIST
105#include "aie/Dialect/AIE/IR/AIETypes.cpp.inc"
106 >();
107 addAttributes<
108#define GET_ATTRDEF_LIST
109#include "aie/Dialect/AIE/IR/AIEAttrs.cpp.inc"
110 >();
111 addOperations<
112#define GET_OP_LIST
113#include "aie/Dialect/AIE/IR/AIEOps.cpp.inc"
114 >();
115 addInterfaces<AIEInlinerInterface, AIEDialectFoldInterface>();
116}
117
118// Helper methods to retrieve the encoding associated to a burst length,
119// or to find the highest available burst length if the requested one is 0
120// (default value).
121
122static std::pair<uint32_t, uint32_t>
123getShimBurstLength(const xilinx::AIE::AIETargetModel &tm,
124 uint32_t burstLength) {
125
126 std::vector<std::pair<uint32_t, uint32_t>> bel =
128
129 // If we have the default burst length (no burst length was specified),
130 // use the highest one available on our target model
131 if (burstLength == 0) {
132 return *llvm::max_element(bel, [](auto pair1, auto pair2) {
133 return pair1.second < pair2.second;
134 });
135 }
136
137 // Note that if we are given a burst size, we are checking its existence in
138 // the pass verification already, so we can safely assume it exists.
139 return *llvm::find_if(bel, [=](auto p) { return p.second == burstLength; });
140}
141
143 uint32_t burstLength) {
144
145 return getShimBurstLength(tm, burstLength).second;
146}
147
149 uint32_t burstLength) {
150
151 return getShimBurstLength(tm, burstLength).first;
152}
153
154Operation *xilinx::AIE::lookupNamedOpIn(Operation *symbolTableOp,
155 StringAttr name) {
156 if (!symbolTableOp->hasTrait<mlir::OpTrait::SymbolTable>() ||
157 symbolTableOp->getRegion(0).empty()) {
158 return nullptr;
159 }
160 // One walk finds both kinds: a symbol op's name is its `sym_name`, and
161 // `getAttr` reads it whether it is inherent or discardable.
162 for (Operation &op : symbolTableOp->getRegion(0).front()) {
163 if (op.getAttrOfType<StringAttr>(mlir::SymbolTable::getSymbolAttrName()) ==
164 name) {
165 return &op;
166 }
167 }
168 return nullptr;
169}
170
172 : context(symbolTableOp->getContext()) {
173 if (!symbolTableOp->hasTrait<mlir::OpTrait::SymbolTable>() ||
174 symbolTableOp->getRegion(0).empty()) {
175 return;
176 }
177 for (Operation &op : symbolTableOp->getRegion(0).front()) {
178 if (auto name = op.getAttrOfType<StringAttr>(
179 mlir::SymbolTable::getSymbolAttrName())) {
180 ops.try_emplace(name, &op);
181 }
182 }
183}
184
185Operation *xilinx::AIE::lookupNamedOpIn(Operation *symbolTableOp,
186 StringRef name) {
187 return lookupNamedOpIn(symbolTableOp,
188 StringAttr::get(symbolTableOp->getContext(), name));
189}
190
191Operation *xilinx::AIE::lookupNamedOp(Operation *from, StringAttr name) {
192 Operation *symbolTableOp = mlir::SymbolTable::getNearestSymbolTable(from);
193 return symbolTableOp ? lookupNamedOpIn(symbolTableOp, name) : nullptr;
194}
195
196Operation *xilinx::AIE::lookupNamedOp(Operation *from, StringRef name) {
197 return lookupNamedOp(from, StringAttr::get(from->getContext(), name));
198}
199
200std::string xilinx::AIE::NamedOpTable::uniqueName(llvm::StringRef prefix,
201 unsigned &counter) const {
202 std::string name;
203 do {
204 name = (prefix + llvm::Twine(counter++)).str();
205 } while (ops.count(StringAttr::get(context, name)));
206 return name;
207}
208
209LogicalResult
210xilinx::AIE::myVerifyOffsetSizeAndStrideOp(OffsetSizeAndStrideOpInterface op) {
211 std::array<unsigned, 3> maxRanks = op.getArrayAttrMaxRanks();
212 // Count checks FIRST, matching mlir::detail::verifyOffsetSizeAndStrideOp.
213 // getMixedOffsets()/getMixedSizes() assert that the number of dynamic
214 // operands equals the number of kDynamic sentinels in the static list, so
215 // running the rank comparison before these validates nothing and aborts on
216 // malformed input instead of diagnosing it.
217 if (failed(verifyListOfOperandsOrIntegers(
218 op, "offset", maxRanks[0], op.getStaticOffsets(), op.getOffsets())))
219 return failure();
220 if (failed(verifyListOfOperandsOrIntegers(
221 op, "size", maxRanks[1], op.getStaticSizes(), op.getSizes())))
222 return failure();
223 if (failed(verifyListOfOperandsOrIntegers(
224 op, "stride", maxRanks[2], op.getStaticStrides(), op.getStrides())))
225 return failure();
226 // Each getMixed* call rebuilds its list, so read them once.
227 const size_t numMixedOffsets = op.getMixedOffsets().size();
228 const size_t numMixedSizes = op.getMixedSizes().size();
229 if (!(numMixedOffsets == 1 && maxRanks[0] == 1) && // NOLINT
230 numMixedOffsets != numMixedSizes)
231 return op->emitError(
232 "expected mixed offsets rank to match mixed sizes rank (")
233 << numMixedOffsets << " vs " << numMixedSizes
234 << ") so the rank of the result type is well-formed.";
235 for (int64_t offset : op.getStaticOffsets())
236 if (offset < 0 && !ShapedType::isDynamic(offset))
237 return op->emitError("expected offsets to be non-negative, but got ")
238 << offset;
239 for (int64_t size : op.getStaticSizes())
240 if (size < 0 && !ShapedType::isDynamic(size))
241 return op->emitError("expected sizes to be non-negative, but got ")
242 << size;
243
244 return success();
245}
246
247static VC1902TargetModel VC1902model;
248static VE2302TargetModel VE2302model;
249static VE2802TargetModel VE2802model;
250static VE3858TargetModel VE3858model;
251static VirtualizedNPU1TargetModel NPUmodel1col(1);
252static VirtualizedNPU1TargetModel NPUmodel2col(2);
253static VirtualizedNPU1TargetModel NPUmodel3col(3);
254static VirtualizedNPU1TargetModel NPUmodel4col(4);
255static NPU2TargetModel NPU2model;
256static VirtualizedNPU2TargetModel NPU2model1col(1);
257static VirtualizedNPU2TargetModel NPU2model2col(2);
258static VirtualizedNPU2TargetModel NPU2model3col(3);
259static VirtualizedNPU2TargetModel NPU2model4col(4);
260static VirtualizedNPU2TargetModel NPU2model5col(5);
261static VirtualizedNPU2TargetModel NPU2model6col(6);
262static VirtualizedNPU2TargetModel NPU2model7col(7);
263
264const AIETargetModel &xilinx::AIE::getTargetModel(Operation *op) {
265 if (auto t = dyn_cast<AIETarget>(op))
266 return t.getTargetModel();
267 if (auto t = op->getParentOfType<AIETarget>())
268 return t.getTargetModel();
269
270 // For backward compatibility, return a basic device model compatible with
271 // the VCK190
272 return VC1902model;
273}
274
276 return getTargetModel(a.getOperation())
277 .getSharedMemory({a.getCol(), a.getRow()}, {b.getCol(), b.getRow()});
278}
279
281 switch (device) {
282 case AIEDevice::xcvc1902:
283 return VC1902model;
284 case AIEDevice::xcve2302:
285 return VE2302model;
286 case AIEDevice::xcve2802:
287 return VE2802model;
288 case AIEDevice::npu1:
289 return NPUmodel4col;
290 case AIEDevice::npu1_1col:
291 return NPUmodel1col;
292 case AIEDevice::npu1_2col:
293 return NPUmodel2col;
294 case AIEDevice::npu1_3col:
295 return NPUmodel3col;
296 case AIEDevice::npu2:
297 return NPU2model;
298 case AIEDevice::npu2_1col:
299 return NPU2model1col;
300 case AIEDevice::npu2_2col:
301 return NPU2model2col;
302 case AIEDevice::npu2_3col:
303 return NPU2model3col;
304 case AIEDevice::npu2_4col:
305 return NPU2model4col;
306 case AIEDevice::npu2_5col:
307 return NPU2model5col;
308 case AIEDevice::npu2_6col:
309 return NPU2model6col;
310 case AIEDevice::npu2_7col:
311 return NPU2model7col;
312 case AIEDevice::xcve3858:
313 return VE3858model;
314 }
315 // No default: label above, so -Wswitch still reports a newly added device.
316 // This handles values that are not enumerators at all, which
317 // aieGetTargetModel admits by casting an unchecked uint32_t.
318 llvm::report_fatal_error("getTargetModel: unknown AIEDevice value " +
319 llvm::Twine(static_cast<uint32_t>(device)));
320}
321
322// Walk the operation hierarchy until we find a containing TileElement.
323// If no parent is a TileElement, then return null.
324static TileElement getParentTileElement(Operation *op) {
325 auto *parent = op->getParentOp();
326 while (!llvm::isa_and_nonnull<DeviceOp, ModuleOp>(parent)) {
327 if (auto element = llvm::dyn_cast<TileElement>(parent))
328 return element;
329 parent = parent->getParentOp();
330 }
331 return llvm::dyn_cast<TileElement>(parent);
332}
333
334// Returns the maximum index described by the input dimensions.
335static int64_t getDimsMaxIdx(ArrayRef<BDDimLayoutAttr> dims) {
336 int64_t maxIdx = 0;
337 for (BDDimLayoutAttr dim : dims) {
338 maxIdx += dim.getStride() * (dim.getSize() - 1);
339 }
340 return maxIdx;
341}
342
344 int64_t elements = 1;
345 for (BDDimLayoutAttr dim : inner)
346 elements *= dim.getSize();
347 return elements;
348}
349
351 ArrayRef<BDDimLayoutAttr> dims, ArrayRef<BDPadLayoutAttr> pads,
352 int64_t objectSize, function_ref<InFlightDiagnostic()> emitError,
353 StreamWindow &window) {
354 if (dims.size() < 2)
355 return emitError() << "a walk over a stream needs an outer dimension of "
356 "outputs and at least one inner dimension";
357 StreamWindow w;
358 w.objectSize = objectSize;
359 w.outputs = dims.front().getSize();
360 int64_t outerStride = dims.front().getStride();
361 if (outerStride % objectSize)
362 return emitError() << "a walk over a stream steps whole objects between "
363 "outputs, but its outer stride "
364 << outerStride << " is not a multiple of the "
365 << objectSize << "-element object";
366 w.advance = outerStride / objectSize;
367 w.inner.assign(dims.begin() + 1, dims.end());
368 w.rowDim = -1;
369 int64_t reach = 0;
370 for (auto [index, dim] : llvm::enumerate(w.inner)) {
371 if (dim.getSize() > 1 && dim.getStride() >= objectSize) {
372 if (w.rowDim >= 0 || dim.getStride() != objectSize)
373 return emitError() << "a walk over a stream may step one inner "
374 "dimension by exactly one "
375 << objectSize << "-element object";
376 w.rowDim = index;
377 continue;
378 }
379 reach += dim.getStride() * (dim.getSize() - 1);
380 }
381 if (reach >= objectSize)
382 return emitError() << "a walk over a stream reads " << reach + 1
383 << " elements of each object, past the " << objectSize
384 << "-element object";
385 w.span = w.rowDim >= 0 ? w.inner[w.rowDim].getSize() : 1;
386 w.top = w.bottom = 0;
387 for (auto [index, pad] : llvm::enumerate(pads)) {
388 if (index == 0) {
389 w.top = pad.getConstPadBefore();
390 w.bottom = pad.getConstPadAfter();
391 } else if (pad.getConstPadBefore() || pad.getConstPadAfter()) {
392 return emitError() << "a walk over a stream pads only its outer "
393 "dimension, with the objects before and after "
394 "the stream";
395 }
396 }
397 w.objects = (w.outputs - 1) * w.advance + w.span - w.top - w.bottom;
398 if (w.objects < w.span || w.top >= w.span || w.bottom >= w.span)
399 return emitError() << "a walk over a stream of " << w.objects
400 << " objects with " << w.top << " and " << w.bottom
401 << " padding objects leaves an output without data";
402 window = w;
403 return success();
404}
405
406SmallVector<StreamWindowStep>
408 SmallVector<StreamWindowStep> steps;
409 int64_t acquired = 0, released = 0;
410 for (int64_t j = 0; j < w.outputs; j++) {
411 int64_t start = j * w.advance - w.top;
412 int64_t newest = std::max(
413 acquired, start + std::clamp<int64_t>(w.objects - start, 0, w.span));
414 int64_t next =
415 j + 1 < w.outputs
416 ? std::clamp<int64_t>((j + 1) * w.advance - w.top, 0, newest)
417 : w.objects;
418 next = std::max(released, next);
419 steps.push_back({j * w.advance, newest - acquired, next - released});
420 acquired = newest;
421 released = next;
422 }
423 return steps;
424}
425
426namespace {
427
428struct UsesAreAccessible {
429 static LogicalResult verifyTrait(Operation *op) {
430 auto thisElement = cast<TileElement>(op);
431
432 // Skip accessibility checks for logical tiles as we cannot tell until tile
433 // is placed
434 if (!isa<TileOp>(thisElement.getTile().getDefiningOp()))
435 return success();
436
437 auto thisID = thisElement.getTileID();
438 auto users = op->getResult(0).getUsers();
439 const auto &targetModel = getTargetModel(op);
440 for (auto *user : users) {
441 // AIE.useLock may be used in a device to set the lock's default value
442 // Allow in a toplevel module for backward compatibility
443 if (llvm::isa_and_nonnull<DeviceOp, ModuleOp>(user->getParentOp())) {
444 continue;
445 }
446 // If any parent or the user itself prescribe that accessibility checks be
447 // skipped, skip the check for that user.
448 if (user->getParentWithTrait<SkipAccessibilityCheckTrait>() ||
449 user->hasTrait<SkipAccessibilityCheckTrait>()) {
450 continue;
451 }
452 TileElement element = llvm::dyn_cast<TileElement>(user);
453 if (!element) {
454 element = getParentTileElement(user);
455 }
456 if (!element) {
457 // This should probably be caught elsewhere as well.
458 return op->emitOpError("is accessed outside of a tile")
459 .attachNote(user->getLoc())
460 << "user";
461 }
462 auto tileID = element.getTileID();
463 if (!targetModel.isLegalMemAffinity(tileID.col, tileID.row, thisID.col,
464 thisID.row)) {
465 return (op->emitOpError("in Column ")
466 << thisID.col << " and Row " << thisID.row
467 << " is accessed from an unreachable tile in Column "
468 << tileID.col << " and Row " << tileID.row)
469 .attachNote(user->getLoc())
470 << "user";
471 }
472 }
473 return success();
474 }
475};
476
477} // namespace
478
479// Check that the operation only contains terminators in
480// TerminatorOpTypes.
481template <typename... TerminatorOpTypes>
483 static LogicalResult verifyTrait(Operation *op) {
484 for (auto &region : op->getRegions()) {
485 for (auto &block : region) {
486 if (!block.empty()) {
487 if (Operation *operation = &block.back();
488 !llvm::isa_and_nonnull<TerminatorOpTypes...>(operation))
489 return operation->emitOpError("is not an allowed terminator")
490 .attachNote(op->getLoc())
491 .append("in this context: ");
492 }
493 }
494 }
495 return success();
496 }
497};
498
499// Check that the given DMA-like op (e.g. MemOp, ShimDMAOp)
500// has valid BDs.
501template <typename ConcreteType>
502LogicalResult HasValidBDs<ConcreteType>::verifyTrait(Operation *op) {
503 auto element = cast<ConcreteType>(op);
504 const auto &targetModel = getTargetModel(op);
505
506 TileLike tile = element.getTileLike();
507 if (!tile)
508 return op->emitOpError("tile must implement TileLike interface");
509
510 int bdMax = targetModel.getNumBDs(tile.getTileType());
511
512 int bdNum = 0;
513 for (auto &block : element.getBody()) {
514 auto bdOps = llvm::to_vector_of<DMABDOp>(block.template getOps<DMABDOp>());
515
516 // Skip entry/end block
517 if (bdOps.empty())
518 continue;
519
520 // Check BD count limit
521 if (bdNum >= bdMax) {
522 return (op->emitOpError("has more than ") << bdMax << " blocks")
523 .attachNote(bdOps.front().getLoc())
524 .append("no space for this BD");
525 }
526 bdNum++;
527
528 // Check exactly 1 DMABDOp per BD block
529 if (bdOps.size() != 1) {
530 return (op->emitOpError("BD block must have exactly one DMABDOp, found ")
531 << bdOps.size())
532 .attachNote(block.front().getLoc())
533 .append("in this BD block");
534 }
535
536 // Check at most 2 UseLockOps per BD block (1 acquire, 1 release)
537 auto useLockOps =
538 llvm::to_vector_of<UseLockOp>(block.template getOps<UseLockOp>());
539 int acquireCount = 0;
540 int releaseCount = 0;
541 for (auto useLock : useLockOps) {
542 if (useLock.acquire() || useLock.acquireGE())
543 acquireCount++;
544 else if (useLock.release())
545 releaseCount++;
546 }
547
548 if (acquireCount > 1) {
549 return (op->emitOpError(
550 "BD block must have at most one acquire UseLockOp, found ")
551 << acquireCount)
552 .attachNote(block.front().getLoc())
553 .append("in this BD block");
554 }
555 if (releaseCount > 1) {
556 return (op->emitOpError(
557 "BD block must have at most one release UseLockOp, found ")
558 << releaseCount)
559 .attachNote(block.front().getLoc())
560 .append("in this BD block");
561 }
562 }
563 return success();
564}
565
566// Check that the given DMA-like op (e.g. MemOp, ShimDMAOp)
567// has valid DMA channels.
568template <typename ConcreteType>
570 DenseSet<DMAChannel> inputChannels;
571 DenseSet<DMAChannel> outputChannels;
572 DenseSet<std::pair<DMAChannelDir, StringAttr>> namedChannels;
573 auto element = cast<ConcreteType>(op);
574 Region &body = element.getBody();
575 if (body.empty())
576 return op->emitOpError("should have non-empty body");
577 for (auto &bodyOp : body.getOps()) {
578 // check for duplicate DMA channels within the same ShimDMAOp
579 if (auto dmaStart = dyn_cast<DMAStartOp>(bodyOp)) {
580 if (FlatSymbolRefAttr endpoint = dmaStart.getEndpoint()) {
581 namedChannels.insert({dmaStart.getChannelDir(), endpoint.getAttr()});
582 continue;
583 }
584 DMAChannel dmaChan = {dmaStart.getChannelDir(),
585 dmaStart.getChannelIndex()};
586 // check if number of input and output channels is more than available
587 // hardware
588 if (dmaChan.direction == DMAChannelDir::S2MM)
589 inputChannels.insert(dmaChan);
590 else
591 outputChannels.insert(dmaChan);
592 }
593 }
594
595 TileLike tile = element.getTileLike();
596 if (!tile)
597 return op->emitOpError("tile must implement TileLike interface");
598
599 auto named = [&](DMAChannelDir dir) -> size_t {
600 return llvm::count_if(namedChannels,
601 [&](auto channel) { return channel.first == dir; });
602 };
603 if (inputChannels.size() + named(DMAChannelDir::S2MM) >
604 tile.getNumSourceConnections(WireBundle::DMA))
605 return op->emitOpError(
606 "uses more input channels than available on this tile");
607
608 if (outputChannels.size() + named(DMAChannelDir::MM2S) >
609 tile.getNumDestConnections(WireBundle::DMA))
610 return op->emitOpError(
611 "uses more output channels than available on this tile");
612 return success();
613}
614
615//===----------------------------------------------------------------------===//
616// ObjectFifoTransportAttr
617//===----------------------------------------------------------------------===//
618
619LogicalResult
620ObjectFifoTransportAttr::verify(function_ref<InFlightDiagnostic()> emitError,
621 ObjectFifoTransportMode mode,
622 PacketInfoAttr packet, bool out_of_order) {
623 // A packet header rides a stream-switch connection the DMAs drive, so only
624 // the paths that may use the DMAs can honour one.
625 if (packet && mode == ObjectFifoTransportMode::SharedMem)
626 return emitError() << "`packet` belongs to a dma or auto transport, not "
627 << stringifyObjectFifoTransportMode(mode);
628 if (out_of_order && !packet)
629 return emitError() << "`out_of_order` requires a packet transport";
630 return success();
631}
632
633//===----------------------------------------------------------------------===//
634// ObjectFifoEndPortAttr
635//===----------------------------------------------------------------------===//
636
637LogicalResult
638ObjectFifoEndPortAttr::verify(function_ref<InFlightDiagnostic()> emitError,
639 WireBundle bundle,
640 std::optional<uint32_t> channel) {
641 if (bundle != WireBundle::DMA && bundle != WireBundle::Core)
642 return emitError() << "an objectFifo end is driven by a DMA channel or a "
643 "Core stream port, not "
644 << stringifyWireBundle(bundle);
645 if (bundle == WireBundle::Core) {
646 if (!channel)
647 return emitError() << "a Core end needs its stream port";
648 if (*channel > 1)
649 return emitError() << "a Core end's stream port is 0 or 1, not "
650 << *channel;
651 }
652 return success();
653}
654
655//===----------------------------------------------------------------------===//
656// ObjectFifoCreateOp
657//===----------------------------------------------------------------------===//
658
659LogicalResult ObjectFifoCreateOp::verify() {
660 for (Type fifoType : {Type(getElemType()), getConsumerElemTypeOrDefault()})
661 if (MemRefType memref = cast<AIEObjectFifoType>(fifoType).getElementType();
662 !memref.hasStaticShape())
663 return emitOpError("type should be static shaped memref, but got ")
664 << memref;
665
666 // MLIR carries an attribute it does not know about rather than rejecting it,
667 // so IR written against the switches these replaced would keep parsing and
668 // quietly take the default path instead. Name them to fail that loudly.
669 static constexpr std::pair<llvm::StringLiteral, llvm::StringLiteral>
670 replaced[] = {
671 {"out_of_order", "transport = #aie.transport<dma, packet = "
672 "#aie.packet_info<>, out_of_order = true>"},
673 {"via_DMA", "transport = #aie.transport<dma>"},
674 {"packet", "transport = #aie.transport<dma, packet = "
675 "#aie.packet_info<>>"},
676 {"packet_id", "transport = #aie.transport<dma, packet = "
677 "#aie.packet_info<pkt_id = 3>>"},
678 {"aie_stream", "prod_port = #aie.end_port<Core : 0> or cons_ports "
679 "= [#aie.end_port<Core : 0>]"},
680 {"aie_stream_port", "prod_port = #aie.end_port<Core : 0> or "
681 "cons_ports = [#aie.end_port<Core : 0>]"},
682 {"prod_dma_channel", "prod_port = #aie.end_port<DMA : 1>"},
683 {"cons_dma_channels", "cons_ports = [#aie.end_port<DMA : 1>]"}};
684 for (auto [gone, replacement] : replaced)
685 if ((*this)->hasAttr(gone))
686 return emitOpError("`")
687 << gone << "` has been replaced; write " << replacement;
688
689 if (isa<ArrayAttr>(getElemNumber())) {
690 if (size_t numDepths = dyn_cast<ArrayAttr>(getElemNumber()).size();
691 numDepths != getConsumerTiles().size() + 1) // +1 for producer depth
692 return emitOpError("does not have enough depths specified for producer "
693 "and for each consumer.");
694 }
695
696 if (ArrayAttr consPorts = getConsPortsAttr();
697 consPorts && consPorts.size() != getConsumerTiles().size())
698 return emitOpError("`cons_ports` has ")
699 << consPorts.size() << " entries for " << getConsumerTiles().size()
700 << " consumers";
701
702 // Helper to get tile interface from Value
703 auto getTileLikeFromValue = [](Value v) -> TileLike {
704 return llvm::dyn_cast<TileLike>(v.getDefiningOp());
705 };
706
707 TileLike producerTile = getTileLikeFromValue(getProducerTile());
708 if (!producerTile)
709 return emitError("producer tile must implement TileLike interface");
710
711 // data layout transformations on shim tiles are handled by runtime operations
712 if (producerTile.isShimTile() && !getDimensionsToStream().empty()) {
713 return emitError(
714 "`dimensionsToStream` data layout transformations are not supported "
715 "on shim tile producers");
716 }
717 for (auto consTileVal : getConsumerTiles()) {
718 TileLike consTile = getTileLikeFromValue(consTileVal);
719 if (!consTile)
720 return emitError("consumer tile must implement TileLike interface");
721 if (consTile.isShimTile() &&
722 !getDimensionsFromStream(consTileVal).empty()) {
723 return emitError(
724 "`dimensionsFromStreamPerConsumer` data layout transformations are "
725 "not supported on shim tile consumers");
726 }
727 }
728
729 if (getRepeatCount().has_value()) {
730 if (producerTile.isShimTile())
731 return emitError("`repeat_count` unavailable for shim tiles");
732 }
733
734 if (getPadValue() != 0) {
735 if (!getPadDimensions().has_value())
736 return emitError("`padValue` requires `padDimensions`");
737 if (!getTargetModel(getOperation()).isMemTilePadValueSupported())
738 return emitError("`padValue` requires the CONSTANT_PAD_VALUE register, "
739 "unavailable on this target");
740 }
741
742 if (usesStream()) {
743 if (getConsumerTiles().size() > 1)
744 return emitError("a Core stream port end can only be used in 1-to-1 "
745 "object FIFOs");
746
747 if (forcesSharedMemory())
748 return emitError("a Core stream port end sends over the stream, not "
749 "through shared memory");
750
751 if (streamsFromProducer()) {
752 if (producerTile.isShimTile() || producerTile.isMemTile())
753 return emitError(
754 "a Core stream port end is not available for shim and mem tiles");
755
756 if (getRepeatCount().has_value())
757 return emitError("`repeat_count` unavailable on stream end");
758
759 if (getInitValues().has_value())
760 return emitError("`init_values` unavailable on stream end");
761
762 if (getIterCount().has_value())
763 return emitError("`iter_count` unavailable on stream end");
764
765 if (!getDimensionsToStream().empty())
766 return emitError("`dimensionsToStream` data layout transformations are "
767 "unavailable on stream end");
768 }
769
770 if (streamsToConsumer(0)) {
771 TileLike consTile = getTileLikeFromValue(getConsumerTiles()[0]);
772 if (consTile && (consTile.isShimTile() || consTile.isMemTile()))
773 return emitError(
774 "a Core stream port end is not available for shim and mem tiles");
775 }
776
777 if (!getDimensionsFromStreamPerConsumer()[0].empty())
778 return emitError("`dimensionsFromStreamPerConsumer` data layout "
779 "transformations are unavailable on stream end");
780 }
781
782 if (getInitValues().has_value()) {
783 if (producerTile.isShimTile())
784 return emitError("`init_values` unavailable for shim tiles");
785 }
786
787 if (auto initValues = getInitValues()) {
788 if ((int)initValues->size() != size())
789 return emitError("`init_values` does not initialize all objects");
790 }
791
792 if (auto iterCountAttr = getIterCount()) {
793 int iterCount = *iterCountAttr;
794 if (iterCount < 1 || iterCount > 256)
795 return emitError("`iter_count` must be between 1 and 256");
796 }
797
798 if (auto consumerElemType = getConsumerElemType()) {
799 auto consType = llvm::dyn_cast<AIEObjectFifoType>(*consumerElemType);
800 if (!consType)
801 return emitError("consumer element type must be an "
802 "!aie.objectfifo<memref<...>> type");
803 auto prodType = llvm::cast<AIEObjectFifoType>(getElemType());
804 auto prodMemref = prodType.getElementType();
805 auto consMemref = consType.getElementType();
806 if (prodMemref.getElementType() != consMemref.getElementType())
807 return emitError("producer and consumer must have the same scalar "
808 "element type, but got ")
809 << prodMemref.getElementType() << " vs "
810 << consMemref.getElementType();
811 int64_t prodSize = prodMemref.getNumElements();
812 int64_t consSize = consMemref.getNumElements();
813 if (consSize <= 0)
814 return emitError("consumer element count must be positive");
815 if (prodSize % consSize != 0)
816 return emitError("producer element size (")
817 << prodSize << ") must be an integer multiple of consumer "
818 << "element size (" << consSize << ")";
819 }
820
821 return success();
822}
823
824TileOp ObjectFifoCreateOp::getProducerTileOp() {
825 return cast<TileOp>(getProducerTile().getDefiningOp());
826}
827
828//===----------------------------------------------------------------------===//
829// ObjectFifoPoolOp
830//===----------------------------------------------------------------------===//
831
832namespace {
833/// Resolve a list of symbol names to the ops they name, skipping any that are
834/// not yet declared.
835template <typename OpTy>
836SmallVector<OpTy> lookupAll(Operation *from, std::optional<ArrayAttr> names) {
837 SmallVector<OpTy> ops;
838 if (names) {
839 for (auto name : names->getAsRange<FlatSymbolRefAttr>()) {
840 if (auto op =
841 dyn_cast_or_null<OpTy>(lookupNamedOp(from, name.getAttr()))) {
842 ops.push_back(op);
843 }
844 }
845 }
846 return ops;
847}
848
849} // namespace
850
851TileLike ObjectFifoPoolOp::getTileLike() {
852 return dyn_cast<TileLike>(getTile().getDefiningOp());
853}
854
855int64_t ObjectFifoPoolOp::getObjectSize() {
856 return llvm::cast<MemRefType>(getElemType()).getNumElements();
857}
858
859int64_t ObjectFifoPoolOp::getObjectsPerBuffer() {
860 if (!getContiguous())
861 return 1;
862 int64_t objects = getDepth();
863 if (auto zeros = getZeroObjects())
864 objects += (*zeros)[0] + (*zeros)[1];
865 return objects;
866}
867
868LogicalResult ObjectFifoPoolOp::verify() {
869 auto &target = (*this)->getParentOfType<DeviceOp>().getTargetModel();
870 bool semaphoreLocks = target.hasProperty(AIETargetModel::UsesSemaphoreLocks);
871
872 // A binary lock travels with the buffer it guards; a semaphore lock counts
873 // objects for a whole segment.
874 if (auto locks = getLocks()) {
875 if (semaphoreLocks) {
876 return emitOpError("'locks' names binary locks, which this device has "
877 "no use for");
878 }
879 if (static_cast<int64_t>(locks->size()) != getDepth()) {
880 return emitOpError("expects one lock per buffer");
881 }
882 }
883
884 if (auto zeros = getZeroObjects()) {
885 if (!getContiguous() || zeros->size() != 2 ||
886 llvm::any_of(*zeros, [](int32_t count) { return count < 0; }))
887 return emitOpError("'zeroObjects' expects [before, after] counts on a "
888 "contiguous pool");
889 }
890
891 if (auto buffers = getBuffers()) {
892 if (getContiguous() && buffers->size() != 1) {
893 return emitOpError("expects one buffer holding its objects side by side");
894 }
895 if (!getContiguous() &&
896 static_cast<int64_t>(buffers->size()) != getDepth()) {
897 return emitOpError("expects 'depth' buffers");
898 }
899 }
900
901 if (auto initValues = getInitValues()) {
902 if (static_cast<int64_t>(initValues->size()) > getDepth()) {
903 return emitOpError("has more 'init_values' than 'depth' objects");
904 }
905 }
906
907 std::vector<ObjectFifoSegmentOp> segments = getSegmentOps();
908 if (segments.empty()) {
909 return emitOpError("expects at least one segment");
910 }
911
912 int64_t previous = -1;
913 int64_t covered = 0;
914 for (ObjectFifoSegmentOp segment : segments) {
915 if (static_cast<int64_t>(segment.getOffset()) <= previous) {
916 return emitOpError("segments must be ordered by increasing offset");
917 }
918 if (static_cast<int64_t>(segment.getOffset()) != covered) {
919 return segment.emitOpError("leaves a gap or overlaps the segment before "
920 "it; segments must tile the object");
921 }
922 previous = segment.getOffset();
923 covered = segment.getOffset() + segment.getSize();
924 if (!semaphoreLocks &&
925 (segment.getProduceLock() || segment.getConsumeLock())) {
926 return emitOpError("segment locks are counting locks, which this "
927 "device has no use for");
928 }
929 }
930
931 if (covered != getObjectSize()) {
932 return emitOpError("segments cover ")
933 << covered << " of " << getObjectSize() << " elements";
934 }
935
936 if (segments.size() > 1 && !semaphoreLocks) {
937 return emitOpError(
938 "multi-segment pools are unsupported on binary lock architectures");
939 }
940
941 return success();
942}
943
944int64_t ObjectFifoPoolOp::getObjectSizeInBytes() {
945 MemRefType elemType = getElemType();
946 DataLayout layout = DataLayout::closest(*this);
947 return elemType.getNumElements() *
948 layout.getTypeSize(elemType.getElementType());
949}
950
951std::vector<ObjectFifoSegmentOp> ObjectFifoPoolOp::getSegmentOps() {
952 auto ops = getSegments().getOps<ObjectFifoSegmentOp>();
953 return {ops.begin(), ops.end()};
954}
955
956Value BufferOp::getBufferTile() { return getTile(); }
957
958SmallVector<BufferLike> ObjectFifoPoolOp::getBufferOps() {
959 // Buffers and locks live beside the pool, not in the symbol table it opens
960 // for its segments.
961 return lookupAll<BufferLike>((*this)->getParentOfType<DeviceOp>(),
962 getBuffers());
963}
964
965SmallVector<LockOp> ObjectFifoPoolOp::getLockOps() {
966 auto device = (*this)->getParentOfType<DeviceOp>();
967 SmallVector<LockOp> locks = lookupAll<LockOp>(device, getLocks());
968 for (ObjectFifoSegmentOp segment : getSegmentOps()) {
969 for (std::optional<FlatSymbolRefAttr> name :
970 {segment.getProduceLockAttr(), segment.getConsumeLockAttr()}) {
971 if (name && *name) {
972 if (auto lock = lookupNamedOp<LockOp>(device, name->getAttr())) {
973 locks.push_back(lock);
974 }
975 }
976 }
977 }
978 return locks;
979}
980
981StringRef ObjectFifoPoolOp::getBaseName() {
982 StringRef name = getSymName();
983 return name.consume_back("__pool") ? name : getSymName();
984}
985
986//===----------------------------------------------------------------------===//
987// ObjectFifo endpoints
988//===----------------------------------------------------------------------===//
989
990namespace {
991
992LogicalResult verifyEndpoint(Operation *op, ObjectFifoPoolOp pool,
993 std::optional<ArrayAttr> segments) {
994 std::vector<ObjectFifoSegmentOp> all = pool.getSegmentOps();
995 if (!segments) {
996 if (all.size() > 1) {
997 return op->emitOpError(
998 "must list segments explicitly for a multi-segment pool");
999 }
1000 return success();
1001 }
1002 if (segments->empty()) {
1003 return op->emitOpError("expects at least one segment");
1004 }
1005
1006 int64_t previous = -1;
1007 for (auto name : segments->getAsRange<FlatSymbolRefAttr>()) {
1008 auto segment = SymbolTable::lookupNearestSymbolFrom<ObjectFifoSegmentOp>(
1009 pool, name.getAttr());
1010 if (!segment) {
1011 return op->emitOpError("references undefined segment '")
1012 << name.getValue() << "' in pool '" << pool.getSymName() << "'";
1013 }
1014 // The endpoint's memref is one run of the object, and `dimensions` pairs
1015 // with this list positionally.
1016 if (static_cast<int64_t>(segment.getOffset()) <= previous) {
1017 return op->emitOpError("segments must be named in increasing offset "
1018 "order");
1019 }
1020 previous = segment.getOffset();
1021 }
1022 return success();
1023}
1024
1025ObjectFifoPoolOp lookupPool(Operation *op, StringRef name) {
1026 auto device = op->getParentOfType<DeviceOp>();
1027 return SymbolTable::lookupNearestSymbolFrom<ObjectFifoPoolOp>(
1028 device, StringAttr::get(op->getContext(), name));
1029}
1030
1031} // namespace
1032
1033/// The subset of `pool`'s segments `selected` names. Omission selects the
1034/// pool's only segment.
1035static std::vector<ObjectFifoSegmentOp>
1036selectSegments(ObjectFifoPoolOp pool, std::optional<ArrayAttr> selected) {
1037 if (!pool) {
1038 return {};
1039 }
1040 std::vector<ObjectFifoSegmentOp> all = pool.getSegmentOps();
1041 if (!selected) {
1042 return all.empty() ? std::vector<ObjectFifoSegmentOp>{}
1043 : std::vector<ObjectFifoSegmentOp>{all.front()};
1044 }
1045 std::vector<ObjectFifoSegmentOp> chosen;
1046 for (auto name : selected->getAsRange<FlatSymbolRefAttr>()) {
1047 if (auto segment =
1048 SymbolTable::lookupNearestSymbolFrom<ObjectFifoSegmentOp>(
1049 pool, name.getAttr())) {
1050 chosen.push_back(segment);
1051 }
1052 }
1053 return chosen;
1054}
1055
1056TileLike ObjectFifoCoreEndpointOp::getTileLike() {
1057 return dyn_cast<TileLike>(getTile().getDefiningOp());
1058}
1059
1060ObjectFifoPoolOp ObjectFifoCoreEndpointOp::getPoolOp() {
1061 return lookupPool(*this, getPool());
1062}
1063
1064std::vector<ObjectFifoSegmentOp>
1065ObjectFifoCoreEndpointOp::getSelectedSegments() {
1066 return selectSegments(getPoolOp(), getSegments());
1067}
1068
1069LogicalResult ObjectFifoCoreEndpointOp::verify() {
1070 ObjectFifoPoolOp pool = getPoolOp();
1071 if (!pool) {
1072 return emitOpError("references undefined pool '") << getPool() << "'";
1073 }
1074 if (failed(verifyEndpoint(*this, pool, getSegments()))) {
1075 return failure();
1076 }
1077
1078 if (auto push = getCorePushAttr()) {
1079 if (drains())
1080 return emitOpError("core_push requires a filling core endpoint");
1081 if (auto values = pool.getInitValues(); values && !values->empty())
1082 return emitOpError("core_push does not support initialized objects");
1083 if (push.getBdTags().size() != static_cast<size_t>(pool.getDepth()))
1084 return emitOpError("core_push requires one BD tag per pool buffer");
1085 if (llvm::any_of(push.getBdTags(), [](int32_t tag) { return tag < 0; }))
1086 return emitOpError("core_push requires nonnegative BD tags");
1087 }
1088
1089 // The core sees one memref, so the segments it selects have to be a single
1090 // run of the object.
1091 int64_t next = -1;
1092 for (ObjectFifoSegmentOp segment : getSelectedSegments()) {
1093 if (next >= 0 && segment.getOffset() != next) {
1094 return emitOpError("a core endpoint's segments must be contiguous");
1095 }
1096 next = segment.getOffset() + segment.getSize();
1097 }
1098 return success();
1099}
1100
1101std::pair<int64_t, int64_t> ObjectFifoCoreEndpointOp::getExtent() {
1102 std::vector<ObjectFifoSegmentOp> selected = getSelectedSegments();
1103 if (selected.empty()) {
1104 return {0, 0};
1105 }
1106 int64_t offset = selected.front().getOffset();
1107 int64_t end = selected.back().getOffset() + selected.back().getSize();
1108 return {offset, end - offset};
1109}
1110
1111MemRefType ObjectFifoCoreEndpointOp::getAccessType() {
1112 MemRefType elemType = getPoolOp().getElemType();
1113 auto [offset, size] = getExtent();
1114 if (offset == 0 && size == elemType.getNumElements()) {
1115 return elemType;
1116 }
1117 return cast<MemRefType>(memref::SubViewOp::inferRankReducedResultType(
1118 {size}, elemType, {offset}, {size}, {1}));
1119}
1120
1121TileLike ObjectFifoDmaEndpointOp::getTileLike() {
1122 return dyn_cast<TileLike>(getTile().getDefiningOp());
1123}
1124
1125ObjectFifoPoolOp ObjectFifoDmaEndpointOp::getPoolOp() {
1126 return lookupPool(*this, getPool());
1127}
1128
1129std::vector<ObjectFifoSegmentOp>
1130ObjectFifoDmaEndpointOp::getSelectedSegments() {
1131 return selectSegments(getPoolOp(), getSegments());
1132}
1133
1134int64_t ObjectFifoDmaEndpointOp::getNumDescriptors() {
1135 ObjectFifoPoolOp pool = getPoolOp();
1136 if (!pool)
1137 return 0;
1138 if (pool.getContiguous()) {
1139 // Taking k turns a pass, k descriptors walk the objects, each k apart.
1140 if (!walksStream()) {
1141 SmallVector<ObjectFifoDmaEndpointOp> ends = turnEndpoints(*this);
1142 if (ends.empty())
1143 return 1;
1144 ArrayRef<Attribute> turns = getTurnsAttr().getValue();
1145 SmallVector<std::pair<int64_t, int64_t>> counts;
1146 for (ObjectFifoDmaEndpointOp end : ends)
1147 counts.push_back(
1148 {llvm::count(turns, FlatSymbolRefAttr::get(end.getSymNameAttr())),
1149 end.getPoolOp().getContiguous() ? 0 : end.getNumDescriptors()});
1150 return llvm::count(turns, FlatSymbolRefAttr::get(getSymNameAttr())) *
1151 turnPasses(counts);
1152 }
1153 StreamWindow window;
1154 return succeeded(getStreamWindow(window)) ? window.outputs : 0;
1155 }
1156 return pool.getDepth() * getSelectedSegments().size();
1157}
1158
1159bool ObjectFifoDmaEndpointOp::walksStream() {
1160 ObjectFifoPoolOp pool = getPoolOp();
1161 auto dims = getDimensions();
1162 return pool && pool.getContiguous() && drains() && dims && !dims->empty() &&
1163 !(*dims)[0].getValue().empty();
1164}
1165
1166LogicalResult ObjectFifoDmaEndpointOp::getStreamWindow(StreamWindow &window) {
1167 auto dims = getDimensions();
1168 if (!dims || dims->empty())
1169 return emitOpError("walks no stream: it has no dimensions");
1170 ArrayRef<BDPadLayoutAttr> pads;
1171 if (auto padding = getPadDimensions(); padding && !padding->empty())
1172 pads = (*padding)[0].getValue();
1173 return inferStreamWindow((*dims)[0].getValue(), pads,
1174 getPoolOp().getObjectSize(),
1175 [&] { return emitOpError(); }, window);
1176}
1177
1178int64_t ObjectFifoDmaEndpointOp::getRepeat() {
1179 ObjectFifoPoolOp pool = getPoolOp();
1180 // The filling end covers the batch in one acquire instead.
1181 return pool && drains() ? pool.getRepeatCount().value_or(1) : 1;
1182}
1183
1184bool ObjectFifoDmaEndpointOp::repeatsInHardware() {
1185 return getRepeat() > 1 &&
1186 (getReleaseStrategy() == ObjectFifoRelease::PushQueue ||
1187 (getNumDescriptors() == 1 && !getIterCount()));
1188}
1189
1190int64_t ObjectFifoDmaEndpointOp::getDispatchPasses() {
1191 ObjectFifoPoolOp pool = getPoolOp();
1192 std::optional<ArrayAttr> turns = getDispatch();
1193 if (!pool || !turns || turns->empty())
1194 return 1;
1195 int64_t depth = std::max<int64_t>(pool.getDepth(), 1);
1196 return std::lcm(depth, static_cast<int64_t>(turns->size())) / depth;
1197}
1198
1199SmallVector<ObjectFifoDmaEndpointOp>
1200xilinx::AIE::turnEndpoints(ObjectFifoDmaEndpointOp endpoint) {
1201 SmallVector<ObjectFifoDmaEndpointOp> ends;
1202 if (std::optional<ArrayAttr> turns = endpoint.getTurns())
1203 for (auto name : turns->getAsRange<FlatSymbolRefAttr>())
1204 if (auto end =
1205 SymbolTable::lookupNearestSymbolFrom<ObjectFifoDmaEndpointOp>(
1206 endpoint, name);
1207 end && !llvm::is_contained(ends, end))
1208 ends.push_back(end);
1209 return ends;
1210}
1211
1212int64_t xilinx::AIE::turnPasses(ArrayRef<std::pair<int64_t, int64_t>> ends) {
1213 int64_t passes = 1;
1214 for (auto [appearances, count] : ends)
1215 if (count > 0)
1216 passes = std::lcm(passes, count / std::gcd(count, appearances));
1217 return passes;
1218}
1219
1220int64_t ObjectFifoDmaEndpointOp::getNumBDs() {
1221 SmallVector<ObjectFifoDmaEndpointOp> ends = turnEndpoints(*this);
1222 if (ends.empty())
1223 return getNumDescriptors() * (repeatsInHardware() ? 1 : getRepeat()) *
1224 getDispatchPasses();
1225 if (ends.front() != *this)
1226 return 0;
1227 ArrayRef<Attribute> turns = getTurnsAttr().getValue();
1228 SmallVector<std::pair<int64_t, int64_t>> counts;
1229 for (ObjectFifoDmaEndpointOp end : ends)
1230 counts.push_back(
1231 {llvm::count(turns, FlatSymbolRefAttr::get(end.getSymNameAttr())),
1232 end.getPoolOp().getContiguous() ? 0 : end.getNumDescriptors()});
1233 return turnPasses(counts) * static_cast<int64_t>(turns.size());
1234}
1235
1236DMAChannelDir ObjectFifoDmaEndpointOp::getRouteDirection() {
1237 return drains() ? DMAChannelDir::MM2S : DMAChannelDir::S2MM;
1238}
1239
1240WireBundle ObjectFifoDmaEndpointOp::getRouteBundle() { return WireBundle::DMA; }
1241
1242std::optional<int> ObjectFifoDmaEndpointOp::getRouteChannel() {
1243 return getChannelIndex();
1244}
1245
1246void ObjectFifoDmaEndpointOp::setRouteChannel(int channel) {
1247 setChannelIndex(channel);
1248}
1249
1250LogicalResult ObjectFifoDmaEndpointOp::verify() {
1251 ObjectFifoPoolOp pool = getPoolOp();
1252 if (!pool) {
1253 return emitOpError("references undefined pool '") << getPool() << "'";
1254 }
1255
1256 if (failed(verifyEndpoint(*this, pool, getSegments()))) {
1257 return failure();
1258 }
1259 if (failed(verifyAssignedPacket(*this, getPacketAttr()))) {
1260 return failure();
1261 }
1262
1263 if (getReleaseStrategy() == ObjectFifoRelease::PushQueue) {
1264 if (!drains() || !getTileLike().isCoreTile() ||
1266 return emitOpError("push_queue requires a sending DMA on a core tile "
1267 "with core-issued task support");
1268 if (auto values = pool.getInitValues(); values && !values->empty())
1269 return emitOpError("push_queue does not support initialized objects");
1270 if (getRepeat() - 1 > getTargetModel(*this).getMaxRepeatCount())
1271 return emitOpError(
1272 "push_queue repeat_count exceeds the hardware task limit");
1273 if (getOutOfOrder() && getRepeat() > 1)
1274 return emitOpError(
1275 "push_queue cannot repeat out-of-order packets: "
1276 "each repetition may need a different destination BD ID");
1277 }
1278
1279 if (std::optional<ArrayAttr> turns = getDispatch()) {
1280 if (!drains())
1281 return emitOpError("only a draining endpoint can dispatch");
1282 if (turns->empty())
1283 return emitOpError("dispatch names no destination");
1284 if (getPacketAttr())
1285 return emitOpError("a dispatching endpoint carries a header per turn in "
1286 "`dispatchPackets`, not one `packet`");
1287 if (pool.getRepeatCount().value_or(1) > 1)
1288 return emitOpError("cannot dispatch a pool that repeats its objects");
1289 if (std::optional<int32_t> iterCount = getIterCount();
1290 iterCount && *iterCount % std::max<int64_t>(getDispatchPasses(), 1))
1291 return emitOpError("iterCount ")
1292 << *iterCount << " is not a whole number of dispatch rounds ("
1293 << getDispatchPasses() << " passes over the pool)";
1294 }
1295 if (std::optional<ArrayAttr> headers = getDispatchPackets()) {
1296 std::optional<ArrayAttr> turns = getDispatch();
1297 if (!turns || turns->size() != headers->size())
1298 return emitOpError("dispatchPackets needs one header per dispatch turn");
1299 for (auto header : headers->getAsRange<PacketInfoAttr>())
1300 if (failed(verifyAssignedPacket(*this, header, "dispatchPackets")))
1301 return failure();
1302 }
1303 if (std::optional<ArrayAttr> turns = getTurns()) {
1304 if (!llvm::is_contained(*turns, FlatSymbolRefAttr::get(getSymNameAttr())))
1305 return emitOpError("takes no turn of its own in `turns`");
1306 for (auto name : turns->getAsRange<FlatSymbolRefAttr>())
1307 if (!SymbolTable::lookupNearestSymbolFrom<ObjectFifoDmaEndpointOp>(*this,
1308 name))
1309 return emitOpError("turns names ")
1310 << name << ", which is no objectfifo.dma_endpoint";
1311 if (getDispatch() || getPacketAttr() || getIterCount() || getRepeat() > 1)
1312 return emitOpError("taking turns on a channel, it cannot also dispatch, "
1313 "carry a header, stop after iterCount or repeat");
1314 for (ObjectFifoDmaEndpointOp end : turnEndpoints(*this))
1315 if (end.getTile() != getTile() || end.getRole() != getRole() ||
1316 end.getTurnsAttr() != getTurnsAttr() ||
1317 end.getChannelIndexAttr() != getChannelIndexAttr())
1318 return emitOpError("takes turns with ")
1319 << end.getSymNameAttr()
1320 << ", which is not on this tile and channel with the same turns";
1321 if (pool.getContiguous() && !walksStream() &&
1322 pool.getDepth() % getNumDescriptors())
1323 return emitOpError("walks a contiguous pool of ")
1324 << pool.getDepth() << " objects in " << getNumDescriptors()
1325 << " turns a pass, which do not divide them";
1326 }
1327
1328 std::optional<ArrayAttr> segmentNames = getSegments();
1329 size_t selectedCount = segmentNames ? segmentNames->size() : 1;
1330 auto dimensions = getDimensions();
1331 if (dimensions && dimensions->size() != selectedCount) {
1332 return emitOpError("dimensions has ")
1333 << dimensions->size() << " entries for " << selectedCount
1334 << " selected segments";
1335 }
1336 auto padding = getPadDimensions();
1337 if (padding && padding->size() != selectedCount) {
1338 return emitOpError("padDimensions has ")
1339 << padding->size() << " entries for " << selectedCount
1340 << " selected segments";
1341 }
1342 if (padding && !drains()) {
1343 return emitOpError("padDimensions is only valid on a draining endpoint");
1344 }
1345 if (getPadValue() != 0 &&
1346 (!padding || llvm::none_of(*padding, [](BDPadLayoutArrayAttr entry) {
1347 return !entry.empty();
1348 }))) {
1349 return emitOpError("nonzero padValue requires a non-empty padDimensions "
1350 "entry");
1351 }
1352
1353 // A walk over the stream reads across the pool's objects, outputs apart.
1354 StreamWindow window;
1355 if (walksStream())
1356 return getStreamWindow(window);
1357
1358 std::vector<ObjectFifoSegmentOp> selected = getSelectedSegments();
1359 for (size_t position = 0; position < selectedCount; ++position) {
1360 BDDimLayoutArrayAttr dims =
1361 dimensions ? (*dimensions)[position] : BDDimLayoutArrayAttr();
1362 BDPadLayoutArrayAttr pads =
1363 padding ? (*padding)[position] : BDPadLayoutArrayAttr();
1364 if (pads && !pads.empty() && (!dims || dims.empty())) {
1365 return emitOpError("padDimensions entry ")
1366 << position << " requires dimensions for the same segment";
1367 }
1368 if (dims && pads && !pads.empty() && dims.size() != pads.size()) {
1369 return emitOpError("dimensions and padDimensions entry ")
1370 << position << " have different ranks";
1371 }
1372 if (dims && !dims.empty() &&
1373 getDimsMaxIdx(dims) >= selected[position].getSize()) {
1374 return emitOpError("dimensions entry ")
1375 << position << " exceeds selected segment '"
1376 << selected[position].getSymName() << "' of size "
1377 << selected[position].getSize();
1378 }
1379 }
1380 return success();
1381}
1382
1383TileLike RouteEndpointOp::getTileLike() {
1384 return dyn_cast<TileLike>(getTile().getDefiningOp());
1385}
1386
1387DMAChannelDir RouteEndpointOp::getRouteDirection() {
1388 // A flow runs from its source, so an end named there sends and every other
1389 // end receives.
1390 auto device = getOperation()->getParentOfType<DeviceOp>();
1391 StringRef name = getSymName();
1392 bool named = false;
1393 for (auto flow : device.getOps<RouteOp>()) {
1394 if (flow.namesAsSource(name)) {
1395 return DMAChannelDir::MM2S;
1396 }
1397 named |= flow.namesAsDestination(name);
1398 }
1399 assert(named && "endpoint is named by no flow, so it has no direction to "
1400 "read; --aie-objectfifo-verify rejects this");
1401 (void)named;
1402 return DMAChannelDir::S2MM;
1403}
1404
1405WireBundle RouteEndpointOp::getRouteBundle() { return getBundle(); }
1406
1407std::optional<int> RouteEndpointOp::getRouteChannel() {
1408 return getChannelIndex();
1409}
1410
1411void RouteEndpointOp::setRouteChannel(int channel) { setChannelIndex(channel); }
1412
1413LogicalResult RouteEndpointOp::verify() {
1414 TileLike tile = getTileLike();
1415 if (!tile) {
1416 return emitOpError("tile operand is not an aie.tile or aie.logical_tile");
1417 }
1418 if (failed(verifyAssignedPacket(*this, getPacketAttr()))) {
1419 return failure();
1420 }
1421 // getRouteDirection() reads this end's direction off the flows naming it, so
1422 // they must all name it on the same side. Packet flows can share the channel;
1423 // a circuit owns it.
1424 StringRef name = getSymName();
1425 SmallVector<RouteOp> sources, dests;
1426 for (auto flow : (*this)->getParentOfType<DeviceOp>().getOps<RouteOp>()) {
1427 if (flow.namesAsSource(name))
1428 sources.push_back(flow);
1429 if (flow.namesAsDestination(name))
1430 dests.push_back(flow);
1431 }
1432 if (!sources.empty() && !dests.empty()) {
1433 return emitOpError("is named as both a source and a destination, so its "
1434 "direction is ambiguous");
1435 }
1436 ArrayRef<RouteOp> sharers = sources.empty() ? dests : sources;
1437 if (sharers.size() > 1) {
1438 if (llvm::any_of(sharers, [](RouteOp flow) { return !flow.getPacket(); }))
1439 return emitOpError("drives one channel, which a circuit flow owns, but "
1440 "it is named ")
1441 << sharers.size() << " times";
1442 llvm::SmallDenseSet<int> ids;
1443 for (RouteOp flow : sources) {
1444 std::optional<uint16_t> id = flow.getPacketAttr().getPktId();
1445 if (!id)
1446 return emitOpError("sends several packet flows, so each must pin its "
1447 "pkt_id for the BDs to state it");
1448 if (!ids.insert(*id).second)
1449 return emitOpError("sends several packet flows, two of which pin the "
1450 "same pkt_id ")
1451 << static_cast<int>(*id) << "; each needs its own";
1452 }
1453 // The port keeps or drops every header it receives.
1454 if (llvm::any_of(dests, [&](RouteOp flow) {
1455 return flow.getKeepPktHeader() != dests[0].getKeepPktHeader();
1456 }))
1457 return emitOpError("receives several packet flows, so they must agree "
1458 "on keep_pkt_header");
1459 }
1460 switch (getBundle()) {
1461 case WireBundle::DMA:
1462 // The runtime drives a shim's DMA, and a DMA program or the runtime
1463 // sequence any other tile's; either names this op for the channel.
1464 return success();
1465 case WireBundle::PLIO:
1466 if (!tile.isShimTile()) {
1467 return emitOpError("a PLIO end is on a shim");
1468 }
1469 return success();
1470 case WireBundle::Core:
1471 return success();
1472 default:
1473 return emitOpError("bundle must be DMA, PLIO or Core");
1474 }
1475}
1476
1477//===----------------------------------------------------------------------===//
1478// RouteOp
1479//===----------------------------------------------------------------------===//
1480
1481// A route's sources print bare when there is one, bracketed when there are
1482// several; both forms parse.
1483static ParseResult parseRouteSources(OpAsmParser &parser, ArrayAttr &sources) {
1484 SmallVector<Attribute> names;
1485 auto parseOne = [&]() -> ParseResult {
1486 FlatSymbolRefAttr name;
1487 if (parser.parseAttribute(name))
1488 return failure();
1489 names.push_back(name);
1490 return success();
1491 };
1492 if (succeeded(parser.parseOptionalLSquare())) {
1493 if (failed(parser.parseOptionalRSquare()) &&
1494 (parser.parseCommaSeparatedList(parseOne) || parser.parseRSquare()))
1495 return failure();
1496 } else if (parseOne()) {
1497 return failure();
1498 }
1499 sources = parser.getBuilder().getArrayAttr(names);
1500 return success();
1501}
1502
1503static void printRouteSources(OpAsmPrinter &p, Operation *, ArrayAttr sources) {
1504 if (sources.size() == 1) {
1505 p.printAttribute(sources[0]);
1506 return;
1507 }
1508 p << '[';
1509 llvm::interleaveComma(sources, p,
1510 [&](Attribute name) { p.printAttribute(name); });
1511 p << ']';
1512}
1513
1514LogicalResult RouteOp::verify() {
1515 if (getSources().empty()) {
1516 return emitOpError("expects at least one source");
1517 }
1518 if (getDestinations().empty()) {
1519 return emitOpError("expects at least one destination");
1520 }
1521
1522 if (getKeepPktHeader() && !getPacket()) {
1523 return emitOpError("keep_pkt_header is only meaningful on a packet flow");
1524 }
1525
1526 auto device = (*this)->getParentOfType<DeviceOp>();
1527 auto lookup = [&](StringRef name) {
1528 return dyn_cast_or_null<RouteEndpoint>(SymbolTable::lookupNearestSymbolFrom(
1529 device, StringAttr::get(getContext(), name)));
1530 };
1531
1532 llvm::SmallDenseSet<StringRef> seen;
1533 for (StringRef source : getSourceNames()) {
1534 if (!lookup(source)) {
1535 return emitOpError("source '") << source << "' is not an endpoint";
1536 }
1537 if (!seen.insert(source).second) {
1538 return emitOpError("names '@") << source << "' as a source twice";
1539 }
1540 }
1541 for (StringRef destination : getDestinationNames()) {
1542 if (!lookup(destination)) {
1543 return emitOpError("destination '")
1544 << destination << "' is not an endpoint";
1545 }
1546 if (!seen.insert(destination).second) {
1547 return emitOpError("names '@")
1548 << destination
1549 << "' more than once; an end is a source or a "
1550 "destination, and only one of each";
1551 }
1552 }
1553
1554 // A circuit joins one slave port to its master ports, so only packets can
1555 // share a destination channel between sources.
1556 if (isFanIn() && !getPacket()) {
1557 return emitOpError("has several sources, so it needs a `packet` header; "
1558 "a circuit cannot merge streams");
1559 }
1560 return success();
1561}
1562
1563BDDimLayoutArrayAttr
1564ObjectFifoCreateOp::getDimensionsFromStream(Value consumerTile) {
1565 int dimsIndex = 0;
1566 for (auto cons : getConsumerTiles()) {
1567 if (cons == consumerTile)
1568 break;
1569 dimsIndex++;
1570 }
1571 return getDimensionsFromStreamPerConsumer()[dimsIndex];
1572}
1573
1575 OpAsmParser &parser, OpAsmParser::UnresolvedOperand &operand,
1576 BDDimLayoutArrayAttr &dimensions) {
1577 std::vector<BDDimLayoutAttr> emptyDims = {};
1578 if (parser.parseOperand(operand))
1579 return failure();
1580 if (succeeded(parser.parseOptionalKeyword("dimensionsToStream"))) {
1581 if (parser.parseCustomAttributeWithFallback<BDDimLayoutArrayAttr>(
1582 dimensions)) {
1583 return failure();
1584 }
1585 } else {
1586 dimensions =
1587 BDDimLayoutArrayAttr::get(parser.getContext(), ArrayRef(emptyDims));
1588 }
1589 return success();
1590}
1591
1592void xilinx::AIE::printObjectFifoProducerTile(OpAsmPrinter &printer,
1593 Operation *op, Value operand,
1594 BDDimLayoutArrayAttr dimensions) {
1595 printer << operand;
1596 if (!dimensions.empty()) {
1597 printer << " dimensionsToStream ";
1598 printer.printStrippedAttrOrType(dimensions);
1599 }
1600}
1601
1602ParseResult xilinx::AIE::parseDMAStartChannel(OpAsmParser &parser,
1603 Attribute &channel) {
1604 StringAttr name;
1605 if (succeeded(parser.parseOptionalSymbolName(name))) {
1606 channel = FlatSymbolRefAttr::get(name);
1607 return success();
1608 }
1609 int32_t index;
1610 if (parser.parseInteger(index))
1611 return failure();
1612 channel = parser.getBuilder().getI32IntegerAttr(index);
1613 return success();
1614}
1615
1616LogicalResult xilinx::AIE::verifyDMAChannelsResolved(DeviceOp device) {
1617 bool resolved = true;
1618 device.walkOutsideRuntimeSequences<DMAStartOp>([&](DMAStartOp start) {
1619 if (FlatSymbolRefAttr endpoint = start.getEndpoint()) {
1620 start.emitOpError() << "names route endpoint " << endpoint
1621 << " in place of a channel index; run "
1622 "--aie-objectfifo-allocate to assign one";
1623 resolved = false;
1624 }
1625 });
1626 return success(resolved);
1627}
1628
1629void xilinx::AIE::printDMAStartChannel(OpAsmPrinter &printer, Operation *op,
1630 Attribute channel) {
1631 if (auto index = dyn_cast<IntegerAttr>(channel))
1632 printer << index.getInt();
1633 else
1634 printer.printAttributeWithoutType(channel);
1635}
1636
1637ParseResult
1639 ObjectFifoPortAttr &port,
1640 SmallVectorImpl<Type> &objects) {
1641 if (parser.parseLParen())
1642 return failure();
1643
1644 StringRef portName;
1645 SMLoc portLoc = parser.getCurrentLocation();
1646 if (succeeded(parser.parseOptionalKeyword(&portName))) {
1647 std::optional<ObjectFifoPort> parsed = symbolizeObjectFifoPort(portName);
1648 if (!parsed)
1649 return parser.emitError(portLoc, "invalid objectFifo port: ") << portName;
1650 port = ObjectFifoPortAttr::get(parser.getContext(), *parsed);
1651 if (parser.parseComma())
1652 return failure();
1653 }
1654
1655 int64_t count;
1656 SMLoc countLoc = parser.getCurrentLocation();
1657 if (parser.parseInteger(count) || parser.parseRParen() ||
1658 parser.parseColon() || parser.parseTypeList(objects))
1659 return failure();
1660
1661 if (count != static_cast<int64_t>(objects.size()))
1662 return parser.emitError(countLoc, "acquires ")
1663 << count << " objects but names " << objects.size() << " of them";
1664 return success();
1665}
1666
1667void xilinx::AIE::printObjectFifoAcquireObjects(OpAsmPrinter &printer,
1668 Operation *op,
1669 ObjectFifoPortAttr port,
1670 TypeRange objects) {
1671 printer << "(";
1672 if (port)
1673 printer << stringifyObjectFifoPort(port.getValue()) << ", ";
1674 printer << objects.size() << ") : ";
1675 llvm::interleaveComma(objects, printer);
1676}
1677
1678ParseResult xilinx::AIE::parseObjectFifoReleaseCount(OpAsmParser &parser,
1679 ObjectFifoPortAttr &port,
1680 IntegerAttr &size) {
1681 if (parser.parseLParen())
1682 return failure();
1683
1684 StringRef portName;
1685 SMLoc portLoc = parser.getCurrentLocation();
1686 if (succeeded(parser.parseOptionalKeyword(&portName))) {
1687 std::optional<ObjectFifoPort> parsed = symbolizeObjectFifoPort(portName);
1688 if (!parsed)
1689 return parser.emitError(portLoc, "invalid objectFifo port: ") << portName;
1690 port = ObjectFifoPortAttr::get(parser.getContext(), *parsed);
1691 if (parser.parseComma())
1692 return failure();
1693 }
1694
1695 int64_t count;
1696 if (parser.parseInteger(count) || parser.parseRParen())
1697 return failure();
1698 size = parser.getBuilder().getI32IntegerAttr(count);
1699 return success();
1700}
1701
1702void xilinx::AIE::printObjectFifoReleaseCount(OpAsmPrinter &printer,
1703 Operation *op,
1704 ObjectFifoPortAttr port,
1705 IntegerAttr size) {
1706 printer << "(";
1707 if (port)
1708 printer << stringifyObjectFifoPort(port.getValue()) << ", ";
1709 printer << size.getInt() << ")";
1710}
1711
1713 OpAsmParser &parser, SmallVectorImpl<OpAsmParser::UnresolvedOperand> &tiles,
1714 BDDimLayoutArrayArrayAttr &dimensions) {
1715 // parseCommaSeparatedList doesn't handle the missing case for "none",
1716 // so we handle it custom here.
1717 std::vector<BDDimLayoutArrayAttr> tileDims = {};
1718
1719 auto parseOneOperand = [&]() -> ParseResult {
1720 if (parser.parseOperand(tiles.emplace_back(), true)) {
1721 return failure();
1722 }
1723 // By default, create empty dimensions array for each consumer; this way,
1724 // we can be certain to have as many entries in the dimensions array as
1725 // there are customer
1726 BDDimLayoutArrayAttr dimAttr =
1727 BDDimLayoutArrayAttr::get(parser.getContext(), {});
1728
1729 if (succeeded(parser.parseOptionalKeyword("dimensionsFromStream"))) {
1730 // If specified, parse actual data layout transform dimensions
1731 if (parser.parseCustomAttributeWithFallback<BDDimLayoutArrayAttr>(
1732 dimAttr)) {
1733 return failure();
1734 }
1735 }
1736 tileDims.emplace_back(dimAttr);
1737 return success();
1738 };
1739
1740 if (parser.parseCommaSeparatedList(AsmParser::Delimiter::None,
1741 parseOneOperand, " in operand list"))
1742 return failure();
1743
1744 dimensions = BDDimLayoutArrayArrayAttr::get(parser.getContext(), tileDims);
1745 return success();
1746}
1747
1749 OpAsmPrinter &printer, Operation *op, OperandRange tiles,
1750 BDDimLayoutArrayArrayAttr dimsPerTileAttr) {
1751 size_t tileIdx = 0;
1752 for (auto tile : tiles) {
1753 printer << tile;
1754 if (dimsPerTileAttr && tileIdx < dimsPerTileAttr.size() &&
1755 dimsPerTileAttr[tileIdx] && !dimsPerTileAttr[tileIdx].empty()) {
1756 printer << " dimensionsFromStream ";
1757 printer.printStrippedAttrOrType(dimsPerTileAttr[tileIdx]);
1758 }
1759 if (tileIdx < tiles.size() - 1) {
1760 printer << ", ";
1761 }
1762 tileIdx++;
1763 }
1764}
1765
1766static void printObjectFifoConsumerElemType(OpAsmPrinter &p,
1767 ObjectFifoCreateOp op,
1768 TypeAttr consumerElemType) {
1769 if (consumerElemType)
1770 p << " -> " << consumerElemType;
1771}
1772
1773ParseResult xilinx::AIE::parseFlowVias(
1774 OpAsmParser &parser, SmallVectorImpl<OpAsmParser::UnresolvedOperand> &vias,
1775 DenseI32ArrayAttr &ingressBundles, DenseI32ArrayAttr &ingressChannels,
1776 DenseI32ArrayAttr &egressBundles, DenseI32ArrayAttr &egressChannels) {
1777 SmallVector<int32_t> inBundles, inChannels, egBundles, egChannels;
1778 auto parseBundle = [&](SmallVectorImpl<int32_t> &out) -> ParseResult {
1779 StringRef kw;
1780 if (parser.parseKeyword(&kw)) {
1781 return failure();
1782 }
1783 auto bundle = symbolizeWireBundle(kw);
1784 if (!bundle) {
1785 return parser.emitError(parser.getCurrentLocation(),
1786 "invalid wire bundle '" + kw + "'");
1787 }
1788 out.push_back(static_cast<int32_t>(*bundle));
1789 return success();
1790 };
1791 // via ( %tile : <ingress bundle> : <ch> -> <egress bundle> : <ch>, ... )
1792 if (succeeded(parser.parseOptionalKeyword("via"))) {
1793 auto parseOne = [&]() -> ParseResult {
1794 int32_t inChan, egChan;
1795 if (parser.parseOperand(vias.emplace_back()) || parser.parseColon() ||
1796 parseBundle(inBundles) || parser.parseColon() ||
1797 parser.parseInteger(inChan) || parser.parseArrow() ||
1798 parseBundle(egBundles) || parser.parseColon() ||
1799 parser.parseInteger(egChan)) {
1800 return failure();
1801 }
1802 inChannels.push_back(inChan);
1803 egChannels.push_back(egChan);
1804 return success();
1805 };
1806 if (parser.parseCommaSeparatedList(AsmParser::Delimiter::Paren, parseOne)) {
1807 return failure();
1808 }
1809 }
1810 if (!vias.empty()) {
1811 MLIRContext *ctx = parser.getContext();
1812 ingressBundles = DenseI32ArrayAttr::get(ctx, inBundles);
1813 ingressChannels = DenseI32ArrayAttr::get(ctx, inChannels);
1814 egressBundles = DenseI32ArrayAttr::get(ctx, egBundles);
1815 egressChannels = DenseI32ArrayAttr::get(ctx, egChannels);
1816 }
1817 return success();
1818}
1819
1820void xilinx::AIE::printFlowVias(OpAsmPrinter &printer, Operation *op,
1821 OperandRange vias,
1822 DenseI32ArrayAttr ingressBundles,
1823 DenseI32ArrayAttr ingressChannels,
1824 DenseI32ArrayAttr egressBundles,
1825 DenseI32ArrayAttr egressChannels) {
1826 if (vias.empty()) {
1827 return;
1828 }
1829 printer << "via (";
1830 for (size_t i = 0; i < vias.size(); i++) {
1831 if (i) {
1832 printer << ", ";
1833 }
1834 printer << vias[i] << " : "
1835 << stringifyWireBundle(static_cast<WireBundle>(ingressBundles[i]))
1836 << " : " << ingressChannels[i] << " -> "
1837 << stringifyWireBundle(static_cast<WireBundle>(egressBundles[i]))
1838 << " : " << egressChannels[i];
1839 }
1840 printer << ")";
1841}
1842
1843static ParseResult parseObjectFifoConsumerElemType(OpAsmParser &parser,
1844 TypeAttr &consumerElemType) {
1845 if (failed(parser.parseOptionalArrow()))
1846 return success(); // no consumer type
1847 Type type;
1848 if (parser.parseType(type))
1849 return failure();
1850 consumerElemType = TypeAttr::get(type);
1851 return success();
1852}
1853
1854static void printObjectFifoInitValues(OpAsmPrinter &p, ObjectFifoCreateOp op,
1855 Attribute numElem, TypeAttr type,
1856 Attribute initValues) {
1857 if (op.getInitValues()) {
1858 p << "= [";
1859 // `numElem` may be an IntegerAttr (scalar depth) or an ArrayAttr of
1860 // per-endpoint depths; initValues populates the producer side, which
1861 // is the first entry of the ArrayAttr.
1862 int depth;
1863 if (isa<ArrayAttr>(numElem)) {
1864 depth =
1865 llvm::cast<mlir::IntegerAttr>(llvm::cast<mlir::ArrayAttr>(numElem)[0])
1866 .getInt();
1867 } else {
1868 depth = llvm::cast<mlir::IntegerAttr>(numElem).getInt();
1869 }
1870 for (int i = 0; i < depth; i++) {
1871 p.printStrippedAttrOrType(llvm::cast<mlir::ArrayAttr>(initValues)[i]);
1872 if (i < depth - 1) {
1873 p << ", ";
1874 }
1875 }
1876 p << "]";
1877 }
1878}
1879
1880static ParseResult parseObjectFifoInitValues(OpAsmParser &parser,
1881 Attribute numElem, TypeAttr type,
1882 Attribute &initValues) {
1883 int depth;
1884 if (isa<ArrayAttr>(numElem)) {
1885 depth =
1886 llvm::cast<mlir::IntegerAttr>(llvm::cast<mlir::ArrayAttr>(numElem)[0])
1887 .getInt();
1888 } else {
1889 depth = llvm::cast<mlir::IntegerAttr>(numElem).getInt();
1890 }
1891 auto objfifoType = llvm::cast<AIEObjectFifoType>(type.getValue());
1892 auto memrefType = llvm::cast<MemRefType>(objfifoType.getElementType());
1893
1894 if (!memrefType.hasStaticShape())
1895 return parser.emitError(parser.getNameLoc())
1896 << "type should be static shaped memref, but got " << memrefType;
1897
1898 if (parser.parseOptionalEqual())
1899 return success();
1900
1901 Type tensorType = mlir::memref::getTensorTypeFromMemRefType(memrefType);
1902 if (parser.parseAttribute(initValues, tensorType))
1903 return failure();
1904 for (int i = 0; i < depth; i++) {
1905 auto initialValues = llvm::dyn_cast<mlir::ArrayAttr>(initValues);
1906 if ((int)initialValues.size() != depth)
1907 return parser.emitError(parser.getNameLoc())
1908 << "initial values should initialize all objects";
1909 if (!llvm::isa<ElementsAttr>(initialValues[i]))
1910 return parser.emitError(parser.getNameLoc())
1911 << "initial value should be an elements attribute";
1912 }
1913
1914 return success();
1915}
1916
1917//===----------------------------------------------------------------------===//
1918// ObjectFifoAllocateOp
1919//===----------------------------------------------------------------------===//
1920
1921LogicalResult ObjectFifoAllocateOp::verify() {
1922 ObjectFifoCreateOp objFifo = getObjectFifo();
1923 if (!objFifo)
1924 return emitError("cannot retrieve associated object FIFO");
1925 if (objFifo.getConsumerTiles().size() != 1)
1926 return emitError("can only be used in 1-to-1 object FIFOs");
1927 if (objFifo.forcesDMA())
1928 return emitError("cannot allocate a shared memory module to objectfifo "
1929 "that asks for a dma transport");
1930 if (objFifo.getRepeatCount().has_value())
1931 return emitError("cannot allocate a shared memory module to objectfifo "
1932 "with set `repeat_count` attribute");
1933 if (!objFifo.getDimensionsToStream().empty())
1934 return emitError("cannot allocate a shared memory module to objectfifo "
1935 "with set dimensions attribute");
1936 if (objFifo.usesStream())
1937 return emitError("cannot allocate a shared memory module to objectfifo "
1938 "using stream port");
1939 for (Operation *op = (*this)->getPrevNode(); op; op = op->getPrevNode())
1940 if (auto other = dyn_cast<ObjectFifoAllocateOp>(op);
1941 other && other.getObjFifoName() == getObjFifoName())
1942 return emitOpError("objectfifo ")
1943 << getObjFifoNameAttr() << " already has an allocate operation";
1944 return success();
1945}
1946
1947TileOp ObjectFifoAllocateOp::getDelegateTileOp() {
1948 return cast<TileOp>(getDelegateTile().getDefiningOp());
1949}
1950
1951ObjectFifoCreateOp ObjectFifoAllocateOp::getObjectFifo() {
1952 Operation *parent = getOperation();
1953 while ((parent = parent->getParentOp())) {
1954 if (parent->hasTrait<OpTrait::SymbolTable>()) {
1955 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
1956 isa_and_nonnull<ObjectFifoCreateOp>(st))
1957 return cast<ObjectFifoCreateOp>(st);
1958 }
1959 }
1960 return {};
1961}
1962
1963//===----------------------------------------------------------------------===//
1964// ObjectFifoLinkOp
1965//===----------------------------------------------------------------------===//
1966
1967LogicalResult ObjectFifoLinkOp::verify() {
1968 if (isJoin() && isDistribute())
1969 return emitError("ObjectFifoLinkOp does not support 'join' and "
1970 "'distribute' at the same time");
1971
1972 if (isTime()) {
1973 if (isMerge() && isDispatch())
1974 return emitOpError("a time link merges or dispatches, not both");
1975 if (!isMerge() && !isDispatch())
1976 return emitOpError("a time link takes turns, so it needs several inputs "
1977 "or several outputs");
1978 if (!getSrcOffsets().empty() || !getDstOffsets().empty())
1979 return emitOpError("a time link moves whole objects and takes no "
1980 "offsets");
1981 std::vector<ObjectFifoCreateOp> fifos = getInputObjectFifos();
1982 std::vector<ObjectFifoCreateOp> outs = getOutputObjectFifos();
1983 fifos.insert(fifos.end(), outs.begin(), outs.end());
1984 for (ObjectFifoCreateOp fifo : fifos) {
1985 if (fifo.getElemType() != fifos.front().getElemType())
1986 return emitOpError("a time link's participants carry one object "
1987 "type, but '")
1988 << fifo.name().getValue() << "' carries " << fifo.getElemType()
1989 << " and '" << fifos.front().name().getValue() << "' carries "
1990 << fifos.front().getElemType();
1991 }
1992 if (isDispatch()) {
1993 for (ObjectFifoCreateOp out : outs)
1994 if (out.getConsumerTiles().size() != 1)
1995 return emitOpError("each turn of a dispatch reaches one consumer, "
1996 "but '")
1997 << out.name().getValue() << "' has "
1998 << out.getConsumerTiles().size();
1999 }
2000 if (isMerge()) {
2001 PacketInfoAttr pinned;
2002 for (ObjectFifoCreateOp in : getInputObjectFifos()) {
2003 PacketInfoAttr header = in.packetHeader();
2004 if (!header || !header.isAssigned())
2005 continue;
2006 if (pinned && pinned != header)
2007 return emitOpError("a merge is one route with one header, but its "
2008 "inputs pin different ones");
2009 pinned = header;
2010 }
2011 }
2012 }
2013
2014 auto sharedTile = getOptionalSharedTile();
2015 if (!sharedTile)
2016 return emitError("ObjectFifoLinkOp must have a link point, i.e., a "
2017 "shared tile between objectFifos");
2018
2019 // A fifo has one producer, so one link at most fills it; each link reading
2020 // it takes one of its consumer ends on the link's tile. A fifo out of one
2021 // link may go into the next, as a chain of mem tile holds does.
2022 DenseMap<Operation *, size_t> reads;
2023 for (auto other :
2024 (*this)->getParentOfType<DeviceOp>().getOps<ObjectFifoLinkOp>()) {
2025 if (other == *this) {
2026 break;
2027 }
2028 for (ObjectFifoCreateOp fifo : getOutputObjectFifos())
2029 if (llvm::is_contained(other.getOutputObjectFifos(), fifo))
2030 return fifo.emitOpError(
2031 "objectfifo cannot be filled by more than one ObjectFifoLinkOp");
2032 for (ObjectFifoCreateOp fifo : getInputObjectFifos())
2033 if (llvm::is_contained(other.getInputObjectFifos(), fifo) &&
2034 other.getOptionalSharedTile() == sharedTile)
2035 reads[fifo]++;
2036 }
2037 for (ObjectFifoCreateOp fifo : getInputObjectFifos())
2038 if (reads.lookup(fifo) >=
2039 static_cast<size_t>(llvm::count(fifo.getConsumerTiles(), *sharedTile)))
2040 return fifo.emitOpError("objectfifo is read by more ObjectFifoLinkOps "
2041 "on a tile than it has consumer ends there");
2042
2043 TileLike tile = llvm::dyn_cast<TileLike>(sharedTile.value().getDefiningOp());
2044 if (!tile)
2045 return emitError("shared tile must implement TileLike interface");
2046 // Each participant occupies its own slice of the shared object, so the link
2047 // point needs a memory module to hold that object and one counting lock pair
2048 // per slice.
2049 if (isJoin() || isDistribute()) {
2050 if (tile.isShimTile())
2051 return emitError("ObjectFifoLinkOp join and distribute are "
2052 "unavailable on shim tiles");
2053 if (!getTargetModel(getOperation())
2055 return emitError("ObjectFifoLinkOp join and distribute require "
2056 "semaphore locks, which this device lacks");
2057 }
2058 if (isTime() && tile.isShimTile())
2059 return emitOpError("a time link holds its objects on the link point, which "
2060 "a shim tile has no memory for");
2061
2062 // A join or distribute slot must hold a whole number of its participant's
2063 // objects, or one straddles two shared objects and the DMA stalls.
2064 auto elementsOf = [](Type objectFifoType) {
2065 return cast<MemRefType>(
2066 cast<AIEObjectFifoType>(objectFifoType).getElementType())
2067 .getNumElements();
2068 };
2069 // The data layout sizes block-float elements too, which have no bit width.
2070 DataLayout dataLayout = DataLayout::closest(getOperation());
2071 auto bytesOf = [&](Type objectFifoType, int64_t elements) {
2072 return elements *
2073 static_cast<int64_t>(dataLayout.getTypeSize(
2074 cast<MemRefType>(
2075 cast<AIEObjectFifoType>(objectFifoType).getElementType())
2076 .getElementType()));
2077 };
2078 auto checkSlot = [&](StringRef role, ObjectFifoCreateOp participant,
2079 int64_t object, ObjectFifoCreateOp shared,
2080 int64_t slotBytes, ArrayAttr offsets) -> LogicalResult {
2081 if (object <= 0)
2082 return emitOpError() << role << " @" << participant.name().getValue()
2083 << " has empty objects";
2084 if (slotBytes % object == 0)
2085 return success();
2086 std::string offsetText;
2087 llvm::raw_string_ostream os(offsetText);
2088 llvm::interleaveComma(offsets, os, [&](Attribute offset) {
2089 os << *getConstantIntValue(offset);
2090 });
2091 return emitOpError() << role << " @" << participant.name().getValue()
2092 << " has " << object << " B objects, but its "
2093 << slotBytes << " B slot in @"
2094 << shared.name().getValue() << " from offsets ["
2095 << offsetText << "] is not a whole number of them";
2096 };
2097
2098 if (isJoin()) {
2099 if (getFifoIns().size() != getSrcOffsets().size())
2100 return emitOpError("number of provided src offsets must be equal "
2101 "to the number of input objectFifos");
2102
2103 if (!getDstOffsets().empty())
2104 return emitOpError("dst offsets should be empty for join");
2105
2106 // Each input's transfer runs to the next input's offset, so the offsets
2107 // must increase; otherwise the lengths come out wrong and the link hangs.
2108 for (auto [idx, len] : llvm::enumerate(getJoinTransferLengths()))
2109 if (len <= 0)
2110 return emitOpError("src offsets must increase: input objectFifo ")
2111 << idx << " would transfer " << len << " elements";
2112
2113 ObjectFifoCreateOp fifoOut = getOutputObjectFifos()[0];
2114 for (auto [fifoIn, slot] :
2115 llvm::zip(getInputObjectFifos(), getJoinTransferLengths())) {
2116 int64_t object =
2117 fifoIn.getConsumerElemType()
2118 ? bytesOf(fifoIn.getConsumerElemTypeOrDefault(),
2119 elementsOf(fifoIn.getConsumerElemTypeOrDefault()))
2120 : bytesOf(fifoIn.getElemType(),
2121 paddedSize(fifoIn.getDimensionsToStream(),
2122 fifoIn.getPadDimensions().value_or(
2123 ArrayRef<BDPadLayoutAttr>{}),
2124 elementsOf(fifoIn.getElemType())));
2125 if (failed(checkSlot("input", fifoIn, object, fifoOut,
2126 bytesOf(fifoOut.getElemType(), slot),
2127 getSrcOffsets())))
2128 return failure();
2129 }
2130 if (!fifoOut.getDimensionsToStream().empty()) {
2131 int64_t maxIdx = getDimsMaxIdx(fifoOut.getDimensionsToStream());
2132 int64_t minInputBufferSize = -1;
2133 for (auto lenIn : getJoinTransferLengths()) {
2134 if (lenIn <= minInputBufferSize || minInputBufferSize < 0)
2135 minInputBufferSize = lenIn;
2136 }
2137 if (minInputBufferSize <= maxIdx) {
2138 return emitOpError()
2139 << "specified output stride(s) and size(s) result in out "
2140 "of bounds access in join input, for index "
2141 << std::to_string(maxIdx) << " in transfer of length "
2142 << std::to_string(minInputBufferSize) << ".";
2143 }
2144 }
2145
2146 } else if (isDistribute()) {
2147 if (getFifoOuts().size() != getDstOffsets().size())
2148 return emitOpError("number of provided dst offsets must be equal "
2149 "to the number of output objectFifos");
2150
2151 if (!getSrcOffsets().empty())
2152 return emitOpError("src offsets should be empty for distribute");
2153
2154 for (auto [idx, len] : llvm::enumerate(getDistributeTransferLengths()))
2155 if (len <= 0)
2156 return emitOpError("dst offsets must increase: output objectFifo ")
2157 << idx << " would transfer " << len << " elements";
2158
2159 ObjectFifoCreateOp fifoIn = getInputObjectFifos()[0];
2160 for (auto [fifoOut, slot] :
2161 llvm::zip(getOutputObjectFifos(), getDistributeTransferLengths())) {
2162 // The memtile sends the slot, padded if the output pads.
2163 if (failed(checkSlot(
2164 "output", fifoOut,
2165 bytesOf(fifoOut.getElemType(), elementsOf(fifoOut.getElemType())),
2166 fifoIn,
2167 bytesOf(fifoIn.getElemType(),
2168 paddedSize(fifoOut.getDimensionsToStream(),
2169 fifoOut.getPadDimensions().value_or(
2170 ArrayRef<BDPadLayoutAttr>{}),
2171 slot)),
2172 getDstOffsets())))
2173 return failure();
2174 }
2175 // The end this link reads: the next on its tile after earlier links'.
2176 size_t end = 0;
2177 for (size_t seen = 0; end < fifoIn.getConsumerTiles().size(); end++)
2178 if (fifoIn.getConsumerTiles()[end] == *sharedTile &&
2179 seen++ == reads.lookup(fifoIn))
2180 break;
2181 BDDimLayoutArrayAttr dims =
2182 fifoIn.getDimensionsFromStreamPerConsumer()[end];
2183 if (!dims.empty()) {
2184 int64_t maxIdx = getDimsMaxIdx(dims);
2185 int64_t minOutputBufferSize = -1;
2186 for (auto lenOut : getDistributeTransferLengths()) {
2187 if (lenOut <= minOutputBufferSize || minOutputBufferSize < 0)
2188 minOutputBufferSize = lenOut;
2189 }
2190 if (minOutputBufferSize <= maxIdx) {
2191 return emitOpError()
2192 << "specified input stride(s) and size(s) result in out "
2193 "of bounds access in distribute output, for index "
2194 << std::to_string(maxIdx) << " in transfer of length "
2195 << std::to_string(minOutputBufferSize) << ".";
2196 }
2197 }
2198 } else {
2199 if (!getSrcOffsets().empty() && !getDstOffsets().empty())
2200 return emitOpError("all offsets should be empty if there is no "
2201 "join or distribute");
2202 // Each pool object leaves whole in one descriptor, so a walk that covers
2203 // less would run its outer dimension on past the walk.
2204 std::vector<ObjectFifoCreateOp> outs = getOutputObjectFifos();
2205 if (!isTime() && !walksStream() && !outs.empty()) {
2206 ArrayRef<BDDimLayoutAttr> dims = outs[0].getDimensionsToStream();
2207 std::optional<ArrayRef<BDPadLayoutAttr>> pads =
2208 outs[0].getPadDimensions();
2209 int64_t walked = 1;
2210 for (BDDimLayoutAttr dim : dims)
2211 walked *= dim.getSize();
2212 int64_t object = elementsOf(getPoolOwner().getElemType());
2213 if (!dims.empty() && (!pads || pads->empty()) && walked != object)
2214 return emitOpError()
2215 << "@" << outs[0].name().getValue() << "'s walk covers "
2216 << walked << " elements, but each transfer sends a whole "
2217 << object
2218 << "-element object; to send part of each input object, walk "
2219 "the stream (an outer dimension stepping whole input "
2220 "objects)";
2221 }
2222 }
2223
2224 if (walksStream()) {
2225 StreamWindow window;
2226 if (failed(getStreamWindow(window)))
2227 return failure();
2228 if (!tile.isMemTile())
2229 return emitOpError("a walk over a stream needs a MemTile link point");
2230 ObjectFifoCreateOp in = getInputObjectFifos()[0];
2231 ObjectFifoCreateOp out = getOutputObjectFifos()[0];
2232 if (in.getRepeatCount() || out.getRepeatCount())
2233 return emitOpError("a walk over a stream does not repeat objects");
2234 if (in.getIterCount() || out.getIterCount())
2235 return emitOpError("a walk over a stream runs without an iter_count");
2236 const AIETargetModel &target = getTargetModel(getOperation());
2237 // Its pool counts the stream's objects on one lock pair, and each output
2238 // is a descriptor of its own on a MemTile channel.
2239 if (window.objects > static_cast<int64_t>(target.getMaxLockValue()))
2240 return emitOpError() << "a walk over a stream of " << window.objects
2241 << " objects needs a lock counting that many, "
2242 "but locks count to "
2243 << target.getMaxLockValue();
2244 int64_t bds = target.getNumBDs(AIETileType::MemTile) / 2;
2245 if (window.outputs > bds)
2246 return emitOpError() << "a walk over a stream lowers to one descriptor "
2247 "per output, "
2248 << window.outputs << ", but a MemTile channel has "
2249 << bds;
2250 }
2251
2252 // Distribute outputs drain one shared pool with a single repeat count.
2253 // Join inputs repeat at independent producer pools.
2254 if (isDistribute()) {
2255 auto fifos = getOutputObjectFifos();
2256 for (size_t i = 1; i < fifos.size(); ++i) {
2257 ObjectFifoCreateOp first = fifos[0], fifo = fifos[i];
2258 int repeat = first.getRepeatCount().value_or(1);
2259 int otherRepeat = fifo.getRepeatCount().value_or(1);
2260 if (repeat != otherRepeat)
2261 return emitOpError()
2262 << "distribute outputs must have equal effective repeat counts: "
2263 "@"
2264 << first.name().getValue() << " has " << repeat
2265 << (first.getRepeatCount() ? "" : " (default)") << ", @"
2266 << fifo.name().getValue() << " has " << otherRepeat
2267 << (fifo.getRepeatCount() ? "" : " (default)");
2268 }
2269 }
2270
2271 return success();
2272}
2273
2274std::optional<Value> ObjectFifoLinkOp::getOptionalSharedTile() {
2275 // An input read by several links meets each at its consumer end on that
2276 // link's tile, whichever of its ends that is.
2277 if (isJoin()) {
2278 Value tile = getOutputObjectFifos()[0].getProducerTile();
2279 for (auto fifoIn : getInputObjectFifos())
2280 if (!llvm::is_contained(fifoIn.getConsumerTiles(), tile))
2281 return {};
2282 return {tile};
2283 }
2284
2285 if (isDistribute()) {
2286 Value tile = getOutputObjectFifos()[0].getProducerTile();
2287 for (auto fifoOut : getOutputObjectFifos())
2288 if (fifoOut.getProducerTile() != tile)
2289 return {};
2290 if (!llvm::is_contained(getInputObjectFifos()[0].getConsumerTiles(), tile))
2291 return {};
2292 return {tile};
2293 }
2294
2295 auto fifoIn = getInputObjectFifos();
2296 if (auto fifoOut = getOutputObjectFifos();
2297 !fifoIn.empty() && !fifoOut.empty())
2298 for (auto consumerIn : fifoIn[0].getConsumerTiles())
2299 if (consumerIn == fifoOut[0].getProducerTile())
2300 return {fifoOut[0].getProducerTile()};
2301 return {};
2302}
2303
2304bool ObjectFifoLinkOp::walksStream() {
2305 if (isJoin() || isDistribute())
2306 return false;
2307 std::vector<ObjectFifoCreateOp> outs = getOutputObjectFifos();
2308 if (outs.empty())
2309 return false;
2310 ArrayRef<BDDimLayoutAttr> dims = outs[0].getDimensionsToStream();
2311 if (dims.size() < 2 || dims.front().getSize() < 2)
2312 return false;
2313 int64_t inner = 1;
2314 for (BDDimLayoutAttr dim : dims.drop_front())
2315 inner *= dim.getSize();
2316 int64_t objectSize =
2317 cast<AIEObjectFifoType>(getInputObjectFifos()[0].getElemType())
2318 .getElementType()
2319 .getNumElements();
2320 // A walk within one input object (rate conversion) is no window.
2321 return getDimsMaxIdx(dims) >= objectSize &&
2322 inner == cast<AIEObjectFifoType>(outs[0].getElemType())
2323 .getElementType()
2324 .getNumElements();
2325}
2326
2327LogicalResult ObjectFifoLinkOp::getStreamWindow(StreamWindow &window) {
2328 ObjectFifoCreateOp in = getInputObjectFifos()[0];
2329 ObjectFifoCreateOp out = getOutputObjectFifos()[0];
2330 ArrayRef<BDPadLayoutAttr> pads;
2331 if (auto padding = out.getPadDimensions())
2332 pads = *padding;
2333 return inferStreamWindow(
2334 out.getDimensionsToStream(), pads,
2335 cast<AIEObjectFifoType>(in.getElemType())
2336 .getElementType()
2337 .getNumElements(),
2338 [&] { return emitOpError(); }, window);
2339}
2340
2341ObjectFifoCreateOp ObjectFifoLinkOp::getPoolOwner() {
2342 std::vector<ObjectFifoCreateOp> ins = getInputObjectFifos();
2343 std::vector<ObjectFifoCreateOp> outs = getOutputObjectFifos();
2344 if (isJoin() || isMerge())
2345 return outs[0];
2346 if (isDistribute() || isDispatch())
2347 return ins[0];
2348 // A window's pool holds the stream it walks, object by object.
2349 if (walksStream())
2350 return ins[0];
2351 auto count = [](ObjectFifoCreateOp fifo) {
2352 return cast<AIEObjectFifoType>(fifo.getElemType())
2353 .getElementType()
2354 .getNumElements();
2355 };
2356 // Padding is applied as the objects leave, so the pool holds what arrives.
2357 if (outs[0].getInitValues() ||
2358 (count(outs[0]) > count(ins[0]) && !outs[0].getPadDimensions()))
2359 return outs[0];
2360 return ins[0];
2361}
2362
2363SmallVector<std::pair<int64_t, int64_t>> ObjectFifoLinkOp::getPoolSegments() {
2364 auto count = [](ObjectFifoCreateOp fifo) {
2365 return cast<AIEObjectFifoType>(fifo.getElemType())
2366 .getElementType()
2367 .getNumElements();
2368 };
2369 int64_t whole = count(getPoolOwner());
2370 if (!isJoin() && !isDistribute())
2371 return {{0, whole}};
2372 ArrayAttr offsets = isJoin() ? getSrcOffsets() : getDstOffsets();
2373 std::vector<ObjectFifoCreateOp> side =
2374 isJoin() ? getInputObjectFifos() : getOutputObjectFifos();
2375 SmallVector<std::pair<int64_t, int64_t>> segments;
2376 for (auto [index, participant] : llvm::enumerate(side)) {
2377 int64_t offset = cast<IntegerAttr>(offsets[index]).getInt();
2378 int64_t next = index + 1 < side.size()
2379 ? cast<IntegerAttr>(offsets[index + 1]).getInt()
2380 : whole;
2381 int64_t size = next - offset;
2382 if (size <= 0)
2383 size = count(participant);
2384 segments.emplace_back(offset, size);
2385 }
2386 return segments;
2387}
2388
2389std::vector<ObjectFifoCreateOp> ObjectFifoLinkOp::getInputObjectFifos() {
2390 std::vector<ObjectFifoCreateOp> inputObjFifos;
2391 Operation *parent = getOperation();
2392 while ((parent = parent->getParentOp())) {
2393 if (parent->hasTrait<OpTrait::SymbolTable>()) {
2394 for (auto sym : getFifoIns()) {
2395 auto name = cast<FlatSymbolRefAttr>(sym);
2396 if (auto *st = SymbolTable::lookupSymbolIn(parent, name);
2397 isa_and_nonnull<ObjectFifoCreateOp>(st))
2398 inputObjFifos.push_back(cast<ObjectFifoCreateOp>(st));
2399 }
2400 }
2401 }
2402 return inputObjFifos;
2403}
2404
2405std::vector<ObjectFifoCreateOp> ObjectFifoLinkOp::getOutputObjectFifos() {
2406 std::vector<ObjectFifoCreateOp> outputObjFifos;
2407 Operation *parent = getOperation();
2408 while ((parent = parent->getParentOp())) {
2409 if (parent->hasTrait<OpTrait::SymbolTable>()) {
2410 for (auto sym : getFifoOuts()) {
2411 auto name = cast<FlatSymbolRefAttr>(sym);
2412 if (auto *st = mlir::SymbolTable::lookupSymbolIn(parent, name);
2413 isa_and_nonnull<ObjectFifoCreateOp>(st))
2414 outputObjFifos.push_back(cast<ObjectFifoCreateOp>(st));
2415 }
2416 }
2417 }
2418 return outputObjFifos;
2419}
2420
2421std::vector<int> ObjectFifoLinkOp::getJoinTransferLengths() {
2422 std::vector<int> lengths;
2423 if (isJoin()) {
2424 auto fifoOut =
2425 llvm::cast<AIEObjectFifoType>(getOutputObjectFifos()[0].getElemType());
2426 auto elemTypeOut = llvm::cast<MemRefType>(fifoOut.getElementType());
2427 int lenOut = elemTypeOut.getNumElements();
2428 // src_offsets is an I64ArrayAttr, so every element is an IntegerAttr and
2429 // getConstantIntValue always yields a value; assert to keep this fail-fast.
2430 auto srcOffset = [&](size_t idx) -> int {
2431 std::optional<int64_t> v = getConstantIntValue(getSrcOffsets()[idx]);
2432 assert(v && "src_offsets element must be a constant integer");
2433 return static_cast<int>(*v);
2434 };
2435 for (size_t i = 0; i < getFifoIns().size(); i++) {
2436 int len = 0;
2437 int offset = srcOffset(i);
2438 if (i == getFifoIns().size() - 1)
2439 len = lenOut - offset;
2440 else
2441 len = srcOffset(i + 1) - offset;
2442 lengths.push_back(len);
2443 }
2444 }
2445 return lengths;
2446}
2447
2448std::vector<int> ObjectFifoLinkOp::getDistributeTransferLengths() {
2449 std::vector<int> lengths;
2450 if (isDistribute()) {
2451 auto fifoIn =
2452 llvm::cast<AIEObjectFifoType>(getInputObjectFifos()[0].getElemType());
2453 auto elemTypeIn = llvm::cast<MemRefType>(fifoIn.getElementType());
2454 int lenIn = elemTypeIn.getNumElements();
2455 // dst_offsets is an I64ArrayAttr, so every element is an IntegerAttr and
2456 // getConstantIntValue always yields a value; assert to keep this fail-fast.
2457 auto dstOffset = [&](size_t idx) -> int {
2458 std::optional<int64_t> v = getConstantIntValue(getDstOffsets()[idx]);
2459 assert(v && "dst_offsets element must be a constant integer");
2460 return static_cast<int>(*v);
2461 };
2462 for (size_t i = 0; i < getFifoOuts().size(); i++) {
2463 int offset = dstOffset(i);
2464 int len = 0;
2465 if (i == getFifoOuts().size() - 1)
2466 len = lenIn - offset;
2467 else
2468 len = dstOffset(i + 1) - offset;
2469 lengths.push_back(len);
2470 }
2471 }
2472 return lengths;
2473}
2474
2475std::optional<int> ObjectFifoLinkOp::getRepeatCount() {
2476 for (auto fifoOut : getOutputObjectFifos())
2477 if (fifoOut.getRepeatCount().has_value())
2478 return {fifoOut.getRepeatCount()};
2479 return {};
2480}
2481
2482//===----------------------------------------------------------------------===//
2483// ObjectFifoRegisterExternalBuffersOp
2484//===----------------------------------------------------------------------===//
2485
2486TileOp ObjectFifoRegisterExternalBuffersOp::getTileOp() {
2487 return cast<TileOp>(getTile().getDefiningOp());
2488}
2489
2490ObjectFifoCreateOp ObjectFifoRegisterExternalBuffersOp::getObjectFifo() {
2491 Operation *parent = getOperation();
2492 while ((parent = parent->getParentOp())) {
2493 if (parent->hasTrait<OpTrait::SymbolTable>()) {
2494 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
2495 isa_and_nonnull<ObjectFifoCreateOp>(st))
2496 return cast<ObjectFifoCreateOp>(st);
2497 }
2498 }
2499 return {};
2500}
2501
2502//===----------------------------------------------------------------------===//
2503// ObjectFifoAcquireOp
2504//===----------------------------------------------------------------------===//
2505
2506namespace {
2507
2508/// Symbol an objectFifo access names: the fifo it belongs to, or the core
2509/// endpoint it works through.
2510Operation *lookupAccessTarget(Operation *op, StringRef name) {
2511 for (Operation *parent = op->getParentOp(); parent;
2512 parent = parent->getParentOp()) {
2513 if (parent->hasTrait<OpTrait::SymbolTable>()) {
2514 if (Operation *target = SymbolTable::lookupSymbolIn(parent, name)) {
2515 return target;
2516 }
2517 }
2518 }
2519 return nullptr;
2520}
2521
2522/// Element type an access sees, or a null type if the symbol names neither a
2523/// fifo nor an endpoint.
2524Type accessElementType(Operation *target) {
2525 if (auto fifo = dyn_cast_or_null<ObjectFifoCreateOp>(target)) {
2526 return llvm::cast<AIEObjectFifoType>(fifo.getElemType()).getElementType();
2527 }
2528 if (auto endpoint = dyn_cast_or_null<ObjectFifoCoreEndpointOp>(target)) {
2529 if (endpoint.getPoolOp()) {
2530 return endpoint.getAccessType();
2531 }
2532 }
2533 return {};
2534}
2535
2536/// A core may only reach a fifo end that is placed on its own tile.
2537LogicalResult verifyAccessPlacement(Operation *op, Operation *target,
2538 std::optional<ObjectFifoPort> port) {
2539 auto core = op->getParentOfType<CoreOp>();
2540 Value coreTile = core.getTile();
2541
2542 if (auto endpoint = dyn_cast_or_null<ObjectFifoCoreEndpointOp>(target)) {
2543 if (port) {
2544 return op->emitOpError("port is implied by the endpoint's role");
2545 }
2546 if (endpoint.getTile() != coreTile) {
2547 return core.emitOpError(
2548 "objectFifo endpoint accessed by core running on another tile");
2549 }
2550 return success();
2551 }
2552
2553 auto fifo = dyn_cast_or_null<ObjectFifoCreateOp>(target);
2554 if (!fifo) {
2555 return op->emitError("cannot retrieve associated object FIFO");
2556 }
2557 if (!port) {
2558 return op->emitOpError("port is required when accessing an objectFifo");
2559 }
2560
2561 if (*port == ObjectFifoPort::Produce) {
2562 if (coreTile != fifo.getProducerTile()) {
2563 return core.emitOpError(
2564 "producer port of objectFifo accessed by core running "
2565 "on non-producer tile");
2566 }
2567 } else {
2568 if (!llvm::is_contained(fifo.getConsumerTiles(), coreTile)) {
2569 return core.emitOpError(
2570 "consumer port of objectFifo accessed by core running "
2571 "on non-consumer tile");
2572 }
2573 }
2574 return success();
2575}
2576
2577} // namespace
2578
2579LogicalResult ObjectFifoAcquireOp::verify() {
2580 auto parent = getOperation()->getParentOfType<CoreOp>();
2581 if (parent == nullptr) {
2582 return emitOpError("must be called from inside a CoreOp");
2583 }
2584
2585 Operation *target = lookupAccessTarget(*this, getObjFifoName());
2586 if (failed(verifyAccessPlacement(*this, target, getPort()))) {
2587 return failure();
2588 }
2589
2590 if (getObjects().empty()) {
2591 return emitOpError("must acquire at least one object");
2592 }
2593
2594 Type elem = accessElementType(target);
2595 if (!elem) {
2596 return success();
2597 }
2598 auto fifo = dyn_cast<ObjectFifoCreateOp>(target);
2599 if (fifo) {
2600 // A fifo may state a depth per endpoint, the producer first.
2601 int index = 0;
2602 if (getPort() == ObjectFifoPort::Consume) {
2603 for (auto [position, consumer] :
2604 llvm::enumerate(fifo.getConsumerTiles())) {
2605 if (consumer == parent.getTile()) {
2606 index = position + 1;
2607 break;
2608 }
2609 }
2610 }
2611 if (static_cast<int>(getObjects().size()) > fifo.size(index)) {
2612 return emitOpError("acquires ")
2613 << getObjects().size() << " objects from an objectFifo that holds "
2614 << fifo.size(index);
2615 }
2616 }
2617 // An asymmetric fifo hands the consumer a different element type.
2618 auto consType = fifo ? llvm::dyn_cast<AIEObjectFifoType>(
2619 fifo.getConsumerElemTypeOrDefault())
2620 : nullptr;
2621 if (fifo && !consType) {
2622 return emitOpError("ObjectFifo consumer element type must be an "
2623 "!aie.objectfifo<memref<...>> type");
2624 }
2625
2626 for (Type object : getObjects().getTypes()) {
2627 if (object != elem && (!consType || consType.getElementType() != object)) {
2628 return emitOpError("acquired object type ")
2629 << object << " does not match the objectFifo's " << elem;
2630 }
2631 }
2632
2633 return success();
2634}
2635
2636ObjectFifoCreateOp ObjectFifoAcquireOp::getObjectFifo() {
2637 Operation *parent = getOperation();
2638 while ((parent = parent->getParentOp())) {
2639 if (parent->hasTrait<OpTrait::SymbolTable>()) {
2640 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
2641 isa_and_nonnull<ObjectFifoCreateOp>(st))
2642 return cast<ObjectFifoCreateOp>(st);
2643 }
2644 }
2645 return {};
2646}
2647
2648//===----------------------------------------------------------------------===//
2649// ObjectFifoReleaseOp
2650//===----------------------------------------------------------------------===//
2651
2652LogicalResult ObjectFifoReleaseOp::verify() {
2653 auto parent = getOperation()->getParentOfType<CoreOp>();
2654 if (parent == nullptr)
2655 return emitOpError("must be called from inside a CoreOp");
2656
2657 Operation *target = lookupAccessTarget(*this, getObjFifoName());
2658 return verifyAccessPlacement(*this, target, getPort());
2659}
2660
2661ObjectFifoCreateOp ObjectFifoReleaseOp::getObjectFifo() {
2662 Operation *parent = getOperation();
2663 while ((parent = parent->getParentOp())) {
2664 if (parent->hasTrait<OpTrait::SymbolTable>()) {
2665 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
2666 isa_and_nonnull<ObjectFifoCreateOp>(st))
2667 return cast<ObjectFifoCreateOp>(st);
2668 }
2669 }
2670 return {};
2671}
2672
2673//===----------------------------------------------------------------------===//
2674// CascadeFlowOp
2675//===----------------------------------------------------------------------===//
2676
2677LogicalResult CascadeFlowOp::verify() {
2678 TileLike src = getSourceTileLike();
2679 TileLike dst = getDestTileLike();
2680
2681 if (!src || !dst)
2682 return emitOpError("source and dest must be tile-like operations");
2683
2684 if (src.isShimTile() || dst.isShimTile())
2685 return emitOpError("shimTile row has no cascade stream interface");
2686 if (src.isMemTile() || dst.isMemTile())
2687 return emitOpError("memTile row has no cascade stream interface");
2688
2689 std::optional<int> srcCol = src.tryGetCol();
2690 std::optional<int> srcRow = src.tryGetRow();
2691 std::optional<int> dstCol = dst.tryGetCol();
2692 std::optional<int> dstRow = dst.tryGetRow();
2693
2694 if (srcCol && srcRow && dstCol && dstRow) {
2695 const auto &t = getTargetModel(*this);
2696 if (!t.isSouth(*srcCol, *srcRow, *dstCol, *dstRow) &&
2697 !t.isWest(*srcCol, *srcRow, *dstCol, *dstRow) &&
2698 !t.isNorth(*srcCol, *srcRow, *dstCol, *dstRow) &&
2699 !t.isEast(*srcCol, *srcRow, *dstCol, *dstRow)) {
2700 return emitOpError("tiles must be adjacent");
2701 }
2702 }
2703
2704 return success();
2705}
2706
2707TileLike CascadeFlowOp::getSourceTileLike() {
2708 return dyn_cast<TileLike>(getSourceTile().getDefiningOp());
2709}
2710
2711TileLike CascadeFlowOp::getDestTileLike() {
2712 return dyn_cast<TileLike>(getDestTile().getDefiningOp());
2713}
2714
2715TileOp CascadeFlowOp::getSourceTileOp() {
2716 if (auto tileOp = dyn_cast_or_null<TileOp>(getSourceTile().getDefiningOp()))
2717 return tileOp;
2718 llvm::report_fatal_error("Calling getSourceTileOp requires TileOp.");
2719}
2720
2721TileOp CascadeFlowOp::getDestTileOp() {
2722 if (auto tileOp = dyn_cast_or_null<TileOp>(getDestTile().getDefiningOp()))
2723 return tileOp;
2724 llvm::report_fatal_error("Calling getDestTileOp requires TileOp.");
2725}
2726
2727//===----------------------------------------------------------------------===//
2728// ConfigureCascadeOp
2729//===----------------------------------------------------------------------===//
2730
2731LogicalResult ConfigureCascadeOp::verify() {
2732 if (!isa<TileOp>(getTile().getDefiningOp()))
2733 return emitOpError("requires a placed tile (aie.tile), not a logical tile");
2734
2735 const auto &t = getTargetModel(*this);
2736 TileOp tile = cast<TileOp>(getTile().getDefiningOp());
2737 CascadeDir inputDir = getInputDir();
2738 CascadeDir outputDir = getOutputDir();
2739
2740 if (tile.isShimTile())
2741 return emitOpError("shimTile row has no cascade stream interface");
2742 if (tile.isMemTile())
2743 return emitOpError("memTile row has no cascade stream interface");
2744
2745 if (isa<AIE2TargetModel>(t)) {
2746 if (inputDir == CascadeDir::South || inputDir == CascadeDir::East) {
2747 return emitOpError("input direction of cascade must be North or West on ")
2748 << stringifyAIEArch(t.getTargetArch());
2749 }
2750 if (outputDir == CascadeDir::North || outputDir == CascadeDir::West) {
2751 return emitOpError(
2752 "output direction of cascade must be South or East on ")
2753 << stringifyAIEArch(t.getTargetArch());
2754 }
2755 } else {
2756 return emitOpError("cascade not supported in ")
2757 << stringifyAIEArch(t.getTargetArch());
2758 }
2759 return success();
2760}
2761
2762//===----------------------------------------------------------------------===//
2763// PutCascadeOp
2764//===----------------------------------------------------------------------===//
2765
2766LogicalResult PutCascadeOp::verify() {
2767 const auto &targetModel = getTargetModel(*this);
2768 Type type = getCascadeValue().getType();
2769 DataLayout dataLayout = DataLayout::closest(*this);
2770 auto bits = dataLayout.getTypeSizeInBits(type);
2771 auto archbits = targetModel.getAccumulatorCascadeSize();
2772 if (bits != archbits)
2773 return emitOpError("type must match architecture cascade width (")
2774 << archbits << " bits in "
2775 << stringifyAIEArch(targetModel.getTargetArch()) << ")";
2776 return success();
2777}
2778
2779//===----------------------------------------------------------------------===//
2780// GetCascadeOp
2781//===----------------------------------------------------------------------===//
2782
2783LogicalResult GetCascadeOp::verify() {
2784 const auto &targetModel = getTargetModel(*this);
2785 Type type = getCascadeValue().getType();
2786 DataLayout dataLayout = DataLayout::closest(*this);
2787 auto bits = dataLayout.getTypeSizeInBits(type);
2788 if (isa<AIE1TargetModel>(targetModel)) {
2789 if (bits != 384)
2790 return emitOpError("must be a 384-bit type");
2791 } else if (isa<AIE2TargetModel>(targetModel)) {
2792 if (bits != 512)
2793 return emitOpError("must be a 512-bit type");
2794 } else
2795 return emitOpError("cascade not supported in ")
2796 << stringifyAIEArch(targetModel.getTargetArch());
2797 return success();
2798}
2799
2800//===----------------------------------------------------------------------===//
2801// DeviceOp
2802//===----------------------------------------------------------------------===//
2803
2804namespace {
2805
2806// A flow endpoint reduced to a comparable key: resolved tile coordinates plus
2807// the stream-switch port, as (col, row, bundle, channel). Only used for
2808// equality and ordering, so the raw WireBundle enum value is a fine component.
2809using PortKey = std::tuple<int, int, int, int>;
2810
2811// Returns the key for the port (`tileValue`, `bundle`, `channel`), or nullopt
2812// when the tile's coordinates are not known yet -- an aie.logical_tile that
2813// --aie-place-tiles has not placed. Two such endpoints cannot be compared
2814// until placement runs, at which point the caller's check runs again on the
2815// resulting aie.tile coordinates.
2816std::optional<PortKey> tryGetPortKey(Value tileValue, WireBundle bundle,
2817 int channel) {
2818 auto tile = llvm::dyn_cast_or_null<TileLike>(tileValue.getDefiningOp());
2819 if (!tile)
2820 return std::nullopt;
2821 std::optional<int> col = tile.tryGetCol();
2822 std::optional<int> row = tile.tryGetRow();
2823 if (!col || !row)
2824 return std::nullopt;
2825 return PortKey{*col, *row, static_cast<int>(bundle), channel};
2826}
2827
2828std::string to_string(const PortKey &key) {
2829 auto [col, row, bundle, channel] = key;
2830 return "(" + std::to_string(col) + ", " + std::to_string(row) + ") " +
2831 stringifyWireBundle(static_cast<WireBundle>(bundle)).str() + " : " +
2832 std::to_string(channel);
2833}
2834
2835// Rejects two aie.flow ops that declare the same source and destination port.
2836// A repeated flow is not a harmless redundancy: Pathfinder::addFlow folds a
2837// flow into any existing flow with the same source, so the duplicate lands in
2838// that flow's destination list a second time and the router is asked to
2839// broadcast to the same endpoint twice.
2840LogicalResult verifyNoDuplicateFlows(DeviceOp device) {
2841 std::map<std::pair<PortKey, PortKey>, FlowOp> flowSeen;
2842 for (FlowOp flow : device.getOps<FlowOp>()) {
2843 std::optional<PortKey> src = tryGetPortKey(
2844 flow.getSource(), flow.getSourceBundle(), flow.getSourceChannel());
2845 std::optional<PortKey> dst = tryGetPortKey(
2846 flow.getDest(), flow.getDestBundle(), flow.getDestChannel());
2847 if (!src || !dst)
2848 continue;
2849 auto [it, inserted] = flowSeen.try_emplace({*src, *dst}, flow);
2850 if (!inserted) {
2851 InFlightDiagnostic diag = flow.emitOpError()
2852 << "duplicates an earlier flow; "
2853 << to_string(*src) << " -> " << to_string(*dst)
2854 << " is already declared";
2855 diag.attachNote(it->second.getLoc()) << "the other flow is here";
2856 return failure();
2857 }
2858 }
2859 return success();
2860}
2861
2862// Rejects two aie.packet_flow ops that carry the same ID between the same set
2863// of sources and destinations under the same mask. Declaring the flow twice
2864// makes the routing problem look more congested than it is and gives the two
2865// copies conflicting keep_pkt_header/priority_route settings when their
2866// attributes disagree.
2867LogicalResult verifyNoDuplicatePacketFlows(DeviceOp device) {
2868 using PacketFlowKey =
2869 std::tuple<int, int, std::vector<PortKey>, std::vector<PortKey>>;
2870 std::map<PacketFlowKey, PacketFlowOp> packetFlowSeen;
2871 for (PacketFlowOp packetFlow : device.getOps<PacketFlowOp>()) {
2872 Region &body = packetFlow.getPorts();
2873 if (body.empty())
2874 continue;
2875 // Sources and destinations are unordered within a packet flow, so sort
2876 // both to give permutations of the same flow the same key.
2877 std::vector<PortKey> sources, dests;
2878 bool comparable = true;
2879 for (Operation &op : body.front()) {
2880 std::vector<PortKey> *endpoints = nullptr;
2881 std::optional<PortKey> port;
2882 if (auto source = dyn_cast<PacketSourceOp>(op)) {
2883 endpoints = &sources;
2884 port = tryGetPortKey(source.getTile(), source.getBundle(),
2885 source.getChannel());
2886 } else if (auto dest = dyn_cast<PacketDestOp>(op)) {
2887 endpoints = &dests;
2888 port =
2889 tryGetPortKey(dest.getTile(), dest.getBundle(), dest.getChannel());
2890 } else {
2891 continue;
2892 }
2893 // One unplaced endpoint makes the whole flow incomparable.
2894 if (!port) {
2895 comparable = false;
2896 break;
2897 }
2898 endpoints->push_back(*port);
2899 }
2900 if (!comparable || sources.empty() || dests.empty())
2901 continue;
2902 llvm::sort(sources);
2903 llvm::sort(dests);
2904 int idBits =
2905 llvm::Log2_32_Ceil(device.getTargetModel().getMaxPacketId() + 1);
2906 int mask = packetFlow.getMask().value_or((1 << idBits) - 1);
2907 auto [it, inserted] = packetFlowSeen.try_emplace(
2908 {packetFlow.IDInt(), mask, std::move(sources), std::move(dests)},
2909 packetFlow);
2910 if (!inserted) {
2911 InFlightDiagnostic diag =
2912 packetFlow.emitOpError()
2913 << "duplicates an earlier packet flow; ID " << packetFlow.IDInt()
2914 << " under mask 0x" << llvm::utohexstr(mask)
2915 << " is already declared between the same sources and destinations";
2916 diag.attachNote(it->second.getLoc()) << "the other packet flow is here";
2917 return failure();
2918 }
2919 }
2920 return success();
2921}
2922
2923// Rejects two aie.flows into one port and a packet flow sharing an endpoint
2924// with an aie.flow: a circuit sends every word from its source, packets
2925// included, and its destination takes words from nothing else.
2926LogicalResult verifyNoSharedCircuitPorts(DeviceOp device) {
2927 std::map<PortKey, FlowOp> circuitSources, circuitDests;
2928 for (FlowOp flow : device.getOps<FlowOp>()) {
2929 std::optional<PortKey> src = tryGetPortKey(
2930 flow.getSource(), flow.getSourceBundle(), flow.getSourceChannel());
2931 std::optional<PortKey> dst = tryGetPortKey(
2932 flow.getDest(), flow.getDestBundle(), flow.getDestChannel());
2933 if (src)
2934 circuitSources.try_emplace(*src, flow);
2935 if (!dst)
2936 continue;
2937 auto [it, inserted] = circuitDests.try_emplace(*dst, flow);
2938 if (inserted)
2939 continue;
2940 InFlightDiagnostic diag =
2941 flow.emitOpError()
2942 << "ends at " << to_string(*dst)
2943 << ", where another circuit flow ends; a port takes one circuit";
2944 diag.attachNote(it->second.getLoc()) << "the other circuit flow is here";
2945 return failure();
2946 }
2947 for (PacketFlowOp packetFlow : device.getOps<PacketFlowOp>()) {
2948 Region &body = packetFlow.getPorts();
2949 if (body.empty())
2950 continue;
2951 for (Operation &op : body.front()) {
2952 std::optional<PortKey> port;
2953 std::map<PortKey, FlowOp> *circuits = nullptr;
2954 StringRef role;
2955 if (auto source = dyn_cast<PacketSourceOp>(op)) {
2956 port = tryGetPortKey(source.getTile(), source.getBundle(),
2957 source.getChannel());
2958 circuits = &circuitSources;
2959 role = "starts";
2960 } else if (auto dest = dyn_cast<PacketDestOp>(op)) {
2961 port =
2962 tryGetPortKey(dest.getTile(), dest.getBundle(), dest.getChannel());
2963 circuits = &circuitDests;
2964 role = "ends";
2965 }
2966 if (!port)
2967 continue;
2968 auto it = circuits->find(*port);
2969 if (it == circuits->end())
2970 continue;
2971 InFlightDiagnostic diag =
2972 packetFlow.emitOpError()
2973 << role << " at " << to_string(*port) << ", where a circuit flow "
2974 << role << "; a port carries either one circuit or packets";
2975 diag.attachNote(it->second.getLoc()) << "the circuit flow is here";
2976 return failure();
2977 }
2978 }
2979 return success();
2980}
2981
2982LogicalResult verifyNoSharedCircuitViaEgresses(DeviceOp device) {
2983 enum class ClaimKind { FlowDest, PacketDest, Connect, MasterSet, Via };
2984 struct Claim {
2985 Operation *op;
2986 ClaimKind kind;
2987 size_t viaIndex = 0;
2988 };
2989 std::map<PortKey, Claim> claimedEgresses;
2990
2991 auto addInterconnectClaims = [&](auto interconnect) {
2992 Value tile = interconnect.getTile();
2993 for (ConnectOp connect :
2994 interconnect.getConnections().template getOps<ConnectOp>()) {
2995 Port dest = connect.destPort();
2996 if (std::optional<PortKey> port =
2997 tryGetPortKey(tile, dest.bundle, dest.channel))
2998 claimedEgresses.try_emplace(*port, Claim{connect, ClaimKind::Connect});
2999 }
3000 for (MasterSetOp masterSet :
3001 interconnect.getConnections().template getOps<MasterSetOp>()) {
3002 Port dest = masterSet.destPort();
3003 if (std::optional<PortKey> port =
3004 tryGetPortKey(tile, dest.bundle, dest.channel))
3005 claimedEgresses.try_emplace(*port,
3006 Claim{masterSet, ClaimKind::MasterSet});
3007 }
3008 };
3009 for (SwitchboxOp switchbox : device.getOps<SwitchboxOp>())
3010 addInterconnectClaims(switchbox);
3011 for (ShimMuxOp shimMux : device.getOps<ShimMuxOp>())
3012 addInterconnectClaims(shimMux);
3013 for (FlowOp flow : device.getOps<FlowOp>()) {
3014 if (std::optional<PortKey> dest = tryGetPortKey(
3015 flow.getDest(), flow.getDestBundle(), flow.getDestChannel()))
3016 claimedEgresses.try_emplace(*dest, Claim{flow, ClaimKind::FlowDest});
3017 }
3018 for (PacketFlowOp packetFlow : device.getOps<PacketFlowOp>()) {
3019 for (PacketDestOp dest : packetFlow.getPorts().getOps<PacketDestOp>()) {
3020 if (std::optional<PortKey> port = tryGetPortKey(
3021 dest.getTile(), dest.getBundle(), dest.getChannel()))
3022 claimedEgresses.try_emplace(*port, Claim{dest, ClaimKind::PacketDest});
3023 }
3024 }
3025
3026 WalkResult result = device.walk([&](FlowOp flow) {
3027 ArrayRef<int32_t> bundles =
3028 flow.getViaEgressBundlesAttr()
3029 ? flow.getViaEgressBundlesAttr().asArrayRef()
3030 : ArrayRef<int32_t>();
3031 ArrayRef<int32_t> channels =
3032 flow.getViaEgressChannelsAttr()
3033 ? flow.getViaEgressChannelsAttr().asArrayRef()
3034 : ArrayRef<int32_t>();
3035 if (bundles.size() != flow.getVias().size() ||
3036 channels.size() != flow.getVias().size()) {
3037 return WalkResult::advance();
3038 }
3039 for (auto [index, via] : llvm::enumerate(flow.getVias())) {
3040 std::optional<PortKey> egress = tryGetPortKey(
3041 via, static_cast<WireBundle>(bundles[index]), channels[index]);
3042 if (!egress) {
3043 continue;
3044 }
3045 Claim viaClaim{flow, ClaimKind::Via, index};
3046 auto [it, inserted] = claimedEgresses.try_emplace(*egress, viaClaim);
3047 if (inserted)
3048 continue;
3049 if (index + 1 == flow.getVias().size() && it->second.op == flow &&
3050 it->second.kind == ClaimKind::FlowDest) {
3051 it->second = viaClaim;
3052 continue;
3053 }
3054 InFlightDiagnostic diag = flow.emitOpError()
3055 << "via " << index << " claims circuit egress "
3056 << to_string(*egress)
3057 << ", which another operation already claims";
3058 Diagnostic &note = diag.attachNote(it->second.op->getLoc());
3059 switch (it->second.kind) {
3060 case ClaimKind::FlowDest:
3061 note << "a circuit flow ends at the same port";
3062 break;
3063 case ClaimKind::PacketDest:
3064 note << "a packet flow ends at the same port";
3065 break;
3066 case ClaimKind::Connect:
3067 note << "a fixed connect drives the same port";
3068 break;
3069 case ClaimKind::MasterSet:
3070 note << "a fixed master set drives the same port";
3071 break;
3072 case ClaimKind::Via:
3073 note << "via " << it->second.viaIndex
3074 << " claims the same circuit egress";
3075 break;
3076 }
3077 return WalkResult::interrupt();
3078 }
3079 return WalkResult::advance();
3080 });
3081 return failure(result.wasInterrupted());
3082}
3083
3084// `mlir::detail::verifySymbolTable` compares names carried by `Symbol` ops.
3085// `aie.buffer`, `aie.external_buffer`, `aie.lock` and `aie.dma` name themselves
3086// with `sym_name` but define an SSA value, so they are not `Symbol` ops and
3087// their names escape that check. `lookupNamedOpIn` resolves them by name, so a
3088// repeated name would bind every reference to whichever op comes first.
3089LogicalResult verifyNoDuplicateNames(Operation *symbolTableOp) {
3090 if (symbolTableOp->getNumRegions() == 0 ||
3091 symbolTableOp->getRegion(0).empty()) {
3092 return success();
3093 }
3094 DenseMap<StringAttr, Operation *> nameSeen;
3095 for (Operation &op : symbolTableOp->getRegion(0).front()) {
3096 auto name = op.getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName());
3097 if (!name) {
3098 continue;
3099 }
3100 auto [it, inserted] = nameSeen.try_emplace(name, &op);
3101 if (inserted) {
3102 continue;
3103 }
3104 // Leave a pair of `Symbol` ops to `verifySymbolTable`, which runs after the
3105 // body and so lets the body ops report about themselves first.
3106 if (isa<SymbolOpInterface>(op) && isa<SymbolOpInterface>(it->second)) {
3107 continue;
3108 }
3109 InFlightDiagnostic diag = op.emitError() << "redefinition of symbol named '"
3110 << name.getValue() << "'";
3111 diag.attachNote(it->second->getLoc())
3112 << "see existing symbol definition here";
3113 return failure();
3114 }
3115 return success();
3116}
3117
3118} // namespace
3119
3120LogicalResult DeviceOp::verify() {
3121 // Cores and flows are the device's own ops, as the router and the CDO read
3122 // them; a walk would also visit every op of the runtime sequences.
3123 //
3124 // A compute tile has exactly one core in hardware, so at most one aie.core
3125 // may resolve to any given (col, row). Cores whose tile is a logical_tile
3126 // with unspecified coordinates are skipped here — they cannot collide until
3127 // --aie-place-tiles assigns them a position, at which point this same check
3128 // runs again on the resulting aie.tile coordinates.
3129 DenseMap<TileID, CoreOp> coreAtTile;
3130 for (CoreOp core : getOps<CoreOp>()) {
3131 auto tile =
3132 llvm::dyn_cast_or_null<TileLike>(core.getTile().getDefiningOp());
3133 if (!tile)
3134 continue;
3135 std::optional<int> col = tile.tryGetCol();
3136 std::optional<int> row = tile.tryGetRow();
3137 if (!col || !row)
3138 continue;
3139 TileID id{*col, *row};
3140 auto [it, inserted] = coreAtTile.try_emplace(id, core);
3141 if (!inserted) {
3142 InFlightDiagnostic diag =
3143 core.emitOpError()
3144 << "tile (" << *col << ", " << *row
3145 << ") already has a core; each compute tile can host only one core";
3146 diag.attachNote(it->second.getLoc()) << "the other core is here";
3147 return failure();
3148 }
3149 }
3150
3151 // A flow declared twice is always a mistake, and one that is otherwise only
3152 // noticed (if at all) as unexplained routing pressure much later on.
3153 if (failed(verifyNoDuplicateFlows(*this)))
3154 return failure();
3155 if (failed(verifyNoDuplicatePacketFlows(*this)))
3156 return failure();
3157 if (failed(verifyNoSharedCircuitPorts(*this)))
3158 return failure();
3159 if (failed(verifyNoSharedCircuitViaEgresses(*this))) {
3160 return failure();
3161 }
3162
3163 if (failed(verifyNoDuplicateNames(*this))) {
3164 return failure();
3165 }
3166
3167 return success();
3168}
3169
3170LogicalResult DeviceOp::verifyRegions() {
3171 // Region verification follows all child invariants, including the joined
3172 // verification of isolated children. Named uses may safely inspect forward
3173 // definitions here. The collection belongs only to this verification sweep.
3175 WalkResult result = getOperation()->walk<WalkOrder::PreOrder>(
3176 [&](Operation *op) -> WalkResult {
3177 if (op != getOperation() && isa<DeviceOp>(op))
3178 return WalkResult::skip();
3179 if (auto user = dyn_cast<NamedOpUserOpInterface>(op))
3180 if (failed(user.verifyNamedOpUses(names)))
3181 return WalkResult::interrupt();
3182 return WalkResult::advance();
3183 });
3184 return failure(result.wasInterrupted());
3185}
3186
3187const AIETargetModel &DeviceOp::getTargetModel() {
3188 return xilinx::AIE::getTargetModel(getDevice());
3189}
3190
3191xilinx::AIE::DeviceOp DeviceOp::getForSymbolInModule(mlir::ModuleOp module,
3192 llvm::StringRef symbol) {
3193 DeviceOp deviceOp;
3194 if (symbol.empty()) {
3195 // If no device name is given, assume 'main'
3196 symbol = "main";
3197 }
3198 Operation *maybeDeviceOp = mlir::SymbolTable::lookupSymbolIn(module, symbol);
3199 if (!maybeDeviceOp) {
3200 return nullptr;
3201 }
3202 deviceOp = llvm::dyn_cast<DeviceOp>(maybeDeviceOp);
3203 return deviceOp;
3204}
3205
3206xilinx::AIE::DeviceOp
3207DeviceOp::getForSymbolInModuleOrError(mlir::ModuleOp module,
3208 llvm::StringRef symbol) {
3209 DeviceOp deviceOp = getForSymbolInModule(module, symbol);
3210 if (!deviceOp) {
3211 if (!symbol.empty()) {
3212 module.emitError("No such device: ") << symbol;
3213 } else {
3214 module.emitError("No 'main' device in module");
3215 }
3216 }
3217 return deviceOp;
3218}
3219
3220//===----------------------------------------------------------------------===//
3221// TileElement
3222//===----------------------------------------------------------------------===//
3223
3224TileOp TileElement::tryGetTileOp() {
3225 auto element = cast<TileElement>(this->getOperation());
3226 return dyn_cast_or_null<TileOp>(element.getTile().getDefiningOp());
3227}
3228
3229TileOp TileElement::getTileOp() {
3230 if (auto tileOp = tryGetTileOp())
3231 return tileOp;
3232 llvm::report_fatal_error("Calling getTileOp requires TileOp.");
3233}
3234
3235//===----------------------------------------------------------------------===//
3236// LogicalTileOp
3237//===----------------------------------------------------------------------===//
3238
3239LogicalResult LogicalTileOp::verify() {
3240 if (failed(verifyAssignedPacket(
3241 *this, (*this)->getAttrOfType<PacketInfoAttr>("controller_id"),
3242 "controller_id")))
3243 return failure();
3244 const auto &targetModel = getTargetModel(*this);
3245 int columns = targetModel.columns();
3246 int rows = targetModel.rows();
3247
3248 // Only verify col/row bounds if they are specified
3249 if (auto col = getCol()) {
3250 if (*col >= columns)
3251 return emitOpError("column index (")
3252 << *col
3253 << ") must be less than the number of columns in the device ("
3254 << columns << ")";
3255 }
3256 if (auto row = getRow()) {
3257 if (*row >= rows)
3258 return emitOpError("row index (")
3259 << *row << ") must be less than the number of rows in the device ("
3260 << rows << ")";
3261 }
3262
3263 // Check that the specified tile type exists on the target device
3264 AIETileType tileType = getTileType();
3265 bool tileTypeExists = false;
3266 for (int col = 0; col < columns && !tileTypeExists; col++) {
3267 for (int row = 0; row < rows && !tileTypeExists; row++) {
3268 if (targetModel.getTileType(col, row) == tileType)
3269 tileTypeExists = true;
3270 }
3271 }
3272 if (!tileTypeExists) {
3273 return emitOpError("tile type '")
3274 << stringifyAIETileType(tileType)
3275 << "' does not exist on the target device";
3276 }
3277
3278 // Check logical tile type matches coordinates on device
3279 // Only validate when both col and row are specified
3280 if (auto col = tryGetCol()) {
3281 if (auto row = tryGetRow()) {
3282 if (targetModel.getTileType(*col, *row) != tileType) {
3283 return emitOpError("declared logical tile type does not match "
3284 "the tile type at coordinates (")
3285 << *col << ", " << *row << ")";
3286 }
3287 }
3288 }
3289
3290 return success();
3291}
3292
3293TileID LogicalTileOp::getCanonicalTileID() {
3294 const auto &targetModel = getTargetModel(*this);
3295
3296 // If col and row are both specified, use them directly
3297 std::optional<int32_t> col = getCol();
3298 std::optional<int32_t> row = getRow();
3299 if (col.has_value() && row.has_value())
3300 return {*col, *row};
3301
3302 // Otherwise, find a representative tile of the given type
3303 AIETileType tileType = getTileType();
3304 for (int col = 0; col < targetModel.columns(); col++) {
3305 for (int row = 0; row < targetModel.rows(); row++) {
3306 if (targetModel.getTileType(col, row) == tileType) {
3307 return {col, row};
3308 }
3309 }
3310 }
3311 llvm_unreachable("No tile of matching tile type found in AIE device");
3312}
3313
3314size_t LogicalTileOp::getNumSourceConnections(WireBundle bundle) {
3315 const auto &targetModel = getTargetModel(*this);
3316 TileID tile = getCanonicalTileID();
3317
3318 if (bundle == WireBundle::Core || bundle == WireBundle::DMA) {
3319 // Note dest is correct here, since direction is reversed.
3320 if (isShimNOCorPLTile())
3321 return targetModel.getNumDestShimMuxConnections(tile.col, tile.row,
3322 bundle);
3323 return targetModel.getNumDestSwitchboxConnections(tile.col, tile.row,
3324 bundle);
3325 }
3326 return 0;
3327}
3328
3329size_t LogicalTileOp::getNumDestConnections(WireBundle bundle) {
3330 const auto &targetModel = getTargetModel(*this);
3331 TileID tile = getCanonicalTileID();
3332
3333 if (bundle == WireBundle::Core || bundle == WireBundle::DMA) {
3334 // Note source is correct here, since direction is reversed.
3335 if (isShimNOCorPLTile())
3336 return targetModel.getNumDestShimMuxConnections(tile.col, tile.row,
3337 bundle);
3338 return targetModel.getNumSourceSwitchboxConnections(tile.col, tile.row,
3339 bundle);
3340 }
3341 return 0;
3342}
3343
3344std::optional<int> LogicalTileOp::tryGetCol() {
3345 if (auto col = getCol())
3346 return col;
3347 return std::nullopt;
3348}
3349
3350std::optional<int> LogicalTileOp::tryGetRow() {
3351 if (auto row = getRow())
3352 return row;
3353 return std::nullopt;
3354}
3355
3356//===----------------------------------------------------------------------===//
3357// Custom Printer and Parser for LogicalTileOp
3358//===----------------------------------------------------------------------===//
3359
3360ParseResult LogicalTileOp::parse(OpAsmParser &parser, OperationState &result) {
3361 AIETileType tileType;
3362 if (parser.parseLess())
3363 return failure();
3364
3365 StringRef tileTypeStr;
3366 if (parser.parseKeyword(&tileTypeStr))
3367 return failure();
3368
3369 auto tileTypeOpt = symbolizeAIETileType(tileTypeStr);
3370 if (!tileTypeOpt)
3371 return parser.emitError(parser.getCurrentLocation(),
3372 "unknown logical tile type: ")
3373 << tileTypeStr;
3374 tileType = *tileTypeOpt;
3375
3376 if (parser.parseGreater())
3377 return failure();
3378
3379 if (parser.parseLParen())
3380 return failure();
3381
3382 std::optional<int32_t> col;
3383 if (succeeded(parser.parseOptionalQuestion())) {
3384 // col is unspecified
3385 } else {
3386 int32_t colVal;
3387 if (parser.parseInteger(colVal))
3388 return failure();
3389 col = colVal;
3390 }
3391
3392 if (parser.parseComma())
3393 return failure();
3394
3395 std::optional<int32_t> row;
3396 if (succeeded(parser.parseOptionalQuestion())) {
3397 // row is unspecified
3398 } else {
3399 int32_t rowVal;
3400 if (parser.parseInteger(rowVal))
3401 return failure();
3402 row = rowVal;
3403 }
3404
3405 if (parser.parseRParen())
3406 return failure();
3407
3408 // Parse optional attributes
3409 if (parser.parseOptionalAttrDict(result.attributes))
3410 return failure();
3411
3412 // Add the parsed attributes to the result
3413 result.getOrAddProperties<LogicalTileOp::Properties>().tile_type =
3414 AIETileTypeAttr::get(parser.getContext(), tileType);
3415 if (col)
3416 result.getOrAddProperties<LogicalTileOp::Properties>().col =
3417 parser.getBuilder().getI32IntegerAttr(*col);
3418 if (row)
3419 result.getOrAddProperties<LogicalTileOp::Properties>().row =
3420 parser.getBuilder().getI32IntegerAttr(*row);
3421
3422 // Add result type (index)
3423 result.addTypes(parser.getBuilder().getIndexType());
3424
3425 return success();
3426}
3427
3428void LogicalTileOp::print(OpAsmPrinter &printer) {
3429 printer << "<" << stringifyAIETileType(getTileType()) << ">";
3430
3431 printer << "(";
3432 if (auto col = getCol())
3433 printer << *col;
3434 else
3435 printer << "?";
3436 printer << ", ";
3437 if (auto row = getRow())
3438 printer << *row;
3439 else
3440 printer << "?";
3441 printer << ")";
3442
3443 SmallVector<StringRef, 3> elidedAttrs = {"tile_type", "col", "row"};
3444 printer.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
3445}
3446
3447//===----------------------------------------------------------------------===//
3448// TileOp
3449//===----------------------------------------------------------------------===//
3450
3451LogicalResult TileOp::verify() {
3452 if (failed(verifyAssignedPacket(
3453 *this, (*this)->getAttrOfType<PacketInfoAttr>("controller_id"),
3454 "controller_id")))
3455 return failure();
3456 const auto &targetModel = getTargetModel(*this);
3457 int columns = targetModel.columns();
3458 int rows = targetModel.rows();
3459 if (colIndex() >= columns)
3460 return emitOpError("column index (")
3461 << colIndex()
3462 << ") must be less than the number of columns in the device ("
3463 << columns << ")";
3464 if (rowIndex() >= rows)
3465 return emitOpError("row index (")
3466 << rowIndex()
3467 << ") must be less than the number of rows in the device (" << rows
3468 << ")";
3469
3470 auto users = getResult().getUsers();
3471 bool found = false;
3472 for (auto *user : users) {
3473 if (llvm::isa<SwitchboxOp>(*user)) {
3474 if (found)
3475 return emitOpError("can only have one switchbox");
3476 found = true;
3477 }
3478 }
3479
3480 return success();
3481}
3482
3483size_t TileOp::getNumSourceConnections(WireBundle bundle) {
3484 const auto &targetModel = getTargetModel(*this);
3485 if (bundle == WireBundle::Core || bundle == WireBundle::DMA)
3486 // Note dest is correct here, since direction is reversed.
3487 {
3488 // Note dest is correct here, since direction is reversed.
3489 if (isShimNOCorPLTile())
3490 return targetModel.getNumDestShimMuxConnections(getCol(), getRow(),
3491 bundle);
3492 return targetModel.getNumDestSwitchboxConnections(getCol(), getRow(),
3493 bundle);
3494 }
3495 return 0;
3496}
3497
3498size_t TileOp::getNumDestConnections(WireBundle bundle) {
3499 const auto &targetModel = getTargetModel(*this);
3500 if (bundle == WireBundle::Core || bundle == WireBundle::DMA)
3501 // Note source is correct here, since direction is reversed.
3502 {
3503 // Note source is correct here, since direction is reversed.
3504 if (isShimNOCorPLTile())
3505 return targetModel.getNumDestShimMuxConnections(getCol(), getRow(),
3506 bundle);
3507 return targetModel.getNumSourceSwitchboxConnections(getCol(), getRow(),
3508 bundle);
3509 }
3510 return 0;
3511}
3512
3513std::optional<int> TileOp::tryGetCol() { return getCol(); }
3514std::optional<int> TileOp::tryGetRow() { return getRow(); }
3515
3516AIETileType TileOp::getTileType() {
3517 const auto &targetModel = getTargetModel(*this);
3518 return targetModel.getTileType(getCol(), getRow());
3519}
3520
3521static bool isLegalTileConnection(TileOp tile,
3522 const AIETargetModel &targetModel,
3523 MasterSetOp masterOp, PacketRulesOp slaveOp) {
3524 auto srcBundle = slaveOp.sourcePort().bundle;
3525 auto srcChan = slaveOp.sourcePort().channel;
3526 auto dstBundle = masterOp.destPort().bundle;
3527 auto dstChan = masterOp.destPort().channel;
3528 return targetModel.isLegalTileConnection(
3529 tile.colIndex(), tile.rowIndex(), srcBundle, srcChan, dstBundle, dstChan);
3530}
3531
3532static bool isLegalTileConnection(TileOp tile,
3533 const AIETargetModel &targetModel,
3534 ConnectOp connectOp) {
3535 auto srcBundle = connectOp.getSourceBundle();
3536 auto srcChan = connectOp.getSourceChannel();
3537 auto dstBundle = connectOp.getDestBundle();
3538 auto dstChan = connectOp.getDestChannel();
3539 return targetModel.isLegalTileConnection(
3540 tile.colIndex(), tile.rowIndex(), srcBundle, srcChan, dstBundle, dstChan);
3541}
3542
3543TileOp TileOp::getOrCreate(mlir::OpBuilder builder, DeviceOp device, int col,
3544 int row, std::optional<mlir::Location> loc) {
3545 TileOp tile = nullptr;
3546 // Find matching predefined tile at device top level, ...
3547 for (auto t : device.getOps<AIE::TileOp>()) {
3548 if (t.getRow() == row && t.getCol() == col) {
3549 tile = t;
3550 break;
3551 }
3552 }
3553 // ... or if undefined, create a new tile op
3554 if (!tile) {
3555 OpBuilder::InsertionGuard guard(builder);
3556 mlir::Block &device_start_block = *device.getBodyRegion().begin();
3557 builder.setInsertionPointToStart(&device_start_block);
3558 tile = TileOp::create(builder, loc.value_or(device.getLoc()),
3559 builder.getIndexType(), col, row);
3560 }
3561 return tile;
3562}
3563
3564//===----------------------------------------------------------------------===//
3565// ShimMuxOp
3566//===----------------------------------------------------------------------===//
3567
3568LogicalResult ShimMuxOp::verify() {
3569 // The port/connection checks below are keyed off the target model for a
3570 // placed tile. Before --aie-place-tiles the tile is still an
3571 // aie.logical_tile, so defer those checks to the post-placement re-verify --
3572 // mirroring SwitchboxOp::verify and the UsesAreAccessible trait.
3573 if (!isa<TileOp>(getTile().getDefiningOp()))
3574 return success();
3575
3576 Region &body = getConnections();
3577 DenseSet<Port> destset;
3578 if (body.empty())
3579 return emitOpError("should have non-empty body");
3580
3581 for (auto &ops : body.front()) {
3582 if (auto connectOp = dyn_cast<ConnectOp>(ops)) {
3583 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
3584 if (destset.count(dest))
3585 return connectOp.emitOpError("targets same destination ")
3586 << stringifyWireBundle(dest.bundle) << ": " << dest.channel
3587 << " as another connect operation";
3588 destset.insert(dest);
3589 } else if (isa<EndOp>(ops)) {
3590 // continue;
3591 } else {
3592 return ops.emitOpError("cannot be contained in a Switchbox op");
3593 }
3594 }
3595 return success();
3596}
3597
3598size_t ShimMuxOp::getNumSourceConnections(WireBundle bundle) {
3599 auto tile = getTileOp();
3600 const auto &targetModel = getTargetModel(*this);
3601 return targetModel.getNumSourceShimMuxConnections(tile.getCol(),
3602 tile.getRow(), bundle);
3603}
3604
3605size_t ShimMuxOp::getNumDestConnections(WireBundle bundle) {
3606 auto tile = getTileOp();
3607 const auto &targetModel = getTargetModel(*this);
3608 return targetModel.getNumDestShimMuxConnections(tile.getCol(), tile.getRow(),
3609 bundle);
3610}
3611
3612//===----------------------------------------------------------------------===//
3613// ShimDMAOp
3614//===----------------------------------------------------------------------===//
3615
3616LogicalResult ShimDMAOp::verify() {
3618 .failed())
3619 return failure();
3620 return success();
3621}
3622
3623TileOp ShimDMAOp::getTileOp() {
3624 return cast<TileElement>(this->getOperation()).getTileOp();
3625}
3626
3627LogicalResult PacketRulesOp::verify() {
3628 if (Region &body = getRules(); body.empty())
3629 return emitOpError("should have non-empty body");
3630 return success();
3631}
3632
3633LogicalResult MasterSetOp::verify() {
3634 int arbiter = -1;
3635 for (auto val : getAmsels()) {
3636 auto amsel = dyn_cast_if_present<AMSelOp>(val.getDefiningOp());
3637 if (!amsel)
3638 return emitOpError("amsel operand must be produced by an 'aie.amsel' op");
3639 if (arbiter != -1 && arbiter != amsel.arbiterIndex())
3640 return emitOpError("a master port can only be tied to one arbiter");
3641 arbiter = amsel.arbiterIndex();
3642 }
3643 return success();
3644}
3645
3646bool MasterSetOp::keepsPktHeader() {
3647 return AIE::keepsPktHeader(
3648 (*this)->getParentOfType<SwitchboxOp>().getTileOp().getTileID(),
3649 destPort(), getKeepPktHeader());
3650}
3651
3653 std::optional<bool> keep) {
3654 if (keep)
3655 return *keep;
3656 return master.bundle != WireBundle::DMA &&
3657 (tile.row != 0 || master.bundle != WireBundle::South);
3658}
3659
3660// A Core or DMA port a flow starts or ends at on a placed tile must exist
3661// there. The placer checks logical tiles' channel budgets.
3662static LogicalResult verifyFlowEndpoint(Operation *op, Value tile, Port port,
3663 bool isSource) {
3664 if (port.bundle != WireBundle::Core && port.bundle != WireBundle::DMA)
3665 return success();
3666 auto tileOp = dyn_cast_or_null<TileOp>(tile.getDefiningOp());
3667 if (!tileOp)
3668 return success();
3669 size_t channels = isSource ? tileOp.getNumDestConnections(port.bundle)
3670 : tileOp.getNumSourceConnections(port.bundle);
3671 if (static_cast<size_t>(port.channel) < channels)
3672 return success();
3673 return op->emitOpError() << (isSource ? "source " : "destination ")
3674 << stringifyWireBundle(port.bundle) << ":"
3675 << port.channel << " does not exist: tile ("
3676 << tileOp.getCol() << ", " << tileOp.getRow()
3677 << ") has " << channels << " "
3678 << stringifyWireBundle(port.bundle) << " port"
3679 << (channels == 1 ? "" : "s")
3680 << (isSource ? " into" : " out of")
3681 << " its stream switch";
3682}
3683
3684static LogicalResult verifyFlowVias(Operation *op, OperandRange vias,
3685 DenseI32ArrayAttr ingressBundles,
3686 DenseI32ArrayAttr ingressChannels,
3687 DenseI32ArrayAttr egressBundles,
3688 DenseI32ArrayAttr egressChannels) {
3689 size_t numVias = vias.size();
3690 DenseI32ArrayAttr arrays[] = {ingressBundles, ingressChannels, egressBundles,
3691 egressChannels};
3692 for (DenseI32ArrayAttr array : arrays) {
3693 size_t size = array ? array.size() : 0;
3694 if (size != numVias) {
3695 return op->emitOpError("has ")
3696 << numVias << " via tile(s) but a via port array of size " << size;
3697 }
3698 }
3699 if (numVias == 0) {
3700 return success();
3701 }
3702
3703 for (auto [index, via] : llvm::enumerate(vias)) {
3704 if (!isa_and_nonnull<TileLike>(via.getDefiningOp())) {
3705 return op->emitOpError("has via operand at index ")
3706 << index << " that is not defined by a tile-like operation";
3707 }
3708 }
3709
3710 auto verifyBundles = [&](DenseI32ArrayAttr bundles,
3711 StringRef direction) -> LogicalResult {
3712 for (auto [index, bundle] : llvm::enumerate(bundles.asArrayRef())) {
3713 if (bundle < 0 ||
3714 bundle > static_cast<int32_t>(getMaxEnumValForWireBundle())) {
3715 return op->emitOpError("has invalid via ")
3716 << direction << " bundle " << bundle << " at index " << index;
3717 }
3718 }
3719 return success();
3720 };
3721 auto verifyChannels = [&](DenseI32ArrayAttr channels,
3722 StringRef direction) -> LogicalResult {
3723 for (auto [index, channel] : llvm::enumerate(channels.asArrayRef())) {
3724 if (channel < 0) {
3725 return op->emitOpError("has negative via ")
3726 << direction << " channel " << channel << " at index " << index;
3727 }
3728 }
3729 return success();
3730 };
3731 if (failed(verifyBundles(ingressBundles, "ingress")) ||
3732 failed(verifyChannels(ingressChannels, "ingress")) ||
3733 failed(verifyBundles(egressBundles, "egress")) ||
3734 failed(verifyChannels(egressChannels, "egress"))) {
3735 return failure();
3736 }
3737 return success();
3738}
3739
3740LogicalResult FlowOp::verify() {
3741 if (failed(verifyFlowEndpoint(*this, getSource(),
3742 {getSourceBundle(), sourceIndex()}, true)))
3743 return failure();
3744 if (failed(verifyFlowEndpoint(*this, getDest(),
3745 {getDestBundle(), destIndex()}, false))) {
3746 return failure();
3747 }
3748
3749 return verifyFlowVias(*this, getVias(), getViaIngressBundlesAttr(),
3750 getViaIngressChannelsAttr(), getViaEgressBundlesAttr(),
3751 getViaEgressChannelsAttr());
3752}
3753
3754LogicalResult PacketSourceOp::verify() {
3755 return verifyFlowEndpoint(*this, getTile(), port(), true);
3756}
3757
3758LogicalResult PacketDestOp::verify() {
3759 return verifyFlowEndpoint(*this, getTile(), port(), false);
3760}
3761
3762LogicalResult PacketFlowOp::verify() {
3763 Region &body = getPorts();
3764 if (body.empty())
3765 return emitOpError("should have non-empty body");
3766
3767 int numSources = 0, numDests = 0;
3768 for (auto &ops : body.front()) {
3769 if (!isa<PacketSourceOp, PacketDestOp, EndOp>(ops))
3770 return ops.emitOpError("cannot be contained in a PacketFlow op");
3771 if (isa<PacketSourceOp>(ops))
3772 ++numSources;
3773 if (isa<PacketDestOp>(ops))
3774 ++numDests;
3775 }
3776
3777 if (numSources < 1)
3778 return emitOpError("must have at least one aie.packet_source");
3779 if (numDests < 1)
3780 return emitOpError("must have at least one aie.packet_dest");
3781 if (!getVias().empty() && numSources != 1) {
3782 return emitOpError(
3783 "with via waypoints must have exactly one aie.packet_source");
3784 }
3785 if (!getVias().empty() && numDests != 1) {
3786 return emitOpError(
3787 "with via waypoints must have exactly one aie.packet_dest");
3788 }
3789
3790 uint8_t id = getID();
3791 if (uint32_t maxId = getTargetModel(getOperation()).getMaxPacketId();
3792 id > maxId)
3793 return emitOpError("packet id ")
3794 << static_cast<int>(id) << " exceeds the maximum of " << maxId;
3795
3796 // A slave port accepts a packet when `incoming & mask == ID`, so a bit set
3797 // in ID and clear in the mask rejects every packet.
3798 if (std::optional<uint8_t> mask = getMask()) {
3799 if ((id & *mask) != id) {
3800 return emitOpError("has ID 0x")
3801 << llvm::utohexstr(id) << " outside mask 0x"
3802 << llvm::utohexstr(*mask) << ", which no packet can match";
3803 }
3804 }
3805
3806 return verifyFlowVias(*this, getVias(), getViaIngressBundlesAttr(),
3807 getViaIngressChannelsAttr(), getViaEgressBundlesAttr(),
3808 getViaEgressChannelsAttr());
3809}
3810
3811//===----------------------------------------------------------------------===//
3812// CoreOp
3813//===----------------------------------------------------------------------===//
3814
3815/// The channels a core's own tile has in `direction`.
3816static uint32_t channelsOfOwnTile(TileOp tile, DMAChannelDir direction,
3817 const AIETargetModel &targetModel) {
3818 int col = tile.getCol(), row = tile.getRow();
3819 if (direction == DMAChannelDir::MM2S)
3820 return targetModel.getNumSourceSwitchboxConnections(col, row,
3821 WireBundle::DMA);
3822 return targetModel.getNumDestSwitchboxConnections(col, row, WireBundle::DMA);
3823}
3824
3825LogicalResult DmaPushQueueOp::verify() {
3826 if (getOutOfOrder() && getDirection() != DMAChannelDir::S2MM)
3827 return emitOpError("out_of_order is only supported on S2MM channels");
3828 if (getBdId().has_value() == getBdTag().has_value())
3829 return emitOpError("names the descriptor it pushes either by bd_id or, "
3830 "before aie-assign-bd-ids runs, by bd_tag");
3831 auto core = (*this)->getParentOfType<CoreOp>();
3832 const AIETargetModel &targetModel = getTargetModel(*this);
3833 if (auto tile = core.getTile().getDefiningOp<TileOp>()) {
3834 uint32_t channels = channelsOfOwnTile(tile, getDirection(), targetModel);
3835 if (getChannel() >= static_cast<int32_t>(channels))
3836 return emitOpError("pushes onto ")
3837 << stringifyDMAChannelDir(getDirection()) << " channel "
3838 << getChannel() << ", but tile (" << tile.getCol() << ", "
3839 << tile.getRow() << ") has " << channels;
3840 }
3841 uint32_t maxRepeat = targetModel.getMaxRepeatCount();
3842 if (getRepeatCount() < 0 ||
3843 static_cast<uint32_t>(getRepeatCount()) > maxRepeat)
3844 return emitOpError("repeat_count must be in [0, ") << maxRepeat << "]";
3845 return success();
3846}
3847
3848LogicalResult DmaPollForQueueSpaceOp::verify() {
3849 auto core = (*this)->getParentOfType<CoreOp>();
3850 const AIETargetModel &targetModel = getTargetModel(*this);
3851 if (auto tile = core.getTile().getDefiningOp<TileOp>()) {
3852 uint32_t channels = channelsOfOwnTile(tile, getDirection(), targetModel);
3853 if (getChannel() >= static_cast<int32_t>(channels))
3854 return emitOpError("waits on ")
3855 << stringifyDMAChannelDir(getDirection()) << " channel "
3856 << getChannel() << ", but tile (" << tile.getCol() << ", "
3857 << tile.getRow() << ") has " << channels;
3858 }
3859 if (targetModel.getDmaTaskQueueDepth() == 0)
3860 return emitOpError("waits on a task queue, which this device has none of");
3861 return success();
3862}
3863
3864LogicalResult CoreOp::verify() {
3865 if (getBody().empty())
3866 return emitOpError("should have non-empty body");
3867 if (getElfFile()) {
3868 // If an ELF file is specified, no MLIR body is allowed (to remove
3869 // ambiguity); the ELF file will fully dictate what runs on the
3870 // core and any MLIR would be ignored.
3871 if (!isEmpty()) {
3872 return emitOpError(
3873 "When `elf_file` attribute is specified, core body must be empty "
3874 "(consist of exactly one `aie.end` op).");
3875 }
3876 }
3877 if (getLinkWith() && getLinkFiles())
3878 return emitOpError(
3879 "cannot specify both 'link_with' (deprecated) and 'link_files' "
3880 "on the same core; run aie-assign-core-link-files to migrate");
3881 if (getLinkWith() && getLinkMergeFiles())
3882 return emitOpError(
3883 "cannot specify both 'link_with' (deprecated) and 'link_merge_files' "
3884 "on the same core; run aie-assign-core-link-files to migrate");
3885 // An artifact is either merged into the core's LLVM module or handed to the
3886 // final link, never both: doing both would define its symbols twice.
3887 if (auto linkFiles = getLinkFiles())
3888 if (auto mergeFiles = getLinkMergeFiles()) {
3889 llvm::SmallSet<StringRef, 8> linked;
3890 for (auto f : linkFiles->getAsRange<StringAttr>())
3891 linked.insert(f.getValue());
3892 for (auto f : mergeFiles->getAsRange<StringAttr>())
3893 if (linked.count(f.getValue()))
3894 return emitOpError("artifact '")
3895 << f.getValue()
3896 << "' appears in both 'link_files' and 'link_merge_files'; an "
3897 "artifact must be either merged or linked, not both";
3898 }
3899 // The core's own sections live in a `core_data` aie.buffer, so the buffer
3900 // verifier covers their placement. Size and measured alignment belong here.
3901 if (auto measured = getMeasuredDataSize())
3902 if (auto declared = getDataSize(); declared && *declared < *measured)
3903 return emitOpError("data_size ")
3904 << *declared << " is smaller than the " << *measured
3905 << " bytes this core's linked sections occupy";
3906 // Where the stack sits. A pin the allocator could never honor is a user
3907 // constraint, so it is rejected here rather than at placement.
3908 const auto &targetModel = getTargetModel(*this);
3909 int64_t localMem = targetModel.getLocalMemorySize();
3910 auto validAlignment = [localMem](int64_t alignment) {
3911 return alignment > 0 && alignment <= localMem &&
3912 llvm::isPowerOf2_64(alignment);
3913 };
3914 if (auto alignment = getMeasuredDataAlignment();
3915 alignment && !validAlignment(*alignment))
3916 return emitOpError("measured_data_alignment must be a power of two between "
3917 "1 and ")
3918 << localMem << " bytes";
3919 if (auto alignments = getMeasuredBankAlignments())
3920 if (!llvm::all_of(*alignments, validAlignment))
3921 return emitOpError("measured_bank_alignments must contain only powers of "
3922 "two between 1 and ")
3923 << localMem << " bytes";
3924 MemoryRun stackRun = getStackRun();
3925 auto tile = dyn_cast_if_present<TileOp>(getTile().getDefiningOp());
3926 int64_t numBanks =
3927 tile ? targetModel.getNumBanks(tile.getCol(), tile.getRow()) : 0;
3928 int64_t bankSize =
3929 numBanks > 0 ? targetModel.getLocalMemorySize() / numBanks : 0;
3930 if (auto bank = getStackBank()) {
3931 if (tile && *bank >= numBanks)
3932 return emitOpError("stack_bank ")
3933 << *bank << " does not exist; this tile has " << numBanks
3934 << " banks";
3935 // Bank-specific stack accesses must stay inside the selected bank.
3936 if (bankSize > 0 && stackRun.size > bankSize)
3937 return emitOpError("stack_bank pins a ")
3938 << stackRun.size << "-byte stack to bank " << *bank
3939 << ", which holds " << bankSize
3940 << " bytes; omit stack_bank and stack_address for legacy "
3941 "placement";
3942 if (getStackAddress() && bankSize > 0 && stackRun.start / bankSize != *bank)
3943 return emitOpError("stack_address 0x")
3944 << llvm::utohexstr(stackRun.start) << " lies in bank "
3945 << stackRun.start / bankSize << ", but stack_bank requests bank "
3946 << *bank;
3947 if (getStackAddress() && bankSize > 0 &&
3948 stackRun.end() > (*bank + 1) * bankSize)
3949 return emitOpError("a ")
3950 << stackRun.size << "-byte stack at 0x"
3951 << llvm::utohexstr(stackRun.start) << " runs past stack_bank "
3952 << *bank << " (ending at 0x"
3953 << llvm::utohexstr((*bank + 1) * bankSize) << ")";
3954 }
3955 // Checked last so they do not pre-empt the diagnostics above on an op with
3956 // more than one defect. Size and placement are separate faults: a stack can
3957 // fit the tile yet be placed so it runs off the end.
3958 if (stackRun.size >= localMem)
3959 return emitOpError(getStackSize() ? "stack_size "
3960 : "measured stack requirement ")
3961 << stackRun.size
3962 << " leaves no local memory for this tile's buffers (" << localMem
3963 << " bytes total)";
3964 if (stackRun.end() > localMem)
3965 return emitOpError("a ") << stackRun.size << "-byte stack at 0x"
3966 << llvm::utohexstr(stackRun.start)
3967 << " runs past this tile's local memory ("
3968 << localMem << " bytes total)";
3969 if (getStackAddress() &&
3970 stackRun.start % targetModel.getCoreStackAlignment() != 0)
3971 return emitOpError("stack_address must be aligned to ")
3972 << targetModel.getCoreStackAlignment()
3973 << " bytes for this target's stack ABI";
3974 return success();
3975}
3976
3977bool CoreOp::isEmpty() {
3978 Region &body = getBody();
3979 // Return iff. core body contains exactly one block with exactly one AIE.EndOp
3980 return (body.hasOneBlock() && body.front().getOperations().size() == 1 &&
3981 llvm::isa<AIE::EndOp>(body.front().front()));
3982}
3983
3984TileOp CoreOp::getTileOp() {
3985 return cast<TileElement>(this->getOperation()).getTileOp();
3986}
3987
3988uint32_t CoreOp::getEffectiveStackSize() {
3989 if (auto declared = getStackSize())
3990 return *declared;
3991 const auto &targetModel = getTargetModel(*this);
3992 uint32_t fallback = targetModel.getDefaultCoreStackSize();
3993 if (auto measured = getMeasuredStackSize())
3994 return std::max<uint32_t>(
3995 fallback,
3996 llvm::alignTo(*measured, targetModel.getCoreStackAlignment()));
3997 return fallback;
3998}
3999
4000MemoryRun CoreOp::getStackRun() {
4001 return {getStackAddress().value_or(0), getEffectiveStackSize()};
4002}
4003
4004//===----------------------------------------------------------------------===//
4005// BufferOp
4006//===----------------------------------------------------------------------===//
4007
4008int64_t BufferOp::getAllocationSize() {
4009 auto type = llvm::cast<MemRefType>(getType());
4010 DataLayout dataLayout = DataLayout::closest(getOperation());
4011 return type.getNumElements() * dataLayout.getTypeSize(type.getElementType());
4012}
4013
4014LogicalResult BufferOp::verify() {
4015 if (UsesAreAccessible::verifyTrait(*this).failed()) {
4016 return failure();
4017 }
4018
4019 // A logical tile stands for a placement the compiler has yet to choose, so
4020 // neither check below has a tile to reason about.
4021 auto tile = dyn_cast_if_present<TileOp>(getTile().getDefiningOp());
4022 if (!tile) {
4023 return success();
4024 }
4025
4026 // A tile has one data region, so the code that looks the buffer up takes the
4027 // first match.
4028 if (getCoreData()) {
4029 for (Operation *user : tile.getResult().getUsers()) {
4030 auto other = dyn_cast<BufferOp>(user);
4031 if (other && other != *this && other.getCoreData()) {
4032 return emitOpError("is a second core_data buffer on tile (")
4033 << tile.getCol() << ", " << tile.getRow()
4034 << "); the core's data region is one extent";
4035 }
4036 }
4037 }
4038
4039 // A pin the allocator cannot honor: the address is outside the bank, so one
4040 // of the two is stale. A zero-sized buffer covers no byte, and one at the top
4041 // of the tile holds the one-past-the-end address, which is in no bank.
4042 std::optional<int32_t> address = getAddress();
4043 std::optional<int32_t> memBank = getMemBank();
4044 if (address && memBank && getAllocationSize() > 0) {
4045 const auto &targetModel = getTargetModel(*this);
4046 int64_t numBanks = targetModel.getNumBanks(tile.getCol(), tile.getRow());
4047 int64_t memSize = tile.isMemTile() ? targetModel.getMemTileSize()
4048 : targetModel.getLocalMemorySize();
4049 if (numBanks > 0) {
4050 int64_t bankSize = memSize / numBanks;
4051 int64_t bankOfAddress = *address / bankSize;
4052 if (bankOfAddress != *memBank) {
4053 return emitOpError("address 0x")
4054 << llvm::utohexstr(*address) << " lies in bank " << bankOfAddress
4055 << ", but mem_bank requests bank " << *memBank;
4056 }
4057 }
4058 }
4059 return success();
4060}
4061
4062// FIXME: make address assignment for buffers explicit and move this function to
4063// an interface
4064int32_t xilinx::AIE::getBufferBaseAddress(Operation *bufOp) {
4065 if (auto buf = dyn_cast<BufferOp>(bufOp)) {
4066 std::optional<int32_t> address = buf.getAddress();
4067 assert(address.has_value() && "buffer must have address assigned");
4068 return *address;
4069 }
4070 if (isa_and_nonnull<ExternalBufferOp>(bufOp))
4071 llvm::report_fatal_error(
4072 "External buffer addresses are assigned at runtime.");
4073 llvm::report_fatal_error("unknown buffer type");
4074}
4075
4076void xilinx::AIE::collectTiles(DeviceOp &device,
4077 DenseMap<TileID, Operation *> &tiles) {
4078 for (auto tile : device.getOps<TileOp>()) {
4079 tiles[tile.getTileID()] = tile;
4080 }
4081}
4082
4084 DeviceOp &device,
4085 DenseMap<Operation *, SmallVector<BufferOp, 4>> &buffers) {
4086 for (BufferOp buffer : device.getOps<BufferOp>()) {
4087 Operation *tileOp = buffer.getTile().getDefiningOp();
4088 buffers[tileOp].push_back(buffer);
4089 }
4090}
4091
4092static void printBufferInitialValue(OpAsmPrinter &p, BufferOp op, Type type,
4093 Attribute initialValue) {
4094 if (op.getInitialValue()) {
4095 p << "= ";
4096 p.printAttributeWithoutType(initialValue);
4097 }
4098}
4099
4100static ParseResult parseBufferInitialValue(OpAsmParser &parser, Type &type,
4101 Attribute &initialValue) {
4102 auto memrefType = llvm::cast<MemRefType>(type);
4103 if (!memrefType.hasStaticShape())
4104 return parser.emitError(parser.getNameLoc())
4105 << "type should be static shaped memref, but got " << type;
4106
4107 if (parser.parseOptionalEqual())
4108 return success();
4109
4110 Type tensorType = mlir::memref::getTensorTypeFromMemRefType(memrefType);
4111 if (parser.parseAttribute(initialValue, tensorType))
4112 return failure();
4113 if (!llvm::isa<ElementsAttr>(initialValue))
4114 return parser.emitError(parser.getNameLoc())
4115 << "initial value should be an elements attribute";
4116 return success();
4117}
4118
4119TileOp BufferOp::getTileOp() {
4120 return cast<TileElement>(this->getOperation()).getTileOp();
4121}
4122
4123//===----------------------------------------------------------------------===//
4124// MemOp
4125//===----------------------------------------------------------------------===//
4126
4127LogicalResult MemOp::verify() {
4128 Region &body = getBody();
4130 .failed())
4131 return failure();
4132
4133 for (auto &bodyOp : body.getOps()) {
4134 if (auto allocOp = dyn_cast<memref::AllocOp>(bodyOp))
4135 if (!allocOp->getAttr("id"))
4136 return allocOp.emitOpError()
4137 << "allocOp in MemOp region should have an id attribute";
4138 }
4139 return success();
4140}
4141
4142TileOp MemOp::getTileOp() {
4143 return cast<TileElement>(this->getOperation()).getTileOp();
4144}
4145
4146//===----------------------------------------------------------------------===//
4147// MemTileDMAOp
4148//===----------------------------------------------------------------------===//
4149
4150LogicalResult MemTileDMAOp::verify() {
4151 assert(getOperation()->getNumRegions() == 1 &&
4152 "MemTileDMAOp has zero region!");
4153
4155 .failed())
4156 return failure();
4157
4158 for (auto &bodyOp : getBody().getOps()) {
4159 if (auto allocOp = dyn_cast<memref::AllocOp>(bodyOp)) {
4160 if (!allocOp->getAttr("id"))
4161 return allocOp.emitOpError()
4162 << "allocOp in MemTileDMAOp region should have an id attribute";
4163 }
4164 if (auto startOp = dyn_cast<DMAStartOp>(bodyOp)) {
4165 // An endpoint's channel is not known yet; allocation keeps one whose
4166 // BDs reach another tile's memory off the local-only channels.
4167 if (!startOp.getEndpoint() && startOp.getChannelIndex() > 3) {
4168 // Channels 4 and 5 in a memtile are restricted to only access local
4169 // buffers and locks.
4170
4171 // TODO: Move this code to the dialect
4172 // Set of blocks found to be reachable within a given region.
4173 llvm::SmallSet<Block *, 16> reachable;
4174 SmallVector<Block *, 16> worklist;
4175 Block *firstBD = startOp.getSuccessor(0);
4176 reachable.insert(firstBD);
4177 worklist.push_back(firstBD);
4178 while (!worklist.empty()) {
4179 Block *block = worklist.pop_back_val();
4180 if (block->empty())
4181 continue;
4182 auto successors = block->getTerminator()->getSuccessors();
4183 for (auto *i : successors) {
4184 if (!reachable.contains(i)) {
4185 reachable.insert(i);
4186 worklist.push_back(i);
4187 }
4188 }
4189 }
4190 for (Block *b : reachable) {
4191 for (DMABDOp bd : b->getOps<DMABDOp>()) {
4192 if (auto bufferOp = bd.getBufferOp();
4193 bufferOp.getTile() != getTile()) {
4194 InFlightDiagnostic err =
4195 bd.emitOpError()
4196 << "is reachable from DMA channel "
4197 << startOp.getChannelIndex()
4198 << " and attempts to access a non-local buffer\n";
4199 err.attachNote(startOp->getLoc()) << "channel";
4200 err.attachNote(bufferOp->getLoc()) << "buffer";
4201 return err;
4202 }
4203 }
4204 for (auto useLock : b->getOps<UseLockOp>()) {
4205 if (auto lockOp = useLock.getLockOp();
4206 lockOp.getTile() != getTile()) {
4207 InFlightDiagnostic err =
4208 useLock.emitOpError()
4209 << "is reachable from DMA channel "
4210 << startOp.getChannelIndex()
4211 << " and attempts to access a non-local lock\n";
4212 err.attachNote(startOp->getLoc()) << "channel";
4213 err.attachNote(lockOp->getLoc()) << "lock";
4214 return err;
4215 }
4216 }
4217 }
4218 }
4219 }
4220 }
4221
4222 return success();
4223}
4224
4225TileOp MemTileDMAOp::getTileOp() {
4226 return cast<TileElement>(this->getOperation()).getTileOp();
4227}
4228
4229//===----------------------------------------------------------------------===//
4230// DMAOp
4231//===----------------------------------------------------------------------===//
4232
4233static bool isBdPacketEnabled(DMABDOp bd) {
4234 if (bd.getPacket().has_value())
4235 return true;
4236 if (Block *blk = bd->getBlock())
4237 return !blk->getOps<DMABDPACKETOp>().empty();
4238 return false;
4239}
4240
4241LogicalResult xilinx::AIE::verifyAssignedPacket(Operation *op,
4242 PacketInfoAttr packet,
4243 StringRef what) {
4244 if (packet && !packet.isAssigned())
4245 return op->emitOpError()
4246 << what
4247 << " has no pkt_id; allocation assigns one, so a header at "
4248 "this level must carry it";
4249 return success();
4250}
4251
4252LogicalResult
4253xilinx::AIE::verifyDMABDOutOfOrderId(DMABDOp bd, bool packetEnabledByContext) {
4254 std::optional<int32_t> oooId = bd.getOutOfOrderId();
4255 if (!oooId.has_value())
4256 return success();
4257 const AIETargetModel &targetModel = getTargetModel(bd.getOperation());
4259 return bd.emitOpError("out_of_order_id is not supported on this device");
4260 if (!packetEnabledByContext && !isBdPacketEnabled(bd))
4261 return bd.emitOpError("out_of_order_id requires a packet-enabled BD");
4262 uint32_t maxOooId = targetModel.getMaxOutOfOrderId();
4263 if (*oooId < 0 || static_cast<uint32_t>(*oooId) > maxOooId)
4264 return bd.emitOpError("out_of_order_id must be in [0, ") << maxOooId << "]";
4265 return success();
4266}
4267
4268// BD iteration bounds. Values are true/element (aie-rt encodes value-1);
4269// size <= 1 disables iteration (stride ignored). The stride is checked in
4270// whole 32-bit words against the tile-specific step field. aiex.npu.writebd
4271// checks the same tile-correct step limit (getDmaBdStepBits) inline in its own
4272// raw-register terms.
4273LogicalResult xilinx::AIE::verifyDMABDIteration(DMABDOp bd,
4274 AIETileType tileType) {
4275 std::optional<BDIterationAttr> iter = bd.getIteration();
4276 if (!iter)
4277 return success();
4278 const AIETargetModel &targetModel = getTargetModel(bd.getOperation());
4280 return bd.emitOpError("BD iteration is not supported on this target");
4281 uint32_t size = iter->getSize(), current = iter->getCurrent();
4282 uint32_t maxSize = 1u << targetModel.getDmaBdIterBits(tileType);
4283 if (size < 1 || size > maxSize)
4284 return bd.emitOpError("BD iteration size must be in [1, ")
4285 << maxSize << "]";
4286 if (size > 1) {
4287 int64_t strideInBytes = static_cast<int64_t>(iter->getStride()) *
4288 bd.getBufferElementTypeWidthInBytes();
4289 if (strideInBytes % 4)
4290 return bd.emitOpError(
4291 "BD iteration stride must be aligned to 32-bit words");
4292 int64_t stepInWords = strideInBytes / 4;
4293 int64_t maxStep = 1LL << targetModel.getDmaBdStepBits(tileType);
4294 if (stepInWords < 1 || stepInWords > maxStep)
4295 return bd.emitOpError() << "BD iteration stride must be in [1, "
4296 << maxStep << "] 32-bit words";
4297 }
4298 if (current >= size)
4299 return bd.emitOpError("BD iteration current must be in [0, size)");
4300 return success();
4301}
4302
4303static LogicalResult verifyDMARepeatCount(Operation *op, int32_t repeatCount) {
4304 uint32_t maxRepeat = getTargetModel(op).getMaxRepeatCount();
4305 if (maxRepeat == 0) {
4306 if (repeatCount != 0)
4307 return op->emitOpError("repeat_count is not supported on this target");
4308 return success();
4309 }
4310 if (repeatCount < 0 || static_cast<uint32_t>(repeatCount) > maxRepeat)
4311 return op->emitOpError("repeat_count ")
4312 << repeatCount << " is out of range [0, " << maxRepeat
4313 << "] for this target";
4314 return success();
4315}
4316
4317LogicalResult
4318xilinx::AIE::verifyOutOfOrderChannel(Operation *op, DMAChannelDir dir,
4319 bool outOfOrder, ArrayRef<DMABDOp> bds,
4320 bool packetEnabledByContext) {
4321 if (!outOfOrder)
4322 return success();
4323 if (dir != DMAChannelDir::S2MM)
4324 return op->emitOpError("out_of_order is only valid on an S2MM channel");
4326 return op->emitOpError(
4327 "out-of-order S2MM DMA is not supported on this device");
4328 if (bds.empty())
4329 return op->emitOpError("out-of-order S2MM channel must have at least one "
4330 "receive buffer descriptor"); // else stall
4331 for (DMABDOp bd : bds) {
4332 if (!packetEnabledByContext && !isBdPacketEnabled(bd))
4333 return bd.emitOpError(
4334 "out-of-order S2MM receive buffer descriptor must be packet-enabled");
4335 if (bd.getOutOfOrderId().has_value())
4336 return bd.emitOpError(
4337 "out_of_order_id belongs on the sender buffer descriptor, not an "
4338 "out-of-order S2MM receive buffer descriptor");
4339 }
4340 // an inter-BD lock dependency can deadlock because arrival order is unknown
4341 DenseMap<Operation *, Operation *> releasedByRecvBd;
4342 for (DMABDOp bd : bds)
4343 for (auto useLock : bd->getBlock()->getOps<UseLockOp>())
4344 if (useLock.release())
4345 if (Operation *lockDef = useLock.getLock().getDefiningOp())
4346 releasedByRecvBd.try_emplace(lockDef, bd.getOperation());
4347 for (DMABDOp bd : bds)
4348 for (auto useLock : bd->getBlock()->getOps<UseLockOp>())
4349 if (useLock.acquire() || useLock.acquireGE())
4350 if (Operation *lockDef = useLock.getLock().getDefiningOp()) {
4351 auto it = releasedByRecvBd.find(lockDef);
4352 if (it != releasedByRecvBd.end() && it->second != bd.getOperation())
4353 return bd.emitOpError(
4354 "out-of-order S2MM prohibits inter-BD lock dependencies; "
4355 "can deadlock");
4356 }
4357 return success();
4358}
4359
4360LogicalResult xilinx::AIE::verifyBdLockPair(Block &block, bool outOfOrder,
4361 UseLockOp &acquire,
4362 UseLockOp &release) {
4363 acquire = nullptr;
4364 release = nullptr;
4365 auto reject = [&](UseLockOp at) {
4366 auto diag = at.emitOpError(
4367 "does not fit its buffer descriptor, which has one lock-acquire field "
4368 "and one lock-release field; a buffer descriptor block uses either no "
4369 "lock or one use_lock(acquire) and one use_lock(release)");
4370 if (outOfOrder)
4371 diag << ", or a use_lock(release) alone when out-of-order";
4372 return diag;
4373 };
4374 for (UseLockOp op : block.getOps<UseLockOp>()) {
4375 UseLockOp &slot = op.release() ? release : acquire;
4376 if (slot)
4377 return reject(op);
4378 slot = op;
4379 }
4380 if (!acquire && !release)
4381 return success();
4382 if (release && (acquire || outOfOrder))
4383 return success();
4384 return reject(acquire ? acquire : release);
4385}
4386
4387LogicalResult DMAOp::verify() {
4388 auto *parentOp = getOperation()->getParentOp();
4389 if (parentOp->getRegion(0).getBlocks().size() > 1)
4390 return emitOpError("DMAOp can only appear in single block region");
4391 if (!parentOp->getRegion(0).getOps<DMAStartOp>().empty())
4392 return emitOpError("DMAOp is not compatible with DMAStart ops");
4393 auto bdRegions = getBds();
4394 for (auto &bdRegion : bdRegions) {
4395 if (!bdRegion.hasOneBlock())
4396 return emitOpError("DMAOp regions must have only one block");
4397 auto bds = llvm::to_vector_of<DMABDOp>(bdRegion.front().getOps<DMABDOp>());
4398 if (bds.size() != 1)
4399 return emitOpError("DMAOp regions/blocks must have exactly one DMABDOp");
4400 auto useLocks =
4401 llvm::to_vector_of<UseLockOp>(bdRegion.front().getOps<UseLockOp>());
4402 if (useLocks.size() != 2)
4403 return emitOpError(
4404 "DMAOp regions/blocks must have exactly two UseLock ops");
4405 }
4406 if (getPadValue() != 0) {
4407 if (!isa<MemTileDMAOp>(parentOp))
4408 return emitOpError("pad_value is only supported on memtile DMA channels");
4409 if (getChannelDir() != DMAChannelDir::MM2S)
4410 return emitOpError("pad_value is only supported on MM2S DMA channels");
4411 if (!getTargetModel(getOperation()).isMemTilePadValueSupported())
4412 return emitOpError("pad_value requires the CONSTANT_PAD_VALUE register, "
4413 "unavailable on this target");
4414 }
4415 if (failed(verifyDMARepeatCount(getOperation(), getRepeatCount())))
4416 return failure();
4417 SmallVector<DMABDOp> bds;
4418 for (auto &bdRegion : getBds())
4419 llvm::append_range(bds, bdRegion.front().getOps<DMABDOp>());
4420 return verifyOutOfOrderChannel(getOperation(), getChannelDir(),
4421 getOutOfOrder(), bds);
4422}
4423
4424//===----------------------------------------------------------------------===//
4425// DMABDOp
4426//===----------------------------------------------------------------------===//
4427
4428BufferOp DMABDOp::getBufferOp() {
4429 return cast<BufferOp>(getBuffer().getDefiningOp());
4430}
4431
4432// The optional `: type` after an SSA operand, defaulting to `defaultType`, so
4433// the common width keeps its untyped spelling.
4434static ParseResult parseOptionalOperandType(OpAsmParser &parser, Type &type,
4435 Type defaultType) {
4436 type = defaultType;
4437 if (succeeded(parser.parseOptionalColon()))
4438 return parser.parseType(type);
4439 return success();
4440}
4441
4442static void printOptionallyTypedOperand(OpAsmPrinter &printer, Value operand,
4443 Type defaultType) {
4444 printer << operand;
4445 if (operand.getType() != defaultType)
4446 printer << " : " << operand.getType();
4447}
4448
4449// Parse/print hooks for the custom<DynamicScalar>($operand, $static_attr,
4450// type($operand)) directive: a single scalar that is either an SSA value
4451// (runtime, `%v` for i32 or `%v : type`) or a compile-time integer constant
4452// (folded into the attribute), so a constant never materializes an operand.
4453// The scalar analog of custom<TypedDynamicIndexList>.
4454static ParseResult
4455parseDynamicScalar(OpAsmParser &parser,
4456 std::optional<OpAsmParser::UnresolvedOperand> &operand,
4457 IntegerAttr &staticAttr, Type &type) {
4458 int64_t intValue;
4459 OptionalParseResult intResult = parser.parseOptionalInteger(intValue);
4460 if (intResult.has_value()) {
4461 if (failed(*intResult))
4462 return failure();
4463 staticAttr =
4464 parser.getBuilder().getI32IntegerAttr(static_cast<int32_t>(intValue));
4465 return success();
4466 }
4467 OpAsmParser::UnresolvedOperand op;
4468 if (parser.parseOperand(op) ||
4469 parseOptionalOperandType(parser, type, parser.getBuilder().getI32Type()))
4470 return failure();
4471 operand = op;
4472 return success();
4473}
4474
4475static void printDynamicScalar(OpAsmPrinter &printer, Operation *,
4476 Value operand, IntegerAttr staticAttr, Type) {
4477 if (operand)
4478 printOptionallyTypedOperand(printer, operand,
4479 IntegerType::get(operand.getContext(), 32));
4480 else
4481 printer << staticAttr.getInt();
4482}
4483
4485 OpAsmParser &parser,
4486 SmallVectorImpl<OpAsmParser::UnresolvedOperand> &values,
4487 DenseI64ArrayAttr &integers, SmallVectorImpl<Type> &types) {
4488 SmallVector<int64_t> ints;
4489 auto parseEntry = [&]() -> ParseResult {
4490 OpAsmParser::UnresolvedOperand operand;
4491 OptionalParseResult isOperand = parser.parseOptionalOperand(operand);
4492 if (!isOperand.has_value())
4493 return parser.parseInteger(ints.emplace_back());
4494 Type type;
4495 if (failed(*isOperand) ||
4496 parseOptionalOperandType(parser, type,
4497 parser.getBuilder().getI64Type()))
4498 return failure();
4499 values.push_back(operand);
4500 types.push_back(type);
4501 ints.push_back(ShapedType::kDynamic);
4502 return success();
4503 };
4504 if (parser.parseCommaSeparatedList(AsmParser::Delimiter::Square, parseEntry))
4505 return failure();
4506 integers = parser.getBuilder().getDenseI64ArrayAttr(ints);
4507 return success();
4508}
4509
4510void xilinx::AIE::printTypedDynamicIndexList(OpAsmPrinter &printer, Operation *,
4511 OperandRange values,
4512 ArrayRef<int64_t> integers,
4513 TypeRange) {
4514 printer << '[';
4515 auto value = values.begin();
4516 llvm::interleaveComma(integers, printer, [&](int64_t i) {
4517 if (!ShapedType::isDynamic(i)) {
4518 printer << i;
4519 return;
4520 }
4521 Value v = *value++;
4522 printOptionallyTypedOperand(printer, v,
4523 IntegerType::get(v.getContext(), 64));
4524 });
4525 printer << ']';
4526}
4527
4528// Split a scalar OpFoldResult into an operand (runtime value) or an i32
4529// attribute (compile-time constant). A null OpFoldResult leaves both unset.
4530static void splitScalarOfr(mlir::OpBuilder &builder, mlir::OpFoldResult ofr,
4531 mlir::Value &operand, mlir::IntegerAttr &attr) {
4532 operand = nullptr;
4533 attr = nullptr;
4534 if (!ofr)
4535 return;
4536 if (auto v = llvm::dyn_cast_if_present<mlir::Value>(ofr)) {
4537 operand = v;
4538 return;
4539 }
4540 auto intAttr =
4541 llvm::cast<mlir::IntegerAttr>(llvm::cast<mlir::Attribute>(ofr));
4542 attr = builder.getI32IntegerAttr(static_cast<int32_t>(intAttr.getInt()));
4543}
4544
4545void DMABDOp::buildMixed(mlir::OpBuilder &builder, mlir::OperationState &state,
4546 mlir::Value buffer, mlir::OpFoldResult offset,
4547 mlir::OpFoldResult len,
4548 llvm::ArrayRef<mlir::OpFoldResult> sizes,
4549 llvm::ArrayRef<mlir::OpFoldResult> strides,
4550 BDPadLayoutArrayAttr padDims, PacketInfoAttr packet) {
4551 // Split each mixed list into its dynamic operands + static array (with the
4552 // ShapedType::kDynamic sentinel for the runtime entries).
4553 llvm::SmallVector<int64_t> staticSizes, staticStrides;
4554 llvm::SmallVector<mlir::Value> dynSizes, dynStrides;
4555 mlir::dispatchIndexOpFoldResults(sizes, dynSizes, staticSizes);
4556 mlir::dispatchIndexOpFoldResults(strides, dynStrides, staticStrides);
4557
4558 // Leave the static arrays unset when there is no ND layout so they elide
4559 // from the printed form.
4560 DenseI64ArrayAttr staticSizesAttr =
4561 staticSizes.empty() ? DenseI64ArrayAttr{}
4562 : builder.getDenseI64ArrayAttr(staticSizes);
4563 DenseI64ArrayAttr staticStridesAttr =
4564 staticStrides.empty() ? DenseI64ArrayAttr{}
4565 : builder.getDenseI64ArrayAttr(staticStrides);
4566
4567 // A constant offset/len lands in the static_* attribute; a runtime value
4568 // becomes the operand (same operand-vs-attr split as sizes/strides).
4569 mlir::Value offsetVal, lenVal;
4570 mlir::IntegerAttr offsetAttr, lenAttr;
4571 splitScalarOfr(builder, offset, offsetVal, offsetAttr);
4572 splitScalarOfr(builder, len, lenVal, lenAttr);
4573
4574 build(builder, state, buffer, /*offset=*/offsetVal, /*len=*/lenVal,
4575 /*static_offset=*/offsetAttr, /*static_len=*/lenAttr,
4576 /*sizes=*/dynSizes, /*strides=*/dynStrides,
4577 /*static_sizes=*/staticSizesAttr,
4578 /*static_strides=*/staticStridesAttr,
4579 /*pad_dimensions=*/padDims,
4580 /*bd_id_val=*/nullptr,
4581 /*bd_id=*/nullptr,
4582 /*packet=*/packet,
4583 /*out_of_order_id=*/nullptr,
4584 /*bd_tag=*/nullptr,
4585 /*out_of_order_tag=*/nullptr,
4586 /*burst_length=*/nullptr,
4587 /*axcache=*/nullptr,
4588 /*iteration=*/nullptr,
4589 /*offset_parameter=*/nullptr,
4590 /*offset_state_table_idx=*/nullptr,
4591 /*length_parameter=*/nullptr,
4592 /*length_unit=*/nullptr,
4593 /*length_state_table_idx=*/nullptr,
4594 /*length_core_encoded=*/nullptr,
4595 /*next_bd_id=*/nullptr);
4596}
4597
4598void DMABDOp::buildWithConstants(mlir::OpBuilder &builder,
4599 mlir::OperationState &state,
4600 mlir::Value buffer, int32_t offset,
4601 int32_t len, BDDimLayoutArrayAttr dims,
4602 BDPadLayoutArrayAttr padDims,
4603 PacketInfoAttr packet) {
4604 // Constant offset/len flow to the static_offset/static_len attributes (no
4605 // arith.constant materialized). i32 matches AIEI32Attr, keeping the cast
4606 // sign-exact for negative offsets.
4607 mlir::OpFoldResult offsetOfr = builder.getI32IntegerAttr(offset);
4608 mlir::OpFoldResult lenOfr = builder.getI32IntegerAttr(len);
4609
4610 // Turn the outermost-first BDDimLayoutArrayAttr into all-constant
4611 // OpFoldResults for buildMixed.
4612 llvm::SmallVector<mlir::OpFoldResult> sizes, strides;
4613 if (dims) {
4614 for (BDDimLayoutAttr d : dims) {
4615 sizes.push_back(builder.getI64IntegerAttr(d.getSize()));
4616 strides.push_back(builder.getI64IntegerAttr(d.getStride()));
4617 }
4618 }
4619 buildMixed(builder, state, buffer, offsetOfr, lenOfr, sizes, strides, padDims,
4620 packet);
4621}
4622
4623llvm::SmallVector<mlir::OpFoldResult> DMABDOp::getMixedSizes() {
4624 return ::mlir::getMixedValues(
4625 getStaticSizes().value_or(llvm::ArrayRef<int64_t>{}), getSizes(),
4626 getContext());
4627}
4628
4629llvm::SmallVector<mlir::OpFoldResult> DMABDOp::getMixedStrides() {
4630 return ::mlir::getMixedValues(
4631 getStaticStrides().value_or(llvm::ArrayRef<int64_t>{}), getStrides(),
4632 getContext());
4633}
4634
4635LogicalResult DMABDOp::verifyMixedSizesAndStrides() {
4636 llvm::ArrayRef<int64_t> staticSizes =
4637 getStaticSizes().value_or(llvm::ArrayRef<int64_t>{});
4638 llvm::ArrayRef<int64_t> staticStrides =
4639 getStaticStrides().value_or(llvm::ArrayRef<int64_t>{});
4640 if (failed(mlir::verifyListOfOperandsOrIntegers(
4641 *this, "sizes", staticSizes.size(), staticSizes, getSizes())))
4642 return failure();
4643 if (failed(mlir::verifyListOfOperandsOrIntegers(
4644 *this, "strides", staticStrides.size(), staticStrides, getStrides())))
4645 return failure();
4646 if (staticSizes.size() != staticStrides.size())
4647 return emitOpError("expected the same number of sizes (")
4648 << staticSizes.size() << ") and strides (" << staticStrides.size()
4649 << ")";
4650 return success();
4651}
4652
4653std::optional<llvm::SmallVector<BDDimLayoutAttr>> DMABDOp::getFoldedDimensions(
4654 llvm::function_ref<mlir::InFlightDiagnostic()> emitError) {
4655 llvm::ArrayRef<int64_t> sizes =
4656 getStaticSizes().value_or(llvm::ArrayRef<int64_t>{});
4657 llvm::ArrayRef<int64_t> strides =
4658 getStaticStrides().value_or(llvm::ArrayRef<int64_t>{});
4659 if (sizes.size() != strides.size()) {
4660 emitError() << "expected the same number of sizes (" << sizes.size()
4661 << ") and strides (" << strides.size() << ")";
4662 return std::nullopt;
4663 }
4664 mlir::OperandRange dynSizes = getSizes(), dynStrides = getStrides();
4665 unsigned nextSize = 0, nextStride = 0;
4666 llvm::SmallVector<BDDimLayoutAttr> dims;
4667 dims.reserve(sizes.size());
4668 for (auto [s, t] : llvm::zip(sizes, strides)) {
4669 std::optional<int64_t> sc =
4670 ShapedType::isDynamic(s)
4671 ? mlir::getConstantIntValue(dynSizes[nextSize++])
4672 : s;
4673 std::optional<int64_t> tc =
4674 ShapedType::isDynamic(t)
4675 ? mlir::getConstantIntValue(dynStrides[nextStride++])
4676 : t;
4677 if (!sc || !tc) {
4678 emitError() << "buffer descriptor size/stride is a runtime value; a "
4679 "compile-time constant is required on this path";
4680 return std::nullopt;
4681 }
4682 dims.push_back(BDDimLayoutAttr::get(
4683 getContext(), static_cast<uint32_t>(*sc), static_cast<uint32_t>(*tc)));
4684 }
4685 return dims;
4686}
4687
4688std::optional<llvm::SmallVector<BDDimLayoutAttr>>
4689DMABDOp::getConstantDimensions() {
4690 return getFoldedDimensions([&]() { return emitOpError(); });
4691}
4692
4693// With dims the furthest index bounds the access (a stride-0 dim may transfer
4694// more elements than the buffer holds); without them the length does. A
4695// runtime-sequence argument's type does not bound the host buffer behind it.
4696LogicalResult DMABDOp::verifyTransferInBounds() {
4697 auto buffer = llvm::dyn_cast<MemRefType>(getBuffer().getType());
4698 if (!buffer || !buffer.hasStaticShape() ||
4699 llvm::isa<BlockArgument>(getBuffer()))
4700 return success();
4701 if ((getOffsetParameterAttr() || getOffsetStateTableIdxAttr()) && !hasLen())
4702 return emitOpError("offset_parameter requires an explicit `len` on a local "
4703 "buffer; the default whole-buffer length runs past the "
4704 "end for any nonzero runtime offset");
4705 std::optional<int32_t> offset = hasOffset() ? getConstantOffset() : 0;
4706 if (!offset || *offset < 0)
4707 return success();
4708 int64_t numElements = buffer.getNumElements();
4709 SmallVector<OpFoldResult> sizes = getMixedSizes();
4710 SmallVector<OpFoldResult> strides = getMixedStrides();
4711 if (sizes.empty()) {
4712 std::optional<int64_t> len =
4713 hasLen() ? std::optional<int64_t>(getConstantLen()) : numElements;
4714 if (!len || *len <= 0 || *offset + *len <= numElements)
4715 return success();
4716 InFlightDiagnostic err = emitOpError("transfer of ")
4717 << *len << " elements at offset " << *offset
4718 << " runs past the end of the " << numElements
4719 << "-element buffer";
4720 if (!hasLen())
4721 err.attachNote() << "an omitted `len` is the whole buffer; give the "
4722 "length to transfer from this offset";
4723 return err;
4724 }
4725 int64_t maxIdx = *offset;
4726 for (auto [size, stride] : llvm::zip(sizes, strides)) {
4727 std::optional<int64_t> s = getConstantIntValue(size);
4728 std::optional<int64_t> t = getConstantIntValue(stride);
4729 if (!s || !t)
4730 return success();
4731 maxIdx += *t * (*s - 1);
4732 }
4733 if (maxIdx < numElements)
4734 return success();
4735 return emitOpError() << "Specified stride(s) and size(s) result in out of "
4736 "bounds access in buffer, for index "
4737 << maxIdx << " in memref of length " << numElements
4738 << ".";
4739}
4740
4741// A BDDimLayoutAttr array (outermost-first) describes a contiguous row-major
4742// scan when the innermost stride is 1 and each outer stride equals the product
4743// of all inner sizes. Used by both DMABDOp verification and canonicalization.
4744int64_t xilinx::AIE::paddedSize(llvm::ArrayRef<BDDimLayoutAttr> dims,
4745 llvm::ArrayRef<BDPadLayoutAttr> pads,
4746 int64_t fallback) {
4747 if (dims.empty() || pads.empty())
4748 return fallback;
4749 int64_t size = 1;
4750 for (auto [dim, pad] : llvm::zip(dims, pads))
4751 size *= dim.getSize() + pad.getConstPadBefore() + pad.getConstPadAfter();
4752 return size;
4753}
4754
4755bool xilinx::AIE::isContiguousBDTransfer(llvm::ArrayRef<BDDimLayoutAttr> dims) {
4756 if (dims.empty())
4757 return true; // no ND layout = trivially contiguous
4758 // Innermost (last) stride must be 1.
4759 if (dims.back().getStride() != 1)
4760 return false;
4761 // Each outer stride must equal the product of all inner sizes.
4762 // Use uint64_t throughout to match the unsigned getSize()/getStride() types.
4763 uint64_t product = 1;
4764 for (int i = static_cast<int>(dims.size()) - 1; i >= 1; --i) {
4765 product *= dims[i].getSize();
4766 // A size-1 dimension's stride is irrelevant (it is never stepped).
4767 if (dims[i - 1].getSize() > 1 && dims[i - 1].getStride() != product)
4768 return false;
4769 }
4770 return true;
4771}
4772
4773std::optional<SmallVector<BDDimLayoutAttr>>
4774xilinx::AIE::normalizedBDDims(ArrayRef<BDDimLayoutAttr> dims, int64_t len) {
4775 SmallVector<BDDimLayoutAttr> kept;
4776 int64_t product = 1;
4777 for (BDDimLayoutAttr dim : dims) {
4778 product *= dim.getSize();
4779 if (dim.getSize() != 1)
4780 kept.push_back(dim);
4781 }
4782 if (kept.size() == dims.size() || len != product)
4783 return std::nullopt;
4784 if (isContiguousBDTransfer(kept))
4785 return SmallVector<BDDimLayoutAttr>{};
4786 return kept;
4787}
4788
4789// The firmware's UPDATE_REG reads the 32-bit length word with its low two bits
4790// cleared, adds the delta, and writes the sum back with them cleared again.
4791// The length word counts 32-bit words, so both the static length and every
4792// added unit must be multiples of four words (16 bytes) to survive.
4793FailureOr<int64_t> xilinx::AIE::getLengthUnitBytes(Operation *op,
4794 int64_t lengthUnit,
4795 BaseMemRefType buffer) {
4796 uint64_t elemBitWidth = buffer.getElementTypeBitWidth();
4797 if (elemBitWidth == 0 || (elemBitWidth % 8) != 0)
4798 return op->emitOpError("length_parameter requires a whole-byte element "
4799 "type");
4800 std::optional<int64_t> unitBytes =
4801 llvm::checkedMul(lengthUnit, static_cast<int64_t>(elemBitWidth / 8));
4802 if (!unitBytes || *unitBytes / 4 > std::numeric_limits<uint32_t>::max())
4803 return op->emitOpError("length_unit (")
4804 << lengthUnit
4805 << " elements) exceeds the 32-bit word count of a BD length";
4806 if (*unitBytes % 16 != 0)
4807 return op->emitOpError("length_unit must be a multiple of 16 bytes, got ")
4808 << *unitBytes << " bytes";
4809 return *unitBytes;
4810}
4811
4813 Operation *op, std::optional<int64_t> lengthUnit, BaseMemRefType buffer,
4814 std::optional<int64_t> lenElems, bool contiguous,
4815 llvm::ArrayRef<int64_t> innerSizes) {
4816 if (!lengthUnit)
4817 return op->emitOpError("length_parameter requires length_unit");
4818 if (failed(getLengthUnitBytes(op, *lengthUnit, buffer)))
4819 return failure();
4820 int64_t elemBytes = buffer.getElementTypeBitWidth() / 8;
4821 if (!lenElems)
4822 return op->emitOpError("length_parameter requires a constant length");
4823 if ((*lenElems * elemBytes) % 16 != 0)
4824 return op->emitOpError("length_parameter requires a static length that is "
4825 "a multiple of 16 bytes, got ")
4826 << *lenElems * elemBytes << " bytes";
4827 if (contiguous)
4828 return success();
4829 llvm::SmallVector<int64_t, 3> sizes(innerSizes);
4830 llvm::SmallVector<int64_t, 3> strides(3, 0);
4831 placeRuntimeLengthDimension(sizes, strides);
4832 if (sizes[2] <= 1)
4833 return op->emitOpError("length_parameter on a non-contiguous pattern "
4834 "requires its second or third dimension to have a "
4835 "size above one: the added length continues it");
4836 int64_t rowElems = sizes[0] * sizes[1];
4837 if (*lengthUnit % rowElems != 0)
4838 return op->emitOpError("length_unit (")
4839 << *lengthUnit << " elements) must be a multiple of the " << rowElems
4840 << " elements moved per step of the dimension the added length "
4841 "continues";
4842 return success();
4843}
4844
4846 llvm::MutableArrayRef<int64_t> sizes,
4847 llvm::MutableArrayRef<int64_t> strides) {
4848 if (sizes[2] > 1 || sizes[1] <= 1)
4849 return;
4850 sizes[2] = sizes[1];
4851 strides[2] = strides[1];
4852 sizes[1] = 1;
4853 strides[1] = 0;
4854}
4855
4857 Operation *op, const AIETargetModel &targetModel, int col, int row) {
4858 AIEArch arch = targetModel.getTargetArch();
4859 if (arch != AIEArch::AIE2 && arch != AIEArch::AIE2p)
4860 return op->emitOpError("length_parameter is only supported on AIE2 and "
4861 "AIE2P devices");
4862 if (!targetModel.isShimNOCTile(col, row))
4863 return op->emitOpError("length_parameter is only supported on shim NOC "
4864 "tiles, got tile (")
4865 << col << ", " << row << ")";
4866 const DmaBdLayout *layout = targetModel.getDmaBdLayout(col, row);
4867 if (!layout || layout->bufferLength.shift != 0 ||
4868 layout->bufferLength.width != 32)
4869 return op->emitOpError("length_parameter requires a BD whose buffer "
4870 "length fills a 32-bit register");
4871 return success();
4872}
4873
4874llvm::SmallVector<uint32_t> xilinx::AIE::getAssignedBdIds(DmaBody program) {
4875 llvm::SmallVector<uint32_t> ids;
4876 program.getDmaBody().walk([&](DMABDOp bd) {
4877 if (auto id = bd.getBdId())
4878 ids.push_back(*id);
4879 });
4880 return ids;
4881}
4882
4883LogicalResult DMABDOp::verify() {
4884 if (getOffsetParameterAttr() || getOffsetStateTableIdxAttr()) {
4885 uint64_t elemBitWidth = llvm::cast<BaseMemRefType>(getBuffer().getType())
4886 .getElementTypeBitWidth();
4887 if (elemBitWidth == 0 || (elemBitWidth % 8) != 0)
4888 return emitOpError("offset_parameter requires a whole-byte element type");
4889 }
4890
4891 if (getLengthParameterAttr() || getLengthStateTableIdxAttr()) {
4892 // The length is patched by a runtime-sequence instruction, so only a BD
4893 // the runtime sequence configures can have one.
4894 if (llvm::isa<MemOp, MemTileDMAOp, ShimDMAOp, DMAOp>(
4895 (*this)->getParentOp()))
4896 return emitOpError("length_parameter is only supported on a BD in a "
4897 "runtime sequence");
4898 std::optional<llvm::SmallVector<BDDimLayoutAttr>> dims =
4899 getFoldedDimensions([&]() {
4900 return emitOpError("length_parameter requires a static pattern: ");
4901 });
4902 if (!dims)
4903 return failure();
4904 // aie-decompose-large-dma-bd leaves a runtime-length BD whole.
4905 if (dims->size() > 4)
4906 return emitOpError("length_parameter requires at most 4 dimensions, got ")
4907 << dims->size();
4908 // The outermost of four dimensions is the iteration, which repeats the
4909 // whole length.
4910 llvm::ArrayRef<BDDimLayoutAttr> inner(*dims);
4911 if (inner.size() == 4)
4912 inner = inner.drop_front();
4913 llvm::SmallVector<int64_t, 3> innerSizes(3, 1);
4914 for (auto [i, dim] : llvm::enumerate(llvm::reverse(inner)))
4915 innerSizes[i] = dim.getSize();
4916 auto buffer = llvm::cast<BaseMemRefType>(getBuffer().getType());
4917 std::optional<int64_t> lenElems;
4918 if (std::optional<int32_t> len = getConstantLen())
4919 lenElems = *len;
4920 else if (!hasLen() && buffer.hasStaticShape())
4921 lenElems = buffer.getNumElements();
4922 if (failed(verifyLengthParameter(*this, getLengthUnit(), buffer, lenElems,
4924 innerSizes)))
4925 return failure();
4926 }
4927
4928 // Skip verification of the BDOp outside of mem operations.
4929 // BDOps may appear elsewhere and subsequent lowerings will place them in the
4930 // correct mem ops.
4931 Operation *p = (*this)->getParentOp();
4932 if (!llvm::isa<MemOp, MemTileDMAOp, ShimDMAOp, DMAOp>(*p)) {
4933 return success();
4934 }
4935
4936 // Check if buffer is an unranked memref (e.g., from function argument)
4937 bool isUnrankedMemRef = llvm::isa<UnrankedMemRefType>(getBuffer().getType());
4938
4939 // For unranked memrefs, we can't verify as strictly since we don't know
4940 // the buffer's defining op or its full type at compile time
4941 if (!isUnrankedMemRef) {
4942 if (!isa<BufferOp, ExternalBufferOp>(getBuffer().getDefiningOp()))
4943 return emitOpError(
4944 "BDs only support BufferOp or ExternalBufferOp operands.");
4945 }
4946
4947 if (getLenInBytes() % 4)
4948 return emitOpError("transfer length must be multiple of 4 (i.e., represent "
4949 "4 byte aligned address)");
4950
4951 TileElement parentTileElement = getParentTileElement(getOperation());
4952 TileLike parentTile = parentTileElement.getTileLike();
4953 if (!parentTile)
4954 return emitOpError("parent tile must implement TileLike interface");
4955
4956 if (!isUnrankedMemRef && getOperation()->getParentOfType<MemOp>() &&
4957 getBufferOp().getTile() != parentTileElement.getTile())
4958 return emitOpError(
4959 "Core tile DMAs can only access a buffer in the same tile.");
4960
4961 const AIETargetModel &targetModel = getTargetModel(getOperation());
4962
4963 uint32_t maxBds = targetModel.getNumBDs(parentTile.getTileType());
4964 if (std::optional<int32_t> bdId = getBdId();
4965 bdId.has_value() && static_cast<uint32_t>(*bdId) >= maxBds)
4966 return emitOpError("bdId attribute exceeds max: ") << maxBds - 1;
4967 if (std::optional<int32_t> nextBdId = getNextBdId();
4968 nextBdId.has_value() && static_cast<uint32_t>(*nextBdId) >= maxBds)
4969 return emitOpError("nextBdId attribute exceeds max: ") << maxBds - 1;
4970
4971 if (getBdIdVal() && getBdId().has_value())
4972 return emitOpError("bd_id and bd_id_val are mutually exclusive");
4973
4974 // Issue #1097: the buffer_length field of a DMA buffer descriptor has a
4975 // tile-type-specific bit width (e.g. on AIE2: 32-bit shim, 17-bit mem tile,
4976 // 14-bit core tile). The hardware field counts address-generation granules
4977 // (32-bit words), so convert the byte length accordingly and reject
4978 // transfers that would overflow the field (they would otherwise be silently
4979 // truncated during lowering). Only checkable when the length is statically
4980 // known: either an explicit constant `len`, or (when no `len` is given) a
4981 // statically-shaped buffer whose full size is used.
4982 {
4983 bool lenStaticallyKnown = getConstantLen().has_value();
4984 if (!lenStaticallyKnown && !hasLen()) {
4985 if (auto shaped = llvm::dyn_cast<mlir::ShapedType>(getBuffer().getType()))
4986 lenStaticallyKnown = shaped.hasStaticShape();
4987 }
4988 // Skip validation for non-positive constant lengths: negative values
4989 // indicate a separate issue (e.g. distribute-link offset inference) and
4990 // would wrap to huge values via getLenInBytes().
4991 if (lenStaticallyKnown) {
4992 if (auto constLen = getConstantLen();
4993 constLen.has_value() && *constLen <= 0) {
4994 // non-positive explicit length — skip overflow check
4995 } else {
4996 uint64_t lenInBytes = getLenInBytes();
4997 uint32_t granularity = targetModel.getAddressGenGranularity();
4998 if (granularity != 0) {
4999 uint64_t lenInWords = lenInBytes * 8 / granularity;
5000 uint64_t maxLen =
5001 targetModel.getDmaBdMaxLen(parentTile.getTileType());
5002 if (lenInWords > maxLen)
5003 return emitOpError()
5004 << "buffer descriptor length (" << lenInWords
5005 << " 32-bit words) exceeds the maximum of " << maxLen
5006 << " words supported by this tile type";
5007 }
5008 }
5009 }
5010 }
5011
5012 if (failed(verifyMixedSizesAndStrides()) || failed(verifyTransferInBounds()))
5013 return failure();
5014
5015 // Fold the mixed sizes/strides to a constant BDDimLayoutAttr list for
5016 // verification; a runtime size/stride yields nullopt plus a diagnostic.
5017 std::optional<llvm::SmallVector<BDDimLayoutAttr>> dims;
5018 if (!getMixedSizes().empty()) {
5019 dims = getConstantDimensions();
5020 if (!dims.has_value())
5021 return failure();
5022 }
5023 if (dims.has_value()) {
5024 // The per-BD ND access-pattern limit is a hardware property of the tile;
5025 // query it from the target model by tile type rather than inferring it
5026 // from the parent op type.
5027 size_t maxNDims = targetModel.getBDMaxDims(parentTile.getTileType());
5028 if (dims->size() > maxNDims)
5029 return emitOpError() << "Cannot give more than "
5030 << std::to_string(maxNDims)
5031 << " dimensions for step sizes and wraps on this "
5032 "tile (got "
5033 << std::to_string(dims->size()) << " dimensions).";
5034
5035 auto buffer = llvm::dyn_cast<MemRefType>(getBuffer().getType());
5036 if (!buffer)
5037 return emitOpError() << "dimensions attribute cannot be used with "
5038 "unranked memref buffer type.";
5039 // A contiguous row-major access on a shim tile is lowered to linear mode
5040 // by aie-dma-tasks-to-npu / aie-dma-to-npu, using the wide buffer_length
5041 // register which is exempt from the 10-bit ND wrap-size limit.
5042 // Skip the per-dimension size check when the BD is on a shim tile and the
5043 // access is contiguous, so the natural ND form can be written without
5044 // triggering a spurious verifier error before lowering.
5045 //
5046 // Note: the verifier early-exit above means we only reach this code when
5047 // the parent op is MemOp, MemTileDMAOp, ShimDMAOp, or DMAOp -- all of
5048 // which are TileElements, so parentTile is always non-null here.
5049 bool skipSizeCheck =
5050 parentTile.isShimTile() && xilinx::AIE::isContiguousBDTransfer(*dims);
5051
5052 // The wrap (size) and step (stride) field widths are tile-type specific
5053 // (e.g. on AIE2: core tiles have an 8-bit wrap, mem/shim tiles 10-bit).
5054 // Wrap fields are unbiased, so a W-bit wrap field admits sizes up to
5055 // 2^W - 1. Step fields are hardware-encoded as actual-1, so an S-bit
5056 // step field admits actual (unbiased, as declared here) strides up to
5057 // 2^S. Deriving the limits from the target model (instead of a single
5058 // hardcoded shim-sized constant) keeps this check correct for all tile
5059 // types, and computing the message from the same value it checks
5060 // against means the two can never disagree again.
5061 uint32_t wrapBits = targetModel.getDmaBdWrapBits(parentTile.getTileType());
5062 uint32_t stepBits = targetModel.getDmaBdStepBits(parentTile.getTileType());
5063 uint64_t maxSize = wrapBits > 0 ? (1ULL << wrapBits) - 1 : 0;
5064 uint64_t maxStride = stepBits > 0 ? (1ULL << stepBits) : 0;
5065
5066 for (BDDimLayoutAttr dim : *dims) {
5067 if (0 == dim.getStride() && dim.getSize() != 1)
5068 return emitOpError()
5069 << "Invalid step size; must be a positive integer.";
5070 if (dim.getStride() > buffer.getNumElements())
5071 return emitOpError() << "Step size " << std::to_string(dim.getStride())
5072 << " exceeds memref size "
5073 << std::to_string(buffer.getNumElements());
5074 if (!skipSizeCheck && dim.getSize() > maxSize)
5075 return emitOpError() << "Size may not exceed " << maxSize << ".";
5076 if (dim.getStride() > maxStride)
5077 return emitOpError() << "Stride may not exceed " << maxStride << ".";
5078 }
5079
5080 // Since streams read 32b words, there's no way to read eg 16b with stride
5081 // of 2 (ie lower halfs of each 32b). So force it to be 1 (and then in
5082 // CDODirect/XAIEV2 scale the size by 4/getBufferElementTypeWidthInBytes).
5083 if (getBufferElementTypeWidthInBytes() < 4 && dims->back().getStride() != 1)
5084 return emitOpError(
5085 "For <32b width datatypes, inner-most dim stride must be 1");
5086
5087 if (getBufferElementTypeWidthInBytes() > 4 && dims->back().getStride() != 1)
5088 return emitOpError(
5089 "For >32b width datatypes, inner-most dim stride must be 1");
5090
5091 // The hardware stepsize/wrap registers are denominated in 32-bit words.
5092 // lib/Targets/AIERT.cpp's static/CDO lowering converts element-
5093 // granularity strides/sizes to words by scaling by
5094 // elementWidthInBytes/4.0 and truncating via an unguarded static_cast;
5095 // when that scale factor isn't an integer, a declared value that isn't
5096 // itself a whole number of words is silently replaced by a different,
5097 // hardware-expressible value instead of being rejected. Reject those
5098 // values here instead, since they are genuinely inexpressible in the
5099 // hardware's word-granularity registers. The trigger condition is
5100 // "element width not a multiple of 4 bytes" rather than "< 4 bytes" so
5101 // that this also covers the `bfp` block-floating-point types, whose
5102 // widths (9 and 17 bytes) are >4 bytes but still not word multiples.
5103 // Dimensions with size == 1 are skipped: the loop runs once so stride is
5104 // never stepped and that dimension's size/stride do not affect hardware
5105 // address generation; word alignment is not required for them.
5106 //
5107 // Which dim is "innermost" is determined by the last dim with size > 1,
5108 // not by array position: `dims` is outermost-first, and trailing dims
5109 // with size == 1 (which never step) do not change which dim actually
5110 // performs the finest-granularity access. If every dim has size == 1,
5111 // there is no innermost dim to check and the whole block is skipped.
5112 int32_t elementWidthInBytes = getBufferElementTypeWidthInBytes();
5113 if (elementWidthInBytes % 4 != 0) {
5114 std::optional<size_t> effectiveInnermost;
5115 for (size_t i = 0; i < dims->size(); i++)
5116 if ((*dims)[i].getSize() > 1)
5117 effectiveInnermost = i;
5118 if (effectiveInnermost.has_value()) {
5119 for (size_t i = 0; i < dims->size(); i++) {
5120 BDDimLayoutAttr dim = (*dims)[i];
5121 if (dim.getSize() <= 1)
5122 continue;
5123 if (i == *effectiveInnermost) {
5124 // Effective innermost dim: the size (element count transferred
5125 // at this level) must itself be a whole number of 32-bit words.
5126 if ((dim.getSize() * elementWidthInBytes) % 4)
5127 return emitOpError()
5128 << "Innermost dim size (" << dim.getSize() << ") * "
5129 << elementWidthInBytes << "-byte element width = "
5130 << (dim.getSize() * elementWidthInBytes)
5131 << " bytes: innermost dim size must be a multiple of 4 "
5132 "bytes for sub-32b element types (the hardware size "
5133 "register is 32-bit-word granularity).";
5134 } else {
5135 // Dim outside the effective innermost dim: the stride (address
5136 // step between elements at this level) must itself be a whole
5137 // number of 32-bit words.
5138 if ((dim.getStride() * elementWidthInBytes) % 4)
5139 return emitOpError()
5140 << "Dim " << i << " stride (" << dim.getStride() << ") * "
5141 << elementWidthInBytes << "-byte element width = "
5142 << (dim.getStride() * elementWidthInBytes)
5143 << " bytes: non-innermost dim stride must be a multiple "
5144 "of 4 bytes for sub-32b element types (the hardware "
5145 "stepsize register is 32-bit-word granularity).";
5146 }
5147 }
5148 }
5149 }
5150 }
5151 if (auto paddims = getPadDimensions(); paddims.has_value()) {
5152 if (!dims.has_value())
5153 return emitOpError() << "Padding requires n-d data layouts expressed as"
5154 << " wrap(s) and stride(s).";
5155 if (!parentTile.isMemTile())
5156 return emitOpError() << "Padding is only supported by memtile dma bds.";
5157 if (dims->size() != paddims->size())
5158 return emitOpError() << "Mismatch number of dimensions between padding(s)"
5159 << " and wrap(s) and stride(s).";
5160 // Measured on npu2: with d3 in use, d2 padding is not applied (and d3 has
5161 // no padding fields).
5162 if (paddims->size() == 4)
5163 for (BDPadLayoutAttr pad : paddims->take_front(2))
5164 if (pad.getConstPadBefore() || pad.getConstPadAfter())
5165 return emitOpError()
5166 << "a four-dimensional walk pads only its two innermost "
5167 "dimensions; the DMA does not apply d2 or d3 padding";
5168 int actuallen = 1;
5169 for (unsigned i = 0; i < paddims->size(); i++) {
5170 auto dim = (*dims)[i];
5171 auto paddim = (*paddims)[i];
5172 actuallen *= paddim.getConstPadBefore() + paddim.getConstPadAfter() +
5173 dim.getSize();
5174 if (std::optional<int32_t> len = getConstantLen(); len.has_value()) {
5175 if (actuallen > *len)
5176 return emitOpError() << "Data exceeds len after padding.";
5177 } else if (getLen()) {
5178 return emitOpError()
5179 << "Padding with a runtime len operand is not yet supported; "
5180 "use a compile-time constant len with padded BDs.";
5181 }
5182 }
5183 if ((paddims->back().getConstPadBefore() *
5184 getBufferElementTypeWidthInBytes()) %
5185 4)
5186 return emitOpError() << "Inner-most padding-before count must result in"
5187 << " padding in 32-bit words.";
5188 if ((paddims->back().getConstPadAfter() *
5189 getBufferElementTypeWidthInBytes()) %
5190 4)
5191 return emitOpError() << "Inner-most padding-after count must result in"
5192 << " padding in 32-bit words.";
5193 }
5194 if (!isUnrankedMemRef &&
5195 (parentTile.isMemTile() || parentTile.isCoreTile())) {
5196 if (auto baseAddr = getBufferOp().getAddress(); baseAddr.has_value()) {
5197 int64_t offsetInBytes = *baseAddr + getOffsetInBytes();
5198 if (offsetInBytes % 4)
5199 return emitOpError("bd address must be 4 byte (32b) aligned; got "
5200 "base+offset: ")
5201 << offsetInBytes << " (bytes)";
5202 }
5203 }
5204 if (auto packetInfo = getPacket()) {
5205 if (failed(verifyAssignedPacket(*this, *packetInfo)))
5206 return failure();
5207 if (packetInfo->getPktType() > 7)
5208 return emitOpError("Packet type field can only hold 3 bits.");
5209 if (packetInfo->assignedId() >
5210 getTargetModel(getOperation()).getMaxPacketId())
5211 return emitOpError("Packet ID field can only hold 5 bits.");
5212 }
5213
5214 if (failed(verifyDMABDOutOfOrderId(*this)))
5215 return failure();
5216
5217 // A runtime len operand or the static_len attribute both count as having a
5218 // length here.
5219 if (!hasLen() && !getBuffer().getType().hasStaticShape())
5220 return emitOpError() << "buffer with dynamic shape requires static length.";
5221
5222 if (getBurstLength() != 0 && !parentTile.isShimNOCTile())
5223 return emitOpError("Burst length is only supported in Shim NOC tiles that "
5224 "are connected to the memory-mapped NOC.");
5225
5226 if (auto axcache = getAxcache()) {
5227 if (!parentTile.isShimNOCTile()) {
5228 return emitOpError("AxCACHE is only supported in Shim NOC tiles "
5229 "that are connected to the memory-mapped NOC.");
5230 }
5231 if (axcache > 0xF) {
5232 return emitOpError("AxCache value out of 4-bit range.");
5233 }
5234 }
5235
5236 return verifyDMABDIteration(*this, parentTile.getTileType());
5237}
5238
5239LogicalResult DMABDPACKETOp::verify() {
5240 // Both fields read back signed (AIEI32Attr), so the range is two-sided even
5241 // though the hardware fields are unsigned: aie.dma_bd_packet(-1, -1) parses.
5242 if (getPacketType() < 0 || getPacketType() > 7)
5243 return emitOpError("Packet type field can only hold 3 bits.");
5244 if (getPacketID() < 0 ||
5245 getPacketID() >
5246 static_cast<int32_t>(getTargetModel(getOperation()).getMaxPacketId()))
5247 return emitOpError("Packet ID field can only hold 5 bits.");
5248 return success();
5249}
5250
5251uint32_t DMABDOp::getAxcacheOrDefault() {
5252 return getAxcache().value_or(
5253 getTargetModel(getOperation()).getDefaultAxCache());
5254}
5255
5256//===----------------------------------------------------------------------===//
5257// DMAStartOp
5258//===----------------------------------------------------------------------===//
5259
5260static LogicalResult FoldDMAStartOp(DMAStartOp op, PatternRewriter &rewriter) {
5261
5262 llvm::SetVector<Block *> reachable;
5263 SmallVector<Block *, 16> worklist;
5264 Block *firstBD = op.getSuccessor(0);
5265 reachable.insert(firstBD);
5266 worklist.push_back(firstBD);
5267 while (!worklist.empty()) {
5268 Block *block = worklist.pop_back_val();
5269 if (block->empty())
5270 continue;
5271 auto successors = block->getTerminator()->getSuccessors();
5272 for (auto *i : successors) {
5273 if (!reachable.contains(i)) {
5274 reachable.insert(i);
5275 worklist.push_back(i);
5276 }
5277 }
5278 }
5279
5280 // BD chain ends with an EndOp, indicating non-repeating pattern: BD chain
5281 // folding not applicable.
5282 if (isa<EndOp>((reachable.back())->getTerminator()))
5283 return failure();
5284
5285 // Check for identical bds.
5286 auto areEquivalentBDs = [](Block *b1, Block *b2) {
5287 auto b1OpRange = b1->without_terminator();
5288 auto b2OpRange = b2->without_terminator();
5289 if (llvm::range_size(b1OpRange) != llvm::range_size(b2OpRange))
5290 return false;
5291 if (!llvm::all_of(llvm::zip_equal(b1OpRange, b2OpRange),
5292 [](std::tuple<Operation &, Operation &> pair) {
5293 return OperationEquivalence::isEquivalentTo(
5294 &std::get<0>(pair), &std::get<1>(pair),
5295 OperationEquivalence::IgnoreLocations);
5296 }))
5297 return false;
5298 return true;
5299 };
5300
5301 // Get a vector of unique BDs.
5302 SmallVector<Block *> uniquePattern;
5303 const auto *patternIt = reachable.begin();
5304 while (patternIt != reachable.end() &&
5305 llvm::none_of(uniquePattern, [patternIt, areEquivalentBDs](Block *b1) {
5306 return areEquivalentBDs(*patternIt, b1);
5307 })) {
5308 uniquePattern.push_back(*patternIt);
5309 patternIt++;
5310 }
5311
5312 // No block repeats, so there is no sub-chain to collapse.
5313 if (patternIt == reachable.end())
5314 return failure();
5315
5316 unsigned idx = 0;
5317 while (patternIt != reachable.end()) {
5318 // BD repetition found. Check if repeating pattern.
5319 if (!areEquivalentBDs(*patternIt, uniquePattern[idx]))
5320 return failure();
5321 patternIt++;
5322 idx = (idx + 1) % uniquePattern.size();
5323 }
5324
5325 // Repeating BD chains detected. Erasing repetitions.
5326 auto lastBDTerm = cast<NextBDOp>(reachable.back()->getTerminator());
5327 auto lastUniqueBDTerm = cast<NextBDOp>(uniquePattern.back()->getTerminator());
5328 rewriter.modifyOpInPlace(lastUniqueBDTerm, [&] {
5329 lastUniqueBDTerm.setSuccessor(lastBDTerm.getSuccessor());
5330 });
5331
5332 return success();
5333}
5334
5335// Canonicalization pattern for DMABDOp: on shim tiles, fold a contiguous
5336// row-major ND access pattern into canonical linear form (no dimensions
5337// attribute), so that the hardware uses the wide buffer_length register.
5338namespace {
5339struct LinearizeContiguousBDTransfer : public mlir::OpRewritePattern<DMABDOp> {
5340 using OpRewritePattern::OpRewritePattern;
5341
5342 mlir::LogicalResult
5343 matchAndRewrite(DMABDOp op, mlir::PatternRewriter &rewriter) const override {
5344 if (op.getStaticSizes().value_or(llvm::ArrayRef<int64_t>{}).empty())
5345 return mlir::failure();
5346
5347 // Only fire for shim DMA BDs: ExternalBufferOp buffer
5348 // (dma_configure_task_for stage), parent TileElement is shim
5349 // (dma_configure_task stage), or inside ShimDMAOp (after full lowering).
5350 bool bufferIsExternal =
5351 isa_and_nonnull<ExternalBufferOp>(op.getBuffer().getDefiningOp());
5352 TileElement parentElem = getParentTileElement(op.getOperation());
5353 TileLike parentTileLike =
5354 parentElem ? parentElem.getTileLike() : TileLike{};
5355 bool parentIsShim = parentTileLike && parentTileLike.isShimTile();
5356 bool inShimDMA = (bool)op->getParentOfType<ShimDMAOp>();
5357 if (!bufferIsExternal && !parentIsShim && !inShimDMA)
5358 return mlir::failure();
5359
5360 // Only ND dimensions that are all-constant can be linearized; decline
5361 // silently on runtime-valued sizes/strides so valid dynamic IR doesn't get
5362 // a spurious diagnostic.
5363 auto isConstant = [](mlir::Value v) {
5364 return mlir::getConstantIntValue(v).has_value();
5365 };
5366 if (!llvm::all_of(op.getSizes(), isConstant) ||
5367 !llvm::all_of(op.getStrides(), isConstant))
5368 return mlir::failure();
5369 std::optional<llvm::SmallVector<BDDimLayoutAttr>> dims =
5370 op.getFoldedDimensions([&]() { return op.emitError(); });
5371 if (!dims.has_value() || dims->empty())
5372 return mlir::failure();
5374 return mlir::failure();
5375 // Already linear (single dimension with stride 1)?
5376 if (dims->size() == 1 && dims->front().getStride() == 1)
5377 return mlir::failure();
5378
5379 int64_t product = 1;
5380 for (BDDimLayoutAttr dim : *dims)
5381 product *= dim.getSize();
5382
5383 // len < product: the outermost dim describes a hardware BD iteration
5384 // (preserved downstream as iteration_size/stride). Don't fold it away.
5385 // A runtime len can't be compared, so bail. Also bail if len is absent but
5386 // the buffer is unranked (can't safely infer product == full transfer).
5387 if (!op.hasLen() && !op.getBuffer().getType().hasStaticShape())
5388 return mlir::failure();
5389 std::optional<int32_t> lenVal = op.getConstantLen();
5390 if (lenVal.has_value() && static_cast<int64_t>(*lenVal) != product)
5391 return mlir::failure();
5392 int32_t len = static_cast<int32_t>(product);
5393
5394 // Rewrite to a linear BD: len as the static_len attribute, sizes/strides
5395 // cleared. Other attributes are preserved.
5396 rewriter.modifyOpInPlace(op, [&]() {
5397 op.getLenMutable().clear();
5398 op.setStaticLen(len);
5399 op.getSizesMutable().clear();
5400 op.getStridesMutable().clear();
5401 op.setStaticSizes(std::nullopt);
5402 op.setStaticStrides(std::nullopt);
5403 });
5404 return mlir::success();
5405 }
5406};
5407} // namespace
5408
5409// Canonicalization pattern for DMABDOp: fold a constant offset/len/size/stride
5410// operand back into the corresponding static_* attribute, removing the operand.
5411namespace {
5412struct FoldConstantBDDimList : public mlir::OpRewritePattern<DMABDOp> {
5413 using OpRewritePattern::OpRewritePattern;
5414
5415 mlir::LogicalResult
5416 matchAndRewrite(DMABDOp op, mlir::PatternRewriter &rewriter) const override {
5417 if (op.getSizes().empty() && op.getStrides().empty() && !op.getOffset() &&
5418 !op.getLen())
5419 return mlir::failure();
5420 llvm::SmallVector<mlir::OpFoldResult> sizes = op.getMixedSizes();
5421 llvm::SmallVector<mlir::OpFoldResult> strides = op.getMixedStrides();
5422 // foldDynamicIndexList returns success() only if it replaced a dynamic
5423 // entry with a constant. Bitwise (not logical) OR is deliberate: both
5424 // calls must run unconditionally, so strides still folds even once
5425 // sizes already succeeded.
5426 // NOLINTNEXTLINE(clang-diagnostic-bitwise-instead-of-logical)
5427 bool changed = succeeded(mlir::foldDynamicIndexList(sizes)) |
5428 succeeded(mlir::foldDynamicIndexList(strides));
5429
5430 // Fold a constant offset/len operand into its static_* attribute.
5431 std::optional<int32_t> foldOffset, foldLen;
5432 if (op.getOffset())
5433 if (auto c = mlir::getConstantIntValue(op.getOffset()))
5434 foldOffset = static_cast<int32_t>(*c);
5435 if (op.getLen())
5436 if (auto c = mlir::getConstantIntValue(op.getLen()))
5437 foldLen = static_cast<int32_t>(*c);
5438 changed |= foldOffset.has_value() || foldLen.has_value();
5439
5440 if (!changed)
5441 return mlir::failure();
5442
5443 llvm::SmallVector<int64_t> staticSizes, staticStrides;
5444 llvm::SmallVector<mlir::Value> dynSizes, dynStrides;
5445 mlir::dispatchIndexOpFoldResults(sizes, dynSizes, staticSizes);
5446 mlir::dispatchIndexOpFoldResults(strides, dynStrides, staticStrides);
5447
5448 // Without `len`, a BD with runtime dims transfers their d0*d1*d2 extent,
5449 // but a fully static one transfers its whole buffer. Pin the extent
5450 // before the last runtime dim folds away so the transfer keeps its size.
5451 bool wasDynamic = !op.getSizes().empty() || !op.getStrides().empty();
5452 if (!op.hasLen() && wasDynamic && dynSizes.empty() && dynStrides.empty() &&
5453 !staticSizes.empty()) {
5454 int64_t extent = 1;
5455 for (int64_t s : llvm::ArrayRef(staticSizes).take_back(3))
5456 extent *= s;
5457 foldLen = static_cast<int32_t>(extent);
5458 }
5459
5460 rewriter.modifyOpInPlace(op, [&]() {
5461 op.getSizesMutable().assign(dynSizes);
5462 op.getStridesMutable().assign(dynStrides);
5463 // Leave the static arrays unset when there is no ND layout so they elide.
5464 if (staticSizes.empty())
5465 op.removeStaticSizesAttr();
5466 else
5467 op.setStaticSizes(staticSizes);
5468 if (staticStrides.empty())
5469 op.removeStaticStridesAttr();
5470 else
5471 op.setStaticStrides(staticStrides);
5472 if (foldOffset) {
5473 op.getOffsetMutable().clear();
5474 op.setStaticOffset(foldOffset);
5475 }
5476 if (foldLen) {
5477 op.getLenMutable().clear();
5478 op.setStaticLen(foldLen);
5479 }
5480 });
5481 return mlir::success();
5482 }
5483};
5484} // namespace
5485
5486void DMABDOp::getCanonicalizationPatterns(RewritePatternSet &results,
5487 MLIRContext *context) {
5488 results.add<FoldConstantBDDimList, LinearizeContiguousBDTransfer>(context);
5489}
5490
5491void DMAStartOp::getCanonicalizationPatterns(RewritePatternSet &results,
5492 MLIRContext *context) {
5493 results.add(FoldDMAStartOp);
5494}
5495
5496RouteEndpointOp DMAStartOp::getEndpointOp() {
5497 FlatSymbolRefAttr name = getEndpoint();
5498 if (!name)
5499 return nullptr;
5500 return SymbolTable::lookupNearestSymbolFrom<RouteEndpointOp>(*this, name);
5501}
5502
5503LogicalResult DMAStartOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
5504 FlatSymbolRefAttr name = getEndpoint();
5505 if (!name)
5506 return success();
5507 auto program = dyn_cast<DmaBody>((*this)->getParentOp());
5508 auto device = (*this)->getParentOfType<DeviceOp>();
5509 if (!program || !device)
5510 return emitOpError("only a tile's DMA program may name its channel by a "
5511 "route endpoint");
5512 auto endpoint = symbolTable.lookupSymbolIn<RouteEndpointOp>(device, name);
5513 if (!endpoint)
5514 return emitOpError() << name << " is not an aie.route_endpoint";
5515 if (endpoint.getBundle() != WireBundle::DMA)
5516 return emitOpError() << name << " names a "
5517 << stringifyWireBundle(endpoint.getBundle())
5518 << " port, not a DMA channel";
5519 auto here = cast<TileLike>(program.getTile().getDefiningOp());
5520 TileLike there = endpoint.getTileLike();
5521 bool sameTile = endpoint.getTile() == program.getTile() ||
5522 (here.tryGetCol() && here.tryGetRow() &&
5523 here.tryGetCol() == there.tryGetCol() &&
5524 here.tryGetRow() == there.tryGetRow());
5525 if (!sameTile)
5526 return emitOpError() << name
5527 << " is on a different tile than this DMA program";
5528 return success();
5529}
5530
5531LogicalResult DMAStartOp::verify() {
5532 if (getPadValue() != 0) {
5533 if (!isa<MemTileDMAOp>(getOperation()->getParentOp()))
5534 return emitOpError("pad_value is only supported on memtile DMA channels");
5535 if (getChannelDir() != DMAChannelDir::MM2S)
5536 return emitOpError("pad_value is only supported on MM2S DMA channels");
5537 if (!getTargetModel(getOperation()).isMemTilePadValueSupported())
5538 return emitOpError("pad_value requires the CONSTANT_PAD_VALUE register, "
5539 "unavailable on this target");
5540 }
5541 if (failed(verifyDMARepeatCount(getOperation(), getRepeatCount())))
5542 return failure();
5543 SmallVector<DMABDOp> bds;
5544 if (getOutOfOrder()) {
5545 llvm::SmallPtrSet<Block *, 8> visited;
5546 for (Block *b = getDest(); b && visited.insert(b).second;
5547 b = b->getNumSuccessors() > 0 ? b->getSuccessor(0) : nullptr)
5548 llvm::append_range(bds, b->getOps<DMABDOp>());
5549 }
5550 return verifyOutOfOrderChannel(getOperation(), getChannelDir(),
5551 getOutOfOrder(), bds);
5552}
5553
5554//===----------------------------------------------------------------------===//
5555// SwitchboxOp
5556//===----------------------------------------------------------------------===//
5557
5558LogicalResult SwitchboxOp::verify() {
5559 // The remaining checks (port bounds, legal-connection rules) are all keyed
5560 // off the target model for a placed tile. Before --aie-place-tiles runs the
5561 // tile is still an aie.logical_tile, so defer those checks to the
5562 // post-placement re-verify -- mirroring how UsesAreAccessible and FlowOp
5563 // skip target-model checks on logical tiles.
5564 if (!isa<TileOp>(getTile().getDefiningOp()))
5565 return success();
5566
5567 Region &body = getConnections();
5568 DenseSet<Port> sourceset;
5569 DenseSet<Port> destset;
5570 auto tile = getTileOp();
5571 const auto &targetModel = getTargetModel(tile);
5572 if (body.empty())
5573 return emitOpError("should have non-empty body");
5574 // A circuit connection claims its source port wherever it appears.
5575 for (auto connectOp : body.front().getOps<ConnectOp>())
5576 sourceset.insert({connectOp.getSourceBundle(), connectOp.sourceIndex()});
5577 for (auto &ops : body.front()) {
5578 // Would be simpler if this could be templatized.
5579 auto checkBound = [&ops](StringRef dir, WireBundle bundle, int index,
5580 int bound) -> LogicalResult {
5581 if (index >= bound) {
5582 if (bound > 0)
5583 return ops.emitOpError("index ")
5584 << index << " for " << dir << " bundle "
5585 << stringifyWireBundle(bundle) << " must be less than "
5586 << bound;
5587 return ops.emitOpError()
5588 << dir << " bundle " << stringifyWireBundle(bundle)
5589 << " not supported; index: " << index << ", bound: " << bound;
5590 }
5591 return success();
5592 };
5593
5594 if (auto connectOp = dyn_cast<ConnectOp>(ops)) {
5595 Port source = {connectOp.getSourceBundle(), connectOp.sourceIndex()};
5596
5597 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
5598 if (destset.count(dest)) {
5599 return connectOp.emitOpError()
5600 << "; connecting " << to_string(source) << " to "
5601 << to_string(dest) << " on "
5602 << to_string(this->getTileOp().getTileID())
5603 << " targets same dst as another connect op; existing "
5604 "destinations: "
5605 << llvm::join(llvm::map_range(
5606 destset, [](auto &p) { return to_string(p); }),
5607 ", ");
5608 }
5609 destset.insert(dest);
5610
5611 if (connectOp.sourceIndex() < 0)
5612 return connectOp.emitOpError("source index cannot be less than zero");
5613
5614 if (checkBound("source", connectOp.getSourceBundle(),
5615 connectOp.sourceIndex(),
5616 getNumSourceConnections(connectOp.getSourceBundle()))
5617 .failed())
5618 return failure();
5619
5620 if (connectOp.destIndex() < 0)
5621 return connectOp.emitOpError("dest index cannot be less than zero");
5622
5623 if (checkBound("dest", connectOp.getDestBundle(), connectOp.destIndex(),
5624 getNumDestConnections(connectOp.getDestBundle()))
5625 .failed())
5626 return failure();
5627
5628 // Stream switch connection constraints
5629 if (!isLegalTileConnection(tile, targetModel, connectOp))
5630 return connectOp.emitOpError("illegal stream switch connection");
5631
5632 } else if (auto connectOp = dyn_cast<MasterSetOp>(ops)) {
5633 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
5634 if (destset.count(dest))
5635 return connectOp.emitOpError("targets same destination ")
5636 << stringifyWireBundle(dest.bundle) << ": " << dest.channel
5637 << " as another connect or masterset operation";
5638 destset.insert(dest);
5639
5640 if (connectOp.destIndex() < 0)
5641 return connectOp.emitOpError("dest index cannot be less than zero");
5642
5643 if (checkBound("dest", connectOp.getDestBundle(), connectOp.destIndex(),
5644 getNumDestConnections(connectOp.getDestBundle()))
5645 .failed())
5646 return failure();
5647
5648 // The single-arbiter invariant is checked by MasterSetOp::verify.
5649 } else if (auto connectOp = dyn_cast<PacketRulesOp>(ops)) {
5650 Port source = {connectOp.getSourceBundle(), connectOp.sourceIndex()};
5651 if (sourceset.count(source))
5652 return connectOp.emitOpError("packet switched source ")
5653 << stringifyWireBundle(source.bundle) << source.channel
5654 << " cannot match another connect or masterset operation";
5655 sourceset.insert(source);
5656
5657 } else if (auto amselOp = dyn_cast<AMSelOp>(ops)) {
5658 std::vector<MasterSetOp> mstrs;
5659 std::vector<PacketRulesOp> slvs;
5660 for (auto *user : amselOp.getResult().getUsers()) {
5661 if (auto s = dyn_cast<PacketRuleOp>(user)) {
5662 auto pktRules = cast<PacketRulesOp>(s->getParentOp());
5663 slvs.push_back(pktRules);
5664 } else if (auto m = dyn_cast<MasterSetOp>(user))
5665 mstrs.push_back(m);
5666 }
5667 for (auto m : mstrs) {
5668 for (auto s : slvs) {
5669 // Stream switch connection constraints
5670 if (!isLegalTileConnection(tile, targetModel, m, s)) {
5671 return amselOp->emitOpError("illegal stream switch connection");
5672 }
5673 }
5674 }
5675 } else if (isa<EndOp>(ops)) {
5676 // continue;
5677 } else {
5678 return ops.emitOpError("cannot be contained in a Switchbox op");
5679 }
5680 }
5681
5682 return success();
5683}
5684
5685template <typename... ParentOpTypes>
5687 static LogicalResult verifyTrait(Operation *op) {
5688 Operation *operation = op->getParentOp();
5689 while (operation) {
5690 if (llvm::isa_and_nonnull<ParentOpTypes...>(operation))
5691 return success();
5692 operation = operation->getParentOp();
5693 }
5694 return failure();
5695 }
5696};
5697
5698TileOp LockOp::getTileOp() {
5699 return cast<TileElement>(this->getOperation()).getTileOp();
5700}
5701
5702LogicalResult LockOp::verify() {
5703 if (auto result = UsesAreAccessible::verifyTrait(*this); result.failed())
5704 return result;
5705
5706 if (auto lockID = getLockID()) {
5707 TileLike tileLike = getTileLike();
5708 if (!tileLike)
5709 return emitOpError("tile operand must implement TileLike interface");
5710 const auto &targetModel = getTargetModel(*this);
5711 auto tileType = tileLike.getTileType();
5712 if (int numLocks = targetModel.getNumLocks(tileType); *lockID >= numLocks)
5713 return emitOpError("lock assigned invalid id (maximum is ")
5714 << numLocks - 1 << ")";
5715 }
5716
5717 return success();
5718}
5719
5720// Look up for compile-time constant lock values, if any.
5721// Returns std::nullopt if lock value does not reference an `arith.constant`.
5722static std::optional<int32_t> getConstantLockValue(UseLockOp op) {
5723 if (auto constant = op.getValue().getDefiningOp<arith::ConstantOp>())
5724 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(constant.getValue()))
5725 return (int32_t)intAttr.getInt();
5726 return std::nullopt;
5727}
5728
5730 static LogicalResult verifyTrait(Operation *op) {
5731 auto *block = op->getBlock();
5732 int lockID = -1;
5733 for (auto op : block->getOps<UseLockOp>()) {
5734 if (auto lock = dyn_cast<LockOp>(op.getLock().getDefiningOp());
5735 lock.getLockID().has_value()) {
5736 if (lockID != -1 && lockID != lock.getLockIDValue())
5737 return failure();
5738 lockID = lock.getLockIDValue();
5739 }
5740 }
5741 return success();
5742 }
5743};
5744
5746 static LogicalResult verifyTrait(Operation *op) {
5747 auto *block = op->getBlock();
5748 int acqValue = -1, relValue = -1;
5749 for (auto op : block->getOps<UseLockOp>()) {
5750 // Non-constant lock values cannot be compared here; the passes that
5751 // require a constant enforce that separately via getConstantValue().
5752 auto value = getConstantLockValue(op);
5753 if (!value)
5754 continue;
5755 if (op.acquire() || op.acquireGE()) {
5756 if (acqValue != -1 && acqValue != *value) {
5757 return failure();
5758 }
5759 acqValue = *value;
5760 } else if (op.release()) {
5761 if (relValue != -1 && relValue != *value) {
5762 return failure();
5763 }
5764 relValue = *value;
5765 }
5766 }
5767 return success();
5768 }
5769};
5770
5772 static LogicalResult verifyTrait(Operation *op) {
5773 if (auto memOp = op->getParentOfType<MemOp>()) {
5774 auto useLock = cast<UseLockOp>(op);
5775 if (auto lock = useLock.getLockOp(); lock.getTile() != memOp.getTile())
5776 return failure();
5777 }
5778 return success();
5779 }
5780};
5781
5782LogicalResult UseLockOp::verify() {
5783 // AIE.useLock cannot be used at the top level
5784 if (llvm::isa_and_nonnull<DeviceOp, ModuleOp>((*this)->getParentOp()))
5785 return (*this)->emitOpError("must be used in a core or memory operation.");
5786
5787 const auto &targetModel = getTargetModel(*this);
5788 if (targetModel.getTargetArch() == AIEArch::AIE1 && acquireGE())
5789 return (*this)->emitOpError(
5790 "AcquireGreaterEqual is not supported in AIE1.");
5791
5792 // Locks used inside a DMA/BD block are configured via static register writes
5793 // and therefore require a compile-time constant value.
5795 .succeeded() &&
5796 !getConstantLockValue(*this))
5797 return (*this)->emitOpError(
5798 "lock value in a DMA/BD block must be a compile-time constant "
5799 "(defined by an arith.constant).");
5800
5801 // Otherwise, AIE.useLock should be inside MemOp, MemTileDMAOp, or
5802 // ShimDMAOp,
5804 .succeeded()) {
5805 if (!(*this)->getBlock())
5806 return (*this)->emitOpError("is not in a block.");
5807
5808 if (targetModel.getTargetArch() == AIEArch::AIE1 &&
5809 UsesOneLockInDMABlock::verifyTrait(*this).failed())
5810 return (*this)->emitOpError(
5811 "used in a DMA block that have multiple locks.");
5812
5814 return (*this)->emitOpError("acquires/releases the lock in a DMA block "
5815 "from/to multiple states.");
5816
5817 if (HasSomeParent<MemOp>::verifyTrait(*this).succeeded() &&
5818 AccessesLocalLocks::verifyTrait(*this).failed())
5819 return (*this)->emitOpError("can only access a lock in the same tile");
5820 return success();
5821
5822 // Or it can be in a CoreOp, or some FuncOp called from a CoreOp
5823 }
5824 if (HasSomeParent<CoreOp, func::FuncOp>::verifyTrait(*this).succeeded()) {
5825 return success();
5826 }
5827 // Or it can be in a DMAConfigureTaskOp or DMAConfigureTaskForOp (for runtime
5828 // DMA configuration). Check by operation name to avoid circular dependency
5829 // with AIEX dialect
5830 {
5831 Operation *operation = (*this)->getParentOp();
5832 while (operation) {
5833 StringRef name = operation->getName().getStringRef();
5834 if (name == "aiex.dma_configure_task" ||
5835 name == "aiex.dma_configure_task_for")
5836 return success();
5837 operation = operation->getParentOp();
5838 }
5839 }
5840 return (*this)->emitOpError()
5841 << "expects some parent op to be one of "
5842 << "AIE::device, AIE::core, func::func, AIE::mem, AIE::shimDMA, or "
5843 "AIEX::dma_configure_task";
5844}
5845
5846#include "aie/Dialect/AIE/IR/AIEEnums.cpp.inc"
5847#include "aie/Dialect/AIE/IR/AIEInterfaces.cpp.inc"
5848
5849//===----------------------------------------------------------------------===//
5850// TraceEventAttr
5851//===----------------------------------------------------------------------===//
5852
5853// Custom parser for TraceEventAttr value (uses shared helper)
5854static ParseResult parseTraceEventValue(AsmParser &parser, Attribute &value) {
5855 return xilinx::AIE::parseTraceEvent(parser, value);
5856}
5857
5858// Custom printer for TraceEventAttr value (uses shared helper)
5859static void printTraceEventValue(AsmPrinter &printer, Attribute value) {
5860 xilinx::AIE::printTraceEventEnum(printer, value);
5861}
5862
5863// Custom parser for TraceEventAttr value (uses shared helper)
5864static ParseResult parseTraceRegValue(OpAsmParser &parser, Attribute &value) {
5865
5866 // Try to parse as number
5867 int64_t intValue;
5868 OptionalParseResult parseResult = parser.parseOptionalInteger(intValue);
5869 if (parseResult.has_value() && succeeded(parseResult.value())) {
5870 MLIRContext *ctx = parser.getContext();
5871 value = IntegerAttr::get(IntegerType::get(ctx, 32), intValue);
5872 return success();
5873 }
5874 return xilinx::AIE::parseTraceEvent(parser, value);
5875}
5876
5877// Custom printer for TraceEventAttr value (uses shared helper)
5878static void printTraceRegValue(OpAsmPrinter &printer, Operation *op,
5879 Attribute value) {
5880 // if it's an intattr
5881 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(value)) {
5882 printer << intAttr.getInt();
5883 return;
5884 }
5885 xilinx::AIE::printTraceEventEnum(printer, value);
5886}
5887
5888// Helper to parse a LockBlocking enum keyword.
5889#define GET_OP_CLASSES
5890#include "aie/Dialect/AIE/IR/AIEOps.cpp.inc"
5891
5892FailureOr<int32_t> UseLockOp::getConstantValue() {
5893 if (auto value = getConstantLockValue(*this))
5894 return *value;
5895 return emitOpError("expected the lock value to be a compile-time constant "
5896 "(defined by an arith.constant).");
5897}
5898
5899LogicalResult UseLockOp::canonicalize(UseLockOp op, PatternRewriter &rewriter) {
5900 // An AcquireGreaterEqual by a compile-time-constant 0 is a no-op: the lock is
5901 // already >= 0 (semaphore values are non-negative) and it decrements the lock
5902 // by 0. A buffer descriptor's acquire is kept: its release needs the pair.
5903 if (op.acquireGE() && op->getBlock()->getOps<DMABDOp>().empty()) {
5904 if (auto value = getConstantLockValue(op); value && *value == 0) {
5905 rewriter.eraseOp(op);
5906 return success();
5907 }
5908 }
5909 return failure();
5910}
5911
5912TileOp SwitchboxOp::getTileOp() {
5913 return cast<TileElement>(this->getOperation()).getTileOp();
5914}
5915
5916size_t SwitchboxOp::getNumSourceConnections(WireBundle bundle) {
5917 auto tile = getTileOp();
5918 const auto &targetModel = getTargetModel(*this);
5919 return targetModel.getNumSourceSwitchboxConnections(tile.getCol(),
5920 tile.getRow(), bundle);
5921}
5922
5923size_t SwitchboxOp::getNumDestConnections(WireBundle bundle) {
5924 auto tile = getTileOp();
5925 const auto &targetModel = getTargetModel(*this);
5926 return targetModel.getNumDestSwitchboxConnections(tile.getCol(),
5927 tile.getRow(), bundle);
5928}
5929
5930TileOp ShimMuxOp::getTileOp() {
5931 return cast<TileElement>(this->getOperation()).getTileOp();
5932}
5933
5934WireBundle xilinx::AIE::getConnectingBundle(WireBundle dir) {
5935 switch (dir) {
5936 case WireBundle::North:
5937 return WireBundle::South;
5938 case WireBundle::South:
5939 return WireBundle::North;
5940 case WireBundle::East:
5941 return WireBundle::West;
5942 case WireBundle::West:
5943 return WireBundle::East;
5944 default:
5945 return dir;
5946 }
5947}
5948
5949std::optional<std::pair<TileID, Port>>
5951 TileID tile, Port port) {
5952 switch (port.bundle) {
5953 case WireBundle::North:
5954 tile.row++;
5955 break;
5956 case WireBundle::South:
5957 tile.row--;
5958 break;
5959 case WireBundle::East:
5960 tile.col++;
5961 break;
5962 case WireBundle::West:
5963 tile.col--;
5964 break;
5965 default:
5966 return std::nullopt;
5967 }
5968 if (!targetModel.isValidTile(tile))
5969 return std::nullopt;
5970 return std::pair(tile, Port{getConnectingBundle(port.bundle), port.channel});
5971}
5972
5973//===----------------------------------------------------------------------===//
5974// BDChainOp
5975//===----------------------------------------------------------------------===//
5976
5977ParseResult BDChainOp::parse(OpAsmParser &parser, OperationState &result) {
5978 SmallVector<OpAsmParser::Argument> entryArgs;
5979
5980 // Symbol name, e.g. @my_chain
5981 StringAttr symNameAttr;
5982 if (parser.parseSymbolName(symNameAttr, SymbolTable::getSymbolAttrName(),
5983 result.attributes)) {
5984 return failure();
5985 }
5986
5987 // Entry arguments (placeholders), e.g. (%addr: memref<1xi32>)
5988 ParseResult argParseResult = parser.parseCommaSeparatedList(
5989 OpAsmParser::Delimiter::Paren, [&]() -> ParseResult {
5990 OpAsmParser::Argument argument;
5991 if (parser.parseArgument(argument, true, true)) {
5992 return failure();
5993 }
5994 entryArgs.push_back(argument);
5995 return success();
5996 });
5997 if (argParseResult) {
5998 return argParseResult;
5999 }
6000
6001 // BD Chain Body
6002 auto *body = result.addRegion();
6003 ParseResult bodyParseResult = parser.parseRegion(*body, entryArgs, false);
6004 if (bodyParseResult) {
6005 return bodyParseResult;
6006 }
6007
6008 return success();
6009}
6010
6011void BDChainOp::print(OpAsmPrinter &printer) {
6012 auto taskName =
6013 (*this)
6014 ->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName())
6015 .getValue();
6016 printer << ' ';
6017 printer.printSymbolName(taskName);
6018
6019 Region &body = getRegion();
6020 auto argsIter = body.getArguments();
6021 printer << '(';
6022 for (auto *it = argsIter.begin(); it != argsIter.end(); ++it) {
6023 if (it != argsIter.begin()) {
6024 printer << ", ";
6025 }
6026 printer.printRegionArgument(*it);
6027 }
6028 printer << ')';
6029
6030 printer << ' ';
6031 printer.printRegion(body, false, true);
6032}
6033
6034//===----------------------------------------------------------------------===//
6035// ShimDMAAllocationOp
6036//===----------------------------------------------------------------------===//
6037
6038LogicalResult ShimDMAAllocationOp::verify() {
6039 TileLike tileLike = llvm::dyn_cast<TileLike>(getTile().getDefiningOp());
6040 if (!tileLike) {
6041 return emitOpError("tile operand must implement TileLike interface");
6042 }
6043
6044 if (!tileLike.isShimNOCorPLTile()) {
6045 // if placed, provide detailed error message
6046 auto col = tileLike.tryGetCol();
6047 auto row = tileLike.tryGetRow();
6048 if (col && row) {
6049 return emitOpError("tile must be a shim tile, but got tile(")
6050 << *col << ", " << *row << ")";
6051 }
6052 return emitOpError("tile must be a shim tile");
6053 }
6054
6055 return verifyAssignedPacket(*this, getPacketAttr());
6056}
6057
6058TileOp ShimDMAAllocationOp::getTileOp() {
6059 return dyn_cast_or_null<TileOp>(getTile().getDefiningOp());
6060}
6061
6062ShimDMAAllocationOp ShimDMAAllocationOp::getForSymbol(DeviceOp device,
6063 llvm::StringRef symbol) {
6064 Operation *maybeOp = device.lookupSymbol(symbol);
6065 if (maybeOp) {
6066 if (ShimDMAAllocationOp op = dyn_cast<ShimDMAAllocationOp>(maybeOp)) {
6067 return op;
6068 }
6069 }
6070 return nullptr;
6071}
6072
6073//===----------------------------------------------------------------------===//
6074// ObjectFifoRearmBindingOp
6075//===----------------------------------------------------------------------===//
6076
6077LogicalResult ObjectFifoRearmBindingOp::verify() {
6078 if (getChannelDirs().size() != getChannelTiles().size())
6079 return emitOpError("expected one channel_dirs entry per channel tile (")
6080 << getChannelTiles().size() << " tiles, " << getChannelDirs().size()
6081 << " dirs)";
6082 if (getChannelIndices().size() != getChannelTiles().size())
6083 return emitOpError("expected one channel_indices entry per channel tile (")
6084 << getChannelTiles().size() << " tiles, "
6085 << getChannelIndices().size() << " indices)";
6086 if (getLockInits().size() != getLocks().size())
6087 return emitOpError("expected one lock_inits entry per lock (")
6088 << getLocks().size() << " locks, " << getLockInits().size()
6089 << " inits)";
6090 // head_bd_ids / repeat_counts are optional (populated by
6091 // --aie-assign-bd-ids), but if present they are one-per-channel and travel as
6092 // a pair: the re-push needs both, so a binding carrying one without the other
6093 // is malformed.
6094 if (getHeadBdIds().has_value() != getRepeatCounts().has_value())
6095 return emitOpError("head_bd_ids and repeat_counts must both be set or both "
6096 "be absent");
6097 if (auto headBdIds = getHeadBdIds();
6098 headBdIds.has_value() && headBdIds->size() != getChannelTiles().size())
6099 return emitOpError("expected one head_bd_ids entry per channel tile (")
6100 << getChannelTiles().size() << " tiles, " << headBdIds->size()
6101 << " ids)";
6102 if (auto repeatCounts = getRepeatCounts();
6103 repeatCounts.has_value() &&
6104 repeatCounts->size() != getChannelTiles().size())
6105 return emitOpError("expected one repeat_counts entry per channel tile (")
6106 << getChannelTiles().size() << " tiles, " << repeatCounts->size()
6107 << " counts)";
6108 for (int32_t dir : getChannelDirs())
6109 if (dir != 0 && dir != 1)
6110 return emitOpError("channel_dirs entries must be 0 (S2MM) or 1 (MM2S), "
6111 "got ")
6112 << dir;
6113 // The lowering resolves each operand to its aie.tile / aie.lock, so reject a
6114 // binding whose operands are not those (otherwise the lowering would cast a
6115 // non-lock/non-tile operand and abort).
6116 for (Value tile : getChannelTiles()) {
6117 auto tileOp = tile.getDefiningOp<TileOp>();
6118 if (!tileOp)
6119 return emitOpError("channel_tiles operands must be aie.tile values");
6120 // A shim DMA endpoint is host-managed: the host re-pushes its BD program
6121 // and re-arms its locks. Re-arming it here would fight the host, so a
6122 // re-arm binding only ever records non-shim channels -- reject a shim tile
6123 // even in a hand-authored binding.
6124 if (tileOp.isShimTile())
6125 return emitOpError("channel_tiles must be non-shim tiles; a shim DMA "
6126 "endpoint is host-managed and is not re-armed here");
6127 }
6128 for (Value lock : getLocks())
6129 if (!lock.getDefiningOp<LockOp>())
6130 return emitOpError("locks operands must be aie.lock values");
6131 return success();
6132}
6133
6134//===----------------------------------------------------------------------===//
6135// RuntimeSequenceOp
6136//===----------------------------------------------------------------------===//
6137
6138ParseResult RuntimeSequenceOp::parse(OpAsmParser &parser,
6139 OperationState &result) {
6140
6141 // Name of this runtime sequence
6142 StringAttr nameAttr;
6143 (void)parser.parseOptionalSymbolName(
6144 nameAttr, mlir::SymbolTable::getSymbolAttrName(), result.attributes);
6145
6146 SmallVector<OpAsmParser::Argument> entryArgs;
6147
6148 // Entry arguments, e.g. (%addr: memref<1xi32>)
6149 ParseResult argParseResult = parser.parseCommaSeparatedList(
6150 OpAsmParser::Delimiter::Paren, [&]() -> ParseResult {
6151 OpAsmParser::Argument argument;
6152 if (parser.parseArgument(argument, true, true)) {
6153 return failure();
6154 }
6155 entryArgs.push_back(argument);
6156 return success();
6157 });
6158 if (argParseResult) {
6159 return argParseResult;
6160 }
6161
6162 // Optional `attributes { ... }` clause (before the body so it does not
6163 // conflict with the `{` that opens the region).
6164 if (parser.parseOptionalAttrDictWithKeyword(result.attributes))
6165 return failure();
6166
6167 // Body
6168 auto *body = result.addRegion();
6169 ParseResult bodyParseResult = parser.parseRegion(*body, entryArgs, false);
6170 if (bodyParseResult) {
6171 return bodyParseResult;
6172 }
6173
6174 return success();
6175}
6176
6177void RuntimeSequenceOp::print(OpAsmPrinter &printer) {
6178 Region &body = getRegion();
6179
6180 auto nameAttr = (*this)->getAttrOfType<StringAttr>(
6181 mlir::SymbolTable::getSymbolAttrName());
6182 if (nameAttr &&
6183 nameAttr != ::mlir::OpBuilder((*this)->getContext())
6184 .getStringAttr(getDefaultRuntimeSequenceName())) {
6185 printer << ' ';
6186 printer.printSymbolName(nameAttr);
6187 }
6188
6189 printer << '(';
6190 for (unsigned i = 0, n = body.getNumArguments(); i < n; i++) {
6191 if (i > 0) {
6192 printer << ", ";
6193 }
6194 printer.printRegionArgument(body.getArgument(i));
6195 }
6196 printer << ')';
6197
6198 printer.printOptionalAttrDictWithKeyword(
6199 (*this)->getAttrs(),
6200 /*elidedAttrs=*/{mlir::SymbolTable::getSymbolAttrName()});
6201
6202 printer << ' ';
6203 printer.printRegion(body, false, true);
6204}
6205
6206LogicalResult RuntimeSequenceOp::verify() {
6207 DeviceOp device = (*this)->getParentOfType<DeviceOp>();
6208 if (!device) {
6209 // this check is redudnant with the HasParent trait, but can't hurt
6210 (*this)->emitOpError() << "must be inside AIE device operation.";
6211 return failure();
6212 }
6213 return success();
6214}
6215
6216RuntimeSequenceOp
6217RuntimeSequenceOp::getForSymbolInDevice(DeviceOp deviceOp,
6218 llvm::StringRef symbol) {
6219 RuntimeSequenceOp runtimeSequenceOp;
6220 if (symbol.empty()) {
6221 auto range = deviceOp.getOps<RuntimeSequenceOp>();
6222 if (range.begin() == range.end()) {
6223 // No runtime sequence in the device; let the caller emit a diagnostic
6224 // rather than dereferencing an end iterator (which crashes).
6225 return nullptr;
6226 }
6227 runtimeSequenceOp = *range.begin();
6228 } else {
6229 Operation *maybeRuntimeSequenceOp =
6230 mlir::SymbolTable::lookupSymbolIn(deviceOp, symbol);
6231 if (!maybeRuntimeSequenceOp) {
6232 return nullptr;
6233 }
6234 runtimeSequenceOp =
6235 llvm::dyn_cast<RuntimeSequenceOp>(maybeRuntimeSequenceOp);
6236 }
6237 return runtimeSequenceOp;
6238}
6239
6240RuntimeSequenceOp
6241RuntimeSequenceOp::getForSymbolInDeviceOrError(DeviceOp deviceOp,
6242 llvm::StringRef symbol) {
6243 RuntimeSequenceOp runtimeSequenceOp = getForSymbolInDevice(deviceOp, symbol);
6244 if (!runtimeSequenceOp) {
6245 if (!symbol.empty()) {
6246 deviceOp.emitError("No such runtime sequence: ") << symbol;
6247 } else {
6248 deviceOp.emitError("No runtime sequence in device");
6249 }
6250 }
6251 return runtimeSequenceOp;
6252}
6253
6254LogicalResult RuntimeSequenceOp::verifyBeforeMaterialization() {
6255 // Check that all symbol references within the runtime sequence
6256 // are either to ShimDMAAllocationOp, DeviceOp or another RuntimeSequenceOp;
6257 // these are the only symbols that can be lowered with the NPU passes
6258 auto result = (*this)->walk([&](Operation *op) {
6259 for (NamedAttribute namedAttr : op->getAttrs()) {
6260 Attribute attr = namedAttr.getValue();
6261 auto walkResult = attr.walk([&](SymbolRefAttr symbolRef) {
6262 Operation *symbolDefOp =
6263 SymbolTable::lookupNearestSymbolFrom(*this, symbolRef);
6264 if (!symbolDefOp) {
6265 if (auto flat = dyn_cast<FlatSymbolRefAttr>(symbolRef)) {
6266 symbolDefOp = lookupNamedOp(*this, flat.getAttr());
6267 }
6268 }
6269 if (symbolDefOp) {
6270 if (!llvm::isa<ShimDMAAllocationOp>(symbolDefOp) &&
6271 !llvm::isa<DeviceOp>(symbolDefOp) &&
6272 !llvm::isa<RuntimeSequenceOp>(symbolDefOp) &&
6273 !llvm::isa<BufferOp>(symbolDefOp) &&
6274 !llvm::isa<ObjectFifoRearmBindingOp>(symbolDefOp) &&
6275 !llvm::isa<memref::GlobalOp>(symbolDefOp)) {
6276 op->emitOpError()
6277 << "references symbol '"
6278 << symbolRef.getRootReference().getValue()
6279 << "' which must be either a ShimDMAAllocationOp, DeviceOp, "
6280 "RuntimeSequenceOp, BufferOp, ObjectFifoRearmBindingOp, or "
6281 "GlobalOp, but got: "
6282 << symbolDefOp->getName().getStringRef();
6283 return WalkResult::interrupt();
6284 }
6285 if (BufferOp bufferOp = llvm::dyn_cast<BufferOp>(symbolDefOp)) {
6286 if (!bufferOp.getAddress()) {
6287 op->emitOpError()
6288 << "Unallocated buffer; fixed addresses are required before "
6289 "runtime sequence materialization.";
6290 return WalkResult::interrupt();
6291 }
6292 }
6293 }
6294 return WalkResult::advance();
6295 });
6296 if (walkResult.wasInterrupted()) {
6297 return WalkResult::interrupt();
6298 }
6299 }
6300 return WalkResult::advance();
6301 });
6302
6303 if (result.wasInterrupted()) {
6304 return failure();
6305 }
6306
6307 return success();
6308}
6309
6310// Include implementations for custom attributes
6311#define GET_ATTRDEF_CLASSES
6312#include "aie/Dialect/AIE/IR/AIEAttrs.cpp.inc"
virtual uint64_t getDmaBdMaxLen(AIETileType tileType) const =0
Return the maximum transfer length, measured in address-generation granules (see getAddressGenGranula...
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 AIEArch getTargetArch() const =0
Return the target architecture.
virtual uint32_t getNumSourceShimMuxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a shimmux.
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 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 bool isLegalTileConnection(int col, int row, WireBundle srcBundle, int srcChan, WireBundle dstBundle, int dstChan) const =0
virtual uint32_t getDmaTaskQueueDepth() const =0
Return how many DMA tasks a single channel's task queue holds (unsupported = 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 bool isValidTile(TileID src) const
Return true if the given tile ID is valid.
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).
bool hasProperty(ModelProperty Prop) const
virtual uint32_t getNumDestShimMuxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a shimmux.
virtual uint32_t getNumDestSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a switchbox.
SharedMemory
Which of two tiles' memory modules both of them can address.
SharedMemory getSharedMemory(TileID a, TileID b) const
Return the memory module a and b can both reach, if any.
virtual uint32_t getAddressGenGranularity() const =0
Return the data bus width of the device.
virtual uint32_t getNumSourceSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a switchbox.
NamedOpTable(mlir::Operation *symbolTableOp)
std::string uniqueName(llvm::StringRef prefix, unsigned &counter) const
bool isContiguousBDTransfer(llvm::ArrayRef< BDDimLayoutAttr > dims)
mlir::ParseResult parseFlowVias(mlir::OpAsmParser &parser, llvm::SmallVectorImpl< mlir::OpAsmParser::UnresolvedOperand > &vias, mlir::DenseI32ArrayAttr &ingressBundles, mlir::DenseI32ArrayAttr &ingressChannels, mlir::DenseI32ArrayAttr &egressBundles, mlir::DenseI32ArrayAttr &egressChannels)
void printObjectFifoProducerTile(mlir::OpAsmPrinter &printer, mlir::Operation *op, mlir::Value operand, BDDimLayoutArrayAttr dimensions)
void printTypedDynamicIndexList(mlir::OpAsmPrinter &printer, mlir::Operation *op, mlir::OperandRange values, llvm::ArrayRef< int64_t > integers, mlir::TypeRange types)
llvm::SmallVector< ObjectFifoDmaEndpointOp > turnEndpoints(ObjectFifoDmaEndpointOp end)
std::optional< llvm::SmallVector< BDDimLayoutAttr > > normalizedBDDims(llvm::ArrayRef< BDDimLayoutAttr > dims, int64_t len)
mlir::LogicalResult verifyLengthParameterTile(mlir::Operation *op, const AIETargetModel &targetModel, int col, int row)
uint32_t getShimBurstLengthBytes(const AIE::AIETargetModel &tm, uint32_t burstLength)
void collectTiles(DeviceOp &device, llvm::DenseMap< TileID, mlir::Operation * > &tiles)
void collectBuffers(DeviceOp &device, llvm::DenseMap< mlir::Operation *, llvm::SmallVector< BufferOp, 4 > > &buffers)
mlir::ParseResult parseTraceEvent(mlir::AsmParser &parser, mlir::Attribute &result)
void printObjectFifoAcquireObjects(mlir::OpAsmPrinter &printer, mlir::Operation *op, ObjectFifoPortAttr port, mlir::TypeRange objects)
mlir::LogicalResult verifyBdLockPair(mlir::Block &block, bool outOfOrder, UseLockOp &acquire, UseLockOp &release)
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)
void printObjectFifoConsumerTiles(mlir::OpAsmPrinter &printer, mlir::Operation *op, mlir::OperandRange tiles, BDDimLayoutArrayArrayAttr dimsPerTileAttr)
mlir::Operation * lookupNamedOpIn(mlir::Operation *symbolTableOp, mlir::StringAttr name)
uint32_t getShimBurstLengthEncoding(const AIE::AIETargetModel &tm, uint32_t burstLength)
mlir::LogicalResult verifyDMABDOutOfOrderId(DMABDOp bd, bool packetEnabledByContext=false)
mlir::ParseResult parseObjectFifoAcquireObjects(mlir::OpAsmParser &parser, ObjectFifoPortAttr &port, llvm::SmallVectorImpl< mlir::Type > &objects)
mlir::LogicalResult verifyOutOfOrderChannel(mlir::Operation *op, DMAChannelDir dir, bool outOfOrder, llvm::ArrayRef< DMABDOp > bds, bool packetEnabledByContext=false)
struct Connect { Port src; Port dst; bool operator==(const Connect &rhs) const { return std::tie(src, dst)==std::tie(rhs.src, rhs.dst); } bool operator!=(const Connect &rhs) const { return !(*this==rhs);} bool operator<(const Connect &rhs) const { return std::tie(src, dst)< std::tie(rhs.src, rhs.dst); } } Connect
Definition AIEDialect.h:299
void printDMAStartChannel(mlir::OpAsmPrinter &printer, mlir::Operation *op, mlir::Attribute channel)
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
int32_t getBufferBaseAddress(mlir::Operation *bufOp)
mlir::ParseResult parseDMAStartChannel(mlir::OpAsmParser &parser, mlir::Attribute &channel)
llvm::SmallVector< uint32_t > getAssignedBdIds(DmaBody program)
llvm::SmallVector< StreamWindowStep > streamWindowSteps(const StreamWindow &w)
int64_t turnPasses(llvm::ArrayRef< std::pair< int64_t, int64_t > > ends)
const AIETargetModel & getTargetModel(mlir::Operation *op)
bool keepsPktHeader(TileID tile, Port master, std::optional< bool > keep)
struct DMAChannel { DMAChannelDir direction; int channel; bool operator==(const DMAChannel &rhs) const { return std::tie(direction, channel)==std::tie(rhs.direction, rhs.channel); } } DMAChannel
Definition AIEDialect.h:314
mlir::LogicalResult verifyDMAChannelsResolved(DeviceOp device)
mlir::Operation * lookupNamedOp(mlir::Operation *from, mlir::StringAttr name)
mlir::ParseResult parseObjectFifoProducerTile(mlir::OpAsmParser &parser, mlir::OpAsmParser::UnresolvedOperand &operand, BDDimLayoutArrayAttr &dimensions)
void printTraceEventEnum(mlir::AsmPrinter &printer, mlir::Attribute attr)
void placeRuntimeLengthDimension(llvm::MutableArrayRef< int64_t > sizes, llvm::MutableArrayRef< int64_t > strides)
mlir::LogicalResult myVerifyOffsetSizeAndStrideOp(mlir::OffsetSizeAndStrideOpInterface op)
mlir::LogicalResult inferStreamWindow(llvm::ArrayRef< BDDimLayoutAttr > dims, llvm::ArrayRef< BDPadLayoutAttr > pads, int64_t objectSize, llvm::function_ref< mlir::InFlightDiagnostic()> emitError, StreamWindow &window)
mlir::LogicalResult verifyDMABDIteration(DMABDOp bd, AIETileType tileType)
void printFlowVias(mlir::OpAsmPrinter &printer, mlir::Operation *op, mlir::OperandRange vias, mlir::DenseI32ArrayAttr ingressBundles, mlir::DenseI32ArrayAttr ingressChannels, mlir::DenseI32ArrayAttr egressBundles, mlir::DenseI32ArrayAttr egressChannels)
struct Port { WireBundle bundle; int channel; bool operator==(const Port &rhs) const { return std::tie(bundle, channel)==std::tie(rhs.bundle, rhs.channel); } bool operator!=(const Port &rhs) const { return !(*this==rhs);} bool operator<(const Port &rhs) const { return std::tie(bundle, channel)< std::tie(rhs.bundle, rhs.channel); } friend std::ostream &operator<<(std::ostream &os, const Port &port) { os<< "("; os<< stringifyWireBundle(port.bundle).str(); os<< ": "<< std::to_string(port.channel)<< ")"; return os; } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Port &port) { os<< to_string(port); return os; } } Port
Definition AIEDialect.h:265
mlir::ParseResult parseObjectFifoReleaseCount(mlir::OpAsmParser &parser, ObjectFifoPortAttr &port, mlir::IntegerAttr &size)
AIETargetModel::SharedMemory sharedMemory(TileOp a, TileOp b)
Which of a's and b's memory modules both tiles can address.
std::optional< std::pair< TileID, Port > > getConnectingWireEndpoint(const AIETargetModel &targetModel, TileID tile, Port port)
mlir::FailureOr< int64_t > getLengthUnitBytes(mlir::Operation *op, int64_t lengthUnit, mlir::BaseMemRefType buffer)
int64_t paddedSize(llvm::ArrayRef< BDDimLayoutAttr > dims, llvm::ArrayRef< BDPadLayoutAttr > pads, int64_t fallback)
mlir::ParseResult parseObjectFifoConsumerTiles(mlir::OpAsmParser &parser, llvm::SmallVectorImpl< mlir::OpAsmParser::UnresolvedOperand > &tiles, BDDimLayoutArrayArrayAttr &dimensions)
void printObjectFifoReleaseCount(mlir::OpAsmPrinter &printer, mlir::Operation *op, ObjectFifoPortAttr port, mlir::IntegerAttr size)
WireBundle getConnectingBundle(WireBundle dir)
AIEArch
Definition Passes.h:19
static LogicalResult verifyTrait(Operation *op)
static LogicalResult verifyTrait(Operation *op)
static LogicalResult verifyTrait(Operation *op)
static LogicalResult verifyTrait(Operation *op)
static LogicalResult verifyTrait(Operation *op)
The register layout of one tile type's DMA buffer descriptor.
static mlir::LogicalResult verifyTrait(mlir::Operation *op)
static mlir::LogicalResult verifyTrait(mlir::Operation *op)
int64_t end() const
llvm::SmallVector< BDDimLayoutAttr > inner
Definition AIEDialect.h:410