Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Midhat Nashar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Complex FIR on the AMD Ryzen AI NPU with IRON
Copyright (c) 2026 Midhat Nashar

This tutorial is adapted from contributor-owned, MIT-licensed source in:

Repository: https://github.com/midhatn/phoenix-sdr-dsp
Revision: 4bc0f158e9208469cf356d1349cef1aff55e8e47
Paths:
tests/m19_complex_fir/fir_complex_kernel.cc
tests/m19_complex_fir/test_fir_complex_m19.py

The tutorial adaptation, documentation, and scripts are distributed under
the MIT License included in this directory.
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<table class="sphinxhide" style="width:100%;">
<tr>
<td align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Xilinx/Image-Collateral/main/logo-white-text.png">
<img alt="AMD logo" src="https://raw.githubusercontent.com/Xilinx/Image-Collateral/main/xilinx-logo.png" width="30%">
</picture>
<h1>AMD Vitis™ Developer Contributed Tutorials</h1>
<a href="https://www.amd.com/en/products/software/adaptive-socs-and-fpgas/vitis.html">See Vitis™ Development Environment on amd.com</a>
</td>
</tr>
</table>

# Complex FIR on the AMD Ryzen AI NPU with IRON

***Reproducible reader toolchain: MLIR-AIE v1.4.1, tag revision `601fc859532f2539bebb33ac89139584c76ae8a2`, LLVM-AIE (Peano) `21.0.0.2026080301+c9c5ecb7`***

- Validated hardware baseline: AMD Ryzen 9 7940HS (Phoenix XDNA1 NPU) in an ASUS TUF Gaming A15 FA507XI
- Host operating system: Windows 11 Pro, build 26200.9168
- Last update: 17 Aug 2026

> **Note**
> This tutorial uses the open-source IRON / MLIR-AIE Python flow for a Ryzen AI NPU. It does not use the classic Versal `aiecompiler` flow. The example has been validated only on the hardware and software baseline listed in [`doc/SETUP.md`](doc/SETUP.md); it is not a claim that every Ryzen 7040- or 8040-series system contains or exposes a compatible NPU.

## What this tutorial demonstrates

The example builds and runs a fixed-size, 8-tap complex finite impulse response (FIR) filter on one AIE2 compute tile. It processes 2048 complex samples stored as 4096 interleaved `bfloat16` elements:

```text
[I0, Q0, I1, Q1, ..., I2047, Q2047]
```

For complex input \(x[n]\) and complex taps \(h[k]\), the kernel computes the causal FIR

\[
y[n] = \sum_{k=0}^{7} h[k]x[n-k], \qquad x[n]=0 \text{ for } n<0.
\]

The source-level arithmetic promotes each input component from `bfloat16` to `float32`, uses internal `float32` taps and accumulation, and converts each output component to `bfloat16`.

This is an instructional functional example. It is not a throughput benchmark, a bitwise conformance test, or proof of a universal one-ULP error bound.

## Files

```text
04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/
├── README.md
├── LICENSE
├── NOTICE
├── src/
│ ├── check_environment.py
│ ├── fir_complex_kernel.cc
│ └── test_fir_complex_m19.py
├── scripts/
│ └── run_tutorial.ps1
└── doc/
├── SETUP.md
├── WALKTHROUGH.md
└── M19_DESIGN.md
```

## Setup and run

Complete the Windows-native IRON setup in [`doc/SETUP.md`](doc/SETUP.md). In a Developer PowerShell for Visual Studio, dot-source the `iron_env.ps1` generated by MLIR-AIE, then run:

```powershell
cd 04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON
.\scripts\run_tutorial.ps1
```

The runner prints the active Python, NumPy, `ml_dtypes`, `aie`, and `pyxrt` locations or versions before compiling. It returns a non-zero exit status if preflight, compilation, dispatch, or validation fails.

## Validation contract

The Python program performs five deterministic host-side checks before NPU compilation:

- I-channel impulse response
- Q-channel impulse response
- I-channel DC response after the seven-sample zero-history warm-up
- complex-tone result cross-checked with NumPy complex convolution
- reduction to a real FIR when imaginary taps and Q input are zero

It then dispatches one deterministic random-I/Q vector generated with NumPy `RandomState(456)`. The silicon output and NumPy reference must:

1. have the same shape;
2. contain only finite values; and
3. satisfy `abs(actual - reference) <= 0.01` for every output element, with no relative-tolerance term.

The script reports the maximum absolute error. The threshold is a fixed functional tolerance; because `bfloat16` spacing varies with exponent, it must not be interpreted as a uniform ULP bound.

The recorded Phoenix silicon run used Python 3.13.15, the `mlir-aie`
1.4.1 wheel, MLIR-AIE source checkout
`3ca0193cea9e2c39ec670a65f93e1dd43c969f22`, LLVM-AIE
`21.0.0.2026080301+c9c5ecb7`, NumPy 2.5.2, and `ml_dtypes` 0.6.0.
It completed with a maximum absolute error of 0.007812.

An abbreviated successful run ends as follows. Device representations, sample values, compilation messages, and the measured maximum error vary by environment.

```text
Running host-side reference checks before NPU compilation...
[reference] I impulse: PASS
[reference] Q impulse: PASS
[reference] DC: PASS (...)
[reference] Complex tone convolution: PASS (...)
[reference] Real-FIR algebraic reduction: PASS (...)
Compiling the 8-tap complex FIR and dispatching to the NPU...
...
Maximum absolute error: <value not greater than 0.010000>
SUCCESS: the NPU output is finite and satisfies the absolute-tolerance contract (<= 0.010000).
PASS!
```

## Constraints

- The C++ kernel is intentionally fixed at 2048 complex samples. The IRON host rejects any `N` other than 4096 interleaved elements and any element type other than `bfloat16`.
- Taps are compile-time `float32` constants. Runtime coefficient updates, streaming across invocations, and state retention are outside this example.
- The first seven outputs include the deterministic zero-history warm-up. The example does not flush a trailing filter tail.
- The validated execution path is native Windows with XRT and a Phoenix XDNA1 NPU.

## License and provenance

The tutorial is licensed under the MIT License. See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE). The source was adapted from contributor-owned code in [`midhatn/phoenix-sdr-dsp`](https://github.com/midhatn/phoenix-sdr-dsp) at the immutable revision recorded in `NOTICE`.

For the mathematical derivation and implementation mapping, read [`doc/WALKTHROUGH.md`](doc/WALKTHROUGH.md) and [`doc/M19_DESIGN.md`](doc/M19_DESIGN.md).

<hr class="sphinxhide"></hr>

<p class="sphinxhide" align="center"><sub>Copyright © 2026 Midhat Nashar. Licensed under the MIT License.</sub></p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Complex FIR design

## Scope

The tutorial implements one fixed block of a causal, 8-tap complex FIR on an AIE2 tile:

- input and output: 4096 interleaved `bfloat16` elements;
- complex samples: 2048;
- taps: eight complex `float32` constants compiled into the kernel;
- startup state: zero history;
- execution: one IRON worker and one input/output object FIFO pair.

The example does not retain history between calls, accept runtime taps, flush a trailing tail, or measure throughput.

## Mathematical specification

Let

\[
x[n] = I_x[n] + jQ_x[n], \qquad h[k] = I_h[k] + jQ_h[k].
\]

The output is

\[
y[n] = \sum_{k=0}^{7} h[k]x[n-k], \qquad x[n]=0 \text{ for } n<0.
\]

Expanding one complex product gives

\[
(I_x+jQ_x)(I_h+jQ_h)
= (I_xI_h-Q_xQ_h) + j(I_xQ_h+Q_xI_h).
\]

The kernel therefore computes

```text
Iout[n] = sum(Ix[n-k] * Ih[k] - Qx[n-k] * Qh[k], k=0..7)
Qout[n] = sum(Ix[n-k] * Qh[k] + Qx[n-k] * Ih[k], k=0..7)
```

The history arrays begin at zero. At iteration `n`, the new sample enters slot 7, so slot `7-k` contains sample `x[n-k]`.

## Tap set

```text
Ih = (+0.05, +0.10, +0.20, +0.30, +0.30, +0.20, +0.10, +0.05)
Qh = (+0.05, +0.10, +0.20, +0.30, -0.30, -0.20, -0.10, -0.05)
```

The nonzero real and imaginary components exercise all four terms of the complex product. The tap set is instructional; the tutorial does not claim a standards-qualified low-pass or Hilbert-transformer response.

## Data representation

Input and output use:

```text
[I0, Q0, I1, Q1, ..., I2047, Q2047]
```

The C++ kernel reads each `bfloat16` component into a `float`, performs source-level `float32` arithmetic with `float32` tap constants, then converts each result to `bfloat16` for storage.

The NumPy reference performs the same mathematical FIR and data-layout mapping. Its loop reduction and the compiler-generated AIE reduction are not asserted to have identical intermediate rounding. Compiler contraction or reassociation and host/device conversion details can produce small numerical differences.

## Validation rule

The silicon acceptance test is deliberately explicit and fail-closed:

```text
shape(actual) == shape(reference)
all values are finite
abs(actual[i] - reference[i]) <= 0.01 for every i
```

There is no relative-tolerance term. A constant absolute threshold is not a uniform bfloat16-ULP threshold because representable spacing varies with exponent. Accordingly, the tutorial makes neither a bitwise-equality claim nor a one-ULP claim.

The single silicon stimulus is a deterministic random vector generated with NumPy `RandomState(456)`. The directed impulse, DC, complex-tone, and real-FIR-reduction checks validate host reference behavior before compilation; they do not constitute additional NPU dispatches.

## IRON mapping

`test_fir_complex_m19.py` constructs:

- one full-block input `ObjectFifo`;
- one full-block output `ObjectFifo`;
- one `ExternalFunction` compiled from `fir_complex_kernel.cc`;
- one `Worker` with a 16 KiB stack request;
- one `Runtime` sequence that fills input and drains output; and
- one `Program` resolved for `iron.get_current_device()`.

The host enforces the fixed ABI before program construction:

- `N` must be 4096; and
- `element_type` must be `ml_dtypes.bfloat16`.

After dispatch, `XRTTensor.numpy()` uses the public pinned API to reconcile device output to host memory.

## References

- NIST Digital Library of Mathematical Functions, complex-number conventions: https://dlmf.nist.gov/1.9
- MLIR-AIE repository: https://github.com/Xilinx/mlir-aie
- MLIR-AIE v1.4.1 native Windows guide: https://xilinx.github.io/mlir-aie/1.4.1/buildHostWinNative/
- `ml_dtypes` bfloat16 implementation: https://github.com/jax-ml/ml_dtypes
- AMD XDNA architecture overview: https://www.amd.com/en/technologies/xdna.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Setup: Ryzen AI NPU and IRON on native Windows

This tutorial was validated on one AMD Phoenix XDNA1 system. Treat the versions below as the tested baseline, not as a promise of compatibility with every Ryzen-branded laptop.

## Tested baseline

| Component | Tested value |
|---|---|
| System | ASUS TUF Gaming A15 FA507XI |
| Processor | AMD Ryzen 9 7940HS |
| NPU | Phoenix XDNA1 / AIE2, reported by XRT as `NPU Phoenix` |
| Operating system | Windows 11 Pro, build 26200.9168 |
| Python | 3.13.15 |
| XRT runtime | 2.21.0 |
| XRT SDK package | 2.21.75 |
| NPU driver | 32.0.20102.3930 |
| NPU firmware | 1.5.5.391 |
| MLIR-AIE Python package | 1.4.1 |
| MLIR-AIE checkout used for the recorded run | `3ca0193cea9e2c39ec670a65f93e1dd43c969f22` |
| Reproducible reader checkout | tag `v1.4.1`, revision `601fc859532f2539bebb33ac89139584c76ae8a2` |
| LLVM-AIE (Peano) | `21.0.0.2026080301+c9c5ecb7` |
| NumPy | 2.5.2 |
| `ml_dtypes` | 0.6.0 |

Newer compatible drivers or firmware may work, but they were not part of this recorded run. Systems without an NPU visible to XRT cannot run the silicon stage.

## Prerequisites

Follow the [MLIR-AIE v1.4.1 native Windows guide](https://xilinx.github.io/mlir-aie/1.4.1/buildHostWinNative/) to install:

- Visual Studio 2022 with the C++ desktop workload;
- 64-bit Python 3.13;
- a compatible AMD XDNA NPU driver;
- the XRT SDK, normally under `C:\Xilinx\XRT`; and
- a checkout-local IRON environment created by `utils\iron_setup.py`.

The official guide uses either an x64 Native Tools command prompt or a Developer PowerShell for Visual Studio. A plain PowerShell session that lacks the Visual Studio compiler environment is not sufficient.

## Create the pinned IRON environment

Open Developer PowerShell for Visual Studio. Create a recursive checkout at the
release tag and let that checkout's setup script install the release-paired
`mlir_aie` wheel and its pinned requirements:

```powershell
cd C:\dev
git clone --recurse-submodules https://github.com/Xilinx/mlir-aie.git mlir-aie-v1.4.1
cd .\mlir-aie-v1.4.1
git checkout v1.4.1
git submodule update --init --recursive
python .\utils\iron_setup.py
. .\iron_env.ps1
```

The leading dot on the activation command is required. In each later
Developer PowerShell session, reactivate the existing environment with:

```powershell
cd C:\dev\mlir-aie-v1.4.1
. .\iron_env.ps1
```

Confirm that the release tag resolves to the reviewed source revision:

```powershell
git rev-parse HEAD
```

Expected for the recorded baseline:

```text
601fc859532f2539bebb33ac89139584c76ae8a2
```

Do not substitute an untagged checkout: `iron_setup.py` uses a rolling
development wheel when no release tag names `HEAD`. At `v1.4.1`, it selects
`mlir_aie==1.4.1`, installs the repository's runtime requirements, and installs
the Peano version pinned by `utils\peano-requirements.txt`.

The recorded silicon run used the later untagged source revision shown in the
tested-baseline table with the same `mlir-aie` 1.4.1 and pinned Peano packages.
That exact local environment is recorded for provenance, but the instructions
above deliberately use the immutable v1.4.1 release path instead of asking
readers to resolve a mutable rolling-wheel channel.

## Verify hardware and Python dependencies

Confirm that XRT sees the NPU:

```powershell
& "C:\Windows\System32\AMD\xrt-smi.exe" examine
```

The device list must contain a compatible NPU. Driver and firmware values do not need to equal the tested baseline exactly unless reproducing that baseline.

Confirm the required distributions and imports:

```powershell
python -m pip show mlir-aie llvm-aie numpy ml-dtypes
python -c "import numpy, ml_dtypes, aie, pyxrt; print(numpy.__version__); print(ml_dtypes.__version__); print(aie.__file__); print(pyxrt.__file__)"
```

Do not separately upgrade `numpy`, `ml_dtypes`, `mlir_aie`, or `llvm_aie`
after setup. The tutorial runner rejects a wrong MLIR-AIE or Peano package and
checks the NumPy and `ml_dtypes` constraints recorded by the v1.4.1 checkout.

## Toolchain readiness gate

Before running this tutorial, execute the SAXPY example recommended by the official Windows guide:

```powershell
cd C:\dev\mlir-aie-v1.4.1\programming_examples\getting_started\01_SAXPY
python saxpy.py
```

Proceed only after SAXPY completes with `PASS!`. This verifies the compiler, XRT runtime, driver, and NPU path independently of the complex FIR example.

## Run the tutorial

From the tutorial directory:

```powershell
.\scripts\run_tutorial.ps1
```

The script disables Python bytecode generation, checks imports, prints the active package information, and propagates non-zero Python exit codes through Windows PowerShell 5.1 and PowerShell 7.
Loading