🏠

Platform Validation AIE 参考数据搬运器

一句话概述

本模块提供验证 AIE(AI Engine)数据通路的参考实现——通过标准化的 MM2S(内存到流)和 S2MM(流到内存)HLS 内核,构成可重用的测试桩(test stub),用于验证 AIE 图的输入输出接口、数据格式转换和流控制协议的正确性。


核心组件

1. mm2s_1.cfg —— AIE 验证用 MM2S 内核配置

文件路径Vitis_Platform_Creation/Feature_Tutorials/04_platform_validation/ref_files/aie_validation/mm2s.cfg

配置内容

[hls]
flow_target=vitis
syn.file=mm2s_1.cpp
syn.top=mm2s_1
package.ip.name=mm2s_1
package.output.format=xo
syn.dataflow.strict_mode=warning
syn.debug.enable=1
syn.interface.m_axi_addr64=1
syn.interface.m_axi_auto_max_ports=0
syn.interface.m_axi_conservative_mode=1
syn.rtl.deadlock_detection=sim
syn.rtl.kernel_profile=1
package.output.syn=true

关键配置项解析

配置项 说明
syn.top=mm2s_1 顶层函数名 内核入口点
syn.interface.m_axi_addr64=1 启用 64-bit 地址 支持 >4GB DDR 空间访问
syn.interface.m_axi_conservative_mode=1 保守 AXI 时序 增加握手机制可靠性,适合验证
syn.rtl.deadlock_detection=sim 死锁检测 仿真时检测数据流死锁
syn.rtl.kernel_profile=1 性能计数器 记录实际执行周期数

2. s2mm_1.cfg —— AIE 验证用 S2MM 内核配置

文件路径Vitis_Platform_Creation/Feature_Tutorials/04_platform_validation/ref_files/aie_validation/s2mm.cfg

配置内容

[hls]
flow_target=vitis
syn.file=s2mm_1.cpp
syn.top=s2mm_1
package.ip.name=s2mm_1
package.output.format=xo
syn.dataflow.strict_mode=warning
syn.debug.enable=1
syn.interface.m_axi_addr64=1
syn.interface.m_axi_auto_max_ports=0
syn.interface.m_axi_conservative_mode=1
syn.rtl.deadlock_detection=sim
syn.rtl.kernel_profile=1
package.output.syn=true

配置与 MM2S 的差异

虽然配置项几乎相同,但 S2MM 的顶层函数 s2mm_1 实现了相反的数据流向:

  • MM2S: DDR (AXI-Full) → AIE (AXI-Stream)
  • S2MM: AIE (AXI-Stream) → DDR (AXI-Full)

MM2S/S2MM 内核实现原理

数据搬运器架构

MM2S (Memory-Mapped to Stream) 内核架构:
┌─────────────────────────────────────────────────────────────────┐
│                     mm2s_1 顶层模块                            │
│  ┌─────────────────────────────────────────────────────────────┐  │
│  │  AXI4-Full Master 接口 (m_axi)                               │  │
│  │  ├── 地址宽度: 64-bit                                       │  │
│  │  ├── 数据宽度: 512-bit (或 256-bit)                         │  │
│  │  └── 突发长度: 支持 1-256 beats                             │  │
│  └─────────────────────┬───────────────────────────────────────┘  │
│                        │ DDR 读请求                               │
│                        ▼                                          │
│  ┌─────────────────────────────────────────────────────────────┐  │
│  │  读数据 FIFO (ping-pong buffer)                             │  │
│  │  ├── 深度: 2×突发长度 (支持背靠背传输)                       │  │
│  │  └── 宽度: 与 AXI 数据宽度匹配                               │  │
│  └─────────────────────┬───────────────────────────────────────┘  │
│                        │ 数据打包                                  │
│                        ▼                                          │
│  ┌─────────────────────────────────────────────────────────────┐  │
│  │  AXI4-Stream Master 接口 (axis)                              │  │
│  │  ├── TDATA: 32-bit (匹配 AIE 接口宽度)                        │  │
│  │  ├── TVALID: 数据有效指示                                     │  │
│  │  ├── TREADY: 下游反压信号                                     │  │
│  │  └── TLAST: 包边界指示 (可选,用于帧同步)                      │  │
│  └─────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

关键设计参数:
- 突发长度:通常配置为 64 或 128 beats,最大化 DDR 带宽利用率
- FIFO 深度:2×突发长度,确保在地址仲裁延迟期间数据不丢失
- 数据宽度转换:512-bit AXI → 32-bit Stream,通过移位寄存器实现串并转换
- 保守模式:启用 `syn.interface.m_axi_conservative_mode`,增加写响应等待和读数据缓冲,提高稳定性

S2MM 内核的对称架构

S2MM (Stream to Memory-Mapped) 内核架构:
┌─────────────────────────────────────────────────────────────────┐
│                     s2mm_1 顶层模块                              │
│  ┌─────────────────────────────────────────────────────────────┐  │
│  │  AXI4-Stream Slave 接口 (axis)                               │  │
│  │  ├── TDATA: 32-bit (来自 AIE)                                 │  │
│  │  ├── TVALID: 上游数据有效                                     │  │
│  │  ├── TREADY: 本地下游反压 (FIFO 满时拉低)                     │  │
│  │  └── TLAST: 帧结束指示,触发写提交                             │  │
│  └─────────────────────┬───────────────────────────────────────┘  │
│                        │ 数据解包                                   │
│                        ▼                                          │
│  ┌─────────────────────────────────────────────────────────────┐  │
│  │  写数据 FIFO (乒乓缓冲)                                      │  │
│  │  ├── 深度: 2×突发长度                                         │  │
│  │  ├── 宽度: 512-bit (聚合后)                                   │  │
│  │  └── 触发条件: 满或 TLAST 或超时                              │  │
│  └─────────────────────┬───────────────────────────────────────┘  │
│                        │ AXI 写事务                                 │
│                        ▼                                          │
│  ┌─────────────────────────────────────────────────────────────┐  │
│  │  AXI4-Full Master 接口 (m_axi)                               │  │
│  │  ├── AW 通道: 写地址 (突发起始地址、长度、类型)                  │  │
│  │  ├── W 通道: 写数据 (512-bit 宽、WSTRB 字节使能)               │  │
│  │  ├── B 通道: 写响应 (等待 slave 确认)                         │  │
│  │  └── 支持非对齐传输和不同突发长度                               │  │
│  └─────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

关键设计要点:
1. TLAST 触发:AIE 在帧结束时置位 TLAST,S2MM 收到后立即提交 FIFO 数据到 DDR
2. 乒乓缓冲:双缓冲确保在写 DDR 期间仍能接收新数据
3. 字节使能:WSTRB 信号处理非 64-byte 对齐的帧尾数据
4. 超时机制:防止因缺少 TLAST 导致的无限等待

HLS 实现关键技术

1. 接口优化策略

// polar_clip 内核接口定义
void polar_clip(
    hls::stream<ap_axis<32, 0, 0, 0>> &in_sample,
    hls::stream<ap_axis<32, 0, 0, 0>> &out_sample
) {
    #pragma HLS INTERFACE ap_ctrl_none port=return
    #pragma HLS INTERFACE axis port=out_sample
    #pragma HLS INTERFACE axis port=in_sample
    
    // 内核逻辑...
}

接口指令解析

指令 含义 硬件影响
ap_ctrl_none 无块级控制协议 内核启动后持续运行,无 ap_start/ap_done/ap_idle 信号
axis AXI4-Stream 接口 生成 TDATA/TVALID/TREADY/TLAST 信号,直接连接 AIE/PL

为何选择 ap_ctrl_none

  • 流式处理模型:数据持续流入流出,无明确的"任务开始/结束"边界
  • 低延迟:消除块级控制信号的同步开销
  • 与 AIE 兼容:AIE 的 PLIO 也是流式接口,协议匹配

2. 数据类型优化

// 输入/输出:32-bit AXI-Stream 打包数据
typedef ap_axis<32, 0, 0, 0> axis_data_t;
// 模板参数: <TDATA_WIDTH, TUSER_WIDTH, TID_WIDTH, TDEST_WIDTH>
// 本例: 32-bit 数据,无用户/ID/目的信号

// 解包/打包函数
void unpack_sample(ap_axis<32,0,0,0> axis_data, ap_cint16 &sample) {
    // 低 16-bit: 实部,高 16-bit: 虚部
    sample.real = (ap_int<16>)(axis_data.data & 0xFFFF);
    sample.imag = (ap_int<16>)((axis_data.data >> 16) & 0xFFFF);
}

ap_axis<32,0,0,0> pack_sample(ap_cint16 sample) {
    ap_axis<32,0,0,0> result;
    result.data = ((ap_uint<32>)sample.imag << 16) | (ap_uint<16>)sample.real;
    result.keep = 0xF;  // 4 字节全部有效
    result.last = 0;    // 包边界由外部控制
    return result;
}

位宽设计决策

AXI4-Stream 数据包格式 (32-bit):
├────────────────────────────────┤
│ 31          16 │ 15           0 │
│    imag[15:0]  │   real[15:0]   │
│   (16 bits)    │   (16 bits)    │
└────────────────────────────────┘

设计理由:
1. 16-bit 实部/虚部是通信基带标准位宽
2. 32-bit 总线宽度匹配 AIE PLIO 的 32-bit 模式
3. 无 TUSER/TID/TDEST 简化协议,降低开销

3. 算法优化(CORDIC 实现细节)

// CORDIC 迭代核心
void cordic_iteration(
    ap_int<32> &X,      // 输入/输出: X 坐标
    ap_int<32> &Y,      // 输入/输出: Y 坐标
    ap_int<32> &Z,      // 输入/输出: 角度累加器
    int i               // 迭代索引
) {
    #pragma HLS INLINE  // 内联展开,消除函数调用开销
    
    // 查找表: arctan(2^-i) 的定点表示
    static const ap_int<32> atan_lut[16] = {
        2949120,  // arctan(2^0)  ≈ 45°
        1740967,  // arctan(2^-1) ≈ 26.565°
        919879,   // arctan(2^-2) ≈ 14.036°
        466945,   // arctan(2^-3) ≈ 7.125°
        234378,   // ...
        117304,
        58666,
        29335,
        14668,
        7334,
        3667,
        1833,
        917,
        458,
        229
    };
    
    // 迭代公式
    if (Y > 0) {
        // 顺时针旋转
        X = X + (Y >> i);
        Y = Y - (X >> i);
        Z = Z - atan_lut[i];
    } else {
        // 逆时针旋转
        X = X - (Y >> i);
        Y = Y + (X >> i);
        Z = Z + atan_lut[i];
    }
}

HLS 优化指令

// 迭代完全展开(消除循环开销)
#pragma HLS UNROLL factor=6  // 6 次迭代完全展开

// 或者使用 PIPELINE 实现 II=1
#pragma HLS PIPELINE II=1
for (int i = 0; i < 6; i++) {
    cordic_iteration(X, Y, Z, i);
}

// LUT 存储在 BRAM
#pragma HLS RESOURCE variable=atan_lut core=ROM_1P_BRAM

验证策略与测试用例

1. AIE 验证流程

┌─────────────────────────────────────────────────────────────────────┐
│                      AIE 验证测试流程                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  阶段 1: AIE 独立验证                                                │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐                           │
│  │  测试向量  │───→│  AIE    │───→│   golden  │                           │
│  │  (输入)   │    │ (待测)   │    │  (期望输出)│                           │
│  └─────────┘    └─────────┘    └─────────┘                           │
│       ↑                              ↓                              │
│       └────────────────── 对比验证 ←──┘                              │
│                                                                     │
│  阶段 2: PL 数据搬运器集成验证                                         │
│  ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐              │
│  │  DDR    │───→│  mm2s   │───→│   AIE   │───→│  s2mm   │───→│  DDR    │              │
│  │ (输入)   │   │ (PL DMA)│   │ (待测)   │   │ (PL DMA)│   │ (输出)   │              │
│  └─────────┘   └─────────┘   └─────────┘   └─────────┘   └─────────┘              │
│       ↑                                                        ↓                   │
│       └──────────────────── 对比验证 ←───────────────────────────┘                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2. 测试向量设计

// 测试向量 1: 零输入测试
// 验证:AIE 在零输入时的输出行为,检查是否有直流偏置
ap_cint16 zero_test[1024];
for (int i = 0; i < 1024; i++) {
    zero_test[i].real = 0;
    zero_test[i].imag = 0;
}

// 测试向量 2: 单音正弦测试 (数字振荡器)
// 验证:AIE 的频率响应和相位线性度
ap_cint16 tone_test[4096];
double freq = 2 * M_PI * 0.1;  // 归一化频率 0.1
for (int i = 0; i < 4096; i++) {
    tone_test[i].real = (short)(32767 * cos(freq * i));
    tone_test[i].imag = (short)(32767 * sin(freq * i));
}

// 测试向量 3: 脉冲响应测试
// 验证:AIE 的冲激响应和系统稳定性
ap_cint16 impulse_test[1024];
impulse_test[0].real = 32767;  // 单位脉冲
impulse_test[0].imag = 0;
for (int i = 1; i < 1024; i++) {
    impulse_test[i].real = 0;
    impulse_test[i].imag = 0;
}

// 测试向量 4: 最大幅度测试
// 验证:AIE 在最大输入时的饱和行为和溢出处理
ap_cint16 max_test[1024];
for (int i = 0; i < 1024; i++) {
    max_test[i].real = 32767;   // 最大正值
    max_test[i].imag = -32768;  // 最小负值(验证符号处理)
}

与其他模块的关系

上游依赖

横向协作

下游消费者

  • AIE_ML_Design_Graphs: 实际 AIE 设计使用本验证模块确认其接口正确性后,才能集成到完整系统

新贡献者指南

添加新的验证测试

若要为新的 AIE 图添加验证测试:

// 1. 在 mm2s_1/s2mm_1.cpp 中适配数据宽度(如果需要)
// 例如:如果 AIE 使用 64-bit 数据而非 32-bit

// 修改前
typedef hls::stream<ap_axiu<32, 0, 0, 0>> stream_t;

// 修改后
typedef hls::stream<ap_axiu<64, 0, 0, 0>> stream_t;

// 2. 在 host 代码中配置新的测试向量
// test_aie_new_graph.cpp

#include "xrt/xrt_bo.h"
#include "xrt/xrt_device.h"
#include "xrt/xrt_kernel.h"

int main(int argc, char** argv) {
    // 打开设备
    auto device = xrt::device(0);
    
    // 加载 XCLBIN
    auto xclbin = device.load_xclbin("aie_new_graph.xclbin");
    
    // 获取内核
    auto mm2s = xrt::kernel(device, xclbin, "mm2s_1");
    auto s2mm = xrt::kernel(device, xclbin, "s2mm_1");
    
    // 分配缓冲区
    size_t buf_size = 4096 * sizeof(int32_t);
    auto in_bo = xrt::bo(device, buf_size, mm2s.group_id(0));
    auto out_bo = xrt::bo(device, buf_size, s2mm.group_id(0));
    
    // 填充测试向量
    auto in_ptr = in_bo.map<int32_t*>();
    generate_test_vector(in_ptr, 4096, TEST_TONE_SINE);  // 单音测试
    in_bo.sync(XCL_BO_SYNC_BO_TO_DEVICE);
    
    // 运行内核
    auto run_mm2s = mm2s(in_bo, nullptr, 4096);  // buffer, unused, size
    auto run_s2mm = s2mm(out_bo, nullptr, 4096);
    
    // 等待完成
    run_mm2s.wait();
    run_s2mm.wait();
    
    // 读取结果
    out_bo.sync(XCL_BO_SYNC_BO_FROM_DEVICE);
    auto out_ptr = out_bo.map<int32_t*>();
    
    // 验证输出
    bool pass = verify_output(out_ptr, 4096, EXPECTED_TONE_RESPONSE);
    
    return pass ? 0 : 1;
}

调试 AIE 验证失败的步骤

问题:MM2S 发送数据但 S2MM 未接收到

诊断步骤:

1. 检查 AIE 图是否编译成功
   $ aiecompiler --workdir=./Work graph.cpp
   $ xchessmk -f Work/options
   确认 libadf.a 生成成功且无错误

2. 验证系统配置连接关系
   检查 system.cfg 中的 sc= 行:
   - mm2s 输出是否连接到 AIE 的正确输入端口
   - AIE 的输出是否连接到 s2mm 的输入
   
3. 使用 x86simulator 仿真 AIE 图
   $ x86simulator --pkg-dir=./Work
   检查 AIE 是否有数据输出到 PLIO

4. 在 Vivado 中添加 ILA (Integrated Logic Analyzer)
   - 探测 AIE 的 PLIO 接口信号 (TVALID/TREADY/TDATA)
   - 探测 MM2S 和 S2MM 的 AXI-Stream 接口
   - 运行时观察握手是否成功

5. 检查数据格式
   - AIE 期望的数据位宽 (32/64/128 bit)
   - 数据打包方式 (实部/虚部顺序)
   - 帧大小和对齐要求

6. 检查时钟和复位
   - AIE 时钟频率是否与 PL 时钟匹配
   - 复位信号是否正确释放
   - 跨时钟域 (CDC) 电路是否正常工作

常见原因和解决方案

现象 可能原因 解决方案
TVALID 始终为高但 TREADY 始终为低 AIE 未启动或死锁 检查 AIE 控制寄存器,确保 graph.run() 已调用
TVALID 和 TREADY 握手成功但数据全零 AIE 内核输出零或数据格式错误 检查 AIE 内核代码,验证输出数据生成逻辑
偶发数据丢失 CDC 电路亚稳态 确保 AIE 和 PL 时钟频率满足约束,检查 CDC FIFO 深度
仅前几个样本正确,后续错误 TLAST 处理不当导致 AIE 状态机错误 检查 MM2S 的 TLAST 生成逻辑,确保与 AIE 期望的帧大小匹配
On this page