Skip to content

Commit 07ce0a8

Browse files
committed
add pkt2mlir
1 parent 0830645 commit 07ce0a8

File tree

7 files changed

+278
-0
lines changed

7 files changed

+278
-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: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include "aie/Targets/AIERT.h"
1717

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

2022
extern "C" {
2123
#include "xaiengine/xaiegbl_defs.h"
@@ -47,8 +49,132 @@ struct TransactionBinaryOperation {
4749
cmd.Size = size;
4850
}
4951
};
52+
53+
// A ControlPacketOperation encapsulates a parsed control packet including
54+
// stream header, control packet header, and optional data payload.
55+
struct ControlPacketOperation {
56+
uint32_t streamHeader; // word 0: parity + pkt_type + pkt_id
57+
uint32_t packetHeader; // word 1: parity + stream_id + opcode + beats + addr
58+
std::vector<uint32_t> data; // payload words (if opcode is write/blockwrite)
59+
60+
// Decoded fields
61+
uint8_t pktType;
62+
uint8_t pktId;
63+
uint8_t streamId;
64+
uint8_t opcode;
65+
uint8_t beats;
66+
uint32_t address;
67+
};
68+
5069
} // namespace
5170

71+
// Check even parity bit (bit 31)
72+
// The parity bit (bit 31) is set to 1 if the popcount of bits[30:0] is even
73+
static bool checkParity(uint32_t word) {
74+
uint32_t p = 0;
75+
uint32_t w = word & 0x7FFFFFFF; // mask off parity bit
76+
while (w) {
77+
p += w & 1;
78+
w >>= 1;
79+
}
80+
// Parity bit should be 1 if popcount is even, 0 if odd
81+
bool expectedParity = (p % 2) == 0;
82+
bool actualParity = (word >> 31) & 1;
83+
return expectedParity == actualParity;
84+
}
85+
86+
// Parse a control packet binary blob. On success return a vector of parsed
87+
// control packet operations. On failure return std::nullopt.
88+
static std::optional<std::vector<ControlPacketOperation>>
89+
parseControlPacket(const std::vector<uint8_t> &data) {
90+
std::vector<ControlPacketOperation> ops;
91+
92+
size_t i = 0;
93+
94+
auto requireBytes = [&](size_t offset, size_t length) -> bool {
95+
if (offset + length > data.size()) {
96+
llvm::errs() << "Control packet binary truncated\n";
97+
return false;
98+
}
99+
return true;
100+
};
101+
102+
auto read32 = [&](size_t offset) -> uint32_t {
103+
uint32_t value;
104+
std::memcpy(&value, data.data() + offset, sizeof(uint32_t));
105+
return value;
106+
};
107+
108+
while (i + 8 <= data.size()) { // Need at least 2 words (stream hdr + pkt hdr)
109+
ControlPacketOperation op;
110+
111+
// Read stream header (word 0)
112+
op.streamHeader = read32(i);
113+
114+
// Read control packet header (word 1)
115+
op.packetHeader = read32(i + 4);
116+
117+
// Verify parity
118+
if (!checkParity(op.streamHeader)) {
119+
llvm::errs() << "Stream header parity check failed at offset " << i << "\n";
120+
return std::nullopt;
121+
}
122+
if (!checkParity(op.packetHeader)) {
123+
llvm::errs() << "Packet header parity check failed at offset " << i + 4 << "\n";
124+
return std::nullopt;
125+
}
126+
127+
// Decode stream header fields
128+
op.pktType = (op.streamHeader >> 12) & 0x7;
129+
op.pktId = op.streamHeader & 0xFF;
130+
131+
// Decode control packet header fields
132+
op.streamId = (op.packetHeader >> 24) & 0x7F;
133+
op.opcode = (op.packetHeader >> 22) & 0x3;
134+
op.beats = (op.packetHeader >> 20) & 0x3;
135+
op.address = op.packetHeader & 0xFFFFF;
136+
137+
i += 8; // consumed 2 words
138+
139+
LLVM_DEBUG(llvm::dbgs() << "Control packet at offset " << (i - 8)
140+
<< ": opcode=" << static_cast<int>(op.opcode)
141+
<< " stream_id=" << static_cast<int>(op.streamId)
142+
<< " addr=0x" << llvm::format("%05X", op.address)
143+
<< " beats=" << static_cast<int>(op.beats) << "\n");
144+
145+
// Read data payload if present (opcode 0x0=write or 0x2=blockwrite)
146+
if (op.opcode == 0x0 || op.opcode == 0x2) {
147+
uint32_t numDataWords = op.beats + 1;
148+
if (!requireBytes(i, numDataWords * 4)) {
149+
llvm::errs() << "Truncated data payload\n";
150+
return std::nullopt;
151+
}
152+
153+
op.data.resize(numDataWords);
154+
for (uint32_t j = 0; j < numDataWords; j++) {
155+
op.data[j] = read32(i + j * 4);
156+
}
157+
i += numDataWords * 4;
158+
159+
LLVM_DEBUG(llvm::dbgs() << " Data: [";
160+
for (size_t j = 0; j < op.data.size(); j++) {
161+
if (j > 0) llvm::dbgs() << ", ";
162+
llvm::dbgs() << op.data[j];
163+
}
164+
llvm::dbgs() << "]\n");
165+
}
166+
167+
ops.push_back(std::move(op));
168+
}
169+
170+
if (i != data.size()) {
171+
llvm::errs() << "Warning: " << (data.size() - i)
172+
<< " bytes remaining after parsing control packets\n";
173+
}
174+
175+
return ops;
176+
}
177+
52178
// Parse a TXN binary blob. On success return the number of columns from the
53179
// header and a vector of parsed operations. On failure return std::nullopt.
54180
static std::optional<int>
@@ -451,6 +577,93 @@ xilinx::AIE::convertTransactionBinaryToMLIR(mlir::MLIRContext *ctx,
451577
return module;
452578
}
453579

580+
// Convert (disassemble) a control packet binary to MLIR. On success return a
581+
// new ModuleOp containing a DeviceOp containing a runtime sequence with the
582+
// control packet binary encoded as a sequence of aiex.control_packet
583+
// operations. On failure return std::nullopt.
584+
std::optional<mlir::ModuleOp>
585+
xilinx::AIE::convertControlPacketBinaryToMLIR(mlir::MLIRContext *ctx,
586+
std::vector<uint8_t> &binary) {
587+
588+
// parse the binary
589+
auto maybeOps = parseControlPacket(binary);
590+
if (!maybeOps) {
591+
llvm::errs() << "Failed to parse control packet binary\n";
592+
return std::nullopt;
593+
}
594+
std::vector<ControlPacketOperation> operations = *maybeOps;
595+
596+
auto loc = mlir::UnknownLoc::get(ctx);
597+
598+
// create a new ModuleOp and set the insertion point
599+
auto module = ModuleOp::create(loc);
600+
OpBuilder builder(module.getBodyRegion());
601+
builder.setInsertionPointToStart(module.getBody());
602+
603+
// Create aie.device - default to npu1_1col
604+
// Note: Control packets don't have device info in the binary format
605+
auto device = builder.create<DeviceOp>(loc, AIEDevice::npu1_1col,
606+
StringAttr::get(builder.getContext()));
607+
device.getRegion().emplaceBlock();
608+
DeviceOp::ensureTerminator(device.getBodyRegion(), builder, loc);
609+
builder.setInsertionPointToStart(device.getBody());
610+
611+
// Create tiles and set controller_id attributes based on packet info
612+
// Group operations by (pktType, pktId) to determine which tile they target
613+
std::map<std::pair<uint8_t, uint8_t>, std::pair<uint32_t, uint32_t>> tileMap;
614+
for (const auto &op : operations) {
615+
auto key = std::make_pair(op.pktType, op.pktId);
616+
if (tileMap.find(key) == tileMap.end()) {
617+
// Extract col/row from address using target model
618+
const AIETargetModel &targetModel = device.getTargetModel();
619+
uint32_t colInt = (op.address >> targetModel.getColumnShift()) & 0x1f;
620+
uint32_t rowInt = (op.address >> targetModel.getRowShift()) & 0x1f;
621+
tileMap[key] = std::make_pair(colInt, rowInt);
622+
623+
// Create tile and set controller_id attribute
624+
auto tile = TileOp::getOrCreate(builder, device, colInt, rowInt);
625+
auto packetInfoAttr = AIE::PacketInfoAttr::get(
626+
builder.getContext(), op.pktType, op.pktId);
627+
tile->setAttr("controller_id", packetInfoAttr);
628+
}
629+
}
630+
631+
// Create aiex.runtime_sequence
632+
std::string seq_name = "configure";
633+
StringAttr seq_sym_name = builder.getStringAttr(seq_name);
634+
auto seq = builder.create<AIEX::RuntimeSequenceOp>(loc, seq_sym_name);
635+
seq.getBody().push_back(new Block);
636+
637+
// Create control packet ops
638+
builder.setInsertionPointToStart(&seq.getBody().front());
639+
for (const auto &op : operations) {
640+
IntegerAttr lengthAttr;
641+
DenseI32ArrayAttr dataAttr;
642+
643+
if (op.opcode == 0x0 || op.opcode == 0x2) {
644+
// Write or blockwrite - has data payload
645+
SmallVector<int32_t> dataVec;
646+
for (uint32_t val : op.data) {
647+
dataVec.push_back(static_cast<int32_t>(val));
648+
}
649+
dataAttr = DenseI32ArrayAttr::get(ctx, ArrayRef<int32_t>(dataVec));
650+
} else if (op.opcode == 0x1) {
651+
// Read - has length but no data
652+
lengthAttr = builder.getI32IntegerAttr(op.beats + 1);
653+
}
654+
655+
builder.create<AIEX::NpuControlPacketOp>(
656+
loc,
657+
builder.getUI32IntegerAttr(op.address),
658+
lengthAttr,
659+
builder.getI32IntegerAttr(op.opcode),
660+
builder.getI32IntegerAttr(op.streamId),
661+
dataAttr);
662+
}
663+
664+
return module;
665+
}
666+
454667
static LogicalResult convertAIEToConfiguration(AIE::DeviceOp device,
455668
StringRef clElfDir,
456669
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)