该篇是FPGA数字信号处理的第9篇,选题为DSP系统中极其常用的FFT运算。上篇介绍了Quartus环境下FFT IP核的使用“FPGA数字信号处理(八)Quartus FFT IP核实现
https://blog.csdn.net/fpgadesigner/article/details/80690345 ”。本文将介绍在Vivado开发环境下使用Xilinx提供的FFT IP核进行FFT运算的设计。
IP核概述
Xilinx的FFT IP核属于收费IP,但是不需要像 Quartus那样通过修改license文件来破解。如果是个人学习,现在网络上流传的license破解文件在破解Vivado的同时也破解了绝大多数可以破解的IP核。只要在IP Catalog界面中Fast Fourier Transform的License状态为“Included”即可正常使用。
IP核参数设置
与Quartus中FFT IP核相比,Vivado的FFT IP核配置起来更复杂,功能也更强大。 打开主界面,左边是IP核的接口图(IP Symbol)、实现消耗的资源等信息(Implementation Details)和计算FFT所需的时间(Latency),右边是Configuration、Implementation和Detailed Implementation三个标签卡。
Vivado的FFT IP核支持多通道输入(Number of Channels)和实时更改FFT的点数(Run Time Configurable Transform Length)。Configuration标签下设置FFT的点数(Transform Length)和工作时钟(Target Clock Frequency),选择一种FFT结构,包括流水线Streaming、基4 Burst、基2 Burst和轻量级基2 Burst,计算速度和消耗的资源依次减少。
Implementation标签卡下设置FFT的数据格式为定点Fixed Point或浮点Float Point;设置输入数据的位宽和相位因子位宽(相当于旋转因子)。还有一些可选的附加信号。“Output Ordering”设置FFT计算结果以自然顺序(Nature Order)或位/数值反序(Bit/Digit Reversed Order)输出。
Detailed Implementation这个Tab中设置优化方式、存储的类型、是否使用DSP单元等与综合、实现有关的信息,比如可以选择复数乘法器和蝶形运算单元的实现结构。
设置完成后,系统会在Latency中显示出计算FFT所需的时间,如下图所示:
可以据此衡量计算速度是否满足设计需求,如不满足可以使用更好性能的FFT结构或选择可以提高计算速度的优化选项,消耗更多的资源来缩短计算周期。
FPGA设计
IP核的接口在Verilog HDL中进行设计时,一定要参考官方文档中给出的时序图。在IP核的配置界面点击“documentation”,可以找到IP核的user guide。 也可以在Xilinx官网或DocNav工具中搜索pg109,查阅FFT IP核的说明。Burst模式、自然顺序输出的时序图如下:
驱动接口时序的Verilog HDL示例代码如下所示:
`timescale 1ns / 1ps
//--------------------------------------------------------
// 使用Xilinx FFT IP核完成FFT运算
//--------------------------------------------------------
module Xilinx_FFT_Guide_liuqi(
input aclk,
input aresetn,
input [11:0] input_data_ch1,
output [23:0] fft_real,
output [23:0] fft_imag,
output reg [46:0] amp,
output fft_out_valid
)
reg [11:0] input_data_ch1_reg
wire [7:0] s_axis_config_tdata
reg s_axis_config_tvalid
wire s_axis_data_tready
reg [31:0] s_axis_data_tdata
reg s_axis_data_tvalid
reg s_axis_data_tlast
wire [47:0] m_axis_data_tdata
wire [15:0] m_axis_data_tuser
wire m_axis_data_tvalid
wire m_axis_data_tlast
reg [7:0] cfg_cnt
reg [13:0] sink_ctl_cnt
reg [23:0] fft_real,fft_imag
reg fft_out_valid
wire event_frame_started
wire event_tlast_unexpected
wire event_tlast_missing
wire event_status_channel_halt
wire event_data_in_channel_halt
wire event_data_out_channel_halt
xfft_0 usr_FFT(
.aclk(aclk),
.aresetn(aresetn),
.s_axis_config_tdata(s_axis_config_tdata),
.s_axis_config_tvalid(s_axis_config_tvalid),
.s_axis_config_tready(),
.s_axis_data_tdata(s_axis_data_tdata),
.s_axis_data_tvalid(s_axis_data_tvalid),
.s_axis_data_tready(),
.s_axis_data_tlast(s_axis_data_tlast),
.m_axis_data_tdata(m_axis_data_tdata),
.m_axis_data_tuser(m_axis_data_tuser),
.m_axis_data_tvalid(m_axis_data_tvalid),
.m_axis_data_tready(1'b1),
.m_axis_data_tlast(m_axis_data_tlast),
.event_frame_started(event_frame_started),
.event_tlast_unexpected(event_tlast_unexpected),
.event_tlast_missing(event_tlast_missing),
.event_status_channel_halt(event_status_channel_halt),
.event_data_in_channel_halt(event_data_in_channel_halt),
.event_data_out_channel_halt(event_data_out_channel_halt)
)
//////////////////////////////fft core config////////////////////////
always@(posedge aclk or negedge aresetn)
begin
if(!aresetn)
cfg_cnt <= 8'd0
else
begin
if(cfg_cnt < 8'd200)
cfg_cnt <= cfg_cnt + 1'b1
else
cfg_cnt <= cfg_cnt
end
end
always@(posedge aclk or negedge aresetn)
begin
if(!aresetn)
s_axis_config_tvalid <= 1'b0
else
begin
if(cfg_cnt < 8'd200)
s_axis_config_tvalid <= 1'b1
else
s_axis_config_tvalid <= 1'b0
end
end
assign s_axis_config_tdata = 8'd1
/////////////////////////////fft sink_in ctl/////////////////////////
always@(posedge aclk or negedge aresetn)
begin
if(!aresetn)
s_axis_data_tdata <= 32'b0
else
s_axis_data_tdata <= {20'd0,input_data_ch1}
end
always@(posedge aclk or negedge aresetn)
begin
if(!aresetn)
sink_ctl_cnt <= 14'd8194
else if(s_axis_config_tvalid)
sink_ctl_cnt <= 14'd0
else if(sink_ctl_cnt == 14'd8192)
sink_ctl_cnt <= 14'd1
else
sink_ctl_cnt <= sink_ctl_cnt + 1'b1
end
//s_axis_data_tvalid
always@(posedge aclk or negedge aresetn)
begin
if(!aresetn)
s_axis_data_tvalid <= 1'b0
else if(sink_ctl_cnt < 14'd1)
s_axis_data_tvalid <= 1'b0
else if(sink_ctl_cnt < 14'd2049)
s_axis_data_tvalid <= 1'b1
else
s_axis_data_tvalid <= 1'b0
end
//s_axis_data_tlast
always@(posedge aclk or negedge aresetn)
begin
if(!aresetn)
s_axis_data_tlast <= 1'b0
else
begin
if(sink_ctl_cnt == 14'd2048)
s_axis_data_tlast <= 1'b1
else
s_axis_data_tlast <= 1'b0
end
end
/////////////////////////////fft sink_in ctl/////////////////////////
always@(posedge aclk)
begin
fft_real <= m_axis_data_tdata[23:0]
end
always@(posedge aclk)
begin
fft_imag <= m_axis_data_tdata[47:24]
end
always@(posedge aclk)
begin
fft_out_valid <= m_axis_data_tvalid
end
/********** 计算频谱的幅值信号 **********/
wire signed [45:0] xkre_square, xkim_square
assign xkre_square = fft_real * fft_real
assign xkim_square = fft_imag * fft_imag
always @(posedge aclk)
amp <= xkre_square + xkim_square
endmodule
注意FFT计算结果输出的实部和虚部供用m_axis_data_tdata数据总线,因此在代码中需要截位分别得到实部和虚部。FFT计算结果的分析可以参考“FPGA数字信号处理(八)Quartus FFT IP核实现
https://blog.csdn.net/fpgadesigner/article/details/80690345 ”中的内容。
仿真与工程下载
系统时钟(即FFT计算时钟)为50MHz,因此频谱范围为0~25MHz。使用MATLAB生成2MHz与15MHz的正弦波信号,分别写入txt文件。编写Testbench分别读取两个txt文件对两个单频信号做FFT分析,文件操作方法参考“Testbench编写指南(一)文件的读写操作”
https://blog.csdn.net/fpgadesigner/article/details/80470972。
首先仿真对2MHz信号的FFT分析。根据上文Latency中的估计,计算时间需要145μs,因此仿真需要运行到大约150μs以上。结果如下:
整个频谱的持续时间恰好是out_valid信号保持高电平的时间,很明显可以看到2MHz信号在频谱中对应的一个频率点。将txt文件替换为15MHz的信号,结果如下:
观察到15Mhz信号的频谱峰值位置明显比2MHz频率靠中,符合事实。
完整的Vivado工程(含testbench仿真)可以在这里下载:
https://download.csdn.net/download/fpgadesigner/10478838 。