MLIR-AIE 1.4.5.dev
AIECoreToStandard.cpp
Go to the documentation of this file.
1//===- AIECoreToStandard.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
13
14#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h"
15#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVMPass.h"
16#include "mlir/Dialect/Arith/IR/Arith.h"
17#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h"
18#include "mlir/Dialect/DLTI/DLTI.h"
19#include "mlir/Dialect/Index/IR/IndexDialect.h"
20#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
21#include "mlir/Dialect/Math/IR/Math.h"
22#include "mlir/Dialect/Ptr/IR/PtrOps.h"
23#include "mlir/Dialect/UB/IR/UBOps.h"
24#include "mlir/Dialect/Vector/IR/VectorOps.h"
25#include "mlir/IR/Attributes.h"
26#include "mlir/IR/IRMapping.h"
27#include "mlir/IR/PatternMatch.h"
28#include "mlir/Pass/Pass.h"
29#include "mlir/Tools/mlir-translate/MlirTranslateMain.h"
30#include "mlir/Transforms/DialectConversion.h"
31
32namespace xilinx::AIE {
33#define GEN_PASS_DEF_AIECORETOSTANDARD
34#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
35} // namespace xilinx::AIE
36
37using namespace mlir;
38using namespace mlir::vector;
39using namespace xilinx;
40using namespace xilinx::AIE;
41
42static StringRef getArchIntrinsicString(AIEArch arch) {
43 switch (arch) {
44 case AIEArch::AIE1:
45 return "aie";
46 case AIEArch::AIE2:
47 return "aie2";
48 case AIEArch::AIE2p:
49 return "aie2p";
50 case AIEArch::AIE2ps:
51 // This arch has no core intrinsic set. The switch lists every AIEArch
52 // value, so a new value raises a -Wswitch warning here.
53 break;
54 }
55 llvm::report_fatal_error("unsupported arch");
56}
57
59 std::tuple<const char *, std::vector<Type>, std::vector<Type>>;
60using IntrinsicDecls = std::vector<IntrinsicDecl>;
61
62static auto getAIE1Intrinsics(OpBuilder &builder) {
63 Type int32Type = IntegerType::get(builder.getContext(), 32);
64 Type int128Type = IntegerType::get(builder.getContext(), 128);
65 Type int384Type = IntegerType::get(builder.getContext(), 384);
66 Type floatType = Float32Type::get(builder.getContext());
67
68 // Note that not all of these are valid for a particular design, or needed.
69 // For right now, we will just accept the noise.
70 IntrinsicDecls functions = {
71 {"debug_i32", {int32Type}, {}},
72 {"llvm.aie.event0", {}, {}},
73 {"llvm.aie.event1", {}, {}},
74 {"llvm.aie.put.ms",
75 {int32Type, int32Type},
76 {}}, //(%channel, %value) -> ()
77 {"llvm.aie.put.wms",
78 {int32Type, int128Type},
79 {}}, //(%channel, %value) -> ()
80 {"llvm.aie.put.fms",
81 {int32Type, floatType},
82 {}}, //(%channel, %value) -> ()
83 {"llvm.aie.get.ss", {int32Type}, {int32Type}}, //(%channel, %value) -> ()
84 {"llvm.aie.get.wss",
85 {int32Type},
86 {int128Type}}, //(%channel, %value) -> ()
87 {"llvm.aie.get.fss", {int32Type}, {floatType}}, //(%channel, %value) -> ()
88 {"llvm.aie.put.mcd", {int384Type}, {}},
89 {"llvm.aie.get.scd", {}, {int384Type}},
90 {"llvm.aie.lock.acquire.reg",
91 {int32Type, int32Type},
92 {}}, //(%lock_id, %lock_val) -> ()
93 {"llvm.aie.lock.release.reg",
94 {int32Type, int32Type},
95 {}}, //(%lock_id, %lock_val) -> ()
96 };
97 return functions;
98}
99
100static auto getAIE2Intrinsics(OpBuilder &builder) {
101 Type int32Type = IntegerType::get(builder.getContext(), 32);
102 Type accType = VectorType::get({16}, int32Type);
103 IntrinsicDecls functions = {
104 {"debug_i32", {int32Type}, {}},
105 {"llvm.aie2.event", {int32Type}, {}},
106 {"llvm.aie2.put.ms", {int32Type, int32Type}, {}}, //(%value, %tlast) -> ()
107 {"llvm.aie2.get.ss", {}, {int32Type, int32Type}}, //() -> (%value, %tlast)
108 {"llvm.aie2.mcd.write.vec",
109 {accType, int32Type},
110 {}}, // (%value, %enable) -> ()
111 {"llvm.aie2.scd.read.vec",
112 {int32Type},
113 {accType}}, // (%enable) -> (%value)
114 {"llvm.aie2.acquire",
115 {int32Type, int32Type},
116 {}}, //(%lock_id, %lock_val) -> ()
117 {"llvm.aie2.release",
118 {int32Type, int32Type},
119 {}}, //(%lock_id, %lock_val) -> ()
120 {"llvm.aie2.set.ctrl.reg",
121 {int32Type, int32Type},
122 {}}, //(%reg_id, %value) -> ()
123 };
124 return functions;
125}
126
127static auto getAIE2pIntrinsics(OpBuilder &builder) {
128 Type int32Type = IntegerType::get(builder.getContext(), 32);
129 Type accType = VectorType::get({16}, int32Type);
130 IntrinsicDecls functions = {
131 {"debug_i32", {int32Type}, {}},
132 {"llvm.aie2p.event", {int32Type}, {}},
133 {"llvm.aie2p.put.ms",
134 {int32Type, int32Type},
135 {}}, //(%value, %tlast) -> ()
136 {"llvm.aie2p.get.ss",
137 {},
138 {int32Type, int32Type}}, //() -> (%value, %tlast)
139 {"llvm.aie2p.mcd.write.vec",
140 {accType, int32Type},
141 {}}, // (%value, %enable) -> ()
142 {"llvm.aie2p.scd.read.vec",
143 {int32Type},
144 {accType}}, // (%enable) -> (%value)
145 {"llvm.aie2p.acquire",
146 {int32Type, int32Type},
147 {}}, //(%lock_id, %lock_val) -> ()
148 {"llvm.aie2p.release",
149 {int32Type, int32Type},
150 {}}, //(%lock_id, %lock_val) -> ()
151 {"llvm.aie2p.set.ctrl.reg",
152 {int32Type, int32Type},
153 {}}, //(%reg_id, %value) -> ()
154 };
155 return functions;
156}
157
158static void declareAIEIntrinsics(AIEArch arch, OpBuilder &builder) {
159 auto registerIntrinsics = [&builder](const IntrinsicDecls &functions) {
160 for (auto &i : functions) {
161 auto [name, argTypes, retTypes] = i;
162 func::FuncOp::create(
163 builder, builder.getUnknownLoc(), name,
164 FunctionType::get(builder.getContext(), argTypes, retTypes))
165 .setPrivate();
166 }
167 };
168 switch (arch) {
169 case AIEArch::AIE1:
170 registerIntrinsics(getAIE1Intrinsics(builder));
171 return;
172 case AIEArch::AIE2:
173 registerIntrinsics(getAIE2Intrinsics(builder));
174 return;
175 case AIEArch::AIE2p:
176 registerIntrinsics(getAIE2pIntrinsics(builder));
177 return;
178 case AIEArch::AIE2ps:
179 // See getArchIntrinsicString: this arch has no intrinsic set.
180 break;
181 }
182 llvm::report_fatal_error("unsupported arch");
183}
184
185// Move all the ops with OpTy inside device, to just before the device.
186template <typename OpTy>
187static void outlineOps(DeviceOp device) {
188 SmallVector<OpTy, 16> ops;
189 for (const auto &op : device.getOps<OpTy>())
190 ops.push_back(op);
191
192 for (const auto &op : ops)
193 op->moveBefore(device);
194}
195
196namespace {
197template <typename MyAIEOp>
198struct AIEOpRemoval : OpConversionPattern<MyAIEOp> {
199 using OpConversionPattern<MyAIEOp>::OpConversionPattern;
200 using OpAdaptor = typename MyAIEOp::Adaptor;
201 ModuleOp &module;
202
203 AIEOpRemoval(MLIRContext *context, ModuleOp &m, PatternBenefit benefit = 1)
204 : OpConversionPattern<MyAIEOp>(context, benefit), module(m) {}
205
206 LogicalResult
207 matchAndRewrite(MyAIEOp op, OpAdaptor adaptor,
208 ConversionPatternRewriter &rewriter) const override {
209 rewriter.eraseOp(op);
210 return success();
211 }
212};
213
214struct AIEDebugOpToStdLowering : OpConversionPattern<DebugOp> {
215 using OpConversionPattern::OpConversionPattern;
216 ModuleOp &module;
217
218 AIEDebugOpToStdLowering(MLIRContext *context, ModuleOp &m,
219 PatternBenefit benefit = 1)
220 : OpConversionPattern(context, benefit), module(m) {}
221
222 LogicalResult
223 matchAndRewrite(DebugOp op, OpAdaptor adaptor,
224 ConversionPatternRewriter &rewriter) const override {
225 std::string funcName = "debug_i32";
226 auto func = module.lookupSymbol<func::FuncOp>(funcName);
227 if (!func)
228 return op.emitOpError("Could not find the intrinsic function ")
229 << funcName;
230 SmallVector<Value, 1> args;
231 args.push_back(op.getArg());
232 func::CallOp::create(rewriter, op.getLoc(), func, args);
233 rewriter.eraseOp(op);
234 return success();
235 }
236};
237
238struct AIEPutStreamToStdLowering : OpConversionPattern<PutStreamOp> {
239 using OpConversionPattern::OpConversionPattern;
240 ModuleOp &module;
241
242 AIEPutStreamToStdLowering(MLIRContext *context, ModuleOp &m,
243 PatternBenefit benefit = 1)
244 : OpConversionPattern(context, benefit), module(m) {}
245
246 LogicalResult
247 matchAndRewrite(PutStreamOp op, OpAdaptor adaptor,
248 ConversionPatternRewriter &rewriter) const override {
249 auto device = op->getParentOfType<DeviceOp>();
250 const auto &targetModel = device.getTargetModel();
251 std::string funcName;
252 if (targetModel.getTargetArch() == AIEArch::AIE1)
253 funcName = "llvm.aie.put.";
254 else if (targetModel.getTargetArch() == AIEArch::AIE2)
255 funcName = "llvm.aie2.put.";
256 else
257 funcName = "llvm.aie2p.put.";
258
259 if (op.isWideStream())
260 funcName += "wms";
261 else if (op.isFloatStream())
262 funcName += "fms";
263 else
264 funcName += "ms";
265
266 auto putMSFunc = module.lookupSymbol<func::FuncOp>(funcName);
267 if (!putMSFunc)
268 return op.emitOpError("Could not find the intrinsic function ")
269 << funcName;
270 SmallVector<Value, 2> args;
271 if (targetModel.getTargetArch() == AIEArch::AIE1) {
272 args.push_back(op.getChannel());
273 args.push_back(op.getStreamValue());
274 } else {
275 args.push_back(op.getStreamValue());
276 args.push_back(arith::ConstantOp::create(
277 rewriter, op.getLoc(), IntegerType::get(rewriter.getContext(), 32),
278 rewriter.getI32IntegerAttr(0))); // tlast
279 }
280 func::CallOp::create(rewriter, op.getLoc(), putMSFunc, args);
281 rewriter.eraseOp(op);
282 return success();
283 }
284};
285
286struct AIEGetStreamToStdLowering : OpConversionPattern<GetStreamOp> {
287 using OpConversionPattern::OpConversionPattern;
288 ModuleOp &module;
289
290 AIEGetStreamToStdLowering(MLIRContext *context, ModuleOp &m,
291 PatternBenefit benefit = 1)
292 : OpConversionPattern(context, benefit), module(m) {}
293
294 LogicalResult
295 matchAndRewrite(GetStreamOp op, OpAdaptor adaptor,
296 ConversionPatternRewriter &rewriter) const override {
297 auto device = op->getParentOfType<DeviceOp>();
298 const auto &targetModel = device.getTargetModel();
299 std::string funcName;
300 if (targetModel.getTargetArch() == AIEArch::AIE1)
301 funcName = "llvm.aie.get.";
302 else if (targetModel.getTargetArch() == AIEArch::AIE2)
303 funcName = "llvm.aie2.get.";
304 else
305 funcName = "llvm.aie2p.get.";
306
307 if (op.isWideStream())
308 funcName += "wss";
309 else if (op.isFloatStream())
310 funcName += "fss";
311 else
312 funcName += "ss";
313
314 auto getSSFunc = module.lookupSymbol<func::FuncOp>(funcName);
315 if (!getSSFunc)
316 return op.emitOpError("Could not find the intrinsic function ")
317 << funcName;
318 SmallVector<Value, 2> args;
319 if (targetModel.getTargetArch() == AIEArch::AIE1)
320 args.push_back(op.getChannel());
321 auto getSSCall =
322 func::CallOp::create(rewriter, op.getLoc(), getSSFunc, args);
323 rewriter.replaceOp(op, getSSCall.getResult(0));
324 // Capture TLAST in AIEv2?
325 return success();
326 }
327};
328
329struct AIEPutCascadeToStdLowering : OpConversionPattern<PutCascadeOp> {
330 using OpConversionPattern::OpConversionPattern;
331 ModuleOp &module;
332
333 AIEPutCascadeToStdLowering(MLIRContext *context, ModuleOp &m,
334 PatternBenefit benefit = 1)
335 : OpConversionPattern(context, benefit), module(m) {}
336
337 LogicalResult
338 matchAndRewrite(PutCascadeOp op, OpAdaptor adaptor,
339 ConversionPatternRewriter &rewriter) const override {
340 auto device = op->getParentOfType<DeviceOp>();
341 const auto &targetModel = device.getTargetModel();
342 std::string funcName;
343 if (targetModel.getTargetArch() == AIEArch::AIE1)
344 funcName = "llvm.aie.put.mcd";
345 else if (targetModel.getTargetArch() == AIEArch::AIE2)
346 funcName = "llvm.aie2.mcd.write.vec";
347 else
348 funcName = "llvm.aie2p.mcd.write.vec";
349 auto putMCDFunc = module.lookupSymbol<func::FuncOp>(funcName);
350 if (!putMCDFunc)
351 return op.emitOpError("Could not find the intrinsic function ")
352 << funcName;
353 SmallVector<Value, 2> args;
354 Value cascadeValue = op.getCascadeValue();
355
356 // Check if we need a bitcast for the input value
357 Type expectedInputType = putMCDFunc.getFunctionType().getInput(0);
358 Type actualInputType = cascadeValue.getType();
359
360 if (expectedInputType != actualInputType) {
361 // Create a bitcast operation to convert from actual input type to
362 // expected type
363 cascadeValue = vector::BitCastOp::create(rewriter, op.getLoc(),
364 expectedInputType, cascadeValue);
365 }
366
367 args.push_back(cascadeValue);
368 if (isa<AIE2TargetModel>(targetModel))
369 args.push_back(arith::ConstantOp::create(
370 rewriter, op.getLoc(), IntegerType::get(rewriter.getContext(), 32),
371 rewriter.getI32IntegerAttr(1))); // enable
372
373 func::CallOp::create(rewriter, op.getLoc(), putMCDFunc, args);
374 rewriter.eraseOp(op);
375 return success();
376 }
377};
378
379struct AIEGetCascadeToStdLowering : OpConversionPattern<GetCascadeOp> {
380 using OpConversionPattern::OpConversionPattern;
381 ModuleOp &module;
382
383 AIEGetCascadeToStdLowering(MLIRContext *context, ModuleOp &m,
384 PatternBenefit benefit = 1)
385 : OpConversionPattern(context, benefit), module(m) {}
386
387 LogicalResult
388 matchAndRewrite(GetCascadeOp op, OpAdaptor adaptor,
389 ConversionPatternRewriter &rewriter) const override {
390 auto device = op->getParentOfType<DeviceOp>();
391 const auto &targetModel = device.getTargetModel();
392 std::string funcName;
393 if (targetModel.getTargetArch() == AIEArch::AIE1)
394 funcName = "llvm.aie.get.scd";
395 else if (targetModel.getTargetArch() == AIEArch::AIE2)
396 funcName = "llvm.aie2.scd.read.vec";
397 else
398 funcName = "llvm.aie2p.scd.read.vec";
399 auto getSCDFunc = module.lookupSymbol<func::FuncOp>(funcName);
400 if (!getSCDFunc)
401 return op.emitOpError("Could not find the intrinsic function ")
402 << funcName;
403 SmallVector<Value, 2> args;
404 if (isa<AIE2TargetModel>(targetModel))
405 args.push_back(arith::ConstantOp::create(
406 rewriter, op.getLoc(), IntegerType::get(rewriter.getContext(), 32),
407 rewriter.getI32IntegerAttr(1))); // enable
408
409 auto getSCDCall =
410 func::CallOp::create(rewriter, op.getLoc(), getSCDFunc, args);
411 Value result = getSCDCall.getResult(0);
412
413 // Check if we need a bitcast
414 Type expectedType = op.getResult().getType();
415 Type intrinsicReturnType = result.getType();
416
417 if (expectedType != intrinsicReturnType) {
418 // Create a bitcast operation to convert from intrinsic return type to
419 // expected type
420 result = vector::BitCastOp::create(rewriter, op.getLoc(), expectedType,
421 result);
422 }
423
424 rewriter.replaceOp(op, result);
425 return success();
426 }
427};
428
429struct AIEUseLockToStdLowering : OpConversionPattern<UseLockOp> {
430 using OpConversionPattern::OpConversionPattern;
431 ModuleOp &module;
432
433 AIEUseLockToStdLowering(MLIRContext *context, ModuleOp &m,
434 PatternBenefit benefit = 1)
435 : OpConversionPattern(context, benefit), module(m) {}
436 LogicalResult
437 matchAndRewrite(UseLockOp useLock, OpAdaptor adaptor,
438 ConversionPatternRewriter &rewriter) const override {
439 if (!isa<DeviceOp>(useLock->getParentOp())) {
440 auto device = useLock->getParentOfType<DeviceOp>();
441 if (!device) {
442 return module.emitOpError("Device Not found!");
443 }
444 const auto &targetModel = device.getTargetModel();
445
446 // Generate the intrinsic name
447 std::string funcName;
448 if (targetModel.getTargetArch() == AIEArch::AIE1)
449 funcName = "llvm.aie.lock.";
450 else if (targetModel.getTargetArch() == AIEArch::AIE2)
451 funcName = "llvm.aie2.";
452 else
453 funcName = "llvm.aie2p.";
454 if (useLock.acquire() || useLock.acquireGE())
455 funcName += "acquire";
456 else if (useLock.release())
457 funcName += "release";
458 if (targetModel.getTargetArch() == AIEArch::AIE1)
459 funcName += ".reg";
460
461 auto useLockFunc = module.lookupSymbol<func::FuncOp>(funcName);
462 if (!useLockFunc)
463 return useLock.emitOpError("Could not find the intrinsic function!");
464
465 SmallVector<Value, 2> args;
466 auto i32Ty = IntegerType::get(rewriter.getContext(), 32);
467 args.push_back(arith::IndexCastOp::create(rewriter, useLock.getLoc(),
468 i32Ty, useLock.getLock()));
469
470 Value value = adaptor.getValue();
471 // AIE2 acquire-greater-equal is encoded as a negative value, so negate
472 // it at runtime.
473 if (useLock.acquireGE()) {
474 Value zero = arith::ConstantOp::create(
475 rewriter, useLock.getLoc(), i32Ty, rewriter.getI32IntegerAttr(0));
476 value = arith::SubIOp::create(rewriter, useLock.getLoc(), zero, value);
477 }
478 args.push_back(value);
479
480 func::CallOp::create(rewriter, useLock.getLoc(), useLockFunc, args);
481 }
482 rewriter.eraseOp(useLock);
483 return success();
484 }
485};
486
487struct AIEBufferToStandard : OpConversionPattern<BufferOp> {
488 using OpConversionPattern::OpConversionPattern;
489 ModuleOp &module;
490 AIEBufferToStandard(MLIRContext *context, ModuleOp &m,
491 PatternBenefit benefit = 1)
492 : OpConversionPattern(context, benefit), module(m) {}
493 LogicalResult
494 matchAndRewrite(BufferOp buffer, OpAdaptor adaptor,
495 ConversionPatternRewriter &rewriter) const override {
496 // The core's own sections fill this extent; nothing refers to it by name.
497 if (buffer.getCoreData()) {
498 rewriter.eraseOp(buffer);
499 return success();
500 }
501 rewriter.setInsertionPointToStart(module.getBody());
502 auto t = llvm::cast<MemRefType>(buffer.getType());
503 auto symName = buffer.name().getValue();
504 // A declaration, never a definition: a buffer lives at the address the
505 // allocator chose, and the linker supplies that address from the generated
506 // script or BCF. `initial_value` is device state, written by whoever
507 // configures the device -- the CDO writer, or mlir_aie_initialize_buffers
508 // on the simulator path. A definition here would place the bytes wherever
509 // `*(.data*)` lands, which costs the core's data region a copy it never
510 // reads.
511 memref::GlobalOp::create(rewriter, buffer.getLoc(), symName,
512 rewriter.getStringAttr("public"), buffer.getType(),
513 /*initial_value=*/nullptr, /*constant*/ false,
514 /*alignment*/ nullptr);
515
516 for (auto &use : make_early_inc_range(buffer.getResult().getUses())) {
517 Operation *user = use.getOwner();
518 rewriter.setInsertionPoint(user);
519 auto allocated =
520 memref::GetGlobalOp::create(rewriter, buffer.getLoc(), t, symName);
521 // Assume that buffers are aligned so they can be vectorized.
522 memref::AssumeAlignmentOp::create(rewriter, buffer.getLoc(), allocated,
523 32);
524
525 use.set(allocated.getResult());
526 }
527
528 rewriter.eraseOp(buffer);
529 return success();
530 }
531};
532
533struct AIECoreToStandardFunc : OpConversionPattern<CoreOp> {
534 using OpConversionPattern::OpConversionPattern;
535 ModuleOp &module;
536 IRMapping &mapper;
537 DenseMap<Operation *, SmallVector<BufferOp, 4>> &tileToBuffers;
538 int tileCol = 0;
539 int tileRow = 0;
540
541 AIECoreToStandardFunc(
542 MLIRContext *context, ModuleOp &m, IRMapping &mapper,
543 DenseMap<Operation *, SmallVector<BufferOp, 4>> &tileToBuffers,
544 PatternBenefit benefit = 1, int tileCol = 1, int tileRow = 1)
545 : OpConversionPattern(context, benefit), module(m), mapper(mapper),
546 tileToBuffers(tileToBuffers), tileCol(tileCol), tileRow(tileRow) {}
547
548 LogicalResult
549 matchAndRewrite(CoreOp op, OpAdaptor adaptor,
550 ConversionPatternRewriter &rewriter) const override {
551
552 int col = op.colIndex();
553 int row = op.rowIndex();
554
555 // Only pull code for the indicated function
556 if ((tileRow != row && tileRow != -1) ||
557 (tileCol != col && tileCol != -1)) {
558 rewriter.eraseOp(op);
559 return success();
560 }
561
562 // The parent should be an AIE.device op.
563 rewriter.setInsertionPointAfter(op->getParentOp());
564
565 std::string coreName = coreFrameSymbolName(col, row);
566 auto coreFunc =
567 func::FuncOp::create(rewriter, op.getLoc(), coreName,
568 FunctionType::get(rewriter.getContext(), {}, {}));
569
570 rewriter.cloneRegionBefore(op.getBody(), coreFunc.getBody(),
571 coreFunc.getBody().begin(), mapper);
572
573 // Set saturation and rounding modes at core entry for AIE2/AIE2p, but
574 // only if the core body contains aievec.srs or bf16 aievec.matmul ops.
575 // Skip for cores with only lock/stream ops to avoid breaking existing
576 // test SSA naming.
577 bool hasSRS = false;
578 bool hasIntegerSRS = false;
579 bool hasBF16Matmul = false;
580 coreFunc.walk([&](Operation *childOp) {
581 StringRef opName = childOp->getName().getStringRef();
582 if (opName == "aievec.srs") {
583 hasSRS = true;
584 // Check if this is an integer SRS (e.g., i32→i8) vs float SRS
585 // (e.g., f32→bf16). Integer SRS needs positive_inf rounding to
586 // match C++ kernel behavior; float SRS works better with floor.
587 if (childOp->getNumResults() > 0) {
588 auto resultType = childOp->getResult(0).getType();
589 if (auto vecType = dyn_cast<VectorType>(resultType)) {
590 if (vecType.getElementType().isInteger())
591 hasIntegerSRS = true;
592 }
593 }
594 }
595 // Detect bf16 matmul ops — these need conv_even rounding to avoid
596 // systematic negative bias from floor rounding in BFP16 arithmetic.
597 if (opName == "aievec.matmul" || opName == "aievec.matmul_aie2p") {
598 if (childOp->getNumOperands() > 0) {
599 auto lhsType = childOp->getOperand(0).getType();
600 if (auto vecType = dyn_cast<VectorType>(lhsType))
601 if (vecType.getElementType().isBF16())
602 hasBF16Matmul = true;
603 }
604 }
605 });
606 if (hasSRS || hasBF16Matmul) {
607 auto device = op->getParentOfType<DeviceOp>();
608 if (device) {
609 AIEArch arch = device.getTargetModel().getTargetArch();
610 if (arch == AIEArch::AIE2 || arch == AIEArch::AIE2p) {
611 std::string ctrlRegFuncName = (arch == AIEArch::AIE2p)
612 ? "llvm.aie2p.set.ctrl.reg"
613 : "llvm.aie2.set.ctrl.reg";
614 auto ctrlRegFunc = module.lookupSymbol<func::FuncOp>(ctrlRegFuncName);
615 if (ctrlRegFunc) {
616 Block &entryBlock = coreFunc.getBody().front();
617 rewriter.setInsertionPointToStart(&entryBlock);
618 Location loc = op.getLoc();
619 // Rounding register index differs between AIE2 and AIE2P:
620 // AIE2: crRnd=6
621 // AIE2P: crRnd=1
622 // Saturation register uses AIE2 index (9) for both architectures.
623 // On AIE2P, index 9 maps to crPackSize (no-op for saturation),
624 // preserving the pre-existing behavior. The AIE2P crSat fix
625 // (index 0) requires updating downstream tests and is tracked
626 // separately.
627 int satRegIdx = 9;
628 int rndRegIdx = (arch == AIEArch::AIE2p) ? 1 : 6;
629 // saturation_mode::saturate = 1
630 auto cSatIdx = arith::ConstantOp::create(
631 rewriter, loc, rewriter.getI32IntegerAttr(satRegIdx));
632 auto c1 = arith::ConstantOp::create(rewriter, loc,
633 rewriter.getI32IntegerAttr(1));
634 func::CallOp::create(rewriter, loc, ctrlRegFunc,
635 ValueRange{cSatIdx, c1});
636 // Rounding mode:
637 // - conv_even (12) for bf16 matmul: eliminates systematic
638 // negative bias from floor rounding in BFP16 arithmetic,
639 // matching ::aie::set_rounding(aie::rounding_mode::conv_even)
640 // used in external C++ matmul kernels.
641 // - positive_inf (9) for integer SRS (shift-round-saturate on
642 // integer data, e.g., i32→i8), unless the core has a bf16
643 // matmul.
644 // - conv_even (12) for float-only SRS (f32→bf16), matching the
645 // round-to-nearest-even semantics of arith.truncf.
646 int roundingMode = hasIntegerSRS && !hasBF16Matmul ? 9 : 12;
647 auto cRndIdx = arith::ConstantOp::create(
648 rewriter, loc, rewriter.getI32IntegerAttr(rndRegIdx));
649 auto cRoundingMode = arith::ConstantOp::create(
650 rewriter, loc, rewriter.getI32IntegerAttr(roundingMode));
651 func::CallOp::create(rewriter, loc, ctrlRegFunc,
652 ValueRange{cRndIdx, cRoundingMode});
653 }
654 }
655 }
656 }
657
658 // Rewrite the AIE.end() op
659 coreFunc.getBody().walk([&](Operation *childOp) {
660 rewriter.setInsertionPointAfter(childOp);
661
662 if (isa<EndOp>(childOp)) {
663 func::ReturnOp::create(rewriter, childOp->getLoc(), ValueRange({}));
664 rewriter.eraseOp(childOp);
665 }
666 });
667
668 rewriter.eraseOp(op);
669 return success();
670 }
671};
672
673// Lower AIE.event to llvm.aie.event intrinsic
674struct AIEEventOpToStdLowering : OpConversionPattern<EventOp> {
675 using OpConversionPattern::OpConversionPattern;
676 ModuleOp &module;
677
678 AIEEventOpToStdLowering(MLIRContext *context, ModuleOp &m,
679 PatternBenefit benefit = 1)
680 : OpConversionPattern(context, benefit), module(m) {}
681
682 LogicalResult
683 matchAndRewrite(EventOp op, OpAdaptor adaptor,
684 ConversionPatternRewriter &rewriter) const override {
685 auto device = op->getParentOfType<DeviceOp>();
686 std::string funcName;
687 SmallVector<Value, 1> args;
688 switch (device.getTargetModel().getTargetArch()) {
689 case AIEArch::AIE1:
690 funcName = "llvm.aie.event" + std::to_string(op.getVal());
691 break;
692 case AIEArch::AIE2:
693 funcName = "llvm.aie2.event";
694 args.push_back(arith::ConstantOp::create(
695 rewriter, op.getLoc(), rewriter.getI32Type(),
696 rewriter.getI32IntegerAttr(op.getVal())));
697 break;
698 case AIEArch::AIE2p:
699 funcName = "llvm.aie2p.event";
700 args.push_back(arith::ConstantOp::create(
701 rewriter, op.getLoc(), rewriter.getI32Type(),
702 rewriter.getI32IntegerAttr(op.getVal())));
703 break;
704 case AIEArch::AIE2ps:
705 // This arch has no event intrinsic. User IR reaches this case, through
706 // an aie.event op on a VE3858 device, so the pattern reports a rewrite
707 // failure.
708 return op.emitOpError(
709 "aie.event is not supported on this device's architecture");
710 }
711 auto eventFunc = module.lookupSymbol<func::FuncOp>(funcName);
712 if (!eventFunc)
713 return op.emitOpError("Could not find the intrinsic function ")
714 << funcName;
715 func::CallOp::create(rewriter, op.getLoc(), eventFunc, args);
716 rewriter.eraseOp(op);
717 return success();
718 }
719};
720
721struct AIECoreToStandardPass
722 : xilinx::AIE::impl::AIECoreToStandardBase<AIECoreToStandardPass> {
723 AIECoreToStandardPass() = default;
724 AIECoreToStandardPass(const AIECoreToStandardOptions &options) {
725 deviceName = options.deviceName;
726 tileCol = options.tileCol;
727 tileRow = options.tileRow;
728 }
729
730 void runOnOperation() override {
731
732 ModuleOp m = getOperation();
733 OpBuilder builder = OpBuilder::atBlockEnd(m.getBody());
734
735 DeviceOp deviceOp = DeviceOp::getForSymbolInModuleOrError(m, deviceName);
736 if (!deviceOp) {
737 return signalPassFailure();
738 }
739 const auto &targetModel = deviceOp.getTargetModel();
740
741 // Copy data layout attribute from DeviceOp to ModuleOp if present
742 if (auto dlAttr = deviceOp->getAttr(DLTIDialect::kDataLayoutAttrName)) {
743 m->setAttr(DLTIDialect::kDataLayoutAttrName, dlAttr);
744 }
745
746 // Ensure that we don't have an incorrect target triple. This may override
747 // some bogus target triple in the original mlir.
748 m->setAttr(LLVM::LLVMDialect::getTargetTripleAttrName(),
749 builder.getStringAttr(
750 getArchIntrinsicString(targetModel.getTargetArch())));
751
752 DenseMap<Operation *, SmallVector<BufferOp, 4>> tileToBuffers;
753
754 // Populate intrinsic functions
755 // Intrinsic information:
756 // peano/llvm-project/llvm/lib/Target/AIE/AIEInstrInfo.td Also take a look
757 // at the tests: peano/llvm-project/llvm/test/CodeGen/AIE
758 builder.setInsertionPointToStart(m.getBody());
759 declareAIEIntrinsics(targetModel.getTargetArch(), builder);
760
761 IRMapping mapper;
762 ConversionTarget target(getContext());
763 target.addLegalDialect<func::FuncDialect>();
764 target.addLegalDialect<cf::ControlFlowDialect>();
765 target.addLegalDialect<memref::MemRefDialect>();
766 target.addLegalDialect<VectorDialect>();
767 target.addLegalDialect<aievec::AIEVecDialect>();
768 target.addLegalDialect<arith::ArithDialect>();
769 target.addLegalDialect<ub::UBDialect>();
770 target.addLegalDialect<math::MathDialect>();
771 target.addLegalDialect<index::IndexDialect>();
772 target.addLegalDialect<ptr::PtrDialect>();
773 target.addLegalOp<func::FuncOp, ModuleOp, UnrealizedConversionCastOp>();
774
775 RewritePatternSet patterns(&getContext());
776 patterns.add<AIEPutStreamToStdLowering, AIEGetStreamToStdLowering,
777 AIEPutCascadeToStdLowering, AIEGetCascadeToStdLowering,
778 AIEDebugOpToStdLowering, AIEUseLockToStdLowering,
779 AIEEventOpToStdLowering>(m.getContext(), m);
780
781 patterns.add<AIEBufferToStandard>(m.getContext(), m, /*benefit*/ 1);
782 if (failed(applyPartialConversion(deviceOp, target, std::move(patterns))))
783 return signalPassFailure();
784
785 RewritePatternSet outlinePatterns(&getContext());
786 outlinePatterns.add<AIECoreToStandardFunc>(m.getContext(), m, mapper,
787 tileToBuffers, /*benefit*/ 1,
788 tileCol, tileRow);
789 if (failed(applyPartialConversion(deviceOp, target,
790 std::move(outlinePatterns))))
791 return signalPassFailure();
792
793 // Move all the func.func ops and memref.globals from the device to the
794 // module
795 outlineOps<memref::GlobalOp>(deviceOp);
796 outlineOps<func::FuncOp>(deviceOp);
797
798 RewritePatternSet removepatterns(&getContext());
799 removepatterns.add<
800 AIEOpRemoval<DeviceOp>, AIEOpRemoval<TileOp>, AIEOpRemoval<FlowOp>,
801 AIEOpRemoval<MemOp>, AIEOpRemoval<ShimDMAOp>, AIEOpRemoval<ShimMuxOp>,
802 AIEOpRemoval<SwitchboxOp>, AIEOpRemoval<LockOp>, AIEOpRemoval<BufferOp>,
803 AIEOpRemoval<ExternalBufferOp>, AIEOpRemoval<ShimDMAAllocationOp>,
804 AIEOpRemoval<CascadeFlowOp>, AIEOpRemoval<ConfigureCascadeOp>>(
805 m.getContext(), m);
806
807 if (failed(applyPartialConversion(m, target, std::move(removepatterns))))
808 return signalPassFailure();
809 }
810};
811} // namespace
812
813std::unique_ptr<OperationPass<ModuleOp>> AIE::createAIECoreToStandardPass() {
814 return std::make_unique<AIECoreToStandardPass>();
815}
816
817std::unique_ptr<OperationPass<ModuleOp>>
818AIE::createAIECoreToStandardPass(const AIECoreToStandardOptions &options) {
819 return std::make_unique<AIECoreToStandardPass>(options);
820}
std::tuple< const char *, std::vector< Type >, std::vector< Type > > IntrinsicDecl
std::vector< IntrinsicDecl > IntrinsicDecls
std::string coreFrameSymbolName(int col, int row)
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createAIECoreToStandardPass()
AIEArch
Definition Passes.h:19
AIEOpRemoval(MLIRContext *context, PatternBenefit benefit=1)
LogicalResult matchAndRewrite(MyOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override