FPGA学习专题-FFT IP核的使用

之前从来没有接触过FFT,但是工作需求,让我不得不对它下手。
具体的FFT的原理我不做过多说明,大概说下是做什么用的。简单说就是频谱分析,说的通俗点就是:一个50Hz的正弦波,对它进行FFT计算,可以得到以下一些信息: 1.该正弦波的频率 2.该正弦波的幅值
你可能会觉得这有什么用?但是你要知道,我输入进FFT的正弦波,事先你是不知道它的任何信息的,包括频率和幅值。FFT就相当于一个盲盒,你给我一杯水我还你一片湖泊。

好,你大概知道了FFT是干嘛的就好了,接下来说怎么用?本文是基于ALTERA的Quartus 18.0版本设计。
step1:产生正弦波
当然,如果你有信号发生器,那就简单多了,不必像我这么麻烦的调DAC,我是使用的查表法,利用ROM中存放的mif文件,依次通过DAC输出,产生的正弦波。(DAC部分的内容会单独给一个专题)
step2:采样正弦波
这个也是一个关键步骤,你需要足够了解ADC的工作时序,采样频率。将上一步产生的正弦波输入进ADC进行采样,转化后的数据保存到一个RAM中,供FFT调用。(ADC部分的内容同样会单独给一个专题)
step3:FFT计算
如果上面比较顺利,那么这里估计会耽误一点时间。因为调用FFT IP核的时候,会有很多的参数,需要很清楚这些参数的功能以及工作时序。

假设你现在已经采样到正弦波数据并且保存到RAM中,准备给FFT使用了,我们开始配置。
①找到FFT IP核

在这里插入图片描述
②配置相关参数
主要参数包括采样点数 数据模式 位宽等。这里需要注意,红框中的两个选项都要执行,不然运行会有报错。

在这里插入图片描述

IP核的配置基本就这些了,然后会生成可以例化的代码,如下

module fft3 (
		input  wire        clk,          //    clk.clk IP核的参考时钟
		input  wire        reset_n,      //    rst.reset_n  复位线
		input  wire        sink_valid,   //   sink.sink_valid  输入数据有效信号
		output wire        sink_ready,   //       .sink_ready  表示FFT IP核准备好接收数据了,实测的时候该信号一直是高电平
		input  wire [1:0]  sink_error,   //       .sink_error  输入报错标志 输入手动置0,不会有影响
		input  wire        sink_sop,     //       .sink_sop    输入数据起始
		input  wire        sink_eop,     //       .sink_eop		输入数据终止
		input  wire [15:0] sink_real,    //       .sink_real	输入数据的实部
		input  wire [15:0] sink_imag,    //       .sink_imag	输入数据的虚部 一般都会直接置0
		input  wire [0:0]  inverse,      //       .inverse		FFT模式选择 正计算置0 逆运算置1
		output wire        source_valid, // source.source_valid		输出数据有效
		input  wire        source_ready, //       .source_ready		准备好输出数据 一般直接拉高,表示一直准备着,随时可以输出数据
		output wire [1:0]  source_error, //       .source_error		输出报错
		output wire        source_sop,   //       .source_sop		输出数据的起始
		output wire        source_eop,   //       .source_eop		输出数据终止
		output wire [15:0] source_real,  //       .source_real		输出数据实部
		output wire [15:0] source_imag,  //       .source_imag		输出数据虚部
		output wire [5:0]  source_exp    //       .source_exp		输出数据缩放因子,实际输出的实部和虚部需要乘以2^source_exp
	);

还是再上个图看看吧
在这里插入图片描述
这些就是在FFT计算的时候需要考虑的一些变量,需要做的就是控制几个输入的信号,来完成整个的FFT计算过程,那么这个就是需要参考各个信号的时序图了。

我们在配置IP核的时候,选择的模式是streaming,因此,找到官方文档查看时序图,如下:
在这里插入图片描述
将输入的波形放大,如下:
在这里插入图片描述
简单分析一下数据输入的时序流程: 当reset拉高后,sink_valid和sink_ready都被直接拉高,sink_valid是需要自己拉高的,sink_ready是IP核自己拉高的,这个值我实测好像是一直是高电平;inverse需要手动拉低;然后给sink_sop一个高脉冲,这时候数据就会开始往FFT中传输了;但是图中没有给出什么时候停止,当sink_eop拉高一个脉冲时,这一帧数据输入完成。FFT就会对sink_sop和sink_eop两个高脉冲之间输入的数据进行FFT计算。这之间的数据点数就是上面设置的1024,当然这个点数由你自己定,数据越多,精度会越高,占用内存也就越高。

然后看一下数据输出的时序图,如下:
在这里插入图片描述
和输入数据类似的:source_ready和sink_valid拉高,表示可以输出了,注意这里的source_ready是手动拉高的,默认一直是高,sink_valid是FFT核拉高的,实测好像一直是高;然后当数据可以输出时,FFT核会将source_sop拉高一个高脉冲,当输出完成1024个数据时,FFT核会将source_eop拉高一个高脉冲,表示一帧数据完成输出。这样就完成了对一帧数据的FFT计算,请注意,这里的一帧数据实际是要计算1024次的,因为有1024个点啊。

最终需要用到的数据就是source_real和source_imag。将这两个值进行平方和计算,然后再开根号,得到的结果我们暂时记作fft_out吧,就是我们需要使用的值。

具体怎么用呢?一开始我们设置了对1024个输入的点进行FFT计算,每一点都会计算得到一组source_real和source_imag,对应的就可以计算出一个fft_out;那么最终就会有1024个fft_out,我们需要在这1024个数据中找到fft_out最大的那个点,假设是第n个点(点号从0-1023计算),那么采样的这个波形的频率就是Fout=(n-1)*Fs/N;其中,Fout是需要测量的信号的频率,n是计算的点号,Fs是ADC的采样频率(这个会在ADC专题中说明),N是采样点数,就是这里的1024。

简单的流程说完了,上示例:


module FFT_Control_Module
(
	//输入端口
	CLK_50M,RST_N,data_real_in_int,
	//输出端口
	fft_bit_cnt,fft_x_cnt,
	vga_freq,vga_fengzhi,
	fft_out,sink_sop,
	sink_ready,sink_valid,
	sink_eop,fft_start,source_error,source_sop,source_eop,source_valid,max_cnt,position,data_max
);

//---------------------------------------------------------------------------
//--	外部端口声明
//---------------------------------------------------------------------------
input					CLK_50M;
input					RST_N;
input 	[ 15:0]	data_real_in_int;//[ 9:0]
output   [10:0] 	fft_bit_cnt;
output   [10:0] 	fft_x_cnt;
output 	[31:0] 	vga_freq;
output 	[31:0] 	vga_fengzhi;
output 	[ 15:0] 	fft_out;//
output sink_sop;
output sink_ready;
output reg sink_valid;
output sink_eop;
output reg fft_start;
output [1:0] source_error;
output source_sop;
output source_eop;
output source_valid;
output reg [10:0] max_cnt;
output reg [10:0] position;
output reg [15:0] data_max;
//---------------------------------------------------------------------------
//--	内部端口声明
//---------------------------------------------------------------------------
wire					inverse;
wire 		[ 15:0] 	data_imag_in_int;//[ 9:0]
wire 		[ 9:0] 	fftpts_array;
wire		[ 1:0]	sink_error;
//wire					sink_ready;
//reg					sink_valid;
reg					sink_valid_n;
wire					fft_ready_valid;
reg		[10:0]	fft_bit_cnt;
reg		[10:0]	fft_bit_cnt_n;
//wire					sink_sop;
//wire					sink_eop;
reg		[ 15:0]	sink_real;
reg		[ 15:0]	sink_real_n;
reg		[ 15:0]	sink_imag;
reg		[ 15:0]	sink_imag_n;
wire 					end_input;
reg					end_test;
reg					end_test_n;
wire					source_ready;
wire					source_valid;
wire	 	[ 15:0]	source_real;
reg 		[ 15:0] 	fft_real_out_int;
reg 		[ 15:0] 	fft_real_out_int_n;
wire	 	[ 15:0]	source_imag;
reg 		[ 15:0]	fft_imag_out_int;
reg 		[ 15:0] 	fft_imag_out_int_n;
wire		[ 5:0]	source_exp;
reg 		[ 5:0]	exponent_out_int;	
reg 		[ 5:0]	exponent_out_int_n;
reg 		[31:0] 	fft_real_image_data;
reg 		[31:0] 	fft_real_image_data_n;
wire		[15:0] 	sqrt_data_out;
wire					fft_read;
//wire					source_sop;
reg   	[ 10:0] 	fft_x_cnt;
reg  		[ 10:0] 	fft_x_cnt_n;
//reg 		[ 15:0] 	data_max;
reg 		[ 15:0] 	data_max_n;
reg 		[ 15:0] 	data_max2;
reg 		[ 15:0] 	data_max2_n;
//reg 		[ 10:0] 	max_cnt;
reg 		[ 10:0] 	max_cnt_n;
reg 		[ 10:0] 	max_cnt2;
reg 		[ 10:0] 	max_cnt2_n;
reg 		[15:0] 	fengzhi_max;
reg 		[15:0] 	fengzhi_max_n;
reg 		[15:0] 	fengzhi_min;
reg 		[15:0] 	fengzhi_min_n;
wire 		[31:0] 	vga_freq;
wire 		[31:0] 	vga_fengzhi;
wire 		[ 10:0] 	fft_out;

reg  [15:0] frames_in_index;
reg  [15:0] frames_out_index;

reg [9:0] position_r;


//reg fft_start;
reg fft_start_n;
//---------------------------------------------------------------------------
//--	逻辑功能实现	
//---------------------------------------------------------------------------
/* FFT控制信号,1:IFFT,0:FFT */
assign 	inverse = 1'b0;

/* FFT输入数据信号 */
assign	data_imag_in_int = 16'b0;

/* FFT数据的个数为1024 */
assign	fftpts_array = 10'd1023;

/* 输入错误信号 */
assign 	sink_error = 2'b0;

//start valid for first cycle to indicate that the file reading should start.
always@(posedge CLK_50M or negedge RST_N)
begin
	if(!RST_N)
		fft_start <= 1'b1;
	else 
		fft_start <= fft_start_n;
end

always@(*)
begin
	if(fft_ready_valid)//sink_valid为1时该式成立,则fft_start_n变为0,也就是两个信号同时会有脉冲信号产生
		fft_start_n = 1'b0;
	else
		fft_start_n = 1'b1;
end

//输入数据帧数
  // count the input frames and increment the index
  always @(posedge CLK_50M or negedge RST_N)
    begin
       if (RST_N == 1'b0)
         frames_in_index <= 'd0;
       else
         begin
            if (sink_eop == 1'b1 & sink_valid == 1'b1 & sink_ready == 1'b1 & frames_in_index < 3000)输入数据的次数,即输入进1024个点就算一帧数据
              frames_in_index <= frames_in_index + 1'b1;
         end
   end 
	
	   // count the output frames and increment the index 这个定义的是输入数据帧数,实际我没使用,而是一直输入进行FFT计算
   always @(posedge CLK_50M or negedge RST_N)
    begin
       if (RST_N == 1'b0)
        frames_out_index <= 'd0;
       else
         begin
            if (source_eop == 1'b1 &  source_valid == 1'b1 & source_ready == 1'b1 & frames_out_index < 3000)
              frames_out_index <= frames_out_index + 1'b1;
         end
    end 

/* 时序电路,用来给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)//end_input = (sink_eop && fft_ready_valid) ? 1'b1 : 1'b0;

		sink_valid_n = 1'b0;
	else if(fft_ready_valid || (fft_start == 1'b1 & !(sink_valid == 1'b1 & sink_ready == 1'b0)) )//(fft_start & !(sink_valid && sink_ready == 1'b0)))
		sink_valid_n <= 1'b1;
	else
		sink_valid_n = 1'b1;
end
//*********************************/

//always@(*)//组合逻辑一般用阻塞赋值
//begin	
//		if(end_test || end_input)
//			sink_valid_n = 1'b1;
//		else
//			sink_valid_n = 1'b1;
//end


/* 输入开始进行FFT转换运算 */
assign	fft_ready_valid = (sink_valid && sink_ready);//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)//这个判断,一开始这里因为sink_valid为0进不来
		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)//1024个数据已经输入完成了
		sink_real_n = 10'b0;
	else if (fft_ready_valid)//(sink_valid && sink_ready)
		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

/* 停止输入信号 */ //signal when all of the input data has been sent to the DUT
assign	end_input = (sink_eop && fft_ready_valid&& frames_in_index == 3000) ? 1'b0 : 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[15] ? (~source_real[15: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[15] ? (~source_imag[15: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 		)
);

/* 开始进行FFT转换运算 */
assign fft_read = (source_valid && source_ready);



/* 时序电路,用来给fft_x_cnt寄存器赋值 */
//***************找最大值可以用,但是最大位置值会复位累计 
always @ (posedge CLK_50M or negedge RST_N)
begin
	if(!RST_N) begin
		position <= 1'b0;
		fft_x_cnt <= 1'b0;
		data_max <= 1'b0;
		end
	else begin
		position <= position_r;
		data_max <= data_max_n;
		fft_x_cnt <= fft_x_cnt_n;
		end
end
//********/

/* 组合电路,用于生成FFT输出数据的计数器 */
//**********找最大值可以用,但是最大位置值会复位累计
always @ (*)
begin
	
	if(fft_ready_valid && (source_sop || (fft_x_cnt == fftpts_array)))begin//输出完成后
		fft_x_cnt_n = 1'b0;	
		position_r = 1'b0;
		data_max_n= 1'b0;
		end
	else if(fft_read) begin  //assign fft_read = (source_valid && source_ready);
	
		fft_x_cnt_n = fft_x_cnt + 1'b1;
		
		if(sqrt_data_out > data_max && fft_x_cnt > 2 && fft_x_cnt < 512) begin
		data_max_n= sqrt_data_out;
		position_r= fft_x_cnt -2'd2;
			end
			
		else
		data_max_n= data_max_n;
		position_r= position_r;
		
		end
		
	else
		
		fft_x_cnt_n = fft_x_cnt;
end
//****************/


*//
always @ (posedge CLK_50M or negedge RST_N)
begin
	if(!RST_N)
		fengzhi_max <= 16'b0;
	else
		fengzhi_max <= fengzhi_max_n;
end

always @ (*)
begin
	if(data_real_in_int > fengzhi_max)
		fengzhi_max_n = data_real_in_int;
	else
		fengzhi_max_n = fengzhi_max;
end

always @ (posedge CLK_50M or negedge RST_N)
begin
	if(!RST_N)
		fengzhi_min <= 16'd255;
	else
		fengzhi_min <= fengzhi_min_n;
end

always @ (*)
begin
	if(fengzhi_min > data_real_in_int)
		fengzhi_min_n = data_real_in_int;
	else
		fengzhi_min_n = fengzhi_min;
end

assign	vga_freq = (position) * 32'd147000 >> 8'd10;//ADC的采样频率147KHz F=Fs*n/N;Fs是采样频率,n是第几个点,N是采样点数,本例中是1024

assign	vga_fengzhi = ((fengzhi_max - fengzhi_min) * 10'd50) >> 8'd8; 

assign 	fft_out = sqrt_data_out;


fft3 fft_init
(
	.clk					(CLK_50M			),//
	.reset_n				(RST_N			),//
	.inverse				(inverse			),//
	.sink_valid			(sink_valid		),//input
	.sink_sop			(sink_sop		),//	
	.sink_eop			(sink_eop		),//
	.sink_real			(sink_real		),//
	.sink_imag			(sink_imag		),//
	.sink_error			(sink_error		),//
	.source_ready		(source_ready	),//
	.sink_ready			(sink_ready		),//output
	.source_error		(source_error	),//
	.source_sop			(source_sop		),//
	.source_eop			(source_eop		),//
	.source_valid		(source_valid	),//
	.source_exp			(source_exp		),//
	.source_real		(source_real	),//
	.source_imag		(source_imag	)//
);


endmodule

代码中有很多的测试代码也放在里面了,也是自己调试过程中的记录,当然,这个是FFT部分的代码,并不是整个工程的,也仅供参考。在计算最大值方面还存在一些问题,就是每次有新的数据帧输入的时候,上一步中计算出来的最大值会被复位,导致输出不会一直稳定,所以在输入一个稳定正弦波的时候,无法一直输出稳定的频率,而是一直在查找过程,不过基本是验证了该方法的可行性,高手可以帮修改一下。

下面是测试波形图:
在这里插入图片描述
我输入的是2000Hz的正弦波,计算出来的值是2009,允许误差。可以增加采样点数,提高精度。
以上就是FFT调试过程的记录,供以后查阅。

  • 17
    点赞
  • 134
    收藏
    觉得还不错? 一键收藏
  • 24
    评论
评论 24
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值