#分数倍抽取(插值)滤波器设计思路 #
本文为原创文章,未经同意,严禁转载!
滤波器的特性我们就不做要求了,这方面不是本文的侧重点。
我们知道,这里分数倍滤波器的流程是:插值->滤波->抽取。至于为什么是这个流程,本文不作详细介绍,有兴趣的童鞋可以自行问度娘。根据这个流程,那滤波器的采样率为:300MHz*2 = 600MHz
。 现我们通过MATLAB设计滤波器如下图:
设置滤波器带宽为20MHz,阻带100MHz。这里主要是为了后面阐述方便,故设计了一个29阶的滤波器,得到30个滤波器系数,从MATLAB中导出xilinx系数(coe)文件如下:
接下来我们该到vivado中生成fir的IP核了。如下图:
发现没有,发现没有。问题来了,IP核要求我们最小工作时钟是400MHz,纳尼,好无奈。。小R不太了解xilinx这个IP核的实现方式到底是怎样的,但是这个要求我们是没法接受的呀。可能有同学说了,我们可以将数据异步到400MHz的时钟下去做滤波呀?当然,400MHz可能还好,如果输入码率更大呢?肯定不能继续生时钟呀!!!且FPGA片子有工作最大时钟。所以这个不是解决这个问题的最好方法。
我们回想一下,fir滤波器原理如下:
其实就是数据和滤波器系数的乘加,小R把本例中插值后的数据列为下图中最左列的关系;这样想来,我们只要满足fir的计算公式,进行分开计算再累加其结果就可以了。小R将原本的滤波器系数拆分为3个滤波器,分别对应关系为下图中右边几列。值得注意的是,我们在拆分的第三滤波器中发现是第3个数据乘第2个系数,但是应该是乘第5个系数,细心的小R发现第三个滤波器的第2个系数就是第5个原系数,小R第三个滤波器处理为最右边一列表示的滤波器就对上了。这样一来,我们就只需要将三个滤波器的结果进行加运算就可以得到滤波后的数据了。
我们先将上面设计的滤波器系数分为三列:
再整理得到如下的三个滤波器系数:
加下来我们生成一下新的滤波器(3个);因为输入的数据被分到三个滤波中,所以每个滤波器的码率就只有原来的1/3了,即100MHz的码率,工作时钟仍然是300MHz。如下图滤波器配置。
根据上述逻辑编写verlog代码。
`timescale 1ns/1ps
/*
Engineer :
Create Date: 2019/08/30 18:38:05
Module Name: fir_I2D3.v
Description:
**********************************************************
记录时间: 2019/08/30 18:38:05 记录人:
*/
module fir_I2D3(
input clk
, input din_vld
, input [15:0] din_data
, input dout_vld
, output [15:0] dout_data
);
localparam PART0 = 0;
localparam PART1 = 1;
localparam PART2 = 2;
reg [2:0] fir_din_vld = 0 ;
wire [2:0] fir_din_rdy ;
reg [15:0] fir_din_data [2:0];
wire [2:0] fir_out_vld ;
wire [39:0] fir_out_data [2:0];
reg [1:0] data_cnt = 0;
always@(posedge clk)begin
if(din_vld)begin
data_cnt <= (data_cnt < 2) ? (data_cnt + 1) : 0;
end else begin
data_cnt <= data_cnt;
end
end
genvar dex;
generate for(dex=0; dex<3; dex=dex+1)begin
always@(posedge clk)begin
fir_din_vld [dex] <= (din_vld && (data_cnt==dex)) ? 1'd1 : 1'd0; //根据计数值送进不同的滤波器
fir_din_data[dex] <= din_data;
end
end endgenerate
ip_fir_I2D3_part0 u_ip_fir_I2D3_part0 (
.aclk (clk ), // input wire aclk
.s_axis_data_tvalid (fir_din_vld [PART0] ), // input wire s_axis_data_tvalid
.s_axis_data_tready (fir_din_rdy [PART0] ), // output wire s_axis_data_tready
.s_axis_data_tdata (fir_din_data[PART0] ), // input wire [15 : 0] s_axis_data_tdata
.m_axis_data_tvalid (fir_out_vld [PART0] ), // output wire m_axis_data_tvalid
.m_axis_data_tdata (fir_out_data[PART0] ) // output wire [39 : 0] m_axis_data_tdata
);
ip_fir_I2D3_part1 u_ip_fir_I2D3_part1 (
.aclk (clk ), // input wire aclk
.s_axis_data_tvalid (fir_din_vld [PART1] ), // input wire s_axis_data_tvalid
.s_axis_data_tready (fir_din_rdy [PART1] ), // output wire s_axis_data_tready
.s_axis_data_tdata (fir_din_data[PART1] ), // input wire [15 : 0] s_axis_data_tdata
.m_axis_data_tvalid (fir_out_vld [PART1] ), // output wire m_axis_data_tvalid
.m_axis_data_tdata (fir_out_data[PART1] ) // output wire [39 : 0] m_axis_data_tdata
);
ip_fir_I2D3_part2 u_ip_fir_I2D3_part2 (
.aclk (clk ), // input wire aclk
.s_axis_data_tvalid (fir_din_vld [PART2] ), // input wire s_axis_data_tvalid
.s_axis_data_tready (fir_din_rdy [PART2] ), // output wire s_axis_data_tready
.s_axis_data_tdata (fir_din_data[PART2] ), // input wire [15 : 0] s_axis_data_tdata
.m_axis_data_tvalid (fir_out_vld [PART2] ), // output wire m_axis_data_tvalid
.m_axis_data_tdata (fir_out_data[PART2] ) // output wire [39 : 0] m_axis_data_tdata
);
reg [39:0] fir_out_data0_d1 = 0;
always@(posedge clk)begin
fir_out_data0_d1 <= fir_out_data[0];
end
reg [40:0] part0_add_part1 = 0;
reg sum_part012_vld = 0;
reg [41:0] sum_part012 = 0;
always@(posedge clk)begin
part0_add_part1 <= {fir_out_data0_d1[39],fir_out_data0_d1} + {fir_out_data[1][39],fir_out_data[1]};
end
always@(posedge clk)begin
sum_part012_vld <= fir_out_vld[2];
sum_part012 <= {part0_add_part1[40],part0_add_part1} + {
{2{fir_out_data[2][39]}},fir_out_data[2]};
end
reg dout_temp_vld ;
reg [16:0] dout_temp_data;
always@(posedge clk)begin
dout_temp_vld <= sum_part012_vld;
dout_temp_data <={sum_part012[41],sum_part012[33:18]};
end
assign dout_vld = dout_temp_vld;
assign dout_data= dout_temp_data[16:1];
endmodule
接下来当然是仿真了,编写仿真激励(本文不再给出)。先仿真直流信号结果如下。这一步也可以用来仿真代码的截位对不对,上述代码中的截位是修改后的,因为在后面的仿真中发现数据有溢出的情况,故多截了一位fir输出数据(和只留信号仿真图的代码比)。
将输入的直流信号替换为DDS,配置输出2MHz(滤波器通带内)的单音信号。仿真结果如下:
修改DDS的频率配置,输出80MHz的信号(滤波器过度带),结果如下;可以发现,80MHz信号输入,输出信号的幅度小了。
修改DDS的频率配置,输出100MHz的信号(阻带),结果如下;发现输出的信号很小,几乎为0。
通过仿真认为,这次的滤波器设计是正确的。
其实,不光是I2D3的可以使用这样的方法,其他分数倍抽取(插值)滤波器也是同样的可以实现。只是逻辑可能会复杂一点。在复杂的逻辑中,我们可以通过设置fir滤波器的输入输出缓存ready信号,这样子可以控制fir滤波器的输出时序。配置如下图:
小R提供本次例子的源代码文件。下载源码。
各位童鞋有其他的实现方法的话记得告诉小R。