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 Vitis™ Developer Contributed Tutorials+ See Vitis™ Development Environment on amd.com + |
+
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..beb5ce659 --- /dev/null +++ b/Developer_Contributed/04-Complex_FIR_on_Ryzen_AI_NPU_with_IRON/src/check_environment.py @@ -0,0 +1,50 @@ +# Copyright (c) 2026 Midhat Nashar +# SPDX-License-Identifier: MIT +"""Validate the Python packages required by the M19 tutorial.""" + +import sys +from importlib.metadata import PackageNotFoundError, version + + +def main(): + expected = { + "mlir-aie": "1.4.1", + "llvm-aie": "21.0.0.2026080301+c9c5ecb7", + } + 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") + ): + 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