目录
1.FIR数字滤波器实现原理
一个 N 阶的 FIR 滤波器输出公式 y(n) 如下:
式1中 h(k)为滤波器的系数,x(n-k)为x(n)延时k个周期。系统的传输函数H(z)可表示成公式2:
从式1看出:滤波过程主要是一组特定的系数与信号完成卷积的过程。从式2看出,在有限的Z平面内它有N-1个零点,同时其 N-1个极点全部位于 z=0 中,因此 FIR 滤波器也被称为全零点滤波器,是一个单位脉冲响应有限长的稳定系统。
FIR滤波器在系数满足一定条件的情况下,它的相频特性是线性的,可以有效的保留信号的相位信息,因此线性相位的 FIR 滤波器在实际工程中有着较为广泛的应用。
2.FPGA实现
Fir滤波器的结构形式分为直接型、级联型、频率取样型、快速卷积型四种,其中最常用,最简单的是直接型。FPGA实现直接型Fir滤波器时,可有采用串行结构、并行结构、分布式结构,以及直接使用器件提供的ip核,下面对每种结构的实现方法进行介绍,代码实现以及仿真测试。
2.1全串行FIR滤波器
2.1.1 原理图
2.1.2 Verilog代码
//这是FirFullSerial.v文件的程序清单
module fir_filter(
rst,clk,Xin,
Yout);
input rst; //复位信号,高电平有效
input clk; //FPGA系统时钟,频率为16kHz
input signed [11:0] Xin; //数据输入频率为2khZ
output signed [28:0] Yout; //滤波后的输出数据
//实例化有符号数乘法器IP核mult
reg signed [11:0] coe; //滤波器为12比特量化数据
wire signed [12:0] add_s; //输入为12比特量化数据,两个对称系数相加需要13比特存储
wire signed [24:0] Mout;
mult_ip Umult (
.clock (clk),
.dataa (coe),
.datab (add_s),
.result (Mout));
//实例化有符号数加法器IP核,对输入数据进行1位符号位扩展,输出结果为13比特数据
reg signed [12:0] add_a;
reg signed [12:0] add_b;
add_ip Uadder (
.dataa (add_a),
.datab (add_b),
.result (add_s));
//3位计数器,计数周期为8,为输入数据速率
reg [2:0] count;
always @(posedge clk or posedge rst)
if (rst)
count = 3'd0;
else
count = count + 1;
//将数据存入移位寄存器Xin_Reg中
reg [11:0] Xin_Reg[15:0];
reg [3:0] i,j;
always @(posedge clk or posedge rst)
if (rst)
//初始化寄存器值为0
begin
for (i=0; i<15; i=i+1)
Xin_Reg[i]=12'd0;
end
else
begin
if (count==7)
begin
for (j=0; j<15; j=j+1)
Xin_Reg[j+1] <= Xin_Reg[j];
Xin_Reg[0] <= Xin;
end
end
//将对称系数的输入数据相加,同时将对应的滤波器系数送入乘法器
//需要注意的是,下面程序只使用了一个加法器及一个乘法器资源
//以8倍数据速率调用乘法器IP核,由于滤波器长度为16,系数具有对称性,故可在一个数据
//周期内完成所有8个滤波器系数与数据的乘法运算
//为了保证加法运算不溢出,输入输出数据均扩展为13比特。
always @(posedge clk or posedge rst)
if (rst)
begin
add_a <= 13'd0;
add_b <= 13'd0;
coe <= 12'd0;
end
else
begin
if (count==3'd0)
begin
add_a <= {Xin_Reg[0][11],Xin_Reg[0]};
add_b <= {Xin_Reg[15][11],Xin_Reg[15]};
coe <= 12'h000;//c0
end
else if (count==3'd1)
begin
add_a <= {Xin_Reg[1][11],Xin_Reg[1]};
add_b <= {Xin_Reg[14][11],Xin_Reg[14]};
coe <= 12'hffd; //c1
end
else if (count==3'd2)
begin
add_a <= {Xin_Reg[2][11],Xin_Reg[2]};
add_b <= {Xin_Reg[13][11],Xin_Reg[13]};
coe <= 12'h00f; //c2
end
else if (count==3'd3)
begin
add_a <= {Xin_Reg[3][11],Xin_Reg[3]};
add_b <= {Xin_Reg[12][11],Xin_Reg[12]};
coe <= 12'h02e; //c3
end
else if (count==3'd4)
begin
add_a <= {Xin_Reg[4][11],Xin_Reg[4]};
add_b <= {Xin_Reg[11][11],Xin_Reg[11]};
coe <= 12'hf8b; //c4
end
else if (count==3'd5)
begin
add_a <= {Xin_Reg[5][11],Xin_Reg[5]};
add_b <= {Xin_Reg[10][11],Xin_Reg[10]};
coe <= 12'hef9; //c5
end
else if (count==3'd6)
begin
add_a <= {Xin_Reg[6][11],Xin_Reg[6]};
add_b <= {Xin_Reg[9][11],Xin_Reg[9]};
coe <= 12'h24e; //c6
end
else
begin
add_a <= {Xin_Reg[7][11],Xin_Reg[7]};
add_b <= {Xin_Reg[8][11],Xin_Reg[8]};
coe <= 12'h7ff; //c7
end
end
//对滤波器系数与输入数据的乘法结果进行累加,并输出滤波后的数据
//考虑到乘法器及累加器的延时,需要计数器为2时对累加器清零,同时输出滤波器结果数据。
//类似的时延长度一方面可通过精确计算获取,但更好的方法是通过行为仿真查看
reg signed [28:0] sum;
reg signed [28:0] yout;
always @(posedge clk or posedge rst)
if (rst)
begin
sum = 29'd0;
yout <= 29'd0;
end
else
begin
if (count==2)
begin
yout <= sum;
sum = 29'd0;
sum =sum + Mout;
end
else
sum = sum + Mout;
end
assign Yout = yout;
endmodule
2.1.3 仿真测试代码
`timescale 1 ns/ 1 ns //设置仿真时间单位:ns
module fir_filter_tb();
// constants
// general purpose registers
reg eachvec;
// test vector input registers
reg [11:0] Xin;
reg clk;
reg rst;
wire clk_data; //数据时钟,速率为系统时钟clk的1/8;
// wires
wire [28:0] Yout;
// assign statements (if any)
fir_filter i1 (
// port map - connection between master ports and signals/registers
.Xin(Xin),
.Yout(Yout),
.clk(clk),
.rst(rst)
);
parameter clk_period=62500; //设置时钟信号周期(频率):16kHz
parameter clk_period_data=clk_period*8;
parameter clk_half_period=clk_period/2;
parameter clk_half_period_data=clk_half_period*8;
parameter data_num=2000; //仿真数据长度
parameter time_sim=data_num*clk_period; //仿真时间
initial
begin
//设置时钟信号初值
clk=1;
//clk_data=1;
//设置复位信号
rst=1;
#200000 rst=0;
//设置仿真时间
#time_sim $finish;
//设置输入信号初值
Xin=12'd10;
end
//产生时钟信号
always
#clk_half_period clk=~clk;
reg [2:0] cn_clk=3'd0;
always @(posedge clk) cn_clk <= cn_clk + 3'd1;
assign clk_data = cn_clk[2];
//从外部TX文件(SinIn.txt)读入数据作为测试激励
integer Pattern;
reg [11:0] stimulus[1:data_num];
initial
begin
//文件必须放置在"工程目录\simulation\modelsim"路径下
//$readmemb("E4_7_Bin_noise.txt",stimulus);
$readmemb("E4_7_Bin_s.txt",stimulus);
Pattern=0;
repeat(data_num)
begin
@(posedge clk_data);
Pattern=Pattern+1;
Xin=stimulus[Pattern];
end
end
//将仿真数据dout写入外部TXT文件中(out.txt)
integer file_out;
initial
begin
//文件放置在"工程目录\simulation\modelsim"路径下
//file_out = $fopen("E4_7_Noiseout.txt");
file_out = $fopen("E4_7_Sout.txt");
if(!file_out)
begin
$display("could not open file!");
$finish;
end
end
wire rst_write;
wire signed [28:0] dout_s;
assign dout_s = Yout; //将dout转换成有符号数据
assign rst_write = clk_data & (!rst);//产生写入时钟信号,复位状态时不写入数据
always @(posedge rst_write )
$fdisplay(file_out,"%d",dout_s);
endmodule
2.1.4 仿真结果图
可见,输出结果dout_a,与输入数据的时钟上升沿clk_data差两个时钟周期(输入数据的时钟上升沿输入数据),正如代码中所写,考虑到加法器和乘法器的延时,所以延迟两个时钟周期读输出数据,及cn_clk=2时输出。
持续更新中