緒論
不起眼的 FIR 濾波器是 FPGA 數字信號處理中最基本的模塊之一,因此了解如何將具有給定抽頭數及其相應系數值的基本模塊組合在一起非常重要。因此,在這個關于 FPGA 上 DSP 基礎實用入門的教程中,將從一個簡單的 15 抽頭低通濾波器 FIR 開始,在 Matlab 中為其生成初始系數值,然后轉換這些值用于編寫 Verilog 模塊。
有限脈沖響應或 FIR 濾波器定義為脈沖響應在特定時間段內穩定為零值的濾波器。脈沖響應穩定到零所花費的時間與濾波器階數(抽頭數)直接相關,濾波器階數是 FIR 的基礎傳遞函數多項式的階數。FIR 的傳遞函數不包含反饋,因此如果輸入一個值為 1 的脈沖,然后輸入一串零值,輸出將只是濾波器的系數值。
濾波器的作用基本都是用于信號調節,主要集中在選擇濾除或允許通過哪些頻率。最簡單的例子之一是低通濾波器,它允許低于某個閾值(截止頻率)的頻率通過,同時大大衰減高于該閾值的頻率,如下圖所示。
該項目的主要重點是在 HDL(具體為 Verilog)中實現 FIR,它可以分解為三個主要邏輯組件:一個循環緩沖器,用于將每個樣本計時到適當地考慮了串行輸入的延遲、每個抽頭系數值的乘法器以及每個抽頭輸出的求和結果的累加器。
由于本項目專注于 FPGA 邏輯中 FIR 的設計機制,所以只是使用 Simulink 中的 FDA 工具和 Matlab 為低通濾波器插入一些簡單參數,然后使用生成的系數值放到 Verilog 模塊中完成濾波器的設計(在后面的步驟中完成)。
選擇實現一個簡單的 15 抽頭低通濾波器 FIR,采樣率為 1Ms/s,通帶頻率為 200kHz,阻帶頻率為 355kHz,得到以下系數:
-0.0265 0 0.0441 0 -0.0934 0 0.3139 0.5000 0.3139 0 -0.0934 0 0.0441 0 -0.0265
為 FIR 模塊創建設計文件
在 Vivado 項目中添加源文件。
在確定 FIR 的順序(抽頭數)并獲得系數值后,接下來需要定義的下一組參數就是輸入樣本、輸出樣本和系數本身的位寬。
對于這個 FIR,選擇將輸入樣本和系數寄存器設置為 16 位寬,并將輸出樣本寄存器設置為 32 位,因為兩個 16 位值的乘積是一個 32 位值(兩個值的寬度相乘得到乘積的寬度,所以如果選擇了 8 位抽頭的 16 位輸入樣本,那么輸出樣本將為 24 位寬)。
這些值也都是帶符號的,因此 MSB 用作符號位,在選擇輸入樣本寄存器的初始寬度時一定要記住這一點。要在 Verilog 中將這些值設置為有符號數據類型,使用關鍵字signed :
`reg signed [15:0] register_name;
`
接下來要解決的是如何在 Verilog 中處理系數值,小數點值需要轉換為定點值。由于所有系數值都小于 1,因此寄存器的所有 15 位(總共 16 位,MSB 是有符號位)都可以用于小數位。通常,必須決定要將寄存器中的多少位用于數字的整數部分與數字的小數部分。因此,轉換分數值抽頭的數學是:(fractional coefficient value)*(2^(15))該乘積的小數值被四舍五入,并且如果系數為負,則計算該值的二進制補碼:
`tap0 = twos(-0.0265 * 32768) = 0xFC9C
tap1 = 0
tap2 = 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
tap3 = 0
tap4 = twos(-0.0934 * 32768) = 0xF40C
tap5 = 0
tap6 = 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
tap7 = 0.5000 * 32768 = 16384 = 0x4000
tap8 = 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
tap9 = 0
tap10 = twos(-0.0934 * 32768) = 0xF40C
tap11 = 0
tap12 = 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
tap13 = 0
tap14 = twos(-0.0265 * 32768) = 0xFC9C
`
現在我們終于準備好關注 FIR 模塊的邏輯,第一個是循環緩沖區,它引入串行輸入樣本流并為濾波器的 15 個抽頭創建一個包含 15 個輸入樣本的數組。
` always @ (posedge clk)
begin
if(enable_buff == 1'b1)
begin
buff0 <= in_sample;
buff1 <= buff0;
buff2 <= buff1;
buff3 <= buff2;
buff4 <= buff3;
buff5 <= buff4;
buff6 <= buff5;
buff7 <= buff6;
buff8 <= buff7;
buff9 <= buff8;
buff10 <= buff9;
buff11 <= buff10;
buff12 <= buff11;
buff13 <= buff12;
buff14 <= buff13;
end
end
`
接下來,乘法階段將每個樣本乘以每個系數值:
` /* Multiply stage of FIR */
always @ (posedge clk)
begin
if (enable_fir == 1'b1)
begin
acc0 <= tap0 * buff0;
acc1 <= tap1 * buff1;
acc2 <= tap2 * buff2;
acc3 <= tap3 * buff3;
acc4 <= tap4 * buff4;
acc5 <= tap5 * buff5;
acc6 <= tap6 * buff6;
acc7 <= tap7 * buff7;
acc8 <= tap8 * buff8;
acc9 <= tap9 * buff9;
acc10 <= tap10 * buff10;
acc11 <= tap11 * buff11;
acc12 <= tap12 * buff12;
acc13 <= tap13 * buff13;
acc14 <= tap14 * buff14;
end
end
`
乘法階段的結果值通過加法累加到寄存器中,最終成為濾波器的輸出數據流。
` /* Accumulate stage of FIR */
always @ (posedge clk)
begin
if (enable_fir == 1'b1)
begin
m_axis_fir_tdata <= acc0 + acc1 + acc2 + acc3 + acc4 + acc5 + acc6 + acc7 + acc8 + acc9 + acc10 + acc11 + acc12 + acc13 + acc14;
end
end
`
最后,邏輯的最后一部分是將數據流進和流出 FIR 模塊的接口。AXI Stream 接口是最常見的接口之一。關鍵方面是允許控制上游和下游設備之間的數據流的tready和tvalid信號。這意味著 FIR 模塊需要向其下游設備提供tvalid信號以指示其輸出是有效數據,并且如果下游設備解除其tready信號,則能夠暫停(但仍保留)其輸出。FIR 模塊還必須能夠與其主端接口上的上游設備以同樣的方式運行。
以下是 FIR 模塊的邏輯設計概述:
請注意tready和tvalid信號如何設置輸入循環緩沖器的使能值和 FIR 的乘法級以及數據或系數通過的每個寄存器都被聲明為有符號的。
FIR模塊Verilog代碼:
`` `timescale 1ns / 1ps
module FIR(
input clk,
input reset,
input signed [15:0] s_axis_fir_tdata,
input [3:0] s_axis_fir_tkeep,
input s_axis_fir_tlast,
input s_axis_fir_tvalid,
input m_axis_fir_tready,
output reg m_axis_fir_tvalid,
output reg s_axis_fir_tready,
output reg m_axis_fir_tlast,
output reg [3:0] m_axis_fir_tkeep,
output reg signed [31:0] m_axis_fir_tdata
);
always @ (posedge clk)
begin
m_axis_fir_tkeep <= 4'hf;
end
always @ (posedge clk)
begin
if (s_axis_fir_tlast == 1'b1)
begin
m_axis_fir_tlast <= 1'b1;
end
else
begin
m_axis_fir_tlast <= 1'b0;
end
end
// 15-tap FIR
reg enable_fir, enable_buff;
reg [3:0] buff_cnt;
reg signed [15:0] in_sample;
reg signed [15:0] buff0, buff1, buff2, buff3, buff4, buff5, buff6, buff7, buff8, buff9, buff10, buff11, buff12, buff13, buff14;
wire signed [15:0] tap0, tap1, tap2, tap3, tap4, tap5, tap6, tap7, tap8, tap9, tap10, tap11, tap12, tap13, tap14;
reg signed [31:0] acc0, acc1, acc2, acc3, acc4, acc5, acc6, acc7, acc8, acc9, acc10, acc11, acc12, acc13, acc14;
/* Taps for LPF running @ 1MSps with a cutoff freq of 400kHz*/
assign tap0 = 16'hFC9C; // twos(-0.0265 * 32768) = 0xFC9C
assign tap1 = 16'h0000; // 0
assign tap2 = 16'h05A5; // 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
assign tap3 = 16'h0000; // 0
assign tap4 = 16'hF40C; // twos(-0.0934 * 32768) = 0xF40C
assign tap5 = 16'h0000; // 0
assign tap6 = 16'h282D; // 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
assign tap7 = 16'h4000; // 0.5000 * 32768 = 16384 = 0x4000
assign tap8 = 16'h282D; // 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
assign tap9 = 16'h0000; // 0
assign tap10 = 16'hF40C; // twos(-0.0934 * 32768) = 0xF40C
assign tap11 = 16'h0000; // 0
assign tap12 = 16'h05A5; // 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
assign tap13 = 16'h0000; // 0
assign tap14 = 16'hFC9C; // twos(-0.0265 * 32768) = 0xFC9C
/* This loop sets the tvalid flag on the output of the FIR high once
* the circular buffer has been filled with input samples for the
* first time after a reset condition. */
always @ (posedge clk or negedge reset)
begin
if (reset ==