MLIR-AIE 1.4.5.dev
AIEStreamDependencyAnalysis.h
Go to the documentation of this file.
1//===- AIEStreamDependencyAnalysis.h ----------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
8#ifndef AIE_DIALECT_AIE_TRANSFORMS_AIESTREAMDEPENDENCYANALYSIS_H
9#define AIE_DIALECT_AIE_TRANSFORMS_AIESTREAMDEPENDENCYANALYSIS_H
10
12
13#include "llvm/ADT/DenseMap.h"
14#include "llvm/ADT/DenseSet.h"
15#include "llvm/ADT/SmallVector.h"
16
17#include <map>
18#include <optional>
19#include <string>
20#include <tuple>
21#include <vector>
22
23namespace xilinx::AIE {
24
25/// A tile port at the edge of the stream fabric.
29
30 bool operator==(const StreamEndpoint &rhs) const {
31 return std::tie(tile, port) == std::tie(rhs.tile, rhs.port);
32 }
33 bool operator!=(const StreamEndpoint &rhs) const { return !(*this == rhs); }
34};
35
36/// One DMA channel of one tile.
39 DMAChannelDir dir;
41
42 bool operator==(const TileDMAChannel &rhs) const {
43 return std::tie(tile, dir, channel) ==
44 std::tie(rhs.tile, rhs.dir, rhs.channel);
45 }
46 bool operator<(const TileDMAChannel &rhs) const {
47 return std::tie(tile, dir, channel) <
48 std::tie(rhs.tile, rhs.dir, rhs.channel);
49 }
50};
51
52/// The BD chain a DMA channel runs, as one aie.dma_start, aie.dma or runtime
53/// task programs it.
55 mlir::Operation *op;
57 llvm::SmallVector<mlir::Block *> bds{};
58 llvm::SmallVector<mlir::Region *> regions{};
59 /// Whether the BD chain runs forever.
60 bool loops = false;
61 /// The first of `bds` a looping chain returns to once it has run them all.
62 size_t loopStart = 0;
63 /// Times one start of the channel runs the chain: its repeat count plus one.
64 uint64_t passes = 1;
65};
66
67/// A switchbox a stream passes: the port it enters by and, where it is packet
68/// switched there, the arbiter it takes.
69struct StreamHop {
72 std::optional<int> arbiter;
73};
74
75/// One stream from a source tile port to a destination tile port, recovered
76/// from the physical switchbox and shim-mux configuration.
80 /// The packet id carried, if any.
81 std::optional<int> packetID;
82 /// The destination stores each packet's header along with its payload.
83 bool keepsPktHeader = false;
84 /// The switchboxes the stream passes, source first, where it is routed.
85 llvm::SmallVector<StreamHop, 8> hops{};
86 /// The stream carries every id that agrees with `packetID` on these bits.
87 int packetMask = ~0;
88
89 /// Whether what a BD sends with header `packet`, or with none, goes on this
90 /// stream.
91 bool carries(std::optional<PacketInfoAttr> packet) const {
92 return !packet || !packetID ||
93 ((packet->assignedId() ^ *packetID) & packetMask) == 0;
94 }
95};
96
97/// A cycle of waits packet streams can deadlock in, given their routes. A
98/// packet holds the arbiter of every switchbox it has entered until its last
99/// word leaves, so one stuck anywhere holds up whatever needs those arbiters,
100/// or queues behind it on a link.
101struct HoldCycle {
102 enum class Wait {
103 /// `waiting` queues behind `holding` on the port both enter `tile` by.
105 /// `waiting`, or `sharer` it queues behind, needs the arbiter `holding`
106 /// holds at `tile`; `sharer` enters by `sharerInput` and `holding` by
107 /// `holderInput`.
109 /// `waiting` fills its receiver, and draining that waits on `holding`.
111 };
112 struct Step {
114 size_t waiting;
115 size_t sharer;
116 size_t holding;
121 /// The wait holds however the arbiters are planned, as `sharer` and
122 /// `holding` go into a receiver they share at `tile`, or have one id and
123 /// go on as one from there.
124 bool forced = false;
125 };
126 llvm::SmallVector<Step, 4> steps;
127};
128
129/// Follows every stream from its source tile port through the configured
130/// switchboxes, using tile geometry for the links between them. A packet
131/// stream is traced once per id its source sends, as read off the BDs and
132/// runtime transfers that program the source; with none known, once per
133/// rule of the first switchbox it enters.
134std::vector<RoutedStream> traceRoutedStreams(DeviceOp device);
135
136/// The streams the aie.flow and aie.packet_flow ops ask for, one per source,
137/// destination and packet id, before any of them is routed.
138std::vector<RoutedStream> requestedStreams(DeviceOp device);
139
140/// Names the stream by its endpoints and packet id, e.g.
141/// "packet flow (0, 1) DMA:0 -> (0, 2) DMA:1 (id 3)".
142std::string describeStream(const RoutedStream &stream);
143
144/// How much data a stream carries over a run and how much a receiver takes in
145/// before it has to wait on another agent, read off BD lengths, repeat
146/// counts, lock initial values and runtime transfers.
148public:
149 StreamVolumeAnalysis(DeviceOp device, llvm::ArrayRef<RoutedStream> streams);
152
153 /// Bytes the stream's source sends with the stream's packet id, headers
154 /// included where the receiver keeps them, or nullopt when that is
155 /// unbounded or unknown. A BD chain that loops sends only what the lock
156 /// tokens other agents can ever release to it let it.
157 std::optional<uint64_t> sendVolume(const RoutedStream &stream) const;
158
159 /// Bytes the receiver at `endpoint` accepts before it waits on another
160 /// agent, or nullopt when it never does. Where the analysis gives up before
161 /// the receiver waits, the bytes it took in so far.
162 std::optional<uint64_t> receiveCapacity(const StreamEndpoint &endpoint) const;
163
164 /// Whether `streams` can send the receiver at `endpoint` more than it takes
165 /// in before it waits on another agent.
166 bool canFill(const StreamEndpoint &endpoint,
167 llvm::ArrayRef<RoutedStream> streams) const;
168
169 /// Whether the source both streams come from can send a packet of `then`
170 /// after one of `first`; nullopt if its program doesn't show or they come
171 /// from different sources.
172 std::optional<bool> maySendAfter(const RoutedStream &first,
173 const RoutedStream &then) const;
174
175private:
176 /// Bytes the looping BD chain `program` sends before an acquire runs out of
177 /// tokens, counting each BD as `bytesOf` says.
178 std::optional<uint64_t>
179 loopedVolume(const DmaChannelProgram &program,
180 llvm::function_ref<uint64_t(DMABDOp)> bytesOf) const;
181 /// Visits, in order, the BDs the looping BD chain `program` runs until an
182 /// acquire runs out of tokens. False if it cannot tell when that is.
183 bool walkLoop(const DmaChannelProgram &program,
184 llvm::function_ref<void(DMABDOp)> visit) const;
185 /// Tokens every agent but `self` can release to `lock` over a run. A core
186 /// runs its body once.
187 std::optional<uint64_t> tokensFromOthers(LockOp lock,
188 const DmaChannelProgram *self) const;
189 std::optional<uint64_t> releasesOver(const DmaChannelProgram &program,
190 LockOp lock) const;
191
192 mutable DeviceOp device;
193 llvm::ArrayRef<RoutedStream> streams;
194 std::map<TileDMAChannel, llvm::SmallVector<DmaChannelProgram, 1>> programs;
195 /// The npu.dma_memcpy_nd ops that run on each shim channel.
196 std::map<TileDMAChannel, llvm::SmallVector<mlir::Operation *>> memcpys;
197 /// The channel program each use_lock in a BD chain belongs to.
198 llvm::DenseMap<mlir::Operation *, const DmaChannelProgram *> lockUseProgram;
199 mutable llvm::DenseSet<const DmaChannelProgram *> visiting;
200};
201
202/// Which agent waits on which. An agent is a core, one DMA channel, or the
203/// controller of a tile, which sends the task-complete tokens the host waits
204/// for on its TileControl port. P waits on Q when P acquires a lock Q
205/// releases, when P sends a stream Q receives or the reverse, or when P is a
206/// runtime-issued channel the host issues only after waiting on Q. Waiting on
207/// a channel waits on the controllers of its column too, whose tokens tell
208/// the host it is done. A channel with no program in the design may wait on
209/// anything on its tile. A receiving channel that takes in all it is sent
210/// before its locks run out waits on no lock. Program order within an agent
211/// is not modeled. A DMA receiver taking one sender's data waits on none of
212/// its other DMA senders: a DMA channel sends a packet whole once it starts,
213/// so it never holds the receiver while waiting on anything. A core can pause
214/// one input while waiting on another.
216public:
217 /// A core, a DMA channel, or a tile's controller. A core's or controller's
218 /// `dma` names only its tile.
219 struct Agent {
220 enum class Kind { Channel, Core, Controller };
223 bool onShim = false;
224
225 static Agent core(TileID tile) {
226 return {{tile, DMAChannelDir::S2MM, 0}, Kind::Core};
227 }
228 static Agent controller(TileID tile) {
229 return {{tile, DMAChannelDir::S2MM, 0}, Kind::Controller};
230 }
231 static Agent channel(const TileDMAChannel &dma) { return {dma}; }
232 /// Each agent at a stream endpoint: the core, DMA channel or controller
233 /// pushing data into (`sending`) or pulling data out of the fabric there.
234 static std::optional<Agent> at(const StreamEndpoint &endpoint,
235 bool sending);
236 };
237
238 StreamWaitGraph(DeviceOp device, llvm::ArrayRef<RoutedStream> streams,
239 const StreamVolumeAnalysis &volumes);
240
241 /// The agent pushing data into (`sending`) or pulling data out of the
242 /// fabric at `endpoint`, if the endpoint has one.
243 std::optional<unsigned> agentAt(const StreamEndpoint &endpoint,
244 bool sending) const;
245
246 /// Agents whose progress frees room in `agent` for more incoming data: a
247 /// core frees itself, a receiving channel waits on whoever releases the
248 /// locks it acquires and, if the host issues it, on what the host waits for
249 /// first.
250 llvm::SmallVector<unsigned> drainersOf(unsigned agent) const;
251
252 /// A shortest chain of waits from an agent in `from` to one in `targets`
253 /// that avoids `avoid`, both ends included; empty when there is none.
254 llvm::SmallVector<unsigned> waitChain(llvm::ArrayRef<unsigned> from,
255 llvm::ArrayRef<unsigned> targets,
256 llvm::ArrayRef<unsigned> avoid) const;
257
258 /// Whether the design programs this agent, so its waits are known rather
259 /// than assumed.
260 bool isModeled(unsigned id) const { return modeled.contains(id); }
261
262 const Agent &getAgent(unsigned id) const { return agents[id]; }
263 std::string describe(unsigned id) const;
264
265private:
266 enum class EdgeKind { Lock, Stream, Host };
267 struct Edge {
268 unsigned to;
269 EdgeKind kind;
270 /// A stream edge from a sender to the receiver it waits on to take its
271 /// data, or from a receiver to the sender it waits on for data.
272 bool sends = false;
273 bool receives = false;
274 };
275
276 unsigned getOrCreate(const Agent &agent);
277 std::optional<unsigned> lookup(const Agent &agent) const;
278 void addEdge(unsigned from, unsigned to, EdgeKind kind, bool sends = false,
279 bool receives = false);
280
281 std::vector<Agent> agents;
282 std::vector<llvm::SmallVector<Edge, 4>> edges;
283 std::map<std::pair<Agent::Kind, TileDMAChannel>, unsigned> agentIDs;
284 llvm::DenseSet<unsigned> modeled;
285};
286
287/// Which streams can deadlock against each other if they share an arbiter or
288/// a link. A packet holds its arbiter grant until tlast, so a stream stalled
289/// at a full receiver holds up whatever else waits on that grant.
291public:
292 /// `streams` must outlive the analysis.
293 StreamDeadlockAnalysis(DeviceOp device, llvm::ArrayRef<RoutedStream> streams);
294
295 /// Whether stream `f`, stalled at its receiver, can keep stream `g` from
296 /// ever arriving: `f` carries more than its receiver takes in before
297 /// waiting, and draining that receiver waits on `g`.
298 bool canBlock(size_t f, size_t g) const;
299
300 /// Whether stream `f` carries nothing, so it neither holds nor waits.
301 bool silent(size_t f) const;
302
303 /// Why `f` can block `g`: the chain of waits from `f`'s receiver to `g`, and
304 /// which links of it are assumed for lack of information. Requires
305 /// canBlock(f, g).
306 std::string explainBlock(size_t f, size_t g) const;
307
308 /// What explainBlock(f, g) takes on trust rather than reads off the design,
309 /// one sentence each. Requires canBlock(f, g).
310 llvm::SmallVector<std::string> assumptions(size_t f, size_t g) const;
311
312private:
313 bool canStall(size_t f) const;
314 llvm::SmallVector<unsigned> blockingChain(size_t f, size_t g) const;
315
316 llvm::ArrayRef<RoutedStream> streams;
317 StreamVolumeAnalysis volumes;
318 StreamWaitGraph graph;
319 /// canStall, silent and canBlock, once asked.
320 mutable std::vector<std::optional<bool>> stalls, silence;
321 mutable llvm::DenseMap<std::pair<size_t, size_t>, bool> blocks;
322};
323
324/// The streams a device asks for or already routes, and which pairs of them
325/// must not share an arbiter or a link. The analysis runs on the first query.
327public:
328 explicit StreamConflicts(DeviceOp device);
331
332 llvm::ArrayRef<RoutedStream> getStreams() const { return streams; }
333
334 /// The streams the flow ops ask for, which come first in getStreams().
335 llvm::ArrayRef<RoutedStream> getRequestedStreams() const {
336 return llvm::ArrayRef(streams).take_front(numRequested);
337 }
338
339 /// Whether `s` and `t` can deadlock if they share an arbiter or a link.
340 /// Streams from one source are serialized there anyway, and streams into
341 /// one destination already wait on each other there, so neither conflicts.
342 /// The packets one source sends with one id move down every branch as one,
343 /// so the same holds for any two streams of such trees.
344 bool conflict(size_t s, size_t t) const;
345
346 /// Whether a routing that puts packet streams `s` and `t` on one arbiter
347 /// anywhere has a hold cycle: they conflict, or the tree of one, stuck at a
348 /// receiver, waits on the other through trees that each block the next.
349 bool mustSeparate(size_t s, size_t t) const;
350
351 /// Why `s` and `t` conflict. Requires mustSeparate(s, t), or (s, t) from
352 /// unavoidable().
353 std::string explain(size_t s, size_t t) const;
354
355 /// The requested streams where the first can hold up the second however
356 /// they are routed, as one source or receiver already orders them. Only
357 /// pairs with a packet stream count: circuits take no arbiter, so a wait
358 /// between two of them is the design's, not a hazard of any routing. Nor do
359 /// pairs that need StreamDeadlockAnalysis::assumptions: the routing cannot
360 /// help those either, and the design may well not do what is assumed.
361 llvm::SmallVector<std::pair<size_t, size_t>> unavoidable() const;
362
363 /// A cycle of waits the packet streams can deadlock in when routed along
364 /// `routes`, indexed like getStreams(), that the routing of some requested
365 /// stream takes part in; nullopt when there is none. Waits between trees
366 /// at the master port of a receiver they share hold however they are
367 /// routed, so they count only with `forcedWaits`. With `definite`, drains
368 /// that need StreamDeadlockAnalysis::assumptions do not count.
369 std::optional<HoldCycle>
370 holdCycle(llvm::ArrayRef<llvm::SmallVector<StreamHop, 8>> routes,
371 bool forcedWaits = false, bool definite = false) const;
372
373 /// The waits of `cycle`, one sentence each.
374 std::string explain(const HoldCycle &cycle) const;
375
376private:
377 bool blocks(size_t s, size_t t) const;
378 bool related(size_t s, size_t t) const;
379 /// What makes a packet stream one with the others of its tree: the same
380 /// source, the same id, and whether a flow op asks for it.
381 using TreeKey = std::tuple<TileID, Port, int, bool>;
382 /// The tree stream `s` belongs to; nullopt for a circuit stream.
383 std::optional<TreeKey> treeKey(size_t s) const;
384 /// The packet trees tree `a` waits on through trees that each block the
385 /// next, each with the pair of streams, the first of the tree before it,
386 /// that it was reached by.
387 const llvm::DenseMap<size_t, std::pair<size_t, size_t>> &
388 waitsFrom(size_t a) const;
389 const StreamDeadlockAnalysis &getAnalysis() const;
390
391 DeviceOp device;
392 std::vector<RoutedStream> streams;
393 size_t numRequested;
394 /// The streams of each tree, and the tree of each stream.
395 std::vector<llvm::SmallVector<size_t, 2>> treeMembers;
396 std::vector<size_t> treeOf;
397 mutable std::vector<
398 std::optional<llvm::DenseMap<size_t, std::pair<size_t, size_t>>>>
399 waits;
400 mutable std::optional<StreamDeadlockAnalysis> analysis;
401};
402
403} // namespace xilinx::AIE
404
405#endif // AIE_DIALECT_AIE_TRANSFORMS_AIESTREAMDEPENDENCYANALYSIS_H
llvm::ArrayRef< RoutedStream > getRequestedStreams() const
The streams the flow ops ask for, which come first in getStreams().
StreamConflicts & operator=(const StreamConflicts &)=delete
llvm::SmallVector< std::pair< size_t, size_t > > unavoidable() const
The requested streams where the first can hold up the second however they are routed,...
bool mustSeparate(size_t s, size_t t) const
Whether a routing that puts packet streams s and t on one arbiter anywhere has a hold cycle: they con...
StreamConflicts(const StreamConflicts &)=delete
llvm::ArrayRef< RoutedStream > getStreams() const
bool conflict(size_t s, size_t t) const
Whether s and t can deadlock if they share an arbiter or a link.
std::optional< HoldCycle > holdCycle(llvm::ArrayRef< llvm::SmallVector< StreamHop, 8 > > routes, bool forcedWaits=false, bool definite=false) const
A cycle of waits the packet streams can deadlock in when routed along routes, indexed like getStreams...
std::string explain(size_t s, size_t t) const
Why s and t conflict.
Which streams can deadlock against each other if they share an arbiter or a link.
bool canBlock(size_t f, size_t g) const
Whether stream f, stalled at its receiver, can keep stream g from ever arriving: f carries more than ...
std::string explainBlock(size_t f, size_t g) const
Why f can block g: the chain of waits from f's receiver to g, and which links of it are assumed for l...
bool silent(size_t f) const
Whether stream f carries nothing, so it neither holds nor waits.
llvm::SmallVector< std::string > assumptions(size_t f, size_t g) const
What explainBlock(f, g) takes on trust rather than reads off the design, one sentence each.
StreamDeadlockAnalysis(DeviceOp device, llvm::ArrayRef< RoutedStream > streams)
streams must outlive the analysis.
How much data a stream carries over a run and how much a receiver takes in before it has to wait on a...
StreamVolumeAnalysis(DeviceOp device, llvm::ArrayRef< RoutedStream > streams)
std::optional< uint64_t > sendVolume(const RoutedStream &stream) const
Bytes the stream's source sends with the stream's packet id, headers included where the receiver keep...
StreamVolumeAnalysis & operator=(const StreamVolumeAnalysis &)=delete
std::optional< uint64_t > receiveCapacity(const StreamEndpoint &endpoint) const
Bytes the receiver at endpoint accepts before it waits on another agent, or nullopt when it never doe...
StreamVolumeAnalysis(const StreamVolumeAnalysis &)=delete
bool canFill(const StreamEndpoint &endpoint, llvm::ArrayRef< RoutedStream > streams) const
Whether streams can send the receiver at endpoint more than it takes in before it waits on another ag...
std::optional< bool > maySendAfter(const RoutedStream &first, const RoutedStream &then) const
Whether the source both streams come from can send a packet of then after one of first; nullopt if it...
std::optional< unsigned > agentAt(const StreamEndpoint &endpoint, bool sending) const
The agent pushing data into (sending) or pulling data out of the fabric at endpoint,...
std::string describe(unsigned id) const
llvm::SmallVector< unsigned > waitChain(llvm::ArrayRef< unsigned > from, llvm::ArrayRef< unsigned > targets, llvm::ArrayRef< unsigned > avoid) const
A shortest chain of waits from an agent in from to one in targets that avoids avoid,...
llvm::SmallVector< unsigned > drainersOf(unsigned agent) const
Agents whose progress frees room in agent for more incoming data: a core frees itself,...
const Agent & getAgent(unsigned id) const
bool isModeled(unsigned id) const
Whether the design programs this agent, so its waits are known rather than assumed.
StreamWaitGraph(DeviceOp device, llvm::ArrayRef< RoutedStream > streams, const StreamVolumeAnalysis &volumes)
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
std::string describeStream(const RoutedStream &stream)
Names the stream by its endpoints and packet id, e.g.
std::vector< RoutedStream > requestedStreams(DeviceOp device)
The streams the aie.flow and aie.packet_flow ops ask for, one per source, destination and packet id,...
std::vector< RoutedStream > traceRoutedStreams(DeviceOp device)
Follows every stream from its source tile port through the configured switchboxes,...
struct Port { WireBundle bundle; int channel; bool operator==(const Port &rhs) const { return std::tie(bundle, channel)==std::tie(rhs.bundle, rhs.channel); } bool operator!=(const Port &rhs) const { return !(*this==rhs);} bool operator<(const Port &rhs) const { return std::tie(bundle, channel)< std::tie(rhs.bundle, rhs.channel); } friend std::ostream &operator<<(std::ostream &os, const Port &port) { os<< "("; os<< stringifyWireBundle(port.bundle).str(); os<< ": "<< std::to_string(port.channel)<< ")"; return os; } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Port &port) { os<< to_string(port); return os; } } Port
Definition AIEDialect.h:265
The BD chain a DMA channel runs, as one aie.dma_start, aie.dma or runtime task programs it.
llvm::SmallVector< mlir::Region * > regions
uint64_t passes
Times one start of the channel runs the chain: its repeat count plus one.
bool loops
Whether the BD chain runs forever.
size_t loopStart
The first of bds a looping chain returns to once it has run them all.
llvm::SmallVector< mlir::Block * > bds
bool forced
The wait holds however the arbiters are planned, as sharer and holding go into a receiver they share ...
A cycle of waits packet streams can deadlock in, given their routes.
llvm::SmallVector< Step, 4 > steps
@ Drain
waiting fills its receiver, and draining that waits on holding.
@ Link
waiting queues behind holding on the port both enter tile by.
@ Arbiter
waiting, or sharer it queues behind, needs the arbiter holding holds at tile; sharer enters by sharer...
One stream from a source tile port to a destination tile port, recovered from the physical switchbox ...
llvm::SmallVector< StreamHop, 8 > hops
The switchboxes the stream passes, source first, where it is routed.
bool carries(std::optional< PacketInfoAttr > packet) const
Whether what a BD sends with header packet, or with none, goes on this stream.
int packetMask
The stream carries every id that agrees with packetID on these bits.
bool keepsPktHeader
The destination stores each packet's header along with its payload.
std::optional< int > packetID
The packet id carried, if any.
A tile port at the edge of the stream fabric.
bool operator==(const StreamEndpoint &rhs) const
bool operator!=(const StreamEndpoint &rhs) const
A switchbox a stream passes: the port it enters by and, where it is packet switched there,...
A core, a DMA channel, or a tile's controller.
static std::optional< Agent > at(const StreamEndpoint &endpoint, bool sending)
Each agent at a stream endpoint: the core, DMA channel or controller pushing data into (sending) or p...
static Agent channel(const TileDMAChannel &dma)
bool operator==(const TileDMAChannel &rhs) const
bool operator<(const TileDMAChannel &rhs) const