10#include "llvm/Support/Debug.h"
21#define DEBUG_TYPE "aie-sa-placer"
28static size_t drawBoundedIndex(std::mt19937 &rng,
size_t upperExclusive) {
29 return static_cast<size_t>(rng()) % upperExclusive;
33static double drawUnitReal(std::mt19937 &rng) {
34 constexpr double denominator =
static_cast<double>(std::mt19937::max()) + 1.0;
35 return static_cast<double>(rng()) / denominator;
39static void deterministicShuffle(MutableArrayRef<TileID> values,
41 for (
size_t i = values.size(); i > 1; --i)
42 std::swap(values[i - 1], values[drawBoundedIndex(rng, i)]);
48static SmallVector<TileID>
50 SmallVector<TileID> neighbors;
51 for (
TileID candidate : {
TileID{tilePos.col - 1, tilePos.row},
52 TileID{tilePos.col + 1, tilePos.row},
53 TileID{tilePos.col, tilePos.row + 1},
54 TileID{tilePos.col, tilePos.row - 1}}) {
55 if (candidate.col < 0 || candidate.col >= targetModel.
columns() ||
56 candidate.row < 0 || candidate.row >= targetModel.
rows())
61 neighbors.push_back(candidate);
67static int64_t alignedBufferBytes(
const AIETargetModel &targetModel,
68 AIETileType tileType, int64_t bytes) {
70 return llvm::alignTo(bytes,
81 int greedyIters,
const SAConfig &cfg)
82 : temperature(startTemp), movesPerIter(movesPerIter), maxIters(maxIters),
83 greedyIters(greedyIters),
84 windowSize(std::max(movesPerIter, cfg.windowSize)), config(cfg) {}
87 return currIteration >= maxIters ||
88 (inGreedyStage && currGreedyIteration >= greedyIters);
92 int total = acceptCount + rejectCount;
93 return (total == 0) ? 1.0 :
static_cast<double>(acceptCount) / total;
97 history.push_back(accepted ? 1 : 0);
98 ++(accepted ? acceptCount : rejectCount);
103 if (!inGreedyStage) {
105 double adaptiveFactor;
107 adaptiveFactor = config.coolFast;
108 else if (ratio > 0.8)
109 adaptiveFactor = config.coolSteady;
110 else if (ratio > 0.15)
111 adaptiveFactor = config.coolBalanced;
113 adaptiveFactor = config.coolStuck;
115 temperature *= adaptiveFactor;
117 if (ratio < config.greedyThreshold || temperature < 1e-8) {
118 inGreedyStage =
true;
122 currGreedyIteration++;
127void SASchedule::trimWindow() {
128 while (
static_cast<int>(history.size()) > windowSize) {
129 if (history.front() == 1)
141void SAPlacer::buildNetModel(SmallVector<ObjectFifoCreateOp> &objectFifos,
142 SmallVector<ObjectFifoLinkOp> &objectFifoLinks) {
144 tileToNetIndices.clear();
146 for (
auto ofOp : objectFifos) {
148 Value producerTile = ofOp.getProducerTile();
149 if (
auto *prodOp = producerTile.getDefiningOp())
150 if (isa<LogicalTileOp>(prodOp))
153 for (Value consumerTile : ofOp.getConsumerTiles()) {
154 if (
auto *consOp = consumerTile.getDefiningOp())
155 if (isa<LogicalTileOp>(consOp))
164 net.
isMulticast = (ofOp.getConsumerTiles().size() >
165 static_cast<unsigned>(config.multicastThreshold));
167 size_t idx = nets.size();
172 tileToNetIndices[ep].push_back(idx);
180void SAPlacer::buildRouteModel(DeviceOp device, ArrayRef<RouteOp> routes) {
183 for (RouteOp route : routes) {
185 auto addEnd = [&](StringRef name,
bool output) {
186 auto endpoint = SymbolTable::lookupNearestSymbolFrom<RouteEndpoint>(
187 route, StringAttr::get(route.getContext(), name));
190 Operation *tileOp = endpoint.getTile().getDefiningOp();
191 if (!isa_and_nonnull<LogicalTileOp>(tileOp))
194 if (endpoint.getRouteBundle() == WireBundle::DMA)
195 routeEnds.push_back({tileOp, output});
197 for (StringRef source : route.getSourceNames())
198 addEnd(source,
true);
199 for (StringRef dest : route.getDestinationNames())
203 unsigned threshold =
static_cast<unsigned>(config.multicastThreshold);
204 net.
isMulticast = route.getDestinations().size() > threshold ||
205 route.getSources().size() > threshold;
206 size_t idx = nets.size();
209 tileToNetIndices[ep].push_back(idx);
212 for (
auto pool : device.getOps<ObjectFifoPoolOp>()) {
213 Operation *tileOp = pool.getTile().getDefiningOp();
214 if (!isa_and_nonnull<LogicalTileOp>(tileOp))
216 staticBufferSizes[tileOp] += pool.getObjectSizeInBytes() * pool.getDepth();
217 auto typeIt = tileTypes.find(tileOp);
218 if (typeIt != tileTypes.end() && typeIt->second == AIETileType::MemTile)
219 poolBDs[tileOp] += pool.getDepth();
223int SAPlacer::computeNetHPWL(
const NetInfo &net)
const {
226 int hpwl = spanCol + spanRow;
228 hpwl *= config.multicastMultiplier;
232 int area = spanCol * spanRow;
236int SAPlacer::computeTotalHPWL()
const {
238 for (
const auto &net : nets)
239 total += computeNetHPWL(net);
243void SAPlacer::initBoundingBoxes() {
244 for (
auto &net : nets) {
249 net.
bb = {first.col, first.col, first.row, first.row, 1, 1, 1, 1};
251 for (
size_t i = 1; i < net.
endpoints.size(); i++) {
257 }
else if (pos.col == net.
bb.
minCol) {
264 }
else if (pos.col == net.
bb.
maxCol) {
271 }
else if (pos.row == net.
bb.
minRow) {
278 }
else if (pos.row == net.
bb.
maxRow) {
289int SAPlacer::evaluateMove(
290 Operation *tile,
TileID newPos,
291 SmallVector<std::pair<size_t, NetBoundingBox>> &backups) {
295 auto it = tileToNetIndices.find(tile);
296 if (it == tileToNetIndices.end())
299 for (
size_t netIdx : it->second) {
300 NetInfo &net = nets[netIdx];
301 backups.push_back({netIdx, net.
bb});
302 int oldCost = computeNetHPWL(net);
305 int newMinCol = INT_MAX, newMaxCol = INT_MIN;
306 int newMinRow = INT_MAX, newMaxRow = INT_MIN;
307 int cMinCol = 0, cMaxCol = 0, cMinRow = 0, cMaxRow = 0;
310 TileID pos = (ep == tile) ? newPos : currentPlacement[ep];
312 if (pos.col < newMinCol) {
315 }
else if (pos.col == newMinCol) {
318 if (pos.col > newMaxCol) {
321 }
else if (pos.col == newMaxCol) {
324 if (pos.row < newMinRow) {
327 }
else if (pos.row == newMinRow) {
330 if (pos.row > newMaxRow) {
333 }
else if (pos.row == newMaxRow) {
338 net.
bb = {newMinCol, newMaxCol, newMinRow, newMaxRow,
339 cMinCol, cMaxCol, cMinRow, cMaxRow};
341 deltaCost += computeNetHPWL(net) - oldCost;
347void SAPlacer::revertMove(
348 SmallVector<std::pair<size_t, NetBoundingBox>> &backups) {
349 for (
auto &[netIdx, savedBB] : backups)
350 nets[netIdx].bb = savedBB;
357bool SAPlacer::isLegalPosition(Operation *tile,
TileID pos)
const {
358 auto it = tileTypes.find(tile);
359 if (it == tileTypes.end())
362 AIETileType needed = it->second;
363 AIETileType actual =
targetModel->getTileType(pos.col, pos.row);
364 if (needed != actual)
368 if (needed == AIETileType::CoreTile) {
369 auto occIt = physToLogical.find(pos);
370 if (occIt != physToLogical.end() && occIt->second != tile)
381std::optional<TileID> SAPlacer::positionOf(Operation *tile)
const {
382 if (
auto it = currentPlacement.find(tile);
383 tile && it != currentPlacement.end())
385 if (
auto tileOp = dyn_cast_or_null<TileOp>(tile))
386 return tileOp.getTileID();
391void SAPlacer::addRouteEndContribution(
size_t endIdx,
int sign) {
392 const RouteEndInfo &end = routeEnds[endIdx];
393 auto posIt = currentPlacement.find(end.tile);
394 if (posIt == currentPlacement.end())
397 currentDMAUsage[posIt->second].second += sign;
399 currentDMAUsage[posIt->second].first += sign;
402void SAPlacer::addFifoContribution(
size_t fifoIdx,
int sign) {
403 const auto &fb = fifoBuffers[fifoIdx];
404 Operation *prod = fb.producer;
407 auto prodPlaceIt = currentPlacement.find(prod);
408 if (prodPlaceIt == currentPlacement.end())
410 TileID prodPos = prodPlaceIt->second;
411 auto prodTypeIt = tileTypes.find(prod);
412 bool prodIsCore = prodTypeIt != tileTypes.end() &&
413 prodTypeIt->second == AIETileType::CoreTile;
414 bool prodIsShim = prodTypeIt != tileTypes.end() &&
415 (prodTypeIt->second == AIETileType::ShimNOCTile ||
416 prodTypeIt->second == AIETileType::ShimPLTile);
420 std::optional<TileID> delegatePos = positionOf(fb.delegate);
429 bool prodCharged =
false;
430 bool prodNeedsDMA =
false;
432 for (
size_t ci = 0; ci < fb.consumers.size(); ci++) {
433 auto *cons = fb.consumers[ci];
436 auto consPlaceIt = currentPlacement.find(cons);
437 if (consPlaceIt == currentPlacement.end())
439 TileID consPos = consPlaceIt->second;
440 auto consTypeIt = tileTypes.find(cons);
441 bool consIsCore = consTypeIt != tileTypes.end() &&
442 consTypeIt->second == AIETileType::CoreTile;
443 bool consIsShim = consTypeIt != tileTypes.end() &&
444 (consTypeIt->second == AIETileType::ShimNOCTile ||
445 consTypeIt->second == AIETileType::ShimPLTile);
450 int consDeclDepth = fb.consumerDepthAt(ci);
451 int consDMADepth = (ci < fb.consumerDMADepths.size())
452 ? fb.consumerDMADepths[ci]
453 : fb.producerDMADepth;
457 !fb.forcesDMA && prodIsCore && consIsCore && prodPos != consPos &&
458 (
targetModel->isLegalMemAffinity(prodPos.col, prodPos.row, consPos.col,
460 targetModel->isLegalMemAffinity(consPos.col, consPos.row, prodPos.col,
463 if (prodPos == consPos) {
465 int depth = std::max(fb.producerDepth, consDeclDepth);
466 int64_t maxSize = std::max(fb.producerSizeBytes, fb.consumerSizeBytes);
467 int64_t bytes = maxSize * depth;
468 currentMemUsage[delegatePos.value_or(prodPos)] += sign * bytes;
469 }
else if (isSharedMem) {
471 bool rightShared =
targetModel->isLegalMemAffinity(
472 prodPos.col, prodPos.row, consPos.col, consPos.row);
474 consPos.col, consPos.row, prodPos.col, prodPos.row);
477 bufTile = *delegatePos;
478 }
else if (rightShared && leftShared) {
481 currentMemUsage.count(prodPos) ? currentMemUsage[prodPos] : 0;
483 currentMemUsage.count(consPos) ? currentMemUsage[consPos] : 0;
484 bufTile = (prodUsed <= consUsed) ? prodPos : consPos;
488 if (fb.consumers.size() == 1)
489 sharedMemDestination[fifoIdx] = bufTile;
491 auto it = sharedMemDestination.find(fifoIdx);
492 bufTile = (it != sharedMemDestination.end()) ? it->second : prodPos;
494 }
else if (rightShared) {
499 int depth = std::max(fb.producerDepth, consDeclDepth);
500 int64_t maxSize = std::max(fb.producerSizeBytes, fb.consumerSizeBytes);
501 currentMemUsage[bufTile] += sign * maxSize * depth;
502 }
else if (prodIsCore && consIsCore) {
505 int64_t bytes = fb.producerSizeBytes * fb.producerDMADepth;
506 currentMemUsage[prodPos] += sign * bytes;
509 int64_t consBytes = fb.consumerSizeBytes * consDMADepth;
510 currentMemUsage[consPos] += sign * consBytes;
511 }
else if (prodIsCore && !consIsCore) {
515 int64_t bytes = fb.producerSizeBytes * fb.producerDMADepth;
516 currentMemUsage[prodPos] += sign * bytes;
521 int64_t consBytes = fb.consumerSizeBytes * consDeclDepth;
522 currentMemUsage[consPos] += sign * consBytes;
524 }
else if (!prodIsCore && consIsCore) {
527 if (!prodCharged && !prodIsShim && !fb.linkSharedProd) {
528 currentMemUsage[prodPos] +=
529 sign * fb.producerSizeBytes * fb.producerDepth;
532 int64_t consBytes = fb.consumerSizeBytes * consDMADepth;
533 currentMemUsage[consPos] += sign * consBytes;
534 }
else if (!prodIsCore && !consIsCore) {
536 if (!prodCharged && !prodIsShim && !fb.linkSharedProd) {
537 currentMemUsage[prodPos] +=
538 sign * fb.producerSizeBytes * fb.producerDepth;
542 int64_t consBytes = fb.consumerSizeBytes * consDeclDepth;
543 currentMemUsage[consPos] += sign * consBytes;
550 bool loopsBack = prodPos == consPos && !prodIsCore && !prodIsShim;
551 if ((prodPos != consPos || loopsBack) && !isSharedMem) {
552 currentDMAUsage[consPos].first += sign;
558 currentDMAUsage[prodPos].second += sign;
561void SAPlacer::initResourceTracking() {
562 currentMemUsage.clear();
563 currentDMAUsage.clear();
564 sharedMemDestination.clear();
567 tileToFifoIndices.clear();
568 for (
size_t i = 0; i < fifoBuffers.size(); i++) {
569 const auto &fb = fifoBuffers[i];
571 tileToFifoIndices[fb.producer].push_back(i);
572 for (
auto *cons : fb.consumers)
574 tileToFifoIndices[cons].push_back(i);
576 tileToFifoIndices[fb.delegate].push_back(i);
581 for (
auto &[op, bufSize] : staticBufferSizes) {
582 auto posIt = currentPlacement.find(op);
583 if (posIt != currentPlacement.end())
584 currentMemUsage[posIt->second] += bufSize;
587 for (
auto &[op, pos] : currentPlacement) {
588 auto typeIt = tileTypes.find(op);
589 if (typeIt == tileTypes.end() || typeIt->second != AIETileType::CoreTile)
591 auto it = stackSizes.find(op);
592 if (it != stackSizes.end())
593 currentMemUsage[pos] += it->second;
596 for (
size_t i = 0; i < fifoBuffers.size(); i++)
597 addFifoContribution(i, +1);
599 tileToRouteEnds.clear();
600 for (
size_t i = 0; i < routeEnds.size(); i++) {
601 tileToRouteEnds[routeEnds[i].tile].push_back(i);
602 addRouteEndContribution(i, +1);
604 cachedResourcePenalty = computePenalty();
607int SAPlacer::computeMemoryPressure()
const {
608 int64_t coreCapacity =
targetModel->getLocalMemorySize();
609 int64_t memTileCapacity =
targetModel->getMemTileSize();
612 for (
auto &[tilePos, used] : currentMemUsage) {
613 auto tileType =
targetModel->getTileType(tilePos.col, tilePos.row);
614 if (tileType == AIETileType::ShimNOCTile ||
615 tileType == AIETileType::ShimPLTile)
618 (tileType == AIETileType::MemTile) ? memTileCapacity : coreCapacity;
619 double utilization =
static_cast<double>(used) / capacity;
620 if (utilization > config.pressureThreshold) {
621 double excess = (utilization - config.pressureThreshold) /
622 (1.0 - config.pressureThreshold);
623 pressure +=
static_cast<int>(config.pressurePerKB * excess * excess);
629int SAPlacer::computePenalty()
const {
630 return computeMemSpilloverPenalty() + computeCoreOverflowPenalty() +
631 computeDMAChannelPenalty() + computeBDCountPenalty() +
632 computeDelegatePenalty();
637int SAPlacer::computeDelegatePenalty()
const {
639 for (
const auto &fb : fifoBuffers) {
640 std::optional<TileID> delegatePos = positionOf(fb.delegate);
643 SmallVector<Operation *, 2> ends = {fb.producer};
644 llvm::append_range(ends, fb.consumers);
645 for (Operation *end : ends) {
646 std::optional<TileID> endPos = positionOf(end);
649 auto shared =
targetModel->getSharedMemory(*delegatePos, *endPos);
653 int dist = std::abs(delegatePos->col - endPos->col) +
654 std::abs(delegatePos->row - endPos->row);
655 penalty += config.delegateWeightPerDist * std::max(dist - 1, 1);
669int SAPlacer::computeMemSpilloverPenalty()
const {
670 int64_t memTileCapacity =
targetModel->getMemTileSize();
676 llvm::SmallVector<std::pair<int64_t, int>> allBufs;
677 llvm::SmallVector<int64_t> memTileBaseline(numCols, 0);
679 for (
const auto &fb : fifoBuffers) {
682 auto prodIt = currentPlacement.find(fb.producer);
683 if (prodIt == currentPlacement.end())
685 TileID prodPos = prodIt->second;
686 auto prodTypeIt = tileTypes.find(fb.producer);
687 if (prodTypeIt == tileTypes.end())
689 if (prodTypeIt->second != AIETileType::MemTile)
691 if (fb.linkSharedProd)
694 int col = prodPos.col;
695 for (
int d = 0; d < fb.producerDepth; d++)
696 allBufs.push_back({fb.producerSizeBytes, col});
698 for (
size_t ci = 0; ci < fb.consumers.size(); ci++) {
699 auto *cons = fb.consumers[ci];
702 auto consIt = currentPlacement.find(cons);
703 if (consIt == currentPlacement.end())
705 auto consTypeIt = tileTypes.find(cons);
706 if (consTypeIt == tileTypes.end() ||
707 consTypeIt->second != AIETileType::MemTile)
709 int consCol = consIt->second.col;
710 int consDepth = fb.consumerDepthAt(ci);
711 for (
int d = 0; d < consDepth; d++)
712 allBufs.push_back({fb.consumerSizeBytes, consCol});
717 for (
auto &[op, bufSize] : staticBufferSizes) {
718 auto posIt = currentPlacement.find(op);
719 if (posIt == currentPlacement.end())
721 auto typeIt = tileTypes.find(op);
722 if (typeIt == tileTypes.end() || typeIt->second != AIETileType::MemTile)
724 memTileBaseline[posIt->second.col] += bufSize;
728 llvm::SmallVector<int64_t> remaining(numCols, 0);
729 for (
int col = 0; col < numCols; col++) {
730 if (
targetModel->getTileType(col, 1) != AIETileType::MemTile)
733 std::max(memTileCapacity - memTileBaseline[col], int64_t{0});
738 [](
const auto &a,
const auto &b) {
return a.first > b.first; });
741 for (
auto &[bufSize, homeCol] : allBufs) {
742 if (remaining[homeCol] >= bufSize) {
743 remaining[homeCol] -= bufSize;
747 int64_t bestCap = -1;
748 for (
int nbr : {homeCol - 1, homeCol + 1}) {
749 if (nbr < 0 || nbr >= numCols)
751 if (
targetModel->getTileType(nbr, 1) != AIETileType::MemTile)
753 if (remaining[nbr] >= bufSize && remaining[nbr] > bestCap) {
754 bestCap = remaining[nbr];
759 remaining[bestNbr] -= bufSize;
762 penalty += ((bufSize + 1023) / 1024) * config.memPenaltyPerKB;
772int SAPlacer::computeCoreOverflowPenalty()
const {
773 int64_t coreCapacity =
targetModel->getLocalMemorySize();
777 llvm::DenseMap<TileID, int64_t> lent;
779 for (
auto &[tilePos, used] : currentMemUsage) {
780 if (
targetModel->getTileType(tilePos.col, tilePos.row) !=
781 AIETileType::CoreTile)
783 if (used <= coreCapacity)
785 int64_t overflow = used - coreCapacity;
788 llvm::SmallVector<std::pair<int64_t, TileID>, 4> nbrSpares;
790 auto it = currentMemUsage.find(nbr);
791 int64_t nbrUsed = (it != currentMemUsage.end()) ? it->second : 0;
793 std::max(coreCapacity - nbrUsed - lent.lookup(nbr), int64_t{0});
795 nbrSpares.push_back({spare, nbr});
797 llvm::sort(nbrSpares,
798 [](
const auto &a,
const auto &b) {
return a.first > b.first; });
801 int64_t unresolved = overflow;
802 auto opIt = physToLogical.find(tilePos);
803 if (opIt != physToLogical.end()) {
804 auto fifoIt = tileToFifoIndices.find(opIt->second);
805 if (fifoIt != tileToFifoIndices.end()) {
806 llvm::SmallVector<int64_t, 8> fifoSizes;
807 llvm::DenseSet<size_t> seen;
808 for (
size_t fi : fifoIt->second) {
809 if (!seen.insert(fi).second)
811 const auto &fb = fifoBuffers[fi];
812 if (fb.consumers.size() != 1 || !fb.producer || !fb.consumers[0] ||
815 auto prodIt = currentPlacement.find(fb.producer);
816 auto consIt = currentPlacement.find(fb.consumers[0]);
817 if (prodIt == currentPlacement.end() ||
818 consIt == currentPlacement.end())
820 if (prodIt->second != tilePos || consIt->second != tilePos)
822 int consDepth = fb.consumerDepths.empty() ? fb.producerDepth
823 : fb.consumerDepths[0];
824 int64_t sz = std::max(fb.producerSizeBytes, fb.consumerSizeBytes) *
825 std::max(fb.producerDepth, consDepth);
826 fifoSizes.push_back(sz);
828 llvm::sort(fifoSizes, std::greater<int64_t>());
831 int64_t totalAbsorbed = 0;
832 for (int64_t sz : fifoSizes) {
833 for (
auto &[spare, nbr] : nbrSpares) {
842 unresolved = overflow - std::min(totalAbsorbed, overflow);
847 penalty += ((unresolved + 1023) / 1024) * config.memPenaltyPerKB;
853int SAPlacer::computeDMAChannelPenalty()
const {
855 for (
auto &[tilePos, usage] : currentDMAUsage) {
857 int used[2] = {usage.first, usage.second};
858 int max[2] = {maxIn, maxOut};
859 for (
int dir : {0, 1}) {
860 if (used[dir] <= max[dir])
862 if (shimSpans &&
targetModel->getTileType(tilePos.col, tilePos.row) ==
863 AIETileType::ShimNOCTile) {
864 SmallVector<StringAttr> ends;
865 for (
auto &[lt, own] : shareableShimEnds) {
866 auto at = currentPlacement.find(lt);
867 if (at != currentPlacement.end() && at->second == tilePos)
868 llvm::append_range(ends, own[dir]);
870 used[dir] +=
static_cast<int>(shimSpans->groups(ends).size()) -
871 static_cast<int>(ends.size());
874 std::max(0, used[dir] - max[dir]) * config.dmaPenaltyPerChannel;
881int SAPlacer::computeBDCountPenalty()
const {
882 int memTileBDMax =
targetModel->getNumBDs(AIETileType::MemTile);
885 llvm::DenseMap<TileID, int> memTileBDs;
886 for (
const auto &fb : fifoBuffers) {
888 auto posIt = currentPlacement.find(fb.producer);
889 auto typeIt = tileTypes.find(fb.producer);
890 if (posIt != currentPlacement.end() && typeIt != tileTypes.end() &&
891 typeIt->second == AIETileType::MemTile && !fb.linkSharedProd)
892 memTileBDs[posIt->second] += fb.producerDepth;
894 for (
size_t ci = 0; ci < fb.consumers.size(); ci++) {
895 auto *cons = fb.consumers[ci];
898 auto posIt = currentPlacement.find(cons);
899 auto typeIt = tileTypes.find(cons);
900 if (posIt == currentPlacement.end() || typeIt == tileTypes.end() ||
901 typeIt->second != AIETileType::MemTile)
903 int depth = fb.consumerDepthAt(ci);
904 memTileBDs[posIt->second] += depth;
907 for (
auto &[op, depth] : poolBDs) {
908 auto posIt = currentPlacement.find(op);
909 if (posIt != currentPlacement.end())
910 memTileBDs[posIt->second] += depth;
912 for (
auto &[tilePos, totalBDs] : memTileBDs) {
913 if (totalBDs > memTileBDMax)
914 penalty += (totalBDs - memTileBDMax) * config.dmaPenaltyPerChannel;
920int SAPlacer::computeAdjacencyPenalty(
921 const Adjacency &adj, ArrayRef<std::pair<int, int>> validOffsets,
924 for (
auto [first, second] : adj.edges) {
925 auto firstIt = currentPlacement.find(first.getOperation());
926 auto secondIt = currentPlacement.find(second.getOperation());
927 if (firstIt == currentPlacement.end() || secondIt == currentPlacement.end())
930 TileID firstPos = firstIt->second;
931 TileID secondPos = secondIt->second;
934 int minDist = INT_MAX;
935 for (
auto [dc, dr] : validOffsets) {
936 int dist = std::abs(secondPos.col - (firstPos.col + dc)) +
937 std::abs(secondPos.row - (firstPos.row + dr));
938 minDist = std::min(minDist, dist);
941 penalty += weight * minDist;
946int SAPlacer::computePairingPenalty()
const {
947 int penalty = computeAdjacencyPenalty(cascadeAdjacency, kCascadeOffsets,
948 config.cascadeWeightPerDist);
949 for (
auto [first, second] : sharedMemAdjacency.edges) {
950 auto firstIt = currentPlacement.find(first.getOperation());
951 auto secondIt = currentPlacement.find(second.getOperation());
952 if (firstIt == currentPlacement.end() || secondIt == currentPlacement.end())
954 TileID a = firstIt->second, b = secondIt->second;
955 if (a == b ||
targetModel->isLegalMemAffinity(a.col, a.row, b.col, b.row) ||
956 targetModel->isLegalMemAffinity(b.col, b.row, a.col, a.row))
958 int distance = std::abs(a.col - b.col) + std::abs(a.row - b.row);
959 penalty += config.sharedMemWeightPerDist * std::max(1, distance - 1);
964void SAPlacer::generateAllocates() {
965 int64_t coreCapacity =
targetModel->getLocalMemorySize();
966 DenseSet<size_t> emittedFifoIndices;
971 SmallVector<TileID> overflowTiles;
972 for (
auto &[tilePos, used] : currentMemUsage) {
973 if (
targetModel->getTileType(tilePos.col, tilePos.row) ==
974 AIETileType::CoreTile &&
976 overflowTiles.push_back(tilePos);
978 for (
auto &tilePos : overflowTiles) {
981 SmallVector<std::pair<TileID, int64_t>, 4> nbrSpares;
983 auto it = currentMemUsage.find(nbr);
984 int64_t nbrUsed = (it != currentMemUsage.end()) ? it->second : 0;
985 int64_t spare = std::max(coreCapacity - nbrUsed, int64_t{0});
987 nbrSpares.push_back({nbr, spare});
989 llvm::sort(nbrSpares, [](
const auto &a,
const auto &b) {
990 return a.second > b.second;
994 auto opIt = physToLogical.find(tilePos);
995 if (opIt == physToLogical.end())
997 auto fifoIt = tileToFifoIndices.find(opIt->second);
998 if (fifoIt == tileToFifoIndices.end())
1002 SmallVector<std::pair<size_t, int64_t>, 8> relocFifos;
1003 DenseSet<size_t> seen;
1004 for (
size_t fi : fifoIt->second) {
1005 if (!seen.insert(fi).second)
1007 const auto &fb = fifoBuffers[fi];
1008 if (fb.consumers.size() != 1 || !fb.producer || !fb.consumers[0] ||
1011 auto prodIt = currentPlacement.find(fb.producer);
1012 auto consIt = currentPlacement.find(fb.consumers[0]);
1013 if (prodIt == currentPlacement.end() || consIt == currentPlacement.end())
1015 if (prodIt->second != tilePos || consIt->second != tilePos)
1018 fb.consumerDepths.empty() ? fb.producerDepth : fb.consumerDepths[0];
1019 int64_t sz = std::max(fb.producerSizeBytes, fb.consumerSizeBytes) *
1020 std::max(fb.producerDepth, consDepth);
1021 relocFifos.push_back({fi, sz});
1023 llvm::sort(relocFifos, [](
const auto &a,
const auto &b) {
1024 return a.second > b.second;
1029 for (
auto &[fifoIdx, fifoSize] : relocFifos) {
1030 if (!emittedFifoIndices.insert(fifoIdx).second)
1032 for (
auto &[nbrPos, nbrSpare] : nbrSpares) {
1033 if (nbrSpare >= fifoSize) {
1035 auto memIt = currentMemUsage.find(nbrPos);
1036 int64_t delegateUsed =
1037 (memIt != currentMemUsage.end()) ? memIt->second : 0;
1038 if (delegateUsed + fifoSize > coreCapacity)
1040 nbrSpare -= fifoSize;
1041 currentMemUsage[nbrPos] += fifoSize;
1042 allocates.push_back({fifoBuffers[fifoIdx].fifoOp, nbrPos});
1052 for (
auto &[fifoIdx, destTile] : sharedMemDestination) {
1053 if (emittedFifoIndices.contains(fifoIdx))
1055 const auto &fb = fifoBuffers[fifoIdx];
1056 if (!fb.producer || fb.consumers.empty() || !fb.consumers[0])
1058 auto prodIt = currentPlacement.find(fb.producer);
1059 auto consIt = currentPlacement.find(fb.consumers[0]);
1060 if (prodIt == currentPlacement.end() || consIt == currentPlacement.end())
1062 if (prodIt->second == consIt->second)
1064 if (destTile != prodIt->second)
1065 allocates.push_back({fb.fifoOp, destTile});
1069int SAPlacer::updateResourcePenalty(
1070 const SmallVector<std::pair<Operation *, TileID>> &oldPlacements) {
1072 DenseSet<size_t> affectedFifos;
1073 for (
auto &[op, _] : oldPlacements) {
1074 auto it = tileToFifoIndices.find(op);
1075 if (it != tileToFifoIndices.end())
1076 for (
size_t idx : it->second)
1077 affectedFifos.insert(idx);
1081 for (
auto &[op, oldPos] : oldPlacements) {
1082 auto bufIt = staticBufferSizes.find(op);
1083 if (bufIt != staticBufferSizes.end())
1084 currentMemUsage[oldPos] -= bufIt->second;
1085 auto stackIt = stackSizes.find(op);
1086 if (stackIt != stackSizes.end())
1087 currentMemUsage[oldPos] -= stackIt->second;
1093 DenseMap<Operation *, TileID> savedPositions;
1094 for (
auto &[op, oldPos] : oldPlacements) {
1095 savedPositions[op] = currentPlacement[op];
1096 currentPlacement[op] = oldPos;
1098 for (
size_t fi : affectedFifos)
1099 addFifoContribution(fi, -1);
1100 for (
auto &[op, _] : oldPlacements)
1101 for (
size_t ei : tileToRouteEnds.lookup(op))
1102 addRouteEndContribution(ei, -1);
1105 for (
auto &[op, newPos] : savedPositions)
1106 currentPlacement[op] = newPos;
1107 for (
size_t fi : affectedFifos)
1108 addFifoContribution(fi, +1);
1109 for (
auto &[op, _] : oldPlacements)
1110 for (
size_t ei : tileToRouteEnds.lookup(op))
1111 addRouteEndContribution(ei, +1);
1114 for (
auto &[op, _] : oldPlacements) {
1115 TileID newPos = currentPlacement[op];
1116 auto bufIt = staticBufferSizes.find(op);
1117 if (bufIt != staticBufferSizes.end())
1118 currentMemUsage[newPos] += bufIt->second;
1119 auto stackIt = stackSizes.find(op);
1120 if (stackIt != stackSizes.end())
1121 currentMemUsage[newPos] += stackIt->second;
1124 cachedResourcePenalty = computePenalty();
1125 return cachedResourcePenalty;
1132bool SAPlacer::generateMove(
1133 SmallVector<std::pair<Operation *, TileID>> &moves) {
1134 if (movableTiles.empty())
1137 for (
int attempt = 0; attempt < config.maxMoveAttempts; attempt++) {
1139 Operation *tile = movableTiles[drawBoundedIndex(rng, movableTiles.size())];
1141 auto typeIt = tileTypes.find(tile);
1142 if (typeIt == tileTypes.end())
1144 AIETileType type = typeIt->second;
1147 const auto &candidates = (type == AIETileType::CoreTile)
1150 if (candidates.empty())
1153 TileID targetPos = candidates[drawBoundedIndex(rng, candidates.size())];
1156 if (type != AIETileType::CoreTile &&
1157 targetModel->getTileType(targetPos.col, targetPos.row) != type)
1160 TileID currentPos = currentPlacement[tile];
1161 if (targetPos == currentPos)
1168 Operation *occupant =
nullptr;
1169 for (
auto *t : movableTiles) {
1170 if (t != tile && currentPlacement[t] == targetPos) {
1178 if (tileTypes[occupant] != type)
1183 moves.push_back({tile, targetPos});
1184 moves.push_back({occupant, currentPos});
1187 if (!isLegalPosition(tile, targetPos))
1190 moves.push_back({tile, targetPos});
1201double SAPlacer::estimateInitialTemperature(
int numSamples) {
1203 std::vector<double> deltaCosts;
1204 deltaCosts.reserve(numSamples);
1206 SmallVector<std::pair<size_t, NetBoundingBox>> backups;
1208 for (
int i = 0; i < numSamples; i++) {
1209 SmallVector<std::pair<Operation *, TileID>> moves;
1210 if (!generateMove(moves))
1214 int delta = evaluateMove(moves[0].first, moves[0].second, backups);
1215 deltaCosts.push_back(delta);
1216 revertMove(backups);
1219 if (deltaCosts.empty())
1225 double maxDelta = 0.0;
1226 for (
double d : deltaCosts)
1227 maxDelta = std::max(maxDelta, std::abs(d));
1229 if (maxDelta == 0.0)
1232 auto getExpectedValue = [&](
double T) ->
double {
1234 for (
double d : deltaCosts) {
1238 ev += d * std::exp(-d / T);
1244 double highT = config.tempSearchCeiling * maxDelta;
1247 for (
int iter = 0; iter < config.tempSearchIters; iter++) {
1248 double ev = getExpectedValue(T);
1249 if (std::abs(highT - lowT) / std::max(T, config.tempSearchEpsilon) <
1250 config.tempSearchTolerance)
1261 T = (lowT + highT) / 2.0;
1271int64_t SAPlacer::computeTileMemoryWeight(Operation *tile)
const {
1275 auto bufIt = staticBufferSizes.find(tile);
1276 if (bufIt != staticBufferSizes.end())
1277 weight += bufIt->second;
1280 auto stackIt = stackSizes.find(tile);
1281 if (stackIt != stackSizes.end())
1282 weight += stackIt->second;
1285 for (
const auto &fb : fifoBuffers) {
1286 bool isProd = (fb.producer == tile);
1288 for (
size_t ci = 0; ci < fb.consumers.size(); ci++) {
1289 if (fb.consumers[ci] == tile) {
1290 consIdx =
static_cast<int>(ci);
1294 if (!isProd && consIdx < 0)
1297 if (isProd && consIdx >= 0) {
1299 int depth = std::max(fb.producerDepth, fb.consumerDepths[consIdx]);
1300 int64_t sz = std::max(fb.producerSizeBytes, fb.consumerSizeBytes);
1301 weight += sz * depth;
1302 }
else if (isProd) {
1303 weight += fb.producerSizeBytes * fb.producerDepth;
1305 weight += fb.consumerSizeBytes * fb.consumerDepths[consIdx];
1315LogicalResult SAPlacer::mergeMemShimTiles(DeviceOp device) {
1322 llvm::DenseMap<std::pair<int, AIETileType>, SmallVector<Operation *>> groups;
1324 for (
auto &[op, pos] : currentPlacement) {
1325 auto it = tileTypes.find(op);
1326 if (it == tileTypes.end())
1328 if (it->second == AIETileType::CoreTile)
1330 groups[{pos.col, it->second}].push_back(op);
1333 for (
auto &[key, ops] : groups) {
1334 if (ops.size() <= 1)
1336 TileID mergeTarget = currentPlacement[ops[0]];
1337 for (
size_t i = 1; i < ops.size(); i++)
1338 currentPlacement[ops[i]] = mergeTarget;
1349 startTime = std::chrono::steady_clock::now();
1353 rng.seed(std::random_device{}());
1359 if (failed(collectAndBuildModel(device)))
1362 if (failed(generateInitialPlacement()))
1365 initializeSAState();
1369 return finalizePlacement(device);
1372LogicalResult SAPlacer::collectAndBuildModel(DeviceOp device) {
1378 if (logicalTiles.empty())
1382 for (
auto lt : logicalTiles)
1383 tileTypes[lt.getOperation()] = lt.getTileType();
1386 device.walk([&](BufferOp bufOp) {
1387 auto *tileOp = bufOp.getTile().getDefiningOp();
1388 if (!tileOp || !isa<LogicalTileOp>(tileOp))
1390 int64_t bytes = bufOp.getAllocationSize();
1391 staticBufferSizes[tileOp] +=
1393 ? alignedBufferBytes(*
targetModel, tileTypes.lookup(tileOp), bytes)
1398 device.walk([&](CoreOp coreOp) {
1399 auto *tileOp = coreOp.getTile().getDefiningOp();
1400 if (!tileOp || !isa<LogicalTileOp>(tileOp))
1404 stackSizes[tileOp] = alignedBufferBytes(*
targetModel, AIETileType::CoreTile,
1405 coreOp.getEffectiveStackSize()) +
1406 alignedBufferBytes(*
targetModel, AIETileType::CoreTile,
1407 coreOp.getDataSize().value_or(
1412 buildNetModel(objectFifos, objectFifoLinks);
1413 buildFifoBufferInfo(device, objectFifos, objectFifoLinks);
1414 buildRouteModel(device, collected.
routes);
1417 device, [&](StringAttr symbol) {
return shimEndOf(device, symbol); });
1419 LLVM_DEBUG(llvm::dbgs() <<
"[SA] Cascade adjacency: "
1420 << cascadeAdjacency.edges.size() <<
" edges\n");
1421 for (ObjectFifoCreateOp fifo : objectFifos)
1422 if (fifo.forcesSharedMemory() && fifo.getConsumerTiles().size() == 1)
1423 sharedMemAdjacency.addEdgeFromValues(fifo.getProducerTile(),
1424 fifo.getConsumerTiles()[0]);
1429void SAPlacer::buildFifoBufferInfo(DeviceOp device,
1430 ArrayRef<ObjectFifoCreateOp> objectFifos,
1431 ArrayRef<ObjectFifoLinkOp> objectFifoLinks) {
1432 mlir::DataLayout dataLayout(device->getParentOfType<ModuleOp>());
1434 llvm::StringMap<Operation *> delegates;
1435 for (
auto alloc : device.getOps<ObjectFifoAllocateOp>())
1436 delegates[alloc.getObjFifoName()] = alloc.getDelegateTile().getDefiningOp();
1438 for (
auto ofOp : objectFifos) {
1440 fb.fifoOp = ofOp.getOperation();
1441 fb.delegate = delegates.lookup(ofOp.getSymName());
1444 auto *prodOp = ofOp.getProducerTile().getDefiningOp();
1445 fb.producer = (isa_and_nonnull<LogicalTileOp>(prodOp)) ? prodOp : nullptr;
1448 for (Value ct : ofOp.getConsumerTiles()) {
1449 auto *consOp = ct.getDefiningOp();
1450 if (isa_and_nonnull<LogicalTileOp>(consOp))
1451 fb.consumers.push_back(consOp);
1455 auto fifoType = llvm::cast<AIEObjectFifoType>(ofOp.getElemType());
1456 auto elemType = llvm::cast<MemRefType>(fifoType.getElementType());
1457 int64_t elementBits =
1458 dataLayout.getTypeSizeInBits(elemType.getElementType());
1459 int64_t producerBytes = elemType.getNumElements() * elementBits / 8;
1460 AIETileType prodType =
1461 fb.producer ? tileTypes.lookup(fb.producer) : AIETileType::CoreTile;
1462 fb.producerSizeBytes =
1463 alignedBufferBytes(*
targetModel, prodType, producerBytes);
1466 int64_t consumerBytes = producerBytes;
1467 if (
auto consType = ofOp.getConsumerElemType()) {
1468 auto consOFType = llvm::cast<AIEObjectFifoType>(consType.value());
1469 auto consMemref = llvm::cast<MemRefType>(consOFType.getElementType());
1471 dataLayout.getTypeSizeInBits(consMemref.getElementType());
1472 consumerBytes = consMemref.getNumElements() * consBits / 8;
1476 bool coreConsumer = llvm::any_of(fb.consumers, [&](Operation *cons) {
1477 return tileTypes.lookup(cons) == AIETileType::CoreTile;
1479 fb.consumerSizeBytes = alignedBufferBytes(
1481 coreConsumer ? AIETileType::CoreTile : AIETileType::MemTile,
1485 if (
auto arrayAttr = dyn_cast<ArrayAttr>(ofOp.getElemNumber())) {
1486 auto values = arrayAttr.getValue();
1490 :
static_cast<int>(cast<IntegerAttr>(values[0]).getInt());
1491 for (
size_t i = 0; i < fb.consumers.size(); i++) {
1493 int d = (idx < values.size())
1494 ?
static_cast<int>(cast<IntegerAttr>(values[idx]).getInt())
1496 fb.consumerDepths.push_back(d);
1500 static_cast<int>(cast<IntegerAttr>(ofOp.getElemNumber()).getInt());
1501 fb.producerDepth = d;
1502 for (
size_t i = 0; i < fb.consumers.size(); i++)
1503 fb.consumerDepths.push_back(d);
1508 bool isLinked =
false;
1509 for (
auto link : objectFifoLinks) {
1510 for (
auto in : link.getInputObjectFifos())
1511 if (in.name() == ofOp.getSymName())
1513 for (
auto out : link.getOutputObjectFifos())
1514 if (out.name() == ofOp.getSymName())
1517 fb.forcesDMA = ofOp.forcesDMA() || ofOp.getRepeatCount().has_value() ||
1518 ofOp.getConsumerElemType().has_value() ||
1519 !ofOp.getDimensionsToStream().empty() || isLinked;
1524 for (
auto link : objectFifoLinks) {
1525 for (
auto out : link.getOutputObjectFifos()) {
1526 if (out.name() == ofOp.getSymName())
1527 fb.linkSharedProd =
true;
1531 fifoBuffers.push_back(fb);
1534 LLVM_DEBUG(llvm::dbgs() <<
"[SA] seed=" << rngSeed <<
"\n");
1535 LLVM_DEBUG(llvm::dbgs() <<
"[SA] Collected " << fifoBuffers.size()
1536 <<
" ObjectFifo buffer requirements\n");
1543 DenseMap<std::pair<Operation *, Operation *>,
int> endpointMaxAcquire;
1544 device.walk([&](CoreOp coreOp) {
1545 auto *tileOp = coreOp.getTile().getDefiningOp();
1546 if (!tileOp || !isa<LogicalTileOp>(tileOp))
1548 coreOp.walk([&](ObjectFifoAcquireOp acqOp) {
1549 auto fifoOp = acqOp.getObjectFifo();
1552 auto endpoint = std::pair{
static_cast<Operation *
>(fifoOp), tileOp};
1553 int acqNum = acqOp.acqNumber();
1554 auto it = endpointMaxAcquire.find(endpoint);
1555 if (it == endpointMaxAcquire.end() || acqNum > it->second)
1556 endpointMaxAcquire[endpoint] = acqNum;
1560 for (
auto &fb : fifoBuffers) {
1562 fb.producerDMADepth = fb.producerDepth;
1564 auto it = endpointMaxAcquire.find(std::pair{fb.fifoOp, fb.producer});
1565 if (it != endpointMaxAcquire.end()) {
1566 int maxAcq = it->second;
1567 fb.producerDMADepth =
1568 (maxAcq == 1 && fb.producerDepth == 1) ? 1 : maxAcq + 1;
1572 for (
size_t ci = 0; ci < fb.consumers.size(); ci++) {
1574 fb.consumerDepths.empty() ? fb.producerDepth : fb.consumerDepths[ci];
1575 if (fb.consumers[ci]) {
1577 endpointMaxAcquire.find(std::pair{fb.fifoOp, fb.consumers[ci]});
1578 if (it != endpointMaxAcquire.end()) {
1579 int maxAcq = it->second;
1580 int declared = fb.consumerDepthAt(ci);
1581 dmaDepth = (maxAcq == 1 && declared == 1) ? 1 : maxAcq + 1;
1584 fb.consumerDMADepths.push_back(dmaDepth);
1589LogicalResult SAPlacer::generateInitialPlacement() {
1590 auto &logicalTiles = collected.logicalTiles;
1593 std::vector<TileID> availComp =
availability.compTiles;
1594 std::vector<TileID> availMem, availShim;
1596 AIETileType type =
targetModel->getTileType(t.col, t.row);
1597 if (type == AIETileType::MemTile)
1598 availMem.push_back(t);
1599 else if (type == AIETileType::ShimNOCTile)
1600 availShim.push_back(t);
1608 int phaseA = (a.col + a.row) % 2;
1609 int phaseB = (b.col + b.row) % 2;
1610 if (phaseA != phaseB)
1611 return phaseA < phaseB;
1613 return a.row < b.row;
1614 return a.col < b.col;
1620 int phaseA = a.col % 2;
1621 int phaseB = b.col % 2;
1622 if (phaseA != phaseB)
1623 return phaseA < phaseB;
1624 return a.col < b.col;
1626 deterministicShuffle(availShim, rng);
1628 size_t compIdx = 0, memIdx = 0, shimIdx = 0;
1630 for (
auto lt : logicalTiles) {
1631 Operation *op = lt.getOperation();
1632 auto col = lt.tryGetCol();
1633 auto row = lt.tryGetRow();
1634 AIETileType type = lt.getTileType();
1639 currentPlacement[op] = pos;
1640 physToLogical[pos] = op;
1641 constrainedTiles.insert(op);
1644 if (type == AIETileType::CoreTile)
1645 llvm::erase(availComp, pos);
1646 else if (type == AIETileType::MemTile)
1647 llvm::erase(availMem, pos);
1648 else if (type == AIETileType::ShimNOCTile)
1649 llvm::erase(availShim, pos);
1656 auto byMemWeightDesc = [
this](Operation *a, Operation *b) {
1657 int64_t wa = computeTileMemoryWeight(a);
1658 int64_t wb = computeTileMemoryWeight(b);
1661 return a->isBeforeInBlock(b);
1664 SmallVector<Operation *> sortedCoreTiles;
1665 for (
auto lt : logicalTiles) {
1666 Operation *op = lt.getOperation();
1667 if (constrainedTiles.contains(op))
1669 if (lt.getTileType() == AIETileType::CoreTile)
1670 sortedCoreTiles.push_back(op);
1672 llvm::sort(sortedCoreTiles, byMemWeightDesc);
1674 for (
auto *op : sortedCoreTiles) {
1677 for (
size_t i = compIdx; i < availComp.size(); i++) {
1678 if (isLegalPosition(op, availComp[i])) {
1680 std::swap(availComp[i], availComp[compIdx]);
1687 return cast<LogicalTileOp>(op).emitError(
1688 "no available physical tile for placement");
1689 currentPlacement[op] = pos;
1690 physToLogical[pos] = op;
1691 movableTiles.push_back(op);
1698 SmallVector<Operation *> sortedMemTiles, shimTiles;
1699 for (
auto lt : logicalTiles) {
1700 Operation *op = lt.getOperation();
1701 if (constrainedTiles.contains(op))
1703 AIETileType type = lt.getTileType();
1704 if (type == AIETileType::MemTile)
1705 sortedMemTiles.push_back(op);
1706 else if (type == AIETileType::ShimNOCTile)
1707 shimTiles.push_back(op);
1709 llvm::sort(sortedMemTiles, byMemWeightDesc);
1711 for (
auto *op : sortedMemTiles) {
1712 if (availMem.empty())
1713 return cast<LogicalTileOp>(op).emitError(
1714 "no available physical tile for placement");
1715 TileID pos = availMem[memIdx % availMem.size()];
1717 currentPlacement[op] = pos;
1718 movableTiles.push_back(op);
1721 for (
auto *op : shimTiles) {
1722 if (availShim.empty())
1723 return cast<LogicalTileOp>(op).emitError(
1724 "no available physical tile for placement");
1725 TileID pos = availShim[shimIdx % availShim.size()];
1727 if (!isLegalPosition(op, pos))
1728 return cast<LogicalTileOp>(op).emitError(
1729 "no available physical tile for placement");
1730 currentPlacement[op] = pos;
1731 movableTiles.push_back(op);
1738void SAPlacer::initializeSAState() {
1739 initBoundingBoxes();
1740 initResourceTracking();
1742 cascadePen = computePairingPenalty();
1743 int memPressure = computeMemoryPressure();
1745 computeTotalHPWL() + getResourcePenalty() + cascadePen + memPressure;
1747 deviceSlots =
availability.compTiles.size() + movableTiles.size();
1749 LLVM_DEBUG(llvm::dbgs() <<
"[SA] Initial cost: " << totalCost
1750 <<
" (HPWL=" << computeTotalHPWL()
1751 <<
", resPenalty=" << getResourcePenalty()
1752 <<
"), movable tiles: " << movableTiles.size()
1753 <<
", nets: " << nets.size()
1754 <<
", fifos: " << fifoBuffers.size() <<
"\n");
1757void SAPlacer::runSAMainLoop() {
1760 !nets.empty() || !fifoBuffers.empty() || !cascadeAdjacency.edges.empty();
1761 if (movableTiles.empty() || !hasCostTerms)
1765 int numMovable = movableTiles.size();
1766 int numNets = nets.size();
1769 std::clamp(
static_cast<int>(numMovable * std::sqrt(std::max(numNets, 1))),
1770 config.minMovesPerIter, config.maxMovesPerIter);
1772 int maxIters = std::max(
1774 static_cast<int>(1000 * std::sqrt(
static_cast<double>(numMovable) *
1775 std::max(numNets, 1))));
1777 int greedyIters = config.greedyMultiplier * numMovable;
1780 auto scaled = [&](
int budget) {
1781 double v = std::min(budget * config.effort,
1782 static_cast<double>(std::numeric_limits<int>::max()));
1783 return std::max(1,
static_cast<int>(v));
1785 movesPerIter = scaled(movesPerIter);
1790 greedyIters = scaled(greedyIters);
1792 int numSamples = std::max(10 * numMovable, 50);
1793 double estimatedT = estimateInitialTemperature(numSamples);
1795 std::min(config.tempScaleEstimate * estimatedT,
1796 static_cast<double>(totalCost) * config.tempScaleCost);
1797 initTemp = std::max(initTemp, 1.0);
1799 LLVM_DEBUG(llvm::dbgs() <<
"[SA] Schedule: movesPerIter=" << movesPerIter
1800 <<
" maxIters=" << maxIters
1801 <<
" initTemp=" << initTemp <<
"\n");
1803 schedule = SASchedule(initTemp, movesPerIter, maxIters, greedyIters, config);
1805 bestPlacement = currentPlacement;
1807 (getResourcePenalty() == 0 && cascadePen == 0) ? totalCost : INT_MAX;
1808 bestOverallPlacement = currentPlacement;
1809 bestOverallCost = totalCost;
1810 zeroDeltaMoves = posDeltaMoves = negDeltaMoves = 0;
1811 acceptedUphill = rejectedMoves = 0;
1813 while (!schedule.limitReached()) {
1814 for (
int m = 0; m < schedule.getMovesPerIter(); m++) {
1816 SmallVector<std::pair<Operation *, TileID>> moves;
1817 if (!generateMove(moves))
1822 if (schedule.getIteration() % config.debugInterval == 0)
1823 LLVM_DEBUG(llvm::dbgs()
1824 <<
"[SA] Iter " << schedule.getIteration()
1825 <<
" T=" << llvm::format(
"%.3e", schedule.getTemperature())
1826 <<
" cost=" << totalCost <<
" best=" << bestCost <<
" accept="
1827 << llvm::format(
"%.3f", schedule.getAcceptanceRatio())
1828 <<
" d0=" << zeroDeltaMoves <<
" d+=" << posDeltaMoves
1829 <<
" d-=" << negDeltaMoves <<
" uphill=" << acceptedUphill
1830 <<
" rej=" << rejectedMoves
1831 << (schedule.isGreedy() ?
" (greedy)" :
"") <<
"\n");
1834 if (schedule.getIteration() % config.fullRecomputeInterval == 0 &&
1835 schedule.getIteration() > 0) {
1836 initResourceTracking();
1837 cascadePen = computePairingPenalty();
1838 totalCost = computeTotalHPWL() + getResourcePenalty() + cascadePen +
1839 computeMemoryPressure();
1850void SAPlacer::tryMove(SmallVector<std::pair<Operation *, TileID>> &moves) {
1852 SmallVector<std::pair<Operation *, TileID>> oldPlacements;
1853 for (
auto &[t, _] : moves)
1854 oldPlacements.push_back({t, currentPlacement[t]});
1855 int oldResPenalty = cachedResourcePenalty;
1856 auto savedMemUsage = currentMemUsage;
1857 auto savedDMAUsage = currentDMAUsage;
1858 auto savedSharedMemDest = sharedMemDestination;
1861 for (
auto &[t, _] : moves)
1862 physToLogical.erase(currentPlacement[t]);
1863 SmallVector<SmallVector<std::pair<size_t, NetBoundingBox>>> allBackups(
1866 for (
size_t i = 0; i < moves.size(); i++) {
1867 hpwlDelta += evaluateMove(moves[i].first, moves[i].second, allBackups[i]);
1868 currentPlacement[moves[i].first] = moves[i].second;
1870 for (
auto &[t, pos] : moves)
1871 physToLogical[pos] = t;
1874 int oldCascade = cascadePen;
1875 int oldPressure = computeMemoryPressure();
1876 int newResPenalty = updateResourcePenalty(oldPlacements);
1877 int newCascade = computePairingPenalty();
1878 int newPressure = computeMemoryPressure();
1879 int delta = hpwlDelta + (newResPenalty - oldResPenalty) +
1880 (newCascade - oldCascade) + (newPressure - oldPressure);
1893 (schedule.getTemperature() > 0 &&
1894 drawUnitReal(rng) < std::exp(-delta / schedule.getTemperature()));
1900 cascadePen = newCascade;
1901 schedule.record(
true);
1902 if (totalCost < bestOverallCost) {
1903 bestOverallCost = totalCost;
1904 bestOverallPlacement = currentPlacement;
1908 if (totalCost < bestCost && newResPenalty == 0 && newCascade == 0) {
1909 bestCost = totalCost;
1910 bestPlacement = currentPlacement;
1914 for (
auto &[t, pos] : moves)
1915 physToLogical.erase(pos);
1916 for (
int i = moves.size() - 1; i >= 0; i--) {
1917 revertMove(allBackups[i]);
1918 currentPlacement[moves[i].first] = oldPlacements[i].second;
1920 for (
auto &[op, oldPos] : oldPlacements)
1921 physToLogical[oldPos] = op;
1922 currentMemUsage = savedMemUsage;
1923 currentDMAUsage = savedDMAUsage;
1924 sharedMemDestination = savedSharedMemDest;
1925 cachedResourcePenalty = oldResPenalty;
1927 schedule.record(
false);
1935void SAPlacer::printPlacementStats(int64_t elapsedMs)
const {
1936 int finalHPWL = computeTotalHPWL();
1937 int finalHardPenalty = getResourcePenalty();
1938 int finalPressure = computeMemoryPressure();
1939 int finalCascade = computePairingPenalty();
1940 bool isLegal = (finalHardPenalty == 0 && finalCascade == 0);
1942 llvm::dbgs() <<
"[SA] seed=" << rngSeed <<
" Final HPWL=" << finalHPWL
1943 <<
", resPenalty=" << finalHardPenalty
1944 <<
", pressure=" << finalPressure <<
", cascade=" << finalCascade
1945 <<
", legal=" << (isLegal ?
"yes" :
"NO") <<
"\n";
1946 llvm::dbgs() <<
"[SA] " << schedule.getIteration() <<
" iters, "
1947 << movableTiles.size() <<
" tiles, " << nets.size() <<
" nets, "
1948 <<
allocates.size() <<
" allocates, " << elapsedMs <<
" ms\n";
1949 llvm::dbgs() <<
"[SA] Moves: d-=" << negDeltaMoves <<
" d0=" << zeroDeltaMoves
1950 <<
" d+=" << posDeltaMoves <<
" uphill=" << acceptedUphill
1951 <<
" rej=" << rejectedMoves <<
"\n";
1954 int64_t coreCap =
targetModel->getLocalMemorySize();
1956 for (
auto &[pos, used] : currentMemUsage) {
1957 auto type =
targetModel->getTileType(pos.col, pos.row);
1958 int64_t cap = (type == AIETileType::MemTile) ? memCap : coreCap;
1959 if (used > cap * 3 / 4)
1960 llvm::dbgs() <<
"[SA-RES] mem(" << pos.col <<
"," << pos.row
1961 <<
"): " << used <<
"/" << cap
1962 << (used > cap ?
" OVERFLOW" :
"") <<
"\n";
1964 for (
auto &[pos, usage] : currentDMAUsage) {
1966 if (usage.first >= maxIn || usage.second >= maxOut)
1967 llvm::dbgs() <<
"[SA-RES] dma(" << pos.col <<
"," << pos.row
1968 <<
"): in=" << usage.first <<
"/" << maxIn
1969 <<
" out=" << usage.second <<
"/" << maxOut
1970 << (usage.first > maxIn || usage.second > maxOut
1977 for (
auto &[pos, used] : currentMemUsage) {
1978 auto type =
targetModel->getTileType(pos.col, pos.row);
1979 if (type != AIETileType::CoreTile || used <= coreCap)
1981 int64_t overflow = used - coreCap;
1983 auto it = currentMemUsage.find(n);
1984 int64_t nbrUsed = it != currentMemUsage.end() ? it->second : 0;
1985 llvm::dbgs() <<
" nbr(" << n.col <<
"," << n.row <<
"): used=" << nbrUsed
1986 <<
" spare=" << std::max(coreCap - nbrUsed, int64_t{0})
1989 llvm::dbgs() <<
"[SA-PENALTY] tile(" << pos.col <<
"," << pos.row
1990 <<
"): used=" << used <<
" cap=" << coreCap
1991 <<
" overflow=" << overflow <<
"\n";
1999LogicalResult SAPlacer::finalizePlacement(DeviceOp device) {
2002 finalCost = totalCost;
2003 if (bestOverallCost < INT_MAX) {
2004 if (bestCost < INT_MAX) {
2005 currentPlacement = bestPlacement;
2006 finalCost = bestCost;
2008 currentPlacement = bestOverallPlacement;
2009 finalCost = bestOverallCost;
2011 physToLogical.clear();
2012 for (
auto &[op, pos] : currentPlacement)
2013 physToLogical[pos] = op;
2014 initResourceTracking();
2018 generateAllocates();
2021 if (failed(mergeMemShimTiles(device)))
2024 auto endTime = std::chrono::steady_clock::now();
2025 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
2026 endTime - startTime);
2028 LLVM_DEBUG(printPlacementStats(elapsed.count()));
2037 int finalPairing = computePairingPenalty();
2038 int finalDelegate = computeDelegatePenalty();
2039 if (finalPairing != 0 || finalDelegate != 0) {
2040 device.emitError(
"SA placer failed to find a legal placement (cascade and "
2041 "shared memory penalty=")
2042 << finalPairing <<
", delegate penalty=" << finalDelegate
2043 <<
"); try increasing --sa-effort";
2046 if (
int estimated = getResourcePenalty() - finalDelegate; estimated != 0)
2047 device.emitWarning(
"SA placer's resource estimate is over capacity "
2050 <<
"); later passes check exactly, and increasing --sa-effort may help";
2052 result = currentPlacement;
virtual int rows() const =0
Return the number of rows in the device.
virtual int columns() const =0
Return the number of columns in the device.
@ First
Only the first tile's module.
virtual bool isLegalMemAffinity(int coreCol, int coreRow, int memCol, int memRow) const =0
Return true if core can access the memory in mem.
static Adjacency buildCascadeAdjacency(llvm::ArrayRef< CascadeFlowOp > cascadeFlows)
static bool satisfiesConstraints(mlir::Operation *tile, TileID pos)
const AIETargetModel * targetModel
llvm::SmallVector< AllocateInfo > allocates
TileAvailability availability
static ShareableShimEnds collectShareableShimEnds(DeviceOp device, llvm::ArrayRef< ObjectFifoCreateOp > objectFifos)
static CollectedOps collectOperations(DeviceOp device)
static std::optional< ShimTransferSpans::End > shimEndOf(DeviceOp device, mlir::StringAttr symbol)
mlir::LogicalResult place(DeviceOp device) override
void record(bool accepted)
bool limitReached() const
double getAcceptanceRatio() const
struct TileID { friend std::ostream &operator<<(std::ostream &os, const TileID &s) { os<< "TileID("<< s.col<< ", "<< s.row<< ")"; return os; } friend std::string to_string(const TileID &s) { std::ostringstream ss; ss<< s; return ss.str(); } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const TileID &s) { os<< to_string(s); return os; } inline bool operator<(const TileID &rhs) const { return std::tie(col, row)< std::tie(rhs.col, rhs.row); } bool operator==(const TileID &rhs) const { return std::tie(col, row)==std::tie(rhs.col, rhs.row); } bool operator!=(const TileID &rhs) const { return !(*this==rhs);} int col, row;} TileID
std::pair< int, int > getDMACapacity(const AIETargetModel &tm, TileID tile)
Get DMA channel capacity (maxIn, maxOut) for a tile position.
uint32_t requiredBufferAlignBits(uint32_t busAlignBits, uint32_t maxVecAlignBits, int64_t sizeBytes)
Return the alignment (in bits) a buffer of sizeBytes must satisfy.
TileMemoryLimits tileMemoryLimits(const AIETargetModel &targetModel, AIETileType tileType)
llvm::SmallVector< mlir::Operation * > endpoints
llvm::SmallVector< RouteOp > routes
llvm::SmallVector< ObjectFifoLinkOp > objectFifoLinks
llvm::SmallVector< ObjectFifoCreateOp > objectFifos
llvm::SmallVector< LogicalTileOp > logicalTiles
A memtile is reached by DMA rather than by core vector load and store, so its bus width also covers t...
uint32_t tileAlignBitWidth