MLIR-AIE 1.4.5.dev
AIEFindFlows.cpp
Go to the documentation of this file.
1//===- AIEFindFlows.cpp -----------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2019-2022 Xilinx, Inc.
4// Copyright (C) 2022-2026 Advanced Micro Devices, Inc.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
11
12#include "mlir/IR/IRMapping.h"
13#include "mlir/Pass/Pass.h"
14#include <map>
15#include <optional>
16#include <set>
17#include <tuple>
18#include <utility>
19#include <vector>
20
21#include "llvm/ADT/DenseSet.h"
22#include "llvm/ADT/STLExtras.h"
23#include "llvm/ADT/SmallBitVector.h"
24
25namespace xilinx::AIE {
26#define GEN_PASS_DEF_AIEFINDFLOWS
27#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
28} // namespace xilinx::AIE
29
30#define DEBUG_TYPE "aie-find-flows"
31
32using namespace mlir;
33using namespace xilinx;
34using namespace xilinx::AIE;
35
36using MaskValue = struct MaskValue {
37 int mask;
38 int value;
39};
40
42 Operation *op;
43 Port port;
44};
45
47 Port port;
48 MaskValue mv;
49 // The interconnect ops that implement this hop (a circuit ConnectOp, or a
50 // packet PacketRuleOp + MasterSetOp + AMSelOp). Erased after lifting.
51 llvm::SmallVector<Operation *, 3> ops;
52};
53
54// One intermediate stop of a flow: the tile whose switchbox the stream passes
55// through, and the ingress/egress ports it uses there.
56struct Via {
57 Value tile;
60};
61
63 PortConnection portConnection;
64 MaskValue mv;
65 llvm::SmallVector<Via, 4> vias;
66 // Interconnect ops traversed on the way to this endpoint.
67 llvm::SmallVector<Operation *, 8> usedOps;
68 bool isPacket = false;
69};
70
72 Operation *op;
75 llvm::SmallVector<Operation *, 3> ops;
76};
77
79 DeviceOp &device;
80 bool splitFanouts = false;
81 mutable llvm::DenseSet<std::pair<Operation *, int>> fanoutIngresses;
82 mutable llvm::DenseSet<std::pair<Operation *, int>> fanoutEgressSeeds;
83 mutable llvm::SmallVector<CircuitFanoutSeed, 8> circuitFanoutSeeds;
84 mutable std::set<std::tuple<Operation *, int, int>> packetMergeEgresses;
85 mutable llvm::DenseSet<Operation *> packetMergeOps;
86
87 static int encodePort(Port p) {
88 return static_cast<int>(p.bundle) * 64 + p.channel;
89 }
90
91public:
92 ConnectivityAnalysis(DeviceOp &d) : device(d) {}
93
94 // Precompute the fan-out and packet merge boundaries between linear route
95 // sections.
97 splitFanouts = true;
98 auto scan = [&](Operation *sw, Region &connections) {
99 llvm::SmallVector<Port, 8> sources;
100 Block &b = connections.front();
101 for (auto connectOp : b.getOps<ConnectOp>()) {
102 if (!llvm::is_contained(sources, connectOp.sourcePort())) {
103 sources.push_back(connectOp.sourcePort());
104 }
105 }
106 for (auto rulesOp : b.getOps<PacketRulesOp>()) {
107 if (!llvm::is_contained(sources, rulesOp.sourcePort())) {
108 sources.push_back(rulesOp.sourcePort());
109 }
110 }
111 // Fan-out: a source that drives more than one output.
112 for (Port p : sources) {
113 if (getConnectionsThroughSwitchbox(connections, p).size() > 1) {
114 fanoutIngresses.insert({sw, encodePort(p)});
115 }
116 }
117
118 const int idMask = (1 << llvm::Log2_32_Ceil(
119 device.getTargetModel().getMaxPacketId() + 1)) -
120 1;
121 using MergeKey = std::pair<int, int>;
122 std::map<MergeKey, SmallVector<Port, 4>> mergeIngresses;
123 std::map<MergeKey, SmallVector<Operation *, 8>> mergeOps;
124 for (PacketRulesOp rules : b.getOps<PacketRulesOp>()) {
125 for (int id = 0; id <= idMask; ++id) {
126 PacketRuleOp selected;
127 for (PacketRuleOp rule :
128 rules.getRules().front().getOps<PacketRuleOp>()) {
129 if ((id & rule.maskInt()) == rule.valueInt()) {
130 selected = rule;
131 break;
132 }
133 }
134 if (!selected)
135 continue;
136 for (MasterSetOp masterSet : b.getOps<MasterSetOp>()) {
137 if (!llvm::is_contained(masterSet.getAmsels(), selected.getAmsel()))
138 continue;
139 MergeKey key{encodePort(masterSet.destPort()), id};
140 if (!llvm::is_contained(mergeIngresses[key], rules.sourcePort()))
141 mergeIngresses[key].push_back(rules.sourcePort());
142 for (Operation *op :
143 {selected.getOperation(), masterSet.getOperation(),
144 selected.getAmsel().getDefiningOp()})
145 if (!llvm::is_contained(mergeOps[key], op))
146 mergeOps[key].push_back(op);
147 }
148 }
149 }
150 for (const auto &[key, ingresses] : mergeIngresses) {
151 if (ingresses.size() < 2)
152 continue;
153 auto [egress, id] = key;
154 packetMergeEgresses.insert({sw, egress, id});
155 packetMergeOps.insert_range(mergeOps[key]);
156 }
157 };
158 for (auto switchOp : device.getOps<SwitchboxOp>()) {
159 scan(switchOp, switchOp.getConnections());
160 }
161 }
162
163 // Consumed and cleared by the pass.
164 llvm::DenseSet<std::pair<Operation *, int>> &getFanoutEgressSeeds() {
165 return fanoutEgressSeeds;
166 }
167 llvm::SmallVector<CircuitFanoutSeed, 8> &getCircuitFanoutSeeds() {
168 return circuitFanoutSeeds;
169 }
170 const llvm::DenseSet<Operation *> &getPacketMergeOps() const {
171 return packetMergeOps;
172 }
173 std::set<std::tuple<Operation *, int, int>> &getPacketMergeEgresses() {
174 return packetMergeEgresses;
175 }
176 static Port decodePort(int e) {
177 return {static_cast<WireBundle>(e / 64), e % 64};
178 }
179
180private:
181 std::optional<PortConnection>
182 getConnectionThroughWire(Operation *op, Port masterPort) const {
183 LLVM_DEBUG(llvm::dbgs() << "Wire:" << *op << " "
184 << stringifyWireBundle(masterPort.bundle) << " "
185 << masterPort.channel << "\n");
186 for (auto wireOp : device.getOps<WireOp>()) {
187 if (wireOp.getSource().getDefiningOp() == op &&
188 wireOp.getSourceBundle() == masterPort.bundle) {
189 Operation *other = wireOp.getDest().getDefiningOp();
190 Port otherPort = {wireOp.getDestBundle(), masterPort.channel};
191 LLVM_DEBUG(llvm::dbgs() << "Connects To:" << *other << " "
192 << stringifyWireBundle(otherPort.bundle) << " "
193 << otherPort.channel << "\n");
194
195 return PortConnection{other, otherPort};
196 }
197 if (wireOp.getDest().getDefiningOp() == op &&
198 wireOp.getDestBundle() == masterPort.bundle) {
199 Operation *other = wireOp.getSource().getDefiningOp();
200 Port otherPort = {wireOp.getSourceBundle(), masterPort.channel};
201 LLVM_DEBUG(llvm::dbgs() << "Connects To:" << *other << " "
202 << stringifyWireBundle(otherPort.bundle) << " "
203 << otherPort.channel << "\n");
204 return PortConnection{other, otherPort};
205 }
206 }
207 LLVM_DEBUG(llvm::dbgs() << "*** Missing Wire!\n");
208 return std::nullopt;
209 }
210
211 std::vector<PortMaskValue>
212 getConnectionsThroughSwitchbox(Region &r, Port sourcePort) const {
213 LLVM_DEBUG(llvm::dbgs() << "Switchbox:\n");
214 Block &b = r.front();
215 std::vector<PortMaskValue> portSet;
216 for (auto connectOp : b.getOps<ConnectOp>()) {
217 if (connectOp.sourcePort() == sourcePort) {
218 MaskValue maskValue = {0, 0};
219 portSet.push_back({connectOp.destPort(), maskValue, {connectOp}});
220 LLVM_DEBUG(llvm::dbgs()
221 << "To:" << stringifyWireBundle(connectOp.destPort().bundle)
222 << " " << connectOp.destPort().channel << "\n");
223 }
224 }
225 for (auto connectOp : b.getOps<PacketRulesOp>()) {
226 if (connectOp.sourcePort() == sourcePort) {
227 LLVM_DEBUG(llvm::dbgs()
228 << "Packet From: "
229 << stringifyWireBundle(connectOp.sourcePort().bundle) << " "
230 << sourcePort.channel << "\n");
231 // In rule order, which is slot order.
232 for (auto ruleOp : connectOp.getRules().front().getOps<PacketRuleOp>())
233 for (auto masterSetOp : b.getOps<MasterSetOp>())
234 for (Value amsel : masterSetOp.getAmsels()) {
235 if (ruleOp.getAmsel() == amsel) {
236 LLVM_DEBUG(llvm::dbgs()
237 << "To:"
238 << stringifyWireBundle(masterSetOp.destPort().bundle)
239 << " " << masterSetOp.destPort().channel << "\n");
240 MaskValue maskValue = {ruleOp.maskInt(), ruleOp.valueInt()};
241 portSet.push_back(
242 {masterSetOp.destPort(),
243 maskValue,
244 {ruleOp, masterSetOp, amsel.getDefiningOp()}});
245 }
246 }
247 }
248 }
249 return portSet;
250 }
251
252 std::vector<PacketConnection> maskSwitchboxConnections(
253 Operation *switchOp, Port ingressPort, ArrayRef<Via> currentVias,
254 ArrayRef<Operation *> currentUsedOps,
255 const std::vector<PortMaskValue> &nextPortMaskValues, MaskValue maskValue,
256 bool currentIsPacket, bool keepPartialFlows,
257 std::vector<PacketConnection> &partialEndpoints) const {
258 std::vector<PacketConnection> worklist;
259 // A slave port hands a packet to the first rule that matches its id, so
260 // once a rule matches every id that can arrive, later rules get none.
261 Operation *takenBy = nullptr;
262 // Only switchbox hops become vias; the shim-mux is not stream-switch
263 // configuration and is regenerated by routing.
264 Value viaTile;
265 if (auto sb = dyn_cast<SwitchboxOp>(switchOp)) {
266 viaTile = sb.getTile();
267 }
268 for (auto &nextPortMaskValue : nextPortMaskValues) {
269 Port nextPort = nextPortMaskValue.port;
270 MaskValue nextMaskValue = nextPortMaskValue.mv;
271 Operation *rule = nextPortMaskValue.ops.front();
272 if (!isa<PacketRuleOp>(rule)) {
273 rule = nullptr;
274 }
275 if (takenBy && rule != takenBy) {
276 continue;
277 }
278 int maskConflicts = nextMaskValue.mask & maskValue.mask;
279 LLVM_DEBUG(llvm::dbgs() << "Mask: " << maskValue.mask << " "
280 << maskValue.value << "\n");
281 LLVM_DEBUG(llvm::dbgs() << "NextMask: " << nextMaskValue.mask << " "
282 << nextMaskValue.value << "\n");
283 LLVM_DEBUG(llvm::dbgs() << maskConflicts << "\n");
284
285 if ((maskConflicts & nextMaskValue.value) !=
286 (maskConflicts & maskValue.value)) {
287 // Incoming packets cannot match this rule. Skip it.
288 continue;
289 }
290 if (rule && (nextMaskValue.mask & ~maskValue.mask) == 0) {
291 takenBy = rule;
292 }
293 MaskValue newMaskValue = {maskValue.mask | nextMaskValue.mask,
294 maskValue.value |
295 (nextMaskValue.mask & nextMaskValue.value)};
296 bool reachesMerge = false;
297 int maxPacketId = device.getTargetModel().getMaxPacketId();
298 for (int id = 0; id <= maxPacketId; ++id) {
299 if ((id & newMaskValue.mask) != newMaskValue.value)
300 continue;
301 if (packetMergeEgresses.count({switchOp, encodePort(nextPort), id})) {
302 reachesMerge = true;
303 break;
304 }
305 }
306 if (reachesMerge) {
307 partialEndpoints.push_back({{switchOp, ingressPort},
308 newMaskValue,
309 llvm::to_vector(currentVias),
310 llvm::to_vector(currentUsedOps),
311 currentIsPacket || rule != nullptr});
312 continue;
313 }
314 SmallVector<Via, 4> newVias(currentVias.begin(), currentVias.end());
315 if (viaTile) {
316 newVias.push_back({viaTile, ingressPort, nextPort});
317 }
318 SmallVector<Operation *, 8> newUsedOps(currentUsedOps.begin(),
319 currentUsedOps.end());
320 newUsedOps.append(nextPortMaskValue.ops.begin(),
321 nextPortMaskValue.ops.end());
322 auto nextConnection = getConnectionThroughWire(switchOp, nextPort);
323
324 if (!nextConnection) {
325 // The switchbox drives nextPort but no wire continues from it (an array
326 // edge or an off-fabric consumer). Under partial recovery this output
327 // port is itself the flow's endpoint.
328 if (keepPartialFlows) {
329 partialEndpoints.push_back({{switchOp, nextPort},
330 newMaskValue,
331 newVias,
332 newUsedOps,
333 currentIsPacket || rule != nullptr});
334 }
335 continue;
336 }
337
338 worklist.push_back({*nextConnection, newMaskValue, newVias, newUsedOps,
339 currentIsPacket || rule != nullptr});
340 }
341 return worklist;
342 }
343
344public:
345 // Follow the single upstream wire out of an interconnect input port.
346 std::optional<PortConnection> upstreamOf(Operation *op, Port port) const {
347 return getConnectionThroughWire(op, port);
348 }
349
350 // Whether `port` is driven (is the destination of a connect or masterset)
351 // inside the given interconnect op.
352 bool drivesPort(Operation *op, Port port) const {
353 Region *r = nullptr;
354 if (auto sb = dyn_cast<SwitchboxOp>(op)) {
355 r = &sb.getConnections();
356 } else if (auto sm = dyn_cast<ShimMuxOp>(op)) {
357 r = &sm.getConnections();
358 }
359 if (!r) {
360 return false;
361 }
362 Block &b = r->front();
363 for (auto connectOp : b.getOps<ConnectOp>()) {
364 if (connectOp.destPort() == port) {
365 return true;
366 }
367 }
368 for (auto masterSetOp : b.getOps<MasterSetOp>()) {
369 if (masterSetOp.destPort() == port) {
370 return true;
371 }
372 }
373 return false;
374 }
375
376 std::vector<PacketConnection> traverse(std::vector<PacketConnection> worklist,
377 bool keepPartialFlows) const {
378 std::vector<PacketConnection> connectedTiles;
379 while (!worklist.empty()) {
380 PacketConnection t = worklist.back();
381 worklist.pop_back();
382 Operation *other = t.portConnection.op;
383 Port otherPort = t.portConnection.port;
384 MaskValue maskValue = t.mv;
385 if (other && other->hasTrait<IsFlowEndPoint>()) {
386 // If we got to a tile, then add it to the result.
387 connectedTiles.push_back(t);
388 continue;
389 }
390 Region *connections = nullptr;
391 if (auto switchOp = dyn_cast_or_null<SwitchboxOp>(other)) {
392 connections = &switchOp.getConnections();
393 } else if (auto switchOp = dyn_cast_or_null<ShimMuxOp>(other)) {
394 connections = &switchOp.getConnections();
395 }
396 if (!connections) {
397 LLVM_DEBUG(llvm::dbgs()
398 << "*** Connection Terminated at unknown operation: ");
399 LLVM_DEBUG(other->dump());
400 continue;
401 }
402 // A fan-out port ends the current linear section: the flow terminates at
403 // this input, the fan-out switchbox stays explicit (its ops are not
404 // lifted), and every output it drives becomes a new section source.
405 if (splitFanouts &&
406 fanoutIngresses.count({other, encodePort(otherPort)})) {
407 connectedTiles.push_back(t);
408 for (PortMaskValue &pmv :
409 getConnectionsThroughSwitchbox(*connections, otherPort)) {
410 if (isa<ConnectOp>(pmv.ops.front())) {
411 auto sameSeed = [&](const CircuitFanoutSeed &seed) {
412 return seed.op == other && seed.ingress == otherPort &&
413 seed.egress == pmv.port;
414 };
415 if (!llvm::any_of(circuitFanoutSeeds, sameSeed)) {
416 circuitFanoutSeeds.push_back(
417 {other, otherPort, pmv.port, pmv.ops});
418 }
419 } else {
420 fanoutEgressSeeds.insert({other, encodePort(pmv.port)});
421 }
422 }
423 continue;
424 }
425 std::vector<PortMaskValue> nextPortMaskValues =
426 getConnectionsThroughSwitchbox(*connections, otherPort);
427 std::vector<PacketConnection> partialEndpoints;
428 std::vector<PacketConnection> newWorkList = maskSwitchboxConnections(
429 other, otherPort, t.vias, t.usedOps, nextPortMaskValues, maskValue,
430 t.isPacket, keepPartialFlows, partialEndpoints);
431 worklist.insert(worklist.end(), newWorkList.begin(), newWorkList.end());
432 connectedTiles.insert(connectedTiles.end(), partialEndpoints.begin(),
433 partialEndpoints.end());
434 if (!nextPortMaskValues.empty() && newWorkList.empty() &&
435 partialEndpoints.empty()) {
436 // No rule matched some incoming packet. This is likely a
437 // configuration error.
438 LLVM_DEBUG(llvm::dbgs() << "No rule matched incoming packet here: ");
439 LLVM_DEBUG(other->dump());
440 }
441 }
442 return connectedTiles;
443 }
444
445 // Get the tiles connected to the given tile, starting from the given
446 // output port of the tile. This is 1:N relationship because each
447 // switchbox can broadcast.
448 // `claim` selects the packet ids followed; the default follows them all.
449 std::vector<PacketConnection>
450 getConnectedTiles(TileOp tileOp, Port port, bool keepPartialFlows,
451 MaskValue claim = {0, 0}) const {
452 LLVM_DEBUG(llvm::dbgs()
453 << "getConnectedTile(" << stringifyWireBundle(port.bundle) << " "
454 << port.channel << ")");
455 LLVM_DEBUG(tileOp.dump());
456 // Traverse from the tile to its connected switchbox.
457 auto t = getConnectionThroughWire(tileOp.getOperation(), port);
458 // If there is no wire to traverse, then just return no connection
459 if (!t) {
460 return {};
461 }
462 return traverse({PacketConnection{*t, claim, {}, {}, false}},
463 keepPartialFlows);
464 }
465
466 std::vector<PacketConnection>
467 getConnectedTilesFromInput(Operation *switchOp, Port inputPort,
468 bool keepPartialFlows,
469 MaskValue claim = {0, 0}) const {
470 return traverse(
472 PortConnection{switchOp, inputPort}, claim, {}, {}, false}},
473 keepPartialFlows);
474 }
475
476 // Get the endpoints reached by a stream leaving the given output port of a
477 // fan-out interconnect: cross the outgoing wire and traverse the next linear
478 // section.
479 std::vector<PacketConnection>
480 getConnectedTilesFromEgress(Operation *switchOp, Port egressPort,
481 bool keepPartialFlows,
482 MaskValue claim = {0, 0}) const {
483 auto t = getConnectionThroughWire(switchOp, egressPort);
484 if (!t) {
485 return {};
486 }
487 return traverse({PacketConnection{*t, claim, {}, {}, true}},
488 keepPartialFlows);
489 }
490
491 std::vector<PacketConnection>
493 bool keepPartialFlows) const {
494 auto connection = getConnectionThroughWire(seed.op, seed.egress);
495 if (!connection) {
496 return {};
497 }
498 Value tile = cast<SwitchboxOp>(seed.op).getTile();
499 SmallVector<Operation *, 8> usedOps(seed.ops.begin(), seed.ops.end());
500 PacketConnection branch{*connection,
501 {0, 0},
502 {{tile, seed.ingress, seed.egress}},
503 std::move(usedOps),
504 false};
505 return traverse({std::move(branch)}, keepPartialFlows);
506 }
507};
508
509// Identifies a flow by its two endpoints -- tile coordinates plus port -- and
510// the packet claim it carries, using kCircuitFlow for circuit-switched flows.
511// Coordinates rather than SSA values, so flows written against different
512// aie.tile ops for the same tile still compare equal.
513static constexpr int kCircuitFlow = -1;
514using FlowKey = std::tuple<int, int, int, int, int, int, int, int, int, int>;
515using FlowKeySet = std::set<FlowKey>;
516
517// Returns nullopt when either endpoint's coordinates are unknown, in which
518// case the caller cannot tell the flow apart from any other and must not
519// dedupe it away.
520static std::optional<FlowKey> tryGetFlowKey(Value srcTileValue, Port srcPort,
521 Value destTileValue, Port destPort,
522 int packetID, int packetMask) {
523 auto coords = [](Value v) -> std::optional<std::pair<int, int>> {
524 auto tile = llvm::dyn_cast_or_null<TileLike>(v.getDefiningOp());
525 if (!tile)
526 return std::nullopt;
527 std::optional<int> col = tile.tryGetCol();
528 std::optional<int> row = tile.tryGetRow();
529 if (!col || !row)
530 return std::nullopt;
531 return std::make_pair(*col, *row);
532 };
533 std::optional<std::pair<int, int>> src = coords(srcTileValue);
534 std::optional<std::pair<int, int>> dest = coords(destTileValue);
535 if (!src || !dest)
536 return std::nullopt;
537 return FlowKey{src->first,
538 src->second,
539 static_cast<int>(srcPort.bundle),
540 srcPort.channel,
541 dest->first,
542 dest->second,
543 static_cast<int>(destPort.bundle),
544 destPort.channel,
545 packetID,
546 packetMask};
547}
548
549// aie.flow names tiles, so an endpoint that is an element of a tile, such as an
550// aie.shim_dma, has to resolve to the tile that owns it.
551static Value resolveEndpointTile(Operation *op) {
552 if (isa<TileOp>(op)) {
553 return op->getResult(0);
554 }
555 if (auto element = dyn_cast<TileElement>(op)) {
556 return element.getTile();
557 }
558 return nullptr;
559}
560
561// The interconnect ops the lifted flows make redundant, and those a route left
562// materialized still needs. An op can be on both, such as a shim-mux connect
563// that user flows and the control overlay share, and then it stays. Each lifted
564// packet flow is listed with the ops it traversed.
565struct LiftedOps {
566 llvm::DenseSet<Operation *> consumed;
567 llvm::DenseSet<Operation *> kept;
568 std::vector<std::pair<PacketFlowOp, SmallVector<Operation *, 8>>> packetFlows;
569};
570
571static void emitFlows(OpBuilder &rewriter, Location loc, Value srcTile,
572 WireBundle srcBundle, int srcChannel,
573 const std::vector<PacketConnection> &endpoints,
574 bool emitVias, bool dropIntraTile, int idMask,
575 FlowKeySet &seen, LiftedOps &lifted) {
576 AIEDialect::IsCtrlPktOverlayAttrHelper overlay(rewriter.getContext());
577 AIEDialect::PriorityRouteAttrHelper prioritizedRule(rewriter.getContext());
578 for (const PacketConnection &c : endpoints) {
579 Operation *destOp = c.portConnection.op;
580 Port destPort = c.portConnection.port;
581 MaskValue maskValue = c.mv;
582 Value destTile = resolveEndpointTile(destOp);
583 if (!destTile) {
584 continue;
585 }
586 // The routing of a priority_route flow carries the is_ctrl_pkt_overlay
587 // marker. The control overlay's flows, which start or end at a TileControl
588 // port, stay materialized, since a lifted flow cannot rebuild the overlay.
589 bool marked = llvm::any_of(c.usedOps, [&](Operation *op) {
590 Operation *rulesParent =
591 isa_and_nonnull<PacketRuleOp>(op) ? op->getParentOp() : nullptr;
592 return (op && overlay.isAttrPresent(op)) ||
593 (rulesParent && overlay.isAttrPresent(rulesParent));
594 });
595 if (marked && (srcBundle == WireBundle::TileControl ||
596 destPort.bundle == WireBundle::TileControl)) {
597 for (Operation *op : c.usedOps) {
598 if (op) {
599 lifted.kept.insert(op);
600 }
601 }
602 continue;
603 }
604 // A path realized only by shim-mux connections crosses no switchbox, so it
605 // has no via to pin it and re-routing cannot reproduce it.
606 if (emitVias && c.vias.empty()) {
607 continue;
608 }
609 // A packet endpoint becomes a logical packet flow, and the pass erases
610 // the lowered physical configuration.
611 bool isPacket = c.isPacket;
612 // The same endpoint is reached twice when a broadcast splits and rejoins,
613 // and the tile, switchbox and shim-mux seeds can each reach one route.
614 // DeviceOp::verify rejects a flow declared twice.
615 std::optional<FlowKey> key =
616 tryGetFlowKey(srcTile, {srcBundle, srcChannel}, destTile, destPort,
617 isPacket ? maskValue.value : kCircuitFlow,
618 isPacket ? maskValue.mask : 0);
619 if (key && !seen.insert(*key).second) {
620 continue;
621 }
622 if (isPacket) {
623 for (Operation *op : c.usedOps) {
624 if (op) {
625 lifted.consumed.insert(op);
626 }
627 }
628 // The lowering stores keep_pkt_header on the master set that drives the
629 // destination. A priority_route flow marks that master set too, and the
630 // rule it starts by at its source. Either can be shared, the master set
631 // with other sources and the rule with the source's other ids, but a
632 // flow that has both is a priority_route one or comes from the same
633 // source as one, which routes the same.
634 BoolAttr keepPktHeader, priorityRoute;
635 bool markedDest = false, markedSource = false;
636 for (Operation *op : c.usedOps) {
637 if (auto ms = dyn_cast_or_null<MasterSetOp>(op)) {
638 if (ms.getDestBundle() == destPort.bundle &&
639 ms.getDestChannel() == destPort.channel) {
640 keepPktHeader = ms.getKeepPktHeaderAttr();
641 markedDest = overlay.isAttrPresent(ms);
642 }
643 }
644 if (isa_and_nonnull<PacketRuleOp>(op) &&
645 prioritizedRule.isAttrPresent(op)) {
646 markedSource = true;
647 }
648 }
649 if (markedDest && markedSource) {
650 priorityRoute = rewriter.getBoolAttr(true);
651 }
652 // The rules along the path accept every id that agrees with value on the
653 // bits mask selects. Which of those a running design sends is not
654 // decidable here, so state the pair and let routing rebuild the same
655 // rules.
656 //
657 // A full-width mask selects exactly one id; in flow IR we express this
658 // by leaving off the mask. The router may merge such flows.
659 IntegerAttr mask;
660 if (maskValue.mask != idMask) {
661 mask = rewriter.getI8IntegerAttr(maskValue.mask);
662 }
663 MLIRContext *ctx = rewriter.getContext();
664 SmallVector<Value> viaTiles;
665 SmallVector<int32_t> ingressBundles, ingressChannels, egressBundles,
666 egressChannels;
667 if (emitVias) {
668 for (const Via &via : c.vias) {
669 viaTiles.push_back(via.tile);
670 ingressBundles.push_back(static_cast<int32_t>(via.ingress.bundle));
671 ingressChannels.push_back(via.ingress.channel);
672 egressBundles.push_back(static_cast<int32_t>(via.egress.bundle));
673 egressChannels.push_back(via.egress.channel);
674 }
675 }
676 auto ib = viaTiles.empty() ? nullptr
677 : DenseI32ArrayAttr::get(ctx, ingressBundles);
678 auto ic = viaTiles.empty() ? nullptr
679 : DenseI32ArrayAttr::get(ctx, ingressChannels);
680 auto eb = viaTiles.empty() ? nullptr
681 : DenseI32ArrayAttr::get(ctx, egressBundles);
682 auto ec = viaTiles.empty() ? nullptr
683 : DenseI32ArrayAttr::get(ctx, egressChannels);
684 auto flowOp = PacketFlowOp::create(
685 rewriter, loc, rewriter.getI8IntegerAttr(maskValue.value), mask,
686 keepPktHeader, priorityRoute, viaTiles, ib, ic, eb, ec);
687 PacketFlowOp::ensureTerminator(flowOp.getPorts(), rewriter, loc);
688 OpBuilder::InsertPoint ip = rewriter.saveInsertionPoint();
689 rewriter.setInsertionPoint(flowOp.getPorts().front().getTerminator());
690 PacketSourceOp::create(rewriter, loc, srcTile, srcBundle, srcChannel);
691 PacketDestOp::create(rewriter, loc, destTile, destPort.bundle,
692 destPort.channel);
693 rewriter.restoreInsertionPoint(ip);
694 lifted.packetFlows.emplace_back(flowOp, c.usedOps);
695 continue;
696 }
697 // No switchbox configuration stands between the endpoints, so there is no
698 // flow to lift.
699 if (c.usedOps.empty()) {
700 continue;
701 }
702 // A fabric entry or fan-out output that ends on its own tile is an orphan
703 // connection, such as an undriven shim-mux write path.
704 if (dropIntraTile && destTile == srcTile && isa<TileOp>(destOp)) {
705 continue;
706 }
707 for (Operation *op : c.usedOps) {
708 if (op) {
709 lifted.consumed.insert(op);
710 }
711 }
712 MLIRContext *ctx = rewriter.getContext();
713 SmallVector<Value> viaTiles;
714 SmallVector<int32_t> ingressBundles, ingressChannels, egressBundles,
715 egressChannels;
716 if (emitVias) {
717 for (const Via &via : c.vias) {
718 viaTiles.push_back(via.tile);
719 ingressBundles.push_back(static_cast<int32_t>(via.ingress.bundle));
720 ingressChannels.push_back(via.ingress.channel);
721 egressBundles.push_back(static_cast<int32_t>(via.egress.bundle));
722 egressChannels.push_back(via.egress.channel);
723 }
724 }
725 auto ib = viaTiles.empty() ? nullptr
726 : DenseI32ArrayAttr::get(ctx, ingressBundles);
727 auto ic = viaTiles.empty() ? nullptr
728 : DenseI32ArrayAttr::get(ctx, ingressChannels);
729 auto eb =
730 viaTiles.empty() ? nullptr : DenseI32ArrayAttr::get(ctx, egressBundles);
731 auto ec = viaTiles.empty() ? nullptr
732 : DenseI32ArrayAttr::get(ctx, egressChannels);
733 FlowOp::create(rewriter, loc, srcTile, srcBundle, srcChannel, destTile,
734 destPort.bundle, destPort.channel, viaTiles, ib, ic, eb, ec);
735 }
736}
737
738// Where rules on a port overlap, which ids each one takes depends on the rules
739// before it, and a traversal of cubes cannot follow that. So each packet id is
740// traced on its own, and the ids that reach the same destinations are
741// described by disjoint cubes. Two flows from one source then claim either the
742// same ids or none in common.
743static std::vector<PacketConnection> exactPacketEndpoints(
744 std::vector<PacketConnection> endpoints, int idMask,
745 llvm::function_ref<std::vector<PacketConnection>(MaskValue)> trace,
746 bool emitVias) {
747 auto isPacket = [](const PacketConnection &c) { return c.isPacket; };
748 if (llvm::none_of(endpoints, isPacket)) {
749 return endpoints;
750 }
751 std::vector<PacketConnection> result;
752 llvm::copy_if(endpoints, std::back_inserter(result),
753 [&](const PacketConnection &c) { return !isPacket(c); });
754
755 auto samePath = [](const PacketConnection &a, const PacketConnection &b) {
756 if (a.portConnection.op != b.portConnection.op ||
757 a.portConnection.port != b.portConnection.port ||
758 a.vias.size() != b.vias.size()) {
759 return false;
760 }
761 return llvm::equal(a.vias, b.vias, [](const Via &x, const Via &y) {
762 return x.tile == y.tile && x.ingress == y.ingress && x.egress == y.egress;
763 });
764 };
765 auto sameDest = [](const PacketConnection &a, const PacketConnection &b) {
766 return a.portConnection.op == b.portConnection.op &&
767 a.portConnection.port == b.portConnection.port;
768 };
769 SmallVector<PacketConnection> paths;
770 auto pathIndex = [&](const PacketConnection &connection) {
771 auto *it = llvm::find_if(paths, [&](const PacketConnection &path) {
772 return emitVias ? samePath(connection, path) : sameDest(connection, path);
773 });
774 if (it != paths.end()) {
775 return static_cast<unsigned>(it - paths.begin());
776 }
777 paths.push_back(connection);
778 return static_cast<unsigned>(paths.size() - 1);
779 };
780 for (const PacketConnection &c : endpoints) {
781 if (isPacket(c)) {
782 pathIndex(c);
783 }
784 }
785
786 // The destinations each id reaches, and the ops it passes to reach each.
787 std::map<SmallVector<unsigned>, SmallVector<int>> idsByDests;
788 std::map<std::pair<int, unsigned>, SmallVector<Operation *, 8>> opsTo;
789 for (int id = 0; id <= idMask; ++id) {
790 SmallVector<unsigned> reached;
791 for (const PacketConnection &c : trace({idMask, id})) {
792 if (!isPacket(c)) {
793 continue;
794 }
795 unsigned path = pathIndex(c);
796 if (!llvm::is_contained(reached, path)) {
797 reached.push_back(path);
798 }
799 SmallVector<Operation *, 8> &ops = opsTo[{id, path}];
800 for (Operation *op : c.usedOps) {
801 if (!llvm::is_contained(ops, op)) {
802 ops.push_back(op);
803 }
804 }
805 }
806 if (!reached.empty()) {
807 llvm::sort(reached);
808 idsByDests[reached].push_back(id);
809 }
810 }
811
812 // Largest cube first, each inside the ids not yet described.
813 auto disjointCubes = [&](ArrayRef<int> ids) {
814 llvm::SmallBitVector left(idMask + 1);
815 for (int id : ids) {
816 left.set(id);
817 }
818 SmallVector<MaskValue> cubes;
819 while (left.any()) {
820 MaskValue best = {idMask, left.find_first()};
821 int bestSize = 1;
822 for (int mask = 0; mask <= idMask; ++mask) {
823 int size = 1 << llvm::popcount(static_cast<unsigned>(idMask & ~mask));
824 if (size <= bestSize) {
825 continue;
826 }
827 for (int value = 0; value <= idMask && size > bestSize; ++value) {
828 if ((value & mask) != value) {
829 continue;
830 }
831 bool inside = true;
832 for (int id = value; id <= idMask && inside; ++id) {
833 inside = (id & mask) != value || left.test(id);
834 }
835 if (inside) {
836 best = {mask, value};
837 bestSize = size;
838 }
839 }
840 }
841 for (int id = 0; id <= idMask; ++id) {
842 if ((id & best.mask) == best.value) {
843 left.reset(id);
844 }
845 }
846 cubes.push_back(best);
847 }
848 return cubes;
849 };
850
851 // In id order, which is the order of each group's lowest id.
852 SmallVector<std::pair<const SmallVector<unsigned> *, ArrayRef<int>>> groups;
853 for (const auto &[reached, ids] : idsByDests) {
854 groups.push_back({&reached, ids});
855 }
856 llvm::sort(groups, [](const auto &a, const auto &b) {
857 return a.second.front() < b.second.front();
858 });
859 for (const auto &[reached, ids] : groups) {
860 SmallVector<MaskValue> cubes = disjointCubes(ids);
861 for (unsigned path : *reached) {
862 for (MaskValue cube : cubes) {
863 SmallVector<Operation *, 8> ops;
864 for (int id : ids) {
865 if ((id & cube.mask) != cube.value) {
866 continue;
867 }
868 for (Operation *op : opsTo[{id, path}]) {
869 if (!llvm::is_contained(ops, op)) {
870 ops.push_back(op);
871 }
872 }
873 }
874 result.push_back(
875 {paths[path].portConnection, cube, paths[path].vias, ops, true});
876 }
877 }
878 }
879 return result;
880}
881
882static void findFlowsFrom(TileOp op, ConnectivityAnalysis &analysis,
883 OpBuilder &rewriter, bool keepPartialFlows,
884 bool emitVias, int idMask, FlowKeySet &seen,
885 LiftedOps &lifted) {
886 Operation *Op = op.getOperation();
887 rewriter.setInsertionPoint(Op->getBlock()->getTerminator());
888
889 std::vector bundles = {WireBundle::Core, WireBundle::DMA};
890 for (WireBundle bundle : bundles) {
891 LLVM_DEBUG(llvm::dbgs()
892 << op << stringifyWireBundle(bundle) << " has "
893 << op.getNumSourceConnections(bundle) << " Connections\n");
894 for (size_t i = 0; i < op.getNumSourceConnections(bundle); i++) {
895 Port port = {bundle, (int)i};
896 std::vector<PacketConnection> tiles = exactPacketEndpoints(
897 analysis.getConnectedTiles(op, port, keepPartialFlows), idMask,
898 [&](MaskValue claim) {
899 return analysis.getConnectedTiles(op, port, keepPartialFlows,
900 claim);
901 },
902 emitVias);
903 LLVM_DEBUG(llvm::dbgs() << tiles.size() << " Flows\n");
904 emitFlows(rewriter, Op->getLoc(), Op->getResult(0), bundle, (int)i, tiles,
905 emitVias, /*dropIntraTile=*/false, idMask, seen, lifted);
906 }
907 }
908}
909
910// Lift flows whose source is not a core/DMA. Seed from every switchbox /
911// shim-mux input port that drives a connection but is not itself driven by an
912// upstream interconnect connection. Ports driven by a core/DMA tile are
913// already covered by findFlowsFrom; ports driven by an upstream interconnect
914// are covered by that interconnect's own source, so both are skipped here to
915// avoid emitting a flow twice.
916static void findFlowsFromInterconnect(Operation *switchOp,
917 ConnectivityAnalysis &analysis,
918 OpBuilder &rewriter,
919 bool keepPartialFlows, bool emitVias,
920 int idMask, FlowKeySet &seen,
921 LiftedOps &lifted) {
922 Region *connections = nullptr;
923 if (auto sb = dyn_cast<SwitchboxOp>(switchOp)) {
924 connections = &sb.getConnections();
925 } else if (auto sm = dyn_cast<ShimMuxOp>(switchOp)) {
926 connections = &sm.getConnections();
927 }
928 if (!connections) {
929 return;
930 }
931 rewriter.setInsertionPoint(switchOp->getBlock()->getTerminator());
932
933 // Distinct source ports of this interconnect's connections and packet rules.
934 llvm::SmallVector<Port, 8> sourcePorts;
935 auto addPort = [&](Port p) {
936 if (!llvm::is_contained(sourcePorts, p)) {
937 sourcePorts.push_back(p);
938 }
939 };
940 Block &b = connections->front();
941 for (auto connectOp : b.getOps<ConnectOp>()) {
942 addPort(connectOp.sourcePort());
943 }
944 for (auto rulesOp : b.getOps<PacketRulesOp>()) {
945 addPort(rulesOp.sourcePort());
946 }
947
948 for (Port p : sourcePorts) {
949 Value srcTile;
950 WireBundle srcBundle = p.bundle;
951 int srcChannel = p.channel;
952 if (auto up = analysis.upstreamOf(switchOp, p)) {
953 Operation *upOp = up->op;
954 Port upPort = up->port;
955 if (upOp && upOp->hasTrait<IsFlowEndPoint>()) {
956 // Driven by a tile. Core/DMA sources are handled by findFlowsFrom;
957 // recover the remaining tile-source bundles (e.g. PLIO) from here.
958 if (upPort.bundle == WireBundle::Core ||
959 upPort.bundle == WireBundle::DMA) {
960 continue;
961 }
962 srcTile = resolveEndpointTile(upOp);
963 srcBundle = upPort.bundle;
964 srcChannel = upPort.channel;
965 } else if (analysis.drivesPort(upOp, upPort)) {
966 // Mid-chain: an upstream interconnect drives this port.
967 continue;
968 } else {
969 // Wire exists but nothing drives it: this input is a fabric entry.
970 srcTile = resolveEndpointTile(switchOp);
971 }
972 } else {
973 // No upstream wire: this input is a fabric entry (array edge).
974 srcTile = resolveEndpointTile(switchOp);
975 }
976 if (!srcTile) {
977 continue;
978 }
979 std::vector<PacketConnection> tiles = exactPacketEndpoints(
980 analysis.getConnectedTilesFromInput(switchOp, p, keepPartialFlows),
981 idMask,
982 [&](MaskValue claim) {
983 return analysis.getConnectedTilesFromInput(switchOp, p,
984 keepPartialFlows, claim);
985 },
986 emitVias);
987 emitFlows(rewriter, switchOp->getLoc(), srcTile, srcBundle, srcChannel,
988 tiles, emitVias, /*dropIntraTile=*/true, idMask, seen, lifted);
989 }
990}
991
993 : public xilinx::AIE::impl::AIEFindFlowsBase<AIEFindFlowsPass> {
994 void getDependentDialects(DialectRegistry &registry) const override {
995 registry.insert<func::FuncDialect>();
996 registry.insert<AIEDialect>();
997 }
998
999 // An interconnect whose region holds nothing but its terminator carries no
1000 // configuration and can be dropped.
1001 static bool isEmptyInterconnect(Region &connections) {
1002 Block &b = connections.front();
1003 return b.getOps<ConnectOp>().empty() && b.getOps<PacketRulesOp>().empty() &&
1004 b.getOps<MasterSetOp>().empty() && b.getOps<AMSelOp>().empty();
1005 }
1006
1007 void runOnOperation() override {
1008
1009 DeviceOp d = getOperation();
1010 ConnectivityAnalysis analysis(d);
1011 d.getTargetModel().validate();
1012 if (clEmitVias) {
1013 analysis.enableSectionSplitting();
1014 }
1015
1016 // Widest mask the target's packet ids can carry.
1017 const int idMask =
1018 (1 << llvm::Log2_32_Ceil(d.getTargetModel().getMaxPacketId() + 1)) - 1;
1019
1020 LiftedOps lifted;
1021 lifted.kept.insert_range(analysis.getPacketMergeOps());
1022 FlowKeySet seen;
1023 OpBuilder builder = OpBuilder::atBlockTerminator(d.getBody());
1024 for (auto tile : d.getOps<TileOp>()) {
1025 findFlowsFrom(tile, analysis, builder, clKeepPartialFlows, clEmitVias,
1026 idMask, seen, lifted);
1027 }
1028 // Lift flows whose source is not a core/DMA (transit fills, packet routing
1029 // steered at runtime, PLIO/edge entries) directly from the interconnect.
1030 if (clKeepPartialFlows) {
1031 for (auto switchOp : d.getOps<SwitchboxOp>()) {
1032 findFlowsFromInterconnect(switchOp, analysis, builder,
1033 clKeepPartialFlows, clEmitVias, idMask, seen,
1034 lifted);
1035 }
1036 for (auto shimMuxOp : d.getOps<ShimMuxOp>()) {
1037 findFlowsFromInterconnect(shimMuxOp, analysis, builder,
1038 clKeepPartialFlows, clEmitVias, idMask, seen,
1039 lifted);
1040 }
1041 }
1042
1043 if (clEmitVias) {
1044 builder.setInsertionPoint(d.getBody()->getTerminator());
1045 for (size_t i = 0; i < analysis.getCircuitFanoutSeeds().size(); ++i) {
1046 CircuitFanoutSeed seed = analysis.getCircuitFanoutSeeds()[i];
1047 Value srcTile = resolveEndpointTile(seed.op);
1048 if (!srcTile) {
1049 continue;
1050 }
1051 std::vector<PacketConnection> tiles =
1053 clKeepPartialFlows);
1054 emitFlows(builder, seed.op->getLoc(), srcTile, seed.ingress.bundle,
1055 seed.ingress.channel, tiles, clEmitVias,
1056 /*dropIntraTile=*/false, idMask, seen, lifted);
1057 }
1058 llvm::DenseSet<std::pair<Operation *, int>> seeded;
1059 bool progress = true;
1060 while (progress) {
1061 progress = false;
1062 llvm::SmallVector<std::pair<Operation *, int>> snapshot(
1063 analysis.getFanoutEgressSeeds().begin(),
1064 analysis.getFanoutEgressSeeds().end());
1065 for (auto &seed : snapshot) {
1066 if (!seeded.insert(seed).second) {
1067 continue;
1068 }
1069 progress = true;
1070 Operation *switchOp = seed.first;
1071 Port egress = ConnectivityAnalysis::decodePort(seed.second);
1072 Value srcTile = resolveEndpointTile(switchOp);
1073 if (!srcTile) {
1074 continue;
1075 }
1076 std::vector<PacketConnection> tiles =
1077 analysis.getConnectedTilesFromEgress(switchOp, egress,
1078 clKeepPartialFlows);
1079 emitFlows(builder, switchOp->getLoc(), srcTile, egress.bundle,
1080 egress.channel, tiles, clEmitVias, /*dropIntraTile=*/true,
1081 idMask, seen, lifted);
1082 }
1083 }
1084
1085 for (const auto &[switchOp, encodedEgress, id] :
1086 analysis.getPacketMergeEgresses()) {
1087 Port egress = ConnectivityAnalysis::decodePort(encodedEgress);
1088 Value srcTile = resolveEndpointTile(switchOp);
1089 if (!srcTile)
1090 continue;
1091 std::vector<PacketConnection> tiles =
1093 switchOp, egress, clKeepPartialFlows, {idMask, id});
1094 emitFlows(builder, switchOp->getLoc(), srcTile, egress.bundle,
1095 egress.channel, tiles, clEmitVias,
1096 /*dropIntraTile=*/true, idMask, seen, lifted);
1097 }
1098 }
1099
1100 if (!clRemoveLifted) {
1101 return;
1102 }
1103
1104 // Every recovered flow makes the interconnect ops it traversed redundant;
1105 // drop exactly those, leaving any configuration that could not be lifted
1106 // (e.g. an unreachable connect) in place.
1107 // A packet flow that shares a rule with a route left materialized stays
1108 // materialized too: the rule claims its ids, so a rerouted copy of the flow
1109 // could not claim them again.
1110 for (bool changed = true; changed;) {
1111 changed = false;
1112 for (auto &[flow, usedOps] : lifted.packetFlows) {
1113 if (!flow || llvm::none_of(usedOps, [&](Operation *op) {
1114 return isa_and_nonnull<PacketRuleOp>(op) &&
1115 lifted.kept.contains(op);
1116 })) {
1117 continue;
1118 }
1119 for (Operation *op : usedOps) {
1120 if (op) {
1121 lifted.kept.insert(op);
1122 }
1123 }
1124 flow.erase();
1125 flow = nullptr;
1126 changed = true;
1127 }
1128 }
1129 for (Operation *op : lifted.consumed) {
1130 if (isa<ConnectOp, PacketRuleOp, MasterSetOp>(op) &&
1131 !lifted.kept.contains(op)) {
1132 op->erase();
1133 }
1134 }
1135 auto cleanupInterconnect = [](Region &connections) {
1136 for (auto amselOp :
1137 llvm::make_early_inc_range(connections.getOps<AMSelOp>())) {
1138 if (amselOp.use_empty()) {
1139 amselOp.erase();
1140 }
1141 }
1142 for (auto rulesOp :
1143 llvm::make_early_inc_range(connections.getOps<PacketRulesOp>())) {
1144 if (rulesOp.getRules().front().getOps<PacketRuleOp>().empty()) {
1145 rulesOp.erase();
1146 }
1147 }
1148 };
1149 for (auto switchOp : d.getOps<SwitchboxOp>()) {
1150 cleanupInterconnect(switchOp.getConnections());
1151 }
1152 for (auto shimMuxOp : d.getOps<ShimMuxOp>()) {
1153 cleanupInterconnect(shimMuxOp.getConnections());
1154 }
1155
1156 // Routing regenerates a wire only for the flows it routes, so a wire that
1157 // reaches an interconnect still holding configuration has to stay, or that
1158 // configuration ends up unreachable.
1159 llvm::DenseSet<Operation *> retained;
1160 auto retainNonEmpty = [&](Operation *op, Region &connections) {
1161 if (!isEmptyInterconnect(connections)) {
1162 retained.insert(op);
1163 }
1164 };
1165 for (auto switchOp : d.getOps<SwitchboxOp>()) {
1166 retainNonEmpty(switchOp, switchOp.getConnections());
1167 }
1168 for (auto shimMuxOp : d.getOps<ShimMuxOp>()) {
1169 retainNonEmpty(shimMuxOp, shimMuxOp.getConnections());
1170 }
1171
1172 llvm::DenseSet<Operation *> wiredToRetained;
1173 for (auto wireOp : llvm::make_early_inc_range(d.getOps<WireOp>())) {
1174 Operation *src = wireOp.getSource().getDefiningOp();
1175 Operation *dst = wireOp.getDest().getDefiningOp();
1176 if (!retained.contains(src) && !retained.contains(dst)) {
1177 wireOp.erase();
1178 continue;
1179 }
1180 wiredToRetained.insert(src);
1181 wiredToRetained.insert(dst);
1182 }
1183
1184 // A surviving wire keeps its operands alive, so an empty interconnect one
1185 // of them names outlives its configuration.
1186 auto eraseIfUnused = [&](Operation *op, Region &connections) {
1187 return isEmptyInterconnect(connections) && !wiredToRetained.contains(op);
1188 };
1189 for (auto switchOp : llvm::make_early_inc_range(d.getOps<SwitchboxOp>())) {
1190 if (eraseIfUnused(switchOp, switchOp.getConnections())) {
1191 switchOp.erase();
1192 }
1193 }
1194 for (auto shimMuxOp : llvm::make_early_inc_range(d.getOps<ShimMuxOp>())) {
1195 if (eraseIfUnused(shimMuxOp, shimMuxOp.getConnections())) {
1196 shimMuxOp.erase();
1197 }
1198 }
1199 }
1200};
1201
1202std::unique_ptr<OperationPass<DeviceOp>> AIE::createAIEFindFlowsPass() {
1203 return std::make_unique<AIEFindFlowsPass>();
1204}
std::tuple< int, int, int, int, int, int, int, int, int, int > FlowKey
struct PortMaskValue { Port port; MaskValue mv; llvm::SmallVector< Operation *, 3 > ops;} PortMaskValue
struct MaskValue { int mask; int value;} MaskValue
std::set< FlowKey > FlowKeySet
struct PacketConnection { PortConnection portConnection; MaskValue mv; llvm::SmallVector< Via, 4 > vias; llvm::SmallVector< Operation *, 8 > usedOps; bool isPacket=false;} PacketConnection
struct PortConnection { Operation *op; Port port;} PortConnection
std::vector< PacketConnection > getConnectedTilesFromEgress(Operation *switchOp, Port egressPort, bool keepPartialFlows, MaskValue claim={0, 0}) const
std::set< std::tuple< Operation *, int, int > > & getPacketMergeEgresses()
std::optional< PortConnection > upstreamOf(Operation *op, Port port) const
static Port decodePort(int e)
std::vector< PacketConnection > getConnectedTilesFromCircuitFanout(const CircuitFanoutSeed &seed, bool keepPartialFlows) const
std::vector< PacketConnection > getConnectedTilesFromInput(Operation *switchOp, Port inputPort, bool keepPartialFlows, MaskValue claim={0, 0}) const
const llvm::DenseSet< Operation * > & getPacketMergeOps() const
llvm::SmallVector< CircuitFanoutSeed, 8 > & getCircuitFanoutSeeds()
llvm::DenseSet< std::pair< Operation *, int > > & getFanoutEgressSeeds()
bool drivesPort(Operation *op, Port port) const
std::vector< PacketConnection > traverse(std::vector< PacketConnection > worklist, bool keepPartialFlows) const
std::vector< PacketConnection > getConnectedTiles(TileOp tileOp, Port port, bool keepPartialFlows, MaskValue claim={0, 0}) const
ConnectivityAnalysis(DeviceOp &d)
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEFindFlowsPass()
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
void getDependentDialects(DialectRegistry &registry) const override
void runOnOperation() override
static bool isEmptyInterconnect(Region &connections)
llvm::SmallVector< Operation *, 3 > ops
llvm::DenseSet< Operation * > kept
std::vector< std::pair< PacketFlowOp, SmallVector< Operation *, 8 > > > packetFlows
llvm::DenseSet< Operation * > consumed
Port egress
Value tile
Port ingress