MLIR-AIE
AIEExpandLoadPdi.cpp
Go to the documentation of this file.
1//===- AIEExpandLoadPdi.cpp -------------------------------------*- C++ -*-===//
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// (c) Copyright 2025 Advanced Micro Devices Inc.
8//
9//===----------------------------------------------------------------------===//
10//
11// This pass expands `npu.load_pdi` operations that reference a device into:
12// 1. An empty device PDI load (causes firmware to reset the device)
13// 2. Explicit configuration operations (write32/blockwrite)
14//
15//===----------------------------------------------------------------------===//
16
21
22#include "mlir/Dialect/MemRef/IR/MemRef.h"
23#include "mlir/Pass/Pass.h"
24
25#include "llvm/ADT/StringRef.h"
26#include "llvm/Support/raw_ostream.h"
27
28namespace xilinx::AIEX {
29#define GEN_PASS_DEF_AIEEXPANDLOADPDI
30#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
31} // namespace xilinx::AIEX
32
33#define DEBUG_TYPE "aie-expand-load-pdi"
34
35using namespace mlir;
36using namespace xilinx;
37using namespace xilinx::AIEX;
38using namespace xilinx::AIE;
39
40namespace {
41
42// Helper to transform a single load_pdi operation
43static LogicalResult transformLoadPdi(NpuLoadPdiOp loadPdiOp, ModuleOp moduleOp,
44 unsigned index) {
45 static unsigned long i = 0;
46 OpBuilder builder(loadPdiOp);
47
48 // Only process load_pdi ops that reference a device
49 auto deviceRefAttr = loadPdiOp.getDeviceRefAttr();
50 if (!deviceRefAttr) {
51 return success();
52 }
53
54 auto referencedDevice = moduleOp.lookupSymbol<AIE::DeviceOp>(deviceRefAttr);
55 if (!referencedDevice) {
56 loadPdiOp.emitError("Referenced symbol '")
57 << deviceRefAttr.getValue() << "' is not a device";
58 return failure();
59 }
60
61 // Create a unique empty device for this reset to avoid PDI address caching
62 OpBuilder::InsertionGuard guard(builder);
63 builder.setInsertionPointToStart(moduleOp.getBody());
64
65 // Find a unique name for the empty device
66 std::string emptyName = "empty_" + std::to_string(index % 2);
67
68 AIE::DeviceOp emptyDevice = moduleOp.lookupSymbol<AIE::DeviceOp>(emptyName);
69 if (!emptyDevice) {
70 auto deviceType = referencedDevice.getDevice();
71 auto loc = builder.getUnknownLoc();
72 emptyDevice = AIE::DeviceOp::create(builder, loc, deviceType,
73 builder.getStringAttr(emptyName));
74 emptyDevice.getRegion().emplaceBlock();
75 Block *deviceBlock = &emptyDevice.getRegion().front();
76 builder.setInsertionPointToEnd(deviceBlock);
77 AIE::EndOp::create(builder, loc);
78 }
79
80 builder.setInsertionPoint(loadPdiOp);
81
82 // Create new empty load_pdi operation; this triggers a device reset.
83 // This is needed even for the first device configuration; without it, the
84 // first iteration of the design would run, but subsequent ones might not.
85 NpuLoadPdiOp::create(builder, loadPdiOp.getLoc(),
86 FlatSymbolRefAttr::get(emptyDevice.getSymNameAttr()),
87 loadPdiOp.getIdAttr(), loadPdiOp.getSizeAttr(),
88 loadPdiOp.getAddressAttr());
89
90 // Generate and insert configuration operations
92 builder, referencedDevice, "",
93 AIEToConfigurationOutputType::Transaction,
94 "loadpdi_" + std::to_string(i)))) {
95 loadPdiOp.emitError("Failed to generate configuration operations");
96 return failure();
97 }
98
99 // Erase the original load_pdi operation
100 loadPdiOp.erase();
101
102 i++;
103
104 return success();
105}
106
107struct AIEExpandLoadPdiPass
108 : public xilinx::AIEX::impl::AIEExpandLoadPdiBase<AIEExpandLoadPdiPass> {
109 void getDependentDialects(DialectRegistry &registry) const override {
110 registry
111 .insert<memref::MemRefDialect, AIE::AIEDialect, AIEX::AIEXDialect>();
112 }
113
114 void runOnOperation() override {
115 auto module = getOperation();
116
117 // Collect all load_pdi operations in program order;
118 // need to collect once, then transform all collected ops;
119 // since the transform inserts a new empty load_pdi, we can't transform as
120 // we walk or it'd infinitely recurse.
121 SmallVector<NpuLoadPdiOp> loadPdiOps;
122
123 module.walk(
124 [&](NpuLoadPdiOp loadPdiOp) { loadPdiOps.push_back(loadPdiOp); });
125
126 // Transform load_pdi ops
127 unsigned idx = 0;
128 for (auto loadPdiOp : loadPdiOps) {
129 if (failed(transformLoadPdi(loadPdiOp, module, idx))) {
130 signalPassFailure();
131 return;
132 }
133 idx++;
134 }
135 }
136};
137
138} // namespace
139
140std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
142 return std::make_unique<AIEExpandLoadPdiPass>();
143}
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createAIEExpandLoadPdiPass()
Include the generated interface declarations.
mlir::LogicalResult generateAndInsertConfigOps(mlir::OpBuilder &builder, xilinx::AIE::DeviceOp device, llvm::StringRef clElfDir="", AIEToConfigurationOutputType outputType=AIEToConfigurationOutputType::Transaction, std::string blockwrite_prefix="config_blockwrite_data_")