MLIR-AIE 1.4.5.dev
AIEToConfiguration.cpp
Go to the documentation of this file.
1//===- AIEToConfiguration.h -------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2024 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
8#include "../PassDetail.h"
9
14#include "aie/Targets/AIERT.h"
15
16#include "mlir/Dialect/Arith/IR/Arith.h"
17#include "mlir/Dialect/MemRef/IR/MemRef.h"
18#include "mlir/IR/IRMapping.h"
19
20#include "llvm/ADT/APInt.h"
21#include "llvm/ADT/DenseMap.h"
22#include "llvm/Support/Debug.h"
23
24extern "C" {
25#include "xaiengine/xaiegbl.h"
26// above needs to go first for u32/u64 typedefs, AieRC, and XAIE_AIG_EXPORT
27// used by xaie_txn.h
28#include "xaiengine/xaie_txn.h"
29}
30
31#include <array>
32#include <cstring>
33#include <optional>
34#include <utility>
35#include <vector>
36
37namespace xilinx {
38#define GEN_PASS_DEF_CONVERTAIETOCONTROLPACKETS
39#define GEN_PASS_DEF_CONVERTAIETOTRANSACTION
40#include "aie/Conversion/Passes.h.inc"
41} // namespace xilinx
42
43#define DEBUG_TYPE "aie-convert-to-config"
44
45using namespace mlir;
46using namespace xilinx;
47using namespace xilinx::AIE;
48
49namespace {
50
51// A TransactionBinaryOperation encapsulates an aie-rt XAie_TxnCmd struct and
52// any additional metadata needed for custom operations that do not map cleanly
53// onto the core command fields.
54struct TransactionBinaryOperation {
55 struct XAie_TxnCmd cmd = {};
56
57 struct SyncPayload {
58 int32_t column;
59 int32_t row;
60 int32_t direction;
61 int32_t channel;
62 int32_t columnCount;
63 int32_t rowCount;
64 };
65
66 struct LoadPdiPayload {
67 uint32_t id;
68 uint32_t size;
69 uint64_t address;
70 };
71
72 struct AddressPatchPayload {
73 uint32_t action;
74 uint32_t addr;
75 int32_t argIdx;
76 // Words 10-11 of the op; see txn_append_address_patch.
77 uint64_t argPlus;
78 };
79
80 std::optional<SyncPayload> sync;
81 std::optional<LoadPdiPayload> loadPdi;
82 std::optional<AddressPatchPayload> addressPatch;
83 std::optional<mlir::Location> sourceLoc;
84
85 TransactionBinaryOperation() = default;
86
87 TransactionBinaryOperation(XAie_TxnOpcode opc, uint32_t mask, uint64_t addr,
88 uint32_t value, const uint8_t *data,
89 uint32_t size) {
90 cmd.Opcode = opc;
91 cmd.Mask = mask;
92 cmd.RegOff = addr;
93 cmd.Value = value;
94 cmd.DataPtr = reinterpret_cast<uint64_t>(data);
95 cmd.Size = size;
96 }
97};
98
99constexpr size_t kTxnHeaderBytes = 16;
100
101struct TxnPreemptHeader {
102 uint8_t opcode;
103 uint8_t level;
104 uint16_t reserved;
105};
106
107struct TxnLoadPdiHeader {
108 uint8_t opcode;
109 uint8_t padding;
110 uint16_t id;
111 uint32_t size;
112 uint64_t address;
113};
114} // namespace
115
116// Parse a TXN binary blob. On success return the number of columns from the
117// header and a vector of parsed operations. On failure return std::nullopt.
118static std::optional<int>
119parseTransactionBinary(const std::vector<uint8_t> &data,
120 std::vector<TransactionBinaryOperation> &ops) {
121
122 if (data.size() < kTxnHeaderBytes) {
123 llvm::errs() << "Transaction binary is too small for header\n";
124 return std::nullopt;
125 }
126
127 uint32_t major = data[0];
128 uint32_t minor = data[1];
129 uint32_t num_cols = data[4];
130
131 uint32_t num_ops, txn_size;
132 std::memcpy(&num_ops, &data[8], 4);
133 std::memcpy(&txn_size, &data[12], 4);
134
135 LLVM_DEBUG(llvm::dbgs() << "Major: " << major << "\n");
136 LLVM_DEBUG(llvm::dbgs() << "Minor: " << minor << "\n");
137 LLVM_DEBUG(llvm::dbgs() << "DevGen: " << data[2] << "\n");
138 LLVM_DEBUG(llvm::dbgs() << "NumRows: " << data[3] << "\n");
139 LLVM_DEBUG(llvm::dbgs() << "NumCols: " << num_cols << "\n");
140 LLVM_DEBUG(llvm::dbgs() << "NumMemTileRows: " << data[5] << "\n");
141 LLVM_DEBUG(llvm::dbgs() << "NumOps: " << num_ops << "\n");
142 LLVM_DEBUG(llvm::dbgs() << "TxnSize: " << txn_size << " bytes\n");
143
144 size_t i = kTxnHeaderBytes;
145
146 auto requireBytes = [&](size_t offset, size_t length) -> bool {
147 if (offset + length > data.size()) {
148 llvm::errs() << "Transaction binary truncated while parsing opcode\n";
149 return false;
150 }
151 return true;
152 };
153
154 auto read32 = [&](size_t offset) -> uint32_t {
155 uint32_t value;
156 std::memcpy(&value, data.data() + offset, sizeof(uint32_t));
157 return value;
158 };
159
160 // Convert opcode from uint8 to a validated opcode byte
161 auto convertOpcode = [](uint8_t opc) -> std::optional<uint8_t> {
162 switch (opc) {
163 case static_cast<uint8_t>(XAie_TxnOpcode::XAIE_IO_WRITE):
164 case static_cast<uint8_t>(XAie_TxnOpcode::XAIE_IO_BLOCKWRITE):
165 case static_cast<uint8_t>(XAie_TxnOpcode::XAIE_IO_MASKWRITE):
166 case 0x6: // XAie_TxnOpcode::XAIE_IO_PREEMPT
167 case 0x8: // XAie_TxnOpcode::XAIE_IO_LOAD_PDI
168 case static_cast<uint8_t>(XAie_TxnOpcode::XAIE_IO_CUSTOM_OP_TCT):
169 case static_cast<uint8_t>(XAie_TxnOpcode::XAIE_IO_CUSTOM_OP_DDR_PATCH):
170 return opc;
171 default:
172 llvm::errs() << "Unhandled opcode: " << std::to_string(opc) << "\n";
173 return std::nullopt;
174 }
175 };
176
177 // Parse the binary blob. There are two versions supported, 0.1 and 1.0.
178 // For both versions, build a list of TransactionBinaryOperation objects
179 // representing the parsed operations.
180 if (major == 0 && minor == 1) {
181 while (i < data.size()) {
182 auto maybeOpcode = convertOpcode(data[i]);
183 if (!maybeOpcode)
184 return std::nullopt;
185 XAie_TxnOpcode opcode = static_cast<XAie_TxnOpcode>(*maybeOpcode);
186 LLVM_DEBUG(llvm::dbgs() << "opcode: " + std::to_string(opcode) << "\n");
187
188 TransactionBinaryOperation op;
189 op.cmd.Opcode = opcode;
190
191 switch (opcode) {
192 case XAie_TxnOpcode::XAIE_IO_WRITE: {
193 LLVM_DEBUG(llvm::dbgs() << "opcode: WRITE (0x00)\n");
194 if (!requireBytes(i, 24))
195 return std::nullopt;
196 uint32_t addrLo = read32(i + 8);
197 uint32_t addrHi = read32(i + 12);
198 uint32_t value = read32(i + 16);
199 uint32_t opSize = read32(i + 20);
200 if (!requireBytes(i, opSize))
201 return std::nullopt;
202 uint64_t addr = (static_cast<uint64_t>(addrHi) << 32) | addrLo;
203 op.cmd.RegOff = addr;
204 op.cmd.Value = value;
205 op.cmd.Size = 0;
206 i += opSize;
207 break;
208 }
209 case XAie_TxnOpcode::XAIE_IO_BLOCKWRITE: {
210 LLVM_DEBUG(llvm::dbgs() << "opcode: BLOCKWRITE (0x01)\n");
211 if (!requireBytes(i, 16))
212 return std::nullopt;
213 uint32_t addr = read32(i + 8);
214 uint32_t opSize = read32(i + 12);
215 if (opSize < 16 || !requireBytes(i, opSize))
216 return std::nullopt;
217 const uint8_t *payload = data.data() + i + 16;
218 uint32_t payloadBytes = opSize - 16;
219 op.cmd.RegOff = addr;
220 op.cmd.DataPtr = reinterpret_cast<uint64_t>(payload);
221 op.cmd.Size = payloadBytes;
222 i += opSize;
223 break;
224 }
225 case XAie_TxnOpcode::XAIE_IO_MASKWRITE: {
226 LLVM_DEBUG(llvm::dbgs() << "opcode: MASKWRITE (0x03)\n");
227 if (!requireBytes(i, 28))
228 return std::nullopt;
229 uint32_t addrLo = read32(i + 8);
230 uint32_t addrHi = read32(i + 12);
231 uint32_t value = read32(i + 16);
232 uint32_t mask = read32(i + 20);
233 uint32_t opSize = read32(i + 24);
234 if (!requireBytes(i, opSize))
235 return std::nullopt;
236 uint64_t addr = (static_cast<uint64_t>(addrHi) << 32) | addrLo;
237 op.cmd.RegOff = addr;
238 op.cmd.Value = value;
239 op.cmd.Mask = mask;
240 op.cmd.Size = opSize;
241 i += opSize;
242 break;
243 }
244 case XAie_TxnOpcode::XAIE_IO_CUSTOM_OP_TCT: {
245 uint32_t opSize = read32(i + 4);
246 if (opSize < 16 || !requireBytes(i, opSize))
247 return std::nullopt;
248 uint32_t descriptor = read32(i + 8);
249 uint32_t config = read32(i + 12);
250 TransactionBinaryOperation::SyncPayload payload{
251 /*column=*/static_cast<int32_t>((descriptor >> 16) & 0xff),
252 /*row=*/static_cast<int32_t>((descriptor >> 8) & 0xff),
253 /*direction=*/static_cast<int32_t>(descriptor & 0xff),
254 /*channel=*/static_cast<int32_t>((config >> 24) & 0xff),
255 /*columnCount=*/static_cast<int32_t>((config >> 16) & 0xff),
256 /*rowCount=*/static_cast<int32_t>((config >> 8) & 0xff)};
257 op.sync = payload;
258 op.cmd.Size = opSize;
259 i += opSize;
260 break;
261 }
262 // Not in the vendored third_party/aie-rt enum (though the driver
263 // protocol defines it); can't add it without forking upstream.
264 case 0x8: { // NOLINT(clang-diagnostic-switch):
265 // XAie_TxnOpcode::XAIE_IO_LOAD_PDI
266 LLVM_DEBUG(llvm::dbgs() << "opcode: LOAD_PDI (0x08)\n");
267 constexpr size_t opSize = sizeof(TxnLoadPdiHeader);
268 if (!requireBytes(i, opSize))
269 return std::nullopt;
270 TxnLoadPdiHeader header;
271 std::memcpy(&header, data.data() + i, opSize);
272 TransactionBinaryOperation::LoadPdiPayload payload{
273 header.id, header.size, header.address};
274 op.loadPdi = payload;
275 op.cmd.Size = opSize;
276 i += opSize;
277 break;
278 }
279 case XAie_TxnOpcode::XAIE_IO_CUSTOM_OP_DDR_PATCH: {
280 uint32_t opSize = read32(i + 4);
281 if (opSize < 44 || !requireBytes(i, opSize))
282 return std::nullopt;
283 uint32_t action = read32(i + 20);
284 uint32_t addr = read32(i + 24);
285 int32_t argIdx = static_cast<int32_t>(read32(i + 32));
286 uint64_t argPlus = static_cast<uint64_t>(read32(i + 40)) |
287 (static_cast<uint64_t>(read32(i + 44)) << 32);
288 TransactionBinaryOperation::AddressPatchPayload payload{
289 action, addr, argIdx, argPlus};
290 op.addressPatch = payload;
291 op.cmd.Size = opSize;
292 i += opSize;
293 break;
294 }
295 case 0x6: { // XAie_TxnOpcode::XAIE_IO_PREEMPT
296 LLVM_DEBUG(llvm::dbgs() << "opcode: PREEMPT (0x06)\n");
297 constexpr size_t opSize = sizeof(TxnPreemptHeader);
298 if (!requireBytes(i, opSize))
299 return std::nullopt;
300 const auto *header =
301 reinterpret_cast<const TxnPreemptHeader *>(data.data() + i);
302 op.cmd.Value = header->level;
303 op.cmd.Size = opSize;
304 i += opSize;
305 break;
306 }
307 default:
308 llvm::errs() << "Unhandled opcode: " << std::to_string(opcode)
309 << " for v0.1 transaction\n";
310 return std::nullopt;
311 }
312
313 ops.push_back(op);
314 }
315 } else if (major == 1 && minor == 0) {
316 while (i < data.size()) {
317 auto maybeOpcode = convertOpcode(data[i]);
318 if (!maybeOpcode)
319 return std::nullopt;
320 XAie_TxnOpcode opcode = static_cast<XAie_TxnOpcode>(*maybeOpcode);
321 LLVM_DEBUG(llvm::dbgs() << "opcode: " + std::to_string(opcode) << "\n");
322
323 TransactionBinaryOperation op;
324 op.cmd.Opcode = opcode;
325
326 switch (opcode) {
327 case XAie_TxnOpcode::XAIE_IO_WRITE: {
328 LLVM_DEBUG(llvm::dbgs() << "opcode: WRITE (0x00)\n");
329 if (!requireBytes(i, 12))
330 return std::nullopt;
331 uint32_t addr = read32(i + 4);
332 uint32_t value = read32(i + 8);
333 op.cmd.RegOff = addr;
334 op.cmd.Value = value;
335 op.cmd.Size = 0;
336 i += 12;
337 break;
338 }
339 case XAie_TxnOpcode::XAIE_IO_BLOCKWRITE: {
340 LLVM_DEBUG(llvm::dbgs() << "opcode: BLOCKWRITE (0x01)\n");
341 if (!requireBytes(i, 12))
342 return std::nullopt;
343 uint32_t addr = read32(i + 4);
344 uint32_t opSize = read32(i + 8);
345 if (opSize < 12 || !requireBytes(i, opSize))
346 return std::nullopt;
347 const uint8_t *payload = data.data() + i + 12;
348 uint32_t payloadBytes = opSize - 12;
349 op.cmd.RegOff = addr;
350 op.cmd.DataPtr = reinterpret_cast<uint64_t>(payload);
351 op.cmd.Size = payloadBytes;
352 i += opSize;
353 break;
354 }
355 case XAie_TxnOpcode::XAIE_IO_MASKWRITE: {
356 LLVM_DEBUG(llvm::dbgs() << "opcode: MASKWRITE (0x03)\n");
357 if (!requireBytes(i, 16))
358 return std::nullopt;
359 uint32_t addr = read32(i + 4);
360 uint32_t value = read32(i + 8);
361 uint32_t mask = read32(i + 12);
362 op.cmd.RegOff = addr;
363 op.cmd.Value = value;
364 op.cmd.Mask = mask;
365 op.cmd.Size = 0;
366 i += 16;
367 break;
368 }
369 case XAie_TxnOpcode::XAIE_IO_CUSTOM_OP_TCT: {
370 uint32_t opSize = read32(i + 4);
371 if (opSize < 16 || !requireBytes(i, opSize))
372 return std::nullopt;
373 uint32_t descriptor = read32(i + 8);
374 uint32_t config = read32(i + 12);
375 TransactionBinaryOperation::SyncPayload payload{
376 /*column=*/static_cast<int32_t>((descriptor >> 16) & 0xff),
377 /*row=*/static_cast<int32_t>((descriptor >> 8) & 0xff),
378 /*direction=*/static_cast<int32_t>(descriptor & 0xff),
379 /*channel=*/static_cast<int32_t>((config >> 24) & 0xff),
380 /*columnCount=*/static_cast<int32_t>((config >> 16) & 0xff),
381 /*rowCount=*/static_cast<int32_t>((config >> 8) & 0xff)};
382 op.sync = payload;
383 op.cmd.Size = opSize;
384 i += opSize;
385 break;
386 }
387 // Not in the vendored third_party/aie-rt enum (though the driver
388 // protocol defines it); can't add it without forking upstream.
389 case 0x8: { // NOLINT(clang-diagnostic-switch):
390 // XAie_TxnOpcode::XAIE_IO_LOAD_PDI
391 LLVM_DEBUG(llvm::dbgs() << "opcode: LOAD_PDI (0x08)\n");
392 constexpr size_t opSize = sizeof(TxnLoadPdiHeader);
393 if (!requireBytes(i, opSize))
394 return std::nullopt;
395 TxnLoadPdiHeader header;
396 std::memcpy(&header, data.data() + i, opSize);
397 TransactionBinaryOperation::LoadPdiPayload payload{
398 header.id, header.size, header.address};
399 op.loadPdi = payload;
400 op.cmd.Size = opSize;
401 i += opSize;
402 break;
403 }
404 case XAie_TxnOpcode::XAIE_IO_CUSTOM_OP_DDR_PATCH: {
405 uint32_t opSize = read32(i + 4);
406 if (opSize < 44 || !requireBytes(i, opSize))
407 return std::nullopt;
408 uint32_t action = read32(i + 20);
409 uint32_t addr = read32(i + 24);
410 int32_t argIdx = static_cast<int32_t>(read32(i + 32));
411 uint64_t argPlus = static_cast<uint64_t>(read32(i + 40)) |
412 (static_cast<uint64_t>(read32(i + 44)) << 32);
413 TransactionBinaryOperation::AddressPatchPayload payload{
414 action, addr, argIdx, argPlus};
415 op.addressPatch = payload;
416 op.cmd.Size = opSize;
417 i += opSize;
418 break;
419 }
420 case 0x6: { // XAie_TxnOpcode::XAIE_IO_PREEMPT
421 LLVM_DEBUG(llvm::dbgs() << "opcode: PREEMPT (0x06)\n");
422 constexpr size_t opSize = sizeof(TxnPreemptHeader);
423 if (!requireBytes(i, opSize))
424 return std::nullopt;
425 const auto *header =
426 reinterpret_cast<const TxnPreemptHeader *>(data.data() + i);
427 op.cmd.Value = header->level;
428 op.cmd.Size = opSize;
429 i += opSize;
430 break;
431 }
432 default:
433 llvm::errs() << "Unhandled opcode: " << std::to_string(opcode)
434 << " for v1.0 transaction\n";
435 return std::nullopt;
436 }
437
438 ops.push_back(op);
439 }
440 } else {
441 llvm::errs() << "Unsupported TXN binary version: " << major << "." << minor
442 << "\n";
443 return std::nullopt;
444 }
445
446 return num_cols;
447}
448
449static LogicalResult generateTransactions(AIERTControl &ctl,
450 const StringRef workDirPath,
451 DeviceOp &targetOp, bool aieSim,
452 bool enableElfs, bool enableInit,
453 bool enableCores,
454 bool skipCtrlPktOverlay = false) {
455 if (enableElfs && !targetOp.getOps<CoreOp>().empty() &&
456 failed(ctl.addAieElfs(targetOp, workDirPath, aieSim)))
457 return failure();
458 if (enableInit && failed(ctl.addInitConfig(targetOp, skipCtrlPktOverlay)))
459 return failure();
460 if (enableCores && !targetOp.getOps<CoreOp>().empty() &&
461 failed(ctl.addCoreEnable(targetOp)))
462 return failure();
463 return success();
464}
465
466// Translate vector of TransactionBinaryOperation to a sequence of transaction
467// ops (npu.write32, npu.maskwrite32, npu.blockwrite). Each emitted op gets the
468// per-op `sourceLoc` from AIERT's instruction-range bracketing if available;
469// otherwise it falls back to `fallbackLoc` (typically the device location).
470static LogicalResult
471emitTransactionOps(OpBuilder &builder, Location fallbackLoc,
472 std::vector<TransactionBinaryOperation> &operations,
473 std::vector<memref::GlobalOp> &global_data) {
474
475 // One constant per distinct value. They all land in the same block through
476 // `builder`, so the first use dominates every later one.
477 //
478 // Because the first use is what creates the op, callers must bind the
479 // operands to locals rather than call this inside an argument list: the
480 // evaluation order of function arguments is unspecified, and it decides the
481 // order the constants are emitted in.
482 llvm::DenseMap<uint32_t, Value> constantCache;
483 auto constant = [&](Location loc, uint32_t value) {
484 Value &cached = constantCache[value];
485 if (!cached)
486 cached = AIEX::createConstantI32(builder, loc, value);
487 return cached;
488 };
489
490 // create the txn ops
491 for (auto [op, payload] : llvm::zip(operations, global_data)) {
492 Location loc = op.sourceLoc.value_or(fallbackLoc);
493
494 if (op.cmd.Opcode == XAie_TxnOpcode::XAIE_IO_WRITE) {
495 Value address = constant(loc, op.cmd.RegOff);
496 Value value = constant(loc, op.cmd.Value);
497 AIEX::NpuWrite32Op::create(builder, loc, address, value, nullptr, nullptr,
498 nullptr);
499 } else if (op.cmd.Opcode == XAie_TxnOpcode::XAIE_IO_BLOCKWRITE) {
500 auto memref = memref::GetGlobalOp::create(builder, loc, payload.getType(),
501 payload.getName());
502 AIEX::NpuBlockWriteOp::create(
503 builder, loc, builder.getUI32IntegerAttr(op.cmd.RegOff),
504 memref.getResult(), nullptr, nullptr, nullptr);
505 } else if (op.cmd.Opcode == XAie_TxnOpcode::XAIE_IO_MASKWRITE) {
506 Value address = constant(loc, op.cmd.RegOff);
507 Value value = constant(loc, op.cmd.Value);
508 Value mask = constant(loc, op.cmd.Mask);
509 AIEX::NpuMaskWrite32Op::create(builder, loc, address, value, mask,
510 nullptr, nullptr, nullptr);
511 } else if (op.cmd.Opcode == XAie_TxnOpcode::XAIE_IO_CUSTOM_OP_TCT) {
512 if (!op.sync) {
513 llvm::errs() << "Missing sync payload while emitting transaction\n";
514 return failure();
515 }
516 const TransactionBinaryOperation::SyncPayload &sync = *op.sync;
517 Value column = constant(loc, sync.column);
518 Value row = constant(loc, sync.row);
519 Value direction = constant(loc, sync.direction);
520 Value channel = constant(loc, sync.channel);
521 Value columnCount = constant(loc, sync.columnCount);
522 Value rowCount = constant(loc, sync.rowCount);
523 AIEX::NpuSyncOp::create(builder, loc, column, row, direction, channel,
524 columnCount, rowCount);
525 } else if (op.cmd.Opcode == 0x8 /* XAie_TxnOpcode::XAIE_IO_LOAD_PDI */) {
526 if (!op.loadPdi) {
527 llvm::errs() << "Missing load_pdi payload while emitting transaction\n";
528 return failure();
529 }
530 const TransactionBinaryOperation::LoadPdiPayload &payloadInfo =
531 *op.loadPdi;
532 auto idAttr =
533 builder.getI32IntegerAttr(static_cast<int32_t>(payloadInfo.id));
534 IntegerAttr sizeAttr =
535 builder.getI32IntegerAttr(static_cast<int32_t>(payloadInfo.size));
536
537 auto ui64Ty =
538 IntegerType::get(builder.getContext(), 64, IntegerType::Unsigned);
539 IntegerAttr addressAttr =
540 IntegerAttr::get(ui64Ty, llvm::APInt(64, payloadInfo.address));
541
542 AIEX::NpuLoadPdiOp::create(builder, loc, /*device_ref=*/nullptr, idAttr,
543 sizeAttr, addressAttr,
544 /*expand_mode=*/nullptr);
545 } else if (op.cmd.Opcode == XAie_TxnOpcode::XAIE_IO_CUSTOM_OP_DDR_PATCH) {
546 if (!op.addressPatch) {
547 llvm::errs()
548 << "Missing address_patch payload while emitting transaction\n";
549 return failure();
550 }
551 const TransactionBinaryOperation::AddressPatchPayload &patch =
552 *op.addressPatch;
553 AIEX::NpuAddressPatchOp::create(
554 builder, loc, patch.addr,
555 /*addr_val=*/mlir::Value(), static_cast<int32_t>(patch.argIdx),
556 AIEX::createConstantArgPlus(builder, loc, patch.argPlus));
557 } else if (op.cmd.Opcode == 0x6 /* XAie_TxnOpcode::XAIE_IO_PREEMPT */) {
558 auto ui8Ty =
559 IntegerType::get(builder.getContext(), 8, IntegerType::Unsigned);
560 auto levelAttr = IntegerAttr::get(ui8Ty, llvm::APInt(8, op.cmd.Value));
561 AIEX::NpuPreemptOp::create(builder, loc, levelAttr);
562 } else {
563 llvm::errs() << "Unhandled txn opcode: " << op.cmd.Opcode << "\n";
564 return failure();
565 }
566 }
567 return success();
568}
569
570// Translate vector of TransactionBinaryOperation to a sequence of control
571// packet ops. Each emitted op gets the per-op `sourceLoc` from AIERT's
572// instruction-range bracketing if available; otherwise it falls back to
573// `fallbackLoc`.
574static LogicalResult
575emitControlPacketOps(OpBuilder &builder, Location fallbackLoc,
576 std::vector<TransactionBinaryOperation> &operations,
577 std::vector<memref::GlobalOp> &global_data) {
578
579 auto *ctx = builder.getContext();
580
581 // create the control packet ops
582 for (auto [op, payload] : llvm::zip(operations, global_data)) {
583 Location loc = op.sourceLoc.value_or(fallbackLoc);
584
585 if (op.cmd.Opcode == XAie_TxnOpcode::XAIE_IO_WRITE) {
586 AIEX::NpuControlPacketOp::create(
587 builder, loc, builder.getUI32IntegerAttr(op.cmd.RegOff), nullptr,
588 /*opcode*/ builder.getI32IntegerAttr(0),
589 /*stream_id*/ builder.getI32IntegerAttr(0),
590 DenseI32ArrayAttr::get(ctx, ArrayRef<int32_t>(op.cmd.Value)));
591 } else if (op.cmd.Opcode == XAie_TxnOpcode::XAIE_IO_BLOCKWRITE) {
592 auto initialValue = payload.getInitialValue();
593 if (!initialValue)
594 continue;
595 auto blockWriteData = dyn_cast<DenseIntElementsAttr>(*initialValue);
596 if (!blockWriteData) {
597 payload.emitError(
598 "Global symbol initial value is not a dense int array");
599 break;
600 }
601 auto blockWriteDataValues = blockWriteData.getValues<int32_t>();
602 // Split block write data into beats of 4 or less, in int32_t.
603 int currAddr = op.cmd.RegOff;
604 for (size_t i = 0; i < blockWriteDataValues.size(); i += 4) {
605 auto last = std::min(blockWriteDataValues.size(), i + 4);
606 SmallVector<int32_t> splitData =
607 SmallVector<int32_t>(blockWriteDataValues.begin() + i,
608 blockWriteDataValues.begin() + last);
609 AIEX::NpuControlPacketOp::create(
610 builder, loc, builder.getUI32IntegerAttr(currAddr), nullptr,
611 /*opcode*/ builder.getI32IntegerAttr(0),
612 /*stream_id*/ builder.getI32IntegerAttr(0),
613 DenseI32ArrayAttr::get(ctx, ArrayRef<int32_t>(splitData)));
614 currAddr += splitData.size() * sizeof(int32_t);
615 }
616
617 } else if (op.cmd.Opcode == XAie_TxnOpcode::XAIE_IO_MASKWRITE) {
618 AIEX::NpuControlPacketOp::create(
619 builder, loc, builder.getUI32IntegerAttr(op.cmd.RegOff), nullptr,
620 /*opcode*/ builder.getI32IntegerAttr(0),
621 /*stream_id*/ builder.getI32IntegerAttr(0),
622 DenseI32ArrayAttr::get(ctx, ArrayRef<int32_t>(op.cmd.Value)));
623 } else {
624 llvm::errs() << "Unhandled txn opcode: " << op.cmd.Opcode << "\n";
625 return failure();
626 }
627 }
628 return success();
629}
630
631// Perform bitwise or on consecutive control packets operating on the same
632// address, to resolve the lack of mask write in control packets.
633static LogicalResult orConsecutiveWritesOnSameAddr(Block *body) {
634 SmallVector<AIEX::NpuControlPacketOp> ctrlPktOps;
635 body->walk(
636 [&](AIEX::NpuControlPacketOp cpOp) { ctrlPktOps.push_back(cpOp); });
637 if (ctrlPktOps.empty())
638 return success();
639
640 SmallVector<Operation *> erased;
641 int addrBuffer = ctrlPktOps[0].getAddress();
642 AIEX::NpuControlPacketOp ctrlPktBuffer = ctrlPktOps[0];
643 for (size_t i = 1; i < ctrlPktOps.size(); i++) {
644 int currentAddrBuffer = ctrlPktOps[i].getAddress();
645 if (addrBuffer != currentAddrBuffer) {
646 addrBuffer = currentAddrBuffer;
647 ctrlPktBuffer = ctrlPktOps[i];
648 continue;
649 }
650 auto bufferedDataAttr = ctrlPktBuffer.getData();
651 auto currentDataAttr = ctrlPktOps[i].getData();
652 if (!bufferedDataAttr || !currentDataAttr) {
653 ctrlPktOps[i].emitError(
654 "cannot OR consecutive control packets on the same address: "
655 "control packet has no data payload");
656 return failure();
657 }
658 auto bufferedData = *bufferedDataAttr;
659 auto currentData = *currentDataAttr;
660 SmallVector<int> newData;
661 for (unsigned j = 0; j < std::max(bufferedData.size(), currentData.size());
662 j++) {
663 if (j < std::min(bufferedData.size(), currentData.size())) {
664 newData.push_back(bufferedData[j] | currentData[j]);
665 continue;
666 }
667 newData.push_back(j < bufferedData.size() ? bufferedData[j]
668 : currentData[j]);
669 }
670 ctrlPktBuffer.getProperties().data = DenseI32ArrayAttr::get(
671 ctrlPktBuffer->getContext(), ArrayRef<int>{newData});
672 erased.push_back(ctrlPktOps[i]);
673 }
674
675 for (auto *e : erased)
676 e->erase();
677
678 return success();
679}
680
681// Take transaction operations and insert them at the _current_ insertion point
682// of the supplied builder.
683static LogicalResult
684convertTransactionOpsToMLIR(OpBuilder builder,
686 std::vector<TransactionBinaryOperation> &operations,
687 AIEX::BlockwriteData &blockwriteData) {
688
689 // for each blockwrite in the binary, find or create a GlobalOp with the data
690 // at the device level
691 std::vector<memref::GlobalOp> global_data;
692 DeviceOp device = blockwriteData.getDevice();
693 Location loc = device.getLoc();
694 {
695 OpBuilder::InsertionGuard guard(builder);
696 builder.setInsertionPointToStart(device.getBody());
697 for (auto &op : operations) {
698 if (op.cmd.Opcode != XAIE_IO_BLOCKWRITE) {
699 global_data.emplace_back(nullptr);
700 continue;
701 }
702 const uint32_t *d = reinterpret_cast<const uint32_t *>(op.cmd.DataPtr);
703 global_data.push_back(blockwriteData.getOrCreate(
704 builder, loc, ArrayRef<uint32_t>(d, op.cmd.Size / 4)));
705 }
706 }
707
708 // create the txn ops
710 if (failed(emitTransactionOps(builder, loc, operations, global_data)))
711 return failure();
712 } else if (outputType == AIE::AIEToConfigurationOutputType::ControlPacket) {
713 if (failed(emitControlPacketOps(builder, loc, operations, global_data)))
714 return failure();
715 // resolve mask writes; control packet doesn't natively support mask write.
716 if (failed(orConsecutiveWritesOnSameAddr(builder.getBlock())))
717 return failure();
718 } else {
719 llvm_unreachable("bad output type");
720 }
721
722 return success();
723}
724
725// Convert (disassemble) a transaction binary to MLIR. On success return a new
726// ModuleOp containing a DeviceOp containing a runtime sequence with the
727// transaction binary encoded as a sequence of npu.write32, npu.maskwrite32 and
728// npu.blockwrite operations. On failure return std::nullopt.
729std::optional<mlir::ModuleOp>
731 std::vector<uint8_t> &binary) {
732
733 // parse the binary
734 std::vector<TransactionBinaryOperation> operations;
735 auto c = parseTransactionBinary(binary, operations);
736 if (!c) {
737 llvm::errs() << "Failed to parse binary\n";
738 return std::nullopt;
739 }
740 int columns = *c;
741
742 // write32/maskwrite32/sync/address_patch now carry their integer fields as
743 // SSA operands materialized via arith.constant, so ensure the dialect is
744 // loaded before emitting them.
745 ctx->getOrLoadDialect<mlir::arith::ArithDialect>();
746
747 auto loc = mlir::UnknownLoc::get(ctx);
748
749 // create a new ModuleOp and set the insertion point
750 auto module = ModuleOp::create(loc);
751 OpBuilder builder(module.getBodyRegion());
752 builder.setInsertionPointToStart(module.getBody());
753
754 // create aie.device
755 std::vector<AIEDevice> devices{AIEDevice::npu1_1col, AIEDevice::npu1_2col,
756 AIEDevice::npu1_3col, AIEDevice::npu1};
757 auto device = DeviceOp::create(builder, loc, devices[columns - 1],
758 DeviceOp::getDefaultDeviceName());
759 device.getRegion().emplaceBlock();
760 DeviceOp::ensureTerminator(device.getBodyRegion(), builder, loc);
761 builder.setInsertionPointToStart(device.getBody());
762
763 // convert the parsed ops to MLIR
764 AIEX::BlockwriteData blockwriteData(device, "config_blockwrite_data_");
765 if (failed(convertTransactionOpsToMLIR(
767 blockwriteData)))
768 return std::nullopt;
769
770 return module;
771}
772
773// Insert the ops of the transaction `ctl` recorded, as `outputType`.
774static LogicalResult
775insertRecordedTransaction(AIERTControl &ctl, OpBuilder &builder,
777 AIEX::BlockwriteData &blockwriteData) {
778 // Export the transactions to a binary buffer
779 std::vector<uint8_t> txn_data = ctl.exportSerializedTransaction();
780
781 // parse the binary data
782 std::vector<TransactionBinaryOperation> operations;
783 if (!parseTransactionBinary(txn_data, operations)) {
784 llvm::errs() << "Failed to parse binary\n";
785 return failure();
786 }
787
788 // Attach per-op source locations from AIERT's instruction-range bracketing.
789 // AIERTControl already projected those onto the serialized transaction's
790 // operations, so the indices line up with what the parser reproduced here.
791 // Ops with no bracketed location keep std::nullopt and inherit the device
792 // fallback location at emit time.
793 const std::vector<mlir::Location> &opLocs = ctl.getTxnOpLocs();
794 // Sharp edge: that projection models how aie-rt's serializer maps recorded
795 // commands to binary operations. A count disagreement means the model has
796 // drifted from the pinned aie-rt and every index past the divergence is
797 // mislabeled -- fail loudly instead of silently. An empty vector just means
798 // nothing was bracketed.
799 assert((opLocs.empty() || opLocs.size() == operations.size()) &&
800 "txn loc/op count mismatch: AIERTControl's command-to-operation "
801 "projection disagrees with the transaction parser; aie-rt's "
802 "serializer has drifted from projectCmdLocsOntoSerializedOps");
803 for (size_t i = 0, e = std::min(opLocs.size(), operations.size()); i < e; ++i)
804 if (!isa<UnknownLoc>(opLocs[i]))
805 operations[i].sourceLoc = opLocs[i];
806
807 return convertTransactionOpsToMLIR(builder, outputType, operations,
808 blockwriteData);
809}
810
812 OpBuilder &builder, xilinx::AIE::DeviceOp device,
813 AIEX::BlockwriteData &blockwriteData, llvm::StringRef clElfDir,
814 AIE::AIEToConfigurationOutputType outputType, bool skipCtrlPktOverlay) {
815 const AIETargetModel &targetModel =
816 (const AIETargetModel &)device.getTargetModel();
817
818 if (!targetModel.hasProperty(AIETargetModel::IsNPU))
819 return failure();
820
821 bool aieSim = false;
822 bool xaieDebug = false;
823
824 AIERTControl ctl(targetModel);
825 if (failed(ctl.setIOBackend(aieSim, xaieDebug)))
826 return failure();
827
828 // start collecting transactions
829 ctl.startTransaction();
830
831 bool generateElfs = true;
832 if (failed(generateTransactions(ctl, clElfDir, device, aieSim, generateElfs,
833 true, true, skipCtrlPktOverlay)))
834 return failure();
835
836 return insertRecordedTransaction(ctl, builder, outputType, blockwriteData);
837}
838
840 OpBuilder &builder, xilinx::AIE::DeviceOp device,
841 AIEX::BlockwriteData &blockwriteData,
843 const AIETargetModel &targetModel =
844 (const AIETargetModel &)device.getTargetModel();
845 if (!targetModel.hasProperty(AIETargetModel::IsNPU))
846 return failure();
847
848 AIERTControl ctl(targetModel);
849 if (failed(ctl.setIOBackend(/*aieSim=*/false, /*xaieDebug=*/false)))
850 return failure();
851 ctl.startTransaction();
852 if (failed(ctl.teardownSwitches(device)) ||
853 failed(ctl.disableCores(device)) || failed(ctl.resetDmaChannels(device)))
854 return failure();
855 return insertRecordedTransaction(ctl, builder, outputType, blockwriteData);
856}
857
858LogicalResult
859xilinx::AIE::insertConfigOps(ArrayRef<std::pair<Operation *, DeviceOp>> sites,
861 bool ctrlPkt = outputType == AIE::AIEToConfigurationOutputType::ControlPacket;
862 // Indexed by whether the site is a teardown.
863 std::array<llvm::DenseMap<DeviceOp, AIEX::BlockwriteData>, 2> blockwriteData;
864 std::array<llvm::DenseMap<std::pair<Operation *, Operation *>,
865 std::unique_ptr<Block>>,
866 2>
867 configs;
868 struct ConstantPool {
869 Operation *last = nullptr;
870 llvm::DenseMap<Attribute, Value> values;
871 };
872 llvm::DenseMap<Operation *, ConstantPool> pools;
873 for (auto [at, configured] : sites) {
874 auto device = at->getParentOfType<DeviceOp>();
875 if (!device)
876 return at->emitError("expected to be nested under an aie.device op");
877 bool teardown = isa<AIEX::NpuWriteTeardownOp>(at);
878 std::unique_ptr<Block> &config = configs[teardown][{configured, device}];
879 if (!config) {
880 AIEX::BlockwriteData &payloads =
881 blockwriteData[teardown]
882 .try_emplace(device, device, teardown ? "teardown_" : "loadpdi_")
883 .first->second;
884 auto generated = std::make_unique<Block>();
885 OpBuilder configBuilder(at->getContext());
886 configBuilder.setInsertionPointToEnd(generated.get());
887 if (teardown ? failed(generateAndInsertTeardownOps(
888 configBuilder, configured, payloads, outputType))
890 configBuilder, configured, payloads,
891 /*clElfDir=*/"", outputType,
892 /*skipCtrlPktOverlay=*/ctrlPkt)))
893 return at->emitError(teardown ? "Failed to generate teardown operations"
894 : "Failed to generate configuration "
895 "operations");
896 config = std::move(generated);
897 }
898 auto seq = at->getParentOfType<RuntimeSequenceOp>();
899 ConstantPool *pool = seq ? &pools[seq] : nullptr;
900 OpBuilder builder(at);
901 IRMapping mapping;
902 for (Operation &op : *config) {
903 Attribute key;
904 if (auto constant = dyn_cast<arith::ConstantOp>(op))
905 key = constant.getValue();
906 else if (auto global = dyn_cast<memref::GetGlobalOp>(op))
907 key = global.getNameAttr();
908 if (!pool || !key) {
909 builder.clone(op, mapping);
910 continue;
911 }
912 Value &pooled = pool->values[key];
913 if (!pooled) {
914 OpBuilder::InsertionGuard guard(builder);
915 if (pool->last)
916 builder.setInsertionPointAfter(pool->last);
917 else
918 builder.setInsertionPointToStart(&seq.getBody().front());
919 pool->last = builder.clone(op);
920 pooled = pool->last->getResult(0);
921 }
922 mapping.map(op.getResult(0), pooled);
923 }
924 }
925 return success();
926}
927
928LogicalResult xilinx::AIE::inlineWriteConfigs(ModuleOp module) {
929 SmallVector<std::pair<Operation *, DeviceOp>> sites;
930 WalkResult walk = module.walk([&](Operation *write) {
931 FlatSymbolRefAttr ref;
932 if (auto config = dyn_cast<AIEX::NpuWriteConfigOp>(write))
933 ref = config.getDeviceRefAttr();
934 else if (auto teardown = dyn_cast<AIEX::NpuWriteTeardownOp>(write))
935 ref = teardown.getDeviceRefAttr();
936 else
937 return WalkResult::advance();
938 auto configured = module.lookupSymbol<DeviceOp>(ref);
939 if (!configured) {
940 write->emitOpError() << ref << " is not a device";
941 return WalkResult::interrupt();
942 }
943 sites.emplace_back(write, configured);
944 return WalkResult::advance();
945 });
946 if (walk.wasInterrupted() ||
948 return failure();
949 for (auto [write, configured] : sites)
950 write->erase();
951 return success();
952}
953
954static LogicalResult
955convertAIEToConfiguration(AIE::DeviceOp device, StringRef clElfDir,
957
958 OpBuilder builder(device.getBodyRegion());
959 // search for aiex.configure ops in runtime sequences by walking the device
960 // and collect them in a vector. If there are none, create a new runtime
961 // sequence. Otherwise assume the insertion point is the first
962 // aiex.configure op.
963 auto loc = device.getLoc();
964 SmallVector<AIEX::ConfigureOp> configureOps;
965 device.walk([&](AIEX::ConfigureOp op) { configureOps.push_back(op); });
966
967 if (configureOps.empty()) {
968 // create aiex.runtime_sequence
969 int id = 0;
970 std::string seq_name = "configure";
971 while (device.lookupSymbol(seq_name))
972 seq_name = "configure" + std::to_string(id++);
973 StringAttr seq_sym_name = builder.getStringAttr(seq_name);
974 auto seq = AIE::RuntimeSequenceOp::create(
975 builder, loc, seq_sym_name, BoolAttr{}, AIE::TraceBufferAttr{},
976 ArrayAttr{}, ArrayAttr{});
977 seq.getBody().push_back(new Block);
978 builder.setInsertionPointToStart(&seq.getBody().front());
979 } else {
980 builder.setInsertionPoint(configureOps.front());
981 }
982
983 // convert the parsed ops to MLIR
984 AIEX::BlockwriteData blockwriteData(device, "config_blockwrite_data_");
985 if (failed(generateAndInsertConfigOps(builder, device, blockwriteData,
986 clElfDir, outputType)))
987 return failure();
988
989 // If we chose the first aiex.configure as insertion point, erase it
990 // and inline its child operations.
991 if (!configureOps.empty()) {
992 // splice the body into the current insertion point
993 builder.getBlock()->getOperations().splice(
994 builder.getInsertionPoint(),
995 configureOps.front().getBody().front().getOperations());
996 configureOps.front().erase();
997 }
998
999 return success();
1000}
1001
1002namespace {
1003
1004template <typename BaseClass, AIE::AIEToConfigurationOutputType MyOutputType>
1005struct ConvertAIEToConfigurationPass : BaseClass {
1006 std::string &ref_clElfDir;
1007 std::string &ref_clDeviceName;
1008 ConvertAIEToConfigurationPass(std::string &clElfDir,
1009 std::string &clDeviceName)
1010 : ref_clElfDir(clElfDir), ref_clDeviceName(clDeviceName) {}
1011
1012 void getDependentDialects(DialectRegistry &registry) const override {
1013 registry.insert<memref::MemRefDialect, AIEX::AIEXDialect,
1014 arith::ArithDialect>();
1015 }
1016
1017 void runOnOperation() override {
1018 AIE::DeviceOp deviceOp = BaseClass::getOperation();
1019 if (!ref_clDeviceName.empty() &&
1020 deviceOp.getSymName() != ref_clDeviceName) {
1021 return;
1022 }
1023 if (failed(
1024 convertAIEToConfiguration(deviceOp, ref_clElfDir, MyOutputType))) {
1025 return BaseClass::signalPassFailure();
1026 }
1027 }
1028};
1029
1030struct ConvertAIEToTransactionPass
1031 : ConvertAIEToConfigurationPass<
1032 xilinx::impl::ConvertAIEToTransactionBase<
1033 ConvertAIEToTransactionPass>,
1034 AIE::AIEToConfigurationOutputType::Transaction> {
1035 ConvertAIEToTransactionPass()
1036 : ConvertAIEToConfigurationPass<
1037 xilinx::impl::ConvertAIEToTransactionBase<
1038 ConvertAIEToTransactionPass>,
1040 clDeviceName) {}
1041};
1042
1043struct ConvertAIEToControlPacketsPass
1044 : ConvertAIEToConfigurationPass<
1045 xilinx::impl::ConvertAIEToControlPacketsBase<
1046 ConvertAIEToControlPacketsPass>,
1047 AIE::AIEToConfigurationOutputType::ControlPacket> {
1048 ConvertAIEToControlPacketsPass()
1049 : ConvertAIEToConfigurationPass<
1050 xilinx::impl::ConvertAIEToControlPacketsBase<
1051 ConvertAIEToControlPacketsPass>,
1053 clDeviceName) {}
1054};
1055
1056} // end anonymous namespace
1057
1058std::unique_ptr<mlir::OperationPass<xilinx::AIE::DeviceOp>>
1060 return std::make_unique<ConvertAIEToTransactionPass>();
1061}
1062
1063std::unique_ptr<mlir::OperationPass<xilinx::AIE::DeviceOp>>
1065 return std::make_unique<ConvertAIEToControlPacketsPass>();
1066}
memref::GlobalOp getOrCreate(OpBuilder &builder, mlir::Location loc, ArrayRef< uint32_t > words)
Definition AIEUtils.cpp:180
AIE::DeviceOp getDevice() const
Definition AIEUtils.h:38
bool hasProperty(ModelProperty Prop) const
mlir::Value createConstantArgPlus(mlir::OpBuilder &builder, mlir::Location loc, uint64_t value)
mlir::Value createConstantI32(mlir::OpBuilder &builder, mlir::Location loc, uint32_t value)
mlir::LogicalResult insertConfigOps(llvm::ArrayRef< std::pair< mlir::Operation *, xilinx::AIE::DeviceOp > > sites, AIEToConfigurationOutputType outputType)
mlir::LogicalResult inlineWriteConfigs(mlir::ModuleOp module)
std::unique_ptr< mlir::OperationPass< xilinx::AIE::DeviceOp > > createConvertAIEToTransactionPass()
mlir::LogicalResult generateAndInsertConfigOps(mlir::OpBuilder &builder, xilinx::AIE::DeviceOp device, xilinx::AIEX::BlockwriteData &blockwriteData, llvm::StringRef clElfDir="", AIEToConfigurationOutputType outputType=AIEToConfigurationOutputType::Transaction, bool skipCtrlPktOverlay=false)
std::optional< mlir::ModuleOp > convertTransactionBinaryToMLIR(mlir::MLIRContext *ctx, std::vector< uint8_t > &binary)
std::unique_ptr< mlir::OperationPass< xilinx::AIE::DeviceOp > > createConvertAIEToControlPacketsPass()
mlir::LogicalResult generateAndInsertTeardownOps(mlir::OpBuilder &builder, xilinx::AIE::DeviceOp device, xilinx::AIEX::BlockwriteData &blockwriteData, AIEToConfigurationOutputType outputType=AIEToConfigurationOutputType::Transaction)
mlir::LogicalResult setIOBackend(bool aieSim, bool xaieDebug)
Definition AIERT.cpp:307
std::vector< uint8_t > exportSerializedTransaction()
Definition AIERT.cpp:1308
mlir::LogicalResult disableCores(DeviceOp &targetOp)
Definition AIERT.cpp:901
mlir::LogicalResult addCoreEnable(DeviceOp &targetOp)
Definition AIERT.cpp:1041
const std::vector< mlir::Location > & getTxnOpLocs() const
Definition AIERT.cpp:1327
mlir::LogicalResult addInitConfig(DeviceOp &targetOp, bool skipCtrlPktOverlay=false)
Definition AIERT.cpp:952
mlir::LogicalResult resetDmaChannels(DeviceOp &targetOp)
Definition AIERT.cpp:911
mlir::LogicalResult addAieElfs(DeviceOp &targetOp, const mlir::StringRef elfPath, bool aieSim)
Definition AIERT.cpp:1207
mlir::LogicalResult teardownSwitches(DeviceOp &targetOp)
Definition AIERT.cpp:860