MLIR-AIE
AIECreatePathFindFlows.cpp
Go to the documentation of this file.
1//===- AIECreatePathfindFlows.cpp -------------------------------*- C++ -*-===//
2//
3// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7// (c) Copyright 2021 Xilinx Inc.
8//
9//===----------------------------------------------------------------------===//
10
14
15#include "mlir/IR/IRMapping.h"
16#include "mlir/IR/PatternMatch.h"
17#include "mlir/Pass/Pass.h"
18#include "mlir/Tools/mlir-translate/MlirTranslateMain.h"
19#include "mlir/Transforms/DialectConversion.h"
20#include "llvm/Support/Debug.h"
21
22using namespace mlir;
23using namespace xilinx;
24using namespace xilinx::AIE;
25
26#define DEBUG_TYPE "aie-create-pathfinder-flows"
27
28namespace {
29// allocates channels between switchboxes ( but does not assign them)
30// instantiates shim-muxes AND allocates channels ( no need to rip these up in )
31struct ConvertFlowsToInterconnect : OpConversionPattern<FlowOp> {
32 using OpConversionPattern::OpConversionPattern;
33 DeviceOp &device;
34 DynamicTileAnalysis &analyzer;
35 ConvertFlowsToInterconnect(MLIRContext *context, DeviceOp &d,
36 DynamicTileAnalysis &a, PatternBenefit benefit = 1)
37 : OpConversionPattern(context, benefit), device(d), analyzer(a) {}
38
39 void addConnection(ConversionPatternRewriter &rewriter,
40 // could be a shim-mux or a switchbox.
41 Interconnect op, FlowOp flowOp, WireBundle inBundle,
42 int inIndex, WireBundle outBundle, int outIndex) const {
43
44 Region &r = op.getConnections();
45 Block &b = r.front();
46 auto point = rewriter.saveInsertionPoint();
47 rewriter.setInsertionPoint(b.getTerminator());
48
49 rewriter.create<ConnectOp>(rewriter.getUnknownLoc(), inBundle, inIndex,
50 outBundle, outIndex);
51
52 rewriter.restoreInsertionPoint(point);
53
54 LLVM_DEBUG(llvm::dbgs()
55 << "\t\taddConnection() (" << op.colIndex() << ","
56 << op.rowIndex() << ") " << stringifyWireBundle(inBundle)
57 << inIndex << " -> " << stringifyWireBundle(outBundle)
58 << outIndex << "\n");
59 }
60
61 mlir::LogicalResult
62 matchAndRewrite(FlowOp flowOp, OpAdaptor adaptor,
63 ConversionPatternRewriter &rewriter) const override {
64 Operation *Op = flowOp.getOperation();
65
66 auto srcTile = cast<TileOp>(flowOp.getSource().getDefiningOp());
67 TileID srcCoords = {srcTile.colIndex(), srcTile.rowIndex()};
68 auto srcBundle = flowOp.getSourceBundle();
69 auto srcChannel = flowOp.getSourceChannel();
70 Port srcPort = {srcBundle, srcChannel};
71
72#ifndef NDEBUG
73 auto dstTile = cast<TileOp>(flowOp.getDest().getDefiningOp());
74 TileID dstCoords = {dstTile.colIndex(), dstTile.rowIndex()};
75 auto dstBundle = flowOp.getDestBundle();
76 auto dstChannel = flowOp.getDestChannel();
77 LLVM_DEBUG(llvm::dbgs()
78 << "\n\t---Begin rewrite() for flowOp: (" << srcCoords.col
79 << ", " << srcCoords.row << ")" << stringifyWireBundle(srcBundle)
80 << srcChannel << " -> (" << dstCoords.col << ", "
81 << dstCoords.row << ")" << stringifyWireBundle(dstBundle)
82 << dstChannel << "\n\t");
83#endif
84
85 // if the flow (aka "net") for this FlowOp hasn't been processed yet,
86 // add all switchbox connections to implement the flow
87 TileID srcSbId = {srcCoords.col, srcCoords.row};
88 PathEndPoint srcPoint = {srcSbId, srcPort};
89 if (analyzer.processedFlows[srcPoint]) {
90 LLVM_DEBUG(llvm::dbgs() << "Flow already processed!\n");
91 rewriter.eraseOp(Op);
92 return failure();
93 }
94 // std::map<TileID, SwitchSetting>
95 SwitchSettings settings = analyzer.flowSolutions[srcPoint];
96 // add connections for all the Switchboxes in SwitchSettings
97 for (const auto &[tileId, setting] : settings) {
98 int col = tileId.col;
99 int row = tileId.row;
100 SwitchboxOp swOp = analyzer.getSwitchbox(rewriter, col, row);
101 int shimCh = srcChannel;
102 bool isShim = analyzer.getTile(rewriter, col, row).isShimNOCorPLTile();
103
104 // TODO: must reserve N3, N7, S2, S3 for DMA connections
105 if (isShim && tileId == srcSbId) {
106
107 // shim DMAs at start of flows
108 if (srcBundle == WireBundle::DMA)
109 // must be either DMA0 -> N3 or DMA1 -> N7
110 shimCh = srcChannel == 0 ? 3 : 7;
111 else if (srcBundle == WireBundle::NOC)
112 // must be NOC0/NOC1 -> N2/N3 or NOC2/NOC3 -> N6/N7
113 shimCh = srcChannel >= 2 ? srcChannel + 4 : srcChannel + 2;
114 else if (srcBundle == WireBundle::PLIO)
115 shimCh = srcChannel;
116
117 ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, col);
118 addConnection(rewriter, cast<Interconnect>(shimMuxOp.getOperation()),
119 flowOp, srcBundle, srcChannel, WireBundle::North, shimCh);
120 }
121 assert(setting.srcs.size() == setting.dsts.size());
122 for (size_t i = 0; i < setting.srcs.size(); i++) {
123 Port src = setting.srcs[i];
124 Port dest = setting.dsts[i];
125
126 // handle special shim connectivity
127 if (isShim && tileId == srcSbId) {
128 addConnection(rewriter, cast<Interconnect>(swOp.getOperation()),
129 flowOp, WireBundle::South, shimCh, dest.bundle,
130 dest.channel);
131 } else if (isShim && (dest.bundle == WireBundle::DMA ||
132 dest.bundle == WireBundle::PLIO ||
133 dest.bundle == WireBundle::NOC)) {
134
135 // shim DMAs at end of flows
136 if (dest.bundle == WireBundle::DMA)
137 // must be either N2 -> DMA0 or N3 -> DMA1
138 shimCh = dest.channel == 0 ? 2 : 3;
139 else if (dest.bundle == WireBundle::NOC)
140 // must be either N2/3/4/5 -> NOC0/1/2/3
141 shimCh = dest.channel + 2;
142 else if (dest.bundle == WireBundle::PLIO)
143 shimCh = dest.channel;
144
145 ShimMuxOp shimMuxOp = analyzer.getShimMux(rewriter, col);
146 addConnection(rewriter, cast<Interconnect>(shimMuxOp.getOperation()),
147 flowOp, WireBundle::North, shimCh, dest.bundle,
148 dest.channel);
149 addConnection(rewriter, cast<Interconnect>(swOp.getOperation()),
150 flowOp, src.bundle, src.channel, WireBundle::South,
151 shimCh);
152 } else {
153 // otherwise, regular switchbox connection
154 addConnection(rewriter, cast<Interconnect>(swOp.getOperation()),
155 flowOp, src.bundle, src.channel, dest.bundle,
156 dest.channel);
157 }
158 }
159
160 LLVM_DEBUG(llvm::dbgs() << tileId << ": " << setting << " | "
161 << "\n");
162 }
163
164 LLVM_DEBUG(llvm::dbgs()
165 << "\n\t\tFinished adding ConnectOps to implement flowOp.\n");
166
167 analyzer.processedFlows[srcPoint] = true;
168 rewriter.eraseOp(Op);
169 return success();
170 }
171};
172
173} // namespace
174
175namespace xilinx::AIE {
176
178 // Apply rewrite rule to switchboxes to add assignments to every 'connect'
179 // operation inside
180 ConversionTarget target(getContext());
181 target.addLegalOp<TileOp>();
182 target.addLegalOp<ConnectOp>();
183 target.addLegalOp<SwitchboxOp>();
184 target.addLegalOp<ShimMuxOp>();
185 target.addLegalOp<EndOp>();
186
187 RewritePatternSet patterns(&getContext());
188 patterns.insert<ConvertFlowsToInterconnect>(d.getContext(), d, analyzer);
189 if (failed(applyPartialConversion(d, target, std::move(patterns))))
190 return signalPassFailure();
191}
192
193template <typename MyOp>
196 using OpAdaptor = typename MyOp::Adaptor;
197
198 explicit AIEOpRemoval(MLIRContext *context, PatternBenefit benefit = 1)
199 : OpConversionPattern<MyOp>(context, benefit) {}
200
201 LogicalResult
202 matchAndRewrite(MyOp op, OpAdaptor adaptor,
203 ConversionPatternRewriter &rewriter) const override {
204 Operation *Op = op.getOperation();
205
206 rewriter.eraseOp(Op);
207 return success();
208 }
209};
210
212 WireBundle currDestBundle,
213 int currDestChannel, TileID finalTile,
214 WireBundle finalDestBundle,
215 int finalDestChannel) {
216
217 if ((currTile == finalTile) && (currDestBundle == finalDestBundle) &&
218 (currDestChannel == finalDestChannel)) {
219 return true;
220 }
221
222 WireBundle neighbourSourceBundle;
223 TileID neighbourTile;
224 if (currDestBundle == WireBundle::East) {
225 neighbourSourceBundle = WireBundle::West;
226 neighbourTile = {currTile.col + 1, currTile.row};
227 } else if (currDestBundle == WireBundle::West) {
228 neighbourSourceBundle = WireBundle::East;
229 neighbourTile = {currTile.col - 1, currTile.row};
230 } else if (currDestBundle == WireBundle::North) {
231 neighbourSourceBundle = WireBundle::South;
232 neighbourTile = {currTile.col, currTile.row + 1};
233 } else if (currDestBundle == WireBundle::South) {
234 neighbourSourceBundle = WireBundle::North;
235 neighbourTile = {currTile.col, currTile.row - 1};
236 } else {
237 return false;
238 }
239
240 int neighbourSourceChannel = currDestChannel;
241 for (const auto &[sbNode, setting] : settings) {
242 TileID tile = {sbNode.col, sbNode.row};
243 if (tile == neighbourTile) {
244 assert(setting.srcs.size() == setting.dsts.size());
245 for (size_t i = 0; i < setting.srcs.size(); i++) {
246 Port src = setting.srcs[i];
247 Port dest = setting.dsts[i];
248 if ((src.bundle == neighbourSourceBundle) &&
249 (src.channel == neighbourSourceChannel)) {
250 if (findPathToDest(settings, neighbourTile, dest.bundle, dest.channel,
251 finalTile, finalDestBundle, finalDestChannel)) {
252 return true;
253 }
254 }
255 }
256 }
257 }
258
259 return false;
260}
261
262void AIEPathfinderPass::runOnPacketFlow(DeviceOp device, OpBuilder &builder) {
263
264 ConversionTarget target(getContext());
265
266 // Map from a port and flowID to
267 DenseMap<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>> packetFlows;
268 DenseMap<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>> ctrlPacketFlows;
269 SmallVector<std::pair<PhysPort, int>, 4> slavePorts;
270 DenseMap<std::pair<PhysPort, int>, int> slaveAMSels;
271 // Flag to keep packet header at packet flow destination
272 DenseMap<PhysPort, BoolAttr> keepPktHeaderAttr;
273 // Map from tileID and master ports to flags labelling control packet flows
274 DenseMap<std::pair<PhysPort, int>, bool> ctrlPktFlows;
275
276 for (auto tileOp : device.getOps<TileOp>()) {
277 int col = tileOp.colIndex();
278 int row = tileOp.rowIndex();
279 tiles[{col, row}] = tileOp;
280 }
281
282 // The logical model of all the switchboxes.
283 DenseMap<TileID, SmallVector<std::pair<Connect, int>, 8>> switchboxes;
284 for (PacketFlowOp pktFlowOp : device.getOps<PacketFlowOp>()) {
285 Region &r = pktFlowOp.getPorts();
286 Block &b = r.front();
287 int flowID = pktFlowOp.IDInt();
288 Port srcPort, destPort;
289 TileOp srcTile, destTile;
290 TileID srcCoords, destCoords;
291
292 for (Operation &Op : b.getOperations()) {
293 if (auto pktSource = dyn_cast<PacketSourceOp>(Op)) {
294 srcTile = dyn_cast<TileOp>(pktSource.getTile().getDefiningOp());
295 srcPort = pktSource.port();
296 srcCoords = {srcTile.colIndex(), srcTile.rowIndex()};
297 } else if (auto pktDest = dyn_cast<PacketDestOp>(Op)) {
298 destTile = dyn_cast<TileOp>(pktDest.getTile().getDefiningOp());
299 destPort = pktDest.port();
300 destCoords = {destTile.colIndex(), destTile.rowIndex()};
301 // Assign "keep_pkt_header flag"
302 auto keep = pktFlowOp.getKeepPktHeader();
303 keepPktHeaderAttr[{destTile, destPort}] =
304 keep ? BoolAttr::get(Op.getContext(), *keep) : nullptr;
305
306 TileID srcSB = {srcCoords.col, srcCoords.row};
307 if (PathEndPoint srcPoint = {srcSB, srcPort};
308 !analyzer.processedFlows[srcPoint]) {
309 SwitchSettings settings = analyzer.flowSolutions[srcPoint];
310 // add connections for all the Switchboxes in SwitchSettings
311 for (const auto &[curr, setting] : settings) {
312 assert(setting.srcs.size() == setting.dsts.size());
313 TileID currTile = {curr.col, curr.row};
314 for (size_t i = 0; i < setting.srcs.size(); i++) {
315 Port src = setting.srcs[i];
316 Port dest = setting.dsts[i];
317 // reject false broadcast
318 if (!findPathToDest(settings, currTile, dest.bundle, dest.channel,
319 destCoords, destPort.bundle,
320 destPort.channel))
321 continue;
322 Connect connect = {{src.bundle, src.channel},
323 {dest.bundle, dest.channel}};
324 if (std::find(switchboxes[currTile].begin(),
325 switchboxes[currTile].end(),
326 std::pair{connect, flowID}) ==
327 switchboxes[currTile].end())
328 switchboxes[currTile].push_back({connect, flowID});
329 // Assign "control packet flows" flag per switchbox, based on
330 // packet flow op attribute
331 auto ctrlPkt = pktFlowOp.getPriorityRoute();
332 ctrlPktFlows[{
333 {analyzer.getTile(builder, curr.col, curr.row), dest},
334 flowID}] = ctrlPkt ? *ctrlPkt : false;
335 }
336 }
337 }
338 }
339 }
340 }
341
342 LLVM_DEBUG(llvm::dbgs() << "Check switchboxes\n");
343
344 for (const auto &[tileId, connects] : switchboxes) {
345 int col = tileId.col;
346 int row = tileId.row;
347 auto tile = analyzer.getTile(builder, col, row);
348 Operation *tileOp = tile.getOperation();
349 LLVM_DEBUG(llvm::dbgs() << "***switchbox*** " << col << " " << row << '\n');
350 for (const auto &[conn, flowID] : connects) {
351 Port sourcePort = conn.src;
352 Port destPort = conn.dst;
353 auto sourceFlow =
354 std::make_pair(std::make_pair(tileOp, sourcePort), flowID);
355 if (ctrlPktFlows[{{tileOp, destPort}, flowID}])
356 ctrlPacketFlows[sourceFlow].push_back({tileOp, destPort});
357 else
358 packetFlows[sourceFlow].push_back({tileOp, destPort});
359 slavePorts.push_back(sourceFlow);
360 LLVM_DEBUG(llvm::dbgs() << "flowID " << flowID << ':'
361 << stringifyWireBundle(sourcePort.bundle) << " "
362 << sourcePort.channel << " -> "
363 << stringifyWireBundle(destPort.bundle) << " "
364 << destPort.channel << "\n");
365 }
366 }
367
368 // amsel()
369 // masterset()
370 // packetrules()
371 // rule()
372
373 // Compute arbiter assignments. Each arbiter has four msels.
374 // Therefore, the number of "logical" arbiters is 6 x 4 = 24
375 // A master port can only be associated with one arbiter
376
377 // A map from Tile and master selectValue to the ports targetted by that
378 // master select.
379 DenseMap<std::pair<Operation *, int>, SmallVector<Port, 4>> masterAMSels;
380
381 // Count of currently used logical arbiters for each tile.
382 DenseMap<Operation *, int> amselValues;
383 int numMsels = 4;
384 int numArbiters = 6;
385
386 // Get arbiter id from amsel
387 auto getArbiterIDFromAmsel = [numArbiters](int amsel) {
388 return amsel % numArbiters;
389 };
390 // Get amsel from arbiter id and msel
391 auto getAmselFromArbiterIDAndMsel = [numArbiters](int arbiter, int msel) {
392 return arbiter + msel * numArbiters;
393 };
394 // Get a new unique amsel from masterAMSels on tile op. Prioritize on
395 // incrementing arbiter id, before incrementing msel
396 auto getNewUniqueAmsel = [&](DenseMap<std::pair<Operation *, int>,
397 SmallVector<Port, 4>>
398 masterAMSels,
399 Operation *tileOp, bool isCtrlPkt) {
400 if (isCtrlPkt) { // Higher AMsel first
401 for (int i = numMsels - 1; i >= 0; i--)
402 for (int a = numArbiters - 1; a >= 0; a--)
403 if (!masterAMSels.count({tileOp, getAmselFromArbiterIDAndMsel(a, i)}))
404 return getAmselFromArbiterIDAndMsel(a, i);
405 } else { // Lower AMsel first
406 for (int i = 0; i < numMsels; i++)
407 for (int a = 0; a < numArbiters; a++)
408 if (!masterAMSels.count({tileOp, getAmselFromArbiterIDAndMsel(a, i)}))
409 return getAmselFromArbiterIDAndMsel(a, i);
410 }
411 tileOp->emitOpError("tile op has used up all arbiter-msel combinations");
412 return -1;
413 };
414 // Get a new unique amsel from masterAMSels on tile op with given arbiter id
415 auto getNewUniqueAmselPerArbiterID =
416 [&](DenseMap<std::pair<Operation *, int>, SmallVector<Port, 4>>
417 masterAMSels,
418 Operation *tileOp, int arbiter) {
419 for (int i = 0; i < numMsels; i++)
420 if (!masterAMSels.count(
421 {tileOp, getAmselFromArbiterIDAndMsel(arbiter, i)}))
422 return getAmselFromArbiterIDAndMsel(arbiter, i);
423 tileOp->emitOpError("tile op arbiter ")
424 << std::to_string(arbiter) << "has used up all its msels";
425 return -1;
426 };
427
428 // Sorting the packet flows in order to get determinsitic amsel allocation;
429 // allocate amsels for control packet flows before others to ensure
430 // consistency in control packet flow overlay.
431 auto getUniqueIdPerFlowPerSB = [](int flowID, WireBundle srcBundle,
432 SmallVector<PhysPort, 4> dests) {
433 int totalNumOfWireBundles = AIE::getMaxEnumValForWireBundle();
434 int currMultiplier = totalNumOfWireBundles;
435 int uniqueId = flowID;
436 uniqueId += currMultiplier + getWireBundleAsInt(srcBundle);
437 currMultiplier += totalNumOfWireBundles;
438 for (auto dst : dests) {
439 uniqueId += currMultiplier;
440 uniqueId += getWireBundleAsInt(dst.second.bundle);
441 currMultiplier += totalNumOfWireBundles;
442 }
443 return uniqueId;
444 };
445 auto getSortedPacketFlows =
446 [&](DenseMap<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>> pktFlows,
447 DenseMap<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>>
448 ctrlPktFlows) {
449 std::vector<
450 std::pair<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>>>
451 sortedpktFlows(pktFlows.begin(), pktFlows.end());
452 std::sort(
453 sortedpktFlows.begin(), sortedpktFlows.end(),
454 [getUniqueIdPerFlowPerSB](const auto &lhs, const auto &rhs) {
455 int lhsUniqueID = getUniqueIdPerFlowPerSB(
456 lhs.first.second, lhs.first.first.second.bundle, lhs.second);
457 int rhsUniqueID = getUniqueIdPerFlowPerSB(
458 rhs.first.second, rhs.first.first.second.bundle, rhs.second);
459 return lhsUniqueID < rhsUniqueID;
460 });
461 std::vector<
462 std::pair<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>>>
463 sortedctrlpktFlows(ctrlPktFlows.begin(), ctrlPktFlows.end());
464 std::sort(
465 sortedctrlpktFlows.begin(), sortedctrlpktFlows.end(),
466 [getUniqueIdPerFlowPerSB](const auto &lhs, const auto &rhs) {
467 int lhsUniqueID = getUniqueIdPerFlowPerSB(
468 lhs.first.second, lhs.first.first.second.bundle, lhs.second);
469 int rhsUniqueID = getUniqueIdPerFlowPerSB(
470 rhs.first.second, rhs.first.first.second.bundle, rhs.second);
471 return lhsUniqueID < rhsUniqueID;
472 });
473 sortedctrlpktFlows.insert(sortedctrlpktFlows.end(),
474 sortedpktFlows.begin(), sortedpktFlows.end());
475 return sortedctrlpktFlows;
476 };
477
478 std::vector<std::pair<std::pair<PhysPort, int>, SmallVector<PhysPort, 4>>>
479 sortedPacketFlows = getSortedPacketFlows(packetFlows, ctrlPacketFlows);
480
481 packetFlows.insert(ctrlPacketFlows.begin(), ctrlPacketFlows.end());
482
483 // Check all multi-cast flows (same source, same ID). They should be
484 // assigned the same arbiter and msel so that the flow can reach all the
485 // destination ports at the same time For destination ports that appear in
486 // different (multicast) flows, it should have a different <arbiterID, msel>
487 // value pair for each flow
488 for (const auto &packetFlow : sortedPacketFlows) {
489 // The Source Tile of the flow
490 Operation *tileOp = packetFlow.first.first.first;
491 if (amselValues.count(tileOp) == 0)
492 amselValues[tileOp] = 0;
493
494 // arb0: 6*0, 6*1, 6*2, 6*3
495 // arb1: 6*0+1, 6*1+1, 6*2+1, 6*3+1
496 // arb2: 6*0+2, 6*1+2, 6*2+2, 6*3+2
497 // arb3: 6*0+3, 6*1+3, 6*2+3, 6*3+3
498 // arb4: 6*0+4, 6*1+4, 6*2+4, 6*3+4
499 // arb5: 6*0+5, 6*1+5, 6*2+5, 6*3+5
500
501 int amselValue = amselValues[tileOp];
502 assert(amselValue < numArbiters && "Could not allocate new arbiter!");
503
504 // Find existing arbiter assignment
505 // If there is an assignment of an arbiter to a master port before, we
506 // assign all the master ports here with the same arbiter but different
507 // msel
508 bool foundMatchedDest =
509 false; // This switchbox's output channels match completely or
510 // partially with an existing amsel entry.
511 int foundPartialMatchArbiter =
512 -1; // This switchbox's output channels match partially with an
513 // existing amsel entry on this arbiter ID (-1 means null).
514 for (const auto &map : masterAMSels) {
515 if (map.first.first != tileOp)
516 continue;
517 amselValue = map.first.second;
518
519 // check if same destinations
520 SmallVector<Port, 4> ports(masterAMSels[{tileOp, amselValue}]);
521
522 // check for complete/partial overlapping amsel -> port mapping with any
523 // previous amsel assignments
524 bool matched =
525 false; // Found at least one port with overlapping amsel assignment
526 bool mismatched = false; // Found at least one port without any
527 // overlapping amsel assignment
528 for (auto dest : packetFlow.second) {
529 Port port = dest.second;
530 if (std::find(ports.begin(), ports.end(), port) == ports.end())
531 mismatched = true;
532 else
533 matched = true;
534 }
535
536 if (matched) {
537 foundMatchedDest = true;
538 if (mismatched)
539 foundPartialMatchArbiter = getArbiterIDFromAmsel(amselValue);
540 else if (ports.size() != packetFlow.second.size())
541 foundPartialMatchArbiter = getArbiterIDFromAmsel(amselValue);
542 break;
543 }
544 }
545
546 if (!foundMatchedDest) {
547 // This packet flow switchbox's output ports completely mismatches with
548 // any existing amsel. Creating a new amsel.
549
550 // Check if any of the master ports have ever been used for ctrl pkts.
551 // Ctrl pkt (i.e. prioritized packet flow) amsel assignment follows a
552 // different strategy (see method below).
553 bool ctrlPktAMsel =
554 llvm::any_of(packetFlow.second, [&](PhysPort destPhysPort) {
555 Port port = destPhysPort.second;
556 return ctrlPktFlows[{{tileOp, port}, packetFlow.first.second}];
557 });
558
559 amselValue = getNewUniqueAmsel(masterAMSels, tileOp, ctrlPktAMsel);
560 // Update masterAMSels with new amsel
561 for (auto dest : packetFlow.second) {
562 Port port = dest.second;
563 masterAMSels[{tileOp, amselValue}].push_back(port);
564 }
565 } else if (foundPartialMatchArbiter >= 0) {
566 // This packet flow switchbox's output ports partially overlaps with
567 // some existing amsel. Creating a new amsel with the same arbiter.
568 amselValue = getNewUniqueAmselPerArbiterID(masterAMSels, tileOp,
569 foundPartialMatchArbiter);
570 // Update masterAMSels with new amsel
571 for (auto dest : packetFlow.second) {
572 Port port = dest.second;
573 masterAMSels[{tileOp, amselValue}].push_back(port);
574 }
575 }
576
577 slaveAMSels[packetFlow.first] = amselValue;
578 amselValues[tileOp] = getArbiterIDFromAmsel(amselValue);
579 }
580
581 // Compute the master set IDs
582 // A map from a switchbox output port to the number of that port.
583 DenseMap<PhysPort, SmallVector<int, 4>> mastersets;
584 for (const auto &[physPort, ports] : masterAMSels) {
585 Operation *tileOp = physPort.first;
586 assert(tileOp);
587 int amselValue = physPort.second;
588 for (auto port : ports) {
589 PhysPort pp = {tileOp, port};
590 mastersets[pp].push_back(amselValue);
591 }
592 }
593
594 LLVM_DEBUG(llvm::dbgs() << "CHECK mastersets\n");
595#ifndef NDEBUG
596 for (const auto &[physPort, values] : mastersets) {
597 Operation *tileOp = physPort.first;
598 WireBundle bundle = physPort.second.bundle;
599 int channel = physPort.second.channel;
600 assert(tileOp);
601 auto tile = dyn_cast<TileOp>(tileOp);
602 LLVM_DEBUG(llvm::dbgs()
603 << "master " << tile << " " << stringifyWireBundle(bundle)
604 << " : " << channel << '\n');
605 for (auto value : values)
606 LLVM_DEBUG(llvm::dbgs() << "amsel: " << value << '\n');
607 }
608#endif
609
610 // Compute mask values
611 // Merging as many stream flows as possible
612 // The flows must originate from the same source port and have different IDs
613 // Two flows can be merged if they share the same destinations
614 SmallVector<SmallVector<std::pair<PhysPort, int>, 4>, 4> slaveGroups;
615 SmallVector<std::pair<PhysPort, int>, 4> workList(slavePorts);
616 while (!workList.empty()) {
617 auto slave1 = workList.pop_back_val();
618 Port slavePort1 = slave1.first.second;
619
620 bool foundgroup = false;
621 for (auto &group : slaveGroups) {
622 auto slave2 = group.front();
623 if (Port slavePort2 = slave2.first.second; slavePort1 != slavePort2)
624 continue;
625
626 bool matched = true;
627 auto dests1 = packetFlows[slave1];
628 auto dests2 = packetFlows[slave2];
629 if (dests1.size() != dests2.size())
630 continue;
631
632 for (auto dest1 : dests1) {
633 if (std::find(dests2.begin(), dests2.end(), dest1) == dests2.end()) {
634 matched = false;
635 break;
636 }
637 }
638
639 if (matched) {
640 group.push_back(slave1);
641 foundgroup = true;
642 break;
643 }
644 }
645
646 if (!foundgroup) {
647 SmallVector<std::pair<PhysPort, int>, 4> group({slave1});
648 slaveGroups.push_back(group);
649 }
650 }
651
652 DenseMap<std::pair<PhysPort, int>, int> slaveMasks;
653 for (const auto &group : slaveGroups) {
654 // Iterate over all the ID values in a group
655 // If bit n-th (n <= 5) of an ID value differs from bit n-th of another ID
656 // value, the bit position should be "don't care", and we will set the
657 // mask bit of that position to 0
658 int mask[5] = {-1, -1, -1, -1, -1};
659 for (auto port : group) {
660 int ID = port.second;
661 for (int i = 0; i < 5; i++) {
662 if (mask[i] == -1)
663 mask[i] = ID >> i & 0x1;
664 else if (mask[i] != (ID >> i & 0x1))
665 mask[i] = 2; // found bit difference --> mark as "don't care"
666 }
667 }
668
669 int maskValue = 0;
670 for (int i = 4; i >= 0; i--) {
671 if (mask[i] == 2) // don't care
672 mask[i] = 0;
673 else
674 mask[i] = 1;
675 maskValue = (maskValue << 1) + mask[i];
676 }
677 for (auto port : group)
678 slaveMasks[port] = maskValue;
679 }
680
681#ifndef NDEBUG
682 LLVM_DEBUG(llvm::dbgs() << "CHECK Slave Masks\n");
683 for (auto map : slaveMasks) {
684 auto port = map.first.first;
685 auto tile = dyn_cast<TileOp>(port.first);
686 WireBundle bundle = port.second.bundle;
687 int channel = port.second.channel;
688 int ID = map.first.second;
689 int mask = map.second;
690
691 LLVM_DEBUG(llvm::dbgs()
692 << "Port " << tile << " " << stringifyWireBundle(bundle) << " "
693 << channel << '\n');
694 LLVM_DEBUG(llvm::dbgs() << "Mask "
695 << "0x" << llvm::Twine::utohexstr(mask) << '\n');
696 LLVM_DEBUG(llvm::dbgs() << "ID "
697 << "0x" << llvm::Twine::utohexstr(ID) << '\n');
698 for (int i = 0; i < 31; i++) {
699 if ((i & mask) == (ID & mask))
700 LLVM_DEBUG(llvm::dbgs() << "matches flow ID "
701 << "0x" << llvm::Twine::utohexstr(i) << '\n');
702 }
703 }
704#endif
705
706 // Realize the routes in MLIR
707
708 // Update tiles map if any new tile op declaration is needed for constructing
709 // the flow.
710 for (const auto &swMap : mastersets) {
711 if (llvm::none_of(
712 tiles,
713 [&swMap](
714 std::pair<xilinx::AIE::TileID, Operation *> &tileMapEntry) {
715 return tileMapEntry.second == swMap.first.first;
716 })) {
717 auto newTileOp = dyn_cast<TileOp>(swMap.first.first);
718 tiles[{newTileOp.colIndex(), newTileOp.rowIndex()}] = newTileOp;
719 }
720 }
721
722 for (auto map : tiles) {
723 Operation *tileOp = map.second;
724 auto tile = dyn_cast<TileOp>(tileOp);
725
726 // Create a switchbox for the routes and insert inside it.
727 builder.setInsertionPointAfter(tileOp);
728 SwitchboxOp swbox =
729 analyzer.getSwitchbox(builder, tile.colIndex(), tile.rowIndex());
730 SwitchboxOp::ensureTerminator(swbox.getConnections(), builder,
731 builder.getUnknownLoc());
732 Block &b = swbox.getConnections().front();
733 builder.setInsertionPoint(b.getTerminator());
734
735 std::vector<bool> amselOpNeededVector(numMsels * numArbiters);
736 for (const auto &map : mastersets) {
737 if (tileOp != map.first.first)
738 continue;
739
740 for (auto value : map.second) {
741 amselOpNeededVector[value] = true;
742 }
743 }
744 // Create all the amsel Ops
745 DenseMap<int, AMSelOp> amselOps;
746 for (int i = 0; i < numMsels; i++) {
747 for (int a = 0; a < numArbiters; a++) {
748 auto amselValue = getAmselFromArbiterIDAndMsel(a, i);
749 if (amselOpNeededVector[amselValue]) {
750 int arbiterID = a;
751 int msel = i;
752 auto amsel =
753 builder.create<AMSelOp>(builder.getUnknownLoc(), arbiterID, msel);
754 amselOps[amselValue] = amsel;
755 }
756 }
757 }
758 // Create all the master set Ops
759 // First collect the master sets for this tile.
760 SmallVector<Port, 4> tileMasters;
761 for (const auto &map : mastersets) {
762 if (tileOp != map.first.first)
763 continue;
764 tileMasters.push_back(map.first.second);
765 }
766 // Sort them so we get a reasonable order
767 std::sort(tileMasters.begin(), tileMasters.end());
768 for (auto tileMaster : tileMasters) {
769 WireBundle bundle = tileMaster.bundle;
770 int channel = tileMaster.channel;
771 SmallVector<int, 4> msels = mastersets[{tileOp, tileMaster}];
772 SmallVector<Value, 4> amsels;
773 for (auto msel : msels) {
774 assert(amselOps.count(msel) == 1);
775 amsels.push_back(amselOps[msel]);
776 }
777
778 builder.create<MasterSetOp>(
779 builder.getUnknownLoc(), builder.getIndexType(), bundle, channel,
780 amsels, keepPktHeaderAttr[{tileOp, tileMaster}]);
781 }
782
783 // Generate the packet rules
784 DenseMap<Port, PacketRulesOp> slaveRules;
785 for (auto group : slaveGroups) {
786 builder.setInsertionPoint(b.getTerminator());
787
788 auto port = group.front().first;
789 if (tileOp != port.first)
790 continue;
791
792 WireBundle bundle = port.second.bundle;
793 int channel = port.second.channel;
794 auto slave = port.second;
795
796 int mask = slaveMasks[group.front()];
797 int ID = group.front().second & mask;
798
799 // Verify that we actually map all the ID's correctly.
800#ifndef NDEBUG
801 for (auto slave : group)
802 assert((slave.second & mask) == ID);
803#endif
804 Value amsel = amselOps[slaveAMSels[group.front()]];
805
806 PacketRulesOp packetrules;
807 if (slaveRules.count(slave) == 0) {
808 packetrules = builder.create<PacketRulesOp>(builder.getUnknownLoc(),
809 bundle, channel);
810 PacketRulesOp::ensureTerminator(packetrules.getRules(), builder,
811 builder.getUnknownLoc());
812 slaveRules[slave] = packetrules;
813 } else
814 packetrules = slaveRules[slave];
815
816 Block &rules = packetrules.getRules().front();
817
818 // Verify ID mapping against all other rules of the same slave.
819 for (auto rule : rules.getOps<PacketRuleOp>()) {
820 auto verifyMask = rule.maskInt();
821 auto verifyValue = rule.valueInt();
822 if ((group.front().second & verifyMask) == verifyValue) {
823 rule->emitOpError("can lead to false packet id match for id ")
824 << ID << ", which is not supposed to pass through this port.";
825 rule->emitRemark("Please consider changing all uses of packet id ")
826 << ID << " to avoid deadlock.";
827 }
828 }
829
830 builder.setInsertionPoint(rules.getTerminator());
831 builder.create<PacketRuleOp>(builder.getUnknownLoc(), mask, ID, amsel);
832 }
833 }
834
835 // Add support for shimDMA
836 // From shimDMA to BLI: 1) shimDMA 0 --> North 3
837 // 2) shimDMA 1 --> North 7
838 // From BLI to shimDMA: 1) North 2 --> shimDMA 0
839 // 2) North 3 --> shimDMA 1
840
841 for (auto switchbox : make_early_inc_range(device.getOps<SwitchboxOp>())) {
842 auto retVal = switchbox->getOperand(0);
843 auto tileOp = retVal.getDefiningOp<TileOp>();
844
845 // Check if it is a shim Tile
846 if (!tileOp.isShimNOCTile())
847 continue;
848
849 // Check if the switchbox is empty
850 if (&switchbox.getBody()->front() == switchbox.getBody()->getTerminator())
851 continue;
852
853 Region &r = switchbox.getConnections();
854 Block &b = r.front();
855
856 // Find if the corresponding shimmux exsists or not
857 int shimExist = 0;
858 ShimMuxOp shimOp;
859 for (auto shimmux : device.getOps<ShimMuxOp>()) {
860 if (shimmux.getTile() == tileOp) {
861 shimExist = 1;
862 shimOp = shimmux;
863 break;
864 }
865 }
866
867 for (Operation &Op : b.getOperations()) {
868 if (auto pktrules = dyn_cast<PacketRulesOp>(Op)) {
869
870 // check if there is MM2S DMA in the switchbox of the 0th row
871 if (pktrules.getSourceBundle() == WireBundle::DMA) {
872
873 // If there is, then it should be put into the corresponding shimmux
874 // If shimmux not defined then create shimmux
875 if (!shimExist) {
876 builder.setInsertionPointAfter(tileOp);
877 shimOp = analyzer.getShimMux(builder, tileOp.colIndex());
878 shimExist = 1;
879 }
880
881 Region &r0 = shimOp.getConnections();
882 Block &b0 = r0.front();
883 builder.setInsertionPointToStart(&b0);
884
885 pktrules.setSourceBundle(WireBundle::South);
886 if (pktrules.getSourceChannel() == 0) {
887 pktrules.setSourceChannel(3);
888 builder.create<ConnectOp>(builder.getUnknownLoc(), WireBundle::DMA,
889 0, WireBundle::North, 3);
890 }
891 if (pktrules.getSourceChannel() == 1) {
892 pktrules.setSourceChannel(7);
893 builder.create<ConnectOp>(builder.getUnknownLoc(), WireBundle::DMA,
894 1, WireBundle::North, 7);
895 }
896 }
897 }
898
899 if (auto mtset = dyn_cast<MasterSetOp>(Op)) {
900
901 // check if there is S2MM DMA in the switchbox of the 0th row
902 if (mtset.getDestBundle() == WireBundle::DMA) {
903
904 // If there is, then it should be put into the corresponding shimmux
905 // If shimmux not defined then create shimmux
906 if (!shimExist) {
907 builder.setInsertionPointAfter(tileOp);
908 shimOp = analyzer.getShimMux(builder, tileOp.colIndex());
909 shimExist = 1;
910 }
911
912 Region &r0 = shimOp.getConnections();
913 Block &b0 = r0.front();
914 builder.setInsertionPointToStart(&b0);
915
916 mtset.setDestBundle(WireBundle::South);
917 if (mtset.getDestChannel() == 0) {
918 mtset.setDestChannel(2);
919 builder.create<ConnectOp>(builder.getUnknownLoc(),
920 WireBundle::North, 2, WireBundle::DMA, 0);
921 }
922 if (mtset.getDestChannel() == 1) {
923 mtset.setDestChannel(3);
924 builder.create<ConnectOp>(builder.getUnknownLoc(),
925 WireBundle::North, 3, WireBundle::DMA, 1);
926 }
927 }
928 }
929 }
930 }
931
932 RewritePatternSet patterns(&getContext());
933
934 if (failed(applyPartialConversion(device, target, std::move(patterns))))
935 signalPassFailure();
936}
937
939
940 // create analysis pass with routing graph for entire device
941 LLVM_DEBUG(llvm::dbgs() << "---Begin AIEPathfinderPass---\n");
942
943 DeviceOp d = getOperation();
944 if (failed(analyzer.runAnalysis(d)))
945 return signalPassFailure();
946 OpBuilder builder = OpBuilder::atBlockTerminator(d.getBody());
947
948 if (clRouteCircuit)
949 runOnFlow(d);
950 if (clRoutePacket)
951 runOnPacketFlow(d, builder);
952
953 // Populate wires between switchboxes and tiles.
954 builder.setInsertionPoint(d.getBody()->getTerminator());
955 for (int col = 0; col <= analyzer.getMaxCol(); col++) {
956 for (int row = 0; row <= analyzer.getMaxRow(); row++) {
957 TileOp tile;
958 if (analyzer.coordToTile.count({col, row}))
959 tile = analyzer.coordToTile[{col, row}];
960 else
961 continue;
962 SwitchboxOp sw;
963 if (analyzer.coordToSwitchbox.count({col, row}))
964 sw = analyzer.coordToSwitchbox[{col, row}];
965 else
966 continue;
967 if (col > 0) {
968 // connections east-west between stream switches
969 if (analyzer.coordToSwitchbox.count({col - 1, row})) {
970 auto westsw = analyzer.coordToSwitchbox[{col - 1, row}];
971 builder.create<WireOp>(builder.getUnknownLoc(), westsw,
972 WireBundle::East, sw, WireBundle::West);
973 }
974 }
975 if (row > 0) {
976 // connections between abstract 'core' of tile
977 builder.create<WireOp>(builder.getUnknownLoc(), tile, WireBundle::Core,
978 sw, WireBundle::Core);
979 // connections between abstract 'dma' of tile
980 builder.create<WireOp>(builder.getUnknownLoc(), tile, WireBundle::DMA,
981 sw, WireBundle::DMA);
982 // connections north-south inside array ( including connection to shim
983 // row)
984 if (analyzer.coordToSwitchbox.count({col, row - 1})) {
985 auto southsw = analyzer.coordToSwitchbox[{col, row - 1}];
986 builder.create<WireOp>(builder.getUnknownLoc(), southsw,
987 WireBundle::North, sw, WireBundle::South);
988 }
989 } else if (row == 0) {
990 if (tile.isShimNOCTile()) {
991 if (analyzer.coordToShimMux.count({col, 0})) {
992 auto shimsw = analyzer.coordToShimMux[{col, 0}];
993 builder.create<WireOp>(
994 builder.getUnknownLoc(), shimsw,
995 WireBundle::North, // Changed to connect into the north
996 sw, WireBundle::South);
997 // PLIO is attached to shim mux
998 if (analyzer.coordToPLIO.count(col)) {
999 auto plio = analyzer.coordToPLIO[col];
1000 builder.create<WireOp>(builder.getUnknownLoc(), plio,
1001 WireBundle::North, shimsw,
1002 WireBundle::South);
1003 }
1004
1005 // abstract 'DMA' connection on tile is attached to shim mux ( in
1006 // row 0 )
1007 builder.create<WireOp>(builder.getUnknownLoc(), tile,
1008 WireBundle::DMA, shimsw, WireBundle::DMA);
1009 }
1010 } else if (tile.isShimPLTile()) {
1011 // PLIO is attached directly to switch
1012 if (analyzer.coordToPLIO.count(col)) {
1013 auto plio = analyzer.coordToPLIO[col];
1014 builder.create<WireOp>(builder.getUnknownLoc(), plio,
1015 WireBundle::North, sw, WireBundle::South);
1016 }
1017 }
1018 }
1019 }
1020 }
1021}
1022
1023std::unique_ptr<OperationPass<DeviceOp>> createAIEPathfinderPass() {
1024 return std::make_unique<AIEPathfinderPass>();
1025}
1026
1027} // namespace xilinx::AIE
ShimMuxOp getShimMux(mlir::OpBuilder &builder, int col)
SwitchboxOp getSwitchbox(mlir::OpBuilder &builder, int col, int row)
TileOp getTile(mlir::OpBuilder &builder, int col, int row)
std::map< PathEndPoint, bool > processedFlows
std::map< PathEndPoint, SwitchSettings > flowSolutions
Include the generated interface declarations.
Connect { Port src Connect
Definition AIEDialect.h:171
int getWireBundleAsInt(WireBundle bundle)
TileID { friend std::ostream &operator<<(std::ostream &os, const TileID &s) { os<< "TileID("<< s.col<< ", "<< s.row<< ")" TileID
std::map< TileID, SwitchSetting > SwitchSettings
TileID dstCoords
Port { WireBundle bundle Port
Definition AIEDialect.h:118
PathEndPoint src
TileID srcCoords
PathEndPoint { PathEndPoint()=default PathEndPoint
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEPathfinderPass()
AIEOpRemoval(MLIRContext *context, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(MyOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
DynamicTileAnalysis analyzer
Definition AIEPasses.h:66
void runOnPacketFlow(DeviceOp d, mlir::OpBuilder &builder)
mlir::DenseMap< TileID, mlir::Operation * > tiles
Definition AIEPasses.h:67
bool findPathToDest(SwitchSettings settings, TileID currTile, WireBundle currDestBundle, int currDestChannel, TileID finalTile, WireBundle finalDestBundle, int finalDestChannel)