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