MLIR-AIE 1.4.6.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 (AIEDialect::ShareableWithAttrHelper(allocOp->getContext())
105 .isAttrPresent(allocOp)
106 ? shareableChannels
107 : usedChannels)
108 .try_emplace(std::make_tuple(getTileKey(allocOp.getTile()),
109 allocOp.getChannelDir(),
110 (int)allocOp.getChannelIndex()),
111 allocOp.getOperation());
112 }
113}
114
116 TileLike tile, DMAChannelDir dir, bool requiresAdjacentTileAccessChannels) {
117 std::optional<int> col = tile.tryGetCol();
118 std::optional<int> row = tile.tryGetRow();
119 const auto &targetModel = getTargetModel(tile);
120 // A tile that is not fully placed stands for any tile of its type.
121 TileID id =
122 col && row
123 ? TileID{*col, *row}
124 : cast<LogicalTileOp>(tile.getOperation()).getCanonicalTileID();
125 int maxChannelNum = targetModel.getNumDMAChannels(id.col, id.row, dir);
126
127 if (!requiresAdjacentTileAccessChannels)
128 return maxChannelNum;
129
130 if (col && row)
131 return std::min<int>(
132 maxChannelNum,
133 targetModel.getMaxChannelNumForAdjacentMemTile(*col, *row));
134
135 // Assignment before placement must be safe at every compatible position,
136 // not assume that unresolved coordinates remove neighbor restrictions.
137 bool foundPosition = false;
138 for (int c = 0; c < targetModel.columns(); ++c) {
139 if (col && c != *col)
140 continue;
141 for (int r = 0; r < targetModel.rows(); ++r) {
142 if ((row && r != *row) ||
143 targetModel.getTileType(c, r) != tile.getTileType())
144 continue;
145 foundPosition = true;
146 maxChannelNum = std::min<int>(
147 maxChannelNum, targetModel.getMaxChannelNumForAdjacentMemTile(c, r));
148 }
149 }
150
151 return foundPosition ? maxChannelNum : 0;
152}
153
155 TileLike tile, DMAChannelDir dir, bool requiresAdjacentTileAccessChannels,
156 Operation *owner) {
157 int limit = getDMAChannelLimit(tile, dir, requiresAdjacentTileAccessChannels);
158 for (int i = 0; i < limit; i++) {
159 if (isChannelFree(tile, dir, i) &&
160 reservePinnedChannel(tile, dir, i, owner) >= 0) {
161 return i;
162 }
163 }
164 return -1;
165}
166
167bool DMAChannelAnalysis::isChannelFree(TileLike tile, DMAChannelDir dir,
168 int channel) {
169 auto key = std::make_tuple(getTileKey(tile->getResult(0)), dir, channel);
170 return !usedChannels.contains(key) && !streamedChannels.contains(key) &&
171 !shareableChannels.contains(key);
172}
173
174int DMAChannelAnalysis::reservePinnedChannel(TileLike tile, DMAChannelDir dir,
175 int channel, Operation *owner,
176 bool shareable) {
177 int maxChannelNum = getDMAChannelLimit(tile, dir, false);
178 if (channel < 0 || channel >= maxChannelNum) {
179 return -1;
180 }
181 auto key = std::make_tuple(getTileKey(tile->getResult(0)), dir, channel);
182 if (auto *reservation = shareableChannels.lookup(key)) {
183 if (!shareable)
184 return -1;
185 auto allowed =
186 AIEDialect::ShareableWithAttrHelper(reservation->getContext())
187 .getAttr(reservation);
188 auto name =
189 owner ? owner->getAttrOfType<StringAttr>("sym_name") : StringAttr();
190 if (!allowed || !name || llvm::none_of(allowed, [&](Attribute attr) {
191 auto symbol = dyn_cast<FlatSymbolRefAttr>(attr);
192 return symbol && symbol.getAttr() == name;
193 }))
194 return -1;
195 }
196 if (usedChannels.try_emplace(key, owner).second) {
197 if (shareable)
198 sharedChannels.insert(key);
199 return channel;
200 }
201 // Packet-switched sources share a channel: the runtime writes its
202 // descriptors one transfer at a time, each under its own header.
203 if (shareable && sharedChannels.contains(key))
204 return channel;
205 return -1;
206}
207
209 DMAChannelDir dir,
210 int channel) {
211 auto key = std::make_tuple(getTileKey(tile->getResult(0)), dir, channel);
212 if (Operation *owner = usedChannels.lookup(key))
213 return owner;
214 if (Operation *owner = streamedChannels.lookup(key))
215 return owner;
216 return shareableChannels.lookup(key);
217}
218
219LogicalResult DMAChannelAnalysis::checkAIEStreamIndex(TileLike tile,
220 DMAChannel chan,
221 Operation *user,
222 bool packet) {
223 auto [it, inserted] = usedStreams.try_emplace(
224 {getTileKey(tile->getResult(0)), chan.direction, chan.channel},
225 StreamClaim{user, packet});
226 if (inserted || (packet && it->second.packet)) {
227 return success();
228 }
229 auto diag = user->emitOpError("Core ")
230 << (chan.direction == DMAChannelDir::MM2S ? "output " : "input ")
231 << chan.channel
232 << " is already in use on this tile; a core stream port carries "
233 "one circuit or only packet flows";
234 diag.attachNote(it->second.owner->getLoc()) << "the other stream is here";
235 return diag;
236}
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 or routing this channel; null if none.
bool isChannelFree(TileLike tile, DMAChannelDir dir, int channel)
Whether first-free assignment could hand out channel: nothing reserves it and no explicit flow routes...
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...
int reservePinnedChannel(TileLike tile, DMAChannelDir dir, int channel, mlir::Operation *owner=nullptr, bool shareable=false)
Claim channel for (tile, dir) so first-free assignment cannot take it.
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
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