前言:复习大学课程中数字信号处理的内容,尽管学了很多理论知识,但与实际的联系还是比较少,也就是说学到的知识不能很好地运用于实践,所以我打算对理论知识进行验证,从MATLAB和FPGA入手,完成滤波器的设计,体会模拟信号数字处理的过程,同时也对这个过程进行记录,方便回顾和完善。
设计一个FIR带通滤波器,截止频率为2400Hz和3600Hz,阻带最小衰减20dB以上,测试信号为1K、2K、3K、4K、5Khz的混频信号,要求3KHz信号顺利通过而过滤其他频率信号。
思路:
1、设计该滤波器,采样频率应该为信号最高频率的2倍以上,我设计的采样频率为40khz,采用凯赛窗设计FIR带通滤波器,通过matlab仿真得到该滤波器的幅频响应:
该滤波器在2KHz处的衰减将近60dB,在1K、4K、5KHz处的衰减也达到了40dB,基本符合设计要求,但为了达到这样的效果,该滤波器的长度被我增加到了64(阶数为63),故它的成本较高。
Matlab代码如下:
fs = 40*10^3;
N = 64;
window = kaiser(N)';
fp = [2400*2/fs 3600*2/fs];
b = fir1(N-1,fp,'bandpass',window);
2、使用FPGA产生测试信号,使用quartus Ⅱ软件的NCO ip核可以产生不同频率的正弦波信号,下面以1KHz正弦波为例:
相位累加器为32位宽,角度分辨率位宽16位,幅度位宽10位,输入时钟为板载时钟50M,输出1KHz的正弦信号,完成配置后生成ip核,实例化后产生1Khz的正弦波信号,代码如下:
wire signed [9:0] sin_1k;
nco nco_inst1
(
.out_valid(),
.fsin_o(sin_1k),
.phi_inc_i(32'd85_899),
.reset_n(1'b1),
.clken(1'b1),
.clk(sys_clk)
);
生成的波形如下:
3、verilog代码中采用全串行结构设计FIR滤波器,由于matlab设计出的FIR滤波器一定是线性相位结构,并且具有对称性,故可采用以下结构图设计滤波器:
采样率为40KHz,故数据输入频率为40KHz,则滤波器时钟CLK为1.28MHz,该时钟可通过由PLL ip核产生。由于滤波器系数的对称性,故只需要对32个滤波器系数与输入数据进行累加乘运算就可以实现滤波功能
为了实现该算法,首先需要生成一个乘法器ip核,完成乘法运算,配置如下:
该乘法器输入位宽分别为14bit和12bit,并且为有符号型,例化该乘法器,将加和数据与滤波器系数输入,获得一次乘法输出,代码如下:
mult mult_inst
(
.clock ( clk ),
.dataa (dataa),
.datab ( datab ),
.result ( Mout )
);
结构图所示的是一个15阶的全串行FIR滤波器结构,而我设计的滤波器为63阶,虽然阶数不同,但核心的算法相同。由于数据输入频率为40KHz,故滤波器时钟频率应该为40KHz * 32 = 1.28MHz,这样才能保证在输入一个新数据前,完成32次乘运算,也就是输出一个y(n)。具体算法如下:
首先,产生一个周期为32的计数器count,每一个CLK上升沿到来,count自增1,故每当count归0,相当于时间经过了40KHz。
verilog代码如下:
reg [4:0] count;
always@(posedge clk or negedge sys_rst)
if(sys_rst == 1'b0)
count <= 5'd0;
else
count <= count + 1'b1;
然后,将新输入的数据保存到寄存器中,这里采用了移位寄存器,保存64个对称的数据,verilog代码如下:
reg signed [12:0] xin_reg[0:63];
reg [5:0]i,j;
always@(posedge clk or negedge sys_rst)
if(sys_rst == 1'b0)
begin
for(i=0;i<63;i=i+1)
xin_reg[i] <= 13'd0;
end
else
begin
if(count == 5'd31)
begin
for(j=0;j<63;j=j+1)
xin_reg[j+1] <= xin_reg[j];
xin_reg[0] <= xin;
end
end
这里定义了64给个有符号的13位寄存器,分别存储输入的数据。
接着,完成两个对称数据相加,例如:x(n)+x(n-63),… ,并且将其结果送入乘法器,同时乘法器另一个乘数端口送入对应的滤波器系数,例如h(0),… ,由于代码过长,这里只展示部分,其他同理,verilog代码如下:
always@(posedge clk or negedge sys_rst)
if(sys_rst == 1'b0)
begin
adda <= 13'd0;
addb <= 13'd0;
datab <= 12'd0;
end
else
begin
if(count == 5'd0)
begin
adda <= {xin_reg[0][12],xin_reg[0]};
addb <= {xin_reg[63][12],xin_reg[63]};
datab <= -12'd74;
end
…
…
…
end
assign dataa = adda + addb;
至此,完成了一次乘法器的输出,而滤波器的输出是由32个类似的输出累加得到,由于乘法器的输入位宽分别为14bit和12bit,故输出位宽为26bit,对32个输出累加的话,需要扩展5位,故滤波器输出为31bit的有符号数,verilog代码如下:
reg signed [30:0] sum;
reg signed [30:0] Yout;
always@(posedge clk or negedge sys_rst)
if(sys_rst == 1'b0)
begin
sum <= 31'd0;
Yout <= 31'd0;
end
else
if(count == 5'd2)
begin
Yout <= sum;
sum <= Mout;
end
else
sum <= sum + Mout;
assign out = Yout;
需要说明的是,因为在count == 0时,给两个加数赋值和给滤波器系数赋值,而真正获得新的数据是在count == 1时,故在count == 1时将两个乘数送入乘法器,而我配置的乘法器有一级流水线,所以乘法器输出在count == 2的时候,通过以上分析,可以明白为什么在count == 2时才会将乘法器输出Mout送给sum,而其他时刻是进行累加运算,至于何时将累加和送给输出呢?同理,由于count的周期为32,所以从count == 2 到count == 31 到 count == 0 ,再到count == 1,刚好完成一个周期,也就是32次累加乘运算,所以在count == 2时(注意D触发器的延时,当count == 2时,此时的sum为count == 1时的sum加上count == 1时的Mout,所以仍然是32个数据的求和,而不是33个数据之和),将滤波数据送出,应该特别注意上面括号中的分析,可以对比波形理解触发器的延时特性。
最后,编写板载测试文件,将滤波器的输出(有符号的31位数据)转换为DA模块的8位无符号DA数据,然后将DA模块时钟和8位数据送到模块中,完成板载输出测试,verilog代码如下:
module boardtst
(
input wire sys_clk ,
input wire sys_rst ,
output wire [7:0] DA ,
output wire DA_clk
);
wire signed [30:0] yout;
fullserial_filter u3
(
.clk (clk_1p28M) ,
.sys_rst (sys_rst) ,
.xin (sin_wave) ,
.out (yout)
);
assign DA = yout[30:23] + 8'd128;
assign DA_clk = sys_clk;
这里要注意的是有符号数据如何转化成无符号数据,因为滤波器输出为31位的有符号输出,而DA模块接收的是8位的无符号型,故应该截取高8位,这相当于将数据右移了23位,然后再加上128,于是完成有符号到无符号的转换。
下面是测试信号与滤波后的信号:
测试信号:
滤波后输出信号:
可以看到,虽然将3KHz的信号分离了出来,但波形并不平滑,这是由于采样速率和转换频率较低造成的。
需要补充的是,滤波器系数的量化位宽会影响到滤波器的性能,通过matlab仿真观察:
可以看出,与未量化相比,8bit量化已经很好地满足了滤波器的性能要求,故使用FPGA设计该滤波器时,将乘法器的输入设置成14bit与8bit就已经足够了。
Matlab代码如下:
b8 = round(b/max(abs(b))*(2^7-1));
b12 = round(b/max(abs(b))*(2^11-1));
b14 = round(b/max(abs(b))*(2^13-1));
pm = 20*log10(abs(fft(b,1024)));pm = pm - max(pm);
pm8 = 20*log10(abs(fft(b8,1024)));pm8 = pm8 - max(pm8);
pm12=20*log10(abs(fft(b12,1024)));pm12=pm12-max(pm12;
pm14=20*log10(abs(fft(b14,1024)));pm14=pm14-max(pm14;
x_f = [0:fs/length(pm):fs/2];
mfpm = pm(1:length(x_f));
mfpm8 = pm8(1:length(x_f));
mfpm12 = pm12(1:length(x_f));
mfpm14 = pm14(1:length(x_f));
plot(x_f,mfpm,'-',x_f,mfpm8,'.-',x_f,mfpm12,'x',x_f,mfpm14,'--');
xlabel('hz');ylabel('db');
legend('未量化','8比特量化','12比特量化','14比特量化');
grid;
至此,完成了设计目标,但该实验仍有很多不足,敬请大家批评指正!
参考文献:
[1] 夏宇闻.Verilog数字系统设计教程(第三版).北京:北京航空航天大学出版社,2013
[2] 杜勇.数字滤波器的MATLAB与FPGA实现——Altera/Verilog版(第二版).北京:电子工业出版社,2019