阶段二:FIR滤波器

⚡ 第11课:FIR分布式运算

DA算法——用查找表替代乘法器的高效FIR

1. 分布式运算(DA)原理

分布式运算(Distributed Arithmetic,DA)用查找表替代乘法器实现FIR。核心:将乘法转换为查表和移位累加。

y = Σh[k]·x[k] = Σh[k]·Σ(x[k]_b·2^b) = Σ2^b·(Σh[k]·x[k]_b)

由于x[k]_b∈{0,1},内层求和只有2^N种可能值,可预存入LUT。N抽头B位输入的FIR:LUT大小2^N×位宽,计算周期B个时钟(串行)或1个(并行),乘法器0个!

2. DA算法详解

📋 DA算法步骤

  1. 将每个输入样本拆分为逐位表示
  2. 用所有输入的同一位作为LUT地址
  3. 查表得到部分和
  4. 按位权移位累加得到最终结果

3. Verilog实现:全串行DA-FIR

//=============================================
// dsp_fir_da_serial.v
// 全串行DA-FIR:每B个时钟输出一次
// 无乘法器!
//=============================================
module dsp_fir_da_serial #(
    parameter TAP = 8,
    parameter DATA_WIDTH = 16,
    parameter OUT_WIDTH = 32
)(
    input  wire                          clk,
    input  wire                          rst_n,
    input  wire                          data_valid,
    input  wire signed [DATA_WIDTH-1:0]  data_in [0:TAP-1],
    output reg  signed [OUT_WIDTH-1:0]   data_out,
    output reg                           out_valid
);
    reg signed [OUT_WIDTH-1:0] da_lut [0:(1<<TAP)-1];
    initial begin
        integer addr,k;
        for (addr=0;addr<(1<<TAP);addr=addr+1) begin
            da_lut[addr] = 0;
            for (k=0;k<TAP;k=k+1) begin
                if (addr[k]) begin
                    case(k)
                        0: da_lut[addr] = da_lut[addr]+16'sd1024;
                        1: da_lut[addr] = da_lut[addr]+16'sd2048;
                        2: da_lut[addr] = da_lut[addr]+16'sd3072;
                        3: da_lut[addr] = da_lut[addr]+16'sd4096;
                        4: da_lut[addr] = da_lut[addr]+16'sd4096;
                        5: da_lut[addr] = da_lut[addr]+16'sd3072;
                        6: da_lut[addr] = da_lut[addr]+16'sd2048;
                        7: da_lut[addr] = da_lut[addr]+16'sd1024;
                        default: da_lut[addr] = da_lut[addr];
                    endcase
                end
            end
        end
    end
    reg [$clog2(DATA_WIDTH)-1:0] bit_cnt;
    reg signed [OUT_WIDTH-1:0] acc;
    reg [TAP-1:0] lut_addr;
    integer i;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin bit_cnt<=0; acc<=0; data_out<=0; out_valid<=1'b0; end
        else if (data_valid) begin
            for (i=0;i<TAP;i=i+1) lut_addr[i] <= data_in[i][bit_cnt];
            if (bit_cnt==DATA_WIDTH-1) begin
                acc <= acc-da_lut[lut_addr];
                data_out <= acc-da_lut[lut_addr];
                out_valid <= 1'b1; bit_cnt <= 0;
            end else begin
                acc <= acc+da_lut[lut_addr];
                bit_cnt <= bit_cnt+1'b1; out_valid <= 1'b0;
            end
        end else out_valid <= 1'b0;
    end
endmodule

4. 全并行DA-FIR

全并行DA同时查B个LUT,一个时钟周期完成计算。资源增大B倍,吞吐率也增大B倍。

//=============================================
// dsp_fir_da_parallel.v
// 全并行DA-FIR:1周期输出
//=============================================
module dsp_fir_da_parallel #(
    parameter TAP = 4,
    parameter DATA_WIDTH = 8,
    parameter OUT_WIDTH = 24
)(
    input  wire                          clk,
    input  wire                          rst_n,
    input  wire                          data_valid,
    input  wire signed [DATA_WIDTH-1:0]  data_in [0:TAP-1],
    output reg  signed [OUT_WIDTH-1:0]   data_out,
    output reg                           out_valid
);
    reg signed [OUT_WIDTH-1:0] da_lut [0:(1<<TAP)-1];
    initial begin
        integer addr,k;
        for (addr=0;addr<(1<<TAP);addr=addr+1) begin
            da_lut[addr] = 0;
            for (k=0;k<TAP;k=k+1) begin
                if (addr[k]) begin
                    case(k)
                        0: da_lut[addr] = da_lut[addr]+16'sd2048;
                        1: da_lut[addr] = da_lut[addr]+16'sd4096;
                        2: da_lut[addr] = da_lut[addr]+16'sd4096;
                        3: da_lut[addr] = da_lut[addr]+16'sd2048;
                    endcase
                end
            end
        end
    end
    reg [TAP-1:0] addr_bits [0:DATA_WIDTH-1];
    reg signed [OUT_WIDTH-1:0] lut_results [0:DATA_WIDTH-1];
    integer i,b;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin data_out<=0; out_valid<=0; end
        else if (data_valid) begin
            for (b=0;b<DATA_WIDTH;b=b+1) begin
                for (i=0;i<TAP;i=i+1) addr_bits[b][i]=data_in[i][b];
                lut_results[b] = da_lut[addr_bits[b]];
            end
            data_out <= 0;
            for (b=0;b<DATA_WIDTH-1;b=b+1)
                data_out <= data_out+(lut_results[b]<<b);
            data_out <= data_out-(lut_results[DATA_WIDTH-1]<<(DATA_WIDTH-1));
            out_valid <= 1;
        end else out_valid <= 0;
    end
endmodule

5. DA-FIR资源与LUT优化

📊 FIR实现对比

指标直接型MAC串行DA并行DA
乘法器TAP个00
LUT02^TAPB·2^TAP
吞吐率1/周期1/B周期1/周期
延迟1周期B周期1周期

当TAP>8时,LUT指数增长。优化方法:LUT分割(16抽头→4个4抽头LUT,2^16=65536→4×2^4=64项);OBC编码(补码表示减少1位地址,LUT减半)。

6. 练习

📝 练习1:DA-LUT构建

4抽头FIR h[n]={1,2,3,4},手动构建DA查找表全部16项。

📝 练习2:符号扩展

解释DA算法中为什么符号位需要减法而不是加法,并推导公式。

📝 练习3:LUT分割

16抽头DA-FIR拆分为4个4输入LUT,画出架构图并计算总LUT项数。

📝 练习4:资源评估

比较8抽头16位FIR三种实现在Xilinx Artix-7上的资源。

🏆 成就解锁:DA架构师

✅ 理解了分布式运算的核心原理

✅ 掌握了DA查找表的构建方法

✅ 实现了串行和并行DA-FIR

✅ 理解了符号位在DA中的特殊处理

✅ 掌握了LUT分割等优化技术

7. DA-FIR在FPGA上的最优实现

FPGA的6输入LUT与DA算法天然匹配:

🔧 FPGA DA实现映射

FPGA资源DA组件说明
6输入LUT6抽头DA表项一个LUT存64项
BRAM(36Kb)大LUT存储可存8K×36位表项
进位链移位累加高效实现加法+移位
SRL16输入移位16级移位寄存器

当抽头数≤6时,一个LUT即可存储完整DA表,实现零乘法器FIR。当抽头数>6时,采用LUT分割+加法器树结构。

8. OBC-DA改进算法

Offset Binary Coding (OBC)是DA的重要改进,将查表次数从B减少到B-1:

x[k] = -x[k][B-1]·2^(B-1) + Σ(b=0 to B-2) x[k][b]·2^b
令y[k][b] = x[k][b] ⊕ x[k][B-1](b 优势:LUT从2^N项降为2^(N-1)项(利用对称性),查表次数B-1

📊 DA变体对比

方法LUT大小周期/输出适用
基本串行DA2^NB小N低吞吐
OBC-DA2^(N-1)B-1中等N
LUT分割DA(N/K)·2^KB或1大N
全并行DAB·2^N1高吞吐小N

7. DA-FIR的FPGA最优映射

FPGA的6输入LUT与DA算法天然匹配:

🔧 FPGA DA资源映射

FPGA资源DA组件说明
6输入LUT6抽头DA表项一个LUT存64项
BRAM(36Kb)大LUT存储可存8K×36位表项
进位链移位累加高效实现加法+移位
SRL16输入移位16级移位寄存器

当抽头数≤6时,一个LUT即可存储完整DA表,实现零乘法器FIR。当抽头数>6时,采用LUT分割+加法器树结构。16抽头DA可拆分为4个4输入LUT(4×2^4=64项 vs 2^16=65536项)。

8. OBC-DA改进算法

Offset Binary Coding (OBC)是DA的重要改进,将查表次数从B减少到B-1:

令y[k][b] = x[k][b] ⊕ x[k][B-1](b<B-1时)
OBC-DA查表地址用y[k][b]替代x[k][b]
LUT从2^N项降为2^(N-1)项,查表次数B-1

📊 DA变体对比

方法LUT大小周期/输出适用
基本串行DA2^NB小N低吞吐
OBC-DA2^(N-1)B-1中等N
LUT分割DA(N/K)·2^KB或1大N
全并行DAB·2^N1高吞吐小N

9. DA-FIR的完整设计示例

设计一个8抽头16位DA-FIR滤波器,用于音频低通处理:

📌 设计规格与实现

10. DA vs MAC的选择指南

📋 选择决策树

条件推荐理由
抽头数≤8,无DSP slice并行DALUT小,吞吐率高
抽头数8-16,低速串行DALUT可接受,面积小
抽头数>16时分复用MACLUT指数增长不可行
有DSP48可用MAC直接型DSP slice比LUT高效
系数可变MACDA系数固化在LUT中
超高速(>500MHz)全并行MACDA查表延迟不可接受

11. DA-FIR的误差分析

DA算法的量化误差来源有三个:

🔍 DA误差分析

DA的量化噪声模型:σ²_DA ≈ (B-1)/12 · 2^(-2Q),Q为LUT输出位宽。与MAC结构的噪声水平相当,但DA的舍入位置不同导致误差模式不同。

12. DA-FIR在通信系统中的应用

📡 通信系统中的DA应用

11. 补充:FIR分布式运算的进阶主题

FIR分布式运算在进阶应用中还有以下重要主题值得深入研究:

📚 进阶研究方向

12. 与前后课程的关联

本课内容在整个DSP课程体系中处于承上启下的位置:

🔗 课程关联图

13. 设计经验总结

在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。

📌 关键设计检查清单

13. 设计经验总结

在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。

📌 关键设计检查清单

13. 设计经验总结

在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。

📌 关键设计检查清单

13. 设计经验总结

在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。

📌 关键设计检查清单

13. 设计经验总结

在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。

📌 关键设计检查清单