From 2d70fda96cd46bec74d238f59cc8a1b6facd003c Mon Sep 17 00:00:00 2001 From: Jeff Fifield Date: Mon, 13 Oct 2025 16:57:59 -0600 Subject: [PATCH 1/5] add pkt2mlir --- include/aie-c/Translation.h | 2 + .../AIEToConfiguration/AIEToConfiguration.h | 4 + lib/CAPI/Translation.cpp | 9 + .../AIEToConfiguration/AIEToConfiguration.cpp | 212 ++++++++++++++++++ python/AIEMLIRModule.cpp | 9 + python/compiler/pkt2mlir/main.py | 40 ++++ python/dialects/aie.py | 1 + 7 files changed, 277 insertions(+) create mode 100644 python/compiler/pkt2mlir/main.py diff --git a/include/aie-c/Translation.h b/include/aie-c/Translation.h index 6f791f8889c..ce01e3666bf 100644 --- a/include/aie-c/Translation.h +++ b/include/aie-c/Translation.h @@ -40,6 +40,8 @@ MLIR_CAPI_EXPORTED MlirLogicalResult aieTranslateToCDODirect( bool xaieDebug, bool enableCores); MLIR_CAPI_EXPORTED MlirOperation aieTranslateBinaryToTxn(MlirContext ctx, MlirStringRef binary); +MLIR_CAPI_EXPORTED MlirOperation +aieTranslateBinaryToControlPackets(MlirContext ctx, MlirStringRef binary); struct AieRtControl { void *ptr; diff --git a/include/aie/Conversion/AIEToConfiguration/AIEToConfiguration.h b/include/aie/Conversion/AIEToConfiguration/AIEToConfiguration.h index f07f0fa0aa0..54fde2904a8 100644 --- a/include/aie/Conversion/AIEToConfiguration/AIEToConfiguration.h +++ b/include/aie/Conversion/AIEToConfiguration/AIEToConfiguration.h @@ -29,6 +29,10 @@ std::optional convertTransactionBinaryToMLIR(mlir::MLIRContext *ctx, std::vector &binary); +std::optional +convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx, + std::vector &binary); + } // namespace xilinx::AIE #endif // AIE_CONVERSION_AIETOCONFIGURATION_AIETOCONFIGURATION_H diff --git a/lib/CAPI/Translation.cpp b/lib/CAPI/Translation.cpp index ff47f9ad9fc..d93c25869ba 100644 --- a/lib/CAPI/Translation.cpp +++ b/lib/CAPI/Translation.cpp @@ -94,6 +94,15 @@ MlirOperation aieTranslateBinaryToTxn(MlirContext ctx, MlirStringRef binary) { return wrap(mod->getOperation()); } +MlirOperation aieTranslateBinaryToControlPackets(MlirContext ctx, + MlirStringRef binary) { + std::vector binaryData(binary.data, binary.data + binary.length); + auto mod = convertControlPacketBinaryToMLIR(unwrap(ctx), binaryData); + if (!mod) + return wrap(ModuleOp().getOperation()); + return wrap(mod->getOperation()); +} + MlirStringRef aieTranslateNpuToBinary(MlirOperation moduleOp, MlirStringRef deviceNameMlir, MlirStringRef sequenceNameMlir) { diff --git a/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp b/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp index 65a1b43e28c..c36b845c5c6 100644 --- a/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp +++ b/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp @@ -16,6 +16,7 @@ #include "aie/Targets/AIERT.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/Format.h" #include extern "C" { @@ -98,8 +99,132 @@ struct TxnLoadPdiHeader { uint32_t size; uint64_t address; }; + +// A ControlPacketOperation encapsulates a parsed control packet including +// stream header, control packet header, and optional data payload. +struct ControlPacketOperation { + uint32_t streamHeader; // word 0: parity + pkt_type + pkt_id + uint32_t packetHeader; // word 1: parity + stream_id + opcode + beats + addr + std::vector data; // payload words (if opcode is write/blockwrite) + + // Decoded fields + uint8_t pktType; + uint8_t pktId; + uint8_t streamId; + uint8_t opcode; + uint8_t beats; + uint32_t address; +}; + } // namespace +// Check even parity bit (bit 31) +// The parity bit (bit 31) is set to 1 if the popcount of bits[30:0] is even +static bool checkParity(uint32_t word) { + uint32_t p = 0; + uint32_t w = word & 0x7FFFFFFF; // mask off parity bit + while (w) { + p += w & 1; + w >>= 1; + } + // Parity bit should be 1 if popcount is even, 0 if odd + bool expectedParity = (p % 2) == 0; + bool actualParity = (word >> 31) & 1; + return expectedParity == actualParity; +} + +// Parse a control packet binary blob. On success return a vector of parsed +// control packet operations. On failure return std::nullopt. +static std::optional> +parseControlPacket(const std::vector &data) { + std::vector ops; + + size_t i = 0; + + auto requireBytes = [&](size_t offset, size_t length) -> bool { + if (offset + length > data.size()) { + llvm::errs() << "Control packet binary truncated\n"; + return false; + } + return true; + }; + + auto read32 = [&](size_t offset) -> uint32_t { + uint32_t value; + std::memcpy(&value, data.data() + offset, sizeof(uint32_t)); + return value; + }; + + while (i + 8 <= data.size()) { // Need at least 2 words (stream hdr + pkt hdr) + ControlPacketOperation op; + + // Read stream header (word 0) + op.streamHeader = read32(i); + + // Read control packet header (word 1) + op.packetHeader = read32(i + 4); + + // Verify parity + if (!checkParity(op.streamHeader)) { + llvm::errs() << "Stream header parity check failed at offset " << i << "\n"; + return std::nullopt; + } + if (!checkParity(op.packetHeader)) { + llvm::errs() << "Packet header parity check failed at offset " << i + 4 << "\n"; + return std::nullopt; + } + + // Decode stream header fields + op.pktType = (op.streamHeader >> 12) & 0x7; + op.pktId = op.streamHeader & 0xFF; + + // Decode control packet header fields + op.streamId = (op.packetHeader >> 24) & 0x7F; + op.opcode = (op.packetHeader >> 22) & 0x3; + op.beats = (op.packetHeader >> 20) & 0x3; + op.address = op.packetHeader & 0xFFFFF; + + i += 8; // consumed 2 words + + LLVM_DEBUG(llvm::dbgs() << "Control packet at offset " << (i - 8) + << ": opcode=" << static_cast(op.opcode) + << " stream_id=" << static_cast(op.streamId) + << " addr=0x" << llvm::format("%05X", op.address) + << " beats=" << static_cast(op.beats) << "\n"); + + // Read data payload if present (opcode 0x0=write or 0x2=blockwrite) + if (op.opcode == 0x0 || op.opcode == 0x2) { + uint32_t numDataWords = op.beats + 1; + if (!requireBytes(i, numDataWords * 4)) { + llvm::errs() << "Truncated data payload\n"; + return std::nullopt; + } + + op.data.resize(numDataWords); + for (uint32_t j = 0; j < numDataWords; j++) { + op.data[j] = read32(i + j * 4); + } + i += numDataWords * 4; + + LLVM_DEBUG(llvm::dbgs() << " Data: ["; + for (size_t j = 0; j < op.data.size(); j++) { + if (j > 0) llvm::dbgs() << ", "; + llvm::dbgs() << op.data[j]; + } + llvm::dbgs() << "]\n"); + } + + ops.push_back(std::move(op)); + } + + if (i != data.size()) { + llvm::errs() << "Warning: " << (data.size() - i) + << " bytes remaining after parsing control packets\n"; + } + + return ops; +} + // Parse a TXN binary blob. On success return the number of columns from the // header and a vector of parsed operations. On failure return std::nullopt. static std::optional @@ -748,6 +873,93 @@ xilinx::AIE::convertTransactionBinaryToMLIR(mlir::MLIRContext *ctx, return module; } +// Convert (disassemble) a control packet binary to MLIR. On success return a +// new ModuleOp containing a DeviceOp containing a runtime sequence with the +// control packet binary encoded as a sequence of aiex.control_packet +// operations. On failure return std::nullopt. +std::optional +xilinx::AIE::convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx, + std::vector &binary) { + + // parse the binary + auto maybeOps = parseControlPacket(binary); + if (!maybeOps) { + llvm::errs() << "Failed to parse control packet binary\n"; + return std::nullopt; + } + std::vector operations = *maybeOps; + + auto loc = mlir::UnknownLoc::get(ctx); + + // create a new ModuleOp and set the insertion point + auto module = ModuleOp::create(loc); + OpBuilder builder(module.getBodyRegion()); + builder.setInsertionPointToStart(module.getBody()); + + // Create aie.device - default to npu1_1col + // Note: Control packets don't have device info in the binary format + auto device = builder.create(loc, AIEDevice::npu1_1col, + StringAttr::get(builder.getContext())); + device.getRegion().emplaceBlock(); + DeviceOp::ensureTerminator(device.getBodyRegion(), builder, loc); + builder.setInsertionPointToStart(device.getBody()); + + // Create tiles and set controller_id attributes based on packet info + // Group operations by (pktType, pktId) to determine which tile they target + std::map, std::pair> tileMap; + for (const auto &op : operations) { + auto key = std::make_pair(op.pktType, op.pktId); + if (tileMap.find(key) == tileMap.end()) { + // Extract col/row from address using target model + const AIETargetModel &targetModel = device.getTargetModel(); + uint32_t colInt = (op.address >> targetModel.getColumnShift()) & 0x1f; + uint32_t rowInt = (op.address >> targetModel.getRowShift()) & 0x1f; + tileMap[key] = std::make_pair(colInt, rowInt); + + // Create tile and set controller_id attribute + auto tile = TileOp::getOrCreate(builder, device, colInt, rowInt); + auto packetInfoAttr = AIE::PacketInfoAttr::get( + builder.getContext(), op.pktType, op.pktId); + tile->setAttr("controller_id", packetInfoAttr); + } + } + + // Create aiex.runtime_sequence + std::string seq_name = "configure"; + StringAttr seq_sym_name = builder.getStringAttr(seq_name); + auto seq = builder.create(loc, seq_sym_name); + seq.getBody().push_back(new Block); + + // Create control packet ops + builder.setInsertionPointToStart(&seq.getBody().front()); + for (const auto &op : operations) { + IntegerAttr lengthAttr; + DenseI32ArrayAttr dataAttr; + + if (op.opcode == 0x0 || op.opcode == 0x2) { + // Write or blockwrite - has data payload + SmallVector dataVec; + for (uint32_t val : op.data) { + dataVec.push_back(static_cast(val)); + } + dataAttr = DenseI32ArrayAttr::get(ctx, ArrayRef(dataVec)); + } else if (op.opcode == 0x1) { + // Read - has length but no data + lengthAttr = builder.getI32IntegerAttr(op.beats + 1); + } + + builder.create( + loc, + builder.getUI32IntegerAttr(op.address), + lengthAttr, + builder.getI32IntegerAttr(op.opcode), + builder.getI32IntegerAttr(op.streamId), + dataAttr); + } + + return module; +} + static LogicalResult convertAIEToConfiguration(AIE::DeviceOp device, StringRef clElfDir, OutputType outputType) { diff --git a/python/AIEMLIRModule.cpp b/python/AIEMLIRModule.cpp index 59eeafe7842..630e4262bf0 100644 --- a/python/AIEMLIRModule.cpp +++ b/python/AIEMLIRModule.cpp @@ -133,6 +133,15 @@ NB_MODULE(_aie, m) { }, "ctx"_a, "binary"_a); + m.def( + "control_packets_binary_to_mlir", + [](MlirContext ctx, nb::bytes bytes) { + MlirStringRef bin = {static_cast(bytes.data()), + bytes.size()}; + return aieTranslateBinaryToControlPackets(ctx, bin); + }, + "ctx"_a, "binary"_a); + m.def( "translate_npu_to_binary", [](MlirOperation op, const std::string &device_name, diff --git a/python/compiler/pkt2mlir/main.py b/python/compiler/pkt2mlir/main.py new file mode 100644 index 00000000000..acbdbca1e41 --- /dev/null +++ b/python/compiler/pkt2mlir/main.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# +# This file is licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Copyright (C) 2025, Advanced Micro Devices, Inc. All rights reserved. + +from aie.ir import * +from aie.dialects.aie import * + +import argparse + + +def main(): + # Parse arguments + parser = argparse.ArgumentParser( + description="Convert AIE control packet binaries to MLIR" + ) + parser.add_argument( + "-file", + "-f", + type=argparse.FileType("rb"), + required=True, + help="Input control packet binary file", + ) + + args = parser.parse_args() + + # Read the data from the file + data = args.file.read() + + with Context() as ctx: + module = control_packets_binary_to_mlir(ctx, data) + + print(str(module)) + + +if __name__ == "__main__": + main() diff --git a/python/dialects/aie.py b/python/dialects/aie.py index 9f60efb9c9a..0d6a17bab4e 100644 --- a/python/dialects/aie.py +++ b/python/dialects/aie.py @@ -38,6 +38,7 @@ translate_aie_vec_to_cpp, translate_mlir_to_llvmir, transaction_binary_to_mlir, + control_packets_binary_to_mlir, ) from ..extras import types as T from ..extras.meta import region_op From bcfe19fc9b4ee92a12c1b647d3eb16c7600a151e Mon Sep 17 00:00:00 2001 From: Jeff Fifield Date: Mon, 13 Oct 2025 16:58:37 -0600 Subject: [PATCH 2/5] format --- lib/CAPI/Translation.cpp | 2 +- .../AIEToConfiguration/AIEToConfiguration.cpp | 83 ++++++------- python/compiler/pkt2mlir/README.md | 117 ++++++++++++++++++ 3 files changed, 159 insertions(+), 43 deletions(-) create mode 100644 python/compiler/pkt2mlir/README.md diff --git a/lib/CAPI/Translation.cpp b/lib/CAPI/Translation.cpp index d93c25869ba..49c5b058df5 100644 --- a/lib/CAPI/Translation.cpp +++ b/lib/CAPI/Translation.cpp @@ -95,7 +95,7 @@ MlirOperation aieTranslateBinaryToTxn(MlirContext ctx, MlirStringRef binary) { } MlirOperation aieTranslateBinaryToControlPackets(MlirContext ctx, - MlirStringRef binary) { + MlirStringRef binary) { std::vector binaryData(binary.data, binary.data + binary.length); auto mod = convertControlPacketBinaryToMLIR(unwrap(ctx), binaryData); if (!mod) diff --git a/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp b/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp index c36b845c5c6..f466b4d0c27 100644 --- a/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp +++ b/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp @@ -103,10 +103,10 @@ struct TxnLoadPdiHeader { // A ControlPacketOperation encapsulates a parsed control packet including // stream header, control packet header, and optional data payload. struct ControlPacketOperation { - uint32_t streamHeader; // word 0: parity + pkt_type + pkt_id - uint32_t packetHeader; // word 1: parity + stream_id + opcode + beats + addr - std::vector data; // payload words (if opcode is write/blockwrite) - + uint32_t streamHeader; // word 0: parity + pkt_type + pkt_id + uint32_t packetHeader; // word 1: parity + stream_id + opcode + beats + addr + std::vector data; // payload words (if opcode is write/blockwrite) + // Decoded fields uint8_t pktType; uint8_t pktId; @@ -138,9 +138,9 @@ static bool checkParity(uint32_t word) { static std::optional> parseControlPacket(const std::vector &data) { std::vector ops; - + size_t i = 0; - + auto requireBytes = [&](size_t offset, size_t length) -> bool { if (offset + length > data.size()) { llvm::errs() << "Control packet binary truncated\n"; @@ -148,50 +148,52 @@ parseControlPacket(const std::vector &data) { } return true; }; - + auto read32 = [&](size_t offset) -> uint32_t { uint32_t value; std::memcpy(&value, data.data() + offset, sizeof(uint32_t)); return value; }; - - while (i + 8 <= data.size()) { // Need at least 2 words (stream hdr + pkt hdr) + + while (i + 8 <= data.size()) { // Need at least 2 words (stream hdr + pkt hdr) ControlPacketOperation op; - + // Read stream header (word 0) op.streamHeader = read32(i); - + // Read control packet header (word 1) op.packetHeader = read32(i + 4); - + // Verify parity if (!checkParity(op.streamHeader)) { - llvm::errs() << "Stream header parity check failed at offset " << i << "\n"; + llvm::errs() << "Stream header parity check failed at offset " << i + << "\n"; return std::nullopt; } if (!checkParity(op.packetHeader)) { - llvm::errs() << "Packet header parity check failed at offset " << i + 4 << "\n"; + llvm::errs() << "Packet header parity check failed at offset " << i + 4 + << "\n"; return std::nullopt; } - + // Decode stream header fields op.pktType = (op.streamHeader >> 12) & 0x7; op.pktId = op.streamHeader & 0xFF; - + // Decode control packet header fields op.streamId = (op.packetHeader >> 24) & 0x7F; op.opcode = (op.packetHeader >> 22) & 0x3; op.beats = (op.packetHeader >> 20) & 0x3; op.address = op.packetHeader & 0xFFFFF; - - i += 8; // consumed 2 words - + + i += 8; // consumed 2 words + LLVM_DEBUG(llvm::dbgs() << "Control packet at offset " << (i - 8) << ": opcode=" << static_cast(op.opcode) << " stream_id=" << static_cast(op.streamId) << " addr=0x" << llvm::format("%05X", op.address) << " beats=" << static_cast(op.beats) << "\n"); - + // Read data payload if present (opcode 0x0=write or 0x2=blockwrite) if (op.opcode == 0x0 || op.opcode == 0x2) { uint32_t numDataWords = op.beats + 1; @@ -199,29 +201,29 @@ parseControlPacket(const std::vector &data) { llvm::errs() << "Truncated data payload\n"; return std::nullopt; } - + op.data.resize(numDataWords); for (uint32_t j = 0; j < numDataWords; j++) { op.data[j] = read32(i + j * 4); } i += numDataWords * 4; - - LLVM_DEBUG(llvm::dbgs() << " Data: ["; - for (size_t j = 0; j < op.data.size(); j++) { - if (j > 0) llvm::dbgs() << ", "; - llvm::dbgs() << op.data[j]; - } - llvm::dbgs() << "]\n"); + + LLVM_DEBUG(llvm::dbgs() << " Data: ["; for (size_t j = 0; + j < op.data.size(); j++) { + if (j > 0) + llvm::dbgs() << ", "; + llvm::dbgs() << op.data[j]; + } llvm::dbgs() << "]\n"); } - + ops.push_back(std::move(op)); } - + if (i != data.size()) { - llvm::errs() << "Warning: " << (data.size() - i) + llvm::errs() << "Warning: " << (data.size() - i) << " bytes remaining after parsing control packets\n"; } - + return ops; } @@ -915,11 +917,11 @@ xilinx::AIE::convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx, uint32_t colInt = (op.address >> targetModel.getColumnShift()) & 0x1f; uint32_t rowInt = (op.address >> targetModel.getRowShift()) & 0x1f; tileMap[key] = std::make_pair(colInt, rowInt); - + // Create tile and set controller_id attribute auto tile = TileOp::getOrCreate(builder, device, colInt, rowInt); - auto packetInfoAttr = AIE::PacketInfoAttr::get( - builder.getContext(), op.pktType, op.pktId); + auto packetInfoAttr = + AIE::PacketInfoAttr::get(builder.getContext(), op.pktType, op.pktId); tile->setAttr("controller_id", packetInfoAttr); } } @@ -935,7 +937,7 @@ xilinx::AIE::convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx, for (const auto &op : operations) { IntegerAttr lengthAttr; DenseI32ArrayAttr dataAttr; - + if (op.opcode == 0x0 || op.opcode == 0x2) { // Write or blockwrite - has data payload SmallVector dataVec; @@ -947,14 +949,11 @@ xilinx::AIE::convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx, // Read - has length but no data lengthAttr = builder.getI32IntegerAttr(op.beats + 1); } - + builder.create( - loc, - builder.getUI32IntegerAttr(op.address), - lengthAttr, + loc, builder.getUI32IntegerAttr(op.address), lengthAttr, builder.getI32IntegerAttr(op.opcode), - builder.getI32IntegerAttr(op.streamId), - dataAttr); + builder.getI32IntegerAttr(op.streamId), dataAttr); } return module; diff --git a/python/compiler/pkt2mlir/README.md b/python/compiler/pkt2mlir/README.md new file mode 100644 index 00000000000..a749b0384dd --- /dev/null +++ b/python/compiler/pkt2mlir/README.md @@ -0,0 +1,117 @@ +# pkt2mlir - Control Packet Binary to MLIR Disassembler + +`pkt2mlir.py` is a tool that converts AIE NPU control packet binaries into human-readable MLIR format. This is useful for debugging, inspecting compiled configurations, and understanding the control packet operations sent to the NPU. + +## Overview + +The tool parses binary control packet files generated by `aie-translate --aie-ctrlpkt-to-bin` and reconstructs the equivalent MLIR module containing: +- Device configuration (`aie.device`) +- Tile controller_id attributes (`PacketInfoAttr`) +- Runtime sequences with control packet operations (`aiex.control_packet`) + +## Usage + +```bash +pkt2mlir.py -f +``` + +### Options + +- `-f`, `-file FILE` : Input control packet binary file (required) +- `-h`, `--help` : Show help message and exit + +## Examples + +### Basic Control Packet Operations Round-trip + +Generate control packet binary and disassemble it: + +```bash +# Create a simple MLIR file with control packets +cat > control_packets.mlir << 'EOF' +module { + aie.device(npu1) { + %tile00 = aie.tile(0, 0) {controller_id = #aie.packet_info} + + aiex.runtime_sequence() { + aiex.control_packet {address = 0x0001F000 : ui32, opcode = 0 : i32, stream_id = 0 : i32, data = array} + aiex.control_packet {address = 0x0001F020 : ui32, opcode = 2 : i32, stream_id = 9 : i32, data = array} + aiex.control_packet {address = 0x00000400 : ui32, opcode = 1 : i32, stream_id = 2 : i32, length = 4 : i32} + } + } +} +EOF + +# Compile to binary +aie-translate --aie-ctrlpkt-to-bin control_packets.mlir -o control_packets.bin +``` + +View the raw control packet binary structure: + +```bash +# Inspect binary structure (2 word header + optional data for each packet) +hexdump -C control_packets.bin +``` + +``` +00000000 00 20 00 03 00 1f 00 00 02 00 00 00 00 20 00 03 |. .......... ..| +00000010 09 b1 f0 20 03 00 00 00 04 00 00 00 05 00 00 00 |... ............| +00000020 06 00 00 00 00 20 00 03 02 70 04 00 |..... ...p..| +``` + +Translate back to MLIR: + +```bash +pkt2mlir.py -f control_packets.bin +``` + +```mlir +module { + aie.device(npu1_1col) { + %shim_noc_tile_0_0 = aie.tile(0, 0) {controller_id = #aie.packet_info} + + aiex.runtime_sequence @configure() { + aiex.control_packet {address = 126976 : ui32, data = array, opcode = 0 : i32, stream_id = 0 : i32} + aiex.control_packet {address = 127008 : ui32, data = array, opcode = 2 : i32, stream_id = 9 : i32} + aiex.control_packet {address = 1024 : ui32, length = 4 : i32, opcode = 1 : i32, stream_id = 2 : i32} + } + } +} +``` + +## Control Packet Binary Format + +Each control packet in the binary consists of: + +1. **Stream Header** (32 bits): + - Bit [31]: Even parity bit + - Bits [14:12]: Packet type (3 bits) + - Bits [7:0]: Packet ID (8 bits) + +2. **Control Packet Header** (32 bits): + - Bit [31]: Even parity bit + - Bits [30:24]: Stream ID (7 bits) + - Bits [23:22]: Opcode (2 bits) + - `0x0` = Write + - `0x1` = Read + - `0x2` = Block write + - Bits [21:20]: Beats (size-1, 2 bits) + - Bits [19:0]: Address (20 bits) + +3. **Data Payload** (optional, 32-bit words): + - Present for write (opcode 0x0) and block write (opcode 0x2) + - Number of words = beats + 1 + +## Use Cases + +- **Debugging**: Inspect what control packets are being sent to the NPU +- **Verification**: Compare generated binaries against expected values +- **Reverse Engineering**: Understand control packet configurations +- **Testing**: Validate round-trip conversion (MLIR → binary → MLIR) +- **Documentation**: Generate human-readable documentation from binaries + +## See Also + +- `txn2mlir.py` - Transaction binary to MLIR disassembler +- `aie-translate --aie-ctrlpkt-to-bin` - Generate control packet binaries +- `aie-translate --aie-npu-to-binary` - Generate transaction binaries From eb04a5096c98fe540676084af3058a9c6c57da42 Mon Sep 17 00:00:00 2001 From: Jeff Fifield Date: Mon, 13 Oct 2025 16:59:18 -0600 Subject: [PATCH 3/5] fixup --- python/compiler/pkt2mlir.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 python/compiler/pkt2mlir.py diff --git a/python/compiler/pkt2mlir.py b/python/compiler/pkt2mlir.py new file mode 100644 index 00000000000..2990ac231bf --- /dev/null +++ b/python/compiler/pkt2mlir.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# +# This file is licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Copyright (C) 2024-2025, Advanced Micro Devices, Inc. All rights reserved. + +from aie.compiler.pkt2mlir.main import main + +if __name__ == "__main__": + main() From e69d91612f7fc088dc3942d9bf28e984c2bcf374 Mon Sep 17 00:00:00 2001 From: Jeff Fifield Date: Mon, 13 Oct 2025 17:01:34 -0600 Subject: [PATCH 4/5] install pkt2mlir --- python/CMakeLists.txt | 4 +++- python/compiler/pkt2mlir.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 97e9b161a53..de4470b69e7 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -110,6 +110,7 @@ declare_mlir_python_sources(AIEPythonSources.Compiler SOURCES_GLOB compiler/aiecc/*.py compiler/txn2mlir/*.py + compiler/pkt2mlir/*.py ) if (AIE_ENABLE_XRT_PYTHON_BINDINGS) @@ -424,8 +425,9 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/compiler/aiecc.py ${CMAKE_CURRENT_SOURCE_DIR}/compiler/txn2mlir.py + ${CMAKE_CURRENT_SOURCE_DIR}/compiler/pkt2mlir.py ${CMAKE_BINARY_DIR}/bin ) # during install -install(PROGRAMS compiler/aiecc.py compiler/txn2mlir.py DESTINATION bin) +install(PROGRAMS compiler/aiecc.py compiler/txn2mlir.py compiler/pkt2mlir.py DESTINATION bin) diff --git a/python/compiler/pkt2mlir.py b/python/compiler/pkt2mlir.py index 2990ac231bf..9493b388460 100644 --- a/python/compiler/pkt2mlir.py +++ b/python/compiler/pkt2mlir.py @@ -4,7 +4,7 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # -# Copyright (C) 2024-2025, Advanced Micro Devices, Inc. All rights reserved. +# Copyright (C) 2025, Advanced Micro Devices, Inc. All rights reserved. from aie.compiler.pkt2mlir.main import main From b0fe4126a0c42d2dcd8551f7ec7ac40e7817a867 Mon Sep 17 00:00:00 2001 From: Jeff Fifield Date: Thu, 23 Oct 2025 15:38:01 -0600 Subject: [PATCH 5/5] Add command line device selection --- include/aie-c/Translation.h | 4 ++-- .../AIEToConfiguration/AIEToConfiguration.h | 7 +++--- lib/CAPI/Translation.cpp | 6 +++-- .../AIEToConfiguration/AIEToConfiguration.cpp | 23 +++++++++---------- python/AIEMLIRModule.cpp | 6 ++--- python/compiler/pkt2mlir/README.md | 14 +++++++++-- python/compiler/pkt2mlir/main.py | 12 +++++++++- 7 files changed, 47 insertions(+), 25 deletions(-) diff --git a/include/aie-c/Translation.h b/include/aie-c/Translation.h index ce01e3666bf..1b67eef4e09 100644 --- a/include/aie-c/Translation.h +++ b/include/aie-c/Translation.h @@ -40,8 +40,8 @@ MLIR_CAPI_EXPORTED MlirLogicalResult aieTranslateToCDODirect( bool xaieDebug, bool enableCores); MLIR_CAPI_EXPORTED MlirOperation aieTranslateBinaryToTxn(MlirContext ctx, MlirStringRef binary); -MLIR_CAPI_EXPORTED MlirOperation -aieTranslateBinaryToControlPackets(MlirContext ctx, MlirStringRef binary); +MLIR_CAPI_EXPORTED MlirOperation aieTranslateBinaryToControlPackets( + MlirContext ctx, MlirStringRef binary, int device); struct AieRtControl { void *ptr; diff --git a/include/aie/Conversion/AIEToConfiguration/AIEToConfiguration.h b/include/aie/Conversion/AIEToConfiguration/AIEToConfiguration.h index 54fde2904a8..fd4b930596e 100644 --- a/include/aie/Conversion/AIEToConfiguration/AIEToConfiguration.h +++ b/include/aie/Conversion/AIEToConfiguration/AIEToConfiguration.h @@ -17,6 +17,7 @@ namespace xilinx::AIE { +enum class AIEDevice : uint32_t; class DeviceOp; std::unique_ptr> @@ -29,9 +30,9 @@ std::optional convertTransactionBinaryToMLIR(mlir::MLIRContext *ctx, std::vector &binary); -std::optional -convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx, - std::vector &binary); +std::optional convertControlPacketBinaryToMLIR( + mlir::MLIRContext *ctx, std::vector &binary, + AIEDevice device = static_cast(4)); /* npu1 */ } // namespace xilinx::AIE diff --git a/lib/CAPI/Translation.cpp b/lib/CAPI/Translation.cpp index 49c5b058df5..4e582f1c911 100644 --- a/lib/CAPI/Translation.cpp +++ b/lib/CAPI/Translation.cpp @@ -95,9 +95,11 @@ MlirOperation aieTranslateBinaryToTxn(MlirContext ctx, MlirStringRef binary) { } MlirOperation aieTranslateBinaryToControlPackets(MlirContext ctx, - MlirStringRef binary) { + MlirStringRef binary, + int device) { std::vector binaryData(binary.data, binary.data + binary.length); - auto mod = convertControlPacketBinaryToMLIR(unwrap(ctx), binaryData); + auto mod = convertControlPacketBinaryToMLIR(unwrap(ctx), binaryData, + static_cast(device)); if (!mod) return wrap(ModuleOp().getOperation()); return wrap(mod->getOperation()); diff --git a/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp b/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp index f466b4d0c27..b982ec6da26 100644 --- a/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp +++ b/lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp @@ -879,9 +879,8 @@ xilinx::AIE::convertTransactionBinaryToMLIR(mlir::MLIRContext *ctx, // new ModuleOp containing a DeviceOp containing a runtime sequence with the // control packet binary encoded as a sequence of aiex.control_packet // operations. On failure return std::nullopt. -std::optional -xilinx::AIE::convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx, - std::vector &binary) { +std::optional xilinx::AIE::convertControlPacketBinaryToMLIR( + mlir::MLIRContext *ctx, std::vector &binary, AIEDevice device) { // parse the binary auto maybeOps = parseControlPacket(binary); @@ -898,13 +897,13 @@ xilinx::AIE::convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx, OpBuilder builder(module.getBodyRegion()); builder.setInsertionPointToStart(module.getBody()); - // Create aie.device - default to npu1_1col - // Note: Control packets don't have device info in the binary format - auto device = builder.create(loc, AIEDevice::npu1_1col, - StringAttr::get(builder.getContext())); - device.getRegion().emplaceBlock(); - DeviceOp::ensureTerminator(device.getBodyRegion(), builder, loc); - builder.setInsertionPointToStart(device.getBody()); + // Create aie.device with specified device type + auto deviceAttr = AIEDeviceAttr::get(ctx, device); + auto deviceOp = builder.create( + loc, deviceAttr, StringAttr::get(builder.getContext())); + deviceOp.getRegion().emplaceBlock(); + DeviceOp::ensureTerminator(deviceOp.getBodyRegion(), builder, loc); + builder.setInsertionPointToStart(deviceOp.getBody()); // Create tiles and set controller_id attributes based on packet info // Group operations by (pktType, pktId) to determine which tile they target @@ -913,13 +912,13 @@ xilinx::AIE::convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx, auto key = std::make_pair(op.pktType, op.pktId); if (tileMap.find(key) == tileMap.end()) { // Extract col/row from address using target model - const AIETargetModel &targetModel = device.getTargetModel(); + const AIETargetModel &targetModel = deviceOp.getTargetModel(); uint32_t colInt = (op.address >> targetModel.getColumnShift()) & 0x1f; uint32_t rowInt = (op.address >> targetModel.getRowShift()) & 0x1f; tileMap[key] = std::make_pair(colInt, rowInt); // Create tile and set controller_id attribute - auto tile = TileOp::getOrCreate(builder, device, colInt, rowInt); + auto tile = TileOp::getOrCreate(builder, deviceOp, colInt, rowInt); auto packetInfoAttr = AIE::PacketInfoAttr::get(builder.getContext(), op.pktType, op.pktId); tile->setAttr("controller_id", packetInfoAttr); diff --git a/python/AIEMLIRModule.cpp b/python/AIEMLIRModule.cpp index 630e4262bf0..5042b54841b 100644 --- a/python/AIEMLIRModule.cpp +++ b/python/AIEMLIRModule.cpp @@ -135,12 +135,12 @@ NB_MODULE(_aie, m) { m.def( "control_packets_binary_to_mlir", - [](MlirContext ctx, nb::bytes bytes) { + [](MlirContext ctx, nb::bytes bytes, int device) { MlirStringRef bin = {static_cast(bytes.data()), bytes.size()}; - return aieTranslateBinaryToControlPackets(ctx, bin); + return aieTranslateBinaryToControlPackets(ctx, bin, device); }, - "ctx"_a, "binary"_a); + "ctx"_a, "binary"_a, "device"_a = 4); /* npu1 */ m.def( "translate_npu_to_binary", diff --git a/python/compiler/pkt2mlir/README.md b/python/compiler/pkt2mlir/README.md index a749b0384dd..7fdf70196a8 100644 --- a/python/compiler/pkt2mlir/README.md +++ b/python/compiler/pkt2mlir/README.md @@ -12,12 +12,14 @@ The tool parses binary control packet files generated by `aie-translate --aie-ct ## Usage ```bash -pkt2mlir.py -f +pkt2mlir.py -f [-d ] ``` ### Options - `-f`, `-file FILE` : Input control packet binary file (required) +- `-d`, `-device DEVICE` : Target AIE device type (default: npu1) + - Supported devices: xcvc1902, xcve2302, xcve2802, npu1, npu1_1col, npu1_2col, npu1_3col, npu2, npu2_1col, npu2_2col, npu2_3col, npu2_4col, npu2_5col, npu2_6col, npu2_7col - `-h`, `--help` : Show help message and exit ## Examples @@ -65,9 +67,17 @@ Translate back to MLIR: pkt2mlir.py -f control_packets.bin ``` +Or specify a different device type: + +```bash +pkt2mlir.py -f control_packets.bin -d npu1 +``` + +Output: + ```mlir module { - aie.device(npu1_1col) { + aie.device(npu1) { %shim_noc_tile_0_0 = aie.tile(0, 0) {controller_id = #aie.packet_info} aiex.runtime_sequence @configure() { diff --git a/python/compiler/pkt2mlir/main.py b/python/compiler/pkt2mlir/main.py index acbdbca1e41..813942a89cf 100644 --- a/python/compiler/pkt2mlir/main.py +++ b/python/compiler/pkt2mlir/main.py @@ -24,14 +24,24 @@ def main(): required=True, help="Input control packet binary file", ) + parser.add_argument( + "-device", + "-d", + type=str, + default="npu1", + help="Target AIE device type (default: npu1)", + ) args = parser.parse_args() # Read the data from the file data = args.file.read() + # Get the device enum value + device_value = getattr(AIEDevice, args.device) + with Context() as ctx: - module = control_packets_binary_to_mlir(ctx, data) + module = control_packets_binary_to_mlir(ctx, data, device_value) print(str(module))