目录
简介
介绍了如何使用Verilog实现具有预生成系数的简单FIR滤波器。
名词解释
FIR(Finite Impulse Response)滤波器:有限长单位冲激响应滤波器,又称为非递归型滤波器,是数字信号处理系统中最基本的元件,它可以在保证任意幅频特性的同时具有严格的线性相频特性,同时其单位抽样响应是有限长的,因而滤波器是稳定的系统。因此,FIR滤波器在通信、图像处理、模式识别等领域都有着广泛的应用,在进入FIR滤波器前,首先要将信号通过A/D器件进行模数转换,把模拟信号转化为数字信号;为了使信号处理能够不发生失真,信号的采样速度必须满足香农采样定理,一般取信号频率上限的4-5倍做为采样频率;一般可用速度较高的逐次逼进式A/D转换器,不论采用乘累加方法还是分布式算法设计FIR滤波器,滤波器输出的数据都是一串序列,要使它能直观地反应出来,还需经过数模转换,因此由FPGA构成的FIR滤波器的输出须外接D/A模块。FPGA有着规整的内部逻辑阵列和丰富的连线资源,特别适合于数字信号处理任务,相对于串行运算为主导的通用DSP芯片来说,其并行性和可扩展性更好,利用FPGA乘累加的快速算法,可以设计出高速的FIR数字滤波器。
项目详情
不起眼的FIR滤波器是FPGA上数字信号处理中最基本的构建块之一,因此了解如何将具有给定数量的抽头及其相应系数值的基本模块组合在一起非常重要。因此,在这个关于fpga上DSP基础知识入门的实用方法的迷你系列中,我将从一个简单的15分路低通滤波器FIR开始,我在Matlab中生成初始系数值,然后将这些值转换为Verilog模块中使用。
有限脉冲响应或FIR滤波器被定义为脉冲响应在一定时间内趋于零值的滤波器,从而使其成为有限的。脉冲响应趋于零所需的时间与滤波器的阶数(抽头的数量)直接相关,即FIR的底层传递函数多项式的阶数。FIR的传递函数不包含反馈,所以如果你输入一个值为1的脉冲,然后是一堆零值,输出将只是滤波器的系数值。
任何滤波器的作用都是对信号进行调理,主要集中在选择滤除或允许通过的频率。最简单的例子之一是低通滤波器,它允许低于某个阈值(截止频率)的频率通过,同时大大衰减高于该阈值的频率,如下图所示。
这个项目的主要重点是在HDL(具体来说是Verilog,但这个概念可以很容易地翻译成VHDL)中实现FIR,它可以分解成三个主要的逻辑组件:一个循环缓冲器,用于将每个样本时钟到适当地考虑串行输入的延迟,每个抽头系数值的乘子,以及用于每个抽头输出的求和结果的累加器寄存器。
于FPGA逻辑中的FIR机制,我只是使用Matlab中的FDA工具在Simulink中插入一些简单的低通滤波器参数,然后使用生成的系数值计算为我的Verilog模块的适当寄存器值(在后面的步骤中完成
项目从一个简单的15抽头系数低通滤波器FIR开始,在Matlab中生成初始系数值,然后进行转换并用于Verilog模块。主要包括三个部分:循环缓冲器、抽头系数乘法器,以及累加寄存器。
要实现一个简单的15抽头低通滤波器,通带频率为200 kHz,阻带频率为355kHz,通过计算得到以下系数
首先是定义输入数据、输出数据和系数本身的位宽,并对系数进行转换:
然后是模块三个功能部分的逻辑设计,以及为模块封装AXI Stream数据流接口。仿真结果如下,FIR滤波器正确过滤信号并正确响应AXI数据流信号:
`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 == 1'b0) //if (reset == 1'b0 || tvalid_in == 1'b0)
begin
buff_cnt <= 4'd0;
enable_fir <= 1'b0;
in_sample <= 8'd0;
end
else if (m_axis_fir_tready == 1'b0 || s_axis_fir_tvalid == 1'b0)
begin
enable_fir <= 1'b0;
buff_cnt <= 4'd15;
in_sample <= in_sample;
end
else if (buff_cnt == 4'd15)
begin
buff_cnt <= 4'd0;
enable_fir <= 1'b1;
in_sample <= s_axis_fir_tdata;
end
else
begin
buff_cnt <= buff_cnt + 1;
in_sample <= s_axis_fir_tdata;
end
end
always @ (posedge clk)
begin
if(reset == 1'b0 || m_axis_fir_tready == 1'b0 || s_axis_fir_tvalid == 1'b0)
begin
s_axis_fir_tready <= 1'b0;
m_axis_fir_tvalid <= 1'b0;
enable_buff <= 1'b0;
end
else
begin
s_axis_fir_tready <= 1'b1;
m_axis_fir_tvalid <= 1'b1;
enable_buff <= 1'b1;
end
end
/* Circular buffer bring in a serial input sample stream that
* creates an array of 15 input samples for the 15 taps of the filter. */
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
else
begin
buff0 <= buff0;
buff1 <= buff1;
buff2 <= buff2;
buff3 <= buff3;
buff4 <= buff4;
buff5 <= buff5;
buff6 <= buff6;
buff7 <= buff7;
buff8 <= buff8;
buff9 <= buff9;
buff10 <= buff10;
buff11 <= buff11;
buff12 <= buff12;
buff13 <= buff13;
buff14 <= buff14;
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
endmodule