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/Interfaces/FoldInterfaces.h"
19#include "mlir/Transforms/InliningUtils.h"
20
21#include "llvm/ADT/SmallSet.h"
22#include "llvm/ADT/TypeSwitch.h"
23
24using namespace mlir;
25using namespace xilinx::AIE;
26
27// Add TableGen'erated dialect definitions (including constructor)
28// We implement the initialize() function further below
29#include "aie/Dialect/AIE/IR/AIEDialect.cpp.inc"
30
31#define GET_TYPEDEF_CLASSES
32#include "aie/Dialect/AIE/IR/AIETypes.cpp.inc"
33
34namespace {
35
36struct AIEInlinerInterface : DialectInlinerInterface {
37 using DialectInlinerInterface::DialectInlinerInterface;
38 // We don't have any special restrictions on what can be inlined into
39 // destination regions. Always allow it.
40 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
41 IRMapping &valueMapping) const final override {
42 return true;
43 }
44
45 // Operations in aie dialect are always legal to inline since they are
46 // pure.
47 bool isLegalToInline(Operation *op, Region *, bool wouldBeCloned,
48 IRMapping &) const final override {
49 return true;
50 }
51
52 // Handle the given inlined terminator by replacing it with a new operation
53 // as necessary. Required when the inlined region has more than one block.
54 void handleTerminator(Operation *op, Block *newDest) const final override {}
55
56 // Handle the given inlined terminator by replacing it with a new operation
57 // as necessary. Required when the region has only one block.
58 void handleTerminator(Operation *op,
59 ValueRange valuesToRepl) const final override {}
60};
61
62struct AIEDialectFoldInterface : DialectFoldInterface {
63 using DialectFoldInterface::DialectFoldInterface;
64
65 /// Registered hook to check if the given region, which is attached to an
66 /// operation that is *not* isolated from above, should be used when
67 /// materializing constants.
68 bool shouldMaterializeInto(Region *region) const final override {
69 // If this is an AIE::CoreOp region, then insert into it.
70 return isa<CoreOp>(region->getParentOp());
71 }
72};
73
74} // end anonymous namespace
75
76void AIEDialect::initialize() {
77 addTypes<
78#define GET_TYPEDEF_LIST
79#include "aie/Dialect/AIE/IR/AIETypes.cpp.inc"
80 >();
81 addAttributes<
82#define GET_ATTRDEF_LIST
83#include "aie/Dialect/AIE/IR/AIEAttrs.cpp.inc"
84 >();
85 addOperations<
86#define GET_OP_LIST
87#include "aie/Dialect/AIE/IR/AIEOps.cpp.inc"
88 >();
89 addInterfaces<AIEInlinerInterface, AIEDialectFoldInterface>();
90}
91
92// Helper class to get a ShimDMAAllocationOp for a given <device, symbol name>
93// pair. An object of this class is invalidated if, for any symbol_name, a
94// ShimDMAAllocationOp that uses it changes, as the cache is not updated in
95// this case.
96
97// Return the first ShimDMAAllocationOp nested inside the DeviceOp 'dev' that
98// uses the symbol 'sym_name'
99std::optional<xilinx::AIE::ShimDMAAllocationOp>
100xilinx::AIE::ShimDMAllocationGetter::get(DeviceOp dev, StringRef sym_name) {
101 auto key = std::make_pair(dev, sym_name);
102 auto it = allocGetter.find(key);
103 if (it != allocGetter.end()) {
104 return it->second;
105 }
106
107 // Call cachelessGet to look for the allocation operation
108 auto allocOp = cachelessGet(dev, sym_name);
109
110 // Only cache the value if it's not empty (i.e., not std::nullopt)
111 if (allocOp.has_value()) {
112 allocGetter[key] = allocOp; // Cache it
113 }
114
115 return allocOp; // Return the found or empty optional
116}
117
118// Finding the ShimDMAAllocationOp for a given <DeviceOp, symbol_name> pair
119// can be slow when the symbol is used in many places. This version of the
120// function is only called when the cache does not have a ShimDMAAllocationOp
121// stored from a previous lookup.
122std::optional<xilinx::AIE::ShimDMAAllocationOp>
123xilinx::AIE::ShimDMAllocationGetter::cachelessGet(DeviceOp dev,
124 mlir::StringRef sym_name) {
125 auto *sym = dev.lookupSymbol(sym_name);
126 if (!sym)
127 return std::nullopt;
128 auto uses = SymbolTable::getSymbolUses(sym, dev);
129 for (auto use : *uses)
130 if (auto infoOp = dyn_cast<AIE::ShimDMAAllocationOp>(use.getUser()))
131 return infoOp;
132 return std::nullopt;
133}
134
135// Helper methods to retrieve the encoding associated to a burst length,
136// or to find the highest available burst length if the requested one is 0
137// (default value).
138
139static std::pair<uint32_t, uint32_t>
140getShimBurstLength(const xilinx::AIE::AIETargetModel &tm,
141 uint32_t burstLength) {
142
143 std::vector<std::pair<uint32_t, uint32_t>> bel =
145
146 // If we have the default burst length (no burst length was specified),
147 // use the highest one available on our target model
148 if (burstLength == 0) {
149 return *std::max_element(
150 bel.begin(), bel.end(),
151 [](auto pair1, auto pair2) { return pair1.second < pair2.second; });
152 }
153
154 // Note that if we are given a burst size, we are checking its existence in
155 // the pass verification already, so we can safely assume it exists.
156 return *std::find_if(bel.begin(), bel.end(),
157 [=](auto p) { return p.second == burstLength; });
158}
159
161 uint32_t burstLength) {
162
163 return getShimBurstLength(tm, burstLength).second;
164}
165
167 uint32_t burstLength) {
168
169 return getShimBurstLength(tm, burstLength).first;
170}
171
172LogicalResult
173xilinx::AIE::myVerifyOffsetSizeAndStrideOp(OffsetSizeAndStrideOpInterface op) {
174 std::array<unsigned, 3> maxRanks = op.getArrayAttrMaxRanks();
175 if (!(op.getMixedOffsets().size() == 1 && maxRanks[0] == 1) && // NOLINT
176 op.getMixedOffsets().size() != op.getMixedSizes().size())
177 return op->emitError(
178 "expected mixed offsets rank to match mixed sizes rank (")
179 << op.getMixedOffsets().size() << " vs " << op.getMixedSizes().size()
180 << ") so the rank of the result type is well-formed.";
181 if (failed(verifyListOfOperandsOrIntegers(
182 op, "offset", maxRanks[0], op.getStaticOffsets(), op.getOffsets())))
183 return failure();
184 if (failed(verifyListOfOperandsOrIntegers(
185 op, "size", maxRanks[1], op.getStaticSizes(), op.getSizes())))
186 return failure();
187 if (failed(verifyListOfOperandsOrIntegers(
188 op, "stride", maxRanks[2], op.getStaticStrides(), op.getStrides())))
189 return failure();
190 for (int64_t offset : op.getStaticOffsets())
191 if (offset < 0 && !ShapedType::isDynamic(offset))
192 return op->emitError("expected offsets to be non-negative, but got ")
193 << offset;
194 for (int64_t size : op.getStaticSizes())
195 if (size < 0 && !ShapedType::isDynamic(size))
196 return op->emitError("expected sizes to be non-negative, but got ")
197 << size;
198
199 return success();
200}
201
202static VC1902TargetModel VC1902model;
203static VE2302TargetModel VE2302model;
204static VE2802TargetModel VE2802model;
205static VirtualizedNPU1TargetModel NPUmodel1col(1);
206static VirtualizedNPU1TargetModel NPUmodel2col(2);
207static VirtualizedNPU1TargetModel NPUmodel3col(3);
208static VirtualizedNPU1TargetModel NPUmodel4col(4);
209static NPU2TargetModel NPU2model;
210static VirtualizedNPU2TargetModel NPU2model1col(1);
211static VirtualizedNPU2TargetModel NPU2model2col(2);
212static VirtualizedNPU2TargetModel NPU2model3col(3);
213static VirtualizedNPU2TargetModel NPU2model4col(4);
214static VirtualizedNPU2TargetModel NPU2model5col(5);
215static VirtualizedNPU2TargetModel NPU2model6col(6);
216static VirtualizedNPU2TargetModel NPU2model7col(7);
217
218const AIETargetModel &xilinx::AIE::getTargetModel(Operation *op) {
219 if (auto t = dyn_cast<AIETarget>(op))
220 return t.getTargetModel();
221 if (auto t = op->getParentOfType<AIETarget>())
222 return t.getTargetModel();
223
224 // For backward compatibility, return a basic device model compatible with
225 // the VCK190
226 return VC1902model;
227}
228
230 switch (device) {
231 case AIEDevice::xcvc1902:
232 return VC1902model;
233 case AIEDevice::xcve2302:
234 return VE2302model;
235 case AIEDevice::xcve2802:
236 return VE2802model;
237 case AIEDevice::npu1:
238 return NPUmodel4col;
239 case AIEDevice::npu1_1col:
240 return NPUmodel1col;
241 case AIEDevice::npu1_2col:
242 return NPUmodel2col;
243 case AIEDevice::npu1_3col:
244 return NPUmodel3col;
245 case AIEDevice::npu2:
246 return NPU2model;
247 case AIEDevice::npu2_1col:
248 return NPU2model1col;
249 case AIEDevice::npu2_2col:
250 return NPU2model2col;
251 case AIEDevice::npu2_3col:
252 return NPU2model3col;
253 case AIEDevice::npu2_4col:
254 return NPU2model4col;
255 case AIEDevice::npu2_5col:
256 return NPU2model5col;
257 case AIEDevice::npu2_6col:
258 return NPU2model6col;
259 case AIEDevice::npu2_7col:
260 return NPU2model7col;
261 }
262 return VC1902model;
263}
264
265// Walk the operation hierarchy until we find a containing TileElement.
266// If no parent is a TileElement, then return null.
267static TileElement getParentTileElement(Operation *op) {
268 auto *parent = op->getParentOp();
269 while (!llvm::isa_and_nonnull<DeviceOp, ModuleOp>(parent)) {
270 if (auto element = llvm::dyn_cast<TileElement>(parent))
271 return element;
272 parent = parent->getParentOp();
273 }
274 return llvm::dyn_cast<TileElement>(parent);
275}
276
277namespace {
278
279struct UsesAreAccessible {
280 static LogicalResult verifyTrait(Operation *op) {
281 auto thisElement = cast<TileElement>(op);
282 auto thisID = thisElement.getTileID();
283 auto users = op->getResult(0).getUsers();
284 const auto &targetModel = getTargetModel(op);
285 for (auto *user : users) {
286 // AIE.useLock may be used in a device to set the lock's default value
287 // Allow in a toplevel module for backward compatibility
288 if (llvm::isa_and_nonnull<DeviceOp, ModuleOp>(user->getParentOp())) {
289 continue;
290 }
291 // If any parent or the user itself prescribe that accessibility checks be
292 // skipped, skip the check for that user.
293 if (user->getParentWithTrait<SkipAccessibilityCheckTrait>() ||
294 user->hasTrait<SkipAccessibilityCheckTrait>()) {
295 continue;
296 }
297 TileElement element = llvm::dyn_cast<TileElement>(user);
298 if (!element) {
299 element = getParentTileElement(user);
300 }
301 if (!element) {
302 // This should probably be caught elsewhere as well.
303 return op->emitOpError("is accessed outside of a tile")
304 .attachNote(user->getLoc())
305 << "user";
306 }
307 auto tileID = element.getTileID();
308 if (!targetModel.isLegalMemAffinity(tileID.col, tileID.row, thisID.col,
309 thisID.row)) {
310 return (op->emitOpError("in Column ")
311 << thisID.col << " and Row " << thisID.row
312 << " is accessed from an unreachable tile in Column "
313 << tileID.col << " and Row " << tileID.row)
314 .attachNote(user->getLoc())
315 << "user";
316 }
317 }
318 return success();
319 }
320};
321
322} // namespace
323
324// Check that the operation only contains terminators in
325// TerminatorOpTypes.
326template <typename... TerminatorOpTypes>
328 static LogicalResult verifyTrait(Operation *op) {
329 for (auto &region : op->getRegions()) {
330 for (auto &block : region) {
331 if (!block.empty()) {
332 if (Operation *operation = &block.back();
333 !llvm::isa_and_nonnull<TerminatorOpTypes...>(operation))
334 return operation->emitOpError("is not an allowed terminator")
335 .attachNote(op->getLoc())
336 .append("in this context: ");
337 }
338 }
339 }
340 return success();
341 }
342};
343
344// Check that the given DMA-like op (e.g. MemOp, ShimDMAOp)
345// has valid BDs.
346template <typename ConcreteType>
347LogicalResult HasValidBDs<ConcreteType>::verifyTrait(Operation *op) {
348 auto element = cast<ConcreteType>(op);
349 const auto &targetModel = getTargetModel(op);
350 int bdMax =
351 targetModel.getNumBDs(element.getTileID().col, element.getTileID().row);
352
353 int bdNum = 0;
354 for (auto &block : element.getBody()) {
355 if (!block.template getOps<DMABDOp>().empty()) {
356 if (bdNum >= bdMax) {
357 auto bd = *block.template getOps<DMABDOp>().begin();
358 return (op->emitOpError("has more than ") << bdMax << " blocks")
359 .attachNote(bd.getLoc())
360 .append("no space for this bd: ");
361 }
362 bdNum++;
363 }
364 }
365 return success();
366}
367
368// Check that the given DMA-like op (e.g. MemOp, ShimDMAOp)
369// has valid DMA channels.
370template <typename ConcreteType>
372 DenseSet<DMAChannel> inputChannels;
373 DenseSet<DMAChannel> outputChannels;
374 auto element = cast<ConcreteType>(op);
375 Region &body = element.getBody();
376 if (body.empty())
377 return op->emitOpError("should have non-empty body");
378 for (auto &bodyOp : body.getOps()) {
379 // check for duplicate DMA channels within the same ShimDMAOp
380 if (auto dmaStart = dyn_cast<DMAStartOp>(bodyOp)) {
381 DMAChannel dmaChan = {dmaStart.getChannelDir(),
382 dmaStart.getChannelIndex()};
383 // check if number of input and output channels is more than available
384 // hardware
385 if (dmaChan.direction == DMAChannelDir::S2MM)
386 inputChannels.insert(dmaChan);
387 else
388 outputChannels.insert(dmaChan);
389 }
390 }
391
392 if (inputChannels.size() >
393 element.getTileOp().getNumSourceConnections(WireBundle::DMA))
394 return op->emitOpError(
395 "uses more input channels than available on this tile");
396
397 if (outputChannels.size() >
398 element.getTileOp().getNumDestConnections(WireBundle::DMA))
399 return op->emitOpError(
400 "uses more output channels than available on this tile");
401 return success();
402}
403
404//===----------------------------------------------------------------------===//
405// ObjectFifoCreateOp
406//===----------------------------------------------------------------------===//
407
408LogicalResult ObjectFifoCreateOp::verify() {
409 if (isa<ArrayAttr>(getElemNumber())) {
410 if (size_t numDepths = dyn_cast<ArrayAttr>(getElemNumber()).size();
411 numDepths != getConsumerTiles().size() + 1) // +1 for producer depth
412 return emitOpError("does not have enough depths specified for producer "
413 "and for each consumer.");
414 }
415
416 if (getProducerTileOp().isShimTile() && !getDimensionsToStream().empty()) {
417 return emitError(
418 "`dimensionsToStream` data layout transformations are not supported "
419 "on shim tile producers");
420 }
421
422 if (getViaSharedMem().has_value()) {
423 if (getConsumerTiles().size() > 1)
424 return emitError(
425 "`via_shared_mem` can only be used in 1-to-1 object FIFOs");
426 if (getVia_DMA())
427 return emitError("`via_shared_mem` and `via_DMA` cannot occur together");
428 }
429
430 if (getRepeatCount().has_value()) {
431 if (getProducerTileOp().isShimTile())
432 return emitError("`repeat_count` unavailable for shim tiles");
433 }
434
435 if (getInitValues().has_value()) {
436 if (getProducerTileOp().isShimTile())
437 return emitError("`init_values` unavailable for shim tiles");
438 }
439
440 if (getInitValues().has_value()) {
441 if ((int)getInitValues().value().size() != size())
442 return emitError("`init_values` does not initialize all objects");
443 }
444
445 return success();
446}
447
448TileOp ObjectFifoCreateOp::getProducerTileOp() {
449 return cast<TileOp>(getProducerTile().getDefiningOp());
450}
451
453 OpAsmParser &parser, OpAsmParser::UnresolvedOperand &operand,
454 BDDimLayoutArrayAttr &dimensions) {
455 std::vector<BDDimLayoutAttr> emptyDims = {};
456 if (parser.parseOperand(operand))
457 return failure();
458 if (succeeded(parser.parseOptionalKeyword("dimensionsToStream"))) {
459 if (parser.parseCustomAttributeWithFallback<BDDimLayoutArrayAttr>(
460 dimensions)) {
461 return failure();
462 }
463 } else {
464 dimensions =
465 BDDimLayoutArrayAttr::get(parser.getContext(), ArrayRef(emptyDims));
466 }
467 return success();
468}
469
470void xilinx::AIE::printObjectFifoProducerTile(OpAsmPrinter &printer,
471 Operation *op, Value operand,
472 BDDimLayoutArrayAttr dimensions) {
473 printer << operand;
474 if (!dimensions.empty()) {
475 printer << " dimensionsToStream ";
476 printer.printStrippedAttrOrType(dimensions);
477 }
478}
479
481 OpAsmParser &parser, SmallVectorImpl<OpAsmParser::UnresolvedOperand> &tiles,
482 BDDimLayoutArrayArrayAttr &dimensions) {
483 // parseCommaSeparatedList doesn't handle the missing case for "none",
484 // so we handle it custom here.
485 std::vector<BDDimLayoutArrayAttr> tileDims = {};
486
487 auto parseOneOperand = [&]() -> ParseResult {
488 if (parser.parseOperand(tiles.emplace_back(), true)) {
489 return failure();
490 }
491 // By default, create empty dimensions array for each consumer; this way,
492 // we can be certain to have as many entries in the dimensions array as
493 // there are customer
494 BDDimLayoutArrayAttr dimAttr =
495 BDDimLayoutArrayAttr::get(parser.getContext(), {});
496
497 if (succeeded(parser.parseOptionalKeyword("dimensionsFromStream"))) {
498 // If specified, parse actual data layout transform dimensions
499 if (parser.parseCustomAttributeWithFallback<BDDimLayoutArrayAttr>(
500 dimAttr)) {
501 return failure();
502 }
503 }
504 tileDims.emplace_back(dimAttr);
505 return success();
506 };
507
508 if (parser.parseCommaSeparatedList(AsmParser::Delimiter::None,
509 parseOneOperand, " in operand list"))
510 return failure();
511
512 dimensions = BDDimLayoutArrayArrayAttr::get(parser.getContext(), tileDims);
513 return success();
514}
515
517 OpAsmPrinter &printer, Operation *op, OperandRange tiles,
518 BDDimLayoutArrayArrayAttr dimsPerTileAttr) {
519 size_t tileIdx = 0;
520 for (auto tile : tiles) {
521 printer << tile;
522 if (dimsPerTileAttr && tileIdx < dimsPerTileAttr.size() &&
523 dimsPerTileAttr[tileIdx] && !dimsPerTileAttr[tileIdx].empty()) {
524 printer << " dimensionsFromStream ";
525 printer.printStrippedAttrOrType(dimsPerTileAttr[tileIdx]);
526 }
527 if (tileIdx < tiles.size() - 1) {
528 printer << ", ";
529 }
530 tileIdx++;
531 }
532}
533
534static void printObjectFifoInitValues(OpAsmPrinter &p, ObjectFifoCreateOp op,
535 Attribute numElem, TypeAttr type,
536 Attribute initValues) {
537 if (op.getInitValues()) {
538 p << "= [";
539 int depth = llvm::dyn_cast<mlir::IntegerAttr>(numElem).getInt();
540 for (int i = 0; i < depth; i++) {
541 p.printStrippedAttrOrType(llvm::dyn_cast<mlir::ArrayAttr>(initValues)[i]);
542 if (i < depth - 1) {
543 p << ", ";
544 }
545 }
546 p << "]";
547 }
548}
549
550static ParseResult parseObjectFifoInitValues(OpAsmParser &parser,
551 Attribute numElem, TypeAttr type,
552 Attribute &initValues) {
553 int depth;
554 if (isa<ArrayAttr>(numElem)) {
555 depth = llvm::dyn_cast<mlir::IntegerAttr>(
556 llvm::dyn_cast<mlir::ArrayAttr>(numElem)[0])
557 .getInt();
558 } else {
559 depth = llvm::dyn_cast<mlir::IntegerAttr>(numElem).getInt();
560 }
561 auto objfifoType = llvm::cast<AIEObjectFifoType>(type.getValue());
562 auto memrefType = llvm::cast<MemRefType>(objfifoType.getElementType());
563
564 if (!memrefType.hasStaticShape())
565 return parser.emitError(parser.getNameLoc())
566 << "type should be static shaped memref, but got " << memrefType;
567
568 if (parser.parseOptionalEqual())
569 return success();
570
571 Type tensorType = mlir::memref::getTensorTypeFromMemRefType(memrefType);
572 if (parser.parseAttribute(initValues, tensorType))
573 return failure();
574 for (int i = 0; i < depth; i++) {
575 auto initialValues = llvm::dyn_cast<mlir::ArrayAttr>(initValues);
576 if ((int)initialValues.size() != depth)
577 return parser.emitError(parser.getNameLoc())
578 << "initial values should initialize all objects";
579 if (!llvm::isa<ElementsAttr>(initialValues[i]))
580 return parser.emitError(parser.getNameLoc())
581 << "initial value should be an elements attribute";
582 }
583
584 return success();
585}
586
587//===----------------------------------------------------------------------===//
588// ObjectFifoLinkOp
589//===----------------------------------------------------------------------===//
590
591LogicalResult ObjectFifoLinkOp::verify() {
592 if (isJoin() && isDistribute())
593 return emitError("ObjectFifoLinkOp does not support 'join' and "
594 "'distribute' at the same time");
595
596 auto sharedTile = getOptionalSharedTile();
597 if (!sharedTile)
598 return emitError("ObjectFifoLinkOp must have a link point, i.e., a "
599 "shared tile between objectFifos");
600
601 TileOp tile = cast<TileOp>(sharedTile.value().getDefiningOp());
602 if (!tile.isMemTile()) {
603 if (isJoin() || isDistribute())
604 return emitError("ObjectFifoLinkOp join and distribute are "
605 "unavailable on compute or shim tiles");
606 }
607
608 if (isJoin()) {
609 if (getFifoIns().size() != getSrcOffsets().size())
610 return emitOpError("number of provided src offsets must be equal "
611 "to the number of input objectFifos");
612
613 if (!getDstOffsets().empty())
614 return emitOpError("dst offsets should be empty for join");
615
616 } else if (isDistribute()) {
617 if (getFifoOuts().size() != getDstOffsets().size())
618 return emitOpError("number of provided dst offsets must be equal "
619 "to the number of output objectFifos");
620
621 if (!getSrcOffsets().empty())
622 return emitOpError("src offsets should be empty for distribute");
623
624 ObjectFifoCreateOp fifoIn = getInputObjectFifos()[0];
625 if (!fifoIn.getDimensionsToStream().empty()) {
626 return emitOpError("currently does not support objectFifos with "
627 "dimensionsToStream.");
628 }
629 for (auto dims : fifoIn.getDimensionsFromStreamPerConsumer()) {
630 if (!dims.empty())
631 return emitOpError("currently does not support objectFifos with "
632 "dimensionsFromStreamPerConsumer.");
633 }
634
635 for (auto fifoOut : getOutputObjectFifos()) {
636 for (auto dims : fifoOut.getDimensionsFromStreamPerConsumer()) {
637 if (!dims.empty())
638 return emitOpError("currently does not support objectFifos with "
639 "dimensionsFromStreamPerConsumer.");
640 }
641 }
642
643 std::vector<int> repeat_counts;
644 for (auto fifoOut : getOutputObjectFifos()) {
645 if (fifoOut.getRepeatCount().has_value())
646 repeat_counts.push_back(fifoOut.getRepeatCount().value());
647 else
648 repeat_counts.push_back(0);
649 }
650 for (auto repeat : repeat_counts)
651 if (repeat_counts[0] != repeat)
652 return emitError("repeat counts of output object FIFOs must be equal");
653
654 } else {
655 if (!getSrcOffsets().empty() && !getDstOffsets().empty())
656 return emitOpError("all offsets should be empty if there is no "
657 "join or distribute");
658 }
659
660 return success();
661}
662
663std::optional<Value> ObjectFifoLinkOp::getOptionalSharedTile() {
664 if (isJoin()) {
665 auto fifoOut = getOutputObjectFifos()[0];
666 for (auto fifoIn : getInputObjectFifos())
667 if (fifoOut.getProducerTile() != fifoIn.getConsumerTiles()[0])
668 return {};
669 return {fifoOut.getProducerTile()};
670 }
671
672 if (isDistribute()) {
673 auto fifoIn = getInputObjectFifos()[0];
674 for (auto fifoOut : getOutputObjectFifos())
675 if (fifoIn.getConsumerTiles()[0] != fifoOut.getProducerTile())
676 return {};
677 return {fifoIn.getConsumerTiles()[0]};
678 }
679
680 auto fifoIn = getInputObjectFifos();
681 if (auto fifoOut = getOutputObjectFifos();
682 !fifoIn.empty() && !fifoOut.empty())
683 for (auto consumerIn : fifoIn[0].getConsumerTiles())
684 if (consumerIn == fifoOut[0].getProducerTile())
685 return {fifoOut[0].getProducerTile()};
686 return {};
687}
688
689std::vector<ObjectFifoCreateOp> ObjectFifoLinkOp::getInputObjectFifos() {
690 std::vector<ObjectFifoCreateOp> inputObjFifos;
691 Operation *parent = getOperation();
692 while ((parent = parent->getParentOp())) {
693 if (parent->hasTrait<OpTrait::SymbolTable>()) {
694 for (auto sym : getFifoIns()) {
695 auto name = dyn_cast<FlatSymbolRefAttr>(sym);
696 if (auto *st = SymbolTable::lookupSymbolIn(parent, name);
697 isa_and_nonnull<ObjectFifoCreateOp>(st))
698 inputObjFifos.push_back(dyn_cast<ObjectFifoCreateOp>(st));
699 }
700 }
701 }
702 return inputObjFifos;
703}
704
705std::vector<ObjectFifoCreateOp> ObjectFifoLinkOp::getOutputObjectFifos() {
706 std::vector<ObjectFifoCreateOp> outputObjFifos;
707 Operation *parent = getOperation();
708 while ((parent = parent->getParentOp())) {
709 if (parent->hasTrait<OpTrait::SymbolTable>()) {
710 for (auto sym : getFifoOuts()) {
711 auto name = dyn_cast<FlatSymbolRefAttr>(sym);
712 if (auto *st = SymbolTable::lookupSymbolIn(parent, name);
713 isa_and_nonnull<ObjectFifoCreateOp>(st))
714 outputObjFifos.push_back(dyn_cast<ObjectFifoCreateOp>(st));
715 }
716 }
717 }
718 return outputObjFifos;
719}
720
721std::vector<int> ObjectFifoLinkOp::getJoinTransferLengths() {
722 std::vector<int> lengths;
723 if (isJoin()) {
724 auto fifoOut =
725 llvm::cast<AIEObjectFifoType>(getOutputObjectFifos()[0].getElemType());
726 auto elemTypeOut = llvm::cast<MemRefType>(fifoOut.getElementType());
727 int lenOut = elemTypeOut.getNumElements();
728 for (size_t i = 0; i < getFifoIns().size(); i++) {
729 int len = 0;
730 int offset = *getConstantIntValue(getSrcOffsets()[i]);
731 if (i == getFifoIns().size() - 1)
732 len = lenOut - *getConstantIntValue(getSrcOffsets()[i]);
733 else
734 len = *getConstantIntValue(getSrcOffsets()[i + 1]) - offset;
735 lengths.push_back(len);
736 }
737 }
738 return lengths;
739}
740
741std::vector<int> ObjectFifoLinkOp::getDistributeTransferLengths() {
742 std::vector<int> lengths;
743 if (isDistribute()) {
744 auto fifoIn =
745 llvm::cast<AIEObjectFifoType>(getInputObjectFifos()[0].getElemType());
746 auto elemTypeIn = llvm::cast<MemRefType>(fifoIn.getElementType());
747 int lenIn = elemTypeIn.getNumElements();
748 for (size_t i = 0; i < getFifoOuts().size(); i++) {
749 int offset = *getConstantIntValue(getDstOffsets()[i]);
750 int len = 0;
751 if (i == getFifoOuts().size() - 1)
752 len = lenIn - *getConstantIntValue(getDstOffsets()[i]);
753 else
754 len = *getConstantIntValue(getDstOffsets()[i + 1]) - offset;
755 lengths.push_back(len);
756 }
757 }
758 return lengths;
759}
760
761std::optional<int> ObjectFifoLinkOp::getRepeatCount() {
762 for (auto fifoOut : getOutputObjectFifos())
763 if (fifoOut.getRepeatCount().has_value())
764 return {fifoOut.getRepeatCount().value()};
765 return {};
766}
767
768//===----------------------------------------------------------------------===//
769// ObjectFifoRegisterExternalBuffersOp
770//===----------------------------------------------------------------------===//
771
772LogicalResult ObjectFifoRegisterExternalBuffersOp::verify() {
773 if (!getTileOp().isShimTile())
774 return emitOpError("tile is not a shim tile");
775
776 return success();
777}
778
779TileOp ObjectFifoRegisterExternalBuffersOp::getTileOp() {
780 return cast<TileOp>(getTile().getDefiningOp());
781}
782
783ObjectFifoCreateOp ObjectFifoRegisterExternalBuffersOp::getObjectFifo() {
784 Operation *parent = getOperation();
785 while ((parent = parent->getParentOp())) {
786 if (parent->hasTrait<OpTrait::SymbolTable>()) {
787 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
788 isa_and_nonnull<ObjectFifoCreateOp>(st))
789 return dyn_cast<ObjectFifoCreateOp>(st);
790 }
791 }
792 return {};
793}
794
795//===----------------------------------------------------------------------===//
796// ObjectFifoAcquireOp
797//===----------------------------------------------------------------------===//
798
799LogicalResult ObjectFifoAcquireOp::verify() {
800 auto parent = getOperation()->getParentOfType<CoreOp>();
801 if (parent == nullptr)
802 return emitOpError("must be called from inside a CoreOp");
803
804 auto coreTile = parent.getTile();
805 auto objFifo = getObjectFifo();
806 if (getPort() == ObjectFifoPort::Produce) {
807 if (coreTile != objFifo.getProducerTile())
808 return parent.emitOpError(
809 "producer port of objectFifo accessed by core running "
810 "on non-producer tile");
811 } else if (getPort() == ObjectFifoPort::Consume) {
812 bool found = false;
813 for (auto consumerTile : objFifo.getConsumerTiles()) {
814 if (coreTile == consumerTile) {
815 found = true;
816 break;
817 }
818 }
819 if (!found)
820 return parent.emitOpError(
821 "consumer port of objectFifo accessed by core running "
822 "on non-consumer tile");
823 }
824
825 auto objFifoElem =
826 llvm::cast<AIEObjectFifoType>(getObjectFifo().getElemType())
827 .getElementType();
828 auto objFifoSubviewElem =
829 llvm::cast<AIEObjectFifoSubviewType>(getResult().getType())
830 .getElementType();
831 if (objFifoElem != objFifoSubviewElem)
832 return emitOpError(
833 "ObjectFifo element and ObjectFifoSubview element must match.\n");
834
835 return success();
836}
837
838ObjectFifoCreateOp ObjectFifoAcquireOp::getObjectFifo() {
839 Operation *parent = getOperation();
840 while ((parent = parent->getParentOp())) {
841 if (parent->hasTrait<OpTrait::SymbolTable>()) {
842 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
843 isa_and_nonnull<ObjectFifoCreateOp>(st))
844 return dyn_cast<ObjectFifoCreateOp>(st);
845 }
846 }
847 return {};
848}
849
850//===----------------------------------------------------------------------===//
851// ObjectFifoReleaseOp
852//===----------------------------------------------------------------------===//
853
854LogicalResult ObjectFifoReleaseOp::verify() {
855 auto parent = getOperation()->getParentOfType<CoreOp>();
856 if (parent == nullptr)
857 return emitOpError("must be called from inside a CoreOp");
858
859 auto coreTile = parent.getTile();
860 auto objFifo = getObjectFifo();
861 if (getPort() == ObjectFifoPort::Produce) {
862 if (coreTile != objFifo.getProducerTile())
863 return parent.emitOpError(
864 "producer port of objectFifo accessed by core running "
865 "on non-producer tile");
866 } else if (getPort() == ObjectFifoPort::Consume) {
867 bool found = false;
868 for (auto consumerTile : objFifo.getConsumerTiles()) {
869 if (coreTile == consumerTile) {
870 found = true;
871 break;
872 }
873 }
874 if (!found)
875 return parent.emitOpError(
876 "consumer port of objectFifo accessed by core running "
877 "on non-consumer tile");
878 }
879
880 return success();
881}
882
883ObjectFifoCreateOp ObjectFifoReleaseOp::getObjectFifo() {
884 Operation *parent = getOperation();
885 while ((parent = parent->getParentOp())) {
886 if (parent->hasTrait<OpTrait::SymbolTable>()) {
887 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
888 isa_and_nonnull<ObjectFifoCreateOp>(st))
889 return dyn_cast<ObjectFifoCreateOp>(st);
890 }
891 }
892 return {};
893}
894
895//===----------------------------------------------------------------------===//
896// ObjectFifoSubviewAccessOp
897//===----------------------------------------------------------------------===//
898
899LogicalResult ObjectFifoSubviewAccessOp::verify() {
900 if (auto parent = getOperation()->getParentOfType<CoreOp>();
901 parent == nullptr)
902 return emitOpError("must be called from inside a CoreOp");
903
904 if (auto acqOp = getSubview().getDefiningOp<ObjectFifoAcquireOp>();
905 getIndex() >= acqOp.acqNumber())
906 return emitOpError("accessed farther than number of acquired elements "
907 "(index out of bounds).");
908
909 return success();
910}
911
912//===----------------------------------------------------------------------===//
913// ObjectFifoRegisterProcessOp
914//===----------------------------------------------------------------------===//
915
916LogicalResult ObjectFifoRegisterProcessOp::verify() {
917 if (getProcessLength() < 1)
918 return emitOpError("process length must be >= 1");
919
920 if (getAcquirePattern().size() != getReleasePattern().size()) {
921 // acquire pattern size = process length (i.e., release pattern will be
922 // duplicated by process length times) OR the other way around
923 if (getAcquirePattern().size() != getProcessLength() &&
924 getProcessLength() != getReleasePattern().size())
925 return emitOpError(
926 "Acquire and Release patterns must be of equal length, or "
927 "longest length of one must be equal to process "
928 "length of the other");
929 }
930
931 return success();
932}
933
934ObjectFifoCreateOp ObjectFifoRegisterProcessOp::getObjectFifo() {
935 Operation *parent = getOperation();
936 while ((parent = parent->getParentOp())) {
937 if (parent->hasTrait<OpTrait::SymbolTable>()) {
938 if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
939 isa_and_nonnull<ObjectFifoCreateOp>(st))
940 return dyn_cast<ObjectFifoCreateOp>(st);
941 }
942 }
943 return {};
944}
945
946//===----------------------------------------------------------------------===//
947// CascadeFlowOp
948//===----------------------------------------------------------------------===//
949
950LogicalResult CascadeFlowOp::verify() {
951 TileOp src = getSourceTileOp();
952 TileOp dst = getDestTileOp();
953 const auto &t = getTargetModel(src);
954
955 if (src.isShimTile() || dst.isShimTile())
956 return emitOpError("shimTile row has no cascade stream interface");
957 if (t.isMemTile(src.colIndex(), src.rowIndex()) ||
958 t.isMemTile(dst.colIndex(), dst.rowIndex()))
959 return emitOpError("memTile row has no cascade stream interface");
960
961 if (!t.isSouth(src.getCol(), src.getRow(), dst.getCol(), dst.getRow()) &&
962 !t.isWest(src.getCol(), src.getRow(), dst.getCol(), dst.getRow()) &&
963 !t.isNorth(src.getCol(), src.getRow(), dst.getCol(), dst.getRow()) &&
964 !t.isEast(src.getCol(), src.getRow(), dst.getCol(), dst.getRow())) {
965 return emitOpError("tiles must be adjacent");
966 }
967 return success();
968}
969
970TileOp CascadeFlowOp::getSourceTileOp() {
971 return cast<TileOp>(getSourceTile().getDefiningOp());
972}
973
974TileOp CascadeFlowOp::getDestTileOp() {
975 return cast<TileOp>(getDestTile().getDefiningOp());
976}
977
978//===----------------------------------------------------------------------===//
979// ConfigureCascadeOp
980//===----------------------------------------------------------------------===//
981
982LogicalResult ConfigureCascadeOp::verify() {
983 const auto &t = getTargetModel(*this);
984 TileOp tile = cast<TileOp>(getTile().getDefiningOp());
985 CascadeDir inputDir = getInputDir();
986 CascadeDir outputDir = getOutputDir();
987
988 if (tile.isShimTile())
989 return emitOpError("shimTile row has no cascade stream interface");
990 if (t.isMemTile(tile.colIndex(), tile.rowIndex()))
991 return emitOpError("memTile row has no cascade stream interface");
992
993 if (isa<AIE2TargetModel>(t)) {
994 if (inputDir == CascadeDir::South || inputDir == CascadeDir::East) {
995 return emitOpError("input direction of cascade must be North or West on ")
996 << stringifyAIEArch(t.getTargetArch());
997 }
998 if (outputDir == CascadeDir::North || outputDir == CascadeDir::West) {
999 return emitOpError(
1000 "output direction of cascade must be South or East on ")
1001 << stringifyAIEArch(t.getTargetArch());
1002 }
1003 } else {
1004 return emitOpError("cascade not supported in ")
1005 << stringifyAIEArch(t.getTargetArch());
1006 }
1007 return success();
1008}
1009
1010//===----------------------------------------------------------------------===//
1011// PutCascadeOp
1012//===----------------------------------------------------------------------===//
1013
1014LogicalResult PutCascadeOp::verify() {
1015 const auto &targetModel = getTargetModel(*this);
1016 Type type = getCascadeValue().getType();
1017 DataLayout dataLayout = DataLayout::closest(*this);
1018 auto bits = dataLayout.getTypeSizeInBits(type);
1019 auto archbits = targetModel.getAccumulatorCascadeSize();
1020 if (bits != archbits)
1021 return emitOpError("type must match architecture cascade width (")
1022 << archbits << " bits in "
1023 << stringifyAIEArch(targetModel.getTargetArch()) << ")";
1024 return success();
1025}
1026
1027//===----------------------------------------------------------------------===//
1028// GetCascadeOp
1029//===----------------------------------------------------------------------===//
1030
1031LogicalResult GetCascadeOp::verify() {
1032 const auto &targetModel = getTargetModel(*this);
1033 Type type = getCascadeValue().getType();
1034 DataLayout dataLayout = DataLayout::closest(*this);
1035 auto bits = dataLayout.getTypeSizeInBits(type);
1036 if (isa<AIE1TargetModel>(targetModel)) {
1037 if (bits != 384)
1038 return emitOpError("must be a 384-bit type");
1039 } else if (isa<AIE2TargetModel>(targetModel)) {
1040 if (bits != 512)
1041 return emitOpError("must be a 512-bit type");
1042 } else
1043 return emitOpError("cascade not supported in ")
1044 << stringifyAIEArch(targetModel.getTargetArch());
1045 return success();
1046}
1047
1048//===----------------------------------------------------------------------===//
1049// DeviceOp
1050//===----------------------------------------------------------------------===//
1051
1052const AIETargetModel &DeviceOp::getTargetModel() {
1053 return xilinx::AIE::getTargetModel(getDevice());
1054}
1055
1056//===----------------------------------------------------------------------===//
1057// TileOp
1058//===----------------------------------------------------------------------===//
1059
1060LogicalResult TileOp::verify() {
1061 const auto &targetModel = getTargetModel(*this);
1062 int columns = targetModel.columns();
1063 int rows = targetModel.rows();
1064 if (colIndex() >= columns)
1065 return emitOpError("column index (")
1066 << colIndex()
1067 << ") must be less than the number of columns in the device ("
1068 << columns << ")";
1069 if (rowIndex() >= rows)
1070 return emitOpError("row index (")
1071 << rowIndex()
1072 << ") must be less than the number of rows in the device (" << rows
1073 << ")";
1074
1075 auto users = getResult().getUsers();
1076 bool found = false;
1077 for (auto *user : users) {
1078 if (llvm::isa<SwitchboxOp>(*user)) {
1079 if (found)
1080 return emitOpError("can only have one switchbox");
1081 found = true;
1082 }
1083 }
1084
1085 if (isShimTile() && getAllocationScheme())
1086 return emitOpError("Shim tiles cannot have an allocation scheme");
1087
1088 return success();
1089}
1090
1091size_t TileOp::getNumSourceConnections(WireBundle bundle) {
1092 const auto &targetModel = getTargetModel(*this);
1093 if (bundle == WireBundle::Core || bundle == WireBundle::DMA)
1094 // Note dest is correct here, since direction is reversed.
1095 {
1096 // Note dest is correct here, since direction is reversed.
1097 if (targetModel.isShimNOCTile(getCol(), getRow()) ||
1098 targetModel.isShimPLTile(getCol(), getRow()))
1099 return targetModel.getNumDestShimMuxConnections(getCol(), getRow(),
1100 bundle);
1101 return targetModel.getNumDestSwitchboxConnections(getCol(), getRow(),
1102 bundle);
1103 }
1104 return 0;
1105}
1106
1107size_t TileOp::getNumDestConnections(WireBundle bundle) {
1108 const auto &targetModel = getTargetModel(*this);
1109 if (bundle == WireBundle::Core || bundle == WireBundle::DMA)
1110 // Note source is correct here, since direction is reversed.
1111 {
1112 // Note source is correct here, since direction is reversed.
1113 if (targetModel.isShimNOCTile(getCol(), getRow()) ||
1114 targetModel.isShimPLTile(getCol(), getRow()))
1115 return targetModel.getNumDestShimMuxConnections(getCol(), getRow(),
1116 bundle);
1117 return targetModel.getNumSourceSwitchboxConnections(getCol(), getRow(),
1118 bundle);
1119 }
1120 return 0;
1121}
1122
1123bool TileOp::isMemTile() {
1124 const auto &targetModel = getTargetModel(*this);
1125 return targetModel.isMemTile(getCol(), getRow());
1126}
1127
1128bool TileOp::isShimNOCTile() {
1129 const auto &targetModel = getTargetModel(*this);
1130 return targetModel.isShimNOCTile(getCol(), getRow());
1131}
1132
1133bool TileOp::isShimPLTile() {
1134 const auto &targetModel = getTargetModel(*this);
1135 return targetModel.isShimPLTile(getCol(), getRow());
1136}
1137
1138bool TileOp::isShimNOCorPLTile() {
1139 const auto &targetModel = getTargetModel(*this);
1140 return targetModel.isShimNOCorPLTile(getCol(), getRow());
1141}
1142
1143bool isLegalTileConnection(TileOp tile, const AIETargetModel &targetModel,
1144 MasterSetOp masterOp, PacketRulesOp slaveOp) {
1145 auto srcBundle = slaveOp.sourcePort().bundle;
1146 auto srcChan = slaveOp.sourcePort().channel;
1147 auto dstBundle = masterOp.destPort().bundle;
1148 auto dstChan = masterOp.destPort().channel;
1149 return targetModel.isLegalTileConnection(
1150 tile.colIndex(), tile.rowIndex(), srcBundle, srcChan, dstBundle, dstChan);
1151}
1152
1153bool isLegalTileConnection(TileOp tile, const AIETargetModel &targetModel,
1154 ConnectOp connectOp) {
1155 auto srcBundle = connectOp.getSourceBundle();
1156 auto srcChan = connectOp.getSourceChannel();
1157 auto dstBundle = connectOp.getDestBundle();
1158 auto dstChan = connectOp.getDestChannel();
1159 return targetModel.isLegalTileConnection(
1160 tile.colIndex(), tile.rowIndex(), srcBundle, srcChan, dstBundle, dstChan);
1161}
1162
1163TileOp TileOp::getOrCreate(mlir::OpBuilder builder, DeviceOp device, int col,
1164 int row) {
1165 TileOp tile = nullptr;
1166 // Find matching predefined tile at device top level, ...
1167 for (auto t : device.getOps<AIE::TileOp>()) {
1168 if (t.getRow() == row && t.getCol() == col) {
1169 tile = t;
1170 break;
1171 }
1172 }
1173 // ... or if undefined, create a new tile op
1174 if (!tile) {
1175 OpBuilder::InsertionGuard guard(builder);
1176 mlir::Block &device_start_block = *device.getBodyRegion().begin();
1177 builder.setInsertionPointToStart(&device_start_block);
1178 tile = builder.create<TileOp>(builder.getUnknownLoc(),
1179 builder.getIndexType(), col, row);
1180 }
1181 return tile;
1182}
1183
1184//===----------------------------------------------------------------------===//
1185// ShimSwitchboxOp
1186//===----------------------------------------------------------------------===//
1187
1188LogicalResult ShimSwitchboxOp::verify() {
1189 Region &body = getConnections();
1190 DenseSet<Port> destset;
1191 if (body.empty())
1192 return emitOpError("should have non-empty body");
1193
1194 for (auto &ops : body.front()) {
1195 if (auto connectOp = dyn_cast<ConnectOp>(ops)) {
1196 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
1197 if (destset.count(dest))
1198 return connectOp.emitOpError("targets same destination ")
1199 << stringifyWireBundle(dest.bundle) << ": " << dest.channel
1200 << " as another connect operation";
1201 destset.insert(dest);
1202 } else if (isa<EndOp>(ops)) {
1203 // continue;
1204 } else {
1205 return ops.emitOpError("cannot be contained in a Switchbox op");
1206 }
1207 }
1208
1209 return success();
1210}
1211
1212//===----------------------------------------------------------------------===//
1213// ShimMuxOp
1214//===----------------------------------------------------------------------===//
1215
1216LogicalResult ShimMuxOp::verify() {
1217 Region &body = getConnections();
1218 DenseSet<Port> destset;
1219 if (body.empty())
1220 return emitOpError("should have non-empty body");
1221
1222 for (auto &ops : body.front()) {
1223 if (auto connectOp = dyn_cast<ConnectOp>(ops)) {
1224 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
1225 if (destset.count(dest))
1226 return connectOp.emitOpError("targets same destination ")
1227 << stringifyWireBundle(dest.bundle) << ": " << dest.channel
1228 << " as another connect operation";
1229 destset.insert(dest);
1230 } else if (isa<EndOp>(ops)) {
1231 // continue;
1232 } else {
1233 return ops.emitOpError("cannot be contained in a Switchbox op");
1234 }
1235 }
1236 return success();
1237}
1238
1239size_t ShimMuxOp::getNumSourceConnections(WireBundle bundle) {
1240 auto tile = getTileOp();
1241 const auto &targetModel = getTargetModel(*this);
1242 return targetModel.getNumSourceShimMuxConnections(tile.getCol(),
1243 tile.getRow(), bundle);
1244}
1245
1246size_t ShimMuxOp::getNumDestConnections(WireBundle bundle) {
1247 auto tile = getTileOp();
1248 const auto &targetModel = getTargetModel(*this);
1249 return targetModel.getNumDestShimMuxConnections(tile.getCol(), tile.getRow(),
1250 bundle);
1251}
1252
1253TileOp ShimMuxOp::getTileOp() {
1254 return cast<TileOp>(getTile().getDefiningOp());
1255}
1256
1257int ShimMuxOp::colIndex() { return getTileOp().colIndex(); }
1258
1259int ShimMuxOp::rowIndex() { return getTileOp().rowIndex(); }
1260
1261//===----------------------------------------------------------------------===//
1262// ShimDMAOp
1263//===----------------------------------------------------------------------===//
1264
1265LogicalResult ShimDMAOp::verify() {
1266 if (!getTileOp().isShimNOCTile())
1267 return emitOpError("must be in a ShimTile with a NOC connection");
1268
1270 .failed())
1271 return failure();
1272 return success();
1273}
1274
1275TileOp ShimDMAOp::getTileOp() {
1276 return cast<TileOp>(getTile().getDefiningOp());
1277}
1278
1279int ShimDMAOp::colIndex() { return getTileOp().colIndex(); }
1280
1281int ShimDMAOp::rowIndex() { return getTileOp().rowIndex(); }
1282
1283LogicalResult PacketRulesOp::verify() {
1284 if (Region &body = getRules(); body.empty())
1285 return emitOpError("should have non-empty body");
1286 return success();
1287}
1288
1289LogicalResult PacketFlowOp::verify() {
1290 Region &body = getPorts();
1291 if (body.empty())
1292 return emitOpError("should have non-empty body");
1293
1294 for (auto &ops : body.front()) {
1295 if (!isa<PacketSourceOp, PacketDestOp, EndOp>(ops))
1296 return ops.emitOpError("cannot be contained in a PacketFlow op");
1297 }
1298
1299 return success();
1300}
1301
1302//===----------------------------------------------------------------------===//
1303// CoreOp
1304//===----------------------------------------------------------------------===//
1305
1306LogicalResult CoreOp::verify() {
1307 if (getBody().empty())
1308 return emitOpError("should have non-empty body");
1309 if (getTileOp().isShimTile())
1310 return emitOpError("CoreOp cannot be created on shim tile, i.e. row == 0");
1311 if (getTileOp().isMemTile())
1312 return emitOpError("CoreOp cannot be created on mem tile");
1313 return success();
1314}
1315
1316int CoreOp::colIndex() { return getTileOp().colIndex(); }
1317
1318int CoreOp::rowIndex() { return getTileOp().rowIndex(); }
1319
1320TileOp CoreOp::getTileOp() { return cast<TileOp>(getTile().getDefiningOp()); }
1321
1322//===----------------------------------------------------------------------===//
1323// BufferOp
1324//===----------------------------------------------------------------------===//
1325
1326int64_t BufferOp::getAllocationSize() {
1327 auto type = llvm::cast<MemRefType>(getType());
1328 DataLayout dataLayout = DataLayout::closest(getOperation());
1329 return type.getNumElements() * dataLayout.getTypeSize(type.getElementType());
1330}
1331
1332TileOp BufferOp::getTileOp() { return cast<TileOp>(getTile().getDefiningOp()); }
1333
1334LogicalResult BufferOp::verify() {
1335 if (UsesAreAccessible::verifyTrait(*this).failed())
1336 return failure();
1337 return success();
1338}
1339
1340// FIXME: make address assignment for buffers explicit and move this function to
1341// an interface
1342int32_t xilinx::AIE::getBufferBaseAddress(Operation *bufOp) {
1343 if (auto buf = dyn_cast<BufferOp>(bufOp)) {
1344 assert(buf.getAddress().has_value() && "buffer must have address assigned");
1345 return buf.getAddress().value();
1346 }
1347 if (isa_and_nonnull<ExternalBufferOp>(bufOp))
1348 llvm::report_fatal_error(
1349 "External buffer addresses are assigned at runtime.");
1350 llvm::report_fatal_error("unknown buffer type");
1351}
1352
1353void xilinx::AIE::collectTiles(DeviceOp &device,
1354 DenseMap<TileID, Operation *> &tiles) {
1355 for (auto tile : device.getOps<TileOp>()) {
1356 int colIndex = tile.colIndex();
1357 int rowIndex = tile.rowIndex();
1358 tiles[{colIndex, rowIndex}] = tile;
1359 }
1360}
1361
1362void xilinx::AIE::collectBuffers(
1363 DeviceOp &device,
1364 DenseMap<Operation *, SmallVector<BufferOp, 4>> &buffers) {
1365 for (BufferOp buffer : device.getOps<BufferOp>()) {
1366 Operation *tileOp = buffer.getTile().getDefiningOp();
1367 buffers[tileOp].push_back(buffer);
1368 }
1369}
1370
1371static void printBufferInitialValue(OpAsmPrinter &p, BufferOp op, Type type,
1372 Attribute initialValue) {
1373 if (op.getInitialValue()) {
1374 p << "= ";
1375 p.printAttributeWithoutType(initialValue);
1376 }
1377}
1378
1379static ParseResult parseBufferInitialValue(OpAsmParser &parser, Type &type,
1380 Attribute &initialValue) {
1381 auto memrefType = llvm::cast<MemRefType>(type);
1382 if (!memrefType.hasStaticShape())
1383 return parser.emitError(parser.getNameLoc())
1384 << "type should be static shaped memref, but got " << type;
1385
1386 if (parser.parseOptionalEqual())
1387 return success();
1388
1389 Type tensorType = mlir::memref::getTensorTypeFromMemRefType(memrefType);
1390 if (parser.parseAttribute(initialValue, tensorType))
1391 return failure();
1392 if (!llvm::isa<ElementsAttr>(initialValue))
1393 return parser.emitError(parser.getNameLoc())
1394 << "initial value should be an elements attribute";
1395 return success();
1396}
1397
1398//===----------------------------------------------------------------------===//
1399// MemOp
1400//===----------------------------------------------------------------------===//
1401
1402LogicalResult MemOp::verify() {
1403 Region &body = getBody();
1405 .failed())
1406 return failure();
1407
1408 for (auto &bodyOp : body.getOps()) {
1409 if (auto allocOp = dyn_cast<memref::AllocOp>(bodyOp))
1410 if (!allocOp->getAttr("id"))
1411 return allocOp.emitOpError()
1412 << "allocOp in MemOp region should have an id attribute";
1413 }
1414 return success();
1415}
1416
1417TileOp MemOp::getTileOp() { return cast<TileOp>(getTile().getDefiningOp()); }
1418
1419int MemOp::colIndex() { return getTileOp().colIndex(); }
1420
1421int MemOp::rowIndex() { return getTileOp().rowIndex(); }
1422
1423//===----------------------------------------------------------------------===//
1424// MemTileDMAOp
1425//===----------------------------------------------------------------------===//
1426
1427LogicalResult MemTileDMAOp::verify() {
1428 assert(getOperation()->getNumRegions() == 1 &&
1429 "MemTileDMAOp has zero region!");
1430
1432 .failed())
1433 return failure();
1434
1435 for (auto &bodyOp : getBody().getOps()) {
1436 if (auto allocOp = dyn_cast<memref::AllocOp>(bodyOp)) {
1437 if (!allocOp->getAttr("id"))
1438 return allocOp.emitOpError()
1439 << "allocOp in MemTileDMAOp region should have an id attribute";
1440 }
1441 if (auto startOp = dyn_cast<DMAStartOp>(bodyOp)) {
1442 if (startOp.getChannelIndex() > 3) {
1443 // Channels 4 and 5 in a memtile are restricted to only access local
1444 // buffers and locks.
1445
1446 // TODO: Move this code to the dialect
1447 // Set of blocks found to be reachable within a given region.
1448 llvm::SmallSet<Block *, 16> reachable;
1449 SmallVector<Block *, 16> worklist;
1450 Block *firstBD = startOp.getSuccessor(0);
1451 reachable.insert(firstBD);
1452 worklist.push_back(firstBD);
1453 while (!worklist.empty()) {
1454 Block *block = worklist.pop_back_val();
1455 if (block->empty())
1456 continue;
1457 auto successors = block->getTerminator()->getSuccessors();
1458 for (auto *i : successors) {
1459 if (!reachable.contains(i)) {
1460 reachable.insert(i);
1461 worklist.push_back(i);
1462 }
1463 }
1464 }
1465 for (Block *b : reachable) {
1466 for (DMABDOp bd : b->getOps<DMABDOp>()) {
1467 if (auto bufferOp = bd.getBufferOp();
1468 bufferOp.getTileOp().colIndex() != colIndex() ||
1469 bufferOp.getTileOp().rowIndex() != rowIndex()) {
1470 InFlightDiagnostic err =
1471 bd.emitOpError()
1472 << "is reachable from DMA channel "
1473 << startOp.getChannelIndex()
1474 << " and attempts to access a non-local buffer\n";
1475 err.attachNote(startOp->getLoc()) << "channel";
1476 err.attachNote(bufferOp->getLoc()) << "buffer";
1477 return err;
1478 }
1479 }
1480 for (auto useLock : b->getOps<UseLockOp>()) {
1481 if (auto lockOp = useLock.getLockOp();
1482 lockOp.getTileOp().colIndex() != colIndex() ||
1483 lockOp.getTileOp().rowIndex() != rowIndex()) {
1484 InFlightDiagnostic err =
1485 useLock.emitOpError()
1486 << "is reachable from DMA channel "
1487 << startOp.getChannelIndex()
1488 << " and attempts to access a non-local lock\n";
1489 err.attachNote(startOp->getLoc()) << "channel";
1490 err.attachNote(lockOp->getLoc()) << "lock";
1491 return err;
1492 }
1493 }
1494 }
1495 }
1496 }
1497 }
1498
1499 return success();
1500}
1501
1502//===----------------------------------------------------------------------===//
1503// DMAOp
1504//===----------------------------------------------------------------------===//
1505
1506LogicalResult DMAOp::verify() {
1507 auto *parentOp = getOperation()->getParentOp();
1508 if (parentOp->getRegion(0).getBlocks().size() > 1)
1509 return emitOpError("DMAOp can only appear in single block region");
1510 if (!parentOp->getRegion(0).getOps<DMAStartOp>().empty())
1511 return emitOpError("DMAOp is not compatible with DMAStart ops");
1512 auto bdRegions = getBds();
1513 for (auto &bdRegion : bdRegions) {
1514 if (!bdRegion.hasOneBlock())
1515 return emitOpError("DMAOp regions must have only one block");
1516 auto bds = llvm::to_vector_of<DMABDOp>(bdRegion.front().getOps<DMABDOp>());
1517 if (bds.size() != 1)
1518 return emitOpError("DMAOp regions/blocks must have exactly one DMABDOp");
1519 auto useLocks =
1520 llvm::to_vector_of<UseLockOp>(bdRegion.front().getOps<UseLockOp>());
1521 if (useLocks.size() != 2)
1522 return emitOpError(
1523 "DMAOp regions/blocks must have exactly two UseLock ops");
1524 }
1525 return success();
1526}
1527
1528//===----------------------------------------------------------------------===//
1529// DMABDOp
1530//===----------------------------------------------------------------------===//
1531
1532BufferOp DMABDOp::getBufferOp() {
1533 return cast<BufferOp>(getBuffer().getDefiningOp());
1534}
1535
1536// let assemblyFormat = [{
1537// `(` $buffer `:` type($buffer) (`,` $offset^)? (`,` $len^)? (`,`
1538// $dimensions^)? (`,` $pad_dimensions^)? (`,` `pad_value` `=` $pad_value^)?
1539// `)` attr-dict
1540// }];
1541ParseResult DMABDOp::parse(OpAsmParser &parser, OperationState &result) {
1542 OpAsmParser::UnresolvedOperand bufferRawOperand{};
1543 ::llvm::ArrayRef<OpAsmParser::UnresolvedOperand> bufferOperands(
1544 &bufferRawOperand, 1);
1545 ::llvm::SMLoc bufferOperandsLoc;
1546 (void)bufferOperandsLoc;
1547 Type bufferRawType{};
1548 ::llvm::ArrayRef<Type> bufferTypes(&bufferRawType, 1);
1549 IntegerAttr offsetAttr;
1550 IntegerAttr lenAttr;
1551 ::xilinx::AIE::BDDimLayoutArrayAttr dimensionsAttr;
1552 ::xilinx::AIE::BDPadLayoutArrayAttr pad_dimensionsAttr;
1553 IntegerAttr pad_valueAttr;
1554 if (parser.parseLParen())
1555 return failure();
1556
1557 bufferOperandsLoc = parser.getCurrentLocation();
1558 if (parser.parseOperand(bufferRawOperand))
1559 return failure();
1560 if (parser.parseColon())
1561 return failure();
1562 if (parser.parseCustomTypeWithFallback(bufferRawType))
1563 return failure();
1564
1565 // offset
1566 if (succeeded(parser.parseOptionalComma())) {
1567 if (parser.parseCustomAttributeWithFallback(
1568 offsetAttr, parser.getBuilder().getIntegerType(32))) {
1569 return failure();
1570 }
1571 if (!offsetAttr)
1572 offsetAttr = parser.getBuilder().getIntegerAttr(
1573 parser.getBuilder().getIntegerType(32), 0);
1574 result.getOrAddProperties<DMABDOp::Properties>().offset = offsetAttr;
1575 }
1576
1577 // len
1578 if (succeeded(parser.parseOptionalComma())) {
1579 if (parser.parseCustomAttributeWithFallback(
1580 lenAttr, parser.getBuilder().getIntegerType(32))) {
1581 return failure();
1582 }
1583 if (lenAttr)
1584 result.getOrAddProperties<DMABDOp::Properties>().len = lenAttr;
1585 }
1586
1587 // dimensions
1588 if (succeeded(parser.parseOptionalComma())) {
1589 if (parser.parseCustomAttributeWithFallback(dimensionsAttr, Type{})) {
1590 return failure();
1591 }
1592 if (dimensionsAttr)
1593 result.getOrAddProperties<DMABDOp::Properties>().dimensions =
1594 dimensionsAttr;
1595 }
1596
1597 // pad_dimensions
1598 if (succeeded(parser.parseOptionalComma())) {
1599 if (parser.parseCustomAttributeWithFallback(pad_dimensionsAttr, Type{})) {
1600 return failure();
1601 }
1602 if (pad_dimensionsAttr)
1603 result.getOrAddProperties<DMABDOp::Properties>().pad_dimensions =
1604 pad_dimensionsAttr;
1605 }
1606
1607 // pad_value
1608 if (succeeded(parser.parseOptionalComma())) {
1609 if (parser.parseKeyword("pad_value"))
1610 return failure();
1611 if (parser.parseEqual())
1612 return failure();
1613
1614 if (parser.parseCustomAttributeWithFallback(
1615 pad_valueAttr, parser.getBuilder().getIntegerType(32))) {
1616 return failure();
1617 }
1618 if (pad_valueAttr)
1619 result.getOrAddProperties<DMABDOp::Properties>().pad_value =
1620 pad_valueAttr;
1621 }
1622 if (parser.parseRParen())
1623 return failure();
1624
1625 auto loc = parser.getCurrentLocation();
1626 if (parser.parseOptionalAttrDict(result.attributes))
1627 return failure();
1628 if (failed(verifyInherentAttrs(result.name, result.attributes, [&]() {
1629 return parser.emitError(loc)
1630 << "'" << result.name.getStringRef() << "' op ";
1631 })))
1632 return failure();
1633
1634 if (parser.resolveOperands(bufferOperands, bufferTypes, bufferOperandsLoc,
1635 result.operands))
1636 return failure();
1637
1638 return success();
1639}
1640
1641void DMABDOp::print(::mlir::OpAsmPrinter &printer) {
1642 printer << "(";
1643 printer << getBuffer();
1644 printer << ' ' << ":";
1645 printer << ' ';
1646 {
1647 auto type = getBuffer().getType();
1648 if (auto validType = ::llvm::dyn_cast<::mlir::MemRefType>(type))
1649 printer.printStrippedAttrOrType(validType);
1650 else
1651 printer << type;
1652 }
1653 if (getLenAttr() ||
1654 getOffsetAttr() !=
1655 ::mlir::OpBuilder((*this)->getContext())
1656 .getIntegerAttr(
1657 ::mlir::OpBuilder((*this)->getContext()).getIntegerType(32),
1658 0)) {
1659 printer << ",";
1660 printer << ' ';
1661 printer.printAttributeWithoutType(getOffsetAttr());
1662 }
1663 if (getLenAttr()) {
1664 printer << ",";
1665 printer << ' ';
1666 printer.printAttributeWithoutType(getLenAttr());
1667 }
1668 if (getDimensionsAttr()) {
1669 printer << ",";
1670 printer << ' ';
1671 printer.printStrippedAttrOrType(getDimensionsAttr());
1672 }
1673 if (getPadDimensionsAttr()) {
1674 printer << ",";
1675 printer << ' ';
1676 printer.printStrippedAttrOrType(getPadDimensionsAttr());
1677 }
1678 if ((getPadValueAttr() &&
1679 getPadValueAttr() !=
1680 ::mlir::OpBuilder((*this)->getContext())
1681 .getIntegerAttr(
1682 ::mlir::OpBuilder((*this)->getContext()).getIntegerType(32),
1683 0))) {
1684 printer << ",";
1685 printer << ' ' << "pad_value";
1686 printer << ' ' << "=";
1687 printer << ' ';
1688 printer.printAttributeWithoutType(getPadValueAttr());
1689 }
1690 printer << ")";
1691 ::llvm::SmallVector<::llvm::StringRef, 2> elidedAttrs;
1692 elidedAttrs.push_back("offset");
1693 elidedAttrs.push_back("len");
1694 elidedAttrs.push_back("dimensions");
1695 elidedAttrs.push_back("pad_dimensions");
1696 elidedAttrs.push_back("pad_value");
1697 printer.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
1698}
1699
1700LogicalResult DMABDOp::verify() {
1701 // Skip verification of the BDOp outside of mem operations.
1702 // BDOps may appear elsewhere and subsequent lowerings will place them in the
1703 // correct mem ops.
1704 Operation *p = (*this)->getParentOp();
1705 if (!llvm::isa<MemOp, MemTileDMAOp, ShimDMAOp, DMAOp>(*p)) {
1706 return success();
1707 }
1708
1709 if (!isa<BufferOp, ExternalBufferOp>(getBuffer().getDefiningOp()))
1710 return emitOpError(
1711 "BDs only support BufferOp or ExternalBufferOp operands.");
1712
1713 if (getLenInBytes() % 4)
1714 return emitOpError("transfer length must be multiple of 4 (i.e., represent "
1715 "4 byte aligned address)");
1716
1717 TileID parentTileId = getParentTileElement(getOperation()).getTileID();
1718
1719 if (getOperation()->getParentOfType<MemOp>() &&
1720 (getBufferOp().getTileOp().colIndex() != parentTileId.col ||
1721 getBufferOp().getTileOp().rowIndex() != parentTileId.row))
1722 return emitOpError(
1723 "Core tile DMAs can only access a buffer in the same tile.");
1724
1725 const AIETargetModel &targetModel = getTargetModel(getOperation());
1726
1727 uint32_t maxBds = targetModel.getNumBDs(parentTileId.col, parentTileId.row);
1728 if (std::optional<int32_t> bdId = getBdId();
1729 bdId.has_value() && static_cast<uint32_t>(*bdId) >= maxBds)
1730 return emitOpError("bdId attribute exceeds max: ") << maxBds - 1;
1731 if (std::optional<int32_t> nextBdId = getNextBdId();
1732 nextBdId.has_value() && static_cast<uint32_t>(*nextBdId) >= maxBds)
1733 return emitOpError("nextBdId attribute exceeds max: ") << maxBds - 1;
1734 if (auto dims = getDimensions(); dims.has_value()) {
1735 size_t maxNDims = 3;
1736 if (getOperation()->getParentOfType<MemTileDMAOp>())
1737 maxNDims = 4;
1738 if (dims->size() > maxNDims)
1739 return emitOpError() << "Cannot give more than "
1740 << std::to_string(maxNDims)
1741 << " dimensions for step sizes and wraps in this "
1742 " tile (got "
1743 << std::to_string(dims->size()) << " dimensions).";
1744
1745 MemRefType buffer = getBuffer().getType();
1746 int64_t maxIdx = 0;
1747 for (BDDimLayoutAttr dim : *dims) {
1748 maxIdx += dim.getStride() * (dim.getSize() - 1);
1749 if (0 == dim.getStride())
1750 return emitOpError()
1751 << "Invalid step size; must be a positive integer.";
1752 if (dim.getStride() > buffer.getNumElements())
1753 return emitOpError() << "Step size " << std::to_string(dim.getStride())
1754 << " exceeds memref size "
1755 << std::to_string(buffer.getNumElements());
1756 if (dim.getSize() >= (1UL << 9) + 1)
1757 return emitOpError() << "Size may not exceed 1023.";
1758 if (dim.getStride() >= (1UL << 19))
1759 return emitOpError() << "Stride may not exceed " << (1 << 20);
1760 }
1761
1762 if (buffer.getNumElements() <= maxIdx)
1763 return emitOpError() << "Specified stride(s) and size(s) result in out "
1764 "of bounds access in buffer, for index "
1765 << std::to_string(maxIdx) << " in memref of length "
1766 << std::to_string(buffer.getNumElements()) << ".";
1767
1768 // Since streams read 32b words, there's no way to read eg 16b with stride
1769 // of 2 (ie lower halfs of each 32b). So force it to be 1 (and then in
1770 // CDODirect/XAIEV2 scale the size by 4/getBufferElementTypeWidthInBytes).
1771 if (getBufferElementTypeWidthInBytes() < 4 && dims->back().getStride() != 1)
1772 return emitOpError(
1773 "For <32b width datatypes, inner-most dim stride must be 1");
1774
1775 if (getBufferElementTypeWidthInBytes() > 4 && dims->back().getStride() != 1)
1776 return emitOpError(
1777 "For >32b width datatypes, inner-most dim stride must be 1");
1778 }
1779 if (auto paddims = getPadDimensions(); paddims.has_value()) {
1780 auto dims = getDimensions();
1781 if (!dims.has_value())
1782 return emitOpError() << "Padding requires n-d data layouts expressed as"
1783 << " wrap(s) and stride(s).";
1784 if (!targetModel.isMemTile(parentTileId.col, parentTileId.row))
1785 return emitOpError() << "Padding is only supported by memtile dma bds.";
1786 if (dims->size() != paddims->size())
1787 return emitOpError() << "Mismatch number of dimensions between padding(s)"
1788 << " and wrap(s) and stride(s).";
1789 int actuallen = 1;
1790 for (unsigned i = 0; i < paddims->size(); i++) {
1791 auto dim = (*dims)[i];
1792 auto paddim = (*paddims)[i];
1793 actuallen *= paddim.getConstPadBefore() + paddim.getConstPadAfter() +
1794 dim.getSize();
1795 if (actuallen > getLen())
1796 return emitOpError() << "Data exceeds len after padding.";
1797 }
1798 if ((paddims->back().getConstPadBefore() *
1799 getBufferElementTypeWidthInBytes()) %
1800 4)
1801 return emitOpError() << "Inner-most padding-before count must result in"
1802 << " padding in 32-bit words.";
1803 if ((paddims->back().getConstPadAfter() *
1804 getBufferElementTypeWidthInBytes()) %
1805 4)
1806 return emitOpError() << "Inner-most padding-after count must result in"
1807 << " padding in 32-bit words.";
1808 }
1809 if (targetModel.isMemTile(parentTileId.col, parentTileId.row) ||
1810 targetModel.isCoreTile(parentTileId.col, parentTileId.row)) {
1811 if (auto baseAddr = getBufferOp().getAddress(); baseAddr.has_value()) {
1812 int offsetInBytes = *baseAddr + getOffsetInBytes();
1813 if (offsetInBytes % 4)
1814 return emitOpError("bd address must be 4 byte (32b) aligned; got "
1815 "base+offset: ")
1816 << offsetInBytes << " (bytes)";
1817 }
1818 }
1819 if (auto packetInfo = getPacket()) {
1820 if (packetInfo->getPktType() > 7)
1821 return emitOpError("Packet type field can only hold 3 bits.");
1822 if (packetInfo->getPktId() > 31)
1823 return emitOpError("Packet ID field can only hold 5 bits.");
1824 }
1825
1826 if (!getLen() && !getBuffer().getType().hasStaticShape())
1827 return emitOpError() << "buffer with dynamic shape requires static length.";
1828
1829 if (getBurstLength() != 0 &&
1830 !targetModel.isShimNOCTile(parentTileId.col, parentTileId.row))
1831 return emitOpError("Burst length is only supported in Shim NOC tiles that "
1832 "are connected to the memory-mapped NOC.");
1833
1834 return success();
1835}
1836
1837TileOp MemTileDMAOp::getTileOp() {
1838 return cast<TileOp>(getTile().getDefiningOp());
1839}
1840
1841int MemTileDMAOp::colIndex() { return getTileOp().colIndex(); }
1842
1843int MemTileDMAOp::rowIndex() { return getTileOp().rowIndex(); }
1844
1845//===----------------------------------------------------------------------===//
1846// DMAStartOp
1847//===----------------------------------------------------------------------===//
1848
1849static LogicalResult FoldDMAStartOp(DMAStartOp op, PatternRewriter &rewriter) {
1850
1851 llvm::SetVector<Block *> reachable;
1852 SmallVector<Block *, 16> worklist;
1853 Block *firstBD = op.getSuccessor(0);
1854 reachable.insert(firstBD);
1855 worklist.push_back(firstBD);
1856 while (!worklist.empty()) {
1857 Block *block = worklist.pop_back_val();
1858 if (block->empty())
1859 continue;
1860 auto successors = block->getTerminator()->getSuccessors();
1861 for (auto *i : successors) {
1862 if (!reachable.contains(i)) {
1863 reachable.insert(i);
1864 worklist.push_back(i);
1865 }
1866 }
1867 }
1868
1869 // BD chain ends with an EndOp, indicating non-repeating pattern: BD chain
1870 // folding not applicable.
1871 if (isa<EndOp>((reachable.back())->getTerminator()))
1872 return failure();
1873
1874 // Check for identical bds.
1875 auto areEquivalentBDs = [](Block *b1, Block *b2) {
1876 auto b1OpRange = b1->without_terminator();
1877 auto b2OpRange = b2->without_terminator();
1878 if (llvm::range_size(b1OpRange) != llvm::range_size(b2OpRange))
1879 return false;
1880 if (!llvm::all_of(llvm::zip_equal(b1OpRange, b2OpRange),
1881 [](std::tuple<Operation &, Operation &> pair) {
1882 return OperationEquivalence::isEquivalentTo(
1883 &std::get<0>(pair), &std::get<1>(pair),
1884 OperationEquivalence::IgnoreLocations);
1885 }))
1886 return false;
1887 return true;
1888 };
1889
1890 // Get a vector of unique BDs.
1891 SmallVector<Block *> uniquePattern;
1892 auto patternIt = reachable.begin();
1893 while (patternIt != reachable.end() &&
1894 llvm::none_of(uniquePattern, [patternIt, areEquivalentBDs](Block *b1) {
1895 return areEquivalentBDs(*patternIt, b1);
1896 })) {
1897 uniquePattern.push_back(*patternIt);
1898 patternIt++;
1899 }
1900
1901 unsigned idx = 0;
1902 while (patternIt != reachable.end()) {
1903 // BD repetition found. Check if repeating pattern.
1904 if (!areEquivalentBDs(*patternIt, uniquePattern[idx]))
1905 return failure();
1906 patternIt++;
1907 idx = (++idx) % uniquePattern.size();
1908 }
1909
1910 // Repeating BD chains detected. Erasing repetitions.
1911 auto lastBDTerm = dyn_cast<NextBDOp>(reachable.back()->getTerminator());
1912 auto lastUniqueBDTerm =
1913 dyn_cast<NextBDOp>(uniquePattern.back()->getTerminator());
1914 lastUniqueBDTerm.setSuccessor(lastBDTerm.getSuccessor());
1915
1916 return success();
1917}
1918
1919void DMAStartOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1920 MLIRContext *context) {
1921 patterns.add(FoldDMAStartOp);
1922}
1923
1924//===----------------------------------------------------------------------===//
1925// SwitchboxOp
1926//===----------------------------------------------------------------------===//
1927
1928LogicalResult SwitchboxOp::verify() {
1929 Region &body = getConnections();
1930 DenseSet<Port> sourceset;
1931 DenseSet<Port> destset;
1932 auto tile = getTileOp();
1933 const auto &targetModel = getTargetModel(tile);
1934 if (body.empty())
1935 return emitOpError("should have non-empty body");
1936 for (auto &ops : body.front()) {
1937 // Would be simpler if this could be templatized.
1938 auto checkBound = [&ops](StringRef dir, WireBundle bundle, int index,
1939 int bound) -> LogicalResult {
1940 if (index >= bound) {
1941 if (bound > 0)
1942 return ops.emitOpError("index ")
1943 << index << " for " << dir << " bundle "
1944 << stringifyWireBundle(bundle) << " must be less than "
1945 << bound;
1946 return ops.emitOpError()
1947 << dir << " bundle " << stringifyWireBundle(bundle)
1948 << " not supported; index: " << index << ", bound: " << bound;
1949 }
1950 return success();
1951 };
1952
1953 if (auto connectOp = dyn_cast<ConnectOp>(ops)) {
1954 Port source = {connectOp.getSourceBundle(), connectOp.sourceIndex()};
1955 sourceset.insert(source);
1956
1957 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
1958 if (destset.count(dest)) {
1959 return connectOp.emitOpError()
1960 << "; connecting " << to_string(source) << " to "
1961 << to_string(dest) << " on "
1962 << to_string(this->getTileOp().getTileID())
1963 << " targets same dst as another connect op; existing "
1964 "destinations: "
1965 << llvm::join(llvm::map_range(
1966 destset, [](auto &p) { return to_string(p); }),
1967 ", ");
1968 }
1969 destset.insert(dest);
1970
1971 if (connectOp.sourceIndex() < 0)
1972 return connectOp.emitOpError("source index cannot be less than zero");
1973
1974 if (checkBound("source", connectOp.getSourceBundle(),
1975 connectOp.sourceIndex(),
1976 getNumSourceConnections(connectOp.getSourceBundle()))
1977 .failed())
1978 return failure();
1979
1980 if (connectOp.destIndex() < 0)
1981 return connectOp.emitOpError("dest index cannot be less than zero");
1982
1983 if (checkBound("dest", connectOp.getDestBundle(), connectOp.destIndex(),
1984 getNumDestConnections(connectOp.getDestBundle()))
1985 .failed())
1986 return failure();
1987
1988 // Stream switch connection constraints
1989 if (!isLegalTileConnection(tile, targetModel, connectOp))
1990 return connectOp.emitOpError("illegal stream switch connection");
1991
1992 } else if (auto connectOp = dyn_cast<MasterSetOp>(ops)) {
1993 Port dest = {connectOp.getDestBundle(), connectOp.destIndex()};
1994 if (destset.count(dest))
1995 return connectOp.emitOpError("targets same destination ")
1996 << stringifyWireBundle(dest.bundle) << ": " << dest.channel
1997 << " as another connect or masterset operation";
1998 destset.insert(dest);
1999
2000 if (connectOp.destIndex() < 0)
2001 return connectOp.emitOpError("dest index cannot be less than zero");
2002
2003 if (checkBound("dest", connectOp.getDestBundle(), connectOp.destIndex(),
2004 getNumDestConnections(connectOp.getDestBundle()))
2005 .failed())
2006 return failure();
2007
2008 int arbiter = -1;
2009 for (auto val : connectOp.getAmsels()) {
2010 auto amsel = dyn_cast<AMSelOp>(val.getDefiningOp());
2011 if (arbiter != -1 && arbiter != amsel.arbiterIndex())
2012 return connectOp.emitOpError(
2013 "a master port can only be tied to one arbiter");
2014 arbiter = amsel.arbiterIndex();
2015 }
2016 } else if (auto connectOp = dyn_cast<PacketRulesOp>(ops)) {
2017 Port source = {connectOp.getSourceBundle(), connectOp.sourceIndex()};
2018 if (sourceset.count(source))
2019 return connectOp.emitOpError("packet switched source ")
2020 << stringifyWireBundle(source.bundle) << source.channel
2021 << " cannot match another connect or masterset operation";
2022 sourceset.insert(source);
2023
2024 } else if (auto amselOp = dyn_cast<AMSelOp>(ops)) {
2025 std::vector<MasterSetOp> mstrs;
2026 std::vector<PacketRulesOp> slvs;
2027 for (auto *user : amselOp.getResult().getUsers()) {
2028 if (auto s = dyn_cast<PacketRuleOp>(user)) {
2029 auto pktRules = dyn_cast<PacketRulesOp>(s->getParentOp());
2030 slvs.push_back(pktRules);
2031 } else if (auto m = dyn_cast<MasterSetOp>(user))
2032 mstrs.push_back(m);
2033 }
2034 for (auto m : mstrs) {
2035 for (auto s : slvs) {
2036 // Stream switch connection constraints
2037 if (!isLegalTileConnection(tile, targetModel, m, s)) {
2038 return amselOp->emitOpError("illegal stream switch connection");
2039 }
2040 }
2041 }
2042 } else if (isa<EndOp>(ops)) {
2043 // continue;
2044 } else {
2045 return ops.emitOpError("cannot be contained in a Switchbox op");
2046 }
2047 }
2048
2049 return success();
2050}
2051
2052TileOp SwitchboxOp::getTileOp() {
2053 return cast<TileOp>(getTile().getDefiningOp());
2054}
2055
2056int SwitchboxOp::colIndex() { return getTileOp().colIndex(); }
2057
2058int SwitchboxOp::rowIndex() { return getTileOp().rowIndex(); }
2059
2060template <typename... ParentOpTypes>
2062 static LogicalResult verifyTrait(Operation *op) {
2063 Operation *operation = op->getParentOp();
2064 while (operation) {
2065 if (llvm::isa_and_nonnull<ParentOpTypes...>(operation))
2066 return success();
2067 operation = operation->getParentOp();
2068 }
2069 return failure();
2070 }
2071};
2072
2073TileOp LockOp::getTileOp() { return cast<TileOp>(getTile().getDefiningOp()); }
2074
2075int LockOp::colIndex() { return getTileOp().colIndex(); }
2076
2077int LockOp::rowIndex() { return getTileOp().rowIndex(); }
2078
2079LogicalResult LockOp::verify() {
2080 if (auto result = UsesAreAccessible::verifyTrait(*this); result.failed())
2081 return result;
2082
2083 if (getLockID().has_value()) {
2084 const auto &targetModel = getTargetModel(getTileOp());
2085 auto tileOp = getTileOp();
2086 if (int numLocks =
2087 targetModel.getNumLocks(tileOp.getCol(), tileOp.getRow());
2088 getLockID().value() >= numLocks)
2089 return emitOpError("lock assigned invalid id (maximum is ")
2090 << numLocks - 1 << ")";
2091 }
2092
2093 return success();
2094}
2095
2097 static LogicalResult verifyTrait(Operation *op) {
2098 auto *block = op->getBlock();
2099 int lockID = -1;
2100 for (auto op : block->getOps<UseLockOp>()) {
2101 if (auto lock = dyn_cast<LockOp>(op.getLock().getDefiningOp());
2102 lock.getLockID().has_value()) {
2103 if (lockID != -1 && lockID != lock.getLockIDValue())
2104 return failure();
2105 lockID = lock.getLockIDValue();
2106 }
2107 }
2108 return success();
2109 }
2110};
2111
2113 static LogicalResult verifyTrait(Operation *op) {
2114 auto *block = op->getBlock();
2115 int acqValue = -1, relValue = -1;
2116 for (auto op : block->getOps<UseLockOp>()) {
2117 if (op.acquire() || op.acquireGE()) {
2118 if (acqValue != -1 && acqValue != op.getLockValue()) {
2119 return failure();
2120 }
2121 acqValue = op.getLockValue();
2122 } else if (op.release()) {
2123 if (relValue != -1 && relValue != op.getLockValue()) {
2124 return failure();
2125 }
2126 relValue = op.getLockValue();
2127 }
2128 }
2129 return success();
2130 }
2131};
2132
2134 static LogicalResult verifyTrait(Operation *op) {
2135 if (auto memOp = op->getParentOfType<MemOp>()) {
2136 auto useLock = dyn_cast<UseLockOp>(op);
2137 if (auto lock = useLock.getLockOp();
2138 lock.getTileOp().colIndex() != memOp.colIndex() ||
2139 lock.getTileOp().rowIndex() != memOp.rowIndex())
2140 return failure();
2141 }
2142 return success();
2143 }
2144};
2145
2146LogicalResult UseLockOp::verify() {
2147 // AIE.useLock cannot be used at the top level
2148 if (llvm::isa_and_nonnull<DeviceOp, ModuleOp>((*this)->getParentOp()))
2149 return (*this)->emitOpError("must be used in a core or memory operation.");
2150
2151 const auto &targetModel = getTargetModel(*this);
2152 if (targetModel.getTargetArch() == AIEArch::AIE1 && acquireGE())
2153 return (*this)->emitOpError(
2154 "AcquireGreaterEqual is not supported in AIE1.");
2155
2156 // Otherwise, AIE.useLock should be inside MemOp, MemTileDMAOp, or
2157 // ShimDMAOp,
2159 .succeeded()) {
2160 if (!(*this)->getBlock())
2161 return (*this)->emitOpError("is not in a block.");
2162
2163 if (targetModel.getTargetArch() == AIEArch::AIE1 &&
2164 UsesOneLockInDMABlock::verifyTrait(*this).failed())
2165 return (*this)->emitOpError(
2166 "used in a DMA block that have multiple locks.");
2167
2169 return (*this)->emitOpError("acquires/releases the lock in a DMA block "
2170 "from/to multiple states.");
2171
2172 if (HasSomeParent<MemOp>::verifyTrait(*this).succeeded() &&
2173 AccessesLocalLocks::verifyTrait(*this).failed())
2174 return (*this)->emitOpError("can only access a lock in the same tile");
2175 return success();
2176
2177 // Or it can be in a CoreOp, or some FuncOp called from a CoreOp
2178 }
2179 if (HasSomeParent<CoreOp, func::FuncOp>::verifyTrait(*this).succeeded()) {
2180 return success();
2181 }
2182 return (*this)->emitOpError()
2183 << "expects some parent op to be one of "
2184 << "AIE::device, AIE::core, func::func, AIE::mem, or AIE::shimDMA";
2185}
2186
2187#include "aie/Dialect/AIE/IR/AIEEnums.cpp.inc"
2188#include "aie/Dialect/AIE/IR/AIEInterfaces.cpp.inc"
2189
2190#define GET_OP_CLASSES
2191#include "aie/Dialect/AIE/IR/AIEOps.cpp.inc"
2192
2193size_t SwitchboxOp::getNumSourceConnections(WireBundle bundle) {
2194 auto tile = getTileOp();
2195 const auto &targetModel = getTargetModel(*this);
2196 return targetModel.getNumSourceSwitchboxConnections(tile.getCol(),
2197 tile.getRow(), bundle);
2198}
2199
2200size_t SwitchboxOp::getNumDestConnections(WireBundle bundle) {
2201 auto tile = getTileOp();
2202 const auto &targetModel = getTargetModel(*this);
2203 return targetModel.getNumDestSwitchboxConnections(tile.getCol(),
2204 tile.getRow(), bundle);
2205}
2206
2207WireBundle xilinx::AIE::getConnectingBundle(WireBundle dir) {
2208 switch (dir) {
2209 case WireBundle::North:
2210 return WireBundle::South;
2211 case WireBundle::South:
2212 return WireBundle::North;
2213 case WireBundle::East:
2214 return WireBundle::West;
2215 case WireBundle::West:
2216 return WireBundle::East;
2217 default:
2218 return dir;
2219 }
2220}
2221
2222//===----------------------------------------------------------------------===//
2223// BDChainOp
2224//===----------------------------------------------------------------------===//
2225
2226ParseResult BDChainOp::parse(OpAsmParser &parser, OperationState &result) {
2227 SmallVector<OpAsmParser::Argument> entryArgs;
2228
2229 // Symbol name, e.g. @my_chain
2230 StringAttr symNameAttr;
2231 if (parser.parseSymbolName(symNameAttr, SymbolTable::getSymbolAttrName(),
2232 result.attributes)) {
2233 return failure();
2234 }
2235
2236 // Entry arguments (placeholders), e.g. (%addr: memref<1xi32>)
2237 ParseResult argParseResult = parser.parseCommaSeparatedList(
2238 OpAsmParser::Delimiter::Paren, [&]() -> ParseResult {
2239 OpAsmParser::Argument argument;
2240 if (parser.parseArgument(argument, true, true)) {
2241 return failure();
2242 }
2243 entryArgs.push_back(argument);
2244 return success();
2245 });
2246 if (argParseResult) {
2247 return argParseResult;
2248 }
2249
2250 // BD Chain Body
2251 auto *body = result.addRegion();
2252 ParseResult bodyParseResult = parser.parseRegion(*body, entryArgs, false);
2253 if (bodyParseResult) {
2254 return bodyParseResult;
2255 }
2256
2257 return success();
2258}
2259
2260void BDChainOp::print(OpAsmPrinter &printer) {
2261 auto taskName =
2262 (*this)
2263 ->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName())
2264 .getValue();
2265 printer << ' ';
2266 printer.printSymbolName(taskName);
2267
2268 Region &body = getRegion();
2269 auto argsIter = body.getArguments();
2270 printer << '(';
2271 for (auto it = argsIter.begin(); it != argsIter.end(); ++it) {
2272 if (it != argsIter.begin()) {
2273 printer << ", ";
2274 }
2275 printer.printRegionArgument(*it);
2276 }
2277 printer << ')';
2278
2279 printer << ' ';
2280 printer.printRegion(body, false, true);
2281}
2282
2283//===----------------------------------------------------------------------===//
2284// ShimDMAAllocationOp
2285//===----------------------------------------------------------------------===//
2286
2287ShimDMAAllocationOp ShimDMAAllocationOp::getForSymbol(DeviceOp device,
2288 llvm::StringRef symbol) {
2289 auto alloc_ops = device.getOps<ShimDMAAllocationOp>();
2290 for (auto it = alloc_ops.begin(); it != alloc_ops.end(); ++it) {
2291 AIE::ShimDMAAllocationOp a = *it;
2292 if (a.getSymName() == symbol) {
2293 return a;
2294 }
2295 }
2296 return nullptr;
2297}
2298
2299// Include implementations for custom attributes
2300#define GET_ATTRDEF_CLASSES
2301#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 getNumBDs(int col, int row) const =0
Return the number of buffer descriptors supported by the DMA in the given tile.
virtual bool isCoreTile(int col, int row) const =0
Return true if the given tile is a 'Core' tile.
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 bool isMemTile(int col, int row) const =0
Return true if the given tile is an AIE2 'Memory' tile.
virtual uint32_t getNumLocks(int col, int row) const =0
Return the number of lock objects.
virtual bool isShimNOCTile(int col, int row) const =0
Return true if the given tile is a Shim NOC tile.
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)
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:159
uint32_t getShimBurstLengthEncoding(const AIE::AIETargetModel &tm, uint32_t burstLength)
int32_t getBufferBaseAddress(mlir::Operation *bufOp)
Port { WireBundle bundle Port
Definition AIEDialect.h:119
const AIETargetModel & getTargetModel(mlir::Operation *op)
PathEndPoint src
mlir::ParseResult parseObjectFifoProducerTile(mlir::OpAsmParser &parser, mlir::OpAsmParser::UnresolvedOperand &operand, BDDimLayoutArrayAttr &dimensions)
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)
std::optional< AIE::ShimDMAAllocationOp > get(DeviceOp dev, mlir::StringRef sym_name)