Farrow 滤波器流式 IO 集成模块
概述
Farrow 滤波器流式 IO 集成模块是 Versal ACAP 平台上 AI Engine (AIE) 与可编程逻辑 (PL) 之间的高速数据搬运层。想象一下,它就像是连接两个高速运转城市的专用货运铁路系统——AI Engine 是计算密集型工厂,而 PL 则是负责原材料输入和成品输出的物流枢纽。
该模块的核心使命是解决一个关键问题:如何在保持 1+ Gsps 采样率的同时,将大量数据从外部 DDR 内存高效地输送到 AIE 进行实时分数延迟滤波处理,并将结果回写。这不是简单的"搬运工"角色,而是一个精心编排的流水线系统,需要在时钟域转换、数据宽度匹配和吞吐量保证之间取得精妙平衡。
数据源1] -->|128-bit AXI-Stream| PLIO0[PLIO_i_0] SRC2[farrow_dma_src
数据源2] -->|128-bit AXI-Stream| PLIO1[PLIO_i_1] SNK[farrow_dma_snk
数据汇聚] <-->|128-bit AXI-Stream| PLIO2[PLIO_o_0] end subgraph AIE["AI Engine Array @ 1250 MHz"] K1[farrow_kernel1
FIR滤波阶段] -->|y3,y2,y1,y0| K2[farrow_kernel2
Horner规则阶段] K2 --> sig_o[输出信号] end DDR[(LPDDR4)] -.->|AXI4-MM| SRC1 DDR -.->|AXI4-MM| SRC2 SNK -.->|AXI4-MM| DDR PLIO0 --> K1 PLIO1 --> K2 PLIO2 --> SNK style PL fill:#e1f5fe style AIE fill:#fff3e0 style DDR fill:#f3e5f5
架构设计:三层数据流模型
第一层:DDR ↔ PL 的数据搬运(DMA 引擎)
系统的起点和终点都是 LPDDR4 内存。为了维持高吞吐量,我们使用两个独立的 HLS DMA 源核 (dma_src1, dma_src2) 和一个 DMA 汇聚核 (dma_snk)。这种分离不是随意的——它反映了 Farrow 滤波器的双输入特性:一路是信号样本 (sig_i),另一路是延迟参数 (del_i)。
关键设计决策:为什么选择 128-bit 数据宽度?
- AIE 运行在 1250 MHz,每个 PLIO 端口每周期传输 32-bit
- PL 运行在 312.5 MHz(即 1250/4)
- 通过 4:1 的时钟比,128-bit @ 312.5 MHz = 32-bit @ 1250 MHz
- 这样实现了时钟域的自然对齐,无需复杂的异步 FIFO
第二层:PL ↔ AIE 的流式桥接(PLIO 接口)
PL 和 AIE 之间的通信通过 AXI4-Stream 协议完成。这里的关键抽象是 PLIO (Processor Logic I/O) —— 它是 Xilinx 定义的跨域边界点。
第三层:AIE 内部的计算流水线
最终的 AIE 实现采用了双核协作架构,这是性能优化的关键成果:
- farrow_kernel1: 执行四个并行的 8-tap FIR 滤波(对称/反对称),生成中间结果 y3, y2, y1, y0
- farrow_kernel2: 执行 Horner 规则的嵌套乘法累加,将中间结果合成为最终输出
这种拆分解决了单核无法同时满足 II=16 周期约束的问题——就像一条生产线如果工序太多,拆成两段并行反而更快。
核心组件详解
1. farrow_dma_src_wrapper - 数据源头引擎
位于 hls/farrow_dma_src/farrow_dma_src.cpp,这是一个经典的双阶段数据流模式:
#pragma HLS DATAFLOW
// 阶段1: 从 DDR 加载到片上 BRAM
load_buffer(mem, buff);
// 阶段2: 从 BRAM 流式输出到 AXI-Stream
transmit(buff, sig_o, loop_cnt);
为什么需要这个两阶段设计?
想象你在搬家:load_buffer 相当于先把散落的物品装进箱子(DDR → BRAM),transmit 则是把箱子整齐地搬上货车(BRAM → Stream)。直接零散搬运效率极低,而批量装箱可以充分利用 DRAM burst 传输的带宽优势。
关键参数:
DEPTH = 1024: 缓冲深度,对应 4096 个 32-bit 样本(因为每次传输 4 个打包样本)loop_cnt: 重复传输次数,用于测试多轮迭代场景NBITS = 128: 总线宽度,打包了 4 个cint16或int32样本
2. farrow_dma_snk_wrapper - 数据汇聚引擎
位于 hls/farrow_dma_snk/farrow_dma_snk.cpp,结构与源端对称但增加了选择性捕获功能:
capture_streams(buff, sig_i, loop_sel, loop_cnt);
read_buffer(mem, buff);
loop_sel 参数的巧妙之处:在多轮迭代测试中,我们可能只想保存某一轮的输出进行验证。loop_sel 让硬件在运行时决定哪一轮数据值得写入 DDR,避免了不必要的数据传输。
3. system.cfg - 系统集成蓝图
这是 Vitis 编译器的配置脚本,定义了完整的系统拓扑:
# 时钟统一配置:所有 DMA 核共享 312.5 MHz
freqhz=312500000:dma_src1.ap_clk,dma_src2.ap_clk,dma_snk.ap_clk
# 内核实例化:声明我们需要 2 个源核 + 1 个汇聚核
nk = farrow_dma_src_wrapper:2:dma_src1,dma_src2
nk = farrow_dma_snk_wrapper:1:dma_snk
# 内存映射:连接到 LPDDR
sp=dma_src1.mem:LPDDR
sp=dma_src2.mem:LPDDR
sp=dma_snk.mem:LPDDR
# 流式连接:PL ↔ AIE
sc = dma_src1.sig_o:ai_engine_0.PLIO_i_0
sc = dma_src2.sig_o:ai_engine_0.PLIO_i_1
sc = ai_engine_0.PLIO_o_0:dma_snk.sig_i
数据流全景追踪
让我们跟随一个样本的完整旅程:
设计权衡与工程决策
权衡 1: 单宽总线 vs. 多窄总线
选择: 使用 128-bit 总线 @ 312.5 MHz,而非 32-bit @ 1250 MHz
理由:
- PL 侧更容易达到较低频率的时序收敛
- 减少跨时钟域 (CDC) 逻辑的复杂度
- 128-bit 对齐天然适配 4 个
cint16样本的向量化处理
代价: 需要额外的打包/解包逻辑,但在 HLS 中由编译器自动处理
权衡 2: 乒乓缓冲 vs. 单缓冲
选择: 内部使用双缓冲 (ping-pong),对外呈现单缓冲语义
理由:
load_buffer和transmit可以并行执行(DATAFLOW)- 隐藏 DDR 访问延迟:当一帧数据被流式输出时,下一帧正在从 DDR 加载
注意: 当前实现使用的是顺序执行模式(先 load 后 transmit),若要真正启用 ping-pong 并行,需要更复杂的索引管理
权衡 3: 双核拆分 vs. 单核优化
选择: 将 Farrow 滤波拆分到两个 AIE 核
背景: 初始单核实现的 II=123,远未达到 II=16 的目标。瓶颈在于:
- 向量寄存器溢出(register spilling)
- SRS (Shift-Round-Saturate) 路径的限制
- 过多的 MAC 操作无法在单核内流水化
收益:
- kernel1 专注 FIR 滤波(II=16)
- kernel2 专注 Horner 求值(三个循环各 II=3)
- 总吞吐量达到 1115 Msps,超过 1 Gsps 目标
代价: 增加了核间通信开销(通过乒乓缓冲),但这是 AIE 架构擅长处理的
新贡献者必读:陷阱与注意事项
1. 数据格式对齐陷阱
危险: del_i_optimized.txt 和 del_i.txt 格式不同!
在 host.cpp 中可以看到:
std::ifstream del_i;
del_i.open("del_i_optimized.txt", std::ifstream::in); // 注意是 _optimized 版本
优化后的格式将 16-bit 延迟值连续放置,低位补零,这使得 AIE 可以直接用 vector_cast 提取,避免了昂贵的 filter_even 操作。如果使用错误的输入文件,延迟值会被错误解析!
2. Loop Count 的双重含义
在 host.cpp 中:
static constexpr int32_t LOOP_CNT_I = 4; // DMA 源端循环次数
static constexpr int32_t LOOP_CNT_O = 4; // DMA 汇聚端期望的循环次数
而在 AIE graph 中:
my_graph.run(NUM_ITER); // NUM_ITER = -1 表示无限运行
理解: DMA 核负责控制数据流动的"批次",而 AIE graph 一旦启动就持续运行。终止条件实际上由 dma_snk 的 loop_cnt 参数控制——当它接收到指定数量的循环后就停止,从而间接停止了数据消费,整个系统自然停顿。
3. HLS INTERFACE pragma 的微妙之处
#pragma HLS interface m_axi port=mem bundle=gmem offset=slave depth=DEPTH
#pragma HLS interface s_axilite port=mem bundle=control
注意到 mem 同时出现在 m_axi 和 s_axilite 中?这是 Vitis 的标准做法:
m_axi: 定义数据传输接口(实际的数据搬运通道)s_axilite: 定义控制接口(用于传递基地址等标量参数)
新手常犯错误: 遗漏 s_axilite 会导致 XRT 无法正确配置 DMA 地址。
4. 时钟频率的隐性契约
system.cfg 中设置的 312.5 MHz 必须与以下保持一致:
- HLS 核的
clock=3.2ns约束(约 312.5 MHz) - AIE 的 1250 MHz 时钟(4 倍频关系)
如果不一致,PLIO 接口的宽度-频率换算就会出错,导致数据损坏或时序违例。
5. 验证容差说明
在 host.cpp 的结果验证部分:
flag |= ( err_re > 5 ) || ( err_im > 5 ); // Matlab is not bit accurate
重要: MATLAB 参考模型使用浮点运算,而 AIE 实现使用定点运算(cint16 × int16)。允许最大 5 LSB 的误差是正常的,不代表实现错误。如果你看到误差在这个范围内,系统是正常工作的。
子模块文档
本模块包含以下核心子模块,每个都有专门的文档页面详细说明其实现细节:
DMA Source Kernel
职责:从 LPDDR4 读取数据并通过 AXI4-Stream 发送到 AIE
关键文件:
hls/farrow_dma_src/farrow_dma_src.cpphls/farrow_dma_src/farrow_dma_src.hhls/farrow_dma_src/hls.cfg
核心组件:farrow_dma_src_wrapper —— HLS 顶层函数,实现 DDR-to-Stream 的数据搬运
DMA Sink Kernel
职责:从 AIE 接收 AXI4-Stream 数据并写回 LPDDR4
关键文件:
hls/farrow_dma_snk/farrow_dma_snk.cpphls/farrow_dma_snk/farrow_dma_snk.hhls/farrow_dma_snk/hls.cfg
核心组件:farrow_dma_snk_wrapper —— HLS 顶层函数,实现 Stream-to-DDR 的数据搬运,支持选择性捕获功能
System Configuration
职责:定义系统级的连接关系、时钟配置和内核实例化
关键文件:
vitis/system.cfg
核心组件:
dma_src1,dma_src2—— 两个 Source 实例dma_snk—— Sink 实例
与相关模块的关系
本模块是 farrow_filter_design_variants 教程的系统集成层。设计演进路径如下:
- farrow_baseline_graph: 纯 AIE 功能验证
- farrow_opt_1/opt_2: 逐步优化 II
- farrow_final: 双核最终实现
- 本模块 (farrow_filter_streaming_io_integration): 添加 PL DMA 层,形成完整系统
上游依赖:
- versal_integration_data_movers: 通用的数据搬运模式
- prime_factor_fft_system_integration: 类似的 DMA 集成参考
下游使用者:
- 完整的 VCK190 硬件部署流程
- 作为其他需要高吞吐量流式输入的 AIE 设计的模板
性能指标总结
| 指标 | 数值 | 备注 |
|---|---|---|
| 目标采样率 | 1000 Msps | 设计规格 |
| 实测稳定吞吐 | ~1115 Msps | 最终优化版本 |
| PL 时钟频率 | 312.5 MHz | 便于时序收敛 |
| AIE 时钟频率 | 1250 MHz | 标准 AIE 频率 |
| 数据总线宽度 | 128-bit (PL), 32-bit (AIE) | 4:1 比例匹配 |
| 内部缓冲深度 | 1024 × 128-bit | 支持 4 轮迭代 |
| 端到端延迟 | ~14.4 μs (4096 样本) | 含启动开销 |
本文档基于 Vitis-Tutorials 15-farrow_filter 设计编写,适用于 Vitis 2024.2 及兼容版本。