FIR滤波器
FIR滤波器,全称为Finite Impulse Response,即有限脉冲响应。滤波器的系统函数为:
当分母中所有ak=0时,即不存在反馈支路,输出数据仅取决于输入,这也就是Finite的由来,在上式的系统函数H(z)中,每个z代表一个延迟delay。
那么根据公式H(z)即可很清楚的了解,FIR简单理解就是对输入进行多级存储,之后对不同级别的缓存*系数b,即可实现信号序列的低通、带通、高通等操作!
FIR低通滤波器的verilog实现
上文中介绍了FIR的工作原理,本部分就手写一个FIR低通滤波器,需要准备的原始材料很简单,
1、输入波形数据
2、滤波器系数b。
获取输入波形数据:
波形生成的方式有很多很多种,起码弯路就走过不少,目前见过的生成方式有两种。第一种为时间段生成法,通过生成一个时间段内的数据,通过循环读取该时间段的数据获取生成波形;第二种方式为周期函数法,通过对0~2pi内的函数值进行读取,在本文中分享两种方式的matlab代码,两种方式中都使用了有符号数转补码的函数,该函数将放大后的波形数据转换为对应的补码:
%下面函数重新新建一个脚本文件
%需要调用了如下函数,将有符号数转换成补码
function b = signed2unsigned(a,wl); %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;
else ;
end
end
end
第一种方式,获取一个周期内的波形数据,之后进行循环读取,这种方式我是从公众号《硅农》中学到的(非常优质的公众号,欢迎大家关注!),以采样频率50Mhz,通带截止频率2Mhz,阻带截止频率4Mhz为例,其波形生成函数如下所示,接下来的操作我就不赘述了,大佬公众号里讲的很明白了:
https://mp.weixin.qq.com/s/UpSJIFBk0RBvoQEmbIvjVg
%产生两个正弦信号sin(x)和sin(8x)叠加后的信号,取64个点,将信号放大,
%转换成无符号数据,存入ROM中作为信号源
% clear all
clc
x = 0 : 2*pi/63 :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
hold on;
y = (y/2) * 32768;%将信号放大32768倍 2^15
b = signed2unsigned(y,16); %有转换为补码输入
fid = fopen('sinx.txt','wt'); %将信号写入一个.txt文件中
fprintf(fid,'%16.0f\n',b);
fclose(fid);
in = sin(x)+sin(8*x);
coeff =[ 0.123642574470134
0.0764392722067448
0.0929760805141543
0.106348480961932
0.114819991146986
0.117786188756463
0.114819991146986
0.106348480961932
0.0929760805141543
0.0764392722067448
0.123642574470134];
out =conv(in,coeff);%卷积滤波
subplot(2,1,1);
plot(in);
xlabel('滤波前');
axis([0,100,-2,2]);
subplot(2,1,2);
plot(out);
xlabel('滤波后');
axis([0,100,-1,1]);
%下面函数重新新建一个脚本文件
%需要调用了如下函数,将有符号数转换成补码
function b = signed2unsigned(a,wl); %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;
else ;
end
end
end
第二种方式是我采用的方式,以采样频率200Khz,通带截止频率10Khz,阻带截止频率30Khz为例,其波形生成函数如下,获取10Khz与45Khz两个波形在0~0.1s时间段内的数据,之后将前1024个点存入到RAM中(bug记录:RAM中的数据要为2的N次方,在写入数据个数为400时读取数据失败。)
clear;
clc;
%采样速率200kHz,所以步进值为1/20000,共1s数据
fs=1/2e5;
x=0:2*pi*fs:0.1; %0.1s
%将10kHz与45kHz正弦波叠加
f0=10e3;
f1=45e3;
y=sin(f0.*x)+sin(f1.*x);
y1=floor(y/2.*(2^15)); %15bit signed ADC
y2= signed2unsigned(y1,16); %有转换为补码输入 signed 15bit-->16bit unsigned
fid=fopen('./sin_mix.txt','w');
fprintf(fid,'memory_initialization_radix=10;\r\n');
fprintf(fid,'memory_initialization_vector=\r\n');
for i=1:1024
fprintf(fid,'%16.0f\r\n',y2(i));
% fprintf(fid,'%4d',yn(i));
end
fclose(fid);
plot(x,y1)
%下面函数重新新建一个脚本文件
%需要调用了如下函数,将有符号数转换成补码
function b = signed2unsigned(a,wl); %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;
else ;
end
end
end
获取该波形文件后将其存到single Port ROM中,之后进行读取测试,观察是否可以读出数据(Waveform Style : analog ; Radix: signed decimal):
接下来的就是懒人操作了,因为FIR IP里面的很多设置开始是真的不理解,所以才从结构的角度开始考虑,反正一个“z”就是一级缓存,一个coef系数就是一个乘数,那么就根据下图FIR结构展开,在读取上述波形数据时,一共11个coe系数,10级缓存,共11个输入数据相乘
最终系统函数如下所示:
//////////////////////////////////////////////////////////////////////////////////
// Engineer: XS
// Create Date: 2021/06/23 10:46:17
// Design Name: 根究FPGA
// Module Name: FIR_top
//////////////////////////////////////////////////////////////////////////////////
module FIR_top2(
input clk,
input rst_n,
output signed [15:0] douta,
output signed [15:0] dout
);
reg [9:0]addr=0;
always@(posedge clk or negedge rst_n)
if(~rst_n)
addr<=9'd0;
else
addr<=addr+1'b1;
blk_mem_gen_0 u0 (
.clka(clk), // input wire clka
.addra(addr), // input wire [5 : 0] addra
.douta(douta) // output wire [15 : 0] douta
);
reg signed[15:0]z_pipeline[0:10];
parameter signed coef_0=-45,
coef_1=19,
coef_2=50,
coef_3=90,
coef_4=122,
coef_5=135,
coef_6=122,
coef_7=90,
coef_8=50,
coef_9=19,
coef_10=-45;
/*
11个coe系数与10级缓存,共11个输入数据相乘
*/
wire [24:0] buf0=z_pipeline[0]*coef_0;
wire [24:0] buf1=z_pipeline[1]*coef_1;
wire [24:0] buf2=z_pipeline[2]*coef_2;
wire [24:0] buf3=z_pipeline[3]*coef_3;
wire [24:0] buf4=z_pipeline[4]*coef_4;
wire [24:0] buf5=z_pipeline[5]*coef_5;
wire [24:0] buf6=z_pipeline[6]*coef_6;
wire [24:0] buf7=z_pipeline[7]*coef_7;
wire [24:0] buf8=z_pipeline[8]*coef_8;
wire [24:0] buf9=z_pipeline[9]*coef_9;
wire [24:0] buf10=z_pipeline[10]*coef_10;
always@(posedge clk or negedge rst_n)
if(~rst_n) begin
z_pipeline[0]<=0;
z_pipeline[1]<=0;
z_pipeline[2]<=0;
z_pipeline[3]<=0;
z_pipeline[4]<=0;
z_pipeline[5]<=0;
z_pipeline[6]<=0;
z_pipeline[7]<=0;
z_pipeline[8]<=0;
z_pipeline[9]<=0;
z_pipeline[10]<=0;
end
else begin
z_pipeline[0]<=douta;
z_pipeline[1]<=z_pipeline[0];
z_pipeline[2]<=z_pipeline[1];
z_pipeline[3]<=z_pipeline[2];
z_pipeline[4]<=z_pipeline[3];
z_pipeline[5]<=z_pipeline[4];
z_pipeline[6]<=z_pipeline[5];
z_pipeline[7]<=z_pipeline[6];
z_pipeline[8]<=z_pipeline[7];
z_pipeline[9]<=z_pipeline[8];
z_pipeline[10]<=z_pipeline[9];
end
wire signed [31:0] sum=buf0+buf1+buf2+buf3+buf4+buf5+buf6+buf7+buf8+buf9+buf10;
assign dout = sum >>> 9;
endmodule
仿真结果如下所示,如果想效果更好的话,可以增加coef系数个数,这样的话就不太适合直接结构法(本文仅是从理解原理的角度的出发):
两种方式的运行环境为vivado2017.4,若需获取工程请于后台回复:FIR01