MLIR-AIE
AIEAssignBuffers.cpp
Go to the documentation of this file.
1//===- AIEAssignBuffers.cpp -------------------------------------*- C++ -*-===//
2//
3// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7// (c) Copyright 2019 Xilinx Inc.
8//
9//===----------------------------------------------------------------------===//
10
13
14#include "mlir/IR/Attributes.h"
15
16#define DEBUG_TYPE "aie-assign-buffers"
17
18using namespace mlir;
19using namespace xilinx;
20using namespace xilinx::AIE;
21
22//===----------------------------------------------------------------------===//
23// BasicAllocation : sequential alloc from largest to smallest
24//===----------------------------------------------------------------------===//
25LogicalResult checkAndPrintOverflow(TileOp tile, int address,
26 int maxDataMemorySize, int stacksize,
27 SmallVector<BufferOp> &buffers) {
28 if (address > maxDataMemorySize) {
29 InFlightDiagnostic error =
30 tile.emitOpError("allocated buffers exceeded available memory\n");
31 auto &note = error.attachNote() << "MemoryMap:\n";
32 auto printbuffer = [&](StringRef name, int address, int size) {
33 note << "\t" << name << " \t"
34 << ": 0x" << llvm::utohexstr(address) << "-0x"
35 << llvm::utohexstr(address + size - 1) << " \t(" << size
36 << " bytes)\n";
37 };
38 if (stacksize > 0)
39 printbuffer("(stack)", 0, stacksize);
40 else
41 error << "(no stack allocated)\n";
42
43 for (auto buffer : buffers) {
44 assert(buffer.getAddress().has_value() &&
45 "buffer must have address assigned");
46 printbuffer(buffer.name(), buffer.getAddress().value(),
47 buffer.getAllocationSize());
48 }
49 return failure();
50 }
51 return success();
52}
53
54LogicalResult basicAllocation(TileOp tile) {
55 auto device = tile->getParentOfType<AIE::DeviceOp>();
56 if (!device)
57 return failure();
58
59 const auto &targetModel = getTargetModel(tile);
60 int maxDataMemorySize = 0;
61 if (tile.isMemTile())
62 maxDataMemorySize = targetModel.getMemTileSize();
63 else
64 maxDataMemorySize = targetModel.getLocalMemorySize();
65
66 SmallVector<BufferOp> buffers;
67 SmallVector<BufferOp> allocated_buffers;
68 // Collect all the buffers for this tile. If the buffer has an address, add
69 // it to allocated_buffers. Otherwise, add it to buffers.
70 device.walk<WalkOrder::PreOrder>([&](BufferOp buffer) {
71 if (buffer.getTileOp() == tile) {
72 if (buffer.getAddress())
73 allocated_buffers.push_back(buffer);
74 else
75 buffers.push_back(buffer);
76 }
77 });
78
79 // Sort buffers by allocation size.
80 std::sort(buffers.begin(), buffers.end(), [](BufferOp a, BufferOp b) {
81 return a.getAllocationSize() > b.getAllocationSize();
82 });
83
84 // Sort allocated_buffers by address
85 std::sort(allocated_buffers.begin(), allocated_buffers.end(),
86 [](BufferOp a, BufferOp b) {
87 return a.getAddress().value() < b.getAddress().value();
88 });
89
90 // Address range owned by the MemTile is 0x80000.
91 // Address range owned by the tile is 0x8000 in
92 // AIE1 and 0x10000 in AIE2, but we need room at
93 // the bottom for stack.
94 int stacksize = 0;
95 int address = 0;
96 if (auto core = tile.getCoreOp()) {
97 stacksize = core.getStackSize();
98 address += stacksize;
99 }
100
101 // As the next address to allocate is assigned, skip over any buffers
102 // from the allocated_buffers list.
103 auto current_alloc = allocated_buffers.begin();
104 for (auto buffer : buffers) {
105 assert(!buffer.getAddress());
106 while (current_alloc != allocated_buffers.end() &&
107 address + buffer.getAllocationSize() >
108 current_alloc->getAddress().value()) {
109 address = current_alloc->getAddress().value() +
110 current_alloc->getAllocationSize();
111 current_alloc++;
112 }
113 buffer.setAddress(address);
114 address += buffer.getAllocationSize();
115 }
116
117 // Sort by smallest address before printing memory map.
118 std::sort(buffers.begin(), buffers.end(), [](BufferOp a, BufferOp b) {
119 assert(a.getAddress().has_value() && "buffer must have address assigned");
120 assert(b.getAddress().has_value() && "buffer must have address assigned");
121 return a.getAddress().value() < b.getAddress().value();
122 });
123 // Check if memory was exceeded on any bank and print debug info.
124 return checkAndPrintOverflow(tile, address, maxDataMemorySize, stacksize,
125 buffers);
126}
127
128//===----------------------------------------------------------------------===//
129// SimpleBankAwareAllocation : round-robin each alloc over available banks
130//===----------------------------------------------------------------------===//
131typedef struct BankLimits {
132 int64_t startAddr;
133 int64_t endAddr;
135
136// Function that given a number of banks and their size, computes
137// the start and end addresses for each bank and fills in the entry
138// in the bankLimits vector.
139void fillBankLimits(int numBanks, int bankSize,
140 std::vector<BankLimits> &bankLimits) {
141 for (int i = 0; i < numBanks; i++) {
142 auto startAddr = bankSize * i;
143 auto endAddr = bankSize * (i + 1);
144 bankLimits.push_back({startAddr, endAddr});
145 }
146}
147
148// Function that sets the address attribute of the given buffer to
149// the given start_addr. It also updates the entry in the
150// nextAddrInBanks for the corresponding bank.
151void setAndUpdateAddressInBank(BufferOp buffer, int64_t start_addr,
152 int64_t end_addr,
153 std::vector<int64_t> &nextAddrInBanks) {
154 // Fixme: alignment
155 buffer.setAddress(start_addr);
156 nextAddrInBanks[buffer.getMemBank().value()] = end_addr;
157}
158
159// Function that checks whether the given buffer already has a set address
160// attribute. If it does, it finds in which bank the buffer is and checks
161// whether there is enough space left for it. If there is the function
162// returns true and if not, the function emits a warning that the address
163// will be overwritten and returns false (which will cause the buffer to be
164// added to the list of buffers without addresses, to be completed later on).
165FailureOr<bool>
166checkAndAddBufferWithAddress(BufferOp buffer, int numBanks,
167 std::vector<int64_t> &nextAddrInBanks,
168 std::vector<BankLimits> &bankLimits) {
169 auto addrAttr = buffer->getAttrOfType<IntegerAttr>("address");
170 if (!addrAttr)
171 return false;
172
173 int addr = addrAttr.getInt();
174 for (int i = 0; i < numBanks; i++) {
175 // if the address is not within the bank, continue
176 if (addr < bankLimits[i].startAddr || addr >= bankLimits[i].endAddr)
177 continue;
178
179 // if the allocator already allocated this address, fail
180 if (addr < nextAddrInBanks[i])
181 return buffer->emitOpError("would override allocated address");
182
183 // the allocator can accomadate this existing allocation
184 nextAddrInBanks[i] = addr + buffer.getAllocationSize();
185 buffer.setMemBank(i);
186 }
187 return true;
188}
189
190// Function that checks whether the given buffer already has a set mem_bank
191// attribute. If it does, it checks whether there is enough space left for
192// it. If there is, it sets the buffer's address field and if not, the
193// function emits a warning that the mem_bank will be overwritten and returns
194// false (which will cause the buffer to be added to the list of buffers
195// without addresses, to be completed later on).
196FailureOr<bool>
197checkAndAddBufferWithMemBank(BufferOp buffer, int numBanks,
198 std::vector<int64_t> &nextAddrInBanks,
199 std::vector<BankLimits> &bankLimits) {
200 auto memBankAttr = buffer->getAttrOfType<IntegerAttr>("mem_bank");
201 if (!memBankAttr)
202 return false;
203
204 int mem_bank = memBankAttr.getInt();
205 int64_t startAddr = nextAddrInBanks[mem_bank];
206 int64_t endAddr = startAddr + buffer.getAllocationSize();
207 if (endAddr > bankLimits[mem_bank].endAddr)
208 return buffer->emitOpError("would override existing mem_bank");
209 setAndUpdateAddressInBank(buffer, startAddr, endAddr, nextAddrInBanks);
210 return true;
211}
212
213// Prints the memory map across banks
214void printMemMap(TileOp tile, SmallVector<BufferOp> &allocatedBuffers,
215 SmallVector<BufferOp> &preAllocatedBuffers, int numBanks,
216 std::vector<BankLimits> &bankLimits, int stacksize) {
217 InFlightDiagnostic error = tile.emitOpError(
218 "Not all requested buffers fit in the available memory.\n");
219 auto &note = error.attachNote()
220 << "Current configuration of buffers in bank(s) : ";
221 note << "MemoryMap:\n";
222 auto printbuffer = [&](StringRef name, int address, int size) {
223 note << "\t"
224 << "\t" << name << " \t"
225 << ": 0x" << llvm::utohexstr(address) << "-0x"
226 << llvm::utohexstr(address + size - 1) << " \t(" << size
227 << " bytes)\n";
228 };
229 for (int i = 0; i < numBanks; i++) {
230 if (i == 0) {
231 if (stacksize > 0)
232 printbuffer("(stack)", 0, stacksize);
233 else
234 note << "(no stack allocated)\n";
235 }
236 note << "\t"
237 << "bank : " << i << "\t"
238 << "0x" << llvm::utohexstr(bankLimits[i].startAddr) << "-0x"
239 << llvm::utohexstr(bankLimits[i].endAddr - 1) << "\n";
240 for (auto buffer : preAllocatedBuffers) {
241 auto addr = buffer.getAddress().value();
242 auto mem_bank = buffer.getMemBank().value();
243 if (mem_bank == i)
244 printbuffer(buffer.name(), addr, buffer.getAllocationSize());
245 }
246 for (auto buffer : allocatedBuffers) {
247 auto addr = buffer.getAddress().value();
248 auto mem_bank = buffer.getMemBank().value();
249 if (mem_bank == i)
250 printbuffer(buffer.name(), addr, buffer.getAllocationSize());
251 }
252 }
253}
254
255// Function that given a buffer will iterate over all the memory banks
256// starting from the given index to try and find a bank with enough
257// space. If it does, it will set the buffer's address and mem_bank
258// attributes and update the nextAddrInBanks vector.
259// If it does not find one with enough space, it will throw an error.
260// Returns true if the buffer was successfully allocated, false otherwise.
261// If no bank has enough space to accommodate the buffer, an error is emitted.
262
263int setBufferAddress(BufferOp buffer, int numBanks, int &startBankIndex,
264 std::vector<int64_t> &nextAddrInBanks,
265 std::vector<BankLimits> &bankLimits) {
266 assert(startBankIndex < numBanks &&
267 "Unexpected input value for startBankIndex");
268 int bankIndex = startBankIndex;
269 bool allocated = false;
270 for (int i = 0; i < numBanks; i++) {
271 int64_t startAddr = nextAddrInBanks[bankIndex];
272 int64_t endAddr = startAddr + buffer.getAllocationSize();
273 if (endAddr <= bankLimits[bankIndex].endAddr) {
274 buffer.setMemBank(bankIndex);
275 setAndUpdateAddressInBank(buffer, startAddr, endAddr, nextAddrInBanks);
276 allocated = true;
277 bankIndex = (bankIndex + 1) % numBanks;
278 startBankIndex = bankIndex;
279 break;
280 }
281 // Move to the next bank
282 bankIndex = (bankIndex + 1) % numBanks;
283 }
284 // If no bank has enough space, throws error
285 if (!allocated) {
286 buffer.emitError("Failed to allocate buffer: ")
287 << buffer.name() << " with size: " << buffer.getAllocationSize()
288 << " bytes.";
289 return false;
290 }
291 return true;
292}
293
294LogicalResult checkAndPrintOverflow(TileOp tile, int numBanks, int stacksize,
295 SmallVector<BufferOp> &allBuffers,
296 std::vector<int64_t> &nextAddrInBanks,
297 std::vector<BankLimits> &bankLimits) {
298 bool foundOverflow = false;
299 std::vector<int> overflow_banks;
300 for (int i = 0; i < numBanks; i++) {
301 if (nextAddrInBanks[i] > bankLimits[i].endAddr) {
302 foundOverflow = true;
303 overflow_banks.push_back(i);
304 }
305 }
306 if (foundOverflow) {
307 InFlightDiagnostic error =
308 tile.emitOpError("allocated buffers exceeded available memory\n");
309 auto &note = error.attachNote() << "Error in bank(s) : ";
310 for (auto bank : overflow_banks)
311 note << bank << " ";
312 note << "\n";
313 note << "MemoryMap:\n";
314 auto printbuffer = [&](StringRef name, int address, int size) {
315 note << "\t"
316 << "\t" << name << " \t"
317 << ": 0x" << llvm::utohexstr(address) << "-0x"
318 << llvm::utohexstr(address + size - 1) << " \t(" << size
319 << " bytes)\n";
320 };
321 for (int i = 0; i < numBanks; i++) {
322 note << "\t"
323 << "bank : " << i << "\t"
324 << "0x" << llvm::utohexstr(bankLimits[i].startAddr) << "-0x"
325 << llvm::utohexstr(bankLimits[i].endAddr - 1) << "\n";
326 if (i == 0) {
327 if (stacksize > 0)
328 printbuffer("(stack)", 0, stacksize);
329 else
330 error << "(no stack allocated)\n";
331 }
332 for (auto buffer : allBuffers) {
333 auto addr = buffer.getAddress().value();
334 auto mem_bank = buffer.getMemBank().value();
335 if (mem_bank == i)
336 printbuffer(buffer.name(), addr, buffer.getAllocationSize());
337 }
338 }
339 return failure();
340 }
341 return success();
342}
343
344// Function to deallocate attributes of buffers in case of a failure
345void deAllocationBuffers(SmallVector<BufferOp> &buffers) {
346 for (auto buffer : buffers) {
347 buffer->removeAttr("address");
348 buffer->removeAttr("mem_bank");
349 }
350}
351
352LogicalResult simpleBankAwareAllocation(TileOp tile) {
353 auto device = tile->getParentOfType<AIE::DeviceOp>();
354 if (!device)
355 return failure();
356
357 std::vector<int64_t>
358 nextAddrInBanks; // each entry is the next address available for use
359 // for that bank
360 // (e.g. nextAddrInBanks[tile_0][1] = next available
361 // address in bank 1 for tile_0)
362 std::vector<BankLimits> bankLimits; // the entries contain pairs of start and
363 // end addresses for each bank
364
365 const auto &targetModel = getTargetModel(tile);
366 int maxDataMemorySize = 0;
367 if (tile.isMemTile())
368 maxDataMemorySize = targetModel.getMemTileSize();
369 else
370 maxDataMemorySize = targetModel.getLocalMemorySize();
371
372 int numBanks = targetModel.getNumBanks(tile.getCol(), tile.getRow());
373 int bankSize = maxDataMemorySize / numBanks;
374
375 // Address range owned by the MemTile is 0x80000.
376 // Address range owned by the tile is 0x8000 in
377 // AIE1 and 0x10000 in AIE2, but we need room at
378 // the bottom for stack.
379 int stacksize = 0;
380 for (int i = 0; i < numBanks; i++)
381 nextAddrInBanks.push_back(bankSize * i);
382 if (auto core = tile.getCoreOp()) {
383 stacksize = core.getStackSize();
384 nextAddrInBanks[0] += stacksize;
385 }
386 fillBankLimits(numBanks, bankSize, bankLimits);
387
388 SmallVector<BufferOp> preAllocatedBuffers;
389 SmallVector<BufferOp> buffersToAlloc;
390 SmallVector<BufferOp> allBuffers;
391 // Collect all the buffers for this tile.
392 device.walk<WalkOrder::PreOrder>([&](BufferOp buffer) {
393 if (buffer.getTileOp() == tile)
394 allBuffers.push_back(buffer);
395 });
396 // If possible, the buffers with an already specified address will not
397 // be overwritten (the available address range of the bank the buffers
398 // are in will start AFTER the specified adress + buffer size).
399 // Buffers with a specified mem_bank will be assigned first, after
400 // the above.
401 for (auto buffer : allBuffers) {
402 if (buffer.getTileOp() == tile) {
403 auto has_addr = checkAndAddBufferWithAddress(buffer, numBanks,
404 nextAddrInBanks, bankLimits);
405 auto has_bank = checkAndAddBufferWithMemBank(buffer, numBanks,
406 nextAddrInBanks, bankLimits);
407 if (failed(has_addr) || failed(has_bank))
408 return failure();
409 if (!has_addr.value() && !has_bank.value())
410 buffersToAlloc.push_back(buffer);
411 else
412 preAllocatedBuffers.push_back(buffer);
413 }
414 }
415
416 // Sort by largest allocation size before allocating.
417 std::sort(buffersToAlloc.begin(), buffersToAlloc.end(),
418 [](BufferOp a, BufferOp b) {
419 return a.getAllocationSize() > b.getAllocationSize();
420 });
421
422 // Set addresses for remaining buffers.
423 SmallVector<BufferOp> allocatedBuffers;
424 int bankIndex = 0;
425 for (auto buffer : buffersToAlloc) {
426 // If the buffer doesn't fit in any of the bank space then
427 // it prints the current memory map of the banks,
428 // deallocates all the buffers, and
429 // returns a failure.
430 if (!setBufferAddress(buffer, numBanks, bankIndex, nextAddrInBanks,
431 bankLimits)) {
432
433 printMemMap(tile, allocatedBuffers, preAllocatedBuffers, numBanks,
434 bankLimits, stacksize);
435 deAllocationBuffers(allocatedBuffers);
436 return failure();
437 } else {
438 allocatedBuffers.push_back(buffer);
439 }
440 }
441
442 // Sort by smallest address before printing memory map.
443 std::sort(allBuffers.begin(), allBuffers.end(), [](BufferOp a, BufferOp b) {
444 assert(a.getAddress().has_value() && "buffer must have address assigned");
445 assert(b.getAddress().has_value() && "buffer must have address assigned");
446 return a.getAddress().value() < b.getAddress().value();
447 });
448 // Check if memory was exceeded on any bank and print debug info.
449 return checkAndPrintOverflow(tile, numBanks, stacksize, allBuffers,
450 nextAddrInBanks, bankLimits);
451}
452
454 : AIEAssignBufferAddressesBase<AIEAssignBufferAddressesPass> {
455
456 void getDependentDialects(DialectRegistry &registry) const override {
457 registry.insert<func::FuncDialect>();
458 registry.insert<AIEDialect>();
459 }
460
461 void runOnOperation() override {
462 DeviceOp device = getOperation();
463 OpBuilder builder = OpBuilder::atBlockTerminator(device.getBody());
464 // Make sure all the buffers have a name
465 int counter = 0;
466 device.walk<WalkOrder::PreOrder>([&](BufferOp buffer) {
467 if (!buffer.hasName()) {
468 std::string name = "_anonymous";
469 name += std::to_string(counter++);
470 buffer->setAttr(SymbolTable::getSymbolAttrName(),
471 builder.getStringAttr(name));
472 }
473 });
474
475 // Select allocation scheme per tile
476 for (auto tile : device.getOps<TileOp>()) {
477 auto tileAllocationScheme = tile.getAllocationScheme();
478
479 if (!tileAllocationScheme)
480 tileAllocationScheme = clAllocScheme;
481
482 if (tileAllocationScheme == "basic-sequential") {
483 if (auto res = basicAllocation(tile); res.failed())
484 return signalPassFailure();
485 } else if (tileAllocationScheme == "bank-aware") {
486 if (auto res = simpleBankAwareAllocation(tile); res.failed())
487 return signalPassFailure();
488 } else {
489 if (auto res = simpleBankAwareAllocation(tile); res.failed()) {
490 if (auto res2 = basicAllocation(tile); res2.failed())
491 return signalPassFailure();
492 }
493 }
494 }
495 }
496};
497
498std::unique_ptr<OperationPass<DeviceOp>>
500 return std::make_unique<AIEAssignBufferAddressesPass>();
501}
LogicalResult simpleBankAwareAllocation(TileOp tile)
void deAllocationBuffers(SmallVector< BufferOp > &buffers)
LogicalResult checkAndPrintOverflow(TileOp tile, int address, int maxDataMemorySize, int stacksize, SmallVector< BufferOp > &buffers)
void setAndUpdateAddressInBank(BufferOp buffer, int64_t start_addr, int64_t end_addr, std::vector< int64_t > &nextAddrInBanks)
int setBufferAddress(BufferOp buffer, int numBanks, int &startBankIndex, std::vector< int64_t > &nextAddrInBanks, std::vector< BankLimits > &bankLimits)
FailureOr< bool > checkAndAddBufferWithMemBank(BufferOp buffer, int numBanks, std::vector< int64_t > &nextAddrInBanks, std::vector< BankLimits > &bankLimits)
void fillBankLimits(int numBanks, int bankSize, std::vector< BankLimits > &bankLimits)
LogicalResult basicAllocation(TileOp tile)
void printMemMap(TileOp tile, SmallVector< BufferOp > &allocatedBuffers, SmallVector< BufferOp > &preAllocatedBuffers, int numBanks, std::vector< BankLimits > &bankLimits, int stacksize)
FailureOr< bool > checkAndAddBufferWithAddress(BufferOp buffer, int numBanks, std::vector< int64_t > &nextAddrInBanks, std::vector< BankLimits > &bankLimits)
Include the generated interface declarations.
const AIETargetModel & getTargetModel(mlir::Operation *op)
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEAssignBufferAddressesPass()
void getDependentDialects(DialectRegistry &registry) const override