MLIR-AIE 1.4.5.dev
AIEInsertTraceFlows.cpp
Go to the documentation of this file.
1//===- AIEInsertTraceFlows.cpp ----------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
12
13#include "mlir/Dialect/Arith/IR/Arith.h"
14#include "mlir/IR/Attributes.h"
15#include "mlir/Pass/Pass.h"
16
17#include "llvm/ADT/STLExtras.h"
18
19#include <climits>
20#include <map>
21#include <set>
22
23namespace xilinx::AIE {
24#define GEN_PASS_DEF_AIEINSERTTRACEFLOWS
25#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
26} // namespace xilinx::AIE
27
28using namespace mlir;
29using namespace xilinx;
30using namespace xilinx::AIE;
31
32namespace {
33
34struct TraceInfo {
35 TraceOp traceOp;
36 TileOp tile;
37 int packetId;
38 TracePacketType packetType;
39 WireBundle tracePort; // Trace:0 (core) or Trace:1 (mem)
40 int traceChannel; // Port number (0 for core, 1 for mem)
41 std::optional<int>
42 startBroadcast; // Broadcast channel for timer sync (if any)
43 std::optional<int> stopBroadcast; // Broadcast channel for trace stop (if any)
44};
45
46/// Per-channel DMA resource allocation.
47struct ChannelDescriptor {
48 int channel; // S2MM channel number
49 int bdId; // Buffer descriptor ID
50};
51
52struct ShimInfo {
53 TileOp shimTile;
54 int channel; // S2MM channel
55 int bdId; // Buffer descriptor ID
56 std::vector<TraceInfo> traceSources; // All traces routed to this shim
57 std::optional<int> startBroadcast; // Broadcast to trigger for start
58 std::optional<int> stopBroadcast; // Broadcast to trigger for stop
59 std::vector<ChannelDescriptor> channels; // Per-channel descriptors
60 std::vector<int> traceChannelAssignment; // Per-trace index into channels
61};
62
63/// The trace buffer of one runtime sequence. The trace units and the routes
64/// belong to the device; the buffer belongs to the sequence.
65struct SequenceTraceConfig {
66 RuntimeSequenceOp seq;
67 int bufferSizeBytes;
68 bool reuseOutputBuffer;
69 int bufferOffset; // byte offset of the trace data in the host buffer
70};
71
72// A dynamic (runtime-sized) runtime sequence carries its transfer sizes as
73// scalar block arguments (the dynamic ABI); its memref args are typed at
74// maximum capacity, not the runtime data size. Any non-memref block argument
75// marks it.
76static bool isDynamicRuntimeSequence(RuntimeSequenceOp seq) {
77 return llvm::any_of(seq.getBody().getArguments(), [](BlockArgument arg) {
78 return !isa<MemRefType>(arg.getType());
79 });
80}
81
82struct AIEInsertTraceFlowsPass
83 : xilinx::AIE::impl::AIEInsertTraceFlowsBase<AIEInsertTraceFlowsPass> {
84
85 void runOnOperation() override {
86 DeviceOp device = getOperation();
87 OpBuilder builder(device);
88 const auto &targetModel = device.getTargetModel();
89
90 // Verify no LogicalTileOps remain — placement must run before this pass
91 bool hasLogicalTile = false;
92 device.walk([&](LogicalTileOp op) {
93 op.emitError() << "LogicalTileOp must be resolved to TileOp before "
94 "running -aie-insert-trace-flows (run -aie-place-tiles "
95 "first)";
96 hasLogicalTile = true;
97 });
98 if (hasLogicalTile)
99 return signalPassFailure();
100
101 // Phase 1: Collect all trace operations
102 SmallVector<TraceOp> traces;
103 device.walk([&](TraceOp trace) { traces.push_back(trace); });
104
105 if (traces.empty())
106 return;
107
108 // Phase 1b: Collect every runtime_sequence with an aie.trace.host_config.
109 // Each one drives the trace units on its own dispatch, so each one gets a
110 // trace buffer and a shim DMA program.
111 SmallVector<RuntimeSequenceOp> runtimeSeqs;
112 SmallVector<TraceHostConfigOp> hostConfigs;
113 RuntimeSequenceOp firstSeq = nullptr;
114 for (auto seq : device.getBody()->getOps<RuntimeSequenceOp>()) {
115 if (!firstSeq)
116 firstSeq = seq;
117 for (auto hc : seq.getBody().front().getOps<TraceHostConfigOp>()) {
118 runtimeSeqs.push_back(seq);
119 hostConfigs.push_back(hc);
120 break;
121 }
122 }
123
124 // Require runtime_sequence when trace ops are present
125 if (!firstSeq) {
126 device.emitError()
127 << "aie.trace ops found but no runtime_sequence defined";
128 return signalPassFailure();
129 }
130
131 // Require trace.host_config in runtime_sequence
132 if (runtimeSeqs.empty()) {
133 firstSeq.emitError()
134 << "runtime_sequence with traces requires aie.trace.host_config";
135 return signalPassFailure();
136 }
137
138 // The packet flows and the stream switch configuration reach the hardware
139 // through the device, so the trace data of every sequence leaves the array
140 // through the same shim.
141 TraceHostConfigOp hostConfig = hostConfigs.front();
142 auto routing = hostConfig.getRouting();
143 int egressShimColFromIR = hostConfig.getEgressShimCol();
144 for (auto other : ArrayRef(hostConfigs).drop_front()) {
145 if (other.getRouting() == routing &&
146 other.getEgressShimCol() == egressShimColFromIR)
147 continue;
148 InFlightDiagnostic err =
149 other.emitError()
150 << "aie.trace.host_config routes trace data differently from another "
151 "runtime_sequence in this device; the routes live in the device's "
152 "stream switches, so every sequence must name the same routing "
153 "and egress column";
154 err.attachNote(hostConfig.getLoc()) << "first aie.trace.host_config here";
155 return signalPassFailure();
156 }
157
158 // The trace buffer is a host buffer, i.e. an argument of the runtime
159 // sequence.
160 //
161 // - Dedicated (default): append a fresh argument to the runtime sequence
162 // for the trace buffer. It lands at the tail, so enabling trace never
163 // perturbs the indices of the data arguments.
164 // - Reuse-output: trace data is written into the tail of the last
165 // existing argument (an output buffer), saving a host buffer. No new
166 // argument is added; the offset skips past the output data.
167 //
168 // Buffer size and reuse mode reach the hardware through the buffer
169 // descriptor that each sequence writes, so each sequence sets its own.
170 SmallVector<SequenceTraceConfig> seqConfigs;
171 for (auto [seq, hc] : llvm::zip(runtimeSeqs, hostConfigs)) {
172 SequenceTraceConfig cfg;
173 cfg.seq = seq;
174 cfg.bufferSizeBytes = hc.getBufferSize();
175 cfg.reuseOutputBuffer = hc.getReuseOutputBuffer();
176 cfg.bufferOffset = 0;
177 if (cfg.reuseOutputBuffer) {
178 auto args = seq.getBody().getArguments();
179 if (args.empty()) {
180 seq.emitError() << "trace.host_config reuse_output_buffer "
181 "requires the runtime_sequence to have at "
182 "least one argument to reuse";
183 return signalPassFailure();
184 }
185 if (isDynamicRuntimeSequence(seq)) {
186 seq.emitError()
187 << "trace.host_config reuse_output_buffer=true cannot be used "
188 "with a dynamic (runtime-sized) runtime_sequence: the trace "
189 "offset would be computed from the last tensor's maximum "
190 "static size, not its runtime transfer size, so trace data "
191 "would be written past the output buffer. Use a separate "
192 "trace buffer instead (reuse_output_buffer=false)";
193 return signalPassFailure();
194 }
195 auto memrefType = cast<MemRefType>(args.back().getType());
196 cfg.bufferOffset = memrefType.getNumElements() *
197 (memrefType.getElementTypeBitWidth() / 8);
198 }
199 seqConfigs.push_back(cfg);
200 }
201
202 for (auto hc : hostConfigs)
203 hc.erase();
204
205 // Phase 2: Analyze traces and allocate resources
206 std::vector<TraceInfo> traceInfos;
207
208 // Precompute auto-allocated packet IDs in (col, row) order. Walking
209 // `traces` in IR order would make IDs depend on the order trace ops
210 // were emitted, which in turn depends on the placer's worker-to-tile
211 // mapping. Two placements that produce the same tile set would then
212 // get different trace overlays and exercise different routing-rule
213 // layouts -- a coupling that has produced false-match routing
214 // failures (e.g. mask=17 matching id 4 on a column-0 switch port).
215 // Sorting by (col, row) makes the id-to-tile binding a pure
216 // function of the active trace tile set.
217 llvm::DenseMap<Operation *, int> autoPacketIds;
218 {
219 // Wrapping past the max would alias two traces onto the same id.
220 const int kMaxPacketId = device.getTargetModel().getMaxPacketId();
221
222 // First pass: bucket traces and collect explicit ids. An explicit
223 // id reserves a slot the auto-allocator must avoid, and two
224 // explicit traces pinning the same id is a silent alias today.
225 SmallVector<TraceOp> autoIdTraces;
226 llvm::DenseMap<int, TracePacketOp> explicitIdOwner;
227 for (auto trace : traces) {
228 TracePacketOp explicitPacket = nullptr;
229 for (auto &op : trace.getBody().getOps()) {
230 if (auto p = dyn_cast<TracePacketOp>(op)) {
231 if (p.getId().has_value()) {
232 explicitPacket = p;
233 break;
234 }
235 }
236 }
237 if (!explicitPacket) {
238 autoIdTraces.push_back(trace);
239 continue;
240 }
241 int id = *explicitPacket.getId();
242 auto [it, inserted] = explicitIdOwner.try_emplace(id, explicitPacket);
243 if (!inserted) {
244 InFlightDiagnostic err =
245 explicitPacket.emitError()
246 << "trace packet id " << id
247 << " is already used by another trace; explicit packet ids "
248 "must be unique across the device";
249 err.attachNote(it->second.getLoc())
250 << "previous use of packet id " << id;
251 return signalPassFailure();
252 }
253 }
254 // stable_sort so that multiple traces on the same tile (e.g. core
255 // + mem trace on the same core tile) get ids in their IR-emission
256 // order rather than an unspecified order.
257 llvm::stable_sort(autoIdTraces, [](TraceOp a, TraceOp b) {
258 auto ta = cast<TileOp>(a.getTile().getDefiningOp());
259 auto tb = cast<TileOp>(b.getTile().getDefiningOp());
260 if (ta.getCol() != tb.getCol())
261 return ta.getCol() < tb.getCol();
262 return ta.getRow() < tb.getRow();
263 });
264 // From clPacketIdStart up, past ids user traces pinned and ids the
265 // device holds (PacketIdSpace).
266 PacketIdSpace held(device);
267 int next = clPacketIdStart;
268 for (auto trace : autoIdTraces) {
269 while (next <= kMaxPacketId &&
270 (explicitIdOwner.count(next) || held.isTaken(next)))
271 ++next;
272 if (next > kMaxPacketId) {
273 device.emitError()
274 << "trace overlay needs " << autoIdTraces.size()
275 << " auto-allocated packet IDs starting at " << clPacketIdStart
276 << " (with " << explicitIdOwner.size()
277 << " id(s) reserved by explicit aie.trace.packet ops, and any "
278 "held by packet flows or pinned routes elsewhere in the "
279 "device), but the hardware packet-id field is 5 bits (max "
280 << kMaxPacketId
281 << "); reduce the number of traced tiles, free up an explicit "
282 "id, or raise -packet-id-start only if you can spare the "
283 "lower IDs";
284 return signalPassFailure();
285 }
286 autoPacketIds[trace.getOperation()] = next++;
287 }
288 }
289
290 // Each (tile, trace unit) pair may be configured at most once: a hardware
291 // trace unit emits a single packet stream, so two aie.trace ops targeting
292 // the same unit would race on one packet id and corrupt routing. The
293 // packet type identifies the unit (Core/Mem/MemTile/ShimTile).
294 std::set<std::tuple<int, int, int>> seenTraceUnits; // (col, row, pktType)
295
296 for (auto trace : traces) {
297 auto tile = cast<TileOp>(trace.getTile().getDefiningOp());
298
299 // Find packet ID and type from trace body
300 std::optional<int> packetId;
301 std::optional<TracePacketType> packetType;
302 TracePacketOp existingPacketOp = nullptr;
303 for (auto &op : trace.getBody().getOps()) {
304 if (auto packetOp = dyn_cast<TracePacketOp>(op)) {
305 existingPacketOp = packetOp;
306 if (auto explicitId = packetOp.getId())
307 packetId = explicitId;
308 packetType = packetOp.getType();
309 break;
310 }
311 }
312
313 // Determine packet type from tile type if not specified
314 if (!packetType) {
315 if (tile.isShimTile()) {
316 packetType = TracePacketType::ShimTile;
317 } else if (tile.isMemTile()) {
318 packetType = TracePacketType::MemTile;
319 } else {
320 // Core tile defaults to core type
321 packetType = TracePacketType::Core;
322 }
323 }
324
325 // Reject a second trace op on the same hardware unit of the same tile.
326 auto unitKey = std::make_tuple(tile.getCol(), tile.getRow(),
327 static_cast<int>(*packetType));
328 if (!seenTraceUnits.insert(unitKey).second) {
329 trace.emitError() << "tile (" << tile.getCol() << ", " << tile.getRow()
330 << ") is traced more than once on the same trace "
331 "unit; each unit can have only one trace "
332 "configuration";
333 return signalPassFailure();
334 }
335
336 // Allocate packet ID if not specified (precomputed in (col, row)
337 // order above).
338 if (!packetId)
339 packetId = autoPacketIds.lookup(trace.getOperation());
340
341 // Make sure the in-IR TracePacketOp carries the (possibly
342 // auto-allocated) id so downstream passes (e.g.,
343 // -aie-trace-to-config) see consistent info.
344 if (!existingPacketOp) {
345 OpBuilder traceBuilder(&trace.getBody().front(),
346 trace.getBody().front().begin());
347 TracePacketOp::create(traceBuilder, trace.getLoc(),
348 traceBuilder.getI32IntegerAttr(*packetId),
349 *packetType);
350 } else if (!existingPacketOp.getId().has_value()) {
351 existingPacketOp.setIdAttr(
352 OpBuilder(existingPacketOp).getI32IntegerAttr(*packetId));
353 }
354
355 // Determine trace port based on packet type
356 WireBundle tracePort = WireBundle::Trace;
357 int traceChannel = 0;
358 if (*packetType == TracePacketType::Mem) {
359 traceChannel = 1; // Mem trace uses port 1
360 }
361
362 // Find start/stop configuration (broadcast or event)
363 std::optional<int> startBroadcast;
364 std::optional<int> stopBroadcast;
365 bool hasStartConfig = false;
366 bool hasStopConfig = false;
367 for (auto &op : trace.getBody().getOps()) {
368 if (auto startOp = dyn_cast<TraceStartEventOp>(op)) {
369 hasStartConfig = true;
370 if (startOp.getBroadcast())
371 startBroadcast = startOp.getBroadcast();
372 }
373 if (auto stopOp = dyn_cast<TraceStopEventOp>(op)) {
374 hasStopConfig = true;
375 if (stopOp.getBroadcast())
376 stopBroadcast = stopOp.getBroadcast();
377 }
378 }
379
380 // Require explicit start/stop configuration
381 if (!hasStartConfig) {
382 trace.emitError() << "trace is missing 'aie.trace.start'";
383 return signalPassFailure();
384 }
385 if (!hasStopConfig) {
386 trace.emitError() << "trace is missing 'aie.trace.stop'";
387 return signalPassFailure();
388 }
389
390 TraceInfo info;
391 info.traceOp = trace;
392 info.tile = tile;
393 info.packetId = *packetId;
394 info.packetType = *packetType;
395 info.tracePort = tracePort;
396 info.traceChannel = traceChannel;
397 info.startBroadcast = startBroadcast;
398 info.stopBroadcast = stopBroadcast;
399 traceInfos.push_back(info);
400 }
401
402 // Phase 2b: Select shim tiles based on routing strategy
403 std::map<int, ShimInfo> shimInfos; // col -> ShimInfo
404
405 if (routing == TraceShimRouting::Single) {
406 // All traces route to a single shim, controlled by the egress_shim_col
407 // parameter (default is 0).
408 int targetCol = egressShimColFromIR;
409 if (targetCol < 0 || targetCol >= targetModel.columns() ||
410 !targetModel.isShimNOCTile(targetCol, 0)) {
411 device.emitError() << "egress_shim_col " << targetCol
412 << " is not a valid shim NOC tile (device has "
413 << targetModel.columns() << " columns)";
414 return signalPassFailure();
415 }
416 TileOp shimTile = nullptr;
417 for (auto tile : device.getOps<TileOp>()) {
418 if (tile.getCol() == targetCol && tile.getRow() == 0) {
419 shimTile = tile;
420 break;
421 }
422 }
423
424 if (!shimTile) {
425 builder.setInsertionPointToStart(&device.getRegion().front());
426 shimTile = TileOp::create(builder, device.getLoc(), targetCol, 0);
427 }
428
429 ShimInfo shimInfo;
430 shimInfo.shimTile = shimTile;
431 shimInfo.channel = clShimChannel;
432 shimInfo.bdId = clDefaultBdId;
433 shimInfo.traceSources = traceInfos;
434 // Collect broadcast channels from traces that use them
435 for (auto &trace : traceInfos) {
436 if (trace.startBroadcast && !shimInfo.startBroadcast)
437 shimInfo.startBroadcast = trace.startBroadcast;
438 if (trace.stopBroadcast && !shimInfo.stopBroadcast)
439 shimInfo.stopBroadcast = trace.stopBroadcast;
440 }
441
442 // Map all source columns to the single shim
443 for (auto &info : traceInfos) {
444 int col = info.tile.getCol();
445 if (shimInfos.find(col) == shimInfos.end()) {
446 shimInfos[col] = shimInfo;
447 }
448 }
449 if (shimInfos.find(targetCol) == shimInfos.end()) {
450 shimInfos[targetCol] = shimInfo;
451 }
452 }
453
454 // Phase 2b-lateral: Optionally redirect shim targets to spare columns.
455 // This is a post-processing step that works with ANY routing strategy.
456 // When per-column routing returns, lateral routing will compose with it
457 // (each column's shim redirects to its nearest spare).
458 if (clLateralRouting) {
459 std::set<int> activeColumns;
460 device.walk([&](CoreOp core) {
461 auto coreTile = cast<TileOp>(core.getTile().getDefiningOp());
462 activeColumns.insert(coreTile.getCol());
463 });
464
465 // Collect all unique shim target columns and find redirects
466 std::map<int, int> redirects; // old target col -> new target col
467 for (auto &[col, shimInfo] : shimInfos) {
468 int curTarget = shimInfo.shimTile.getCol();
469 if (redirects.count(curTarget))
470 continue; // already computed
471
472 int spare = -1;
473 if (clLateralTargetCol >= 0) {
474 // Forced lateral target: always redirect unless it's a no-op.
475 if (clLateralTargetCol == curTarget)
476 continue;
477 // Validate the forced column is in range and is a shim NOC tile.
478 int numCols = targetModel.columns();
479 if (clLateralTargetCol >= numCols ||
480 !targetModel.isShimNOCTile(clLateralTargetCol, 0)) {
481 device.emitError() << "lateral-target-col " << clLateralTargetCol
482 << " is not a valid shim NOC tile (device has "
483 << numCols << " columns)";
484 return signalPassFailure();
485 }
486 spare = clLateralTargetCol;
487 } else {
488 // Auto-detect: only redirect from active columns to a spare.
489 if (activeColumns.count(curTarget) == 0)
490 continue; // already spare, no redirect needed
491 spare = findNearestSpareColumn(curTarget, activeColumns, targetModel);
492 }
493 if (spare >= 0)
494 redirects[curTarget] = spare;
495 }
496
497 // Apply redirects: rebuild shimInfos with new target columns
498 if (!redirects.empty()) {
499 std::map<int, ShimInfo> newShimInfos;
500 for (auto &[col, shimInfo] : shimInfos) {
501 int curTarget = shimInfo.shimTile.getCol();
502 auto it = redirects.find(curTarget);
503 if (it != redirects.end()) {
504 int newTarget = it->second;
505 shimInfo.shimTile = getOrCreateShim(device, builder, newTarget);
506 }
507 newShimInfos[col] = shimInfo;
508 }
509 shimInfos = std::move(newShimInfos);
510 }
511 }
512
513 // Phase 2b-conflict: Check S2MM channel availability on target shims.
514 // If channels are occupied, adjust: use free channel, redirect lateral,
515 // or error.
516 auto usedChannels = scanUsedS2MMChannels(device);
517
518 for (auto &[col, shimInfo] : shimInfos) {
519 int shimCol = shimInfo.shimTile.getCol();
520 auto usedIt = usedChannels.find(shimCol);
521 if (usedIt == usedChannels.end())
522 continue; // No conflicts on this shim
523
524 const auto &usedSet = usedIt->second;
525 int freeCount = 2 - static_cast<int>(usedSet.size());
526
527 if (freeCount <= 0) {
528 // No channels free -- try lateral redirect
529 if (clLateralRouting) {
530 std::set<int> activeColumns;
531 device.walk([&](CoreOp core) {
532 auto coreTile = cast<TileOp>(core.getTile().getDefiningOp());
533 activeColumns.insert(coreTile.getCol());
534 });
535 // Also treat columns with full shims as "active" for spare search
536 for (auto &[fullCol, channels] : usedChannels) {
537 if (static_cast<int>(channels.size()) >= 2)
538 activeColumns.insert(fullCol);
539 }
540 int spare =
541 findNearestSpareColumn(shimCol, activeColumns, targetModel);
542 if (spare >= 0) {
543 shimInfo.shimTile = getOrCreateShim(device, builder, spare);
544 // A spare has no core but can still carry one flow; it is not
545 // full, so its other channel is free.
546 shimInfo.channel = usedChannels[spare].count(clShimChannel)
547 ? 1 - clShimChannel
548 : clShimChannel;
549 continue;
550 }
551 }
552 // No lateral available -- emit error
553 device.emitError()
554 << "no S2MM channels available on shim tile at column " << shimCol
555 << " (both channels in use by existing flows or objectFifos); "
556 "set egress_shim_col to a column with a free channel, or "
557 "enable lateral-routing to redirect to a spare column";
558 return signalPassFailure();
559 }
560
561 if (freeCount == 1) {
562 // One channel free -- use it
563 int freeChannel = -1;
564 for (int ch = 0; ch < 2; ch++) {
565 if (usedSet.count(ch) == 0) {
566 freeChannel = ch;
567 break;
568 }
569 }
570 shimInfo.channel = freeChannel;
571 }
572 }
573
574 // Build channel descriptors and trace-to-channel assignments
575 for (auto &[col, shimInfo] : shimInfos) {
576 int shimCol = shimInfo.shimTile.getCol();
577 std::set<int> available = {0, 1};
578 auto usedIt = usedChannels.find(shimCol);
579 if (usedIt != usedChannels.end()) {
580 for (int ch : usedIt->second)
581 available.erase(ch);
582 }
583 shimInfo.channels =
584 buildChannelDescriptors(shimInfo.traceSources.size(),
585 shimInfo.channel, shimInfo.bdId, available);
586 // Round-robin assignment of traces to channels
587 for (size_t i = 0; i < shimInfo.traceSources.size(); i++) {
588 shimInfo.traceChannelAssignment.push_back(i % shimInfo.channels.size());
589 }
590 }
591
592 // Phase 2c: Rewrite broadcast to USER_EVENT for destination shim tiles
593 // (shim can't listen for its own broadcast)
594 for (auto &info : traceInfos) {
595 if (!info.tile.isShimTile())
596 continue;
597
598 int col = info.tile.getCol();
599 auto shimIt = shimInfos.find(col);
600 if (shimIt == shimInfos.end())
601 continue;
602
603 // Check if this traced shim tile IS the destination shim
604 if (shimIt->second.shimTile.getTileID() != info.tile.getTileID())
605 continue;
606
607 // This shim is both traced and the destination - rewrite start/stop ops
608 // to use USER_EVENT_1/0 instead of broadcast
609 for (auto &op : info.traceOp.getBody().getOps()) {
610 if (auto startOp = dyn_cast<TraceStartEventOp>(op)) {
611 if (startOp.getBroadcast()) {
612 // Replace broadcast with USER_EVENT_1
613 startOp->removeAttr("broadcast");
614 startOp->setAttr("event", TraceEventAttr::get(builder.getContext(),
615 builder.getStringAttr(
616 "USER_EVENT_1")));
617 }
618 }
619 if (auto stopOp = dyn_cast<TraceStopEventOp>(op)) {
620 if (stopOp.getBroadcast()) {
621 // Replace broadcast with USER_EVENT_0
622 stopOp->removeAttr("broadcast");
623 stopOp->setAttr("event", TraceEventAttr::get(builder.getContext(),
624 builder.getStringAttr(
625 "USER_EVENT_0")));
626 }
627 }
628 }
629
630 info.startBroadcast = std::nullopt;
631 info.stopBroadcast = std::nullopt;
632 }
633
634 // Phase 3: Insert packet flows
635 // Insert before the device terminator
636 Block &deviceBlock = device.getRegion().front();
637 builder.setInsertionPoint(deviceBlock.getTerminator());
638
639 for (auto &info : traceInfos) {
640 // Find target shim for this trace
641 int col = info.tile.getCol();
642 ShimInfo &shimInfo = shimInfos[col];
643
644 // Find this trace's channel assignment
645 int chanIdx = 0;
646 for (size_t i = 0; i < shimInfo.traceSources.size(); i++) {
647 if (shimInfo.traceSources[i].packetId == info.packetId) {
648 chanIdx = shimInfo.traceChannelAssignment[i];
649 break;
650 }
651 }
652 auto &chanDesc = shimInfo.channels[chanIdx];
653
654 // Create packet flow
655 auto packetFlowOp = PacketFlowOp::create(
656 builder, device.getLoc(), builder.getI8IntegerAttr(info.packetId),
657 nullptr, nullptr);
658
659 Block *flowBody = new Block();
660 packetFlowOp.getPorts().push_back(flowBody);
661 OpBuilder flowBuilder = OpBuilder::atBlockEnd(flowBody);
662
663 PacketSourceOp::create(flowBuilder, device.getLoc(),
664 Value(info.tile.getResult()), info.tracePort,
665 info.traceChannel);
666
667 PacketDestOp::create(flowBuilder, device.getLoc(),
668 Value(shimInfo.shimTile.getResult()),
669 WireBundle::DMA, chanDesc.channel);
670
671 EndOp::create(flowBuilder, device.getLoc());
672
673 packetFlowOp->setAttr("keep_pkt_header", builder.getBoolAttr(true));
674 }
675
676 // Phase 4: Program each runtime sequence to drain its own trace buffer.
677 for (const SequenceTraceConfig &cfg : seqConfigs) {
678 if (failed(emitSequenceTraceOps(cfg, traceInfos, shimInfos)))
679 return signalPassFailure();
680 }
681 }
682
683 /// Give `cfg.seq` a trace buffer and the shim DMA program that drains the
684 /// trace stream into it. `traceInfos` and `shimInfos` describe the
685 /// device-wide overlay, which every sequence of the device drives.
686 LogicalResult emitSequenceTraceOps(const SequenceTraceConfig &cfg,
687 std::vector<TraceInfo> &traceInfos,
688 std::map<int, ShimInfo> &shimInfos) {
689 DeviceOp device = getOperation();
690 OpBuilder builder(device);
691 const auto &targetModel = device.getTargetModel();
692 RuntimeSequenceOp runtimeSeq = cfg.seq;
693 int bufferSizeBytes = cfg.bufferSizeBytes;
694
695 // A second S2MM channel doubles the bytes the DMAs write. The host and
696 // -aie-fuse-trace-buffers read the byte count from the argument type, so
697 // the type must cover every channel.
698 size_t maxChannels = 1;
699 for (auto &[col, shimInfo] : shimInfos)
700 maxChannels = std::max(maxChannels, shimInfo.channels.size());
701 int traceBytesClaimed = static_cast<int>(maxChannels) * bufferSizeBytes;
702
703 // `aiex.npu.address_patch` names the buffer by SSA value. `aiex.run` may
704 // inline this sequence into a caller with a different argument list.
705 Value traceBuffer;
706 BlockArgument appendedTraceArg; // null when reusing the output buffer
707 if (cfg.reuseOutputBuffer) {
708 traceBuffer = runtimeSeq.getBody().getArguments().back();
709 } else {
710 // An i8 memref makes the type state the buffer's byte size, which both
711 // the host and -aie-fuse-trace-buffers read.
712 appendedTraceArg = runtimeSeq.getBody().front().addArgument(
713 MemRefType::get({traceBytesClaimed},
714 IntegerType::get(device.getContext(), 8)),
715 runtimeSeq.getLoc());
716 traceBuffer = appendedTraceArg;
717 }
718
719 runtimeSeq.setTraceBufferAttr(TraceBufferAttr::get(
720 device.getContext(),
721 appendedTraceArg ? appendedTraceArg.getArgNumber()
722 : runtimeSeq.getBody().getNumArguments() - 1,
723 cfg.bufferOffset, traceBytesClaimed, bool(appendedTraceArg)));
724
725 Block &seqBlock = runtimeSeq.getBody().front();
726
727 // Find the last TraceStartConfigOp in the runtime sequence
728 Operation *lastStartConfig = nullptr;
729 for (auto &op : seqBlock.getOperations()) {
730 if (isa<TraceStartConfigOp>(op)) {
731 lastStartConfig = &op;
732 }
733 }
734
735 // Insert after the last start_config op, or at start if none found
736 if (lastStartConfig) {
737 builder.setInsertionPointAfter(lastStartConfig);
738 } else {
739 builder.setInsertionPointToStart(&seqBlock);
740 }
741
742 // 4b. Configure timer sync for tiles using broadcast-based start
743 std::set<std::tuple<int, int, bool>> processedTiles;
744 for (auto &info : traceInfos) {
745 if (!info.startBroadcast)
746 continue;
747
748 int col = info.tile.getCol();
749 int row = info.tile.getRow();
750 bool isMemTrace = info.packetType == TracePacketType::Mem;
751
752 if (processedTiles.count({col, row, isMemTrace}))
753 continue;
754 processedTiles.insert({col, row, isMemTrace});
755
756 // Skip destination shim tiles (handled in 4f)
757 if (info.tile.isShimTile()) {
758 auto shimIt = shimInfos.find(col);
759 if (shimIt != shimInfos.end() &&
760 shimIt->second.shimTile.getTileID() == info.tile.getTileID()) {
761 continue;
762 }
763 }
764
765 // Compute timer control address
766 uint32_t timerCtrlAddr =
767 computeTimerCtrlAddress(info.tile, targetModel, isMemTrace);
768
769 std::string broadcastEventName;
770 if (info.tile.isShimTile()) {
771 broadcastEventName =
772 "BROADCAST_A_" + std::to_string(*info.startBroadcast);
773 } else {
774 broadcastEventName =
775 "BROADCAST_" + std::to_string(*info.startBroadcast);
776 }
777
778 auto broadcastEvent = targetModel.lookupEvent(
779 broadcastEventName, info.tile.getTileID(), isMemTrace);
780 if (!broadcastEvent) {
781 info.traceOp.emitError() << "Failed to lookup broadcast event '"
782 << broadcastEventName << "'";
783 return failure();
784 }
785 const RegisterInfo *timerReg = targetModel.lookupRegister(
786 "Timer_Control", info.tile.getTileID(), isMemTrace);
787 if (!timerReg)
788 llvm::report_fatal_error("Failed to lookup Timer_Control register");
789 const BitFieldInfo *resetField = timerReg->getField("Reset_Event");
790 if (!resetField)
791 llvm::report_fatal_error(
792 "Failed to lookup Reset_Event field in Timer_Control");
793 auto timerCtrlValue =
794 targetModel.encodeFieldValue(*resetField, *broadcastEvent);
795 if (!timerCtrlValue)
796 llvm::report_fatal_error("Broadcast event does not fit in "
797 "Timer_Control's Reset_Event field");
798
799 xilinx::AIEX::NpuWrite32Op::create(
800 builder, runtimeSeq.getLoc(),
801 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), timerCtrlAddr),
802 AIEX::createConstantI32(builder, runtimeSeq.getLoc(),
803 *timerCtrlValue),
804 nullptr, builder.getI32IntegerAttr(col),
805 builder.getI32IntegerAttr(row));
806 }
807
808 // 4c-4f. Insert per-shim configurations
809 std::set<int> configuredShimCols;
810 for (auto &[col, shimInfo] : shimInfos) {
811 int shimCol = shimInfo.shimTile.getCol();
812 if (!configuredShimCols.insert(shimCol).second)
813 continue;
814
815 for (auto [chanIdx, chanDesc] : llvm::enumerate(shimInfo.channels)) {
816 // Convert buffer size (bytes) to 32-bit words for buffer_length
817 int bufferLengthWords = bufferSizeBytes / 4;
818
819 // 4c. Write buffer descriptor
820 xilinx::AIEX::NpuWriteBdOp::create(
821 builder, runtimeSeq.getLoc(),
822 shimCol, // column
823 chanDesc.bdId, // bd_id
824 bufferLengthWords, // buffer_length (in 32-bit words)
825 0, // buffer_offset
826 1, // enable_packet
827 0, // out_of_order_id
828 0, // packet_id (not used for reception)
829 0, // packet_type (not used for reception)
830 0, 0, 0, 0, 0,
831 0, // d0_size, d0_stride, d1_size, d1_stride, d2_size, d2_stride
832 0, 0, 0, // iteration_current, iteration_size, iteration_stride
833 0, // next_bd
834 0, // row
835 0, // use_next_bd
836 1, // valid_bd
837 0, 0, 0, 0, 0, // lock_rel_val, lock_rel_id, lock_acq_enable,
838 // lock_acq_val, lock_acq_id
839 0, 0, 0, 0, 0, 0, // d0_zero_before, d1_zero_before, d2_zero_before,
840 // d0_zero_after, d1_zero_after, d2_zero_after
841 clTraceBurstLength, // burst_length
842 // axcache left unset: the blockwrite lowering resolves the
843 // target default, so baking it here would only duplicate it.
844 mlir::IntegerAttr());
845
846 // 4d. Address patch -- the channels split the buffer between them, so
847 // channel i starts bufferSizeBytes past channel i-1.
848 uint32_t bdAddress = computeBDAddress(shimCol, chanDesc.bdId,
849 shimInfo.shimTile, targetModel);
850 int chanOffset =
851 cfg.bufferOffset + static_cast<int>(chanIdx) * bufferSizeBytes;
852 xilinx::AIEX::NpuAddressPatchOp::create(
853 builder, runtimeSeq.getLoc(), bdAddress, /*addr_val=*/mlir::Value(),
854 traceBuffer,
855 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), chanOffset));
856
857 // 4e. DMA channel configuration — set Controller_ID from tile attribute
858 uint32_t ctrlAddr =
859 computeCtrlAddress(DMAChannelDir::S2MM, chanDesc.channel,
860 shimInfo.shimTile, targetModel);
861 ensureControllerIdAttr(shimInfo.shimTile, targetModel);
862 auto ctrlIdAttr =
863 shimInfo.shimTile->getAttrOfType<PacketInfoAttr>("controller_id");
864 std::string ctrlRegName =
865 (chanDesc.channel == 0) ? "DMA_S2MM_0_Ctrl" : "DMA_S2MM_1_Ctrl";
866 const RegisterInfo *ctrlReg = targetModel.lookupRegister(
867 ctrlRegName, shimInfo.shimTile.getTileID());
868 if (!ctrlReg)
869 llvm::report_fatal_error(llvm::Twine("Failed to lookup ") +
870 ctrlRegName);
871 const BitFieldInfo *ctrlIdField = ctrlReg->getField("Controller_ID");
872 if (!ctrlIdField)
873 llvm::report_fatal_error("Failed to lookup Controller_ID field in " +
874 llvm::Twine(ctrlRegName));
875 auto ctrlIdValue =
876 targetModel.encodeFieldValue(*ctrlIdField, ctrlIdAttr.assignedId());
877 if (!ctrlIdValue)
878 llvm::report_fatal_error("Controller ID does not fit in the "
879 "Controller_ID field");
880 auto ctrlIdMask = targetModel.getFieldMask(*ctrlIdField);
881 if (!ctrlIdMask)
882 llvm::report_fatal_error(
883 "Controller_ID field does not fit in 32-bit register");
884 xilinx::AIEX::NpuMaskWrite32Op::create(
885 builder, runtimeSeq.getLoc(),
886 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), ctrlAddr),
887 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), *ctrlIdValue),
888 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), *ctrlIdMask),
889 nullptr, builder.getI32IntegerAttr(shimCol),
890 builder.getI32IntegerAttr(0));
891
892 // Push BD to task queue
893 std::string taskQueueRegName = (chanDesc.channel == 0)
894 ? "DMA_S2MM_0_Task_Queue"
895 : "DMA_S2MM_1_Task_Queue";
896 const RegisterInfo *queueReg = targetModel.lookupRegister(
897 taskQueueRegName, shimInfo.shimTile.getTileID());
898 if (!queueReg)
899 llvm::report_fatal_error(llvm::Twine("Failed to lookup ") +
900 taskQueueRegName);
901 const BitFieldInfo *tokenField =
902 queueReg->getField("Enable_Token_Issue");
903 const BitFieldInfo *bdIdField = queueReg->getField("Start_BD_ID");
904 if (!tokenField || !bdIdField)
905 llvm::report_fatal_error(
906 "Failed to lookup Enable_Token_Issue or Start_BD_ID fields");
907 auto tokenValue = targetModel.encodeFieldValue(*tokenField, 1);
908 auto bdIdValue =
909 targetModel.encodeFieldValue(*bdIdField, chanDesc.bdId);
910 if (!tokenValue)
911 llvm::report_fatal_error("Enable_Token_Issue does not fit in the "
912 "field of " +
913 llvm::Twine(taskQueueRegName));
914 if (!bdIdValue)
915 llvm::report_fatal_error("BD ID does not fit in the Start_BD_ID "
916 "field of " +
917 llvm::Twine(taskQueueRegName));
918 uint32_t queueValue = *tokenValue | *bdIdValue;
919 xilinx::AIEX::NpuWrite32Op::create(
920 builder, runtimeSeq.getLoc(),
921 AIEX::createConstantI32(builder, runtimeSeq.getLoc(),
922 queueReg->offset),
923 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), queueValue),
924 nullptr, builder.getI32IntegerAttr(shimCol),
925 builder.getI32IntegerAttr(0));
926 }
927
928 // 4f. Shim timer and broadcast control (only if start broadcast is used)
929 if (shimInfo.startBroadcast) {
930 // Look up USER_EVENT_1 from target model (trigger event)
931 auto userEvent1 = targetModel.lookupEvent(
932 "USER_EVENT_1", shimInfo.shimTile.getTileID(), false);
933 if (!userEvent1)
934 llvm::report_fatal_error("Failed to lookup USER_EVENT_1 event");
935
936 uint32_t shimTimerCtrlAddr =
937 computeTimerCtrlAddress(shimInfo.shimTile, targetModel, false);
938 const RegisterInfo *shimTimerReg = targetModel.lookupRegister(
939 "Timer_Control", shimInfo.shimTile.getTileID(), false);
940 if (!shimTimerReg)
941 llvm::report_fatal_error("Failed to lookup shim Timer_Control");
942 const BitFieldInfo *shimResetField =
943 shimTimerReg->getField("Reset_Event");
944 if (!shimResetField)
945 llvm::report_fatal_error(
946 "Failed to lookup Reset_Event in shim Timer_Control");
947 auto shimTimerCtrlValue =
948 targetModel.encodeFieldValue(*shimResetField, *userEvent1);
949 if (!shimTimerCtrlValue)
950 llvm::report_fatal_error("USER_EVENT_1 does not fit in shim "
951 "Timer_Control's Reset_Event field");
952 xilinx::AIEX::NpuWrite32Op::create(
953 builder, runtimeSeq.getLoc(),
954 AIEX::createConstantI32(builder, runtimeSeq.getLoc(),
955 shimTimerCtrlAddr),
956 AIEX::createConstantI32(builder, runtimeSeq.getLoc(),
957 *shimTimerCtrlValue),
958 nullptr, builder.getI32IntegerAttr(shimCol),
959 builder.getI32IntegerAttr(0));
960
961 // Configure broadcast register with USER_EVENT_1
962 std::string broadcastRegName =
963 "Event_Broadcast" + std::to_string(*shimInfo.startBroadcast) + "_A";
964 const RegisterInfo *broadcastReg = targetModel.lookupRegister(
965 broadcastRegName, shimInfo.shimTile.getTileID());
966 if (!broadcastReg)
967 llvm::report_fatal_error(llvm::Twine("Failed to lookup ") +
968 broadcastRegName);
969 xilinx::AIEX::NpuWrite32Op::create(
970 builder, runtimeSeq.getLoc(),
971 AIEX::createConstantI32(builder, runtimeSeq.getLoc(),
972 broadcastReg->offset),
973 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), *userEvent1),
974 nullptr, builder.getI32IntegerAttr(shimCol),
975 builder.getI32IntegerAttr(0));
976
977 // Generate USER_EVENT_1 to trigger the broadcast
978 const RegisterInfo *eventGenReg = targetModel.lookupRegister(
979 "Event_Generate", shimInfo.shimTile.getTileID());
980 if (!eventGenReg)
981 llvm::report_fatal_error("Failed to lookup Event_Generate register");
982 xilinx::AIEX::NpuWrite32Op::create(
983 builder, runtimeSeq.getLoc(),
984 AIEX::createConstantI32(builder, runtimeSeq.getLoc(),
985 eventGenReg->offset),
986 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), *userEvent1),
987 nullptr, builder.getI32IntegerAttr(shimCol),
988 builder.getI32IntegerAttr(0));
989 }
990 }
991
992 // Phase 4g: Insert trace stop at end of runtime sequence
993 builder.setInsertionPointToEnd(&seqBlock);
994
995 std::set<int> stoppedShimCols;
996 for (auto &[col, shimInfo] : shimInfos) {
997 if (!shimInfo.stopBroadcast)
998 continue;
999
1000 int shimCol = shimInfo.shimTile.getCol();
1001 if (!stoppedShimCols.insert(shimCol).second)
1002 continue;
1003
1004 auto userEvent0 = targetModel.lookupEvent(
1005 "USER_EVENT_0", shimInfo.shimTile.getTileID(), false);
1006 if (!userEvent0)
1007 llvm::report_fatal_error("Failed to lookup USER_EVENT_0 event");
1008
1009 std::string broadcastRegName =
1010 "Event_Broadcast" + std::to_string(*shimInfo.stopBroadcast) + "_A";
1011 const RegisterInfo *broadcastReg = targetModel.lookupRegister(
1012 broadcastRegName, shimInfo.shimTile.getTileID());
1013 if (!broadcastReg)
1014 llvm::report_fatal_error(llvm::Twine("Failed to lookup ") +
1015 broadcastRegName);
1016 xilinx::AIEX::NpuWrite32Op::create(
1017 builder, runtimeSeq.getLoc(),
1018 AIEX::createConstantI32(builder, runtimeSeq.getLoc(),
1019 broadcastReg->offset),
1020 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), *userEvent0),
1021 nullptr, builder.getI32IntegerAttr(shimCol),
1022 builder.getI32IntegerAttr(0));
1023
1024 const RegisterInfo *stopEventGenReg = targetModel.lookupRegister(
1025 "Event_Generate", shimInfo.shimTile.getTileID());
1026 if (!stopEventGenReg)
1027 llvm::report_fatal_error("Failed to lookup Event_Generate register");
1028 xilinx::AIEX::NpuWrite32Op::create(
1029 builder, runtimeSeq.getLoc(),
1030 AIEX::createConstantI32(builder, runtimeSeq.getLoc(),
1031 stopEventGenReg->offset),
1032 AIEX::createConstantI32(builder, runtimeSeq.getLoc(), *userEvent0),
1033 nullptr, builder.getI32IntegerAttr(shimCol),
1034 builder.getI32IntegerAttr(0));
1035 }
1036
1037 return success();
1038 }
1039
1040private:
1041 /// Ensure a tile has a controller_id attribute. If missing, assign one
1042 /// using the target model's mapping.
1043 void ensureControllerIdAttr(TileOp tile, const AIETargetModel &tm) {
1044 if (tile->hasAttr("controller_id"))
1045 return;
1046 auto idMap = tm.getTileToControllerIdMap();
1047 int pktId = idMap[{tile.colIndex(), tile.rowIndex()}];
1048 auto pktInfoAttr = PacketInfoAttr::get(tile->getContext(), 0, pktId);
1049 tile->setAttr("controller_id", pktInfoAttr);
1050 }
1051
1052 uint32_t computeBDAddress(int col, int bdId, TileOp shimTile,
1053 const AIETargetModel &tm) {
1054 int row = shimTile.getRow();
1055 return tm.getDmaBdAddress(col, row, bdId) +
1056 tm.getDmaBdAddressOffset(col, row);
1057 }
1058
1059 // Compute DMA task queue address
1060 uint32_t computeTaskQueueAddress(DMAChannelDir dir, int channel,
1061 TileOp shimTile, const AIETargetModel &tm) {
1062 std::string regName;
1063 if (dir == DMAChannelDir::S2MM) {
1064 regName =
1065 (channel == 0) ? "DMA_S2MM_0_Task_Queue" : "DMA_S2MM_1_Task_Queue";
1066 } else { // MM2S
1067 regName =
1068 (channel == 0) ? "DMA_MM2S_0_Task_Queue" : "DMA_MM2S_1_Task_Queue";
1069 }
1070 const RegisterInfo *reg = tm.lookupRegister(regName, shimTile.getTileID());
1071 if (!reg)
1072 llvm::report_fatal_error(llvm::Twine("Failed to lookup ") + regName);
1073 return reg->offset;
1074 }
1075
1076 // Compute DMA control register address
1077 uint32_t computeCtrlAddress(DMAChannelDir dir, int channel, TileOp shimTile,
1078 const AIETargetModel &tm) {
1079 std::string regName;
1080 if (dir == DMAChannelDir::S2MM) {
1081 regName = (channel == 0) ? "DMA_S2MM_0_Ctrl" : "DMA_S2MM_1_Ctrl";
1082 } else { // MM2S
1083 regName = (channel == 0) ? "DMA_MM2S_0_Ctrl" : "DMA_MM2S_1_Ctrl";
1084 }
1085 const RegisterInfo *reg = tm.lookupRegister(regName, shimTile.getTileID());
1086 if (!reg)
1087 llvm::report_fatal_error(llvm::Twine("Failed to lookup ") + regName);
1088 return reg->offset;
1089 }
1090
1091 // Compute timer control address based on tile type
1092 uint32_t computeTimerCtrlAddress(TileOp tile, const AIETargetModel &tm,
1093 bool isMemTrace) {
1094 // Use register database to lookup Timer_Control for the appropriate module
1095 const RegisterInfo *reg =
1096 tm.lookupRegister("Timer_Control", tile.getTileID(), isMemTrace);
1097 if (!reg)
1098 llvm::report_fatal_error("Failed to lookup Timer_Control register");
1099 return reg->offset;
1100 }
1101
1102 /// Build channel descriptors. Always includes the primary channel.
1103 /// Adds a secondary channel when distribute-channels is enabled and there
1104 /// are multiple traces. AIE2 shim tiles have exactly 2 S2MM DMA channels.
1105 std::vector<ChannelDescriptor>
1106 buildChannelDescriptors(size_t numTraces, int primaryChannel, int primaryBdId,
1107 const std::set<int> &availableChannels) {
1108 std::vector<ChannelDescriptor> chans;
1109 chans.push_back({primaryChannel, primaryBdId});
1110 if (clDistributeChannels && numTraces > 1 && primaryBdId > 0) {
1111 int ch2 = (primaryChannel == 1) ? 0 : 1;
1112 // Only add secondary channel if it's available (not claimed by existing
1113 // flows)
1114 if (availableChannels.count(ch2)) {
1115 chans.push_back({ch2, primaryBdId - 1});
1116 }
1117 }
1118 return chans;
1119 }
1120
1121 /// Find or create a shim tile at the given column.
1122 TileOp getOrCreateShim(DeviceOp device, OpBuilder &builder, int col) {
1123 for (auto tile : device.getOps<TileOp>()) {
1124 if (tile.getCol() == col && tile.getRow() == 0)
1125 return tile;
1126 }
1127 OpBuilder::InsertionGuard guard(builder);
1128 builder.setInsertionPointToStart(&device.getRegion().front());
1129 return TileOp::create(builder, device.getLoc(), col, 0);
1130 }
1131
1132 /// Scan the device for existing S2MM channel claims on shim tiles.
1133 /// Checks aie.flow destinations, aie.packet_flow destinations,
1134 /// ShimDMAAllocationOp declarations, and objectFifos that end at a shim.
1135 std::map<int, std::set<int>> scanUsedS2MMChannels(DeviceOp device) {
1136 std::map<int, std::set<int>> used; // shimCol -> set of used S2MM channels
1137
1138 device.walk([&](FlowOp flow) {
1139 auto destTile = cast<TileOp>(flow.getDest().getDefiningOp());
1140 if (destTile.isShimTile() && flow.getDestBundle() == WireBundle::DMA) {
1141 used[destTile.getCol()].insert(flow.getDestChannel());
1142 }
1143 });
1144
1145 device.walk([&](PacketFlowOp pktFlow) {
1146 for (auto &op : pktFlow.getPorts().front()) {
1147 if (auto dest = dyn_cast<PacketDestOp>(op)) {
1148 auto destTile = cast<TileOp>(dest.getTile().getDefiningOp());
1149 if (destTile.isShimTile() && dest.getBundle() == WireBundle::DMA) {
1150 used[destTile.getCol()].insert(dest.getChannel());
1151 }
1152 }
1153 }
1154 });
1155
1156 device.walk([&](ShimDMAAllocationOp alloc) {
1157 if (alloc.getChannelDir() == DMAChannelDir::S2MM) {
1158 auto tile = alloc.getTileOp();
1159 used[tile.getCol()].insert(alloc.getChannelIndex());
1160 }
1161 });
1162
1163 // This pass runs before objectFifo lowering, so an objectFifo that ends
1164 // at a shim has no flow yet. Claim what its lowering will: every
1165 // consumer pin first, then the lowest free S2MM channel for each
1166 // unpinned consumer.
1167 SmallVector<TileOp> unpinned;
1168 device.walk([&](ObjectFifoCreateOp fifo) {
1169 if (fifo.getPlio())
1170 return;
1171 for (auto [i, consumer] : llvm::enumerate(fifo.getConsumerTiles())) {
1172 auto tile = dyn_cast_or_null<TileOp>(consumer.getDefiningOp());
1173 if (!tile || !tile.isShimTile())
1174 continue;
1175 ObjectFifoEndPortAttr port = fifo.getConsumerPort(i);
1176 if (std::optional<int> pin = port ? port.channelIndex() : std::nullopt)
1177 used[tile.getCol()].insert(*pin);
1178 else
1179 unpinned.push_back(tile);
1180 }
1181 });
1182 for (TileOp tile : unpinned) {
1183 auto &channels = used[tile.getCol()];
1184 int ch = 0;
1185 while (channels.count(ch))
1186 ++ch;
1187 channels.insert(ch);
1188 }
1189
1190 return used;
1191 }
1192
1193 /// Find the nearest NOC shim column without active cores.
1194 /// Returns -1 if no spare column exists.
1195 int findNearestSpareColumn(int sourceCol, const std::set<int> &activeColumns,
1196 const AIETargetModel &targetModel) {
1197 int bestCol = -1;
1198 int bestDist = INT_MAX;
1199 int numCols = targetModel.columns();
1200 for (int c = 0; c < numCols; c++) {
1201 if (activeColumns.count(c) == 0 && targetModel.isShimNOCTile(c, 0)) {
1202 int dist = std::abs(c - sourceCol);
1203 if (dist > 0 && dist < bestDist) {
1204 bestDist = dist;
1205 bestCol = c;
1206 }
1207 }
1208 }
1209 return bestCol;
1210 }
1211};
1212
1213} // namespace
1214
1215std::unique_ptr<OperationPass<DeviceOp>>
1217 return std::make_unique<AIEInsertTraceFlowsPass>();
1218}
llvm::DenseMap< TileID, int > getTileToControllerIdMap(bool columnWiseUniqueIDs=true) const
Return a mapping from tile coordinates to controller IDs.
const RegisterInfo * lookupRegister(llvm::StringRef name, TileID tile, bool isMem=false) const
Register Database API - provides access to register and event information for trace configuration and...
virtual uint64_t getDmaBdAddress(int col, int row, uint32_t bd_id, int channel=-1, AIE::DMAChannelDir direction=AIE::DMAChannelDir::MM2S) const =0
Return the array address of the dma buffer descriptor for the given col, row, buffer descriptor id,...
virtual uint32_t getDmaBdAddressOffset(int col, int row) const =0
Return the offset of the base address field within the shim dma buffer descriptor.
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.
mlir::Value createConstantI32(mlir::OpBuilder &builder, mlir::Location loc, uint32_t value)
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEInsertTraceFlowsPass()
const BitFieldInfo * getField(llvm::StringRef fieldName) const