MLIR-AIE 1.4.5.dev
AIELowerDmaChannelReset.cpp
Go to the documentation of this file.
1//===- AIELowerDmaChannelReset.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/Dialect/Arith/IR/Arith.h"
13#include "mlir/IR/SymbolTable.h"
14#include "mlir/Pass/Pass.h"
15#include "mlir/Transforms/DialectConversion.h"
16
17namespace xilinx::AIEX {
18#define GEN_PASS_DEF_AIELOWERDMACHANNELRESET
19#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
20} // namespace xilinx::AIEX
21
22#define DEBUG_TYPE "aie-lower-dma-channel-reset"
23
24using namespace mlir;
25using namespace xilinx;
26using namespace xilinx::AIE;
27using namespace xilinx::AIEX;
28
29// Expand every aiex.dma_channel_reset_for in `device` into the resident re-arm
30// trio of its objectFIFO -- one aiex.dma_channel_reset + START_QUEUE re-push
31// (aiex.npu.push_queue) per non-shim channel and one aiex.set_lock per
32// producer/consumer lock -- resolved through the fifo's
33// aie.objectfifo_rearm_binding. The head BD id + repeat of each channel are
34// read straight off the binding (populated by --aie-assign-bd-ids), so this
35// does not re-scan the objectFIFO-emitted aie.mem chain. The emitted
36// dma_channel_reset ops are lowered to npu.maskwrite32 by the conversion below
37// in the same pass; the push_queue / set_lock ops are lowered by aie-dma-to-npu
38// / aie-lower-set- lock later in the pipeline, so this pass runs before them.
39static LogicalResult expandDmaChannelResetForOps(DeviceOp device) {
40 SymbolTable symbolTable(device);
41
42 // One resident endpoint resolved from the binding, ready to re-arm.
43 struct ResolvedEndpoint {
44 Value tile;
45 DMAChannelDir dir;
46 int channel;
47 int col;
48 int row;
49 uint32_t headBdId;
50 int32_t
51 repeatCount; // already the N-1 biased value on the resident dma_start
52 };
53
54 SmallVector<DmaChannelResetForOp> toErase;
55 WalkResult res = device.walk([&](DmaChannelResetForOp op) -> WalkResult {
56 auto binding =
57 symbolTable.lookup<ObjectFifoRearmBindingOp>(op.getObjfifo());
58 if (!binding) {
59 op.emitOpError("could not resolve '")
60 << op.getObjfifo()
61 << "' to an aie.objectfifo_rearm_binding; run "
62 "--aie-objectFifo-stateful-transform first";
63 return WalkResult::interrupt();
64 }
65
66 ArrayRef<int32_t> dirs = binding.getChannelDirs();
67 ArrayRef<int32_t> chans = binding.getChannelIndices();
68 ValueRange tiles = binding.getChannelTiles();
69
70 // The per-channel head BD id + repeat live on the binding, put there by
71 // --aie-assign-bd-ids (or set directly on a hand-authored binding). A
72 // binding still missing them cannot be re-pushed: either that pass has not
73 // run, or it could not resolve an endpoint's resident DMA channel.
74 std::optional<ArrayRef<int32_t>> headBdIds = binding.getHeadBdIds();
75 std::optional<ArrayRef<int32_t>> repeats = binding.getRepeatCounts();
76 if (!headBdIds || !repeats) {
77 op.emitOpError("objectFIFO re-arm binding '")
78 << op.getObjfifo()
79 << "' has no head_bd_ids/repeat_counts; run --aie-assign-bd-ids "
80 "before this pass, or set them on the binding";
81 return WalkResult::interrupt();
82 }
83
84 OpBuilder builder(op);
85 Location loc = op.getLoc();
86 MLIRContext *ctx = builder.getContext();
87
88 // Resolve every endpoint up front so a failure aborts before we emit
89 // anything for this op.
90 SmallVector<ResolvedEndpoint> endpoints;
91 for (unsigned i = 0; i < tiles.size(); ++i) {
92 auto tileOp = tiles[i].getDefiningOp<TileOp>();
93 if (!tileOp) {
94 op.emitOpError("re-arm binding channel tile is not an aie.tile");
95 return WalkResult::interrupt();
96 }
97 DMAChannelDir dir =
98 dirs[i] == 0 ? DMAChannelDir::S2MM : DMAChannelDir::MM2S;
99 ResolvedEndpoint ep;
100 ep.tile = tiles[i];
101 ep.dir = dir;
102 ep.channel = chans[i];
103 ep.col = tileOp.getCol();
104 ep.row = tileOp.getRow();
105 ep.headBdId = static_cast<uint32_t>((*headBdIds)[i]);
106 ep.repeatCount = (*repeats)[i];
107 endpoints.push_back(ep);
108 }
109
110 // Guard the lock operands (the verifier already requires them to be
111 // aie.lock, but stay defensive so a malformed binding cannot make the
112 // set_lock builder cast a non-lock operand and abort).
113 for (Value lock : binding.getLocks())
114 if (!lock.getDefiningOp<AIE::LockOp>()) {
115 op.emitOpError("re-arm binding lock operand is not an aie.lock");
116 return WalkResult::interrupt();
117 }
118
119 // The re-arm, ordered so a channel is only restarted once its locks are
120 // re-armed:
121 // 1. reset every channel (drain the queue, clear the run FSM),
122 // 2. re-arm the fifo locks to their init values,
123 // 3. re-push each channel's start queue (required on aie2p: a DMA
124 // channel has no enable bit, so the only way to restart it is a
125 // START_QUEUE write).
126
127 // 1. dma_channel_reset per endpoint (lowered to maskwrite32 below).
128 for (const ResolvedEndpoint &ep : endpoints)
129 DmaChannelResetOp::create(builder, loc, ep.tile,
130 DMAChannelDirAttr::get(ctx, ep.dir),
131 builder.getI32IntegerAttr(ep.channel));
132
133 // 2. set_lock per lock, to the value the fifo was armed with.
134 ValueRange locks = binding.getLocks();
135 ArrayRef<int32_t> lockInits = binding.getLockInits();
136 for (unsigned j = 0; j < locks.size(); ++j)
137 SetLockOp::create(builder, loc, locks[j],
138 createConstantI32(builder, loc, lockInits[j]));
139
140 // 3. START_QUEUE re-push per endpoint, emitted as aiex.npu.push_queue so
141 // the command-word encoding, the queue address, and the bd_id/repeat range
142 // checks all live in one place -- the aie-dma-to-npu lowering and
143 // NpuPushQueueOp::verify. The token bit is only set for a shim S2MM
144 // channel; these endpoints are core/mem, so it is always false.
145 for (const ResolvedEndpoint &ep : endpoints) {
146 bool issueToken = ep.row == 0 && ep.dir == DMAChannelDir::S2MM;
147 // Materialize the operands in fixed statements before the create() so
148 // their emission order is deterministic (function-argument evaluation
149 // order is unspecified in C++).
150 Value repeatVal = createConstantI32(
151 builder, loc, static_cast<uint32_t>(ep.repeatCount));
152 Value bdVal = createConstantI32(builder, loc, ep.headBdId);
153 NpuPushQueueOp::create(builder, loc, builder.getI32IntegerAttr(ep.col),
154 builder.getI32IntegerAttr(ep.row),
155 DMAChannelDirAttr::get(ctx, ep.dir),
156 builder.getI32IntegerAttr(ep.channel),
157 builder.getBoolAttr(issueToken), repeatVal, bdVal,
158 /*repeat_state_table_idx=*/nullptr);
159 }
160
161 toErase.push_back(op);
162 return WalkResult::advance();
163 });
164
165 if (res.wasInterrupted())
166 return failure();
167
168 for (DmaChannelResetForOp op : toErase)
169 op.erase();
170
171 // Drop re-arm bindings that are now unreferenced (their reset_for users were
172 // lowered). They are device-body metadata like aie.shim_dma_allocation;
173 // erasing the dead ones keeps the lowered module clean.
174 SmallVector<ObjectFifoRearmBindingOp> deadBindings;
175 for (auto binding : device.getOps<ObjectFifoRearmBindingOp>())
176 if (SymbolTable::symbolKnownUseEmpty(binding.getSymNameAttr(), device))
177 deadBindings.push_back(binding);
178 for (ObjectFifoRearmBindingOp binding : deadBindings)
179 binding.erase();
180
181 return success();
182}
183
185 : OpConversionPattern<DmaChannelResetOp> {
186 using OpConversionPattern<DmaChannelResetOp>::OpConversionPattern;
187
189 : OpConversionPattern(context) {}
190
191 LogicalResult
192 matchAndRewrite(DmaChannelResetOp op, OpAdaptor adaptor,
193 ConversionPatternRewriter &rewriter) const override {
194 AIE::DeviceOp dev = op->getParentOfType<AIE::DeviceOp>();
195 const AIE::AIETargetModel &tm = dev.getTargetModel();
196
197 AIE::TileOp tile = op.getTileOp();
198 int col = tile.getCol();
199 int row = tile.getRow();
200 int channel = op.getChannel();
201 AIE::DMAChannelDir dir = op.getDirection();
202
203 // NpuMaskWrite32Op re-folds col/row, so pass the local offset + col/row, as
204 // AIELowerSetLock does for the lock address.
205 uint32_t ctrlAddrLocal =
206 tm.getLocalDmaControlAddress(col, row, channel, dir);
207
208 // Resolve the channel's reset bit from the register database rather than
209 // restating the layout here. The DMA control registers live in a tile's
210 // memory module (mem tiles resolve to their own module regardless), so the
211 // lookup is isMem; shim tiles are rejected by the verifier.
212 std::string ctrlRegName =
213 (dir == AIE::DMAChannelDir::S2MM ? "DMA_S2MM_" : "DMA_MM2S_") +
214 std::to_string(channel) + "_Ctrl";
215 const AIE::RegisterInfo *ctrlReg =
216 tm.lookupRegister(ctrlRegName, tile.getTileID(), /*isMem=*/true);
217 if (!ctrlReg)
218 return op.emitOpError("target has no ")
219 << ctrlRegName << " register in its register database";
220 const AIE::BitFieldInfo *resetField = ctrlReg->getField("Reset");
221 if (!resetField)
222 return op.emitOpError()
223 << ctrlRegName << " has no Reset field in the register database";
224 std::optional<uint32_t> resetMask = tm.getFieldMask(*resetField);
225 if (!resetMask)
226 return op.emitOpError()
227 << ctrlRegName << " Reset field does not fit in a 32-bit register";
228 std::optional<uint32_t> resetValue = tm.encodeFieldValue(*resetField, 1);
229 if (!resetValue)
230 return op.emitOpError()
231 << ctrlRegName
232 << " Reset field value does not fit in its bit width";
233
234 Location loc = op.getLoc();
235 IntegerAttr colAttr = rewriter.getI32IntegerAttr(col);
236 IntegerAttr rowAttr = rewriter.getI32IntegerAttr(row);
237
238 // Reset pulse: assert the reset bit, then clear it. Both writes mask to the
239 // reset bit only, so the pulse preserves the other CTRL fields
240 // (DECOMPRESSION_ENABLE, ENABLE_OUT_OF_ORDER, CONTROLLER_ID, FOT_MODE)
241 // instead of clobbering them. This mirrors aie-rt's XAie_DmaChannelReset,
242 // which drives the reset bit with a MaskWrite32. Constants are materialized
243 // in named locals so the emitted IR order does not depend on unspecified
244 // C++ argument-evaluation order.
245 Value assertAddr = createConstantI32(rewriter, loc, ctrlAddrLocal);
246 Value assertVal = createConstantI32(rewriter, loc, *resetValue);
247 Value assertMask = createConstantI32(rewriter, loc, *resetMask);
248 NpuMaskWrite32Op::create(rewriter, loc, assertAddr, assertVal, assertMask,
249 nullptr, colAttr, rowAttr);
250
251 Value clearAddr = createConstantI32(rewriter, loc, ctrlAddrLocal);
252 Value clearVal = createConstantI32(rewriter, loc, 0u);
253 Value clearMask = createConstantI32(rewriter, loc, *resetMask);
254 rewriter.replaceOpWithNewOp<NpuMaskWrite32Op>(
255 op, clearAddr, clearVal, clearMask, nullptr, colAttr, rowAttr);
256
257 return success();
258 };
259};
260
263 AIELowerDmaChannelResetPass> {
264 void runOnOperation() override {
265 DeviceOp device = getOperation();
266
267 // First expand any aiex.dma_channel_reset_for into its re-arm trio; the
268 // dma_channel_reset ops it emits are then lowered by the conversion below,
269 // together with any standalone ones.
270 if (failed(expandDmaChannelResetForOps(device)))
271 return signalPassFailure();
272
273 ConversionTarget target(getContext());
274 target.addLegalOp<NpuMaskWrite32Op>();
275 target.addLegalDialect<arith::ArithDialect>();
276 target.addIllegalOp<DmaChannelResetOp>();
277
278 RewritePatternSet patterns(&getContext());
279 patterns.add<DmaChannelResetToMaskWrite32Pattern>(&getContext());
280
281 SmallVector<Operation *> roots;
282 device.walk([&](DmaChannelResetOp op) { roots.push_back(op); });
283 if (failed(applyPartialConversion(roots, target, std::move(patterns))))
284 signalPassFailure();
285 }
286};
287
288std::unique_ptr<OperationPass<DeviceOp>>
290 return std::make_unique<AIELowerDmaChannelResetPass>();
291}
uint32_t getLocalDmaControlAddress(int col, int row, int channel, AIE::DMAChannelDir direction) const
Return the DMA task-queue register address relative to its tile.
mlir::Value createConstantI32(mlir::OpBuilder &builder, mlir::Location loc, uint32_t value)
std::unique_ptr< mlir::OperationPass< AIE::DeviceOp > > createAIELowerDmaChannelResetPass()
LogicalResult matchAndRewrite(DmaChannelResetOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
Bit field information for a register.
const BitFieldInfo * getField(llvm::StringRef fieldName) const