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。需要进一步优化。
设计权衡
优点
- 减少寄存器压力:合并缓冲区降低了向量寄存器需求
- 简化数据提取:避免
filter_even的开销 - 保持功能正确性:数值结果与基线版本一致(Max error LSB = 1)
局限
- 单核瓶颈:所有计算仍在单个 tile 内完成,受限于单核计算能力
- 循环体仍复杂:FIR 计算和 Horner 级联在同一个循环内,限制了编译器的调度自由度
- 内存带宽未充分利用:中间结果仍保留在寄存器中,没有利用 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_antisymaie::vector_cast<T>()chess_prepare_for_pipeliningchess_loop_range(min,max)