MLIR-AIE 1.4.5.dev
AIESplitFlowVias.cpp
Go to the documentation of this file.
1//===- AIESplitFlowVias.cpp -------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
11
12#include "mlir/Pass/Pass.h"
13
14namespace xilinx::AIE {
15#define GEN_PASS_DEF_AIESPLITFLOWVIAS
16#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
17} // namespace xilinx::AIE
18
19using namespace mlir;
20using namespace xilinx;
21using namespace xilinx::AIE;
22
23namespace {
24
25// True when (srcTile, srcBundle:srcChannel) and (dstTile, dstBundle:dstChannel)
26// are the two ends of a single physical inter-switchbox wire. Such a segment is
27// realized by the wire itself and must not become a routable flow: doing so
28// would double-drive the port that the adjacent local via flow produces.
29static bool isDirectWire(Value srcTile, WireBundle srcBundle, int srcChannel,
30 Value dstTile, WireBundle dstBundle, int dstChannel) {
31 if (srcChannel != dstChannel) {
32 return false;
33 }
34 auto src = srcTile.getDefiningOp<TileOp>();
35 auto dst = dstTile.getDefiningOp<TileOp>();
36 if (!src || !dst) {
37 return false;
38 }
39 int sc = src.colIndex(), sr = src.rowIndex();
40 int dc = dst.colIndex(), dr = dst.rowIndex();
41 if (sc == dc) {
42 if (srcBundle == WireBundle::North && dstBundle == WireBundle::South &&
43 dr == sr + 1) {
44 return true;
45 }
46 if (srcBundle == WireBundle::South && dstBundle == WireBundle::North &&
47 dr == sr - 1) {
48 return true;
49 }
50 }
51 if (sr == dr) {
52 if (srcBundle == WireBundle::East && dstBundle == WireBundle::West &&
53 dc == sc + 1) {
54 return true;
55 }
56 if (srcBundle == WireBundle::West && dstBundle == WireBundle::East &&
57 dc == sc - 1) {
58 return true;
59 }
60 }
61 return false;
62}
63
64static bool isDirectShimMux(Value srcTile, WireBundle srcBundle, int srcChannel,
65 Value dstTile, WireBundle dstBundle,
66 int dstChannel) {
67 auto tile = srcTile.getDefiningOp<TileOp>();
68 if (srcTile != dstTile || !tile || !tile.isShimNOCorPLTile())
69 return false;
70 if (dstBundle == WireBundle::South)
71 return dstChannel == shimMuxChannelFrom({srcBundle, srcChannel});
72 if (srcBundle == WireBundle::South)
73 return srcChannel == shimMuxChannelTo({dstBundle, dstChannel});
74 return false;
75}
76
77static FailureOr<std::pair<Value, WireBundle>> wireEndpoint(DeviceOp device,
78 OpBuilder &builder,
79 Value tileValue,
80 WireBundle bundle) {
81 auto tile = cast<TileOp>(tileValue.getDefiningOp());
82 if (!llvm::is_contained({WireBundle::North, WireBundle::South,
83 WireBundle::East, WireBundle::West},
84 bundle))
85 return std::make_pair(tileValue, bundle);
86
87 auto endpoint = getConnectingWireEndpoint(device.getTargetModel(),
88 tile.getTileID(), Port{bundle, 0});
89 if (!endpoint)
90 return failure();
91 TileID neighbor = endpoint->first;
92
93 for (TileOp candidate : device.getOps<TileOp>())
94 if (candidate.getTileID() == neighbor)
95 return std::pair<Value, WireBundle>(candidate.getResult(),
96 endpoint->second.bundle);
97
98 OpBuilder::InsertionGuard guard(builder);
99 builder.setInsertionPoint(tile);
100 return std::pair<Value, WireBundle>(
101 TileOp::create(builder, tile.getLoc(), neighbor.col, neighbor.row)
102 .getResult(),
103 endpoint->second.bundle);
104}
105
106static void emitPacketFlow(OpBuilder &builder, Location loc, Operation *anchor,
107 Value srcTile, WireBundle srcBundle, int srcChannel,
108 Value dstTile, WireBundle dstBundle, int dstChannel,
109 int packetID, IntegerAttr maskAttr,
110 BoolAttr keepPktHeaderAttr,
111 BoolAttr priorityRouteAttr) {
112 OpBuilder::InsertionGuard guard(builder);
113 builder.setInsertionPoint(anchor);
114 auto pf = PacketFlowOp::create(builder, loc, static_cast<uint8_t>(packetID),
115 keepPktHeaderAttr, priorityRouteAttr);
116 if (maskAttr)
117 pf.setMaskAttr(maskAttr);
118 PacketFlowOp::ensureTerminator(pf.getPorts(), builder, loc);
119 builder.setInsertionPoint(pf.getPorts().front().getTerminator());
120 PacketSourceOp::create(builder, loc, srcTile, srcBundle, srcChannel);
121 PacketDestOp::create(builder, loc, dstTile, dstBundle, dstChannel);
122}
123
124// Emit the routable portion of a segment between two pinned ports, eliding the
125// parts the wires and vias already realize: a degenerate same-port segment or
126// a single inter-switchbox wire. Whatever remains is a gap the router must fill
127// and becomes a flow -- circuit when `packetID` < 0, otherwise a
128// packet_flow(packetID).
129static void emitRoutableSegment(OpBuilder &builder, Location loc,
130 Operation *anchor, Value srcTile,
131 WireBundle srcBundle, int srcChannel,
132 Value dstTile, WireBundle dstBundle,
133 int dstChannel, int packetID,
134 mlir::IntegerAttr maskAttr = {},
135 mlir::BoolAttr keepPktHeaderAttr = {},
136 mlir::BoolAttr priorityRouteAttr = {}) {
137 if (srcTile == dstTile && srcBundle == dstBundle &&
138 srcChannel == dstChannel) {
139 return;
140 }
141 if (isDirectWire(srcTile, srcBundle, srcChannel, dstTile, dstBundle,
142 dstChannel)) {
143 return;
144 }
145 OpBuilder::InsertionGuard guard(builder);
146 builder.setInsertionPoint(anchor);
147 if (packetID < 0) {
148 FlowOp::create(builder, loc, srcTile, srcBundle, srcChannel, dstTile,
149 dstBundle, dstChannel);
150 return;
151 }
152 // Preserve the mask across every section so the router derives the same
153 // packet claim for the gaps and local via flows.
154 emitPacketFlow(builder, loc, anchor, srcTile, srcBundle, srcChannel, dstTile,
155 dstBundle, dstChannel, packetID, maskAttr, keepPktHeaderAttr,
156 priorityRouteAttr);
157}
158
159struct AIESplitFlowViasPass
160 : public xilinx::AIE::impl::AIESplitFlowViasBase<AIESplitFlowViasPass> {
161 void runOnOperation() override {
162 DeviceOp device = getOperation();
163 OpBuilder builder(device.getContext());
164
165 SmallVector<FlowOp> flowsWithVias;
166 for (auto flow : device.getOps<FlowOp>()) {
167 if (!flow.getVias().empty()) {
168 flowsWithVias.push_back(flow);
169 }
170 }
171
172 for (FlowOp flow : flowsWithVias) {
173 ArrayRef<int32_t> ingressBundles =
174 flow.getViaIngressBundlesAttr().asArrayRef();
175 ArrayRef<int32_t> ingressChannels =
176 flow.getViaIngressChannelsAttr().asArrayRef();
177 ArrayRef<int32_t> egressBundles =
178 flow.getViaEgressBundlesAttr().asArrayRef();
179 ArrayRef<int32_t> egressChannels =
180 flow.getViaEgressChannelsAttr().asArrayRef();
181 Location loc = flow.getLoc();
182
183 // Running source of the next segment, starting at the flow's source.
184 Value srcTile = flow.getSource();
185 WireBundle srcBundle = flow.getSourceBundle();
186 int srcChannel = flow.getSourceChannel();
187
188 auto emitSegment = [&](Value dstTile, WireBundle dstBundle,
189 int dstChannel) {
190 emitRoutableSegment(builder, loc, flow, srcTile, srcBundle, srcChannel,
191 dstTile, dstBundle, dstChannel, /*packetID=*/-1);
192 };
193
194 for (size_t i = 0, e = flow.getVias().size(); i < e; i++) {
195 Value viaTile = flow.getVias()[i];
196 auto ingressBundle = static_cast<WireBundle>(ingressBundles[i]);
197 int ingressChannel = ingressChannels[i];
198 auto egressBundle = static_cast<WireBundle>(egressBundles[i]);
199 int egressChannel = egressChannels[i];
200
201 bool foldIngress =
202 isDirectShimMux(srcTile, srcBundle, srcChannel, viaTile,
203 ingressBundle, ingressChannel);
204 if (!foldIngress) {
205 bool directWire =
206 isDirectWire(srcTile, srcBundle, srcChannel, viaTile,
207 ingressBundle, ingressChannel);
208 auto segmentSource = std::make_pair(srcTile, srcBundle);
209 if (i > 0 && !directWire) {
210 auto endpoint = wireEndpoint(device, builder, srcTile, srcBundle);
211 if (failed(endpoint)) {
212 flow.emitOpError("via egress has no neighboring tile");
213 return signalPassFailure();
214 }
215 segmentSource = *endpoint;
216 }
217 auto wireSource =
218 wireEndpoint(device, builder, viaTile, ingressBundle);
219 if (failed(wireSource)) {
220 flow.emitOpError("via ingress has no neighboring tile");
221 return signalPassFailure();
222 }
223 auto [wireTile, wireBundle] = *wireSource;
224 srcTile = segmentSource.first;
225 srcBundle = segmentSource.second;
226 emitSegment(wireTile, wireBundle, ingressChannel);
227 }
228
229 Value localSrcTile = foldIngress ? srcTile : viaTile;
230 WireBundle localSrcBundle = foldIngress ? srcBundle : ingressBundle;
231 int localSrcChannel = foldIngress ? srcChannel : ingressChannel;
232
233 Value nextTile = i + 1 < e ? flow.getVias()[i + 1] : flow.getDest();
234 WireBundle nextBundle =
235 i + 1 < e ? static_cast<WireBundle>(ingressBundles[i + 1])
236 : flow.getDestBundle();
237 int nextChannel =
238 i + 1 < e ? ingressChannels[i + 1] : flow.getDestChannel();
239 bool foldEgress = isDirectShimMux(viaTile, egressBundle, egressChannel,
240 nextTile, nextBundle, nextChannel);
241
242 OpBuilder::InsertionGuard guard(builder);
243 builder.setInsertionPoint(flow);
244 FlowOp::create(builder, loc, localSrcTile, localSrcBundle,
245 localSrcChannel, foldEgress ? nextTile : viaTile,
246 foldEgress ? nextBundle : egressBundle,
247 foldEgress ? nextChannel : egressChannel);
248
249 srcTile = foldEgress ? nextTile : viaTile;
250 srcBundle = foldEgress ? nextBundle : egressBundle;
251 srcChannel = foldEgress ? nextChannel : egressChannel;
252 }
253
254 if (srcTile != flow.getDest() || srcBundle != flow.getDestBundle() ||
255 srcChannel != flow.getDestChannel()) {
256 auto endpoint = wireEndpoint(device, builder, srcTile, srcBundle);
257 if (failed(endpoint)) {
258 flow.emitOpError("via egress has no neighboring tile");
259 return signalPassFailure();
260 }
261 std::tie(srcTile, srcBundle) = *endpoint;
262 }
263 emitSegment(flow.getDest(), flow.getDestBundle(), flow.getDestChannel());
264 flow.erase();
265 }
266
267 // Packet sections pin their route with vias too. Each via becomes a local
268 // packet flow so the router can plan its rules, master sets, and arbiters
269 // with the other packet flows.
270 SmallVector<PacketFlowOp> pktFlowsWithVias;
271 for (auto pf : device.getOps<PacketFlowOp>()) {
272 if (!pf.getVias().empty()) {
273 pktFlowsWithVias.push_back(pf);
274 }
275 }
276
277 for (PacketFlowOp pf : pktFlowsWithVias) {
278 Location loc = pf.getLoc();
279 int id = pf.IDInt();
280 ArrayRef<int32_t> inB = pf.getViaIngressBundlesAttr().asArrayRef();
281 ArrayRef<int32_t> inC = pf.getViaIngressChannelsAttr().asArrayRef();
282 ArrayRef<int32_t> egB = pf.getViaEgressBundlesAttr().asArrayRef();
283 ArrayRef<int32_t> egC = pf.getViaEgressChannelsAttr().asArrayRef();
284
285 // A section has exactly one source and one dest; the gaps between the
286 // pinned vias (and before the first / after the last) are routed.
287 PacketSourceOp source;
288 PacketDestOp dest;
289 for (Operation &op : pf.getPorts().front()) {
290 if (auto s = dyn_cast<PacketSourceOp>(op)) {
291 source = s;
292 } else if (auto d = dyn_cast<PacketDestOp>(op)) {
293 dest = d;
294 }
295 }
296 Value srcTile = source.getTile();
297 WireBundle srcBundle = source.getBundle();
298 int srcChannel = source.getChannel();
299 auto emitSegment = [&](Value dstTile, WireBundle dstBundle,
300 int dstChannel, BoolAttr keepPktHeader = {}) {
301 emitRoutableSegment(builder, loc, pf, srcTile, srcBundle, srcChannel,
302 dstTile, dstBundle, dstChannel, id,
303 pf.getMaskAttr(), keepPktHeader,
304 pf.getPriorityRouteAttr());
305 };
306
307 for (size_t i = 0, e = pf.getVias().size(); i < e; i++) {
308 Value viaTile = pf.getVias()[i];
309 auto ingress = static_cast<WireBundle>(inB[i]);
310 int ingressChannel = inC[i];
311 auto egress = static_cast<WireBundle>(egB[i]);
312 int egressChannel = egC[i];
313
314 bool foldIngress = isDirectShimMux(srcTile, srcBundle, srcChannel,
315 viaTile, ingress, ingressChannel);
316 if (!foldIngress) {
317 bool directWire = isDirectWire(srcTile, srcBundle, srcChannel,
318 viaTile, ingress, ingressChannel);
319 auto segmentSource = std::make_pair(srcTile, srcBundle);
320 if (i > 0 && !directWire) {
321 auto endpoint = wireEndpoint(device, builder, srcTile, srcBundle);
322 if (failed(endpoint)) {
323 pf.emitOpError("via egress has no neighboring tile");
324 return signalPassFailure();
325 }
326 segmentSource = *endpoint;
327 }
328 auto wireSource = wireEndpoint(device, builder, viaTile, ingress);
329 if (failed(wireSource)) {
330 pf.emitOpError("via ingress has no neighboring tile");
331 return signalPassFailure();
332 }
333 auto [wireTile, wireBundle] = *wireSource;
334 srcTile = segmentSource.first;
335 srcBundle = segmentSource.second;
336 emitSegment(wireTile, wireBundle, ingressChannel);
337 }
338
339 Value localSrcTile = foldIngress ? srcTile : viaTile;
340 WireBundle localSrcBundle = foldIngress ? srcBundle : ingress;
341 int localSrcChannel = foldIngress ? srcChannel : ingressChannel;
342
343 Value nextTile = i + 1 < e ? pf.getVias()[i + 1] : dest.getTile();
344 WireBundle nextBundle =
345 i + 1 < e ? static_cast<WireBundle>(inB[i + 1]) : dest.getBundle();
346 int nextChannel = i + 1 < e ? inC[i + 1] : dest.getChannel();
347 bool foldEgress = isDirectShimMux(viaTile, egress, egressChannel,
348 nextTile, nextBundle, nextChannel);
349
350 BoolAttr keepPktHeader;
351 if ((foldEgress && nextTile == dest.getTile() &&
352 nextBundle == dest.getBundle() &&
353 nextChannel == dest.getChannel()) ||
354 (viaTile == dest.getTile() && egress == dest.getBundle() &&
355 egressChannel == dest.getChannel()))
356 keepPktHeader = pf.getKeepPktHeaderAttr();
357 emitPacketFlow(
358 builder, loc, pf, localSrcTile, localSrcBundle, localSrcChannel,
359 foldEgress ? nextTile : viaTile, foldEgress ? nextBundle : egress,
360 foldEgress ? nextChannel : egressChannel, id, pf.getMaskAttr(),
361 keepPktHeader, pf.getPriorityRouteAttr());
362
363 srcTile = foldEgress ? nextTile : viaTile;
364 srcBundle = foldEgress ? nextBundle : egress;
365 srcChannel = foldEgress ? nextChannel : egressChannel;
366 }
367
368 if (srcTile != dest.getTile() || srcBundle != dest.getBundle() ||
369 srcChannel != dest.getChannel()) {
370 auto endpoint = wireEndpoint(device, builder, srcTile, srcBundle);
371 if (failed(endpoint)) {
372 pf.emitOpError("via egress has no neighboring tile");
373 return signalPassFailure();
374 }
375 std::tie(srcTile, srcBundle) = *endpoint;
376 }
377 emitSegment(dest.getTile(), dest.getBundle(), dest.getChannel(),
378 pf.getKeepPktHeaderAttr());
379 pf.erase();
380 }
381 }
382};
383
384} // namespace
385
386std::unique_ptr<OperationPass<DeviceOp>> AIE::createAIESplitFlowViasPass() {
387 return std::make_unique<AIESplitFlowViasPass>();
388}
int shimMuxChannelTo(Port dst)
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::unique_ptr< mlir::OperationPass< DeviceOp > > createAIESplitFlowViasPass()
int shimMuxChannelFrom(Port src)
struct Port { WireBundle bundle; int channel; bool operator==(const Port &rhs) const { return std::tie(bundle, channel)==std::tie(rhs.bundle, rhs.channel); } bool operator!=(const Port &rhs) const { return !(*this==rhs);} bool operator<(const Port &rhs) const { return std::tie(bundle, channel)< std::tie(rhs.bundle, rhs.channel); } friend std::ostream &operator<<(std::ostream &os, const Port &port) { os<< "("; os<< stringifyWireBundle(port.bundle).str(); os<< ": "<< std::to_string(port.channel)<< ")"; return os; } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Port &port) { os<< to_string(port); return os; } } Port
Definition AIEDialect.h:265
std::optional< std::pair< TileID, Port > > getConnectingWireEndpoint(const AIETargetModel &targetModel, TileID tile, Port port)