MLIR-AIE 1.4.6.dev
AIECtrlPacketToDma.cpp
Go to the documentation of this file.
1//===- AIECtrlPacketToDma.cpp -----------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2024 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
11
12#include "mlir/Dialect/Arith/IR/Arith.h"
13#include "mlir/IR/Attributes.h"
14#include "mlir/IR/IRMapping.h"
15#include "mlir/Pass/Pass.h"
16
17#include "llvm/ADT/TypeSwitch.h"
18
19namespace xilinx::AIEX {
20#define GEN_PASS_DEF_AIECTRLPACKETTODMA
21#define GEN_PASS_DEF_AIECTRLPACKETINFERTILES
22#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
23} // namespace xilinx::AIEX
24
25#define DEBUG_TYPE "aie-ctrl-packet-to-dma"
26
27using namespace mlir;
28using namespace xilinx;
29using namespace xilinx::AIE;
30using namespace xilinx::AIEX;
31
32static ShimDMAAllocationOp getCtrlPktShimAlloc(DeviceOp device, int col) {
33 AIEDialect::IsCtrlPktOverlayAttrHelper overlay(device.getContext());
34 for (auto alloc : device.getOps<AIE::ShimDMAAllocationOp>())
35 if (overlay.isAttrPresent(alloc) &&
36 alloc.getChannelDir() == AIE::DMAChannelDir::MM2S &&
37 alloc.getTileOp().colIndex() == col)
38 return alloc;
39 return {};
40}
41
44 AIECtrlPacketInferTilesPass> {
45 void runOnOperation() override {
46 DeviceOp device = getOperation();
47 const auto &targetModel = device.getTargetModel();
48 OpBuilder devBuilder = OpBuilder::atBlockBegin(device.getBody());
49
50 auto sequenceOps = device.getOps<AIE::RuntimeSequenceOp>();
51 for (auto f : sequenceOps) {
52 auto ctrlPktOps = f.getOps<AIEX::NpuControlPacketOp>();
53 for (auto ctrlPktOp : ctrlPktOps) {
54 auto tOp = TileOp::getOrCreate(devBuilder, device,
55 (int)ctrlPktOp.getColumnFromAddr(),
56 (int)ctrlPktOp.getRowFromAddr());
57 // Assign controller id
58 auto tileIDMap = targetModel.getTileToControllerIdMap(true);
59 if (tOp->hasAttr("controller_id"))
60 continue;
61 auto pktInfoAttr = AIE::PacketInfoAttr::get(
62 tOp->getContext(), /*pkt_type*/ 0,
63 /*pkt_id*/ tileIDMap[{tOp.colIndex(), tOp.rowIndex()}]);
64 tOp->setAttr("controller_id", pktInfoAttr);
65 }
66 }
67 }
68};
69
71 : xilinx::AIEX::impl::AIECtrlPacketToDmaBase<AIECtrlPacketToDmaPass> {
72 void runOnOperation() override {
73 DeviceOp device = getOperation();
74 const auto &targetModel = device.getTargetModel();
75 auto *ctx = device->getContext();
76 auto loc = device->getLoc();
77
78 if (targetModel.getTargetArch() == AIEArch::AIE1)
79 return; // Disable this pass for AIE1; AIE1 support NYI.
80
81 SmallVector<Operation *> erased;
82 auto sequenceOps = device.getOps<AIE::RuntimeSequenceOp>();
83 for (auto f : sequenceOps) {
84
85 auto controlPacketOps = f.getOps<AIEX::NpuControlPacketOp>();
86 if (controlPacketOps.empty())
87 continue;
88
89 OpBuilder builder(f);
90
91 IRMapping mapping;
92
93 auto newSeq = AIE::RuntimeSequenceOp::create(
94 builder, loc, f.getSymNameAttr(), BoolAttr{}, f.getTraceBufferAttr(),
95 f.getTraceSlicesAttr(), f.getScratchpadParametersAttr());
96 newSeq.getBody().push_back(new Block);
97
98 // Copy the arguments from the old sequence to the new one.
99 for (auto arg : f.getBody().getArguments()) {
100 // Add the argument to the new sequence.
101 auto newArg = newSeq.getBody().addArgument(arg.getType(), arg.getLoc());
102 // Replace all uses of the old argument with the new one.
103 arg.replaceAllUsesWith(newArg);
104 // Add the mapping for the argument.
105 mapping.map(arg, newArg);
106 }
107
108 // Using dynamic shape for ctrl pkt stream.
109 auto ctrlPktMemrefType = MemRefType::get(
110 ShapedType::kDynamic, IntegerType::get(ctx, 32), nullptr, nullptr);
111 auto newBlockArg = newSeq.getBody().addArgument(ctrlPktMemrefType, loc);
112
113 builder.setInsertionPointToStart(&newSeq.getBody().front());
114
115 // Collect all npu.control_packet ops, grouped by location in 'batches'
116 struct BatchInfo {
117 TileID tileId;
118 int64_t startOffset;
119 int64_t totalSize;
120 std::string shimDmaAllocName;
121 int shimChan;
122 Operation *first;
123 };
124 std::vector<BatchInfo> batches;
125
126 int64_t ddrOffset = 0;
127 Block &entry = f.getBody().front();
128
129 // First pass: collect and batch control packet operations
130 bool new_batch = true;
131 for (Operation &o : entry) {
132 auto ctrlPktOp = dyn_cast<NpuControlPacketOp>(&o);
133
134 // A non-control_packet op ends the current batch
135 if (!ctrlPktOp) {
136 new_batch = true;
137 continue;
138 }
139 int col = ctrlPktOp.getColumnFromAddr();
140 int row = ctrlPktOp.getRowFromAddr();
141
142 // A read's length describes its response, not an outgoing payload.
143 int64_t ctrlPktSize = 2; // ctrl info word + packet header
144 if (auto data = ctrlPktOp.getData())
145 ctrlPktSize += data->size();
146
147 // Read requests must end their DMA transfer.
148 bool batchable = ctrlPktOp.getOpcode() == 0;
149 if (targetModel.getTargetArch() == AIEArch::AIE2p && batchable &&
150 !new_batch && batches.back().tileId == TileID{col, row}) {
151 // Add to existing batch
152 batches.back().totalSize += ctrlPktSize;
153 } else {
154 // Start new batch
155 ShimDMAAllocationOp alloc = getCtrlPktShimAlloc(device, col);
156 if (!alloc) {
157 ctrlPktOp.emitOpError("column ")
158 << col
159 << " has no control packet shim DMA allocation; run "
160 "-aie-generate-column-control-overlay with "
161 "configuration=true, then -aie-import-overlay first";
162 return signalPassFailure();
163 }
164 batches.push_back({TileID{col, row}, ddrOffset, ctrlPktSize,
165 alloc.getSymName().str(),
166 static_cast<int>(alloc.getChannelIndex()), &o});
167 }
168 new_batch = !batchable;
169 ddrOffset += ctrlPktSize;
170 }
171
172 // Second pass: emit batched operations in original order
173 auto batchIt = batches.begin();
174
175 for (Operation &o : entry) {
176 auto ctrlPktOp = dyn_cast<NpuControlPacketOp>(&o);
177 if (!ctrlPktOp) {
178 builder.clone(o, mapping);
179 continue;
180 }
181
182 // There are no more control packet batches to emit
183 if (batchIt == batches.end())
184 continue;
185
186 // Check if this is the first packet of a new batch, otherwise skip it.
187 if (batchIt->first != &o)
188 continue;
189
190 int col = ctrlPktOp.getColumnFromAddr();
191
192 // Emit the batched DMA operation for this (col, row) pair
193 const std::vector<int64_t> staticOffsets = {0, 0, 0,
194 batchIt->startOffset};
195 const std::vector<int64_t> staticSizes = {1, 1, 1, batchIt->totalSize};
196 const std::vector<int64_t> staticStrides = {0, 0, 0, 1};
197
198 SymbolRefAttr metadata =
199 SymbolRefAttr::get(builder.getContext(), batchIt->shimDmaAllocName);
200 NpuDmaMemcpyNdOp::create(builder, loc, newBlockArg, staticOffsets,
201 staticSizes, staticStrides, metadata,
202 /*id=*/0, /*issue_token=*/true);
203
204 Value shimRow = AIEX::createConstantI32(builder, loc, 0);
205 Value shimCol = AIEX::createConstantI32(builder, loc, col);
206 Value dir = AIEX::createConstantI32(builder, loc, 1); // MM2S
207 Value chan = AIEX::createConstantI32(builder, loc, batchIt->shimChan);
208 Value col_num = AIEX::createConstantI32(builder, loc, 1);
209 Value row_num = AIEX::createConstantI32(builder, loc, 1);
210 AIEX::NpuSyncOp::create(builder, loc, shimCol, shimRow, dir, chan,
211 col_num, row_num);
212 ++batchIt;
213 }
214
215 erased.push_back(f);
216 }
217
218 for (auto *e : erased)
219 e->erase();
220 }
221};
222
223std::unique_ptr<OperationPass<DeviceOp>>
225 return std::make_unique<AIECtrlPacketInferTilesPass>();
226}
227std::unique_ptr<OperationPass<DeviceOp>> AIEX::createAIECtrlPacketToDmaPass() {
228 return std::make_unique<AIECtrlPacketToDmaPass>();
229}
std::unique_ptr< mlir::OperationPass< AIE::DeviceOp > > createAIECtrlPacketInferTilesPass()
std::unique_ptr< mlir::OperationPass< AIE::DeviceOp > > createAIECtrlPacketToDmaPass()
mlir::Value createConstantI32(mlir::OpBuilder &builder, mlir::Location loc, uint32_t value)
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