🏠

Farrow 优化阶段 1 (Farrow Optimization Stage 1)

概述

本模块是 Farrow 滤波器设计演进的第二阶段,在 farrow_baseline_graph 的基础上进行了首次性能优化。主要目标是通过减少寄存器压力和简化数据流来提高吞吐量。

核心优化点

1. 状态缓冲区合并

基线版本

alignas(32) TT_SIG f3_state[STATE_LEN];
alignas(32) TT_SIG f2_state[STATE_LEN];
alignas(32) TT_SIG f1_state[STATE_LEN];
alignas(32) TT_SIG f0_state[STATE_LEN];

优化版本

alignas(32) TT_SIG f_state[STATE_LEN];

四个子滤波器共享同一个输入信号,因此它们的状态缓冲区内容完全相同。合并后减少了 75% 的状态存储需求。

2. 系数表合并

基线版本:四个独立的系数数组(f3_taps, f2_taps, f1_taps, f0_taps),每个 8 个元素。

优化版本:单个合并数组:

static constexpr unsigned F_TAPS = 16;
alignas(32) static constexpr int16 f_taps[F_TAPS] = {
    206,-1264,6606,-14835,    // f3 系数(反转后)
    906,-3543,10352,-7628,    // f2 系数(反转后)
    -51,316,-1652,20093,      // f1 系数(反转后)
    -226,886,-2588,10099      // f0 系数(反转后)
};

通过 aie::sliding_mul_sym_xy_ops<>::mul_sym/mul_antisym() 的偏移参数选择不同系数组:

  • offset=0:访问 f3 系数
  • offset=4:访问 f2 系数
  • offset=8:访问 f1 系数
  • offset=12:访问 f0 系数

3. 延迟输入格式优化

基线版本的问题: PLIO 每周期传输 32-bit,但只需要 16-bit 的延迟参数。原始实现使用 aie::filter_even()int32 向量中提取偶数位置的 int16,这消耗了额外的时钟周期。

优化方案: 预处理延迟输入数据,将有效的 int16 样本连续排列,后面补零。这样可以直接使用 aie::vector_cast<int16>() 转换,无需过滤操作。

// 基线版本
auto p_del_i = aie::begin_vector<8>(del_i);
del = aie::filter_even(aie::vector_cast<int16>(*p_del_i++));

// 优化版本
auto p_del_i = aie::begin_vector<4>(del_i);
del = aie::vector_cast<int16>(*p_del_i++); *p_del_i++;  // 跳过填充

代码结构

farrow_kernel.h

class farrow_kernel {
public:
    typedef cint16  TT_SIG;
    typedef int32   TT_DEL;
    typedef cacc48  TT_ACC;
    
    // 合并后的系数表
    static constexpr unsigned F_TAPS = 16;
    alignas(32) static constexpr int16 f_taps[F_TAPS];
    
    static constexpr unsigned BUFFER_SIZE = 1024;
    static constexpr unsigned DNSHIFT = 14;
    
    // 合并后的状态
    static constexpr unsigned STATE_LEN = 8;
    alignas(32) TT_SIG f_state[STATE_LEN];
    
    void run(input_buffer<TT_SIG,...>& sig_i,
             input_buffer<TT_DEL,...>& del_i,
             output_buffer<TT_SIG,...>& sig_o);
};

farrow_kernel.cpp 核心循环

void farrow_kernel::run(...) {
    // 初始化
    v_buff.insert(1, aie::load_v<8>(f_state));
    f_coeffs = aie::load_v<16>(f_taps);
    
    auto p_sig_i = aie::begin_vector<8>(sig_i);
    auto p_del_i = aie::begin_vector<4>(del_i);  // 注意:改为 vector<4>
    auto p_sig_o = aie::begin_vector<8>(sig_o);
    
    for (unsigned rr = 0; rr < BUFFER_SIZE/16; rr++)
        chess_loop_range(1,)
        chess_prepare_for_pipelining
    {
        v_buff.insert(0, *p_sig_i++);
        del = aie::vector_cast<int16>(*p_del_i++); *p_del_i++;  // 简化提取
        
        // 使用偏移量访问不同系数组
        acc_f3 = aie::sliding_mul_sym_xy_ops<...>::mul_antisym(f_coeffs, 0, v_buff, 9);
        acc_f2 = aie::sliding_mul_sym_xy_ops<...>::mul_sym(f_coeffs, 4, v_buff, 9);
        acc_f1 = aie::sliding_mul_sym_xy_ops<...>::mul_antisym(f_coeffs, 8, v_buff, 9);
        acc_f0 = aie::sliding_mul_sym_xy_ops<...>::mul_sym(f_coeffs, 12, v_buff, 9);
        
        // ... Horner 级联计算
    }
}

性能提升

指标 基线版本 优化阶段 1 改进
Initiation Interval (II) 123 82 33% ↓
原始吞吐量 ~205 MSPS ~301 MSPS 47% ↑
向量寄存器使用 高(溢出) 中等 改善

虽然 II 从 123 降低到 82,但仍远未达到目标 II=16。需要进一步优化。

设计权衡

优点

  1. 减少寄存器压力:合并缓冲区降低了向量寄存器需求
  2. 简化数据提取:避免 filter_even 的开销
  3. 保持功能正确性:数值结果与基线版本一致(Max error LSB = 1)

局限

  1. 单核瓶颈:所有计算仍在单个 tile 内完成,受限于单核计算能力
  2. 循环体仍复杂:FIR 计算和 Horner 级联在同一个循环内,限制了编译器的调度自由度
  3. 内存带宽未充分利用:中间结果仍保留在寄存器中,没有利用 tile 内存的多 bank 特性

下一步优化方向

优化阶段 2 将采用**循环拆分(Loop Fission)**策略:

  • 将 FIR 计算和 Horner 级联拆分为独立循环
  • 中间结果写入 tile 内存,释放寄存器压力
  • 允许编译器对每个小循环进行更激进的流水线优化

参见 farrow_optimization_stage_2_graph

文件位置

AI_Engine_Development/AIE/Design_Tutorials/15-farrow_filter/aie/farrow_optimize1/
├── farrow_kernel.h       # Kernel 类定义
├── farrow_kernel.cpp     # Kernel 实现
├── farrow_graph.h        # Graph 定义
├── farrow_app.cpp        # 应用顶层
└── Makefile              # 构建脚本

关键 API 参考

  • aie::sliding_mul_sym_xy_ops<Lanes,Points,CoeffStep,DataStep,CoeffType,DataType>::mul_sym/mul_antisym
  • aie::vector_cast<T>()
  • chess_prepare_for_pipelining
  • chess_loop_range(min,max)
On this page