MLIR-AIE 1.4.6.dev
AIEObjectFifoInsertCoreQueuePushes.cpp
Go to the documentation of this file.
1//===- AIEObjectFifoInsertCoreQueuePushes.cpp -------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7//
8// Replenish S2MM task queues as cores release received objects.
9
12
13#include "mlir/Dialect/Arith/IR/Arith.h"
14#include "mlir/Dialect/MemRef/IR/MemRef.h"
15#include "mlir/Dialect/SCF/IR/SCF.h"
16#include "mlir/IR/Builders.h"
17#include "mlir/Pass/Pass.h"
18
19namespace xilinx::AIE {
20#define GEN_PASS_DEF_AIEOBJECTFIFOINSERTCOREQUEUEPUSHES
21#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
22} // namespace xilinx::AIE
23
24using namespace mlir;
25using namespace xilinx;
26using namespace xilinx::AIE;
27
28namespace {
29
30struct AIEObjectFifoInsertCoreQueuePushesPass
31 : public xilinx::AIE::impl::AIEObjectFifoInsertCoreQueuePushesBase<
32 AIEObjectFifoInsertCoreQueuePushesPass> {
33 using Base = xilinx::AIE::impl::AIEObjectFifoInsertCoreQueuePushesBase<
34 AIEObjectFifoInsertCoreQueuePushesPass>;
35 using Base::Base;
36
37 /// A counter in the core's own memory, set to `initial`.
38 Value makeCounter(CoreOp core, int32_t initial) {
39 OpBuilder builder(core.getContext());
40 Block &entry = core.getBody().front();
41 builder.setInsertionPointToStart(&entry);
42 Location loc = core.getLoc();
43 Type i32 = builder.getI32Type();
44 Value slot =
45 memref::AllocaOp::create(builder, loc, MemRefType::get({}, i32));
46 Value start = arith::ConstantOp::create(builder, loc, i32,
47 builder.getI32IntegerAttr(initial));
48 memref::StoreOp::create(builder, loc, start, slot, ValueRange{});
49 return slot;
50 }
51
52 /// Count `released` objects against `counter`, and push once the count runs
53 /// out. `perPush` objects make up one push, whose task repeats `repeat`
54 /// times over the descriptor each object names.
55 void emitCountAndPush(OpBuilder &builder, Location loc, Value counter,
56 int32_t released, int32_t perPush, int32_t repeat,
57 int channel, bool assumeQueueSpace) {
58 Type i32 = builder.getI32Type();
59 Value left = memref::LoadOp::create(builder, loc, counter, ValueRange{});
60 Value step = arith::ConstantOp::create(builder, loc, i32,
61 builder.getI32IntegerAttr(released));
62 Value zero = arith::ConstantOp::create(builder, loc, i32,
63 builder.getI32IntegerAttr(0));
64 Value run = arith::ConstantOp::create(builder, loc, i32,
65 builder.getI32IntegerAttr(perPush));
66 Value rest = arith::SubIOp::create(builder, loc, left, step);
67 Value spent = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::sle,
68 rest, zero);
69 auto push =
70 scf::IfOp::create(builder, loc, spent, /*withElseRegion=*/false);
71 {
72 OpBuilder::InsertionGuard guard(builder);
73 builder.setInsertionPointToStart(push.thenBlock());
74 // A push onto a full queue is discarded, which strands whoever waits on
75 // the objects it would have covered.
76 if (checkQueueSpace && !assumeQueueSpace)
77 DmaPollForQueueSpaceOp::create(builder, loc, DMAChannelDir::S2MM,
78 channel);
79 // Out-of-order ignores the descriptor a push names.
80 DmaPushQueueOp::create(builder, loc, DMAChannelDir::S2MM, channel,
81 builder.getI32IntegerAttr(0), /*bd_tag=*/nullptr,
82 repeat, /*out_of_order=*/true);
83 }
84 // Carry what the run overshot by, so the pushes stay in step with the
85 // objects however many a release covers.
86 Value renewed = arith::AddIOp::create(builder, loc, rest, run);
87 Value next = arith::SelectOp::create(builder, loc, spent, renewed, rest);
88 memref::StoreOp::create(builder, loc, next, counter, ValueRange{});
89 }
90
91 void runOnOperation() override {
92 DeviceOp device = getOperation();
93 const AIETargetModel &targetModel = device.getTargetModel();
94
95 using ChannelKey = std::tuple<int, int, int>;
96 auto channelKey = [](ObjectFifoDmaEndpointOp endpoint) -> ChannelKey {
97 auto tile = endpoint.getTile().getDefiningOp<TileOp>();
98 return {tile.getCol(), tile.getRow(),
99 endpoint.getChannelIndex().value_or(-1)};
100 };
101 DenseSet<ChannelKey> unboundedChannels;
102 for (auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>())
103 if (endpoint.getOutOfOrder() && !endpoint.drains() &&
104 !endpoint.getIterCount())
105 unboundedChannels.insert(channelKey(endpoint));
106
107 // Credits belong to the channel, including bounded FIFOs sharing it with
108 // an unbounded FIFO. Every such pool needs a local core release to count.
109 DenseMap<StringRef, ObjectFifoDmaEndpointOp> receiverOfPool;
110 for (auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>())
111 if (endpoint.getOutOfOrder() && !endpoint.drains() &&
112 unboundedChannels.contains(channelKey(endpoint)))
113 receiverOfPool[endpoint.getPool()] = endpoint;
114 if (receiverOfPool.empty())
115 return;
116 DenseSet<StringRef> replenishedPools;
117
118 int32_t repeat = static_cast<int32_t>(targetModel.getMaxRepeatCount());
119 int32_t perPush = repeat + 1;
120
121 for (CoreOp core : device.getOps<CoreOp>()) {
122 // Each channel takes one count, since every object the core frees on it
123 // belongs to the same queue however many fifos share the channel.
124 DenseMap<int, SmallVector<std::pair<ObjectFifoReleaseOp, int32_t>>>
125 releasesOfChannel;
126 WalkResult walked = core.walk([&](ObjectFifoReleaseOp release) {
127 auto endpoint =
128 SymbolTable::lookupNearestSymbolFrom<ObjectFifoCoreEndpointOp>(
129 device, release.getObjFifoNameAttr());
130 if (!endpoint || !endpoint.drains())
131 return WalkResult::advance();
132 auto it = receiverOfPool.find(endpoint.getPool());
133 if (it == receiverOfPool.end())
134 return WalkResult::advance();
135 std::optional<int32_t> channel = it->second.getChannelIndex();
136 if (!channel) {
137 it->second.emitOpError("receives out of order without a channel; "
138 "run --aie-objectfifo-allocate first");
139 return WalkResult::interrupt();
140 }
141 auto receiverTile = it->second.getTile().getDefiningOp<TileOp>();
142 auto coreTile = core.getTileOp();
143 if (receiverTile.getCol() != coreTile.getCol() ||
144 receiverTile.getRow() != coreTile.getRow())
145 return WalkResult::advance();
146 replenishedPools.insert(endpoint.getPool());
147 releasesOfChannel[*channel].emplace_back(release, release.relNumber());
148 return WalkResult::advance();
149 });
150 if (walked.wasInterrupted())
151 return signalPassFailure();
152 if (releasesOfChannel.empty())
153 continue;
154
155 for (auto &[channel, releases] : releasesOfChannel) {
156 // The channel holds a full run of objects before the core frees any,
157 // so the count starts at one: the first release pushes the run that
158 // follows the one the configuration pushed.
159 Value counter = makeCounter(core, 1);
160 for (auto [release, size] : releases) {
161 OpBuilder builder(release.getContext());
162 builder.setInsertionPointAfter(release);
163 emitCountAndPush(builder, release.getLoc(), counter, size, perPush,
164 repeat, channel, release.getAssumeQueueSpace());
165 }
166 }
167 }
168 for (auto &[pool, receiver] : receiverOfPool) {
169 if (!replenishedPools.contains(pool)) {
170 receiver.emitOpError(
171 "unbounded out-of-order receiving channel requires a local core "
172 "consumer release to replenish it; specify iter_count on every "
173 "FIFO sharing this channel");
174 return signalPassFailure();
175 }
176 }
177 }
178};
179
180} // namespace
181
182std::unique_ptr<OperationPass<DeviceOp>>
184 return std::make_unique<AIEObjectFifoInsertCoreQueuePushesPass>();
185}
186
187std::unique_ptr<OperationPass<DeviceOp>>
189 const AIEObjectFifoInsertCoreQueuePushesOptions &options) {
190 return std::make_unique<AIEObjectFifoInsertCoreQueuePushesPass>(options);
191}
virtual uint32_t getMaxRepeatCount() const =0
Return the largest DMA task repeat count (unsupported = 0).
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEObjectFifoInsertCoreQueuePushesPass()