背景
傅里叶变换能将信号的时域变换到频域。在频域中,系统响应等于信号与系统传递函数的乘积,相比直接在时域做卷积,先进行傅里叶变换再相乘最后逆变换的步骤虽看似复杂,但在工程技术上更易实现。
传统的傅里叶变换针对连续时间信号,而工程上无法对信号进行连续采样,因此诞生了离散傅里叶变换(DFT)。由于 DFT 算法时间复杂度为 O(n^2),优化后的快速傅里叶变换(FFT)将其降至 O(nlogn)。
本文主要介绍在 FPGA 中利用 IP 核实现 FFT 的方法,包括原理简述、IP 配置、实例化及数据处理。
一、原理与目的
1. 傅里叶变换
傅里叶变换将任意信号通过多种不同频率的正弦信号叠加而成。不同频率对应不同的幅值,通过频率与幅值的一一对应,得到任意信号的幅频特性曲线。
2. 快速傅里叶变换
在傅里叶变换基础上加入离散时间信号应用即为离散傅里叶变换;对其算法优化得到快速傅里叶变换(FFT)。FFT 的核心是蝶形运算和旋转因子。以基 2 的 N 点 FFT 为例,需注意时域抽取和频域抽取的关系,重点关注二进制逆序排列的操作。

3. 目的
学会配置 FFT 模块,获取频谱数据,并利用频谱数据进行后续操作。
二、配置 FFT
在 IP Catalog 中搜索 FFT,双击选项进行命名。FFT 的 IP 配置界面相对整洁,从上到下解读每个选项:
1. Transform
- Length(长度):即 N 点 FFT。N 的大小与采样频率 Fs 和频率分辨率 dF 的关系为 dF = Fs / N。N 越大,频率分辨率越高,但占用资源也越大。根据 FPGA 资源情况,此处选用 1024 点 FFT。
- Direction(方向):指运算方向,包括正向 FFT(Forward)、逆向 FFT(Reverse)和双向变换(Bi-directional)。此处选择双向变换。
2. I/O
- Data Flow(数据流):代表芯片的数据输入方式。包括 Streaming(流水线)、Burst(爆发)、Variable Streaming 和 Buffered Burst。Streaming 输入方式占用较高资源但运算速度快,输入一个周期数据后下一个周期即可输出。此处选择 Streaming 模式。
- Input Order/Output Order:选择 Nature(自然数)。
3. Data and Twiddle
- Representation(表示):Fixed Point(固定点)、Single Floating Point(单浮点)、Block Floating Point(块浮点)。Fixed Point 资源消耗最低但有溢出风险;Single Floating Point 资源占用较多;Block Floating Point 介于两者之间,是大部分 FFT 模块的配置选择。
- Data Input Width/Twiddle Width:位宽越宽,精度越高。
三、实例化
生成 HDL 工作并确认引脚。实例化分为前期数据准备、芯片接入、后期数据处理三个步骤。
1. 芯片接入
- clk/reset_n:时钟信号和低电平复位信号。
- inverse:0 代表 FFT 正变换,1 代表逆变换。
- sink_valid:直接输入 1。
- sink_ready:芯片给外部发出可以输入信号的'准备好了'信号。
- sink_error:直接输入 2'b00。高电平代表引脚功能生效(数据错误),不进行运算。
- sink_sop/sink_eop:输入信号的起始标识和结束标识,一般间隔为设置的 N 点 FFT 的 N 倍时钟,高电平有效。

- sink_real/sink_imag:输入信号的实部和虚部。正变换通常只有实部,虚部置 0。
- source_exp:配合 source_real 和 source_imag 使用,用于 Block Floating Point 中的公共指数值。
2. 数据准备
最重要的是起始符和结束符、输入信号格式。起始符上升沿代表数据开始输入,结束符上升沿代表数据结束输入。结束符在起始符的前一个时钟信号上升沿开始。
3. 数据处理
获取输出数据的实部、虚部、指数后,首先进行补码换成原码,再进行能量谱转换。注意幅频特性线的对称性,只取前 N/2 部分。
4. 代码实现
// 1024 points FFT and search peak
// Use 1024kHz sampling frequency, for the resolution frequency will be about 1kHz
// The two signal frequency in the FFT will be saved in Var fft_out[0] and fft_out[1]
wire signed [11:0] Data_signed;
assign Data_signed = Din[11] ? Din - 12'h800 : Din + 12'h800; // 偏移至有符号范围(-2048~2047)
reg [9:0] sample_count; // 0~1023
always @(posedge clk1024k) begin
if (sample_count == 10'd1023) sample_count <= 0;
else sample_count <= sample_count + 1;
end
// Gain FFT frame
wire sink_sop;
wire sink_eop;
assign sink_sop = (sample_count == 10'd0);
assign sink_eop = (sample_count == 10'd1023);
wire [11:0] real_out;
wire [11:0] imag_out;
wire source_valid;
wire freq_start;
wire freq_end;
wire [ 5:0] exp;
fft1024 u_fft (
.clk (clk1024k),
.reset_n (1),
.inverse (0),
.sink_valid (1),
.sink_sop (sink_sop),
.sink_eop (sink_eop),
.sink_real (Data_signed),
.sink_imag (12'b0),
.sink_error (2'b00),
.source_ready (1),
.source_valid (fft_valid),
.source_real (real_out),
.source_imag (imag_out),
.source_error (),
.source_sop (freq_start),
.source_eop (freq_end),
.source_exp (exp)
);
// Gain magnitude
wire [ 11:0] real_num;
wire [ 11:0] imag_num;
wire [ 23:0] power;
assign real_num = real_out[11] ? (~real_out + 1) : real_out;
assign imag_num = imag_out[11] ? (~imag_out + 1) : imag_out;
assign power = (real_num * real_num) + (imag_num * imag_num);
wire [11:0] magnitude_temp;
wire [17:0] magnitude;
sqrt u_sqrt(
.radical(power),
.q(magnitude_temp)
);
assign magnitude = magnitude_temp << exp;
// Get spectrum
reg [ 9:0] cnt;
reg freq_end_d;
always @(posedge clk1024k) begin
if (freq_start) cnt <= 0;
else if (fft_valid) cnt <= cnt + 1;
if (fft_valid && cnt <= 10'd512) begin
// magnitude 即为当前频率下的幅频特性
end
end
四、注意事项
配置过程中常见问题常出现在 FFT 模块芯片上,尤其是数据输入模式。若对内存理解不深,除 Streaming 外其他模式可能涉及内存导致输出错乱。即使使用 Streaming,起始标志和结束标志也需要仔细推演,避免标志符错位。
建议:
- 不太会使用内存的,建议使用 Streaming。
- 使用 Streaming 输入方式时,起始标志和结束标志需仔细推演,最好配合仿真验证。
总结
本文介绍了基于 FPGA IP 核的 FFT 开发流程。虽然可以通过原理自己写代码实现 FFT,但消耗资源较大,不足以满足实际工程需求。为满足工程需要,使用系统的 IP 核配置更为合适。以上方法基于理论理解与实际操作整理,供参考。
