该篇是FPGA数字信号处理的第9篇,选题为DSP系统中极其常用的FFT运算。上篇介绍了Quartus环境下FFT IP核的使用“FPGA数字信号处理(八)Quartus FFT IP核实现https://blog.csdn.net/fpgadesigner/article/details/80690345 ”。本文将介绍在Vivado开发环境下使用Xilinx提供的FFT IP核进行FFT运算的设计。
Xilinx的FFT IP核属于收费IP,但是不需要像 Quartus那样通过修改license文件来破解。如果是个人学习,现在网络上流传的license破解文件在破解Vivado的同时也破解了绝大多数可以破解的IP核。只要在IP Catalog界面中Fast Fourier Transform的License状态为“Included”即可正常使用。
与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结构或选择可以提高计算速度的优化选项,消耗更多的资源来缩短计算周期。
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 。