MLIR-AIE 1.4.6.dev
AIEAssignBufferDescriptorIDs.cpp
Go to the documentation of this file.
1//===- AIEAssignBufferDescriptorIDs.cpp -------------------------*- C++ -*-===//
2//
3// Copyright (C) 2024 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
13
14#include "mlir/IR/Block.h"
15#include "mlir/IR/Builders.h"
16#include "mlir/IR/Region.h"
17#include "mlir/IR/ValueRange.h"
18#include "mlir/IR/Visitors.h"
19#include "mlir/Pass/Pass.h"
20#include "mlir/Support/LLVM.h"
21
22#include "llvm/ADT/STLExtras.h"
23#include "llvm/ADT/SmallVector.h"
24
25#include <cassert>
26#include <cstdint>
27#include <map>
28#include <memory>
29#include <optional>
30#include <set>
31#include <utility>
32
33namespace xilinx::AIE {
34#define GEN_PASS_DEF_AIEASSIGNBUFFERDESCRIPTORIDS
35#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
36} // namespace xilinx::AIE
37
38#define DEBUG_TYPE "aie-assign-bd-ids"
39
40using namespace mlir;
41using namespace xilinx;
42using namespace xilinx::AIE;
43
47
48std::optional<uint32_t> BdIdGenerator::nextBdId(int channelIndex,
49 uint32_t alignment) {
50 uint32_t bdId = 0;
51 uint32_t maxBdId = targetModel.getNumBDs(col, row) - 1;
52 // Find the next free BD ID. This is not an efficient algorithm, but doesn't
53 for (; (bdIdAlreadyAssigned(bdId) ||
54 !targetModel.isBdChannelAccessible(col, row, bdId, channelIndex)) &&
55 bdId <= maxBdId;
56 bdId += alignment)
57 ;
58 if (bdId > maxBdId) {
59 return std::nullopt;
60 }
61 assignBdId(bdId);
62 return std::optional<uint32_t>(bdId);
63}
64
65void BdIdGenerator::assignBdId(uint32_t bdId) {
66 assert(!alreadyAssigned.count(bdId) && "bdId has already been assigned");
67 alreadyAssigned.insert(bdId);
68}
69
71 return alreadyAssigned.count(bdId);
72}
73
74void BdIdGenerator::freeBdId(uint32_t bdId) { alreadyAssigned.erase(bdId); }
75
76// Read the head buffer-descriptor id and (biased) START_QUEUE repeat count of
77// the resident DMA channel (dir, channel) on `tile`, off the placed
78// aie.mem / aie.memtile_dma chain, exactly as the load-time start-queue push
79// does. Returns false if there is no such resident channel or its head BD id is
80// not yet assigned. Only the block form (aie.dma_start) is scanned; the
81// objectFIFO transform always emits that form.
82static bool resolveResidentChannel(DeviceOp device, TileOp tile,
83 DMAChannelDir dir, int channel,
84 uint32_t &headBdId, int32_t &repeatCount) {
85 auto scanRegion = [&](Region &region) -> bool {
86 for (Block &block : region)
87 for (DMAStartOp startOp : block.getOps<DMAStartOp>()) {
88 if (startOp.getChannelDir() != dir ||
89 startOp.getChannelIndex() != channel)
90 continue;
91 Block *dest = startOp.getDest();
92 if (!dest)
93 return false;
94 auto bds = dest->getOps<DMABDOp>();
95 if (bds.empty())
96 return false;
97 DMABDOp head = *bds.begin();
98 if (!head.getBdId().has_value())
99 return false;
100 headBdId = head.getBdId().value();
101 repeatCount = startOp.getRepeatCount();
102 return true;
103 }
104 return false;
105 };
106 for (MemOp mem : device.getOps<MemOp>())
107 if (mem.getTile() == tile.getResult())
108 return scanRegion(mem->getRegion(0));
109 for (MemTileDMAOp mem : device.getOps<MemTileDMAOp>())
110 if (mem.getTile() == tile.getResult())
111 return scanRegion(mem->getRegion(0));
112 return false;
113}
114
115// Populate each objectFIFO re-arm binding's head_bd_ids / repeat_counts now
116// that BD ids are assigned, so the later aiex.dma_channel_reset_for lowering
117// reads them straight off the binding instead of re-scanning the emitted chain.
118// A binding that already carries them (hand-authored) is left untouched; one
119// whose endpoints cannot all be resolved is left unpopulated for the lowering
120// to diagnose.
121static void populateRearmBindings(DeviceOp device) {
122 for (auto binding : device.getOps<ObjectFifoRearmBindingOp>()) {
123 if (binding.getHeadBdIds().has_value() ||
124 binding.getRepeatCounts().has_value())
125 continue;
126 ValueRange tiles = binding.getChannelTiles();
127 ArrayRef<int32_t> dirs = binding.getChannelDirs();
128 ArrayRef<int32_t> chans = binding.getChannelIndices();
129 SmallVector<int32_t> headBdIds, repeatCounts;
130 bool resolvedAll = true;
131 for (unsigned i = 0; i < tiles.size(); ++i) {
132 auto tileOp = tiles[i].getDefiningOp<TileOp>();
133 DMAChannelDir dir =
134 dirs[i] == 0 ? DMAChannelDir::S2MM : DMAChannelDir::MM2S;
135 uint32_t headBdId = 0;
136 int32_t repeatCount = 0;
137 if (!tileOp || !resolveResidentChannel(device, tileOp, dir, chans[i],
138 headBdId, repeatCount)) {
139 resolvedAll = false;
140 break;
141 }
142 headBdIds.push_back(static_cast<int32_t>(headBdId));
143 repeatCounts.push_back(repeatCount);
144 }
145 if (!resolvedAll)
146 continue;
147 OpBuilder b(binding);
148 binding.setHeadBdIdsAttr(b.getDenseI32ArrayAttr(headBdIds));
149 binding.setRepeatCountsAttr(b.getDenseI32ArrayAttr(repeatCounts));
150 }
151}
152
155 AIEAssignBufferDescriptorIDsPass> {
156 void runOnOperation() override {
157 DeviceOp targetOp = getOperation();
158 if (failed(verifyDMAChannelsResolved(targetOp)))
159 return signalPassFailure();
160 const AIETargetModel &targetModel = targetOp.getTargetModel();
161
162 auto memOps = llvm::to_vector_of<TileElement>(targetOp.getOps<MemOp>());
163 llvm::append_range(memOps, targetOp.getOps<MemTileDMAOp>());
164 llvm::append_range(memOps, targetOp.getOps<ShimDMAOp>());
165
166 // BD IDs are per-tile, but a tile can be described by more than one op
167 // here -- key the generator by tile, not by op, or two ops on the same
168 // tile hand out the same ids and silently collide.
169 std::map<std::pair<int, int>, BdIdGenerator> gens;
170 for (TileElement memOp : memOps) {
171 int col = memOp.getTileID().col;
172 int row = memOp.getTileID().row;
173
174 auto emplaced =
175 gens.try_emplace(std::make_pair(col, row), col, row, targetModel);
176 BdIdGenerator &gen = emplaced.first->second;
177 if (emplaced.second) {
178 for (TileOp tile : targetOp.getOps<TileOp>()) {
179 if (tile.getCol() != col || tile.getRow() != row)
180 continue;
181 if (auto reserved = tile->getAttrOfType<DenseI32ArrayAttr>(
182 "aiex.reserved_bd_ids"))
183 for (int32_t bdId : reserved.asArrayRef()) {
184 auto id = static_cast<uint32_t>(bdId);
185 if (!gen.bdIdAlreadyAssigned(id))
186 gen.assignBdId(id);
187 }
188 }
189 }
190 bool bdIdCollision = false;
191 memOp->walk<WalkOrder::PreOrder>([&](DMABDOp bd) {
192 if (!bd.getBdId().has_value())
193 return;
194 uint32_t bdId = bd.getBdId().value();
195 if (gen.bdIdAlreadyAssigned(bdId)) {
196 bd.emitOpError() << "assigned bd_id " << bdId
197 << " is already used by another BD on this tile";
198 bdIdCollision = true;
199 return;
200 }
201 gen.assignBdId(bdId);
202 });
203 if (bdIdCollision)
204 return signalPassFailure();
205 }
206
207 struct Assignment {
208 DMABDOp bd;
209 int col, row, channel;
210 };
211 SmallVector<Assignment> assignments;
212 std::map<int32_t, SmallVector<size_t>> receiversOfTag;
213 for (TileElement memOp : memOps) {
214 DenseMap<Block *, int> channels;
215 Region &region = memOp->getRegion(0);
216 for (Block &block : region)
217 for (auto start : block.getOps<DMAStartOp>()) {
218 Block *dest = start.getDest();
219 while (dest && !channels.contains(dest)) {
220 channels[dest] = start.getChannelIndex();
221 dest = dest->hasNoSuccessors() ? nullptr : dest->getSuccessor(0);
222 }
223 }
224 memOp->walk([&](DMABDOp bd) {
225 auto dma = bd->getParentOfType<DMAOp>();
226 int channel =
227 dma ? dma.getChannelIndex() : channels.lookup(bd->getBlock());
228 if (auto tag = bd.getBdTag())
229 receiversOfTag[*tag].push_back(assignments.size());
230 assignments.push_back(
231 {bd, memOp.getTileID().col, memOp.getTileID().row, channel});
232 });
233 }
234
235 // Broadcast destinations must agree on a hardware ID. Reserve their
236 // common IDs before ordinary allocation can consume them on one tile.
237 for (auto &[tag, indices] : receiversOfTag) {
238 if (indices.size() < 2)
239 continue;
240 std::optional<uint32_t> fixed;
241 for (size_t i : indices) {
242 if (auto id = assignments[i].bd.getBdId()) {
243 if (fixed && *fixed != static_cast<uint32_t>(*id)) {
244 assignments[i].bd.emitOpError(
245 "broadcast receiving buffers must have matching bd_id values "
246 "for bd_tag ")
247 << tag;
248 return signalPassFailure();
249 }
250 fixed = id;
251 }
252 }
253 auto available = [&, &indices = indices](uint32_t id) {
254 std::set<std::pair<int, int>> tiles;
255 for (size_t i : indices) {
256 auto &a = assignments[i];
257 auto tile = std::make_pair(a.col, a.row);
258 if (!tiles.insert(tile).second ||
259 id >= targetModel.getNumBDs(a.col, a.row) ||
260 !targetModel.isBdChannelAccessible(a.col, a.row, id, a.channel) ||
261 (!a.bd.getBdId() && gens.at(tile).bdIdAlreadyAssigned(id)))
262 return false;
263 }
264 return true;
265 };
266 uint32_t id = fixed.value_or(0);
267 auto &first = assignments[indices.front()];
268 uint32_t limit = targetModel.getNumBDs(first.col, first.row);
269 if (!fixed)
270 while (id < limit && !available(id))
271 ++id;
272 if (id >= limit || !available(id)) {
273 first.bd.emitOpError("cannot allocate a common bd_id for broadcast "
274 "receiving buffers with bd_tag ")
275 << tag;
276 return signalPassFailure();
277 }
278 for (size_t i : indices) {
279 auto &a = assignments[i];
280 if (!a.bd.getBdId()) {
281 gens.at({a.col, a.row}).assignBdId(id);
282 a.bd.setBdId(id);
283 }
284 }
285 }
286 for (auto &a : assignments) {
287 auto &gen = gens.at({a.col, a.row});
288 if (auto id = a.bd.getBdId()) {
289 if (!targetModel.isBdChannelAccessible(a.col, a.row, *id, a.channel)) {
290 a.bd.emitOpError()
291 << "assigned bd_id " << *id << " is not accessible from channel "
292 << a.channel << " on this tile";
293 return signalPassFailure();
294 }
295 } else {
296 auto nextId = gen.nextBdId(a.channel);
297 if (!nextId) {
298 a.bd.emitOpError()
299 << "Allocator exhausted available BD IDs (maximum "
300 << targetModel.getNumBDsForChannel(a.col, a.row, a.channel)
301 << " available for channel " << a.channel << ").";
302 return signalPassFailure();
303 }
304 a.bd.setBdId(*nextId);
305 }
306 }
307 for (TileElement memOp : memOps) {
308 auto dmaOps = memOp.getOperation()->getRegion(0).getOps<DMAOp>();
309 if (!dmaOps.empty()) {
310 for (auto dmaOp : dmaOps) {
311 auto bdRegions = dmaOp.getBds();
312 for (auto *bdRegionIt = bdRegions.begin();
313 bdRegionIt != bdRegions.end();) {
314 auto &block = bdRegionIt->getBlocks().front();
315 DMABDOp bd = *block.getOps<DMABDOp>().begin();
316 std::optional<int> nextBdId;
317 if (++bdRegionIt != bdRegions.end())
318 nextBdId =
319 (*bdRegionIt->getBlocks().front().getOps<DMABDOp>().begin())
320 .getBdId();
321 else if (dmaOp.getLoop())
322 nextBdId = (*bdRegions.front()
323 .getBlocks()
324 .front()
325 .getOps<DMABDOp>()
326 .begin())
327 .getBdId();
328 bd.setNextBdId(nextBdId);
329 }
330 }
331 } else {
332 DenseMap<Block *, int> blockBdIdMap;
333 for (Block &block : memOp.getOperation()->getRegion(0)) {
334 if (block.getOps<DMABDOp>().empty())
335 continue;
336 DMABDOp bd = *block.getOps<DMABDOp>().begin();
337 auto bdId = bd.getBdId();
338 assert(bdId.has_value() &&
339 "DMABDOp should have bd_id assigned by now");
340 blockBdIdMap[&block] = *bdId;
341 }
342
343 for (Block &block : memOp.getOperation()->getRegion(0)) {
344 if (block.getOps<DMABDOp>().empty())
345 continue;
346 DMABDOp bd = *block.getOps<DMABDOp>().begin();
347 std::optional<int> nextBdId;
348 if (block.getNumSuccessors()) {
349 assert(llvm::range_size(block.getSuccessors()) == 1 &&
350 "should have only one successor block");
351 Block *nextBlock = block.getSuccessor(0);
352 if (blockBdIdMap.contains(nextBlock)) {
353 nextBdId = blockBdIdMap[nextBlock];
354 } else if (nextBlock->getOperations().size() != 1 ||
355 !isa<EndOp>(nextBlock->getOperations().front())) {
356 bd.emitOpError("must be followed by a block with a dma_bd or "
357 "by a block with only aie.end");
358 return signalPassFailure();
359 }
360 bd.setNextBdId(nextBdId);
361 }
362 }
363 }
364 }
365
366 if (failed(resolveOutOfOrderTags(targetOp)))
367 return signalPassFailure();
368 populateRearmBindings(targetOp);
369 }
370
371 /// Resolve BD tags after hardware ID assignment.
372 LogicalResult resolveOutOfOrderTags(DeviceOp device) {
373 DenseMap<int32_t, int> idOfTag;
374 DenseMap<std::pair<Value, int32_t>, int> localIdOfTag;
375 WalkResult receivers = device.walk([&](DMABDOp bd) {
376 std::optional<uint32_t> id = bd.getBdId();
377 if (std::optional<int32_t> tag = bd.getBdTag(); tag && id) {
378 auto tile = bd->getParentOfType<TileElement>();
379 if (tile)
380 localIdOfTag[{tile.getTile(), *tag}] = *id;
381 auto [it, inserted] = idOfTag.try_emplace(*tag, *id);
382 if (!inserted && it->second != static_cast<int>(*id)) {
383 bd.emitOpError("broadcast receiving buffers must have matching bd_id "
384 "values for bd_tag ")
385 << *tag;
386 return WalkResult::interrupt();
387 }
388 }
389 bd.removeBdTagAttr();
390 return WalkResult::advance();
391 });
392 if (receivers.wasInterrupted())
393 return failure();
394 WalkResult walked = device.walk([&](DMABDOp bd) {
395 std::optional<int32_t> tag = bd.getOutOfOrderTag();
396 if (!tag)
397 return WalkResult::advance();
398 auto it = idOfTag.find(*tag);
399 if (it == idOfTag.end()) {
400 bd.emitOpError("sends out of order into a descriptor that no "
401 "receiving tile holds");
402 return WalkResult::interrupt();
403 }
404 bd.setOutOfOrderId(it->second);
405 bd.removeOutOfOrderTagAttr();
406 return WalkResult::advance();
407 });
408 if (walked.wasInterrupted())
409 return failure();
410 walked = device.walk([&](DmaPushQueueOp push) {
411 std::optional<int32_t> tag = push.getBdTag();
412 if (!tag)
413 return WalkResult::advance();
414 Value tile = push->getParentOfType<CoreOp>().getTile();
415 auto it = localIdOfTag.find({tile, *tag});
416 if (it == localIdOfTag.end()) {
417 push.emitOpError("pushes a descriptor that this tile does not hold");
418 return WalkResult::interrupt();
419 }
420 push.setBdId(it->second);
421 push.removeBdTagAttr();
422 return WalkResult::advance();
423 });
424 return failure(walked.wasInterrupted());
425 }
426};
427
428std::unique_ptr<OperationPass<DeviceOp>>
430 return std::make_unique<AIEAssignBufferDescriptorIDsPass>();
431}
virtual bool isBdChannelAccessible(int col, int row, uint32_t bd_id, int channel) const =0
Return true iff buffer descriptor bd_id on tile (col, row) can be submitted on channel channel.
virtual uint32_t getNumBDs(AIETileType tileType) const =0
Return the number of buffer descriptors for a given tile type.
uint32_t getNumBDsForChannel(int col, int row, int channel) const
Return the number of buffer descriptors accessible on channel channel for the tile at (col,...
mlir::LogicalResult verifyDMAChannelsResolved(DeviceOp device)
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEAssignBufferDescriptorIDsPass()
LogicalResult resolveOutOfOrderTags(DeviceOp device)
Resolve BD tags after hardware ID assignment.
bool bdIdAlreadyAssigned(uint32_t bdId)
const AIETargetModel & targetModel
void assignBdId(uint32_t bdId)
void freeBdId(uint32_t bdId)
std::set< uint32_t > alreadyAssigned
BdIdGenerator(int col, int row, const AIETargetModel &targetModel)
std::optional< uint32_t > nextBdId(int channelIndex, uint32_t alignment=1)