31#include "mlir/Dialect/Arith/IR/Arith.h"
32#include "mlir/Dialect/MemRef/IR/MemRef.h"
33#include "mlir/Pass/Pass.h"
35#include "llvm/ADT/DenseMap.h"
36#include "llvm/ADT/MapVector.h"
37#include "llvm/ADT/StringRef.h"
38#include "llvm/Support/FormatVariadic.h"
39#include "llvm/Support/raw_ostream.h"
46#define GEN_PASS_DEF_AIEEXPANDLOADPDI
47#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
50#define DEBUG_TYPE "aie-expand-load-pdi"
59static AIE::DeviceOp controlOverlayOf(AIE::DeviceOp device) {
60 auto ref = device.getOverlayAttr();
61 return ref ? device->getParentOfType<ModuleOp>().lookupSymbol<AIE::DeviceOp>(
66using OverlayCopies = llvm::DenseMap<AIE::DeviceOp, AIE::DeviceOp>;
69static AIE::DeviceOp overlayPreload(AIE::DeviceOp overlay,
unsigned parity,
70 OverlayCopies &copies) {
73 auto [it, inserted] = copies.try_emplace(overlay);
76 auto copy = cast<AIE::DeviceOp>(overlay->clone());
77 copy.setSymName((overlay.getSymName() +
"_copy").str());
78 SymbolTable(overlay->getParentOfType<ModuleOp>()).insert(copy);
79 return it->second = copy;
85verifyOverlayReachesTiles(NpuLoadPdiOp loadPdiOp, AIE::DeviceOp overlay,
86 iterator_range<Block::iterator> packets) {
87 std::set<AIE::TileID> reached;
88 overlay.walk([&](AIE::SwitchboxOp sb) {
89 for (Operation &op : sb.getConnections().front()) {
90 std::optional<AIE::Port> dest;
91 if (
auto master = dyn_cast<AIE::MasterSetOp>(op))
92 dest = master.destPort();
93 else if (
auto connect = dyn_cast<AIE::ConnectOp>(op))
94 dest = connect.destPort();
95 if (dest ==
AIE::Port{AIE::WireBundle::TileControl, 0})
96 reached.insert({sb.colIndex(), sb.rowIndex()});
99 for (Operation &op : packets) {
100 auto packet = dyn_cast<NpuControlPacketOp>(op);
103 AIE::TileID tile{
static_cast<int>(packet.getColumnFromAddr()),
104 static_cast<int>(packet.getRowFromAddr())};
105 if (reached.count(tile))
107 return loadPdiOp.emitError()
108 <<
"a control-packet reload configures tile (" << tile.col <<
", "
109 << tile.row <<
"), but @" << overlay.getSymName()
110 <<
" routes no control packets to it";
118static AIE::DeviceOp getOrCreateEmptyDevice(ModuleOp moduleOp,
119 AIE::AIEDevice deviceType,
120 unsigned parity, Location loc) {
121 std::string emptyName =
"empty_" + std::to_string(parity);
122 if (
auto existing = moduleOp.lookupSymbol<AIE::DeviceOp>(emptyName)) {
124 loc = FusedLoc::get(moduleOp.getContext(), {existing.getLoc(), loc});
125 existing->setLoc(loc);
126 existing.getRegion().front().getTerminator()->setLoc(loc);
130 OpBuilder builder(moduleOp.getContext());
131 builder.setInsertionPointToStart(moduleOp.getBody());
132 auto emptyDevice = AIE::DeviceOp::create(
133 builder, loc, deviceType, builder.getStringAttr(emptyName),
nullptr);
134 emptyDevice.getRegion().emplaceBlock();
135 builder.setInsertionPointToEnd(&emptyDevice.getRegion().front());
136 AIE::EndOp::create(builder, loc);
142struct CtrlPktReload {
144 AIE::DeviceOp device;
145 NpuLoadPdiOp preload;
146 AIE::DeviceOp overlay;
150static void setInsertionPointToSegmentEnd(OpBuilder &builder,
151 NpuLoadPdiOp loadPdiOp) {
152 Operation *op = loadPdiOp->getNextNode();
153 while (op && !isa<NpuLoadPdiOp>(op) && !op->hasTrait<OpTrait::IsTerminator>())
154 op = op->getNextNode();
156 builder.setInsertionPoint(op);
158 builder.setInsertionPointToEnd(loadPdiOp->getBlock());
168static LogicalResult transformLoadPdi(
169 NpuLoadPdiOp loadPdiOp, ModuleOp moduleOp,
unsigned index,
170 AIEX::ExpandMode defaultMode,
bool keepPreload,
bool teardown,
172 SmallVectorImpl<CtrlPktReload> &ctrlPktReloads, OverlayCopies &copies) {
173 OpBuilder builder(loadPdiOp);
176 auto deviceRefAttr = loadPdiOp.getDeviceRefAttr();
177 if (!deviceRefAttr) {
182 AIEX::ExpandMode mode = loadPdiOp.getExpandMode().value_or(defaultMode);
183 if (mode == AIEX::ExpandMode::none)
185 bool ctrlPkt = (mode == AIEX::ExpandMode::ctrlpkt);
187 auto referencedDevice = moduleOp.lookupSymbol<AIE::DeviceOp>(deviceRefAttr);
188 if (!referencedDevice) {
189 loadPdiOp.emitError(
"Referenced symbol '")
190 << deviceRefAttr.getValue() <<
"' is not a device";
194 AIE::DeviceOp overlay;
196 overlay = controlOverlayOf(referencedDevice);
198 return referencedDevice.emitOpError(
199 "requires an overlay symbol reference for control-packet "
206 FlatSymbolRefAttr preloadRef;
208 preloadRef = FlatSymbolRefAttr::get(
209 overlayPreload(overlay, index % 2, copies).getSymNameAttr());
211 AIE::DeviceOp emptyDevice =
212 getOrCreateEmptyDevice(moduleOp, referencedDevice.getDevice(),
213 index % 2, loadPdiOp.getLoc());
214 preloadRef = FlatSymbolRefAttr::get(emptyDevice.getSymNameAttr());
217 AIEX::ExpandModeAttr::get(builder.getContext(), AIEX::ExpandMode::none);
219 NpuLoadPdiOp::create(builder, loadPdiOp.getLoc(), preloadRef,
221 nullptr, expandNone);
223 NpuLoadPdiOp::create(builder, loadPdiOp.getLoc(), preloadRef,
224 loadPdiOp.getIdAttr(), loadPdiOp.getSizeAttr(),
225 loadPdiOp.getAddressAttr(), expandNone);
227 Operation *beforeConfig = loadPdiOp->getPrevNode();
230 NpuWriteConfigOp::create(builder, loadPdiOp.getLoc(), deviceRefAttr);
232 setInsertionPointToSegmentEnd(builder, loadPdiOp);
233 NpuWriteTeardownOp::create(builder, loadPdiOp.getLoc(), deviceRefAttr);
239 ctrlPktReloads.push_back({loadPdiOp, referencedDevice,
240 cast<NpuLoadPdiOp>(beforeConfig), overlay});
246 builder, referencedDevice, *configData,
"", outputType,
248 loadPdiOp.emitError(
"Failed to generate configuration operations");
251 if (failed(verifyOverlayReachesTiles(
253 {beforeConfig ? std::next(beforeConfig->getIterator())
254 : loadPdiOp->getBlock()->begin(),
255 loadPdiOp->getIterator()})))
260 Location loc = loadPdiOp.getLoc();
261 setInsertionPointToSegmentEnd(builder, loadPdiOp);
264 builder, referencedDevice, *teardownData, outputType,
266 emitError(loc,
"Failed to generate teardown operations");
275verifyExpansion(ModuleOp module, ArrayRef<NpuLoadPdiOp> loadPdiOps,
276 function_ref<AIEX::ExpandMode(NpuLoadPdiOp)> expandMode,
277 bool teardown,
bool initEntry) {
278 if (initEntry && !teardown)
279 return module.emitError(
280 "aie-expand-load-pdi: init-entry requires teardown");
282 for (
auto device : module.getOps<AIE::DeviceOp>())
284 return existing->emitOpError()
286 <<
"', which aie-expand-load-pdi reserves under init-entry";
289 for (NpuLoadPdiOp loadPdiOp : loadPdiOps) {
290 if (expandMode(loadPdiOp) == AIEX::ExpandMode::none)
294 auto dev =
module.lookupSymbol<AIE::DeviceOp>(loadPdiOp.getDeviceRefAttr());
296 return start->emitOpError(
297 "starts a shim DMA channel, which teardown cannot stop");
302struct AIEExpandLoadPdiPass
303 :
public xilinx::AIEX::impl::AIEExpandLoadPdiBase<AIEExpandLoadPdiPass> {
304 using AIEExpandLoadPdiBase::AIEExpandLoadPdiBase;
306 void getDependentDialects(DialectRegistry ®istry)
const override {
307 registry.insert<arith::ArithDialect, memref::MemRefDialect, AIE::AIEDialect,
308 AIEX::AIEXDialect>();
311 void runOnOperation()
override {
312 auto module = getOperation();
318 SmallVector<NpuLoadPdiOp> loadPdiOps;
321 [&](NpuLoadPdiOp loadPdiOp) { loadPdiOps.push_back(loadPdiOp); });
324 AIEX::ExpandMode defaultMode =
325 clCtrlPkt ? AIEX::ExpandMode::ctrlpkt : AIEX::ExpandMode::write32;
327 auto expandMode = [&](NpuLoadPdiOp loadPdiOp) {
328 return loadPdiOp.getDeviceRefAttr()
329 ? loadPdiOp.getExpandMode().value_or(defaultMode)
330 : AIEX::ExpandMode::none;
340 using ArrayState = std::pair<AIEX::ExpandMode, FlatSymbolRefAttr>;
341 struct PreloadParity {
342 unsigned firstParity = 0;
343 unsigned lastParity = 0;
345 AIE::AIEDevice device = {};
346 AIE::DeviceOp firstOverlay, lastOverlay;
348 struct SequenceLoads {
349 std::array<PreloadParity, 2> preloads;
350 ArrayState last = {AIEX::ExpandMode::none, {}};
352 llvm::MapVector<AIE::RuntimeSequenceOp, SequenceLoads> sequences;
353 for (
auto [index, loadPdiOp] : llvm::enumerate(loadPdiOps)) {
354 auto seq = loadPdiOp->getParentOfType<AIE::RuntimeSequenceOp>();
357 SequenceLoads &loads = sequences[seq];
358 AIEX::ExpandMode mode = expandMode(loadPdiOp);
359 loads.last = {mode, {}};
360 if (mode == AIEX::ExpandMode::none)
363 module.lookupSymbol<AIE::DeviceOp>(loadPdiOp.getDeviceRefAttr());
366 auto overlay = mode == AIEX::ExpandMode::ctrlpkt ? controlOverlayOf(dev)
368 if (mode == AIEX::ExpandMode::ctrlpkt && !overlay) {
369 dev.emitOpError(
"requires an overlay symbol reference for "
370 "control-packet configuration");
371 return signalPassFailure();
373 loads.last.second = overlay ? dev.getOverlayAttr() : FlatSymbolRefAttr{};
374 auto &entry = loads.preloads[mode == AIEX::ExpandMode::ctrlpkt];
376 entry.firstParity = index % 2;
378 entry.firstOverlay = overlay;
380 entry.lastParity = index % 2;
381 entry.lastOverlay = overlay;
382 entry.device = dev.getDevice();
392 llvm::MapVector<AIE::DeviceOp, ArrayState> initEntries;
394 for (
auto &[seq, loads] : sequences)
395 initEntries.try_emplace(seq->getParentOfType<AIE::DeviceOp>(),
397 for (
auto &[seq, loads] : sequences) {
398 auto device = seq->getParentOfType<AIE::DeviceOp>();
399 auto *it = initEntries.find(device);
400 if (it != initEntries.end() &&
401 (loads.last.first == AIEX::ExpandMode::none ||
402 loads.last != it->second))
403 initEntries.erase(it);
407 if (failed(verifyExpansion(module, loadPdiOps, expandMode, clTeardown,
409 return signalPassFailure();
411 OverlayCopies copies;
412 SmallVector<CtrlPktReload> ctrlPktReloads;
413 llvm::DenseMap<AIE::DeviceOp, AIEX::BlockwriteData> configData,
416 llvm::DenseMap<Operation *, std::optional<ArrayState>> arrayState;
417 for (
auto [idx, loadPdiOp] : llvm::enumerate(loadPdiOps)) {
418 auto seq = loadPdiOp->getParentOfType<AIE::RuntimeSequenceOp>();
419 std::optional<ArrayState> start;
421 if (
auto *it = initEntries.find(seq->getParentOfType<AIE::DeviceOp>());
422 it != initEntries.end())
424 std::optional<ArrayState> &state =
425 arrayState.try_emplace(seq, start).first->second;
430 module.lookupSymbol<AIE::DeviceOp>(loadPdiOp.getDeviceRefAttr());
431 auto mode = expandMode(loadPdiOp);
432 ArrayState next = {mode, dev && mode == AIEX::ExpandMode::ctrlpkt
433 ? dev.getOverlayAttr()
434 : FlatSymbolRefAttr{}};
435 bool keepPreload = !clTeardown || state != next;
438 if (clTeardown && expandMode(loadPdiOp) == AIEX::ExpandMode::ctrlpkt) {
439 auto device = loadPdiOp->getParentOfType<AIE::DeviceOp>();
441 &configData.try_emplace(device, device,
"loadpdi_").first->second;
442 teardown = &teardownData.try_emplace(device, device,
"teardown_")
445 if (failed(transformLoadPdi(loadPdiOp, module, idx, defaultMode,
446 keepPreload, clTeardown, config, teardown,
447 ctrlPktReloads, copies))) {
453 SmallVector<std::pair<Operation *, AIE::DeviceOp>> ctrlPktSites;
454 for (CtrlPktReload &reload : ctrlPktReloads)
455 ctrlPktSites.emplace_back(reload.load, reload.device);
461 for (CtrlPktReload &reload : ctrlPktReloads) {
462 if (failed(verifyOverlayReachesTiles(
463 reload.load, reload.overlay,
464 {std::next(reload.preload->getIterator()),
465 reload.load->getIterator()}))) {
472 for (
auto [device, kind] : initEntries) {
473 FlatSymbolRefAttr preloadRef =
474 kind.first == AIEX::ExpandMode::ctrlpkt
476 : FlatSymbolRefAttr::get(
477 getOrCreateEmptyDevice(module, device.getDevice(), 0,
480 OpBuilder builder(device.getContext());
481 builder.setInsertionPoint(
482 *device.getOps<AIE::RuntimeSequenceOp>().begin());
483 auto init = AIE::RuntimeSequenceOp::create(
484 builder, device.getLoc(),
486 AIE::TraceBufferAttr{}, ArrayAttr{}, ArrayAttr{});
487 builder.setInsertionPointToStart(&init.getBody().emplaceBlock());
488 NpuLoadPdiOp::create(builder, device.getLoc(), preloadRef,
492 AIEX::ExpandModeAttr::get(builder.getContext(),
493 AIEX::ExpandMode::none));
497 return signalPassFailure();
518 for (
auto &[seq, loads] : sequences) {
519 for (
auto [ctrlPkt, info] : llvm::enumerate(loads.preloads)) {
520 if (!info.seen || info.firstParity != info.lastParity ||
521 info.firstOverlay != info.lastOverlay)
523 unsigned parity = 1 - info.lastParity;
524 FlatSymbolRefAttr preloadRef;
526 preloadRef = FlatSymbolRefAttr::get(
527 overlayPreload(info.lastOverlay, parity, copies)
530 AIE::DeviceOp emptyDevice =
531 getOrCreateEmptyDevice(module, info.device, parity, seq.getLoc());
532 preloadRef = FlatSymbolRefAttr::get(emptyDevice.getSymNameAttr());
534 OpBuilder builder(seq.getContext());
535 Block &body = seq.getBody().front();
536 if (!body.empty() && body.back().hasTrait<OpTrait::IsTerminator>())
537 builder.setInsertionPoint(&body.back());
539 builder.setInsertionPointToEnd(&body);
540 NpuLoadPdiOp::create(builder, seq.getLoc(), preloadRef,
544 AIEX::ExpandModeAttr::get(seq.getContext(),
545 AIEX::ExpandMode::none));
553std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
555 return std::make_unique<AIEExpandLoadPdiPass>();
constexpr llvm::StringLiteral initEntrySequenceName
Name of the runtime sequence aie-expand-load-pdi{init-entry} adds.
mlir::Operation * findShimDmaStart(AIE::DeviceOp device)
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createAIEExpandLoadPdiPass()
mlir::LogicalResult insertConfigOps(llvm::ArrayRef< std::pair< mlir::Operation *, xilinx::AIE::DeviceOp > > sites, AIEToConfigurationOutputType outputType)
mlir::LogicalResult inlineWriteConfigs(mlir::ModuleOp module)
mlir::LogicalResult generateAndInsertConfigOps(mlir::OpBuilder &builder, xilinx::AIE::DeviceOp device, xilinx::AIEX::BlockwriteData &blockwriteData, llvm::StringRef clElfDir="", AIEToConfigurationOutputType outputType=AIEToConfigurationOutputType::Transaction, bool skipCtrlPktOverlay=false)
struct TileID { friend std::ostream &operator<<(std::ostream &os, const TileID &s) { os<< "TileID("<< s.col<< ", "<< s.row<< ")"; return os; } friend std::string to_string(const TileID &s) { std::ostringstream ss; ss<< s; return ss.str(); } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const TileID &s) { os<< to_string(s); return os; } inline bool operator<(const TileID &rhs) const { return std::tie(col, row)< std::tie(rhs.col, rhs.row); } bool operator==(const TileID &rhs) const { return std::tie(col, row)==std::tie(rhs.col, rhs.row); } bool operator!=(const TileID &rhs) const { return !(*this==rhs);} int col, row;} TileID
mlir::LogicalResult generateAndInsertTeardownOps(mlir::OpBuilder &builder, xilinx::AIE::DeviceOp device, xilinx::AIEX::BlockwriteData &blockwriteData, AIEToConfigurationOutputType outputType=AIEToConfigurationOutputType::Transaction, bool skipCtrlPktOverlay=false)
struct Port { WireBundle bundle; int channel; bool operator==(const Port &rhs) const { return std::tie(bundle, channel)==std::tie(rhs.bundle, rhs.channel); } bool operator!=(const Port &rhs) const { return !(*this==rhs);} bool operator<(const Port &rhs) const { return std::tie(bundle, channel)< std::tie(rhs.bundle, rhs.channel); } friend std::ostream &operator<<(std::ostream &os, const Port &port) { os<< "("; os<< stringifyWireBundle(port.bundle).str(); os<< ": "<< std::to_string(port.channel)<< ")"; return os; } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Port &port) { os<< to_string(port); return os; } } Port