MLIR-AIE 1.4.5.dev
AIEUtils.cpp
Go to the documentation of this file.
1//===- AIEUtils.cpp ---------------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2025 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
10#include "mlir/Dialect/Utils/StaticValueUtils.h"
11#include "mlir/Interfaces/ControlFlowInterfaces.h"
12#include "llvm/ADT/SmallPtrSet.h"
13
14using namespace mlir;
15using namespace xilinx;
16
17std::optional<AIEX::SubviewTraceResult>
19 int64_t offsetInBytes = 0;
20 Value current = value;
21
22 // Walk through the chain of operations until we reach a block argument
23 while (current) {
24 // Check if we've reached a block argument
25 if (auto blockArg = dyn_cast<BlockArgument>(current)) {
26 return SubviewTraceResult{blockArg, offsetInBytes};
27 }
28
29 Operation *defOp = current.getDefiningOp();
30 if (!defOp) {
31 return std::nullopt;
32 }
33
34 // Handle memref.cast (just pass through)
35 if (auto castOp = dyn_cast<memref::CastOp>(defOp)) {
36 current = castOp.getSource();
37 continue;
38 }
39
40 // Handle memref.reinterpret_cast (validate and pass through)
41 if (auto reinterpretOp = dyn_cast<memref::ReinterpretCastOp>(defOp)) {
42 auto sourceType =
43 dyn_cast<MemRefType>(reinterpretOp.getSource().getType());
44 if (!sourceType) {
45 return std::nullopt;
46 }
47
48 // Validate that source is contiguous (all strides must be 1)
49 if (auto strided = dyn_cast<StridedLayoutAttr>(sourceType.getLayout())) {
50 for (int64_t stride : strided.getStrides()) {
51 if (stride != 1) {
52 return std::nullopt; // Non-contiguous memory, cannot safely
53 // reinterpret
54 }
55 }
56 }
57
58 current = reinterpretOp.getSource();
59 continue;
60 }
61
62 // Handle memref.subview. Accepts any source rank; byte-offset delta is
63 // (resultOffset - sourceOffset) from the strided layouts. The result
64 // slice must remain row-major contiguous so callers can treat it as
65 // linear from the returned base offset.
66 if (auto subviewOp = dyn_cast<memref::SubViewOp>(defOp)) {
67 auto sourceType = subviewOp.getSourceType();
68 auto resultType = subviewOp.getType();
69
70 if (llvm::any_of(subviewOp.getStaticOffsets(), ShapedType::isDynamic) ||
71 llvm::any_of(subviewOp.getStaticSizes(), ShapedType::isDynamic) ||
72 llvm::any_of(subviewOp.getStaticStrides(), ShapedType::isDynamic))
73 return std::nullopt;
74
75 // No skipping in source.
76 if (llvm::any_of(subviewOp.getStaticStrides(),
77 [](int64_t s) { return s != 1; }))
78 return std::nullopt;
79
80 unsigned elemSizeInBits =
81 sourceType.getElementType().getIntOrFloatBitWidth();
82 if (elemSizeInBits % 8 != 0)
83 return std::nullopt;
84
85 llvm::SmallVector<int64_t> srcStrides, resStrides;
86 int64_t srcOff, resOff;
87 if (failed(sourceType.getStridesAndOffset(srcStrides, srcOff)) ||
88 failed(resultType.getStridesAndOffset(resStrides, resOff)))
89 return std::nullopt;
90 if (srcOff == ShapedType::kDynamic || resOff == ShapedType::kDynamic)
91 return std::nullopt;
92
93 // Result must be row-major contiguous: innermost stride == 1, and each
94 // outer stride equals the product of all inner sizes. (Size-1 dims are
95 // free: their stride is never stepped.) Without this, e.g. a column
96 // slice of a 2D row-major memref would be accepted and patched as if
97 // linear.
98 ArrayRef<int64_t> resShape = resultType.getShape();
99 if (!resShape.empty()) {
100 if (resStrides.back() != 1)
101 return std::nullopt;
102 uint64_t product = 1;
103 for (int d = static_cast<int>(resShape.size()) - 1; d > 0; --d) {
104 product *= static_cast<uint64_t>(resShape[d]);
105 if (resShape[d - 1] > 1 &&
106 static_cast<uint64_t>(resStrides[d - 1]) != product)
107 return std::nullopt;
108 }
109 }
110
111 offsetInBytes += (resOff - srcOff) * (elemSizeInBits / 8);
112
113 current = subviewOp.getSource();
114 continue;
115 }
116
117 // Handle memref.view. The fused full-ELF path materializes every DMA
118 // buffer as a typed, contiguous view sliced out of a single flat byte
119 // arena (a runtime-sequence block argument) at a constant byte offset:
120 // %v = memref.view %arena[%byte_off][] : memref<Nxi8> to memref<...>
121 // A memref.view result always has an identity (contiguous) layout, so only
122 // the byte offset needs to be accumulated before following the source.
123 if (auto viewOp = dyn_cast<memref::ViewOp>(defOp)) {
124 if (!viewOp.getSizes().empty())
125 return std::nullopt; // dynamic result sizes unsupported
126 std::optional<int64_t> byteShift =
127 getConstantIntValue(viewOp.getByteShift());
128 if (!byteShift)
129 return std::nullopt; // non-constant byte offset
130 offsetInBytes += *byteShift;
131 current = viewOp.getSource();
132 continue;
133 }
134
135 // Encountered an unsupported operation
136 return std::nullopt;
137 }
138
139 return std::nullopt;
140}
141
142std::optional<unsigned> AIEX::getHostBufferArgIndex(BlockArgument arg) {
143 if (!isa<BaseMemRefType>(arg.getType()))
144 return std::nullopt;
145 unsigned index = 0;
146 for (BlockArgument other : arg.getOwner()->getArguments()) {
147 if (other == arg)
148 return index;
149 if (isa<BaseMemRefType>(other.getType()))
150 index++;
151 }
152 return std::nullopt;
153}
154
155unsigned AIEX::getHostBufferCount(Block &block) {
156 return llvm::count_if(block.getArguments(), [](BlockArgument arg) {
157 return isa<BaseMemRefType>(arg.getType());
158 });
159}
160
161AIEX::BlockwriteData::BlockwriteData(AIE::DeviceOp dev, StringRef prefix)
162 : dev(dev), prefix(prefix) {
163 for (Operation &op : dev.getBody()->getOperations()) {
164 if (auto global = dyn_cast<memref::GlobalOp>(op))
165 if (auto initVal = global.getInitialValue();
166 initVal && global.getConstant())
167 byValue.try_emplace(*initVal, global);
168 auto symName =
169 op.getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName());
170 if (!symName)
171 continue;
172 StringRef suffix = symName.getValue();
173 unsigned idx;
174 if (suffix.consume_front(prefix) && !suffix.getAsInteger(10, idx) &&
175 idx >= nextId)
176 nextId = idx + 1;
177 }
178}
179
180memref::GlobalOp AIEX::BlockwriteData::getOrCreate(OpBuilder &builder,
181 Location loc,
182 ArrayRef<uint32_t> words) {
183 int64_t numWords = words.size();
184 MemRefType memrefType = MemRefType::get({numWords}, builder.getI32Type());
185 TensorType tensorType =
186 RankedTensorType::get({numWords}, builder.getI32Type());
187 auto initVal = DenseElementsAttr::get<uint32_t>(tensorType, words);
188
189 // The attribute is uniqued and carries the shape, but not the memref's
190 // layout or memory space.
191 memref::GlobalOp &global = byValue[initVal];
192 if (global && global.getType() == memrefType)
193 return global;
194 global = memref::GlobalOp::create(
195 builder, loc, prefix + std::to_string(nextId++),
196 builder.getStringAttr("private"), memrefType, initVal, true, nullptr);
197 return global;
198}
199
201 OpBuilder &builder, Operation *bdOp, BaseMemRefType bufType,
202 uint64_t registerAddr) {
203 auto idxAttr = bdOp->getAttrOfType<IntegerAttr>("offset_state_table_idx");
204 assert(idxAttr && "emitUpdateBdAddressFromOffsetParameter called without "
205 "offset_state_table_idx attribute");
206
207 uint8_t stateIdx = static_cast<uint8_t>(idxAttr.getUInt());
208 uint32_t elemBytes = bufType.getElementTypeBitWidth() / 8;
209
210 // Use func=mul with func_arg=elemBytes so the firmware computes
211 // StateTable[idx] * elemBytes = byte offset, added into the BD address
212 // register.
213 AIEX::NpuUpdateFromScratchpadOp::create(
214 builder, bdOp->getLoc(), stateIdx, AIEX::StateTableFunc::Mul,
215 /*func_arg=*/elemBytes,
216 /*address=*/static_cast<uint32_t>(registerAddr),
217 /*buffer=*/nullptr, /*column=*/nullptr, /*row=*/nullptr);
218 return success();
219}
220
222 OpBuilder &builder, Operation *bdOp, BaseMemRefType bufType,
223 int64_t lengthUnit, const AIE::AIETargetModel &targetModel, int col,
224 int row, int bdId) {
225 auto idxAttr = bdOp->getAttrOfType<IntegerAttr>("length_state_table_idx");
226 assert(idxAttr && "emitUpdateBdLengthFromParameter called without "
227 "length_state_table_idx attribute");
228
229 if (failed(AIE::verifyLengthParameterTile(bdOp, targetModel, col, row)))
230 return failure();
231 uint64_t registerAddr =
232 targetModel.getDmaBdAddress(col, row, bdId) +
233 4 * targetModel.getDmaBdLayout(col, row)->bufferLength.word;
234
235 uint8_t stateIdx = static_cast<uint8_t>(idxAttr.getUInt());
236 FailureOr<int64_t> unitBytes =
237 AIE::getLengthUnitBytes(bdOp, lengthUnit, bufType);
238 if (failed(unitBytes))
239 return failure();
240
241 // The length register counts 32-bit words, so func=mul adds n * unitBytes / 4
242 // words: func_arg is unitBytes / 4 for an addr parameter (StateTable[idx]
243 // holds n) and unitBytes / 16 for a core parameter (it holds n << 2).
244 int64_t bytesPerStateUnit = bdOp->hasAttr("length_core_encoded") ? 16 : 4;
245 AIEX::NpuUpdateFromScratchpadOp::create(
246 builder, bdOp->getLoc(), stateIdx, AIEX::StateTableFunc::Mul,
247 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
248 /*func_arg=*/static_cast<uint32_t>(*unitBytes / bytesPerStateUnit),
249 /*address=*/static_cast<uint32_t>(registerAddr),
250 /*buffer=*/nullptr, /*column=*/nullptr, /*row=*/nullptr);
251 return success();
252}
253
254std::string AIEX::scratchpadCommandName(AIE::RuntimeSequenceOp seq) {
255 auto device = seq->getParentOfType<AIE::DeviceOp>();
256 return (device.getSymName() + ":" + seq.getSymName()).str();
257}
258
259void AIEX::emitScratchpadParamsFile(ModuleOp moduleOp, llvm::raw_ostream &os) {
260 SmallVector<AIEX::ScratchpadParameterOp> allParams;
261 moduleOp.walk([&](AIEX::ScratchpadParameterOp p) { allParams.push_back(p); });
262
263 unsigned numSlots = 0;
264 for (auto p : allParams)
265 if (auto idx = p.getStateTableIdx())
266 numSlots = std::max(numSlots, static_cast<unsigned>(*idx) + 1);
267 os << allParams.size() << " " << numSlots << "\n";
268 for (auto p : allParams) {
269 std::string typeStr;
270 llvm::raw_string_ostream ts(typeStr);
271 p.getType().print(ts);
272 ts.flush();
273 auto kind = p.getKind();
274 assert(kind && "expected --aie-lower-scratchpad-parameters to have "
275 "assigned every parameter a kind");
276 StringRef kindStr = AIEX::stringifyScratchpadParameterKind(*kind);
277 os << p.getSymName() << " ";
278 if (std::optional<uint8_t> stateTableIdx = p.getStateTableIdx())
279 os << static_cast<unsigned>(*stateTableIdx);
280 else
281 os << "-";
282 os << " " << typeStr << " " << kindStr;
283 auto minValue = p.getMinValue();
284 auto maxValue = p.getMaxValue();
285 if (minValue && maxValue)
286 os << " " << static_cast<int32_t>(*minValue) << " "
287 << static_cast<int32_t>(*maxValue);
288 else
289 os << " - -";
290 os << "\n";
291 }
292
293 SmallVector<std::pair<StringRef, AIEX::JointBoundAttr>> jointBounds;
294 for (auto p : allParams)
295 if (ArrayAttr bounds = p.getJointBoundsAttr())
296 for (auto bound : bounds.getAsRange<AIEX::JointBoundAttr>())
297 jointBounds.push_back({p.getSymName(), bound});
298 os << jointBounds.size() << "\n";
299 for (auto [length, bound] : jointBounds)
300 os << length << " " << bound.getOffset().getValue() << " "
301 << bound.getLengthStep() << " " << bound.getMax() << "\n";
302
303 SmallVector<AIE::RuntimeSequenceOp> commands;
304 moduleOp.walk([&](AIE::RuntimeSequenceOp seq) {
305 if (seq.getScratchpadParametersAttr())
306 commands.push_back(seq);
307 });
308 os << commands.size() << "\n";
309 for (AIE::RuntimeSequenceOp seq : commands) {
310 ArrayAttr reads = seq.getScratchpadParametersAttr();
311 os << scratchpadCommandName(seq) << " " << reads.size();
312 for (auto ref : reads.getAsRange<FlatSymbolRefAttr>())
313 os << " " << ref.getValue();
314 os << "\n";
315 }
316}
317
319 Value task, SmallVectorImpl<DMAConfigureTaskOp> &configures) {
320 bool complete = true;
321 llvm::SmallPtrSet<Value, 8> seen;
322 SmallVector<Value> worklist{task};
323 while (!worklist.empty()) {
324 Value v = worklist.pop_back_val();
325 if (!v || !seen.insert(v).second)
326 continue;
327 if (auto cfg = v.getDefiningOp<DMAConfigureTaskOp>()) {
328 configures.push_back(cfg);
329 continue;
330 }
331
332 Operation *regionBranchOp;
333 if (auto res = dyn_cast<OpResult>(v))
334 regionBranchOp = res.getOwner();
335 else
336 regionBranchOp = cast<BlockArgument>(v).getOwner()->getParentOp();
337 auto rbi = dyn_cast_or_null<RegionBranchOpInterface>(regionBranchOp);
338 if (!rbi) {
339 complete = false;
340 continue;
341 }
342
343 RegionBranchInverseSuccessorMapping mapping;
344 rbi.getSuccessorInputOperandMapping(mapping);
345 auto operands = mapping.lookup(v);
346 if (operands.empty())
347 complete = false;
348 for (OpOperand *operand : operands)
349 worklist.push_back(operand->get());
350 }
351 return complete;
352}
353
354AIEX::DMAConfigureTaskOp AIEX::getUniqueReachableConfigure(Value task) {
355 SmallVector<DMAConfigureTaskOp> configures;
356 if (!getReachableConfigures(task, configures) || configures.size() != 1)
357 return nullptr;
358 return configures.front();
359}
memref::GlobalOp getOrCreate(OpBuilder &builder, mlir::Location loc, ArrayRef< uint32_t > words)
Definition AIEUtils.cpp:180
BlockwriteData(AIE::DeviceOp dev, llvm::StringRef prefix)
Definition AIEUtils.cpp:161
virtual const DmaBdLayout * getDmaBdLayout(AIETileType tileType) const
Return the register layout of a buffer descriptor on the given tile type, or null if the target has n...
virtual uint64_t getDmaBdAddress(int col, int row, uint32_t bd_id, int channel=-1, AIE::DMAChannelDir direction=AIE::DMAChannelDir::MM2S) const =0
Return the array address of the dma buffer descriptor for the given col, row, buffer descriptor id,...
unsigned getHostBufferCount(Block &block)
Definition AIEUtils.cpp:155
DMAConfigureTaskOp getUniqueReachableConfigure(Value task)
Definition AIEUtils.cpp:354
std::optional< SubviewTraceResult > traceSubviewToBlockArgument(Value value)
Definition AIEUtils.cpp:18
std::string scratchpadCommandName(AIE::RuntimeSequenceOp seq)
Definition AIEUtils.cpp:254
void emitScratchpadParamsFile(mlir::ModuleOp moduleOp, llvm::raw_ostream &os)
bool getReachableConfigures(Value task, SmallVectorImpl< DMAConfigureTaskOp > &configures)
Definition AIEUtils.cpp:318
LogicalResult emitUpdateBdAddressFromOffsetParameter(OpBuilder &builder, Operation *bdOp, BaseMemRefType bufType, uint64_t registerAddr)
Definition AIEUtils.cpp:200
LogicalResult emitUpdateBdLengthFromParameter(OpBuilder &builder, Operation *bdOp, BaseMemRefType bufType, int64_t lengthUnit, const AIE::AIETargetModel &targetModel, int col, int row, int bdId)
Definition AIEUtils.cpp:221
std::optional< unsigned > getHostBufferArgIndex(BlockArgument arg)
Definition AIEUtils.cpp:142
mlir::LogicalResult verifyLengthParameterTile(mlir::Operation *op, const AIETargetModel &targetModel, int col, int row)
mlir::FailureOr< int64_t > getLengthUnitBytes(mlir::Operation *op, int64_t lengthUnit, mlir::BaseMemRefType buffer)