13#include "mlir/Interfaces/ViewLikeInterface.h"
14#include "llvm/ADT/DenseSet.h"
15#include "llvm/ADT/MapVector.h"
16#include "llvm/ADT/STLExtras.h"
17#include "llvm/ADT/TypeSwitch.h"
18#include "llvm/Support/Debug.h"
26#define DEBUG_TYPE "aie-placer"
28static std::optional<TileID> resolvePeerPosition(TileLike peer,
32 if (!first || !second)
34 unsigned idx =
edges.size();
35 edges.push_back({first, second});
36 if (mlir::isa<LogicalTileOp>(first.getOperation()))
38 if (mlir::isa<LogicalTileOp>(second.getOperation()))
45 auto aT = dyn_cast_or_null<TileLike>(a.getDefiningOp());
46 auto bT = dyn_cast_or_null<TileLike>(b.getDefiningOp());
55 for (
int col = 0; col < tm.
columns(); col++) {
56 for (
int row = 0; row < tm.
rows(); row++) {
59 case AIETileType::CoreTile:
62 case AIETileType::MemTile:
63 case AIETileType::ShimNOCTile:
75 device.walk([&](Operation *op) {
76 llvm::TypeSwitch<Operation *>(op)
77 .Case<LogicalTileOp>([&](
auto lt) { ops.
logicalTiles.push_back(lt); })
78 .Case<ObjectFifoCreateOp>(
80 .Case<ObjectFifoLinkOp>(
82 .Case<ObjectFifoAllocateOp>(
84 .Case<CascadeFlowOp>([&](
auto cf) { ops.
cascadeFlows.push_back(cf); })
85 .Case<FlowOp>([&](
auto f) { ops.
flows.push_back(f); })
86 .Case<PacketFlowOp>([&](
auto pf) { ops.
pktFlows.push_back(pf); })
87 .Case<RouteOp>([&](
auto route) { ops.
routes.push_back(route); });
93 llvm::stable_sort(ops.
logicalTiles, [&](LogicalTileOp a, LogicalTileOp b) {
94 if (a.getTileType() != b.getTileType())
95 return a.getTileType() > b.getTileType();
96 return rank.lookup(a) < rank.lookup(b);
102 if (
auto lt = dyn_cast<LogicalTileOp>(tile)) {
103 if (
auto c = lt.tryGetCol(); c && *c != pos.col)
105 if (
auto r = lt.tryGetRow(); r && *r != pos.row)
118 return a.col < b.col;
119 return a.row < b.row;
123 return a.row < b.row;
124 return a.col < b.col;
134 if (coresPerCol.has_value()) {
135 llvm::DenseMap<int, int> coresInColumn;
137 coresInColumn[tile.col]++;
138 int maxDeviceCoresPerCol = 0;
139 for (
const auto &[col, count] : coresInColumn)
140 maxDeviceCoresPerCol = std::max(maxDeviceCoresPerCol, count);
141 deviceCoresPerCol = maxDeviceCoresPerCol;
142 limitCoresPerColumn(*coresPerCol,
targetModel.columns());
146void SequentialPlacer::limitCoresPerColumn(
int maxCoresPerCol,
int numColumns) {
147 llvm::DenseMap<int, std::vector<TileID>> tilesByColumn;
149 tilesByColumn[tile.col].push_back(tile);
153 std::vector<TileID> limitedTiles;
154 for (
int col = 0; col < numColumns; col++) {
155 auto it = tilesByColumn.find(col);
156 if (it == tilesByColumn.end())
158 const auto &tilesInCol = it->second;
160 std::min(tilesInCol.size(),
static_cast<size_t>(maxCoresPerCol));
161 limitedTiles.insert(limitedTiles.end(), tilesInCol.begin(),
162 tilesInCol.begin() + numToTake);
174 return device.emitError() <<
"SequentialPlacer::place called before "
175 "initialize(); targetModel is null";
176 if (coresPerCol.has_value() && *coresPerCol > deviceCoresPerCol) {
177 return device.emitError() <<
"requested cores-per-col (" << *coresPerCol
178 <<
") exceeds device capacity ("
179 << deviceCoresPerCol <<
" cores per column)";
184 auto &logicalTiles = collected.logicalTiles;
185 auto &objectFifos = collected.objectFifos;
186 auto &objectFifoLinks = collected.objectFifoLinks;
187 auto &cascadeFlows = collected.cascadeFlows;
188 auto &flows = collected.flows;
189 auto &pktFlows = collected.pktFlows;
190 auto &routes = collected.routes;
193 auto bufferAdjacency =
194 buildBufferAdjacency(logicalTiles, collected.objectFifoAllocates);
200 auto channelRequirements =
201 buildChannelRequirements(objectFifos, objectFifoLinks);
202 addChannelRequirementsFromFlows(flows, pktFlows, routes, channelRequirements);
203 countTurns(device, objectFifos, channelRequirements);
204 buildStreamDemand(objectFifos);
205 buildRuntimeBdProfiles(device, objectFifos);
208 device, [&](StringAttr symbol) {
return shimEndOf(device, symbol); });
216 llvm::DenseMap<Operation *, int> forcedSharedMemIn, forcedSharedMemOut;
217 for (
auto ofOp : objectFifos) {
218 if (!ofOp.forcesSharedMemory() || ofOp.getConsumerTiles().size() != 1)
221 dyn_cast_or_null<LogicalTileOp>(ofOp.getProducerTile().getDefiningOp());
222 auto consumer = dyn_cast_or_null<LogicalTileOp>(
223 ofOp.getConsumerTiles()[0].getDefiningOp());
224 if (!producer || producer.getTileType() != AIETileType::CoreTile)
226 if (!consumer || consumer.getTileType() != AIETileType::CoreTile)
228 if (producer.getOperation() == consumer.getOperation())
230 ++forcedSharedMemOut[producer.getOperation()];
231 ++forcedSharedMemIn[consumer.getOperation()];
245 llvm::DenseMap<Operation *, int> needNeighborIn, needNeighborOut;
246 for (
auto lt : logicalTiles) {
247 if (lt.getTileType() != AIETileType::CoreTile)
249 auto [peerIn, peerOut] =
250 totalComputePeers(lt.getOperation(), computePeerAdjacency);
251 int inBudget = lt.getNumDestConnections(WireBundle::DMA);
252 int outBudget = lt.getNumSourceConnections(WireBundle::DMA);
253 auto [nonPeerIn, nonPeerOut] =
254 channelRequirements.lookup(lt.getOperation());
255 needNeighborIn[lt.getOperation()] =
256 std::max({0, peerIn - (inBudget - nonPeerIn),
257 forcedSharedMemIn.lookup(lt.getOperation())});
258 needNeighborOut[lt.getOperation()] =
259 std::max({0, peerOut - (outBudget - nonPeerOut),
260 forcedSharedMemOut.lookup(lt.getOperation())});
261 LLVM_DEBUG(llvm::dbgs()
262 <<
DEBUG_TYPE <<
": LTO " << lt.getLoc() <<
" peerIn=" << peerIn
263 <<
" peerOut=" << peerOut <<
" nonPeerIn=" << nonPeerIn
264 <<
" nonPeerOut=" << nonPeerOut
265 <<
" => needNeighborIn=" << needNeighborIn[lt.getOperation()]
266 <<
" needNeighborOut=" << needNeighborOut[lt.getOperation()]
269 PlacementContext ctx{*
targetModel, computePeerAdjacency, needNeighborIn,
275 FlowMembership flowIndex =
276 buildFlowMembership(flows, pktFlows, routes, objectFifos);
277 CoreGroups coreGroups = buildCoreGroups(logicalTiles, flowIndex);
282 auto bufferPred = [
this](
TileID consumerPos,
TileID ownerPos) {
283 return targetModel->isLegalMemAffinity(consumerPos.col, consumerPos.row,
284 ownerPos.col, ownerPos.row);
286 auto bufferLabel = [](
bool thisIsConsumer) -> StringRef {
287 return thisIsConsumer ?
"shared-L1 buffer owner"
288 :
"shared-L1 buffer consumer";
293 auto cascadePred = [
this](
TileID srcPos,
TileID dstPos) {
294 return targetModel->isSouth(srcPos.col, srcPos.row, dstPos.col,
296 targetModel->isEast(srcPos.col, srcPos.row, dstPos.col, dstPos.row);
298 auto cascadeLabel = [](
bool thisIsSrc) -> StringRef {
299 return thisIsSrc ?
"cascade destination" :
"cascade source";
307 return targetModel->isLegalMemAffinity(aPos.col, aPos.row, bPos.col,
309 targetModel->isLegalMemAffinity(bPos.col, bPos.row, aPos.col,
312 auto sharedMemLabel = [](
bool thisIsProducer) -> StringRef {
313 return thisIsProducer ?
"shared-memory consumer" :
"shared-memory producer";
316 AdjacencyKind bufferKind{
317 bufferAdjacency, bufferPred, bufferLabel,
"shared-L1 buffer",
318 "shared-L1 buffer adjacency requires this LTO to be on a tile "
319 "whose L1 is shared with the buffer owner's tile (N/S neighbor, "
320 "or W neighbor per the device's checkerboard rule)"};
321 AdjacencyKind cascadeKind{
322 cascadeAdjacency, cascadePred, cascadeLabel,
"cascade",
323 "cascade adjacency requires the destination tile to be one row "
324 "South or one column East of the source tile"};
325 AdjacencyKind sharedMemKind{
326 sharedMemAdjacency, sharedMemPred, sharedMemLabel,
327 "shared-memory transport",
328 "a `shared_mem` objectFifo requires its producer and consumer to "
329 "land on tiles that share a memory module (N/S neighbor, or W "
330 "neighbor per the device's checkerboard rule)"};
350 SmallVector<LogicalTileOp> orderedTiles;
351 llvm::DenseSet<unsigned> emittedGroups;
352 for (
auto lt : logicalTiles) {
353 auto group = coreGroups.groupOf.find(lt.getOperation());
354 if (group == coreGroups.groupOf.end())
355 orderedTiles.push_back(lt);
356 else if (emittedGroups.insert(group->second).second)
357 llvm::append_range(orderedTiles, coreGroups.members[group->second]);
359 llvm::stable_sort(orderedTiles, [&](LogicalTileOp a, LogicalTileOp b) {
360 auto rank = [](LogicalTileOp lt) {
361 bool hasCol = lt.tryGetCol().has_value();
362 bool hasRow = lt.tryGetRow().has_value();
363 if (hasCol && hasRow)
365 if (hasCol || hasRow)
369 int ra = rank(a), rb = rank(b);
377 int pa = ctx.placementPriority(a.getOperation());
378 int pb = ctx.placementPriority(b.getOperation());
384 return ctx.neighborDemand(a.getOperation()) >
385 ctx.neighborDemand(b.getOperation());
390 auto unpinned = [](LogicalTileOp lt) {
391 return !lt.tryGetCol() && !lt.tryGetRow();
393 SmallVector<LogicalTileOp> sequenced;
394 llvm::DenseSet<Operation *> queued;
395 for (
auto start : orderedTiles) {
396 if (!queued.insert(start.getOperation()).second)
398 size_t next = sequenced.size();
399 sequenced.push_back(start);
400 for (; next < sequenced.size(); ++next) {
401 LogicalTileOp lt = sequenced[next];
405 [&](TileLike peer,
bool ) {
406 auto peerLt = dyn_cast<LogicalTileOp>(peer.getOperation());
407 if (peerLt && unpinned(peerLt) &&
408 queued.insert(peerLt.getOperation()).second)
409 sequenced.push_back(peerLt);
413 orderedTiles = std::move(sequenced);
415 for (
auto logicalTile : orderedTiles) {
417 auto col = logicalTile.tryGetCol();
418 auto row = logicalTile.tryGetRow();
421 if (failed(enforceAdjacency(logicalTile, tile, bufferKind)))
423 if (failed(enforceAdjacency(logicalTile, tile, cascadeKind)))
425 if (failed(enforceAdjacency(logicalTile, tile, sharedMemKind)))
427 if (failed(validateAndUpdateChannelUsage(
428 logicalTile, tile, channelRequirements, PlacementOrigin::Pinned)))
431 recordPlacement(logicalTile, tile, ctx,
"pinned");
436 if (logicalTile.getTileType() == AIETileType::CoreTile) {
437 auto isReservedForOtherBound = [&](Operation *lto,
TileID candidate) {
438 return ctx.isReservedForOther(lto, candidate);
440 SmallVector<int> preferredCols =
441 preferredGroupColumns(logicalTile, coreGroups);
442 UnpinnedPlacementInputs inputs{
443 bufferAdjacency, bufferPred,
444 cascadeAdjacency, cascadePred,
445 sharedMemAdjacency, sharedMemPred,
446 computePeerAdjacency, needNeighborIn,
447 needNeighborOut, isReservedForOtherBound,
450 findUnconstrainedCoreCandidate(logicalTile, col, row, inputs);
451 std::optional<TileID> placement = search.placement;
452 bool sawConstraintMatch = search.sawConstraintMatch;
453 bool allConstraintMatchesFailedAdjacency =
454 search.allConstraintMatchesFailedAdjacency;
455 bool computePeerWasCause = search.computePeerWasCause;
458 bool adjacencyWasCause =
459 sawConstraintMatch && allConstraintMatchesFailedAdjacency &&
460 bufferAdjacency.hasEdges(logicalTile.getOperation());
461 bool hasCascade = cascadeAdjacency.hasEdges(logicalTile.getOperation());
463 sharedMemAdjacency.hasEdges(logicalTile.getOperation());
464 InFlightDiagnostic diag = logicalTile.emitError();
466 diag <<
"no compute tile available matching constraint ("
467 << (col ? std::to_string(*col) :
"?") <<
", "
468 << (row ? std::to_string(*row) :
"?") <<
")"
469 << (adjacencyWasCause ?
" and shared-L1 buffer adjacency" :
"")
470 << (hasCascade ?
" and cascade adjacency" :
"")
471 << (hasSharedMem ?
" and shared-memory transport" :
"")
472 << (computePeerWasCause ?
" and compute-peer DMA budget" :
"");
474 diag <<
"no available compute tiles for placement"
475 << (adjacencyWasCause
476 ?
" (shared-L1 buffer adjacency unsatisfiable)"
478 << (hasCascade ?
" (cascade adjacency unsatisfiable)" :
"")
479 << (hasSharedMem ?
" (shared-memory transport unsatisfiable)"
481 << (computePeerWasCause
482 ?
" (compute-peer DMA budget unsatisfiable)"
487 if (coresPerCol.has_value() && deviceCoresPerCol > 0) {
490 <<
"cores-per-col=" << *coresPerCol <<
" leaves "
491 << columns * *coresPerCol <<
" of this device's "
492 << columns * deviceCoresPerCol
493 <<
" compute tiles placeable and may be limiting placement; "
494 "consider raising it or reducing the design's core count";
496 if (adjacencyWasCause)
497 attachPeerNotes(diag, logicalTile, bufferAdjacency, bufferLabel);
499 attachPeerNotes(diag, logicalTile, cascadeAdjacency, cascadeLabel);
501 attachPeerNotes(diag, logicalTile, sharedMemAdjacency,
503 if (computePeerWasCause) {
504 attachPeerNotes(diag, logicalTile, computePeerAdjacency,
505 [](
bool thisIsProducer) -> StringRef {
506 return thisIsProducer ?
"compute-peer consumer"
507 :
"compute-peer producer";
510 <<
"to fix, pin this LTO to a position that is shared-L1 with "
511 "the placed compute-peers above, or pin one of those peers "
512 "to a different coordinate so this LTO's constraints become "
518 if (failed(validateAndUpdateChannelUsage(logicalTile, *placement,
520 PlacementOrigin::Selected)))
523 recordPlacement(logicalTile, *placement, ctx,
"unconstrained");
526 if (logicalTile.getTileType() == AIETileType::ShimPLTile) {
527 return logicalTile.emitError(
528 "DMA channel-based SequentialPlacer does not support unplaced "
529 "ShimPLTiles (no DMAs).");
535 return placeNonCoreLogicalTiles(logicalTiles, flowIndex, channelRequirements);
538SequentialPlacer::CoreGroups
539SequentialPlacer::buildCoreGroups(ArrayRef<LogicalTileOp> logicalTiles,
540 const FlowMembership &flowIndex)
const {
542 SmallVector<std::pair<LogicalTileOp, llvm::MapVector<Operation *, int>>>
544 llvm::DenseMap<Operation *, int> coresOnHub;
545 for (
auto lt : logicalTiles) {
546 if (lt.getTileType() != AIETileType::CoreTile)
548 llvm::MapVector<Operation *, int> counts;
549 auto it = flowIndex.ltoFlows.find(lt.getResult());
550 if (it != flowIndex.ltoFlows.end())
551 for (
auto &peers : it->second)
552 for (Value p : peers) {
553 auto tile = dyn_cast_or_null<TileLike>(p.getDefiningOp());
554 if (tile && !tile.isCoreTile())
555 ++counts[p.getDefiningOp()];
557 for (
auto &entry : counts)
558 ++coresOnHub[entry.first];
559 coreFlows.emplace_back(lt, std::move(counts));
565 llvm::DenseMap<Operation *, unsigned> groupOfHub;
566 for (
auto &[lt, counts] : coreFlows) {
567 Operation *hub =
nullptr;
569 for (
auto &[peer, n] : counts)
570 if (!hub || n > best ||
571 (n == best && coresOnHub[peer] < coresOnHub[hub])) {
575 unsigned idx = groups.members.size();
577 auto [it, inserted] = groupOfHub.try_emplace(hub, idx);
580 groups.members[idx].push_back(lt);
581 groups.groupOf[lt.getOperation()] = idx;
585 groups.members.push_back({lt});
586 groups.groupOf[lt.getOperation()] = idx;
592SequentialPlacer::preferredGroupColumns(LogicalTileOp logicalTile,
593 const CoreGroups &groups)
const {
594 auto group = groups.groupOf.find(logicalTile.getOperation());
595 if (group == groups.groupOf.end())
597 SmallVector<std::pair<int, int>> placedCols;
599 for (
auto member : groups.members[group->second]) {
600 auto placed =
result.find(member.getOperation());
601 if (placed ==
result.end()) {
605 auto *entry = llvm::find_if(
606 placedCols, [&](
auto &e) {
return e.first == placed->second.col; });
607 if (entry == placedCols.end())
608 placedCols.push_back({placed->second.col, 1});
612 llvm::stable_sort(placedCols, [](
auto &a,
auto &b) {
613 return a.second != b.second ? a.second > b.second : a.first < b.first;
616 SmallVector<int> cols;
617 for (
auto &[col, n] : placedCols)
620 llvm::MapVector<int, int> freeCores;
623 for (
auto &[col, n] : freeCores)
624 if (n >= unplaced && !llvm::is_contained(cols, col))
629LogicalResult SequentialPlacer::placeNonCoreLogicalTiles(
630 ArrayRef<LogicalTileOp> logicalTiles,
const FlowMembership &flowIndex,
631 const llvm::DenseMap<Operation *, std::pair<int, int>>
632 &channelRequirements) {
637 SmallVector<LogicalTileOp> nonCoreOrdered;
638 for (
auto logicalTile : logicalTiles) {
639 if (
result.count(logicalTile.getOperation()))
641 AIETileType tileType = logicalTile.getTileType();
642 if (tileType == AIETileType::CoreTile ||
643 tileType == AIETileType::ShimPLTile)
645 nonCoreOrdered.push_back(logicalTile);
647 llvm::stable_sort(nonCoreOrdered, [&](LogicalTileOp a, LogicalTileOp b) {
648 auto demand = [&](LogicalTileOp lt) {
649 auto chans = channelRequirements.lookup(lt.getOperation());
650 return chans.first + chans.second;
652 return demand(a) > demand(b);
655 for (
auto logicalTile : nonCoreOrdered) {
656 if (failed(placeNonCoreTileByCentroid(logicalTile, flowIndex,
657 channelRequirements)))
663int SequentialPlacer::PlacementContext::placementPriority(Operation *op)
const {
664 int self = neighborDemand(op);
667 [&](TileLike peer,
bool ) {
668 best = std::max(best, neighborDemand(peer.getOperation()));
670 return std::max(self, best);
673void SequentialPlacer::PlacementContext::reserveNeighborSlots(
674 Operation *placedOp,
TileID at) {
675 if (neighborDemand(placedOp) <= 0)
681bool SequentialPlacer::PlacementContext::isReservedForOther(
682 Operation *lto,
TileID candidate)
const {
683 auto it = reservedFor.find(candidate);
684 if (it == reservedFor.end())
686 for (Operation *owner : it->second) {
691 [&](TileLike peer,
bool ) {
692 if (peer.getOperation() == owner)
701SequentialPlacer::UnpinnedSearchResult
702SequentialPlacer::findUnconstrainedCoreCandidate(
703 LogicalTileOp logicalTile, std::optional<int> col, std::optional<int> row,
704 const UnpinnedPlacementInputs &inputs) {
705 UnpinnedSearchResult
result;
706 auto neighborDemand = [&](Operation *op) {
707 return inputs.needNeighborIn.lookup(op) + inputs.needNeighborOut.lookup(op);
717 auto computeNeighborCount = [&](
TileID t) {
720 targetModel->getTileType(t.col, t.row - 1) == AIETileType::CoreTile)
723 targetModel->getTileType(t.col, t.row + 1) == AIETileType::CoreTile)
727 SmallVector<TileID> orderedCandidates(
availability.compTiles.begin(),
729 if (neighborDemand(logicalTile.getOperation()) > 0) {
730 llvm::stable_sort(orderedCandidates, [&](
TileID a,
TileID b) {
732 return a.col < b.col;
733 int na = computeNeighborCount(a);
734 int nb = computeNeighborCount(b);
737 return a.row < b.row;
739 }
else if (!inputs.preferredCols.empty()) {
742 auto rankOf = [&](
TileID t) {
743 return std::distance(inputs.preferredCols.begin(),
744 llvm::find(inputs.preferredCols, t.col));
746 llvm::stable_sort(orderedCandidates, [&](
TileID a,
TileID b) {
747 return rankOf(a) < rankOf(b);
755 auto leavesPeerRoom = [&](
TileID candidate) {
756 llvm::DenseSet<TileID> claimed;
758 forEachPeer(logicalTile.getOperation(), inputs.bufferAdjacency,
759 [&](TileLike peer,
bool thisIsFirst) {
760 if (!room || resolvePeerPosition(peer, this->result))
764 llvm::find_if(availability.compTiles, [&](TileID t) {
765 return t != candidate && !claimed.contains(t) &&
766 (thisIsFirst ? inputs.bufferPred(candidate, t)
767 : inputs.bufferPred(t, candidate));
771 claimed.insert(*free);
776 auto fitsColumnStreams = [&](
TileID candidate) {
777 StreamDemand demand = streamDemand.lookup(logicalTile);
778 StreamDemand used = columnStreamsUsed.lookup(candidate.col);
779 auto incoming = columnIncomingFifos.find(candidate.col);
780 if (incoming != columnIncomingFifos.end())
781 for (Operation *fifo : incomingFifos.lookup(logicalTile))
782 demand.
southIn -= incoming->second.contains(fifo);
785 int bottomRow = candidate.row;
786 while (bottomRow > 0 &&
787 targetModel->isCoreTile(candidate.col, bottomRow - 1))
791 static_cast<int>(
targetModel->getNumSourceSwitchboxConnections(
792 candidate.col, bottomRow, WireBundle::South))) &&
795 static_cast<int>(
targetModel->getNumDestSwitchboxConnections(
796 candidate.col, bottomRow, WireBundle::South)));
802 for (
int pass = 0; pass < 3; ++pass) {
803 for (
const TileID &candidate : orderedCandidates) {
804 if (col && candidate.col != *col)
806 if (row && candidate.row != *row)
809 (inputs.isReservedForOther(logicalTile.getOperation(), candidate) ||
810 !leavesPeerRoom(candidate)))
812 if (pass == 0 && !fitsColumnStreams(candidate))
814 result.sawConstraintMatch =
true;
815 if (!satisfiesAdjacency(logicalTile, candidate, inputs.bufferAdjacency,
818 result.allConstraintMatchesFailedAdjacency =
false;
819 if (!satisfiesAdjacency(logicalTile, candidate, inputs.cascadeAdjacency,
822 if (!satisfiesAdjacency(logicalTile, candidate, inputs.sharedMemAdjacency,
823 inputs.sharedMemPred))
825 if (!satisfiesComputePeer(
826 logicalTile, candidate, inputs.computePeerAdjacency,
827 inputs.needNeighborIn, inputs.needNeighborOut)) {
828 result.computePeerWasCause =
true;
831 result.placement = candidate;
838LogicalResult SequentialPlacer::validateAndUpdateChannelUsage(
839 LogicalTileOp logicalTile,
TileID tile,
840 const llvm::DenseMap<Operation *, std::pair<int, int>> &channelRequirements,
841 PlacementOrigin origin) {
842 auto [inChannels, outChannels] =
843 channelRequirements.lookup(logicalTile.getOperation());
845 if (!hasAvailableChannels(tile, inChannels, outChannels) &&
846 !fitsSharingShim(tile, logicalTile, {inChannels, outChannels})) {
847 int maxIn = logicalTile.getNumDestConnections(WireBundle::DMA);
848 int maxOut = logicalTile.getNumSourceConnections(WireBundle::DMA);
849 int availIn = maxIn -
availability.inputChannelsUsed[tile];
850 int availOut = maxOut -
availability.outputChannelsUsed[tile];
852 auto diag = logicalTile.emitError();
853 diag <<
"tile (" << tile.col <<
", " << tile.row <<
") requires "
854 << inChannels <<
" input/" << outChannels
855 <<
" output DMA channels, but only " << availIn <<
" input/"
856 << availOut <<
" output available";
857 if (origin == PlacementOrigin::Selected)
858 diag.attachNote() <<
"placer selected this tile; to fix, pin this LTO "
859 "to a tile with more spare DMA capacity, or reduce "
860 "the LTO's DMA fanin (e.g. via memtile staging)";
865 updateChannelUsage(tile, DmaDir::In, inChannels);
867 updateChannelUsage(tile, DmaDir::Out, outChannels);
868 recordStreamAndBdUsage(logicalTile, tile);
869 recordShimEnds(tile, logicalTile);
874llvm::DenseMap<Operation *, std::pair<int, int>>
875SequentialPlacer::buildChannelRequirements(
876 SmallVector<ObjectFifoCreateOp> &objectFifos,
877 SmallVector<ObjectFifoLinkOp> &objectFifoLinks) {
878 llvm::DenseMap<Operation *, std::pair<int, int>> channelRequirements;
881 llvm::StringMap<ObjectFifoCreateOp> fifoNameToOp;
882 for (
auto ofOp : objectFifos) {
883 fifoNameToOp[ofOp.getSymName()] = ofOp;
891 llvm::DenseMap<Operation *, Operation *> linkTileOf;
892 llvm::StringMap<Operation *> linkTileOfSource;
893 llvm::DenseSet<llvm::StringRef> linkedAsDest;
894 for (
auto linkOp : objectFifoLinks) {
895 Operation *linkTileOp =
nullptr;
896 for (
auto dstFifoAttr : linkOp.getFifoOuts())
897 if (
auto sym = dyn_cast<FlatSymbolRefAttr>(dstFifoAttr)) {
898 linkedAsDest.insert(sym.getValue());
899 auto it = fifoNameToOp.find(sym.getValue());
900 if (!linkTileOp && it != fifoNameToOp.end())
901 linkTileOp = it->second.getProducerTile().getDefiningOp();
903 linkTileOf[linkOp.getOperation()] = linkTileOp;
904 for (
auto srcFifoAttr : linkOp.getFifoIns())
905 if (
auto sym = dyn_cast<FlatSymbolRefAttr>(srcFifoAttr))
906 linkTileOfSource[sym.getValue()] = linkTileOp;
913 for (
auto ofOp : objectFifos) {
914 Operation *linkConsumer = linkTileOfSource.lookup(ofOp.getSymName());
915 bool skipProducerSide = linkedAsDest.count(ofOp.getSymName());
917 Value producerTile = ofOp.getProducerTile();
918 auto *producerOp = producerTile.getDefiningOp();
919 auto producerLogicalTile = dyn_cast_or_null<LogicalTileOp>(producerOp);
922 bool producerNeedsDMA =
false;
924 for (Value consumerTile : ofOp.getConsumerTiles()) {
925 auto *consumerOp = consumerTile.getDefiningOp();
926 auto consumerLogicalTile = dyn_cast_or_null<LogicalTileOp>(consumerOp);
930 if (producerLogicalTile && consumerLogicalTile &&
931 producerLogicalTile.getTileType() == AIETileType::CoreTile &&
932 consumerLogicalTile.getTileType() == AIETileType::CoreTile)
937 if (consumerOp && consumerOp != linkConsumer)
938 channelRequirements[consumerOp].first++;
940 producerNeedsDMA =
true;
945 if (producerNeedsDMA && producerOp && !skipProducerSide)
946 channelRequirements[producerOp].second++;
951 for (
auto linkOp : objectFifoLinks) {
953 Operation *linkTileOp = linkTileOf.lookup(linkOp.getOperation());
959 int numInputChannels = linkOp.isMerge() ? 1 : linkOp.getFifoIns().size();
960 int numOutputChannels =
961 linkOp.isDispatch() ? 1 : linkOp.getFifoOuts().size();
963 channelRequirements[linkTileOp].first += numInputChannels;
964 channelRequirements[linkTileOp].second += numOutputChannels;
967 return channelRequirements;
970void SequentialPlacer::countTurns(
971 DeviceOp device, ArrayRef<ObjectFifoCreateOp> objectFifos,
972 llvm::DenseMap<Operation *, std::pair<int, int>> &requirements) {
973 for (CoreOp core : device.getOps<CoreOp>()) {
974 auto tile = dyn_cast_or_null<LogicalTileOp>(core.getTile().getDefiningOp());
975 if (!tile || !requirements.count(tile))
977 auto &[in, out] = requirements[tile];
978 for (
bool coreDrains : {
true,
false}) {
979 int &need = coreDrains ? in : out;
980 int budget = coreDrains ? tile.getNumDestConnections(WireBundle::DMA)
981 : tile.getNumSourceConnections(WireBundle::DMA);
984 llvm::MapVector<Operation *, SmallVector<ObjectFifoCreateOp>> byTile;
985 for (ObjectFifoCreateOp fifo : objectFifos) {
986 auto consumers = fifo.getConsumerTiles();
987 if (consumers.size() != 1)
989 Value mine = coreDrains ? consumers[0] : fifo.getProducerTile();
990 Value other = coreDrains ? fifo.getProducerTile() : consumers[0];
991 auto peer = dyn_cast_or_null<LogicalTileOp>(other.getDefiningOp());
992 if (mine == tile.getResult() && peer &&
993 peer.getTileType() == AIETileType::MemTile)
994 byTile[peer].push_back(fifo);
996 for (
auto &[peer, fifos] : byTile) {
997 if (fifos.size() < 2 || need <= budget)
999 SmallVector<StringAttr> names;
1000 SmallVector<int64_t> depths;
1001 for (ObjectFifoCreateOp fifo : fifos) {
1002 names.push_back(fifo.getSymNameAttr());
1003 depths.push_back(fifo.size(coreDrains ? 1 : 0));
1007 if (succeeded(turns.
read(core, names, coreDrains, reason)) &&
1009 need -=
static_cast<int>(fifos.size()) - 1;
1016static BufferOp traceToBuffer(Value val) {
1017 for (Operation *op = val.getDefiningOp(); op;) {
1018 if (
auto buf = dyn_cast<BufferOp>(op))
1020 if (
auto view = dyn_cast<ViewLikeOpInterface>(op)) {
1021 val = view.getViewSource();
1022 op = val.getDefiningOp();
1030static std::optional<TileID>
1033 return std::nullopt;
1034 auto it = placed.find(peer.getOperation());
1035 if (it != placed.end())
1037 auto col = peer.tryGetCol();
1038 auto row = peer.tryGetRow();
1040 return TileID{*col, *row};
1041 return std::nullopt;
1044Placer::Adjacency SequentialPlacer::buildBufferAdjacency(
1045 ArrayRef<LogicalTileOp> logicalTiles,
1046 ArrayRef<ObjectFifoAllocateOp>
allocates) {
1050 llvm::DenseSet<std::pair<Operation *, Operation *>> seen;
1051 auto addOnce = [&](TileLike user, TileLike owner) {
1052 if (seen.insert({user.getOperation(), owner.getOperation()}).second)
1053 adjacency.addEdge(user, owner);
1059 dyn_cast_or_null<TileLike>(alloc.getDelegateTile().getDefiningOp());
1060 ObjectFifoCreateOp fifo = alloc.getObjectFifo();
1061 if (!delegate || !fifo || delegate.getTileType() != AIETileType::CoreTile)
1063 SmallVector<Value> ends{fifo.getProducerTile()};
1064 llvm::append_range(ends, fifo.getConsumerTiles());
1065 for (Value end : ends) {
1066 auto tile = dyn_cast_or_null<TileLike>(end.getDefiningOp());
1067 if (tile && tile.getTileType() == AIETileType::CoreTile &&
1068 tile.getOperation() != delegate.getOperation())
1069 addOnce(tile, delegate);
1072 for (
auto consumer : logicalTiles) {
1073 if (consumer.getTileType() != AIETileType::CoreTile)
1075 CoreOp core =
nullptr;
1076 for (Operation *user : consumer.getResult().getUsers())
1077 if (
auto c = dyn_cast<CoreOp>(user)) {
1084 core.getBody().walk([&](Operation *op) {
1085 for (Value operand : op->getOperands()) {
1086 BufferOp buf = traceToBuffer(operand);
1089 auto owner = dyn_cast_or_null<TileLike>(buf.getTile().getDefiningOp());
1090 if (owner && owner.getOperation() != consumer.getOperation())
1091 addOnce(TileLike(consumer), owner);
1101 for (
auto cf : cascadeFlows) {
1102 TileLike src = cf.getSourceTileLike();
1103 TileLike dst = cf.getDestTileLike();
1114 for (
auto ofOp : objectFifos) {
1116 dyn_cast_or_null<LogicalTileOp>(ofOp.getProducerTile().getDefiningOp());
1117 if (!producer || producer.getTileType() != AIETileType::CoreTile)
1119 for (Value consumerVal : ofOp.getConsumerTiles()) {
1121 dyn_cast_or_null<LogicalTileOp>(consumerVal.getDefiningOp());
1122 if (!consumer || consumer.getTileType() != AIETileType::CoreTile)
1127 if (producer.getOperation() == consumer.getOperation())
1129 adjacency.
addEdge(TileLike(producer), TileLike(consumer));
1138 for (
auto ofOp : objectFifos)
1139 if (ofOp.forcesSharedMemory() && ofOp.getConsumerTiles().size() == 1)
1141 ofOp.getConsumerTiles()[0]);
1145bool SequentialPlacer::satisfiesAdjacency(
1146 LogicalTileOp logicalTile,
TileID candidate,
const Adjacency &adjacency,
1147 llvm::function_ref<
bool(
TileID firstPos,
TileID secondPos)> pred)
const {
1149 forEachPeer(logicalTile.getOperation(), adjacency,
1150 [&](TileLike peer,
bool thisIsFirst) {
1153 auto peerPos = resolvePeerPosition(peer, result);
1156 TileID firstPos = thisIsFirst ? candidate : *peerPos;
1157 TileID secondPos = thisIsFirst ? *peerPos : candidate;
1158 if (!pred(firstPos, secondPos))
1164LogicalResult SequentialPlacer::enforceAdjacency(LogicalTileOp logicalTile,
1166 const AdjacencyKind &kind) {
1167 if (satisfiesAdjacency(logicalTile, tile, kind.adjacency, kind.pred))
1169 auto diag = logicalTile.emitError()
1170 <<
"tile (" << tile.col <<
", " << tile.row <<
") violates "
1171 << kind.name <<
" adjacency";
1172 attachPeerNotes(diag, logicalTile, kind.adjacency, kind.peerLabel);
1173 diag.attachNote() << kind.constraintHint;
1177void SequentialPlacer::recordPlacement(LogicalTileOp logicalTile,
TileID tile,
1178 PlacementContext &ctx,
1179 StringRef debugLabel) {
1180 result[logicalTile] = tile;
1181 LLVM_DEBUG(llvm::dbgs() <<
DEBUG_TYPE <<
": placed " << debugLabel <<
" LTO "
1182 << logicalTile.getLoc() <<
" at (" << tile.col <<
", "
1183 << tile.row <<
")\n");
1184 if (logicalTile.getTileType() == AIETileType::CoreTile) {
1185 recordColumnStreamUsage(logicalTile, tile.col);
1187 ctx.reserveNeighborSlots(logicalTile.getOperation(), tile);
1192SequentialPlacer::totalComputePeers(Operation *op,
const Adjacency &adjacency) {
1193 int in = 0, out = 0;
1196 forEachPeer(op, adjacency, [&](TileLike ,
bool thisIsFirst) {
1205bool SequentialPlacer::satisfiesComputePeer(
1206 LogicalTileOp logicalTile,
TileID candidate,
1208 const llvm::DenseMap<Operation *, int> &needNeighborIn,
1209 const llvm::DenseMap<Operation *, int> &needNeighborOut)
const {
1215 if (!computePeerAdjacency.hasEdges(logicalTile.getOperation()))
1220 int selfNonNeighborIn = 0, selfNonNeighborOut = 0;
1221 int selfNeighborIn = 0, selfNeighborOut = 0;
1222 forEachPeer(logicalTile.getOperation(), computePeerAdjacency,
1223 [&](TileLike peer,
bool thisIsFirst) {
1224 auto peerPos = resolvePeerPosition(peer, result);
1228 isSharedComputeMemory(*targetModel, candidate, *peerPos);
1229 bool thisIsConsumer = !thisIsFirst;
1237 ++selfNonNeighborIn;
1239 ++selfNonNeighborOut;
1242 auto [selfTotalIn, selfTotalOut] =
1243 totalComputePeers(logicalTile.getOperation(), computePeerAdjacency);
1244 int selfNeedIn = needNeighborIn.lookup(logicalTile.getOperation());
1245 int selfNeedOut = needNeighborOut.lookup(logicalTile.getOperation());
1246 int selfSlackIn = selfTotalIn - selfNeedIn;
1247 int selfSlackOut = selfTotalOut - selfNeedOut;
1248 if (selfNonNeighborIn > selfSlackIn)
1250 if (selfNonNeighborOut > selfSlackOut)
1260 if (selfNeedIn > 0 || selfNeedOut > 0) {
1261 int freeNeighborSlots = 0;
1264 ++freeNeighborSlots;
1266 int remainingInNeed = std::max(0, selfNeedIn - selfNeighborIn);
1267 int remainingOutNeed = std::max(0, selfNeedOut - selfNeighborOut);
1268 if (remainingInNeed + remainingOutNeed > freeNeighborSlots)
1276 bool symmetricOk =
true;
1278 logicalTile.getOperation(), computePeerAdjacency,
1279 [&](TileLike peer,
bool thisIsFirst) {
1282 auto peerLTO = dyn_cast_or_null<LogicalTileOp>(peer.getOperation());
1285 auto peerPos = resolvePeerPosition(peer, result);
1291 bool logicalIsPeerOut = !thisIsFirst;
1292 int peerNeedDir = logicalIsPeerOut
1293 ? needNeighborOut.lookup(peerLTO.getOperation())
1294 : needNeighborIn.lookup(peerLTO.getOperation());
1295 auto [peerTotalIn, peerTotalOut] =
1296 totalComputePeers(peerLTO.getOperation(), computePeerAdjacency);
1297 int peerTotalDir = logicalIsPeerOut ? peerTotalOut : peerTotalIn;
1298 int peerSlackDir = peerTotalDir - peerNeedDir;
1301 int peerNonNeighborDir = 0;
1303 peerLTO.getOperation(), computePeerAdjacency,
1304 [&](TileLike peerPeer, bool peerLTOIsFirst) {
1309 bool peerIsConsumerHere = !peerLTOIsFirst;
1310 if (logicalIsPeerOut == peerIsConsumerHere)
1312 if (peerPeer.getOperation() == logicalTile.getOperation()) {
1315 if (!isSharedComputeMemory(*targetModel, *peerPos, candidate))
1316 ++peerNonNeighborDir;
1319 auto pp = resolvePeerPosition(peerPeer, result);
1322 if (!isSharedComputeMemory(*targetModel, *peerPos, *pp))
1323 ++peerNonNeighborDir;
1325 if (peerNonNeighborDir > peerSlackDir)
1326 symmetricOk =
false;
1331void SequentialPlacer::attachPeerNotes(
1332 InFlightDiagnostic &diag, LogicalTileOp logicalTile,
1334 llvm::function_ref<StringRef(
bool thisIsFirst)> labelPeer)
const {
1335 int unplacedCount = 0;
1336 forEachPeer(logicalTile.getOperation(), adjacency,
1337 [&](TileLike peer,
bool thisIsFirst) {
1338 auto peerPos = resolvePeerPosition(peer, result);
1343 diag.attachNote(peer.getLoc())
1344 << labelPeer(thisIsFirst) <<
" peer placed at ("
1345 << peerPos->col <<
", " << peerPos->row <<
")";
1347 if (unplacedCount > 0)
1348 diag.attachNote() << unplacedCount
1349 <<
" additional peer(s) on this LTO are not yet placed "
1350 "(placed after this LTO; their positions may "
1351 "subsequently constrain or unblock this placement)";
1357 for (
auto ofOp : objectFifos)
1358 for (Value consumer : ofOp.getConsumerTiles())
1365 ArrayRef<ObjectFifoCreateOp> objectFifos) {
1366 llvm::DenseSet<std::pair<Operation *, Value>> registered;
1367 for (
auto reg : device.getOps<ObjectFifoRegisterExternalBuffersOp>())
1368 registered.insert({reg.getObjectFifo(), reg.getTile()});
1370 auto shareable = [&](ObjectFifoCreateOp fifo, Value tile,
1371 ObjectFifoEndPortAttr port) -> Operation * {
1372 auto lt = dyn_cast_or_null<LogicalTileOp>(tile.getDefiningOp());
1373 if (!lt || lt.getTileType() != AIETileType::ShimNOCTile || fifo.getPlio() ||
1374 (port && port.channelIndex()) || registered.contains({fifo, tile}))
1378 for (
auto fifo : objectFifos) {
1380 shareable(fifo, fifo.getProducerTile(), fifo.getProdPortAttr()))
1381 ends[lt][1].push_back(fifo.getSymNameAttr());
1382 for (
auto [i, consumer] : llvm::enumerate(fifo.getConsumerTiles()))
1383 if (Operation *lt = shareable(fifo, consumer, fifo.getConsumerPort(i)))
1384 ends[lt][0].push_back(fifo.getSymNameAttr());
1390 StringAttr symbol) {
1391 auto fifo = dyn_cast_or_null<ObjectFifoCreateOp>(
1392 SymbolTable::lookupNearestSymbolFrom(device, symbol));
1394 return std::nullopt;
1396 dyn_cast_or_null<TileLike>(fifo.getProducerTile().getDefiningOp());
1401 ArrayRef<PacketFlowOp> pktFlows) {
1403 for (
auto flow : flows)
1406 for (
auto pktFlow : pktFlows) {
1407 SmallVector<Value> srcs, dsts;
1408 pktFlow.walk([&](Operation *op) {
1409 if (
auto src = dyn_cast<PacketSourceOp>(op))
1410 srcs.push_back(src.getTile());
1411 else if (
auto dst = dyn_cast<PacketDestOp>(op))
1412 dsts.push_back(dst.getTile());
1430void SequentialPlacer::addChannelRequirementsFromFlows(
1431 ArrayRef<FlowOp> flows, ArrayRef<PacketFlowOp> pktFlows,
1432 ArrayRef<RouteOp> routes,
1433 llvm::DenseMap<Operation *, std::pair<int, int>> &channelRequirements) {
1435 llvm::DenseSet<std::tuple<Operation *, int>> seenSrc, seenDst;
1436 namedChannels.clear();
1438 auto incIfDMA = [&](Operation *tileOp, WireBundle bundle,
1439 std::optional<int> channel,
bool isOutput) {
1440 if (!tileOp || !isa<LogicalTileOp>(tileOp))
1442 if (bundle != WireBundle::DMA)
1444 auto &seen = isOutput ? seenSrc : seenDst;
1445 if (channel && !seen.insert({tileOp, *channel}).second)
1448 namedChannels[tileOp].push_back({isOutput, *channel});
1450 channelRequirements[tileOp].second++;
1452 channelRequirements[tileOp].first++;
1455 for (
auto flow : flows) {
1456 incIfDMA(flow.getSource().getDefiningOp(), flow.getSourceBundle(),
1457 flow.getSourceChannel(),
true);
1458 incIfDMA(flow.getDest().getDefiningOp(), flow.getDestBundle(),
1459 flow.getDestChannel(),
false);
1462 for (
auto pktFlow : pktFlows) {
1463 pktFlow.walk([&](Operation *op) {
1464 if (
auto src = dyn_cast<PacketSourceOp>(op)) {
1465 incIfDMA(src.getTile().getDefiningOp(), src.getBundle(),
1466 src.getChannel(),
true);
1467 }
else if (
auto dst = dyn_cast<PacketDestOp>(op)) {
1468 incIfDMA(dst.getTile().getDefiningOp(), dst.getBundle(),
1469 dst.getChannel(),
false);
1476 llvm::DenseSet<Operation *> countedEndpoints;
1477 for (
auto route : routes) {
1478 auto count = [&](StringRef name,
bool isOutput) {
1479 auto endpoint = SymbolTable::lookupNearestSymbolFrom<RouteEndpoint>(
1480 route, StringAttr::get(route.getContext(), name));
1481 if (endpoint && countedEndpoints.insert(endpoint).second)
1482 incIfDMA(endpoint.getTile().getDefiningOp(), endpoint.getRouteBundle(),
1483 endpoint.getRouteChannel(), isOutput);
1485 for (StringRef source : route.getSourceNames())
1486 count(source,
true);
1487 for (StringRef dest : route.getDestinationNames())
1492SequentialPlacer::FlowMembership SequentialPlacer::buildFlowMembership(
1493 ArrayRef<FlowOp> flows, ArrayRef<PacketFlowOp> pktFlows,
1494 ArrayRef<RouteOp> routes, ArrayRef<ObjectFifoCreateOp> objectFifos) {
1499 auto isLto = [](Value v) {
1500 return v && mlir::isa_and_nonnull<LogicalTileOp>(v.getDefiningOp());
1502 auto addEntry = [&](Value lto, ArrayRef<Value> peers) {
1503 if (!isLto(lto) || peers.empty())
1505 idx.ltoFlows[lto].push_back(SmallVector<Value>(peers.begin(), peers.end()));
1508 for (
auto flow : flows) {
1509 Value s = flow.getSource(), d = flow.getDest();
1513 for (
auto pf : pktFlows) {
1514 SmallVector<Value> srcs, dsts;
1515 pf.walk([&](Operation *op) {
1516 if (
auto src = dyn_cast<PacketSourceOp>(op))
1517 srcs.push_back(src.getTile());
1518 else if (
auto dst = dyn_cast<PacketDestOp>(op))
1519 dsts.push_back(dst.getTile());
1521 for (Value s : srcs)
1523 for (Value d : dsts)
1526 for (
auto route : routes) {
1527 auto tileOf = [&](StringRef name) -> Value {
1528 auto endpoint = SymbolTable::lookupNearestSymbolFrom<RouteEndpoint>(
1529 route, StringAttr::get(route.getContext(), name));
1530 return endpoint ? endpoint.getTile() : Value();
1532 SmallVector<Value> srcs, dsts;
1533 for (StringRef source : route.getSourceNames())
1534 if (Value tile = tileOf(source))
1535 srcs.push_back(tile);
1536 for (StringRef dest : route.getDestinationNames())
1537 if (Value tile = tileOf(dest))
1538 dsts.push_back(tile);
1539 for (Value s : srcs)
1541 for (Value d : dsts)
1544 for (
auto of : objectFifos) {
1545 Value prod = of.getProducerTile();
1546 auto cons = of.getConsumerTiles();
1547 SmallVector<Value> consVec(cons.begin(), cons.end());
1548 addEntry(prod, consVec);
1549 for (Value c : consVec)
1550 addEntry(c, {prod});
1556SequentialPlacer::computeCentroidColumn(LogicalTileOp logicalTile,
1557 const FlowMembership &flowIndex) {
1568 Value ltoVal = logicalTile.getResult();
1569 auto ltoIt = flowIndex.ltoFlows.find(ltoVal);
1570 if (ltoIt == flowIndex.ltoFlows.end())
1571 return std::nullopt;
1576 auto resolvePeerCol = [&](Value v) -> std::optional<int> {
1577 Operation *defOp = v.getDefiningOp();
1579 return std::nullopt;
1581 if (
auto tileOp = dyn_cast<TileOp>(defOp))
1582 return tileOp.getCol();
1583 auto lto = dyn_cast<LogicalTileOp>(defOp);
1585 return std::nullopt;
1586 if (
auto c = lto.tryGetCol())
1588 auto it =
result.find(defOp);
1590 return it->second.col;
1591 return std::nullopt;
1597 auto resolveLtoCoreCols = [&](Value v) {
1598 SmallVector<int> out;
1599 auto it = flowIndex.ltoFlows.find(v);
1600 if (it == flowIndex.ltoFlows.end())
1602 for (
auto &peerList : it->second)
1603 for (Value p : peerList)
1604 if (
auto col = resolvePeerCol(p))
1605 out.push_back(*col);
1609 SmallVector<SmallVector<int>> perFlowDests;
1610 for (
auto &peers : ltoIt->second) {
1611 SmallVector<int> dests;
1612 for (Value p : peers) {
1613 if (
auto col = resolvePeerCol(p)) {
1614 dests.push_back(*col);
1619 for (
int c : resolveLtoCoreCols(p))
1623 perFlowDests.push_back(std::move(dests));
1626 if (perFlowDests.empty())
1627 return std::nullopt;
1629 double meanDestCol = 0.0;
1631 for (
auto &dests : perFlowDests)
1632 for (
int d : dests) {
1636 meanDestCol /= destCount;
1640 int bestCost = std::numeric_limits<int>::max();
1641 double bestTiebreak = std::numeric_limits<double>::infinity();
1642 for (
int S = 0; S < numCols; ++S) {
1644 for (
auto &dests : perFlowDests) {
1645 if (dests.size() == 1) {
1646 cost += std::abs(S - dests[0]);
1648 int lo = *llvm::min_element(dests);
1649 int hi = *llvm::max_element(dests);
1656 double tiebreak = std::abs(S - meanDestCol);
1657 if (cost < bestCost || (cost == bestCost && tiebreak < bestTiebreak)) {
1659 bestTiebreak = tiebreak;
1666LogicalResult SequentialPlacer::placeNonCoreTileByCentroid(
1667 LogicalTileOp logicalTile,
const FlowMembership &flowIndex,
1668 const llvm::DenseMap<Operation *, std::pair<int, int>>
1669 &channelRequirements) {
1670 std::optional<int> centroidCol =
1671 computeCentroidColumn(logicalTile, flowIndex);
1672 auto colConstraint = logicalTile.tryGetCol();
1677 std::optional<int> targetCol = colConstraint ? colConstraint : centroidCol;
1678 if (!targetCol && !spreadUnanchoredTiles)
1681 auto [numInputChannels, numOutputChannels] =
1682 channelRequirements.lookup(logicalTile.getOperation());
1684 auto maybeTile = findTileWithCapacity(targetCol,
availability.nonCompTiles,
1685 numInputChannels, numOutputChannels,
1686 logicalTile.getTileType(), logicalTile,
1689 maybeTile = findTileWithCapacity(targetCol,
availability.nonCompTiles,
1690 numInputChannels, numOutputChannels,
1691 logicalTile.getTileType(), logicalTile,
1694 if (!maybeTile && mergeLogicalTiles &&
1695 logicalTile.getTileType() == AIETileType::ShimNOCTile) {
1696 std::optional<std::pair<int, int>> bestKey;
1697 for (
int col = 0, numCols =
targetModel->columns(); col < numCols; ++col) {
1699 if ((colConstraint && col != *colConstraint) ||
1700 !fitsSharingShim(tile, logicalTile,
1701 {numInputChannels, numOutputChannels}))
1703 std::pair<int, int> key{targetCol ? std::abs(col - *targetCol) : 0, col};
1704 if (!bestKey || key < *bestKey) {
1711 AIETileType requestedType = logicalTile.getTileType();
1712 StringRef tileTypeName = stringifyAIETileType(requestedType);
1713 auto diag = logicalTile.emitError();
1714 CapacityDiagnosis diagnosis = diagnoseCapacityExhaustion(
1715 requestedType, numInputChannels, numOutputChannels);
1716 if (!diagnosis.capacityExistsSomewhere) {
1717 diag <<
"no " << tileTypeName <<
" on the device has " << numInputChannels
1718 <<
" input/" << numOutputChannels
1719 <<
" output DMA channel(s) free: all " << diagnosis.tilesOfType
1720 <<
" " << tileTypeName <<
"(s) are at " << diagnosis.inUsed <<
"/"
1721 << diagnosis.inMax <<
" input, " << diagnosis.outUsed <<
"/"
1722 << diagnosis.outMax <<
" output channels used";
1723 diag.attachNote() <<
"this is the device's total " << tileTypeName
1724 <<
" DMA budget, not a placement choice -- no column "
1725 "has spare capacity to pin to";
1726 int shortIn = diagnosis.closestShortIn;
1727 int shortOut = diagnosis.closestShortOut;
1728 auto &remedy = diag.attachNote();
1729 remedy <<
"the closest " << tileTypeName <<
" is ";
1730 if (shortIn > 0 && shortOut > 0)
1731 remedy <<
"short " << shortIn <<
" input/" << shortOut
1732 <<
" output channel(s); ";
1733 else if (shortIn > 0)
1734 remedy <<
"short " << shortIn <<
" input channel(s); ";
1735 else if (shortOut > 0)
1736 remedy <<
"short " << shortOut <<
" output channel(s); ";
1737 remedy <<
"fan traffic through a MemTile via aie.objectfifo.link, or "
1738 "reduce the number of DMA-attached ObjectFIFOs feeding this "
1741 diag <<
"no " << tileTypeName <<
" has sufficient DMA capacity for "
1742 << numInputChannels <<
" input/" << numOutputChannels
1743 <<
" output channels ";
1745 diag <<
"at column " << *colConstraint;
1746 else if (centroidCol)
1747 diag <<
"near centroid column " << *centroidCol;
1749 diag <<
"on any column";
1750 diag.attachNote() <<
"every " << tileTypeName
1751 <<
" with spare DMA capacity already hosts a "
1752 "different logical tile (merge-logical-tiles is "
1753 "off); enable merging, or pin logical tiles "
1754 "one-per-column if fewer of them than physical "
1755 << tileTypeName <<
"s remain unclaimed";
1760 result[logicalTile] = *maybeTile;
1762 auto showCol = [](std::optional<int> c) {
1763 return c ? std::to_string(*c) : std::string(
"none");
1765 llvm::dbgs() <<
DEBUG_TYPE <<
": placed non-core LTO "
1766 << logicalTile.getLoc() <<
" at (" << maybeTile->col <<
", "
1767 << maybeTile->row <<
") (centroid col=" << showCol(centroidCol)
1768 <<
", target col=" << showCol(targetCol) <<
")\n";
1770 if (!mergeLogicalTiles)
1771 assignedNonCoreTiles.insert(*maybeTile);
1772 if (numInputChannels > 0)
1773 updateChannelUsage(*maybeTile, DmaDir::In, numInputChannels);
1774 if (numOutputChannels > 0)
1775 updateChannelUsage(*maybeTile, DmaDir::Out, numOutputChannels);
1776 recordStreamAndBdUsage(logicalTile, *maybeTile);
1777 recordShimEnds(*maybeTile, logicalTile);
1781bool SequentialPlacer::fitsSharingShim(
TileID tile, Operation *logicalTile,
1782 std::pair<int, int> required) {
1783 if (
targetModel->getTileType(tile.col, tile.row) !=
1784 AIETileType::ShimNOCTile ||
1790 int need[2] = {required.first, required.second};
1791 int max[2] = {maxIn, maxOut};
1792 auto own = shareableShimEnds.lookup(logicalTile);
1793 for (
int dir : {0, 1}) {
1794 if (used[dir] + need[dir] <= max[dir])
1796 SmallVector<StringAttr> ends(shimEndsAt[tile][dir]);
1797 llvm::append_range(ends, own[dir]);
1798 int fixed = used[dir] + need[dir] -
static_cast<int>(ends.size());
1799 if (fixed +
static_cast<int>(shimSpans->groups(ends).size()) > max[dir])
1805void SequentialPlacer::recordShimEnds(
TileID tile, Operation *logicalTile) {
1806 auto own = shareableShimEnds.find(logicalTile);
1807 if (own == shareableShimEnds.end())
1809 for (
int dir : {0, 1})
1810 llvm::append_range(shimEndsAt[tile][dir], own->second[dir]);
1813std::optional<TileID> SequentialPlacer::findTileWithCapacity(
1814 std::optional<int> targetCol, llvm::ArrayRef<TileID> tiles,
1815 int requiredInputChannels,
int requiredOutputChannels,
1816 AIETileType requestedType, Operation *logicalTile,
bool withinBudgets) {
1829 std::optional<TileID> best;
1830 std::tuple<int, int, int, int> bestKey;
1832 for (
auto &tile : tiles) {
1833 if (
targetModel->getTileType(tile.col, tile.row) != requestedType)
1835 if (!hasAvailableChannels(tile, requiredInputChannels,
1836 requiredOutputChannels))
1841 if (!mergeLogicalTiles && assignedNonCoreTiles.contains(tile))
1843 if (withinBudgets && !fitsStreamAndBdBudgets(logicalTile, tile))
1845 if (llvm::any_of(namedChannels.lookup(logicalTile), [&](
auto named) {
1846 return availability.namedChannelsUsed[tile].contains(named);
1849 int dist = targetCol ? std::abs(tile.col - *targetCol) : 0;
1850 if (requestedType == AIETileType::MemTile &&
1851 !cast<LogicalTileOp>(logicalTile).tryGetCol())
1852 dist += broadcastFanoutUsed.lookup(tile);
1855 (requiredInputChannels == 0 && requiredOutputChannels == 0)) {
1860 if (requiredInputChannels > 0)
1862 if (requiredOutputChannels > 0)
1865 std::tuple<int, int, int, int> key{dist, load, tile.col, tile.row};
1866 if (!best || key < bestKey) {
1875SequentialPlacer::CapacityDiagnosis
1876SequentialPlacer::diagnoseCapacityExhaustion(AIETileType requestedType,
1877 int requiredInputChannels,
1878 int requiredOutputChannels) {
1883 CapacityDiagnosis diagnosis;
1884 for (
int col = 0, numCols =
targetModel->columns(); col < numCols; ++col) {
1885 for (
int row = 0, numRows =
targetModel->rows(); row < numRows; ++row) {
1886 if (
targetModel->getTileType(col, row) != requestedType)
1889 ++diagnosis.tilesOfType;
1891 diagnosis.inMax += maxIn;
1892 diagnosis.outMax += maxOut;
1893 int usedIn =
availability.inputChannelsUsed.lookup(tile);
1894 int usedOut =
availability.outputChannelsUsed.lookup(tile);
1895 diagnosis.inUsed += usedIn;
1896 diagnosis.outUsed += usedOut;
1897 int shortIn = std::max(0, requiredInputChannels - (maxIn - usedIn));
1898 int shortOut = std::max(0, requiredOutputChannels - (maxOut - usedOut));
1899 if (diagnosis.tilesOfType == 1 ||
1900 shortIn + shortOut <
1901 diagnosis.closestShortIn + diagnosis.closestShortOut) {
1902 diagnosis.closestShortIn = shortIn;
1903 diagnosis.closestShortOut = shortOut;
1905 if (hasAvailableChannels(tile, requiredInputChannels,
1906 requiredOutputChannels))
1907 diagnosis.capacityExistsSomewhere =
true;
1913void SequentialPlacer::updateChannelUsage(
TileID tile, DmaDir direction,
1915 if (direction == DmaDir::Out)
1920 if (!hasAvailableChannels(tile, 0, 0)) {
1921 AIETileType type =
targetModel->getTileType(tile.col, tile.row);
1926bool SequentialPlacer::hasAvailableChannels(
TileID tile,
int inputChannels,
1927 int outputChannels) {
1931 int currentOut =
availability.outputChannelsUsed[tile];
1933 return (currentIn + inputChannels <= maxIn) &&
1934 (currentOut + outputChannels <= maxOut);
1937void SequentialPlacer::recordColumnStreamUsage(Operation *tile,
int col) {
1938 auto &incoming = columnIncomingFifos[col];
1939 for (Operation *fifo : incomingFifos.lookup(tile))
1940 incoming.insert(fifo);
1941 auto &used = columnStreamsUsed[col];
1942 used += streamDemand.lookup(tile);
1943 used.southIn = incoming.size();
1946void SequentialPlacer::buildStreamDemand(
1947 ArrayRef<ObjectFifoCreateOp> objectFifos) {
1948 streamDemand.clear();
1949 columnStreamsUsed.clear();
1950 incomingFifos.clear();
1951 columnIncomingFifos.clear();
1952 broadcastFanout.clear();
1953 broadcastFanoutUsed.clear();
1956 auto rank = [](Operation *op) -> std::optional<int> {
1957 auto tile = dyn_cast_or_null<TileLike>(op);
1959 return std::nullopt;
1960 if (tile.isShimTile())
1962 return tile.isMemTile() ? 1 : 2;
1964 for (
auto of : objectFifos) {
1965 Operation *producer = of.getProducerTile().getDefiningOp();
1966 std::optional<int> producerRank = rank(producer);
1969 if (*producerRank == 1)
1970 broadcastFanout[producer] +=
1971 std::max(0,
static_cast<int>(of.getConsumerTiles().size()) - 1);
1972 bool north =
false, south =
false;
1973 for (Value consumerTile : of.getConsumerTiles()) {
1974 Operation *consumer = consumerTile.getDefiningOp();
1975 std::optional<int> consumerRank = rank(consumer);
1976 if (!consumerRank || *consumerRank == *producerRank)
1978 if (*consumerRank > *producerRank) {
1980 streamDemand[consumer].southIn++;
1981 if (*consumerRank == 2)
1982 incomingFifos[consumer].push_back(of.getOperation());
1985 streamDemand[consumer].northIn++;
1989 streamDemand[producer].northOut++;
1991 streamDemand[producer].southOut++;
1993 for (
auto &[op, demand] : streamDemand)
1994 if (
auto tile = dyn_cast<TileOp>(op))
1995 if (tile.isCoreTile())
1996 recordColumnStreamUsage(op, tile.getCol());
1997 for (
auto &[op, fanout] : broadcastFanout)
1998 if (
auto tile = dyn_cast<TileOp>(op))
1999 broadcastFanoutUsed[{tile.getCol(), tile.getRow()}] += fanout;
2002void SequentialPlacer::buildRuntimeBdProfiles(
2003 DeviceOp device, ArrayRef<ObjectFifoCreateOp> objectFifos) {
2004 llvm::StringMap<Operation *> shimOfFifo;
2005 for (
auto of : objectFifos) {
2006 SmallVector<Value> ends{of.getProducerTile()};
2007 llvm::append_range(ends, of.getConsumerTiles());
2008 for (Value end : ends) {
2009 auto lt = dyn_cast_or_null<LogicalTileOp>(end.getDefiningOp());
2010 if (lt && lt.getTileType() == AIETileType::ShimNOCTile)
2011 shimOfFifo[of.getSymName()] = lt;
2014 if (shimOfFifo.empty())
2018 return targetModel->getTileType(t.col, t.row) == AIETileType::ShimNOCTile;
2025 SmallVector<std::pair<Operation *, int>> events;
2026 device.walk([&](RuntimeSequenceOp seq) {
2027 llvm::MapVector<Value, std::pair<Operation *, int>> live;
2028 seq.walk([&](Operation *op) {
2029 if (
auto task = dyn_cast<AIEX::DMAConfigureTaskForOp>(op)) {
2031 shimOfFifo.lookup(task.getAlloc().getRootReference().getValue());
2035 task.getBody().walk([&](DMABDOp bd) {
2038 bds += succeeded(plan) && !plan->pieces.empty() ? plan->pieces.size()
2041 live[task.getResult()] = {shim, bds};
2042 events.push_back({shim, bds});
2046 if (
auto await = dyn_cast<AIEX::DMAAwaitTaskOp>(op))
2047 released = await.getTask();
2048 else if (
auto free = dyn_cast<AIEX::DMAFreeTaskOp>(op))
2049 released = free.getTask();
2050 auto *it = live.find(released);
2051 if (it == live.end())
2053 events.push_back({it->second.first, -it->second.second});
2056 for (
auto &[task, held] : live)
2057 events.push_back({held.first, -held.second});
2060 llvm::MapVector<Operation *, int> running;
2061 for (
auto &[shim, delta] : events)
2062 if (running.insert({shim, 0}).second)
2063 runtimeBdProfiles[shim].assign(events.size(), 0);
2064 for (
auto [idx, event] : llvm::enumerate(events)) {
2065 running[
event.first] +=
event.second;
2066 for (
auto &[shim, n] : running)
2067 runtimeBdProfiles[shim][idx] = n;
2071bool SequentialPlacer::fitsStreamAndBdBudgets(Operation *logicalTile,
2075 StreamDemand demand = streamDemand.lookup(logicalTile);
2076 StreamDemand streams =
availability.streamsUsed.lookup(tile);
2081 tile.col, tile.row, WireBundle::North))) ||
2084 tile.col, tile.row, WireBundle::South))) ||
2087 tile.col, tile.row, WireBundle::North))) ||
2090 tile.col, tile.row, WireBundle::South))))
2093 auto profile = runtimeBdProfiles.find(logicalTile);
2094 if (profile == runtimeBdProfiles.end())
2096 SmallVector<int> used =
availability.runtimeBdsUsed.lookup(tile);
2097 used.resize(profile->second.size(), 0);
2098 int numBDs = tm.
getNumBDs(tile.col, tile.row);
2099 return llvm::all_of(llvm::zip(profile->second, used), [&](
auto live) {
2100 return !std::get<0>(live) ||
2101 std::get<0>(live) + std::get<1>(live) <= numBDs;
2105void SequentialPlacer::recordStreamAndBdUsage(Operation *logicalTile,
2107 broadcastFanoutUsed[tile] += broadcastFanout.lookup(logicalTile);
2108 for (
auto named : namedChannels.lookup(logicalTile))
2110 if (
auto it = streamDemand.find(logicalTile); it != streamDemand.end())
2112 auto profile = runtimeBdProfiles.find(logicalTile);
2113 if (profile == runtimeBdProfiles.end())
2115 SmallVector<int> &used =
availability.runtimeBdsUsed[tile];
2116 used.resize(profile->second.size(), 0);
2117 for (
auto [idx, live] : llvm::enumerate(profile->second))
2122 auto removeFromVector = [&](std::vector<TileID> &vec) {
2123 vec.erase(std::remove(vec.begin(), vec.end(), tile), vec.end());
2127 case AIETileType::CoreTile:
2131 case AIETileType::MemTile:
2132 case AIETileType::ShimNOCTile:
2133 case AIETileType::ShimPLTile:
virtual AIETileType getTileType(int col, int row) const =0
Return the tile type for the given tile coordinates.
virtual int rows() const =0
Return the number of rows in the device.
virtual uint32_t getNumBDs(AIETileType tileType) const =0
Return the number of buffer descriptors for a given tile type.
virtual int columns() const =0
Return the number of columns in the device.
virtual uint32_t getNumDestSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a switchbox.
virtual uint32_t getNumSourceSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a switchbox.
mlir::LogicalResult read(llvm::ArrayRef< ObjectFifoCoreEndpointOp > ends, std::string &reason)
Reads the order off the program of the core working through ends, all draining (the channel fills the...
bool keepsUp(llvm::ArrayRef< int64_t > depths)
Whether a channel moving objects in turns() order forever, through pools of depths,...
llvm::DenseMap< mlir::Operation *, std::array< llvm::SmallVector< mlir::StringAttr >, 2 > > ShareableShimEnds
static Adjacency buildCascadeAdjacency(llvm::ArrayRef< CascadeFlowOp > cascadeFlows)
static bool satisfiesConstraints(mlir::Operation *tile, TileID pos)
static Adjacency buildSharedMemAdjacency(llvm::ArrayRef< ObjectFifoCreateOp > objectFifos)
static Adjacency buildObjectFifoAdjacency(llvm::ArrayRef< ObjectFifoCreateOp > objectFifos)
static Adjacency buildFlowAdjacency(llvm::ArrayRef< FlowOp > flows, llvm::ArrayRef< PacketFlowOp > pktFlows)
const AIETargetModel * targetModel
virtual void initialize(const AIETargetModel &tm)
static Adjacency buildComputePeerAdjacency(llvm::ArrayRef< ObjectFifoCreateOp > objectFifos)
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)
void initialize(const AIETargetModel &targetModel) override
mlir::LogicalResult place(DeviceOp device) override
mlir::FailureOr< TaskBdDecomposition > decomposeTaskBdPattern(xilinx::AIE::DMABDOp bd, const xilinx::AIE::AIETargetModel &targetModel, int tileCol, int tileRow)
Plan the split of bd, a runtime task's descriptor, on tile (tileCol, tileRow).
void forEachPeer(mlir::Operation *op, const Placer::Adjacency &adjacency, F &&fn)
llvm::DenseMap< mlir::Operation *, TileID > PlacementResult
void forEachMemAffinityNeighbor(const AIETargetModel &targetModel, TileID at, F &&fn)
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.
void addEdge(TileLike first, TileLike second)
llvm::DenseMap< mlir::Operation *, llvm::SmallVector< unsigned, 2 > > tileToEdges
void addEdgeFromValues(mlir::Value a, mlir::Value b)
llvm::SmallVector< std::pair< TileLike, TileLike >, 4 > edges
llvm::SmallVector< RouteOp > routes
llvm::SmallVector< ObjectFifoLinkOp > objectFifoLinks
llvm::SmallVector< CascadeFlowOp > cascadeFlows
llvm::SmallVector< PacketFlowOp > pktFlows
llvm::SmallVector< FlowOp > flows
llvm::SmallVector< ObjectFifoCreateOp > objectFifos
llvm::SmallVector< ObjectFifoAllocateOp > objectFifoAllocates
llvm::SmallVector< LogicalTileOp > logicalTiles
A shim end a runtime sequence transfers through.
llvm::DenseMap< mlir::Operation *, unsigned > rank() const
Rank the nodes by their features and then, a round at a time until no class splits,...
static StructuralGraph of(DeviceOp device)
The ops directly in device but its runtime sequences, each with the ops nested in it,...
std::vector< TileID > compTiles
void removeTile(TileID tile, AIETileType type)
std::vector< TileID > nonCompTiles
llvm::DenseSet< TileID > compTilesSet