MLIR-AIE 1.4.5.dev
AIETargetShared.cpp
Go to the documentation of this file.
1//===- AIETargetShared.cpp --------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2021-2022 Xilinx, Inc.
4// Copyright (C) 2022-2023 Advanced Micro Devices, Inc.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//
8//===----------------------------------------------------------------------===//
9
11
15
16#include "mlir/Target/LLVMIR/Import.h"
17
18#include "llvm/ADT/StringExtras.h"
19
20using namespace mlir;
21using namespace xilinx;
22using namespace xilinx::AIE;
23using namespace xilinx::AIEX;
24
25namespace xilinx::AIE {
26
27std::string tileLocStr(StringRef col, StringRef row) {
28 std::string str;
29 llvm::raw_string_ostream rss(str);
30 rss << "XAie_TileLoc(" << col << "," << row << ")";
31 return str;
32}
33
34std::string tileLocStr(int col, int row) {
35 return tileLocStr(std::to_string(col), std::to_string(row));
36}
37
38std::string tileDMAInstStr(StringRef col, StringRef row, StringRef bdNum) {
39 std::string str;
40 llvm::raw_string_ostream rss(str);
41 rss << "dma_tile" << col << row << "_bd" << bdNum;
42 return str;
43}
44
45std::string tileDMAInstStr(int col, int row, int bdNum) {
46 return tileDMAInstStr(std::to_string(col), std::to_string(row),
47 std::to_string(bdNum));
48}
49
50std::string tileDMAInstRefStr(StringRef col, StringRef row, StringRef bdNum) {
51 std::string str;
52 llvm::raw_string_ostream rss(str);
53 rss << "&(" << tileDMAInstStr(col, row, bdNum) << ")";
54 return str;
55}
56
57std::string tileDMAInstRefStr(int col, int row, int bdNum) {
58 return tileDMAInstRefStr(std::to_string(col), std::to_string(row),
59 std::to_string(bdNum));
60}
61
62std::string packetStr(StringRef id, StringRef type) {
63 std::string str;
64 llvm::raw_string_ostream rss(str);
65 rss << "XAie_PacketInit(" << id << "," << type << ")";
66 return str;
67}
68
69std::string packetStr(int id, int type) {
70 return packetStr(std::to_string(id), std::to_string(type));
71}
72
73static std::string tileDMATensorStr(StringRef col, StringRef row,
74 StringRef bdNum) {
75 std::string str;
76 llvm::raw_string_ostream rss(str);
77 rss << "dma_tile_" << col << "_" << row << "_bd_" << bdNum << "_tensor";
78 return str;
79}
80
81static std::string tileDMATensorStr(int col, int row, int bdNum) {
82 return tileDMATensorStr(std::to_string(col), std::to_string(row),
83 std::to_string(bdNum));
84}
85
86void generateXAieDmaSetMultiDimAddr(raw_ostream &output, int ndims,
87 ArrayRef<BDDimLayoutAttr> dims, int col,
88 int row, int bdNum, int baseAddrA,
89 int64_t offsetA, uint32_t lenA,
90 int elementWidthInBytes,
91 const char *errorRetval) {
92 // libxaie requires stride in multiples of 32b
93 double elementWidthIn32bWords =
94 static_cast<double>(elementWidthInBytes) / 4.0;
95 std::string tensor = tileDMATensorStr(col, row, bdNum);
96 output << "XAie_DmaTensor " << tensor << " = {};\n";
97 output << tensor << ".NumDim = " << std::to_string(ndims) << ";\n";
98 output << tensor
99 << ".Dim ="
100 "__mlir_aie_alloc_dim_desc("
101 << std::to_string(ndims) << ");\n";
102 output << "if(NULL == " << tensor << ".Dim){\n"
103 << " return " << errorRetval << ";\n"
104 << "}\n";
105 for (size_t i = 0; i < dims.size(); i++) {
106 uint16_t size;
107 uint32_t stride;
108 // Pass down dimensions in reverse order; in the MLIR, this allows us
109 // to specify strides/sizes in the same order as we would access a
110 // multi-dim C array, with the highest dimension first.
111 int j = dims.size() - i - 1;
112 if (j > 0) {
113 stride =
114 static_cast<uint32_t>(dims[i].getStride() * elementWidthIn32bWords);
115 size = dims[i].getSize();
116 } else {
117 stride = dims[i].getStride();
118 size = static_cast<uint16_t>(dims[i].getSize() * elementWidthIn32bWords);
119 }
120 stride = stride > 0 ? stride : 1;
121 // Assume AIE-ML architecture; we assert this above
122 output << tensor << ".Dim[" << std::to_string(j) << "].AieMlDimDesc"
123 << " = { /* Stride */ " << std::to_string(stride) << ", /* Size */ "
124 << std::to_string(size) << "};\n";
125 }
126 if ((baseAddrA + offsetA) % 4) {
127 llvm::report_fatal_error("bd address must be 4B (32b) aligned");
128 }
129 output << "__mlir_aie_try(XAie_DmaSetMultiDimAddr("
130 << tileDMAInstRefStr(col, row, bdNum) << ", " << "&" << tensor << ", "
131 << "0x" << llvm::utohexstr(baseAddrA + offsetA) << ", "
132 << " /* len */ " << lenA << "));\n";
133}
134
135// Traverse through a chain of blocks.
136llvm::SetVector<Block *> getOrderedChainOfBlocks(Region *region) {
137 // Get the region's entry block, then start traversing through the chain of
138 // blocks.
139 llvm::SetVector<Block *> blockVector;
140 SmallVector<Block *, 16> worklist;
141 Block *firstBD = &region->front();
142 blockVector.insert(firstBD);
143 worklist.push_back(firstBD);
144 while (!worklist.empty()) {
145 Block *block = worklist.pop_back_val();
146 if (block->empty()) {
147 continue;
148 }
149 auto successors = block->getTerminator()->getSuccessors();
150 for (auto *i : successors) {
151 if (!blockVector.contains(i)) {
152 blockVector.insert(i);
153 worklist.push_back(i);
154 }
155 }
156 }
157 return blockVector;
158}
159
160llvm::SmallPtrSet<Block *, 8>
161collectOutOfOrderBlocks(const llvm::SetVector<Block *> &blockVector) {
162 // Scoped to this channel only.
163 llvm::SmallPtrSet<Block *, 8> channelHeads;
164 for (Block *block : blockVector) {
165 for (auto startOp : block->getOps<DMAStartOp>()) {
166 channelHeads.insert(startOp.getDest());
167 }
168 }
169
170 llvm::SmallPtrSet<Block *, 8> oooBlocks;
171 for (Block *block : blockVector) {
172 for (auto startOp : block->getOps<DMAStartOp>()) {
173 if (!startOp.getOutOfOrder()) {
174 continue;
175 }
176 Block *b = startOp.getDest();
177 while (b && oooBlocks.insert(b).second && b->getNumSuccessors() > 0) {
178 Block *next = b->getSuccessor(0);
179 // Another channel's head or this own channel's head.
180 if (channelHeads.contains(next)) {
181 break;
182 }
183 b = next;
184 }
185 }
186 }
187 return oooBlocks;
188}
189
190std::optional<std::vector<char>>
191denseAttrToBytes(mlir::DenseElementsAttr denseInit) {
192 mlir::Type elemType = denseInit.getElementType();
193 if (!elemType.isIntOrIndex() && !llvm::isa<mlir::FloatType>(elemType)) {
194 return std::nullopt;
195 }
196
197 std::vector<char> bytes;
198 // A float reaches its byte image through APInt too, so one loop covers both.
199 // The APInt object aliases its own value only for a width that fits the
200 // inline union, so this reads getRawData() instead of &value.
201 auto append = [&bytes](const llvm::APInt &value) {
202 size_t byteSize = (value.getBitWidth() + 7) / 8;
203 const auto *first = reinterpret_cast<const char *>(value.getRawData());
204 bytes.insert(bytes.end(), first, first + byteSize);
205 };
206 if (elemType.isIntOrIndex()) {
207 for (const llvm::APInt &intVal : denseInit.getValues<llvm::APInt>()) {
208 append(intVal);
209 }
210 } else {
211 for (const llvm::APFloat &floatVal : denseInit.getValues<llvm::APFloat>()) {
212 append(floatVal.bitcastToAPInt());
213 }
214 }
215 return bytes;
216}
217
218MemoryRun coreDataRegion(TileOp tile, llvm::ArrayRef<BufferOp> buffers) {
219 BufferOp coreData;
220 for (auto buf : buffers) {
221 if (buf.getCoreData()) {
222 coreData = buf;
223 }
224 }
225 if (coreData) {
226 if (std::optional<int32_t> addr = coreData.getAddress()) {
227 return MemoryRun{*addr, (int64_t)coreData.getAllocationSize()};
228 }
229 }
230
231 const auto &targetModel = getTargetModel(tile);
232 CoreOp core = tile.getCoreOp();
233 llvm::SmallVector<std::pair<int64_t, int64_t>> occupied;
234 MemoryRun stackRun = core ? core.getStackRun() : MemoryRun{};
235 occupied.emplace_back(stackRun.start, stackRun.end());
236 for (auto buf : buffers) {
237 int64_t base = getBufferBaseAddress(buf);
238 occupied.emplace_back(base, base + buf.getAllocationSize());
239 }
240 return largestFreeRun(
241 targetModel.getLocalMemorySize(), std::move(occupied),
242 std::max<int64_t>(targetModel.getComputeTileMaxVectorAlignBits() / 8, 1));
243}
244
245llvm::SmallVector<MemoryRun> coreBankRegions(TileOp tile,
246 llvm::ArrayRef<BufferOp> buffers,
247 MemoryRun dataRun) {
248 const auto &targetModel = getTargetModel(tile);
249 int numBanks = targetModel.getNumBanks(tile.getCol(), tile.getRow());
250 llvm::SmallVector<MemoryRun> regions;
251 if (numBanks <= 0) {
252 return regions;
253 }
254 int64_t memSize = targetModel.getLocalMemorySize();
255 int64_t bankSize = memSize / numBanks;
256 int64_t align =
257 std::max<int64_t>(targetModel.getComputeTileMaxVectorAlignBits() / 8, 1);
258
259 CoreOp core = tile.getCoreOp();
260 llvm::SmallVector<std::pair<int64_t, int64_t>> occupied;
261 MemoryRun stackRun = core ? core.getStackRun() : MemoryRun{};
262 occupied.emplace_back(stackRun.start, stackRun.end());
263 // A reservation held for a bank *is* that bank's region: the allocator set it
264 // aside for precisely the sections this function is sizing a region for.
265 // Counting it as occupied would hand the region back the space it was told to
266 // keep, and hand the section a hole somewhere else -- so it may be the
267 // largest free run in the bank while the reservation sits in a smaller one.
268 llvm::SmallVector<std::optional<MemoryRun>> reserved(numBanks, std::nullopt);
269 for (auto buf : buffers) {
270 int64_t base = getBufferBaseAddress(buf);
271 if (auto bank = buf.getMemBank();
272 buf.getBankReserved() && bank && *bank < numBanks) {
273 reserved[*bank] = MemoryRun{base, buf.getAllocationSize()};
274 continue;
275 }
276 occupied.emplace_back(base, base + buf.getAllocationSize());
277 }
278 // The unpinned region is spoken for, so a bank only offers what it leaves.
279 occupied.emplace_back(dataRun.start, dataRun.end());
280
281 for (int bank = 0; bank < numBanks; ++bank) {
282 if (auto region = reserved[bank]) {
283 regions.push_back(*region);
284 continue;
285 }
286 MemoryRun window{bankSize * bank, bankSize};
287 MemoryRun run = largestFreeRunIn(window, occupied, align);
288 // A bank with nothing spare still has to name its own base. Left at the
289 // default the region would report address 0, which is inside bank 0.
290 if (run.size == 0) {
291 run.start = window.start;
292 }
293 regions.push_back(run);
294 }
295 return regions;
296}
297
298} // namespace xilinx::AIE
MemoryRun largestFreeRunIn(MemoryRun window, llvm::SmallVector< std::pair< int64_t, int64_t > > occupied, int64_t alignBytes=1)
llvm::SmallPtrSet< mlir::Block *, 8 > collectOutOfOrderBlocks(const llvm::SetVector< mlir::Block * > &blockVector)
Collect every BD block reached from an out-of-order aie.dma_start channel.
std::string tileDMAInstStr(llvm::StringRef col, llvm::StringRef row, llvm::StringRef bdNum)
llvm::SmallVector< MemoryRun > coreBankRegions(TileOp tile, llvm::ArrayRef< BufferOp > buffers, MemoryRun dataRun)
One extent per memory bank for the core's bank-pinned statics, tile-relative and indexed by bank.
void generateXAieDmaSetMultiDimAddr(llvm::raw_ostream &output, int ndims, llvm::ArrayRef< BDDimLayoutAttr > dims, int col, int row, int bdNum, int baseAddrA, int64_t offsetA, uint32_t lenA, int elementWidthInBytes, const char *errorRet)
int32_t getBufferBaseAddress(mlir::Operation *bufOp)
MemoryRun largestFreeRun(int64_t memSize, llvm::SmallVector< std::pair< int64_t, int64_t > > occupied, int64_t alignBytes=1)
const AIETargetModel & getTargetModel(mlir::Operation *op)
std::optional< std::vector< char > > denseAttrToBytes(mlir::DenseElementsAttr denseInit)
A buffer's initial_value as the bytes to write to its tile memory, in element order.
MemoryRun coreDataRegion(TileOp tile, llvm::ArrayRef< BufferOp > buffers)
The extent the core compiler gets for its own .data, .rodata and .bss, tile-relative.
std::string tileDMAInstRefStr(llvm::StringRef col, llvm::StringRef row, llvm::StringRef bdNum)
llvm::SetVector< mlir::Block * > getOrderedChainOfBlocks(mlir::Region *region)
std::string tileLocStr(llvm::StringRef col, llvm::StringRef row)
std::string packetStr(llvm::StringRef id, llvm::StringRef type)
int64_t end() const