MLIR-AIE 1.4.6.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>
161collectUnchainedBlocks(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> unchainedBlocks;
171 for (Block *block : blockVector) {
172 for (auto startOp : block->getOps<DMAStartOp>()) {
173 if (!startOp.getOutOfOrder() && !startOp.getCorePushes()) {
174 continue;
175 }
176 Block *b = startOp.getDest();
177 while (b && unchainedBlocks.insert(b).second &&
178 b->getNumSuccessors() > 0) {
179 Block *next = b->getSuccessor(0);
180 // Another channel's head or this own channel's head.
181 if (channelHeads.contains(next)) {
182 break;
183 }
184 b = next;
185 }
186 }
187 }
188 return unchainedBlocks;
189}
190
191std::optional<std::vector<char>>
192denseAttrToBytes(mlir::DenseElementsAttr denseInit) {
193 mlir::Type elemType = denseInit.getElementType();
194 if (!elemType.isIntOrIndex() && !llvm::isa<mlir::FloatType>(elemType)) {
195 return std::nullopt;
196 }
197
198 std::vector<char> bytes;
199 // A float reaches its byte image through APInt too, so one loop covers both.
200 // The APInt object aliases its own value only for a width that fits the
201 // inline union, so this reads getRawData() instead of &value.
202 auto append = [&bytes](const llvm::APInt &value) {
203 size_t byteSize = (value.getBitWidth() + 7) / 8;
204 const auto *first = reinterpret_cast<const char *>(value.getRawData());
205 bytes.insert(bytes.end(), first, first + byteSize);
206 };
207 if (elemType.isIntOrIndex()) {
208 for (const llvm::APInt &intVal : denseInit.getValues<llvm::APInt>()) {
209 append(intVal);
210 }
211 } else {
212 for (const llvm::APFloat &floatVal : denseInit.getValues<llvm::APFloat>()) {
213 append(floatVal.bitcastToAPInt());
214 }
215 }
216 return bytes;
217}
218
219MemoryRun coreDataRegion(TileOp tile, llvm::ArrayRef<BufferOp> buffers) {
220 BufferOp coreData;
221 for (auto buf : buffers) {
222 if (buf.getCoreData()) {
223 coreData = buf;
224 }
225 }
226 if (coreData) {
227 if (std::optional<int32_t> addr = coreData.getAddress()) {
228 return MemoryRun{*addr, (int64_t)coreData.getAllocationSize()};
229 }
230 }
231
232 const auto &targetModel = getTargetModel(tile);
233 CoreOp core = tile.getCoreOp();
234 llvm::SmallVector<std::pair<int64_t, int64_t>> occupied;
235 MemoryRun stackRun = core ? core.getStackRun() : MemoryRun{};
236 occupied.emplace_back(stackRun.start, stackRun.end());
237 for (auto buf : buffers) {
238 int64_t base = getBufferBaseAddress(buf);
239 occupied.emplace_back(base, base + buf.getAllocationSize());
240 }
241 return largestFreeRun(
242 targetModel.getLocalMemorySize(), std::move(occupied),
243 std::max<int64_t>(targetModel.getComputeTileMaxVectorAlignBits() / 8, 1));
244}
245
246llvm::SmallVector<MemoryRun> coreBankRegions(TileOp tile,
247 llvm::ArrayRef<BufferOp> buffers,
248 MemoryRun dataRun) {
249 const auto &targetModel = getTargetModel(tile);
250 int numBanks = targetModel.getNumBanks(tile.getCol(), tile.getRow());
251 llvm::SmallVector<MemoryRun> regions;
252 if (numBanks <= 0) {
253 return regions;
254 }
255 int64_t memSize = targetModel.getLocalMemorySize();
256 int64_t bankSize = memSize / numBanks;
257 int64_t align =
258 std::max<int64_t>(targetModel.getComputeTileMaxVectorAlignBits() / 8, 1);
259
260 CoreOp core = tile.getCoreOp();
261 llvm::SmallVector<std::pair<int64_t, int64_t>> occupied;
262 MemoryRun stackRun = core ? core.getStackRun() : MemoryRun{};
263 occupied.emplace_back(stackRun.start, stackRun.end());
264 // A reservation held for a bank *is* that bank's region: the allocator set it
265 // aside for precisely the sections this function is sizing a region for.
266 // Counting it as occupied would hand the region back the space it was told to
267 // keep, and hand the section a hole somewhere else -- so it may be the
268 // largest free run in the bank while the reservation sits in a smaller one.
269 llvm::SmallVector<std::optional<MemoryRun>> reserved(numBanks, std::nullopt);
270 for (auto buf : buffers) {
271 int64_t base = getBufferBaseAddress(buf);
272 if (auto bank = buf.getMemBank();
273 buf.getBankReserved() && bank && *bank < numBanks) {
274 reserved[*bank] = MemoryRun{base, buf.getAllocationSize()};
275 continue;
276 }
277 occupied.emplace_back(base, base + buf.getAllocationSize());
278 }
279 // The unpinned region is spoken for, so a bank only offers what it leaves.
280 occupied.emplace_back(dataRun.start, dataRun.end());
281
282 for (int bank = 0; bank < numBanks; ++bank) {
283 if (auto region = reserved[bank]) {
284 regions.push_back(*region);
285 continue;
286 }
287 MemoryRun window{bankSize * bank, bankSize};
288 MemoryRun run = largestFreeRunIn(window, occupied, align);
289 // A bank with nothing spare still has to name its own base. Left at the
290 // default the region would report address 0, which is inside bank 0.
291 if (run.size == 0) {
292 run.start = window.start;
293 }
294 regions.push_back(run);
295 }
296 return regions;
297}
298
299} // namespace xilinx::AIE
MemoryRun largestFreeRunIn(MemoryRun window, llvm::SmallVector< std::pair< int64_t, int64_t > > occupied, int64_t alignBytes=1)
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::SmallPtrSet< mlir::Block *, 8 > collectUnchainedBlocks(const llvm::SetVector< mlir::Block * > &blockVector)
Collect BD blocks that require use_next_bd=0 during configuration.
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