Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions include/aie/Dialect/AIEX/IR/AIEX.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<AIE::TileOp>(getTile().getDefiningOp());
}
}];
let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// ScratchpadParameter ops
//===----------------------------------------------------------------------===//
Expand Down
3 changes: 2 additions & 1 deletion include/aie/Dialect/AIEX/Transforms/AIEXPasses.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ std::unique_ptr<mlir::OperationPass<AIE::DeviceOp>> createAIELowerSetLockPass();
std::unique_ptr<mlir::OperationPass<AIE::DeviceOp>>
createAIELowerDmaChannelResetPass();
std::unique_ptr<mlir::OperationPass<AIE::DeviceOp>>
createAIELowerCoreResetPass();
std::unique_ptr<mlir::OperationPass<AIE::DeviceOp>>
createAIETransformBfpTypesPass();
std::unique_ptr<mlir::OperationPass<AIE::DeviceOp>> createAIELowerSetLockPass();
std::unique_ptr<mlir::OperationPass<AIE::DeviceOp>>
createAIETxnToControlPacketPass();
std::unique_ptr<mlir::OperationPass<AIE::DeviceOp>>
Expand Down
10 changes: 10 additions & 0 deletions include/aie/Dialect/AIEX/Transforms/AIEXPasses.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [{
Expand Down
32 changes: 32 additions & 0 deletions lib/Dialect/AIEX/IR/AIEXDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<AIE::TileOp>(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
//===----------------------------------------------------------------------===//
Expand Down
104 changes: 104 additions & 0 deletions lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp
Original file line number Diff line number Diff line change
@@ -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<CoreResetOp> {
using OpConversionPattern<CoreResetOp>::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<NpuMaskWrite32Op>(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<NpuMaskWrite32Op>(
op, clearAddr, clearVal, clearMask, nullptr, colAttr, rowAttr);

return success();
};
};

struct AIELowerCoreResetPass
: public xilinx::AIEX::impl::AIELowerCoreResetBase<AIELowerCoreResetPass> {
void runOnOperation() override {
DeviceOp device = getOperation();

ConversionTarget target(getContext());
target.addLegalOp<NpuMaskWrite32Op>();
target.addLegalDialect<arith::ArithDialect>();
target.addIllegalOp<CoreResetOp>();

RewritePatternSet patterns(&getContext());
patterns.add<CoreResetToMaskWrite32Pattern>(&getContext());

if (failed(applyPartialConversion(device, target, std::move(patterns))))
signalPassFailure();
}
};

std::unique_ptr<OperationPass<DeviceOp>>
xilinx::AIEX::createAIELowerCoreResetPass() {
return std::make_unique<AIELowerCoreResetPass>();
}
1 change: 1 addition & 0 deletions lib/Dialect/AIEX/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ add_mlir_dialect_library(AIEXTransforms
AIECtrlPacketToDma.cpp
AIELowerSetLock.cpp
AIELowerDmaChannelReset.cpp
AIELowerCoreReset.cpp
AIELowerScratchpadParameters.cpp
AIETransformBfpTypes.cpp
AIETxnToControlPacket.cpp
Expand Down
70 changes: 70 additions & 0 deletions test/Passes/lower-core-reset/core_reset.mlir
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
51 changes: 51 additions & 0 deletions test/Passes/lower-core-reset/core_reset_invalid.mlir
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
1 change: 1 addition & 0 deletions tools/aiecc/IRTransforms.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading