Skip to content

Commit 6db9090

Browse files
committed
[AIEPlacer] Address PR #3042 review feedback
Reuse, simplification, and broader peer-op support driven by Copilot review comments and self-review: * Reuse: delegate cascade direction check to `targetModel->isSouth` / `isEast` (the same predicates `AIELowerCascadeFlowsPass` uses), so placer and lowering agree by construction. Removes the inline arithmetic and its inverted-feeling comments. * Data structure: replace `pair<LogicalTileOp, bool>` adjacency entries with a single edge list (`{src, dst}`) plus per-tile index. The `thisIsCascadeDst` boolean falls out — direction is derived at probe time by comparing op identity to edge endpoints. Move `CascadeAdjacency` into `SequentialPlacer`'s private section. * Mixed `aie.tile` / `aie.logical_tile` cascade flows: edges now hold `TileLike`. A `resolvePeerPosition` helper uniformly handles three peer coord sources — placed `LogicalTileOp` (in `result`), `TileOp` (always pinned), and pre-pinned `LogicalTileOp` (via `tryGetCol`/`tryGetRow`). `tileToEdges` only indexes `LogicalTileOp` endpoints since `TileOp`s are never visited by the placer. * Iteration-order brittleness: the `tryGetCol`/`tryGetRow` fallback fully subsumes the earlier stable-partition workaround — an unconstrained tile iterated before its pinned peer now resolves the peer's pin coords directly during candidate filtering. Partition removed. * Error messages: each cascade-induced placement failure now attaches one MLIR diagnostic note per placed peer, naming role (source/destination) and physical position. Test comment fixed to match `AIELowerCascadeFlows`'s own wording (one row North or one column West, rows-up convention noted). * Tests: add `cascade_chain_pinned_middle` (mid-chain anchor), `cascade_hybrid_tileop_peer` (positive `aie.tile` peer), `cascade_partial_constraint_unsatisfiable` (candidate-filter rejection path with new note format), `cascade_hybrid_unsatisfiable` (negative `aie.tile` peer with conflicting partial constraint). All 116 tests pass in `test/place-tiles` + `test/dialect/AIE` (1 pre-existing XFAIL). Downstream mlir-air rebuilds clean against the new public API (`CascadeAdjacency` is now private to `SequentialPlacer` so the change is encapsulated anyway).
1 parent b77202a commit 6db9090

4 files changed

Lines changed: 192 additions & 81 deletions

File tree

include/aie/Dialect/AIE/Transforms/AIEPlacer.h

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,6 @@ enum class PlacerType { SequentialPlacer };
2222
// maps logical tile operations to physical coordinates
2323
using PlacementResult = llvm::DenseMap<mlir::Operation *, TileID>;
2424

25-
// For each LogicalTileOp involved in a cascade flow, the set of (peer,
26-
// thisIsCascadeDst) tuples. `thisIsCascadeDst==true` means this tile is the
27-
// destination of a cascade_flow whose source is `peer`; the lowered cascade
28-
// requires `peer` to be one row higher (north) or one column to the left
29-
// (west) of this tile. `thisIsCascadeDst==false` is the reverse.
30-
using CascadeAdjacency =
31-
llvm::DenseMap<mlir::Operation *,
32-
llvm::SmallVector<std::pair<LogicalTileOp, bool>, 2>>;
33-
3425
// Track available tiles and resource usage
3526
struct TileAvailability {
3627
std::vector<TileID> compTiles;
@@ -123,15 +114,26 @@ class SequentialPlacer : public Placer {
123114
llvm::SmallVector<ObjectFifoCreateOp> &objectFifos,
124115
llvm::SmallVector<ObjectFifoLinkOp> &objectFifoLinks);
125116

126-
void buildCascadeAdjacency(llvm::ArrayRef<CascadeFlowOp> cascadeFlows,
127-
CascadeAdjacency &adjacency);
117+
// `tileToEdges` indexes into `edges` only for `LogicalTileOp` endpoints
118+
// (the ones the placer visits); `TileOp` peers contribute coords via
119+
// `TileLike::tryGetCol`/`tryGetRow`.
120+
struct CascadeAdjacency {
121+
llvm::SmallVector<std::pair<TileLike, TileLike>, 4> edges;
122+
llvm::DenseMap<mlir::Operation *, llvm::SmallVector<unsigned, 2>>
123+
tileToEdges;
124+
};
125+
126+
CascadeAdjacency
127+
buildCascadeAdjacency(llvm::ArrayRef<CascadeFlowOp> cascadeFlows);
128128

129-
// Returns true iff placing `logicalTile` at `candidate` would satisfy every
130-
// cascade adjacency constraint relative to peers that are already placed in
131-
// `result`. Unplaced peers impose no constraint at this point — they will
132-
// be checked when they themselves get placed.
129+
// Unplaced peers without pin coords defer; symmetric predicate makes one
130+
// check per endpoint sufficient.
133131
bool satisfiesCascadeAdjacency(LogicalTileOp logicalTile, TileID candidate,
134132
const CascadeAdjacency &adjacency) const;
133+
134+
void attachCascadePeerNotes(mlir::InFlightDiagnostic &diag,
135+
LogicalTileOp logicalTile,
136+
const CascadeAdjacency &adjacency) const;
135137
};
136138

137139
} // namespace xilinx::AIE

lib/Dialect/AIE/Transforms/AIEPlacer.cpp

Lines changed: 79 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,7 @@ LogicalResult SequentialPlacer::place(DeviceOp device) {
138138
auto channelRequirements =
139139
buildChannelRequirements(objectFifos, objectFifoLinks);
140140

141-
CascadeAdjacency cascadeAdjacency;
142-
buildCascadeAdjacency(cascadeFlows, cascadeAdjacency);
141+
auto cascadeAdjacency = buildCascadeAdjacency(cascadeFlows);
143142

144143
// Phase 3: Place constrained tiles then compute tiles
145144
size_t nextCompIdx = 0;
@@ -149,10 +148,13 @@ LogicalResult SequentialPlacer::place(DeviceOp device) {
149148
auto row = logicalTile.tryGetRow();
150149
if (col && row) {
151150
TileID tile{*col, *row};
152-
if (!satisfiesCascadeAdjacency(logicalTile, tile, cascadeAdjacency))
153-
return logicalTile.emitError()
154-
<< "tile (" << tile.col << ", " << tile.row
155-
<< ") violates cascade adjacency with an already-placed peer";
151+
if (!satisfiesCascadeAdjacency(logicalTile, tile, cascadeAdjacency)) {
152+
auto diag = logicalTile.emitError()
153+
<< "tile (" << tile.col << ", " << tile.row
154+
<< ") violates cascade adjacency";
155+
attachCascadePeerNotes(diag, logicalTile, cascadeAdjacency);
156+
return failure();
157+
}
156158
if (failed(validateAndUpdateChannelUsage(logicalTile, tile,
157159
channelRequirements, true)))
158160
return failure();
@@ -190,17 +192,21 @@ LogicalResult SequentialPlacer::place(DeviceOp device) {
190192
}
191193

192194
if (!placement) {
193-
bool hasCascade = cascadeAdjacency.count(logicalTile.getOperation());
195+
bool hasCascade =
196+
cascadeAdjacency.tileToEdges.count(logicalTile.getOperation());
197+
InFlightDiagnostic diag = logicalTile.emitError();
194198
if (col || row) {
195-
return logicalTile.emitError()
196-
<< "no compute tile available matching constraint ("
197-
<< (col ? std::to_string(*col) : "?") << ", "
198-
<< (row ? std::to_string(*row) : "?") << ")"
199-
<< (hasCascade ? " and cascade adjacency" : "");
200-
}
201-
return logicalTile.emitError()
202-
<< "no available compute tiles for placement"
199+
diag << "no compute tile available matching constraint ("
200+
<< (col ? std::to_string(*col) : "?") << ", "
201+
<< (row ? std::to_string(*row) : "?") << ")"
202+
<< (hasCascade ? " and cascade adjacency" : "");
203+
} else {
204+
diag << "no available compute tiles for placement"
203205
<< (hasCascade ? " (cascade adjacency unsatisfiable)" : "");
206+
}
207+
if (hasCascade)
208+
attachCascadePeerNotes(diag, logicalTile, cascadeAdjacency);
209+
return failure();
204210
}
205211

206212
if (failed(validateAndUpdateChannelUsage(logicalTile, *placement,
@@ -488,52 +494,75 @@ SequentialPlacer::buildChannelRequirements(
488494
return channelRequirements;
489495
}
490496

491-
void SequentialPlacer::buildCascadeAdjacency(
492-
ArrayRef<CascadeFlowOp> cascadeFlows, CascadeAdjacency &adjacency) {
497+
static std::optional<TileID>
498+
resolvePeerPosition(TileLike peer, const PlacementResult &placed) {
499+
auto it = placed.find(peer.getOperation());
500+
if (it != placed.end())
501+
return it->second;
502+
auto col = peer.tryGetCol();
503+
auto row = peer.tryGetRow();
504+
if (col && row)
505+
return TileID{*col, *row};
506+
return std::nullopt;
507+
}
508+
509+
SequentialPlacer::CascadeAdjacency
510+
SequentialPlacer::buildCascadeAdjacency(ArrayRef<CascadeFlowOp> cascadeFlows) {
511+
CascadeAdjacency adjacency;
493512
for (auto cf : cascadeFlows) {
494-
auto src =
495-
dyn_cast_or_null<LogicalTileOp>(cf.getSourceTile().getDefiningOp());
496-
auto dst =
497-
dyn_cast_or_null<LogicalTileOp>(cf.getDestTile().getDefiningOp());
513+
TileLike src = cf.getSourceTileLike();
514+
TileLike dst = cf.getDestTileLike();
498515
if (!src || !dst)
499516
continue;
500-
// src records dst as a peer for which it is the cascade source.
501-
adjacency[src.getOperation()].push_back({dst, /*thisIsCascadeDst=*/false});
502-
// dst records src as a peer for which it is the cascade destination.
503-
adjacency[dst.getOperation()].push_back({src, /*thisIsCascadeDst=*/true});
517+
unsigned idx = adjacency.edges.size();
518+
adjacency.edges.push_back({src, dst});
519+
if (isa<LogicalTileOp>(src.getOperation()))
520+
adjacency.tileToEdges[src.getOperation()].push_back(idx);
521+
if (isa<LogicalTileOp>(dst.getOperation()))
522+
adjacency.tileToEdges[dst.getOperation()].push_back(idx);
523+
}
524+
return adjacency;
525+
}
526+
527+
void SequentialPlacer::attachCascadePeerNotes(
528+
InFlightDiagnostic &diag, LogicalTileOp logicalTile,
529+
const CascadeAdjacency &adjacency) const {
530+
auto it = adjacency.tileToEdges.find(logicalTile.getOperation());
531+
if (it == adjacency.tileToEdges.end())
532+
return;
533+
for (unsigned idx : it->second) {
534+
auto [edgeSrc, edgeDst] = adjacency.edges[idx];
535+
bool thisIsSrc = edgeSrc.getOperation() == logicalTile.getOperation();
536+
TileLike peer = thisIsSrc ? edgeDst : edgeSrc;
537+
auto peerPos = resolvePeerPosition(peer, result);
538+
if (!peerPos)
539+
continue;
540+
diag.attachNote(peer.getLoc())
541+
<< "cascade " << (thisIsSrc ? "destination" : "source")
542+
<< " peer placed at (" << peerPos->col << ", " << peerPos->row << ")";
504543
}
505544
}
506545

507546
bool SequentialPlacer::satisfiesCascadeAdjacency(
508547
LogicalTileOp logicalTile, TileID candidate,
509548
const CascadeAdjacency &adjacency) const {
510-
auto it = adjacency.find(logicalTile.getOperation());
511-
if (it == adjacency.end())
549+
auto it = adjacency.tileToEdges.find(logicalTile.getOperation());
550+
if (it == adjacency.tileToEdges.end())
512551
return true;
513552

514-
for (auto peerEntry : it->second) {
515-
LogicalTileOp peer = peerEntry.first;
516-
bool thisIsCascadeDst = peerEntry.second;
517-
auto placedIt = result.find(peer.getOperation());
518-
if (placedIt == result.end())
519-
continue; // Peer not placed yet — constraint deferred.
520-
TileID peerPos = placedIt->second;
521-
522-
// Cascade lowering only supports source-north-of-dest (vertical) or
523-
// source-west-of-dest (horizontal). So when this tile is the dst, the
524-
// peer (=src) must be at (peer.col, peer.row+1) [one row higher = north]
525-
// or (peer.col-1, peer.row) [one col left = west] of `candidate`.
526-
bool ok;
527-
if (thisIsCascadeDst) {
528-
// peer must be one row above OR one column left of candidate.
529-
ok = (peerPos.col == candidate.col && peerPos.row == candidate.row + 1) ||
530-
(peerPos.row == candidate.row && peerPos.col == candidate.col - 1);
531-
} else {
532-
// peer must be one row below OR one column right of candidate.
533-
ok = (peerPos.col == candidate.col && peerPos.row == candidate.row - 1) ||
534-
(peerPos.row == candidate.row && peerPos.col == candidate.col + 1);
535-
}
536-
if (!ok)
553+
// Same predicate as AIELowerCascadeFlowsPass: src must be one row North
554+
// or one column West of dst (rows increase upward).
555+
for (unsigned idx : it->second) {
556+
auto [edgeSrc, edgeDst] = adjacency.edges[idx];
557+
bool thisIsSrc = edgeSrc.getOperation() == logicalTile.getOperation();
558+
TileLike peer = thisIsSrc ? edgeDst : edgeSrc;
559+
auto peerPos = resolvePeerPosition(peer, result);
560+
if (!peerPos)
561+
continue;
562+
TileID srcPos = thisIsSrc ? candidate : *peerPos;
563+
TileID dstPos = thisIsSrc ? *peerPos : candidate;
564+
if (!targetModel->isSouth(srcPos.col, srcPos.row, dstPos.col, dstPos.row) &&
565+
!targetModel->isEast(srcPos.col, srcPos.row, dstPos.col, dstPos.row))
537566
return false;
538567
}
539568
return true;

test/place-tiles/sequential_placer/test_place_cascade.mlir

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@
1010

1111
// RUN: aie-opt --split-input-file --aie-place-tiles %s | FileCheck %s
1212

13-
// Cascade chain with no constraints — sequential greedy fill places head at
14-
// (0,2); each subsequent tile in the chain must be south or east of its peer.
15-
// South of (0,2) is row 1 which is not a CoreTile, so the chain extends east
16-
// along row 2.
13+
// Unconstrained chain extends east along row 2 (south of row 2 is non-core).
1714
// CHECK-LABEL: @cascade_chain_unconstrained
1815
module @cascade_chain_unconstrained {
1916
aie.device(npu1) {
@@ -38,8 +35,7 @@ module @cascade_chain_unconstrained {
3835

3936
// -----
4037

41-
// Cascade chain with the head pinned to the top of column 0 — chain extends
42-
// south down the same column.
38+
// Pinned head at top of column 0 — chain extends south down the column.
4339
// CHECK-LABEL: @cascade_chain_vertical
4440
module @cascade_chain_vertical {
4541
aie.device(npu1) {
@@ -64,8 +60,7 @@ module @cascade_chain_vertical {
6460

6561
// -----
6662

67-
// Cascade between two pre-pinned tiles that satisfy the constraint — placer
68-
// is a pass-through, no errors.
63+
// Pre-pinned compatible cascade — placer is a pass-through.
6964
// CHECK-LABEL: @cascade_pinned_compatible
7065
module @cascade_pinned_compatible {
7166
aie.device(npu1) {
@@ -79,3 +74,59 @@ module @cascade_pinned_compatible {
7974
// CHECK-NOT: aie.logical_tile
8075
}
8176
}
77+
78+
// -----
79+
80+
// Pinned peer appears in IR after unconstrained peer; %a's candidates are
81+
// filtered against %b's pin coords (not just placed peers), so %a → (0,4).
82+
// CHECK-LABEL: @cascade_pinned_anchor_after_unconstrained
83+
module @cascade_pinned_anchor_after_unconstrained {
84+
aie.device(npu1) {
85+
// CHECK-DAG: %[[A:.*]] = aie.tile(0, 4)
86+
%a = aie.logical_tile<CoreTile>(?, ?)
87+
// CHECK-DAG: %[[B:.*]] = aie.tile(0, 3)
88+
%b = aie.logical_tile<CoreTile>(0, 3)
89+
aie.cascade_flow(%a, %b)
90+
aie.core(%a) { aie.end }
91+
aie.core(%b) { aie.end }
92+
// CHECK-NOT: aie.logical_tile
93+
}
94+
}
95+
96+
// -----
97+
98+
// Hybrid cascade: TileOp src + LogicalTileOp dst. Dst adapts to TileOp's
99+
// coords via the TileLike interface (would otherwise be silently dropped).
100+
// CHECK-LABEL: @cascade_hybrid_tileop_peer
101+
module @cascade_hybrid_tileop_peer {
102+
aie.device(npu1) {
103+
%src = aie.tile(1, 3)
104+
// CHECK-DAG: %[[D:.*]] = aie.tile(1, 2)
105+
%dst = aie.logical_tile<CoreTile>(?, ?)
106+
aie.cascade_flow(%src, %dst)
107+
aie.core(%src) { aie.end }
108+
aie.core(%dst) { aie.end }
109+
// CHECK-NOT: aie.logical_tile
110+
}
111+
}
112+
113+
// -----
114+
115+
// Mid-chain pin: A→B→C with B pinned at (1,3). Both ends adapt: A west, C south.
116+
// CHECK-LABEL: @cascade_chain_pinned_middle
117+
module @cascade_chain_pinned_middle {
118+
aie.device(npu1) {
119+
// CHECK-DAG: %[[A:.*]] = aie.tile(0, 3)
120+
%a = aie.logical_tile<CoreTile>(?, ?)
121+
// CHECK-DAG: %[[B:.*]] = aie.tile(1, 3)
122+
%b = aie.logical_tile<CoreTile>(1, 3)
123+
// CHECK-DAG: %[[C:.*]] = aie.tile(1, 2)
124+
%c = aie.logical_tile<CoreTile>(?, ?)
125+
aie.cascade_flow(%a, %b)
126+
aie.cascade_flow(%b, %c)
127+
aie.core(%a) { aie.end }
128+
aie.core(%b) { aie.end }
129+
aie.core(%c) { aie.end }
130+
// CHECK-NOT: aie.logical_tile
131+
}
132+
}

test/place-tiles/sequential_placer/test_place_errors.mlir

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,15 @@ module @compute_tiles_exhausted {
182182

183183
// -----
184184

185-
// Cascade chain too long for a single column on npu1_1col (4 core rows).
186-
// Head pinned at row 5, chain extends south to (0,4), (0,3), (0,2), then the
187-
// fifth tile has no valid neighbour (row 1 is non-core and there's no col 1).
185+
// 5-tile chain on npu1_1col (4 core rows): chain exhausts the column.
188186
module @cascade_chain_unsatisfiable {
189187
aie.device(npu1_1col) {
190188
%a = aie.logical_tile<CoreTile>(0, 5)
191189
%b = aie.logical_tile<CoreTile>(?, ?)
192190
%c = aie.logical_tile<CoreTile>(?, ?)
193191
%d = aie.logical_tile<CoreTile>(?, ?)
194192
// CHECK: error: no available compute tiles for placement (cascade adjacency unsatisfiable)
193+
// CHECK: note: cascade source peer placed at (0, 2)
195194
%e = aie.logical_tile<CoreTile>(?, ?)
196195
aie.cascade_flow(%a, %b)
197196
aie.cascade_flow(%b, %c)
@@ -207,14 +206,13 @@ module @cascade_chain_unsatisfiable {
207206

208207
// -----
209208

210-
// Cascade direction that the cascade_flow verifier accepts (cardinal
211-
// adjacent) but the lowering rejects: source must be south or west of dest,
212-
// not north or east. Here %src at (0,4) is north of %dst at (0,5), so the
213-
// placer rejects when %src is placed after %dst.
209+
// Verifier-legal but lowering-illegal direction: lowering requires src to be
210+
// one row North or one column West of dst (rows increase upward).
214211
module @cascade_pinned_wrong_direction {
215212
aie.device(npu1) {
213+
// CHECK: error: tile (0, 5) violates cascade adjacency
214+
// CHECK: note: cascade source peer placed at (0, 4)
216215
%dst = aie.logical_tile<CoreTile>(0, 5)
217-
// CHECK: error: tile (0, 4) violates cascade adjacency with an already-placed peer
218216
%src = aie.logical_tile<CoreTile>(0, 4)
219217
aie.cascade_flow(%src, %dst)
220218
aie.core(%src) { aie.end }
@@ -224,6 +222,37 @@ module @cascade_pinned_wrong_direction {
224222

225223
// -----
226224

225+
// Partial constraint (col=0) is incompatible with pinned src at (1,3).
226+
// Exercises candidate-filter rejection (col-0 tiles exist, none satisfy).
227+
module @cascade_partial_constraint_unsatisfiable {
228+
aie.device(npu1) {
229+
%src = aie.logical_tile<CoreTile>(1, 3)
230+
// CHECK: error: no compute tile available matching constraint (0, ?) and cascade adjacency
231+
// CHECK: note: cascade source peer placed at (1, 3)
232+
%dst = aie.logical_tile<CoreTile>(0, ?)
233+
aie.cascade_flow(%src, %dst)
234+
aie.core(%src) { aie.end }
235+
aie.core(%dst) { aie.end }
236+
}
237+
}
238+
239+
// -----
240+
241+
// Hybrid cascade with conflicting partial constraint on the logical peer.
242+
module @cascade_hybrid_unsatisfiable {
243+
aie.device(npu1) {
244+
%src = aie.tile(1, 3)
245+
// CHECK: error: no compute tile available matching constraint (3, ?) and cascade adjacency
246+
// CHECK: note: cascade source peer placed at (1, 3)
247+
%dst = aie.logical_tile<CoreTile>(3, ?)
248+
aie.cascade_flow(%src, %dst)
249+
aie.core(%src) { aie.end }
250+
aie.core(%dst) { aie.end }
251+
}
252+
}
253+
254+
// -----
255+
227256
// Mixed input/output exceeds capacity
228257
module @mixed_channels_exceed_capacity {
229258
aie.device(npu1) {

0 commit comments

Comments
 (0)