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