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/SetVector.h"
38#include "llvm/ADT/StringRef.h"
39#include "llvm/Support/FormatVariadic.h"
40#include "llvm/Support/raw_ostream.h"
47#define GEN_PASS_DEF_AIEEXPANDLOADPDI
48#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
51#define DEBUG_TYPE "aie-expand-load-pdi"
63static constexpr llvm::StringLiteral kCtrlPktOverlayName =
"ctrl_pkt_overlay";
67static constexpr llvm::StringLiteral kCtrlPktOverlayCopyName =
68 "ctrl_pkt_overlay_copy";
73static AIE::DeviceOp getOrCreateCtrlPktOverlayCopy(ModuleOp moduleOp,
74 Operation *errLocOp) {
76 moduleOp.lookupSymbol<AIE::DeviceOp>(kCtrlPktOverlayCopyName))
79 auto orig = moduleOp.lookupSymbol<AIE::DeviceOp>(kCtrlPktOverlayName);
81 errLocOp->emitError(
"ctrl-pkt mode requires a `@")
82 << kCtrlPktOverlayName <<
"` device in the module";
86 OpBuilder builder(moduleOp.getContext());
87 builder.setInsertionPointAfter(orig);
88 auto *cloned = builder.clone(*orig.getOperation());
89 auto clonedDev = cast<AIE::DeviceOp>(cloned);
90 clonedDev.setSymName(kCtrlPktOverlayCopyName);
102 bool operator<(
const SwitchPort &other)
const {
103 return std::tie(tile, master, port, slot) <
104 std::tie(other.tile, other.master, other.port, other.slot);
107 std::string describe()
const {
109 llvm::formatv(
"tile ({0}, {1}) {2} {3} : {4}", tile.col, tile.row,
110 master ?
"master" :
"slave",
111 AIE::stringifyWireBundle(port.bundle), port.channel);
113 s += llvm::formatv(
" slot {0}", slot).str();
128static SmallVector<PortSetting> portSettings(Operation &op,
129 AIE::SwitchboxOp sb) {
130 auto key = [&](
bool master,
AIE::Port port,
int slot = -1) {
131 return SwitchPort{{sb.colIndex(), sb.rowIndex()}, master, port, slot};
133 auto amsel = [](Value v) {
134 auto a = v.getDefiningOp<AIE::AMSelOp>();
135 return llvm::formatv(
"{0}.{1}", a.arbiterIndex(), a.getMselValue()).str();
137 AIE::AIEDialect::IsCtrlPktOverlayAttrHelper overlay(op.getContext());
138 bool skipped = overlay.isAttrPresent(&op);
139 SmallVector<PortSetting> settings;
140 if (
auto connect = dyn_cast<AIE::ConnectOp>(op)) {
143 {key(
true, connect.destPort()),
144 llvm::formatv(
"circuit {0} : {1}",
145 AIE::stringifyWireBundle(src.bundle), src.channel),
147 settings.push_back({key(
false, src),
"circuit", &op, skipped});
148 }
else if (
auto masterSet = dyn_cast<AIE::MasterSetOp>(op)) {
151 for (Value v : masterSet.getAmsels()) {
152 auto a = cast<AIE::AMSelOp>(v.getDefiningOp());
153 arbiter = a.arbiterIndex();
154 mask |= 1 << a.getMselValue();
157 llvm::formatv(
"packet {0}/{1} {2}", arbiter, mask,
158 masterSet.keepsPktHeader() ?
"keep" :
"drop")
160 settings.push_back({key(
true, masterSet.destPort()), value, &op, skipped});
161 }
else if (
auto rules = dyn_cast<AIE::PacketRulesOp>(op)) {
163 auto ruleOps = rules.getRules().front().getOps<AIE::PacketRuleOp>();
166 bool anySkipped = skipped;
167 for (
auto [slot, rule] : llvm::enumerate(ruleOps)) {
168 bool ruleSkipped = skipped || overlay.isAttrPresent(rule);
169 anySkipped |= ruleSkipped;
170 settings.push_back({key(
false, rules.sourcePort(), slot),
171 llvm::formatv(
"{0}/{1}>{2}", rule.valueInt(),
172 rule.maskInt(), amsel(rule.getAmsel())),
175 settings.insert(settings.begin(), {key(false, rules.sourcePort()),
"packet",
185static LogicalResult verifyReloadKeepsOverlay(AIE::DeviceOp device,
186 AIE::DeviceOp overlay) {
187 StringRef overlayName = overlay.getSymName();
188 std::map<SwitchPort, std::string> overlaySettings;
189 overlay.walk([&](AIE::SwitchboxOp sb) {
190 for (Operation &op : sb.getConnections().front())
191 for (PortSetting &setting : portSettings(op, sb))
192 overlaySettings[setting.port] = setting.value;
194 WalkResult result = device.walk([&](AIE::SwitchboxOp sb) {
195 for (Operation &op : sb.getConnections().front()) {
196 for (
auto &[port, value, setter, skipped] : portSettings(op, sb)) {
197 auto it = overlaySettings.find(port);
198 if (skipped && it == overlaySettings.end()) {
200 <<
"a control-packet reload skips this op, but @" << overlayName
201 <<
" does not set " << port.describe();
202 return WalkResult::interrupt();
204 if (skipped && it->second != value) {
206 <<
"a control-packet reload skips this op, but @" << overlayName
207 <<
" sets " << port.describe() <<
" differently";
208 return WalkResult::interrupt();
210 if (!skipped && it != overlaySettings.end()) {
212 <<
"a control-packet reload rewrites " << port.describe()
213 <<
", which the control packets of @" << overlayName
215 return WalkResult::interrupt();
219 return WalkResult::advance();
221 return failure(result.wasInterrupted());
227verifyOverlayReachesTiles(NpuLoadPdiOp loadPdiOp, AIE::DeviceOp overlay,
228 iterator_range<Block::iterator> packets) {
229 std::set<AIE::TileID> reached;
230 overlay.walk([&](AIE::SwitchboxOp sb) {
231 for (Operation &op : sb.getConnections().front()) {
232 std::optional<AIE::Port> dest;
233 if (
auto master = dyn_cast<AIE::MasterSetOp>(op))
234 dest = master.destPort();
235 else if (
auto connect = dyn_cast<AIE::ConnectOp>(op))
236 dest = connect.destPort();
237 if (dest ==
AIE::Port{AIE::WireBundle::TileControl, 0})
238 reached.insert({sb.colIndex(), sb.rowIndex()});
241 for (Operation &op : packets) {
242 auto packet = dyn_cast<NpuControlPacketOp>(op);
245 AIE::TileID tile{
static_cast<int>(packet.getColumnFromAddr()),
246 static_cast<int>(packet.getRowFromAddr())};
247 if (reached.count(tile))
249 return loadPdiOp.emitError()
250 <<
"a control-packet reload configures tile (" << tile.col <<
", "
251 << tile.row <<
"), but @" << overlay.getSymName()
252 <<
" routes no control packets to it";
260static AIE::DeviceOp getOrCreateEmptyDevice(ModuleOp moduleOp,
261 AIE::AIEDevice deviceType,
262 unsigned parity, Location loc) {
263 std::string emptyName =
"empty_" + std::to_string(parity);
264 if (
auto existing = moduleOp.lookupSymbol<AIE::DeviceOp>(emptyName)) {
266 loc = FusedLoc::get(moduleOp.getContext(), {existing.getLoc(), loc});
267 existing->setLoc(loc);
268 existing.getRegion().front().getTerminator()->setLoc(loc);
272 OpBuilder builder(moduleOp.getContext());
273 builder.setInsertionPointToStart(moduleOp.getBody());
274 auto emptyDevice = AIE::DeviceOp::create(builder, loc, deviceType,
275 builder.getStringAttr(emptyName));
276 emptyDevice.getRegion().emplaceBlock();
277 builder.setInsertionPointToEnd(&emptyDevice.getRegion().front());
278 AIE::EndOp::create(builder, loc);
284struct CtrlPktReload {
286 AIE::DeviceOp device;
287 NpuLoadPdiOp preload;
288 AIE::DeviceOp overlay;
292static void setInsertionPointToSegmentEnd(OpBuilder &builder,
293 NpuLoadPdiOp loadPdiOp) {
294 Operation *op = loadPdiOp->getNextNode();
295 while (op && !isa<NpuLoadPdiOp>(op) && !op->hasTrait<OpTrait::IsTerminator>())
296 op = op->getNextNode();
298 builder.setInsertionPoint(op);
300 builder.setInsertionPointToEnd(loadPdiOp->getBlock());
307transformLoadPdi(NpuLoadPdiOp loadPdiOp, ModuleOp moduleOp,
unsigned index,
308 AIEX::ExpandMode defaultMode,
bool keepPreload,
bool teardown,
309 SmallVectorImpl<CtrlPktReload> &ctrlPktReloads) {
310 OpBuilder builder(loadPdiOp);
313 auto deviceRefAttr = loadPdiOp.getDeviceRefAttr();
314 if (!deviceRefAttr) {
319 AIEX::ExpandMode mode = loadPdiOp.getExpandMode().value_or(defaultMode);
320 if (mode == AIEX::ExpandMode::none)
322 bool ctrlPkt = (mode == AIEX::ExpandMode::ctrlpkt);
324 auto referencedDevice = moduleOp.lookupSymbol<AIE::DeviceOp>(deviceRefAttr);
325 if (!referencedDevice) {
326 loadPdiOp.emitError(
"Referenced symbol '")
327 << deviceRefAttr.getValue() <<
"' is not a device";
331 FlatSymbolRefAttr preloadRef;
332 AIE::DeviceOp overlay;
339 StringRef overlayName =
340 (index % 2 == 0) ? kCtrlPktOverlayName : kCtrlPktOverlayCopyName;
341 if (index % 2 != 0) {
343 getOrCreateCtrlPktOverlayCopy(moduleOp, loadPdiOp.getOperation());
347 overlay = moduleOp.lookupSymbol<AIE::DeviceOp>(kCtrlPktOverlayName);
349 loadPdiOp.emitError(
"ctrl-pkt mode requires a `@")
350 << kCtrlPktOverlayName <<
"` device in the module";
354 if (failed(verifyReloadKeepsOverlay(referencedDevice, overlay)))
356 preloadRef = FlatSymbolRefAttr::get(builder.getContext(), overlayName);
357 }
else if (keepPreload) {
359 AIE::DeviceOp emptyDevice = getOrCreateEmptyDevice(
360 moduleOp, referencedDevice.getDevice(), index % 2, loadPdiOp.getLoc());
361 preloadRef = FlatSymbolRefAttr::get(emptyDevice.getSymNameAttr());
364 builder.setInsertionPoint(loadPdiOp);
368 NpuLoadPdiOp preload =
369 NpuLoadPdiOp::create(builder, loadPdiOp.getLoc(), preloadRef,
373 AIEX::ExpandModeAttr::get(builder.getContext(),
374 AIEX::ExpandMode::none));
377 ctrlPktReloads.push_back({loadPdiOp, referencedDevice, preload, overlay});
381 NpuLoadPdiOp::create(builder, loadPdiOp.getLoc(), preloadRef,
382 loadPdiOp.getIdAttr(), loadPdiOp.getSizeAttr(),
383 loadPdiOp.getAddressAttr(),
385 AIEX::ExpandModeAttr::get(builder.getContext(),
386 AIEX::ExpandMode::none));
387 NpuWriteConfigOp::create(builder, loadPdiOp.getLoc(), deviceRefAttr);
389 setInsertionPointToSegmentEnd(builder, loadPdiOp);
390 NpuWriteTeardownOp::create(builder, loadPdiOp.getLoc(), deviceRefAttr);
399verifyExpansion(ModuleOp module, ArrayRef<NpuLoadPdiOp> loadPdiOps,
400 function_ref<AIEX::ExpandMode(NpuLoadPdiOp)> expandMode,
401 bool teardown,
bool initEntry) {
402 if (initEntry && !teardown)
403 return module.emitError(
404 "aie-expand-load-pdi: init-entry requires teardown");
406 for (
auto device : module.getOps<AIE::DeviceOp>())
408 return existing->emitOpError()
410 <<
"', which aie-expand-load-pdi reserves under init-entry";
413 for (NpuLoadPdiOp loadPdiOp : loadPdiOps) {
414 AIEX::ExpandMode mode = expandMode(loadPdiOp);
415 if (mode == AIEX::ExpandMode::ctrlpkt)
416 return loadPdiOp.emitError(
417 "teardown is not implemented for control packet reconfiguration");
418 if (mode == AIEX::ExpandMode::none)
422 auto dev =
module.lookupSymbol<AIE::DeviceOp>(loadPdiOp.getDeviceRefAttr());
424 return start->emitOpError(
425 "starts a shim DMA channel, which teardown cannot stop");
430struct AIEExpandLoadPdiPass
431 :
public xilinx::AIEX::impl::AIEExpandLoadPdiBase<AIEExpandLoadPdiPass> {
432 using AIEExpandLoadPdiBase::AIEExpandLoadPdiBase;
434 void getDependentDialects(DialectRegistry ®istry)
const override {
435 registry.insert<arith::ArithDialect, memref::MemRefDialect, AIE::AIEDialect,
436 AIEX::AIEXDialect>();
439 void runOnOperation()
override {
440 auto module = getOperation();
446 SmallVector<NpuLoadPdiOp> loadPdiOps;
449 [&](NpuLoadPdiOp loadPdiOp) { loadPdiOps.push_back(loadPdiOp); });
452 AIEX::ExpandMode defaultMode =
453 clCtrlPkt ? AIEX::ExpandMode::ctrlpkt : AIEX::ExpandMode::write32;
455 auto expandMode = [&](NpuLoadPdiOp loadPdiOp) {
456 return loadPdiOp.getDeviceRefAttr()
457 ? loadPdiOp.getExpandMode().value_or(defaultMode)
458 : AIEX::ExpandMode::none;
468 struct PreloadParity {
469 unsigned firstParity = 0;
470 unsigned lastParity = 0;
472 AIE::AIEDevice device = {};
474 struct SequenceLoads {
475 std::array<PreloadParity, 2> preloads;
476 AIEX::ExpandMode last = AIEX::ExpandMode::none;
478 llvm::MapVector<AIE::RuntimeSequenceOp, SequenceLoads> sequences;
479 for (
auto [index, loadPdiOp] : llvm::enumerate(loadPdiOps)) {
480 auto seq = loadPdiOp->getParentOfType<AIE::RuntimeSequenceOp>();
483 SequenceLoads &loads = sequences[seq];
484 AIEX::ExpandMode mode = loads.last = expandMode(loadPdiOp);
485 if (mode == AIEX::ExpandMode::none)
488 module.lookupSymbol<AIE::DeviceOp>(loadPdiOp.getDeviceRefAttr());
491 auto &entry = loads.preloads[mode == AIEX::ExpandMode::ctrlpkt];
493 entry.firstParity = index % 2;
496 entry.lastParity = index % 2;
497 entry.device = dev.getDevice();
506 llvm::SetVector<AIE::DeviceOp> initEntries;
508 for (AIE::RuntimeSequenceOp seq : llvm::make_first_range(sequences))
509 initEntries.insert(seq->getParentOfType<AIE::DeviceOp>());
510 for (
auto &[seq, loads] : sequences)
511 if (loads.last != AIEX::ExpandMode::write32)
512 initEntries.remove(seq->getParentOfType<AIE::DeviceOp>());
515 if (failed(verifyExpansion(module, loadPdiOps, expandMode, clTeardown,
517 return signalPassFailure();
519 SmallVector<CtrlPktReload> ctrlPktReloads;
521 llvm::DenseMap<Operation *, std::optional<AIEX::ExpandMode>> arrayState;
522 for (
auto [idx, loadPdiOp] : llvm::enumerate(loadPdiOps)) {
523 auto seq = loadPdiOp->getParentOfType<AIE::RuntimeSequenceOp>();
524 std::optional<AIEX::ExpandMode> start;
525 if (seq && initEntries.contains(seq->getParentOfType<AIE::DeviceOp>()))
526 start = AIEX::ExpandMode::write32;
527 std::optional<AIEX::ExpandMode> &state =
528 arrayState.try_emplace(seq, start).first->second;
532 bool keepPreload = !clTeardown || state != expandMode(loadPdiOp);
533 state = expandMode(loadPdiOp);
534 if (failed(transformLoadPdi(loadPdiOp, module, idx, defaultMode,
535 keepPreload, clTeardown, ctrlPktReloads))) {
541 SmallVector<std::pair<Operation *, AIE::DeviceOp>> ctrlPktSites;
542 for (CtrlPktReload &reload : ctrlPktReloads)
543 ctrlPktSites.emplace_back(reload.load, reload.device);
549 for (CtrlPktReload &reload : ctrlPktReloads) {
550 if (failed(verifyOverlayReachesTiles(
551 reload.load, reload.overlay,
552 {std::next(reload.preload->getIterator()),
553 reload.load->getIterator()}))) {
560 for (AIE::DeviceOp device : initEntries) {
561 OpBuilder builder(device.getContext());
562 builder.setInsertionPoint(
563 *device.getOps<AIE::RuntimeSequenceOp>().begin());
564 auto init = AIE::RuntimeSequenceOp::create(
565 builder, device.getLoc(),
567 AIE::TraceBufferAttr{}, ArrayAttr{}, ArrayAttr{});
568 builder.setInsertionPointToStart(&init.getBody().emplaceBlock());
569 AIE::DeviceOp emptyDevice = getOrCreateEmptyDevice(
570 module, device.getDevice(), 0, device.getLoc());
571 NpuLoadPdiOp::create(builder, device.getLoc(),
572 FlatSymbolRefAttr::get(emptyDevice.getSymNameAttr()),
576 AIEX::ExpandModeAttr::get(builder.getContext(),
577 AIEX::ExpandMode::none));
581 return signalPassFailure();
601 for (
auto &[seq, loads] : sequences) {
602 for (
auto [ctrlPkt, info] : llvm::enumerate(loads.preloads)) {
603 if (!info.seen || info.firstParity != info.lastParity)
605 unsigned parity = 1 - info.lastParity;
606 FlatSymbolRefAttr preloadRef;
608 if (parity == 1 && !getOrCreateCtrlPktOverlayCopy(module, seq)) {
612 preloadRef = FlatSymbolRefAttr::get(
614 parity == 0 ? kCtrlPktOverlayName : kCtrlPktOverlayCopyName);
616 AIE::DeviceOp emptyDevice =
617 getOrCreateEmptyDevice(module, info.device, parity, seq.getLoc());
618 preloadRef = FlatSymbolRefAttr::get(emptyDevice.getSymNameAttr());
620 OpBuilder builder(seq.getContext());
621 Block &body = seq.getBody().front();
622 if (!body.empty() && body.back().hasTrait<OpTrait::IsTerminator>())
623 builder.setInsertionPoint(&body.back());
625 builder.setInsertionPointToEnd(&body);
626 NpuLoadPdiOp::create(builder, seq.getLoc(), preloadRef,
630 AIEX::ExpandModeAttr::get(seq.getContext(),
631 AIEX::ExpandMode::none));
639std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
641 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)
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
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