MLIR-AIE 1.4.5.dev
AIECheckAgainstImage.cpp
Go to the documentation of this file.
1//===- AIECheckAgainstImage.cpp ---------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
11
12#include "mlir/Parser/Parser.h"
13#include "mlir/Pass/Pass.h"
14
15#include "llvm/ADT/StringExtras.h"
16
17#include <map>
18#include <set>
19
20using namespace mlir;
21using namespace xilinx;
22using namespace xilinx::AIE;
23
24namespace xilinx::AIE {
25#define GEN_PASS_DEF_AIECHECKAGAINSTIMAGE
26#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
27} // namespace xilinx::AIE
28
29namespace {
30
31using Coords = std::pair<int, int>;
32
33/// Where a DMA channel is: its tile, direction and index.
34struct Channel {
35 Coords tile;
36 DMAChannelDir dir;
37 int index;
38 bool operator<(const Channel &other) const {
39 return std::tie(tile, dir, index) <
40 std::tie(other.tile, other.dir, other.index);
41 }
42 bool operator==(const Channel &other) const {
43 return !(*this < other) && !(other < *this);
44 }
45};
46
47struct Buffer {
48 Coords tile;
49 std::optional<int64_t> address, bank;
50};
51
52struct Lock {
53 Coords tile;
54 std::optional<int64_t> id;
55};
56
57struct Chain {
58 Channel channel;
59 SmallVector<int64_t> bdIds;
60};
61
62/// The static allocation of one device, each resource under a name that does
63/// not depend on where it was put: a shim end under its allocation's symbol,
64/// a buffer or lock under its own, a DMA chain or runtime task under the
65/// buffers it moves, a switchbox under its tile.
66struct Allocation {
67 std::map<std::string, Channel> shimEnds;
68 std::map<std::string, Buffer> buffers;
69 std::map<std::string, Lock> locks;
70 std::map<std::string, Chain> chains;
71 std::map<std::string, std::set<Channel>> tasks;
72 std::map<std::pair<Coords, std::string>, std::set<std::string>> switches;
73};
74
75Coords coordsOf(Value tile) {
76 auto op = cast<TileOp>(tile.getDefiningOp());
77 return {op.colIndex(), op.rowIndex()};
78}
79
80std::string nameOf(Operation *op) {
81 if (auto name =
82 op->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName()))
83 return name.str();
84 return "";
85}
86
87/// The named buffers, each with the offset it is moved at, or failing them the
88/// named locks, that `ops` touch: a name for a DMA chain or task that the
89/// inputs of one join, writing one buffer at different offsets, do not share.
90std::string identity(ArrayRef<Operation *> ops) {
91 std::set<std::string> buffers, locks;
92 for (Operation *op : ops) {
93 if (auto bd = dyn_cast<DMABDOp>(op)) {
94 if (auto buffer = bd.getBuffer().getDefiningOp())
95 if (std::string name = nameOf(buffer); !name.empty())
96 buffers.insert(name + "@" +
97 std::to_string(bd.getConstantOffset().value_or(-1)));
98 } else if (auto use = dyn_cast<UseLockOp>(op)) {
99 if (auto lock = use.getLock().getDefiningOp())
100 if (std::string name = nameOf(lock); !name.empty())
101 locks.insert(name);
102 }
103 }
104 return llvm::join(buffers.empty() ? locks : buffers, ", ");
105}
106
107Allocation allocationOf(DeviceOp device) {
108 Allocation allocation;
109 for (auto alloc : device.getOps<ShimDMAAllocationOp>())
110 allocation.shimEnds[alloc.getSymName().str()] = {
111 coordsOf(alloc.getTile()), alloc.getChannelDir(),
112 static_cast<int>(alloc.getChannelIndex())};
113 for (auto buffer : device.getOps<BufferOp>())
114 if (std::string name = nameOf(buffer); !name.empty())
115 allocation.buffers[name] = {coordsOf(buffer.getTile()),
116 buffer.getAddress(), buffer.getMemBank()};
117 for (auto lock : device.getOps<LockOp>())
118 if (std::string name = nameOf(lock); !name.empty())
119 allocation.locks[name] = {coordsOf(lock.getTile()), lock.getLockID()};
120
121 for (auto program : device.getOps<DmaBody>())
122 for (Block &block : program.getDmaBody())
123 for (auto start : block.getOps<DMAStartOp>()) {
124 SmallVector<Operation *> ops;
125 SmallVector<int64_t> bdIds;
126 SmallPtrSet<Block *, 8> seen;
127 for (Block *at = start.getDest(); at && seen.insert(at).second;) {
128 if (at->getOps<DMABDOp>().empty())
129 break;
130 for (Operation &op : *at) {
131 ops.push_back(&op);
132 if (auto bd = dyn_cast<DMABDOp>(op))
133 bdIds.push_back(bd.getBdId().value_or(-1));
134 }
135 auto next = dyn_cast<NextBDOp>(at->getTerminator());
136 at = next ? next.getDest() : nullptr;
137 }
138 std::string name = identity(ops);
139 if (name.empty())
140 continue;
141 allocation.chains[name] = {{coordsOf(program.getTile()),
142 start.getChannelDir(),
143 start.getChannelIndex()},
144 bdIds};
145 }
146
147 device.walk([&](AIEX::DMAConfigureTaskOp task) {
148 SmallVector<Operation *> ops;
149 task.getBody().walk([&](Operation *op) { ops.push_back(op); });
150 if (std::string name = identity(ops); !name.empty())
151 allocation.tasks[name].insert({coordsOf(task.getTile()),
152 task.getDirection(),
153 static_cast<int>(task.getChannel())});
154 });
155
156 auto addSwitch = [&](Operation *box, Value tile, StringRef kind) {
157 auto &connects = allocation.switches[{coordsOf(tile), kind.str()}];
158 box->walk([&](ConnectOp connect) {
159 connects.insert((stringifyWireBundle(connect.getSourceBundle()) + ":" +
160 Twine(connect.sourceIndex()) + "->" +
161 stringifyWireBundle(connect.getDestBundle()) + ":" +
162 Twine(connect.destIndex()))
163 .str());
164 });
165 };
166 for (auto box : device.getOps<SwitchboxOp>())
167 addSwitch(box, box.getTile(), "switchbox");
168 for (auto mux : device.getOps<ShimMuxOp>())
169 addSwitch(mux, mux.getTile(), "shim mux");
170 return allocation;
171}
172
173std::string str(Coords tile) {
174 return ("(" + Twine(tile.first) + ", " + Twine(tile.second) + ")").str();
175}
176
177std::string str(const Channel &channel) {
178 return (str(channel.tile) + " " + stringifyDMAChannelDir(channel.dir) + " " +
179 Twine(channel.index))
180 .str();
181}
182
183std::string str(const std::set<Channel> &channels) {
184 SmallVector<std::string> parts;
185 for (const Channel &channel : channels)
186 parts.push_back(str(channel));
187 return llvm::join(parts, " and ");
188}
189
190std::string str(std::optional<int64_t> value) {
191 return value ? std::to_string(*value) : "none";
192}
193
194/// `names`, a long list shortened to its first two and a count.
195std::string shortList(ArrayRef<std::string> names) {
196 if (names.size() <= 3)
197 return llvm::join(names, ", ");
198 return names[0] + ", " + names[1] + " and " +
199 std::to_string(names.size() - 2) + " more";
200}
201
202/// An identity's first two parts and a count, for display.
203std::string shortName(StringRef name) {
204 SmallVector<StringRef> parts;
205 name.split(parts, ", ");
206 return shortList(llvm::to_vector(
207 llvm::map_range(parts, [](StringRef part) { return part.str(); })));
208}
209
210/// What differs between the image's allocation and this stream's, each line
211/// a resource and what to pin.
212SmallVector<std::string> differences(const Allocation &image,
213 const Allocation &stream) {
214 SmallVector<std::string> lines;
215 auto both = [](const auto &a, const auto &b, auto fn) {
216 std::set<std::string> keys;
217 for (auto &[key, _] : a)
218 keys.insert(key);
219 for (auto &[key, _] : b)
220 keys.insert(key);
221 for (const std::string &key : keys) {
222 auto ia = a.find(key), ib = b.find(key);
223 fn(key, ia == a.end() ? nullptr : &ia->second,
224 ib == b.end() ? nullptr : &ib->second);
225 }
226 };
227
228 both(image.shimEnds, stream.shimEnds,
229 [&](const std::string &name, const Channel *i, const Channel *s) {
230 StringRef fifo = StringRef(name).rsplit("_shim_alloc").first;
231 if (i && s && *i == *s)
232 return;
233 lines.push_back((fifo + "'s shim end: image " +
234 (i ? str(*i) : "none") + ", this stream " +
235 (s ? str(*s) : "none") +
236 "; pin it to keep it stable across shapes")
237 .str());
238 });
239
240 // A tile that moved shows in every buffer it holds; name it once.
241 std::map<std::pair<Coords, Coords>, SmallVector<std::string>> moved;
242 for (auto &[name, i] : image.buffers)
243 if (auto it = stream.buffers.find(name);
244 it != stream.buffers.end() && it->second.tile != i.tile)
245 moved[{i.tile, it->second.tile}].push_back(name);
246 for (auto &[tiles, names] : moved)
247 lines.push_back("the tile holding " + shortList(names) + ": image " +
248 str(tiles.first) + ", this stream " + str(tiles.second) +
249 "; pin it to keep it stable across shapes");
250
251 both(image.chains, stream.chains,
252 [&](const std::string &name, const Chain *i, const Chain *s) {
253 if (i && s && i->channel == s->channel) {
254 auto ids = [](ArrayRef<int64_t> bdIds) {
255 return llvm::join(
256 llvm::map_range(bdIds,
257 [](int64_t id) { return std::to_string(id); }),
258 ", ");
259 };
260 if (i->bdIds != s->bdIds)
261 lines.push_back("the BD ids of the chain moving " +
262 shortName(name) + " at " + str(i->channel) +
263 ": image " + ids(i->bdIds) + ", this stream " +
264 ids(s->bdIds));
265 return;
266 }
267 lines.push_back("the DMA channel moving " + shortName(name) +
268 ": image " + (i ? str(i->channel) : "none") +
269 ", this stream " + (s ? str(s->channel) : "none") +
270 "; pin its endpoint to keep it stable across shapes");
271 });
272
273 both(image.tasks, stream.tasks,
274 [&](const std::string &name, const std::set<Channel> *i,
275 const std::set<Channel> *s) {
276 // A task only one sequence issues is no allocation of its own.
277 if (!i || !s || *i == *s)
278 return;
279 lines.push_back(
280 "the runtime tasks moving " + shortName(name) + ": image " +
281 str(*i) + ", this stream " + str(*s) +
282 "; pin their endpoint to keep it stable across shapes");
283 });
284
285 both(image.buffers, stream.buffers,
286 [&](const std::string &name, const Buffer *i, const Buffer *s) {
287 if (i && s && i->tile != s->tile)
288 return;
289 if (i && s && i->address == s->address && i->bank == s->bank)
290 return;
291 if (!i || !s) {
292 lines.push_back("buffer " + name + ": only " +
293 (i ? "the image" : "this stream") + " has it, at " +
294 str(i ? i->tile : s->tile));
295 return;
296 }
297 lines.push_back("buffer " + name + " on " + str(i->tile) +
298 ": image address " + str(i->address) + " (bank " +
299 str(i->bank) + "), this stream " + str(s->address) +
300 " (bank " + str(s->bank) + ")");
301 });
302
303 both(image.locks, stream.locks,
304 [&](const std::string &name, const Lock *i, const Lock *s) {
305 if (i && s && i->tile == s->tile && i->id == s->id)
306 return;
307 if (i && s && i->tile != s->tile)
308 return;
309 if (!i || !s) {
310 lines.push_back("lock " + name + ": only " +
311 (i ? "the image" : "this stream") + " has it");
312 return;
313 }
314 lines.push_back("lock " + name + " on " + str(i->tile) +
315 ": image id " + str(i->id) + ", this stream id " +
316 str(s->id));
317 });
318
319 size_t routes = 0;
320 std::set<std::pair<Coords, std::string>> boxes;
321 for (auto &[key, _] : image.switches)
322 boxes.insert(key);
323 for (auto &[key, _] : stream.switches)
324 boxes.insert(key);
325 for (auto &key : boxes) {
326 std::set<std::string> none;
327 auto ia = image.switches.find(key), is = stream.switches.find(key);
328 const auto &i = ia == image.switches.end() ? none : ia->second;
329 const auto &s = is == stream.switches.end() ? none : is->second;
330 SmallVector<std::string> onlyImage, onlyStream;
331 std::set_difference(i.begin(), i.end(), s.begin(), s.end(),
332 std::back_inserter(onlyImage));
333 std::set_difference(s.begin(), s.end(), i.begin(), i.end(),
334 std::back_inserter(onlyStream));
335 if (onlyImage.empty() && onlyStream.empty())
336 continue;
337 ++routes;
338 lines.push_back(
339 key.second + " " + str(key.first) + ": image connects " +
340 (onlyImage.empty() ? "none" : llvm::join(onlyImage, ", ")) +
341 ", this stream " +
342 (onlyStream.empty() ? "none" : llvm::join(onlyStream, ", ")));
343 }
344 if (routes)
345 lines.push_back("the routes follow the placement and channels above");
346 return lines;
347}
348
349struct AIECheckAgainstImagePass
350 : xilinx::AIE::impl::AIECheckAgainstImageBase<AIECheckAgainstImagePass> {
351 using AIECheckAgainstImageBase::AIECheckAgainstImageBase;
352
353 void runOnOperation() override {
354 ModuleOp module = getOperation();
355 if (clImage.empty()) {
356 module.emitError("aie-check-against-image needs image=<module>");
357 return signalPassFailure();
358 }
359 OwningOpRef<ModuleOp> image =
360 parseSourceFile<ModuleOp>(clImage, ParserConfig(&getContext()));
361 if (!image) {
362 module.emitError("could not read the image's module ") << clImage;
363 return signalPassFailure();
364 }
365 DenseMap<StringAttr, DeviceOp> imageDevices;
366 for (auto device : image->getOps<DeviceOp>())
367 imageDevices[device.getSymNameAttr()] = device;
368
369 bool mismatch = false;
370 for (auto stream : module.getOps<DeviceOp>()) {
371 DeviceOp target = imageDevices.lookup(stream.getSymNameAttr());
372 if (!target) {
373 stream.emitOpError("has no device of its name in the image's module ")
374 << clImage;
375 mismatch = true;
376 continue;
377 }
378 SmallVector<std::string> lines =
379 differences(allocationOf(target), allocationOf(stream));
380 if (lines.empty())
381 continue;
382 auto diag = mlir::emitError(stream.getLoc())
383 << "this instruction stream would program resources its "
384 "image ("
385 << clImage << ") does not have; " << lines.size()
386 << " differ:";
387 for (const std::string &line : lines)
388 diag << "\n " << line;
389 mismatch = true;
390 }
391 if (mismatch)
392 signalPassFailure();
393 }
394};
395
396} // namespace
397
398std::unique_ptr<OperationPass<ModuleOp>>
400 return std::make_unique<AIECheckAgainstImagePass>();
401}
402
403std::unique_ptr<OperationPass<ModuleOp>>
405 const AIECheckAgainstImageOptions &options) {
406 return std::make_unique<AIECheckAgainstImagePass>(options);
407}
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createAIECheckAgainstImagePass()