天天看點

FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

FPGA/Verilog 設計FIR濾波器

文章目錄

  • FPGA/Verilog 設計FIR濾波器
    • 前言
    • 設計目标
    • 基礎知識
      • 數字濾波器
      • FIR濾波器
    • 具體實作
      • matlab-擷取FIR抽頭系數
      • matlab-産生波形資料
      • FPGA-導入波形資料
      • FPGA - FIR結構設計
        • part1-移位寄存器
        • part2-乘法器
        • part3-加法器
    • 仿真結果
      • 仿真波形
      • 方案對比
        • 方案一:類c寫法
        • 方案二:例化ip寫法
    • 結語

前言

這應該是第一次的FPGA(DSP方向)的實戰(也算不上)分享.也算是小班教學的其中一節課吧.

話不多說,先給大家介紹一下這次要幹啥先:學過信号與系統的可以直接跳過基礎知識…

系統環境:

matlab2018a

quartus16.0

Multisim

VScode

ubuntu18.04

INTEL-EP4CE15F23C8

設計目标

設計一個10階的FIR低通濾波器,濾波器的通帶截止頻率是2MHz,阻帶截止頻率是4MHz.

基礎知識

數字濾波器

濾波器是一種對信号有處理作用的器件或電路,其主要作用是讓有用信号盡可能無衰減地通過,對無用信号盡可能大地衰減.而數字濾波器就是一個按預定的有限精度算法實作的,将輸入的數字信号轉換為所要求的輸出數字信号的線性時不變系統.

如果對上面加粗的莫得認識的話,那你就直接想成是用數字電路實作的濾波器得了.

FIR濾波器

FIR(Finite Impulse Response,FIR)濾波器,中文名叫有限沖激響應濾波器,在講沖激響應之前,我們不妨預習/回顧一下信号與系統:

FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

就是一個輸入信号,經過一個系統,每個系統會有他對應的系統函數來處理輸入信号,最後得到輸出信号.對常見的濾波器來說,他們的理想系統函數往往是這些:

  1. 低通濾波器(紅線部分),也是這篇blog要實作的東西
    FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器
  2. others
    FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

注意看橫坐标,我們可以看到,輸入信号是時域進來的,但是系統函數是頻域來的.而他們的命名也是跟頻率有關系的.

這個時候我們就需要用到傅裡葉變換了,至于詳情,請看:

這是一個通俗易懂的傅裡葉變換簡介/教程

我們可以看到頻域上的乘積就是時域上的卷積:

y ( n ) = x ( n ) h ( n ) = ∑ k = 0 N h ( k ) x ( n − k ) y(n) = x(n)h(n) = \sum^N_{k=0}h(k)x(n-k) y(n)=x(n)h(n)=k=0∑N​h(k)x(n−k)

用Z變換來就是:

H ( z ) = ∑ n = 0 N − 1 h ( n ) z − n H(z) = \sum^{N-1}_{n=0}h(n)z^{-n} H(z)=n=0∑N−1​h(n)z−n

突然想起并不是在教DSP,是以我們了解到這裡就夠了,也就是說,我們隻要把h(k)求出來,然後知道他可以有濾波的效果就完事了.

這裡用的是FIR的橫向結構:

FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

我們可以看到主要有三個部分,第一部分是對輸入信号的延時,第二部分是輸入信号和抽頭系數的相乘,第三部分是相乘結果的累加.這也是fpga的基本結構假設

具體實作

matlab-擷取FIR抽頭系數

在matlab指令行視窗打入filterDesigner(善用tab鍵):

FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

大家大概可以看見面闆下面有很多東西,分别是:

  1. 指定濾波器類型,是IIR還是FIR,用什麼算法生成
  2. 濾波器階數,是特定階數還是自動生成最小階數的
  3. 采樣頻率,信号頻率,截止頻率
  4. 衰減倍數

    當中的折衷關系不是這裡的重點,根據設計要求,最後要改成這樣:

    FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器
    記得按下方的design filter.

右上的圖就是系統函數的形狀了.

接下來點[file]->[Export]導出,快捷鍵ctrl+E,直接導出到workspace就可以了

然後在指令行視窗打:round(Num*512),意思是放大512倍并取整,友善我們後面在fpga中做定點數乘法

FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

儲存下來即可

matlab-産生波形資料

因為懶得接信号源和寫ad子產品驅動,這次實驗直接在fpga中用一個rom的ip核儲存波形資料

matlab代碼如下:

  1. 生成波形
%産生兩個正弦信号sin(x)和sin(8x)疊加後的信号,取128個點,将信号放大,
%轉換成無符号資料,存入ROM中作為信号源
clear all
clc
depth = 128;
width = 16;
x = 0 : 2*pi/(depth-1) :2*pi;
y = sin(x)+sin(8*x);
plot(x,sin(x),'r')	%紅色為sin(x)函數
hold on
plot(x,sin(8*x),'g')	%綠色為sin(8x)函數
hold on
plot(x,y,'b')	%藍色為生成的混合信号
grid
y = (y/2) * 32768;%将信号放大32768倍
b = signed2unsigned(y,width);  %轉換為無符号數輸入

%下面函數重新建立一個腳本檔案
%需要調用了如下函數,将有符号數轉換成無符号數
function b = signed2unsigned(a,wl)
%This function covert an signed integer number into an unsinged integer
%number. a is the input vector while wl means the width of input number;
%Example: a = [-2,-1,0,1];
%signed2unsigned(a,3); THEN return [2,3,0,1]
k = 2^(wl)*(a<0);
b = k + a;;
b = fix(b+0.5);
for i = 1:length(a)
    if (b(i) == 65536)
        b(i) = 0;
    end
end
           
  1. 編寫mif檔案
%編寫mif檔案
fid = fopen('sinx.mif','wt'); %将信号寫入一個.mif檔案中
fprintf(fid,'WIDTH=%d;\n',width);%寫入存儲位寬8位
fprintf(fid,'DEPTH=%d;\n',depth);%寫入存儲深度1024
fprintf(fid,'ADDRESS_RADIX=UNS;\n');%寫入位址類型為無符号整型
fprintf(fid,'DATA_RADIX=UNS;');%寫入資料類型為無符号整型
fprintf(fid,'CONTENT BEGIN\n');%起始内容
for num=0 : 127 
fprintf(fid,'%d:%16.0f;\n',num,b(num+1));
end
fclose(fid);

           

運作整套代碼我們也可以看見:

FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

紅色是2MHz的正弦波,綠色是8MHz的正弦波,藍色是混合信号.

FPGA-導入波形資料

這裡我們需要例化一個ROM子產品來在fpga上預存前面生成的波形檔案,打開quartus,[Tools]->[IP Catalog]

查找ROM,會找到一個在[Basic Function]->[On Chip Memory]下面的ROM:1-PORT(其實隻要看見這個名字就可以了)輕按兩下例化

  1. 設定好位寬和資料長度
    FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器
  2. 由于對ROM沒有别的要求,是以看直接next到下圖這個位置:
    FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

    填進剛剛在matlab裡面生成的資料檔案.

    然後直接finish就可以了

  3. 例化這個建立的ROM,順便加時鐘
//例化ROM子產品
data_rom 			ROM_Init
(
	.address			(address_rom		),	//rom的位址端口
	.clock	    		(CLK_50M			),	//rom的時鐘端口
	.q					(data_rom	)	//rom的資料端口
);

//時序電路,用來給rom_addr寄存器指派
always @ (posedge CLK_50M or negedge RST_N)
begin
	if(!RST_N)								//判斷複位
		address_rom <= 7'd0;				//初始化time_cnt值
	else
		address_rom <= address_rom + 2 ;			//用來給time_cnt指派
end
		//這裡為什麼是   +2   後面再讨論
           

這樣子,每一個時鐘周期下面我們在data_rom下面都可以得到一個波形資料.

  1. 順道把抽頭系數定義一下
localparam COEFF1     	=	63;  
localparam COEFF2     	=	39;  
localparam COEFF3     	=	48;  
localparam COEFF4     	=	54;  
localparam COEFF5     	=	59;  
localparam COEFF6     	=	60;  
localparam COEFF7     	=	59;  
localparam COEFF8     	=	54;  
localparam COEFF9     	=	48;  
localparam COEFF10    	=	39;  	
localparam COEFF11    	=	63;   	
           

FPGA - FIR結構設計

由前面我們講過,FIR有三個部分,有三大部分,第一部分是對輸入信号的延時,第二部分是輸入信号和抽頭系數的相乘,第三部分是相乘結果的累加.是以我們也可以分三部分來寫這個東西

part1-移位寄存器

我們可以用移位寄存器來達到輸入信号的延時.

//pipeline 1 
always @ (posedge CLK_50M or negedge RST_N)
begin
    if(!RST_N)begin
        data_shift[0]  <= 0;
        data_shift[1]  <= 0;
        data_shift[2]  <= 0;
        data_shift[3]  <= 0;
        data_shift[4]  <= 0;
        data_shift[5]  <= 0;
        data_shift[6]  <= 0;
        data_shift[7]  <= 0;
        data_shift[8]  <= 0;
        data_shift[9]  <= 0;
        data_shift[10] <= 0;
    end
    else begin
        data_shift[10]  <= data_shift[9];
        data_shift[9]  <= data_shift[8];
        data_shift[8]  <= data_shift[7];
        data_shift[7]  <= data_shift[6];   
        data_shift[6]  <= data_shift[5];   
        data_shift[5]  <= data_shift[4]; 
        data_shift[4]  <= data_shift[3]; 
        data_shift[3]  <= data_shift[2];
        data_shift[2]  <= data_shift[1];
        data_shift[1]  <= data_shift[0]; 
        data_shift[0]  <= data_rom;
    end                  
end
           

part2-乘法器

這裡本來想例化乘法器ip來寫的,但是鑒于很懶,是以就直接在verilog上面的乘号來代替,将優化丢給了編譯器.(是以下面的代碼架構是别人家的)

always @ (posedge CLK_50M or negedge RST_N)
begin
    if(!RST_N)
        mul_data[0] <= 0;
    else
        mul_data[0]  <= data_shift[0]   *  COEFF1;
end

always @(posedge CLK_50M or negedge RST_N)begin
    if(!RST_N)
        mul_data[1] <= 0;
    else
        mul_data[1]  <= data_shift[1]   *  COEFF2;
end

………………………………………………………………………

always @(posedge CLK_50M or negedge RST_N)begin
    if(!RST_N)
        mul_data[9] <= 0;
    else
        mul_data[9]  <= data_shift[9]   *  COEFF10;
end
always @(posedge CLK_50M or negedge RST_N)begin
    if(!RST_N)
        mul_data[10] <= 0;
		 else
        mul_data[10]  <= data_shift[10]   *  COEFF11;
end

           

part3-加法器

這裡給出兩種寫法,順道說明一下fpga裡面的些許技巧

  1. 類c寫法
always @(posedge CLK_50M or negedge RST_N)begin
    if(!RST_N)begin
        dout_r <= 0;
    end
    else 
        dout_r <= mul_data[0]+mul_data[1]+mul_data[2]+mul_data[3]+mul_data[4]+mul_data[5]+mul_data[6]+mul_data[7]+mul_data[8]+mul_data[9]+mul_data[10];
end
           
  1. 例化ip寫法

    用的是parallel_add的ip,其實隻要自己手動優化一下,也可以不用ip.因為他隻能做8的倍數的,但是我的濾波器隻有10階.雖然有點浪費資源,但是簡單示範的話,把其他位置0就完事了.例化過程較為簡單,就不介紹了

wire [19:0] add_result;
 add unit_add(
	.clock       (CLK_50M),
	.data0x      (mul_data[0][21:6]),
	.data10x     (mul_data[1][21:6]),
	.data11x     (mul_data[2][21:6]),
	.data12x     (mul_data[3][21:6]),
	.data13x     (mul_data[4][21:6]),
	.data14x     (mul_data[5][21:6]),
	.data15x     (mul_data[6][21:6]),
	.data1x      (mul_data[7][21:6]),
	.data2x      (mul_data[8][21:6]),
	.data3x      (mul_data[9][21:6]),
	.data4x      (mul_data[10][21:6]),
	.data5x     (16'b0),
	.data6x     (16'b0),
	.data7x     (16'b0),
	.data8x     (16'b0),
	.data9x     (16'b0),
	.result     (add_result)
);
           

至此,整個系統的各個關鍵部件就搭建完成了.

仿真結果

仿真波形

FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

第一個波形是輸入信号,

第二個波形是加法器的輸出,

第三個波形是用上面第一種加法器寫出來的波形,

第四個波形是用上面第二種加法器寫出來的波形.

可以看出基本有那麼一點點的濾波的作用,然而噪聲還是非常的大,其中最主要的原因就在于,這個FIR濾波器隻有十階,再加上FIR本身的濾波特性,莫得辦法啦

而事實上由于階數太少了,以至于濾波效果還沒有收到字長效應的影響,這也是讓我始料未及的…

方案對比

寫這個最主要的原因是為了回答一下那些說用類c寫法來直接寫fpga的大兄弟們…因為真的很多人學了之後都這樣子…

方案一:類c寫法

我們來看一下他的資源占用和最高運作速率:

FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器
FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

總共用了857個邏輯單元,最高運作速率是59.2MHz

方案二:例化ip寫法

FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器
FPGA/Verilog 設計FIR濾波器FPGA/Verilog 設計FIR濾波器

總共用了797個邏輯單元,最高運作速率是190.59MHz.

還千萬不要忘記了,這個ip是16個輸入位的,而我們隻用了其中的11個!

是以結果就是,我用的資源比你少,速度可以比你快,出來的波形還一樣.雖然類C寫法确實帶來了不少的友善,但是他确實敵不過速度的制約,速度的制約出問題之後,顯然時序限制就是天荒夜談了.

結語

這一個應用或許是fpga在dsp上面較為簡單的一個應用了,用fpga寫dsp,還是要有紮紮實實的dsp基礎才行.

這個也志在跑通一個流程吧,FIR其實有很多東西,考慮到篇幅,我都沒有寫出來.不過也莫得關系了.

還有就是希望csdn的markdown編輯器可以早日有對Verilog和matlab的支援.

期末愉快

如果你覺得有丶收獲的話

繼續閱讀