Skip to content

Commit 12bb1af

Browse files
committed
add pkt2mlir
1 parent 96655a8 commit 12bb1af

File tree

7 files changed

+277
-0
lines changed

7 files changed

+277
-0
lines changed

include/aie-c/Translation.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ MLIR_CAPI_EXPORTED MlirLogicalResult aieTranslateToCDODirect(
4040
bool xaieDebug, bool enableCores);
4141
MLIR_CAPI_EXPORTED MlirOperation aieTranslateBinaryToTxn(MlirContext ctx,
4242
MlirStringRef binary);
43+
MLIR_CAPI_EXPORTED MlirOperation
44+
aieTranslateBinaryToControlPackets(MlirContext ctx, MlirStringRef binary);
4345

4446
struct AieRtControl {
4547
void *ptr;

include/aie/Conversion/AIEToConfiguration/AIEToConfiguration.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ std::optional<mlir::ModuleOp>
2929
convertTransactionBinaryToMLIR(mlir::MLIRContext *ctx,
3030
std::vector<uint8_t> &binary);
3131

32+
std::optional<mlir::ModuleOp>
33+
convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx,
34+
std::vector<uint8_t> &binary);
35+
3236
} // namespace xilinx::AIE
3337

3438
#endif // AIE_CONVERSION_AIETOCONFIGURATION_AIETOCONFIGURATION_H

lib/CAPI/Translation.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ MlirOperation aieTranslateBinaryToTxn(MlirContext ctx, MlirStringRef binary) {
9494
return wrap(mod->getOperation());
9595
}
9696

97+
MlirOperation aieTranslateBinaryToControlPackets(MlirContext ctx,
98+
MlirStringRef binary) {
99+
std::vector<uint8_t> binaryData(binary.data, binary.data + binary.length);
100+
auto mod = convertControlPacketBinaryToMLIR(unwrap(ctx), binaryData);
101+
if (!mod)
102+
return wrap(ModuleOp().getOperation());
103+
return wrap(mod->getOperation());
104+
}
105+
97106
MlirStringRef aieTranslateNpuToBinary(MlirOperation moduleOp,
98107
MlirStringRef deviceNameMlir,
99108
MlirStringRef sequenceNameMlir) {

lib/Conversion/AIEToConfiguration/AIEToConfiguration.cpp

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "aie/Targets/AIERT.h"
1717

1818
#include "llvm/Support/Debug.h"
19+
#include "llvm/Support/Format.h"
1920
#include <llvm/ADT/APInt.h>
2021

2122
extern "C" {
@@ -98,8 +99,132 @@ struct TxnLoadPdiHeader {
9899
uint32_t size;
99100
uint64_t address;
100101
};
102+
103+
// A ControlPacketOperation encapsulates a parsed control packet including
104+
// stream header, control packet header, and optional data payload.
105+
struct ControlPacketOperation {
106+
uint32_t streamHeader; // word 0: parity + pkt_type + pkt_id
107+
uint32_t packetHeader; // word 1: parity + stream_id + opcode + beats + addr
108+
std::vector<uint32_t> data; // payload words (if opcode is write/blockwrite)
109+
110+
// Decoded fields
111+
uint8_t pktType;
112+
uint8_t pktId;
113+
uint8_t streamId;
114+
uint8_t opcode;
115+
uint8_t beats;
116+
uint32_t address;
117+
};
118+
101119
} // namespace
102120

121+
// Check even parity bit (bit 31)
122+
// The parity bit (bit 31) is set to 1 if the popcount of bits[30:0] is even
123+
static bool checkParity(uint32_t word) {
124+
uint32_t p = 0;
125+
uint32_t w = word & 0x7FFFFFFF; // mask off parity bit
126+
while (w) {
127+
p += w & 1;
128+
w >>= 1;
129+
}
130+
// Parity bit should be 1 if popcount is even, 0 if odd
131+
bool expectedParity = (p % 2) == 0;
132+
bool actualParity = (word >> 31) & 1;
133+
return expectedParity == actualParity;
134+
}
135+
136+
// Parse a control packet binary blob. On success return a vector of parsed
137+
// control packet operations. On failure return std::nullopt.
138+
static std::optional<std::vector<ControlPacketOperation>>
139+
parseControlPacket(const std::vector<uint8_t> &data) {
140+
std::vector<ControlPacketOperation> ops;
141+
142+
size_t i = 0;
143+
144+
auto requireBytes = [&](size_t offset, size_t length) -> bool {
145+
if (offset + length > data.size()) {
146+
llvm::errs() << "Control packet binary truncated\n";
147+
return false;
148+
}
149+
return true;
150+
};
151+
152+
auto read32 = [&](size_t offset) -> uint32_t {
153+
uint32_t value;
154+
std::memcpy(&value, data.data() + offset, sizeof(uint32_t));
155+
return value;
156+
};
157+
158+
while (i + 8 <= data.size()) { // Need at least 2 words (stream hdr + pkt hdr)
159+
ControlPacketOperation op;
160+
161+
// Read stream header (word 0)
162+
op.streamHeader = read32(i);
163+
164+
// Read control packet header (word 1)
165+
op.packetHeader = read32(i + 4);
166+
167+
// Verify parity
168+
if (!checkParity(op.streamHeader)) {
169+
llvm::errs() << "Stream header parity check failed at offset " << i << "\n";
170+
return std::nullopt;
171+
}
172+
if (!checkParity(op.packetHeader)) {
173+
llvm::errs() << "Packet header parity check failed at offset " << i + 4 << "\n";
174+
return std::nullopt;
175+
}
176+
177+
// Decode stream header fields
178+
op.pktType = (op.streamHeader >> 12) & 0x7;
179+
op.pktId = op.streamHeader & 0xFF;
180+
181+
// Decode control packet header fields
182+
op.streamId = (op.packetHeader >> 24) & 0x7F;
183+
op.opcode = (op.packetHeader >> 22) & 0x3;
184+
op.beats = (op.packetHeader >> 20) & 0x3;
185+
op.address = op.packetHeader & 0xFFFFF;
186+
187+
i += 8; // consumed 2 words
188+
189+
LLVM_DEBUG(llvm::dbgs() << "Control packet at offset " << (i - 8)
190+
<< ": opcode=" << static_cast<int>(op.opcode)
191+
<< " stream_id=" << static_cast<int>(op.streamId)
192+
<< " addr=0x" << llvm::format("%05X", op.address)
193+
<< " beats=" << static_cast<int>(op.beats) << "\n");
194+
195+
// Read data payload if present (opcode 0x0=write or 0x2=blockwrite)
196+
if (op.opcode == 0x0 || op.opcode == 0x2) {
197+
uint32_t numDataWords = op.beats + 1;
198+
if (!requireBytes(i, numDataWords * 4)) {
199+
llvm::errs() << "Truncated data payload\n";
200+
return std::nullopt;
201+
}
202+
203+
op.data.resize(numDataWords);
204+
for (uint32_t j = 0; j < numDataWords; j++) {
205+
op.data[j] = read32(i + j * 4);
206+
}
207+
i += numDataWords * 4;
208+
209+
LLVM_DEBUG(llvm::dbgs() << " Data: [";
210+
for (size_t j = 0; j < op.data.size(); j++) {
211+
if (j > 0) llvm::dbgs() << ", ";
212+
llvm::dbgs() << op.data[j];
213+
}
214+
llvm::dbgs() << "]\n");
215+
}
216+
217+
ops.push_back(std::move(op));
218+
}
219+
220+
if (i != data.size()) {
221+
llvm::errs() << "Warning: " << (data.size() - i)
222+
<< " bytes remaining after parsing control packets\n";
223+
}
224+
225+
return ops;
226+
}
227+
103228
// Parse a TXN binary blob. On success return the number of columns from the
104229
// header and a vector of parsed operations. On failure return std::nullopt.
105230
static std::optional<int>
@@ -731,6 +856,93 @@ xilinx::AIE::convertTransactionBinaryToMLIR(mlir::MLIRContext *ctx,
731856
return module;
732857
}
733858

859+
// Convert (disassemble) a control packet binary to MLIR. On success return a
860+
// new ModuleOp containing a DeviceOp containing a runtime sequence with the
861+
// control packet binary encoded as a sequence of aiex.control_packet
862+
// operations. On failure return std::nullopt.
863+
std::optional<mlir::ModuleOp>
864+
xilinx::AIE::convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx,
865+
std::vector<uint8_t> &binary) {
866+
867+
// parse the binary
868+
auto maybeOps = parseControlPacket(binary);
869+
if (!maybeOps) {
870+
llvm::errs() << "Failed to parse control packet binary\n";
871+
return std::nullopt;
872+
}
873+
std::vector<ControlPacketOperation> operations = *maybeOps;
874+
875+
auto loc = mlir::UnknownLoc::get(ctx);
876+
877+
// create a new ModuleOp and set the insertion point
878+
auto module = ModuleOp::create(loc);
879+
OpBuilder builder(module.getBodyRegion());
880+
builder.setInsertionPointToStart(module.getBody());
881+
882+
// Create aie.device - default to npu1_1col
883+
// Note: Control packets don't have device info in the binary format
884+
auto device = builder.create<DeviceOp>(loc, AIEDevice::npu1_1col,
885+
StringAttr::get(builder.getContext()));
886+
device.getRegion().emplaceBlock();
887+
DeviceOp::ensureTerminator(device.getBodyRegion(), builder, loc);
888+
builder.setInsertionPointToStart(device.getBody());
889+
890+
// Create tiles and set controller_id attributes based on packet info
891+
// Group operations by (pktType, pktId) to determine which tile they target
892+
std::map<std::pair<uint8_t, uint8_t>, std::pair<uint32_t, uint32_t>> tileMap;
893+
for (const auto &op : operations) {
894+
auto key = std::make_pair(op.pktType, op.pktId);
895+
if (tileMap.find(key) == tileMap.end()) {
896+
// Extract col/row from address using target model
897+
const AIETargetModel &targetModel = device.getTargetModel();
898+
uint32_t colInt = (op.address >> targetModel.getColumnShift()) & 0x1f;
899+
uint32_t rowInt = (op.address >> targetModel.getRowShift()) & 0x1f;
900+
tileMap[key] = std::make_pair(colInt, rowInt);
901+
902+
// Create tile and set controller_id attribute
903+
auto tile = TileOp::getOrCreate(builder, device, colInt, rowInt);
904+
auto packetInfoAttr = AIE::PacketInfoAttr::get(
905+
builder.getContext(), op.pktType, op.pktId);
906+
tile->setAttr("controller_id", packetInfoAttr);
907+
}
908+
}
909+
910+
// Create aiex.runtime_sequence
911+
std::string seq_name = "configure";
912+
StringAttr seq_sym_name = builder.getStringAttr(seq_name);
913+
auto seq = builder.create<AIEX::RuntimeSequenceOp>(loc, seq_sym_name);
914+
seq.getBody().push_back(new Block);
915+
916+
// Create control packet ops
917+
builder.setInsertionPointToStart(&seq.getBody().front());
918+
for (const auto &op : operations) {
919+
IntegerAttr lengthAttr;
920+
DenseI32ArrayAttr dataAttr;
921+
922+
if (op.opcode == 0x0 || op.opcode == 0x2) {
923+
// Write or blockwrite - has data payload
924+
SmallVector<int32_t> dataVec;
925+
for (uint32_t val : op.data) {
926+
dataVec.push_back(static_cast<int32_t>(val));
927+
}
928+
dataAttr = DenseI32ArrayAttr::get(ctx, ArrayRef<int32_t>(dataVec));
929+
} else if (op.opcode == 0x1) {
930+
// Read - has length but no data
931+
lengthAttr = builder.getI32IntegerAttr(op.beats + 1);
932+
}
933+
934+
builder.create<AIEX::NpuControlPacketOp>(
935+
loc,
936+
builder.getUI32IntegerAttr(op.address),
937+
lengthAttr,
938+
builder.getI32IntegerAttr(op.opcode),
939+
builder.getI32IntegerAttr(op.streamId),
940+
dataAttr);
941+
}
942+
943+
return module;
944+
}
945+
734946
static LogicalResult convertAIEToConfiguration(AIE::DeviceOp device,
735947
StringRef clElfDir,
736948
OutputType outputType) {

python/AIEMLIRModule.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ NB_MODULE(_aie, m) {
133133
},
134134
"ctx"_a, "binary"_a);
135135

136+
m.def(
137+
"control_packets_binary_to_mlir",
138+
[](MlirContext ctx, nb::bytes bytes) {
139+
MlirStringRef bin = {static_cast<const char *>(bytes.data()),
140+
bytes.size()};
141+
return aieTranslateBinaryToControlPackets(ctx, bin);
142+
},
143+
"ctx"_a, "binary"_a);
144+
136145
m.def(
137146
"translate_npu_to_binary",
138147
[](MlirOperation op, const std::string &device_name,

python/compiler/pkt2mlir/main.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env python3
2+
#
3+
# This file is licensed under the Apache License v2.0 with LLVM Exceptions.
4+
# See https://llvm.org/LICENSE.txt for license information.
5+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
#
7+
# Copyright (C) 2025, Advanced Micro Devices, Inc. All rights reserved.
8+
9+
from aie.ir import *
10+
from aie.dialects.aie import *
11+
12+
import argparse
13+
14+
15+
def main():
16+
# Parse arguments
17+
parser = argparse.ArgumentParser(
18+
description="Convert AIE control packet binaries to MLIR"
19+
)
20+
parser.add_argument(
21+
"-file",
22+
"-f",
23+
type=argparse.FileType("rb"),
24+
required=True,
25+
help="Input control packet binary file",
26+
)
27+
28+
args = parser.parse_args()
29+
30+
# Read the data from the file
31+
data = args.file.read()
32+
33+
with Context() as ctx:
34+
module = control_packets_binary_to_mlir(ctx, data)
35+
36+
print(str(module))
37+
38+
39+
if __name__ == "__main__":
40+
main()

python/dialects/aie.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
translate_aie_vec_to_cpp,
3939
translate_mlir_to_llvmir,
4040
transaction_binary_to_mlir,
41+
control_packets_binary_to_mlir,
4142
)
4243
from ..extras import types as T
4344
from ..extras.meta import region_op

0 commit comments

Comments
 (0)