引言
快速傅里叶变换或反变换(FFT/IFFT)是一种重要的信号分析方法,在各种如图像处理,通信及信号处理等工程领域具有非常重要的应用,因此研究其工程实现具有非常重要的意义。Xilinx公司在其Vivado开发工具中提供了FFT/IFFT的 IP核,供开发人员很方便的调用和使用,因此,本文主要对Vivado中的Xilinx FFT/IFFT IP核使用流程展开详细介绍。
1、FFT/IFFT IP核的创建
在使用FFT/IFFT IP核之前,需要在Vivado软件中进行创建,配置相关的参数,流程如下:
- 查找IP核

- 进行Configuration窗口下的配置

- 进行Implementation窗口下的配置

- 进行Detailed Implementation窗口下的配置

- 配置完成后可以在配置窗口的右边看到实现的一些具体细节

在下面这个图中尤为需要注意的是,当在对S_AXIS_CONFIG_TDATA送控制数据时,1代表该IP核执行FFT功能,0代表该IP核执行IFFT功能。

2、FFT/IFFT IP核在Modelsim中的实际仿真
由于IP核实现FFT或IFFT功能只是一个控制信息的不同,因此下面我们以实现FFT功能为例介绍简单的仿真流程。
由于此次是跨平台仿真,所以需要进行特定仿真环境的搭建,在这我不在详细介绍,具体细节请见如下链接:如何在QuestaSim或ModelSim中使用Vivado的IP进行仿真
我们仿真所需要的文件是xxx_netlist.v文件,将该文件与我们的测试文件一起添加到Modelsim工程中后,即可进行仿真,该文件位置如下图:

由于Vivado中的IP核的接口大部分基于AXI4-Stream协议,因此,在编写测试文件之前,要仔细阅读搞清楚AXI4-Stream协议的相关时序,然后才能进行正确的数据读写,具体内容请见如下链接:AXI4-Stream协议总结
在本次测试仿真中,我们采用的仿真数据是三种频率数据的混合,进行谱分析,频率分别为2KHz,6KHz和9KHz,测试数据由Matlab产生,产生文件如下:
Fs = 20000; %采样频率
N = 2^12; %采样点数
t = 0:1/Fs:N/Fs-1/Fs;%时间跨度
s = sin(2000*2*pi*t) + sin(6000*2*pi*t) + sin(9000*2*pi*t);
figure(1);
subplot(2,1,1);
plot(t,s,'r','LineWidth',1.2);
title('时域波形');
axis([0,100/Fs,-3,3]);
set(gca,'LineWidth',1.2);
%画频谱图
df1=Fs/(N-1);%分辨率
f1=(0:N-1)*df1;%其中每点的频率
%默认做N=4096点FFT
%不同点数的FFT的频率分辨率时不一样的,点数越多,分辨率越高
%如128点FFT即从输入信号中任选128个点进行傅里叶变换,输出结果表示频谱信息
Y1=fft(s);
subplot(2,1,2);
%对称频谱图的一半
plot(f1,abs(Y1),'r','LineWidth',1.2);
title('频谱图');
set(gca,'LineWidth',1.2);
Matlab仿真下的时域波形及频谱图如下图所示:

Verilog Testbench文件如下:
`timescale 1 ns / 1 ps
module fft_tb ();
glbl glbl();
reg clk;
reg rst_n;
reg signed [15:0] Time_data_I [127:0];
wire fft_s_config_tready;
reg signed [31:0] fft_s_data_tdata;
reg fft_s_data_tvalid;
wire fft_s_data_tready;
reg fft_s_data_tlast;
wire signed [47:0] fft_m_data_tdata;
wire signed [7:0] fft_m_data_tuser;
wire fft_m_data_tvalid;
reg fft_m_data_tready;
wire fft_m_data_tlast;
wire fft_event_frame_started;
wire fft_event_tlast_unexpected;
wire fft_event_tlast_missing;
wire fft_event_status_channel_halt;
wire fft_event_data_in_channel_halt;
wire fft_event_data_out_channel_halt;
reg [7:0] count;
reg signed [23:0] fft_i_out;
reg signed [23:0] fft_q_out;
reg signed [47:0] fft_abs;
initial begin
clk = 1'b1;
rst_n = 1'b1;
#5 rst_n = 1'b0;
#5 rst_n = 1'b1;
fft_m_data_tready = 1'b1;
$readmemh("F:/FPGA_DSP/FFT_IP_Test/BeforeFilterHex.txt",Time_data_I);
end
always #5 clk = ~clk;
//发送时域数据到FFT IP核,主->从
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
fft_s_data_tvalid <= 1'b0;
fft_s_data_tdata <= 32'd0;
fft_s_data_tlast <= 1'b0;
count <= 8'd0;
end
else if (fft_s_data_tready) begin//FFT IP核(从设备)已经准备好接收数据,主设备开始发送有效数据
if(count == 8'd127)begin
fft_s_data_tvalid <= 1'b1;
fft_s_data_tlast<=1'b1;//tlast置1
fft_s_data_tdata <= {16'd0,Time_data_I[count]};
count <= 8'd0;
end
else begin
fft_s_data_tvalid <= 1'b1;
fft_s_data_tlast<=1'b0;//tlast置0
fft_s_data_tdata <= {16'd0,Time_data_I[count]};
count <= count + 1'b1;
end
end
else begin
fft_s_data_tvalid <= 1'b0;
fft_s_data_tlast <= 1'b0;
fft_s_data_tdata <= fft_s_data_tdata;
end
end
//取频谱数据出来
always @(posedge clk) begin
if (fft_m_data_tvalid) begin
fft_i_out<=fft_m_data_tdata[23:0];
fft_q_out<=fft_m_data_tdata[47:24];
end
end
always @(posedge clk) begin
fft_abs<=$signed(fft_i_out)* $signed(fft_i_out)+ $signed(fft_q_out)* $signed(fft_q_out);
end
//fft ip核例化
xfft_0 u_fft(
.aclk(clk), // 时钟信号(input)
.aresetn(rst_n), // 复位信号,低有效(input)
.s_axis_config_tdata(8'd1), // ip核设置参数内容,为1时做FFT运算,为0时做IFFT运算(input)
.s_axis_config_tvalid(1'b1), // ip核配置输入有效,可直接设置为1(input)
.s_axis_config_tready(fft_s_config_tready), // output wire s_axis_config_tready
//作为接收时域数据时是从设备
.s_axis_data_tdata(fft_s_data_tdata), // 把时域信号往FFT IP核传输的数据通道,[31:16]为虚部,[15:0]为实部(input,主->从)
.s_axis_data_tvalid(fft_s_data_tvalid), // 表示主设备正在驱动一个有效的传输(input,主->从)
.s_axis_data_tready(fft_s_data_tready), // 表示从设备已经准备好接收一次数据传输(output,从->主),当tvalid和tready同时为高时,启动数据传输
.s_axis_data_tlast(fft_s_data_tlast), // 主设备向从设备发送传输结束信号(input,主->从,拉高为结束)
//作为发送频谱数据时是主设备
.m_axis_data_tdata(fft_m_data_tdata), // FFT输出的频谱数据,[47:24]对应的是虚部数据,[23:0]对应的是实部数据(output,主->从)。
.m_axis_data_tuser(fft_m_data_tuser), // 输出频谱的索引(output,主->从),该值*fs/N即为对应频点;
.m_axis_data_tvalid(fft_m_data_tvalid), // 表示主设备正在驱动一个有效的传输(output,主->从)
.m_axis_data_tready(fft_m_data_tready), // 表示从设备已经准备好接收一次数据传输(input,从->主),当tvalid和tready同时为高时,启动数据传输
.m_axis_data_tlast(fft_m_data_tlast), // 主设备向从设备发送传输结束信号(output,主->从,拉高为结束)
//其他输出数据
.event_frame_started(fft_event_frame_started), // output wire event_frame_started
.event_tlast_unexpected(fft_event_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(fft_event_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(fft_event_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(fft_event_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(fft_event_data_out_channel_halt) // output wire event_data_out_channel_halt
);
endmodule仿真结果如下图所示:
