MLIR-AIE 1.4.5.dev
AIETxnToControlPacket.cpp
Go to the documentation of this file.
1//===- AIETxnToControlPacket.cpp --------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2025 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
11
12#include "mlir/IR/PatternMatch.h"
13#include "mlir/Pass/Pass.h"
14#include "mlir/Transforms/DialectConversion.h"
15
16#include <algorithm>
17
18namespace xilinx::AIEX {
19#define GEN_PASS_DEF_AIELEGALIZECONTROLPACKET
20#define GEN_PASS_DEF_AIETXNTOCONTROLPACKET
21#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
22} // namespace xilinx::AIEX
23
24#define DEBUG_TYPE "aie-txn-to-control"
25
26using namespace mlir;
27using namespace xilinx;
28
29namespace {
30
31/// Pattern to convert transaction operations to control operations
32struct BlockWriteToControlPacketPattern
33 : public OpRewritePattern<AIEX::NpuBlockWriteOp> {
34 using OpRewritePattern<AIEX::NpuBlockWriteOp>::OpRewritePattern;
35
36 LogicalResult matchAndRewrite(AIEX::NpuBlockWriteOp op,
37 PatternRewriter &rewriter) const override {
38
39 Value memref = op.getData();
40 int64_t width = cast<MemRefType>(memref.getType()).getElementTypeBitWidth();
41 if (width != 32) {
42 return op.emitWarning("Only 32-bit data type is supported for now");
43 }
44
45 memref::GetGlobalOp getGlobal = memref.getDefiningOp<memref::GetGlobalOp>();
46 if (!getGlobal) {
47 return op.emitError("Only MemRefs from memref.get_global are supported");
48 }
49
50 auto global = dyn_cast_if_present<memref::GlobalOp>(
51 op->getParentOfType<AIE::DeviceOp>().lookupSymbol(getGlobal.getName()));
52 if (!global) {
53 return op.emitError("Global symbol not found");
54 }
55
56 auto initVal = global.getInitialValue();
57 if (!initVal) {
58 return op.emitError("Global symbol has no initial value");
59 }
60
61 auto data = dyn_cast<DenseIntElementsAttr>(*initVal);
62 if (!data) {
63 return op.emitError(
64 "Global symbol initial value is not a dense int array");
65 }
66 std::vector<int32_t> dataVec(data.value_begin<int32_t>(),
67 data.value_end<int32_t>());
68 AIEX::NpuControlPacketOp::create(
69 rewriter, op->getLoc(), op.getAddressAttr(), nullptr,
70 /*opcode*/ rewriter.getI32IntegerAttr(0),
71 /*stream_id*/ rewriter.getI32IntegerAttr(0),
72 DenseI32ArrayAttr::get(op->getContext(), dataVec));
73 rewriter.eraseOp(op);
74 return success();
75 }
76};
77
78// A control packet's data words land in one aligned line of `maxWords` words;
79// words past the line's end wrap to its start.
80static bool fitsInLine(AIEX::NpuControlPacketOp op, uint32_t maxWords) {
81 uint32_t wordInLine = (op.getAddress() / sizeof(uint32_t)) % maxWords;
82 return wordInLine + op.getDataAttr().size() <= maxWords;
83}
84
85// Splits a control packet's data into packets that each stay in one line.
86struct ControlPacketSplitPattern
87 : public OpRewritePattern<AIEX::NpuControlPacketOp> {
88 using OpRewritePattern<AIEX::NpuControlPacketOp>::OpRewritePattern;
89
90 ControlPacketSplitPattern(MLIRContext *ctx, uint32_t max_payload_size)
91 : OpRewritePattern(ctx), max_payload_size(max_payload_size) {}
92
93 LogicalResult matchAndRewrite(AIEX::NpuControlPacketOp op,
94 PatternRewriter &rewriter) const override {
95 auto data = op.getData();
96 if (!data || fitsInLine(op, max_payload_size))
97 return failure(); // No splitting needed
98
99 auto *context = op.getContext();
100 auto loc = op.getLoc();
101 uint32_t address = op.getAddress();
102 for (const auto *it = data->begin(); it != data->end();) {
103 uint32_t wordInLine = (address / sizeof(uint32_t)) % max_payload_size;
104 uint32_t count =
105 std::min<uint32_t>(max_payload_size - wordInLine, data->end() - it);
106 SmallVector<int32_t, 4> chunkData(it, it + count);
107 AIEX::NpuControlPacketOp::create(
108 rewriter, loc, rewriter.getUI32IntegerAttr(address), nullptr,
109 op.getOpcodeAttr(), op.getStreamIdAttr(),
110 DenseI32ArrayAttr::get(context, chunkData));
111 address += count * sizeof(uint32_t);
112 it += count;
113 }
114
115 rewriter.eraseOp(op);
116 return success();
117 }
118
119private:
120 uint32_t max_payload_size;
121};
122
123struct AIETxnToControlPacketPass
124 : public xilinx::AIEX::impl::AIETxnToControlPacketBase<
125 AIETxnToControlPacketPass> {
126 void runOnOperation() override {
127 AIE::DeviceOp device = getOperation();
128
129 ConversionTarget target(getContext());
130 target.addIllegalOp<AIEX::NpuBlockWriteOp>();
131 target.addLegalOp<AIEX::NpuControlPacketOp>();
132
133 RewritePatternSet patterns(&getContext());
134 patterns.add<BlockWriteToControlPacketPattern>(&getContext());
135
136 if (failed(applyPartialConversion(device, target, std::move(patterns)))) {
137 signalPassFailure();
138 }
139 }
140};
141
142struct AIELegalizeControlPacketPass
143 : public xilinx::AIEX::impl::AIELegalizeControlPacketBase<
144 AIELegalizeControlPacketPass> {
145 void runOnOperation() override {
146 AIE::DeviceOp device = getOperation();
147
148 ConversionTarget target(getContext());
149 target.addDynamicallyLegalOp<AIEX::NpuControlPacketOp>([](Operation *op) {
150 auto packetOp = cast<AIEX::NpuControlPacketOp>(op);
151 return !packetOp.getData() || fitsInLine(packetOp, 4);
152 });
153
154 RewritePatternSet patterns(&getContext());
155 patterns.add<ControlPacketSplitPattern>(&getContext(), 4);
156
157 if (failed(applyPartialConversion(device, target, std::move(patterns)))) {
158 signalPassFailure();
159 }
160 }
161};
162
163} // namespace
164
165std::unique_ptr<OperationPass<AIE::DeviceOp>>
167 return std::make_unique<AIETxnToControlPacketPass>();
168}
169
170std::unique_ptr<OperationPass<AIE::DeviceOp>>
172 return std::make_unique<AIELegalizeControlPacketPass>();
173}
std::unique_ptr< mlir::OperationPass< AIE::DeviceOp > > createAIETxnToControlPacketPass()
std::unique_ptr< mlir::OperationPass< AIE::DeviceOp > > createAIELegalizeControlPacketPass()