DA算法——用查找表替代乘法器的高效FIR
分布式运算(Distributed Arithmetic,DA)用查找表替代乘法器实现FIR。核心:将乘法转换为查表和移位累加。
由于x[k]_b∈{0,1},内层求和只有2^N种可能值,可预存入LUT。N抽头B位输入的FIR:LUT大小2^N×位宽,计算周期B个时钟(串行)或1个(并行),乘法器0个!
//=============================================
// 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
全并行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
| 指标 | 直接型MAC | 串行DA | 并行DA |
|---|---|---|---|
| 乘法器 | TAP个 | 0 | 0 |
| LUT | 0 | 2^TAP | B·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减半)。
4抽头FIR h[n]={1,2,3,4},手动构建DA查找表全部16项。
解释DA算法中为什么符号位需要减法而不是加法,并推导公式。
16抽头DA-FIR拆分为4个4输入LUT,画出架构图并计算总LUT项数。
比较8抽头16位FIR三种实现在Xilinx Artix-7上的资源。
✅ 理解了分布式运算的核心原理
✅ 掌握了DA查找表的构建方法
✅ 实现了串行和并行DA-FIR
✅ 理解了符号位在DA中的特殊处理
✅ 掌握了LUT分割等优化技术
FPGA的6输入LUT与DA算法天然匹配:
| FPGA资源 | DA组件 | 说明 |
|---|---|---|
| 6输入LUT | 6抽头DA表项 | 一个LUT存64项 |
| BRAM(36Kb) | 大LUT存储 | 可存8K×36位表项 |
| 进位链 | 移位累加 | 高效实现加法+移位 |
| SRL16 | 输入移位 | 16级移位寄存器 |
当抽头数≤6时,一个LUT即可存储完整DA表,实现零乘法器FIR。当抽头数>6时,采用LUT分割+加法器树结构。
Offset Binary Coding (OBC)是DA的重要改进,将查表次数从B减少到B-1:
| 方法 | LUT大小 | 周期/输出 | 适用 |
|---|---|---|---|
| 基本串行DA | 2^N | B | 小N低吞吐 |
| OBC-DA | 2^(N-1) | B-1 | 中等N |
| LUT分割DA | (N/K)·2^K | B或1 | 大N |
| 全并行DA | B·2^N | 1 | 高吞吐小N |
FPGA的6输入LUT与DA算法天然匹配:
| FPGA资源 | DA组件 | 说明 |
|---|---|---|
| 6输入LUT | 6抽头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项)。
Offset Binary Coding (OBC)是DA的重要改进,将查表次数从B减少到B-1:
| 方法 | LUT大小 | 周期/输出 | 适用 |
|---|---|---|---|
| 基本串行DA | 2^N | B | 小N低吞吐 |
| OBC-DA | 2^(N-1) | B-1 | 中等N |
| LUT分割DA | (N/K)·2^K | B或1 | 大N |
| 全并行DA | B·2^N | 1 | 高吞吐小N |
设计一个8抽头16位DA-FIR滤波器,用于音频低通处理:
| 条件 | 推荐 | 理由 |
|---|---|---|
| 抽头数≤8,无DSP slice | 并行DA | LUT小,吞吐率高 |
| 抽头数8-16,低速 | 串行DA | LUT可接受,面积小 |
| 抽头数>16 | 时分复用MAC | LUT指数增长不可行 |
| 有DSP48可用 | MAC直接型 | DSP slice比LUT高效 |
| 系数可变 | MAC | DA系数固化在LUT中 |
| 超高速(>500MHz) | 全并行MAC | DA查表延迟不可接受 |
DA算法的量化误差来源有三个:
DA的量化噪声模型:σ²_DA ≈ (B-1)/12 · 2^(-2Q),Q为LUT输出位宽。与MAC结构的噪声水平相当,但DA的舍入位置不同导致误差模式不同。
FIR分布式运算在进阶应用中还有以下重要主题值得深入研究:
本课内容在整个DSP课程体系中处于承上启下的位置:
在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。
在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。
在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。
在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。
在实际工程中,FIR分布式运算的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。