MLIR-AIE 1.4.5.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 <utility>
31
32namespace xilinx::AIE {
33#define GEN_PASS_DEF_AIEASSIGNBUFFERDESCRIPTORIDS
34#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
35} // namespace xilinx::AIE
36
37#define DEBUG_TYPE "aie-assign-bd-ids"
38
39using namespace mlir;
40using namespace xilinx;
41using namespace xilinx::AIE;
42
46
47std::optional<uint32_t> BdIdGenerator::nextBdId(int channelIndex,
48 uint32_t alignment) {
49 uint32_t bdId = 0;
50 uint32_t maxBdId = targetModel.getNumBDs(col, row) - 1;
51 // Find the next free BD ID. This is not an efficient algorithm, but doesn't
52 for (; (bdIdAlreadyAssigned(bdId) ||
53 !targetModel.isBdChannelAccessible(col, row, bdId, channelIndex)) &&
54 bdId <= maxBdId;
55 bdId += alignment)
56 ;
57 if (bdId > maxBdId) {
58 return std::nullopt;
59 }
60 assignBdId(bdId);
61 return std::optional<uint32_t>(bdId);
62}
63
64void BdIdGenerator::assignBdId(uint32_t bdId) {
65 assert(!alreadyAssigned.count(bdId) && "bdId has already been assigned");
66 alreadyAssigned.insert(bdId);
67}
68
70 return alreadyAssigned.count(bdId);
71}
72
73void BdIdGenerator::freeBdId(uint32_t bdId) { alreadyAssigned.erase(bdId); }
74
75// Read the head buffer-descriptor id and (biased) START_QUEUE repeat count of
76// the resident DMA channel (dir, channel) on `tile`, off the placed
77// aie.mem / aie.memtile_dma chain, exactly as the load-time start-queue push
78// does. Returns false if there is no such resident channel or its head BD id is
79// not yet assigned. Only the block form (aie.dma_start) is scanned; the
80// objectFIFO transform always emits that form.
81static bool resolveResidentChannel(DeviceOp device, TileOp tile,
82 DMAChannelDir dir, int channel,
83 uint32_t &headBdId, int32_t &repeatCount) {
84 auto scanRegion = [&](Region &region) -> bool {
85 for (Block &block : region)
86 for (DMAStartOp startOp : block.getOps<DMAStartOp>()) {
87 if (startOp.getChannelDir() != dir ||
88 startOp.getChannelIndex() != channel)
89 continue;
90 Block *dest = startOp.getDest();
91 if (!dest)
92 return false;
93 auto bds = dest->getOps<DMABDOp>();
94 if (bds.empty())
95 return false;
96 DMABDOp head = *bds.begin();
97 if (!head.getBdId().has_value())
98 return false;
99 headBdId = head.getBdId().value();
100 repeatCount = startOp.getRepeatCount();
101 return true;
102 }
103 return false;
104 };
105 for (MemOp mem : device.getOps<MemOp>())
106 if (mem.getTile() == tile.getResult())
107 return scanRegion(mem->getRegion(0));
108 for (MemTileDMAOp mem : device.getOps<MemTileDMAOp>())
109 if (mem.getTile() == tile.getResult())
110 return scanRegion(mem->getRegion(0));
111 return false;
112}
113
114// Populate each objectFIFO re-arm binding's head_bd_ids / repeat_counts now
115// that BD ids are assigned, so the later aiex.dma_channel_reset_for lowering
116// reads them straight off the binding instead of re-scanning the emitted chain.
117// A binding that already carries them (hand-authored) is left untouched; one
118// whose endpoints cannot all be resolved is left unpopulated for the lowering
119// to diagnose.
120static void populateRearmBindings(DeviceOp device) {
121 for (auto binding : device.getOps<ObjectFifoRearmBindingOp>()) {
122 if (binding.getHeadBdIds().has_value() ||
123 binding.getRepeatCounts().has_value())
124 continue;
125 ValueRange tiles = binding.getChannelTiles();
126 ArrayRef<int32_t> dirs = binding.getChannelDirs();
127 ArrayRef<int32_t> chans = binding.getChannelIndices();
128 SmallVector<int32_t> headBdIds, repeatCounts;
129 bool resolvedAll = true;
130 for (unsigned i = 0; i < tiles.size(); ++i) {
131 auto tileOp = tiles[i].getDefiningOp<TileOp>();
132 DMAChannelDir dir =
133 dirs[i] == 0 ? DMAChannelDir::S2MM : DMAChannelDir::MM2S;
134 uint32_t headBdId = 0;
135 int32_t repeatCount = 0;
136 if (!tileOp || !resolveResidentChannel(device, tileOp, dir, chans[i],
137 headBdId, repeatCount)) {
138 resolvedAll = false;
139 break;
140 }
141 headBdIds.push_back(static_cast<int32_t>(headBdId));
142 repeatCounts.push_back(repeatCount);
143 }
144 if (!resolvedAll)
145 continue;
146 OpBuilder b(binding);
147 binding.setHeadBdIdsAttr(b.getDenseI32ArrayAttr(headBdIds));
148 binding.setRepeatCountsAttr(b.getDenseI32ArrayAttr(repeatCounts));
149 }
150}
151
154 AIEAssignBufferDescriptorIDsPass> {
155 void runOnOperation() override {
156 DeviceOp targetOp = getOperation();
157 if (failed(verifyDMAChannelsResolved(targetOp)))
158 return signalPassFailure();
159 const AIETargetModel &targetModel = targetOp.getTargetModel();
160
161 auto memOps = llvm::to_vector_of<TileElement>(targetOp.getOps<MemOp>());
162 llvm::append_range(memOps, targetOp.getOps<MemTileDMAOp>());
163 llvm::append_range(memOps, targetOp.getOps<ShimDMAOp>());
164
165 // BD IDs are per-tile, but a tile can be described by more than one op
166 // here -- key the generator by tile, not by op, or two ops on the same
167 // tile hand out the same ids and silently collide.
168 std::map<std::pair<int, int>, BdIdGenerator> gens;
169 for (TileElement memOp : memOps) {
170 int col = memOp.getTileID().col;
171 int row = memOp.getTileID().row;
172
173 auto emplaced =
174 gens.try_emplace(std::make_pair(col, row), col, row, targetModel);
175 BdIdGenerator &gen = emplaced.first->second;
176 if (emplaced.second) {
177 for (TileOp tile : targetOp.getOps<TileOp>()) {
178 if (tile.getCol() != col || tile.getRow() != row)
179 continue;
180 if (auto reserved = tile->getAttrOfType<DenseI32ArrayAttr>(
181 "aiex.reserved_bd_ids"))
182 for (int32_t bdId : reserved.asArrayRef()) {
183 auto id = static_cast<uint32_t>(bdId);
184 if (!gen.bdIdAlreadyAssigned(id))
185 gen.assignBdId(id);
186 }
187 }
188 }
189 auto checkBdChannelAccessible = [&](DMABDOp bd, int bdId,
190 int channelIndex) -> bool {
191 if (targetModel.isBdChannelAccessible(col, row, bdId, channelIndex))
192 return true;
193 bd.emitOpError() << "assigned bd_id " << bdId
194 << " is not accessible from channel " << channelIndex
195 << " on this tile";
196 return false;
197 };
198 bool bdIdCollision = false;
199 memOp->walk<WalkOrder::PreOrder>([&](DMABDOp bd) {
200 if (!bd.getBdId().has_value())
201 return;
202 uint32_t bdId = bd.getBdId().value();
203 if (gen.bdIdAlreadyAssigned(bdId)) {
204 bd.emitOpError() << "assigned bd_id " << bdId
205 << " is already used by another BD on this tile";
206 bdIdCollision = true;
207 return;
208 }
209 gen.assignBdId(bdId);
210 });
211 if (bdIdCollision)
212 return signalPassFailure();
213
214 auto dmaOps = memOp.getOperation()->getRegion(0).getOps<DMAOp>();
215 if (!dmaOps.empty()) {
216 for (auto dmaOp : dmaOps) {
217 auto bdRegions = dmaOp.getBds();
218 for (auto &bdRegion : bdRegions) {
219 auto &block = bdRegion.getBlocks().front();
220 DMABDOp bd = *block.getOps<DMABDOp>().begin();
221 if (auto existingBdId = bd.getBdId()) {
222 assert(
223 gen.bdIdAlreadyAssigned(*existingBdId) &&
224 "bdId assigned by user but not found during previous walk");
225 int channelIndex = dmaOp.getChannelIndex();
226 if (!checkBdChannelAccessible(bd, *existingBdId, channelIndex))
227 return signalPassFailure();
228 } else {
229 std::optional<int32_t> nextId =
230 gen.nextBdId(dmaOp.getChannelIndex());
231 if (!nextId) {
232 int channelIndex = dmaOp.getChannelIndex();
233 bd.emitOpError()
234 << "Allocator exhausted available BD IDs (maximum "
235 << targetModel.getNumBDsForChannel(col, row, channelIndex)
236 << " available for channel " << channelIndex << ").";
237 return signalPassFailure();
238 }
239 bd.setBdId(nextId);
240 }
241 }
242 }
243 } else {
244 DenseMap<Block *, int> blockChannelMap;
245 // Associate with each block the channel index specified by the
246 // dma_start
247 for (Block &block : memOp.getOperation()->getRegion(0))
248 for (auto op : block.getOps<DMAStartOp>()) {
249 int chNum = op.getChannelIndex();
250 blockChannelMap[&block] = chNum;
251 Block *dest = op.getDest();
252 while (dest) {
253 blockChannelMap[dest] = chNum;
254 if (dest->hasNoSuccessors())
255 break;
256 dest = dest->getSuccessors()[0];
257 if (blockChannelMap.contains(dest))
258 dest = nullptr;
259 }
260 }
261
262 for (Block &block : memOp.getOperation()->getRegion(0)) {
263 if (block.getOps<DMABDOp>().empty())
264 continue;
265 assert(blockChannelMap.count(&block));
266 DMABDOp bd = (*block.getOps<DMABDOp>().begin());
267 if (auto existingBdId = bd.getBdId()) {
268 assert(gen.bdIdAlreadyAssigned(*existingBdId) &&
269 "bdId assigned by user but not found during previous walk");
270 int channelIndex = blockChannelMap[&block];
271 if (!checkBdChannelAccessible(bd, *existingBdId, channelIndex))
272 return signalPassFailure();
273 } else {
274 std::optional<int32_t> nextId =
275 gen.nextBdId(blockChannelMap[&block]);
276 if (!nextId) {
277 int channelIndex = blockChannelMap[&block];
278 bd.emitOpError()
279 << "Allocator exhausted available BD IDs (maximum "
280 << targetModel.getNumBDsForChannel(col, row, channelIndex)
281 << " available for channel " << channelIndex << ").";
282 return signalPassFailure();
283 }
284 bd.setBdId(nextId);
285 }
286 }
287 }
288 }
289 for (TileElement memOp : memOps) {
290 auto dmaOps = memOp.getOperation()->getRegion(0).getOps<DMAOp>();
291 if (!dmaOps.empty()) {
292 for (auto dmaOp : dmaOps) {
293 auto bdRegions = dmaOp.getBds();
294 for (auto *bdRegionIt = bdRegions.begin();
295 bdRegionIt != bdRegions.end();) {
296 auto &block = bdRegionIt->getBlocks().front();
297 DMABDOp bd = *block.getOps<DMABDOp>().begin();
298 std::optional<int> nextBdId;
299 if (++bdRegionIt != bdRegions.end())
300 nextBdId =
301 (*bdRegionIt->getBlocks().front().getOps<DMABDOp>().begin())
302 .getBdId();
303 else if (dmaOp.getLoop())
304 nextBdId = (*bdRegions.front()
305 .getBlocks()
306 .front()
307 .getOps<DMABDOp>()
308 .begin())
309 .getBdId();
310 bd.setNextBdId(nextBdId);
311 }
312 }
313 } else {
314 DenseMap<Block *, int> blockBdIdMap;
315 for (Block &block : memOp.getOperation()->getRegion(0)) {
316 if (block.getOps<DMABDOp>().empty())
317 continue;
318 DMABDOp bd = *block.getOps<DMABDOp>().begin();
319 auto bdId = bd.getBdId();
320 assert(bdId.has_value() &&
321 "DMABDOp should have bd_id assigned by now");
322 blockBdIdMap[&block] = *bdId;
323 }
324
325 for (Block &block : memOp.getOperation()->getRegion(0)) {
326 if (block.getOps<DMABDOp>().empty())
327 continue;
328 DMABDOp bd = *block.getOps<DMABDOp>().begin();
329 std::optional<int> nextBdId;
330 if (block.getNumSuccessors()) {
331 assert(llvm::range_size(block.getSuccessors()) == 1 &&
332 "should have only one successor block");
333 Block *nextBlock = block.getSuccessor(0);
334 if (blockBdIdMap.contains(nextBlock)) {
335 nextBdId = blockBdIdMap[nextBlock];
336 } else if (nextBlock->getOperations().size() != 1 ||
337 !isa<EndOp>(nextBlock->getOperations().front())) {
338 bd.emitOpError("must be followed by a block with a dma_bd or "
339 "by a block with only aie.end");
340 return signalPassFailure();
341 }
342 bd.setNextBdId(nextBdId);
343 }
344 }
345 }
346 }
347
348 populateRearmBindings(targetOp);
349 }
350};
351
352std::unique_ptr<OperationPass<DeviceOp>>
354 return std::make_unique<AIEAssignBufferDescriptorIDsPass>();
355}
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.
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()
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)