程序地带

Vivado Xilinx FFT IP核v9.0 使用详解(附仿真实例)


Vivado Xilinx FFT IP核v9.0 使用详解(附仿真实例)

前几天我导让我研究研究在FPGA上做FFT,作为一个迈进FPGA大门的小白,摸索之旅相当艰难~,现把学习FFT IP核的过程记录下来,为各位同胞提供参考。


一 傅里叶变换FFT

想必大家对傅里叶老人家都不陌生了,网上也有这方面的很多资料。通过FFT将时域信号转换到频域,从而对一些在时域上难以分析的信号在频域上进行处理。在这里,我们需要注意采样频率、FFT采样点数这两个参数:


根据奈奎斯特采样定理,采样频率需大于信号频率的两倍;FFT采样点数,代表对信号在频域的采样数;

采样频率Fs和采样点数N决定了信号的频域分辨力,即分辨力=Fs/N,即N越大,频域分辨力越好,反之频域分辨力越差。


二 Xilinx FFT v9.0
1.输入输出端口

在这里插入图iii描述 如上图所示,左侧的端口均为输入端口,右侧端口均为输出端口,其中,S_AXIS_DATA为输入数据端口,我们要进行FFT的数据需要通过这根线输入给IP核;S_AXIS_CONFIG为输入配置端口,这个信号包含了对数据进行FFT还是IFFT、缩放因子、FFT变换点数等信息;FFT变换后的数据从M_AXIS_DATA端口输出。这些端口的具体功能可以参见pg109手册。


2.Vivado中IP核的配置

打开Vivado软件,我的版本是2018.04 在这里插入图片描述 找到FFT IP核后,双击,弹出如下对话框: 在这里插入图片描述 第二页implementation 在这里插入图片描述 第三页 在这里插入图片描述 配置完成后,我们可以点击左侧的implementation detail选项卡,看到IP核的具体信息: 在这里插入图片描述 其中包含了S_AXIS_DATA_TDATA、S_AXIS_CONFIG_TDATA以及M_AXIS_DATA_TDATA的数据格式,我们需要加以关注:


S_AXIS_DATA_TDATA:共32位,其中低16位为输入数据的实部,高16位为输入数据的虚部(但在实际使用中,高16位才是实部,低16位是虚部,如果有大神明白是咋回事儿,欢迎留言)S_AXIS_CONFIG_TDATA:最低位第0位,决定对数据进行FFT还是IFFT,置1时FFT,清零时IIFT,由于要进行补零操作,因此在最终写入S_AXIS_CONFIG_TDATA时,除了最低位以外,还要再补七个零,补到8位M_AXIS_DATA_TDATA:48位数据输出,低24位为实部,高24位为虚部
3.软件仿真

IP核配置完成后,下面开始编写我们的TestBench文件。 我们通过matlab对F(t) = 200 + 100cos(2pi10t) + 100cos(2pi30t) 这个信号以Fs = 100HZ进行采样,采样点数N = 128,采样完成后,将数据转换为16位二进制,并存入txt文件中。matlab程序如下:


clear
Fs=100; %采样率1ns一个点
%t=0:1/Fs:63/Fs; %数据时长:64个采样周期
N = 128;
n = 1:N;
t = n/Fs;
% 生成测试信号
f1 = 10; %
f2 = 30; %
s1 = cos(2*pi*f1*t);
s2 = cos(2*pi*f2*t);
signalN = 2 + s1 + s2 ;
data_before_fft = 100*signalN; %系数放大100倍
fp = fopen('D:ynq_Coredata_before_fft.txt','w');
for i = 1:N
if(data_before_fft(i)>=0)
temp= dec2bin(data_before_fft(i),16);
else
temp= dec2bin(data_before_fft(i)+2^16+1, 16);
end
for j=1:16
fprintf(fp,'%s',temp(j));
end
fprintf(fp,' ');
end
fclose(fp);
y = fft(data_before_fft,N);
y = abs(y);
f = n*Fs/N;
plot(f,y);

程序执行结束后,我们可以看到在指定目录下新建了一个txt文件,内容如下所示: 在这里插入图片描述 由于我们在配置IP核的时候配置了数据位宽为16位,因此我们存入的数据也要设置为16位的。采样点数N=128,因此一共有128个这样的数据。


得到采样数据后,在vivado中新建一个sim文件: 在这里插入图片描述


TB文件代码如下:


`timescale 1ns / 1ps
module FFT_test2();
reg clk;
reg rst_n;
reg signed [15:0] Time_data_I[127:0];
reg data_finish_flag;
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'b0;
fft_m_data_tready = 1'b1;
$readmemb("D:/Zynq_Core/data_before_fft.txt",Time_data_I);
end
always #5 clk = ~clk;
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;
data_finish_flag <= 1'b0;
count <= 8'd0;
rst_n = 1'b1;
end
else if (fft_s_data_tready) begin
if(count == 8'd127) begin
fft_s_data_tvalid <= 1'b1;
fft_s_data_tlast <= 1'b1;
fft_s_data_tdata <= {Time_data_I[count],16'd0};
count <= 8'd0;
data_finish_flag <= 1'b1;
end
else begin
fft_s_data_tvalid <= 1'b1;
fft_s_data_tlast <= 1'b0;
fft_s_data_tdata <= {Time_data_I[count],16'd0};
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

由于我们设置程序一直保持正向FFT模式,因此将s_axis_config_tdata始终写入1即可。


同时我们还要注意文件读入函数readmemb(),这一函数是以二进制格式读入数据,而readmemh() 是以16进制读入数据,大家不要搞混了。我就是用readmemh() 弄了半天,结果数据一个也不对,找了半天才发现那是h不是b…[cry][cry][cry]


4.仿真分析

运行仿真后,时序图如下所示: 在这里插入图片描述 如图所示,首先判断fft_s_data_tready信号是否为高电平,即IP核是否准备好了接收数据,当检测到该信号有效后,将fft_s_data_tvalid信号拉高,准备向IP核写入数据,并开启count计数。在fft_s_data_tvalid有效期间内,读出指定txt文件中的数据,并在低16位进行补零处理后,按顺序写入到fft_s_data_tdata信号线中。当count计数到127,即最后一个数据时,将fft_s_data_tlast信号拉高,代表数据写入完成。


可以看到,在数据写入完成后(fft_s_data_tlast出现脉冲),fft_s_data_tready变为低电平,则代表IP核此时变为忙状态,不能再继续写入数据。 在这里插入图片描述 延时一段时间后,fft_m_data_tvalid变为高电平,代表fft_m_data_tdata中将输出有效数据,即128点FFT的计算结果。结果的实部和虚部分别见上图中的fft_q_out和fft_i_out。将IP核的计算结果与matlab的计算结果相对比,发现实部数据基本正确,虚部数据略有偏差。 在这里插入图片描述 通过对IP核的计算结果进行分析,发现数据在第0个、第14个和34个数据的位置出现峰值,对应0HZ、10HZ和30HZ,正代表着原始信号中的这三个频率分量,因此FFT IP核计算结果正确无误。


同时,从仿真中还可以看出,当FFT计算结果输出完成后,信号fft_m_data_tlast变为高电平,代表数据输出结束,并在延时一小段时间后,fft_s_data_tready重新变为低电平,代表IP核重新进入到空闲状态。可以进行对IP核下一组数据的输入。


以上就是FFT IP核仿真调试的全部内容,其他复杂的功能在使用到后再进行后续更新~~~


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41594632/article/details/112689545

随机推荐

机器学习高阶训练营

课时001:mlcamp_course_info.mp4课时002:课程介绍.mp4课时003:凸集、凸函数、判定凸函数.mp4课时004:tr...

xuan2717 阅读(142)

ORM思想

ORM思想

ORM思想publicList<Book>findAll(){Connectionconnection=null;PreparedStatementpreparedStatemen...

xiaotai1234 阅读(302)

Vue2.x中使用旧版swiper问题相关

在Vue2.x中使用旧版本swiper问题记录首先在main.js中设置全局引入import'swiper/swiper-bundle.css'importVueAwesomeSwip...

GoGee 阅读(703)

场景结合微信生态的产品能力落地刷脸支付

每一次支付创新,都会开启一条新赛道。移动支付的便捷加快了无现金社会的变革,现金支付、信用卡支付、移动支付,现如今刷脸支付已成为支付新的趋势。今年4月ÿ...

场景定制 阅读(957)

2020-12-14

JS冒泡数组极简教程(带注释)<!DOCTYPEhtml><html><head><metacharset="UT...

曦和百里 阅读(840)

【遥感学习笔记】

波段蓝波段:该波段位于水体衰减系数最小、散射最弱部位,对水的穿透力最大,可获得更多水下细节,用于判别水深、浅海水下地形、水体浑浊度等࿰...

Prophet.Z 阅读(729)