![cover](https://img-blog.csdnimg.cn/20210914115710987.png)
quartus FFT IP核使用
想必学过信号的同学都知道“时域”和“频域”是信号的基本性质,也是分析信号的两个重要方式。(注:本文指的信号均为一维信号,二维信号一般泛指图像信号)信号的时域信息,即我们使用示波器采集信号时,在示波器界面上显示的信号的波形。但信号的频域信息往往隐藏其中,我们不能直接观察信号的频域信息。此时我们需要采集一段时域信号,并对其做傅里叶变换即可得到信号的频域数据。因此接下来,一休哥将带领大家来学习FFT I
想必学过信号的同学都知道“时域”和“频域”是信号的基本性质,也是分析信号的两个重要方式。(注:本文指的信号均为一维信号,二维信号一般泛指图像信号)
信号的时域信息,即我们使用示波器采集信号时,在示波器界面上显示的信号的波形。但信号的频域信息往往隐藏其中,我们不能直接观察信号的频域信息。此时我们需要采集一段时域信号,并对其做傅里叶变换即可得到信号的频域数据。
因此接下来,一休哥将带领大家来学习FFT IP核的使用。
本文将分四个小部分来展开介绍:
1、回顾信号处理相关知识
2、FFT IP核的配置
3、FFT IP核的使用
4、FFT IP核的调试结果分析
本文涉及到的全部资料链接:
链接:http://pan.baidu.com/s/1mi1HLkG 密码:fxmt
1 回顾信号处理相关知识
在介绍FFT IP核之前呢,在这里我们需要了解一些信号处理知识,梳理清楚一些概念问题。
①什么是信号处理?
从信号的连续性角度来说,信号处理分连续信号处理和离散信号处理。但是,连续信号处理只是在理论上存在着的,在实际的工程项目中,我们往往接触到的是离散信号处理。为什么这么说呢,这是因为我们想要处理信号,首先就要得到信号,而得到信号的方式就是使用ADC。ADC的作用就是将模拟信号转换为数字信号,而数字信号也就是离散信号。因为,事实上,我们做工程的时候,用到的往往是离散信号处理,而连续信号处理在理论分析与仿真上用的很多。
②傅里叶变换与FFT的区别
耳熟能详的,大家都知道傅里叶变换,它的作用就是实现信号的时频域转换。而信号分连续和离散,所以对离散信号做傅里叶变换,则是离散傅里叶变换,简称DFT(Discrete Fourier Transform)。由于DFT的运算量非常大,所以为了简化操作,就出现了一种高效的算法,即快速傅里叶变换(FFT——Fast Fourier Transform)。
③使用FFT的准备工作——ADC的若干性质
在对信号进行FFT处理之前,通常我们会使用ADC模块对信号进行一段时间的采样。采样频率与ADC芯片的性能有很大关系,ADC芯片的最小转换速率近似等于信号的最大采样频率。事实上,ADC芯片分为串行和并行两种,并行ADC芯片的转换速率比串行ADC芯片大,而我们进行信号采样时,我们所用的采样频率要小于转换速率的90%,所以在进行高速信号处理时,我们一般选用并行ADC芯片。当然,并行ADC芯片很适合做高速处理,但是由于其价格昂贵,精度一般较低等缺点,我们在选择ADC芯片的时候,需要从转换速率、数据精度和价格之间来做权衡。
最大转换速率:ADC芯片更新一次有效输出数据的最短时间。
数据精度:ADC芯片输出数据的位宽。
2 FFT IP核的配置
好了,说了这么多,该来的干货总算是来了。下面,我们将时间交给FFT。
本次实验,一休哥选用的是锆石科技推出的Altera系列经典开发板——A4。
实验平台为:Quartus II 13.1
FPGA芯片:EP4CE10F17C8
首先,我们打开Quartus II 13.1软件,点击MegaWizard Plug-In Manager,搜索FFT,选择后进入FFT核设置界面。如下图所示。
我们将在“Step1:Parameterize”中设置FFT核的相关参数,由于一休哥暂时没有计划介绍FFT的仿真,所以选择不设置Step2部分,但是也不影响大家对FFT IP核的理解和运用。
①点击“Step1:Parameterize”,进入FFT IP核的参数配置。从下图中我们可以看到,转换长度(Transform Length)为1024个点,数据输入精度(Data Input Precision)为10bits。设置旋转因子精度(Twiddle Precision)时,我们一般让它与数据输入精度一样即可,为10bits。
②设置FFT核的输入/输出数据流的方式为Streaming模式。
FFT的IP核提供4种运算结构,以方便用户根据运算速度及硬件资源情况进行选择使用。按运算速度从高到低(相对的,资源占用从多到少)的顺序排列为Streaming、Variable Streaming、Buffered Burst、Burst。其中Variable Streaming可以处理浮点型数据,Burst与Buffered Burst采用的蝶形运算单元更少,为了节约资源而牺牲了处理速度。
③设置FFT核的结构,选择“4 Mults/2 Adders”来实现复数乘法运算。(Mults为乘法器,Adders为加法器。)。选择“DSP Blocks/Logic Cells”(DSP块/逻辑资源)来实现乘法运算。
④经过上述三个步骤的配置后,Step1就结束了,由于我们不弄仿真,所以允许一休哥我跳过Step2,直接点击“Step3:Generate”。至此,我们就算成功配置完了FFT IP核了。
⑤在生成FFT IP核的界面中我们可以看到IP核所有的引脚。
注:实际的输出数据为(source_real + j* source_imag)*2^ source_exp。因此FFT实际的运算结果需要将实部和虚部与缩放因子相结合。如果缩放因子为负,实部和虚部要左移相应的位数,如果为正则右移。
3 FFT IP核的使用
配置完IP核之后,我们该如何使用呢,其实很简单,我们只需要参考FFT IP核的时序图来编写相应代码不就OK了吗。在准备本文之前,一休哥特意去了Altera官网去搜索了FFT IP核的官方文档。然后一休哥很快就找到了FFT IP核在Streaming模式下的输入输出时序图。
首先,我们来看看输入时序。在reset_n复位之后,确保sink_valid和sink_ready同时有效,并拉高一个时钟的sink_sop信号即可开始输入数据。当输入最后一个数据的同时拉高一个时钟的sink_eop信号,之后将sink_valid拉低,则表示一次输入结束。
FFT IP核的输出时序也十分简单,当我们需要接受输出数据时,只需拉高source_ready信号,当source_valid有效且source_sop拉高一个时钟周期时,表示FFT IP核开始输出数据,随后直到输出完最后一个数据的同时source_eop拉高一个时钟周期。
学习完FFT IP核的时序后,接下来我们来看FFT IP核的控制代码:
/* FFT控制信号,1:IFFT,0:FFT */
assign inverse = 1'b0;
/* FFT输入数据信号 */
assign data_imag_in_int = 10'b0;
/* FFT数据的个数为1024 */
assign fftpts_array = 1023;
/* 输入错误信号 */
assign sink_error = 2'b0;
/* 时序电路,用来给sink_valid寄存器赋值 */
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N)
sink_valid <= 1'b0;
else
sink_valid <= sink_valid_n;
end
/* 组合电路,输入的有效信号 */
always @ (*)
begin
if(end_test || end_input)
sink_valid_n = 1'b0;
else
sink_valid_n = 1'b1;
end
/* 输入开始进行FFT转换运算 */
assign fft_ready_valid = (sink_valid && sink_ready);
/* 时序电路,用来给fft_bit_cnt寄存器赋值 */
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N)
fft_bit_cnt <= 10'b0;
else
fft_bit_cnt <= fft_bit_cnt_n;
end
/* 组合电路,FFT位计数器 */
always @ (*)
begin
if(fft_ready_valid && (fft_bit_cnt == fftpts_array))
fft_bit_cnt_n = 1'b0;
else if(fft_ready_valid)
fft_bit_cnt_n = fft_bit_cnt + 1'b1;
else
fft_bit_cnt_n = fft_bit_cnt;
end
/* 输入流的开始 */
assign sink_sop = (fft_bit_cnt == 1'b0) ? 1'b1 : 1'b0 ;
/* 输入流的结束 */
assign sink_eop = (fft_bit_cnt == fftpts_array) ? 1'b1 : 1'b0;
/* 时序电路,用来给sink_real寄存器赋值 */
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N)
sink_real <= 10'b0;
else
sink_real <= sink_real_n;
end
/* 组合电路,输入的实部信号 */
always @ (*)
begin
if(end_test || end_input)
sink_real_n = 10'b0;
else if (fft_ready_valid)
sink_real_n = data_real_in_int;
else
sink_real_n = sink_real;
end
/* 时序电路,用来给sink_imag寄存器赋值 */
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N)
sink_imag <= 10'b0;
else
sink_imag <= sink_imag_n;
end
/* 组合电路,输入的虚部信号 */
always @ (*)
begin
if(end_test || end_input)
sink_imag_n = 10'b0;
else if (fft_ready_valid)
sink_imag_n = data_imag_in_int;
else
sink_imag_n = sink_imag;
end
/* 停止输出信号 */
assign end_input = (sink_eop && fft_ready_valid) ? 1'b1 : 1'b0;
/* 时序电路,用来给end_test寄存器赋值 */
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N)
end_test <= 1'b0;
else
end_test <= end_test_n;
end
/* 组合电路,结束测试信号 */
always @ (*)
begin
if(end_input)
end_test_n = 1'b1;
else
end_test_n = end_test;
end
/* 源准备信号 */
assign source_ready = 1'b1;
/* 时序电路,用来给fft_real_out_int寄存器赋值 */
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N)
fft_real_out_int <= 10'b0;
else
fft_real_out_int <= fft_real_out_int_n;
end
/* 组合电路,FFT输出的实部信号 */
always @ (*)
begin
if(source_valid && source_ready)
fft_real_out_int_n = source_real[9] ? (~source_real[9:0]+1) : source_real;
else
fft_real_out_int_n = fft_real_out_int;
end
/* 时序电路,用来给fft_imag_out_int寄存器赋值 */
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N)
fft_imag_out_int <= 10'b0;
else
fft_imag_out_int <= fft_imag_out_int_n;
end
/* 组合电路,FFT输出的虚部信号 */
always @ (*)
begin
if(source_valid && source_ready)
fft_imag_out_int_n = source_imag[9] ? (~source_imag[9:0]+1) : source_imag;
else
fft_imag_out_int_n = fft_imag_out_int;
end
/* 时序电路,用来给exponent_out_int寄存器赋值 */
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N)
exponent_out_int <= 10'b0;
else
exponent_out_int <= exponent_out_int_n;
end
/* 组合电路,用于生成FFT输出的指数信号 */
always @ (*)
begin
if(source_valid && source_ready)
exponent_out_int_n = source_exp;
else
exponent_out_int_n = exponent_out_int;
end
/* 时序电路,用来给fft_real_image_data寄存器赋值 */
always @ (posedge CLK_50M or negedge RST_N)
begin
if(!RST_N)
fft_real_image_data <= 32'b0;
else
fft_real_image_data <= fft_real_image_data_n;
end
/* 组合电路,用于生成FFT输出的数据信号 */
always @ (*)
begin
if(source_valid && source_ready)
fft_real_image_data_n = fft_real_out_int*fft_real_out_int + fft_imag_out_int*fft_imag_out_int;
else
fft_real_image_data_n = fft_real_image_data;
end
/* 平方根模块 */
SQRT_Module SQRT_Init
(
.radical (fft_real_image_data ),
.q (sqrt_data_out )
);
4 FFT IP核的调试结果分析
- 经过以上3个部分的介绍,相信大家已经对FFT IP核有一定的了解了。接下来,一休哥带领大家来分析一个具体的工程——FFT频谱仪。(注:本工程是在锆石科技推出的示波器例程A4_Oscilloscope_Top的基础上修改的)
- 在这个例程中,首先使用一个10bits串行DAC芯片输出频率为500Hz的周期信号,通过拨码开关可以选择输出周期信号的类型为正弦波、三角波和锯齿波。然后将DAC与ADC外部相连,ADC输出8bits数据,并将ADC数据同时存入两个双口RAM中,一个RAM用于VGA模块读取以实时显示信号的波形图,另一个RAM被FFT控制模块读取用于做FFT。经过FFT之后的输出数据也被存入第三个双口RAM中,用于VGA模块读取以实时显示信号的频谱图。
- 这个工程虽然是一个开发板的例程,但对初学小白来说,还是相当复杂的,也足见锆石科技的用心良苦吧!(ˇˍˇ)因此,一休哥肯定不能对整个工程进行讲解,一来是没有这个时间,二来是锆石科技的教程文档中已经做出了详细的介绍了,我就别瞎操心了。
- 在此,一休哥只讲述与本文相关的内容,与FFT相关的内容,也就是与信号处理相关的内容。
- 在分析信号处理时,首先,我们要知道ADC的采样频率和精度,刚才精度已经说了是8bits,那么采样频率呢。
- 采样频率之前讲过,就是数据的更新频率。由于本次采用的是串行ADC芯片,其与FPGA相连的引脚有三个,AD_CLK,AD_CS,AD_DATA。其中AD_CS是一个周期信号,其实它的频率就等效于采样频率。
借助Signaltap工具,一休哥采集了三种信号,可以看到AD_CS的一个周期,ADC的一个采样周期状态机,ADC数据的更新周期都为1242个采样周期,因此可以算出它的频率为50M/1242=40257.6Hz。(注:50M为Signaltap的采样时钟。)
知道了ADC的采样频率为40257.6Hz后,根据奈奎斯特定理,当采样频率fs大于信号中最高频率f的2倍时(fs>2f),采样之后的数字信号完整地保留了原始信号中的信息。
这句话,其实可以翻译一下,当采样频率为fs时,信号做FFT变换之后,只能得到频率范围为0-fs/2的频谱成分信息。换句话说,如果一个信号经过采样频率为fs采样后,会丢失频率大于fs/2的信号成分信息。
不知道大家对一休哥的这番话有多少领悟呢,没领悟没关系,咱接着说。
对于FFT,我们需要关心两个参数,采样频率(40257.6Hz),采样点数(1024个点)。如输入信号为正弦波,当我们往FFT IP核中输入1024个采样数据后,FFT IP核会滞后输出1024个实部加虚部数据,再对数据求模(对实部和虚部分别平分、相加、开根号)。得到的频谱图为下图。
大家是不是觉得这幅图看起来十分别扭,别急,我们还需要转换一下,将左边的512个数据右移512个点,右边512个数据左移512个点,这样就变成了我们熟悉的频谱图了。
大家都知道,频谱图是对称结构的,我们现在只分析左半部分。还记得刚才那句话吗,当采样频率为fs时,信号做FFT变换之后,只能得到频率范围为0-fs/2的频谱成分信息。换句话说,如果一个信号经过采样频率为fs采样后,会丢失频率大于fs/2的信号成分信息。
可以这么理解,采样频率为40257.6Hz,因此左半部分的频谱范围为0-20128.8Hz,由于左半部分有512个点,因此相邻点之间的频率差为39.3Hz,也就是说,此时,频谱图的频率分辨率为39.3Hz。
频率分辨率有什么用呢,由于测试时的输入信号频率为500Hz,因此根据刚才所说的,我们可以算出在频谱图的左半部分中,第500Hz/39.3=12.7个点的频谱成分应该是最大的。但是由于输出的都是整点数,所以应该是第13个点的值最大。
自然地,大家会想到,为什么不能让频率分辨率变得更小呢。有两种方法,第一个是降低采样频率,但是这样会丢失更多的信号频率成分;第二个是增加采样点数,但是这样会消耗更多的资源。所以,大家需要根据自身需要来选择合适的频率分辨率。一休哥在多说一句,FFT是不适合用于求取周期信号的频率的,因为得不偿失。
说了这么多,由于本文重在讲解FFT相关的信号处理知识,所以对程序讲解较少,不过一休哥觉得已经将自己认为重要的东西都教给大家了,相信大家之后看一休哥的工程代码,理解一下应该不难。最后,我们来直接看工程的效果图吧。依次是正弦波,锯齿波和三角波。
![Logo](https://devpress.csdnimg.cn/79de2bf0b7994defa4242ef90d5513fa.jpg)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)