阶段一:信号基础

⚡ 第06课:FFT完整引擎

从蝶形单元到完整FFT处理器——可综合的实时频谱分析核心

1. 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

2. 流水线FFT架构详解

流水线FFT(也称为SDF - Single-path Delay Feedback)是工程中应用最广的架构。每一级包含:

SDF架构关键:第s级需要2^(log₂N-s-1)个延迟单元,蝶形在每隔2^(log₂N-s-1)个周期激活一次

3. Verilog实现:完整N点FFT处理器

//=============================================
// 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

3.1 SDF单级处理模块

//=============================================
// 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

4. Verilog实现:FFT测试台

//=============================================
// 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

5. FFT处理器性能优化

🔧 关键优化技术

6. 实际应用中的FFT

📡 典型FFT应用场景

应用点数采样率特殊要求
音频频谱仪1024-409644.1kHz加窗重叠
OFDM调制64-204820MHz+低延迟、连续
雷达脉冲压缩256-8192100MHz+高吞吐率
医学成像256-512可变高精度
5G NR4096122.88MHz超低延迟

7. 练习

📝 练习1:FFT验证

用上述FFT处理器计算x[n] = {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}(16点脉冲),验证输出是否为全1序列。

📝 练习2:IFFT实现

基于已有FFT核心,实现IFFT:方法一——交换实部虚部,做FFT,再交换;方法二——使用共轭旋转因子,结果除以N。

📝 练习3:块浮点FFT

修改 dsp_fft_sdf_stage.v,添加块浮点检测逻辑:每级结束后检测最大指数,下一级开始前统一移位对齐。

📝 练习4:连续FFT

设计双缓冲架构:当一个缓冲在做FFT时,另一个缓冲接收新数据,实现帧间无缝切换。

🏆 成就解锁:FFT架构师

✅ 掌握了FFT处理器三种架构

✅ 实现了完整SDF流水线FFT处理器

✅ 理解了级间缩放与溢出防护

✅ 掌握了FFT/IFFT的复用设计

✅ 了解了FFT的工程优化技术

11. 补充:FFT完整引擎的进阶主题

FFT完整引擎在进阶应用中还有以下重要主题值得深入研究:

📚 进阶研究方向

12. 与前后课程的关联

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

🔗 课程关联图

13. 设计经验总结

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

📌 关键设计检查清单

13. 设计经验总结

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

📌 关键设计检查清单