MLIR-AIE 1.4.5.dev
AIEObjectFifoLowerDMAs.cpp
Go to the documentation of this file.
1//===- AIEObjectFifoLowerDMAs.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
14using namespace mlir;
15using namespace xilinx;
16using namespace xilinx::AIE;
17
18namespace xilinx::AIE {
19#define GEN_PASS_DEF_AIEOBJECTFIFOLOWERDMAS
20#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
21} // namespace xilinx::AIE
22
23namespace {
24
25/// One buffer descriptor's worth of work: which object, which slice of it, and
26/// the locks guarding that slice.
27struct Descriptor {
28 Value buffer;
29 int64_t offset;
30 int64_t size;
31 BDDimLayoutArrayAttr dimensions;
32 BDPadLayoutArrayAttr padding;
33 FlatSymbolRefAttr acquireLock;
34 FlatSymbolRefAttr releaseLock;
35 /// Which of the pool's objects this moves; also the index of the object's
36 /// own binary lock, where the device uses those.
37 int64_t object;
38 /// Lock units taken and given back, where they are not one object's.
39 std::optional<int64_t> acquireValue = std::nullopt;
40 std::optional<int64_t> releaseValue = std::nullopt;
41 /// How the address steps on each run of a descriptor that walks a
42 /// contiguous pool's objects in turn.
43 BDIterationAttr iteration = nullptr;
44};
45
46Block *findEndOpBlock(Region &region) {
47 Block *endBlock = nullptr;
48 for (Block &block : region) {
49 if (!block.getOps<EndOp>().empty()) {
50 endBlock = &block;
51 }
52 }
53 return endBlock;
54}
55
56struct AIEObjectFifoLowerDMAsPass
57 : public xilinx::AIE::impl::AIEObjectFifoLowerDMAsBase<
58 AIEObjectFifoLowerDMAsPass> {
59
60 DeviceOp device;
61 OpBuilder builder{static_cast<MLIRContext *>(nullptr)};
62
63 /// The DMA program of `tile`, created empty if the tile has none yet.
64 DmaBody dmaProgramFor(TileLike tile, Location loc) {
65 Value wanted = tile->getResult(0);
66 for (auto program : device.getOps<DmaBody>()) {
67 if (program.getTile() == wanted) {
68 return program;
69 }
70 }
71
72 OpBuilder::InsertionGuard g(builder);
73 builder.setInsertionPoint(device.getBody()->getTerminator());
74 DmaBody program;
75 if (tile.isShimTile()) {
76 program = ShimDMAOp::create(builder, loc, builder.getIndexType(), wanted);
77 } else if (tile.isMemTile()) {
78 program = MemTileDMAOp::create(builder, loc, wanted);
79 } else {
80 program = MemOp::create(builder, loc, wanted);
81 }
82
83 builder.setInsertionPointToStart(&program.getDmaBody().emplaceBlock());
84 EndOp::create(builder, loc);
85 return program;
86 }
87
88 /// Objects the endpoint moves.
89 SmallVector<Value> buffersOf(ObjectFifoPoolOp pool) {
90 SmallVector<Value> buffers;
91 for (BufferLike buffer : pool.getBufferOps()) {
92 buffers.push_back(buffer.getBuffer());
93 }
94 return buffers;
95 }
96
97 /// A contiguous pool's one buffer holds its objects side by side. Its
98 /// filling end steps through them with one descriptor; its draining end
99 /// walks the stream with one descriptor per output, each taking the objects
100 /// up to the newest it reads and giving back those no later one reads.
101 SmallVector<Descriptor>
102 contiguousDescriptors(ObjectFifoDmaEndpointOp endpoint, Value buffer,
103 bool drains) {
104 ObjectFifoPoolOp pool = endpoint.getPoolOp();
105 ObjectFifoSegmentOp segment = endpoint.getSelectedSegments().front();
106 FlatSymbolRefAttr take =
107 drains ? segment.getConsumeLockAttr() : segment.getProduceLockAttr();
108 FlatSymbolRefAttr give =
109 drains ? segment.getProduceLockAttr() : segment.getConsumeLockAttr();
110 MLIRContext *context = builder.getContext();
111 if (!endpoint.walksStream()) {
112 BDDimLayoutArrayAttr dims;
113 if (auto all = endpoint.getDimensions(); all && !all->empty())
114 dims = (*all)[0];
115 int64_t object = pool.getObjectSize();
116 int64_t first = 0;
117 if (auto zeros = pool.getZeroObjects())
118 first = (*zeros)[0] * object;
119 int64_t walkers = endpoint.getNumDescriptors();
120 SmallVector<Descriptor> descriptors;
121 for (int64_t j = 0; j < walkers; j++)
122 descriptors.push_back(
123 {buffer, first + j * object + segment.getOffset(),
124 segment.getSize(), dims, BDPadLayoutArrayAttr(), take, give, j,
125 std::nullopt, std::nullopt,
126 BDIterationAttr::get(context, pool.getDepth() / walkers,
127 walkers * object, 0)});
128 return descriptors;
129 }
130
131 StreamWindow window;
132 if (failed(endpoint.getStreamWindow(window)))
133 return {};
134 BDDimLayoutArrayAttr dims =
135 BDDimLayoutArrayAttr::get(context, window.inner);
136 SmallVector<Descriptor> descriptors;
137 for (StreamWindowStep step : streamWindowSteps(window))
138 descriptors.push_back({buffer, step.first * window.objectSize,
139 window.transfer(), dims, BDPadLayoutArrayAttr(),
140 take, give, 0, step.acquire, step.release,
141 nullptr});
142 return descriptors;
143 }
144
145 /// Buffer-major, segment-minor: the DMA walks the objects in turn, and within
146 /// each object the slices this endpoint is responsible for.
147 SmallVector<Descriptor> descriptorsFor(ObjectFifoDmaEndpointOp endpoint,
148 ArrayRef<Value> buffers, bool drains) {
149 if (endpoint.getPoolOp().getContiguous())
150 return contiguousDescriptors(endpoint, buffers.front(), drains);
151 std::vector<ObjectFifoSegmentOp> segments = endpoint.getSelectedSegments();
152 SmallVector<BDDimLayoutArrayAttr> dimensions;
153 if (auto allDimensions = endpoint.getDimensions()) {
154 llvm::append_range(dimensions, *allDimensions);
155 }
156 SmallVector<BDPadLayoutArrayAttr> padding;
157 if (auto allPadding = endpoint.getPadDimensions()) {
158 llvm::append_range(padding, *allPadding);
159 }
160
161 SmallVector<Descriptor> descriptors;
162 for (auto [index, buffer] : llvm::enumerate(buffers)) {
163 for (auto [position, segment] : llvm::enumerate(segments)) {
164 descriptors.push_back(
165 {buffer, segment.getOffset(), segment.getSize(),
166 position < dimensions.size() ? dimensions[position]
167 : BDDimLayoutArrayAttr(),
168 position < padding.size() ? padding[position]
169 : BDPadLayoutArrayAttr(),
170 drains ? segment.getConsumeLockAttr()
171 : segment.getProduceLockAttr(),
172 drains ? segment.getProduceLockAttr()
173 : segment.getConsumeLockAttr(),
174 static_cast<int64_t>(index)});
175 }
176 }
177 return descriptors;
178 }
179
180 void emitDescriptor(ObjectFifoDmaEndpointOp endpoint, ObjectFifoPoolOp pool,
181 Descriptor &descriptor, PacketInfoAttr packet,
182 Block *successor, bool binaryLocks, bool drains) {
183 Location loc = endpoint.getLoc();
184 int count = drains ? 1 : pool.getRepeatCount().value_or(1);
185
186 LockOp acquireLock, releaseLock;
187 int acquireValue = count, releaseValue = count;
188 auto action = LockAction::AcquireGreaterEqual;
189 if (binaryLocks) {
190 // A binary lock's value encodes which side owns the buffer.
191 SmallVector<LockOp> locks = pool.getLockOps();
192 if (descriptor.object < (int64_t)locks.size()) {
193 acquireLock = releaseLock = locks[descriptor.object];
194 acquireValue = drains ? 1 : 0;
195 releaseValue = drains ? 0 : 1;
196 action = LockAction::Acquire;
197 }
198 } else if (descriptor.acquireLock) {
199 acquireLock =
200 lookupNamedOp<LockOp>(device, descriptor.acquireLock.getAttr());
201 releaseLock =
202 lookupNamedOp<LockOp>(device, descriptor.releaseLock.getAttr());
203 }
204
205 if (descriptor.acquireValue)
206 acquireValue = *descriptor.acquireValue;
207 if (descriptor.releaseValue)
208 releaseValue = *descriptor.releaseValue;
209 if (acquireLock) {
210 UseLockOp::create(builder, loc, acquireLock, action, acquireValue);
211 }
212 if (packet) {
213 DMABDPACKETOp::create(builder, loc, packet.getPktType(),
214 packet.assignedId());
215 }
216
217 DMABDOp bd;
218 if (descriptor.dimensions && drains && descriptor.padding) {
219 bd = DMABDOp::create(builder, loc, descriptor.buffer, descriptor.offset,
220 paddedSize(descriptor.dimensions, descriptor.padding,
221 descriptor.size),
222 descriptor.dimensions, descriptor.padding);
223 } else if (descriptor.dimensions) {
224 bd = DMABDOp::create(builder, loc, descriptor.buffer, descriptor.offset,
225 descriptor.size, descriptor.dimensions);
226 } else {
227 bd = DMABDOp::create(builder, loc, descriptor.buffer, descriptor.offset,
228 descriptor.size);
229 }
230 if (descriptor.iteration && descriptor.iteration.getSize() > 1)
231 bd.setIterationAttr(descriptor.iteration);
232
233 if (releaseLock) {
234 UseLockOp::create(builder, loc, releaseLock, LockAction::Release,
235 releaseValue);
236 }
237 NextBDOp::create(builder, loc, successor);
238 }
239
240 /// A `dma_start` of `channel` on the endpoint's tile, chained after the
241 /// tile's others; returns the first BD's block and the program's end block.
242 std::pair<Block *, Block *> startChain(ObjectFifoDmaEndpointOp endpoint,
243 int channel, int taskCount) {
244 Location loc = endpoint.getLoc();
245 DmaBody program = dmaProgramFor(endpoint.getTileLike(), loc);
246 Block *endBlock = findEndOpBlock(program.getDmaBody());
247 Block *lastDmaBlock = endBlock->getSinglePredecessor();
248 Block *dmaBlock = builder.createBlock(endBlock);
249 Block *bdBlock = builder.createBlock(endBlock);
250
251 builder.setInsertionPointToStart(dmaBlock);
252 DMAStartOp::create(builder, loc, endpoint.getRouteDirection(), channel,
253 taskCount, endpoint.getPadValue(),
254 /*out_of_order=*/false, bdBlock, endBlock);
255 if (lastDmaBlock) {
256 lastDmaBlock->getTerminator()->setSuccessor(dmaBlock, 1);
257 }
258 return {bdBlock, endBlock};
259 }
260
261 /// Endpoints taking turns on one channel share one chain: the k-th BD moves
262 /// the next object of the endpoint `turns` names k-th, through its own pool.
263 LogicalResult lowerTurns(ObjectFifoDmaEndpointOp first, int channel) {
264 SmallVector<ObjectFifoDmaEndpointOp> ends = turnEndpoints(first);
265 SmallVector<SmallVector<Descriptor>> descriptors;
266 for (ObjectFifoDmaEndpointOp end : ends) {
267 descriptors.push_back(
268 descriptorsFor(end, buffersOf(end.getPoolOp()), end.drains()));
269 if (static_cast<int64_t>(descriptors.back().size()) !=
270 end.getNumDescriptors() ||
271 descriptors.back().empty())
272 return end.emitOpError("takes turns, but lowers to ")
273 << descriptors.back().size() << " descriptors per pass for "
274 << end.getNumDescriptors();
275 }
276 SmallVector<unsigned> turns;
277 for (auto name : first.getTurnsAttr().getAsRange<FlatSymbolRefAttr>())
278 turns.push_back(llvm::find_if(ends,
279 [&](ObjectFifoDmaEndpointOp end) {
280 return end.getSymNameAttr() ==
281 name.getAttr();
282 }) -
283 ends.begin());
284
285 auto [bdBlock, endBlock] = startChain(first, channel, /*taskCount=*/0);
286 bool binaryLocks = !device.getTargetModel().hasProperty(
288 SmallVector<size_t> next(ends.size(), 0);
289 int64_t total = first.getNumBDs();
290 Block *current = bdBlock;
291 for (int64_t k = 0; k < total; k++) {
292 unsigned i = turns[k % turns.size()];
293 Block *successor =
294 k + 1 < total ? builder.createBlock(endBlock) : bdBlock;
295 builder.setInsertionPointToStart(current);
296 emitDescriptor(ends[i], ends[i].getPoolOp(), descriptors[i][next[i]],
297 PacketInfoAttr(), successor, binaryLocks,
298 ends[i].drains());
299 next[i] = (next[i] + 1) % descriptors[i].size();
300 current = successor;
301 }
302 return success();
303 }
304
305 LogicalResult lowerEndpoint(ObjectFifoDmaEndpointOp endpoint, int channel) {
306 bool drains = endpoint.drains();
307 ObjectFifoPoolOp pool = endpoint.getPoolOp();
308 SmallVector<Value> buffers = buffersOf(pool);
309 if (buffers.empty()) {
310 return success();
311 }
312
313 SmallVector<Descriptor> descriptors =
314 descriptorsFor(endpoint, buffers, drains);
315 if (descriptors.empty()) {
316 return success();
317 }
318 // Allocation budgeted the channel's BDs by getNumBDs.
319 if (static_cast<int64_t>(descriptors.size()) !=
320 endpoint.getNumDescriptors())
321 return endpoint.emitOpError()
322 << "lowers to " << descriptors.size()
323 << " descriptors per pass, but its channel was allocated for "
324 << endpoint.getNumDescriptors();
325
326 Location loc = endpoint.getLoc();
327 // emitDescriptor scales the filling end's lock count instead.
328 int64_t repeat = endpoint.getRepeat();
329 std::optional<int32_t> iterCount = endpoint.getIterCount();
330
331 bool repeatInHardware = endpoint.repeatsInHardware();
332 // A dispatching chain runs until its turns line up with the pool's
333 // objects again, so one run covers that many passes.
334 int64_t passes = endpoint.getDispatchPasses();
335 int taskCount = iterCount ? *iterCount / passes - 1
336 : (repeatInHardware ? repeat - 1 : 0);
337 int64_t copies = repeatInHardware ? 1 : repeat;
338 SmallVector<PacketInfoAttr> turns;
339 if (std::optional<ArrayAttr> headers = endpoint.getDispatchPackets()) {
340 llvm::append_range(turns, headers->getAsRange<PacketInfoAttr>());
341 } else if (endpoint.getDispatch()) {
342 return endpoint.emitOpError(
343 "has no dispatchPackets; run --aie-objectfifo-allocate");
344 }
345 // A contiguous pool's descriptor steps through every object, so it has no
346 // one object for a turn to follow.
347 if (!turns.empty() && pool.getContiguous())
348 return endpoint.emitOpError(
349 "dispatches from a contiguous pool, which has no per-object BDs");
350 int64_t depth = pool.getDepth();
351
352 auto [bdBlock, endBlock] = startChain(endpoint, channel, taskCount);
353 bool binaryLocks = !device.getTargetModel().hasProperty(
355 size_t total = descriptors.size() * copies * passes;
356 size_t emitted = 0;
357 Block *current = bdBlock;
358 for (int64_t pass = 0; pass < passes; pass++) {
359 for (Descriptor &descriptor : descriptors) {
360 PacketInfoAttr packet = endpoint.getPacketAttr();
361 if (!turns.empty())
362 packet = turns[(pass * depth + descriptor.object) % turns.size()];
363 for (int64_t copy = 0; copy < copies; copy++) {
364 Block *successor;
365 if (emitted + 1 < total) {
366 successor = builder.createBlock(endBlock);
367 } else if (iterCount) {
368 // A bounded chain exits after its final iteration.
369 successor = builder.createBlock(endBlock);
370 builder.setInsertionPointToStart(successor);
371 EndOp::create(builder, loc);
372 } else {
373 successor = bdBlock;
374 }
375 builder.setInsertionPointToStart(current);
376 emitDescriptor(endpoint, pool, descriptor, packet, successor,
377 binaryLocks, drains);
378 current = successor;
379 emitted++;
380 }
381 }
382 }
383 return success();
384 }
385
386 void runOnOperation() override {
387 device = getOperation();
388 builder = OpBuilder(device.getContext());
389
390 SmallVector<ObjectFifoDmaEndpointOp> endpoints(
391 device.getOps<ObjectFifoDmaEndpointOp>());
392 // A tile's chains, and so the BD ids they get, follow its channels rather
393 // than the order the endpoints were written in.
394 DenseMap<Value, size_t> tileOrder;
395 for (ObjectFifoDmaEndpointOp endpoint : endpoints)
396 tileOrder.try_emplace(endpoint.getTile(), tileOrder.size());
397 llvm::stable_sort(
398 endpoints, [&](ObjectFifoDmaEndpointOp a, ObjectFifoDmaEndpointOp b) {
399 return std::make_tuple(tileOrder.lookup(a.getTile()),
400 a.getRouteDirection(),
401 a.getChannelIndex().value_or(-1)) <
402 std::make_tuple(tileOrder.lookup(b.getTile()),
403 b.getRouteDirection(),
404 b.getChannelIndex().value_or(-1));
405 });
406 for (ObjectFifoDmaEndpointOp endpoint : endpoints) {
407 std::optional<int> channel = endpoint.getChannelIndex();
408 if (!channel) {
409 endpoint.emitOpError("has no channel; run --aie-objectfifo-allocate");
410 return signalPassFailure();
411 }
412 SmallVector<ObjectFifoDmaEndpointOp> turns = turnEndpoints(endpoint);
413 if (turns.empty() ? failed(lowerEndpoint(endpoint, *channel))
414 : turns.front() == endpoint &&
415 failed(lowerTurns(endpoint, *channel)))
416 return signalPassFailure();
417 }
418 for (ObjectFifoDmaEndpointOp endpoint : endpoints)
419 endpoint.erase();
420
421 // Nothing to program at the far end of these; the routes they asked for
422 // have been drawn.
423 for (auto endpoint :
424 llvm::make_early_inc_range(device.getOps<RouteEndpointOp>())) {
425 endpoint.erase();
426 }
427 }
428};
429
430} // namespace
431
432std::unique_ptr<OperationPass<DeviceOp>>
434 return std::make_unique<AIEObjectFifoLowerDMAsPass>();
435}
llvm::SmallVector< ObjectFifoDmaEndpointOp > turnEndpoints(ObjectFifoDmaEndpointOp end)
llvm::SmallVector< StreamWindowStep > streamWindowSteps(const StreamWindow &w)
mlir::Operation * lookupNamedOp(mlir::Operation *from, mlir::StringAttr name)
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEObjectFifoLowerDMAsPass()
int64_t paddedSize(llvm::ArrayRef< BDDimLayoutAttr > dims, llvm::ArrayRef< BDPadLayoutAttr > pads, int64_t fallback)
llvm::SmallVector< BDDimLayoutAttr > inner
Definition AIEDialect.h:410