MLIR-AIE 1.4.5.dev
AIECertVerify.cpp
Go to the documentation of this file.
1//===- AIECertVerify.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/Pass/Pass.h"
13
14#include "llvm/ADT/STLExtras.h"
15#include "llvm/ADT/SmallVector.h"
16#include "llvm/ADT/StringRef.h"
17
18#include <algorithm>
19#include <map>
20#include <set>
21#include <tuple>
22#include <utility>
23
24namespace xilinx::AIEX {
25#define GEN_PASS_DEF_AIECERTVERIFY
26#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
27} // namespace xilinx::AIEX
28
29using namespace mlir;
30using namespace xilinx;
31
32#define DEBUG_TYPE "cert-verify"
33
34namespace {
35
36// The uC (CERT group) id of a cert op is the group_id of its nearest enclosing
37// cert.attach_to_group, or 0 if it is at device level.
38static int getUcGroupId(Operation *op) {
39 auto group = op->getParentOfType<AIEX::CertAttachToGroupOp>();
40 return group ? group.getGroupID() : 0;
41}
42
43// G-localbar: within each uC scope, all cert.local_barrier ops that share a
44// local_barrier_id must reside in the same cert.page (their participants must
45// be co-resident on one page, else the firmware hangs).
46static LogicalResult checkLocalBarrierColocation(AIE::DeviceOp device) {
47 LogicalResult result = success();
48 // Before page formation, a standalone job is its own co-location scope.
49 std::map<std::pair<int, int>, Operation *> firstScope;
50 device.walk(
51 [&](AIEX::CertLocalBarrierOp barrier) {
52 int uc = getUcGroupId(barrier);
53 int id = barrier.getLocalBarrierId();
54 Operation *scope = barrier->getParentOfType<AIEX::CertPageOp>();
55 if (!scope)
56 scope = barrier->getParentOfType<AIEX::CertJobOp>();
57 auto key = std::make_pair(uc, id);
58 auto it = firstScope.find(key);
59 if (it == firstScope.end()) {
60 firstScope[key] = scope;
61 return;
62 }
63 if (it->second != scope) {
64 barrier.emitError()
65 << "cert.local_barrier with local_barrier_id " << id << " in uC "
66 << uc << " must be on the same page as its other participants";
67 result = failure();
68 }
69 });
70 return result;
71}
72
73// Firmware keeps one outstanding TCT count per actor. Concurrent jobs waiting
74// on the same (tile, channel) would over-wait; sequential waits within a job or
75// on different pages are safe. Bare jobs use their own scope until
76// legalization.
77static LogicalResult checkWaitTctsUniqueness(AIE::DeviceOp device) {
78 LogicalResult result = success();
79 // (page scope, tile, channel) -> the job that already waits on that actor.
80 std::map<std::tuple<Operation *, int, int>, Operation *> seen;
81 device.walk([&](AIEX::CertWaitTCTSOp wait) {
82 int tile = wait.getTileId();
83 int channel = wait.getChannelId();
84 Operation *job = wait->getParentOfType<AIEX::CertJobOp>();
85 Operation *scope = wait->getParentOfType<AIEX::CertPageOp>();
86 if (!scope)
87 scope = job;
88 auto key = std::make_tuple(scope, tile, channel);
89 auto it = seen.find(key);
90 if (it == seen.end()) {
91 seen[key] = job;
92 return;
93 }
94 // Same job: sequential within one thread of control, so legal.
95 if (it->second == job)
96 return;
97 wait.emitError() << "more than one job on this cert.page waits on tile "
98 << tile << " channel " << channel << " in uC "
99 << getUcGroupId(wait)
100 << "; at most one job per page may wait_tcts on a given "
101 "(tile, channel)";
102 result = failure();
103 });
104 return result;
105}
106
107static std::set<int> computePresentUcs(AIE::DeviceOp device) {
108 std::set<int> present;
109 device.walk([&](AIEX::CertAttachToGroupOp group) {
110 present.insert(group.getGroupID());
111 });
112 if (!device.getBody()->getOps<AIEX::CertPageOp>().empty() ||
113 !device.getBody()->getOps<AIEX::CertJobOp>().empty() ||
114 !device.getBody()->getOps<AIEX::CertSectionOp>().empty())
115 present.insert(0);
116 return present;
117}
118
119// Firmware requires each controller to expose the same preemption IDs,
120// including multiplicity. Compare against the lowest present controller ID.
121static LogicalResult checkPreemptConsistency(AIE::DeviceOp device) {
122 std::map<int, std::multiset<int>> preemptIds;
123 std::map<int, AIEX::CertAttachToGroupOp> repAttach;
124 std::set<int> present;
125
126 // Every attach_to_group establishes a present uC.
127 device.walk([&](AIEX::CertAttachToGroupOp group) {
128 int uc = group.getGroupID();
129 present.insert(uc);
130 if (!repAttach.count(uc))
131 repAttach[uc] = group;
132 });
133
134 // Device-level cert content lives on uC 0.
135 if (!device.getBody()->getOps<AIEX::CertPageOp>().empty() ||
136 !device.getBody()->getOps<AIEX::CertJobOp>().empty() ||
137 !device.getBody()->getOps<AIEX::CertSectionOp>().empty())
138 present.insert(0);
139
140 // Collect the preempt ids per uC.
141 device.walk([&](AIEX::CertPreemptOp preempt) {
142 int uc = getUcGroupId(preempt);
143 present.insert(uc);
144 preemptIds[uc].insert(preempt.getId());
145 });
146
147 if (present.size() < 2)
148 return success();
149
150 int ref = *present.begin();
151 const std::multiset<int> &refSet = preemptIds[ref];
152 LogicalResult result = success();
153 for (int uc : present) {
154 if (uc == ref)
155 continue;
156 if (preemptIds[uc] != refSet) {
157 Operation *loc = repAttach.count(uc) ? repAttach[uc].getOperation()
158 : device.getOperation();
159 loc->emitError()
160 << "cert.preempt ids on uC " << uc << " differ from uC " << ref
161 << "; all uCs must have the same preempt ids (G-preempt)";
162 result = failure();
163 }
164 }
165 return result;
166}
167
168// Group/placement ids must be valid microcontroller indices for the device:
169// nonnegative, less than the number of uCs, and (defensively) < 32 so they are
170// representable in a party mask. Rejecting invalid ids up front keeps later
171// checks (e.g. the remote-barrier mask shift) free of undefined behavior.
172static LogicalResult checkGroupIds(AIE::DeviceOp device) {
173 const AIE::AIETargetModel &tm = device.getTargetModel();
174 int64_t numUcs = std::min<int64_t>(tm.getNumControllers(), 32);
175 LogicalResult result = success();
176 auto checkId = [&](Operation *op, int64_t id, llvm::StringRef what) {
177 if (id < 0 || id >= numUcs) {
178 op->emitError() << what << " " << id << " is not a valid uC id [0, "
179 << numUcs << ")";
180 result = failure();
181 }
182 };
183 device.walk([&](AIEX::CertAttachToGroupOp g) {
184 checkId(g, g.getGroupID(), "cert.attach_to_group group id");
185 });
186 // The verifier may run before placement lowering; check any leftover attrs.
187 device.walk([&](AIEX::CertPageOp p) {
188 if (auto pl = p.getPlacementAttr())
189 checkId(p, pl.getInt(), "cert.page placement");
190 });
191 return result;
192}
193
194// G-uC / rendezvous: cross-uC remote barriers must actually rendezvous. Group
195// occurrences by remote_barrier_id and require: all occurrences agree on
196// party_mask; every mask bit names a present uC that has a matching occurrence
197// (so nobody waits forever); each participating uC sets its own bit; and at
198// most one occurrence per uC per id (one participant per column). Assumes group
199// ids are already validated by checkGroupIds (shifts are still guarded).
200static LogicalResult checkRemoteBarrierParties(AIE::DeviceOp device) {
201 std::set<int> present = computePresentUcs(device);
202 LogicalResult result = success();
203
204 std::map<int, SmallVector<AIEX::CertRemoteBarrierOp, 4>> byId;
205 device.walk([&](AIEX::CertRemoteBarrierOp b) {
206 byId[b.getRemoteBarrierId()].push_back(b);
207 });
208
209 for (auto &entry : byId) {
210 int id = entry.first;
211 SmallVector<AIEX::CertRemoteBarrierOp, 4> &occs = entry.second;
212
213 // All occurrences of an id must describe the same rendezvous.
214 uint32_t mask = occs.front().getPartyMask();
215 bool masksAgree = llvm::all_of(occs, [&](AIEX::CertRemoteBarrierOp b) {
216 return b.getPartyMask() == mask;
217 });
218 if (!masksAgree) {
219 for (auto b : occs)
220 b.emitError() << "cert.remote_barrier id " << id
221 << " occurrences disagree on party_mask";
222 result = failure();
223 continue;
224 }
225
226 // Participating uCs (from occurrences); own-bit + one-per-uC checks.
227 std::map<int, int> occPerUc;
228 for (auto b : occs) {
229 int own = getUcGroupId(b);
230 occPerUc[own]++;
231 if (own >= 0 && own < 32 && !((mask >> own) & 1u)) {
232 b.emitError() << "cert.remote_barrier id " << id << " on uC " << own
233 << " excludes its own uC from party_mask";
234 result = failure();
235 }
236 }
237 for (auto &[uc, count] : occPerUc)
238 if (count > 1) {
239 occs.front().emitError()
240 << "cert.remote_barrier id " << id << " has " << count
241 << " occurrences on uC " << uc
242 << " (at most one participant per uC per rendezvous)";
243 result = failure();
244 }
245
246 // Every mask bit must be a present uC that has a matching barrier.
247 for (int bit = 0; bit < 32; ++bit) {
248 if (!((mask >> bit) & 1u))
249 continue;
250 if (!present.count(bit)) {
251 occs.front().emitError()
252 << "cert.remote_barrier id " << id << " party_mask references uC "
253 << bit << " which is not present in the design";
254 result = failure();
255 } else if (!occPerUc.count(bit)) {
256 occs.front().emitError()
257 << "cert.remote_barrier id " << id << " party_mask includes uC "
258 << bit << " but that uC has no matching remote_barrier(" << id
259 << ") to rendezvous with";
260 result = failure();
261 }
262 }
263 }
264 return result;
265}
266
267// G-barlimits: local_barrier_id must be in [0, 15] and remote_barrier_id in
268// [0, 6], matching the assembler operands $rb0..$rb6; a single uC may
269// use at most 16 distinct local ids and 7 distinct
270// remote ids (the firmware barrier-id budget per microcontroller).
271static LogicalResult checkBarrierLimits(AIE::DeviceOp device) {
272 LogicalResult result = success();
273 std::map<int, std::set<int>> localIds;
274 std::map<int, std::set<int>> remoteIds;
275
276 device.walk([&](AIEX::CertLocalBarrierOp barrier) {
277 int id = barrier.getLocalBarrierId();
278 if (id < 0 || id > 15) {
279 barrier.emitError() << "cert.local_barrier local_barrier_id " << id
280 << " out of range [0, 15]";
281 result = failure();
282 }
283 localIds[getUcGroupId(barrier)].insert(id);
284 });
285
286 device.walk([&](AIEX::CertRemoteBarrierOp barrier) {
287 int id = barrier.getRemoteBarrierId();
288 if (id < 0 || id > 6) {
289 barrier.emitError() << "cert.remote_barrier remote_barrier_id " << id
290 << " out of range [0, 6]";
291 result = failure();
292 }
293 remoteIds[getUcGroupId(barrier)].insert(id);
294 });
295
296 for (auto &[uc, ids] : localIds)
297 if (ids.size() > 16) {
298 device.emitError() << "uC " << uc << " uses " << ids.size()
299 << " distinct local_barrier ids (max 16)";
300 result = failure();
301 }
302 for (auto &[uc, ids] : remoteIds)
303 if (ids.size() > 7) {
304 device.emitError() << "uC " << uc << " uses " << ids.size()
305 << " distinct remote_barrier ids (max 7)";
306 result = failure();
307 }
308 return result;
309}
310
311struct AIECertVerifyPass
312 : xilinx::AIEX::impl::AIECertVerifyBase<AIECertVerifyPass> {
313 void runOnOperation() override {
314 AIE::DeviceOp device = getOperation();
315 if (failed(checkGroupIds(device)))
316 signalPassFailure();
317 if (failed(checkLocalBarrierColocation(device)))
318 signalPassFailure();
319 if (failed(checkWaitTctsUniqueness(device)))
320 signalPassFailure();
321 if (failed(checkPreemptConsistency(device)))
322 signalPassFailure();
323 if (failed(checkRemoteBarrierParties(device)))
324 signalPassFailure();
325 if (failed(checkBarrierLimits(device)))
326 signalPassFailure();
327 }
328};
329
330} // namespace
331
332std::unique_ptr<OperationPass<AIE::DeviceOp>> AIEX::createAIECertVerifyPass() {
333 return std::make_unique<AIECertVerifyPass>();
334}
virtual uint32_t getNumControllers() const
Total number of microcontrollers (uC) in the device.
std::unique_ptr< mlir::OperationPass< AIE::DeviceOp > > createAIECertVerifyPass()