diff --git a/include/aie/Dialect/AIEX/IR/AIEX.td b/include/aie/Dialect/AIEX/IR/AIEX.td index f8c4ba81190..8c4819bf20c 100644 --- a/include/aie/Dialect/AIEX/IR/AIEX.td +++ b/include/aie/Dialect/AIEX/IR/AIEX.td @@ -1441,6 +1441,51 @@ def AIEX_DmaChannelResetOp: AIEX_Op<"dma_channel_reset", [HasParent<"AIE::Runtim let hasVerifier = 1; } +def AIEX_CoreResetOp: AIEX_Op<"core_reset", [HasParent<"AIE::RuntimeSequenceOp">, SkipAccessibilityCheckTrait]> { + let summary = "Reset a compute core from a runtime sequence"; + let description = [{ + Pulses the reset bit of the CORE_CONTROL register on `tile`. The pulse is two + `npu.maskwrite32` writes masked to the reset bit (assert, then deassert), so + it preserves the ENABLE field packed in the same CORE_CONTROL word instead of + clobbering it. This mirrors aie-rt's XAie_CoreReset followed by + XAie_CoreUnreset. The operation is non blocking and offers no synchronization + guarantees. + + Resetting clears the core's processor state (program counter and registers); + it does not touch the tile's data memory, locks, or DMA. The intended use is + re-arming a resident kernel across dispatches without a full PDI reload: + discard stale core state while keeping resident data in place. This assumes + the resident core is still enabled; re-arming a core that has cleared its + ENABLE bit also needs a re-enable, which this op does not emit. + + Only core tiles have a CORE_CONTROL register; mem and shim tiles are rejected + by the verifier because they have no core to reset. This matches aie-rt's + XAie_CoreReset, which errors on non core tiles. + + `tile` is an SSA `aie.tile` value, so the target is named the same way as + `aiex.dma_channel_reset` and `aiex.dma_configure_task`. + + Example: + ``` + %tile = aie.tile(2, 3) + aiex.core_reset(%tile) + ``` + }]; + let arguments = ( + ins Index:$tile + ); + let assemblyFormat = [{ `(` $tile `)` attr-dict }]; + let extraClassDeclaration = [{ + AIE::TileOp getTileOp(); + }]; + let extraClassDefinition = [{ + AIE::TileOp CoreResetOp::getTileOp() { + return cast(getTile().getDefiningOp()); + } + }]; + let hasVerifier = 1; +} + //===----------------------------------------------------------------------===// // ScratchpadParameter ops //===----------------------------------------------------------------------===// diff --git a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h index 8cb33149f88..b60e1e931a5 100644 --- a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h +++ b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h @@ -53,8 +53,9 @@ std::unique_ptr> createAIELowerSetLockPass(); std::unique_ptr> createAIELowerDmaChannelResetPass(); std::unique_ptr> +createAIELowerCoreResetPass(); +std::unique_ptr> createAIETransformBfpTypesPass(); -std::unique_ptr> createAIELowerSetLockPass(); std::unique_ptr> createAIETxnToControlPacketPass(); std::unique_ptr> diff --git a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.td b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.td index 8768cc8d026..d12565374ee 100644 --- a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.td +++ b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.td @@ -365,6 +365,16 @@ def AIELowerDmaChannelReset : Pass<"aie-lower-dma-channel-reset", "AIE::DeviceOp ]; } +def AIELowerCoreReset : Pass<"aie-lower-core-reset", "AIE::DeviceOp"> { + let summary = "Lowers all aiex.core_reset operations to npu.maskwrite32"; + let constructor = "xilinx::AIEX::createAIELowerCoreResetPass()"; + let dependentDialects = [ + "mlir::arith::ArithDialect", + "xilinx::AIE::AIEDialect", + "xilinx::AIEX::AIEXDialect", + ]; +} + def AIELowerScratchpadParameters : Pass<"aie-lower-scratchpad-parameters", "mlir::ModuleOp"> { let summary = "Lower scratchpad parameter ops and emit parameter-sync preambles"; let description = [{ diff --git a/lib/Dialect/AIEX/IR/AIEXDialect.cpp b/lib/Dialect/AIEX/IR/AIEXDialect.cpp index 634e1469554..ca196467726 100644 --- a/lib/Dialect/AIEX/IR/AIEXDialect.cpp +++ b/lib/Dialect/AIEX/IR/AIEXDialect.cpp @@ -1319,6 +1319,38 @@ LogicalResult AIEX::DmaChannelResetOp::verify() { return success(); } +//===----------------------------------------------------------------------===// +// CoreResetOp +//===----------------------------------------------------------------------===// + +LogicalResult AIEX::CoreResetOp::verify() { + const auto &targetModel = AIE::getTargetModel(*this); + + // The op lowers to an NPU control-packet write; the runtime sequence has no + // meaning on AIE1. Reject it explicitly, as SetLockOp does. + if (targetModel.getTargetArch() == AIE::AIEArch::AIE1) + return emitOpError("aiex.core_reset is not supported on AIE1."); + + auto tile = dyn_cast_or_null(getTile().getDefiningOp()); + if (!tile) + return emitOpError() << "tile operand must be produced by an aie.tile op"; + int col = tile.getCol(); + int row = tile.getRow(); + // The tile coordinates are bounded by aie.tile's own verifier, so this op + // does not re-check them for range. + + // Only core tiles have a CORE_CONTROL register with a reset bit. Mem and shim + // tiles have no compute core, so there is nothing valid to lower to. This + // matches aie-rt's XAie_CoreReset, which errors on any tile that is not an + // AIE (core) tile. + if (!targetModel.isCoreTile(col, row)) + return emitOpError() << "tile (" << col << ", " << row + << ") has no core to reset (only core tiles have a " + "CORE_CONTROL register)"; + + return success(); +} + //===----------------------------------------------------------------------===// // BlockFloatingPointType //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp b/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp new file mode 100644 index 00000000000..18006b71786 --- /dev/null +++ b/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp @@ -0,0 +1,104 @@ +//===- AIELowerCoreReset.cpp ------------------------------------*- C++ -*-===// +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "aie/Dialect/AIE/IR/AIEDialect.h" +#include "aie/Dialect/AIEX/IR/AIEXDialect.h" +#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h" + +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +namespace xilinx::AIEX { +#define GEN_PASS_DEF_AIELOWERCORERESET +#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc" +} // namespace xilinx::AIEX + +#define DEBUG_TYPE "aie-lower-core-reset" + +using namespace mlir; +using namespace xilinx; +using namespace xilinx::AIE; +using namespace xilinx::AIEX; + +// CORE_CONTROL register, tile-local offset. Uniform across the AIE2 family: +// XAIE2PGBL_CORE_MODULE_CORE_CONTROL (aie2p) and +// XAIEMLGBL_CORE_MODULE_CORE_CONTROL (aie2) are both 0x00032000. The op only +// lowers on core tiles (the verifier rejects mem/shim), which exist on aie2 and +// aie2p; the npu runtime sequence path is AIE2-only. +static constexpr uint32_t kCoreCtrlAddr = 0x00032000; + +// CORE_CONTROL reset bit (bit 1). Matches +// XAIE2PGBL_CORE_MODULE_CORE_CONTROL_RESET_MASK. Bit 0 of the same word is +// ENABLE, which the mask preserves. +static constexpr uint32_t kCoreCtrlResetMask = 0x2; + +struct CoreResetToMaskWrite32Pattern : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + CoreResetToMaskWrite32Pattern(MLIRContext *context) + : OpConversionPattern(context) {} + + LogicalResult + matchAndRewrite(CoreResetOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + AIE::TileOp tile = op.getTileOp(); + int col = tile.getCol(); + int row = tile.getRow(); + + Location loc = op.getLoc(); + IntegerAttr colAttr = rewriter.getI32IntegerAttr(col); + IntegerAttr rowAttr = rewriter.getI32IntegerAttr(row); + + // NpuMaskWrite32Op re-folds col/row from the attributes, so the address + // operand is the tile-local CORE_CONTROL offset, mirroring how + // AIELowerSetLock passes the local lock address with col/row. + // + // Reset pulse: assert the reset bit, then clear it. Both writes mask to the + // reset bit only, so the pulse preserves the ENABLE field packed in the + // same CORE_CONTROL word instead of clobbering it. This mirrors aie-rt's + // XAie_CoreReset/XAie_CoreUnreset, which drive the reset bit with a + // MaskWrite32. Constants are materialized in named locals so the emitted IR + // order does not depend on unspecified C++ argument-evaluation order. + Value assertAddr = createConstantI32(rewriter, loc, kCoreCtrlAddr); + Value assertVal = createConstantI32(rewriter, loc, kCoreCtrlResetMask); + Value assertMask = createConstantI32(rewriter, loc, kCoreCtrlResetMask); + rewriter.create(loc, assertAddr, assertVal, assertMask, + nullptr, colAttr, rowAttr); + + Value clearAddr = createConstantI32(rewriter, loc, kCoreCtrlAddr); + Value clearVal = createConstantI32(rewriter, loc, 0u); + Value clearMask = createConstantI32(rewriter, loc, kCoreCtrlResetMask); + rewriter.replaceOpWithNewOp( + op, clearAddr, clearVal, clearMask, nullptr, colAttr, rowAttr); + + return success(); + }; +}; + +struct AIELowerCoreResetPass + : public xilinx::AIEX::impl::AIELowerCoreResetBase { + void runOnOperation() override { + DeviceOp device = getOperation(); + + ConversionTarget target(getContext()); + target.addLegalOp(); + target.addLegalDialect(); + target.addIllegalOp(); + + RewritePatternSet patterns(&getContext()); + patterns.add(&getContext()); + + if (failed(applyPartialConversion(device, target, std::move(patterns)))) + signalPassFailure(); + } +}; + +std::unique_ptr> +xilinx::AIEX::createAIELowerCoreResetPass() { + return std::make_unique(); +} diff --git a/lib/Dialect/AIEX/Transforms/CMakeLists.txt b/lib/Dialect/AIEX/Transforms/CMakeLists.txt index 12f56863b12..7dea9707b9f 100644 --- a/lib/Dialect/AIEX/Transforms/CMakeLists.txt +++ b/lib/Dialect/AIEX/Transforms/CMakeLists.txt @@ -24,6 +24,7 @@ add_mlir_dialect_library(AIEXTransforms AIECtrlPacketToDma.cpp AIELowerSetLock.cpp AIELowerDmaChannelReset.cpp + AIELowerCoreReset.cpp AIELowerScratchpadParameters.cpp AIETransformBfpTypes.cpp AIETxnToControlPacket.cpp diff --git a/test/Passes/lower-core-reset/core_reset.mlir b/test/Passes/lower-core-reset/core_reset.mlir new file mode 100644 index 00000000000..810506899ad --- /dev/null +++ b/test/Passes/lower-core-reset/core_reset.mlir @@ -0,0 +1,70 @@ +//===- core_reset.mlir -----------------------------------------*- MLIR -*-===// +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt -split-input-file --aie-lower-core-reset %s | FileCheck %s --implicit-check-not=aiex.npu.write32 + +// Each aiex.core_reset lowers to a masked reset pulse on the tile's CORE_CONTROL +// register: set the reset bit (value 0x2, mask 0x2), then clear it (value 0x0, +// mask 0x2). Masking to bit 1 preserves the ENABLE field (bit 0) packed in the +// same word instead of clobbering it -- the --implicit-check-not on +// aiex.npu.write32 guards against any stray unmasked write sneaking in. CORE_CONTROL +// is at tile-local offset 0x32000 = 204800 on every core tile, so the address is +// the same for every accepted tile and only the column/row attributes change. +// The target tile is named by an SSA aie.tile value, like aiex.dma_channel_reset. +// Only core tiles have a core to reset, so those are the only tiles the op accepts +// (see core_reset_invalid.mlir). +module { + aie.device(npu2) { + %tile_0_3 = aie.tile(0, 3) + %tile_1_2 = aie.tile(1, 2) + aie.runtime_sequence() { + // Core tile (0, 3): CORE_CONTROL local 0x32000 = 204800. + // CHECK: %[[ADDRA:.*]] = arith.constant 204800 : i32 + // CHECK: %[[SETA:.*]] = arith.constant 2 : i32 + // CHECK: %[[MASKA:.*]] = arith.constant 2 : i32 + // CHECK: aiex.npu.maskwrite32(%[[ADDRA]], %[[SETA]], %[[MASKA]]) {column = 0 : i32, row = 3 : i32} : i32, i32, i32 + // CHECK: %[[ADDRA2:.*]] = arith.constant 204800 : i32 + // CHECK: %[[CLRA:.*]] = arith.constant 0 : i32 + // CHECK: %[[MASKA2:.*]] = arith.constant 2 : i32 + // CHECK: aiex.npu.maskwrite32(%[[ADDRA2]], %[[CLRA]], %[[MASKA2]]) {column = 0 : i32, row = 3 : i32} : i32, i32, i32 + aiex.core_reset(%tile_0_3) + + // A different core tile (1, 2): same CORE_CONTROL address, same reset value + // and mask, only the column/row attributes change. Value and mask are pinned + // exactly (not wildcards) so a per-tile value/mask bug cannot slip through. + // CHECK: %[[ADDRB:.*]] = arith.constant 204800 : i32 + // CHECK: %[[SETB:.*]] = arith.constant 2 : i32 + // CHECK: %[[MASKB:.*]] = arith.constant 2 : i32 + // CHECK: aiex.npu.maskwrite32(%[[ADDRB]], %[[SETB]], %[[MASKB]]) {column = 1 : i32, row = 2 : i32} : i32, i32, i32 + // CHECK: %[[ADDRB2:.*]] = arith.constant 204800 : i32 + // CHECK: %[[CLRB:.*]] = arith.constant 0 : i32 + // CHECK: %[[MASKB2:.*]] = arith.constant 2 : i32 + // CHECK: aiex.npu.maskwrite32(%[[ADDRB2]], %[[CLRB]], %[[MASKB2]]) {column = 1 : i32, row = 2 : i32} : i32, i32, i32 + // CHECK-NOT: aiex.core_reset + aiex.core_reset(%tile_1_2) + } + } +} + +// ----- + +// CORE_CONTROL is at the same 0x32000 offset on npu1 (Phoenix), so the lowering +// is device-independent across the AIE2 family. This pins the npu1 address so a +// future change that made the address device-conditional and got npu1 wrong would +// fail here. +module { + aie.device(npu1) { + %tile_0_3 = aie.tile(0, 3) + aie.runtime_sequence() { + // CHECK: %[[ADDRC:.*]] = arith.constant 204800 : i32 + // CHECK: %[[SETC:.*]] = arith.constant 2 : i32 + // CHECK: %[[MASKC:.*]] = arith.constant 2 : i32 + // CHECK: aiex.npu.maskwrite32(%[[ADDRC]], %[[SETC]], %[[MASKC]]) {column = 0 : i32, row = 3 : i32} : i32, i32, i32 + aiex.core_reset(%tile_0_3) + } + } +} diff --git a/test/Passes/lower-core-reset/core_reset_invalid.mlir b/test/Passes/lower-core-reset/core_reset_invalid.mlir new file mode 100644 index 00000000000..7c46f95e0b4 --- /dev/null +++ b/test/Passes/lower-core-reset/core_reset_invalid.mlir @@ -0,0 +1,51 @@ +//===- core_reset_invalid.mlir ---------------------------------*- MLIR -*-===// +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt -split-input-file -verify-diagnostics --aie-lower-core-reset %s + +// A mem tile is rejected: only core tiles have a CORE_CONTROL register with a +// reset bit. On npu2 row 1 is a mem tile, which has no compute core to reset. +// This matches aie-rt's XAie_CoreReset, which errors on non core tiles. +// (An out-of-range coordinate needs no test here: the tile is an SSA aie.tile +// value, whose own verifier bounds the column/row against the device.) +module { + aie.device(npu2) { + %mem_tile = aie.tile(0, 1) + aie.runtime_sequence() { + // expected-error @+1 {{tile (0, 1) has no core to reset (only core tiles have a CORE_CONTROL register)}} + aiex.core_reset(%mem_tile) + } + } +} + +// ----- + +// A shim NOC tile is rejected for the same reason: on npu2 row 0 is a shim tile, +// which has no compute core to reset. +module { + aie.device(npu2) { + %shim_tile = aie.tile(0, 0) + aie.runtime_sequence() { + // expected-error @+1 {{tile (0, 0) has no core to reset (only core tiles have a CORE_CONTROL register)}} + aiex.core_reset(%shim_tile) + } + } +} + +// ----- + +// AIE1 is rejected: the op lowers to an NPU control-packet write and the runtime +// sequence has no meaning on AIE1, matching SetLockOp's AIE1 rejection. +module { + aie.device(xcvc1902) { + %tile = aie.tile(0, 3) + aie.runtime_sequence() { + // expected-error @+1 {{not supported on AIE1}} + aiex.core_reset(%tile) + } + } +} diff --git a/tools/aiecc/IRTransforms.h b/tools/aiecc/IRTransforms.h index 15e7134a872..1169ab2ab28 100644 --- a/tools/aiecc/IRTransforms.h +++ b/tools/aiecc/IRTransforms.h @@ -741,6 +741,7 @@ getNpuDmaLoweringPipeline(mlir::MLIRContext *ctx) { dpm.addPass(X::createAIEDmaToNpuPass()); dpm.addPass(X::createAIELowerSetLockPass()); dpm.addPass(X::createAIELowerDmaChannelResetPass()); + dpm.addPass(X::createAIELowerCoreResetPass()); return pm; }