From 6be6916809444fc3b89eff6c4fd0d3933bde98f9 Mon Sep 17 00:00:00 2001 From: MIDHAT NASHAR Date: Mon, 17 Aug 2026 02:19:10 +0300 Subject: [PATCH 1/2] Developer_Contributed: add Ryzen AI complex FIR tutorial Signed-off-by: MIDHAT NASHAR --- .../LICENSE | 21 + .../NOTICE | 13 + .../README.md | 128 ++++++ .../doc/M19_DESIGN.md | 104 +++++ .../doc/SETUP.md | 126 ++++++ .../doc/WALKTHROUGH.md | 93 +++++ .../scripts/run_tutorial.ps1 | 49 +++ .../src/check_environment.py | 39 ++ .../src/fir_complex_kernel.cc | 126 ++++++ .../src/test_fir_complex_m19.py | 372 ++++++++++++++++++ Developer_Contributed/README.md | 6 + 11 files changed, 1077 insertions(+) create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/LICENSE create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/NOTICE create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/README.md create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/M19_DESIGN.md create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/SETUP.md create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/WALKTHROUGH.md create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/scripts/run_tutorial.ps1 create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/check_environment.py create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/fir_complex_kernel.cc create mode 100644 Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/test_fir_complex_m19.py diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/LICENSE b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/LICENSE new file mode 100644 index 000000000..ec7067a55 --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/LICENSE @@ -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. diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/NOTICE b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/NOTICE new file mode 100644 index 000000000..1925fd8da --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/NOTICE @@ -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. diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/README.md b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/README.md new file mode 100644 index 000000000..bac862f6f --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/README.md @@ -0,0 +1,128 @@ + + + + +
+ + + AMD logo + +

AMD Vitis™ Developer Contributed Tutorials

+ See Vitis™ Development Environment on amd.com +
+ +# 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: +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). + +
+ +

Copyright © 2026 Midhat Nashar. Licensed under the MIT License.

diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/M19_DESIGN.md b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/M19_DESIGN.md new file mode 100644 index 000000000..8162e6741 --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/M19_DESIGN.md @@ -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 diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/SETUP.md b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/SETUP.md new file mode 100644 index 000000000..3642290a4 --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/SETUP.md @@ -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. diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/WALKTHROUGH.md b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/WALKTHROUGH.md new file mode 100644 index 000000000..6adf518cc --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/doc/WALKTHROUGH.md @@ -0,0 +1,93 @@ +# Walkthrough: math, kernel, host, and validation + +## Math + +An FIR filter forms each output from a weighted history of input samples. This tutorial uses the causal convention: + +```text +y[n] = h[0]x[n] + h[1]x[n-1] + ... + h[7]x[n-7] +``` + +Both samples and taps are complex. If `x = Ix + jQx` and `h = Ih + jQh`, one product expands to: + +```text +real = Ix*Ih - Qx*Qh +imag = Ix*Qh + Qx*Ih +``` + +The kernel applies that expansion to each of the eight tap/sample pairs. + +## Kernel + +Open `src/fir_complex_kernel.cc`. The external function accepts one interleaved input buffer and one output buffer: + +```cpp +void fir_complex_kernel( + const bfloat16 *__restrict in_iq, + bfloat16 *__restrict out_iq +); +``` + +Two 8-element `float` arrays hold I and Q history. For each complex input sample, the kernel: + +1. promotes the I and Q components from `bfloat16` to `float`; +2. shifts both history arrays left and inserts the new sample at slot 7; +3. evaluates the real and imaginary 8-tap sums; and +4. converts both results to `bfloat16` in the output buffer. + +The history begins at zero. The first seven results are therefore the causal zero-history warm-up, not discarded samples. + +## Host program + +Open `src/test_fir_complex_m19.py`. The `@iron.jit` function builds a single-worker program around the C++ external function. + +The fixed-size guard is part of the ABI: + +```python +if N != 4096: + raise ValueError(...) +if element_type is not bfloat16: + raise TypeError(...) +``` + +The runtime fills one full input object-FIFO item and drains one full output item: + +```python +def sequence(a_in, c_out, in_prod, out_cons): + in_prod.fill(a_in) + out_cons.drain(c_out, wait=True) +``` + +After dispatch, `output_tensor.numpy()` synchronizes the XRT-backed output to host memory through the public tensor API. + +## Reference checks + +Before compilation, the script tests the NumPy reference with directed inputs: + +- **I impulse:** output equals the complex tap sequence. +- **Q impulse:** real output equals `-Qh`; imaginary output equals `Ih`. +- **DC:** from output index 7, the result equals the real and imaginary tap sums. +- **Complex tone:** the loop reference is cross-checked against NumPy complex convolution using the quantized input. +- **Real-FIR reduction:** zero imaginary taps and Q input reduce the equation to a real convolution. + +These are host checks. They validate the reference and mathematical mapping but do not execute additional silicon cases. + +## Silicon validation + +The NPU case uses 2048 deterministic complex samples generated by NumPy `RandomState(456)`, quantized to interleaved `bfloat16`. The program compiles the C++ kernel through Peano, dispatches it through XRT, retrieves the output, and compares it with the NumPy reference. + +The comparison rejects: + +- shape differences; +- any NaN or infinity; and +- any element whose absolute error is greater than `0.01`. + +The comparison contains no relative-tolerance term. It reports the first failing index and values if the threshold is exceeded. + +Run everything with: + +```powershell +.\scripts\run_tutorial.ps1 +``` + +The PowerShell wrapper checks the environment first and explicitly converts non-zero native Python exit codes into script failures. A successful run ends with one tutorial-owned `PASS!` line. diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/scripts/run_tutorial.ps1 b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/scripts/run_tutorial.ps1 new file mode 100644 index 000000000..85a126675 --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/scripts/run_tutorial.ps1 @@ -0,0 +1,49 @@ +# Copyright (c) 2026 Midhat Nashar +# SPDX-License-Identifier: MIT +# +# Run this script from a Developer PowerShell for Visual Studio after +# dot-sourcing the MLIR-AIE checkout's iron_env.ps1 activation helper. + +$ErrorActionPreference = "Stop" +$env:PYTHONDONTWRITEBYTECODE = "1" + +$here = Split-Path -Parent $MyInvocation.MyCommand.Path +$root = Split-Path -Parent $here +$preflightFile = Join-Path $root "src\check_environment.py" +$testFile = Join-Path $root "src\test_fir_complex_m19.py" +$python = Get-Command python -ErrorAction Stop + +function Invoke-PythonChecked { + param( + [Parameter(Mandatory = $true)] + [string[]]$Arguments, + + [Parameter(Mandatory = $true)] + [string]$FailureMessage + ) + + & $python.Source @Arguments + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0) { + throw "$FailureMessage (python exit code $exitCode)" + } +} + +Write-Host "Tutorial root: $root" +Write-Host "Python: $($python.Source)" +Write-Host "Preflight: $preflightFile" +Write-Host "Test file: $testFile" + +Push-Location $root +try { + Invoke-PythonChecked ` + -Arguments @($preflightFile) ` + -FailureMessage "IRON/XRT dependency preflight failed" + + Invoke-PythonChecked ` + -Arguments @($testFile) ` + -FailureMessage "Complex FIR tutorial failed" +} +finally { + Pop-Location +} diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/check_environment.py b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/check_environment.py new file mode 100644 index 000000000..908ce0a13 --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/check_environment.py @@ -0,0 +1,39 @@ +# Copyright (c) 2026 Midhat Nashar +# SPDX-License-Identifier: MIT +"""Validate the Python packages required by the M19 tutorial.""" + +import sys +from importlib.metadata import version + +import aie +import ml_dtypes +import numpy +import pyxrt +from numpy.lib import NumpyVersion + + +def main(): + expected = { + "mlir-aie": "1.4.1", + "llvm-aie": "21.0.0.2026080301+c9c5ecb7", + } + actual = {name: version(name) for name in expected} + if actual != expected: + raise RuntimeError(f"package mismatch: expected {expected}, found {actual}") + if not ( + NumpyVersion("2.5.1") <= NumpyVersion(numpy.__version__) < NumpyVersion("3.0.0") + ): + raise RuntimeError(f"NumPy {numpy.__version__} is outside [2.5.1, 3.0)") + if NumpyVersion(ml_dtypes.__version__) < NumpyVersion("0.5.4"): + raise RuntimeError(f"ml_dtypes {ml_dtypes.__version__} is below 0.5.4") + + print("Python:", sys.version) + print("Packages:", actual) + print("NumPy:", numpy.__version__) + print("ml_dtypes:", ml_dtypes.__version__) + print("aie:", aie.__file__) + print("pyxrt:", pyxrt.__file__) + + +if __name__ == "__main__": + main() diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/fir_complex_kernel.cc b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/fir_complex_kernel.cc new file mode 100644 index 000000000..c57352457 --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/fir_complex_kernel.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2026 Midhat Nashar +// SPDX-License-Identifier: MIT +// +// Purpose: Fixed-size complex FIR filter kernel for AIE2. +// Applies an 8-tap complex FIR h[k] = Ih[k] + j*Qh[k] to an +// interleaved bfloat16 I/Q input vector (4096 bf16 elements = +// 2048 complex I/Q samples), producing an interleaved bfloat16 +// I/Q output vector of the same shape. +// Validated target: AMD Ryzen 9 7940HS Phoenix / XDNA1 / AIE2 on Windows 11. +// Scaling: Direct bfloat16 operand load, float32 multiply-accumulate, +// then float32-to-bfloat16 conversion on store. +// Complex multiply identity (Oppenheim & Schafer, DTSP 3e, section 2.2; +// NIST DLMF section 1.9): +// (Ix + j Qx) * (Ih + j Qh) = (Ix*Ih - Qx*Qh) + j*(Ix*Qh + Qx*Ih). +// State requirements: Stateless across kernel invocations; internal state +// is two 8-float shift registers (hist_i, hist_q). +// Boundary behavior: Zero-history warmup for the first seven outputs. +// +// Design note: shift-and-ingest organization (M8 convention). +// +// This kernel follows the M8 pipeline convention from the upstream project +// (https://github.com/midhatn/phoenix-sdr-dsp/blob/main/tests/m8_pipeline/pipeline_kernel.cc) +// line-for-line in loop shape: a single 2048-iteration flat loop, no separate +// prime or tail phase. At the start of the loop hist_i and hist_q are zero, and +// each iteration: +// 1. reads one (I, Q) pair from in_iq into scalars ii, qq; +// 2. shifts hist_i and hist_q left by one slot; +// 3. writes ii and qq into hist_i[L-1] and hist_q[L-1]; +// 4. computes the two dot products +// Iacc = sum_{k=0..L-1} ( hist_i[L-1-k]*cIk - hist_q[L-1-k]*cQk ) +// Qacc = sum_{k=0..L-1} ( hist_i[L-1-k]*cQk + hist_q[L-1-k]*cIk ) +// which is the textbook direct-form convolution +// out[i] = sum_{k=0..L-1} h[k] * x[i-k] +// (Oppenheim & Schafer, DTSP 3e, section 5.2) with x[n] = 0 for n < 0. +// 5. stores Iacc and Qacc into out_iq[2i], out_iq[2i+1]. +// +// The first L-1 outputs are the transient response of the filter to a +// zero-history startup; they still match the reference in +// test_fir_complex_m19.py term-for-term because the reference performs +// the same shift-and-ingest walk. +// +// This organization requires no separate prime or tail loop and has no +// data-dependent branch in the main body. + +#define NOCPP + +#include + +extern "C" { + +void fir_complex_kernel( + const bfloat16 *__restrict in_iq, + bfloat16 *__restrict out_iq +) { + event0(); + + // 8 complex taps h[k] = Ih[k] + j*Qh[k]. + // Qh is an antisymmetric Hilbert-transformer-flavoured sequence + // chosen to exercise all four terms of the complex multiply. + const float cI0 = 0.05f; + const float cI1 = 0.10f; + const float cI2 = 0.20f; + const float cI3 = 0.30f; + const float cI4 = 0.30f; + const float cI5 = 0.20f; + const float cI6 = 0.10f; + const float cI7 = 0.05f; + + const float cQ0 = 0.05f; + const float cQ1 = 0.10f; + const float cQ2 = 0.20f; + const float cQ3 = 0.30f; + const float cQ4 = -0.30f; + const float cQ5 = -0.20f; + const float cQ6 = -0.10f; + const float cQ7 = -0.05f; + + // Shift-register windows. Zero-history warmup: the first L-1 outputs + // are computed with some slots still zero, matching the reference + // in test_fir_complex_m19.py which walks the same schedule. + float hist_i[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + float hist_q[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + +#pragma clang loop unroll_count(4) + for (int i = 0; i < 2048; ++i) { + float ii = (float)in_iq[2 * i ]; + float qq = (float)in_iq[2 * i + 1]; + + // Shift the window left by one, ingesting the new sample at slot 7. + // This exactly mirrors the upstream M8 pipeline kernel lines 52-56: + // https://github.com/midhatn/phoenix-sdr-dsp/blob/main/tests/m8_pipeline/pipeline_kernel.cc + hist_i[0] = hist_i[1]; hist_i[1] = hist_i[2]; hist_i[2] = hist_i[3]; hist_i[3] = hist_i[4]; + hist_i[4] = hist_i[5]; hist_i[5] = hist_i[6]; hist_i[6] = hist_i[7]; hist_i[7] = ii; + + hist_q[0] = hist_q[1]; hist_q[1] = hist_q[2]; hist_q[2] = hist_q[3]; hist_q[3] = hist_q[4]; + hist_q[4] = hist_q[5]; hist_q[5] = hist_q[6]; hist_q[6] = hist_q[7]; hist_q[7] = qq; + + // Textbook direct-form: out[i] = sum_{k=0..L-1} h[k] * x[i-k]. + // Newest sample hist[7] pairs with tap 0; oldest sample hist[0] + // pairs with tap L-1. + float Iacc = hist_i[7] * cI0 - hist_q[7] * cQ0 + + hist_i[6] * cI1 - hist_q[6] * cQ1 + + hist_i[5] * cI2 - hist_q[5] * cQ2 + + hist_i[4] * cI3 - hist_q[4] * cQ3 + + hist_i[3] * cI4 - hist_q[3] * cQ4 + + hist_i[2] * cI5 - hist_q[2] * cQ5 + + hist_i[1] * cI6 - hist_q[1] * cQ6 + + hist_i[0] * cI7 - hist_q[0] * cQ7; + + float Qacc = hist_i[7] * cQ0 + hist_q[7] * cI0 + + hist_i[6] * cQ1 + hist_q[6] * cI1 + + hist_i[5] * cQ2 + hist_q[5] * cI2 + + hist_i[4] * cQ3 + hist_q[4] * cI3 + + hist_i[3] * cQ4 + hist_q[3] * cI4 + + hist_i[2] * cQ5 + hist_q[2] * cI5 + + hist_i[1] * cQ6 + hist_q[1] * cI6 + + hist_i[0] * cQ7 + hist_q[0] * cI7; + + out_iq[2 * i ] = (bfloat16)Iacc; + out_iq[2 * i + 1] = (bfloat16)Qacc; + } + + event1(); +} + +} diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/test_fir_complex_m19.py b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/test_fir_complex_m19.py new file mode 100644 index 000000000..e835bdd18 --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/test_fir_complex_m19.py @@ -0,0 +1,372 @@ +# Copyright (c) 2026 Midhat Nashar +# SPDX-License-Identifier: MIT +# +# Fixed-size complex FIR example for AMD Phoenix XDNA1 / AIE2. +# +# The device kernel consumes 4096 interleaved bfloat16 I/Q elements +# (2048 complex samples), computes an 8-tap causal complex FIR with float32 +# taps and accumulation, and converts each result to bfloat16 on store. +# +# Validation contract: +# * deterministic host-side checks run before compilation; +# * one deterministic random-I/Q vector is dispatched to the NPU; +# * silicon and reference outputs must be finite and differ by no more than +# ABS_TOL in absolute value, with no relative-tolerance term. +# +# This is a functional validation example, not a bitwise-conformance test or +# a performance benchmark. See ../doc/M19_DESIGN.md for the numerical model. + +from pathlib import Path + +import numpy as np +from aie import iron +from aie.iron import ( + CompileTime, + ExternalFunction, + In, + ObjectFifo, + Out, + Program, + Runtime, + Worker, +) +from aie.utils.config import cxx_header_path +from aie.utils.hostruntime.xrtruntime.tensor import XRTTensor +from ml_dtypes import bfloat16 + +N_ELEMENTS = 4096 +N_COMPLEX = N_ELEMENTS // 2 +L = 8 +ABS_TOL = np.float32(0.01) +RANDOM_SEED = 456 + +# These float32 values match the constants in fir_complex_kernel.cc. +COEFFS_I_F = np.array( + [0.05, 0.10, 0.20, 0.30, 0.30, 0.20, 0.10, 0.05], + dtype=np.float32, +) +COEFFS_Q_F = np.array( + [0.05, 0.10, 0.20, 0.30, -0.30, -0.20, -0.10, -0.05], + dtype=np.float32, +) + + +def _f32_coeffs(Ih=None, Qh=None): + """Return validated float32 tap arrays.""" + Ih_out = COEFFS_I_F if Ih is None else np.asarray(Ih, dtype=np.float32) + Qh_out = COEFFS_Q_F if Qh is None else np.asarray(Qh, dtype=np.float32) + if Ih_out.shape != (L,) or Qh_out.shape != (L,): + raise ValueError(f"Ih and Qh must both have shape ({L},)") + return Ih_out, Qh_out + + +def _validate_interleaved_input(in_bf16): + """Validate the reference model's interleaved I/Q input.""" + arr = np.asarray(in_bf16) + if arr.ndim != 1 or arr.size == 0 or arr.size % 2 != 0: + raise ValueError("input must be a non-empty 1-D array of interleaved I/Q") + if arr.dtype != np.dtype(bfloat16): + raise TypeError(f"input dtype must be bfloat16; got {arr.dtype}") + return arr + + +def complex_fir_reference(in_bf16, Ih=None, Qh=None): + """Compute the causal complex FIR in float32 and return bfloat16 output. + + The mathematical operation is + + y[n] = sum(h[k] * x[n-k], k=0..L-1), with x[n] = 0 for n < 0. + + Input samples are promoted from bfloat16 to float32. Taps remain float32. + Results are converted to bfloat16 once after each output accumulation. + The AIE compiler may choose a different legal float32 reduction schedule, + so device validation uses the explicit absolute tolerance ABS_TOL. + """ + arr = _validate_interleaved_input(in_bf16) + Ih_f32, Qh_f32 = _f32_coeffs(Ih, Qh) + + in_f32 = arr.astype(np.float32) + Ix = in_f32[0::2] + Qx = in_f32[1::2] + count = Ix.size + + hist_i = np.zeros(L, dtype=np.float32) + hist_q = np.zeros(L, dtype=np.float32) + ref = np.zeros(2 * count, dtype=np.float32) + + for i in range(count): + hist_i[:-1] = hist_i[1:] + hist_q[:-1] = hist_q[1:] + hist_i[-1] = Ix[i] + hist_q[-1] = Qx[i] + + Iacc = np.float32(0.0) + Qacc = np.float32(0.0) + for k in range(L): + si = hist_i[L - 1 - k] + sq = hist_q[L - 1 - k] + Iacc += si * Ih_f32[k] - sq * Qh_f32[k] + Qacc += si * Qh_f32[k] + sq * Ih_f32[k] + + ref[2 * i] = Iacc + ref[2 * i + 1] = Qacc + + return ref.astype(bfloat16) + + +@iron.jit +def complex_fir( + input_iq: In, + output_iq: Out, + *, + N: CompileTime[int], + element_type: CompileTime[type], +): + """Build the fixed-size IRON program used by this tutorial.""" + if N != N_ELEMENTS: + raise ValueError( + f"complex_fir requires N={N_ELEMENTS}; got N={N}. " + "The C++ kernel has a fixed 2048-complex-sample loop bound." + ) + if element_type is not bfloat16: + raise TypeError( + f"complex_fir requires element_type=bfloat16; got {element_type}" + ) + + in_ty = np.ndarray[(N,), np.dtype[element_type]] + out_ty = np.ndarray[(N,), np.dtype[element_type]] + + of_in = ObjectFifo(in_ty, name="in_iq") + of_out = ObjectFifo(out_ty, name="out_iq") + current_dir = Path(__file__).parent.resolve() + + fir_func = ExternalFunction( + "fir_complex_kernel", + source_file=str(current_dir / "fir_complex_kernel.cc"), + arg_types=[in_ty, out_ty], + include_dirs=[cxx_header_path()], + ) + + def core_body(of_in, of_out, fir_func): + elem_in = of_in.acquire(1) + elem_out = of_out.acquire(1) + fir_func(elem_in, elem_out) + of_in.release(1) + of_out.release(1) + + worker = Worker( + core_body, + fn_args=[of_in.cons(), of_out.prod(), fir_func], + stack_size=0x4000, + ) + + def sequence(a_in, c_out, in_prod, out_cons): + in_prod.fill(a_in) + out_cons.drain(c_out, wait=True) + + runtime = Runtime( + sequence, + [in_ty, out_ty, of_in.prod(), of_out.cons()], + ) + program = Program(iron.get_current_device(), runtime, workers=[worker]) + return program.resolve_program() + + +def _pack_iq(Ix_f32, Qx_f32): + """Pack equal-length float arrays as interleaved bfloat16 I/Q.""" + Ix = np.asarray(Ix_f32, dtype=np.float32) + Qx = np.asarray(Qx_f32, dtype=np.float32) + if Ix.ndim != 1 or Qx.ndim != 1 or Ix.shape != Qx.shape: + raise ValueError("Ix and Qx must be equal-length 1-D arrays") + packed = np.empty(2 * Ix.size, dtype=np.float32) + packed[0::2] = Ix + packed[1::2] = Qx + return packed.astype(bfloat16) + + +def _assert_abs_close(actual, expected, label, atol=ABS_TOL): + """Fail closed on shape, non-finite, or absolute-tolerance violations.""" + actual_arr = np.asarray(actual) + expected_arr = np.asarray(expected) + if actual_arr.shape != expected_arr.shape: + raise AssertionError( + f"{label}: shape mismatch {actual_arr.shape} != {expected_arr.shape}" + ) + + actual_f32 = actual_arr.astype(np.float32) + expected_f32 = expected_arr.astype(np.float32) + if not np.isfinite(actual_f32).all(): + bad = int(np.flatnonzero(~np.isfinite(actual_f32))[0]) + raise AssertionError(f"{label}: non-finite actual output at index {bad}") + if not np.isfinite(expected_f32).all(): + bad = int(np.flatnonzero(~np.isfinite(expected_f32))[0]) + raise AssertionError(f"{label}: non-finite reference output at index {bad}") + + abs_error = np.abs(actual_f32 - expected_f32) + max_error = float(np.max(abs_error)) + failing = np.flatnonzero(abs_error > np.float32(atol)) + if failing.size: + index = int(failing[0]) + raise AssertionError( + f"{label}: absolute error exceeds {float(atol):.6f} at index {index}; " + f"actual={float(actual_f32[index]):.8g}, " + f"expected={float(expected_f32[index]):.8g}, " + f"abs_error={float(abs_error[index]):.8g}, " + f"max_abs_error={max_error:.8g}" + ) + return max_error + + +def _local_i_impulse_check(): + """A unit I impulse must produce the complex tap sequence.""" + Ix = np.zeros(N_COMPLEX, dtype=np.float32) + Qx = np.zeros(N_COMPLEX, dtype=np.float32) + Ix[0] = 1.0 + actual = complex_fir_reference(_pack_iq(Ix, Qx)) + + expected = np.zeros(N_ELEMENTS, dtype=np.float32) + expected[0 : 2 * L : 2] = COEFFS_I_F + expected[1 : 2 * L : 2] = COEFFS_Q_F + expected = expected.astype(bfloat16) + if not np.array_equal(actual, expected): + raise AssertionError("I-impulse reference check failed") + print("[reference] I impulse: PASS") + + +def _local_q_impulse_check(): + """A unit Q impulse exercises both cross terms of complex multiply.""" + Ix = np.zeros(N_COMPLEX, dtype=np.float32) + Qx = np.zeros(N_COMPLEX, dtype=np.float32) + Qx[0] = 1.0 + actual = complex_fir_reference(_pack_iq(Ix, Qx)) + + expected = np.zeros(N_ELEMENTS, dtype=np.float32) + expected[0 : 2 * L : 2] = -COEFFS_Q_F + expected[1 : 2 * L : 2] = COEFFS_I_F + expected = expected.astype(bfloat16) + if not np.array_equal(actual, expected): + raise AssertionError("Q-impulse reference check failed") + print("[reference] Q impulse: PASS") + + +def _local_dc_check(): + """After seven warm-up samples, DC output must equal the tap sums.""" + Ix = np.ones(N_COMPLEX, dtype=np.float32) + Qx = np.zeros(N_COMPLEX, dtype=np.float32) + actual = complex_fir_reference(_pack_iq(Ix, Qx)).astype(np.float32) + + expected_i = float(bfloat16(np.sum(COEFFS_I_F, dtype=np.float32))) + expected_q = float(bfloat16(np.sum(COEFFS_Q_F, dtype=np.float32))) + steady_i = actual[2 * (L - 1) :: 2] + steady_q = actual[2 * (L - 1) + 1 :: 2] + err_i = _assert_abs_close(steady_i, np.full_like(steady_i, expected_i), "DC I") + err_q = _assert_abs_close(steady_q, np.full_like(steady_q, expected_q), "DC Q") + print( + "[reference] DC: PASS " + f"(sum Ih={expected_i:.6f}, sum Qh={expected_q:.6f}, " + f"max_abs_error={max(err_i, err_q):.6f})" + ) + + +def _local_complex_tone_check(): + """Cross-check the loop reference against NumPy's complex convolution.""" + sample_index = np.arange(N_COMPLEX, dtype=np.float32) + phase = np.float32(2.0 * np.pi * 5.0 / N_COMPLEX) * sample_index + ideal = np.exp(1j * phase).astype(np.complex64) + packed = _pack_iq(ideal.real, ideal.imag) + quantized = packed.astype(np.float32) + x_quantized = quantized[0::2].astype(np.complex64) + 1j * quantized[1::2].astype( + np.complex64 + ) + taps = (COEFFS_I_F + 1j * COEFFS_Q_F).astype(np.complex64) + expected_complex = np.convolve(x_quantized, taps, mode="full")[:N_COMPLEX] + expected = _pack_iq(expected_complex.real, expected_complex.imag) + actual = complex_fir_reference(packed) + max_error = _assert_abs_close(actual, expected, "complex-tone convolution") + print(f"[reference] Complex tone convolution: PASS (max_abs_error={max_error:.6f})") + + +def _local_real_fir_reduction_check(): + """With zero imaginary taps/input, the formula reduces to a real FIR.""" + rng = np.random.RandomState(123) + Ix = rng.uniform(0.1, 1.0, N_COMPLEX).astype(np.float32) + Qx = np.zeros(N_COMPLEX, dtype=np.float32) + packed = _pack_iq(Ix, Qx) + zero_q_taps = np.zeros(L, dtype=np.float32) + actual = complex_fir_reference( + packed, + Ih=COEFFS_I_F, + Qh=zero_q_taps, + ).astype(np.float32)[0::2] + + quantized_i = packed.astype(np.float32)[0::2] + expected_f32 = np.convolve(quantized_i, COEFFS_I_F, mode="full")[:N_COMPLEX] + expected = expected_f32.astype(bfloat16).astype(np.float32) + max_error = _assert_abs_close(actual, expected, "real-FIR reduction") + print( + "[reference] Real-FIR algebraic reduction: PASS " + f"(max_abs_error={max_error:.6f})" + ) + + +def _run_local_reference_checks(): + print("Running host-side reference checks before NPU compilation...") + _local_i_impulse_check() + _local_q_impulse_check() + _local_dc_check() + _local_complex_tone_check() + _local_real_fir_reduction_check() + + +def main(): + print("=== Complex FIR on AMD Ryzen AI NPU with IRON ===") + print(f"Target device: {iron.get_current_device()}") + print( + f"Vector: {N_ELEMENTS} bfloat16 elements " + f"({N_COMPLEX} interleaved complex I/Q samples)" + ) + print(f"Complex taps: {L}; silicon acceptance: max absolute error <= {ABS_TOL}") + + _run_local_reference_checks() + + rng = np.random.RandomState(RANDOM_SEED) + Ix = rng.uniform(-1.0, 1.0, N_COMPLEX).astype(np.float32) + Qx = rng.uniform(-1.0, 1.0, N_COMPLEX).astype(np.float32) + input_bf16 = _pack_iq(Ix, Qx) + output_bf16 = np.zeros(N_ELEMENTS, dtype=bfloat16) + + input_tensor = XRTTensor(input_bf16, dtype=bfloat16) + output_tensor = XRTTensor(output_bf16, dtype=bfloat16) + + print("Compiling the 8-tap complex FIR and dispatching to the NPU...") + result = complex_fir( + input_tensor, + output_tensor, + N=N_ELEMENTS, + element_type=bfloat16, + ) + print(f"Kernel execution result: {result}") + + # XRTTensor.numpy() is the public API in the pinned MLIR-AIE v1.4.1 release. It + # reconciles device output to host memory before returning the NumPy view. + actual = np.asarray(output_tensor.numpy()) + expected = complex_fir_reference(input_bf16) + max_error = _assert_abs_close( + actual, + expected, + "silicon random-I/Q validation", + ) + + print(f"Input sample [0..3]: {input_bf16[:4]}") + print(f"Reference [0..3]: {expected[:4]}") + print(f"Silicon output [0..3]: {actual[:4]}") + print(f"Maximum absolute error: {max_error:.6f}") + print( + "SUCCESS: the NPU output is finite and satisfies the " + f"absolute-tolerance contract (<= {float(ABS_TOL):.6f})." + ) + print("PASS!") + + +if __name__ == "__main__": + main() diff --git a/Developer_Contributed/README.md b/Developer_Contributed/README.md index 73fa8a7d1..cccaa0ef9 100644 --- a/Developer_Contributed/README.md +++ b/Developer_Contributed/README.md @@ -41,12 +41,18 @@ Versal Custom Thin Platform Extensible System Versal VCK190 System Example Design based on a thin custom platform (Minimal clocks and AXI exposed to PL) including HLS/RTL kernels and AI Engine kernel using a full Makefile build-flow. + DSP Design on AI Engine with GUI and Makefile Flows This tutorial implements a FIR filter from the Vitis DSP Library into the AI Engine domain, either with Makefile or GUI based flows. The design runs on the VCK190 board. The host application applies XRT APIs and Petalinux. + Vitis HLS Optimization Techniques on Embedded Boards This tutorial illustrates some C/C++ code optimization techniques for high performance with Vitis HLS. Some HLS are also implemented into ZCU102 or VCK190 target boards with the Vitis HW Acceleration flow. + +Complex FIR on the AMD Ryzen AI NPU with IRON +This tutorial implements an 8-tap causal complex FIR on a Phoenix XDNA1 NPU using the open-source IRON/MLIR-AIE Python flow and validates the silicon output against a NumPy reference. + From 679eb43f8881a77474182f410d3ee903ca69b7b3 Mon Sep 17 00:00:00 2001 From: MIDHAT NASHAR Date: Mon, 17 Aug 2026 02:30:53 +0300 Subject: [PATCH 2/2] M19: improve missing dependency preflight errors Signed-off-by: MIDHAT NASHAR --- .../src/check_environment.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/check_environment.py b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/check_environment.py index 908ce0a13..beb5ce659 100644 --- a/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/check_environment.py +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/check_environment.py @@ -3,13 +3,7 @@ """Validate the Python packages required by the M19 tutorial.""" import sys -from importlib.metadata import version - -import aie -import ml_dtypes -import numpy -import pyxrt -from numpy.lib import NumpyVersion +from importlib.metadata import PackageNotFoundError, version def main(): @@ -17,9 +11,26 @@ def main(): "mlir-aie": "1.4.1", "llvm-aie": "21.0.0.2026080301+c9c5ecb7", } - actual = {name: version(name) for name in expected} + try: + actual = {name: version(name) for name in expected} + except PackageNotFoundError as exc: + raise RuntimeError( + f"required Python distribution is not installed: {exc.name}" + ) from None if actual != expected: raise RuntimeError(f"package mismatch: expected {expected}, found {actual}") + + try: + import aie + import ml_dtypes + import numpy + import pyxrt + from numpy.lib import NumpyVersion + except ModuleNotFoundError as exc: + raise RuntimeError( + f"required Python module is not importable: {exc.name}" + ) from None + if not ( NumpyVersion("2.5.1") <= NumpyVersion(numpy.__version__) < NumpyVersion("3.0.0") ):