11#include "mlir/Dialect/SCF/IR/SCF.h"
12#include "mlir/Pass/Pass.h"
13#include "llvm/ADT/SetVector.h"
23#define GEN_PASS_DEF_AIEOBJECTFIFOVERIFY
24#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
30 SmallVector<Operation *> fillers;
31 SmallVector<Operation *> drainers;
36struct TransferBudget {
37 ObjectFifoDmaEndpointOp endpoint;
46static constexpr int64_t kRunsForever = (int64_t{1} << 24) - 2;
48static std::optional<int64_t> getStaticTripCount(scf::ForOp forOp) {
49 if (forOp.getSingleLowerBound() && forOp.getSingleUpperBound() &&
50 forOp.getSingleStep()) {
51 if (std::optional<llvm::APInt> tc = forOp.getStaticTripCount()) {
52 return tc->getSExtValue();
62static std::optional<int64_t> staticExecutions(Operation *op) {
64 for (Operation *cur = op; !isa<CoreOp>(cur); cur = cur->getParentOp()) {
65 if (cur->getBlock() != &cur->getParentRegion()->front()) {
68 Operation *parent = cur->getParentOp();
69 if (isa<CoreOp>(parent)) {
72 auto forOp = dyn_cast<scf::ForOp>(parent);
76 std::optional<int64_t> trip = getStaticTripCount(forOp);
77 if (!trip || *trip >= kRunsForever ||
78 (*trip != 0 && count > std::numeric_limits<int64_t>::max() / *trip)) {
86struct AIEObjectFifoVerifyPass
87 :
public xilinx::AIE::impl::AIEObjectFifoVerifyBase<
88 AIEObjectFifoVerifyPass> {
94 LogicalResult verifyActors(ObjectFifoPoolOp pool,
95 ArrayRef<SegmentActors> actors) {
96 bool external = llvm::any_of(pool.getBufferOps(), [](BufferLike buffer) {
97 return isa<ExternalBufferOp>(buffer.getOperation());
99 std::vector<ObjectFifoSegmentOp> segmentOps = pool.getSegmentOps();
100 for (
auto [index, segment] : llvm::enumerate(actors)) {
101 ObjectFifoSegmentOp segmentOp = segmentOps[index];
102 bool manualFill = segmentOp.getManualFill();
103 bool manualDrain = segmentOp.getManualDrain();
104 if (segment.fillers.size() > 1 ||
105 (manualFill && !segment.fillers.empty())) {
106 return pool.emitOpError(
"segment ")
107 << index <<
" is filled by more than one endpoint";
109 if (segment.drainers.size() > 1 ||
110 (manualDrain && !segment.drainers.empty())) {
111 return pool.emitOpError(
"segment ")
112 << index <<
" is drained by more than one endpoint";
116 bool hostDrains = external && !segment.fillers.empty();
117 bool hostFills = external && !segment.drainers.empty();
118 if (segment.drainers.empty() && !hostDrains && !manualDrain) {
119 return pool.emitOpError(
"segment ") << index <<
" has no drainer";
121 if (segment.fillers.empty() && !pool.getInitValues() && !hostFills &&
123 return pool.emitOpError(
"segment ") << index <<
" has no filler";
130 LogicalResult verifyFlows(DeviceOp device,
131 SymbolTableCollection &symbolTables) {
132 DenseMap<StringRef, int> appearances;
133 for (
auto flow : device.getOps<RouteOp>()) {
134 for (StringRef source : flow.getSourceNames()) {
135 appearances[source]++;
137 for (StringRef dest : flow.getDestinationNames()) {
142 for (
auto endpoint : device.getOps<RouteEndpoint>()) {
144 appearances.lookup(cast<SymbolOpInterface>(*endpoint).getName());
146 return endpoint->emitOpError(
"is not connected by any flow");
148 if (
auto dma = dyn_cast<ObjectFifoDmaEndpointOp>(*endpoint);
149 dma && dma.getDispatch()) {
150 if (failed(verifyDispatch(device, dma, symbolTables)))
155 if (count > 1 && !isa<RouteEndpointOp>(endpoint.getOperation())) {
156 return endpoint->emitOpError(
157 "drives one channel, so at most one flow may name it, but "
159 << count <<
" times";
168 LogicalResult verifyDispatch(DeviceOp device,
169 ObjectFifoDmaEndpointOp endpoint,
170 SymbolTableCollection &symbolTables) {
171 StringRef name = endpoint.getSymName();
172 llvm::SmallSetVector<StringRef, 4> turns;
173 for (StringRef dest :
174 endpoint.getDispatchAttr().getAsValueRange<FlatSymbolRefAttr>())
176 llvm::SmallDenseSet<StringRef, 4> reached;
177 for (
auto flow : device.getOps<RouteOp>()) {
178 if (flow.namesAsDestination(name))
179 return flow.emitOpError(
"names dispatching endpoint @")
180 << name <<
" as a destination";
181 if (!flow.namesAsSource(name))
183 if (flow.getSources().size() != 1 || flow.getDestinations().size() != 1)
184 return flow.emitOpError(
"leaves dispatching endpoint @")
186 <<
" for one destination, so it has one source and "
188 if (!flow.getPacket())
189 return flow.emitOpError(
"leaves dispatching endpoint @")
190 << name <<
" and needs a packet header to tell its turns apart";
191 StringRef dest = *flow.getDestinationNames().begin();
192 if (!turns.contains(dest))
193 return flow.emitOpError(
"reaches @")
194 << dest <<
", which dispatching endpoint @" << name
195 <<
" never takes a turn for";
196 reached.insert(dest);
198 for (StringRef dest : turns)
199 if (!reached.contains(dest))
200 return endpoint.emitOpError(
"dispatches to @")
201 << dest <<
", but no route from it reaches there";
206 int64_t elements = transferElements(endpoint);
207 for (StringRef dest : turns) {
208 auto destEndpoint = symbolTables.lookupSymbolIn<ObjectFifoDmaEndpointOp>(
209 device, StringAttr::get(device.getContext(), dest));
212 int64_t destElements = transferElements(destEndpoint);
213 if (destElements != elements)
214 return endpoint.emitOpError(
"dispatches, so every turn moves ")
215 << elements <<
" elements, but @" << dest <<
" moves "
223 static int64_t transferElements(ObjectFifoDmaEndpointOp endpoint) {
224 int64_t elements = 0;
225 std::optional<ArrayRef<BDDimLayoutArrayAttr>> dimensions =
226 endpoint.getDimensions();
227 for (
auto [index, segment] :
228 llvm::enumerate(endpoint.getSelectedSegments())) {
229 BDDimLayoutArrayAttr dims;
230 if (dimensions && index < dimensions->size()) {
231 dims = (*dimensions)[index];
233 if (dims && !dims.empty()) {
235 for (BDDimLayoutAttr dim : dims) {
236 product *= dim.getSize();
240 elements += segment.getSize();
250 LogicalResult verifyFanIn(DeviceOp device,
251 SymbolTableCollection &symbolTables) {
252 for (
auto flow : device.getOps<RouteOp>()) {
253 if (!flow.isFanIn()) {
256 std::optional<std::pair<StringRef, int64_t>> first;
257 auto check = [&](StringRef name) -> LogicalResult {
258 auto endpoint = symbolTables.lookupSymbolIn<ObjectFifoDmaEndpointOp>(
259 device, StringAttr::get(device.getContext(), name));
263 int64_t elements = transferElements(endpoint);
265 first = {name, elements};
268 if (elements != first->second) {
269 return flow.emitOpError(
"fans in, so every end moves one object of "
270 "the same size, but @")
271 << first->first <<
" moves " << first->second
272 <<
" elements and @" << name <<
" moves " << elements;
276 for (StringRef name : flow.getSourceNames()) {
277 if (failed(check(name))) {
281 for (StringRef name : flow.getDestinationNames()) {
282 if (failed(check(name))) {
295 LogicalResult verifyOverRelease(DeviceOp device) {
296 for (
auto coreOp : device.getOps<CoreOp>()) {
297 WalkResult result = coreOp.walk([&](Operation *loop) {
299 return WalkResult::advance();
301 auto directlyIn = [&](Operation *op) {
304 DenseMap<StringRef, int64_t> acquired;
305 DenseMap<StringRef, int64_t> released;
306 DenseMap<StringRef, Operation *> blame;
307 DenseSet<StringRef> spansNestedLoop;
309 loop->walk([&](ObjectFifoAcquireOp a) {
310 StringRef key = a.getObjFifoName();
311 if (!directlyIn(a)) {
312 spansNestedLoop.insert(key);
314 acquired[key] += a.acqNumber();
315 blame.try_emplace(key, a);
318 loop->walk([&](ObjectFifoReleaseOp r) {
319 StringRef key = r.getObjFifoName();
320 if (!directlyIn(r)) {
321 spansNestedLoop.insert(key);
323 released[key] += r.relNumber();
324 blame.try_emplace(key, r);
328 for (
auto &[key, count] : released) {
329 if (spansNestedLoop.contains(key) || count <= acquired.lookup(key)) {
332 blame.lookup(key)->emitOpError(
333 "cannot release more elements than are already acquired");
334 return WalkResult::interrupt();
336 return WalkResult::advance();
338 if (result.wasInterrupted()) {
351 LogicalResult verifyTransferBudgets(DeviceOp device) {
352 DenseMap<Operation *, TransferBudget> budgets;
353 for (
auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>()) {
354 std::optional<int32_t> iterations = endpoint.getIterCount();
355 ObjectFifoPoolOp pool = endpoint.getPoolOp();
356 if (!iterations || !pool) {
359 budgets[pool] = {endpoint, *iterations,
360 int64_t{*iterations} * pool.getDepth()};
362 if (budgets.empty()) {
366 DenseMap<StringRef, int64_t> released;
367 DenseMap<StringRef, ObjectFifoReleaseOp> blame;
368 device.walk([&](ObjectFifoReleaseOp release) {
369 std::optional<int64_t> runs = staticExecutions(release);
373 StringRef key = release.getObjFifoName();
374 released[key] += *runs * release.relNumber();
375 blame.try_emplace(key, release);
378 for (
auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>()) {
380 auto budget = budgets.find(endpoint.getPoolOp());
381 if (budget == budgets.end() || endpoint.getSegments()) {
384 StringRef name = endpoint.getSymName();
385 int64_t count = released.lookup(name);
386 if (count <= budget->second.objects) {
389 ObjectFifoDmaEndpointOp dma = budget->second.endpoint;
390 InFlightDiagnostic diag =
391 blame.lookup(name).emitOpError(
"releases ")
392 << count <<
" objects through @" << name
393 <<
" over the run, but the pool's DMA endpoint stops after "
394 << budget->second.objects <<
" (iterCount "
395 << budget->second.iterations <<
" x depth "
396 << endpoint.getPoolOp().getDepth()
397 <<
"), so the core would wait forever for the rest";
398 diag.attachNote(dma.getLoc()) <<
"the DMA endpoint is here";
404 void runOnOperation()
override {
405 DeviceOp device = getOperation();
407 DenseMap<Operation *, SmallVector<SegmentActors>> actorsPerPool;
408 for (
auto pool : device.getOps<ObjectFifoPoolOp>()) {
409 actorsPerPool[pool].resize(pool.getSegmentOps().size());
412 auto record = [&](
auto endpoint) {
413 ObjectFifoPoolOp pool = endpoint.getPoolOp();
417 auto &actors = actorsPerPool[pool];
418 std::vector<ObjectFifoSegmentOp> all = pool.getSegmentOps();
419 for (ObjectFifoSegmentOp segment : endpoint.getSelectedSegments()) {
420 size_t index = llvm::find(all, segment) - all.begin();
421 (endpoint.drains() ? actors[index].drainers : actors[index].fillers)
422 .push_back(endpoint);
426 for (
auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>()) {
429 for (
auto endpoint : device.getOps<ObjectFifoDmaEndpointOp>()) {
433 for (
auto pool : device.getOps<ObjectFifoPoolOp>()) {
434 if (failed(verifyActors(pool, actorsPerPool[pool]))) {
435 return signalPassFailure();
439 SymbolTableCollection symbolTables;
440 if (failed(verifyFlows(device, symbolTables)) ||
441 failed(verifyFanIn(device, symbolTables)) ||
442 failed(verifyOverRelease(device)) ||
443 failed(verifyTransferBudgets(device))) {
444 return signalPassFailure();
451std::unique_ptr<OperationPass<DeviceOp>>
453 return std::make_unique<AIEObjectFifoVerifyPass>();
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEObjectFifoVerifyPass()
mlir::Operation * getObjectFifoLoopParent(mlir::Operation *op)
The innermost loop around op that isObjectFifoLoop accepts, or null.
bool isObjectFifoLoop(mlir::Operation *op)
Whether op is a loop that objectFifo lowering unrolls by the rotation period of the objectFifos acces...