MLIR-AIE
AIEDialect.cpp
Go to the documentation of this file.
1//===- AIEDialect.cpp -------------------------------------------*- C++ -*-===//
2//
3// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7// (c) Copyright 2019 Xilinx Inc.
8//
9//===----------------------------------------------------------------------===//
10
12
14#include "mlir/Dialect/Func/IR/FuncOps.h"
15#include "mlir/Dialect/MemRef/IR/MemRef.h"
16#include "mlir/IR/DialectImplementation.h"
17#include "mlir/IR/OpDefinition.h"
18#include "mlir/IR/SymbolTable.h"
19#include "mlir/Interfaces/FoldInterfaces.h"
20#include "mlir/Transforms/InliningUtils.h"
21
22#include "llvm/ADT/SmallSet.h"
23#include "llvm/ADT/TypeSwitch.h"
24
25using namespace mlir;
26using namespace xilinx::AIE;
27
28// Add TableGen'erated dialect definitions (including constructor)
29// We implement the initialize() function further below
30#include "aie/Dialect/AIE/IR/AIEDialect.cpp.inc"
31
32#define GET_TYPEDEF_CLASSES
33#include "aie/Dialect/AIE/IR/AIETypes.cpp.inc"
34
35namespace {
36
37struct AIEInlinerInterface : DialectInlinerInterface {
38 using DialectInlinerInterface::DialectInlinerInterface;
39 // We don't have any special restrictions on what can be inlined into
40 // destination regions. Always allow it.
41 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
42 IRMapping &valueMapping) const final override {
43 return true;
44 }
45
46 // Operations in aie dialect are always legal to inline since they are
47 // pure.
48 bool isLegalToInline(Operation *op, Region *, bool wouldBeCloned,
49 IRMapping &) const final override {
50 return true;
51 }
52
53 // Handle the given inlined terminator by replacing it with a new operation
54 // as necessary. Required when the inlined region has more than one block.
55 void handleTerminator(Operation *op, Block *newDest) const final override {}
56
57 // Handle the given inlined terminator by replacing it with a new operation
58 // as necessary. Required when the region has only one block.
59 void handleTerminator(Operation *op,
60 ValueRange valuesToRepl) const final override {}
61};
62
63struct AIEDialectFoldInterface : DialectFoldInterface {
64 using DialectFoldInterface::DialectFoldInterface;
65
66 /// Registered hook to check if the given region, which is attached to an
67 /// operation that is *not* isolated from above, should be used when
68 /// materializing constants.
69 bool shouldMaterializeInto(Region *region) const final override {
70 // If this is an AIE::CoreOp region, then insert into it.
71 return isa<CoreOp>(region->getParentOp());
72 }
73};
74
75} // end anonymous namespace
76
77void AIEDialect::initialize() {
78 addTypes<
79#define GET_TYPEDEF_LIST
80#include "aie/Dialect/AIE/IR/AIETypes.cpp.inc"
81 >();
82 addAttributes<
83#define GET_ATTRDEF_LIST
84#include "aie/Dialect/AIE/IR/AIEAttrs.cpp.inc"
85 >();
86 addOperations<
87#define GET_OP_LIST
88#include "aie/Dialect/AIE/IR/AIEOps.cpp.inc"
89 >();
90 addInterfaces<AIEInlinerInterface, AIEDialectFoldInterface>();
91}
92
93// Helper methods to retrieve the encoding associated to a burst length,
94// or to find the highest available burst length if the requested one is 0
95// (default value).
96
97static std::pair<uint32_t, uint32_t>
98getShimBurstLength(const xilinx::AIE::AIETargetModel &tm,
99 uint32_t burstLength) {
100
101 std::vector<std::pair<uint32_t, uint32_t>> bel =
103
104 // If we have the default burst length (no burst length was specified),
105 // use the highest one available on our target model
106 if (burstLength == 0) {
107 return *std::max_element(
108 bel.begin(), bel.end(),
109 [](auto pair1, auto pair2) { return pair1.second < pair2.second; });
110 }
111
112 // Note that if we are given a burst size, we are checking its existence in
113 // the pass verification already, so we can safely assume it exists.
114 return *std::find_if(bel.begin(), bel.end(),
115 [=](auto p) { return p.second == burstLength; });
116}
117
119 uint32_t burstLength) {
120
121 return getShimBurstLength(tm, burstLength).second;
122}
123
125 uint32_t burstLength) {
126
127 return getShimBurstLength(tm, burstLength).first;
128}
129
130LogicalResult
131xilinx::AIE::myVerifyOffsetSizeAndStrideOp(OffsetSizeAndStrideOpInterface op) {
132 std::array<unsigned, 3> maxRanks = op.getArrayAttrMaxRanks();
133 if (!(op.getMixedOffsets().size() == 1 && maxRanks[0] == 1) && // NOLINT
134 op.getMixedOffsets().size() != op.getMixedSizes().size())
135 return op->emitError(
136 "expected mixed offsets rank to match mixed sizes rank (")
137 << op.getMixedOffsets().size() << " vs " << op.getMixedSizes().size()
138 << ") so the rank of the result type is well-formed.";
139 if (failed(verifyListOfOperandsOrIntegers(
140 op, "offset", maxRanks[0], op.getStaticOffsets(), op.getOffsets())))
141 return failure();
142 if (failed(verifyListOfOperandsOrIntegers(
143 op, "size", maxRanks[1], op.getStaticSizes(), op.getSizes())))
144 return failure();
145 if (failed(verifyListOfOperandsOrIntegers(
146 op, "stride", maxRanks[2], op.getStaticStrides(), op.getStrides())))
147 return failure();
148 for (int64_t offset : op.getStaticOffsets())
149 if (offset < 0 && !ShapedType::isDynamic(offset))
150 return op->emitError("expected offsets to be non-negative, but got ")
151 << offset;
152 for (int64_t size : op.getStaticSizes())
153 if (size < 0 && !ShapedType::isDynamic(size))
154 return op->emitError("expected sizes to be non-negative, but got ")
155 << size;
156
157 return success();
158}
159
160static VC1902TargetModel VC1902model;
161static VE2302TargetModel VE2302model;
162static VE2802TargetModel VE2802model;
163static VirtualizedNPU1TargetModel NPUmodel1col(1);
164static VirtualizedNPU1TargetModel NPUmodel2col(2);
165static VirtualizedNPU1TargetModel NPUmodel3col(3);
166static VirtualizedNPU1TargetModel NPUmodel4col(4);
167static NPU2TargetModel NPU2model;
168static VirtualizedNPU2TargetModel NPU2model1col(1);
169static VirtualizedNPU2TargetModel NPU2model2col(2);
170static VirtualizedNPU2TargetModel NPU2model3col(3);
171static VirtualizedNPU2TargetModel NPU2model4col(4);
172static VirtualizedNPU2TargetModel NPU2model5col(5);
173static VirtualizedNPU2TargetModel NPU2model6col(6);
174static VirtualizedNPU2TargetModel NPU2model7col(7);
175
176const AIETargetModel &xilinx::AIE::getTargetModel(Operation *op) {
177 if (auto t = dyn_cast<AIETarget>(op))
178 return t.getTargetModel();
179 if (auto t = op->getParentOfType<AIETarget>())
180 return t.getTargetModel();
181
182 // For backward compatibility, return a basic device model compatible with
183 // the VCK190
184 return VC1902model;
185}
186
188 switch (device) {
189 case AIEDevice::xcvc1902:
190 return VC1902model;
191 case AIEDevice::xcve2302:
192 return VE2302model;
193 case AIEDevice::xcve2802:
194 return VE2802model;
195 case AIEDevice::npu1:
196 return NPUmodel4col;
197 case AIEDevice::npu1_1col:
198 return NPUmodel1col;
199 case AIEDevice::npu1_2col:
200 return NPUmodel2col;
201 case AIEDevice::npu1_3col:
202 return NPUmodel3col;
203 case AIEDevice::npu2:
204 return NPU2model;
205 case AIEDevice::npu2_1col:
206 return NPU2model1col;
207 case AIEDevice::npu2_2col:
208 return NPU2model2col;
209 case AIEDevice::npu2_3col:
210 return NPU2model3col;
211 case AIEDevice::npu2_4col:
212 return NPU2model4col;
213 case AIEDevice::npu2_5col:
214 return NPU2model5col;
215 case AIEDevice::npu2_6col:
216 return NPU2model6col;
217 case AIEDevice::npu2_7col:
218 return NPU2model7col;
219 }
220 return VC1902model;
221}
222
223// Walk the operation hierarchy until we find a containing TileElement.
224// If no parent is a TileElement, then return null.
225static TileElement getParentTileElement(Operation *op) {
226 auto *parent = op->getParentOp();
227 while (!llvm::isa_and_nonnull<DeviceOp, ModuleOp>(parent)) {
228 if (auto element = llvm::dyn_cast<TileElement>(parent))
229 return element;
230 parent = parent->getParentOp();
231 }
232 return llvm::dyn_cast<TileElement>(parent);
233}
234
235// Returns the maximum index described by the input dimensions.
236static int64_t getDimsMaxIdx(ArrayRef<BDDimLayoutAttr> dims) {
237 int64_t maxIdx = 0;
238 for (BDDimLayoutAttr dim : dims) {
239 maxIdx += dim.getStride() * (dim.getSize() - 1);
240 }
241 return maxIdx;
242}
243
244namespace {
245
246struct UsesAreAccessible {
247 static LogicalResult verifyTrait(Operation *op) {
248 auto thisElement = cast<TileElement>(op);
249
250 // Skip accessibility checks for logical tiles as we cannot tell until tile
251 // is placed
252 if (!isa<TileOp>(thisElement.getTile().getDefiningOp()))
253 return success();
254
255 auto thisID = thisElement.getTileID();
256 auto users = op->getResult(0).getUsers();
257 const auto &targetModel = getTargetModel(op);
258 for (auto *user : users) {
259 // AIE.useLock may be used in a device to set the lock's default value
260 // Allow in a toplevel module for backward compatibility
261 if (llvm::isa_and_nonnull<DeviceOp, ModuleOp>(user->getParentOp())) {
262 continue;
263 }
264 // If any parent or the user itself prescribe that accessibility checks be
265 // skipped, skip the check for that user.
266 if (user->getParentWithTrait<SkipAccessibilityCheckTrait>() ||
267 user->hasTrait<SkipAccessibilityCheckTrait>()) {
268 continue;
269 }
270 TileElement element = llvm::dyn_cast<TileElement>(user);
271 if (!element) {
272 element = getParentTileElement(user);
273 }
274 if (!element) {
275 // This should probably be caught elsewhere as well.
276 return op->emitOpError("is accessed outside of a tile")
277 .attachNote(user->getLoc())
278 << "user";
279 }
280 auto tileID = element.getTileID();
281 if (!targetModel.isLegalMemAffinity(tileID.col, tileID.row, thisID.col,
282 thisID.row)) {
283 return (op->emitOpError("in Column ")
284 << thisID.col << " and Row " << thisID.row
285 << " is accessed from an unreachable tile in Column "
286 << tileID.col << " and Row " << tileID.row)
287 .attachNote(user->getLoc())
288 << "user";
289 }
290 }
291 return success();
292 }
293};
294
295} // namespace
296
297// Check that the operation only contains terminators in
298// TerminatorOpTypes.
299template <typename... TerminatorOpTypes>
301 static LogicalResult verifyTrait(Operation *op) {
302 for (auto &region : op->getRegions()) {
303 for (auto &block : region) {
304 if (!block.empty()) {
305 if (Operation *operation = &block.back();
306 !llvm::isa_and_nonnull<TerminatorOpTypes...>(operation))
307 return operation->emitOpError("is not an allowed terminator")
308 .attachNote(op->getLoc())
309 .append("in this context: ");
310 }
311 }
312 }
313 return success();
314 }
315};
316
317// Check that the given DMA-like op (e.g. MemOp, ShimDMAOp)
318// has valid BDs.
319template <typename ConcreteType>
320LogicalResult HasValidBDs<ConcreteType>::verifyTrait(Operation *op) {
321 auto element = cast<ConcreteType>(op);
322 const auto &targetModel = getTargetModel(op);
323
324 TileLike tile = element.getTileLike();
325 if (!tile)
326 return op->emitOpError("tile must implement TileLike interface");
327
328 int bdMax = targetModel.getNumBDs(tile.getTileType());
329
330 int bdNum = 0;
331 for (auto &block : element.getBody()) {
332 auto bdOps = llvm::to_vector_of<DMABDOp>(block.template getOps<DMABDOp>());
333
334 // Skip entry/end block
335 if (bdOps.empty())
336 continue;
337
338 // Check BD count limit
339 if (bdNum >= bdMax) {
340 return (op->emitOpError("has more than ") << bdMax << " blocks")
341 .attachNote(bdOps.front().getLoc())
342 .append("no space for this BD");
343 }
344 bdNum++;
345
346 // Check exactly 1 DMABDOp per BD block
347 if (bdOps.size() != 1) {
348 return (op->emitOpError("BD block must have exactly one DMABDOp, found ")
349 << bdOps.size())
350 .attachNote(block.front().getLoc())
351 .append("in this BD block");
352 }
353
354 // Check at most 2 UseLockOps per BD block (1 acquire, 1 release)
355 auto useLockOps =
356 llvm::to_vector_of<UseLockOp>(block.template getOps<UseLockOp>());
357 int acquireCount = 0;
358 int releaseCount = 0;
359 for (auto useLock : useLockOps) {
360 if (useLock.acquire() || useLock.acquireGE())
361 acquireCount++;
362 else if (useLock.release())
363 releaseCount++;
364 }
365
366 if (acquireCount > 1) {
367 return (op->emitOpError(
368 "BD block must have at most one acquire UseLockOp, found ")
369 << acquireCount)
370 .attachNote(block.front().getLoc())
371 .append("in this BD block");
372 }
373 if (releaseCount > 1) {
374 return (op->emitOpError(
375 "BD block must have at most one release UseLockOp, found ")
376 << releaseCount)
377 .attachNote(block.front().getLoc())
378 .append("in this BD block");
379 }
380 }
381 return success();
382}
383
384// Check that the given DMA-like op (e.g. MemOp, ShimDMAOp)
385// has valid DMA channels.
386template <typename ConcreteType>
388 DenseSet<DMAChannel> inputChannels;
389 DenseSet<DMAChannel> outputChannels;
390 auto element = cast<ConcreteType>(op);
391 Region &body = element.getBody();
392 if (body.empty())
393 return op->emitOpError("should have non-empty body");
394 for (auto &bodyOp : body.getOps()) {
395 // check for duplicate DMA channels within the same ShimDMAOp
396 if (auto dmaStart = dyn_cast<DMAStartOp>(bodyOp)) {
397 DMAChannel dmaChan = {dmaStart.getChannelDir(),
398 dmaStart.getChannelIndex()};
399 // check if number of input and output channels is more than available
400 // hardware
401 if (dmaChan.direction == DMAChannelDir::S2MM)
402 inputChannels.insert(dmaChan);
403 else
404 outputChannels.insert(dmaChan);
405 }
406 }
407
408 TileLike tile = element.getTileLike();
409 if (!tile)
410 return op->emitOpError("tile must implement TileLike interface");
411
412 if (inputChannels.size() > tile.getNumSourceConnections(WireBundle::DMA))
413 return op->emitOpError(
414 "uses more input channels than available on this tile");
415
416 if (outputChannels.size() > tile.getNumDestConnections(WireBundle::DMA))
417 return op->emitOpError(
418 "uses more output channels than available on this tile");
419 return success();
420}
421
422//===----------------------------------------------------------------------===//
423// ObjectFifoCreateOp
424//===----------------------------------------------------------------------===//
425
426LogicalResult ObjectFifoCreateOp::verify() {
427 if (isa<ArrayAttr>(getElemNumber())) {
428 if (size_t numDepths = dyn_cast<ArrayAttr>(getElemNumber()).size();
429 numDepths != getConsumerTiles().size() + 1) // +1 for producer depth
430 return emitOpError("does not have enough depths specified for producer "
431 "and for each consumer.");
432 }
433
434 // Helper to get tile interface from Value
435 auto getTileLikeFromValue = [](Value v) -> TileLike {
436 return llvm::dyn_cast<TileLike>(v.getDefiningOp());
437 };
438
439 TileLike producerTile = getTileLikeFromValue(getProducerTile());
440 if (!producerTile)
441 return emitError("producer tile must implement TileLike interface");
442
443 // data layout transformations on shim tiles are handled by runtime operations
444 if (producerTile.isShimTile() && !getDimensionsToStream().empty()) {
445 return emitError(
446 "`dimensionsToStream` data layout transformations are not supported "
447 "on shim tile producers");
448 }
449 for (auto consTileVal : getConsumerTiles()) {
450 TileLike consTile = getTileLikeFromValue(consTileVal);
451 if (!consTile)
452 return emitError("consumer tile must implement TileLike interface");
453 if (consTile.isShimTile() &&
454 !getDimensionsFromStream(consTileVal).empty()) {
455 return emitError(
456 "`dimensionsFromStreamPerConsumer` data layout transformations are "
457 "not supported on shim tile consumers");
458 }
459 }
460
461 if (getRepeatCount().has_value()) {
462 if (producerTile.isShimTile())
463 return emitError("`repeat_count` unavailable for shim tiles");
464 }
465
466 if (getAieStreamPort().has_value()) {
467 if (!getAieStream().has_value())
468 return emitError("`aie_stream` must be defined");
469 }
470
471 if (getAieStream().has_value()) {
472 if (getConsumerTiles().size() > 1)
473 return emitError("`aie_stream` can only be used in 1-to-1 object FIFOs");
474
475 if (!getAieStreamPort().has_value())
476 return emitError("`aie_stream_port` must be defined");
477
478 if (getAieStream().value() == 0 || getAieStream().value() == 2) {
479 if (producerTile.isShimTile() || producerTile.isMemTile())
480 return emitError(
481 "`aie_stream` is not available for shim and mem tiles");
482
483 if (getRepeatCount().has_value())
484 return emitError("`repeat_count` unavailable on stream end");
485
486 if (getInitValues().has_value())
487 return emitError("`init_values` unavailable on stream end");
488
489 if (getIterCount().has_value())
490 return emitError("`iter_count` unavailable on stream end");
491
492 if (!getDimensionsToStream().empty())
493 return emitError("`dimensionsToStream` data layout transformations are "
494 "unavailable on stream end");
495 }
496
497 if (getAieStream().value() == 1 || getAieStream().value() == 2) {
498 TileLike consTile = getTileLikeFromValue(getConsumerTiles()[0]);
499 if (consTile && (consTile.isShimTile() || consTile.isMemTile()))
500 return emitError(
501 "`aie_stream` is not available for shim and mem tiles");
502 }
503
504 if (!getDimensionsFromStreamPerConsumer()[0].empty())
505 return emitError("`dimensionsFromStreamPerConsumer` data layout "
506 "transformations are unavailable on stream end");
507 }
508
509 if (getInitValues().has_value()) {
510 if (producerTile.isShimTile())
511 return emitError("`init_values` unavailable for shim tiles");
512 }
513
514 if (getInitValues().has_value()) {
515 if ((int)getInitValues().value().size() != size())
516 return emitError("`init_values` does not initialize all objects");
517 }
518
519 if (getIterCount().has_value()) {
520 int iterCount = getIterCount().value();
521 if (iterCount < 1 || iterCount > 256)
522 return emitError("`iter_count` must be between 1 and 256");
523
524 // Check that either producer or at least one consumer is a MemTile
525 bool hasMemTile = producerTile.isMemTile();
526 if (!hasMemTile) {
527 for (auto consTileVal : getConsumerTiles()) {
528 TileLike consTile = getTileLikeFromValue(consTileVal);
529 if (consTile && consTile.isMemTile()) {
530 hasMemTile = true;
531 break;
532 }
533 }
534 }
535 if (!hasMemTile)
536 return emitError("`iter_count` is currently only supported on MemTiles");
537 }
538
539 return success();
540}
541
542TileOp ObjectFifoCreateOp::getProducerTileOp() {
543 return cast<TileOp>(getProducerTile().getDefiningOp());
544}
545
546BDDimLayoutArrayAttr
547ObjectFifoCreateOp::getDimensionsFromStream(Value consumerTile) {
548 int dimsIndex = 0;
549 for (auto cons : getConsumerTiles()) {
550 if (cons == consumerTile)
551 break;
552 else
553 dimsIndex++;
554 }
555 return getDimensionsFromStreamPerConsumer()[dimsIndex];
556}
557
559 OpAsmParser &parser, OpAsmParser::UnresolvedOperand &operand,
560 BDDimLayoutArrayAttr &dimensions) {
561 std::vector<BDDimLayoutAttr> emptyDims = {};
562 if (parser.parseOperand(operand))
563 return failure();
564 if (succeeded(parser.parseOptionalKeyword("dimensionsToStream"))) {
565 if (parser.parseCustomAttributeWithFallback<BDDimLayoutArrayAttr>(
566 dimensions)) {
567 return failure();
568 }
569 } else {
570 dimensions =
571 BDDimLayoutArrayAttr::get(parser.getContext(), ArrayRef(emptyDims));
572 }
573 return success();
574}
575
576void xilinx::AIE::printObjectFifoProducerTile(OpAsmPrinter &printer,
577 Operation *op, Value operand,
578 BDDimLayoutArrayAttr dimensions) {
579 printer << operand;
580 if (!dimensions.empty()) {
581 printer << " dimensionsToStream ";
582 printer.printStrippedAttrOrType(dimensions);
583 }
584}
585
587 OpAsmParser &parser, SmallVectorImpl<OpAsmParser::UnresolvedOperand> &tiles,
588 BDDimLayoutArrayArrayAttr &dimensions) {
589 // parseCommaSeparatedList doesn't handle the missing case for "none",
590 // so we handle it custom here.
591 std::vector<BDDimLayoutArrayAttr> tileDims = {};
592
593 auto parseOneOperand = [&]() -> ParseResult {
594 if (parser.parseOperand(tiles.emplace_back(), true)) {
595 return failure();
596 }
597 // By default, create empty dimensions array for each consumer; this way,
598 // we can be certain to have as many entries in the dimensions array as
599 // there are customer
600 BDDimLayoutArrayAttr dimAttr =
601 BDDimLayoutArrayAttr::get(parser.getContext(), {});
602
603 if (succeeded(parser.parseOptionalKeyword("dimensionsFromStream"))) {
604 // If specified, parse actual data layout transform dimensions
605 if (parser.parseCustomAttributeWithFallback<BDDimLayoutArrayAttr>(
606 dimAttr)) {
607 return failure();
608 }
609 }
610 tileDims.emplace_back(dimAttr);
611 return success();
612 };
613
614 if (parser.parseCommaSeparatedList(AsmParser::Delimiter::None,
615 parseOneOperand, " in operand list"))
616 return failure();
617
618 dimensions = BDDimLayoutArrayArrayAttr::get(parser.getContext(), tileDims);
619 return success();
620}
621
623 OpAsmPrinter &printer, Operation *op, OperandRange tiles,
624 BDDimLayoutArrayArrayAttr dimsPerTileAttr) {
625 size_t tileIdx = 0;
626 for (auto tile : tiles) {
627 printer << tile;
628 if (dimsPerTileAttr && tileIdx < dimsPerTileAttr.size() &&
629 dimsPerTileAttr[tileIdx] && !dimsPerTileAttr[tileIdx].empty()) {
630 printer << " dimensionsFromStream ";
631 printer.printStrippedAttrOrType(dimsPerTileAttr[tileIdx]);
632 }
633 if (tileIdx < tiles.size() - 1) {
634 printer << ", ";
635 }
636 tileIdx++;
637 }
638}
639
640static void printObjectFifoInitValues(OpAsmPrinter &p, ObjectFifoCreateOp op,
641 Attribute numElem, TypeAttr type,
642 Attribute initValues) {
643 if (op.getInitValues()) {
644 p << "= [";
645 int depth = llvm::dyn_cast<mlir::IntegerAttr>(numElem).getInt();
646 for (int i = 0; i < depth; i++) {
647 p.printStrippedAttrOrType(llvm::dyn_cast<mlir::ArrayAttr>(initValues)[i]);
648 if (i < depth - 1) {
649 p << ", ";
650 }
651 }
652 p << "]";
653 }
654}
655
656static ParseResult parseObjectFifoInitValues(OpAsmParser &parser,
657 Attribute numElem, TypeAttr type,
658 Attribute &initValues) {
659 int depth;
660 if (isa<ArrayAttr>(numElem)) {
661 depth = llvm::dyn_cast<mlir::IntegerAttr>(
662 llvm::dyn_cast<mlir::ArrayAttr>(numElem)[0])
663 .getInt();
664 } else {
665 depth = llvm::dyn_cast<mlir::IntegerAttr>(numElem).getInt();
666 }
667 auto objfifoType = llvm::cast<AIEObjectFifoType>(type.getValue());
668 auto memrefType = llvm::cast<MemRefType>(objfifoType.getElementType());
669
670 if (!memrefType.hasStaticShape())
671 return parser.emitError(parser.getNameLoc())
672 << "type should be static shaped memref, but got " << memrefType;
673
674 if (parser.parseOptionalEqual())
675 return success();
676
677 Type tensorType = mlir::memref::getTensorTypeFromMemRefType(memrefType);
678 if (parser.parseAttribute(initValues, tensorType))
679 return failure();
680 for (int i = 0; i < depth; i++) {
681 auto initialValues = llvm::dyn_cast<mlir::ArrayAttr>(initValues);
682 if ((int)initialValues.size() != depth)
683 return parser.emitError(parser.getNameLoc())
684 << "initial values should initialize all objects";
685 if (!llvm::isa<ElementsAttr>(initialValues[i]))
686 return parser.emitError(parser.getNameLoc())
687 << "initial value should be an elements attribute";
688 }
689
690 return success();
691}
692
693//===----------------------------------------------------------------------===//
694// ObjectFifoAllocateOp
695//===----------------------------------------------------------------------===//
696
697LogicalResult ObjectFifoAllocateOp::verify() {
698 ObjectFifoCreateOp objFifo = getObjectFifo();
699 if (!objFifo)
700 return emitError("cannot retrieve associated object FIFO");
701 if (objFifo.getConsumerTiles().size() != 1)
702 return emitError("can only be used in 1-to-1 object FIFOs");
703 if (objFifo.getVia_DMA())
704 return emitError("cannot allocate a shared memory module to objectfifo "
705 "with set `via_DMA` attribute");
706 if (objFifo.getRepeatCount().has_value())
707 return emitError("cannot allocate a shared memory module to objectfifo "
708 "with set `repeat_count` attribute");
709 if (!objFifo.getDimensionsToStream().empty())
710 return emitError("cannot allocate a shared memory module to objectfifo "
711 "with set dimensions attribute");
712 if (objFifo.getAieStream().has_value())
713 return emitError("cannot allocate a shared memory module to objectfifo "
714 "using stream port");
715 return success();
716}
717
718TileOp ObjectFifoAllocateOp::getDelegateTileOp() {
719 return cast<TileOp>(getDelegateTile().getDefiningOp());
720}
721
722ObjectFifoCreateOp ObjectFifoAllocateOp::getObjectFifo() {
723 Operation *parent = getOperation();
724 while ((parent = parent->getParentOp())) {
725 if (parent->hasTrait<OpTrait::SymbolTable>()) {
726 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
727 isa_and_nonnull<ObjectFifoCreateOp>(st))
728 return dyn_cast<ObjectFifoCreateOp>(st);
729 }
730 }
731 return {};
732}
733
734//===----------------------------------------------------------------------===//
735// ObjectFifoLinkOp
736//===----------------------------------------------------------------------===//
737
738LogicalResult ObjectFifoLinkOp::verify() {
739 if (isJoin() && isDistribute())
740 return emitError("ObjectFifoLinkOp does not support 'join' and "
741 "'distribute' at the same time");
742
743 auto sharedTile = getOptionalSharedTile();
744 if (!sharedTile)
745 return emitError("ObjectFifoLinkOp must have a link point, i.e., a "
746 "shared tile between objectFifos");
747
748 TileLike tile = llvm::dyn_cast<TileLike>(sharedTile.value().getDefiningOp());
749 if (!tile)
750 return emitError("shared tile must implement TileLike interface");
751 if (!tile.isMemTile()) {
752 if (isJoin() || isDistribute())
753 return emitError("ObjectFifoLinkOp join and distribute are "
754 "unavailable on compute or shim tiles");
755 }
756
757 if (isJoin()) {
758 if (getFifoIns().size() != getSrcOffsets().size())
759 return emitOpError("number of provided src offsets must be equal "
760 "to the number of input objectFifos");
761
762 if (!getDstOffsets().empty())
763 return emitOpError("dst offsets should be empty for join");
764
765 ObjectFifoCreateOp fifoOut = getOutputObjectFifos()[0];
766 if (!fifoOut.getDimensionsToStream().empty()) {
767 int64_t maxIdx = getDimsMaxIdx(fifoOut.getDimensionsToStream());
768 int64_t minInputBufferSize = -1;
769 for (auto lenIn : getJoinTransferLengths()) {
770 if (lenIn <= minInputBufferSize || minInputBufferSize < 0)
771 minInputBufferSize = lenIn;
772 }
773 if (minInputBufferSize <= maxIdx) {
774 return emitOpError()
775 << "specified output stride(s) and size(s) result in out "
776 "of bounds access in join input, for index "
777 << std::to_string(maxIdx) << " in transfer of length "
778 << std::to_string(minInputBufferSize) << ".";
779 }
780 }
781
782 } else if (isDistribute()) {
783 if (getFifoOuts().size() != getDstOffsets().size())
784 return emitOpError("number of provided dst offsets must be equal "
785 "to the number of output objectFifos");
786
787 if (!getSrcOffsets().empty())
788 return emitOpError("src offsets should be empty for distribute");
789
790 ObjectFifoCreateOp fifoIn = getInputObjectFifos()[0];
791 if (!fifoIn.getDimensionsFromStream(sharedTile.value()).empty()) {
792 int64_t maxIdx =
793 getDimsMaxIdx(fifoIn.getDimensionsFromStream(sharedTile.value()));
794 int64_t minOutputBufferSize = -1;
795 for (auto lenOut : getDistributeTransferLengths()) {
796 if (lenOut <= minOutputBufferSize || minOutputBufferSize < 0)
797 minOutputBufferSize = lenOut;
798 }
799 if (minOutputBufferSize <= maxIdx) {
800 return emitOpError()
801 << "specified input stride(s) and size(s) result in out "
802 "of bounds access in distribute output, for index "
803 << std::to_string(maxIdx) << " in transfer of length "
804 << std::to_string(minOutputBufferSize) << ".";
805 }
806 }
807
808 std::vector<int> repeat_counts;
809 for (auto fifoOut : getOutputObjectFifos()) {
810 if (fifoOut.getRepeatCount().has_value()) {
811 repeat_counts.push_back(fifoOut.getRepeatCount().value());
812 } else {
813 repeat_counts.push_back(0);
814 }
815 }
816 for (auto repeat : repeat_counts)
817 if (repeat_counts[0] != repeat)
818 return emitError("repeat counts of output object FIFOs must be equal");
819
820 } else {
821 if (!getSrcOffsets().empty() && !getDstOffsets().empty())
822 return emitOpError("all offsets should be empty if there is no "
823 "join or distribute");
824 }
825
826 return success();
827}
828
829std::optional<Value> ObjectFifoLinkOp::getOptionalSharedTile() {
830 if (isJoin()) {
831 auto fifoOut = getOutputObjectFifos()[0];
832 for (auto fifoIn : getInputObjectFifos())
833 if (fifoOut.getProducerTile() != fifoIn.getConsumerTiles()[0])
834 return {};
835 return {fifoOut.getProducerTile()};
836 }
837
838 if (isDistribute()) {
839 auto fifoIn = getInputObjectFifos()[0];
840 for (auto fifoOut : getOutputObjectFifos())
841 if (fifoIn.getConsumerTiles()[0] != fifoOut.getProducerTile())
842 return {};
843 return {fifoIn.getConsumerTiles()[0]};
844 }
845
846 auto fifoIn = getInputObjectFifos();
847 if (auto fifoOut = getOutputObjectFifos();
848 !fifoIn.empty() && !fifoOut.empty())
849 for (auto consumerIn : fifoIn[0].getConsumerTiles())
850 if (consumerIn == fifoOut[0].getProducerTile())
851 return {fifoOut[0].getProducerTile()};
852 return {};
853}
854
855std::vector<ObjectFifoCreateOp> ObjectFifoLinkOp::getInputObjectFifos() {
856 std::vector<ObjectFifoCreateOp> inputObjFifos;
857 Operation *parent = getOperation();
858 while ((parent = parent->getParentOp())) {
859 if (parent->hasTrait<OpTrait::SymbolTable>()) {
860 for (auto sym : getFifoIns()) {
861 auto name = dyn_cast<FlatSymbolRefAttr>(sym);
862 if (auto *st = SymbolTable::lookupSymbolIn(parent, name);
863 isa_and_nonnull<ObjectFifoCreateOp>(st))
864 inputObjFifos.push_back(dyn_cast<ObjectFifoCreateOp>(st));
865 }
866 }
867 }
868 return inputObjFifos;
869}
870
871std::vector<ObjectFifoCreateOp> ObjectFifoLinkOp::getOutputObjectFifos() {
872 std::vector<ObjectFifoCreateOp> outputObjFifos;
873 Operation *parent = getOperation();
874 while ((parent = parent->getParentOp())) {
875 if (parent->hasTrait<OpTrait::SymbolTable>()) {
876 for (auto sym : getFifoOuts()) {
877 auto name = dyn_cast<FlatSymbolRefAttr>(sym);
878 if (auto *st = mlir::SymbolTable::lookupSymbolIn(parent, name);
879 isa_and_nonnull<ObjectFifoCreateOp>(st))
880 outputObjFifos.push_back(dyn_cast<ObjectFifoCreateOp>(st));
881 }
882 }
883 }
884 return outputObjFifos;
885}
886
887std::vector<int> ObjectFifoLinkOp::getJoinTransferLengths() {
888 std::vector<int> lengths;
889 if (isJoin()) {
890 auto fifoOut =
891 llvm::cast<AIEObjectFifoType>(getOutputObjectFifos()[0].getElemType());
892 auto elemTypeOut = llvm::cast<MemRefType>(fifoOut.getElementType());
893 int lenOut = elemTypeOut.getNumElements();
894 for (size_t i = 0; i < getFifoIns().size(); i++) {
895 int len = 0;
896 int offset = *getConstantIntValue(getSrcOffsets()[i]);
897 if (i == getFifoIns().size() - 1)
898 len = lenOut - *getConstantIntValue(getSrcOffsets()[i]);
899 else
900 len = *getConstantIntValue(getSrcOffsets()[i + 1]) - offset;
901 lengths.push_back(len);
902 }
903 }
904 return lengths;
905}
906
907std::vector<int> ObjectFifoLinkOp::getDistributeTransferLengths() {
908 std::vector<int> lengths;
909 if (isDistribute()) {
910 auto fifoIn =
911 llvm::cast<AIEObjectFifoType>(getInputObjectFifos()[0].getElemType());
912 auto elemTypeIn = llvm::cast<MemRefType>(fifoIn.getElementType());
913 int lenIn = elemTypeIn.getNumElements();
914 for (size_t i = 0; i < getFifoOuts().size(); i++) {
915 int offset = *getConstantIntValue(getDstOffsets()[i]);
916 int len = 0;
917 if (i == getFifoOuts().size() - 1)
918 len = lenIn - *getConstantIntValue(getDstOffsets()[i]);
919 else
920 len = *getConstantIntValue(getDstOffsets()[i + 1]) - offset;
921 lengths.push_back(len);
922 }
923 }
924 return lengths;
925}
926
927std::optional<int> ObjectFifoLinkOp::getRepeatCount() {
928 for (auto fifoOut : getOutputObjectFifos())
929 if (fifoOut.getRepeatCount().has_value())
930 return {fifoOut.getRepeatCount().value()};
931 return {};
932}
933
934//===----------------------------------------------------------------------===//
935// ObjectFifoRegisterExternalBuffersOp
936//===----------------------------------------------------------------------===//
937
938TileOp ObjectFifoRegisterExternalBuffersOp::getTileOp() {
939 return cast<TileOp>(getTile().getDefiningOp());
940}
941
942ObjectFifoCreateOp ObjectFifoRegisterExternalBuffersOp::getObjectFifo() {
943 Operation *parent = getOperation();
944 while ((parent = parent->getParentOp())) {
945 if (parent->hasTrait<OpTrait::SymbolTable>()) {
946 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
947 isa_and_nonnull<ObjectFifoCreateOp>(st))
948 return dyn_cast<ObjectFifoCreateOp>(st);
949 }
950 }
951 return {};
952}
953
954//===----------------------------------------------------------------------===//
955// ObjectFifoAcquireOp
956//===----------------------------------------------------------------------===//
957
958LogicalResult ObjectFifoAcquireOp::verify() {
959 auto parent = getOperation()->getParentOfType<CoreOp>();
960 if (parent == nullptr)
961 return emitOpError("must be called from inside a CoreOp");
962
963 auto coreTile = parent.getTile();
964 auto objFifo = getObjectFifo();
965 if (!objFifo)
966 return emitError("cannot retrieve associated object FIFO");
967 if (getPort() == ObjectFifoPort::Produce) {
968 if (coreTile != objFifo.getProducerTile())
969 return parent.emitOpError(
970 "producer port of objectFifo accessed by core running "
971 "on non-producer tile");
972 } else if (getPort() == ObjectFifoPort::Consume) {
973 bool found = false;
974 for (auto consumerTile : objFifo.getConsumerTiles()) {
975 if (coreTile == consumerTile) {
976 found = true;
977 break;
978 }
979 }
980 if (!found)
981 return parent.emitOpError(
982 "consumer port of objectFifo accessed by core running "
983 "on non-consumer tile");
984 }
985
986 auto objFifoElem =
987 llvm::cast<AIEObjectFifoType>(getObjectFifo().getElemType())
988 .getElementType();
989 auto objFifoSubviewElem =
990 llvm::cast<AIEObjectFifoSubviewType>(getResult().getType())
991 .getElementType();
992 if (objFifoElem != objFifoSubviewElem)
993 return emitOpError(
994 "ObjectFifo element and ObjectFifoSubview element must match.\n");
995
996 return success();
997}
998
999ObjectFifoCreateOp ObjectFifoAcquireOp::getObjectFifo() {
1000 Operation *parent = getOperation();
1001 while ((parent = parent->getParentOp())) {
1002 if (parent->hasTrait<OpTrait::SymbolTable>()) {
1003 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
1004 isa_and_nonnull<ObjectFifoCreateOp>(st))
1005 return dyn_cast<ObjectFifoCreateOp>(st);
1006 }
1007 }
1008 return {};
1009}
1010
1011//===----------------------------------------------------------------------===//
1012// ObjectFifoReleaseOp
1013//===----------------------------------------------------------------------===//
1014
1015LogicalResult ObjectFifoReleaseOp::verify() {
1016 auto parent = getOperation()->getParentOfType<CoreOp>();
1017 if (parent == nullptr)
1018 return emitOpError("must be called from inside a CoreOp");
1019
1020 auto coreTile = parent.getTile();
1021 auto objFifo = getObjectFifo();
1022 if (!objFifo)
1023 return emitError("cannot retrieve associated object FIFO");
1024 if (getPort() == ObjectFifoPort::Produce) {
1025 if (coreTile != objFifo.getProducerTile())
1026 return parent.emitOpError(
1027 "producer port of objectFifo accessed by core running "
1028 "on non-producer tile");
1029 } else if (getPort() == ObjectFifoPort::Consume) {
1030 bool found = false;
1031 for (auto consumerTile : objFifo.getConsumerTiles()) {
1032 if (coreTile == consumerTile) {
1033 found = true;
1034 break;
1035 }
1036 }
1037 if (!found)
1038 return parent.emitOpError(
1039 "consumer port of objectFifo accessed by core running "
1040 "on non-consumer tile");
1041 }
1042
1043 return success();
1044}
1045
1046ObjectFifoCreateOp ObjectFifoReleaseOp::getObjectFifo() {
1047 Operation *parent = getOperation();
1048 while ((parent = parent->getParentOp())) {
1049 if (parent->hasTrait<OpTrait::SymbolTable>()) {
1050 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
1051 isa_and_nonnull<ObjectFifoCreateOp>(st))
1052 return dyn_cast<ObjectFifoCreateOp>(st);
1053 }
1054 }
1055 return {};
1056}
1057
1058//===----------------------------------------------------------------------===//
1059// ObjectFifoSubviewAccessOp
1060//===----------------------------------------------------------------------===//
1061
1062LogicalResult ObjectFifoSubviewAccessOp::verify() {
1063 if (auto parent = getOperation()->getParentOfType<CoreOp>();
1064 parent == nullptr)
1065 return emitOpError("must be called from inside a CoreOp");
1066
1067 if (auto acqOp = getSubview().getDefiningOp<ObjectFifoAcquireOp>();
1068 getIndex() >= acqOp.acqNumber())
1069 return emitOpError("accessed farther than number of acquired elements "
1070 "(index out of bounds).");
1071
1072 return success();
1073}
1074
1075//===----------------------------------------------------------------------===//
1076// ObjectFifoRegisterProcessOp
1077//===----------------------------------------------------------------------===//
1078
1079LogicalResult ObjectFifoRegisterProcessOp::verify() {
1080 if (getProcessLength() < 1)
1081 return emitOpError("process length must be >= 1");
1082
1083 if (getAcquirePattern().size() != getReleasePattern().size()) {
1084 // acquire pattern size = process length (i.e., release pattern will be
1085 // duplicated by process length times) OR the other way around
1086 if (getAcquirePattern().size() != getProcessLength() &&
1087 getProcessLength() != getReleasePattern().size())
1088 return emitOpError(
1089 "Acquire and Release patterns must be of equal length, or "
1090 "longest length of one must be equal to process "
1091 "length of the other");
1092 }
1093
1094 return success();
1095}
1096
1097ObjectFifoCreateOp ObjectFifoRegisterProcessOp::getObjectFifo() {
1098 Operation *parent = getOperation();
1099 while ((parent = parent->getParentOp())) {
1100 if (parent->hasTrait<OpTrait::SymbolTable>()) {
1101 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
1102 isa_and_nonnull<ObjectFifoCreateOp>(st))
1103 return dyn_cast<ObjectFifoCreateOp>(st);
1104 }
1105 }
1106 return {};
1107}
1108
1109//===----------------------------------------------------------------------===//
1110// CascadeFlowOp
1111//===----------------------------------------------------------------------===//
1112
1113LogicalResult CascadeFlowOp::verify() {
1114 TileLike src = getSourceTileLike();
1115 TileLike dst = getDestTileLike();
1116
1117 if (!src || !dst)
1118 return emitOpError("source and dest must be tile-like operations");
1119
1120 if (src.isShimTile() || dst.isShimTile())
1121 return emitOpError("shimTile row has no cascade stream interface");
1122 if (src.isMemTile() || dst.isMemTile())
1123 return emitOpError("memTile row has no cascade stream interface");
1124
1125 std::optional<int> srcCol = src.tryGetCol();
1126 std::optional<int> srcRow = src.tryGetRow();
1127 std::optional<int> dstCol = dst.tryGetCol();
1128 std::optional<int> dstRow = dst.tryGetRow();
1129
1130 if (srcCol && srcRow && dstCol && dstRow) {
1131 const auto &t = getTargetModel(*this);
1132 if (!t.isSouth(*srcCol, *srcRow, *dstCol, *dstRow) &&
1133 !t.isWest(*srcCol, *srcRow, *dstCol, *dstRow) &&
1134 !t.isNorth(*srcCol, *srcRow, *dstCol, *dstRow) &&
1135 !t.isEast(*srcCol, *srcRow, *dstCol, *dstRow)) {
1136 return emitOpError("tiles must be adjacent");
1137 }
1138 }
1139
1140 return success();
1141}
1142
1143TileLike CascadeFlowOp::getSourceTileLike() {
1144 return dyn_cast<TileLike>(getSourceTile().getDefiningOp());
1145}
1146
1147TileLike CascadeFlowOp::getDestTileLike() {
1148 return dyn_cast<TileLike>(getDestTile().getDefiningOp());
1149}
1150
1151TileOp CascadeFlowOp::getSourceTileOp() {
1152 if (auto tileOp = dyn_cast_or_null<TileOp>(getSourceTile().getDefiningOp()))
1153 return tileOp;
1154 llvm::report_fatal_error("Calling getSourceTileOp requires TileOp.");
1155}
1156
1157TileOp CascadeFlowOp::getDestTileOp() {
1158 if (auto tileOp = dyn_cast_or_null<TileOp>(getDestTile().getDefiningOp()))
1159 return tileOp;
1160 llvm::report_fatal_error("Calling getDestTileOp requires TileOp.");
1161}
1162
1163//===----------------------------------------------------------------------===//
1164// ConfigureCascadeOp
1165//===----------------------------------------------------------------------===//
1166
1167LogicalResult ConfigureCascadeOp::verify() {
1168 if (!isa<TileOp>(getTile().getDefiningOp()))
1169 return emitOpError("requires a placed tile (aie.tile), not a logical tile");
1170
1171 const auto &t = getTargetModel(*this);
1172 TileOp tile = cast<TileOp>(getTile().getDefiningOp());
1173 CascadeDir inputDir = getInputDir();
1174 CascadeDir outputDir = getOutputDir();
1175
1176 if (tile.isShimTile())
1177 return emitOpError("shimTile row has no cascade stream interface");
1178 if (tile.isMemTile())
1179 return emitOpError("memTile row has no cascade stream interface");
1180
1181 if (isa<AIE2TargetModel>(t)) {
1182 if (inputDir == CascadeDir::South || inputDir == CascadeDir::East) {
1183 return emitOpError("input direction of cascade must be North or West on ")
1184 << stringifyAIEArch(t.getTargetArch());
1185 }
1186 if (outputDir == CascadeDir::North || outputDir == CascadeDir::West) {
1187 return emitOpError(
1188 "output direction of cascade must be South or East on ")
1189 << stringifyAIEArch(t.getTargetArch());
1190 }
1191 } else {
1192 return emitOpError("cascade not supported in ")
1193 << stringifyAIEArch(t.getTargetArch());
1194 }
1195 return success();
1196}
1197
1198//===----------------------------------------------------------------------===//
1199// PutCascadeOp
1200//===----------------------------------------------------------------------===//
1201
1202LogicalResult PutCascadeOp::verify() {
1203 const auto &targetModel = getTargetModel(*this);
1204 Type type = getCascadeValue().getType();
1205 DataLayout dataLayout = DataLayout::closest(*this);
1206 auto bits = dataLayout.getTypeSizeInBits(type);
1207 auto archbits = targetModel.getAccumulatorCascadeSize();
1208 if (bits != archbits)
1209 return emitOpError("type must match architecture cascade width (")
1210 << archbits << " bits in "
1211 << stringifyAIEArch(targetModel.getTargetArch()) << ")";
1212 return success();
1213}
1214
1215//===----------------------------------------------------------------------===//
1216// GetCascadeOp
1217//===----------------------------------------------------------------------===//
1218
1219LogicalResult GetCascadeOp::verify() {
1220 const auto &targetModel = getTargetModel(*this);
1221 Type type = getCascadeValue().getType();
1222 DataLayout dataLayout = DataLayout::closest(*this);
1223 auto bits = dataLayout.getTypeSizeInBits(type);
1224 if (isa<AIE1TargetModel>(targetModel)) {
1225 if (bits != 384)
1226 return emitOpError("must be a 384-bit type");
1227 } else if (isa<AIE2TargetModel>(targetModel)) {
1228 if (bits != 512)
1229 return emitOpError("must be a 512-bit type");
1230 } else
1231 return emitOpError("cascade not supported in ")
1232 << stringifyAIEArch(targetModel.getTargetArch());
1233 return success();
1234}
1235
1236//===----------------------------------------------------------------------===//
1237// DeviceOp
1238//===----------------------------------------------------------------------===//
1239
1240const AIETargetModel &DeviceOp::getTargetModel() {
1241 return xilinx::AIE::getTargetModel(getDevice());
1242}
1243
1244xilinx::AIE::DeviceOp DeviceOp::getForSymbolInModule(mlir::ModuleOp module,
1245 llvm::StringRef symbol) {
1246 DeviceOp deviceOp;
1247 if (!symbol.size()) {
1248 // If no device name is given, assume 'main'
1249 symbol = "main";
1250 }
1251 Operation *maybeDeviceOp = mlir::SymbolTable::lookupSymbolIn(module, symbol);
1252 if (!maybeDeviceOp) {
1253 return nullptr;
1254 }
1255 deviceOp = llvm::dyn_cast<DeviceOp>(maybeDeviceOp);
1256 return deviceOp;
1257}
1258
1259xilinx::AIE::DeviceOp
1260DeviceOp::getForSymbolInModuleOrError(mlir::ModuleOp module,
1261 llvm::StringRef symbol) {
1262 DeviceOp deviceOp = getForSymbolInModule(module, symbol);
1263 if (!deviceOp) {
1264 if (!symbol.empty()) {
1265 module.emitError("No such device: ") << symbol;
1266 } else {
1267 module.emitError("No 'main' device in module");
1268 }
1269 }
1270 return deviceOp;
1271}
1272
1273//===----------------------------------------------------------------------===//
1274// TileElement
1275//===----------------------------------------------------------------------===//
1276
1277TileOp TileElement::getTileOp() {
1278 auto element = cast<TileElement>(this->getOperation());
1279 Operation *definingOp = element.getTile().getDefiningOp();
1280 if (auto tileOp = dyn_cast_or_null<TileOp>(definingOp))
1281 return tileOp;
1282 llvm::report_fatal_error("Calling getTileOp requires TileOp.");
1283}
1284
1285//===----------------------------------------------------------------------===//
1286// LogicalTileOp
1287//===----------------------------------------------------------------------===//
1288
1289LogicalResult LogicalTileOp::verify() {
1290 const auto &targetModel = getTargetModel(*this);
1291 int columns = targetModel.columns();
1292 int rows = targetModel.rows();
1293
1294 // Only verify col/row bounds if they are specified
1295 if (auto col = getCol()) {
1296 if (*col >= columns)
1297 return emitOpError("column index (")
1298 << *col
1299 << ") must be less than the number of columns in the device ("
1300 << columns << ")";
1301 }
1302 if (auto row = getRow()) {
1303 if (*row >= rows)
1304 return emitOpError("row index (")
1305 << *row << ") must be less than the number of rows in the device ("
1306 << rows << ")";
1307 }
1308
1309 // Check that the specified tile type exists on the target device
1310 AIETileType tileType = getTileType();
1311 bool tileTypeExists = false;
1312 for (int col = 0; col < columns && !tileTypeExists; col++) {
1313 for (int row = 0; row < rows && !tileTypeExists; row++) {
1314 if (targetModel.getTileType(col, row) == tileType)
1315 tileTypeExists = true;
1316 }
1317 }
1318 if (!tileTypeExists) {
1319 return emitOpError("tile type '")
1320 << stringifyAIETileType(tileType)
1321 << "' does not exist on the target device";
1322 }
1323
1324 // Check logical tile type matches coordinates on device
1325 // Only validate when both col and row are specified
1326 if (auto col = tryGetCol()) {
1327 if (auto row = tryGetRow()) {
1328 if (targetModel.getTileType(*col, *row) != tileType) {
1329 return emitOpError("declared logical tile type does not match "
1330 "the tile type at coordinates (")
1331 << *col << ", " << *row << ")";
1332 }
1333 }
1334 }
1335
1336 if (isShimNOCorPLTile() && getAllocationScheme())
1337 return emitOpError("Shim tiles cannot have an allocation scheme");
1338
1339 return success();
1340}
1341
1342TileID LogicalTileOp::getCanonicalTileID() {
1343 const auto &targetModel = getTargetModel(*this);
1344
1345 // If col and row are both specified, use them directly
1346 if (getCol().has_value() && getRow().has_value()) {
1347 return {getCol().value(), getRow().value()};
1348 }
1349
1350 // Otherwise, find a representative tile of the given type
1351 AIETileType tileType = getTileType();
1352 for (int col = 0; col < targetModel.columns(); col++) {
1353 for (int row = 0; row < targetModel.rows(); row++) {
1354 if (targetModel.getTileType(col, row) == tileType) {
1355 return {col, row};
1356 }
1357 }
1358 }
1359 llvm_unreachable("No tile of matching tile type found in AIE device");
1360}
1361
1362size_t LogicalTileOp::getNumSourceConnections(WireBundle bundle) {
1363 const auto &targetModel = getTargetModel(*this);
1364 TileID tile = getCanonicalTileID();
1365
1366 if (bundle == WireBundle::Core || bundle == WireBundle::DMA) {
1367 // Note dest is correct here, since direction is reversed.
1368 if (isShimNOCorPLTile())
1369 return targetModel.getNumDestShimMuxConnections(tile.col, tile.row,
1370 bundle);
1371 return targetModel.getNumDestSwitchboxConnections(tile.col, tile.row,
1372 bundle);
1373 }
1374 return 0;
1375}
1376
1377size_t LogicalTileOp::getNumDestConnections(WireBundle bundle) {
1378 const auto &targetModel = getTargetModel(*this);
1379 TileID tile = getCanonicalTileID();
1380
1381 if (bundle == WireBundle::Core || bundle == WireBundle::DMA) {
1382 // Note source is correct here, since direction is reversed.
1383 if (isShimNOCorPLTile())
1384 return targetModel.getNumDestShimMuxConnections(tile.col, tile.row,
1385 bundle);
1386 return targetModel.getNumSourceSwitchboxConnections(tile.col, tile.row,
1387 bundle);
1388 }
1389 return 0;
1390}
1391
1392std::optional<int> LogicalTileOp::tryGetCol() {
1393 if (auto col = getCol())
1394 return col;
1395 return std::nullopt;
1396}
1397
1398std::optional<int> LogicalTileOp::tryGetRow() {
1399 if (auto row = getRow())
1400 return row;
1401 return std::nullopt;
1402}
1403
1404//===----------------------------------------------------------------------===//
1405// Custom Printer and Parser for LogicalTileOp
1406//===----------------------------------------------------------------------===//
1407
1408ParseResult LogicalTileOp::parse(OpAsmParser &parser, OperationState &result) {
1409 AIETileType tileType;
1410 if (parser.parseLess())
1411 return failure();
1412
1413 StringRef tileTypeStr;
1414 if (parser.parseKeyword(&tileTypeStr))
1415 return failure();
1416
1417 auto tileTypeOpt = symbolizeAIETileType(tileTypeStr);
1418 if (!tileTypeOpt)
1419 return parser.emitError(parser.getCurrentLocation(),
1420 "unknown logical tile type: ")
1421 << tileTypeStr;
1422 tileType = *tileTypeOpt;
1423
1424 if (parser.parseGreater())
1425 return failure();
1426
1427 if (parser.parseLParen())
1428 return failure();
1429
1430 std::optional<int32_t> col;
1431 if (succeeded(parser.parseOptionalQuestion())) {
1432 // col is unspecified
1433 } else {
1434 int32_t colVal;
1435 if (parser.parseInteger(colVal))
1436 return failure();
1437 col = colVal;
1438 }
1439
1440 if (parser.parseComma())
1441 return failure();
1442
1443 std::optional<int32_t> row;
1444 if (succeeded(parser.parseOptionalQuestion())) {
1445 // row is unspecified
1446 } else {
1447 int32_t rowVal;
1448 if (parser.parseInteger(rowVal))
1449 return failure();
1450 row = rowVal;
1451 }
1452
1453 if (parser.parseRParen())
1454 return failure();
1455
1456 // Parse optional attributes
1457 if (parser.parseOptionalAttrDict(result.attributes))
1458 return failure();
1459
1460 // Add the parsed attributes to the result
1461 result.getOrAddProperties<LogicalTileOp::Properties>().tile_type =
1462 AIETileTypeAttr::get(parser.getContext(), tileType);
1463 if (col)
1464 result.getOrAddProperties<LogicalTileOp::Properties>().col =
1465 parser.getBuilder().getI32IntegerAttr(*col);
1466 if (row)
1467 result.getOrAddProperties<LogicalTileOp::Properties>().row =
1468 parser.getBuilder().getI32IntegerAttr(*row);
1469
1470 // Add result type (index)
1471 result.addTypes(parser.getBuilder().getIndexType());
1472
1473 return success();
1474}
1475
1476void LogicalTileOp::print(OpAsmPrinter &printer) {
1477 printer << "<" << stringifyAIETileType(getTileType()) << ">";
1478
1479 printer << "(";
1480 if (auto col = getCol())
1481 printer << *col;
1482 else
1483 printer << "?";
1484 printer << ", ";
1485 if (auto row = getRow())
1486 printer << *row;
1487 else
1488 printer << "?";
1489 printer << ")";
1490
1491 SmallVector<StringRef, 3> elidedAttrs = {"tile_type", "col", "row"};
1492 printer.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
1493}
1494
1495//===----------------------------------------------------------------------===//
1496// TileOp
1497//===----------------------------------------------------------------------===//
1498
1499LogicalResult TileOp::verify() {
1500 const auto &targetModel = getTargetModel(*this);
1501 int columns = targetModel.columns();
1502 int rows = targetModel.rows();
1503 if (colIndex() >= columns)
1504 return emitOpError("column index (")
1505 << colIndex()
1506 << ") must be less than the number of columns in the device ("
1507 << columns << ")";
1508 if (rowIndex() >= rows)
1509 return emitOpError("row index (")
1510 << rowIndex()
1511 << ") must be less than the number of rows in the device (" << rows
1512 << ")";
1513
1514 auto users = getResult().getUsers();
1515 bool found = false;
1516 for (auto *user : users) {
1517 if (llvm::isa<SwitchboxOp>(*user)) {
1518 if (found)
1519 return emitOpError("can only have one switchbox");
1520 found = true;
1521 }
1522 }
1523
1524 if (isShimNOCorPLTile() && getAllocationScheme())
1525 return emitOpError("Shim tiles cannot have an allocation scheme");
1526
1527 return success();
1528}
1529
1530size_t TileOp::getNumSourceConnections(WireBundle bundle) {
1531 const auto &targetModel = getTargetModel(*this);
1532 if (bundle == WireBundle::Core || bundle == WireBundle::DMA)
1533 // Note dest is correct here, since direction is reversed.
1534 {
1535 // Note dest is correct here, since direction is reversed.
1536 if (isShimNOCorPLTile())
1537 return targetModel.getNumDestShimMuxConnections(getCol(), getRow(),
1538 bundle);
1539 return targetModel.getNumDestSwitchboxConnections(getCol(), getRow(),
1540 bundle);
1541 }
1542 return 0;
1543}
1544
1545size_t TileOp::getNumDestConnections(WireBundle bundle) {
1546 const auto &targetModel = getTargetModel(*this);
1547 if (bundle == WireBundle::Core || bundle == WireBundle::DMA)
1548 // Note source is correct here, since direction is reversed.
1549 {
1550 // Note source is correct here, since direction is reversed.
1551 if (isShimNOCorPLTile())
1552 return targetModel.getNumDestShimMuxConnections(getCol(), getRow(),
1553 bundle);
1554 return targetModel.getNumSourceSwitchboxConnections(getCol(), getRow(),
1555 bundle);
1556 }
1557 return 0;
1558}
1559
1560std::optional<int> TileOp::tryGetCol() { return getCol(); }
1561std::optional<int> TileOp::tryGetRow() { return getRow(); }
1562
1563AIETileType TileOp::getTileType() {
1564 const auto &targetModel = getTargetModel(*this);
1565 return targetModel.getTileType(getCol(), getRow());
1566}
1567
1568bool isLegalTileConnection(TileOp tile, const AIETargetModel &targetModel,
1569 MasterSetOp masterOp, PacketRulesOp slaveOp) {
1570 auto srcBundle = slaveOp.sourcePort().bundle;
1571 auto srcChan = slaveOp.sourcePort().channel;
1572 auto dstBundle = masterOp.destPort().bundle;
1573 auto dstChan = masterOp.destPort().channel;
1574 return targetModel.isLegalTileConnection(
1575 tile.colIndex(), tile.rowIndex(), srcBundle, srcChan, dstBundle, dstChan);
1576}
1577
1578bool isLegalTileConnection(TileOp tile, const AIETargetModel &targetModel,
1579 ConnectOp connectOp) {
1580 auto srcBundle = connectOp.getSourceBundle();
1581 auto srcChan = connectOp.getSourceChannel();
1582 auto dstBundle = connectOp.getDestBundle();
1583 auto dstChan = connectOp.getDestChannel();
1584 return targetModel.isLegalTileConnection(
1585 tile.colIndex(), tile.rowIndex(), srcBundle, srcChan, dstBundle, dstChan);
1586}
1587
1588TileOp TileOp::getOrCreate(mlir::OpBuilder builder, DeviceOp device, int col,
1589 int row) {
1590 TileOp tile = nullptr;
1591 // Find matching predefined tile at device top level, ...
1592 for (auto t : device.getOps<AIE::TileOp>()) {
1593 if (t.getRow() == row && t.getCol() == col) {
1594 tile = t;
1595 break;
1596 }
1597 }
1598 // ... or if undefined, create a new tile op
1599 if (!tile) {
1600 OpBuilder::InsertionGuard guard(builder);
1601 mlir::Block &device_start_block = *device.getBodyRegion().begin();
1602 builder.setInsertionPointToStart(&device_start_block);
1603 tile = TileOp::create(builder, builder.getUnknownLoc(),
1604 builder.getIndexType(), col, row);
1605 }
1606 return tile;
1607}
1608
1609//===----------------------------------------------------------------------===//
1610// ShimSwitchboxOp
1611//===----------------------------------------------------------------------===//
1612
1613LogicalResult ShimSwitchboxOp::verify() {
1614 Region &body = getConnections();
1615 DenseSet<Port> destset;
1616 if (body.empty())
1617 return emitOpError("should have non-empty body");
1618
1619 for (auto &ops : body.front()) {
1620 if (auto connectOp = dyn_cast<ConnectOp>(ops)) {
1621 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
1622 if (destset.count(dest))
1623 return connectOp.emitOpError("targets same destination ")
1624 << stringifyWireBundle(dest.bundle) << ": " << dest.channel
1625 << " as another connect operation";
1626 destset.insert(dest);
1627 } else if (isa<EndOp>(ops)) {
1628 // continue;
1629 } else {
1630 return ops.emitOpError("cannot be contained in a Switchbox op");
1631 }
1632 }
1633
1634 return success();
1635}
1636
1637//===----------------------------------------------------------------------===//
1638// ShimMuxOp
1639//===----------------------------------------------------------------------===//
1640
1641LogicalResult ShimMuxOp::verify() {
1642 // ShimMux requires a placed tile (TileOp), not a logical tile
1643 if (!isa<TileOp>(getTile().getDefiningOp()))
1644 return emitOpError("requires a placed tile (aie.tile), not a logical tile");
1645
1646 Region &body = getConnections();
1647 DenseSet<Port> destset;
1648 if (body.empty())
1649 return emitOpError("should have non-empty body");
1650
1651 for (auto &ops : body.front()) {
1652 if (auto connectOp = dyn_cast<ConnectOp>(ops)) {
1653 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
1654 if (destset.count(dest))
1655 return connectOp.emitOpError("targets same destination ")
1656 << stringifyWireBundle(dest.bundle) << ": " << dest.channel
1657 << " as another connect operation";
1658 destset.insert(dest);
1659 } else if (isa<EndOp>(ops)) {
1660 // continue;
1661 } else {
1662 return ops.emitOpError("cannot be contained in a Switchbox op");
1663 }
1664 }
1665 return success();
1666}
1667
1668size_t ShimMuxOp::getNumSourceConnections(WireBundle bundle) {
1669 auto tile = getTileOp();
1670 const auto &targetModel = getTargetModel(*this);
1671 return targetModel.getNumSourceShimMuxConnections(tile.getCol(),
1672 tile.getRow(), bundle);
1673}
1674
1675size_t ShimMuxOp::getNumDestConnections(WireBundle bundle) {
1676 auto tile = getTileOp();
1677 const auto &targetModel = getTargetModel(*this);
1678 return targetModel.getNumDestShimMuxConnections(tile.getCol(), tile.getRow(),
1679 bundle);
1680}
1681
1682//===----------------------------------------------------------------------===//
1683// ShimDMAOp
1684//===----------------------------------------------------------------------===//
1685
1686LogicalResult ShimDMAOp::verify() {
1688 .failed())
1689 return failure();
1690 return success();
1691}
1692
1693TileOp ShimDMAOp::getTileOp() {
1694 return cast<TileElement>(this->getOperation()).getTileOp();
1695}
1696
1697LogicalResult PacketRulesOp::verify() {
1698 if (Region &body = getRules(); body.empty())
1699 return emitOpError("should have non-empty body");
1700 return success();
1701}
1702
1703LogicalResult PacketFlowOp::verify() {
1704 Region &body = getPorts();
1705 if (body.empty())
1706 return emitOpError("should have non-empty body");
1707
1708 int numSources = 0, numDests = 0;
1709 for (auto &ops : body.front()) {
1710 if (!isa<PacketSourceOp, PacketDestOp, EndOp>(ops))
1711 return ops.emitOpError("cannot be contained in a PacketFlow op");
1712 if (isa<PacketSourceOp>(ops))
1713 ++numSources;
1714 if (isa<PacketDestOp>(ops))
1715 ++numDests;
1716 }
1717
1718 if (numSources != 1)
1719 return emitOpError("must have exactly one aie.packet_source (got ")
1720 << numSources << ")";
1721 if (numDests < 1)
1722 return emitOpError("must have at least one aie.packet_dest");
1723
1724 return success();
1725}
1726
1727//===----------------------------------------------------------------------===//
1728// CoreOp
1729//===----------------------------------------------------------------------===//
1730
1731LogicalResult CoreOp::verify() {
1732 if (getBody().empty())
1733 return emitOpError("should have non-empty body");
1734 if (getElfFile()) {
1735 // If an ELF file is specified, no MLIR body is allowed (to remove
1736 // ambiguity); the ELF file will fully dictate what runs on the
1737 // core and any MLIR would be ignored.
1738 if (!isEmpty()) {
1739 return emitOpError(
1740 "When `elf_file` attribute is specified, core body must be empty "
1741 "(consist of exactly one `aie.end` op).");
1742 }
1743 }
1744 if (getLinkWith() && getLinkFiles())
1745 return emitOpError(
1746 "cannot specify both 'link_with' (deprecated) and 'link_files' "
1747 "on the same core; run aie-assign-core-link-files to migrate");
1748 return success();
1749}
1750
1751bool CoreOp::isEmpty() {
1752 Region &body = getBody();
1753 // Return iff. core body contains exactly one block with exactly one AIE.EndOp
1754 return (body.hasOneBlock() && body.front().getOperations().size() == 1 &&
1755 llvm::isa<AIE::EndOp>(body.front().front()));
1756}
1757
1758TileOp CoreOp::getTileOp() {
1759 return cast<TileElement>(this->getOperation()).getTileOp();
1760}
1761
1762//===----------------------------------------------------------------------===//
1763// BufferOp
1764//===----------------------------------------------------------------------===//
1765
1766int64_t BufferOp::getAllocationSize() {
1767 auto type = llvm::cast<MemRefType>(getType());
1768 DataLayout dataLayout = DataLayout::closest(getOperation());
1769 return type.getNumElements() * dataLayout.getTypeSize(type.getElementType());
1770}
1771
1772LogicalResult BufferOp::verify() {
1773 if (UsesAreAccessible::verifyTrait(*this).failed())
1774 return failure();
1775 return success();
1776}
1777
1778// FIXME: make address assignment for buffers explicit and move this function to
1779// an interface
1780int32_t xilinx::AIE::getBufferBaseAddress(Operation *bufOp) {
1781 if (auto buf = dyn_cast<BufferOp>(bufOp)) {
1782 assert(buf.getAddress().has_value() && "buffer must have address assigned");
1783 return buf.getAddress().value();
1784 }
1785 if (isa_and_nonnull<ExternalBufferOp>(bufOp))
1786 llvm::report_fatal_error(
1787 "External buffer addresses are assigned at runtime.");
1788 llvm::report_fatal_error("unknown buffer type");
1789}
1790
1791void xilinx::AIE::collectTiles(DeviceOp &device,
1792 DenseMap<TileID, Operation *> &tiles) {
1793 for (auto tile : device.getOps<TileOp>()) {
1794 tiles[tile.getTileID()] = tile;
1795 }
1796}
1797
1799 DeviceOp &device,
1800 DenseMap<Operation *, SmallVector<BufferOp, 4>> &buffers) {
1801 for (BufferOp buffer : device.getOps<BufferOp>()) {
1802 Operation *tileOp = buffer.getTile().getDefiningOp();
1803 buffers[tileOp].push_back(buffer);
1804 }
1805}
1806
1807static void printBufferInitialValue(OpAsmPrinter &p, BufferOp op, Type type,
1808 Attribute initialValue) {
1809 if (op.getInitialValue()) {
1810 p << "= ";
1811 p.printAttributeWithoutType(initialValue);
1812 }
1813}
1814
1815static ParseResult parseBufferInitialValue(OpAsmParser &parser, Type &type,
1816 Attribute &initialValue) {
1817 auto memrefType = llvm::cast<MemRefType>(type);
1818 if (!memrefType.hasStaticShape())
1819 return parser.emitError(parser.getNameLoc())
1820 << "type should be static shaped memref, but got " << type;
1821
1822 if (parser.parseOptionalEqual())
1823 return success();
1824
1825 Type tensorType = mlir::memref::getTensorTypeFromMemRefType(memrefType);
1826 if (parser.parseAttribute(initialValue, tensorType))
1827 return failure();
1828 if (!llvm::isa<ElementsAttr>(initialValue))
1829 return parser.emitError(parser.getNameLoc())
1830 << "initial value should be an elements attribute";
1831 return success();
1832}
1833
1834TileOp BufferOp::getTileOp() {
1835 return cast<TileElement>(this->getOperation()).getTileOp();
1836}
1837
1838//===----------------------------------------------------------------------===//
1839// MemOp
1840//===----------------------------------------------------------------------===//
1841
1842LogicalResult MemOp::verify() {
1843 Region &body = getBody();
1845 .failed())
1846 return failure();
1847
1848 for (auto &bodyOp : body.getOps()) {
1849 if (auto allocOp = dyn_cast<memref::AllocOp>(bodyOp))
1850 if (!allocOp->getAttr("id"))
1851 return allocOp.emitOpError()
1852 << "allocOp in MemOp region should have an id attribute";
1853 }
1854 return success();
1855}
1856
1857TileOp MemOp::getTileOp() {
1858 return cast<TileElement>(this->getOperation()).getTileOp();
1859}
1860
1861//===----------------------------------------------------------------------===//
1862// MemTileDMAOp
1863//===----------------------------------------------------------------------===//
1864
1865LogicalResult MemTileDMAOp::verify() {
1866 assert(getOperation()->getNumRegions() == 1 &&
1867 "MemTileDMAOp has zero region!");
1868
1870 .failed())
1871 return failure();
1872
1873 for (auto &bodyOp : getBody().getOps()) {
1874 if (auto allocOp = dyn_cast<memref::AllocOp>(bodyOp)) {
1875 if (!allocOp->getAttr("id"))
1876 return allocOp.emitOpError()
1877 << "allocOp in MemTileDMAOp region should have an id attribute";
1878 }
1879 if (auto startOp = dyn_cast<DMAStartOp>(bodyOp)) {
1880 if (startOp.getChannelIndex() > 3) {
1881 // Channels 4 and 5 in a memtile are restricted to only access local
1882 // buffers and locks.
1883
1884 // TODO: Move this code to the dialect
1885 // Set of blocks found to be reachable within a given region.
1886 llvm::SmallSet<Block *, 16> reachable;
1887 SmallVector<Block *, 16> worklist;
1888 Block *firstBD = startOp.getSuccessor(0);
1889 reachable.insert(firstBD);
1890 worklist.push_back(firstBD);
1891 while (!worklist.empty()) {
1892 Block *block = worklist.pop_back_val();
1893 if (block->empty())
1894 continue;
1895 auto successors = block->getTerminator()->getSuccessors();
1896 for (auto *i : successors) {
1897 if (!reachable.contains(i)) {
1898 reachable.insert(i);
1899 worklist.push_back(i);
1900 }
1901 }
1902 }
1903 for (Block *b : reachable) {
1904 for (DMABDOp bd : b->getOps<DMABDOp>()) {
1905 if (auto bufferOp = bd.getBufferOp();
1906 bufferOp.getTile() != getTile()) {
1907 InFlightDiagnostic err =
1908 bd.emitOpError()
1909 << "is reachable from DMA channel "
1910 << startOp.getChannelIndex()
1911 << " and attempts to access a non-local buffer\n";
1912 err.attachNote(startOp->getLoc()) << "channel";
1913 err.attachNote(bufferOp->getLoc()) << "buffer";
1914 return err;
1915 }
1916 }
1917 for (auto useLock : b->getOps<UseLockOp>()) {
1918 if (auto lockOp = useLock.getLockOp();
1919 lockOp.getTile() != getTile()) {
1920 InFlightDiagnostic err =
1921 useLock.emitOpError()
1922 << "is reachable from DMA channel "
1923 << startOp.getChannelIndex()
1924 << " and attempts to access a non-local lock\n";
1925 err.attachNote(startOp->getLoc()) << "channel";
1926 err.attachNote(lockOp->getLoc()) << "lock";
1927 return err;
1928 }
1929 }
1930 }
1931 }
1932 }
1933 }
1934
1935 return success();
1936}
1937
1938TileOp MemTileDMAOp::getTileOp() {
1939 return cast<TileElement>(this->getOperation()).getTileOp();
1940}
1941
1942//===----------------------------------------------------------------------===//
1943// DMAOp
1944//===----------------------------------------------------------------------===//
1945
1946LogicalResult DMAOp::verify() {
1947 auto *parentOp = getOperation()->getParentOp();
1948 if (parentOp->getRegion(0).getBlocks().size() > 1)
1949 return emitOpError("DMAOp can only appear in single block region");
1950 if (!parentOp->getRegion(0).getOps<DMAStartOp>().empty())
1951 return emitOpError("DMAOp is not compatible with DMAStart ops");
1952 auto bdRegions = getBds();
1953 for (auto &bdRegion : bdRegions) {
1954 if (!bdRegion.hasOneBlock())
1955 return emitOpError("DMAOp regions must have only one block");
1956 auto bds = llvm::to_vector_of<DMABDOp>(bdRegion.front().getOps<DMABDOp>());
1957 if (bds.size() != 1)
1958 return emitOpError("DMAOp regions/blocks must have exactly one DMABDOp");
1959 auto useLocks =
1960 llvm::to_vector_of<UseLockOp>(bdRegion.front().getOps<UseLockOp>());
1961 if (useLocks.size() != 2)
1962 return emitOpError(
1963 "DMAOp regions/blocks must have exactly two UseLock ops");
1964 }
1965 return success();
1966}
1967
1968//===----------------------------------------------------------------------===//
1969// DMABDOp
1970//===----------------------------------------------------------------------===//
1971
1972BufferOp DMABDOp::getBufferOp() {
1973 return cast<BufferOp>(getBuffer().getDefiningOp());
1974}
1975
1976// let assemblyFormat = [{
1977// `(` $buffer `:` type($buffer) (`,` $offset^)? (`,` $len^)? (`,`
1978// $dimensions^)? (`,` $pad_dimensions^)? (`,` `pad_value` `=` $pad_value^)?
1979// `)` attr-dict
1980// }];
1981ParseResult DMABDOp::parse(OpAsmParser &parser, OperationState &result) {
1982 OpAsmParser::UnresolvedOperand bufferRawOperand{};
1983 ::llvm::ArrayRef<OpAsmParser::UnresolvedOperand> bufferOperands(
1984 &bufferRawOperand, 1);
1985 ::llvm::SMLoc bufferOperandsLoc;
1986 (void)bufferOperandsLoc;
1987 Type bufferRawType{};
1988 ::llvm::ArrayRef<Type> bufferTypes(&bufferRawType, 1);
1989 IntegerAttr offsetAttr;
1990 IntegerAttr lenAttr;
1991 ::xilinx::AIE::BDDimLayoutArrayAttr dimensionsAttr;
1992 ::xilinx::AIE::BDPadLayoutArrayAttr pad_dimensionsAttr;
1993 IntegerAttr pad_valueAttr;
1994 if (parser.parseLParen())
1995 return failure();
1996
1997 bufferOperandsLoc = parser.getCurrentLocation();
1998 if (parser.parseOperand(bufferRawOperand))
1999 return failure();
2000 if (parser.parseColon())
2001 return failure();
2002 if (parser.parseCustomTypeWithFallback(bufferRawType))
2003 return failure();
2004
2005 // offset
2006 if (succeeded(parser.parseOptionalComma())) {
2007 if (parser.parseCustomAttributeWithFallback(
2008 offsetAttr, parser.getBuilder().getIntegerType(32))) {
2009 return failure();
2010 }
2011 if (!offsetAttr)
2012 offsetAttr = parser.getBuilder().getIntegerAttr(
2013 parser.getBuilder().getIntegerType(32), 0);
2014 result.getOrAddProperties<DMABDOp::Properties>().offset = offsetAttr;
2015 }
2016
2017 // len
2018 if (succeeded(parser.parseOptionalComma())) {
2019 if (parser.parseCustomAttributeWithFallback(
2020 lenAttr, parser.getBuilder().getIntegerType(32))) {
2021 return failure();
2022 }
2023 if (lenAttr)
2024 result.getOrAddProperties<DMABDOp::Properties>().len = lenAttr;
2025 }
2026
2027 // dimensions
2028 if (succeeded(parser.parseOptionalComma())) {
2029 if (parser.parseCustomAttributeWithFallback(dimensionsAttr, Type{})) {
2030 return failure();
2031 }
2032 if (dimensionsAttr)
2033 result.getOrAddProperties<DMABDOp::Properties>().dimensions =
2034 dimensionsAttr;
2035 }
2036
2037 // pad_dimensions
2038 if (succeeded(parser.parseOptionalComma())) {
2039 if (parser.parseCustomAttributeWithFallback(pad_dimensionsAttr, Type{})) {
2040 return failure();
2041 }
2042 if (pad_dimensionsAttr)
2043 result.getOrAddProperties<DMABDOp::Properties>().pad_dimensions =
2044 pad_dimensionsAttr;
2045 }
2046
2047 // pad_value
2048 if (succeeded(parser.parseOptionalComma())) {
2049 if (parser.parseKeyword("pad_value"))
2050 return failure();
2051 if (parser.parseEqual())
2052 return failure();
2053
2054 if (parser.parseCustomAttributeWithFallback(
2055 pad_valueAttr, parser.getBuilder().getIntegerType(32))) {
2056 return failure();
2057 }
2058 if (pad_valueAttr)
2059 result.getOrAddProperties<DMABDOp::Properties>().pad_value =
2060 pad_valueAttr;
2061 }
2062 if (parser.parseRParen())
2063 return failure();
2064
2065 auto loc = parser.getCurrentLocation();
2066 if (parser.parseOptionalAttrDict(result.attributes))
2067 return failure();
2068 if (failed(verifyInherentAttrs(result.name, result.attributes, [&]() {
2069 return parser.emitError(loc)
2070 << "'" << result.name.getStringRef() << "' op ";
2071 })))
2072 return failure();
2073
2074 if (parser.resolveOperands(bufferOperands, bufferTypes, bufferOperandsLoc,
2075 result.operands))
2076 return failure();
2077
2078 return success();
2079}
2080
2081void DMABDOp::print(::mlir::OpAsmPrinter &printer) {
2082 printer << "(";
2083 printer << getBuffer();
2084 printer << ' ' << ":";
2085 printer << ' ';
2086 {
2087 auto type = getBuffer().getType();
2088 if (auto validType = ::llvm::dyn_cast<::mlir::MemRefType>(type))
2089 printer.printStrippedAttrOrType(validType);
2090 else
2091 printer << type;
2092 }
2093 if (getLenAttr() ||
2094 getOffsetAttr() !=
2095 ::mlir::OpBuilder((*this)->getContext())
2096 .getIntegerAttr(
2097 ::mlir::OpBuilder((*this)->getContext()).getIntegerType(32),
2098 0)) {
2099 printer << ",";
2100 printer << ' ';
2101 printer.printAttributeWithoutType(getOffsetAttr());
2102 }
2103 if (getLenAttr()) {
2104 printer << ",";
2105 printer << ' ';
2106 printer.printAttributeWithoutType(getLenAttr());
2107 }
2108 if (getDimensionsAttr()) {
2109 printer << ",";
2110 printer << ' ';
2111 printer.printStrippedAttrOrType(getDimensionsAttr());
2112 }
2113 if (getPadDimensionsAttr()) {
2114 printer << ",";
2115 printer << ' ';
2116 printer.printStrippedAttrOrType(getPadDimensionsAttr());
2117 }
2118 if ((getPadValueAttr() &&
2119 getPadValueAttr() !=
2120 ::mlir::OpBuilder((*this)->getContext())
2121 .getIntegerAttr(
2122 ::mlir::OpBuilder((*this)->getContext()).getIntegerType(32),
2123 0))) {
2124 printer << ",";
2125 printer << ' ' << "pad_value";
2126 printer << ' ' << "=";
2127 printer << ' ';
2128 printer.printAttributeWithoutType(getPadValueAttr());
2129 }
2130 printer << ")";
2131 ::llvm::SmallVector<::llvm::StringRef, 2> elidedAttrs;
2132 elidedAttrs.push_back("offset");
2133 elidedAttrs.push_back("len");
2134 elidedAttrs.push_back("dimensions");
2135 elidedAttrs.push_back("pad_dimensions");
2136 elidedAttrs.push_back("pad_value");
2137 printer.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
2138}
2139
2140LogicalResult DMABDOp::verify() {
2141 // Skip verification of the BDOp outside of mem operations.
2142 // BDOps may appear elsewhere and subsequent lowerings will place them in the
2143 // correct mem ops.
2144 Operation *p = (*this)->getParentOp();
2145 if (!llvm::isa<MemOp, MemTileDMAOp, ShimDMAOp, DMAOp>(*p)) {
2146 return success();
2147 }
2148
2149 // Check if buffer is an unranked memref (e.g., from function argument)
2150 bool isUnrankedMemRef = llvm::isa<UnrankedMemRefType>(getBuffer().getType());
2151
2152 // For unranked memrefs, we can't verify as strictly since we don't know
2153 // the buffer's defining op or its full type at compile time
2154 if (!isUnrankedMemRef) {
2155 if (!isa<BufferOp, ExternalBufferOp>(getBuffer().getDefiningOp()))
2156 return emitOpError(
2157 "BDs only support BufferOp or ExternalBufferOp operands.");
2158 }
2159
2160 if (getLenInBytes() % 4)
2161 return emitOpError("transfer length must be multiple of 4 (i.e., represent "
2162 "4 byte aligned address)");
2163
2164 TileElement parentTileElement = getParentTileElement(getOperation());
2165 TileLike parentTile = parentTileElement.getTileLike();
2166 if (!parentTile)
2167 return emitOpError("parent tile must implement TileLike interface");
2168
2169 if (!isUnrankedMemRef && getOperation()->getParentOfType<MemOp>() &&
2170 getBufferOp().getTile() != parentTileElement.getTile())
2171 return emitOpError(
2172 "Core tile DMAs can only access a buffer in the same tile.");
2173
2174 const AIETargetModel &targetModel = getTargetModel(getOperation());
2175
2176 uint32_t maxBds = targetModel.getNumBDs(parentTile.getTileType());
2177 if (std::optional<int32_t> bdId = getBdId();
2178 bdId.has_value() && static_cast<uint32_t>(*bdId) >= maxBds)
2179 return emitOpError("bdId attribute exceeds max: ") << maxBds - 1;
2180 if (std::optional<int32_t> nextBdId = getNextBdId();
2181 nextBdId.has_value() && static_cast<uint32_t>(*nextBdId) >= maxBds)
2182 return emitOpError("nextBdId attribute exceeds max: ") << maxBds - 1;
2183 if (auto dims = getDimensions(); dims.has_value()) {
2184 size_t maxNDims = 3;
2185 if (getOperation()->getParentOfType<MemTileDMAOp>())
2186 maxNDims = 4;
2187 if (dims->size() > maxNDims)
2188 return emitOpError() << "Cannot give more than "
2189 << std::to_string(maxNDims)
2190 << " dimensions for step sizes and wraps in this "
2191 " tile (got "
2192 << std::to_string(dims->size()) << " dimensions).";
2193
2194 auto buffer = llvm::dyn_cast<MemRefType>(getBuffer().getType());
2195 if (!buffer)
2196 return emitOpError() << "dimensions attribute cannot be used with "
2197 "unranked memref buffer type.";
2198 int64_t maxIdx = getDimsMaxIdx(*dims);
2199 if (buffer.getNumElements() <= maxIdx)
2200 return emitOpError() << "Specified stride(s) and size(s) result in out "
2201 "of bounds access in buffer, for index "
2202 << std::to_string(maxIdx) << " in memref of length "
2203 << std::to_string(buffer.getNumElements()) << ".";
2204
2205 for (BDDimLayoutAttr dim : *dims) {
2206 if (0 == dim.getStride())
2207 return emitOpError()
2208 << "Invalid step size; must be a positive integer.";
2209 if (dim.getStride() > buffer.getNumElements())
2210 return emitOpError() << "Step size " << std::to_string(dim.getStride())
2211 << " exceeds memref size "
2212 << std::to_string(buffer.getNumElements());
2213 if (dim.getSize() >= (1UL << 9) + 1)
2214 return emitOpError() << "Size may not exceed 1023.";
2215 if (dim.getStride() >= (1UL << 19))
2216 return emitOpError() << "Stride may not exceed " << (1 << 20);
2217 }
2218
2219 // Since streams read 32b words, there's no way to read eg 16b with stride
2220 // of 2 (ie lower halfs of each 32b). So force it to be 1 (and then in
2221 // CDODirect/XAIEV2 scale the size by 4/getBufferElementTypeWidthInBytes).
2222 if (getBufferElementTypeWidthInBytes() < 4 && dims->back().getStride() != 1)
2223 return emitOpError(
2224 "For <32b width datatypes, inner-most dim stride must be 1");
2225
2226 if (getBufferElementTypeWidthInBytes() > 4 && dims->back().getStride() != 1)
2227 return emitOpError(
2228 "For >32b width datatypes, inner-most dim stride must be 1");
2229 }
2230 if (auto paddims = getPadDimensions(); paddims.has_value()) {
2231 auto dims = getDimensions();
2232 if (!dims.has_value())
2233 return emitOpError() << "Padding requires n-d data layouts expressed as"
2234 << " wrap(s) and stride(s).";
2235 if (!parentTile.isMemTile())
2236 return emitOpError() << "Padding is only supported by memtile dma bds.";
2237 if (dims->size() != paddims->size())
2238 return emitOpError() << "Mismatch number of dimensions between padding(s)"
2239 << " and wrap(s) and stride(s).";
2240 int actuallen = 1;
2241 for (unsigned i = 0; i < paddims->size(); i++) {
2242 auto dim = (*dims)[i];
2243 auto paddim = (*paddims)[i];
2244 actuallen *= paddim.getConstPadBefore() + paddim.getConstPadAfter() +
2245 dim.getSize();
2246 if (actuallen > getLen())
2247 return emitOpError() << "Data exceeds len after padding.";
2248 }
2249 if ((paddims->back().getConstPadBefore() *
2250 getBufferElementTypeWidthInBytes()) %
2251 4)
2252 return emitOpError() << "Inner-most padding-before count must result in"
2253 << " padding in 32-bit words.";
2254 if ((paddims->back().getConstPadAfter() *
2255 getBufferElementTypeWidthInBytes()) %
2256 4)
2257 return emitOpError() << "Inner-most padding-after count must result in"
2258 << " padding in 32-bit words.";
2259 }
2260 if (!isUnrankedMemRef &&
2261 (parentTile.isMemTile() || parentTile.isCoreTile())) {
2262 if (auto baseAddr = getBufferOp().getAddress(); baseAddr.has_value()) {
2263 int offsetInBytes = *baseAddr + getOffsetInBytes();
2264 if (offsetInBytes % 4)
2265 return emitOpError("bd address must be 4 byte (32b) aligned; got "
2266 "base+offset: ")
2267 << offsetInBytes << " (bytes)";
2268 }
2269 }
2270 if (auto packetInfo = getPacket()) {
2271 if (packetInfo->getPktType() > 7)
2272 return emitOpError("Packet type field can only hold 3 bits.");
2273 if (packetInfo->getPktId() > 31)
2274 return emitOpError("Packet ID field can only hold 5 bits.");
2275 }
2276
2277 if (!getLen() && !getBuffer().getType().hasStaticShape())
2278 return emitOpError() << "buffer with dynamic shape requires static length.";
2279
2280 if (getBurstLength() != 0 && !parentTile.isShimNOCTile())
2281 return emitOpError("Burst length is only supported in Shim NOC tiles that "
2282 "are connected to the memory-mapped NOC.");
2283
2284 return success();
2285}
2286
2287//===----------------------------------------------------------------------===//
2288// DMAStartOp
2289//===----------------------------------------------------------------------===//
2290
2291static LogicalResult FoldDMAStartOp(DMAStartOp op, PatternRewriter &rewriter) {
2292
2293 llvm::SetVector<Block *> reachable;
2294 SmallVector<Block *, 16> worklist;
2295 Block *firstBD = op.getSuccessor(0);
2296 reachable.insert(firstBD);
2297 worklist.push_back(firstBD);
2298 while (!worklist.empty()) {
2299 Block *block = worklist.pop_back_val();
2300 if (block->empty())
2301 continue;
2302 auto successors = block->getTerminator()->getSuccessors();
2303 for (auto *i : successors) {
2304 if (!reachable.contains(i)) {
2305 reachable.insert(i);
2306 worklist.push_back(i);
2307 }
2308 }
2309 }
2310
2311 // BD chain ends with an EndOp, indicating non-repeating pattern: BD chain
2312 // folding not applicable.
2313 if (isa<EndOp>((reachable.back())->getTerminator()))
2314 return failure();
2315
2316 // Check for identical bds.
2317 auto areEquivalentBDs = [](Block *b1, Block *b2) {
2318 auto b1OpRange = b1->without_terminator();
2319 auto b2OpRange = b2->without_terminator();
2320 if (llvm::range_size(b1OpRange) != llvm::range_size(b2OpRange))
2321 return false;
2322 if (!llvm::all_of(llvm::zip_equal(b1OpRange, b2OpRange),
2323 [](std::tuple<Operation &, Operation &> pair) {
2324 return OperationEquivalence::isEquivalentTo(
2325 &std::get<0>(pair), &std::get<1>(pair),
2326 OperationEquivalence::IgnoreLocations);
2327 }))
2328 return false;
2329 return true;
2330 };
2331
2332 // Get a vector of unique BDs.
2333 SmallVector<Block *> uniquePattern;
2334 auto patternIt = reachable.begin();
2335 while (patternIt != reachable.end() &&
2336 llvm::none_of(uniquePattern, [patternIt, areEquivalentBDs](Block *b1) {
2337 return areEquivalentBDs(*patternIt, b1);
2338 })) {
2339 uniquePattern.push_back(*patternIt);
2340 patternIt++;
2341 }
2342
2343 unsigned idx = 0;
2344 while (patternIt != reachable.end()) {
2345 // BD repetition found. Check if repeating pattern.
2346 if (!areEquivalentBDs(*patternIt, uniquePattern[idx]))
2347 return failure();
2348 patternIt++;
2349 idx = (++idx) % uniquePattern.size();
2350 }
2351
2352 // Repeating BD chains detected. Erasing repetitions.
2353 auto lastBDTerm = dyn_cast<NextBDOp>(reachable.back()->getTerminator());
2354 auto lastUniqueBDTerm =
2355 dyn_cast<NextBDOp>(uniquePattern.back()->getTerminator());
2356 lastUniqueBDTerm.setSuccessor(lastBDTerm.getSuccessor());
2357
2358 return success();
2359}
2360
2361void DMAStartOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2362 MLIRContext *context) {
2363 patterns.add(FoldDMAStartOp);
2364}
2365
2366//===----------------------------------------------------------------------===//
2367// SwitchboxOp
2368//===----------------------------------------------------------------------===//
2369
2370LogicalResult SwitchboxOp::verify() {
2371 // Switchbox requires a placed tile (TileOp), not a logical tile
2372 if (!isa<TileOp>(getTile().getDefiningOp()))
2373 return emitOpError("requires a placed tile (aie.tile), not a logical tile");
2374
2375 Region &body = getConnections();
2376 DenseSet<Port> sourceset;
2377 DenseSet<Port> destset;
2378 auto tile = getTileOp();
2379 const auto &targetModel = getTargetModel(tile);
2380 if (body.empty())
2381 return emitOpError("should have non-empty body");
2382 for (auto &ops : body.front()) {
2383 // Would be simpler if this could be templatized.
2384 auto checkBound = [&ops](StringRef dir, WireBundle bundle, int index,
2385 int bound) -> LogicalResult {
2386 if (index >= bound) {
2387 if (bound > 0)
2388 return ops.emitOpError("index ")
2389 << index << " for " << dir << " bundle "
2390 << stringifyWireBundle(bundle) << " must be less than "
2391 << bound;
2392 return ops.emitOpError()
2393 << dir << " bundle " << stringifyWireBundle(bundle)
2394 << " not supported; index: " << index << ", bound: " << bound;
2395 }
2396 return success();
2397 };
2398
2399 if (auto connectOp = dyn_cast<ConnectOp>(ops)) {
2400 Port source = {connectOp.getSourceBundle(), connectOp.sourceIndex()};
2401 sourceset.insert(source);
2402
2403 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
2404 if (destset.count(dest)) {
2405 return connectOp.emitOpError()
2406 << "; connecting " << to_string(source) << " to "
2407 << to_string(dest) << " on "
2408 << to_string(this->getTileOp().getTileID())
2409 << " targets same dst as another connect op; existing "
2410 "destinations: "
2411 << llvm::join(llvm::map_range(
2412 destset, [](auto &p) { return to_string(p); }),
2413 ", ");
2414 }
2415 destset.insert(dest);
2416
2417 if (connectOp.sourceIndex() < 0)
2418 return connectOp.emitOpError("source index cannot be less than zero");
2419
2420 if (checkBound("source", connectOp.getSourceBundle(),
2421 connectOp.sourceIndex(),
2422 getNumSourceConnections(connectOp.getSourceBundle()))
2423 .failed())
2424 return failure();
2425
2426 if (connectOp.destIndex() < 0)
2427 return connectOp.emitOpError("dest index cannot be less than zero");
2428
2429 if (checkBound("dest", connectOp.getDestBundle(), connectOp.destIndex(),
2430 getNumDestConnections(connectOp.getDestBundle()))
2431 .failed())
2432 return failure();
2433
2434 // Stream switch connection constraints
2435 if (!isLegalTileConnection(tile, targetModel, connectOp))
2436 return connectOp.emitOpError("illegal stream switch connection");
2437
2438 } else if (auto connectOp = dyn_cast<MasterSetOp>(ops)) {
2439 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
2440 if (destset.count(dest))
2441 return connectOp.emitOpError("targets same destination ")
2442 << stringifyWireBundle(dest.bundle) << ": " << dest.channel
2443 << " as another connect or masterset operation";
2444 destset.insert(dest);
2445
2446 if (connectOp.destIndex() < 0)
2447 return connectOp.emitOpError("dest index cannot be less than zero");
2448
2449 if (checkBound("dest", connectOp.getDestBundle(), connectOp.destIndex(),
2450 getNumDestConnections(connectOp.getDestBundle()))
2451 .failed())
2452 return failure();
2453
2454 int arbiter = -1;
2455 for (auto val : connectOp.getAmsels()) {
2456 auto amsel = dyn_cast<AMSelOp>(val.getDefiningOp());
2457 if (arbiter != -1 && arbiter != amsel.arbiterIndex())
2458 return connectOp.emitOpError(
2459 "a master port can only be tied to one arbiter");
2460 arbiter = amsel.arbiterIndex();
2461 }
2462 } else if (auto connectOp = dyn_cast<PacketRulesOp>(ops)) {
2463 Port source = {connectOp.getSourceBundle(), connectOp.sourceIndex()};
2464 if (sourceset.count(source))
2465 return connectOp.emitOpError("packet switched source ")
2466 << stringifyWireBundle(source.bundle) << source.channel
2467 << " cannot match another connect or masterset operation";
2468 sourceset.insert(source);
2469
2470 } else if (auto amselOp = dyn_cast<AMSelOp>(ops)) {
2471 std::vector<MasterSetOp> mstrs;
2472 std::vector<PacketRulesOp> slvs;
2473 for (auto *user : amselOp.getResult().getUsers()) {
2474 if (auto s = dyn_cast<PacketRuleOp>(user)) {
2475 auto pktRules = dyn_cast<PacketRulesOp>(s->getParentOp());
2476 slvs.push_back(pktRules);
2477 } else if (auto m = dyn_cast<MasterSetOp>(user))
2478 mstrs.push_back(m);
2479 }
2480 for (auto m : mstrs) {
2481 for (auto s : slvs) {
2482 // Stream switch connection constraints
2483 if (!isLegalTileConnection(tile, targetModel, m, s)) {
2484 return amselOp->emitOpError("illegal stream switch connection");
2485 }
2486 }
2487 }
2488 } else if (isa<EndOp>(ops)) {
2489 // continue;
2490 } else {
2491 return ops.emitOpError("cannot be contained in a Switchbox op");
2492 }
2493 }
2494
2495 return success();
2496}
2497
2498template <typename... ParentOpTypes>
2500 static LogicalResult verifyTrait(Operation *op) {
2501 Operation *operation = op->getParentOp();
2502 while (operation) {
2503 if (llvm::isa_and_nonnull<ParentOpTypes...>(operation))
2504 return success();
2505 operation = operation->getParentOp();
2506 }
2507 return failure();
2508 }
2509};
2510
2511TileOp LockOp::getTileOp() {
2512 return cast<TileElement>(this->getOperation()).getTileOp();
2513}
2514
2515LogicalResult LockOp::verify() {
2516 if (auto result = UsesAreAccessible::verifyTrait(*this); result.failed())
2517 return result;
2518
2519 if (getLockID().has_value()) {
2520 TileLike tileLike = getTileLike();
2521 if (!tileLike)
2522 return emitOpError("tile operand must implement TileLike interface");
2523 const auto &targetModel = getTargetModel(*this);
2524 auto tileType = tileLike.getTileType();
2525 if (int numLocks = targetModel.getNumLocks(tileType);
2526 getLockID().value() >= numLocks)
2527 return emitOpError("lock assigned invalid id (maximum is ")
2528 << numLocks - 1 << ")";
2529 }
2530
2531 return success();
2532}
2533
2535 static LogicalResult verifyTrait(Operation *op) {
2536 auto *block = op->getBlock();
2537 int lockID = -1;
2538 for (auto op : block->getOps<UseLockOp>()) {
2539 if (auto lock = dyn_cast<LockOp>(op.getLock().getDefiningOp());
2540 lock.getLockID().has_value()) {
2541 if (lockID != -1 && lockID != lock.getLockIDValue())
2542 return failure();
2543 lockID = lock.getLockIDValue();
2544 }
2545 }
2546 return success();
2547 }
2548};
2549
2551 static LogicalResult verifyTrait(Operation *op) {
2552 auto *block = op->getBlock();
2553 int acqValue = -1, relValue = -1;
2554 for (auto op : block->getOps<UseLockOp>()) {
2555 if (op.acquire() || op.acquireGE()) {
2556 if (acqValue != -1 && acqValue != op.getLockValue()) {
2557 return failure();
2558 }
2559 acqValue = op.getLockValue();
2560 } else if (op.release()) {
2561 if (relValue != -1 && relValue != op.getLockValue()) {
2562 return failure();
2563 }
2564 relValue = op.getLockValue();
2565 }
2566 }
2567 return success();
2568 }
2569};
2570
2572 static LogicalResult verifyTrait(Operation *op) {
2573 if (auto memOp = op->getParentOfType<MemOp>()) {
2574 auto useLock = dyn_cast<UseLockOp>(op);
2575 if (auto lock = useLock.getLockOp(); lock.getTile() != memOp.getTile())
2576 return failure();
2577 }
2578 return success();
2579 }
2580};
2581
2582LogicalResult UseLockOp::verify() {
2583 // AIE.useLock cannot be used at the top level
2584 if (llvm::isa_and_nonnull<DeviceOp, ModuleOp>((*this)->getParentOp()))
2585 return (*this)->emitOpError("must be used in a core or memory operation.");
2586
2587 const auto &targetModel = getTargetModel(*this);
2588 if (targetModel.getTargetArch() == AIEArch::AIE1 && acquireGE())
2589 return (*this)->emitOpError(
2590 "AcquireGreaterEqual is not supported in AIE1.");
2591
2592 // Otherwise, AIE.useLock should be inside MemOp, MemTileDMAOp, or
2593 // ShimDMAOp,
2595 .succeeded()) {
2596 if (!(*this)->getBlock())
2597 return (*this)->emitOpError("is not in a block.");
2598
2599 if (targetModel.getTargetArch() == AIEArch::AIE1 &&
2600 UsesOneLockInDMABlock::verifyTrait(*this).failed())
2601 return (*this)->emitOpError(
2602 "used in a DMA block that have multiple locks.");
2603
2605 return (*this)->emitOpError("acquires/releases the lock in a DMA block "
2606 "from/to multiple states.");
2607
2608 if (HasSomeParent<MemOp>::verifyTrait(*this).succeeded() &&
2609 AccessesLocalLocks::verifyTrait(*this).failed())
2610 return (*this)->emitOpError("can only access a lock in the same tile");
2611 return success();
2612
2613 // Or it can be in a CoreOp, or some FuncOp called from a CoreOp
2614 }
2615 if (HasSomeParent<CoreOp, func::FuncOp>::verifyTrait(*this).succeeded()) {
2616 return success();
2617 }
2618 // Or it can be in a DMAConfigureTaskOp (for runtime DMA configuration)
2619 // Check by operation name to avoid circular dependency with AIEX dialect
2620 {
2621 Operation *operation = (*this)->getParentOp();
2622 while (operation) {
2623 if (operation->getName().getStringRef() == "aiex.dma_configure_task")
2624 return success();
2625 operation = operation->getParentOp();
2626 }
2627 }
2628 return (*this)->emitOpError()
2629 << "expects some parent op to be one of "
2630 << "AIE::device, AIE::core, func::func, AIE::mem, AIE::shimDMA, or "
2631 "AIEX::dma_configure_task";
2632}
2633
2634#include "aie/Dialect/AIE/IR/AIEEnums.cpp.inc"
2635#include "aie/Dialect/AIE/IR/AIEInterfaces.cpp.inc"
2636
2637//===----------------------------------------------------------------------===//
2638// TraceEventAttr
2639//===----------------------------------------------------------------------===//
2640
2641// Custom parser for TraceEventAttr value (uses shared helper)
2642static ParseResult parseTraceEventValue(AsmParser &parser, Attribute &value) {
2643 return xilinx::AIE::parseTraceEvent(parser, value);
2644}
2645
2646// Custom printer for TraceEventAttr value (uses shared helper)
2647static void printTraceEventValue(AsmPrinter &printer, Attribute value) {
2648 xilinx::AIE::printTraceEventEnum(printer, value);
2649}
2650
2651// Custom parser for TraceEventAttr value (uses shared helper)
2652static ParseResult parseTraceRegValue(OpAsmParser &parser, Attribute &value) {
2653
2654 // Try to parse as number
2655 int64_t intValue;
2656 OptionalParseResult parseResult = parser.parseOptionalInteger(intValue);
2657 if (parseResult.has_value() && succeeded(parseResult.value())) {
2658 MLIRContext *ctx = parser.getContext();
2659 value = IntegerAttr::get(IntegerType::get(ctx, 32), intValue);
2660 return success();
2661 }
2662 return xilinx::AIE::parseTraceEvent(parser, value);
2663}
2664
2665// Custom printer for TraceEventAttr value (uses shared helper)
2666static void printTraceRegValue(OpAsmPrinter &printer, Operation *op,
2667 Attribute value) {
2668 // if it's an intattr
2669 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(value)) {
2670 printer << intAttr.getInt();
2671 return;
2672 }
2673 xilinx::AIE::printTraceEventEnum(printer, value);
2674}
2675
2676#define GET_OP_CLASSES
2677#include "aie/Dialect/AIE/IR/AIEOps.cpp.inc"
2678
2679TileOp SwitchboxOp::getTileOp() {
2680 return cast<TileElement>(this->getOperation()).getTileOp();
2681}
2682
2683size_t SwitchboxOp::getNumSourceConnections(WireBundle bundle) {
2684 auto tile = getTileOp();
2685 const auto &targetModel = getTargetModel(*this);
2686 return targetModel.getNumSourceSwitchboxConnections(tile.getCol(),
2687 tile.getRow(), bundle);
2688}
2689
2690size_t SwitchboxOp::getNumDestConnections(WireBundle bundle) {
2691 auto tile = getTileOp();
2692 const auto &targetModel = getTargetModel(*this);
2693 return targetModel.getNumDestSwitchboxConnections(tile.getCol(),
2694 tile.getRow(), bundle);
2695}
2696
2697TileOp ShimMuxOp::getTileOp() {
2698 return cast<TileElement>(this->getOperation()).getTileOp();
2699}
2700
2701WireBundle xilinx::AIE::getConnectingBundle(WireBundle dir) {
2702 switch (dir) {
2703 case WireBundle::North:
2704 return WireBundle::South;
2705 case WireBundle::South:
2706 return WireBundle::North;
2707 case WireBundle::East:
2708 return WireBundle::West;
2709 case WireBundle::West:
2710 return WireBundle::East;
2711 default:
2712 return dir;
2713 }
2714}
2715
2716//===----------------------------------------------------------------------===//
2717// BDChainOp
2718//===----------------------------------------------------------------------===//
2719
2720ParseResult BDChainOp::parse(OpAsmParser &parser, OperationState &result) {
2721 SmallVector<OpAsmParser::Argument> entryArgs;
2722
2723 // Symbol name, e.g. @my_chain
2724 StringAttr symNameAttr;
2725 if (parser.parseSymbolName(symNameAttr, SymbolTable::getSymbolAttrName(),
2726 result.attributes)) {
2727 return failure();
2728 }
2729
2730 // Entry arguments (placeholders), e.g. (%addr: memref<1xi32>)
2731 ParseResult argParseResult = parser.parseCommaSeparatedList(
2732 OpAsmParser::Delimiter::Paren, [&]() -> ParseResult {
2733 OpAsmParser::Argument argument;
2734 if (parser.parseArgument(argument, true, true)) {
2735 return failure();
2736 }
2737 entryArgs.push_back(argument);
2738 return success();
2739 });
2740 if (argParseResult) {
2741 return argParseResult;
2742 }
2743
2744 // BD Chain Body
2745 auto *body = result.addRegion();
2746 ParseResult bodyParseResult = parser.parseRegion(*body, entryArgs, false);
2747 if (bodyParseResult) {
2748 return bodyParseResult;
2749 }
2750
2751 return success();
2752}
2753
2754void BDChainOp::print(OpAsmPrinter &printer) {
2755 auto taskName =
2756 (*this)
2757 ->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName())
2758 .getValue();
2759 printer << ' ';
2760 printer.printSymbolName(taskName);
2761
2762 Region &body = getRegion();
2763 auto argsIter = body.getArguments();
2764 printer << '(';
2765 for (auto it = argsIter.begin(); it != argsIter.end(); ++it) {
2766 if (it != argsIter.begin()) {
2767 printer << ", ";
2768 }
2769 printer.printRegionArgument(*it);
2770 }
2771 printer << ')';
2772
2773 printer << ' ';
2774 printer.printRegion(body, false, true);
2775}
2776
2777//===----------------------------------------------------------------------===//
2778// ShimDMAAllocationOp
2779//===----------------------------------------------------------------------===//
2780
2781LogicalResult ShimDMAAllocationOp::verify() {
2782 TileLike tileLike = llvm::dyn_cast<TileLike>(getTile().getDefiningOp());
2783 if (!tileLike) {
2784 return emitOpError("tile operand must implement TileLike interface");
2785 }
2786
2787 if (!tileLike.isShimNOCorPLTile()) {
2788 // if placed, provide detailed error message
2789 auto col = tileLike.tryGetCol();
2790 auto row = tileLike.tryGetRow();
2791 if (col && row) {
2792 return emitOpError("tile must be a shim tile, but got tile(")
2793 << *col << ", " << *row << ")";
2794 }
2795 return emitOpError("tile must be a shim tile");
2796 }
2797
2798 return success();
2799}
2800
2801TileOp ShimDMAAllocationOp::getTileOp() {
2802 return cast<TileOp>(getTile().getDefiningOp());
2803}
2804
2805ShimDMAAllocationOp ShimDMAAllocationOp::getForSymbol(DeviceOp device,
2806 llvm::StringRef symbol) {
2807 Operation *maybeOp = device.lookupSymbol(symbol);
2808 if (maybeOp) {
2809 if (ShimDMAAllocationOp op = dyn_cast<ShimDMAAllocationOp>(maybeOp)) {
2810 return op;
2811 }
2812 }
2813 return nullptr;
2814}
2815
2816//===----------------------------------------------------------------------===//
2817// RuntimeSequenceOp
2818//===----------------------------------------------------------------------===//
2819
2820ParseResult RuntimeSequenceOp::parse(OpAsmParser &parser,
2821 OperationState &result) {
2822
2823 // Name of this runtime sequence
2824 StringAttr nameAttr;
2825 (void)parser.parseOptionalSymbolName(
2826 nameAttr, mlir::SymbolTable::getSymbolAttrName(), result.attributes);
2827
2828 SmallVector<OpAsmParser::Argument> entryArgs;
2829
2830 // Entry arguments, e.g. (%addr: memref<1xi32>)
2831 ParseResult argParseResult = parser.parseCommaSeparatedList(
2832 OpAsmParser::Delimiter::Paren, [&]() -> ParseResult {
2833 OpAsmParser::Argument argument;
2834 if (parser.parseArgument(argument, true, true)) {
2835 return failure();
2836 }
2837 entryArgs.push_back(argument);
2838 return success();
2839 });
2840 if (argParseResult) {
2841 return argParseResult;
2842 }
2843
2844 // Body
2845 auto *body = result.addRegion();
2846 ParseResult bodyParseResult = parser.parseRegion(*body, entryArgs, false);
2847 if (bodyParseResult) {
2848 return bodyParseResult;
2849 }
2850
2851 return success();
2852}
2853
2854void RuntimeSequenceOp::print(OpAsmPrinter &printer) {
2855 Region &body = getRegion();
2856
2857 auto nameAttr = (*this)->getAttrOfType<StringAttr>(
2858 mlir::SymbolTable::getSymbolAttrName());
2859 if (nameAttr &&
2860 nameAttr != ::mlir::OpBuilder((*this)->getContext())
2861 .getStringAttr(getDefaultRuntimeSequenceName())) {
2862 printer << ' ';
2863 printer.printSymbolName(nameAttr);
2864 }
2865
2866 printer << '(';
2867 for (unsigned i = 0, n = body.getNumArguments(); i < n; i++) {
2868 if (i > 0) {
2869 printer << ", ";
2870 }
2871 printer.printRegionArgument(body.getArgument(i));
2872 }
2873 printer << ')';
2874
2875 printer << ' ';
2876 printer.printRegion(body, false, true);
2877}
2878
2879LogicalResult RuntimeSequenceOp::verify() {
2880 DeviceOp device = (*this)->getParentOfType<DeviceOp>();
2881 if (!device) {
2882 // this check is redudnant with the HasParent trait, but can't hurt
2883 (*this)->emitOpError() << "must be inside AIE device operation.";
2884 return failure();
2885 }
2886 return success();
2887}
2888
2889RuntimeSequenceOp
2890RuntimeSequenceOp::getForSymbolInDevice(DeviceOp deviceOp,
2891 llvm::StringRef symbol) {
2892 RuntimeSequenceOp runtimeSequenceOp;
2893 if (!symbol.size()) {
2894 runtimeSequenceOp = *deviceOp.getOps<RuntimeSequenceOp>().begin();
2895 } else {
2896 Operation *maybeRuntimeSequenceOp =
2897 mlir::SymbolTable::lookupSymbolIn(deviceOp, symbol);
2898 if (!maybeRuntimeSequenceOp) {
2899 return nullptr;
2900 }
2901 runtimeSequenceOp =
2902 llvm::dyn_cast<RuntimeSequenceOp>(maybeRuntimeSequenceOp);
2903 }
2904 return runtimeSequenceOp;
2905}
2906
2907RuntimeSequenceOp
2908RuntimeSequenceOp::getForSymbolInDeviceOrError(DeviceOp deviceOp,
2909 llvm::StringRef symbol) {
2910 RuntimeSequenceOp runtimeSequenceOp = getForSymbolInDevice(deviceOp, symbol);
2911 if (!runtimeSequenceOp) {
2912 if (!symbol.empty()) {
2913 deviceOp.emitError("No such runtime sequence: ") << symbol;
2914 } else {
2915 deviceOp.emitError("No runtime sequence in device");
2916 }
2917 }
2918 return runtimeSequenceOp;
2919}
2920
2921LogicalResult RuntimeSequenceOp::verifyBeforeMaterialization() {
2922 // Check that all symbol references within the runtime sequence
2923 // are either to ShimDMAAllocationOp, DeviceOp or another RuntimeSequenceOp;
2924 // these are the only symbols that can be lowered with the NPU passes
2925 auto result = (*this)->walk([&](Operation *op) {
2926 for (NamedAttribute namedAttr : op->getAttrs()) {
2927 Attribute attr = namedAttr.getValue();
2928 auto walkResult = attr.walk([&](SymbolRefAttr symbolRef) {
2929 Operation *symbolDefOp =
2930 SymbolTable::lookupNearestSymbolFrom(*this, symbolRef);
2931 if (symbolDefOp) {
2932 if (!llvm::isa<ShimDMAAllocationOp>(symbolDefOp) &&
2933 !llvm::isa<DeviceOp>(symbolDefOp) &&
2934 !llvm::isa<RuntimeSequenceOp>(symbolDefOp) &&
2935 !llvm::isa<BufferOp>(symbolDefOp) &&
2936 !llvm::isa<memref::GlobalOp>(symbolDefOp)) {
2937 op->emitOpError()
2938 << "references symbol '"
2939 << symbolRef.getRootReference().getValue()
2940 << "' which must be either a ShimDMAAllocationOp, DeviceOp, "
2941 "RuntimeSequenceOp, BufferOp or GlobalOp, but got: "
2942 << symbolDefOp->getName().getStringRef();
2943 return WalkResult::interrupt();
2944 }
2945 if (BufferOp bufferOp = llvm::dyn_cast<BufferOp>(symbolDefOp)) {
2946 if (!bufferOp.getAddress()) {
2947 op->emitOpError()
2948 << "Unallocated buffer; fixed addresses are required before "
2949 "runtime sequence materialization.";
2950 return WalkResult::interrupt();
2951 }
2952 }
2953 }
2954 return WalkResult::advance();
2955 });
2956 if (walkResult.wasInterrupted()) {
2957 return WalkResult::interrupt();
2958 }
2959 }
2960 return WalkResult::advance();
2961 });
2962
2963 if (result.wasInterrupted()) {
2964 return failure();
2965 }
2966
2967 return success();
2968}
2969
2970// Include implementations for custom attributes
2971#define GET_ATTRDEF_CLASSES
2972#include "aie/Dialect/AIE/IR/AIEAttrs.cpp.inc"
bool isLegalTileConnection(TileOp tile, const AIETargetModel &targetModel, MasterSetOp masterOp, PacketRulesOp slaveOp)
virtual AIEArch getTargetArch() const =0
Return the target architecture.
virtual uint32_t getNumLocks(AIETileType tileType) const =0
Return the number of lock objects for a given tile type.
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 getNumBDs(AIETileType tileType) const =0
Return the number of buffer descriptors for a given tile type.
virtual uint32_t getNumDestSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a switchbox.
virtual uint32_t getNumSourceSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a switchbox.
bool empty(const std::string &s)
Definition cxxopts.hpp:275
std::shared_ptr< Value > value()
Definition cxxopts.hpp:1026
Include the generated interface declarations.
void printObjectFifoConsumerTiles(mlir::OpAsmPrinter &printer, mlir::Operation *op, mlir::OperandRange tiles, BDDimLayoutArrayArrayAttr dimensions)
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)
TileID { friend std::ostream &operator<<(std::ostream &os, const TileID &s) { os<< "TileID("<< s.col<< ", "<< s.row<< ")" TileID
friend std::string to_string(const TileID &s)
DMAChannel { DMAChannelDir direction DMAChannel
Definition AIEDialect.h:173
uint32_t getShimBurstLengthEncoding(const AIE::AIETargetModel &tm, uint32_t burstLength)
int32_t getBufferBaseAddress(mlir::Operation *bufOp)
Port { WireBundle bundle Port
Definition AIEDialect.h:128
const AIETargetModel & getTargetModel(mlir::Operation *op)
PathEndPoint src
mlir::ParseResult parseObjectFifoProducerTile(mlir::OpAsmParser &parser, mlir::OpAsmParser::UnresolvedOperand &operand, BDDimLayoutArrayAttr &dimensions)
void printTraceEventEnum(mlir::AsmPrinter &printer, mlir::Attribute attr)
mlir::LogicalResult myVerifyOffsetSizeAndStrideOp(mlir::OffsetSizeAndStrideOpInterface op)
void printObjectFifoProducerTile(mlir::OpAsmPrinter &printer, mlir::Operation *op, mlir::Value tile, BDDimLayoutArrayAttr dimensions)
mlir::ParseResult parseObjectFifoConsumerTiles(mlir::OpAsmParser &parser, llvm::SmallVectorImpl< mlir::OpAsmParser::UnresolvedOperand > &tiles, BDDimLayoutArrayArrayAttr &dimensions)
WireBundle getConnectingBundle(WireBundle dir)
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)
static mlir::LogicalResult verifyTrait(mlir::Operation *op)
static mlir::LogicalResult verifyTrait(mlir::Operation *op)