MLIR-AIE 1.4.5.dev
AIEPlacer.h
Go to the documentation of this file.
1//===- AIEPlacer.h ----------------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
8#ifndef AIE_PLACER_H
9#define AIE_PLACER_H
10
14
15#include <array>
16#include <deque>
17#include <random>
18
19namespace xilinx::AIE {
20
21/// Placement algorithm type for pass option
23
24/// Get DMA channel capacity (maxIn, maxOut) for a tile position.
25inline std::pair<int, int> getDMACapacity(const AIETargetModel &tm,
26 TileID tile) {
27 if (tile.row == 0)
28 return {
29 tm.getNumDestShimMuxConnections(tile.col, tile.row, WireBundle::DMA),
30 tm.getNumSourceShimMuxConnections(tile.col, tile.row, WireBundle::DMA)};
31 return {
32 tm.getNumDestSwitchboxConnections(tile.col, tile.row, WireBundle::DMA),
33 tm.getNumSourceSwitchboxConnections(tile.col, tile.row, WireBundle::DMA)};
34}
35
36// Streams through the North and South ports of a tile's stream switch.
38 int northIn = 0, northOut = 0, southIn = 0, southOut = 0;
39
41 northIn += other.northIn;
42 northOut += other.northOut;
43 southIn += other.southIn;
44 southOut += other.southOut;
45 return *this;
46 }
47};
48
49// maps logical tile operations to physical coordinates
50using PlacementResult = llvm::DenseMap<mlir::Operation *, TileID>;
51
52// Track available tiles and resource usage
54 std::vector<TileID> compTiles;
55 std::vector<TileID> nonCompTiles; // Memory and shim tiles
56
57 // O(1) membership shadow of `compTiles` used by SequentialPlacer's
58 // satisfiesComputePeer forward-look, which checks whether each of a
59 // candidate's physical compute neighbors is still free. Without it the
60 // forward-look would do an O(|compTiles|) std::find per direction per
61 // candidate per LTO, which scales as O(N * M^2) on large arrays. Must be
62 // kept in sync with `compTiles` by any code that mutates the vector
63 // (initialize(), limitCoresPerColumn(), removeTile()).
64 llvm::DenseSet<TileID> compTilesSet;
65
66 llvm::DenseMap<TileID, int> inputChannelsUsed;
67 llvm::DenseMap<TileID, int> outputChannelsUsed;
68 llvm::DenseMap<TileID, StreamDemand> streamsUsed;
69 // Live runtime-sequence BDs after each task event, as in
70 // SequentialPlacer::runtimeBdProfiles.
71 llvm::DenseMap<TileID, llvm::SmallVector<int>> runtimeBdsUsed;
72 // DMA channels, as (output, index), that flows of the logical tiles placed
73 // here name by index.
74 llvm::DenseMap<TileID, llvm::DenseSet<std::pair<int, int>>> namedChannelsUsed;
75
76 void removeTile(TileID tile, AIETileType type);
77};
78
79// Abstract placer interface
80class Placer {
81public:
82 Placer() = default;
83 virtual ~Placer() = default;
84
85 virtual void initialize(const AIETargetModel &tm);
86
87 virtual mlir::LogicalResult place(DeviceOp device) = 0;
88
89 virtual llvm::StringRef getName() const = 0;
90
91 std::optional<TileID> getPlacement(mlir::Operation *logicalTile) const {
92 auto it = result.find(logicalTile);
93 if (it != result.end())
94 return it->second;
95 return std::nullopt;
96 }
97
98 // Buffer allocation redirects: (ObjectFifoCreateOp, delegate TileID)
99 // Generated when a core tile's buffers exceed local memory and must
100 // spill to a neighbor tile's memory module.
101 using AllocateInfo = std::pair<mlir::Operation *, TileID>;
102 llvm::SmallVector<AllocateInfo> &getAllocates() { return allocates; }
103
104 // Per-LTO peer edges indexed by either endpoint. `tileToEdges` only
105 // indexes `LogicalTileOp` endpoints; `TileOp` peers carry their own
106 // coords.
107 struct Adjacency {
108 llvm::SmallVector<std::pair<TileLike, TileLike>, 4> edges;
109 llvm::DenseMap<mlir::Operation *, llvm::SmallVector<unsigned, 2>>
111
112 void addEdge(TileLike first, TileLike second);
113
114 // Convenience for IR walkers: skip if either Value isn't a TileLike.
115 void addEdgeFromValues(mlir::Value a, mlir::Value b);
116
117 // True if `op` has at least one edge in this adjacency.
118 bool hasEdges(mlir::Operation *op) const {
119 return tileToEdges.count(op) != 0;
120 }
121 };
122
123 // IR operations collected from a DeviceOp for placement.
125 llvm::SmallVector<LogicalTileOp> logicalTiles;
126 llvm::SmallVector<ObjectFifoCreateOp> objectFifos;
127 llvm::SmallVector<ObjectFifoLinkOp> objectFifoLinks;
128 llvm::SmallVector<ObjectFifoAllocateOp> objectFifoAllocates;
129 llvm::SmallVector<CascadeFlowOp> cascadeFlows;
130 llvm::SmallVector<FlowOp> flows;
131 llvm::SmallVector<PacketFlowOp> pktFlows;
132 llvm::SmallVector<RouteOp> routes;
133 };
134
135 // Collect all placement-relevant operations from the device.
136 static CollectedOps collectOperations(DeviceOp device);
137
138 // Check if a logical tile's col/row constraints are satisfied at a position.
139 static bool satisfiesConstraints(mlir::Operation *tile, TileID pos);
140
141 // Adjacency builders: pure IR walkers with no placer-specific state.
142
143 // Edge: (cascade source, dest).
144 static Adjacency
145 buildCascadeAdjacency(llvm::ArrayRef<CascadeFlowOp> cascadeFlows);
146
147 // Edge: (producer LTO, consumer LTO) for every ObjectFifo whose producer
148 // AND consumer are CoreTile LTOs.
149 static Adjacency
150 buildComputePeerAdjacency(llvm::ArrayRef<ObjectFifoCreateOp> objectFifos);
151
152 // Edge: (producer, consumer) per forcesSharedMemory() ObjectFifo with one
153 // consumer.
154 static Adjacency
155 buildSharedMemAdjacency(llvm::ArrayRef<ObjectFifoCreateOp> objectFifos);
156
157 // Edge: (producer, consumer_i) per fifo.
158 static Adjacency
159 buildObjectFifoAdjacency(llvm::ArrayRef<ObjectFifoCreateOp> objectFifos);
160
161 // Edge: (src, dst) per `aie.flow`; cross-product per `aie.packet_flow`.
162 static Adjacency buildFlowAdjacency(llvm::ArrayRef<FlowOp> flows,
163 llvm::ArrayRef<PacketFlowOp> pktFlows);
164
165 // The objectFIFO ends each shim LTO hosts that may take turns on a channel
166 // with ends never in flight with them (ShimTransferSpans): DMA ends naming
167 // no channel, [0] receiving and [1] sending, as objectFIFO allocation sees
168 // them.
170 llvm::DenseMap<mlir::Operation *,
171 std::array<llvm::SmallVector<mlir::StringAttr>, 2>>;
172 static ShareableShimEnds
173 collectShareableShimEnds(DeviceOp device,
174 llvm::ArrayRef<ObjectFifoCreateOp> objectFifos);
175
176 // The objectFIFO end a runtime sequence names `symbol` for, before split:
177 // the fifo itself, sending when its producer is a shim tile.
178 static std::optional<ShimTransferSpans::End>
179 shimEndOf(DeviceOp device, mlir::StringAttr symbol);
180
181protected:
183 llvm::SmallVector<AllocateInfo> allocates;
184 const AIETargetModel *targetModel = nullptr;
186};
187
188// Invoke `fn(peer, thisIsFirst)` for each edge in `adjacency` that
189// mentions `op`. `peer` is the OTHER endpoint of the edge; `thisIsFirst`
190// is true when `op` is `edge.first`.
191template <typename F>
192inline void forEachPeer(mlir::Operation *op, const Placer::Adjacency &adjacency,
193 F &&fn) {
194 auto it = adjacency.tileToEdges.find(op);
195 if (it == adjacency.tileToEdges.end())
196 return;
197 for (unsigned idx : it->second) {
198 auto [first, second] = adjacency.edges[idx];
199 bool thisIsFirst = first.getOperation() == op;
200 TileLike peer = thisIsFirst ? second : first;
201 fn(peer, thisIsFirst);
202 }
203}
204
205// Whether two compute tiles can share one memory module: unlike buffer
206// ownership (where only the consumer must reach the owner's module),
207// sharing has no owner/consumer asymmetry, so either tile reaching the
208// other's module is enough. isLegalMemAffinity itself is directional (see
209// AIETargetModel::getSharedMemory's None/First/Second/Either), so compute-
210// peer neighbor-ness -- what `--aie-objectfifo-split`'s auto transport
211// actually tests -- has to check both.
212inline bool isSharedComputeMemory(const AIETargetModel &targetModel, TileID a,
213 TileID b) {
214 return targetModel.isLegalMemAffinity(a.col, a.row, b.col, b.row) ||
215 targetModel.isLegalMemAffinity(b.col, b.row, a.col, a.row);
216}
217
218// Invoke `fn(TileID)` on every CoreTile neighbor of `at` that can share
219// compute-peer memory with it per the target model.
220template <typename F>
221inline void forEachMemAffinityNeighbor(const AIETargetModel &targetModel,
222 TileID at, F &&fn) {
223 for (auto [dc, dr] :
224 {std::pair{0, -1}, std::pair{0, 1}, std::pair{-1, 0}, std::pair{1, 0}}) {
225 int nc = at.col + dc;
226 int nr = at.row + dr;
227 if (nc < 0 || nc >= targetModel.columns())
228 continue;
229 if (nr < 0 || nr >= targetModel.rows())
230 continue;
231 if (targetModel.getTileType(nc, nr) != AIETileType::CoreTile)
232 continue;
233 if (!isSharedComputeMemory(targetModel, at, TileID{nc, nr}))
234 continue;
235 fn(TileID{nc, nr});
236 }
237}
238
239// Sequential placement algorithm
240//
241// Greedy, single-pass placer that maps each `aie.logical_tile` (LTO) to a
242// physical (col, row). Four phases:
243//
244// 1. Collect LogicalTileOp / ObjectFifo* / Cascade* / Flow* / PacketFlow* /
245// Route from the device.
246// 2. Build per-LTO constraints: buffer/cascade/compute-peer adjacencies,
247// channel requirements, and needNeighborIn/Out (the minimum number of
248// compute peers per LTO that MUST land on a shared-L1 neighbor to fit
249// the per-tile DMA budget). Group the compute LTOs by the non-core
250// tile each shares the most flows with (buildCoreGroups).
251// 3. Place compute tiles. Make each core group contiguous, then sort by
252// constraint level (pinned -> partial -> unpinned) and by descending
253// placementPriority. For each LTO: validate buffer/cascade/compute-peer
254// constraints and channel budget, then claim the tile, trying first
255// the columns that keep its group together. Unpinned candidates are
256// tried in two passes: first the slots with column stream capacity and
257// not soft-reserved for another demand-bearing LTO's peers, then a
258// fallback accepting overloaded columns and reserved slots. Placing a
259// demand-bearing LTO soft-reserves its mem-affinity neighbor slots for
260// that LTO's compute peers.
261// 4. Place each remaining non-core (mem/shim) LTO near the column
262// centroid of its placed-core peers. With mergeLogicalTiles == true
263// (default) several non-core LTOs may share one physical tile when
264// DMA capacity allows, preferring tiles whose North/South stream ports
265// and runtime-sequence BDs also fit; with mergeLogicalTiles == false
266// each non-core LTO claims its own physical tile.
267//
268// Greedy: once a tile is chosen the decision is final, with no backtracking.
269// If a later LTO's constraints become unsatisfiable given prior placements,
270// the placer emits an error pointing at the LTOs the user can manually pin
271// to break the deadlock.
272//
273// Compute-to-compute ObjectFifos consume no DMA channel when their endpoints
274// land on shared-L1 neighbor tiles, so they enter the placer as a separate
275// compute-peer adjacency rather than as channel demand.
276class SequentialPlacer : public Placer {
277public:
278 SequentialPlacer(std::optional<int> coresPerCol = std::nullopt,
279 bool mergeLogicalTiles = true,
280 bool spreadUnanchoredTiles = false)
281 : coresPerCol(coresPerCol), mergeLogicalTiles(mergeLogicalTiles),
282 spreadUnanchoredTiles(spreadUnanchoredTiles) {}
283
284 void initialize(const AIETargetModel &targetModel) override;
285
286 mlir::LogicalResult place(DeviceOp device) override;
287
288 llvm::StringRef getName() const override { return "sequential_placer"; }
289
290private:
291 std::optional<int> coresPerCol;
292 bool mergeLogicalTiles;
293 // When a non-core LTO has no CoreTile peers, rank candidates by DMA load
294 // instead of by distance to column 0. See placeNonCoreTileByCentroid.
295 bool spreadUnanchoredTiles;
296 // Physical tiles already assigned to a non-core aie.logical_tile. Used
297 // only when mergeLogicalTiles == false to forbid mapping a second
298 // non-core aie.logical_tile onto a tile that already hosts one.
299 llvm::DenseSet<TileID> assignedNonCoreTiles;
300 int deviceCoresPerCol = 0; // Actual cores per column in device
301
302 // Shim channels are counted one per end until a shim tile runs out; then
303 // ends never in flight together count once, as allocation will share them.
304 std::optional<ShimTransferSpans> shimSpans;
305 ShareableShimEnds shareableShimEnds;
306 llvm::DenseMap<TileID, std::array<llvm::SmallVector<mlir::StringAttr>, 2>>
307 shimEndsAt;
308 bool fitsSharingShim(TileID tile, mlir::Operation *logicalTile,
309 std::pair<int, int> required);
310 void recordShimEnds(TileID tile, mlir::Operation *logicalTile);
311
312 // Counts once in `requirements` each group of fifos between a core and one
313 // mem tile that allocation's planTurnSharing will put on one channel.
314 void countTurns(
315 DeviceOp device, llvm::ArrayRef<ObjectFifoCreateOp> objectFifos,
316 llvm::DenseMap<mlir::Operation *, std::pair<int, int>> &requirements);
317
318 // DMA channel direction selector.
319 enum class DmaDir { In, Out };
320
321 // Whether an LTO's placement was pinned by the user (via col/row
322 // attributes) or chosen by the placer.
323 enum class PlacementOrigin { Pinned, Selected };
324
325 void limitCoresPerColumn(int maxCoresPerCol, int numColumns);
326
327 // targetCol == nullopt means no routing anchor is known, so candidates are
328 // ranked by DMA load rather than by distance to a column. `withinBudgets`
329 // also requires fitsStreamAndBdBudgets.
330 std::optional<TileID>
331 findTileWithCapacity(std::optional<int> targetCol,
332 llvm::ArrayRef<TileID> tiles, int requiredInputChannels,
333 int requiredOutputChannels, AIETileType requestedType,
334 mlir::Operation *logicalTile, bool withinBudgets);
335
336 // Diagnosis for a failed findTileWithCapacity() call. `findTileWithCapacity`
337 // scans every tile of `requestedType` on the device -- `targetCol` orders
338 // candidates, it does not exclude any -- so a failure is either (a) no
339 // tile of this type has the requested channels free ANYWHERE (device-wide
340 // DMA exhaustion: pinning a column cannot create capacity), or (b) at
341 // least one tile has them free but is already claimed by a different
342 // non-core logical tile (only possible under merge-logical-tiles=false).
343 // `capacityExistsSomewhere` selects between the two; the totals let the
344 // caller report the real budget instead of guessing at it.
345 struct CapacityDiagnosis {
346 bool capacityExistsSomewhere = false;
347 int tilesOfType = 0;
348 int inUsed = 0, inMax = 0, outUsed = 0, outMax = 0;
349 // Both shortfalls belong to one tile, minimizing their sum.
350 int closestShortIn = 0, closestShortOut = 0;
351 };
352 CapacityDiagnosis diagnoseCapacityExhaustion(AIETileType requestedType,
353 int requiredInputChannels,
354 int requiredOutputChannels);
355
356 void updateChannelUsage(TileID tile, DmaDir direction, int numChannels);
357
358 // North/South streams of each tile's ObjectFifos: a memtile has 6
359 // DMA inputs but only 4 streams from the core above, and no East/West.
360 llvm::DenseMap<mlir::Operation *, StreamDemand> streamDemand;
361 // Aggregate traffic between a column's cores and the tiles below them.
362 llvm::DenseMap<int, StreamDemand> columnStreamsUsed;
363 // Broadcast consumers in one column share a single incoming stream.
364 llvm::DenseMap<mlir::Operation *, llvm::SmallVector<mlir::Operation *>>
365 incomingFifos;
366 llvm::DenseMap<int, llvm::DenseSet<mlir::Operation *>> columnIncomingFifos;
367 void recordColumnStreamUsage(mlir::Operation *tile, int col);
368 // Extra broadcast destinations beyond the first, which uses one DMA channel.
369 llvm::DenseMap<mlir::Operation *, int> broadcastFanout;
370 llvm::DenseMap<TileID, int> broadcastFanoutUsed;
371 void buildStreamDemand(llvm::ArrayRef<ObjectFifoCreateOp> objectFifos);
372
373 // Live runtime-sequence BDs of each ShimNOCTile LTO after every task event,
374 // counting each descriptor as aie-decompose-large-dma-bd splits it.
375 llvm::DenseMap<mlir::Operation *, llvm::SmallVector<int>> runtimeBdProfiles;
376
377 // DMA channels, as (output, index), that each logical tile's flows name by
378 // index. Two logical tiles naming the same one cannot share a physical tile.
379 llvm::DenseMap<mlir::Operation *, llvm::SmallVector<std::pair<int, int>>>
380 namedChannels;
381 void buildRuntimeBdProfiles(DeviceOp device,
382 llvm::ArrayRef<ObjectFifoCreateOp> objectFifos);
383
384 // True if `tile`'s stream ports and BDs still fit `logicalTile`'s demand on
385 // top of what earlier placements use.
386 bool fitsStreamAndBdBudgets(mlir::Operation *logicalTile, TileID tile) const;
387 // Records the streams, BDs and named DMA channels `logicalTile` takes on
388 // `tile`.
389 void recordStreamAndBdUsage(mlir::Operation *logicalTile, TileID tile);
390
391 bool hasAvailableChannels(TileID tile, int inputChannels, int outputChannels);
392
393 mlir::LogicalResult validateAndUpdateChannelUsage(
394 LogicalTileOp logicalTile, TileID tile,
395 const llvm::DenseMap<mlir::Operation *, std::pair<int, int>>
396 &channelRequirements,
397 PlacementOrigin origin);
398
399 llvm::DenseMap<mlir::Operation *, std::pair<int, int>>
400 buildChannelRequirements(
401 llvm::SmallVector<ObjectFifoCreateOp> &objectFifos,
402 llvm::SmallVector<ObjectFifoLinkOp> &objectFifoLinks);
403
404 // Per-place() neighbor-demand maps and soft-reservation table.
405 struct PlacementContext {
406 const AIETargetModel &targetModel;
407 const Adjacency &computePeerAdjacency;
408 const llvm::DenseMap<mlir::Operation *, int> &needNeighborIn;
409 const llvm::DenseMap<mlir::Operation *, int> &needNeighborOut;
410 // Mutable: each placement of an LTO with neighbor demand reserves
411 // its mem-affinity neighbor slots for that LTO's compute peers.
412 llvm::DenseMap<TileID, llvm::SmallVector<mlir::Operation *, 2>>
413 reservedFor{};
414
415 int neighborDemand(mlir::Operation *op) const {
416 return needNeighborIn.lookup(op) + needNeighborOut.lookup(op);
417 }
418 // Priority = max(self demand, highest peer demand). A peer of a
419 // high-fanin Worker rises to the front of the sort so its neighbor
420 // slots aren't consumed by unrelated LTOs.
421 int placementPriority(mlir::Operation *op) const;
422 // Soft-reserve `at`'s mem-affinity compute-tile neighbors for
423 // `placedOp`'s peers, if `placedOp` has non-zero neighbor demand.
424 void reserveNeighborSlots(mlir::Operation *placedOp, TileID at);
425 // True if `candidate` is reserved by any LTO that is NOT a compute
426 // peer of `lto` (i.e. would compete for the slot).
427 bool isReservedForOther(mlir::Operation *lto, TileID candidate) const;
428 };
429
430 // A pairwise-legality adjacency constraint (buffer, cascade, ...)
431 // bundled with everything the placer needs to check it and report a
432 // useful diagnostic when it fails: the edge set, the predicate, the
433 // peer-role label for notes, the short name for the error message,
434 // and the educational hint text appended after the peer notes.
435 struct AdjacencyKind {
436 const Adjacency &adjacency;
437 llvm::function_ref<bool(TileID firstPos, TileID secondPos)> pred;
438 llvm::function_ref<llvm::StringRef(bool thisIsFirst)> peerLabel;
439 llvm::StringRef name; // e.g. "shared-L1 buffer", "cascade"
440 llvm::StringRef constraintHint; // appended as a final attachNote
441 };
442
443 // Check `logicalTile` at `tile` against `kind`'s adjacency. On
444 // failure, emit a fully-formed diagnostic (error + peer notes +
445 // constraint hint) and return failure.
446 mlir::LogicalResult enforceAdjacency(LogicalTileOp logicalTile, TileID tile,
447 const AdjacencyKind &kind);
448
449 // Record that `logicalTile` is placed at `tile`. For CoreTile LTOs
450 // this also removes the tile from availability and asks `ctx` to
451 // soft-reserve the tile's mem-affinity neighbor slots for the LTO's
452 // compute peers. `debugLabel` ("pinned" / "unconstrained") names the
453 // placement path in the LLVM_DEBUG trace.
454 void recordPlacement(LogicalTileOp logicalTile, TileID tile,
455 PlacementContext &ctx, llvm::StringRef debugLabel);
456
457 // Per-call inputs for `findUnconstrainedCoreCandidate`. Bundles the
458 // adjacency + demand state that Phase 3 builds for each candidate
459 // filter.
460 struct UnpinnedPlacementInputs {
461 const Adjacency &bufferAdjacency;
462 llvm::function_ref<bool(TileID, TileID)> bufferPred;
463 const Adjacency &cascadeAdjacency;
464 llvm::function_ref<bool(TileID, TileID)> cascadePred;
465 const Adjacency &sharedMemAdjacency;
466 llvm::function_ref<bool(TileID, TileID)> sharedMemPred;
467 const Adjacency &computePeerAdjacency;
468 const llvm::DenseMap<mlir::Operation *, int> &needNeighborIn;
469 const llvm::DenseMap<mlir::Operation *, int> &needNeighborOut;
470 llvm::function_ref<bool(mlir::Operation *, TileID)> isReservedForOther;
471 // Columns to try first, in order, to keep the LTO with its core group
472 // (see buildCoreGroups). Ignored for LTOs with neighbor demand.
473 llvm::ArrayRef<int> preferredCols;
474 };
475
476 // Outputs of `findUnconstrainedCoreCandidate`. `placement` is set on
477 // success; on failure the three boolean flags describe which filter
478 // rejected the most candidates so place() can construct an actionable
479 // diagnostic.
480 struct UnpinnedSearchResult {
481 std::optional<TileID> placement;
482 bool sawConstraintMatch = false;
483 bool allConstraintMatchesFailedAdjacency = true;
484 bool computePeerWasCause = false;
485 };
486
487 // Two-pass candidate search for an unpinned (or partially-pinned)
488 // CoreTile LTO. First pass prefers candidates that aren't
489 // soft-reserved by an unrelated demand-bearing LTO's peers; second
490 // pass falls back to reserved candidates. Each candidate is filtered
491 // by buffer adjacency, cascade adjacency, and the compute-peer DMA
492 // budget. Does not mutate placer state.
493 UnpinnedSearchResult
494 findUnconstrainedCoreCandidate(LogicalTileOp logicalTile,
495 std::optional<int> col, std::optional<int> row,
496 const UnpinnedPlacementInputs &inputs);
497
498 // Edge: (consumer LTO, owner tile). Predicate: `isLegalMemAffinity`.
499 // SequentialPlacer-only: walks CoreOps and BufferOps, and takes each end of
500 // an allocated fifo as a consumer of its delegate tile.
502 buildBufferAdjacency(llvm::ArrayRef<LogicalTileOp> logicalTiles,
503 llvm::ArrayRef<ObjectFifoAllocateOp> allocates);
504
505 // Per-LTO peer-Value lists, one list per flow that mentions the LTO.
506 // Built once and shared so each centroid lookup is O(#flows on the LTO).
507 struct FlowMembership {
508 llvm::DenseMap<mlir::Value,
509 llvm::SmallVector<llvm::SmallVector<mlir::Value>>>
510 ltoFlows;
511 };
512
513 // Phase 4 driver: for every still-unplaced non-core LTO, place it near
514 // the centroid column of its core peers, iterating non-core LTOs in
515 // descending channel-demand order so the heaviest consumers get first
516 // pick of physical tiles.
517 mlir::LogicalResult placeNonCoreLogicalTiles(
518 llvm::ArrayRef<LogicalTileOp> logicalTiles,
519 const FlowMembership &flowIndex,
520 const llvm::DenseMap<mlir::Operation *, std::pair<int, int>>
521 &channelRequirements);
522
523 // Core LTOs grouped by the non-core LTO each shares the most flows with
524 // (its hub), members in program order. A core with no non-core peer is a
525 // group of its own. Placing a group in one column lets the hub's
526 // centroid land on that column.
527 struct CoreGroups {
528 llvm::DenseMap<mlir::Operation *, unsigned> groupOf;
529 llvm::SmallVector<llvm::SmallVector<LogicalTileOp>> members;
530 };
531
532 CoreGroups buildCoreGroups(llvm::ArrayRef<LogicalTileOp> logicalTiles,
533 const FlowMembership &flowIndex) const;
534
535 // Columns to try first for `logicalTile`: those already holding members
536 // of its group, most members first; for a group's first member, the
537 // columns with a free core for every member, in column order. Empty
538 // means no preference.
539 llvm::SmallVector<int> preferredGroupColumns(LogicalTileOp logicalTile,
540 const CoreGroups &groups) const;
541
542 FlowMembership
543 buildFlowMembership(llvm::ArrayRef<FlowOp> flows,
544 llvm::ArrayRef<PacketFlowOp> pktFlows,
545 llvm::ArrayRef<RouteOp> routes,
546 llvm::ArrayRef<ObjectFifoCreateOp> objectFifos);
547
548 // Pick the column that minimizes total routing cost across the LTO's
549 // flows. See AIEPlacer.cpp for the per-flow cost formulas and tiebreak.
550 // Returns nullopt when the LTO has no CoreTile peer to measure against.
551 std::optional<int> computeCentroidColumn(LogicalTileOp logicalTile,
552 const FlowMembership &flowIndex);
553
554 mlir::LogicalResult placeNonCoreTileByCentroid(
555 LogicalTileOp logicalTile, const FlowMembership &flowIndex,
556 const llvm::DenseMap<mlir::Operation *, std::pair<int, int>>
557 &channelRequirements);
558
559 // Pairwise legality check. `pred(firstPos, secondPos)` is evaluated for
560 // every edge mentioning `logicalTile`; unplaced peers defer.
561 bool satisfiesAdjacency(
562 LogicalTileOp logicalTile, TileID candidate, const Adjacency &adjacency,
563 llvm::function_ref<bool(TileID firstPos, TileID secondPos)> pred) const;
564
565 // Compute-peer DMA budget check. Returns true if placing `logicalTile` at
566 // `candidate` keeps the compute-peer neighbor demand satisfiable for BOTH
567 // the LTO being placed AND every already-placed compute peer of the LTO.
568 // A compute LTO with peer demand N (N compute-peer fifos must use shared
569 // neighbor memory to fit DMA budget) has slack (totalComputePeers − N): it
570 // can afford at most that many non-neighbor compute peers. The check also
571 // forward-looks: unplaced peers still need a free physical compute-tile
572 // neighbor of `candidate` to land on.
573 bool satisfiesComputePeer(
574 LogicalTileOp logicalTile, TileID candidate,
575 const Adjacency &computePeerAdjacency,
576 const llvm::DenseMap<mlir::Operation *, int> &needNeighborIn,
577 const llvm::DenseMap<mlir::Operation *, int> &needNeighborOut) const;
578
579 // Compute-peer in/out edge counts for `op`, looked up from a prebuilt
580 // `computePeerAdjacency`. Returns {0, 0} if `op` has no entry. Producer
581 // is edges[idx].first, consumer is .second (per buildComputePeerAdjacency).
582 static std::pair<int, int> totalComputePeers(mlir::Operation *op,
583 const Adjacency &adjacency);
584
585 // Diagnostic peer notes. `labelPeer(thisIsFirst)` names the peer endpoint
586 // role; the attached note reads "<label> peer placed at (col, row)".
587 void attachPeerNotes(
588 mlir::InFlightDiagnostic &diag, LogicalTileOp logicalTile,
589 const Adjacency &adjacency,
590 llvm::function_ref<llvm::StringRef(bool thisIsFirst)> labelPeer) const;
591
592 void addChannelRequirementsFromFlows(
593 llvm::ArrayRef<FlowOp> flows, llvm::ArrayRef<PacketFlowOp> pktFlows,
594 llvm::ArrayRef<RouteOp> routes,
595 llvm::DenseMap<mlir::Operation *, std::pair<int, int>>
596 &channelRequirements);
597};
598
599// Configuration for the SA placer's cost model and schedule.
600//
601// The SA placer minimizes a total cost with four components:
602//
603// totalCost = HPWL + resourcePenalty + cascadePenalty + memoryPressure
604//
605// 1. HPWL (half-perimeter wire length): sum of bounding-box perimeters
606// across all nets.
607//
608// 2. Resource penalty (hard, blocks legality): penalizes MemTile buffer
609// overflow, core tile overflow, DMA channel overuse, BD count
610// overuse, and fifo ends that can't reach their allocate's delegate.
611//
612// 3. Cascade penalty (hard, blocks legality): penalizes cascade put/get
613// pairs that aren't adjacent.
614//
615// 4. Memory pressure (soft, guides optimization): quadratic penalty when
616// tile utilization exceeds a threshold.
617struct SAConfig {
618 // HPWL (cost 1)
619 int multicastThreshold = 4; // consumer count above which penalty applies
620 int multicastMultiplier = 2; // HPWL multiplier for multicast nets
621
622 // Resource penalty (cost 2)
623 int memPenaltyPerKB = 30; // per KB of unresolved memory overflow
624 int dmaPenaltyPerChannel = 20; // per DMA channel or BD over limit
625 int delegateWeightPerDist = 30; // per distance from an allocate's delegate
626
627 // Cascade penalty (cost 3)
628 int cascadeWeightPerDist = 30; // per Manhattan distance to valid position
629 // A shared-memory pair apart, per distance past a neighbour: above a
630 // fifo's worth of memory overflow, which a neighbour's spare room absorbs.
632
633 // The core's own sections (tables, constants) a core that declares no
634 // data_size is taken to need, before aiecc measures them after placement.
635 int coreSectionsBytes = 3 * 1024;
636
637 // Memory pressure (cost 4)
638 int pressurePerKB = 3; // quadratic pressure above threshold
639 double pressureThreshold = 0.75; // utilization fraction triggering pressure
640
641 // Adaptive cooling
642 double coolFast = 0.995; // accept ratio > 96%: cool faster
643 double coolSteady = 0.998; // accept ratio > 80%: steady progress
644 double coolBalanced = 0.999; // accept ratio > 15%: balanced exploration
645 double coolStuck = 0.9998; // accept ratio <= 15%: barely cool
646 double greedyThreshold = 0.005; // switch to greedy (T=0) below this
647
648 // Schedule sizing
650 int maxMovesPerIter = 2000;
651 int minMaxIters = 10000;
652 int greedyMultiplier = 50; // greedyIters = multiplier * numMovable
653 double effort = 1.0; // scales movesPerIter and greedyIters
654 double tempScaleEstimate = 10.0; // initTemp cap: scale * estimated T
655 double tempScaleCost = 2.0; // initTemp cap: scale * totalCost
656
657 // Temperature estimation
658 int tempSearchIters = 100; // binary search iterations
659 double tempSearchCeiling = 5.0; // highT = ceiling * maxDelta
660 double tempSearchEpsilon = 1e-10; // zero guard in binary search
661 double tempSearchTolerance = 1e-6; // convergence tolerance
662
663 // SA loop intervals
664 int debugInterval = 5000; // iterations between debug log lines
665 int fullRecomputeInterval = 10000; // iterations between full cost recompute
666
667 // Move generation
668 int maxMoveAttempts = 20; // retries per move before giving up
669
670 // Acceptance tracking
671 int windowSize = 100; // sliding window for acceptance ratio
672};
673
674// SA temperature schedule with windowed acceptance tracking.
676public:
677 SASchedule() = default;
678 SASchedule(double startTemp, int movesPerIter, int maxIters, int greedyIters,
679 const SAConfig &cfg = {});
680
681 double getTemperature() const { return temperature; }
682 int getIteration() const { return currIteration; }
683 int getMovesPerIter() const { return movesPerIter; }
684 bool isGreedy() const { return inGreedyStage; }
685 bool limitReached() const;
686 double getAcceptanceRatio() const;
687 void record(bool accepted);
688 void cool();
689
690private:
691 double temperature = 0.0;
692 int currIteration = 0;
693 int currGreedyIteration = 0;
694 int movesPerIter = 100;
695 int maxIters = 5000;
696 int greedyIters = 100;
697 int windowSize = 100;
698 SAConfig config;
699 bool inGreedyStage = false;
700 std::deque<int> history;
701 int acceptCount = 0;
702 int rejectCount = 0;
703
704 void trimWindow();
705};
706
707// Per-net bounding box state for incremental HPWL updates.
708// countAt* tracks how many endpoints sit at each extreme so we can avoid
709// full rescans when an endpoint moves away from a non-unique extreme.
711 int minCol = 0, maxCol = 0, minRow = 0, maxRow = 0;
714};
715
716struct NetInfo {
717 llvm::SmallVector<mlir::Operation *> endpoints; // producer + all consumers
719 bool isMulticast = false; // >4 consumers triggers 2x HPWL penalty
720};
721
722// Simulated annealing placement algorithm
723//
724// Uses HPWL (half-perimeter wire length) cost with incremental bounding-box
725// updates. Nets are derived from ObjectFifo producer/consumer connectivity.
726// All tile types (compute, mem, shim) participate in SA moves. After SA
727// converges, a merge pass collapses mem/shim logical tiles that landed in the
728// same column onto a shared physical tile when DMA capacity permits.
729class SAPlacer : public Placer {
730public:
731 explicit SAPlacer(unsigned seed = 0, SAConfig config = {})
732 : rngSeed(seed), config(config) {}
733
734 mlir::LogicalResult place(DeviceOp device) override;
735 llvm::StringRef getName() const override { return "sa_placer"; }
736 int getFinalCost() const { return finalCost; }
737
738private:
739 // Configuration
740 unsigned rngSeed;
741 SAConfig config;
742 int finalCost = 0;
743 std::mt19937 rng;
744 SASchedule schedule;
745
746 // Collected IR state
747 CollectedOps collected;
748 llvm::DenseMap<mlir::Operation *, AIETileType> tileTypes;
749 llvm::DenseMap<mlir::Operation *, int64_t> staticBufferSizes;
750 llvm::DenseMap<mlir::Operation *, int64_t> stackSizes;
751
752 // Net model
753 struct FifoBufferInfo {
754 mlir::Operation *fifoOp = nullptr;
755 mlir::Operation *producer = nullptr;
756 llvm::SmallVector<mlir::Operation *> consumers;
757 mlir::Operation *delegate = nullptr;
758 int64_t producerSizeBytes = 0;
759 int64_t consumerSizeBytes = 0;
760 int producerDepth = 0;
761 llvm::SmallVector<int> consumerDepths;
762 int producerDMADepth = 0;
763 llvm::SmallVector<int> consumerDMADepths;
764 bool forcesDMA = false;
765 bool linkSharedProd = false;
766
767 int consumerDepthAt(size_t ci) const {
768 return (ci < consumerDepths.size()) ? consumerDepths[ci] : producerDepth;
769 }
770 };
771 llvm::SmallVector<NetInfo> nets;
772 llvm::DenseMap<mlir::Operation *, llvm::SmallVector<size_t>> tileToNetIndices;
773 llvm::SmallVector<FifoBufferInfo> fifoBuffers;
774
775 // A route's DMA end on a logical tile: one channel, in or out, that moves
776 // with the tile.
777 struct RouteEndInfo {
778 mlir::Operation *tile;
779 bool output;
780 };
781 llvm::SmallVector<RouteEndInfo> routeEnds;
782 llvm::DenseMap<mlir::Operation *, llvm::SmallVector<size_t>> tileToRouteEnds;
783 // Descriptors the pools on each logical tile need, for the mem-tile budget.
784 llvm::DenseMap<mlir::Operation *, int> poolBDs;
785
786 // Placement state
787 llvm::DenseMap<mlir::Operation *, TileID> currentPlacement;
788 llvm::DenseMap<TileID, mlir::Operation *> physToLogical;
789 llvm::SmallVector<mlir::Operation *> movableTiles;
790 llvm::DenseSet<mlir::Operation *> constrainedTiles;
791
792 // Resource tracking
793 llvm::DenseMap<mlir::Operation *, llvm::SmallVector<size_t>>
794 tileToFifoIndices;
795 llvm::DenseMap<TileID, int64_t> currentMemUsage;
796 llvm::DenseMap<TileID, std::pair<int, int>> currentDMAUsage;
797 llvm::DenseMap<size_t, TileID> sharedMemDestination;
798 int cachedResourcePenalty = 0;
799 // An over-full shim tile is charged only for the channels allocation
800 // still needs once ends never in flight together share them.
801 std::optional<ShimTransferSpans> shimSpans;
802 ShareableShimEnds shareableShimEnds;
803
804 // Cascade
805 Adjacency cascadeAdjacency;
806 static constexpr std::pair<int, int> kCascadeOffsets[] = {{1, 0}, {0, -1}};
807 // Ends of fifos that ask for shared memory, which must share a module.
808 Adjacency sharedMemAdjacency;
809
810 // SA loop state
811 int totalCost = 0;
812 int cascadePen = 0;
813 int bestCost = INT_MAX; // best legal (resPenalty==0 && cascade==0)
814 int bestOverallCost = INT_MAX; // best regardless of legality (fallback)
815 PlacementResult bestPlacement; // corresponds to bestCost
816 PlacementResult bestOverallPlacement; // corresponds to bestOverallCost
817 int deviceSlots = 0;
818 int zeroDeltaMoves = 0, posDeltaMoves = 0, negDeltaMoves = 0;
819 int acceptedUphill = 0, rejectedMoves = 0;
820 std::chrono::steady_clock::time_point startTime;
821
822 // Phase methods
823 mlir::LogicalResult collectAndBuildModel(DeviceOp device);
824 mlir::LogicalResult generateInitialPlacement();
825 void initializeSAState();
826 void runSAMainLoop();
827 mlir::LogicalResult finalizePlacement(DeviceOp device);
828 mlir::LogicalResult mergeMemShimTiles(DeviceOp device);
829
830 // Cost and resource methods
831 void initResourceTracking();
832 void addFifoContribution(size_t fifoIdx, int sign);
833 void addRouteEndContribution(size_t endIdx, int sign);
834 int updateResourcePenalty(
835 const llvm::SmallVector<std::pair<mlir::Operation *, TileID>>
836 &oldPlacements);
837 int getResourcePenalty() const { return cachedResourcePenalty; }
838 int computePenalty() const;
839 int computeMemSpilloverPenalty() const;
840 int computeCoreOverflowPenalty() const;
841 int computeDMAChannelPenalty() const;
842 int computeBDCountPenalty() const;
843 int computeDelegatePenalty() const;
844 std::optional<TileID> positionOf(mlir::Operation *tile) const;
845 int computeMemoryPressure() const;
846 int computeAdjacencyPenalty(const Adjacency &adj,
847 llvm::ArrayRef<std::pair<int, int>> validOffsets,
848 int weight) const;
849 /// Cascade neighbours placed apart, and shared-memory ends placed on
850 /// tiles sharing no memory module, by how far.
851 int computePairingPenalty() const;
852 void generateAllocates();
853
854 // Move methods
855 void tryMove(llvm::SmallVector<std::pair<mlir::Operation *, TileID>> &moves);
856 bool
857 generateMove(llvm::SmallVector<std::pair<mlir::Operation *, TileID>> &moves);
858 int evaluateMove(
859 mlir::Operation *tile, TileID newPos,
860 llvm::SmallVector<std::pair<size_t, NetBoundingBox>> &backups);
861 void
862 revertMove(llvm::SmallVector<std::pair<size_t, NetBoundingBox>> &backups);
863
864 // Utility methods
865 void buildNetModel(llvm::SmallVector<ObjectFifoCreateOp> &objectFifos,
866 llvm::SmallVector<ObjectFifoLinkOp> &objectFifoLinks);
867 void buildFifoBufferInfo(DeviceOp device,
868 llvm::ArrayRef<ObjectFifoCreateOp> objectFifos,
869 llvm::ArrayRef<ObjectFifoLinkOp> objectFifoLinks);
870 void buildRouteModel(DeviceOp device, llvm::ArrayRef<RouteOp> routes);
871 int computeNetHPWL(const NetInfo &net) const;
872 int computeTotalHPWL() const;
873 void initBoundingBoxes();
874 double estimateInitialTemperature(int numSamples);
875 bool isLegalPosition(mlir::Operation *tile, TileID pos) const;
876 int64_t computeTileMemoryWeight(mlir::Operation *tile) const;
877 void printPlacementStats(int64_t elapsedMs) const;
878};
879
880} // namespace xilinx::AIE
881
882#endif // AIE_PLACER_H
virtual uint32_t getNumSourceShimMuxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a shimmux.
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 int columns() const =0
Return the number of columns in the device.
virtual uint32_t getNumDestShimMuxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a shimmux.
virtual uint32_t getNumDestSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of destinations of connections inside a switchbox.
virtual bool isLegalMemAffinity(int coreCol, int coreRow, int memCol, int memRow) const =0
Return true if core can access the memory in mem.
virtual uint32_t getNumSourceSwitchboxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a switchbox.
virtual mlir::LogicalResult place(DeviceOp device)=0
llvm::DenseMap< mlir::Operation *, std::array< llvm::SmallVector< mlir::StringAttr >, 2 > > ShareableShimEnds
Definition AIEPlacer.h:169
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
Definition AIEPlacer.h:184
std::pair< mlir::Operation *, TileID > AllocateInfo
Definition AIEPlacer.h:101
virtual void initialize(const AIETargetModel &tm)
Definition AIEPlacer.cpp:51
static Adjacency buildComputePeerAdjacency(llvm::ArrayRef< ObjectFifoCreateOp > objectFifos)
virtual ~Placer()=default
llvm::SmallVector< AllocateInfo > allocates
Definition AIEPlacer.h:183
virtual llvm::StringRef getName() const =0
PlacementResult result
Definition AIEPlacer.h:182
std::optional< TileID > getPlacement(mlir::Operation *logicalTile) const
Definition AIEPlacer.h:91
llvm::SmallVector< AllocateInfo > & getAllocates()
Definition AIEPlacer.h:102
TileAvailability availability
Definition AIEPlacer.h:185
static ShareableShimEnds collectShareableShimEnds(DeviceOp device, llvm::ArrayRef< ObjectFifoCreateOp > objectFifos)
static CollectedOps collectOperations(DeviceOp device)
Definition AIEPlacer.cpp:73
static std::optional< ShimTransferSpans::End > shimEndOf(DeviceOp device, mlir::StringAttr symbol)
SAPlacer(unsigned seed=0, SAConfig config={})
Definition AIEPlacer.h:731
llvm::StringRef getName() const override
Definition AIEPlacer.h:735
int getFinalCost() const
Definition AIEPlacer.h:736
mlir::LogicalResult place(DeviceOp device) override
void record(bool accepted)
double getTemperature() const
Definition AIEPlacer.h:681
int getIteration() const
Definition AIEPlacer.h:682
double getAcceptanceRatio() const
int getMovesPerIter() const
Definition AIEPlacer.h:683
bool isGreedy() const
Definition AIEPlacer.h:684
SequentialPlacer(std::optional< int > coresPerCol=std::nullopt, bool mergeLogicalTiles=true, bool spreadUnanchoredTiles=false)
Definition AIEPlacer.h:278
llvm::StringRef getName() const override
Definition AIEPlacer.h:288
void initialize(const AIETargetModel &targetModel) override
mlir::LogicalResult place(DeviceOp device) override
void forEachPeer(mlir::Operation *op, const Placer::Adjacency &adjacency, F &&fn)
Definition AIEPlacer.h:192
llvm::DenseMap< mlir::Operation *, TileID > PlacementResult
Definition AIEPlacer.h:50
void forEachMemAffinityNeighbor(const AIETargetModel &targetModel, TileID at, F &&fn)
Definition AIEPlacer.h:221
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
PlacerType
Placement algorithm type for pass option.
Definition AIEPlacer.h:22
std::pair< int, int > getDMACapacity(const AIETargetModel &tm, TileID tile)
Get DMA channel capacity (maxIn, maxOut) for a tile position.
Definition AIEPlacer.h:25
bool isSharedComputeMemory(const AIETargetModel &targetModel, TileID a, TileID b)
Definition AIEPlacer.h:212
llvm::SmallVector< mlir::Operation * > endpoints
Definition AIEPlacer.h:717
NetBoundingBox bb
Definition AIEPlacer.h:718
void addEdge(TileLike first, TileLike second)
Definition AIEPlacer.cpp:31
llvm::DenseMap< mlir::Operation *, llvm::SmallVector< unsigned, 2 > > tileToEdges
Definition AIEPlacer.h:110
void addEdgeFromValues(mlir::Value a, mlir::Value b)
Definition AIEPlacer.cpp:42
bool hasEdges(mlir::Operation *op) const
Definition AIEPlacer.h:118
llvm::SmallVector< std::pair< TileLike, TileLike >, 4 > edges
Definition AIEPlacer.h:108
llvm::SmallVector< RouteOp > routes
Definition AIEPlacer.h:132
llvm::SmallVector< ObjectFifoLinkOp > objectFifoLinks
Definition AIEPlacer.h:127
llvm::SmallVector< CascadeFlowOp > cascadeFlows
Definition AIEPlacer.h:129
llvm::SmallVector< PacketFlowOp > pktFlows
Definition AIEPlacer.h:131
llvm::SmallVector< FlowOp > flows
Definition AIEPlacer.h:130
llvm::SmallVector< ObjectFifoCreateOp > objectFifos
Definition AIEPlacer.h:126
llvm::SmallVector< ObjectFifoAllocateOp > objectFifoAllocates
Definition AIEPlacer.h:128
llvm::SmallVector< LogicalTileOp > logicalTiles
Definition AIEPlacer.h:125
StreamDemand & operator+=(const StreamDemand &other)
Definition AIEPlacer.h:40
std::vector< TileID > compTiles
Definition AIEPlacer.h:54
llvm::DenseMap< TileID, int > outputChannelsUsed
Definition AIEPlacer.h:67
void removeTile(TileID tile, AIETileType type)
std::vector< TileID > nonCompTiles
Definition AIEPlacer.h:55
llvm::DenseSet< TileID > compTilesSet
Definition AIEPlacer.h:64
llvm::DenseMap< TileID, int > inputChannelsUsed
Definition AIEPlacer.h:66
llvm::DenseMap< TileID, StreamDemand > streamsUsed
Definition AIEPlacer.h:68
llvm::DenseMap< TileID, llvm::DenseSet< std::pair< int, int > > > namedChannelsUsed
Definition AIEPlacer.h:74
llvm::DenseMap< TileID, llvm::SmallVector< int > > runtimeBdsUsed
Definition AIEPlacer.h:71