从蝶形单元到完整FFT处理器——可综合的实时频谱分析核心
完整的FFT处理器需要将蝶形运算、旋转因子生成、地址控制和数据存储整合在一起。常见的FFT处理器架构有三种:
| 架构 | 特点 | 资源 | 延迟 | 吞吐率 |
|---|---|---|---|---|
| 顺序(单蝶形) | 一个蝶形单元,迭代计算 | 最少 | N·log₂N/2 周期 | 1 FFT/N·log₂N/2 |
| 流水线(每级一个蝶形) | log₂N个蝶形级联 | 中等 | N + log₂N 周期 | 连续 |
| 并行(全蝶形) | N/2个蝶形并行 | 最大 | log₂N 周期 | 1 FFT/log₂N |
流水线FFT(也称为SDF - Single-path Delay Feedback)是工程中应用最广的架构。每一级包含:
//=============================================
// dsp_fft_core.v
// N点FFT处理器核心(DIT, 流水线SDF架构)
// 支持正向/逆向FFT
//=============================================
module dsp_fft_core #(
parameter N = 16, // FFT点数(必须为2的幂)
parameter DATA_WIDTH = 16, // 数据位宽
parameter TWIDDLE_WIDTH = 16, // 旋转因子位宽
parameter SCALE_SHIFT = 1 // 每级缩放移位(防止溢出)
)(
input wire clk,
input wire rst_n,
input wire fwd_inv, // 0=正向FFT, 1=逆向FFT
input wire data_valid,
input wire signed [DATA_WIDTH-1:0] data_re_in,
input wire signed [DATA_WIDTH-1:0] data_im_in,
output wire signed [DATA_WIDTH-1:0] data_re_out,
output wire signed [DATA_WIDTH-1:0] data_im_out,
output wire out_valid,
output wire fft_done
);
localparam LOG_N = $clog2(N);
localparam STAGES = LOG_N;
// 内部级间信号
wire signed [DATA_WIDTH-1:0] stage_re [0:STAGES];
wire signed [DATA_WIDTH-1:0] stage_im [0:STAGES];
wire stage_dv [0:STAGES];
assign stage_re[0] = data_re_in;
assign stage_im[0] = data_im_in;
assign stage_dv[0] = data_valid;
assign data_re_out = stage_re[STAGES];
assign data_im_out = stage_im[STAGES];
assign out_valid = stage_dv[STAGES];
assign fft_done = stage_dv[STAGES];
// 生成每一级SDF处理单元
genvar s;
generate
for (s = 0; s < STAGES; s = s + 1) begin : gen_stage
dsp_fft_sdf_stage #(
.N(N),
.STAGE(s),
.DATA_WIDTH(DATA_WIDTH),
.TWIDDLE_WIDTH(TWIDDLE_WIDTH),
.SCALE_SHIFT(SCALE_SHIFT)
) sdf_inst (
.clk(clk),
.rst_n(rst_n),
.fwd_inv(fwd_inv),
.data_re_in(stage_re[s]),
.data_im_in(stage_im[s]),
.data_valid(stage_dv[s]),
.data_re_out(stage_re[s+1]),
.data_im_out(stage_im[s+1]),
.out_valid(stage_dv[s+1])
);
end
endgenerate
endmodule
//=============================================
// dsp_fft_sdf_stage.v
// FFT SDF架构单级处理模块
// 包含:延迟缓冲 + 蝶形单元 + 旋转因子乘法
//=============================================
module dsp_fft_sdf_stage #(
parameter N = 16,
parameter STAGE = 0,
parameter DATA_WIDTH = 16,
parameter TWIDDLE_WIDTH = 16,
parameter SCALE_SHIFT = 1
)(
input wire clk,
input wire rst_n,
input wire fwd_inv,
input wire signed [DATA_WIDTH-1:0] data_re_in,
input wire signed [DATA_WIDTH-1:0] data_im_in,
input wire data_valid,
output reg signed [DATA_WIDTH-1:0] data_re_out,
output reg signed [DATA_WIDTH-1:0] data_im_out,
output reg out_valid
);
localparam LOG_N = $clog2(N);
localparam DELAY_LEN = 2**(LOG_N - STAGE - 1);
// 延迟缓冲区
reg signed [DATA_WIDTH-1:0] delay_re [0:DELAY_LEN-1];
reg signed [DATA_WIDTH-1:0] delay_im [0:DELAY_LEN-1];
reg [$clog2(DELAY_LEN)-1:0] delay_ptr;
// 旋转因子查找表(本级所需)
localparam NUM_TWIDDLES = 2**(STAGE);
reg signed [TWIDDLE_WIDTH-1:0] tw_re [0:NUM_TWIDDLES-1];
reg signed [TWIDDLE_WIDTH-1:0] tw_im [0:NUM_TWIDDLES-1];
initial begin
integer i;
for (i = 0; i < NUM_TWIDDLES; i = i + 1) begin
tw_re[i] = $rtoi(
(2.0**(TWIDDLE_WIDTH-1)-1) * $cos(2.0*3.14159265*i/NUM_TWIDDLES/2)
);
tw_im[i] = $rtoi(
-(2.0**(TWIDDLE_WIDTH-1)-1) * $sin(2.0*3.14159265*i/NUM_TWIDDLES/2)
);
end
end
// 计数器:控制蝶形/直通模式
reg [$clog2(DELAY_LEN*2)-1:0] sample_cnt;
wire butterfly_active = (sample_cnt >= DELAY_LEN) && data_valid;
reg [$clog2(NUM_TWIDDLES)-1:0] twiddle_idx;
// 蝶形运算中间结果
reg signed [DATA_WIDTH-1:0] bf_a_re, bf_a_im;
reg signed [DATA_WIDTH-1:0] bf_b_re, bf_b_im;
reg signed [DATA_WIDTH+TWIDDLE_WIDTH-1:0] wb_re, wb_im;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
delay_ptr <= 0;
sample_cnt <= 0;
twiddle_idx <= 0;
data_re_out <= 0;
data_im_out <= 0;
out_valid <= 1'b0;
end else if (data_valid) begin
out_valid <= 1'b1;
if (!butterfly_active) begin
// 直通模式:将输入存入延迟缓冲
delay_re[delay_ptr] <= data_re_in;
delay_im[delay_ptr] <= data_im_in;
// 输出延迟缓冲最旧的数据(前半阶段输出0)
data_re_out <= 0;
data_im_out <= 0;
delay_ptr <= delay_ptr + 1'b1;
end else begin
// 蝶形模式
bf_a_re <= delay_re[delay_ptr];
bf_a_im <= delay_im[delay_ptr];
bf_b_re <= data_re_in;
bf_b_im <= data_im_in;
// W * B
if (fwd_inv == 1'b0) begin
wb_re <= tw_re[twiddle_idx] * bf_b_re - tw_im[twiddle_idx] * bf_b_im;
wb_im <= tw_re[twiddle_idx] * bf_b_im + tw_im[twiddle_idx] * bf_b_re;
end else begin
// IFFT使用共轭旋转因子
wb_re <= tw_re[twiddle_idx] * bf_b_re + tw_im[twiddle_idx] * bf_b_im;
wb_im <= tw_re[twiddle_idx] * bf_b_im - tw_im[twiddle_idx] * bf_b_re;
end
// 输出 A + W*B(缩放防溢出)
data_re_out <= (bf_a_re + wb_re[DATA_WIDTH+TWIDDLE_WIDTH-2:TWIDDLE_WIDTH-1]) >>> SCALE_SHIFT;
data_im_out <= (bf_a_im + wb_im[DATA_WIDTH+TWIDDLE_WIDTH-2:TWIDDLE_WIDTH-1]) >>> SCALE_SHIFT;
// 将 A - W*B 写回延迟缓冲
delay_re[delay_ptr] <= (bf_a_re - wb_re[DATA_WIDTH+TWIDDLE_WIDTH-2:TWIDDLE_WIDTH-1]) >>> SCALE_SHIFT;
delay_im[delay_ptr] <= (bf_a_im - wb_im[DATA_WIDTH+TWIDDLE_WIDTH-2:TWIDDLE_WIDTH-1]) >>> SCALE_SHIFT;
twiddle_idx <= twiddle_idx + 1'b1;
delay_ptr <= delay_ptr + 1'b1;
end
// 计数器循环
if (sample_cnt == DELAY_LEN * 2 - 1) begin
sample_cnt <= 0;
twiddle_idx <= 0;
delay_ptr <= 0;
end else begin
sample_cnt <= sample_cnt + 1'b1;
end
end else begin
out_valid <= 1'b0;
end
end
endmodule
//=============================================
// tb_fft_core.v
// FFT处理器测试台
//=============================================
`timescale 1ns/1ps
module tb_fft_core;
parameter N = 16;
parameter DATA_WIDTH = 16;
parameter TWIDDLE_WIDTH = 16;
parameter CLK_PERIOD = 10;
reg clk;
reg rst_n;
reg fwd_inv;
reg data_valid;
reg signed [DATA_WIDTH-1:0] data_re_in;
reg signed [DATA_WIDTH-1:0] data_im_in;
wire signed [DATA_WIDTH-1:0] data_re_out;
wire signed [DATA_WIDTH-1:0] data_im_out;
wire out_valid;
wire fft_done;
dsp_fft_core #(
.N(N),
.DATA_WIDTH(DATA_WIDTH),
.TWIDDLE_WIDTH(TWIDDLE_WIDTH),
.SCALE_SHIFT(1)
) uut (
.clk(clk),
.rst_n(rst_n),
.fwd_inv(fwd_inv),
.data_valid(data_valid),
.data_re_in(data_re_in),
.data_im_in(data_im_in),
.data_re_out(data_re_out),
.data_im_out(data_im_out),
.out_valid(out_valid),
.fft_done(fft_done)
);
initial clk = 0;
always #(CLK_PERIOD/2) clk = ~clk;
// 测试输入:单频正弦波
integer n;
real freq;
initial begin
$dumpfile("fft_core.vcd");
$dumpvars(0, tb_fft_core);
rst_n = 0; fwd_inv = 0; data_valid = 0;
data_re_in = 0; data_im_in = 0;
#100;
rst_n = 1;
// 输入一个单频信号:频率 = 2 * fs/N
freq = 2.0;
for (n = 0; n < N; n = n + 1) begin
@(posedge clk);
data_valid <= 1;
data_re_in <= $rtoi(32767.0 * $cos(2.0 * 3.14159265 * freq * n / N));
data_im_in <= 0;
end
@(posedge clk);
data_valid <= 0;
// 等待FFT完成
wait(fft_done);
#1000;
// 测试IFFT
fwd_inv = 1;
// ... (类似输入FFT输出数据)
$display("=== FFT测试完成 ===");
$finish;
end
endmodule
| 应用 | 点数 | 采样率 | 特殊要求 |
|---|---|---|---|
| 音频频谱仪 | 1024-4096 | 44.1kHz | 加窗重叠 |
| OFDM调制 | 64-2048 | 20MHz+ | 低延迟、连续 |
| 雷达脉冲压缩 | 256-8192 | 100MHz+ | 高吞吐率 |
| 医学成像 | 256-512 | 可变 | 高精度 |
| 5G NR | 4096 | 122.88MHz | 超低延迟 |
用上述FFT处理器计算x[n] = {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}(16点脉冲),验证输出是否为全1序列。
基于已有FFT核心,实现IFFT:方法一——交换实部虚部,做FFT,再交换;方法二——使用共轭旋转因子,结果除以N。
修改 dsp_fft_sdf_stage.v,添加块浮点检测逻辑:每级结束后检测最大指数,下一级开始前统一移位对齐。
设计双缓冲架构:当一个缓冲在做FFT时,另一个缓冲接收新数据,实现帧间无缝切换。
✅ 掌握了FFT处理器三种架构
✅ 实现了完整SDF流水线FFT处理器
✅ 理解了级间缩放与溢出防护
✅ 掌握了FFT/IFFT的复用设计
✅ 了解了FFT的工程优化技术
FFT完整引擎在进阶应用中还有以下重要主题值得深入研究:
本课内容在整个DSP课程体系中处于承上启下的位置:
在实际工程中,FFT完整引擎的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。
在实际工程中,FFT完整引擎的设计经验总结如下:(1)始终从系统级需求出发,不要过早陷入实现细节;(2)先用浮点仿真验证算法正确性,再转为定点;(3)系数量化后必须重新验证频率响应;(4)硬件实现前用C/MATLAB模型作为参考;(5)综合后检查时序和资源是否符合预期;(6)板级验证时用已知的测试信号对比参考输出。每一步验证都是必要的,跳过任何一步都可能导致最终的调试时间成倍增加。