Skip to content

Repository files navigation

vd100-aie-ma-crossover

AIE-ML Moving Average Crossover kernel for the VD100 (XCVE2302).
Part of the vd100-aie-pipeline Vitis system project.


Overview

This component implements a dual moving-average crossover trading signal detector running entirely on the AIE-ML array of the Versal AI Edge VE2302 (VD100 board).

Price ticks stream into the AIE graph via an HLS mm2s DMA kernel. Each iteration the graph outputs three int32 values — fast MA, slow MA, and a BUY/SELL/HOLD signal — which are collected by an HLS s2mm DMA kernel into PS DDR for the host application to read.

PS DDR (int32[])
    │
    ▼
mm2s (HLS) ──AXI4-Stream──► AIE mygraph (ma_crossover)
                                         │
                              AXI4-Stream▼
                             s2mm (HLS) ──► PS DDR (int32[])

Algorithm

MA Crossover Logic

Each AIE iteration receives BLOCK_SIZE=56 new int32 price samples.
An ADF margin of SLOW_MA_PERIOD=50 samples carries history from the previous iteration, giving the kernel 106 samples total to work with.

Constant Value Description
FAST_MA_PERIOD 10 Fast moving average window (periods)
SLOW_MA_PERIOD 50 Slow moving average window (periods)
BLOCK_SIZE 56 int32 input samples per AIE iteration
NUM_MARGIN_SAMPLES 56 ADF margin size — must equal BLOCK_SIZE
Output per block 3 { fast_ma, slow_ma, signal }

Signal encoding:

Value Meaning
1 BUY — fast MA crossed above slow MA
-1 SELL — fast MA crossed below slow MA
0 HOLD — no crossover

Crossover detection uses previous-vs-current MA comparison:

BUY:  prev_fast_ma <= prev_slow_ma  AND  fast_ma > slow_ma
SELL: prev_fast_ma >= prev_slow_ma  AND  fast_ma < slow_ma
HOLD: otherwise

Buffer Layout

ptr[0  .. 49]   = margin (50 samples — history from previous iteration)
ptr[50 .. 105]  = new samples this iteration (56 samples)
                  total = 106 samples available to kernel

Source Files

vd100-aie-ma-crossover/
├── src/
│   ├── kernels/
│   │   ├── kernels.cc        # AIE kernel: ma_crossover<M> template
│   │   └── include.h         # Constants: FAST_MA_PERIOD, SLOW_MA_PERIOD, BLOCK_SIZE
│   ├── project.h             # MAGraph class (ADF graph definition)
│   ├── project.cpp           # Graph instantiation: MAGraph mygraph; main() for sim
│   ├── kernels.h             # Kernel function declarations
│   ├── mm2s.cpp              # HLS PL kernel: PS DDR → AXI4-Stream → AIE
│   └── s2mm.cpp              # HLS PL kernel: AIE → AXI4-Stream → PS DDR
├── data/
│   ├── input.txt             # Golden test input (4 blocks × 56 samples)
│   └── golden.txt            # Expected output for simulation verification
├── aiecompiler.cfg           # AIE compiler configuration
├── CMakeLists.txt            # Build config (for Vitis system project integration)
└── vitis-comp.json           # Vitis component descriptor

HLS Kernel Signatures

// mm2s — streams from PS DDR into AIE
void mm2s(ap_int<32>* mem, hls::stream<ap_axis<32,0,0,0>>& s, int size);

// s2mm — collects AIE output into PS DDR
void s2mm(ap_int<32>* mem, hls::stream<ap_axis<32,0,0,0>>& s, int size);

Both kernels: mem = arg[0]group_id(0) for XRT BO allocation.
size is in bytes (not element count) — the host must pass count * sizeof(int32_t).


Build

This component is built as part of the vd100_ma_system_project Vitis system project, not standalone. The system project handles:

  1. AIE compilation (v++ --compile --target hw)
  2. HLS kernel synthesis (v++ --compile --target hw)
  3. Link (platform + AIE + HLS kernels → aie.xclbin)
  4. Package → BOOT.BIN components

AIE Compilation (reference)

v++ --compile \
    --target hw \
    --platform <path_to_vd100_platform>/vd100_platform.xpfm \
    --config aiecompiler.cfg \
    -o aie.o \
    src/project.cpp

Simulation

The project.cpp main() function runs 4 iterations using data/input.txt. Compare output against data/golden.txt to verify correctness:

# x86 simulation (fast, no hardware needed)
v++ --compile --target x86sim ...
# or via Vitis IDE: Run → AIE Simulation

Critical: BOOT.BIN Must Include AIE CDO Partitions

This is the most important undocumented gotcha for Versal AIE under Linux.

The Yocto xilinx-bootbin recipe by default generates a BOOT.BIN that does not include the AIE partition initialisation CDOs. Without them, PLM has no AIE data to process at boot and all tiles remain clock_gated permanently. XRT then fails silently in an infinite retry loop when graph.run() is called.

Symptom

# All tiles clock_gated — XRT hammers ioctl, app hangs
cat /sys/class/aie/aieaperture_0_17/aiepart_0_17/core
0_2: clock_gated
...
# dmesg floods with:
aie aiepart_0_17: Tile(7,2) is gated. Failed to write to 0xe200004

Root Cause

The v++ package step generates two CDO files that must be in BOOT.BIN:

Partition File Purpose
aie_dev_part libadf/sw/aie.cdo.device.partition.reset.bin AIE partition device init
aie_image aie.merged.cdo.bin Tile init, clock enable, ELF load

Without aie.merged.cdo.bin: PLM never enables AIE column clocks → tiles stay gated.

Fix — Yocto bbappend

Add to meta-vd100_v3/recipes-bsp/bootbin/xilinx-bootbin_1.0.bbappend:

FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

SRC_URI += "file://aie.cdo.device.partition.reset.bin \
            file://aie.merged.cdo.bin"

python do_configure:append() {
    workdir = d.getVar('WORKDIR')
    b = d.getVar('B')
    for f in ['aie.cdo.device.partition.reset.bin', 'aie.merged.cdo.bin']:
        src = os.path.join(workdir, f)
        dst = os.path.join(b, f)
        shutil.copyfile(src, dst)
        bb.note('VD100 AIE: copied %s -> %s' % (src, dst))
    fp = d.getVar('BIF_FILE_PATH')
    with open(fp, 'r') as fh:
        content = fh.read()
    content = content.rstrip()
    if content.endswith('}'):
        content = content[:-1]
    content += """
\timage {
\t\tname=aie_dev_part, id=0x18800000
\t\t{ type=cdo, file=aie.cdo.device.partition.reset.bin }
\t}
\timage {
\t\tname=aie_image, id=0x18800000
\t\t{ type=cdo, file=aie.merged.cdo.bin }
\t}
}"""
    with open(fp, 'w') as fh:
        fh.write(content)
    bb.note('VD100 AIE: BIF updated with aie_dev_part and aie_image')
}

Copy the CDO files from the v++ package output:

# Source locations after v++ package
libadf/sw/aie.cdo.device.partition.reset.bin
package/aie.merged.cdo.bin

# Destination
meta-vd100_v3/recipes-bsp/bootbin/files/aie.cdo.device.partition.reset.bin
meta-vd100_v3/recipes-bsp/bootbin/files/aie.merged.cdo.bin

Note: os and shutil imports are not needed in the bbappend — they are already in scope from the parent recipe's do_configure. Adding import os will cause UnboundLocalError due to Python/BitBake scoping behaviour.

Verify BOOT.BIN

bootgen -read BOOT.BIN -arch versal 2>/dev/null | grep -E "Image:|aie"

Expected output:

+---Image: aie2_subsys [id:0x421c028]
+---Image: aie_dev_part [id:0x18800000]
+---Image: aie_image [id:0x18800000]

Critical: zocl DT Node

The zocl kernel driver must be configured with 32 IRQs from the AXI interrupt controller, not 4 hardcoded GIC SPI interrupts.

Generate the correct DT node using sdtgen:

sdtgen set_dt_param -dir sdt_out -zocl enable

Without this, zocl probes with wrong IRQ configuration, hardware state is reported incorrectly (tiles show reset instead of clock_gated), and AXI errors cause kernel panics instead of returning EINVAL.


Golden Test Vector

data/input.txt — 4 blocks × 56 samples:

Block Content Expected Output
1 56 × 5000 fast=5000, slow=5000, HOLD
2 56 × 4990 fast=4990, slow=4990, HOLD
3 55 × 4990 + 1 × 5600 fast=5051, slow=5002, BUY
4 56 × 5600 fast=5600, slow=5600, HOLD

Block 3 triggers the crossover: a single 5600 spike pushes the fast MA above slow MA.


Hardware

Item Value
Board VD100 (XCVE2302-SFVA784-1LP-E-S)
AIE tile col=8, row=0 (shim row)
PL clock 100 MHz
Timing closure WNS 4.217ns, WHS 0.018ns
XRT version 2025.2
Vitis version 2025.2
Kernel 6.12.40-xilinx (stock, no patches)

Lessons Learned

  • BOOT.BIN CDO gap — the most impactful undocumented issue. No error is raised; tiles silently remain clock_gated. Always verify with bootgen -read.
  • No kernel patches required — stock kernel + correct zocl DT node + correct BOOT.BIN CDOs = working pipeline. All prior kernel workarounds were symptoms of the missing CDOs.
  • JTAG interference — connecting Vitis JTAG while the XRT application is running causes the AIE array to be partially reinitialised, producing zero output. Always disconnect JTAG before running the PS host application.
  • sdtgen -zocl enable is mandatory — without it, zocl uses the wrong IRQ configuration and reports incorrect hardware state.
  • ADF vs AIE Graph APIgraph.run() (no args) is ADF; graph.run(N) is AIE Graph API. Do not mix. Use graph.run(-1) for indefinite iterations.

About

AIE-ML moving average crossover kernel for VD100 (XCVE2302). Dual MA (fast 10 / slow 50 period), BUY/SELL/HOLD signal. 56 int32 samples/iteration via HLS DMA. Used in vd100-aie-pipeline. Vitis 2025.2.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages