文章目录
- 一、什么是FIR滤波器?
- 二、Verilog实现FIR低通滤波
-
- [2.1 matlab获取抽头系数](#2.1 matlab获取抽头系数)
- [2.2 Verilog代码](#2.2 Verilog代码)
- [2.3 仿真观察](#2.3 仿真观察)
- [三、Xilinx FIR IP核的说明以及使用](#三、Xilinx FIR IP核的说明以及使用)
-
- [3.1 端口信号](#3.1 端口信号)
- [3.2 TDATA结构](#3.2 TDATA结构)
- [3.3 IP调用](#3.3 IP调用)
- [3.4 Verilog代码](#3.4 Verilog代码)
- [3.5 仿真观察](#3.5 仿真观察)
一、什么是FIR滤波器?
FIR(Finite Impulse Response,有限冲激响应)滤波器是一种数字滤波器,其输出信号仅依赖于当前和过去有限数量的输入信号。FIR 滤波器的特点是其冲激响应是有限的,即在输入信号的冲激响应结束后,输出信号会在有限的时间内归零。表达式如下:
y [ n ] = b 0 x [ n ] + b 1 x [ n − 1 ] + b 2 x [ n − 2 ] + . . . . . . . . . . . + b m x [ n − m ] y[n] = b_0x[n]+b_1x[n-1]+b_2x[n-2]+...........+b_mx[n-m] y[n]=b0x[n]+b1x[n−1]+b2x[n−2]+...........+bmx[n−m]
其中 b 0 b_0 b0, b 1 b_1 b1, b 2 b_2 b2... b m b_m bm称为滤波器的系数也叫抽头,m是滤波器的阶数,这个公式其实就是离散信号的卷积公式,具体推导请看《卷积的理解、卷积公式的推导以及计算》这篇文章。
何为滤波器?就是滤除掉信号中不要的频率分量,常见的滤波器分为高通滤波器、低通滤波器、带通滤波器、带阻滤波器。下图为一个加了高斯白噪声的正弦波信号以及它的频谱图:
我们可以看到,一个有高斯白噪声的正弦信号在频谱图中每个频率点都有幅度,如果我们想要滤除掉这些噪声从而恢复出我们想要的纯洁信号,只需要找到一个信号在我们想要的频率范围内有幅度,其它频率范围为0,然后相乘就能够滤除,具体如下:
例如一个信号的频谱如上所示,它的频谱特性表现为50HZ以下有信号,50HZ以上没信号。所以我们只需要把上面加了高斯白噪声的正弦信号的频谱和这个信号的频谱相乘,就能够过滤出50HZ以上的高频的白噪声,相乘的结果如下所示:
然后把频率相乘后的信号经过IFFT就能恢复出原始信号,如下所示:
上面就是一个滤波的过程,那么和FIR有什么关系呢? 我们现在知道滤波就是在频域部分相乘进行过滤的,根据卷积公式:频率相乘=时域卷积,因此我们只需要在时域中把我们想要的信号和一个滤波函数的信号进行卷积就能实现过滤。FIR也叫有限冲激响应,它公式中的抽头系数其实就是滤波器的单位冲激响应,我们只需要用有限个单位冲激响应与信号卷积就能实现滤波。
二、Verilog实现FIR低通滤波
上面我们知道滤波就是时域卷积运算,那么我们就需要设计一个滤波函数,得到它的单位冲击响应,这里使用matlab来设计一个低通滤波器。
2.1 matlab获取抽头系数
首先再matlab命令栏里输入fdatool,然后弹出滤波器设计工具,界面如下:
设计完成后我们点左上角导出系数,这里先选择导出到工作区:
这样我们得到了15阶,采样频率为100MHZ,截止频率为7MHZ的低通滤波抽头,因为FPGA不能处理小数,因此我们需要把抽头适当的放大一下,这里我们放大2048倍:
我们可以看到滤波器抽头系数是对称的,因此我们在被Verilog代码里只需要调用8个乘法器,这样可以省下一半的乘法器资源。
2.2 Verilog代码
c
`timescale 1ns / 1ps
module fir_low(
input aclk ,
input aresetn
);
//dds
wire m_dds_10m_data_tvalid ;
wire signed [15:0] m_dds_10m_data_tdata ;
wire m_dds_5m_data_tvalid ;
wire signed [15:0] m_dds_5m_data_tdata ;
wire signed [31:0] dds_mix ;
reg m_dds_mix_data_valid ;
// fir
reg signed [31:0] signal_reg [0:15]; //定义16个寄存器缓存待滤波的信号 //定位第几个寄存器
wire [11:0] fir_para [0:7]; //抽头系数
reg signed [32:0] first_end_signal_add[0:7] ;//8个寄存器,存放16个信号的首尾之和
wire signed [44:0] mult_out [0:7]; //乘法器后的结果
reg signed [46:0] sum1 ;
reg signed [46:0] sum2 ;
reg signed [47:0] sum_out ;
//assign
assign fir_para[0] = -12'd11 ;
assign fir_para[1] = 12'd23 ;
assign fir_para[2] = 12'd65 ;
assign fir_para[3] = 12'd112 ;
assign fir_para[4] = 12'd158 ;
assign fir_para[5] = 12'd199 ;
assign fir_para[6] = 12'd229 ;
assign fir_para[7] = 12'd245 ;
//ip
dds_10m u0_dds_10m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_dds_10m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_dds_10m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
dds_5m u0_dds_5m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_dds_5m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_dds_5m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
mult_dds u0_mult_dds (
.CLK(aclk), // input wire CLK
.A(m_dds_10m_data_tdata), // input wire [15 : 0] A
.B(m_dds_5m_data_tdata), // input wire [15 : 0] B
.P(dds_mix) // output wire [31 : 0] P
);
//混频信号经过乘法器后会延迟一拍
always @(posedge aclk) begin
if(aresetn == 1'b0)
m_dds_mix_data_valid <= 1'b0;
else
m_dds_mix_data_valid <= (m_dds_10m_data_tvalid && m_dds_5m_data_tvalid);
end
integer i;
//使用for语句循环移位,将信号分别暂存到对应的寄存器里
always @(posedge aclk) begin
if(aresetn == 1'b0)begin
for (i=0; i<16; i = i+1)begin
signal_reg[i] <= 'd0;
end
end
else if(m_dds_mix_data_valid == 1'b1)begin
signal_reg[0] <= dds_mix;
for (i=0; i<15; i = i+1)begin
signal_reg[i+1] <= signal_reg[i];
end
end
end
integer j;
//因为fir系数对称,所以只需要调用阶数的一半的乘法器即可,调用乘法器之前先把首位寄存器相加
always @(posedge aclk) begin
if(aresetn == 1'b0)begin
for(j=0 ; j<8 ; j= j+1)begin
first_end_signal_add[j] <= 'd0;
end
end
else begin
for(j=0 ; j<8 ; j= j+1)begin
first_end_signal_add[j] <= signal_reg[j] + signal_reg[15-j];
end
end
end
//调用8个有符号乘法器
genvar k;
generate
for(k=0; k<8; k = k +1)begin
mult_33_12 u_mult_33_12 (
.CLK(aclk), // input wire CLK
.A(first_end_signal_add[k]), // input wire [32 : 0] A
.B(fir_para[k]), // input wire [11 : 0] B
.P(mult_out[k]) // output wire [44 : 0] P
);
end
endgenerate
always @(posedge aclk) begin
if(aresetn == 1'b0) begin
sum1 <= 'd0;
sum2 <= 'd0;
sum_out <= 'd0;
end
else begin
sum1 = mult_out[0] + mult_out[1] + mult_out[2] + mult_out[3];
sum2 = mult_out[4] + mult_out[5] + mult_out[6] + mult_out[7];
sum_out = sum1 + sum2;
end
end
endmodule
在代码里,我们把滤波器抽头系数固定好,因为是对称的,所以我们只固定前八个即可。
2.3 仿真观察
我们使用dds将一个10MHZ的信号和一个5MHZ的信号进行混频后再经过FIR滤波器,因为我们的低通滤波截至频率为7MHZ,因此我们经过滤波器后10MHZ的信号将被滤掉,只剩下5MHZ的信号,仿真结果正确。
三、Xilinx FIR IP核的说明以及使用
3.1 端口信号
FIR IP核的端口如上所示,不同的配置接出来的端口不一样,端口说明如下:
|----------------------|------|-------------------------------------|
| 端口名称 | 输入方向 | 端口说明 |
| aclk | 输入 | 上升沿有效 |
| aclken | 输入 | 时钟使能信号,高电平有效 |
| aresetn | 输入 | 低电平有效同步复位信号。 需要至少两个周期的 aresetn 有效脉冲 |
| s_axis_config_tvalid | 输入 | 配置数据有效信号 |
| s_axis_config_tready | 输出 | 配置数据准备信号 |
| s_axis_config_tdata | 输入 | 配置数据 |
| s_axis_config_tlast | 输入 | 一包配置数据的最后一个数据指示信号 |
|----------------------|----|--------------|
| s_axis_reload_tvalid | 输入 | 重载数据有效信号 |
| s_axis_reload_tready | 输出 | 重载数据准备信号 |
| s_axis_reload_tdata | 输入 | 重载数据 |
| s_axis_reload_tlast | 输入 | 输入最后一个数据指示信号 |
|--------------------|----|----------------|
| s_axis_data_tvalid | 输入 | 输入数据有效信号 |
| s_axis_data_tready | 输出 | 输入数据准备信号 |
| s_axis_data_tdata | 输入 | 输入数据,传送要过滤的数据流 |
| s_axis_data_tlast | 输入 | 输入最后一个数据指示信号 |
| s_axis_data_tuser | 输入 | 输入用户信号 |
|--------------------|------|--------------------------------------------------------------------|
| 端口名称 | 输入方向 | 端口说明 |
| m_axis_data_tvalid | 输出 | 输出数据有效信号 |
| m_axis_data_tready | 输入 | 输出数据准备信号 |
| m_axis_data_tdata | 输出 | 输出数据 |
| m_axis_data_tuser | 输出 | 用于输出数据通道。可选择从输入数据 tuser 端口传送用户字段和/或 chan ID 字段,以识别当前样本属于哪个 TDM 通道。 |
| m_axis_data_tlast | 输出 | 输出最后一个数据指示信号 |
|---------------------------------|------|-------------------------------------------|
| 端口名称 | 输入方向 | 端口说明 |
| event_s_data_tlast_missing | 输出 | 表示输入 DATA tlast 未按内部通道计数器预期的那样被置位。 |
| event_s_data_tlast_unexpected | 输出 | 表示当内部通道计数器未预期输入 DATA tlast 时,该输入被置位 |
| event_s_data_chanid_incorrect | 输出 | 代表输入 DATA tuser 端口的 chan ID 字段与内部计数器的值不匹配 |
| event_s_reload_tlast_missing | 输出 | 表示 RELOAD tlast 没有按照内部计数器的预期被置位 |
| event_s_reload_tlast_unexpected | 输出 | 表示当内部计数器未预期到 RELOAD tlast 时,该信号被置位 |
| event_s_config_tlast_missing | 输出 | 表示当内部计数器期望时 CONFIG tlast 未被断言 |
| event_s_config_tlast_unexpected | 输出 | 表示当内部计数器未预期到 CONFIG tlast 时,该信号被置位 |
3.2 TDATA结构
输入输出数据结构一致,只是位宽不一样
3.3 IP调用
IP文档其它的等以后用到了再详细看,这里先配置快速用起来,配置步骤如下:
其它默认即可。
3.4 Verilog代码
我们删除前面自己写的代码,只保留dds以及FIR IP核,代码如下:
c
`timescale 1ns / 1ps
module fir_low_1(
input aclk ,
input aresetn
);
//dds
wire m_dds_10m_data_tvalid ;
wire signed [15:0] m_dds_10m_data_tdata ;
wire m_dds_5m_data_tvalid ;
wire signed [15:0] m_dds_5m_data_tdata ;
wire signed [31:0] dds_mix ;
reg m_dds_mix_data_valid ;
wire m_axis_data_tvalid ;
wire [55:0] m_axis_data_tdata ;
//ip
dds_10m u0_dds_10m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_dds_10m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_dds_10m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
dds_5m u0_dds_5m (
.aclk(aclk), // input wire aclk
.aresetn(aresetn), // input wire aresetn
.m_axis_data_tvalid(m_dds_5m_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_dds_5m_data_tdata) // output wire [15 : 0] m_axis_data_tdata
);
mult_dds u0_mult_dds (
.CLK(aclk), // input wire CLK
.A(m_dds_10m_data_tdata), // input wire [15 : 0] A
.B(m_dds_5m_data_tdata), // input wire [15 : 0] B
.P(dds_mix) // output wire [31 : 0] P
);
always @(posedge aclk) begin
if(aresetn == 1'b0)
m_dds_mix_data_valid <= 1'b0;
else
m_dds_mix_data_valid <= (m_dds_10m_data_tvalid && m_dds_5m_data_tvalid);
end
fir_low u0_fir_low (
.aclk(aclk), // input wire aclk
.s_axis_data_tvalid(m_dds_mix_data_valid), // input wire s_axis_data_tvalid
.s_axis_data_tready(), // output wire s_axis_data_tready
.s_axis_data_tdata(dds_mix), // input wire [31 : 0] s_axis_data_tdata
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_data_tdata) // output wire [55 : 0] m_axis_data_tdata
);
endmodule
3.5 仿真观察
我们直接的打开仿真
和我们自己写的代码仿真一致。