FFT(Fast Fourier Transform),快速傅立葉變換,是一種 DFT(離散傅里葉變換)的高效算法。 在以時頻變換分析為基礎的數字處理方法中,有著不可替代的作用。
FFT 原理
公式推導
DFT 的運算公式為:
其中,
將離散傅里葉變換公式拆分成奇偶項,則前 N/2 個點可以表示為:
同理,后 N/2 個點可以表示為:
由此可知,后 N/2 個點的值完全可以通過計算前 N/2 個點時的中間過程值確定。 對 A[k] 與 B[k] 繼續進行奇偶分解,直至變成 2 點的 DFT,這樣就可以避免很多的重復計算,實現了快速離散傅里葉變換(FFT)的過程。
算法結構
8 點 FFT 計算的結構示意圖如下。
由圖可知,只需要簡單的計算幾次乘法和加法,便可完成離散傅里葉變換過程,而不是對每個數據進行繁瑣的相乘和累加。
重要特性
(1) 級的概念
每分割一次,稱為一級運算。
設 FFT 運算點數為 N,共有 M 級運算,則它們滿足:
每一級運算的標識為 m = 0, 1, 2, ..., M-1。
為了便于分割計算,FFT 點數 N 的取值經常為 2 的整數次冪。
(2) 蝶形單元
FFT 計算結構由若干個蝶形運算單元組成,每個運算單元示意圖如下:
蝶形單元的輸入輸出滿足:
其中,。
每一個蝶形單元運算時,進行了一次乘法和兩次加法。
每一級中,均有 N/2 個蝶形單元。
故完成一次 FFT 所需要的乘法次數和加法次數分別為:
(3) 組的概念
每一級 N/2 個蝶形單元可分為若干組,每一組有著相同的結構與因子分布。
例如 m=0 時,可以分為 N/2=4 組。
m=1 時,可以分為 N/4=2 組。
m=M-1 時,此時只能分為 1 組。
(4) 因子分布
因子存在于 m 級,其中 。
在 8 點 FFT 第二級運算中,即 m=1 ,蝶形運算因子可以化簡為:
(5) 碼位倒置
對于 N=8 點的 FFT 計算,X(0) ~ X(7) 位置對應的 2 進制碼為:
X(000), X(001), X(010), X(011), X(100), X(101), X(110), X(111)
將其位置的 2 進制碼進行翻轉:
X(000), X(100), X(010), X(110), X(001), X(101), X(011), X(111)
此時位置對應的 10 進制為:
X(0), X(4), X(2), X(6), X(1), X(5), X(3), X(7)
恰好對應 FFT 第一級輸入數據的順序。
該特性有利于 FFT 的編程實現。
FFT 設計
設計說明
為了利用仿真簡單的說明 FFT 的變換過程,數據點數取較小的值 8。
如果數據是串行輸入,需要先進行緩存,所以設計時數據輸入方式為并行。
數據輸入分為實部和虛部共 2 部分,所以計算結果也分為實部和虛部。
設計采用流水結構,暫不考慮資源消耗的問題。
為了使設計結構更加簡單,這里做一步妥協,乘法計算直接使用乘號。 如果 FFT 設計應用于實際,一定要將乘法結構換成可以流水的乘法器,或使用官方提供的效率較高的乘法器 IP。
蝶形單元設計
蝶形單元為定點運算,需要對旋轉因子進行定點量化。
借助 matlab 將旋轉因子擴大 8192 倍(左移 13 位),可參考附錄。
為了防止蝶形運算中的乘法和加法導致位寬逐級增大,每一級運算完成后,要對輸出數據進行固定位寬的截位,也可去掉旋轉因子倍數增大而帶來的影響。
代碼如下:
`timescale 1ns/100ps
/**************** butter unit *************************
Xm(p) ------------------------> Xm+1(p)
- ->
- -
-
- -
- ->
Xm(q) ------------------------> Xm+1(q)
Wn -1
*//////////////////////////////////////////////////////
module butterfly
(
input clk,
input rstn,
input en,
input signed [23:0] xp_real, // Xm(p)
input signed [23:0] xp_imag,
input signed [23:0] xq_real, // Xm(q)
input signed [23:0] xq_imag,
input signed [15:0] factor_real, // Wnr
input signed [15:0] factor_imag,
output valid,
output signed [23:0] yp_real, //Xm+1(p)
output signed [23:0] yp_imag,
output signed [23:0] yq_real, //Xm+1(q)
output signed [23:0] yq_imag);
reg [4:0] en_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
en_r <= 'b0 ;
end
else begin
en_r <= {en_r[3:0], en} ;
end
end
//=====================================================//
//(1.0) Xm(q) mutiply and Xm(p) delay
reg signed [39:0] xq_wnr_real0;
reg signed [39:0] xq_wnr_real1;
reg signed [39:0] xq_wnr_imag0;
reg signed [39:0] xq_wnr_imag1;
reg signed [39:0] xp_real_d;
reg signed [39:0] xp_imag_d;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
xp_real_d <= 'b0;
xp_imag_d <= 'b0;
xq_wnr_real0 <= 'b0;
xq_wnr_real1 <= 'b0;
xq_wnr_imag0 <= 'b0;
xq_wnr_imag1 <= 'b0;
end
else if (en) begin
xq_wnr_real0 <= xq_real * factor_real;
xq_wnr_real1 <= xq_imag * factor_imag;
xq_wnr_imag0 <= xq_real * factor_imag;
xq_wnr_imag1 <= xq_imag * factor_real;
//expanding 8192 times as Wnr
xp_real_d <= {{4{xp_real[23]}}, xp_real[22:0], 13'b0};
xp_imag_d <= {{4{xp_imag[23]}}, xp_imag[22:0], 13'b0};
end
end
//(1.1) get Xm(q) mutiplied-results and Xm(p) delay again
reg signed [39:0] xp_real_d1;
reg signed [39:0] xp_imag_d1;
reg signed [39:0] xq_wnr_real;
reg signed [39:0] xq_wnr_imag;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
xp_real_d1 <= 'b0;
xp_imag_d1 <= 'b0;
xq_wnr_real <= 'b0 ;
xq_wnr_imag <= 'b0 ;
end
else if (en_r[0]) begin
xp_real_d1 <= xp_real_d;
xp_imag_d1 <= xp_imag_d;
//提前設置好位寬余量,防止數據溢出
xq_wnr_real <= xq_wnr_real0 - xq_wnr_real1 ;
xq_wnr_imag <= xq_wnr_imag0 + xq_wnr_imag1 ;
end
end
//======================================================//
//(2.0) butter results
reg signed [39:0] yp_real_r;
reg signed [39:0] yp_imag_r;
reg signed [39:0] yq_real_r;
reg signed [39:0] yq_imag_r;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
yp_real_r <= 'b0;
yp_imag_r <= 'b0;
yq_real_r <= 'b0;
yq_imag_r <= 'b0;
end
else if (en_r[1]) begin
yp_real_r <= xp_real_d1 + xq_wnr_real;
yp_imag_r <= xp_imag_d1 + xq_wnr_imag;
yq_real_r <= xp_real_d1 - xq_wnr_real;
yq_imag_r <= xp_imag_d1 - xq_wnr_imag;
end
end
//(3) discard the low 13bits because of Wnr
assign yp_real = {yp_real_r[39], yp_real_r[13+23:13]};
assign yp_imag = {yp_imag_r[39], yp_imag_r[13+23:13]};
assign yq_real = {yq_real_r[39], yq_real_r[13+23:13]};
assign yq_imag = {yq_imag_r[39], yq_imag_r[13+23:13]};
assign valid = en_r[2];
endmodule
頂層例化
根據 FFT 算法結構示意圖,將蝶形單元例化,完成最后的 FFT 功能。
可根據每一級蝶形單元的輸入輸出對應關系,依次手動例化 12 次,也可利用 generate 進行例化,此時就需要非常熟悉 FFT 中“組”和“級”的特點:
(1) 8 點 FFT 設計,需要 3 級運算,每一級有 4 個蝶形單元,每一級的組數目分別是 4、2、1。
(2) 每一級的組內一個蝶形單元中兩個輸入端口的距離恒為 (m 為級標號,對應左移運算 1<<< span="">m),組內兩個蝶形單元的第一個輸入端口間的距離為 1。
(3) 每一級相鄰組間的第一個蝶形單元的第一個輸入端口的距離為 (對應左移運算 2<<< span="">m)。
例化代碼如下。
其中,矩陣信號 xm_real(xm_imag)的一維、二維地址是代表級和組的標識。
在判斷信號端口之間的連接關系時,使用了看似復雜的判斷邏輯,而且還帶有乘號,其實最終生成的電路和手動編寫代碼例化 12 個蝶形單元的方式是完全相同的。 因為 generate 中的變量只是輔助生成實際的電路,相關值的計算判斷都已經在編譯時完成。 這些變量更不會生成實際的電路,只是為更快速的模塊例化提供了一種方法。
timescale 1ns/100ps
module fft8 (
input clk,
input rstn,
input en,
input signed [23:0] x0_real,
input signed [23:0] x0_imag,
input signed [23:0] x1_real,
input signed [23:0] x1_imag,
input signed [23:0] x2_real,
input signed [23:0] x2_imag,
input signed [23:0] x3_real,
input signed [23:0] x3_imag,
input signed [23:0] x4_real,
input signed [23:0] x4_imag,
input signed [23:0] x5_real,
input signed [23:0] x5_imag,
input signed [23:0] x6_real,
input signed [23:0] x6_imag,
input signed [23:0] x7_real,
input signed [23:0] x7_imag,
output valid,
output signed [23:0] y0_real,
output signed [23:0] y0_imag,
output signed [23:0] y1_real,
output signed [23:0] y1_imag,
output signed [23:0] y2_real,
output signed [23:0] y2_imag,
output signed [23:0] y3_real,
output signed [23:0] y3_imag,
output signed [23:0] y4_real,
output signed [23:0] y4_imag,
output signed [23:0] y5_real,
output signed [23:0] y5_imag,
output signed [23:0] y6_real,
output signed [23:0] y6_imag,
output signed [23:0] y7_real,
output signed [23:0] y7_imag
);
//operating data
wire signed [23:0] xm_real [3:0] [7:0];
wire signed [23:0] xm_imag [3:0] [7:0];
wire en_connect [15:0] ;
assign en_connect[0] = en;
assign en_connect[1] = en;
assign en_connect[2] = en;
assign en_connect[3] = en;
//factor, multiplied by 0x2000
wire signed [15:0] factor_real [3:0] ;
wire signed [15:0] factor_imag [3:0];
assign factor_real[0] = 16'h2000; //1
assign factor_imag[0] = 16'h0000; //0
assign factor_real[1] = 16'h16a0; //sqrt(2)/2
assign factor_imag[1] = 16'he95f; //-sqrt(2)/2
assign factor_real[2] = 16'h0000; //0
assign factor_imag[2] = 16'he000; //-1
assign factor_real[3] = 16'he95f; //-sqrt(2)/2
assign factor_imag[3] = 16'he95f; //-sqrt(2)/2
//輸入初始化,和碼位有關倒置
assign xm_real[0][0] = x0_real;
assign xm_real[0][1] = x4_real;
assign xm_real[0][2] = x2_real;
assign xm_real[0][3] = x6_real;
assign xm_real[0][4] = x1_real;
assign xm_real[0][5] = x5_real;
assign xm_real[0][6] = x3_real;
assign xm_real[0][7] = x7_real;
assign xm_imag[0][0] = x0_imag;
assign xm_imag[0][1] = x4_imag;
assign xm_imag[0][2] = x2_imag;
assign xm_imag[0][3] = x6_imag;
assign xm_imag[0][4] = x1_imag;
assign xm_imag[0][5] = x5_imag;
assign xm_imag[0][6] = x3_imag;
assign xm_imag[0][7] = x7_imag;
//butter instantiaiton
//integer index[11:0] ;
genvar m, k;
generate
//3 stage
for(m=0; m<=2; m=m+1) begin: stage
for (k=0; k<=3; k=k+1) begin: unit
butterfly u_butter(
.clk (clk ) ,
.rstn (rstn ) ,
.en (en_connect[m*4 + k] ) ,
//是否再組內?組編號+組內編號:下組編號+新組內編號
.xp_real (xm_real[ m ] [k[m:0] < (1<3 :m] << (m+1)) + k[m:0] :
(k[3:m] << (m+1)) + (k[m:0]-(1<0] < (1<3:m] << (m+1)) + k[m:0] :
(k[3:m] << (m+1)) + (k[m:0]-(1<0] < (1<3:m] << (m+1)) + k[m:0] :
(k[3:m] << (m+1)) + (k[m:0]-(1<1<
測試平臺
testbench 編寫如下,主要用于 16 路實、復數據的連續輸入。 因為每次 FFT 只有 8 點數據,所以送入的數據比較隨意,并不是正弦波等規則的數據。
`timescale 1ns/100ps
module test ;
reg clk;
reg rstn;
reg en ;
reg signed [23:0] x0_real;
reg signed [23:0] x0_imag;
reg signed [23:0] x1_real;
reg signed [23:0] x1_imag;
reg signed [23:0] x2_real;
reg signed [23:0] x2_imag;
reg signed [23:0] x3_real;
reg signed [23:0] x3_imag;
reg signed [23:0] x4_real;
reg signed [23:0] x4_imag;
reg signed [23:0] x5_real;
reg signed [23:0] x5_imag;
reg signed [23:0] x6_real;
reg signed [23:0] x6_imag;
reg signed [23:0] x7_real;
reg signed [23:0] x7_imag;
wire valid;
wire signed [23:0] y0_real;
wire signed [23:0] y0_imag;
wire signed [23:0] y1_real;
wire signed [23:0] y1_imag;
wire signed [23:0] y2_real;
wire signed [23:0] y2_imag;
wire signed [23:0] y3_real;
wire signed [23:0] y3_imag;
wire signed [23:0] y4_real;
wire signed [23:0] y4_imag;
wire signed [23:0] y5_real;
wire signed [23:0] y5_imag;
wire signed [23:0] y6_real;
wire signed [23:0] y6_imag;
wire signed [23:0] y7_real;
wire signed [23:0] y7_imag;
initial begin
clk = 0; //50MHz
rstn = 0 ;
#10 rstn = 1;
forever begin
#10 clk = ~clk; //50MHz
end
end
fft8 u_fft (
.clk (clk ),
.rstn (rstn ),
.en (en ),
.x0_real (x0_real),
.x0_imag (x0_imag),
.x1_real (x1_real),
.x1_imag (x1_imag),
.x2_real (x2_real),
.x2_imag (x2_imag),
.x3_real (x3_real),
.x3_imag (x3_imag),
.x4_real (x4_real),
.x4_imag (x4_imag),
.x5_real (x5_real),
.x5_imag (x5_imag),
.x6_real (x6_real),
.x6_imag (x6_imag),
.x7_real (x7_real),
.x7_imag (x7_imag),
.valid (valid),
.y0_real (y0_real),
.y0_imag (y0_imag),
.y1_real (y1_real),
.y1_imag (y1_imag),
.y2_real (y2_real),
.y2_imag (y2_imag),
.y3_real (y3_real),
.y3_imag (y3_imag),
.y4_real (y4_real),
.y4_imag (y4_imag),
.y5_real (y5_real),
.y5_imag (y5_imag),
.y6_real (y6_real),
.y6_imag (y6_imag),
.y7_real (y7_real),
.y7_imag (y7_imag));
//data input
initial begin
en = 0 ;
x0_real = 24'd10;
x1_real = 24'd20;
x2_real = 24'd30;
x3_real = 24'd40;
x4_real = 24'd10;
x5_real = 24'd20;
x6_real = 24'd30;
x7_real = 24'd40;
x0_imag = 24'd0;
x1_imag = 24'd0;
x2_imag = 24'd0;
x3_imag = 24'd0;
x4_imag = 24'd0;
x5_imag = 24'd0;
x6_imag = 24'd0;
x7_imag = 24'd0;
@(negedge clk) ;
en = 1 ;
forever begin
@(negedge clk) ;
x0_real = (x0_real > 22'h3F_ffff) ? 'b0 : x0_real + 1 ;
x1_real = (x1_real > 22'h3F_ffff) ? 'b0 : x1_real + 1 ;
x2_real = (x2_real > 22'h3F_ffff) ? 'b0 : x2_real + 31 ;
x3_real = (x3_real > 22'h3F_ffff) ? 'b0 : x3_real + 1 ;
x4_real = (x4_real > 22'h3F_ffff) ? 'b0 : x4_real + 23 ;
x5_real = (x5_real > 22'h3F_ffff) ? 'b0 : x5_real + 1 ;
x6_real = (x6_real > 22'h3F_ffff) ? 'b0 : x6_real + 6 ;
x7_real = (x7_real > 22'h3F_ffff) ? 'b0 : x7_real + 1 ;
x0_imag = (x0_imag > 22'h3F_ffff) ? 'b0 : x0_imag + 2 ;
x1_imag = (x1_imag > 22'h3F_ffff) ? 'b0 : x1_imag + 5 ;
x2_imag = (x2_imag > 22'h3F_ffff) ? 'b0 : x2_imag + 3 ;
x3_imag = (x3_imag > 22'h3F_ffff) ? 'b0 : x3_imag + 6 ;
x4_imag = (x4_imag > 22'h3F_ffff) ? 'b0 : x4_imag + 4 ;
x5_imag = (x5_imag > 22'h3F_ffff) ? 'b0 : x5_imag + 8 ;
x6_imag = (x6_imag > 22'h3F_ffff) ? 'b0 : x6_imag + 11 ;
x7_imag = (x7_imag > 22'h3F_ffff) ? 'b0 : x7_imag + 7 ;
end
end
//finish simulation
initial #1000 $finish ;
endmodule
仿真結果
大致可以看出,FFT 結果可以流水輸出。
用 matlab 自帶的 FFT 函數對相同數據進行運算,前 2 組數據 FFT 結果如下。
可以看出,第一次輸入的數據信號只有實部有效時,FFT 結果是完全一樣的。
但是第二次輸入的數據復部也有信號,此時兩者之間的結果開始有誤差,有時誤差還很大。
用 matlab 對 Verilog 實現的 FFT 過程進行模擬,發現此過程的 FFT 結果和 Verilog 實現的 FFT 結果基本一致。
將有誤差的兩種 FFT 結果取絕對值進行比較,圖示如下。
可以看出,FFT 結果的趨勢大致相同,但在個別點有肉眼可見的誤差。
設計總結:
就如設計蝶形單元時所說,旋轉因子量化時,位寬的選擇就會引入誤差。
而且每個蝶形單元的運算結果都會進行截取,也會引入誤差。
matlab 計算 FFT 時不用考慮精度問題,以其最高精度對數據進行 FFT 計算。
以上所述,都會導致最后兩種 FFT 計算方式結果的差異。
感興趣的學者,可以將旋轉因子和輸入數據位寬再進行一定的增加,FFT 點數也可以增加,然后再進行仿真對比,相對誤差應該會減小。
附錄:matlab 使用
生成旋轉因子
8 點 FFT 只需要用到 4 個旋轉因子。 旋轉因子擴大倍數為 8192。
clear all;close all;clc;
%=======================================================
% Wnr calcuting
%=======================================================
for r = 0:3
Wnr_factor = cos(pi/4*r) - j*sin(pi/4*r) ;
Wnr_integer = floor(Wnr_factor * 2^13) ;
if (real(Wnr_integer)<0)
Wnr_real = real(Wnr_integer) + 2^16 ; %負數的補碼
else
Wnr_real = real(Wnr_integer) ;
end
if (imag(Wnr_integer)<0)
Wnr_imag = imag(Wnr_integer) + 2^16 ;
else
Wnr_imag = imag(Wnr_integer);
end
Wnr(2*r+1,:) = dec2hex(Wnr_real) %實部
Wnr(2*r+2,:) = dec2hex(Wnr_imag) %虛部
end
FFT 結果對比
matlab 模擬 Verilog 實現 FFT 的過程如下,也包括 2 種 FFT 結果的對比。
clear all;close all;clc;
%=======================================================
% 8dots fft
%=======================================================
for r=0:3
Wnr(r+1) = cos(pi/4*r) - j*sin(pi/4*r) ;
end
x = [10, 20, 30, 40, 10, 20 ,30 ,40];
step = [1+2j, 1+5j, 31+3j, 1+6j, 23+4j, 1+8j, 6+11j, 1+7j];
x2 = x + step;
xm0 = [x2(0+1), x2(4+1), x2(2+1), x2(6+1), x2(1+1), x2(5+1), x2(3+1), x2(7+1)] ;
%% stage1
xm1(1) = xm0(1) + xm0(2)*Wnr(1) ;
xm1(2) = xm0(1) - xm0(2)*Wnr(1) ;
xm1(3) = xm0(3) + xm0(4)*Wnr(1) ;
xm1(4) = xm0(3) - xm0(4)*Wnr(1) ;
xm1(5) = xm0(5) + xm0(6)*Wnr(1) ;
xm1(6) = xm0(5) - xm0(6)*Wnr(1) ;
xm1(7) = xm0(7) + xm0(8)*Wnr(1) ;
xm1(8) = xm0(7) - xm0(8)*Wnr(1) ;
floor(xm1(:))
%% stage2
xm2(1) = xm1(1) + xm1(3)*Wnr(1) ;
xm2(3) = xm1(1) - xm1(3)*Wnr(1) ;
xm2(2) = xm1(2) + xm1(4)*Wnr(2) ;
xm2(4) = xm1(2) - xm1(4)*Wnr(2) ;
xm2(5) = xm1(5) + xm1(7)*Wnr(1) ;
xm2(7) = xm1(5) - xm1(7)*Wnr(1) ;
xm2(6) = xm1(6) + xm1(8)*Wnr(2) ;
xm2(8) = xm1(6) - xm1(8)*Wnr(2) ;
floor(xm2(:))
%% stage3
xm3(1) = xm2(1) + xm2(5)*Wnr(1) ;
xm3(5) = xm2(1) - xm2(5)*Wnr(1) ;
xm3(2) = xm2(2) + xm2(6)*Wnr(2) ;
xm3(6) = xm2(2) - xm2(6)*Wnr(2) ;
xm3(3) = xm2(3) + xm2(7)*Wnr(3) ;
xm3(7) = xm2(3) - xm2(7)*Wnr(3) ;
xm3(4) = xm2(4) + xm2(8)*Wnr(4) ;
xm3(8) = xm2(4) - xm2(8)*Wnr(4) ;
floor(xm3(:))
%% fft
fft1 = fft(x)
fft2 = fft(x2)
plot(1:8, abs(fft2))
hold on
plot(1:8, abs(xm3), 'r')
-
FFT
+關注
關注
15文章
434瀏覽量
59366 -
Verilog
+關注
關注
28文章
1351瀏覽量
110075 -
運算
+關注
關注
0文章
130瀏覽量
25785 -
傅立葉變換
+關注
關注
3文章
105瀏覽量
32381
發布評論請先 登錄
相關推薦
評論