目录
一.FFT核简介... 2
二.FFT核的使用... 2
三.例子... 7
1.设计思路... 7
2.产生输入数据... 7
3.FPGA设计FFT运算... 10
4.Modelsim仿真... 19
5.Matlab分析... 21
四.总结... 23
文章格式有问题,图片传不上来,可以看附件中的文件
FFT核的使用
一.FFT核简介
FFT-V2.0.0是Altera公司2004年2月新发布的FFT知识产权核,它是一个高性能、高度参数化的快速傅里叶变换(FFT)处理器,支持Cyclone、Stratix II、Stratix GX、Stratix系列FPGA器件。该FFT Core功能是执行高性能的正向复数FFT或反向的FFT(IFFT),采用基2/4频域抽取(DIF)的FFT算法,其转换长度为2m,这里 6≤m≤14。在其内部,FFT采用块浮点结构,以在最大信噪比(SNR)和最小资源需求之间获得最大的收益。FFT Core接收一个长度为N的、二进制补码格式、顺序输入的复数序列作为输入,输出转换域的、顺序的复数数据序列。同时,一个累加块指数被输出,表示块浮点的量化因子。FFT Core的转换方向事先由一个输入端口为每个数据转换块指定。
FFT Core可以设置两种不同的引擎结构:四输出(Quad-output FFT engine)和单输出(Single-outputFFT engine)。对于要求转换时间尽量小的应用,四输出引擎结构是最佳的选择;对于要求资源尽量少的应用,单输出引擎结构比较合适。为了增加整个FFT Core的吞吐量,可以采用多并行引擎结构。
FFT Core支持3种I/O数据流结构:连续(streaming)、缓冲突发(Buffered Burst)、突发(Burst)。连续I/O数据流结构允许处理连续输入数据,输出连续复数数据流,而不中断输入和输出数据;缓冲突发I/O数据流结构与连续结构相比,需要更少的存储资源,但是,这是以减少平均吞吐量为代价的;突发数据流结构的操作与缓冲突发方式基本上一致,但突发方式则需要更少的存储资源,这也是以降低吞吐量为代价的。
Step 1.设置参数
图 1 图 2
转换长度(N=,m=6~16)、数据精度(8~24bit)、旋转因子精度就不啰嗦了,下一页,如图3:
图 3 图 4
1.引擎
FFT_IP核函数可以通过定制参数来使用两种不同的引擎结构:四输出(Quad-output)或单输出(Single-output)引擎结构。为了增加FFT_IP核函数的总吞吐量,可以在一个FFT_IP核函数中使用多个并行的引擎。
◆ 四输出FFT引擎结构
对于需要最少转换时间的应用,四输出FFT引擎结构是最佳选择。四输出指的是内部FFT碟形处理器的吞吐量,这种引擎实现结构在一个单时钟周期内计算所有四个基-4碟形复数输出。如图5,复数据x[k,m]从内部存储器并行读出,并由变换开关(SW)重新排序。紧接着,排序后的数据由基-4处理器处理并得到复数输出G[k,m]。由于基-4按频率抽选(DIF)分解方法固有的数学特点,在蝶形处理器输出上仅需要3个复数乘法器完成3次乘旋转因子(有一个旋转因子为1,不需要乘)计算。为了辨别数据的最大动态范围,四个输出由块浮点单元(BFPU)并行估计,丢弃适当的最低位(LSB),在写入内部存储器之前对复数数值进行四舍五入并由SW重新排序然后输出。
图 5
◆ 单输出FFT引擎结构
在需要最小尺寸FFT函数的应用中,单输出引擎最适合。单输出也指的是内部FFT碟形处理器的吞吐量。如图6,在这种结构中,每个时钟周期计算一个基-4碟形单元的其中一个单碟形输出。这种结构只需要一个单独的复数乘法器。
图 6
2.数据流
FFT_IP核函数支持的I/O数据流结构选项包括:流(Streaming)、缓冲突发(Buffered Brust)和突发(Brust)。
◆ 流(Streaming)I/O数据流结构
流I/O数据流结构允许输入数据连续处理,并输出连续的复数数据流。如图7,图8,这个过程不需要停止FFT函数数据流的进出。
图 7
图 8
◆ 变量流数据结构(VariableStreaming)
这个其实和Streaming差不多,只是VS中你可以通过改变fftpts的值来改变FFT的点数。然而其他的时序都是相同的。输入数据的顺序是顺序输入,输出数据的顺序是位倒序输出,假如你选择的是顺序输出,那将加大你系统的资源消耗,因为系统还得增加个模块将输出转换为顺序输出。
数据表示用的是定点数,而非浮点数。
◆ 缓冲突发(BufferedBrust)I/O数据流方式
如图9,虽然输入输出流出现了中断,但这种结构对输入数据进行了缓存,因而不需要等到第一个数据块的运算结果输出完毕才开始进行第二个数据块的数据输入。
图 9
◆ 突发(Brust)I/O数据流方式
这是三种数据流结构中消耗资源最少的,单数其数据吞吐量也是最小的。这种结构没有进行输入数据的缓存,因此必须等到第一个数据块的运算结果输出完毕以后才可以进行第二个数据块的数据输入。可以清楚地看到图9和图10的不同之处,图9在第一帧数据没有输出时,可以输入数据,但图10必须在第一帧数据输出完成时,才能继续输入数据。
图 10
3.结构
如图4所示,可以设置FFT核所采用的硬件资源种类,如采用“4 Mults/2 Adders”还是“3Mults/5 Adders”的结构来实现复数乘法运算;采用“DSP Blocks”还是“Logic Cells”来实现乘法运算。用户可以根据芯片资源及设计要求合理选择这些参数,实现速度与逻辑资源面积的最优设计。
Step2:建立仿真
图 11 图 12
Step3:生成IP核
Step1: 用Matlab产生输入数据,并将输入数据写入到.mif文件中,用做测试数据;
Step2: 用FPGA对输入数据做FFT运算;
Step3: Modelsim仿真,并将运算后的数据输出到.txt文件,用于Matlab分析;
Step4: 用Matlab对运算后的数据分析
fc = 23e6;
fs = 100e6;%采样频率
depth =1024; %存储器的单元数
widths = 16;%数据宽度
n = 0:depth-1;
x = 480*cos(2*pi*fc*n/fs+pi/6) +20*randn(1,depth);
y = 480*sin(2*pi*fc*n/fs+pi/6) +20*randn(1,depth);
z = x + i*y;%FPGA的输入信号
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%观察FPGA输入信号的FFT
xk =fft(z,512);
t = (0:511)*fs/512;
subplot(311);
plot(t,20*log10(abs(xk)));title('幅频特性');
subplot(312);
plot(t,real(xk));title('实部');
subplot(313);
plot(t,imag(xk));title('虚部');
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%对FPGA输入信号进行量化,便于存入ROM中
x = x/max(abs(x));%归一化
y = y/max(abs(y));
Qx = round(x*(2^(widths-1)-1));
Qy = round(y*(2^(widths-1)-1));
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%产生.mif文件
fidr =fopen('F:\Matlab\FPGA\mif_generate\re.mif','wt')
fprintf(fidr , 'depth = %d;\n',depth);
fprintf(fidr, 'width = %d;\n',widths);
fprintf(fidr, 'address_radix = uns;\n');
fprintf(fidr,'data_radix = bin;\n');
fprintf(fidr,'content begin\n');
for(i =1:depth)
Bx = dec2bin(Qx(i)+(Qx(i)<0)*2^widths,widths);
fprintf(fidr,'%d:',i-1);
for j=1:widths
fprintf(fidr,'%d',(Bx(j)=='1'));
end
fprintf(fidr,';\r\n');
end
fprintf(fidr, 'end;');
fclose(fidr);
fidi =fopen('F:\Matlab\FPGA\mif_generate\im.mif','wt')
fprintf(fidi , 'depth = %d;\n',depth);
fprintf(fidi, 'width = %d;\n',widths);
fprintf(fidi, 'address_radix = uns;\n');
fprintf(fidi,'data_radix = bin;\n');
fprintf(fidi,'content begin\n');
for(m =1:depth)
Bx = dec2bin(Qx(m)+(Qx(m)<0)*2^widths,widths);
fprintf(fidi,'%d:',m-1);
for q=1:widths
fprintf(fidi,'%d',(Bx(q)=='1'));
end
fprintf(fidi,';\r\n');
end
fprintf(fidi, 'end;');
fclose(fidi);
图13 FPGA输入信号的FFT变换
图14 输入信号的实部 图15 输入信号的虚部
图 16
怎么建工程,添加IP核之类的就不细说了,FFT设置如图2,3,4,5,即512点FFT,输入输出数据位16位二进制补码。再添加两个ROM分别储存输入数据的实部和虚部,初始化文件就为图14,图15.
文件:FFTtest.v
/*****************************************************************************
模块名:FFTtest
功能:测试FFT核
作者:冬瓜
时间:2015.10.29
Email:[email protected]
******************************************************************************/
module FFTtest
(
clk,rst_n,
start,
//datain_re,
//datain_im,
dataout_re,
dataout_im,
exp,
index,
rdy
);
input clk,rst_n;//时钟,复位信号
input start;//输入使能信号,高电平有效,与输入数据同步拉高
//input signed[ 15:0]datain_re,datain_im;//输入数据实部和虚部
output rdy;//输出有效,与输出数据同步拉高
output [ 5:0 ]index;//输出数据标号
output signed[ 5:0 ]exp;//旋转因子
output signed[ 15:0]dataout_re,dataout_im;//输出数据,8位二进制补码
wire signed[ 15:0 ]datain_re,datain_im;//输入数据实部和虚部
wire sink_ready,source_ready;
wire inverse;
wire source_sop,source_eop,source_valid;
//wire [ 5:0 ]source_exp;
wire [ 1:0 ]sink_error,source_error;
assign sink_error = 'd0;
assign source_ready = 'd1;
assign inverse = 'd0;//FFT变换
reg sink_valid,sink_sop,sink_eop;
reg [ 10:0 ]cnt;
always @( posedge clk or negedge rst_n )
if(!rst_n ) begin
sink_sop <= 'd0;
sink_eop <= 'd0;
sink_valid <= 'd0;
cnt <= 'd0;
end
else if( start )
begin
cnt <= cnt + 1'b1;
if(cnt == 1 )
sink_sop <= 'd1;
else
sink_sop <= 'd0;
if(cnt == 512 )
sink_eop <= 'd1;
else
sink_eop <= 'd0;
if((cnt>=1)&&(cnt<=512))
sink_valid <= 'd1;
else
sink_valid <= 'd0;
end
reg [ 9:0 ]addr;
always @( posedge clk or negedge rst_n )
if(!rst_n )
addr <= 'd0;
else if( start )
addr <= addr + 1'b1;
else
addr <= 'd0;
//FFT核,实现512点FFT正变换,在sink_valid/sink_sop/sink_eop
//控制下,每2048个数据进行一次正变换
fft u1(
.clk(clk),
.reset_n(rst_n),
.inverse(inverse),
.sink_real(datain_re),
.sink_imag(datain_im),
.sink_sop(sink_sop),
.sink_eop(sink_eop),
.sink_valid(sink_valid),
.sink_error(sink_error),
.source_error(source_error),
.source_ready(source_ready),
.sink_ready(sink_ready),
.source_real(dataout_re),
.source_imag(dataout_im),
.source_exp(exp),
.source_valid(rdy),
.source_sop(source_sop),
.source_eop(source_eop)
);
re u2
(
.address( addr ),
.clock(clk ),
.rden(start ),
.q(datain_re )
);
im u3
(
.address( addr ),
.clock(clk ),
.rden(start ),
.q(datain_im )
);
Endmodule
文件:FFTtest.vt
`timescale 1 ps/ 1 ps
module FFTtest_vlg_tst();
// constants
// general purpose registers
// test vector input registers
reg clk;
reg [15:0] datain_im;
reg [15:0] datain_re;
reg rst_n;
reg start;
// wires
wire signed[15:0] dataout_im;
wire signed[15:0] dataout_re;
wire signed[5:0] exp;
wire [5:0] index;
wire rdy;
// assign statements (if any)
FFTtest i1 (
// port map - connection between masterports and signals/registers
.clk(clk),
// .datain_im(datain_im),
// .datain_re(datain_re),
.dataout_im(dataout_im),
.dataout_re(dataout_re),
.exp(exp),
.index(index),
.rdy(rdy),
.rst_n(rst_n),
.start(start)
);
parameter clk_period = 20;
parameter data_period = clk_period;
parameter half_clk_period = clk_period/2;
parameter half_data_period = data_period/2;
parameter data_num = 4096;
parameter time_sim = data_num*data_period;
initial
begin
clk= 0;
rst_n = 0;
#200;
rst_n = 1;
#10;
start = 1;
#time_sim $finish;
end
always #half_clk_period clk = ~clk;
wire clk_write;
assign clk_write = rst_n & clk &rdy;
//从txt文件中读取输入数据实部
integer addr1;
reg [ 15:0 ]stimuls_re[ 1:data_num ];
initial
begin
#300;
$readmemb("fft_core_real_input.txt",stimuls_re);
addr1 = 0;
repeat(data_num)
begin
addr1 = addr1 + 1'b1;
datain_re = stimuls_re[ addr1 ];
#data_period;
end
end
//从txt文件中读取输入数据虚部
integer addr2;
reg [ 15:0 ]stimuls_im[ 1:data_num ];
initial
begin
#300;
$readmemb("fft_core_imag_input.txt",stimuls_im);
addr2 = 0;
repeat(data_num)
begin
addr2 = addr2 + 1'b1;
datain_im = stimuls_im[ addr2 ];
#data_period;
end
end
//将输出数据实部写入txt文件
integer file_output_re;
initial
begin
file_output_re = $fopen("re_output.txt");
if(!file_output_re )
begin
$display("cannot open file!");
$finish;
end
end
always @( posedge clk_write )
$fdisplay(file_output_re,"%d",dataout_re);
//将输出数据虚部写入txt文件
integer file_output_im;
initial
begin
file_output_im= $fopen("im_output.txt");
if(!file_output_im )
begin
$display("cannot open file!");
$finish;
end
end
always @( posedge clk_write )
$fdisplay(file_output_im,"%d",dataout_im);
//将输出的指数写入txt文件
integer file_output_exp;
initial
begin
file_output_exp = $fopen("exp_output.txt");
if(!file_output_exp )
begin
$display("cannot open file!");
$finish;
end
end
always @( posedge clk_write )
$fdisplay(file_output_exp,"%d",exp);
endmodule
编译之前:ToolàOptionsàEDA Tool Options右侧写入Modelsim的路径,如图17,再AssignmentàSettingàEDA Tool OptionsàSimulation,选择如图18,(当然这步也可以不用,因为在Quartus调用Modelsim仿真FFT核没问题,但设计中有ROM,需要别的仿真库),再编译
图 17
图 18
图 19
打开TimeQuest,如图20,21,设置完成后,Read SDC File,Update Timing Netlist,如图22。
图 20
图 21
图 22
(1)新建Modelsim工程,将FFTtest.vo和FFTtest.vt文件添加到工程中,将FFTtest.vo文件(网表文件)中的initial$sdf_annotate("FFTtest_v.sdo");注释掉(这句是用来做后仿真的,这里只做功能仿真,FFTtest_v.sdo是延时信息文件),再编译。
图 23
(2)建立仿真库
①FileàNewàLibrary,命名为sim;
图24 图25
②在Modelsim安装路径下新建文件夹sim,将QuartusII安装目录D:\altera\11.0\quartus\eda\sim_lib下的220model.v、220model.vhd、220pack.vhd、altera_mf.v、altera_primitives.v、cycloneiii_atoms.v、sgate.v文件复制到sim文件夹中(不仅仅为了此次设计,以后也会用到);
③更改WorkSpace.File->Change Direction->sim;
④编译sim库,如图26;
图26
⑤更改Modelsim配置文件,先将Modelsim安装路径下的modelsim.ini文件只读属性去掉,如图27,再在;mvc_lib =$MODEL_TECH/../mvc_lib后面加上sim = $MODEL_TECH/../sim,最后改回只读属性,关掉Modelsim软件,最后重新打开
图27
(3)仿真,Simulate->StartSimulation,再按图28,29配置,最后点击Ok
图28 图29
仿真结果如图30,实部和虚部在2.3Mhz处出现尖峰。
图30
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %{tool}
% file : fft_tb.m
%
% Description : Thefollowing Matlab testbench excercises the Altera FFT Model fft_model.m
% generated by Altera'sFFT Megacore and outputs results to text files.
%
% 2001-2009 AlteraCorporation, All Rights Reserved
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% ParameterizationSpace
close all;
clear all;
clc;
fs = 100e6;
% Read input complexvector from source text files
fidr = fopen('F:\Matlab\FPGA\fft\re_output.txt','r');
%Modelsim仿真产生输出txt文件,注意放到自己相应的路径
[xreali,N]=fscanf(fidr,'%lg',inf);
fclose(fidr);
fidi = fopen('F:\Matlab\FPGA\fft\im_output.txt','r');
ximagi=fscanf(fidi,'%lg',inf);
fclose(fidi);
fide = fopen('F:\Matlab\FPGA\fft\exp_output.txt','r');
exp=fscanf(fide,'%lg',inf);
fclose(fide);
% Create input complexrow vector from source text files
x = (xreali.*power(2,exp)) +j*(ximagi.*power(2,exp));
t = (0:N-1)*fs/512;
plot(t,20*log10(abs(x)));
xlabel('ƵÂÊ£¨Hz£©');
ylabel('·ù¶È£¨dB£©');
title('N=512,fs=100MHz');
grid on;
图31
对比图31和图13,可以发现FPGA实现了FFT运算。
1.从仿真图中可以看出,实现了512点的FFT运算
2.虽然输入数据是从ROM里读取的,但可以去掉ROM,将数据从外部输入,将输入有效信号与输入数据同步拉高
3.由于采用的Burst模式,因此需要等待第一帧转换完成才能输出,也就是说至少要仿真512*2=1024个时钟周期