🏠

滤波、多速率与Farrow设计模块 (filtering_multirate_and_farrow_designs)

一句话概述

本模块是AI引擎(AIE)数字信号处理(DSP)的核心实现库,专注于实时流式滤波多速率信号处理(抽取/插值/信道化)以及Farrow结构任意重采样。它展示了如何在Xilinx Versal AIE架构上实现从基础FIR到复杂多相滤波器组的完整设计空间探索。


为什么需要这个模块?

问题空间

在现代通信、雷达和电子战系统中,数字前端(DFE)需要处理:

  1. 宽带信号处理:GHz级采样率需要并行处理(SSR, Super Sample Rate)
  2. 灵活的信道化:将宽带信号分割到多个窄带信道(分析滤波器组)
  3. 任意速率转换:非整数倍抽取/插值(Farrow结构避免重算滤波器系数)
  4. 硬件实现效率:在AIE的SIMD向量处理器上最大化MAC利用率

解决策略

本模块采用分层抽象策略:

  • 底层:AIE内核级优化(向量MAC、循环展开、双缓冲)
  • 中层:ADF图拓扑(数据流连接、PLIO接口、参数化SSR)
  • 顶层:系统集成(XRT控制、DMA数据搬运、HLS协同设计)

心智模型:数字信号处理的"工厂流水线"

想象一个24小时不间断运转的信号处理工厂

1. 原料进货区(PLIO接口)

  • 卡车卸货 = PL侧DMA搬运数据到AIE阵列边界
  • 集装箱规格 = 64位或128位PLIO数据宽度(plio_64_bits, plio_32_bits
  • 提货单 = 数据文件(.txt仿真激励)或实时流(硬件运行时)

2. 组装车间(AIE Graph)

  • 生产线布局 = ADF graph 类定义的数据流拓扑
  • 工位(Kernel) = 实际执行MAC运算的AIE tile
  • 传送带(Stream) = AIE内部 cascade/stream 连接,零延迟数据传输
  • 在制品缓存 = ping-pong buffer(双缓冲隐藏数据搬运延迟)

3. 质量控制(多速率处理)

  • 抽样检查站 = 抽取(Decimation):每N个样品保留1个
  • 插值填充站 = 插值(Interpolation):在样品间插入零,再滤波
  • 信道分拣线 = 多相滤波器组:并行处理多个子带

4. 精密加工(Farrow结构)

  • 数控加工中心 = 多项式插值:根据分数延迟μ动态计算滤波器输出
  • 刀具补偿 = 泰勒级数展开:y(t) = x(n) + μ·Δx + μ²·Δ²x/2 + ...
  • 实时补偿 = 不需要重算滤波器系数,仅需调整多项式系数

5. 成品出货(输出PLIO)

  • 包装发货 = 处理后的数据流回传到PL侧DMA
  • 物流跟踪 = XRT API监控图执行状态(graph.run(), graph.end()

架构全景图

graph TB subgraph Host["Host Control (ARM/x86)"] XRT[XRT Runtime API
xrt::graph, xrt::kernel] CTRL[Graph Control
init/run/end] end subgraph PL["Programmable Logic (PL)"] DMA[DMA Data Movers
mm2s/s2mm] HLS[HLS Kernels
fir_hls, datamover] PLIO[PLIO Interfaces
64/128-bit streams] end subgraph AIE["AI Engine Array (AIE)"] subgraph GraphTop["Top-Level Graphs"] FarrowG[Farrow Filter Graphs
initial/opt1/opt2/final] PolyG[Polyphase FIR Graph
m16_ssr8] FIRG[FIR Chain Graph
AIE vs HLS] SSRG[SSR Two-Tone Graph
fir2] end subgraph Kernels["Kernel Library"] FIRK[FIR Kernels
sliding_mac_sym] DDC[DDC Stage Kernels
fir_89t_sym_buf] FarrowK[Farrow Polynomial
fractional delay] end subgraph Buffers["Buffer Management"] PingPong[Ping-Pong Buffers
buffer_internal] CircBuff[Circular Buffers
cyclic_add] end end Host -->|xrt::device| PL PL -->|stream| AIE AIE -->|stream| PL PL -->|DMA| Host GraphTop --> Kernels GraphTop --> Buffers Kernels --> Buffers

关键组件职责

层级 组件 职责 关键文件
Host XRT Controller 设备初始化、图生命周期管理 fir_aie_app.cpp
PL DMA Movers 数据搬运 mm2s/s2mm datamover_class
PL HLS Kernels HLS实现的FIR对比 fir_hls.h, fir_class
AIE Top Graphs ADF图定义、PLIO连接 *_app.cpp (各子目录)
AIE FIR Kernels 向量MAC运算 ddc_kernel_stage.h
AIE Buffer Mgmt 循环缓冲区、指针运算 buffer_internal

数据流端到端追踪

Farrow滤波器的典型处理流程为例,展示数据如何从Host流向AIE再返回:

1. 初始化阶段(配置工厂)

// Host侧:打开设备并加载xclbin
auto dhdl = xrtDeviceOpen(0);
auto xclbin = load_xclbin(dhdl, xclbinFilename);
auto top = reinterpret_cast<const axlf*>(xclbin.data());

// 初始化ADF图
fir_graph.init(dhdl, top);  // 调用xrtGraphOpen,建立Host与AIE的通信通道

发生了什么:XRT解析xclbin(包含编译后的AIE配置和PL比特流),初始化AIE阵列的互连和DMA引擎。

2. 数据注入阶段(原料进厂)

// 配置DMA数据搬运器
datamover_krnl.init(dhdl, top, iterCnt);
// 设置DMA参数:源地址、目的地址、传输长度

// 启动AIE图(开始处理)
fir_graph.run();  // xrtGraphRun,释放AIE内核的stall信号

// 启动DMA(开始数据搬运)
datamover_krnl.run();  // xrtRunStart,DMA开始从DDR读取数据推向AIE

数据流动

DDR (Host) → DMA (PL) → PLIO (AIE边界) → Stream (AIE内部) → Kernel (处理)

关键细节:PLIO的plio_64_bits定义了每周期传输64位(4个cint16),这是AIE向量宽度(256位)的1/4,需要4个周期填满一个向量。

3. 内核处理阶段(车间加工)

在AIE内部,Farrow滤波器执行以下运算(以farrow_final为例):

// 内核级伪代码(实际为AIE intrinsic)
for each sample:
    // 1. 读取输入信号和分数延迟μ
    x = read_stream(sig_i);   // 当前输入样本
    mu = read_stream(del_i);  // 分数延迟值[0,1)
    
    // 2. 多项式插值(Farrow结构核心)
    // y(n+μ) = c0(μ)*x(n) + c1(μ)*x(n-1) + c2(μ)*x(n-2) + ...
    // 其中c_k(μ)是关于μ的多项式系数
    
    v0 = load_tap0(x_history);  // 加载滤波器状态
    v1 = load_tap1(x_history);
    
    // AIE向量MAC运算
    acc = mul(v0, poly_coeff[0]);  // c0(μ) * x(n)
    acc = mac(acc, v1, poly_coeff[1]); // + c1(μ) * x(n-1)
    
    // 3. 更新历史缓冲区(移位寄存器逻辑)
    shift_register(x_history, x);
    
    // 4. 输出结果
    write_stream(sig_o, acc.to_vector());

关键优化点(从farrow_initialfarrow_final的演进):

  • 初始版:直接计算多项式,每次插值需要K次乘加(K为多项式阶数)
  • 优化1:预计算μ的幂次,利用AIE的向量shuffle减少重复计算
  • 优化2:将分数延迟映射到查找表,用索引代替实时多项式求值
  • 最终版:采用转置Farrow结构,将μ的依赖移到滤波器系数端,实现真正的并行MAC

4. 数据返回阶段(成品出货)

// 等待处理完成
datamover_krnl.waitTo_complete();  // 阻塞直到DMA完成
fir_krnl.waitTo_complete();        // 等待PL HLS内核(如有)

// 错误检查(基于ap_return寄存器)
datamover_krnl.golden_check(&errCnt);

// 清理资源
fir_graph.close();      // xrtGraphClose
datamover_krnl.close(); // xrtRunClose/xrtKernelClose
xrtDeviceClose(dhdl);   // 释放设备

错误检查机制

  • AIE侧:通过仿真时的printf或硬件的event_trace验证
  • PL侧:HLS内核的ap_return总线返回错误计数(xrtKernelReadRegister读取0x10地址)
  • Host侧:对比输出数据与Golden Reference(errCnt > 1500视为失败)

关键设计决策与权衡

1. AIE vs HLS:分工的哲学

本模块同时提供AIE和HLS实现(fir_aie_app.cpp vs fir_hls_app.cpp),这不是重复劳动,而是架构层面的对比研究

选择AIE当

  • 需要确定性延迟(硬实时)
  • 向量长度匹配AIE的256位SIMD(8×cint16或16×int16)
  • 算法可分解为滑动窗MAC(FIR、卷积、相关)

选择HLS当

  • 需要复杂控制流(分支、循环嵌套、递归)
  • 数据位宽非标准(如18位、24位自定义定点)
  • 需要与RTL IP无缝集成(如使用现有的Verilog FIR核)

本模块的决策:对于标准FIR,AIE在功耗和面积上通常优于HLS,但HLS作为黄金参考模型用于验证AIE实现的数值正确性。

2. SSR(Super Sample Rate)的复杂度 trade-off

fir2_app.cpp(SSR Two-Tone Filter)中,使用了std::array<input_plio, NPORTS_O>创建多端口IO。

SSR的必要性

  • AIE单核峰值算力为1 MAC/周期(int16×int16)或0.5 MAC/周期(cint16×cint16)
  • 对于1GSPS采样率、16位精度的信号,需要4个AIE核并行(SSR=4)才能跟上数据流

SSR带来的复杂性

  • 多相分解:输入必须按"轮转"方式分配到各个核(核0拿样本0,4,8...,核1拿1,5,9...)
  • 边界同步:多核间的相位对齐需要精确的启动时序控制
  • PLIO倍增:每个SSR通道需要独立的PLIO,增加布线资源消耗

设计选择:本模块的SSR实现采用静态多相分配(编译时确定轮询模式),而非动态负载均衡,这是为了确定性延迟——每个样本的处理周期数固定,无竞争条件。

3. Farrow结构的优化演进

farrow_initialfarrow_final的四个版本,展示了算法-架构协同优化的经典过程:

Stage 0: 直接实现(数学教科书版)

# 每样本需要计算
mu = read_delay()  # 分数延迟
y = 0
for k in range(K):  # K阶多项式
    mu_k = mu**k    # 幂运算!
    y += c[k] * x[n-k] * mu_k

问题:幂运算mu**k在AIE上需要多次乘法,且依赖前一次结果(串行)。

Stage 1: 霍纳法则(Horner's Method)

# 重写为嵌套形式,减少乘法次数
y = c[0]*x[n] + mu*(c[1]*x[n-1] + mu*(c[2]*x[n-2] + ...))

改进:乘法次数从O(K²)降到O(K),但仍有数据依赖(μ必须逐层传递)。

Stage 2: 转置Farrow结构(Transposed Farrow)

这是架构层面的突破

# 将μ的依赖移到滤波器系数端
# y = Σ (c_k(μ) * x[n-k]), 其中 c_k(μ) = Σ (c[k][m] * μ^m)

现在所有乘法可并行执行!AIE的向量MAC可同时计算多个c_k(μ)*x[n-k]。

Stage 3: 查找表优化(Final)

# 对μ进行量化,预计算所有可能的c_k(μ)
mu_index = quantize(mu, 8)  # 8位量化,256个条目
c_k = LUT[mu_index][k]    # 查表代替实时计算

最终优化:将多项式求值转化为查表+内积,AIE可在单周期完成8个16位查表(向量LUT指令)。

关键洞察:Farrow优化的核心不是"让代码更快",而是重构算法以匹配AIE的SIMD架构——从串行幂运算到并行MAC,最终到LUT查表。

4. 内存与缓冲策略

ddc_kernel_stage.h中,可以看到复杂的循环缓冲区管理

struct buffer_internal {
    buffer_datatype * restrict head;  // 缓冲区基址
    buffer_datatype * restrict ptr;   // 当前读写指针
};

// 循环地址计算(cyclic_add是AIE特有指令)
w->ptr = cyclic_add(w->ptr, count, w->head, buffer_size);

设计决策分析

为什么不用std::queue或动态分配?

  • AIE内核不支持C++标准库(无malloc/new)
  • 确定性延迟要求禁止动态内存(碎片、不确定性分配时间)
  • cyclic_add是AIE专用硬件指令,单周期完成模运算

双缓冲(Ping-Pong)机制: 在fir2_app.cpppolyphase_fir_app.cpp中,虽然没有显式代码,但ADF运行时自动管理:

  • Ping缓冲区:当前AIE核正在读取的数据
  • Pong缓冲区:DMA正在写入的下一帧数据
  • 零拷贝切换:通过指针交换(非数据拷贝)实现无缝流水

对齐要求

  • AIE要求缓冲区32字节对齐(256位向量宽度)
  • alignas(32)ddc_kernel_stage.h中强制对齐
  • 未对齐访问将导致2倍性能损失(需两次128位加载拼接)

子模块划分与导航

本模块按设计复杂度递进应用场景划分为四个子模块:

1. farrow_filter_design_evolution —— 算法架构协同优化

核心内容:展示Farrow滤波器从数学公式到硬件实现的四阶段进化。

  • farrow_baseline_graph: 教科书式直接实现(高延迟、串行计算)
  • farrow_optimization_stage_1_graph: 霍纳法则优化(减少乘法次数)
  • farrow_optimization_stage_2_graph: 转置结构(并行MAC,匹配AIE SIMD)
  • farrow_final_implementation_graph: 查找表优化(单周期向量LUT)

学习价值:理解"算法重构比代码微调更重要"的硬件设计哲学。

2. fir_aie_vs_hls_chain_and_kernel_contracts —— 实现范式对比

核心内容:同一FIR滤波器的AIE与HLS双实现,量化对比性能/功耗/面积(PPA)。

  • aie_host_app: XRT控制AIE图,展示fir_chain_class的图生命周期管理
  • hls_host_app: 对比HLS内核的fir_class控制逻辑
  • hls_pl_kernel: fir_params结构体的HLS FIR库参数化配置

学习价值:建立"AIE用于规整向量计算、HLS用于复杂控制"的选型直觉。

3. multirate_channelizer_and_ddc_primitives —— 多速率处理原语

核心内容:信道化器、DDC(数字下变频)、多相分解的底层实现。

  • polyphase_fir_app: 16通道SSR8多相滤波器组,polyphase_fir_graph的并行架构
  • ddc_kernel_stage: 89抽头/199抽头对称FIR的buffer_internal循环缓冲区管理
  • taps_M16_init: 多相抽头系数的内存布局与初始化

学习价值:掌握"多相分解=并行性+降采样率"的硬件映射技巧。

4. ssr_two_tone_filter_graph —— 超采样率架构

核心内容:SSR>1时的高吞吐滤波器设计,处理多GHz采样率。

  • fir2_app: SSR架构的fir2_graphNPORTS_O参数化的多端口IO
  • 数据轮询: 输入样本按"round-robin"分配到并行内核的时序控制
  • 相位同步: SSR各通道间的启动对齐与反压处理

学习价值:理解"SSR=N等同于N倍并行度,但需解决数据交织"的复杂度来源。


扩展阅读与关联模块

上游依赖模块

下游应用模块

相关教程


设计决策深度分析

决策1:ADF Graph vs HLS Dataflow —— 编程模型的选择

背景:Versal AIE支持两种编程模型:

  • ADF(Adaptive Data Flow):声明式图定义,编译器自动映射到AIE阵列
  • HLS C++:命令式代码,Vitis HLS综合到RTL再映射到AIE/PL

本模块的混合策略

// ADF方式:声明连接关系(AIE部分)
class dut_graph : public graph {
    farrow_graph farrow;
    dut_graph() {
        connect<stream>(sig_i[0].out[0], farrow.sig_i[0]);  // 声明式连接
    }
};

// HLS方式:命令式控制(PL部分)
void datamover_class::run() {
    xrtRunStart(datamover_rhdl);  // 显式命令式启动
}

权衡分析

维度 ADF Graph HLS Dataflow
抽象级别 高(声明拓扑) 中(命令式代码)
调度控制 编译器自动调度 开发者显式控制
适用场景 规则数据流(FIR、FFT) 不规则控制(包处理、协议)
调试难度 中等(需理解编译器映射) 低(可单步调试C++)
性能上限 高(编译器全局优化) 中(受限于局部综合)

最终选择

  • AIE计算密集型部分:ADF(利用编译器的并行调度优化)
  • PL控制密集型部分:HLS(便于调试和与RTL集成)
  • Host控制:XRT C++ API(标准化设备管理)

决策2:循环缓冲区 vs 行缓冲区 —— 内存架构的选择

ddc_kernel_stage.h中,FIR实现采用了循环缓冲区(Circular Buffer)而非传统的行缓冲区(Line Buffer)

行缓冲区方式(传统DSP):

// 每次新样本到达,所有数据移位
void fir_shift_register(int x_new) {
    for (int i = N-1; i > 0; i--) {
        buffer[i] = buffer[i-1];  // 数据搬运!
    }
    buffer[0] = x_new;
}

问题:每次样本O(N)的数据搬运,AIE的Load/Store单元成为瓶颈。

循环缓冲区方式(本模块采用):

struct buffer_internal {
    v8cint16 * restrict head;  // 缓冲区起始
    v8cint16 * restrict ptr;   // 当前样本指针(仅移动指针,不搬数据!)
};

// 新样本到达:仅更新指针,旧数据自然被覆盖
void buffer_write(buffer_internal *w, v8cint16 value) {
    *((v8cint16 * restrict)(w->ptr)) = value;
    w->ptr = cyclic_add(w->ptr, 1, w->head, BUFFER_SIZE);  // 指针循环
}

优势

  • O(1)更新:无论FIR长度多长,新样本到达仅需1次写+1次指针更新
  • AIE硬件支持cyclic_add是专用硬件指令,单周期完成模地址计算
  • 向量友好v8cint16(8个复数)对齐到256位AIE向量宽度

权衡

  • 读取复杂性:读取历史样本需要计算相对于当前指针的偏移,使用buffer_read需要处理循环回绕
  • 初始化成本:缓冲区必须预填充有效数据才能开始处理("冷启动"延迟)

决策3:固定点 vs 浮点 —— 数值精度的选择

整个模块采用**定点数(Fixed-Point)**表示,关键参数在fir_hls.hddc_kernel_stage.h中定义:

// HLS侧定点配置
#define FIR_VALUE_WIDTH 16
typedef ap_fixed<FIR_VALUE_WIDTH, FIR_VALUE_WIDTH> Data_t;  // Q16.0 或自定义

// AIE侧定点配置(隐式)
const int DDC_SHIFT = 15;  // 15位小数部分,即Q1.15格式

为何不用浮点?

AIE架构虽然支持浮点,但本模块选择定点基于以下工程权衡:

维度 定点 (Fixed) 浮点 (Float)
AIE硬件 原生支持,单周期MAC 需特殊流水,延迟3-5周期
功耗 低(整数运算) 高(对数运算、归一化)
动态范围 需手动缩放(scale) 自动(指数域)
精度控制 精确位宽可控(16/18/24) 固定32/64位,可能过度精度

模块中的精度管理策略

  1. 输入阶段:16位ADC数据直接映射到AIE的cint16(复数16位实部+16位虚部)
  2. 中间累加器:使用48位累加器(cacc48)防止MAC溢出:
    aie::accum<cacc48,4> acc;  // 4个并行48位累加器
    acc = aie::sliding_mac_sym<...>(acc, coeff, ...);
    
  3. 输出阶段:右移15位(DDC_SHIFT)将48位结果截断回16位,四舍五入由aie::sliding_mac_sym的硬件自动处理

溢出与饱和

  • 本模块假设输入信号经过AGC(自动增益控制)预处理,确保不会溢出
  • 若需饱和处理,需在MAC后显式调用saturate(),但会引入额外周期

新贡献者必读:陷阱与隐性契约

陷阱1:PLIO带宽与AIE处理速率的失配

场景:你设计了一个FIR图,AIE核每周期处理1个样本,但PLIO每4周期才传输1个向量(4个样本)。

后果:AIE核大部分时间处于stall等待状态,实际吞吐量只有理论值的25%。

检测方法:在仿真波形中观察plio_in_stall信号(高电平表示PLIO等待AIE读取)。

解决

  • 提高PLIO频率(从312.5MHz到625MHz)
  • 增宽PLIO到128位(plio_128_bits
  • 或降低AIE处理速率(插入wait()周期)

陷阱2:AIE缓冲区对齐错误

场景:在Host代码中分配缓冲区:

std::vector<int16_t> buffer(256);  // 栈分配,不保证32字节对齐
xrtBOCreate(device, buffer.data(), ...);

后果:AIE DMA读取未对齐地址,触发misaligned_access异常(硬件错误,难以调试)。

必须遵守的契约

// 正确:使用对齐分配
alignas(32) int16_t buffer[256];
// 或使用XRT的分配函数
xrtBOAlloc(device, size, XRT_BO_FLAGS_HOST_ONLY, 0);

陷阱3:图生命周期与DMA的竞态条件

错误顺序

graph.run();      // 1. 启动AIE(开始从PLIO读取)
dma.run();        // 2. 启动DMA(数据尚未到达PLIO)

后果:AIE读取空PLIO,陷入无限stall;或读取垃圾数据。

正确时序

dma.init();       // 1. DMA准备(配置地址、长度)
graph.init();     // 2. AIE初始化(复位、加载系数)
graph.run();      // 3. AIE就绪(等待数据)
dma.run();        // 4. DMA启动(数据流入)
graph.wait();     // 5. 等待AIE处理完成
dma.wait();       // 6. 等待DMA搬出完成

陷阱4:HLS与AIE的数值不匹配

场景:同一个FIR滤波器,AIE和HLS实现输出不同(差异>1 LSB)。

根本原因

  • 舍入模式:AIE的sliding_mac_sym使用截断(truncate),HLS的ap_fixed默认四舍五入(round)
  • 溢出处理:AIE饱和(saturate)是可选模式,HLS默认饱和
  • 中间精度:AIE累加器固定48位,HLS可配置任意精度

调试方法

// 在AIE内核中插入打印(仅仿真)
printf("acc=%lld, shift=%d, out=%d\n", acc, shift, out);

// 对比HLS的ap_int内建打印
std::cout << "acc=" << acc.to_string(2) << std::endl;

解决:统一舍入策略——在AIE输出阶段手动加0.5后截断,模拟四舍五入:

// AIE中模拟四舍五入
acc = mac(acc, 0.5, 1);  // 加0.5 LSB
out = acc.to_vector<cint16>(0);  // 然后截断

扩展阅读与关联模块

上游依赖模块

下游应用模块

相关教程


总结

filtering_multirate_and_farrow_designs模块是AIE DSP的百科全书。它从简单的FIR起步,经过多相分解、SSR并行化,最终到达Farrow的任意重采样——完整覆盖了通信系统数字前端的核心信号处理链。

对于新加入的工程师,建议按以下路径学习:

  1. 先读 fir_aie_app.cpp 理解Host-AIE交互基础
  2. 再读 farrow_final/farrow_app.cpp 理解完整信号链
  3. 深入 ddc_kernel_stage.h 掌握AIE intrinsics优化
  4. 对比 fir_hls_app.cpp 与AIE版本,建立实现范式直觉

本模块不仅是代码集合,更是硬件-软件协同设计的教学案例——每一个优化决策都根植于对AIE架构(SIMD向量、确定性延迟、流式内存)的深刻理解。

On this page