13#include "mlir/IR/Attributes.h"
14#include "mlir/Pass/Pass.h"
16#include "llvm/ADT/SetVector.h"
17#include "llvm/ADT/SmallSet.h"
25#define GEN_PASS_DEF_AIEGENERATECOLUMNCONTROLOVERLAY
26#define GEN_PASS_DEF_AIEASSIGNTILECTRLIDS
27#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
30#define DEBUG_TYPE "aie-generate-column-control-overlay"
37DenseMap<AIE::TileID, int>
48 DenseMap<int, int> rowMap;
50 thresholdsToNextShimChannel;
55 while (!targetModel.
isShimNOCTile(shimTile.col, shimTile.row)) {
57 if (shimTile.col == targetModel.
columns()) {
61 assert(shimTile.col != targetModel.
columns() ||
62 shimTile.row != targetModel.
rows());
66 shimTile.col, shimTile.row, AIE::WireBundle::DMA);
67 for (
int i = 1; i < numShimChans + 1; i++)
68 thresholdsToNextShimChannel.push_back(targetModel.
rows() / numShimChans *
71 if (bundle == WireBundle::DMA) {
73 for (
int r = 0; r < targetModel.
rows(); r++) {
74 if (r >= thresholdsToNextShimChannel[shimChanIdx])
76 rowMap[r] = shimChanIdx;
78 }
else if (bundle == WireBundle::South) {
79 for (
int r = 0; r < targetModel.
rows(); r++)
89 registry.insert<AIEDialect>();
92 DeviceOp device = getOperation();
93 const auto &targetModel = device.getTargetModel();
95 if (targetModel.getTargetArch() == AIEArch::AIE1)
99 llvm::MapVector<AIE::TileID, AIE::TileOp> tiles;
100 llvm::SmallSet<int, 1> occupiedCols;
101 for (
auto tile : device.getOps<AIE::TileOp>()) {
102 int colIndex = tile.colIndex();
103 int rowIndex = tile.rowIndex();
104 tiles[{colIndex, rowIndex}] = tile;
105 occupiedCols.insert(colIndex);
110 for (
int col : occupiedCols) {
111 SmallVector<AIE::TileOp> tilesOnCol;
112 for (
auto &[tId, tOp] : tiles) {
115 tilesOnCol.push_back(tOp);
118 for (
auto tOp : tilesOnCol) {
119 if (tOp->hasAttr(
"controller_id"))
121 auto pktInfoAttr = AIE::PacketInfoAttr::get(
122 tOp->getContext(), 0,
123 tileIDMap[{tOp.colIndex(), tOp.rowIndex()}]);
124 tOp->setAttr(
"controller_id", pktInfoAttr);
132 AIEGenerateColumnControlOverlayPass> {
136 registry.insert<AIEDialect>();
137 registry.insert<memref::MemRefDialect>();
140 ModuleOp module = getOperation();
141 OpBuilder builder(module.getContext());
145 SmallVector<DeviceOp> sourceDevices;
146 for (
auto dev : module.getOps<DeviceOp>()) {
147 if (dev.getSymName() ==
"ctrl_pkt_overlay")
149 sourceDevices.push_back(dev);
154 SmallVector<DeviceOp> participating;
155 for (
auto dev : sourceDevices) {
157 if (clEmitStandaloneOverlay) {
158 dev.setHasCtrlPktOverlay(
false);
165 if (clEmitStandaloneOverlay) {
166 dev.setHasCtrlPktOverlay(
true);
170 participating.push_back(dev);
177 llvm::SmallSet<AIE::TileID, 4> sharedTokenTiles;
178 if (clEmitStandaloneOverlay) {
184 for (
auto dev : participating) {
185 llvm::SmallSet<AIE::TileID, 4> tokenTiles =
186 clEmitStandaloneOverlay ? sharedTokenTiles
189 return signalPassFailure();
190 if (clEmitStandaloneOverlay)
191 dev.setHasCtrlPktOverlay(
true);
195 if (clEmitStandaloneOverlay) {
197 sharedTokenTiles))) {
198 return signalPassFailure();
207 llvm::SmallSetVector<AIE::TileID, 8> &unionTiles,
208 llvm::DenseMap<AIE::TileID, AIE::TileOp> &prototypeTile) {
209 for (
auto dev : devices)
210 for (
auto tOp : dev.getOps<AIE::TileOp>()) {
212 unionTiles.insert(
id);
213 if (!prototypeTile.contains(
id))
214 prototypeTile[id] = tOp;
222 DeviceOp device,
const llvm::SmallSetVector<AIE::TileID, 8> &unionTiles,
223 const llvm::DenseMap<AIE::TileID, AIE::TileOp> &prototypeTile) {
224 llvm::SmallSet<AIE::TileID, 8> existing;
225 for (
auto tOp : device.getOps<AIE::TileOp>())
226 existing.insert({tOp.colIndex(), tOp.rowIndex()});
227 OpBuilder b = OpBuilder::atBlockBegin(device.getBody());
228 for (
auto id : unionTiles) {
229 if (existing.contains(
id))
231 b.clone(*prototypeTile.lookup(
id).getOperation());
238 llvm::SmallSetVector<AIE::TileID, 8> unionTiles;
239 llvm::DenseMap<AIE::TileID, AIE::TileOp> prototypeTile;
241 for (
auto dev : devices)
250 ArrayRef<DeviceOp> participating,
251 const llvm::SmallSet<AIE::TileID, 4> &tokenTiles) {
252 if (participating.empty())
256 DeviceOp firstDev = participating.front();
257 auto refDevice = firstDev.getDevice();
258 for (
auto dev : llvm::drop_begin(participating)) {
259 if (dev.getDevice() != refDevice) {
260 return dev->emitOpError(
261 "cannot generate a single standalone ctrl_pkt_overlay device: "
262 "participating devices have mismatched target architectures.");
266 if (module.lookupSymbol(
"ctrl_pkt_overlay")) {
267 return module.emitOpError(
268 "a symbol named `ctrl_pkt_overlay` already exists in the module;
"
269 "cannot create a standalone ctrl_pkt_overlay device.
");
272 builder.setInsertionPointToEnd(module.getBody());
273 Location loc = firstDev.getLoc();
274 auto overlayDevice = AIE::DeviceOp::create(
275 builder, loc, refDevice, builder.getStringAttr("ctrl_pkt_overlay
"));
276 overlayDevice.getRegion().emplaceBlock();
277 builder.setInsertionPointToEnd(&overlayDevice.getRegion().front());
278 AIE::EndOp::create(builder, loc);
280 // Populate the overlay device with the union of tiles referenced across
281 // participating devices, then route the overlay onto it.
282 llvm::SmallSetVector<AIE::TileID, 8> unionTiles;
283 llvm::DenseMap<AIE::TileID, AIE::TileOp> prototypeTile;
284 collectTileUnion(participating, unionTiles, prototypeTile);
285 cloneMissingTiles(overlayDevice, unionTiles, prototypeTile);
287 if (failed(applyOverlayToDevice(overlayDevice, tokenTiles)))
290 overlayDevice.setHasCtrlPktOverlay(true);
294 // Apply the column-control overlay to `device` in place, routing the
295 // TileControl port of every tile in `tokenTiles` back to its shim. Returns
296 // failure on a routing conflict.
298 applyOverlayToDevice(DeviceOp device,
299 const llvm::SmallSet<AIE::TileID, 4> &tokenTiles) {
300 const auto &targetModel = device.getTargetModel();
301 OpBuilder builder = OpBuilder::atBlockTerminator(device.getBody());
303 if (targetModel.getTargetArch() == AIEArch::AIE1)
304 return success(); // Disable this pass for AIE1; AIE1 support NYI.
306 // Collect existing TileOps
307 llvm::MapVector<AIE::TileID, AIE::TileOp> tiles;
308 for (auto tile : device.getOps<AIE::TileOp>())
309 tiles[{tile.colIndex(), tile.rowIndex()}] = tile;
313 int minOccupiedCol = tiles.front().first.col;
314 int maxOccupiedCol = minOccupiedCol;
315 int maxOccupiedRow = 0;
316 llvm::SmallSet<int, 4> declaredCols;
317 for (auto &[tId, tOp] : tiles) {
318 minOccupiedCol = std::min(minOccupiedCol, tId.col);
319 maxOccupiedCol = std::max(maxOccupiedCol, tId.col);
320 maxOccupiedRow = std::max(maxOccupiedRow, tId.row);
321 declaredCols.insert(tId.col);
324 // Both widenings below are scoped to the control-packet configuration path
325 // (`route-shim-to-tile-ctrl`); do not add tile declarations if the control
326 // overlay was not requested, as to not congest routing needlessly.
327 SmallVector<int> colsToCover;
328 if (clRouteShimDmaToTileCTRL) {
329 // Cover the full column range between the leftmost and rightmost occupied
330 // column, not just the occupied columns. This is required so that a shim
331 // DMA allocation is later emitted for the intermediate columns that a
332 // flow will route through.
333 for (int col = minOccupiedCol; col <= maxOccupiedCol; col++)
334 colsToCover.push_back(col);
336 for (auto &[tId, tOp] : tiles)
337 if (!llvm::is_contained(colsToCover, tId.col))
338 colsToCover.push_back(tId.col);
341 auto tileIDMap = getTileToControllerIdMap(true, targetModel);
342 for (int col : colsToCover) {
343 builder.setInsertionPointToStart(device.getBody());
344 AIE::TileOp shimTile = TileOp::getOrCreate(builder, device, col, 0);
345 if (clRouteShimDmaToTileCTRL)
346 tiles[{col, 0}] = shimTile;
348 if (clRouteShimCTRLToTCT == "all-tiles
" ||
349 clRouteShimCTRLToTCT == "shim-only
") {
350 // Get all tile ops on column col
351 SmallVector<AIE::TileOp> tilesOnCol;
352 for (auto &[tId, tOp] : tiles) {
355 if (clRouteShimCTRLToTCT == "shim-only
" && !tOp.isShimNOCorPLTile() &&
356 !tokenTiles.contains(tId))
358 // A routed response only covers the tile it comes from.
359 if (hasRoutedResponse(device, tOp, tileIDMap))
361 tilesOnCol.push_back(tOp);
364 if (!tilesOnCol.empty() &&
365 failed(generatePacketFlowsForControl(
366 builder, device, shimTile, AIE::WireBundle::South, tilesOnCol,
367 AIE::WireBundle::TileControl, 0, tileIDMap, false)))
370 if (clRouteShimDmaToTileCTRL) {
371 // Ensure tiles exist for the full range from shim (row 0) to the
372 // highest existing tile in the column. Intermediate tiles (e.g. mem
373 // tiles) are needed for control packet routing and will also need
374 // their switchboxes configured via control packets.
376 for (auto &[tId, tOp] : tiles) {
378 maxRow = std::max(maxRow, tId.row);
380 // A column the design declared no tile in is only in range because
381 // flows route through it, and such a flow can traverse it at any row up
382 // to the highest row in use. getRowToShimChanMap splits the rows into
383 // one contiguous range per shim channel, so covering only the shim row
384 // here would allocate just one of the channels its packets get
385 // addressed to. Test against the columns the design itself declared,
386 // not against `tiles`, which now also holds the shim materialized just
388 if (!declaredCols.contains(col))
389 maxRow = maxOccupiedRow;
390 SmallVector<AIE::TileOp> tilesOnCol;
391 for (int row = 0; row <= maxRow; row++) {
392 auto tOp = TileOp::getOrCreate(builder, device, col, row);
393 tilesOnCol.push_back(tOp);
396 if (failed(generatePacketFlowsForControl(
397 builder, device, shimTile, AIE::WireBundle::DMA, tilesOnCol,
398 AIE::WireBundle::TileControl, 0, tileIDMap, true)))
405 // Tiles a runtime sequence asks a task-complete token of. The token leaves
406 // the tile through its TileControl port, so without a route from there to
407 // the shim an await on it never returns.
408 static llvm::SmallSet<AIE::TileID, 4>
409 collectTokenIssuingTiles(ArrayRef<DeviceOp> devices) {
410 llvm::SmallSet<AIE::TileID, 4> tiles;
411 auto addTile = [&](Value tileValue) {
414 if (auto tile = dyn_cast_or_null<TileOp>(tileValue.getDefiningOp()))
415 tiles.insert({tile.colIndex(), tile.rowIndex()});
417 for (DeviceOp device : devices)
418 device.walk([&](Operation *op) {
419 if (auto task = dyn_cast<AIEX::DMAConfigureTaskOp>(op)) {
420 if (task.getIssueToken())
421 addTile(task.getTile());
422 } else if (auto task = dyn_cast<AIEX::DMAConfigureTaskForOp>(op)) {
423 if (!task.getIssueToken())
425 if (auto endpoint = dyn_cast_or_null<RouteEndpointOp>(
426 SymbolTable::lookupSymbolIn(device, task.getAlloc())))
427 addTile(endpoint.getTile());
428 } else if (auto push = dyn_cast<AIEX::NpuPushQueueOp>(op)) {
429 if (push.getIssueToken())
430 tiles.insert({static_cast<int>(push.getColumn()),
431 static_cast<int>(push.getRow())});
437 // Return true when the user has explicitly disabled overlay generation for
438 // this device via `needs_ctrl_pkt_overlay = false`.
439 static bool deviceOptedOut(DeviceOp device) {
440 auto attr = device->getAttrOfType<BoolAttr>("needs_ctrl_pkt_overlay
");
441 return attr && !attr.getValue();
444 // A one-source, one-destination packet flow reduced to a comparable key:
445 // the packet ID plus both endpoints as (col, row, bundle, channel). This is
446 // the shape every control flow below has, and keying on coordinates rather
447 // than on the defining op is what makes it match DeviceOp::verify's notion
449 using CtrlFlowKey = std::tuple<int, int, int, int, int, int, int, int, int>;
451 // Nullopt when either endpoint's coordinates are not known yet -- an
452 // aie.logical_tile that --aie-place-tiles has not placed. Such a flow cannot
453 // be told apart from the one about to be created, and the verifier skips it
454 // for the same reason. A pinned aie.logical_tile does have coordinates, so
455 // this goes through TileLike rather than TileOp to catch it.
456 static std::optional<CtrlFlowKey>
457 tryGetCtrlFlowKey(int id, Value srcTileValue, WireBundle srcBundle,
458 int srcChan, Value destTileValue, WireBundle destBundle,
460 auto srcTile = dyn_cast_or_null<TileLike>(srcTileValue.getDefiningOp());
461 auto destTile = dyn_cast_or_null<TileLike>(destTileValue.getDefiningOp());
462 if (!srcTile || !destTile)
464 std::optional<int> srcCol = srcTile.tryGetCol();
465 std::optional<int> srcRow = srcTile.tryGetRow();
466 std::optional<int> destCol = destTile.tryGetCol();
467 std::optional<int> destRow = destTile.tryGetRow();
468 if (!srcCol || !srcRow || !destCol || !destRow)
471 id, *srcCol, *srcRow, static_cast<int>(srcBundle),
472 srcChan, *destCol, *destRow, static_cast<int>(destBundle),
476 // The packet flows `device` already declares in the shape this pass emits,
477 // keyed so one can be recognised before it is created a second time. A flow
478 // with more than one source or destination is a different shape and is left
480 static std::map<CtrlFlowKey, AIE::PacketFlowOp>
481 collectExistingCtrlFlows(DeviceOp device) {
482 std::map<CtrlFlowKey, AIE::PacketFlowOp> flows;
483 for (auto flow : device.getOps<AIE::PacketFlowOp>()) {
484 auto sources = flow.getOps<AIE::PacketSourceOp>();
485 auto dests = flow.getOps<AIE::PacketDestOp>();
486 if (!llvm::hasSingleElement(sources) || !llvm::hasSingleElement(dests))
488 AIE::PacketSourceOp src = *sources.begin();
489 AIE::PacketDestOp dest = *dests.begin();
490 std::optional<CtrlFlowKey> key = tryGetCtrlFlowKey(
491 flow.IDInt(), src.getTile(), src.getBundle(), src.channelIndex(),
492 dest.getTile(), dest.getBundle(), dest.channelIndex());
494 flows.try_emplace(*key, flow);
499 // The packet ID of control packets to and from `tile`.
500 static int getControllerID(TileOp tile, DenseMap<TileID, int> &tileIDMap) {
501 if (auto id = tile->getAttrOfType<AIE::PacketInfoAttr>("controller_id
"))
502 return id.assignedId();
503 return tileIDMap[{tile.colIndex(), tile.rowIndex()}];
506 // Name of the shim DMA allocation the runtime sequence issues or receives
507 // control packets through.
508 static std::string getCtrlPktAllocName(int col, bool isShimMM2S, int chan) {
509 std::string name = "ctrlpkt
";
510 name += "_col
" + std::to_string(col);
511 name += isShimMM2S ? "_mm2s
" : "_s2mm
";
512 name += "_chan
" + std::to_string(chan);
516 // True when packets with ID `pktID` entering the switchbox of `from` on any
517 // of `startPorts` are routed to `toPort` of `to`'s switchbox, through a
518 // connect or master marked is_ctrl_pkt_overlay. The ID is traced through the
519 // routed switchboxes rather than inferred from the final rule.
520 static bool hasRoutedPacket(DeviceOp device, int pktID, TileID from,
521 ArrayRef<Port> startPorts, TileID to,
523 AIEDialect::IsCtrlPktOverlayAttrHelper overlay(device.getContext());
524 DenseMap<TileID, AIE::SwitchboxOp> switchboxes;
525 for (auto switchbox : device.getOps<AIE::SwitchboxOp>()) {
526 auto tile = dyn_cast<TileLike>(switchbox.getTile().getDefiningOp());
529 std::optional<int> c = tile.tryGetCol(), r = tile.tryGetRow();
531 switchboxes[{*c, *r}] = switchbox;
534 SmallVector<std::pair<TileID, Port>> worklist;
535 for (Port port : startPorts)
536 worklist.push_back({from, port});
537 std::set<std::tuple<int, int, int, int>> visited;
538 while (!worklist.empty()) {
539 auto [tileID, port] = worklist.pop_back_val();
541 .insert({tileID.col, tileID.row, static_cast<int>(port.bundle),
545 auto switchbox = switchboxes.lookup(tileID);
548 Block &connections = switchbox.getConnections().front();
550 SmallVector<std::pair<Port, Operation *>> outputs;
551 for (auto connect : connections.getOps<AIE::ConnectOp>())
552 if (connect.sourcePort() == port)
553 outputs.push_back({connect.destPort(), connect});
554 // The first rule matching the packet ID picks the master. Pathfinder
555 // marks a slave's packet_rules only from the first group it routes, so
556 // the marker on the rules is not reliable.
557 for (auto rules : connections.getOps<AIE::PacketRulesOp>()) {
558 if (rules.sourcePort() != port)
560 for (auto rule : rules.getRules().front().getOps<AIE::PacketRuleOp>()) {
561 if ((pktID & rule.maskInt()) != rule.valueInt())
563 for (auto master : connections.getOps<AIE::MasterSetOp>())
564 if (llvm::is_contained(master.getAmsels(), rule.getAmsel()))
565 outputs.push_back({master.destPort(), master});
570 for (auto [dest, op] : outputs) {
571 if (tileID == to && dest == toPort) {
572 if (overlay.isAttrPresent(op))
577 getConnectingWireEndpoint(device.getTargetModel(), tileID, dest);
580 worklist.push_back(*next);
586 // A routed response must actually connect TileControl:0 of `src` to South:0
587 // on its column's shim. A priority request ending at TileControl is not a
588 // response, even though pathfinder marks it is_ctrl_pkt_overlay.
589 static bool hasRoutedResponse(DeviceOp device, TileOp src,
590 DenseMap<TileID, int> &tileIDMap) {
591 int col = src.colIndex();
592 return hasRoutedPacket(device, getControllerID(src, tileIDMap),
593 {col, src.rowIndex()},
594 {Port{WireBundle::TileControl, 0}}, {col, 0},
595 Port{WireBundle::South, 0});
598 // A routed request must connect DMA:`shimChan` of `shimTile` to
599 // TileControl:0 of `dest`. Pathfinder moves the shim DMA behind the shim
600 // mux, so the request enters the shim's switchbox on the South port the mux
601 // connects that DMA channel to.
602 static bool hasRoutedRequest(DeviceOp device, TileOp shimTile, int shimChan,
603 TileOp dest, DenseMap<TileID, int> &tileIDMap) {
604 SmallVector<Port> startPorts = {Port{WireBundle::DMA, shimChan}};
605 for (auto shimMux : device.getOps<AIE::ShimMuxOp>()) {
606 if (shimMux.getTile() != shimTile.getResult())
609 shimMux.getConnections().front().getOps<AIE::ConnectOp>())
610 if (connect.sourcePort() == Port{WireBundle::DMA, shimChan} &&
611 connect.destPort().bundle == WireBundle::North)
612 startPorts.push_back(
613 Port{WireBundle::South, connect.destPort().channel});
615 return hasRoutedPacket(device, getControllerID(dest, tileIDMap),
616 {shimTile.colIndex(), shimTile.rowIndex()},
617 startPorts, {dest.colIndex(), dest.rowIndex()},
618 Port{WireBundle::TileControl, 0});
621 // True when `tile` is used only by its routing, and its switchbox only
622 // passes streams between neighbouring switchboxes: none starts or ends at
624 static bool isRoutedThroughOnly(TileOp tile) {
625 auto isNeighbour = [](Port port) {
626 return port.bundle == WireBundle::North ||
627 port.bundle == WireBundle::South ||
628 port.bundle == WireBundle::East || port.bundle == WireBundle::West;
630 bool hasSwitchbox = false;
631 for (Operation *user : tile->getUsers()) {
632 if (isa<AIE::WireOp>(user))
634 auto switchbox = dyn_cast<AIE::SwitchboxOp>(user);
638 for (Operation &op : switchbox.getConnections().front()) {
639 if (auto connect = dyn_cast<AIE::ConnectOp>(op)) {
640 if (!isNeighbour(connect.sourcePort()) ||
641 !isNeighbour(connect.destPort()))
643 } else if (auto rules = dyn_cast<AIE::PacketRulesOp>(op)) {
644 if (!isNeighbour(rules.sourcePort()))
646 } else if (auto master = dyn_cast<AIE::MasterSetOp>(op)) {
647 if (!isNeighbour(master.destPort()))
655 // Every occupied column needs its own shim response route. An overlay
656 // routed on only one column must not suppress the remaining columns. With
657 // route-shim-to-tct=all-tiles, every tile also needs its own response route,
658 // except a tile that is only routed through. Pathfinder declares such a tile
659 // when a flow crosses it, and a first run of this pass never covered it.
660 bool hasRoutedResponses(DeviceOp device, DenseMap<TileID, int> &tileIDMap) {
661 llvm::SmallSet<int, 4> cols;
662 llvm::DenseMap<int, TileOp> shims;
663 for (auto tile : device.getOps<AIE::TileOp>()) {
664 cols.insert(tile.colIndex());
665 if (tile.rowIndex() == 0)
666 shims[tile.colIndex()] = tile;
670 if (!llvm::all_of(cols, [&](int col) {
671 auto it = shims.find(col);
672 return it != shims.end() &&
673 hasRoutedResponse(device, it->second, tileIDMap);
676 if (clRouteShimCTRLToTCT != "all-tiles
")
678 return llvm::all_of(device.getOps<AIE::TileOp>(), [&](TileOp tile) {
679 return isRoutedThroughOnly(tile) ||
680 hasRoutedResponse(device, tile, tileIDMap);
684 // With route-shim-to-tile-ctrl, every tile applyOverlayToDevice sends
685 // requests to needs a routed request from its shim DMA channel, and that
686 // channel needs its shim DMA allocation. Tiles only routed through do not
687 // widen the columns and rows covered, as a first run never covered them.
688 static bool hasRoutedRequests(DeviceOp device,
689 DenseMap<TileID, int> &tileIDMap) {
690 llvm::DenseMap<TileID, TileOp> tiles;
691 llvm::DenseMap<int, int> maxRowOfCol;
692 std::optional<int> minCol, maxCol;
693 int maxOccupiedRow = 0;
694 for (auto tile : device.getOps<AIE::TileOp>()) {
695 int col = tile.colIndex(), row = tile.rowIndex();
696 tiles[{col, row}] = tile;
697 if (isRoutedThroughOnly(tile))
699 minCol = std::min(minCol.value_or(col), col);
700 maxCol = std::max(maxCol.value_or(col), col);
701 maxOccupiedRow = std::max(maxOccupiedRow, row);
702 maxRowOfCol[col] = std::max(maxRowOfCol.lookup(col), row);
704 if (!minCol || !maxCol)
706 auto rowToShimChanMap =
707 getRowToShimChanMap(device.getTargetModel(), WireBundle::DMA);
708 for (int col = *minCol; col <= *maxCol; col++) {
709 TileOp shimTile = tiles.lookup({col, 0});
712 auto it = maxRowOfCol.find(col);
713 int maxRow = it != maxRowOfCol.end() ? it->second : maxOccupiedRow;
714 for (int row = 0; row <= maxRow; row++) {
715 TileOp tile = tiles.lookup({col, row});
716 int chan = rowToShimChanMap[row];
718 !device.lookupSymbol(getCtrlPktAllocName(col, true, chan)) ||
719 !hasRoutedRequest(device, shimTile, chan, tile, tileIDMap))
726 // True when `device` already carries every route this pass would add.
727 // Responses alone do not complete an overlay that also routes requests.
728 // Responses are only skipped when route-shim-to-tct=disable leaves requests
729 // as the sole routes requested; with neither requested, they still decide.
730 bool deviceHasControlOverlay(DeviceOp device) {
731 auto tileIDMap = getTileToControllerIdMap(true, device.getTargetModel());
732 if ((clRouteShimCTRLToTCT != "disable
" || !clRouteShimDmaToTileCTRL) &&
733 !hasRoutedResponses(device, tileIDMap))
735 return !clRouteShimDmaToTileCTRL || hasRoutedRequests(device, tileIDMap);
738 AIE::PacketFlowOp createPacketFlowOp(OpBuilder &builder, Location loc,
739 int &flowID, Value source,
740 xilinx::AIE::WireBundle sourceBundle,
741 uint32_t sourceChannel, Value dest,
742 xilinx::AIE::WireBundle destBundle,
743 uint32_t destChannel,
744 mlir::BoolAttr keep_pkt_header = nullptr,
745 mlir::BoolAttr ctrl_pkt_flow = nullptr) {
746 OpBuilder::InsertionGuard guard(builder);
748 AIE::PacketFlowOp pktFlow = AIE::PacketFlowOp::create(
749 builder, loc, flowID++, keep_pkt_header, ctrl_pkt_flow);
750 Region &r_pktFlow = pktFlow.getPorts();
751 Block *b_pktFlow = builder.createBlock(&r_pktFlow);
752 builder.setInsertionPointToStart(b_pktFlow);
753 AIE::PacketSourceOp::create(builder, loc, source, sourceBundle,
755 AIE::PacketDestOp::create(builder, loc, dest, destBundle, destChannel);
756 AIE::EndOp::create(builder, loc);
760 // Get a vector of shim channels not reserved by any circuit-switched aie.flow
762 SmallVector<int> getAvailableShimChans(DeviceOp device, TileOp shimTile,
763 WireBundle shimWireBundle,
765 SmallVector<int> availableShimChans;
766 DenseMap<int, AIE::FlowOp> flowOpUsers;
767 const auto &targetModel = device.getTargetModel();
769 for (auto *user : shimTile.getResult().getUsers()) {
770 auto fOp = dyn_cast<AIE::FlowOp>(user);
773 if (isShimMM2S && fOp.getSource() == shimTile &&
774 fOp.getSourceBundle() == shimWireBundle)
775 flowOpUsers[fOp.getSourceChannel()] = fOp;
776 else if (!isShimMM2S && fOp.getDest() == shimTile &&
777 fOp.getDestBundle() == shimWireBundle)
778 flowOpUsers[fOp.getDestChannel()] = fOp;
780 int numShimChans = 0;
782 numShimChans = targetModel.getNumSourceShimMuxConnections(
783 shimTile.colIndex(), shimTile.rowIndex(), shimWireBundle);
785 numShimChans = targetModel.getNumDestShimMuxConnections(
786 shimTile.colIndex(), shimTile.rowIndex(), shimWireBundle);
787 for (int i = 0; i < numShimChans; i++) {
788 if (!flowOpUsers.count(i))
789 availableShimChans.push_back(i);
792 return availableShimChans;
795 // Create packet flows per col which moves control packets to and from shim
797 LogicalResult generatePacketFlowsForControl(
798 OpBuilder builder, DeviceOp device, TileOp shimTile,
799 WireBundle shimWireBundle, const SmallVector<AIE::TileOp> &ctrlTiles,
800 WireBundle ctrlWireBundle, int coreOrMemChanId,
801 DenseMap<TileID, int> tileIDMap, bool isShimMM2S) {
802 int ctrlPktFlowID = 0;
803 auto rowToShimChanMap =
804 getRowToShimChanMap(device.getTargetModel(), shimWireBundle);
805 // Get all available shim channels, to verify that the one being used is
807 auto availableShimChans =
808 getAvailableShimChans(device, shimTile, shimWireBundle, isShimMM2S);
809 // The overlay a device already carries -- from a user-written control flow,
810 // or from an earlier run of this pass, which aiecc does whenever the input
811 // was already overlaid -- must not be laid down a second time. A flow
812 // declared twice is rejected by DeviceOp::verify.
813 std::map<CtrlFlowKey, AIE::PacketFlowOp> existingCtrlFlows =
814 collectExistingCtrlFlows(device);
816 builder.setInsertionPoint(device.getBody()->getTerminator());
817 for (auto tOp : ctrlTiles) {
818 if (tOp->hasAttr("controller_id
"))
820 (int)tOp->getAttrOfType<AIE::PacketInfoAttr>("controller_id
")
823 ctrlPktFlowID = tileIDMap[{tOp.colIndex(), tOp.rowIndex()}];
824 // Check shim channel availability
825 if (!llvm::is_contained(availableShimChans,
826 rowToShimChanMap[tOp.rowIndex()])) {
828 "failed to generate column control overlay from shim dma to tile
"
829 "ctrl ports, because some shim mm2s dma channels were reserved
"
830 "from routing control packets.
");
834 auto keep_pkt_header = builder.getBoolAttr(true);
835 auto ctrl_pkt_flow = builder.getBoolAttr(true);
836 int shimChan = rowToShimChanMap[tOp.rowIndex()];
837 std::optional<CtrlFlowKey> key =
838 isShimMM2S ? tryGetCtrlFlowKey(ctrlPktFlowID, shimTile,
839 shimWireBundle, shimChan, tOp,
840 ctrlWireBundle, coreOrMemChanId)
841 : tryGetCtrlFlowKey(ctrlPktFlowID, tOp, ctrlWireBundle,
842 coreOrMemChanId, shimTile,
843 shimWireBundle, shimChan);
844 // Only the flow itself is affected by this; the shim DMA allocation
845 // below is emitted either way, guarded by its own name lookup.
846 auto it = key ? existingCtrlFlows.find(*key) : existingCtrlFlows.end();
847 if (it != existingCtrlFlows.end()) {
848 // The device already declares this control flow -- written by hand, or
849 // laid down by an earlier run of this pass over the same input, which
850 // aiecc does whenever its input was already overlaid. Declaring it a
851 // second time is the duplicate DeviceOp::verify rejects, so adopt the
852 // overlay's attributes onto the flow that is already there instead.
853 // They are what tells --aie-create-pathfinder-flows this is a control
854 // flow; that pass takes the last writer per destination port, so
855 // before this pass deduplicated, the copy it appended is what set
856 // them. Dropping them here would silently reroute the switchbox.
857 it->second.setKeepPktHeader(keep_pkt_header.getValue());
858 it->second.setPriorityRoute(ctrl_pkt_flow.getValue());
859 } else if (!isShimMM2S || !hasRoutedRequest(device, shimTile, shimChan,
861 // A request the switchboxes already route is not declared again; its
862 // shim DMA allocation below is still emitted.
863 AIE::PacketFlowOp created =
865 ? createPacketFlowOp(builder, tOp.getLoc(), ctrlPktFlowID,
866 shimTile, shimWireBundle, shimChan, tOp,
867 ctrlWireBundle, coreOrMemChanId,
868 keep_pkt_header, ctrl_pkt_flow)
869 : createPacketFlowOp(builder, tOp.getLoc(), ctrlPktFlowID, tOp,
870 ctrlWireBundle, coreOrMemChanId, shimTile,
871 shimWireBundle, shimChan, keep_pkt_header,
873 // Both endpoints are placed tiles here, so the key is always present;
874 // guarded rather than asserted so an unplaced one just means no
875 // deduplication, never a dropped flow.
877 existingCtrlFlows.try_emplace(*key, created);
880 // Generate shim dma alloc ops as handle for runtime sequence to pickup,
881 // when issuing control packets
882 if (shimWireBundle != WireBundle::DMA)
885 AIE::DMAChannelDir dir =
886 isShimMM2S ? AIE::DMAChannelDir::MM2S : AIE::DMAChannelDir::S2MM;
887 int chan = rowToShimChanMap[tOp.rowIndex()];
888 std::string dma_name =
889 getCtrlPktAllocName(shimTile.colIndex(), isShimMM2S, chan);
891 // check to see if ShimDMAAllocationOp already exists
892 if (device.lookupSymbol(dma_name))
895 AIE::ShimDMAAllocationOp::create(
896 builder, tOp.getLoc(), StringRef(dma_name), shimTile.getResult(), dir,
897 rowToShimChanMap[tOp.rowIndex()], false, nullptr);
902 // Get packet-flow op with the same source or destination
903 AIE::PacketFlowOp getPktFlowWithSameSrcOrDst(DeviceOp device, TileOp srcTile,
904 WireBundle srcBundle,
905 int srcChan, TileOp destTile,
906 WireBundle destBundle,
908 AIE::PacketFlowOp result = nullptr;
909 device.walk([&](AIE::PacketFlowOp fOp) {
910 for (auto srcOp : fOp.getOps<AIE::PacketSourceOp>()) {
911 if (srcOp.getTile() == srcTile && srcOp.getBundle() == srcBundle &&
912 srcOp.channelIndex() == srcChan) {
917 for (auto destOp : fOp.getOps<AIE::PacketDestOp>()) {
918 if (destOp.getTile() == destTile && destOp.getBundle() == destBundle &&
919 destOp.channelIndex() == destChan) {
929std::unique_ptr<OperationPass<DeviceOp>> AIE::createAIEAssignTileCtrlIDsPass() {
930 return std::make_unique<AIEAssignTileCtrlIDsPass>();
933std::unique_ptr<OperationPass<mlir::ModuleOp>>
934AIE::createAIEGenerateColumnControlOverlayPass() {
935 return std::make_unique<AIEGenerateColumnControlOverlayPass>();
938std::unique_ptr<OperationPass<mlir::ModuleOp>>
939AIE::createAIEGenerateColumnControlOverlayPass(
940 const AIEGenerateColumnControlOverlayOptions &options) {
941 return std::make_unique<AIEGenerateColumnControlOverlayPass>(options);
944void populateAIEColumnControlOverlay(DeviceOp &device) {}
DenseMap< int, int > getRowToShimChanMap(const AIETargetModel &targetModel, WireBundle bundle)
DenseMap< AIE::TileID, int > getTileToControllerIdMap(bool clColumnWiseUniqueIDs, const AIETargetModel &targetModel)
llvm::DenseMap< TileID, int > getTileToControllerIdMap(bool columnWiseUniqueIDs=true) const
Return a mapping from tile coordinates to controller IDs.
virtual uint32_t getNumSourceShimMuxConnections(int col, int row, WireBundle bundle) const =0
Return the number of sources of connections inside a shimmux.
virtual int rows() const =0
Return the number of rows in the device.
bool isShimNOCTile(int col, int row) const
Return true if the given tile is a ShimNOC tile.
virtual int columns() const =0
Return the number of columns in the device.
struct TileID { friend std::ostream &operator<<(std::ostream &os, const TileID &s) { os<< "TileID("<< s.col<< ", "<< s.row<< ")"; return os; } friend std::string to_string(const TileID &s) { std::ostringstream ss; ss<< s; return ss.str(); } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const TileID &s) { os<< to_string(s); return os; } inline bool operator<(const TileID &rhs) const { return std::tie(col, row)< std::tie(rhs.col, rhs.row); } bool operator==(const TileID &rhs) const { return std::tie(col, row)==std::tie(rhs.col, rhs.row); } bool operator!=(const TileID &rhs) const { return !(*this==rhs);} int col, row;} TileID
void getDependentDialects(DialectRegistry ®istry) const override
void runOnOperation() override
void getDependentDialects(DialectRegistry ®istry) const override
static void collectTileUnion(ArrayRef< DeviceOp > devices, llvm::SmallSetVector< AIE::TileID, 8 > &unionTiles, llvm::DenseMap< AIE::TileID, AIE::TileOp > &prototypeTile)
static void cloneMissingTiles(DeviceOp device, const llvm::SmallSetVector< AIE::TileID, 8 > &unionTiles, const llvm::DenseMap< AIE::TileID, AIE::TileOp > &prototypeTile)
void runOnOperation() override
static void shareTilesAcrossDevices(ArrayRef< DeviceOp > devices)
bool deviceHasControlOverlay(DeviceOp device)
LogicalResult applyOverlayToDevice(DeviceOp device, const llvm::SmallSet< AIE::TileID, 4 > &tokenTiles)
static llvm::SmallSet< AIE::TileID, 4 > collectTokenIssuingTiles(ArrayRef< DeviceOp > devices)
static bool deviceOptedOut(DeviceOp device)
LogicalResult createOverlayDevice(ModuleOp module, OpBuilder &builder, ArrayRef< DeviceOp > participating, const llvm::SmallSet< AIE::TileID, 4 > &tokenTiles)