FFT(快速傅里葉變換)作為數(shù)字信號處理的核心算法具有重要的研究價值,可應(yīng)用于傅里葉變換所能涉及的任何領(lǐng)域,如圖像處理、音頻編碼、頻譜分析、雷達(dá)信號脈沖壓縮等數(shù)字信號處理領(lǐng)域。FFT的鮮明特征之一是計算離散傅里葉變換(DFT)的高效算法,把計算N點DFT的乘法運算量從N2次降低到N/2*log2N次。而采用FPGA實現(xiàn)FFT的緣由在于:FPGA具有并行處理、流水線處理、易編程、片上資源豐富等方面特點,用于實現(xiàn)高速、大點數(shù)的FFT優(yōu)勢明顯。 本設(shè)計使用的軟件編程環(huán)境是Xilinx公司的Vivado 2018.3,筆者將從FFT IP核的創(chuàng)建,模塊文件的編寫,波形仿真等方面來具體講解FFT在Xilinx FPGA上的實現(xiàn)。
1.FFT IP核的創(chuàng)建
(1)在Vivado軟件主界面,打開IP Catalog,在搜索框內(nèi)輸入FFT,然后找到Digital Signal Processing->Transforms->FFTs目錄下的Fast Fourier Transform,雙擊進(jìn)入配置界面。
(2)進(jìn)入到配置界面,左邊是IP核的接口圖、實現(xiàn)的一些細(xì)節(jié)信息和FFT的延遲,右邊是Configuration、Implementation和Detailed Implementation三個標(biāo)簽卡。 Vivado的FFT IP核支持多通道輸入(Number of Channels)和實時更改FFT的點數(shù)(Run Time Configurable Transform Length)。Configuration標(biāo)簽下可設(shè)置FFT的點數(shù)(Transform Length)和工作時鐘(Target Clock Frequency),以及選擇一種FFT結(jié)構(gòu)。FFT的結(jié)構(gòu)包括流水線Streaming、基4 Burst、基2 Burst和輕量級基2 Burst,它們的計算速度和消耗的資源依次減少,可根據(jù)工程實際進(jìn)行選擇。
?Implementation標(biāo)簽卡下可設(shè)置FFT的數(shù)據(jù)格式為定點Fixed Point或浮點Float Point;輸出截位方式選擇:不截位(Unscaled),截位(Scaled),塊浮點(Block Floating Point);設(shè)置輸入數(shù)據(jù)的位寬和相位因子位寬。還有一些可選的附加信號,如時鐘使能(ACLKEN),復(fù)位信號(ARESETn,低有效)等。“Output Ordering”用以選擇FFT計算結(jié)果以自然順序(Nature Order)或位倒序(Bit/Digit Reversed Order)輸出。
?Detailed Implementation里可設(shè)置優(yōu)化方式、存儲的類型。存儲類型分為兩種:Block RAM(塊RAM)和Distributed RAM(分布式RAM);優(yōu)化方式可選擇資源最優(yōu)或者速度最優(yōu)。
(3)配置完成后,可在Latency下看到計算fft所需的時間,可以以此衡量設(shè)計是否滿足實時處理的要求。如不滿足,可選擇性能更好的FFT結(jié)構(gòu)或選擇可以提高運算速度的優(yōu)化選項
? 2.模塊文件的編寫 IP核工作必須要滿足一定的時序要求,所以需要將數(shù)據(jù)按照一定時序送入IP核。IP核交互是用AXI-Stream接口,關(guān)于AXI-Stream接口的時序可自行查一些相關(guān)資料,這里不做詳細(xì)介紹。簡言之,AXI-Stream接口分為主機(jī)(master)和從機(jī)(slave),主機(jī)為發(fā)起端,從機(jī)為響應(yīng)端,只有ready信號和valid信號同時為高時數(shù)據(jù)才能被有效寫入或讀出。舉個例子,主機(jī)檢測從機(jī)發(fā)出的ready信號,當(dāng)為高時將valid信號拉高即可從從機(jī)讀出或向從機(jī)寫入數(shù)據(jù)。 ? module fft_test( input clk, input rst_n, input? tvalid_i, input? [31:0] tdata_i, //input? fft_s_data_tlast, output fft_s_config_tready, ? output? ? ?
? ? fft_s_data_tready, output [47:0] fft_m_data_tdata, output? ?
? ? ? fft_m_data_tvalid, output? ? ? ?
? fft_m_data_tlast, output [7:0]? ?
fft_m_data_tuser, output? ? ? ?
? fft_event_frame_started, output? ? ?
? ? fft_event_tlast_unexpected, output? ? ? ?
? fft_event_tlast_missing, output? ? ? ?
? fft_event_status_channel_halt, output? ? ?
? ? fft_event_data_in_channel_halt, output? ? ?
? ? fft_event_data_out_channel_halt ); ? reg? fft_s_data_tvalid=1'b0; reg? [31:0] fft_s_data_tdata=32'd0; reg? fft_s_data_tlast=1'b0; reg[7:0]? count=8'd0; ?
always @(posedge? clk)? begin ? if(!rst_n) begin ?
? fft_s_data_tvalid<=1'b0; ? ?
fft_s_data_tdata<=32'd0; ? ? fft_s_data_tlast<=1'b0; ? ?
count<=8'd0; ? end ? else if (tvalid_i && fft_s_data_tready) begin ?
? if(count==127)begin
? ? ? ?fft_s_data_tvalid<=1'b1; ?
?fft_s_data_tlast<=1'b1; ? ?
fft_s_data_tdata<=tdata_i; ? ? ?
?count<=0;? end
? ? ?else begin ? ?
? ?fft_s_data_tvalid=1'b1; ? ?
? ?count<=count+1; ?
?fft_s_data_tlast<=1'b0; ?
?fft_s_data_tdata<=tdata_i; ?
? ?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 ?
? xfft_0 u_fft( ?
? .aclk(clk),? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? // input wire aclk ?
? .aresetn(rst_n),? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// input wire aresetn ?
? .s_axis_config_tdata(8'd1),? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? // input wire [7 : 0] s_axis_config_tdata ?
? .s_axis_config_tvalid(1'b1),?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// input wire s_axis_config_tvalid ?
? .s_axis_config_tready(fft_s_config_tready),? ?
? ? ? ? ? ? // output wire s_axis_config_tready ?
? .s_axis_data_tdata(fft_s_data_tdata),? ? ? ? ? ?
? ? ? ? ? // input wire [31 : 0] s_axis_data_tdata ?
? .s_axis_data_tvalid(fft_s_data_tvalid),? ? ? ? ? ? ?
? ? ? // input wire s_axis_data_tvalid ?
? .s_axis_data_tready(fft_s_data_tready),? ? ? ? ? ?
? ? ? ? // output wire s_axis_data_tready ? ?
.s_axis_data_tlast(fft_s_data_tlast),? ? ? ? ? ? ? ? ? ?
? // input wire s_axis_data_tlast ?
? .m_axis_data_tdata(fft_m_data_tdata),? ? ? ? ? ? ? ? ? ?
? // output wire [47 : 0] m_axis_data_tdata ? ?
.m_axis_data_tuser(fft_m_data_tuser),? ? ? ? ?
? ? ? ? ? ? // output wire [7 : 0] m_axis_data_tuser
? ? .m_axis_data_tvalid(fft_m_data_tvalid),? ? ? ? ? ?
? ? ? ? // output wire m_axis_data_tvalid ? ?
.m_axis_data_tready(1'b1),? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ?// input wire m_axis_data_tready
? ? .m_axis_data_tlast(fft_m_data_tlast),? ? ? ? ?
? ? ? ? ? ? // output wire m_axis_data_tlast ?
? .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
3.功能仿真 模塊編寫完成后,需要通過功能仿真來驗證我們設(shè)計邏輯的正確性。進(jìn)行仿真之前,我們需要編寫仿真測試文件(testbench)。
module testbench; reg? clk; reg? rst_n; reg [15:0] dati_in; reg [15:0] datq_in; reg [23:0]
? dati_out; reg [23:0]? datq_out; reg [15:0]?
?dataI [127:0]; reg [47:0]? fft_abs; ?
reg? fft_s_data_tvalid; wire? [31:0] fft_s_data_tdata; //reg? fft_s_data_tlast; wire fft_s_config_tready; wire? ?
? ? ? fft_s_data_tready; wire [47:0] fft_m_data_tdata; wire? ? ? ?
? fft_m_data_tvalid; wire? ? ?
? ? fft_m_data_tlast; wire [7:0]? ?
fft_m_data_tuser; 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; ?
initial? begin clk=1; rst_n=0; //fft_s_data_tlast=1'b0; fft_s_data_tvalid=1'b0;
dati_in=0; datq_in=0; dati_out=0; datq_out=0; fft_abs=0; ?
$readmemb("C:/Users/radar/Desktop/Science/FPGA/FFT/y1.txt",dataI); #100 rst_n=1; end
? always #5? clk=~clk; reg[7:0]? count=0; ?
always @(posedge? clk)? begin
? ?if (fft_s_data_tready) begin ? ?
?if(count==128) begin ?
? ? ?fft_s_data_tvalid=1'b0; ?
?? ?//fft_s_data_tlast=1'b0; ?
?#10000 ?
? ? ?count=0; ?
? ?end else if(count==127)begin ? ?
? ?dati_in<= dataI[count];
? ? ? ?datq_in<=16'd0; ? ?
? ?fft_s_data_tvalid<=1'b1; ?
?//fft_s_data_tlast<=1'b1; ? ? ?
?count<=count+1;?
end ? ?
?else begin ? ? ?
?dati_in<= dataI[count]; ?
? ? ?datq_in<=16'd0; ?
? ? ?fft_s_data_tvalid=1'b1; ?
? ? ?count<=count+1; ?
?//fft_s_data_tlast<=1'b0; ? ? ?
end ? ?
end ?
end
? assign fft_s_data_tdata = {datq_in,dati_in}; ?
fft_test u_fft_test( .clk(clk), .rst_n(rst_n), .tvalid_i(fft_s_data_tvalid), .tdata_i(fft_s_data_tdata), //.fft_s_data_tlast(fft_s_data_tlast),
.fft_s_config_tready(fft_s_config_tready), .fft_s_data_tready(fft_s_data_tready), .fft_m_data_tdata(fft_m_data_tdata),
.fft_m_data_tvalid(fft_m_data_tvalid), .fft_m_data_tlast(fft_m_data_tlast), .fft_m_data_tuser(fft_m_data_tuser),
.fft_event_frame_started(fft_event_frame_started),
.fft_event_tlast_unexpected(fft_event_tlast_unexpected), .fft_event_tlast_missing(fft_event_tlast_missing),
.fft_event_status_channel_halt(fft_event_status_channel_halt), .fft_event_data_in_channel_halt(fft_event_data_in_channel_halt),
.fft_event_data_out_channel_halt(fft_event_data_out_channel_halt) ); ?
always @(posedge clk) begin ?
if(fft_m_data_tvalid) begin ?
? dati_out<=fft_m_data_tdata[23:0]; ? ?
datq_out<=fft_m_data_tdata[47:24];? ??
? end end ? always @(posedge clk) begin ?
fft_abs<=$signed(dati_out)* $signed(dati_out)+ $signed(datq_out)* $signed(datq_out);
end endmodule testbench中輸入的時域波形數(shù)據(jù)是我們通過matlab生成的,在matlab中我們仿真的是采樣率為2kHz情況下,頻率分別為50Hz和200Hz的兩正弦波疊加后的信號。
N=128; n=1:N; f0=50; f1=200; fs=2e3;
y=sin(2*pi*f0.*n/fs)+2*sin(2*pi*f1.*n/fs);
figure; plot(y); Y=fft(y);
figure; plot(abs(Y)); y1=y';
q=quantizer([16 12]);
y2=num2bin(q,y1);
fid1=fopen('C:/Users/radar/Desktop/y1.txt','wt'); for i=1:N
? ? fwrite(fid1,y2(i,:)); ?
? fprintf(fid1,' ');
end fclose(fid1); 利用modelsim進(jìn)行功能仿真時我們將仿真時長設(shè)置為20us。為了直觀驗證fft是否正確,可將輸入的時域數(shù)據(jù)的實部和做完fft后信號功率值的數(shù)據(jù)格式均設(shè)置為anolog(模擬),如下圖,可以看到fft后的功率譜為兩根獨立的譜線,分別代表50Hz和200Hz兩個頻率點,和matlab仿真結(jié)果一致。
?
對于該IP核更復(fù)雜的應(yīng)用,大家可以閱讀Xilinx官方提供的文檔,根據(jù)自己的實際需要進(jìn)行設(shè)計。
編輯:黃飛
評論