From d01e97ebdbdffd1bb824a1ee78755604baa31097 Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Tue, 21 Jul 2026 15:11:10 +0300 Subject: [PATCH 1/4] [AIEX] Add aiex.core_reset runtime-sequence op Add a runtime-sequence op that resets a compute core, lowering to a masked pulse of the CORE_CONTROL reset bit (npu.maskwrite32). It mirrors aiex.set_lock: a non-blocking control op with no synchronization guarantees, meant to sit inside an aie.runtime_sequence. The runtime sequence can already set a lock (aiex.set_lock) but has no way to reset a compute core. 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. The lowering emits the reset as two npu.maskwrite32 writes masked to the reset bit (bit 1): assert, then deassert. CORE_CONTROL packs ENABLE (bit 0) and RESET (bit 1) in one word, so a plain write32 pulse would clobber ENABLE; masking to bit 1 preserves it. This mirrors aie-rt's XAie_CoreReset and XAie_CoreUnreset, which drive the reset bit with a MaskWrite32. The reset clears the core's processor state (PC and registers), not the tile's data memory, locks, or DMA; re-arming a core that has cleared ENABLE also needs a re-enable, which this op does not emit. The verifier accepts only core tiles: it rejects mem and shim tiles (no core to reset, matching XAie_CoreReset), coordinates outside the device, and AIE1 (the NPU runtime sequence has no meaning there, as SetLockOp also rejects). --- include/aie/Dialect/AIEX/IR/AIEX.td | 34 ++++++ .../aie/Dialect/AIEX/Transforms/AIEXPasses.h | 2 + .../aie/Dialect/AIEX/Transforms/AIEXPasses.td | 10 ++ lib/Dialect/AIEX/IR/AIEXDialect.cpp | 33 ++++++ .../AIEX/Transforms/AIELowerCoreReset.cpp | 104 ++++++++++++++++++ lib/Dialect/AIEX/Transforms/CMakeLists.txt | 1 + test/Passes/lower-core-reset/core_reset.mlir | 66 +++++++++++ .../lower-core-reset/core_reset_invalid.mlir | 61 ++++++++++ tools/aiecc/IRTransforms.h | 1 + 9 files changed, 312 insertions(+) create mode 100644 lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp create mode 100644 test/Passes/lower-core-reset/core_reset.mlir create mode 100644 test/Passes/lower-core-reset/core_reset_invalid.mlir diff --git a/include/aie/Dialect/AIEX/IR/AIEX.td b/include/aie/Dialect/AIEX/IR/AIEX.td index f8c4ba81190..b89b0a548fb 100644 --- a/include/aie/Dialect/AIEX/IR/AIEX.td +++ b/include/aie/Dialect/AIEX/IR/AIEX.td @@ -1441,6 +1441,40 @@ 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 the tile at (`column`, + `row`). 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. + + Example: + ``` + aiex.core_reset(2, 3) + ``` + }]; + let arguments = ( + ins I32Attr:$column, + I32Attr:$row + ); + let assemblyFormat = [{ `(` $column `,` $row `)` attr-dict }]; + 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..26eda1da5a6 100644 --- a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h +++ b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h @@ -53,6 +53,8 @@ std::unique_ptr> createAIELowerSetLockPass(); std::unique_ptr> createAIELowerDmaChannelResetPass(); std::unique_ptr> +createAIELowerCoreResetPass(); +std::unique_ptr> createAIETransformBfpTypesPass(); std::unique_ptr> createAIELowerSetLockPass(); 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..938af534b14 100644 --- a/lib/Dialect/AIEX/IR/AIEXDialect.cpp +++ b/lib/Dialect/AIEX/IR/AIEXDialect.cpp @@ -1319,6 +1319,39 @@ 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."); + + int col = getColumn(); + int row = getRow(); + // The column/row are raw attributes, not derived from an aie.tile op, so + // nothing else bounds them. Reject coordinates outside the device before they + // reach the lowering and emit a write to a nonexistent tile. + if (!targetModel.isValidTile(AIE::TileID{col, row})) + return emitOpError() << "tile (" << col << ", " << row + << ") is out of range for this device"; + + // 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..3e25cd6e995 --- /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 { + int col = op.getColumn(); + int row = op.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..38ebaa51451 --- /dev/null +++ b/test/Passes/lower-core-reset/core_reset.mlir @@ -0,0 +1,66 @@ +//===- 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. +// 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) { + 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(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(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) { + 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(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..f7ed47fa5e6 --- /dev/null +++ b/test/Passes/lower-core-reset/core_reset_invalid.mlir @@ -0,0 +1,61 @@ +//===- 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. +module { + aie.device(npu2) { + 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(0, 1) + } + } +} + +// ----- + +// 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) { + 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(0, 0) + } + } +} + +// ----- + +// A column past the edge of the array is rejected before it can reach the +// lowering and emit a write to a nonexistent tile. npu2 has 8 columns (0-7), so +// column 99 is out of range. The column/row are raw attributes, not derived from +// an aie.tile op, so the verifier is the only thing that bounds them. +module { + aie.device(npu2) { + aie.runtime_sequence() { + // expected-error @+1 {{tile (99, 3) is out of range for this device}} + aiex.core_reset(99, 3) + } + } +} + +// ----- + +// 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) { + aie.runtime_sequence() { + // expected-error @+1 {{not supported on AIE1}} + aiex.core_reset(0, 3) + } + } +} 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; } From f231b2477ec3b1fd79650505cbb27bb6a01bedcf Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Tue, 21 Jul 2026 17:55:46 +0300 Subject: [PATCH 2/4] [AIEX] Drop duplicate createAIELowerSetLockPass declaration AIEXPasses.h declared createAIELowerSetLockPass() twice. The second is redundant; removing it, per review. --- include/aie/Dialect/AIEX/Transforms/AIEXPasses.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h index 26eda1da5a6..b60e1e931a5 100644 --- a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h +++ b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h @@ -56,7 +56,6 @@ std::unique_ptr> createAIELowerCoreResetPass(); std::unique_ptr> createAIETransformBfpTypesPass(); -std::unique_ptr> createAIELowerSetLockPass(); std::unique_ptr> createAIETxnToControlPacketPass(); std::unique_ptr> From c3febbfa550b21fdaf78eae8f76dfee6fdba3e35 Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Tue, 21 Jul 2026 19:08:05 +0300 Subject: [PATCH 3/4] [AIEX] clang-format AIELowerCoreReset.cpp Fix the code-format CI check: trim the file-header banner to 80 columns and let clang-format reflow a comment and a call. --- lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp b/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp index 3e25cd6e995..45e1c139cfc 100644 --- a/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp +++ b/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp @@ -1,4 +1,4 @@ -//===- AIELowerCoreReset.cpp -------------------------------------*- C++ -*-===// +//===- AIELowerCoreReset.cpp ------------------------------------*- C++ -*-===// // // Copyright (C) 2026 Advanced Micro Devices, Inc. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception @@ -58,8 +58,8 @@ struct CoreResetToMaskWrite32Pattern : OpConversionPattern { // 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 + // 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. @@ -72,9 +72,8 @@ struct CoreResetToMaskWrite32Pattern : OpConversionPattern { 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); + rewriter.replaceOpWithNewOp( + op, clearAddr, clearVal, clearMask, nullptr, colAttr, rowAttr); return success(); }; From 3dbc0e4afd2f7a3c391b1c34af4141d6a3d7c707 Mon Sep 17 00:00:00 2001 From: Taimuraz Kaitmazov Date: Thu, 23 Jul 2026 01:12:56 +0300 Subject: [PATCH 4/4] [AIEX] core_reset: take an SSA tile operand Address review feedback: name the target tile with an SSA aie.tile value instead of raw column/row attributes, matching aiex.dma_channel_reset and aiex.dma_configure_task. The op now takes Index:$tile with a getTileOp() accessor, and both the verifier and the lowering read the column and row from the tile op. Because aie.tile carries its own verifier that bounds the coordinates against the device, core_reset no longer needs a separate out of range check, so that branch and its invalid test are dropped. The lit tests declare the tile at device scope and pass it by value. --- include/aie/Dialect/AIEX/IR/AIEX.td | 31 +++++++++++++------ lib/Dialect/AIEX/IR/AIEXDialect.cpp | 15 +++++---- .../AIEX/Transforms/AIELowerCoreReset.cpp | 5 +-- test/Passes/lower-core-reset/core_reset.mlir | 10 ++++-- .../lower-core-reset/core_reset_invalid.mlir | 26 +++++----------- 5 files changed, 46 insertions(+), 41 deletions(-) diff --git a/include/aie/Dialect/AIEX/IR/AIEX.td b/include/aie/Dialect/AIEX/IR/AIEX.td index b89b0a548fb..8c4819bf20c 100644 --- a/include/aie/Dialect/AIEX/IR/AIEX.td +++ b/include/aie/Dialect/AIEX/IR/AIEX.td @@ -1444,12 +1444,12 @@ def AIEX_DmaChannelResetOp: AIEX_Op<"dma_channel_reset", [HasParent<"AIE::Runtim 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 the tile at (`column`, - `row`). 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. + 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 @@ -1462,16 +1462,27 @@ def AIEX_CoreResetOp: AIEX_Op<"core_reset", [HasParent<"AIE::RuntimeSequenceOp"> 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: ``` - aiex.core_reset(2, 3) + %tile = aie.tile(2, 3) + aiex.core_reset(%tile) ``` }]; let arguments = ( - ins I32Attr:$column, - I32Attr:$row + ins Index:$tile ); - let assemblyFormat = [{ `(` $column `,` $row `)` attr-dict }]; + let assemblyFormat = [{ `(` $tile `)` attr-dict }]; + let extraClassDeclaration = [{ + AIE::TileOp getTileOp(); + }]; + let extraClassDefinition = [{ + AIE::TileOp CoreResetOp::getTileOp() { + return cast(getTile().getDefiningOp()); + } + }]; let hasVerifier = 1; } diff --git a/lib/Dialect/AIEX/IR/AIEXDialect.cpp b/lib/Dialect/AIEX/IR/AIEXDialect.cpp index 938af534b14..ca196467726 100644 --- a/lib/Dialect/AIEX/IR/AIEXDialect.cpp +++ b/lib/Dialect/AIEX/IR/AIEXDialect.cpp @@ -1331,14 +1331,13 @@ LogicalResult AIEX::CoreResetOp::verify() { if (targetModel.getTargetArch() == AIE::AIEArch::AIE1) return emitOpError("aiex.core_reset is not supported on AIE1."); - int col = getColumn(); - int row = getRow(); - // The column/row are raw attributes, not derived from an aie.tile op, so - // nothing else bounds them. Reject coordinates outside the device before they - // reach the lowering and emit a write to a nonexistent tile. - if (!targetModel.isValidTile(AIE::TileID{col, row})) - return emitOpError() << "tile (" << col << ", " << row - << ") is out of range for this device"; + 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 diff --git a/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp b/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp index 45e1c139cfc..18006b71786 100644 --- a/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp +++ b/lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp @@ -46,8 +46,9 @@ struct CoreResetToMaskWrite32Pattern : OpConversionPattern { LogicalResult matchAndRewrite(CoreResetOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - int col = op.getColumn(); - int row = op.getRow(); + AIE::TileOp tile = op.getTileOp(); + int col = tile.getCol(); + int row = tile.getRow(); Location loc = op.getLoc(); IntegerAttr colAttr = rewriter.getI32IntegerAttr(col); diff --git a/test/Passes/lower-core-reset/core_reset.mlir b/test/Passes/lower-core-reset/core_reset.mlir index 38ebaa51451..810506899ad 100644 --- a/test/Passes/lower-core-reset/core_reset.mlir +++ b/test/Passes/lower-core-reset/core_reset.mlir @@ -14,10 +14,13 @@ // 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 @@ -28,7 +31,7 @@ module { // 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(0, 3) + 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 @@ -42,7 +45,7 @@ module { // 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(1, 2) + aiex.core_reset(%tile_1_2) } } } @@ -55,12 +58,13 @@ module { // 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(0, 3) + 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 index f7ed47fa5e6..7c46f95e0b4 100644 --- a/test/Passes/lower-core-reset/core_reset_invalid.mlir +++ b/test/Passes/lower-core-reset/core_reset_invalid.mlir @@ -10,11 +10,14 @@ // 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(0, 1) + aiex.core_reset(%mem_tile) } } } @@ -25,24 +28,10 @@ module { // 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(0, 0) - } - } -} - -// ----- - -// A column past the edge of the array is rejected before it can reach the -// lowering and emit a write to a nonexistent tile. npu2 has 8 columns (0-7), so -// column 99 is out of range. The column/row are raw attributes, not derived from -// an aie.tile op, so the verifier is the only thing that bounds them. -module { - aie.device(npu2) { - aie.runtime_sequence() { - // expected-error @+1 {{tile (99, 3) is out of range for this device}} - aiex.core_reset(99, 3) + aiex.core_reset(%shim_tile) } } } @@ -53,9 +42,10 @@ module { // 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(0, 3) + aiex.core_reset(%tile) } } }