近来开展FFT相关的研究,尝试了一下Xilinx 的FFT IP核,在此分享一下;
源码不上传了(关键代码文中已有),有需要的请在评论区留下邮箱。
本例子的框架为:
FFT IP核设置-->
MATLAB生成模拟的正弦波并保存在.coe文件中-->
实例化一个Block Memory generator 单口ROM读取.coe文件-->
建立Testbench读取ROM,并将其输出的数据 作为FFT IP核的输入-->
查看FFT ip核的输出,并于MATLAB对比。
一、使用工具
Vivado 2018.3,FFT ip核 版本:V9.1;Block Memory generator IP核:V8.4
Matlab;
二、FFT IP核配置
(1)设置为单通道FFT,变换长度设置为1024,FFT架构选择Radix-4;
(2)设置数据类型为定点数,位宽设置为16,那么输入数据格式fix16_15,Phase Factor Witch保持默认
特别注意,这个例子中的数据输出的顺序设置为了Natural,还可以设置为Reversed,本文后面会给出对比。
(3)其他设置保持默认即可。然后点击Finish,完成IP核的配置。
三、仿真数据生成
采用Matlab生成正弦波,频率为50 Hz和120Hz叠加,数据长度设置为2048,波形如下:
在Matlab中实现的FFT变换结果如下:
然后将时域数据保存为.coe格式,供后续Vidado仿真使用。
四、建立仿真
(1)上一步完成了模拟数据的生成,并保存为了.coe格式,本节介绍将.coe中的数据加载到单口ROM中。
新建一个Vivado Block Memory generator,本例中的版本为8.4。
比较简单,Memory type选择Single Port ROM;
数据位宽与MATLAB保存的coe一致,设置为16,深度足够即可,coe中有2048个数据,再此设置深度大于2048
选择保存的.coe文件。
(2)TestBench
实例化两个IP:
ROM your_instance_name (
.clka(clk), // input wire clka
.ena(ena), // input wire ena
.addra(addra), // input wire [15 : 0] addra
.douta(douta) // output wire [15 : 0] douta 延时2个时钟输出
);
xfft_0 xfft_tb(
.aclk(clk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.s_axis_config_tdata(s_axis_config_tdata), // input wire [15 : 0] s_axis_config_tdata
.s_axis_config_tvalid(s_axis_config_tvalid), // input wire s_axis_config_tvalid
.s_axis_config_tready(s_axis_config_tready), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_data_tdata), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(s_axis_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(s_axis_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(s_axis_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_data_tdata), // output wire [31 : 0] m_axis_data_tdata
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tlast(m_axis_data_tlast), // output wire m_axis_data_tlast
.event_frame_started(event_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_tlast_missing), // output wire event_tlast_missing
.event_data_in_channel_halt(event_data_in_channel_halt) // output wire event_data_in_channel_halt
);
设置数据流程,从ROM取出,然后输入到FFT,这段设置最为关键,要和IP核的时序图对应。
always @(posedge clk)
begin
if(!aresetn)
cnt <= 0;
else
begin
cnt <= cnt + 1'b1;
if(cnt == 0)
s_axis_config_tvalid <= 1'b1;
else if(cnt == 3)
s_axis_config_tvalid <= 1'b0;
else if(cnt == 5) //ROM开始读出数据
ena <= 1'b1;
else if(cnt == 7) //开始传输数据
s_axis_data_tvalid <= 1'b1;
else if(cnt == MAX_SIZE + 7)
s_axis_data_tlast <= 1'b1;
else if(cnt == MAX_SIZE + 8)
begin
s_axis_data_tvalid <= 1'b0;
s_axis_data_tlast <= 1'b0;
end
end
end
注意:
FFT s_axis_config_tdata的设置,此处需要特别注意,IP核手册中有详细的解释,NFFT plus padding和CP_LEN plus padding仅在IP核设置了run time configurable transform point size有效,本例子没有设置,因此s_axis_config_tdata的最低为FWD/INV,这个Bit设置为1表示FFT,设置为0表示IFFT,此处设置为1。
SCALE_SCH也是需要特别注意的地方,IP核手册P25有详细解释。
wire [15 : 0] s_axis_config_tdata;
assign s_axis_config_tdata = 16'b0000_0_00_00_00_01_01_1; //10bit + 1bit(FFT/IFFT)
然后是FFP IP核输出数据的处理,比较简单,分成Im和Re两部分。
四、仿真结果
仿真结果如下: