赞
踩
关于这方面的内容,有些文章已经写的很好很详细了。不过我在使用的过程中,还是踩了一些坑,我在这里详细的介绍了IP核每一个设置的作用,然后写了个fft计算和ifft计算的环路的测试程序。应该可以帮大家学会使用fft的同时,也对它有个较为全面的理解。
利用FPGA计算FFT和MATLAB的结果是一样的,可以获得同样的实部和虚部,还可以获得相应的频率坐标,虽然由于字节有限长的影响,精度会差些,但可以设置32位,一般也够用了。
下面是我用matlab和fpga分布做fft和ifft得到的一些结果,原始信号是一个正弦一个余弦的单频信号,时域如下图
下面是分别利用matlab和fpga做fft的结果,可以看到形状是一致的,然后最大值的数值也基本一致。具体哪个点对应哪个频率可以通过,点数(m_axis_data_tuser)/N*fs来计算,和matlab是一致的,不过fpga中没有fftshift函数可以用。
对其做ifft,结果如下:
这是我画这些图对应的matlab程序,大家后面做测试的时候可以直接用,fpga部分在讲完IP核后再给出。
N=128; %FFT的点数
f1=30;
f2=70;
fs=600;
t=(1:N)/fs;
s=cos(2*pi*f2*t)+sin(2*pi*f1*t);
% s=sin(2*pi*f1*t);
data_fft_before=floor(s*100);
figure;plot(t,data_fft_before);
title('时域原始信号')
f=linspace(0,fs,N);
fft_s=fft(data_fft_before);figure;plot(f,real(fft_s),'r');hold;plot(f,imag(fft_s),'b')
title('频域');
legend('实部','虚部');figure;plot(f,abs(fft_s));
title('频域幅值');
ifft_s=ifft(fft_s);absffts=real(ifft_s);
figure;plot(absffts)
title('反傅里叶变换还原的信号');%下面是把数据转换为16位的2进制数,如果后面选择不同位宽,把这里的16改成相应位宽就行
fp=fopen('D:\Matlab2016\bin\fft\data_fft_before.txt','w');%这里改成你自己需要的文件路径
for i=1:N
if(data_fft_before(i)>=0)
temp=dec2bin(data_fft_before(i),16); %这里的16是FPGA输入数据位宽
else
temp=dec2bin(data_fft_before(i)+2^16,16);
end
for j=1:16
fprintf(fp,'%s',temp(j));
end
fprintf(fp,'\r\n');
end
fclose(fp);
如果不想看说明,可以直接按我图上的进行配置就行。
ip核的调出,选FFT就行。
下面这个是LTE(Long-Term Evolution)无线通信技术的标准而设计。该IP核是为了满足LTE系统中特殊的FFT大小(如LTE信道带宽所需的FFT点数),以及URE(资源元素)或子载波间隔等LTE参数优化的,一般不需要用这个。
(1)Number of Channels:这个参数可以选择做FFT的通道数,也就是这个IP核同时对几组数据做FFT/IFFT。我在其他地方用的时候需要做两组FFT,然后把它们相乘后做IFFT,设置通道为2,这还方便我解决数据同步的问题。
(2)Transform Length:FFT的点数,一般输入的信号长度要和这个数值相同,不足的情况下,IP核会在末尾自动补零.
(3)Target Clock Frequency (目标时钟频率):按我的理解这是IP核的工作频率,它得和下面的数据吞吐量相适应。
(4)Target Data Throughput (目标数据吞吐量):这个参数指的是FFT IP核期望处理的数据吞吐量,表明IP核应该能够每秒处理多少样本数,从而保证FFT变换的输出与输入能够按照一定速率进行,以匹配系统其他部分的处理速率,如ADC(Digital-to-Analog Converter)数据捕捉率或后续数据处理模块。(3)和(4)是给自动选择架构时计算时延做参考的,IP核不能保证达到相应的吞吐率。
(5)Architecture Choice(FPAG架构的选择):从上面到下面的架构,速度依次降低,复杂度也依次降低。选好后可以在左上角的选项卡里看到一次FFT的计算时间。
(6)Run time configurable Transform length(FPGA长度实时改变):选这个意味着可以在运行过程中改变FFT的计算长度。例如,你可能有一个无线通信系统,在其中需要针对不同的信道带宽进行FFT操作,这会要求FFT变换的长度根据情况进行调整。若FFT IP核支持运行时配置,那么可以在软件控制下更改其变换长度,如从1024点切换到2048点或其他任何支持的长度。
(1)Data Format:选择输入输出数据采样是采用定点格式(Fixed point),还是采用IEEE-754单精度(Floating point)(32位)浮点格式。当内核处于多通道配置中时,浮点格式不可用。
下面两个选项是用来解决FPGA计算的有限字符问题,指数字系统中数值的表示、操作和运算由于位宽限制导致的问题。由于FPGA中的资源有限,对于数字电路设计而言,我们不能使用无限位宽来表示一个数,而是需要根据应用需求和资源限制使用固定的位数。
(2)Scaling Opitons:
Unscaled: 所有的整数位增长都携带到输出中,会使用更多的FPGA资源。这里我踩过一个坑:因为我的测试程序是把fft得到的数据直接给到ifft的输入端,因为选择了这个选项,数据位扩展了,所以输入的数据有问题,但这种问题vivado是不会报错的。
Scaled:用户定义的缩放计划决定数据如何在FFT阶段之间缩放,通过寄存机s_axis_config_data进行配置。
Block Floating-Point:内核确定需要多少缩放才能充分利用可用的动态范围,并将缩放因子报告为块指数。
(3)Rounding Model(一般如果你使用的位宽足够时也不用纠结这个):
截断(Truncation):这是最简单的舍入模式,直接舍弃掉所有多余的LSBs,不添加任何额外的逻辑来处理这些位。这意味着数据始终向下舍入至最接近的值,可能会导致系统性偏差或误差累积,特别是在处理大量数据时这种误差的效果可能会被放大。
收敛舍入(Convergent Rounding):收敛舍入也被称之为向偶数舍入(round-to-even)或银行家舍入法(Bankers Rounding)。这种舍入方法相对公平,并且无偏。当一个数字的小数部分正好等于0.5(即一半)时,根据数字的奇偶性来决定是向上还是向下舍入。如果该数字是奇数,那么结果将向上舍入到下一个偶数;如果是偶数,则舍入到最近的偶数。这种方法可以消除随机舍入误差累积的偏差,更平均地分布上舍和下舍的情况,并且在统计上是无偏的。
(4) Precision Options:输入数据和相位因子可以独立配置宽度从8到34位,包括。当数据格式为浮点时,输入数据宽度固定为32位,相位因子宽度可设置为24或25位,具体取决于所需的噪声性能和可用资源。
(5)Control Signals:时钟使能(aclken)和同步清除(aresetn)是可选引脚。如果两者都被选中,同步清除将覆盖时钟启用。如果不选择某个选项,则可以节省一些逻辑资源,并且可以实现更高的时钟频率。
(6)Output Ordering:这个决定数据是按什么顺序输出的,一般选natural order就行。
(7)optional output fileds :选项输出字段
xk_index:FFT 变幻的结果索引,在m_axis_data_user中有相应的字段。
OVFLO是变换中溢出的指示信号,对应event_fft_overflow。
(8)Throttle Schemes: 在性能和数据定时需求之间进行权衡。实时模式通常提供更小、更快的设计,但对必须提供和使用数据的时间有严格的限制。非实时模式没有这样的限制,但设计可能更大更慢。
这一页是关于数据存储的设置,一般默认就行。
IP核使用的是AXI协议,它的主要特点是不管是发配置还是数据都会有个握手的过程,通过valid和ready这两个信号,其中发送方控制valid信号,接收方控制ready信号,这两个信号同时拉高时,也就是双方都准备好了,传输的内容才有效。
实例化之前,先对IP核使用的通信协议的引脚进行介绍 ,主要是配置通道,数据输入通道,数据输出通道
.aclk(aclk), 输入时钟
.aresetn(aresetn), 复位
Input[N:0]: s_axis_config_tdata:
一般用到最低位控制。1fft 0ifft。如果我们只需要FFT就直接设置N'b1就ok。前面几位的配置可以去参考手册,是关于缩放和循环前缀的配置。
Input: s_axis_config_tvalid:配置数据有效。这是是我们给IP核输入配置数据,没有特殊要求一直拉高就行,拉高两个时钟周期之后,将端口s_axis_data_tvalid和s_axis_data_tready拉高。
Output: s_axis_config_tready:可以接收配置数据了。复位两个时钟周期后,该口给1输出;若干个时钟周期后,自动归零。没有特殊需要直接出去wire就好。
Input: s_axis_data_tvalid:输入数据有效。当IP核准备好接收数据,也就是s_axis_data_tready高电平后,将s_axis_data_tvalid拉高N个周期,输入N个数据进行fft;N是FFT的点数。
Output: s_axis_data_tready:复位两个时钟周期后,该引脚;此时ip核初始化完成,可进行数据输入。 我们可以通过这个信号来拉高valid信号。
Input[M:0]: s_axis_data_tdata:将数据输入进行FFT运算。高一半的位是虚部,低一半的位是实部。输入实数的时候可以把虚部全部置零,注意这里输入的是有符合的二进制数。
Input: s_axis_data_tlast:输入最后一个数据时拉高,停止数据输入。TLAST是起到一个输入数据末端指示作用。但根据我的测试,就算不配置这个引脚也不影响fft的计算,看手册上说它会影响后面的event信号,也都是一些指示信号。
Output[M1:0]:m_axis_data_tdata:高位为虚部,低位为实部。想看幅值的话,在FPGA里不好开方,我们一般看的是幅值的平方,也就是功率谱。
Output: m_axis_data_tvalid:当ip核计算完以后会拉高,输出N个点的数据后拉低。
Output[M2:0]:m_axis_data_tuser:输出值*fs/L为对应频点。这里和matlab是一样的,第一个点也是直流的值。
Input: m_axis_data_tready:这是告诉ip核我们准备好接收数据了,我们当然时刻准备着,一直拉高就行。
还有一些引脚相对来说没那么重要,这里就不一一介绍了。
测试程序如下,结合上面的matlab程序是可以直接用的。
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/12/26 14:41:06
// Design Name:
// Module Name: fft_test
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module fft_test();
reg clk,rstn;
reg signed [15:0]TIME_DATA[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[31:0]fft_m_data_tdata;
wire signed[31:0]ifft_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;
reg[10:0] count;
reg signed [15:0]fft_i_out;
reg signed [15:0]fft_r_out;
reg signed [15:0]ifft_i_out;
reg signed [15:0]ifft_r_out;
reg signed [31:0]fft_abs;
reg signed [31:0]ifft_abs;
initial begin
clk=1'b1;
rstn=1'b0;
fft_m_data_tready=1'b1;
#100;
rstn<=1'b1;
$readmemb("D:/Matlab2016/bin/fft/data_fft_before.txt",TIME_DATA);//这里注意要改成matlab生成数据时的路径,而且注意不是\,而是/。
end
always #5 clk=~clk;
//循环给fft核输入128位数据
always @(posedge clk or negedge rstn)begin
if(!rstn)begin
fft_s_data_tvalid<=1'b0;
count<=0;
fft_s_data_tlast<=1'b0;
end
else if(fft_s_data_tready )begin
if(count<8'd128)
begin
fft_s_data_tvalid<=1;
fft_s_data_tdata<={16'b0,TIME_DATA[count]}; //这里虚部直接给的0
count<=count+1;
fft_s_data_tlast<=1'b0;end
else begin
fft_s_data_tvalid<=0;
fft_s_data_tlast<=1'b1;
#3000;
count<=0;
end
end
else begin
fft_s_data_tvalid<=0;
end
end
//高位赋值给虚部,低位赋值给实部
always @(posedge clk)begin
if(fft_m_data_tvalid)
begin
fft_i_out=fft_m_data_tdata[31:16];
fft_r_out=fft_m_data_tdata[15:0];
end
end
//求幅值,这里的值是没有开方的
always @(posedge clk)begin
fft_abs<=$signed(fft_i_out)*$signed(fft_i_out)+$signed(fft_r_out)*$signed(fft_r_out);
end
//实例化fft核
xfft_0 fft0(
.aclk (clk),
.aresetn (rstn),
.s_axis_config_tdata (8'b1),
.s_axis_config_tvalid (1'b1),
.s_axis_config_tready (fft_s_config_tready),
.s_axis_data_tdata (fft_s_data_tdata),
.s_axis_data_tvalid (fft_s_data_tvalid),
.s_axis_data_tready (fft_s_data_tready),
.s_axis_data_tlast (fft_s_data_tlast),
.m_axis_data_tdata (fft_m_data_tdata),
.m_axis_data_tuser (fft_m_data_tuser),
.m_axis_data_tvalid (fft_m_data_tvalid),
.m_axis_data_tready (fft_m_data_tready),
.m_axis_data_tlast (fft_m_data_tlast)
);
wire signed[31:0] ifft_s_data_tdata;
assign ifft_s_data_tdata=fft_m_data_tdata;
wire ifft_s_data_tready;
wire ifft_s_data_tvalid;
assign ifft_s_data_tvalid=fft_m_data_tvalid;
wire ifft_s_data_tlast;
assign ifft_s_data_tlast=fft_m_data_tlast;
wire signed[7:0]ifft_m_data_tuser;
wire ifft_m_data_tlast;
wire ifft_m_data_tvalid;
wire ifft_s_config_tready;
//实例化ifft核,把fft核输出的数据直接作为它的输入
xfft_0 ifft0(
.aclk (clk),
.aresetn (rstn),
.s_axis_config_tdata (8'b0), //因为是做iit,所以这里配置为0
.s_axis_config_tvalid (1'b1),
.s_axis_config_tready (ifft_s_config_tready),
.s_axis_data_tdata (ifft_s_data_tdata),
.s_axis_data_tvalid (ifft_s_data_tvalid),
.s_axis_data_tready (ifft_s_data_tready),
.s_axis_data_tlast (ifft_s_data_tlast),
.m_axis_data_tdata (ifft_m_data_tdata),
.m_axis_data_tuser (ifft_m_data_tuser),
.m_axis_data_tvalid (ifft_m_data_tvalid),
.m_axis_data_tready (fft_m_data_tready),
.m_axis_data_tlast (ifft_m_data_tlast)
);
always @(posedge clk)begin
if(ifft_m_data_tvalid)
begin
ifft_i_out=ifft_m_data_tdata[31:16];
ifft_r_out=ifft_m_data_tdata[15:0];
end
end
always @(posedge clk)begin
ifft_abs<=$signed(ifft_i_out)*$signed(ifft_i_out)+$signed(ifft_r_out)*$signed(ifft_r_out);
end
endmodule
写这篇文章还是花了挺多的时间的,希望能对大家有所帮助,有什么问题欢迎一起讨论~
奥利给
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。