1. 程式人生 > >FPGA數字訊號處理(三)序列FIR濾波器Verilog設計

FPGA數字訊號處理(三)序列FIR濾波器Verilog設計

該篇是FPGA數字訊號處理的第三篇,選題為DSP系統中極其常用的FIR濾波器。本文將在上一篇“FPGA數字訊號處理(二)並行FIR濾波器Verilog設計” https://blog.csdn.net/fpgadesigner/article/details/80594627的基礎上,繼續介紹序列結構FIR濾波器的Verilog HDL設計方法。

序列FIR

並行FIR使用n/2(藉助線性相位FIR濾波器h(n)的對稱性)個乘法器同時做乘法,將結果累加作為FIR濾波輸出。這樣的好處是每個時鐘都能完成一次運算,得到一個輸出結果。缺點是資源使用會隨著FIR階數的增加不斷增加。
這裡寫圖片描述
序列FIR是通過增加計算週期來減少資源使用的結構,如上圖所示。使用一個加法器將對稱係數對應的資料相加,一個乘法器進行資料和係數的乘法,即所有的係數相乘依次佔用一個乘法器,同時累加器不斷累加。因此需要n/2個時鐘週期才能將所有的資料計算完畢,得到輸出結果。

通常把只使用一個加法器依次進行“對稱係數的對應資料相加”的工作,這種結構稱為全序列FIR濾波器;使用n/2個加法器分別完成“對稱係數的對應資料相加”,這種結構稱為半序列FIR濾波器。

FPGA設計

全序列結構和半序列結構的區別僅在於使用加法器數量的不同,兩者計算速度相同,顯然選擇全序列結構FIR更優。設計參考自杜勇老師的《數字濾波器的MATLAB與FPGA實現》。本設計將在Vivado環境下進行模擬。

使用MATLAB設計一個2kHz取樣,500Hz截止的15階低通濾波器(h(n)長度為16),量化位數為12bit,輸入訊號位寬也為12bit。Verilog設計程式碼如下。

模組介面:

module Xilinx_SerialFIR_liuqi
(
    input rst,                    //復位訊號,高電平有效
    input clk,                    //FPGA系統時鐘,頻率為16kHz
    input signed [11:0] Xin,        //資料輸入頻率為2khZ
    output signed [28:0]Yout      //濾波後的輸出資料
);

介面與上一篇程式碼相同。不過有一個隱形的區別是系統時鐘,並行結構由於一個時鐘輸出一個數據,系統時鐘與輸入資料頻率(即取樣頻率)相同即可;但是序列結構由於需要n/2倍(此處為8倍)個週期完成一次運算,則FPGA系統時鐘需要是取樣頻率的n/2倍。
接下來先例項化全序列FIR濾波器結構所需的乘法器和加法器IP核。

reg signed [12:0] add_a,add_b;
wire signed [12:0] add_s;

c_addsub_0 add
(
    .A (add_a),
    .B (add_b),
    .S (add_s)
);

reg signed [11:0] coe;     //12bit量化濾波器係數
wire signed [24:0] Mout;  

mult_gen_0 mult
(
    .CLK (clk),
    .A   (coe),
    .B   (add_s),
    .P   (Mout)
);

加法器和乘法器的位寬問題在上一篇中已經介紹,這裡不再贅述。由於每8個時鐘才完成一次計算,因此需要一個系統時鐘的8分頻時鐘,來控制輸入資料的移位和濾波結果資料的輸出。這裡沒有采用分頻時鐘的控制方式,而是使用一個模8計數器來達到同樣的效果:

reg [2:0] cnt;
reg [11:0] Xin_Reg[15:0];
reg [3:0] i,j;

always @ (posedge clk or posedge rst)
    if (rst) cnt <= 'd0;
    else cnt <= cnt + 1'b1;

always @ (posedge clk or posedge rst)
    if (rst) begin   //清0
        Xin_Reg[15] <= 'd0;   
        for (i=0; i<15; i=i+1'b1)
            Xin_Reg[i] <= 'd0;    end
    else begin
        if (cnt == 'd7) begin
            for (j=0; j<15; j=j+1'b1)   //移位
                Xin_Reg[j+1] <= Xin_Reg[j];
            Xin_Reg[0] <= Xin;
        end
    end

每當cnt從0計數到7時,8個時鐘已經完成了一次運算,輸入資料移位。需要注意的是清零那一部分程式,杜老的程式中有考慮不周的地方:如果僅用for迴圈清零,那麼Xin_Reg[15]是無法被清零到的。因此需要單獨對這個暫存器清0。

可能有人會想到將for迴圈的控制語句修改為:for (i=0; i<=15; i=i+1’b1)或for (i=0; i<16; i=i+1’b1),這都是錯誤的。前者是因為Verilog中<=僅作為賦值語句,沒有“小於等於”的功能;後者是因為i是一個4位暫存器,迴圈加下去會無限滿足這個for條件,無法跳出。各位不妨在Vivado中綜合一下試試。

接下來就是在cnt的控制下,依次使用例項化好的乘法器和加法器完成運算。由於輸入資料為二進位制補碼帶符號數,因此在加法器相加前需要使用Verilog中的拼接運算子{}擴充套件符號位到最高位:

always @ (posedge clk or posedge rst)
    if (rst) begin
        add_a <= 'd0; add_b <= 'd0; coe <= 'd0;
    end
    else begin
        if (cnt == 'd0) begin //第一個週期
            add_a <= {Xin_Reg[0][11],Xin_Reg[0]};
            add_b <= {Xin_Reg[15][11],Xin_Reg[15]};
            coe <= 12'h000;
        end
        else if (cnt == 'd1) begin //第二個週期
            add_a <= {Xin_Reg[1][11],Xin_Reg[1]};
            add_b <= {Xin_Reg[14][11],Xin_Reg[14]};
            coe <= 12'hffd;
        end    
        else if (cnt == 'd2) begin //第三個週期
            add_a <= {Xin_Reg[2][11],Xin_Reg[2]};
            add_b <= {Xin_Reg[13][11],Xin_Reg[13]};
            coe <= 12'h00f;
        end
        else if (cnt == 'd3) begin //第四個週期
            add_a <= {Xin_Reg[3][11],Xin_Reg[3]};
            add_b <= {Xin_Reg[12][11],Xin_Reg[12]};
            coe <= 12'h02e;
        end
        else if (cnt == 'd4) begin //第五個週期
            add_a <= {Xin_Reg[4][11],Xin_Reg[4]};
            add_b <= {Xin_Reg[11][11],Xin_Reg[11]};
            coe <= 12'hf8b;
        end
        else if (cnt == 'd5) begin //第六個週期
            add_a <= {Xin_Reg[5][11],Xin_Reg[5]};
            add_b <= {Xin_Reg[10][11],Xin_Reg[10]};
            coe <= 12'hef9;
        end
        else if (cnt == 'd6) begin //第七個週期
            add_a <= {Xin_Reg[6][11],Xin_Reg[6]};
            add_b <= {Xin_Reg[9][11],Xin_Reg[9]};
            coe <= 12'h24e;
        end
        else begin                //第八個週期
            add_a <= {Xin_Reg[7][11],Xin_Reg[7]};
            add_b <= {Xin_Reg[8][11],Xin_Reg[8]};
            coe <= 12'h7ff;
        end                                               
    end

對乘法器的計算結果進行累加,得到濾波輸出結果:

reg signed [28:0] sum;
reg signed [28:0] yout;

always @ (posedge clk or posedge rst)
    if (rst) begin
        sum <= 'd0; yout <= 'd0;
    end
    else begin
        if (cnt == 'd2) begin
            yout <= sum; sum <= 'd0; sum <= sum+Mout;
        end
        else sum <= sum+Mout;
    end

assign Yout = yout;

可以看到序列結構的FIR乘法、加法運算是在8個時鐘內完成,因此每8個時鐘才能獲得一個輸出。這裡選擇在cnt為2時才輸出資料和清零,是考慮到乘法器和累加運算都需要佔用一個時鐘週期。

模擬與工程下載

使用MATLAB生成一個200khz+800kHz的混合頻率訊號,寫入txt檔案,。編寫Testbench讀取txt檔案對訊號濾波,檔案操作方法參考“Testbench編寫指南(一)檔案的讀寫操作”https://blog.csdn.net/fpgadesigner/article/details/80470972
對正弦訊號的濾波如下圖所示:
這裡寫圖片描述
發現Yout的幅度變化很低,檢測後發現24-29bit都是符號位,將0-24bit提取為一個新的虛擬匯流排Yout_bus。明顯看到經過500Hz低通濾波器濾波後,輸入的200+800Hz訊號只剩下200Hz的單頻訊號。

完整的Vivado工程(含testbench模擬)可以在這裡下載:https://download.csdn.net/download/fpgadesigner/10463087。Quartus環境下的設計與模擬工程可以參考杜勇老師《數字濾波器的MATLAB與FPGA實現》書中所帶光盤裡的內容。很推薦大家購買這本書學習。