21#include "mlir/Dialect/Arith/IR/Arith.h"
22#include "mlir/Dialect/Func/IR/FuncOps.h"
23#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
24#include "mlir/Dialect/SCF/IR/SCF.h"
25#include "mlir/IR/Builders.h"
26#include "mlir/Pass/Pass.h"
29#define GEN_PASS_DEF_AIELOWERCOREISSUEDCONFIGURATION
30#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
41constexpr uint64_t kTileMemoryMappedBase = 0x80000;
44struct TileAccessIntrinsics {
45 std::string write, read;
48static std::optional<TileAccessIntrinsics> tileAccessIntrinsics(
AIEArch arch) {
51 return TileAccessIntrinsics{
"llvm.aie2.write.tm",
"llvm.aie2.read.tm"};
53 return TileAccessIntrinsics{
"llvm.aie2p.write.tm",
"llvm.aie2p.read.tm"};
61pushWrites(DmaPushQueueOp op,
int col,
int row,
63 SmallVectorImpl<std::pair<uint64_t, uint32_t>> &writes) {
65 return op.emitOpError(
"names its descriptor by tag; run "
66 "--aie-assign-bd-ids first");
68 if (failed(ctl.setIOBackend(
false,
false)))
69 return op.emitOpError(
"could not start aie-rt to collect its writes");
70 ctl.startTransaction();
74 if (failed(ctl.pushToBdQueueAndEnable(
75 *op.getOperation(), col, row, op.getChannel(), op.getDirection(),
76 op.getBdId().value_or(0), op.getRepeatCount(), 0,
77 op.getOutOfOrder(),
false)))
80 std::vector<uint8_t> txn = ctl.exportSerializedTransaction();
81 std::vector<TransactionBinaryOperation> ops;
83 return op.emitOpError(
"could not read back what aie-rt recorded");
91 (
static_cast<uint64_t
>(row) << targetModel.
getRowShift());
92 uint64_t tileSpan = 1ull << targetModel.
getRowShift();
94 if (txnOp.cmd.Opcode != XAie_TxnOpcode::XAIE_IO_WRITE)
95 return op.emitOpError(
"pushing this task takes a ")
96 <<
static_cast<int>(txnOp.cmd.Opcode)
97 <<
" transaction command, which a core cannot make; only plain "
98 "register writes lower here";
99 uint64_t addr = txnOp.cmd.RegOff;
100 if (addr < tileBase || addr >= tileBase + tileSpan)
101 return op.emitOpError(
"pushing this task writes ")
102 << addr <<
", outside tile (" << col <<
", " << row
103 <<
"); a core reaches only its own tile's registers";
104 writes.emplace_back(addr - tileBase, txnOp.cmd.Value);
107 return op.emitOpError(
"aie-rt recorded no writes for this push");
111struct AIELowerCoreIssuedConfigurationPass
112 :
public xilinx::AIE::impl::AIELowerCoreIssuedConfigurationBase<
113 AIELowerCoreIssuedConfigurationPass> {
118 func::FuncOp declare(DeviceOp device, StringRef name, TypeRange argTypes,
119 TypeRange resultTypes) {
120 if (
auto decl = device.lookupSymbol<func::FuncOp>(name))
122 OpBuilder builder(device.getContext());
123 builder.setInsertionPointToStart(device.getBody());
124 auto decl = func::FuncOp::create(
125 builder, device.getLoc(), name,
126 FunctionType::get(device.getContext(), argTypes, resultTypes));
132 Value tilePointer(OpBuilder &builder, Location loc, uint64_t offset) {
133 Type i32 = builder.getI32Type();
134 Type ptr = LLVM::LLVMPointerType::get(builder.getContext());
135 Value addr = arith::ConstantOp::create(
137 builder.getI32IntegerAttr(
138 static_cast<int32_t
>(kTileMemoryMappedBase + offset)));
139 return LLVM::IntToPtrOp::create(builder, loc, ptr, addr);
142 LogicalResult lowerPush(DmaPushQueueOp op,
const AIETargetModel &targetModel,
143 func::FuncOp write) {
144 CoreOp core = op->getParentOfType<CoreOp>();
145 TileOp tile = core.getTileOp();
146 SmallVector<std::pair<uint64_t, uint32_t>> writes;
148 pushWrites(op, tile.getCol(), tile.getRow(), targetModel, writes)))
151 core.setProcessorBus(
true);
153 OpBuilder builder(op);
154 Type i32 = builder.getI32Type();
155 for (
auto [offset, value] : writes) {
156 Value valueOp = arith::ConstantOp::create(
157 builder, op.getLoc(), i32,
158 builder.getI32IntegerAttr(
static_cast<int32_t
>(value)));
159 Value addrPtr = tilePointer(builder, op.getLoc(), offset);
164 func::CallOp::create(builder, op.getLoc(), write,
165 ValueRange{valueOp, addrPtr});
171 LogicalResult lowerPoll(DmaPollForQueueSpaceOp op,
172 const AIETargetModel &targetModel,
174 CoreOp core = op->getParentOfType<CoreOp>();
175 TileOp tile = core.getTileOp();
177 tile.getCol(), tile.getRow(), op.getChannel(), op.getDirection());
179 return op.emitOpError(
"this tile reports no DMA status register to read");
182 return op.emitOpError(
183 "this tile reports no task-queue field in its DMA status");
184 core.setProcessorBus(
true);
186 OpBuilder builder(op);
187 Location loc = op.getLoc();
188 Type i32 = builder.getI32Type();
189 uint32_t localAddr = *statusAddr & ((1u << targetModel.
getRowShift()) - 1);
190 Value addrPtr = tilePointer(builder, loc, localAddr);
191 Value maskOp = arith::ConstantOp::create(
193 builder.getI32IntegerAttr(
static_cast<int32_t
>(mask)));
197 << llvm::countr_zero(mask);
198 Value fullOp = arith::ConstantOp::create(
200 builder.getI32IntegerAttr(
static_cast<int32_t
>(full)));
204 auto loop = scf::WhileOp::create(builder, loc, TypeRange{}, ValueRange{});
205 OpBuilder::InsertionGuard guard(builder);
206 Block *before = builder.createBlock(&loop.getBefore());
207 builder.setInsertionPointToStart(before);
208 Value status = func::CallOp::create(builder, loc, read, ValueRange{addrPtr})
210 Value size = arith::AndIOp::create(builder, loc, status, maskOp);
211 Value isFull = arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::eq,
213 scf::ConditionOp::create(builder, loc, isFull, ValueRange{});
215 Block *after = builder.createBlock(&loop.getAfter());
216 builder.setInsertionPointToStart(after);
217 scf::YieldOp::create(builder, loc, ValueRange{});
223 void runOnOperation()
override {
224 DeviceOp device = getOperation();
225 const AIETargetModel &targetModel = device.getTargetModel();
227 SmallVector<DmaPushQueueOp> pushes;
228 SmallVector<DmaPollForQueueSpaceOp> polls;
229 device.walk([&](Operation *op) {
230 if (
auto push = dyn_cast<DmaPushQueueOp>(op))
231 pushes.push_back(push);
232 else if (
auto poll = dyn_cast<DmaPollForQueueSpaceOp>(op))
233 polls.push_back(poll);
235 if (pushes.empty() && polls.empty())
238 std::optional<TileAccessIntrinsics> intrinsics =
241 Operation *first = pushes.empty() ? polls.front().getOperation()
242 : pushes.front().getOperation();
243 first->emitOpError(
"this device has no access a core can make to its "
244 "own tile's registers");
245 return signalPassFailure();
248 OpBuilder builder(device.getContext());
249 Type i32 = builder.getI32Type();
250 Type ptr = LLVM::LLVMPointerType::get(device.getContext());
252 if (!polls.empty()) {
253 func::FuncOp read = declare(device, intrinsics->read, {ptr}, {i32});
254 for (DmaPollForQueueSpaceOp op : polls)
255 if (failed(lowerPoll(op, targetModel, read)))
256 return signalPassFailure();
258 if (!pushes.empty()) {
260 declare(device, intrinsics->write, {i32, ptr}, TypeRange{});
261 for (DmaPushQueueOp op : pushes)
262 if (failed(lowerPush(op, targetModel, write)))
263 return signalPassFailure();
270std::unique_ptr<OperationPass<DeviceOp>>
272 return std::make_unique<AIELowerCoreIssuedConfigurationPass>();
virtual AIEArch getTargetArch() const =0
Return the target architecture.
virtual uint32_t getDmaTaskQueueDepth() const =0
Return how many DMA tasks a single channel's task queue holds (unsupported = 0).
virtual uint32_t getDmaTaskQueueSizeMask() const
Return the mask selecting the live task-queue occupancy field within the getDmaStatusAddress() regist...
virtual uint32_t getColumnShift() const =0
virtual std::optional< uint32_t > getDmaStatusAddress(int, int, int, AIE::DMAChannelDir) const
Return the address of the DMA status register for a channel, or nullopt when this target's layout has...
virtual uint32_t getRowShift() const =0
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIELowerCoreIssuedConfigurationPass()
std::optional< int > parseTransactionBinary(const std::vector< uint8_t > &data, std::vector< TransactionBinaryOperation > &ops)
Parse an exported transaction.