MLIR-AIE 1.4.5.dev
AIEVerifyRuntimeRearm.cpp
Go to the documentation of this file.
1//===- AIEVerifyRuntimeRearm.cpp --------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7//
8// EXPERIMENTAL, opt-in diagnostic (not in the default pipeline; validate on
9// hardware before relying on it). Rejects an aiex.dma_channel_reset whose
10// objectFIFO locks are not re-armed anywhere in the module. That is a
11// necessary, not sufficient, condition for a safe channel reset.
12//
13// A channel reset drains the DMA task queue and leaves the objectFIFO lock
14// counters frozen; if a bound lock is never re-armed the channel's peer blocks
15// on an acquire that never releases (host: `qds_device::wait() unexpected
16// command state`). This catches the missing-lock-re-arm mistake. It does NOT
17// prove the reset is safe: the complementary start-queue re-push (no
18// runtime-sequence op yet for a resident core/mem channel) is not checked, so a
19// design that re-arms locks but not the queue still deadlocks and still passes.
20//
21// A lock counts as re-armed by an aiex.set_lock. The pass runs before set_lock
22// is lowered, so it matches the semantic op instead of decoding a lowered
23// npu.write32 (which would duplicate the set_lock encoding here). Re-arms are
24// collected module-wide, so a re-arm in a later dispatch's sequence is
25// honoured. Assumes the objectFIFO stateful transform has run (channels and
26// locks materialised).
27//
28//===----------------------------------------------------------------------===//
29
33
34#include "mlir/Pass/Pass.h"
35
36#include "llvm/ADT/STLExtras.h"
37#include "llvm/ADT/SmallPtrSet.h"
38#include "llvm/ADT/SmallVector.h"
39
40#include <array>
41#include <map>
42
43namespace xilinx::AIEX {
44#define GEN_PASS_DEF_AIEVERIFYRUNTIMEREARM
45#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
46} // namespace xilinx::AIEX
47
48using namespace mlir;
49using namespace xilinx;
50using namespace xilinx::AIE;
51using namespace xilinx::AIEX;
52
53namespace {
54
55using ChannelKey = std::array<int, 4>; // {col, row, direction, channel}
56
57struct AIEVerifyRuntimeRearmPass
58 : xilinx::AIEX::impl::AIEVerifyRuntimeRearmBase<AIEVerifyRuntimeRearmPass> {
59
60 // channel -> the objectFIFO locks its BD chain uses, built in one device
61 // walk.
62 FailureOr<std::map<ChannelKey, SmallVector<LockOp>>>
63 mapChannelLocks(DeviceOp dev) {
64 std::map<ChannelKey, SmallVector<LockOp>> m;
65 WalkResult wr = dev.walk([&](DMAStartOp start) -> WalkResult {
66 Operation *memOp = start->getParentOp();
67 TileOp tile;
68 if (auto x = dyn_cast<MemOp>(memOp))
69 tile = x.getTileOp();
70 else if (auto x = dyn_cast<MemTileDMAOp>(memOp))
71 tile = x.getTileOp();
72 if (!tile)
73 return WalkResult::advance();
74 if (start.getEndpoint()) {
75 start.emitOpError("requires a DMA channel index; run "
76 "--aie-objectfifo-allocate before "
77 "--aie-verify-runtime-rearm");
78 return WalkResult::interrupt();
79 }
80 ChannelKey key{tile.getCol(), tile.getRow(),
81 static_cast<int>(start.getChannelDir()),
82 start.getChannelIndex()};
83 SmallVector<LockOp> &locks = m[key];
84 // Walk the BD chain (dest, then next_bd successors); the last next_bd
85 // loops back, so stop on a revisit.
86 llvm::SmallPtrSet<Block *, 8> visited;
87 for (Block *b = start.getDest(); b && visited.insert(b).second;
88 b = (b->getNumSuccessors() == 1 ? b->getSuccessor(0) : nullptr))
89 for (auto use : b->getOps<UseLockOp>())
90 if (auto l = dyn_cast_or_null<LockOp>(use.getLock().getDefiningOp()))
91 if (!llvm::is_contained(locks, l))
92 locks.push_back(l);
93 return WalkResult::advance();
94 });
95 if (wr.wasInterrupted())
96 return failure();
97 return m;
98 }
99
100 void runOnOperation() override {
101 DeviceOp dev = getOperation();
102 auto channelLocks = mapChannelLocks(dev);
103 if (failed(channelLocks)) {
104 signalPassFailure();
105 return;
106 }
107
108 // Locks re-armed anywhere in the module by an aiex.set_lock. Module-wide so
109 // a re-arm in another dispatch's sequence counts. The pass runs before
110 // set_lock is lowered, so matching the op is enough; no need to decode a
111 // lowered npu.write32 to the lock's register.
112 llvm::SmallPtrSet<Operation *, 16> reArmedBySetLock;
113 dev.walk([&](SetLockOp sl) {
114 reArmedBySetLock.insert(sl.getLockOp().getOperation());
115 });
116
117 auto isReArmed = [&](LockOp l) -> bool {
118 return reArmedBySetLock.contains(l.getOperation());
119 };
120
121 WalkResult wr = dev.walk([&](DmaChannelResetOp reset) -> WalkResult {
122 TileOp tile = reset.getTileOp();
123 if (!tile)
124 return WalkResult::advance();
125 ChannelKey key{tile.getCol(), tile.getRow(),
126 static_cast<int>(reset.getDirection()),
127 static_cast<int>(reset.getChannel())};
128 auto it = channelLocks->find(key);
129 if (it == channelLocks->end())
130 return WalkResult::advance();
131 SmallVector<LockOp> frozen;
132 for (LockOp l : it->second)
133 if (!isReArmed(l))
134 frozen.push_back(l);
135 if (frozen.empty())
136 return WalkResult::advance();
137 InFlightDiagnostic err = reset.emitOpError(
138 "resets a DMA channel whose objectFIFO lock is never re-armed; the "
139 "semaphore counter stays frozen and the channel's peer will block "
140 "forever on acquire (a runtime deadlock). Re-arm each bound lock "
141 "with an aiex.set_lock");
142 for (LockOp l : frozen)
143 err.attachNote(l.getLoc())
144 << "this lock is bound to the reset channel and is not re-armed";
145 return WalkResult::interrupt();
146 });
147 if (wr.wasInterrupted())
148 signalPassFailure();
149 }
150};
151
152} // namespace
153
154std::unique_ptr<OperationPass<DeviceOp>>
156 return std::make_unique<AIEVerifyRuntimeRearmPass>();
157}
std::unique_ptr< mlir::OperationPass< AIE::DeviceOp > > createAIEVerifyRuntimeRearmPass()