MLIR-AIE 1.4.5.dev
AIECreateCores.cpp
Go to the documentation of this file.
1//===- AIECreateCores.cpp ---------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2019-2022 Xilinx, Inc.
4// Copyright (C) 2022-2026 Advanced Micro Devices, Inc.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
12
13#include "mlir/Dialect/Arith/IR/Arith.h"
14#include "mlir/Dialect/Func/IR/FuncOps.h"
15#include "mlir/IR/Attributes.h"
16#include "mlir/IR/IRMapping.h"
17#include "mlir/IR/PatternMatch.h"
18#include "mlir/Pass/Pass.h"
19#include "mlir/Tools/mlir-translate/MlirTranslateMain.h"
20#include "mlir/Transforms/DialectConversion.h"
21
22namespace xilinx::AIEX {
23#define GEN_PASS_DEF_AIECREATECORES
24#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
25} // namespace xilinx::AIEX
26
27using namespace mlir;
28using namespace xilinx;
29using namespace xilinx::AIE;
30using namespace xilinx::AIEX;
31
32struct RemoveAIEFuncs : public OpConversionPattern<func::FuncOp> {
33 using OpConversionPattern<func::FuncOp>::OpConversionPattern;
34 DenseMap<func::FuncOp, std::pair<int, int>> &funcs;
35
36 RemoveAIEFuncs(MLIRContext *context,
37 DenseMap<func::FuncOp, std::pair<int, int>> &funcs,
38 PatternBenefit benefit = 1)
39 : OpConversionPattern<func::FuncOp>(context, benefit), funcs(funcs) {}
40
41 LogicalResult
42 matchAndRewrite(func::FuncOp op, OpAdaptor adaptor,
43 ConversionPatternRewriter &rewriter) const override {
44 Operation *Op = op.getOperation();
45 if (funcs.find(op) == funcs.end())
46 return failure();
47
48 rewriter.eraseOp(Op);
49 return success();
50 }
51};
52
53struct RemoveAIECalls : public OpConversionPattern<func::CallOp> {
54 using OpConversionPattern<func::CallOp>::OpConversionPattern;
55
56 RemoveAIECalls(MLIRContext *context, PatternBenefit benefit = 1)
57 : OpConversionPattern<func::CallOp>(context, benefit) {}
58
59 LogicalResult
60 matchAndRewrite(func::CallOp op, OpAdaptor adaptor,
61 ConversionPatternRewriter &rewriter) const override {
62 Operation *Op = op.getOperation();
63 if (!op->getAttr("aie.x") || !op->getAttr("aie.y"))
64 return failure();
65
66 rewriter.eraseOp(Op);
67 return success();
68 }
69};
70
72 : public xilinx::AIEX::impl::AIECreateCoresBase<AIECreateCoresPass> {
73 void runOnOperation() override {
74
75 DeviceOp device = getOperation();
76 OpBuilder builder = OpBuilder::atBlockTerminator(device.getBody());
77
78 DenseMap<TileID, Operation *> tiles;
79 DenseMap<Operation *, CoreOp> cores;
80 DenseMap<Operation *, MemOp> mems;
81 DenseMap<Value, Value> buffers;
82 DenseMap<func::FuncOp, std::pair<int, int>> funcs;
83
84 // Collect existing TileOps
85 for (auto tile : device.getOps<TileOp>()) {
86 int colIndex = tile.colIndex();
87 int rowIndex = tile.rowIndex();
88 tiles[{colIndex, rowIndex}] = tile;
89 }
90
91 // Bind FuncOp to an AIE core based on attributes of the CallOp
92 // A CoreOp will be created for the core, and the FuncOp body is cloned
93 // to the CoreOp region
94 for (auto callOp : device.getOps<func::CallOp>()) {
95 if (!callOp->getAttr("aie.x") || !callOp->getAttr("aie.y"))
96 continue;
97
98 SmallVector<Value, 4> callOperands(callOp.getArgOperands());
99 SmallVector<std::pair<MemRefType, int>, 4> coreBufTypes;
100
101 int colIndex = callOp->getAttrOfType<IntegerAttr>("aie.x").getInt();
102 int rowIndex = callOp->getAttrOfType<IntegerAttr>("aie.y").getInt();
103
104 // get or create TileOp
105 if (!tiles[{colIndex, rowIndex}]) {
106 builder.setInsertionPointToStart(device.getBody());
107 TileOp tile =
108 TileOp::create(builder, callOp.getLoc(), colIndex, rowIndex);
109 tiles[{colIndex, rowIndex}] = tile;
110 }
111 Operation *tileOp = tiles[{colIndex, rowIndex}];
112 TileOp tile = cast<TileOp>(tileOp);
113 builder.setInsertionPointAfter(tileOp);
114
115 // create MemOp
116 if (!mems[tileOp]) {
117 for (unsigned i = 0; i < callOperands.size(); i++) {
118 Value operand = callOperands[i]; // Should be produced by an AllocOp
119 MemRefType t = nullptr;
120 if (llvm::isa<MemRefType>(operand.getType())) {
121 t = llvm::cast<MemRefType>(operand.getType());
122 } else if (operand.getType().isIntOrFloat()) {
123 // promote scalar type to memref type
124 int64_t shape[1] = {1};
125 t = MemRefType::get(shape, operand.getType());
126 }
127
128 assert(t && "Unsupported type!");
129 coreBufTypes.push_back({t, i});
130 BufferOp buf = BufferOp::create(
131 builder, callOp.getLoc(), t, tile, /*sym_name*/ nullptr,
132 /*address*/ nullptr, /*initial_value*/ nullptr,
133 /*mem_bank*/ nullptr, /*aligned*/ nullptr);
134 buffers[callOperands[i]] = buf;
135 operand.replaceAllUsesWith(buf.getResult());
136 }
137
138 MemOp mem = MemOp::create(builder, callOp.getLoc(),
139 builder.getIndexType(), tile);
140 Region &r = mem.getBody();
141 Block *endBlock = builder.createBlock(&r);
142
143 // block terminator
144 builder.setInsertionPointToStart(endBlock);
145 EndOp::create(builder, callOp.getLoc());
146 mems[tileOp] = mem;
147 }
148
149 // create CoreOp with buffer reference
150 if (CallOpInterface call =
151 dyn_cast<CallOpInterface>(callOp.getOperation())) {
152 Operation *callable = call.resolveCallable();
153 if (func::FuncOp func = dyn_cast<func::FuncOp>(callable)) {
154 funcs[func] = {colIndex, rowIndex};
155
156 IRMapping mapper;
157
158 builder.setInsertionPoint(callOp);
159
160 CoreOp core;
161 Block *currentBlock;
162
163 if (!cores[tileOp]) {
164 core = CoreOp::create(builder, callOp.getLoc(), tile);
165 Region &r = core.getBody();
166 currentBlock = builder.createBlock(&r);
167 builder.setInsertionPointToStart(currentBlock);
168 } else {
169 core = cores[tileOp];
170 currentBlock = &core.getBody().back();
171 builder.setInsertionPoint(currentBlock->getTerminator());
172 }
173
174 // Mapping between function arguments (FuncOp) and AIE buffers
175 // (CoreOp) We will create one buffer for each function argument If
176 // the function argument's type is a scalar, we promote it to a
177 // one-element memref, and do a load to the buffer at index 0
178 for (auto pair : coreBufTypes) {
179 int operandID = pair.second;
180 Value arg = func.getArgument(operandID);
181 Value buf = buffers[callOperands[operandID]];
182 if (arg.getType().isIntOrFloat()) {
183 assert(pair.first.getShape().size() == 1 &&
184 "Expected MemRefType of shape 1");
185 assert(pair.first.getShape()[0] == 1 &&
186 "Expected MemRefType of single element");
187
188 Value zero =
189 arith::ConstantIndexOp::create(builder, callOp.getLoc(), 0);
190 auto loadOp = memref::LoadOp::create(builder, callOp.getLoc(),
191 arg.getType(), buf, zero);
192 mapper.map(arg, loadOp);
193 } else {
194 mapper.map(arg, buf);
195 }
196 }
197
198 // Clone ops from the original function to CoreOp's body
199 for (auto &childOp : func.getCallableRegion()->getOps()) {
200 // skip ReturnOp since it lives only within a funcOp
201 if (auto returnOp = dyn_cast<func::ReturnOp>(childOp))
202 continue;
203
204 builder.clone(childOp, mapper);
205 }
206 if (!cores[tileOp]) {
207 // block terminator
208 EndOp::create(builder, callOp.getLoc());
209 cores[tileOp] = core;
210 }
211 }
212 }
213 }
214
215 // Setup FlowOps
216 // Since memcpy moves data from one memory module to another, we use
217 // WireBundle::DMA for both the source and the destination In addition, we
218 // only have two DMA ports per each direction (MM2S/S2MM), and in a
219 // circuit-switch mode, dest port/channel sharing is not possible.
220 // Therefore, we will generate error if the number of logical flows
221 // (streams) targeting the same destination (S2MM) is more than 2
222 // DenseMap<Value, int> destChannel;
223 // for (auto op : device.getOps<MemcpyOp>()) {
224 // builder.setInsertionPoint(op);
225 // TileOp srcTile = dyn_cast<TileOp>(op.srcTile().getDefiningOp());
226 // TileOp dstTile = dyn_cast<TileOp>(op.dstTile().getDefiningOp());
227 // // TODO: perhaps a better approach is to not assert here, but rather
228 // have a subsequent pass
229 // // that legally relocates the ports
230 // assert(destChannel[op.dstTile()] <= 2 &&
231 // "Could not allocate more than two dest. channel when creating
232 // FlowOp");
233 // // WireBundle[1] = DMA
234 // FlowOp::create(builder, callOp.getLoc(), srcTile, 1, 0,
235 // dstTile, 1, destChannel[op.dstTile()]); destChannel[op.dstTile()]++;
236 // }
237
238 ConversionTarget target(getContext());
239 RewritePatternSet patterns(&getContext());
240 target.addLegalOp<DMAStartOp>();
241 target.addLegalOp<DMABDOp>();
242 target.addLegalOp<UseTokenOp>();
243 target.addLegalOp<NextBDOp>();
244
245 // Remove standard CallOps and FuncOps that are bound to AIE CoreOps
246 patterns.insert<RemoveAIECalls>(device.getContext());
247 patterns.insert<RemoveAIEFuncs>(device.getContext(), funcs);
248
249 if (failed(applyPartialConversion(device, target, std::move(patterns))))
250 signalPassFailure();
251 }
252};
253
254std::unique_ptr<OperationPass<DeviceOp>>
256 return std::make_unique<AIECreateCoresPass>();
257}
std::unique_ptr< mlir::OperationPass< AIE::DeviceOp > > createAIECreateCoresPass()
void runOnOperation() override
RemoveAIECalls(MLIRContext *context, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(func::CallOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
RemoveAIEFuncs(MLIRContext *context, DenseMap< func::FuncOp, std::pair< int, int > > &funcs, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(func::FuncOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
DenseMap< func::FuncOp, std::pair< int, int > > & funcs