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; // 最小负值(验证符号处理)
}
与其他模块的关系
上游依赖
- Vitis_Platform_Creation_Tutorials: 父模块,提供平台验证的整体策略和架构上下文
- export_to_vivado_baseline_system_connectivity: AIE 验证器需要配合基础系统配置使用,理解
system.cfg中的连接关系
横向协作
- platform_validation_pl_reference_data_movers: PL 验证数据搬运器。AIE 验证和 PL 验证通常分阶段进行,先验证纯 PL 通路,再验证 AIE+PL 集成。
下游消费者
- 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 期望的帧大小匹配 |