Skip to content

[AIEX] Add aiex.core_reset runtime-sequence op#3375

Merged
hunhoffe merged 4 commits into
Xilinx:mainfrom
atassis:aiex-core-reset
Jul 23, 2026
Merged

[AIEX] Add aiex.core_reset runtime-sequence op#3375
hunhoffe merged 4 commits into
Xilinx:mainfrom
atassis:aiex-core-reset

Conversation

@atassis

@atassis atassis commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

What this adds

A runtime-sequence op that resets a compute core, lowering to a masked pulse of the CORE_CONTROL register
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.

Why it is needed

The runtime sequence is the per-dispatch control program. Today it can configure and start DMA transfers,
and it can set a lock (aiex.set_lock), but it has no way to reset the compute core. That gap is the reason
for this op.

The motivating case is re-arming a resident kernel across dispatches on aie2p. A core that is started once
and left resident, so its program can run again on the next dispatch without a fresh PDI load, carries its
processor state from the previous dispatch. The heavy way to return it to a known state is a full load_pdi
reload of the device image, which re-initializes everything and defeats the point of keeping the core
resident. This op is the scoped alternative: reset just the core, in place, from the control program.

What the reset does and does not touch

CORE_CONTROL.RESET resets the processor: program counter and registers. It does not clear the tile's data
memory (that is the whole point, the resident data stays put), and it does not touch locks or the tile DMA.
So this op is the core-state half of a dispatch-boundary re-arm; a caller that needs a full clean handshake
also resets the relevant locks (aiex.set_lock) and DMA, the same way aie-rt re-initializes those
separately around a core reset. It also assumes the resident core is still enabled: the pulse preserves the
ENABLE bit but does not set it, so a core that has already cleared ENABLE needs a separate re-enable. I have
kept those as separate concerns rather than folding enable/lock/DMA into this one op, so it stays a single
well-defined primitive.

Why an op rather than a raw write32, and why the pulse is masked

A raw npu.write32 to CORE_CONTROL works but is opaque, and a plain write of the reset bit would clobber the
rest of the register. CORE_CONTROL packs ENABLE (bit 0) and RESET (bit 1) in one word. So the lowering emits
the reset as two npu.maskwrite32 writes masked to bit 1 (assert, then deassert), which preserves ENABLE
instead of overwriting it. This mirrors aie-rt's XAie_CoreReset and XAie_CoreUnreset, which drive the
reset bit with a MaskWrite32.

One deliberate difference from the sibling dma_channel_reset: that op computes its register address through
the target model (getDmaControlAddress), which varies by tile class, channel, and direction. CORE_CONTROL
does not vary, it sits at tile-local offset 0x32000 on every generation (XAIE2PGBL_CORE_MODULE_CORE_CONTROL
on aie2p, XAIEMLGBL_CORE_MODULE_CORE_CONTROL on aie2, both 0x32000), and there is no existing
getCoreControlAddress on the target model. Rather than add a virtual that returns the same constant for
every override, the lowering uses the constant directly with a comment tying it to the aie-rt register name.
If maintainers would prefer the symmetry with getDmaControlAddress, I am happy to add
getCoreControlAddress(col, row) to AIETargetModel instead.

Notes for review

  1. The verifier accepts only core tiles. It rejects mem and shim tiles (no compute core, matching
    XAie_CoreReset, which errors on any non core tile), coordinates outside the device array (the column and
    row are raw attributes, so this is the only thing that bounds them), and AIE1 (the NPU runtime sequence has
    no meaning there, matching set_lock).
  2. The op takes just (column, row). It resets the whole core, so there is no channel or direction parameter.
  3. Verified by lit: a lowering test that checks the masked pulse (value/mask at the CORE_CONTROL address, on
    aie2p and aie2, with --implicit-check-not guarding against a stray unmasked write) and an invalid test
    that checks the verifier rejects mem, shim, out-of-range, and AIE1.
  4. Validated on device (aie2p / Ryzen AI NPU). Injecting the exact masked pulse this op lowers to onto two
    resident objectFIFO cores resets and restarts them without disturbing the result (a two-stage streaming
    design keeps producing correct output, reproduced). A control that instead clears ENABLE on one of those
    cores breaks the run, which confirms the writes reach the core and that masking the pulse to the reset bit
    (preserving ENABLE) is what keeps it correct. The attr-form maskwrite32 also survives the full lowering to
    an xclbin, so the op needs no absolute-address form. The one case I did not exercise is a run-once core that
    has cleared ENABLE after reaching Done; re-arming that also needs a re-enable, as the description notes.
  5. I am deliberately keeping the higher-level question out of this PR, whether a resident core should carry an
    attribute that emits this reset automatically. That is a lifecycle design question. This PR is the missing
    mechanism.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new aiex.core_reset runtime-sequence operation to reset an AIE compute core via a masked pulse on the tile’s CORE_CONTROL.RESET bit, plus the corresponding lowering pass and lit coverage.

Changes:

  • Introduce aiex.core_reset op (with verifier restricting use to in-range core tiles on AIE2-family devices).
  • Add --aie-lower-core-reset pass that lowers aiex.core_reset to two aiex.npu.maskwrite32 ops (assert + deassert) at tile-local offset 0x32000, masked to bit 1.
  • Add lit tests for both lowering behavior and verifier diagnostics; wire the pass into the aiecc NPU lowering pipeline.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tools/aiecc/IRTransforms.h Adds the new lowering pass into the NPU lowering pipeline.
lib/Dialect/AIEX/Transforms/CMakeLists.txt Builds the new lowering implementation file.
lib/Dialect/AIEX/Transforms/AIELowerCoreReset.cpp Implements conversion pattern + pass to lower aiex.core_reset to masked control writes.
lib/Dialect/AIEX/IR/AIEXDialect.cpp Adds CoreResetOp verifier (AIE1 rejection, bounds checking, core-tile-only constraint).
include/aie/Dialect/AIEX/Transforms/AIEXPasses.td Declares the new lowering pass and its registration metadata.
include/aie/Dialect/AIEX/Transforms/AIEXPasses.h Declares the new pass factory function (and nearby pass factory declarations).
include/aie/Dialect/AIEX/IR/AIEX.td Defines the new aiex.core_reset op, syntax, and documentation.
test/Passes/lower-core-reset/core_reset.mlir Checks lowering emits the correct masked reset pulse and no unmasked write32.
test/Passes/lower-core-reset/core_reset_invalid.mlir Checks verifier rejects mem/shim, out-of-range tiles, and AIE1.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread include/aie/Dialect/AIEX/Transforms/AIEXPasses.h
@atassis

atassis commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Hi, @jgmelber! Thanks for reviewing my work, again! I have a PR and idea, on which you might be a natural reviewer (this pr). I have added a SubtargetFeature capability set, mirroring amdgpu's model, but mostly this is a subtask of my more broader look at what whole architecture and contract could look like.
Idea is the link in PR (an article).

@hunhoffe

Copy link
Copy Markdown
Collaborator

@atassis I'd be interested in hearing more about your proposal but I have some trouble with the link. Can you please summarize here (or in a discussion)?

@atassis

atassis commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

https://atassis.github.io/writing/amd-contract-first-e99255/ does this help? Temporarily deployed it onto github pages.
Long story short- #1172 adds a SubtargetFeature capability set to the AIE backend (capability is a set, not the __AIE_ARCH__ level), and the broader idea is that freezing a few interface contracts would let the stack stop moving in lockstep.

@atassis

atassis commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

#3390
I have landed this into discussion, also so we can separate PR review from my discussion

@andrej andrej left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's useful to have an explicit instruction for core resets, thanks for this contribution.

But could we use an SSA tile, like in the channel reset op, rather than hard-coded column/row values?

Comment thread include/aie/Dialect/AIEX/IR/AIEX.td Outdated
Comment on lines +1419 to +1420
ins I32Attr:$column,
I32Attr:$row

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not a reference to an SSA %tile?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good eye, I have missed that, thanks! Already fixed

atassis added 4 commits July 23, 2026 01:22
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).
AIEXPasses.h declared createAIELowerSetLockPass() twice. The second is
redundant; removing it, per review.
Fix the code-format CI check: trim the file-header banner to 80 columns and let clang-format reflow a comment and a call.
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.
@hunhoffe
hunhoffe added this pull request to the merge queue Jul 23, 2026
Merged via the queue into Xilinx:main with commit 1e6b13c Jul 23, 2026
83 of 84 checks passed
@atassis
atassis deleted the aiex-core-reset branch July 23, 2026 17:15
jskimko added a commit to jskimko/mlir-aie that referenced this pull request Jul 23, 2026
…channel_reset

Add on-board npu-xrt tests that drive the merged reset ops (Xilinx#3375, Xilinx#3370)
instead of raw register writes, as op-based counterparts of the core/ and dma/
families:

- core_reset_op: aiex.core_reset supplies the reset->unreset pulse on
  CORE_CONTROL (0x32000, bit 1) = XAie_CoreReset + XAie_CoreUnreset. Because the
  core has run to aie.end it is no longer enabled, so the test adds a masked
  re-enable of bit 0 mirroring XAie_CoreEnable -- making the whole test the
  driver's XAie_CoreReset -> XAie_CoreUnreset -> XAie_CoreEnable sequence, every
  write masked to one field. On-board, the op alone leaves the core halted --
  pinning the op's documented still-enabled assumption.
- dma_channel_reset_op: aiex.dma_channel_reset is a drop-in for dma/'s four
  reset writes on DMA_MM2S_0_Ctrl (0x1DE10, bit 1) = XAie_DmaChannelReset; the
  re-push BD + lock re-arm remain around it.

Both ops lower to a masked maskwrite32 pulse on the same register and reset bit
the raw siblings write, matching the aie-rt driver (all masked field writes).
The ops previously had only aie-opt FileCheck coverage; these are their first
on-silicon tests. Each op-based test compiles its raw sibling's test.cpp
directly (%S/../core, %S/../dma) rather than duplicating the host harness.
All five local_reset families pass on npu2 (Strix).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants