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