MLIR-AIE 1.4.5.dev
AIEObjectFifoSplit.cpp
Go to the documentation of this file.
1//===- AIEObjectFifoSplit.cpp -----------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
10
11#include "mlir/IR/Attributes.h"
12#include "mlir/Pass/Pass.h"
13#include "llvm/ADT/DenseMap.h"
14
15namespace xilinx::AIE {
16#define GEN_PASS_DEF_AIEOBJECTFIFOSPLIT
17#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
18} // namespace xilinx::AIE
19
20using namespace mlir;
21using namespace xilinx;
22using namespace xilinx::AIE;
23
24namespace {
25
26/// Where the buffers of a fifo end live, and which of a shared pool's segments
27/// that end occupies.
28struct PoolRef {
29 ObjectFifoPoolOp pool;
30 SmallVector<int32_t> segments;
31};
32
33std::optional<ObjectFifoLinkOp> getOptionalLinkOp(ObjectFifoCreateOp op) {
34 auto device = op->getParentOfType<DeviceOp>();
35 for (ObjectFifoLinkOp linkOp : device.getOps<ObjectFifoLinkOp>()) {
36 for (ObjectFifoCreateOp in : linkOp.getInputObjectFifos()) {
37 if (in == op) {
38 return linkOp;
39 }
40 }
41 for (ObjectFifoCreateOp out : linkOp.getOutputObjectFifos()) {
42 if (out == op) {
43 return linkOp;
44 }
45 }
46 }
47 return {};
48}
49
50/// The aie.objectfifo.allocate naming `op`, if any. The allocate verifier
51/// rejects a second one for the same fifo.
52std::optional<ObjectFifoAllocateOp>
53getOptionalAllocateOp(ObjectFifoCreateOp op) {
54 for (auto alloc :
55 op->getParentOfType<DeviceOp>().getOps<ObjectFifoAllocateOp>()) {
56 if (alloc.getObjFifoName() == op.name().getValue()) {
57 return alloc;
58 }
59 }
60 return std::nullopt;
61}
62
63/// Whether `delegate`'s memory module is reachable from both ends of `op`.
64bool delegateReachesBothEnds(ObjectFifoCreateOp op, TileOp delegate) {
65 auto consumerTileOp = cast<TileOp>(op.getConsumerTiles()[0].getDefiningOp());
66 auto reachesDelegate = [&](TileOp end) {
67 using SharedMemory = AIETargetModel::SharedMemory;
68 SharedMemory shared = sharedMemory(delegate, end);
69 return shared == SharedMemory::First || shared == SharedMemory::Either;
70 };
71 return reachesDelegate(op.getProducerTileOp()) &&
72 reachesDelegate(consumerTileOp);
73}
74
75/// Why a fifo needs DMAs, or nothing when both ends reach one memory module
76/// and neither end asks the DMA to reshape the data on the way.
77std::optional<StringRef> dmaReason(ObjectFifoCreateOp createOp,
79 if (createOp.forcesDMA())
80 return "it asks for a dma transport";
81 if (createOp.getRepeatCount())
82 return "it repeats objects (`repeat_count`)";
83 if (createOp.usesStream())
84 return "an end is a Core stream port";
85 if (createOp.getConsumerElemType())
86 return "its consumer takes a different element type";
87
88 if (createOp.getConsumerTiles().size() == 1 &&
89 createOp.getDimensionsToStream().empty()) {
90 auto consumerTileOp =
91 cast<TileOp>(createOp.getConsumerTiles()[0].getDefiningOp());
92 shared = sharedMemory(createOp.getProducerTileOp(), consumerTileOp);
93 // A delegate both ends reach holds the objects even where the ends share
94 // no memory of their own.
96 if (auto alloc = getOptionalAllocateOp(createOp);
97 alloc &&
98 delegateReachesBothEnds(createOp, alloc->getDelegateTileOp()))
100 }
101 }
102
104 if (createOp.getConsumerTiles().size() > 1)
105 return "it has more than one consumer";
106 if (!createOp.getDimensionsToStream().empty())
107 return "its producer reshapes the data (`dimensionsToStream`)";
108 return "its ends share no memory module";
109 }
110
111 for (BDDimLayoutArrayAttr dims :
112 createOp.getDimensionsFromStreamPerConsumer()) {
113 if (!dims.empty()) {
114 return "a consumer reshapes the data (`dimensionsFromStream`)";
115 }
116 }
117
118 // A link passes objects between two fifos through a tile's DMAs, unless an
119 // aie.objectfifo.allocate names a delegate tile whose memory both ends of
120 // this fifo can reach directly.
121 if (getOptionalLinkOp(createOp)) {
122 if (auto alloc = getOptionalAllocateOp(createOp)) {
123 if (delegateReachesBothEnds(createOp, alloc->getDelegateTileOp()))
124 return std::nullopt;
125 }
126 return "it is linked through a tile's DMAs";
127 }
128
129 return std::nullopt;
130}
131
132/// Objects a fifo needs on `tile`: enough to cover the largest acquire made
133/// there, plus one so a core can hold an object while the next arrives. A fifo
134/// that states a depth per endpoint is taken at its word instead.
135int objectCountOn(DeviceOp device, Value tile, ObjectFifoCreateOp objFifo) {
136 if (objFifo.size() == 0) {
137 return 0;
138 }
139
140 int maxAcquire = 0;
141 for (auto coreOp : device.getOps<CoreOp>()) {
142 if (coreOp.getTile() == tile) {
143 coreOp.walk([&](ObjectFifoAcquireOp acqOp) {
144 if (acqOp.getObjectFifo() == objFifo) {
145 maxAcquire = std::max(maxAcquire, acqOp.acqNumber());
146 }
147 });
148 }
149 }
150
151 if (maxAcquire == 0) {
152 return objFifo.size();
153 }
154 if (maxAcquire == 1 && objFifo.size() == 1) {
155 return 1;
156 }
157 return maxAcquire + 1;
158}
159
160/// Objects a fifo whose ends share one memory module keeps in its single pool:
161/// the most that any end states, so the deeper end can acquire all of its own.
162int sharedObjectCount(ObjectFifoCreateOp objFifo) {
163 int depth = objFifo.size();
164 if (auto depths = dyn_cast<ArrayAttr>(objFifo.getElemNumber())) {
165 for (int index = 1; index < static_cast<int>(depths.size()); ++index) {
166 depth = std::max(depth, objFifo.size(index));
167 }
168 }
169 return depth;
170}
171
172bool hasCoreAccess(DeviceOp device, Value tile, ObjectFifoCreateOp objFifo,
173 ObjectFifoPort port) {
174 for (auto coreOp : device.getOps<CoreOp>()) {
175 if (coreOp.getTile() != tile) {
176 continue;
177 }
178 bool found = false;
179 coreOp.walk([&](Operation *op) {
180 if (auto acq = dyn_cast<ObjectFifoAcquireOp>(op)) {
181 found |= acq.getObjectFifo() == objFifo && acq.getPort() == port;
182 } else if (auto rel = dyn_cast<ObjectFifoReleaseOp>(op)) {
183 found |= rel.getObjectFifo() == objFifo && rel.getPort() == port;
184 }
185 });
186 if (found) {
187 return true;
188 }
189 }
190 return false;
191}
192
193struct AIEObjectFifoSplitPass
194 : public xilinx::AIE::impl::AIEObjectFifoSplitBase<AIEObjectFifoSplitPass> {
195
196 DeviceOp device;
197 OpBuilder builder{static_cast<MLIRContext *>(nullptr)};
198
199 /// Fill `pool`'s body with one segment per extent, named for its position.
200 void createSegments(ObjectFifoPoolOp pool, Location loc,
201 ArrayRef<std::pair<int64_t, int64_t>> extents) {
202 OpBuilder::InsertionGuard guard(builder);
203 builder.createBlock(&pool.getSegments());
204 for (auto [index, extent] : llvm::enumerate(extents)) {
205 ObjectFifoSegmentOp::create(
206 builder, loc, builder.getStringAttr("s" + std::to_string(index)),
207 builder.getI32IntegerAttr(extent.first),
208 builder.getI32IntegerAttr(extent.second),
209 /*produceLock=*/FlatSymbolRefAttr(),
210 /*consumeLock=*/FlatSymbolRefAttr());
211 }
212 }
213
214 /// The names `ref` selects, empty on a single-segment pool.
215 ArrayAttr selectedSegmentNames(PoolRef ref) {
216 std::vector<ObjectFifoSegmentOp> all = ref.pool.getSegmentOps();
217 if (all.size() <= 1) {
218 return {};
219 }
220 SmallVector<Attribute> names;
221 for (int32_t index : ref.segments) {
222 names.push_back(FlatSymbolRefAttr::get(builder.getContext(),
223 all[index].getSymName()));
224 }
225 return builder.getArrayAttr(names);
226 }
227
228 ObjectFifoPoolOp createPool(Location loc, StringRef name, Value tile,
229 int depth, MemRefType elemType,
230 ObjectFifoCreateOp from,
231 ArrayRef<std::pair<int64_t, int64_t>> extents,
232 bool holdsInitialContents,
233 std::optional<int> repeatCount,
234 bool disableSynchronization = false) {
235 auto pool = ObjectFifoPoolOp::create(
236 builder, loc, name, tile, depth, elemType, /*buffers=*/ArrayAttr(),
237 /*locks=*/ArrayAttr(),
238 repeatCount ? builder.getI32IntegerAttr(*repeatCount) : IntegerAttr(),
239 disableSynchronization || from.getDisableSynchronization(),
240 builder.getStringAttr(from.name().getValue()),
241 holdsInitialContents ? from.getInitValuesAttr() : ArrayAttr(),
242 /*contiguous=*/false, /*zeroObjects=*/nullptr);
243 createSegments(pool, loc, extents);
244 return pool;
245 }
246
247 ObjectFifoCoreEndpointOp createCoreEndpoint(Location loc, StringRef name,
248 Value tile, PoolRef ref,
249 ObjectFifoRole role) {
250 return ObjectFifoCoreEndpointOp::create(builder, loc, name, tile, role,
251 ref.pool.getSymName(),
252 selectedSegmentNames(ref));
253 }
254
255 ObjectFifoDmaEndpointOp createDmaEndpoint(Location loc, StringRef name,
256 Value tile, PoolRef ref,
257 ObjectFifoRole role,
258 ObjectFifoCreateOp from,
259 BDDimLayoutArrayAttr dims,
260 std::optional<int> channelIndex) {
261 // Both ends of the fifo carry `depth * repeat_count * iter_count` objects.
262 // The draining end spends `repeat_count` on descriptors, replaying each
263 // object in one pass of its chain; the filling end spends it on passes.
264 std::optional<int32_t> iterCount;
265 if (std::optional<int32_t> fifoIterations = from.getIterCount()) {
266 iterCount = role == ObjectFifoRole::Drain
267 ? *fifoIterations
268 : *fifoIterations * from.getRepeatCount().value_or(1);
269 }
270 ArrayAttr segments = selectedSegmentNames(ref);
271
272 BDDimLayoutArrayArrayAttr dimensions;
273 if (dims && !dims.empty()) {
274 SmallVector<BDDimLayoutArrayAttr> perSegment(ref.segments.size(), dims);
275 dimensions =
276 BDDimLayoutArrayArrayAttr::get(builder.getContext(), perSegment);
277 }
278
279 BDPadLayoutArrayArrayAttr padding;
280 std::optional<ArrayRef<BDPadLayoutAttr>> padDims = from.getPadDimensions();
281 if (role == ObjectFifoRole::Drain && padDims && !padDims->empty()) {
282 SmallVector<BDPadLayoutArrayAttr> perSegment(ref.segments.size(),
283 from.getPadDimensionsAttr());
284 padding =
285 BDPadLayoutArrayArrayAttr::get(builder.getContext(), perSegment);
286 }
287
288 return ObjectFifoDmaEndpointOp::create(
289 builder, loc, builder.getStringAttr(name), tile,
290 ObjectFifoRoleAttr::get(builder.getContext(), role),
291 FlatSymbolRefAttr::get(builder.getContext(), ref.pool.getSymName()),
292 segments,
293 channelIndex ? builder.getI32IntegerAttr(*channelIndex) : IntegerAttr(),
294 dimensions, padding,
295 from.getPadValue() ? builder.getI32IntegerAttr(from.getPadValue())
296 : IntegerAttr(),
297 iterCount ? builder.getI32IntegerAttr(*iterCount) : IntegerAttr(),
298 /*packet=*/PacketInfoAttr(),
299 builder.getStringAttr(from.name().getValue()), /*dispatch=*/ArrayAttr(),
300 /*dispatchPackets=*/ArrayAttr(), /*turns=*/ArrayAttr());
301 }
302
303 /// An end with no pool behind it: a shim whose transfers the runtime issues,
304 /// a PLIO boundary, or a core's raw stream port.
305 RouteEndpointOp createRouteEndpoint(Location loc, StringRef name, Value tile,
306 WireBundle bundle,
307 std::optional<int> channelIndex,
308 ObjectFifoCreateOp from) {
309 return RouteEndpointOp::create(
310 builder, loc, builder.getStringAttr(name), tile,
311 WireBundleAttr::get(builder.getContext(), bundle),
312 channelIndex ? builder.getI32IntegerAttr(*channelIndex) : IntegerAttr(),
313 /*packet=*/PacketInfoAttr(),
314 builder.getStringAttr(from.name().getValue()));
315 }
316
317 /// External buffers registered against a fifo's shim end form a pool in DDR.
318 /// An unregistered end receives its address from the runtime at dispatch.
319 std::optional<PoolRef> registeredPool(ObjectFifoCreateOp fifo, Value tile,
320 StringRef name, MemRefType elemType) {
321 SmallVector<Attribute> names;
322 for (auto regOp : device.getOps<ObjectFifoRegisterExternalBuffersOp>()) {
323 if (regOp.getTile() != tile || regOp.getObjectFifo() != fifo) {
324 continue;
325 }
326 for (Value buffer : regOp.getExternalBuffers()) {
327 auto external = cast<ExternalBufferOp>(buffer.getDefiningOp());
328 names.push_back(SymbolRefAttr::get(external.getSymNameAttr()));
329 // The registered buffer's own extent is what the DMA moves, which need
330 // not be the fifo's object size.
331 elemType = cast<MemRefType>(external.getType());
332 }
333 }
334 if (names.empty()) {
335 return std::nullopt;
336 }
337
338 auto pool = ObjectFifoPoolOp::create(
339 builder, fifo.getLoc(), name, tile, (int)names.size(), elemType,
340 builder.getArrayAttr(names),
341 /*locks=*/ArrayAttr(), /*repeatCount=*/IntegerAttr(),
342 fifo.getDisableSynchronization(),
343 builder.getStringAttr(fifo.name().getValue()),
344 /*initValues=*/ArrayAttr(), /*contiguous=*/false,
345 /*zeroObjects=*/nullptr);
346 createSegments(pool, fifo.getLoc(), {{0, elemType.getNumElements()}});
347 return PoolRef{pool, {0}};
348 }
349
350 /// Point a core's accesses at the endpoint it works through.
351 void retargetCoreAccesses(ObjectFifoCreateOp fifo, ObjectFifoPort port,
352 Value tile, StringRef endpointName) {
353 auto name = FlatSymbolRefAttr::get(builder.getContext(), endpointName);
354 // Each access moves to the endpoint for the port it named, so this cannot
355 // be a blanket replaceAllSymbolUses: the two ports of one fifo become two
356 // different symbols.
357 for (auto coreOp : device.getOps<CoreOp>()) {
358 if (coreOp.getTile() != tile) {
359 continue;
360 }
361 coreOp.walk([&](Operation *op) {
362 if (auto acq = dyn_cast<ObjectFifoAcquireOp>(op)) {
363 if (acq.getObjectFifo() == fifo && acq.getPort() == port) {
364 acq.setObjFifoNameAttr(name);
365 acq.removePortAttr();
366 }
367 } else if (auto rel = dyn_cast<ObjectFifoReleaseOp>(op)) {
368 if (rel.getObjectFifo() == fifo && rel.getPort() == port) {
369 rel.setObjFifoNameAttr(name);
370 rel.removePortAttr();
371 }
372 }
373 });
374 }
375 }
376
377 void runOnOperation() override;
378
379 /// Every pool sits on a concrete tile, so the fifo's ends must be placed.
380 LogicalResult verifyTilesArePlaced() {
381 for (auto fifo : device.getOps<ObjectFifoCreateOp>()) {
382 auto placed = [](Value tile) {
383 return isa_and_nonnull<TileOp>(tile.getDefiningOp());
384 };
385 if (!placed(fifo.getProducerTile())) {
386 return fifo.emitOpError("producer tile is not a placed aie.tile; run "
387 "--aie-place-tiles before this pass");
388 }
389 for (Value consumer : fifo.getConsumerTiles()) {
390 if (!placed(consumer)) {
391 return fifo.emitOpError("consumer tile is not a placed aie.tile; run "
392 "--aie-place-tiles before this pass");
393 }
394 }
395 }
396 return success();
397 }
398
399 /// A delegate tile can only hold a fifo's objects if both ends reach its
400 /// memory module. A fifo that meets a link elsewhere has no objects of its
401 /// own to place, so its delegate is moot.
402 LogicalResult verifyAllocateDelegates() {
403 for (auto fifo : device.getOps<ObjectFifoCreateOp>()) {
404 if (getOptionalLinkOp(fifo) && !linkPoolOwner.contains(fifo)) {
405 continue;
406 }
407 auto alloc = getOptionalAllocateOp(fifo);
408 if (alloc && !delegateReachesBothEnds(fifo, alloc->getDelegateTileOp())) {
409 return alloc->emitOpError("objectfifo has no shared memory access to "
410 "delegate tile's memory module");
411 }
412 }
413 return success();
414 }
415
416 /// A fifo end wired to a Core stream port holds no objects, so there is
417 /// nothing for a core to acquire or release there.
418 LogicalResult verifyStreamPortAccesses() {
419 auto check = [&](Operation *op, ObjectFifoCreateOp fifo,
420 std::optional<ObjectFifoPort> port, StringRef verb) {
421 if (!fifo || !port || !fifo.usesStream()) {
422 return success();
423 }
424 bool onStreamPort = *port == ObjectFifoPort::Produce
425 ? fifo.streamsFromProducer()
426 : fifo.streamsToConsumer(0);
427 if (!onStreamPort) {
428 return success();
429 }
430 return LogicalResult(op->emitOpError("cannot ")
431 << verb << " objectfifo stream port");
432 };
433 for (auto coreOp : device.getOps<CoreOp>()) {
434 auto result = coreOp.walk([&](Operation *op) {
435 LogicalResult ok = success();
436 if (auto acq = dyn_cast<ObjectFifoAcquireOp>(op)) {
437 ok = check(op, acq.getObjectFifo(), acq.getPort(), "acquire from");
438 } else if (auto rel = dyn_cast<ObjectFifoReleaseOp>(op)) {
439 ok = check(op, rel.getObjectFifo(), rel.getPort(), "release from");
440 }
441 return failed(ok) ? WalkResult::interrupt() : WalkResult::advance();
442 });
443 if (result.wasInterrupted()) {
444 return failure();
445 }
446 }
447 return success();
448 }
449
450 /// The DMAs on either side of a link own the shared pool between them, so a
451 /// core sharing the link point has no claim on the objects passing through.
452 LogicalResult verifyLinkAccesses() {
453 for (auto linkOp : device.getOps<ObjectFifoLinkOp>()) {
454 auto sharedTile = linkOp.getOptionalSharedTile();
455 if (!sharedTile) {
456 continue;
457 }
458 auto reject = [&](ObjectFifoCreateOp fifo) {
459 return LogicalResult(
460 linkOp.emitOpError("core on the shared tile cannot access '")
461 << fifo.name().getValue() << "': a link moves objects by DMA");
462 };
463 for (ObjectFifoCreateOp in : linkOp.getInputObjectFifos()) {
464 if (hasCoreAccess(device, *sharedTile, in, ObjectFifoPort::Consume)) {
465 return reject(in);
466 }
467 }
468 for (ObjectFifoCreateOp out : linkOp.getOutputObjectFifos()) {
469 if (hasCoreAccess(device, *sharedTile, out, ObjectFifoPort::Produce)) {
470 return reject(out);
471 }
472 }
473 }
474 return success();
475 }
476
477 /// Ends of a linked fifo that resolve to the link's shared pool. Each link
478 /// reading a fifo takes the next of its consumer ends on the link's tile.
479 DenseMap<Operation *, PoolRef> linkedProducerEnd;
480 DenseMap<std::pair<Operation *, Value>, SmallVector<PoolRef>>
481 linkedConsumerEnd;
482 /// Fifos whose own objects a link's shared pool holds.
483 DenseSet<Operation *> linkPoolOwner;
484 /// Shim endpoint each fifo is driven through, where it has one.
485 DenseMap<Operation *, std::string> shimEndpointName;
486
487 /// The time link each merging input or dispatched output takes turns in.
488 DenseMap<Operation *, ObjectFifoLinkOp> timeLinkOf;
489 /// A time link's one shared endpoint on the link point.
490 DenseMap<Operation *, ObjectFifoDmaEndpointOp> timeLinkEndpoint;
491 /// What each time link gathers while its participants are split: a merge's
492 /// sources, and a dispatch's destination per output.
493 DenseMap<Operation *, SmallVector<Attribute>> mergeSources;
494 DenseMap<Operation *, DenseMap<Operation *, Attribute>> dispatchTurns;
495
496 /// The endpoint a time link's turns share, made by whichever participant
497 /// reaches it first.
498 ObjectFifoDmaEndpointOp timeLinkEndpointFor(ObjectFifoLinkOp link,
499 Location loc, PoolRef ref,
500 std::optional<int> channel);
501 void createTimeLinkRoutes();
502 LogicalResult createLinkPools();
503};
504
505/// A link's participants meet on one pool on the shared tile: the fifo carrying
506/// whole objects owns its size, and each of the other side's fifos occupies one
507/// segment.
508LogicalResult AIEObjectFifoSplitPass::createLinkPools() {
509 for (auto linkOp : device.getOps<ObjectFifoLinkOp>()) {
510 auto sharedTile = linkOp.getOptionalSharedTile();
511 if (!sharedTile) {
512 continue;
513 }
514
515 std::vector<ObjectFifoCreateOp> ins = linkOp.getInputObjectFifos();
516 std::vector<ObjectFifoCreateOp> outs = linkOp.getOutputObjectFifos();
517 bool isJoin = linkOp.isJoin();
518 bool isDistribute = linkOp.isDistribute();
519
520 ObjectFifoCreateOp owner = linkOp.getPoolOwner();
521 if (linkOp.isMerge()) {
522 for (ObjectFifoCreateOp in : ins)
523 timeLinkOf[in] = linkOp;
524 }
525 if (linkOp.isDispatch()) {
526 for (ObjectFifoCreateOp out : outs)
527 timeLinkOf[out] = linkOp;
528 }
529
530 auto elemType = cast<MemRefType>(
531 cast<AIEObjectFifoType>(owner.getElemType()).getElementType());
532 builder.setInsertionPoint(owner);
533
534 SmallVector<std::pair<int64_t, int64_t>> extents = linkOp.getPoolSegments();
535
536 bool ownerIsOutput = llvm::is_contained(outs, owner);
537 std::string name;
538 int depth;
539 // A walk over the stream reads across the objects of one image, so the
540 // pool holds the image's objects side by side.
541 bool window = linkOp.walksStream();
542 StreamWindow walk;
543 if (window) {
544 if (failed(linkOp.getStreamWindow(walk)))
545 return failure();
546 name = (owner.name().getValue() + "_cons_pool").str();
547 depth = walk.objects;
548 } else if (ownerIsOutput) {
549 name = (owner.name().getValue() + "_pool").str();
550 depth = owner.size();
551 } else {
552 auto consumers = owner.getConsumerTiles();
553 size_t earlier = linkedConsumerEnd.lookup({owner, *sharedTile}).size();
554 int index = -1;
555 for (size_t seen = 0; seen <= earlier;) {
556 assert(static_cast<size_t>(index + 1) < consumers.size() &&
557 "fewer consumer tiles than already-linked pools for this tile");
558 if (consumers[++index] == *sharedTile)
559 seen++;
560 }
561 name = (owner.name().getValue() +
562 (consumers.size() > 1 ? "_" + std::to_string(index) + "_cons_pool"
563 : "_cons_pool"))
564 .str();
565 depth = isa<ArrayAttr>(owner.getElemNumber())
566 ? owner.size(index + 1)
567 : objectCountOn(device, *sharedTile, owner);
568 }
569
570 // The pool is shared by every participant, but `owner` is only one of them:
571 // whether its locks are generated must not depend on which fifo that is.
572 auto asksToSkipLocks = [](ObjectFifoCreateOp fifo) {
573 return fifo.getDisableSynchronization();
574 };
575 bool disableSynchronization = llvm::any_of(ins, asksToSkipLocks) ||
576 llvm::any_of(outs, asksToSkipLocks);
577
578 auto pool = createPool(linkOp.getLoc(), name, *sharedTile, depth, elemType,
579 owner, extents,
580 /*holdsInitialContents=*/ownerIsOutput,
581 linkOp.getRepeatCount(), disableSynchronization);
582 if (window) {
583 pool.setContiguous(true);
584 // The padding objects sit in the buffer as zeros, since a MemTile
585 // descriptor walking four dimensions ignores its padding.
586 if (walk.top || walk.bottom)
587 pool.setZeroObjects(SmallVector<int32_t>{
588 static_cast<int32_t>(walk.top), static_cast<int32_t>(walk.bottom)});
589 }
590 linkPoolOwner.insert(owner);
591
592 SmallVector<int32_t> allSegments;
593 for (int32_t index = 0; index < (int32_t)extents.size(); ++index) {
594 allSegments.push_back(index);
595 }
596 for (auto [index, in] : llvm::enumerate(ins)) {
597 linkedConsumerEnd[{in, *sharedTile}].push_back(PoolRef{
598 pool, isJoin ? SmallVector<int32_t>{static_cast<int32_t>(index)}
599 : allSegments});
600 }
601 for (auto [index, out] : llvm::enumerate(outs)) {
602 linkedProducerEnd[out] = PoolRef{
603 pool, isDistribute ? SmallVector<int32_t>{static_cast<int32_t>(index)}
604 : allSegments};
605 }
606 }
607 return success();
608}
609
610ObjectFifoDmaEndpointOp
611AIEObjectFifoSplitPass::timeLinkEndpointFor(ObjectFifoLinkOp link, Location loc,
612 PoolRef ref,
613 std::optional<int> channel) {
614 ObjectFifoDmaEndpointOp &endpoint = timeLinkEndpoint[link];
615 if (!endpoint) {
616 ObjectFifoCreateOp owner = link.isMerge() ? link.getOutputObjectFifos()[0]
617 : link.getInputObjectFifos()[0];
618 endpoint = createDmaEndpoint(
619 loc,
620 (owner.name().getValue() +
621 (link.isMerge() ? "_merge_dma" : "_dispatch_dma"))
622 .str(),
623 ref.pool.getTile(), ref,
624 link.isMerge() ? ObjectFifoRole::Fill : ObjectFifoRole::Drain, owner,
625 BDDimLayoutArrayAttr(), channel);
626 }
627 return endpoint;
628}
629
630/// A merge becomes one fan-in route into its shared endpoint; a dispatch
631/// names its turns on its shared endpoint in the link's output order.
632void AIEObjectFifoSplitPass::createTimeLinkRoutes() {
633 for (auto link : device.getOps<ObjectFifoLinkOp>()) {
634 ObjectFifoDmaEndpointOp endpoint = timeLinkEndpoint.lookup(link);
635 if (!endpoint)
636 continue;
637 MLIRContext *ctx = builder.getContext();
638 if (link.isMerge()) {
639 PacketInfoAttr header = PacketInfoAttr::get(ctx, 0, std::nullopt);
640 for (ObjectFifoCreateOp in : link.getInputObjectFifos())
641 if (PacketInfoAttr pinned = in.packetHeader();
642 pinned && pinned.isAssigned())
643 header = pinned;
644 builder.setInsertionPoint(link);
645 RouteOp::create(builder, link.getLoc(),
646 builder.getArrayAttr(mergeSources[link]),
647 builder.getArrayAttr(
648 {FlatSymbolRefAttr::get(ctx, endpoint.getSymName())}),
649 header);
650 continue;
651 }
652 SmallVector<Attribute> turns;
653 for (ObjectFifoCreateOp out : link.getOutputObjectFifos())
654 turns.push_back(dispatchTurns[link].lookup(out));
655 endpoint.setDispatchAttr(builder.getArrayAttr(turns));
656 }
657}
658
659void AIEObjectFifoSplitPass::runOnOperation() {
660 device = getOperation();
661 builder = OpBuilder(device.getContext());
662 builder.setInsertionPoint(device.getBody()->getTerminator());
663
664 SmallVector<ObjectFifoCreateOp> fifos(device.getOps<ObjectFifoCreateOp>());
665
666 if (failed(verifyTilesArePlaced()) || failed(verifyStreamPortAccesses()) ||
667 failed(verifyLinkAccesses())) {
668 return signalPassFailure();
669 }
670
671 if (failed(createLinkPools()) || failed(verifyAllocateDelegates())) {
672 return signalPassFailure();
673 }
674
675 for (ObjectFifoCreateOp fifo : fifos) {
676 builder.setInsertionPoint(fifo);
677 Location loc = fifo.getLoc();
678 StringRef fifoName = fifo.name().getValue();
679 auto fifoType = cast<AIEObjectFifoType>(fifo.getElemType());
680 auto elemType = cast<MemRefType>(fifoType.getElementType());
681 auto consElemType = cast<MemRefType>(
682 cast<AIEObjectFifoType>(fifo.getConsumerElemTypeOrDefault())
683 .getElementType());
684
685 auto sharedModule = AIETargetModel::SharedMemory::None;
686 std::optional<StringRef> needsDMAs = dmaReason(fifo, sharedModule);
687 if (needsDMAs && fifo.forcesSharedMemory()) {
688 fifo.emitOpError("asks for a shared_mem transport, but ") << *needsDMAs;
689 return signalPassFailure();
690 }
691 bool shared = !needsDMAs;
692
693 if (shared) {
694 PoolRef ref;
695 if (auto linked = linkedProducerEnd.find(fifo);
696 linked != linkedProducerEnd.end()) {
697 ref = linked->second;
698 } else if (auto linked =
699 linkedConsumerEnd.find({fifo, fifo.getConsumerTiles()[0]});
700 linked != linkedConsumerEnd.end()) {
701 ref = linked->second.front();
702 } else {
703 Value tile = sharedModule == AIETargetModel::SharedMemory::Second
704 ? fifo.getConsumerTiles()[0]
705 : fifo.getProducerTile();
706 // An aie.objectfifo.allocate names the tile whose memory is to hold the
707 // objects, in place of either end's own.
708 if (auto alloc = getOptionalAllocateOp(fifo)) {
709 if (!delegateReachesBothEnds(fifo, alloc->getDelegateTileOp())) {
710 alloc->emitOpError("objectfifo has no shared memory access to "
711 "delegate tile's memory module");
712 return signalPassFailure();
713 }
714 tile = alloc->getDelegateTile();
715 }
716 // Both ends address the one pool, so it needs the deeper end's depth
717 // even when init_values fill only the producer's share of it.
718 ref = PoolRef{createPool(loc, (fifoName + "_pool").str(), tile,
719 sharedObjectCount(fifo), elemType, fifo,
720 {{0, elemType.getNumElements()}},
721 /*holdsInitialContents=*/true,
722 fifo.getRepeatCount()),
723 {0}};
724 }
725
726 if (hasCoreAccess(device, fifo.getProducerTile(), fifo,
727 ObjectFifoPort::Produce)) {
728 auto name = (fifoName + "_prod").str();
729 createCoreEndpoint(loc, name, fifo.getProducerTile(), ref,
730 ObjectFifoRole::Fill);
731 retargetCoreAccesses(fifo, ObjectFifoPort::Produce,
732 fifo.getProducerTile(), name);
733 }
734 if (hasCoreAccess(device, fifo.getConsumerTiles()[0], fifo,
735 ObjectFifoPort::Consume)) {
736 auto name = (fifoName + "_cons").str();
737 createCoreEndpoint(loc, name, fifo.getConsumerTiles()[0], ref,
738 ObjectFifoRole::Drain);
739 retargetCoreAccesses(fifo, ObjectFifoPort::Consume,
740 fifo.getConsumerTiles()[0], name);
741 }
742 continue;
743 }
744
745 Value prodTile = fifo.getProducerTile();
746 // A fifo end wired straight to a Core stream port has no objects of its
747 // own: whatever the core writes goes out on the port.
748 ObjectFifoEndPortAttr prodPort = fifo.getProdPortAttr();
749 std::optional<int> prodChannel =
750 prodPort ? prodPort.channelIndex() : std::nullopt;
751
752 bool prodIsShim = cast<TileOp>(prodTile.getDefiningOp()).isShimTile();
753 std::optional<PoolRef> prodRef;
754 // Objects entering a repeating link arrive that many at a time.
755 if (auto linked = linkedProducerEnd.find(fifo);
756 linked != linkedProducerEnd.end()) {
757 prodRef = linked->second;
758 } else if (prodIsShim) {
759 if (!fifo.getPlio()) {
760 prodRef = registeredPool(fifo, prodTile, (fifoName + "_pool").str(),
761 elemType);
762 }
763 } else if (!fifo.streamsFromProducer()) {
764 int depth = fifo.getInitValues() ? fifo.size()
765 : objectCountOn(device, prodTile, fifo);
766 auto pool =
767 createPool(loc, (fifoName + "_pool").str(), prodTile, depth, elemType,
768 fifo, {{0, elemType.getNumElements()}},
769 /*holdsInitialContents=*/true, fifo.getRepeatCount());
770 prodRef = PoolRef{pool, {0}};
771 }
772
773 if (prodRef &&
774 hasCoreAccess(device, prodTile, fifo, ObjectFifoPort::Produce)) {
775 auto name = (fifoName + "_prod").str();
776 createCoreEndpoint(loc, name, prodTile, *prodRef, ObjectFifoRole::Fill);
777 retargetCoreAccesses(fifo, ObjectFifoPort::Produce, prodTile, name);
778 }
779
780 std::string prodDmaName = (fifoName + "_prod_dma").str();
781 ObjectFifoLinkOp timeLink = timeLinkOf.lookup(fifo);
782 if (timeLink && timeLink.isDispatch()) {
783 prodDmaName = timeLinkEndpointFor(timeLink, loc, *prodRef, prodChannel)
784 .getSymName();
785 } else if (prodRef) {
786 createDmaEndpoint(loc, prodDmaName, prodTile, *prodRef,
787 ObjectFifoRole::Drain, fifo,
788 fifo.getDimensionsToStreamAttr(), prodChannel);
789 } else {
790 createRouteEndpoint(loc, prodDmaName, prodTile,
791 fifo.streamsFromProducer() ? WireBundle::Core
792 : fifo.getPlio() ? WireBundle::PLIO
793 : WireBundle::DMA,
794 prodChannel, fifo);
795 }
796 if (prodIsShim) {
797 shimEndpointName[fifo] = prodDmaName;
798 }
799
800 SmallVector<Attribute> destinations;
801 ArrayRef<BDDimLayoutArrayAttr> consumerDims =
802 fifo.getDimensionsFromStreamPerConsumer();
803 int consumerIndex = 0;
804 DenseMap<Value, unsigned> linkedOn;
805 for (Value consumerTile : fifo.getConsumerTiles()) {
806 std::string suffix = fifo.getConsumerTiles().size() > 1
807 ? ("_" + std::to_string(consumerIndex) + "_cons")
808 : "_cons";
809 bool consIsShim = cast<TileOp>(consumerTile.getDefiningOp()).isShimTile();
810
811 std::optional<PoolRef> consRef;
812 if (auto linked = linkedConsumerEnd.find({fifo, consumerTile});
813 linked != linkedConsumerEnd.end() &&
814 linkedOn[consumerTile] < linked->second.size()) {
815 consRef = linked->second[linkedOn[consumerTile]++];
816 } else if (consIsShim) {
817 if (!fifo.getPlio()) {
818 consRef =
819 registeredPool(fifo, consumerTile,
820 (fifoName + suffix + "_pool").str(), consElemType);
821 }
822 } else if (!fifo.streamsToConsumer(consumerIndex)) {
823 int depth = isa<ArrayAttr>(fifo.getElemNumber())
824 ? fifo.size(consumerIndex + 1)
825 : objectCountOn(device, consumerTile, fifo);
826 auto pool = createPool(loc, (fifoName + suffix + "_pool").str(),
827 consumerTile, depth, consElemType, fifo,
828 {{0, consElemType.getNumElements()}},
829 /*holdsInitialContents=*/false,
830 /*repeatCount=*/std::nullopt);
831 consRef = PoolRef{pool, {0}};
832 }
833
834 if (consRef &&
835 hasCoreAccess(device, consumerTile, fifo, ObjectFifoPort::Consume)) {
836 auto name = (fifoName + suffix).str();
837 createCoreEndpoint(loc, name, consumerTile, *consRef,
838 ObjectFifoRole::Drain);
839 retargetCoreAccesses(fifo, ObjectFifoPort::Consume, consumerTile, name);
840 }
841
842 ObjectFifoEndPortAttr consPort = fifo.getConsumerPort(consumerIndex);
843 std::optional<int> consChannel =
844 consPort ? consPort.channelIndex() : std::nullopt;
845
846 std::string consDmaName = (fifoName + suffix + "_dma").str();
847 if (timeLink && timeLink.isMerge()) {
848 consDmaName = timeLinkEndpointFor(timeLink, loc, *consRef, consChannel)
849 .getSymName();
850 } else if (consRef) {
851 createDmaEndpoint(loc, consDmaName, consumerTile, *consRef,
852 ObjectFifoRole::Fill, fifo,
853 consumerDims.empty() ? BDDimLayoutArrayAttr()
854 : consumerDims[consumerIndex],
855 consChannel);
856 } else {
857 createRouteEndpoint(loc, consDmaName, consumerTile,
858 fifo.streamsToConsumer(consumerIndex)
859 ? WireBundle::Core
860 : fifo.getPlio() ? WireBundle::PLIO
861 : WireBundle::DMA,
862 consChannel, fifo);
863 }
864 destinations.push_back(
865 FlatSymbolRefAttr::get(builder.getContext(), consDmaName));
866 if (consIsShim) {
867 shimEndpointName[fifo] = consDmaName;
868 }
869 consumerIndex++;
870 }
871
872 FlatSymbolRefAttr source =
873 FlatSymbolRefAttr::get(builder.getContext(), prodDmaName);
874 if (timeLink && timeLink.isMerge()) {
875 mergeSources[timeLink].push_back(source);
876 continue;
877 }
878 // A dispatch tells its turns apart by header, so each of its routes
879 // carries one.
880 PacketInfoAttr header = fifo.packetHeader();
881 if (timeLink && timeLink.isDispatch()) {
882 dispatchTurns[timeLink][fifo] = destinations.front();
883 if (!header)
884 header = PacketInfoAttr::get(builder.getContext(), 0, std::nullopt);
885 }
886 RouteOp::create(builder, loc, builder.getArrayAttr({source}),
887 builder.getArrayAttr(destinations), header);
888 }
889
890 createTimeLinkRoutes();
891
892 SmallVector<Operation *> toErase;
893 for (ObjectFifoCreateOp fifo : fifos) {
894 // The runtime sequence drives a fifo through its shim end.
895 auto shim = shimEndpointName.find(fifo);
896 if (shim != shimEndpointName.end()) {
897 (void)SymbolTable::replaceAllSymbolUses(
898 fifo, builder.getStringAttr(shim->second), device);
899 }
900 toErase.push_back(fifo);
901 }
902 for (auto linkOp : device.getOps<ObjectFifoLinkOp>()) {
903 toErase.push_back(linkOp);
904 }
905 for (auto allocOp : device.getOps<ObjectFifoAllocateOp>()) {
906 toErase.push_back(allocOp);
907 }
908 for (auto regOp : device.getOps<ObjectFifoRegisterExternalBuffersOp>()) {
909 toErase.push_back(regOp);
910 }
911 for (Operation *op : toErase) {
912 op->erase();
913 }
914}
915
916} // namespace
917
918std::unique_ptr<OperationPass<DeviceOp>>
920 return std::make_unique<AIEObjectFifoSplitPass>();
921}
SharedMemory
Which of two tiles' memory modules both of them can address.
@ None
Neither tile can address the other's memory module.
AIETargetModel::SharedMemory sharedMemory(TileOp a, TileOp b)
Which of a's and b's memory modules both tiles can address.
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEObjectFifoSplitPass()