MLIR-AIE 1.4.5.dev
AIEDMAChannelAnalysis.cpp
Go to the documentation of this file.
1//===- AIEDMAChannelAnalysis.cpp --------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2021-2022 Xilinx, Inc.
4// Copyright (C) 2022-2026 Advanced Micro Devices, Inc.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
11
12using namespace mlir;
13using namespace xilinx;
14using namespace xilinx::AIE;
15
16Value DMAChannelAnalysis::getTileKey(Value tile) {
17 auto tileOp = cast<TileLike>(tile.getDefiningOp());
18 auto col = tileOp.tryGetCol();
19 auto row = tileOp.tryGetRow();
20 if (!col || !row)
21 return tile;
22 return tilesByCoordinate.try_emplace({*col, *row}, tile).first->second;
23}
24
26 for (auto program : device.getOps<DmaBody>()) {
27 for (Block &block : program.getDmaBody()) {
28 for (auto start : block.getOps<DMAStartOp>()) {
29 // The route endpoint it names claims the channel; see assignChannels.
30 if (start.getEndpoint())
31 continue;
32 usedChannels.try_emplace(std::make_tuple(getTileKey(program.getTile()),
33 start.getChannelDir(),
34 start.getChannelIndex()),
35 start.getOperation());
36 }
37 }
38 }
39
40 // A flow ending at a tile's DMA claims that channel's stream even when no
41 // DMA body here starts it: its BDs may come from the runtime sequence alone.
42 // First-free assignment must skip it, but a pinned request may name it --
43 // that is the DMA feeding or draining the flow, as when allocation reruns
44 // over flows it lowered itself.
45 auto streamDMA = [&](Value tile, DMAChannelDir dir, int channel,
46 Operation *owner) {
47 streamedChannels.try_emplace(
48 std::make_tuple(getTileKey(tile), dir, channel), owner);
49 };
50 for (auto flowOp : device.getOps<FlowOp>()) {
51 if (flowOp.getSourceBundle() == WireBundle::Core) {
52 usedStreams.try_emplace({getTileKey(flowOp.getSource()),
53 DMAChannelDir::MM2S, flowOp.getSourceChannel()},
54 StreamClaim{flowOp, false});
55 }
56 if (flowOp.getDestBundle() == WireBundle::Core) {
57 usedStreams.try_emplace({getTileKey(flowOp.getDest()),
58 DMAChannelDir::S2MM, flowOp.getDestChannel()},
59 StreamClaim{flowOp, false});
60 }
61 if (flowOp.getSourceBundle() == WireBundle::DMA)
62 streamDMA(flowOp.getSource(), DMAChannelDir::MM2S,
63 flowOp.getSourceChannel(), flowOp);
64 if (flowOp.getDestBundle() == WireBundle::DMA)
65 streamDMA(flowOp.getDest(), DMAChannelDir::S2MM, flowOp.getDestChannel(),
66 flowOp);
67 }
68 for (auto packetFlow : device.getOps<PacketFlowOp>()) {
69 for (auto source : packetFlow.getOps<PacketSourceOp>())
70 if (source.getBundle() == WireBundle::DMA)
71 streamDMA(source.getTile(), DMAChannelDir::MM2S, source.getChannel(),
72 packetFlow);
73 for (auto dest : packetFlow.getOps<PacketDestOp>())
74 if (dest.getBundle() == WireBundle::DMA)
75 streamDMA(dest.getTile(), DMAChannelDir::S2MM, dest.getChannel(),
76 packetFlow);
77 }
78
79 // A task the runtime sequence configures on a channel by index programs that
80 // channel, as a DMA body would.
81 device.walk([&](AIEX::DMAConfigureTaskOp task) {
82 usedChannels.try_emplace(std::make_tuple(getTileKey(task.getTile()),
83 task.getDirection(),
84 (int)task.getChannel()),
85 task.getOperation());
86 });
87
88 for (auto flow : device.getOps<PacketFlowOp>()) {
89 Block &ports = flow.getPorts().front();
90 for (auto source : ports.getOps<PacketSourceOp>())
91 if (source.getBundle() == WireBundle::Core)
92 usedStreams.try_emplace({getTileKey(source.getTile()),
93 DMAChannelDir::MM2S, source.channelIndex()},
94 StreamClaim{source, true});
95 for (auto dest : ports.getOps<PacketDestOp>())
96 if (dest.getBundle() == WireBundle::Core)
97 usedStreams.try_emplace({getTileKey(dest.getTile()),
98 DMAChannelDir::S2MM, dest.channelIndex()},
99 StreamClaim{dest, true});
100 }
101
102 // Shim allocations reserve channels outside the DMA bodies above.
103 for (auto allocOp : device.getOps<ShimDMAAllocationOp>()) {
104 usedChannels.try_emplace(std::make_tuple(getTileKey(allocOp.getTile()),
105 allocOp.getChannelDir(),
106 (int)allocOp.getChannelIndex()),
107 allocOp.getOperation());
108 }
109}
110
112 TileLike tile, DMAChannelDir dir, bool requiresAdjacentTileAccessChannels) {
113 int maxChannelNum = (dir == DMAChannelDir::MM2S)
114 ? tile.getNumSourceConnections(WireBundle::DMA)
115 : tile.getNumDestConnections(WireBundle::DMA);
116
117 if (!requiresAdjacentTileAccessChannels)
118 return maxChannelNum;
119
120 std::optional<int> col = tile.tryGetCol();
121 std::optional<int> row = tile.tryGetRow();
122 const auto &targetModel = getTargetModel(tile);
123 if (col && row)
124 return std::min<int>(
125 maxChannelNum,
126 targetModel.getMaxChannelNumForAdjacentMemTile(*col, *row));
127
128 // Assignment before placement must be safe at every compatible position,
129 // not assume that unresolved coordinates remove neighbor restrictions.
130 bool foundPosition = false;
131 for (int c = 0; c < targetModel.columns(); ++c) {
132 if (col && c != *col)
133 continue;
134 for (int r = 0; r < targetModel.rows(); ++r) {
135 if ((row && r != *row) ||
136 targetModel.getTileType(c, r) != tile.getTileType())
137 continue;
138 foundPosition = true;
139 maxChannelNum = std::min<int>(
140 maxChannelNum, targetModel.getMaxChannelNumForAdjacentMemTile(c, r));
141 }
142 }
143
144 return foundPosition ? maxChannelNum : 0;
145}
146
148 TileLike tile, DMAChannelDir dir, bool requiresAdjacentTileAccessChannels,
149 Operation *owner) {
150 int limit = getDMAChannelLimit(tile, dir, requiresAdjacentTileAccessChannels);
151 for (int i = 0; i < limit; i++) {
152 if (isChannelFree(tile, dir, i) &&
153 reservePinnedChannel(tile, dir, i, owner) >= 0) {
154 return i;
155 }
156 }
157 return -1;
158}
159
160bool DMAChannelAnalysis::isChannelFree(TileLike tile, DMAChannelDir dir,
161 int channel) {
162 auto key = std::make_tuple(getTileKey(tile->getResult(0)), dir, channel);
163 return !usedChannels.contains(key) && !streamedChannels.contains(key);
164}
165
166int DMAChannelAnalysis::reservePinnedChannel(TileLike tile, DMAChannelDir dir,
167 int channel, Operation *owner) {
168 int maxChannelNum = getDMAChannelLimit(tile, dir, false);
169 if (channel < 0 || channel >= maxChannelNum) {
170 return -1;
171 }
172 return usedChannels
173 .try_emplace(std::make_tuple(getTileKey(tile->getResult(0)),
174 dir, channel),
175 owner)
176 .second
177 ? channel
178 : -1;
179}
180
182 DMAChannelDir dir,
183 int channel) {
184 auto key = std::make_tuple(getTileKey(tile->getResult(0)), dir, channel);
185 if (Operation *owner = usedChannels.lookup(key))
186 return owner;
187 return streamedChannels.lookup(key);
188}
189
190LogicalResult DMAChannelAnalysis::checkAIEStreamIndex(TileLike tile,
191 DMAChannel chan,
192 Operation *user,
193 bool packet) {
194 auto [it, inserted] = usedStreams.try_emplace(
195 {getTileKey(tile->getResult(0)), chan.direction, chan.channel},
196 StreamClaim{user, packet});
197 if (inserted || (packet && it->second.packet)) {
198 return success();
199 }
200 auto diag = user->emitOpError("Core ")
201 << (chan.direction == DMAChannelDir::MM2S ? "output " : "input ")
202 << chan.channel
203 << " is already in use on this tile; a core stream port carries "
204 "one circuit or only packet flows";
205 diag.attachNote(it->second.owner->getLoc()) << "the other stream is here";
206 return diag;
207}
int getDMAChannelIndex(TileLike tile, DMAChannelDir dir, bool requiresAdjacentTileAccessChannels, mlir::Operation *owner=nullptr)
Next free channel of tile in dir, or -1 when the tile has none left.
mlir::Operation * getDMAChannelOwner(TileLike tile, DMAChannelDir dir, int channel)
Operation reserving this channel, or else the flow routing it; null if neither.
bool isChannelFree(TileLike tile, DMAChannelDir dir, int channel)
Whether first-free assignment could hand out channel: nothing reserves it and no explicit flow routes...
int reservePinnedChannel(TileLike tile, DMAChannelDir dir, int channel, mlir::Operation *owner=nullptr)
Claim channel for (tile, dir) so first-free assignment cannot take it.
static int getDMAChannelLimit(TileLike tile, DMAChannelDir dir, bool requiresAdjacentTileAccessChannels)
Exclusive upper bound on channel indices eligible for this transfer.
mlir::LogicalResult checkAIEStreamIndex(TileLike tile, DMAChannel chan, mlir::Operation *user, bool packet=false)
Claim a raw stream port for user, reporting on it when the port is taken; packet users may share it w...
const AIETargetModel & getTargetModel(mlir::Operation *op)
struct DMAChannel { DMAChannelDir direction; int channel; bool operator==(const DMAChannel &rhs) const { return std::tie(direction, channel)==std::tie(rhs.direction, rhs.channel); } } DMAChannel
Definition AIEDialect.h:314