MLIR-AIE 1.4.6.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 /*out_of_order_tags=*/DenseI32ArrayAttr());
244 createSegments(pool, loc, extents);
245 return pool;
246 }
247
248 ObjectFifoCoreEndpointOp createCoreEndpoint(Location loc, StringRef name,
249 Value tile, PoolRef ref,
250 ObjectFifoRole role) {
251 return ObjectFifoCoreEndpointOp::create(
252 builder, loc, name, tile, role, ref.pool.getSymName(),
253 selectedSegmentNames(ref),
254 /*core_push=*/ObjectFifoCorePushAttr());
255 }
256
257 ObjectFifoDmaEndpointOp createDmaEndpoint(Location loc, StringRef name,
258 Value tile, PoolRef ref,
259 ObjectFifoRole role,
260 ObjectFifoCreateOp from,
261 BDDimLayoutArrayAttr dims,
262 std::optional<int> channelIndex) {
263 // Both ends of the fifo carry `depth * repeat_count * iter_count` objects.
264 // The draining end spends `repeat_count` on descriptors, replaying each
265 // object in one pass of its chain; the filling end spends it on passes.
266 std::optional<int32_t> iterCount;
267 if (std::optional<int32_t> fifoIterations = from.getIterCount()) {
268 iterCount = role == ObjectFifoRole::Drain
269 ? *fifoIterations
270 : *fifoIterations * from.getRepeatCount().value_or(1);
271 }
272 ArrayAttr segments = selectedSegmentNames(ref);
273
274 BDDimLayoutArrayArrayAttr dimensions;
275 if (dims && !dims.empty()) {
276 SmallVector<BDDimLayoutArrayAttr> perSegment(ref.segments.size(), dims);
277 dimensions =
278 BDDimLayoutArrayArrayAttr::get(builder.getContext(), perSegment);
279 }
280
281 BDPadLayoutArrayArrayAttr padding;
282 std::optional<ArrayRef<BDPadLayoutAttr>> padDims = from.getPadDimensions();
283 if (role == ObjectFifoRole::Drain && padDims && !padDims->empty()) {
284 SmallVector<BDPadLayoutArrayAttr> perSegment(ref.segments.size(),
285 from.getPadDimensionsAttr());
286 padding =
287 BDPadLayoutArrayArrayAttr::get(builder.getContext(), perSegment);
288 }
289
290 return ObjectFifoDmaEndpointOp::create(
291 builder, loc, builder.getStringAttr(name), tile,
292 ObjectFifoRoleAttr::get(builder.getContext(), role),
293 FlatSymbolRefAttr::get(builder.getContext(), ref.pool.getSymName()),
294 segments,
295 channelIndex ? builder.getI32IntegerAttr(*channelIndex) : IntegerAttr(),
296 dimensions, padding,
297 from.getPadValue() ? builder.getI32IntegerAttr(from.getPadValue())
298 : IntegerAttr(),
299 iterCount ? builder.getI32IntegerAttr(*iterCount) : IntegerAttr(),
300 /*packet=*/PacketInfoAttr(),
301 builder.getStringAttr(from.name().getValue()), /*dispatch=*/ArrayAttr(),
302 /*dispatchPackets=*/ArrayAttr(), /*turns=*/ArrayAttr(),
303 from.getOutOfOrder() ? builder.getUnitAttr() : UnitAttr(),
304 /*out_of_order_tags=*/DenseI32ArrayAttr(),
305 role == ObjectFifoRole::Drain ? from.getReleaseStrategyAttr()
306 : ObjectFifoReleaseAttr());
307 }
308
309 /// An end with no pool behind it: a shim whose transfers the runtime issues,
310 /// a PLIO boundary, or a core's raw stream port.
311 RouteEndpointOp createRouteEndpoint(Location loc, StringRef name, Value tile,
312 WireBundle bundle,
313 std::optional<int> channelIndex,
314 ObjectFifoCreateOp from) {
315 auto endpoint = RouteEndpointOp::create(
316 builder, loc, builder.getStringAttr(name), tile,
317 WireBundleAttr::get(builder.getContext(), bundle),
318 channelIndex ? builder.getI32IntegerAttr(*channelIndex) : IntegerAttr(),
319 /*packet=*/PacketInfoAttr(),
320 builder.getStringAttr(from.name().getValue()));
321 if (bundle == WireBundle::DMA &&
322 cast<TileOp>(tile.getDefiningOp()).isShimTile()) {
323 AIEDialect::ShareableWithAttrHelper sharing(builder.getContext());
324 if (auto allowed = sharing.getAttr(from))
325 sharing.setAttr(endpoint, allowed);
326 }
327 return endpoint;
328 }
329
330 /// External buffers registered against a fifo's shim end form a pool in DDR.
331 /// An unregistered end receives its address from the runtime at dispatch.
332 std::optional<PoolRef> registeredPool(ObjectFifoCreateOp fifo, Value tile,
333 StringRef name, MemRefType elemType) {
334 SmallVector<Attribute> names;
335 for (auto regOp : device.getOps<ObjectFifoRegisterExternalBuffersOp>()) {
336 if (regOp.getTile() != tile || regOp.getObjectFifo() != fifo) {
337 continue;
338 }
339 for (Value buffer : regOp.getExternalBuffers()) {
340 auto external = cast<ExternalBufferOp>(buffer.getDefiningOp());
341 names.push_back(SymbolRefAttr::get(external.getSymNameAttr()));
342 // The registered buffer's own extent is what the DMA moves, which need
343 // not be the fifo's object size.
344 elemType = cast<MemRefType>(external.getType());
345 }
346 }
347 if (names.empty()) {
348 return std::nullopt;
349 }
350
351 auto pool = ObjectFifoPoolOp::create(
352 builder, fifo.getLoc(), name, tile, (int)names.size(), elemType,
353 builder.getArrayAttr(names),
354 /*locks=*/ArrayAttr(), /*repeatCount=*/IntegerAttr(),
355 fifo.getDisableSynchronization(),
356 builder.getStringAttr(fifo.name().getValue()),
357 /*initValues=*/ArrayAttr(), /*contiguous=*/false,
358 /*zeroObjects=*/nullptr,
359 /*out_of_order_tags=*/DenseI32ArrayAttr());
360 createSegments(pool, fifo.getLoc(), {{0, elemType.getNumElements()}});
361 return PoolRef{pool, {0}};
362 }
363
364 /// Point a core's accesses at the endpoint it works through.
365 void retargetCoreAccesses(ObjectFifoCreateOp fifo, ObjectFifoPort port,
366 Value tile, StringRef endpointName) {
367 auto name = FlatSymbolRefAttr::get(builder.getContext(), endpointName);
368 // Each access moves to the endpoint for the port it named, so this cannot
369 // be a blanket replaceAllSymbolUses: the two ports of one fifo become two
370 // different symbols.
371 for (auto coreOp : device.getOps<CoreOp>()) {
372 if (coreOp.getTile() != tile) {
373 continue;
374 }
375 coreOp.walk([&](Operation *op) {
376 if (auto acq = dyn_cast<ObjectFifoAcquireOp>(op)) {
377 if (acq.getObjectFifo() == fifo && acq.getPort() == port) {
378 acq.setObjFifoNameAttr(name);
379 acq.removePortAttr();
380 }
381 } else if (auto rel = dyn_cast<ObjectFifoReleaseOp>(op)) {
382 if (rel.getObjectFifo() == fifo && rel.getPort() == port) {
383 rel.setObjFifoNameAttr(name);
384 rel.removePortAttr();
385 }
386 }
387 });
388 }
389 }
390
391 void runOnOperation() override;
392
393 /// Every pool sits on a concrete tile, so the fifo's ends must be placed.
394 LogicalResult verifyTilesArePlaced() {
395 for (auto fifo : device.getOps<ObjectFifoCreateOp>()) {
396 auto placed = [](Value tile) {
397 return isa_and_nonnull<TileOp>(tile.getDefiningOp());
398 };
399 if (!placed(fifo.getProducerTile())) {
400 return fifo.emitOpError("producer tile is not a placed aie.tile; run "
401 "--aie-place-tiles before this pass");
402 }
403 for (Value consumer : fifo.getConsumerTiles()) {
404 if (!placed(consumer)) {
405 return fifo.emitOpError("consumer tile is not a placed aie.tile; run "
406 "--aie-place-tiles before this pass");
407 }
408 }
409 }
410 return success();
411 }
412
413 /// Sharing a receiving channel requires the author's explicit opt-in to
414 /// the ordering constraints of out-of-order packet reception.
415 LogicalResult
416 verifyOutOfOrderForSharedChannels(ArrayRef<ObjectFifoCreateOp> fifos) {
417 DenseMap<std::tuple<int, int, int>, SmallVector<ObjectFifoCreateOp>>
418 onChannel;
419 for (ObjectFifoCreateOp fifo : fifos) {
421 if (!dmaReason(fifo, shared))
422 continue;
423 for (auto [index, consumer] : llvm::enumerate(fifo.getConsumerTiles())) {
424 ObjectFifoEndPortAttr port = fifo.getConsumerPort(index);
425 if (!port || port.isCore())
426 continue;
427 std::optional<int> pinnedChannel = port.channelIndex();
428 if (!pinnedChannel)
429 continue;
430 int channel = *pinnedChannel;
431 auto tile = consumer.getDefiningOp<TileOp>();
432 if (channel < 0 || !tile)
433 continue;
434 onChannel[{tile.getCol(), tile.getRow(), channel}].push_back(fifo);
435 }
436 }
437 for (auto &[channel, sharing] : onChannel) {
438 if (sharing.size() < 2)
439 continue;
440 for (ObjectFifoCreateOp fifo : sharing) {
441 if (!fifo.getOutOfOrder())
442 return fifo.emitOpError(
443 "shares a receiving channel; explicitly set out_of_order = true "
444 "on its packet transport");
445 }
446 }
447 return success();
448 }
449
450 /// Two fifos pinned to one sending channel can only share it when the core
451 /// pushes a task per object, since a channel cycling on one fifo's buffers
452 /// never reaches the other's, so pinning them together selects that.
453 void selectPushQueueForSharedChannels(ArrayRef<ObjectFifoCreateOp> fifos) {
454 DenseMap<std::tuple<int, int, int>, SmallVector<ObjectFifoCreateOp>>
455 onChannel;
456 for (ObjectFifoCreateOp fifo : fifos) {
458 if (!dmaReason(fifo, shared))
459 continue;
460 ObjectFifoEndPortAttr port = fifo.getProdPortAttr();
461 std::optional<int> channel =
462 port && !port.isCore() ? port.channelIndex() : std::nullopt;
463 auto tile = fifo.getProducerTile().getDefiningOp<TileOp>();
464 // Shared shim transfers are submitted by the runtime.
465 if (!channel || !tile || tile.isShimTile())
466 continue;
467 onChannel[{tile.getCol(), tile.getRow(), *channel}].push_back(fifo);
468 }
469 for (auto &[channel, sharing] : onChannel)
470 if (sharing.size() > 1)
471 for (ObjectFifoCreateOp fifo : sharing)
472 fifo.setReleaseStrategy(ObjectFifoRelease::PushQueue);
473 }
474
475 LogicalResult verifyPushQueueProducers(ArrayRef<ObjectFifoCreateOp> fifos) {
476 for (ObjectFifoCreateOp fifo : fifos) {
477 if (fifo.getReleaseStrategy() != ObjectFifoRelease::PushQueue)
478 continue;
480 if (!dmaReason(fifo, shared))
481 continue;
482 if (auto values = fifo.getInitValues(); values && !values->empty())
483 return fifo.emitOpError(
484 "push_queue does not support initialized objects");
485 auto tile = fifo.getProducerTileOp();
486 if (!tile.isCoreTile() || fifo.streamsFromProducer() ||
487 device.getTargetModel().getDmaTaskQueueDepth() == 0)
488 return fifo.emitOpError(
489 "push_queue requires a sending DMA on a core tile "
490 "with core-issued task support");
491 bool hasRelease = false;
492 for (CoreOp core : device.getOps<CoreOp>()) {
493 if (core.getTile() == fifo.getProducerTile())
494 core.walk([&](ObjectFifoReleaseOp release) {
495 hasRelease |= release.getObjectFifo() == fifo &&
496 release.getPort() == ObjectFifoPort::Produce;
497 });
498 }
499 if (!hasRelease)
500 return fifo.emitOpError("push_queue requires producer releases in a "
501 "local core to submit send tasks");
502 }
503 return success();
504 }
505
506 /// A delegate tile can only hold a fifo's objects if both ends reach its
507 /// memory module. A fifo that meets a link elsewhere has no objects of its
508 /// own to place, so its delegate is moot.
509 LogicalResult verifyAllocateDelegates() {
510 for (auto fifo : device.getOps<ObjectFifoCreateOp>()) {
511 if (getOptionalLinkOp(fifo) && !linkPoolOwner.contains(fifo)) {
512 continue;
513 }
514 auto alloc = getOptionalAllocateOp(fifo);
515 if (alloc && !delegateReachesBothEnds(fifo, alloc->getDelegateTileOp())) {
516 return alloc->emitOpError("objectfifo has no shared memory access to "
517 "delegate tile's memory module");
518 }
519 }
520 return success();
521 }
522
523 /// A fifo end wired to a Core stream port holds no objects, so there is
524 /// nothing for a core to acquire or release there.
525 LogicalResult verifyStreamPortAccesses() {
526 auto check = [&](Operation *op, ObjectFifoCreateOp fifo,
527 std::optional<ObjectFifoPort> port, StringRef verb) {
528 if (!fifo || !port || !fifo.usesStream()) {
529 return success();
530 }
531 bool onStreamPort = *port == ObjectFifoPort::Produce
532 ? fifo.streamsFromProducer()
533 : fifo.streamsToConsumer(0);
534 if (!onStreamPort) {
535 return success();
536 }
537 return LogicalResult(op->emitOpError("cannot ")
538 << verb << " objectfifo stream port");
539 };
540 for (auto coreOp : device.getOps<CoreOp>()) {
541 auto result = coreOp.walk([&](Operation *op) {
542 LogicalResult ok = success();
543 if (auto acq = dyn_cast<ObjectFifoAcquireOp>(op)) {
544 ok = check(op, acq.getObjectFifo(), acq.getPort(), "acquire from");
545 } else if (auto rel = dyn_cast<ObjectFifoReleaseOp>(op)) {
546 ok = check(op, rel.getObjectFifo(), rel.getPort(), "release from");
547 }
548 return failed(ok) ? WalkResult::interrupt() : WalkResult::advance();
549 });
550 if (result.wasInterrupted()) {
551 return failure();
552 }
553 }
554 return success();
555 }
556
557 /// The DMAs on either side of a link own the shared pool between them, so a
558 /// core sharing the link point has no claim on the objects passing through.
559 LogicalResult verifyLinkAccesses() {
560 for (auto linkOp : device.getOps<ObjectFifoLinkOp>()) {
561 auto sharedTile = linkOp.getOptionalSharedTile();
562 if (!sharedTile) {
563 continue;
564 }
565 auto reject = [&](ObjectFifoCreateOp fifo) {
566 return LogicalResult(
567 linkOp.emitOpError("core on the shared tile cannot access '")
568 << fifo.name().getValue() << "': a link moves objects by DMA");
569 };
570 for (ObjectFifoCreateOp in : linkOp.getInputObjectFifos()) {
571 if (hasCoreAccess(device, *sharedTile, in, ObjectFifoPort::Consume)) {
572 return reject(in);
573 }
574 }
575 for (ObjectFifoCreateOp out : linkOp.getOutputObjectFifos()) {
576 if (hasCoreAccess(device, *sharedTile, out, ObjectFifoPort::Produce)) {
577 return reject(out);
578 }
579 }
580 }
581 return success();
582 }
583
584 /// Ends of a linked fifo that resolve to the link's shared pool. Each link
585 /// reading a fifo takes the next of its consumer ends on the link's tile.
586 DenseMap<Operation *, PoolRef> linkedProducerEnd;
587 DenseMap<std::pair<Operation *, Value>, SmallVector<PoolRef>>
588 linkedConsumerEnd;
589 /// Fifos whose own objects a link's shared pool holds.
590 DenseSet<Operation *> linkPoolOwner;
591 /// Shim endpoint each fifo is driven through, where it has one.
592 DenseMap<Operation *, std::string> shimEndpointName;
593
594 /// The time link each merging input or dispatched output takes turns in.
595 DenseMap<Operation *, ObjectFifoLinkOp> timeLinkOf;
596 /// A time link's one shared endpoint on the link point.
597 DenseMap<Operation *, ObjectFifoDmaEndpointOp> timeLinkEndpoint;
598 /// What each time link gathers while its participants are split: a merge's
599 /// sources, and a dispatch's destination per output.
600 DenseMap<Operation *, SmallVector<Attribute>> mergeSources;
601 DenseMap<Operation *, DenseMap<Operation *, Attribute>> dispatchTurns;
602
603 /// The endpoint a time link's turns share, made by whichever participant
604 /// reaches it first.
605 ObjectFifoDmaEndpointOp timeLinkEndpointFor(ObjectFifoLinkOp link,
606 PoolRef ref,
607 std::optional<int> channel);
608 void createTimeLinkRoutes();
609 LogicalResult createLinkPools();
610};
611
612// A link's pool and its shared merge or dispatch endpoint hold every
613// participant's objects, so they are located at the link and at each fifo.
614static Location linkLocation(ObjectFifoLinkOp link) {
615 SmallVector<Location> locs{link.getLoc()};
616 for (ObjectFifoCreateOp fifo : link.getInputObjectFifos())
617 locs.push_back(fifo.getLoc());
618 for (ObjectFifoCreateOp fifo : link.getOutputObjectFifos())
619 locs.push_back(fifo.getLoc());
620 return FusedLoc::get(link.getContext(), locs);
621}
622
623/// A link's participants meet on one pool on the shared tile: the fifo carrying
624/// whole objects owns its size, and each of the other side's fifos occupies one
625/// segment.
626LogicalResult AIEObjectFifoSplitPass::createLinkPools() {
627 for (auto linkOp : device.getOps<ObjectFifoLinkOp>()) {
628 auto sharedTile = linkOp.getOptionalSharedTile();
629 if (!sharedTile) {
630 continue;
631 }
632
633 std::vector<ObjectFifoCreateOp> ins = linkOp.getInputObjectFifos();
634 std::vector<ObjectFifoCreateOp> outs = linkOp.getOutputObjectFifos();
635 bool isJoin = linkOp.isJoin();
636 bool isDistribute = linkOp.isDistribute();
637
638 ObjectFifoCreateOp owner = linkOp.getPoolOwner();
639 if (linkOp.isMerge()) {
640 for (ObjectFifoCreateOp in : ins)
641 timeLinkOf[in] = linkOp;
642 }
643 if (linkOp.isDispatch()) {
644 for (ObjectFifoCreateOp out : outs)
645 timeLinkOf[out] = linkOp;
646 }
647
648 auto elemType = cast<MemRefType>(
649 cast<AIEObjectFifoType>(owner.getElemType()).getElementType());
650 builder.setInsertionPoint(owner);
651
652 SmallVector<std::pair<int64_t, int64_t>> extents = linkOp.getPoolSegments();
653
654 bool ownerIsOutput = llvm::is_contained(outs, owner);
655 std::string name;
656 int depth;
657 // A walk over the stream reads across the objects of one image, so the
658 // pool holds the image's objects side by side.
659 bool window = linkOp.walksStream();
660 StreamWindow walk;
661 if (window) {
662 if (failed(linkOp.getStreamWindow(walk)))
663 return failure();
664 name = (owner.name().getValue() + "__cons__pool").str();
665 depth = walk.objects;
666 } else if (ownerIsOutput) {
667 name = (owner.name().getValue() + "__pool").str();
668 depth = owner.size();
669 } else {
670 auto consumers = owner.getConsumerTiles();
671 size_t earlier = linkedConsumerEnd.lookup({owner, *sharedTile}).size();
672 int index = -1;
673 for (size_t seen = 0; seen <= earlier;) {
674 assert(static_cast<size_t>(index + 1) < consumers.size() &&
675 "fewer consumer tiles than already-linked pools for this tile");
676 if (consumers[++index] == *sharedTile)
677 seen++;
678 }
679 name =
680 (owner.name().getValue() +
681 (consumers.size() > 1 ? "__cons" + std::to_string(index) + "__pool"
682 : "__cons__pool"))
683 .str();
684 depth = isa<ArrayAttr>(owner.getElemNumber())
685 ? owner.size(index + 1)
686 : objectCountOn(device, *sharedTile, owner);
687 }
688
689 // The pool is shared by every participant, but `owner` is only one of them:
690 // whether its locks are generated must not depend on which fifo that is.
691 auto asksToSkipLocks = [](ObjectFifoCreateOp fifo) {
692 return fifo.getDisableSynchronization();
693 };
694 bool disableSynchronization = llvm::any_of(ins, asksToSkipLocks) ||
695 llvm::any_of(outs, asksToSkipLocks);
696
697 auto pool = createPool(linkLocation(linkOp), name, *sharedTile, depth,
698 elemType, owner, extents,
699 /*holdsInitialContents=*/ownerIsOutput,
700 linkOp.getRepeatCount(), disableSynchronization);
701 if (window) {
702 pool.setContiguous(true);
703 // The padding objects sit in the buffer as zeros, since a MemTile
704 // descriptor walking four dimensions ignores its padding.
705 if (walk.top || walk.bottom)
706 pool.setZeroObjects(SmallVector<int32_t>{
707 static_cast<int32_t>(walk.top), static_cast<int32_t>(walk.bottom)});
708 }
709 linkPoolOwner.insert(owner);
710
711 SmallVector<int32_t> allSegments;
712 for (int32_t index = 0; index < (int32_t)extents.size(); ++index) {
713 allSegments.push_back(index);
714 }
715 for (auto [index, in] : llvm::enumerate(ins)) {
716 linkedConsumerEnd[{in, *sharedTile}].push_back(PoolRef{
717 pool, isJoin ? SmallVector<int32_t>{static_cast<int32_t>(index)}
718 : allSegments});
719 }
720 for (auto [index, out] : llvm::enumerate(outs)) {
721 linkedProducerEnd[out] = PoolRef{
722 pool, isDistribute ? SmallVector<int32_t>{static_cast<int32_t>(index)}
723 : allSegments};
724 }
725 }
726 return success();
727}
728
729ObjectFifoDmaEndpointOp
730AIEObjectFifoSplitPass::timeLinkEndpointFor(ObjectFifoLinkOp link, PoolRef ref,
731 std::optional<int> channel) {
732 ObjectFifoDmaEndpointOp &endpoint = timeLinkEndpoint[link];
733 if (!endpoint) {
734 ObjectFifoCreateOp owner = link.isMerge() ? link.getOutputObjectFifos()[0]
735 : link.getInputObjectFifos()[0];
736 endpoint = createDmaEndpoint(
737 linkLocation(link),
738 (owner.name().getValue() +
739 (link.isMerge() ? "__merge__dma" : "__dispatch__dma"))
740 .str(),
741 ref.pool.getTile(), ref,
742 link.isMerge() ? ObjectFifoRole::Fill : ObjectFifoRole::Drain, owner,
743 BDDimLayoutArrayAttr(), channel);
744 }
745 return endpoint;
746}
747
748/// A merge becomes one fan-in route into its shared endpoint; a dispatch
749/// names its turns on its shared endpoint in the link's output order.
750void AIEObjectFifoSplitPass::createTimeLinkRoutes() {
751 for (auto link : device.getOps<ObjectFifoLinkOp>()) {
752 ObjectFifoDmaEndpointOp endpoint = timeLinkEndpoint.lookup(link);
753 if (!endpoint)
754 continue;
755 MLIRContext *ctx = builder.getContext();
756 if (link.isMerge()) {
757 PacketInfoAttr header = PacketInfoAttr::get(ctx, 0, std::nullopt);
758 for (ObjectFifoCreateOp in : link.getInputObjectFifos())
759 if (PacketInfoAttr pinned = in.packetHeader();
760 pinned && pinned.isAssigned())
761 header = pinned;
762 builder.setInsertionPoint(link);
763 RouteOp::create(builder, link.getLoc(),
764 builder.getArrayAttr(mergeSources[link]),
765 builder.getArrayAttr(
766 {FlatSymbolRefAttr::get(ctx, endpoint.getSymName())}),
767 header);
768 continue;
769 }
770 SmallVector<Attribute> turns;
771 for (ObjectFifoCreateOp out : link.getOutputObjectFifos())
772 turns.push_back(dispatchTurns[link].lookup(out));
773 endpoint.setDispatchAttr(builder.getArrayAttr(turns));
774 }
775}
776
777void AIEObjectFifoSplitPass::runOnOperation() {
778 device = getOperation();
779 builder = OpBuilder(device.getContext());
780 builder.setInsertionPoint(device.getBody()->getTerminator());
781
782 SmallVector<ObjectFifoCreateOp> fifos(device.getOps<ObjectFifoCreateOp>());
783
784 if (failed(verifyTilesArePlaced()) || failed(verifyStreamPortAccesses()) ||
785 failed(verifyLinkAccesses()) ||
786 failed(verifyOutOfOrderForSharedChannels(fifos))) {
787 return signalPassFailure();
788 }
789
790 selectPushQueueForSharedChannels(fifos);
791 if (failed(verifyPushQueueProducers(fifos)))
792 return signalPassFailure();
793
794 if (failed(createLinkPools()) || failed(verifyAllocateDelegates())) {
795 return signalPassFailure();
796 }
797
798 for (ObjectFifoCreateOp fifo : fifos) {
799 builder.setInsertionPoint(fifo);
800 Location loc = fifo.getLoc();
801 StringRef fifoName = fifo.name().getValue();
802 auto fifoType = cast<AIEObjectFifoType>(fifo.getElemType());
803 auto elemType = cast<MemRefType>(fifoType.getElementType());
804 auto consElemType = cast<MemRefType>(
805 cast<AIEObjectFifoType>(fifo.getConsumerElemTypeOrDefault())
806 .getElementType());
807
808 auto sharedModule = AIETargetModel::SharedMemory::None;
809 std::optional<StringRef> needsDMAs = dmaReason(fifo, sharedModule);
810 if (needsDMAs && fifo.forcesSharedMemory()) {
811 fifo.emitOpError("asks for a shared_mem transport, but ") << *needsDMAs;
812 return signalPassFailure();
813 }
814 bool shared = !needsDMAs;
815
816 if (shared) {
817 PoolRef ref;
818 if (auto linked = linkedProducerEnd.find(fifo);
819 linked != linkedProducerEnd.end()) {
820 ref = linked->second;
821 } else if (auto linked =
822 linkedConsumerEnd.find({fifo, fifo.getConsumerTiles()[0]});
823 linked != linkedConsumerEnd.end()) {
824 ref = linked->second.front();
825 } else {
826 Value tile = sharedModule == AIETargetModel::SharedMemory::Second
827 ? fifo.getConsumerTiles()[0]
828 : fifo.getProducerTile();
829 // An aie.objectfifo.allocate names the tile whose memory is to hold the
830 // objects, in place of either end's own.
831 if (auto alloc = getOptionalAllocateOp(fifo)) {
832 if (!delegateReachesBothEnds(fifo, alloc->getDelegateTileOp())) {
833 alloc->emitOpError("objectfifo has no shared memory access to "
834 "delegate tile's memory module");
835 return signalPassFailure();
836 }
837 tile = alloc->getDelegateTile();
838 }
839 // Both ends address the one pool, so it needs the deeper end's depth
840 // even when init_values fill only the producer's share of it.
841 ref = PoolRef{createPool(loc, (fifoName + "__pool").str(), tile,
842 sharedObjectCount(fifo), elemType, fifo,
843 {{0, elemType.getNumElements()}},
844 /*holdsInitialContents=*/true,
845 fifo.getRepeatCount()),
846 {0}};
847 }
848
849 if (hasCoreAccess(device, fifo.getProducerTile(), fifo,
850 ObjectFifoPort::Produce)) {
851 auto name = (fifoName + "__prod").str();
852 createCoreEndpoint(loc, name, fifo.getProducerTile(), ref,
853 ObjectFifoRole::Fill);
854 retargetCoreAccesses(fifo, ObjectFifoPort::Produce,
855 fifo.getProducerTile(), name);
856 }
857 if (hasCoreAccess(device, fifo.getConsumerTiles()[0], fifo,
858 ObjectFifoPort::Consume)) {
859 auto name = (fifoName + "__cons").str();
860 createCoreEndpoint(loc, name, fifo.getConsumerTiles()[0], ref,
861 ObjectFifoRole::Drain);
862 retargetCoreAccesses(fifo, ObjectFifoPort::Consume,
863 fifo.getConsumerTiles()[0], name);
864 }
865 continue;
866 }
867
868 Value prodTile = fifo.getProducerTile();
869 // A fifo end wired straight to a Core stream port has no objects of its
870 // own: whatever the core writes goes out on the port.
871 ObjectFifoEndPortAttr prodPort = fifo.getProdPortAttr();
872 std::optional<int> prodChannel =
873 prodPort ? prodPort.channelIndex() : std::nullopt;
874
875 bool prodIsShim = cast<TileOp>(prodTile.getDefiningOp()).isShimTile();
876 std::optional<PoolRef> prodRef;
877 // Objects entering a repeating link arrive that many at a time.
878 if (auto linked = linkedProducerEnd.find(fifo);
879 linked != linkedProducerEnd.end()) {
880 prodRef = linked->second;
881 } else if (prodIsShim) {
882 if (!fifo.getPlio()) {
883 prodRef = registeredPool(fifo, prodTile, (fifoName + "__pool").str(),
884 elemType);
885 }
886 } else if (!fifo.streamsFromProducer()) {
887 int depth = fifo.getInitValues() ? fifo.size()
888 : objectCountOn(device, prodTile, fifo);
889 auto pool =
890 createPool(loc, (fifoName + "__pool").str(), prodTile, depth,
891 elemType, fifo, {{0, elemType.getNumElements()}},
892 /*holdsInitialContents=*/true, fifo.getRepeatCount());
893 prodRef = PoolRef{pool, {0}};
894 }
895
896 if (prodRef &&
897 hasCoreAccess(device, prodTile, fifo, ObjectFifoPort::Produce)) {
898 auto name = (fifoName + "__prod").str();
899 createCoreEndpoint(loc, name, prodTile, *prodRef, ObjectFifoRole::Fill);
900 retargetCoreAccesses(fifo, ObjectFifoPort::Produce, prodTile, name);
901 }
902
903 std::string prodDmaName = (fifoName + "__prod__dma").str();
904 ObjectFifoLinkOp timeLink = timeLinkOf.lookup(fifo);
905 if (timeLink && timeLink.isDispatch()) {
906 prodDmaName =
907 timeLinkEndpointFor(timeLink, *prodRef, prodChannel).getSymName();
908 } else if (prodRef) {
909 createDmaEndpoint(loc, prodDmaName, prodTile, *prodRef,
910 ObjectFifoRole::Drain, fifo,
911 fifo.getDimensionsToStreamAttr(), prodChannel);
912 } else {
913 createRouteEndpoint(loc, prodDmaName, prodTile,
914 fifo.streamsFromProducer() ? WireBundle::Core
915 : fifo.getPlio() ? WireBundle::PLIO
916 : WireBundle::DMA,
917 prodChannel, fifo);
918 }
919 if (prodIsShim) {
920 shimEndpointName[fifo] = prodDmaName;
921 }
922
923 SmallVector<Attribute> destinations;
924 ArrayRef<BDDimLayoutArrayAttr> consumerDims =
925 fifo.getDimensionsFromStreamPerConsumer();
926 int consumerIndex = 0;
927 DenseMap<Value, unsigned> linkedOn;
928 for (Value consumerTile : fifo.getConsumerTiles()) {
929 std::string suffix = fifo.getConsumerTiles().size() > 1
930 ? ("__cons" + std::to_string(consumerIndex))
931 : "__cons";
932 bool consIsShim = cast<TileOp>(consumerTile.getDefiningOp()).isShimTile();
933
934 std::optional<PoolRef> consRef;
935 if (auto linked = linkedConsumerEnd.find({fifo, consumerTile});
936 linked != linkedConsumerEnd.end() &&
937 linkedOn[consumerTile] < linked->second.size()) {
938 consRef = linked->second[linkedOn[consumerTile]++];
939 } else if (consIsShim) {
940 if (!fifo.getPlio()) {
941 consRef = registeredPool(fifo, consumerTile,
942 (fifoName + suffix + "__pool").str(),
943 consElemType);
944 }
945 } else if (!fifo.streamsToConsumer(consumerIndex)) {
946 int depth = isa<ArrayAttr>(fifo.getElemNumber())
947 ? fifo.size(consumerIndex + 1)
948 : objectCountOn(device, consumerTile, fifo);
949 auto pool = createPool(loc, (fifoName + suffix + "__pool").str(),
950 consumerTile, depth, consElemType, fifo,
951 {{0, consElemType.getNumElements()}},
952 /*holdsInitialContents=*/false,
953 /*repeatCount=*/std::nullopt);
954 consRef = PoolRef{pool, {0}};
955 }
956
957 if (consRef &&
958 hasCoreAccess(device, consumerTile, fifo, ObjectFifoPort::Consume)) {
959 auto name = (fifoName + suffix).str();
960 createCoreEndpoint(loc, name, consumerTile, *consRef,
961 ObjectFifoRole::Drain);
962 retargetCoreAccesses(fifo, ObjectFifoPort::Consume, consumerTile, name);
963 }
964
965 ObjectFifoEndPortAttr consPort = fifo.getConsumerPort(consumerIndex);
966 std::optional<int> consChannel =
967 consPort ? consPort.channelIndex() : std::nullopt;
968
969 std::string consDmaName = (fifoName + suffix + "__dma").str();
970 if (timeLink && timeLink.isMerge()) {
971 consDmaName =
972 timeLinkEndpointFor(timeLink, *consRef, consChannel).getSymName();
973 } else if (consRef) {
974 createDmaEndpoint(loc, consDmaName, consumerTile, *consRef,
975 ObjectFifoRole::Fill, fifo,
976 consumerDims.empty() ? BDDimLayoutArrayAttr()
977 : consumerDims[consumerIndex],
978 consChannel);
979 } else {
980 createRouteEndpoint(loc, consDmaName, consumerTile,
981 fifo.streamsToConsumer(consumerIndex)
982 ? WireBundle::Core
983 : fifo.getPlio() ? WireBundle::PLIO
984 : WireBundle::DMA,
985 consChannel, fifo);
986 }
987 destinations.push_back(
988 FlatSymbolRefAttr::get(builder.getContext(), consDmaName));
989 if (consIsShim) {
990 shimEndpointName[fifo] = consDmaName;
991 }
992 consumerIndex++;
993 }
994
995 FlatSymbolRefAttr source =
996 FlatSymbolRefAttr::get(builder.getContext(), prodDmaName);
997 if (timeLink && timeLink.isMerge()) {
998 mergeSources[timeLink].push_back(source);
999 continue;
1000 }
1001 // A dispatch tells its turns apart by header, so each of its routes
1002 // carries one.
1003 PacketInfoAttr header = fifo.packetHeader();
1004 if (timeLink && timeLink.isDispatch()) {
1005 dispatchTurns[timeLink][fifo] = destinations.front();
1006 if (!header)
1007 header = PacketInfoAttr::get(builder.getContext(), 0, std::nullopt);
1008 }
1009 RouteOp::create(builder, loc, builder.getArrayAttr({source}),
1010 builder.getArrayAttr(destinations), header);
1011 }
1012
1013 createTimeLinkRoutes();
1014
1015 SmallVector<Operation *> toErase;
1016 for (ObjectFifoCreateOp fifo : fifos) {
1017 // The runtime sequence drives a fifo through its shim end.
1018 auto shim = shimEndpointName.find(fifo);
1019 if (shim != shimEndpointName.end()) {
1020 (void)SymbolTable::replaceAllSymbolUses(
1021 fifo, builder.getStringAttr(shim->second), device);
1022 }
1023 toErase.push_back(fifo);
1024 }
1025 for (auto linkOp : device.getOps<ObjectFifoLinkOp>()) {
1026 toErase.push_back(linkOp);
1027 }
1028 for (auto allocOp : device.getOps<ObjectFifoAllocateOp>()) {
1029 toErase.push_back(allocOp);
1030 }
1031 for (auto regOp : device.getOps<ObjectFifoRegisterExternalBuffersOp>()) {
1032 toErase.push_back(regOp);
1033 }
1034 for (Operation *op : toErase) {
1035 op->erase();
1036 }
1037}
1038
1039} // namespace
1040
1041std::unique_ptr<OperationPass<DeviceOp>>
1043 return std::make_unique<AIEObjectFifoSplitPass>();
1044}
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()