IIR数字滤波器在很多领域中都有着广阔的应用。与FIR数字滤波器相比,IIR数字滤波器可以用较低的阶数获得较高的选择性,而且所用存储单元少。经济效率高。一个N阶IIR数字滤波器的系统函数为: 其线性常系数差分方程为: 用FPGA实现滤波的基本思想就是基于式(2)来实现的。如果知道了系统的输入序列(滤波器的输入),那么,只要根据所给的滤波器的指标,然后通过MATLAB仿真出系数矢量b和a,再采用递推算法求解差分方程,就能求出输出序列(滤波器的输出)。 1 滤波器的MATLAB设计 由于本文采用巴特沃斯滤波器,故需要在工具箱中调用的两个函数buttord和butter的调用格式为: [N,WC]=buttord(wp,ws,Rp,As) [b,a] =butter(N,WC) 其中N为滤波器阶数:wp和ws分别为通带截止频率矢量和阻带截止频率矢量,单位为π,一般需要模拟频率指标对采样频率的一半作归一化;Rp和As分别为通带最大衰减和阻带最小衰减,单位dB;wc为3 dB边缘频率矢量;b和a即为方程(2)中的系数矢量。 获得系数b和a之后,调用函数freqz(b,a,k,Fs)即可按照下式计算k点的复频率响应矢量H: 然后便可绘出k点的幅频和相频特性曲线,以用于检查计算出的系数是否满足所需要的滤波器指标。 2 编写VHDL语言代码注意事项 乘加运算过程中的数据是有符号的二进制补码。通常在Xilinx ISE集成开发环境下建立的VHDL源文件头部都会有“use IEEE.STD_LOGIC_UNSIGNED.ALL;”。如将其改为“use IEEE.STD_LOGIC_SIGNED.ALL;” 即应该包含有符号数运算程序包。这样就能保证代码中的所有std_logic_vector型数据按照有符号二进制补码的规则进行运算。 由于FPGA内部不能表示浮点数,因此只能用有限精度方法来实现数据的运算,即用数据(包括方程(2)的输入输出和系数)的整数部分(截去小数部分)作近似运算,且需要std_logic_vector数据类型来表示数据整数部分的二进制补码形式,但这样会产生截断误差。为了减小截断误差。应该将数据扩大适当的倍数(通常是倍, 为正整数), 以使小数部分可以忽略不计。扩大的倍数越大,截断误差就越小。得到的数据就越精确,但是。用来表示数据整数部分的std_logic_vector型数据长度会越大,这样就会占用越多的FPGA内部资源。因此。适当的选择数据扩大倍数是个关键。此外。各种数据转换为std_logic_vector型数据的长度选取至少应足以表示二进制补码(包括符号位)。若FPGA内部资源充足,可以通过增加std_logic_vector型数据长度来减小截断误差,提高运算精度。 通常由MATLAB仿真得到的系数b都远小于1。因此要适当选择正整数L。运算时可给系数b和n 的第一个系数除外)同乘以2L,之后取整得到B=round(b*2L)和A=round(a*2L)<round() 函数并对其实数四舍五入。然后将其带入函数freqz(),即在MATLAB中仿真频率特性是否满足既定指标,若不满足则增大L。若满足则将B和A(系数A (1)指定为1不变)化为二进制补码, 以使其作为VHDL代码中std_logic_vector型的con stant常量数据。 当前时刻输入的x(n)有时可能太小,为减小截断误差,应该选择适当的整数 ,以给x(n)乘以2M,即给表示当前时刻输入的std_logic_vector变量后补上M个‘0’。这样。得到的当前时刻输出y(n)就是扩大了2M倍的数据,应该除以2L+M才是当前时刻的真实输出。而VHDL语言不支持除法运算,故应采用截去末尾L+M位的方法来近似除法运算。这种做法相当于原始输出y除以2L+M后截去小数部分。 在用示波器观测时。滤波器的输出波形可能带有许多大幅度尖锐毛刺。从而严重影响了滤波器的性能。毛刺是由于组合电路的竞争而使电路输出发生瞬时错误的现象。通常消除毛刺的方法是在具体的电路中加个锁存器。本文采取另一优化方法。即在源代码中通过符号“<=”把输出信号赋给一个中间信号,再把中间信号作为输出。这相当于将信号作一个延时再输出。这种方法不需要知道具体的电路结构。也无需编写其它代码模块,因此优化更为简便快捷。而且优化效果非常好。 3 滤波器MATLAB设计的FPGA实现 下面以一个简单的低通滤波器设计实例来说明从MATLAB设计到FPGA实现的整个过程。该低通滤波器的系统采样频率为40 MHz。通带截止频率为1 MHz,阻带截止频率为5 MHz,通带内最大衰减为3 dB,阻带内最小衰减为40 dB,而对相位不作要求。 而其硬件平台上的主要器件有Xilinx公司的Spartan2E系列30万门FPGA芯片XC2S300E及PROM器件XCl8V04。模数转换芯片则采用AD公司的AD9218,数模转换芯片选用AD公司的AD9765,另外,还有40 MHz晶振等。其系统框图如图1所示。 图1 系统结构原理框图 3.1 MATLAB设计 MATLAB设计的具体代码如下: Fs=40; /采样频率单位MHz wp=1*2/Fs; /通带截止频率单位P1 ws=5*2/Fs; /阻带截止频率单位PI Rp=3; /通带内最大衰减单位dB As=40; /阻带内最小衰减单位dB [N,WC]=buttord(wp,ws,Rp,As)/N为滤波器阶数 [b,a]=butter(N,wc)/得到差分方程系数矢量 B=round(b*2^12) A=round(a*2^12) /系数乘以212后取整 figure(1) freqz(B,A,2000,Vs); /绘出2000点频率特性曲线 这样。在运行之后。便可得到: N=3 b=0.0006 0.0018 0.0018 0.0006 a=1.0000 -2.6444 2.3493 -0.7001 B=2 7 7 2 A =4096 -10832 9623 -2868 图2是由系数B和A绘出的幅频特性曲线。 下面是递推算法的MATLAB描述: t=0:1/Fs:5-1/Fs;/时间单位U8 l=length(t); for i=1:l y=B(1)*x0+B(2)*x1+B(3)*x2+B(4) *x3-A(2)*y1-A(3)*y2-A(4)*y3; x3=x2; /这里y对应的系数A(1)指定为1 x2=x1; x1=x0; x0=x(i)*2^8; /输入扩大2s倍 y3=y2; y2=y1; y1=floor(y/2^12);/函数floor()截去小数部分 y_out(i)=floor(y/2^20);/y除以220后截去小数部分 end 若以输入分别为0.5 MHz、3 MHz、6 MHz的正弦波来测试滤波器输出,则可得出如图3所示的仿真结果。可见,该系数B和A可以满足低通滤波器的技术指标。 图3 滤波器的仿真输出
3.2 VHDL代码顶层模块 图4是该MATLAB设计的顶层模块“top_level” 的示意图。 图中,Gclk为FPGA全局时钟输入(来自40MHz晶振),AD9218clk和AD9765clk是由Gclk直通送往AD9218和AD9765的驱动时钟:AD9218data_out(9:0)是来自AD9218的10位滤波器输入信号,设计时可与核心模块“lpF”的输入data_in(9:0)相连;AD9765data_in(9:0)是送往AD9765的10位滤波器输出信号.可与核心模块“lpf”的输出data_out(9:0)相连。 图5给出了顶层模块的FPGA资源占用情况,由图5可见,该系统的资源占用率非常少。 图5 顶层模块的FPGA资源占用情况
3.3 VHDL代码核心模块 图6所示为用于信号处理的核心模块“lpf”。 在核心模块VHDL代码编写时应当注意语句“ use IEEE.STD_LOGIC_SIGNED.ALL;”,并使用有符号数运算程序包。另外,在将设计好的整数系数B和A转换为二进制补码时,为方便起见,可使用程序包STD_LOGIC_SIGNED.vhd中的类型转换运算符CONV_STD_LOGIC_VECTOR()来接收整数和转换后的长度等两个参数,然后返回STD_LOGIC_VECTOR型。 本系统的代码结构体architecture采用行为描述方式。它类似于高级语言,其优点在于只需描述清楚输入与输出的行为。而无需花费更多的时间和精力关注设计功能的门级实现。因为这些完全可以由EDA工具综合生成,因而可大大缩短开发设计的时间。 核心模块“lpf”的VHDL语言源代码如下: library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_SIGNED.ALL; entity lpf is --实体和端口说明 Port(data_in:in std_logic_vector(9 downto 0);--滤波器数据输入 data_out:out std_logic_vector(9 downto 0):= (others=>'0';--数据输出 clk_in:in std_logic);--40 MHz时钟输入 end lpf; architecture Behavioral of lpf is --结构体说明,以下是系数B和A的类型转换 constant b0:std_logic_vector(14 downto 0):= conv std_logic_vector(2,15); constant bl: std_logic_vector(14 downto 0) := conv_std_logic_vector(7,15); constant b2:std_logic_vector(14 downto 0):= conv_std_logic_vector(7,15); constant b3:std_logic_vector(14 downto 0):= conv_std_logic_vector(2,15); constant a1:std_logic_vector(14 downto 0):= conv_std_logic_vector(-10832,15); constant a2:std_logic_vector(14 downto 0):= conv_std_logic_vector(9623,15); constant a3:std_logic_vector(14 downto 0):= conv_std_logic_vector(-2868,15); begin process(clk_in) --以下设所有变量初值为0 variable x0:std_logic_vector(18 downto 0) :=(others=>'0');--当前时刻输入 variable x1:std_logic_vector(18 downto 0) :=(others=>'0'); variable x2:std_logic_vector(18 downto 0) :=(others=>"o3); variable x3:std_logic_vector(18 downto 0) (others=>'0'); variable y1:std_logic_vector(18 downto 0) :=(others=>'0'); variable y2:std_logic_vector(18 downto 0) :=(others=>'0'); variable y3:std_logic_vector(18 downto 0) :=(others=>'0'); variable y:std_logic_vector(33 downto 0):= (others=>'0'); begin --进程中语句顺序执行 if clk_in'event and clk_in='1' then y:=b0*x+b1*x1+b2*x2+b3*x3+a1*y1 -a2*y2-a3*y3; x3:=x2; x2:=xl; xl:=x0; x0:=data_in(9)&data_in&b"0000_0000"; --当前输入扩大倍并保持符号不变 y3:=y2; y2:=y1; y1:=y(30 downto 12);--中间变量截取 为19位(10位输入+8位扩展+1位符号) data_out<=y(30 downto 21);--得到滤波器10位输出 end if; end process; end Behavioral; 在modelsim上对本核心模块进行仿真及代码优化时,其测试激励仍然可以分别选用0.5 MHz、3 MHz和6 MHz的正弦波。产生的方法有两种:一是采用ISE中集成的测试激励生成器HDL Bencher新建Test bench waveform型文件.并从中输入一周期正弦数据;二是将正弦数据存为文本文件,然后以TEXTIO方式读取。限于篇幅,具体操作这里不作介绍。 这种行为仿真(Simulate Behavioral Mode1)的波形与图3相同,可见,本模块源代码在功能上完全正确。但时序仿真(布局布线后仿真Simulate Post-Place&Route VHDL Model)的滤波器输出波形中的大幅度尖锐毛刺严重影响了滤波器的性能。消除毛刺的具体做法是将进程中的最后一条信号赋值语句改为: data_out_temp<=y(30 downto 21); data_out<=data_out_temp; 经过上述赋值语句的修改,再经优化之后进行时序仿真以及后面的硬件验证,就会发现,滤波器输出中的毛刺全部被消除,波形平滑,可见优化效果非常好。
3.4 硬件平台的验证 将该设计方案在硬件平台上进行验证时,先给硬件平台加电,再将程序通过JTAG线下载到PROM中.然后给AD9218数据输入端加正弦波信号,示波器CH1和CH2探针分别搭在AD9218数据输入端和AD9765数据输出端。这样,当信号在0.7 MHz频率以下变化时,两个通道的正弦波形相同,只有相位上有一点差别;当信号从0.7~1MHz频率范围变化时,通道CH2波形幅度有微弱减小;当信号从1~5 MHz频率范围变化时,通道CH2波形迅速衰减为一条水平线。当CH1端分别加0.5 MHz、3 MHz和6 MHz的正弦波时,两通道显示的波形相同。 4 一般IIR数字滤波器的快捷实现 现在总结一下一般IIR数字滤波器的设计及实现方案。 (1)仿真系数 根据所定技术指标通过MATLAB计算出原始系数矢量b和a,然后选择适当的扩大倍数,并将系数扩大后取整得到B和A,再根据B和A仿真差分方程递推算法(注意函数floor()用来仿真VHDL代码中std_logic_vector型数据截去末尾几位,以仿真除法运算)以及频域和时域波形最终确定系数B和A (当前时刻输出所对应的系数A(1)=1)。 (2)写VHDL代码 除了当前时刻所输出的所对应系数A(1)外,还应当将所有系数都转换为std_logic_vector型常量,同时,还要使初始化程序中所有时刻的输入输出变量都为0。然后再在进程process中写递推算法代码。 (3)Modelsim仿真 用行为级仿真可检查所写代码在功能上是否正确。时序仿真则用于观察布局布线后滤波器的输出波形。 (4)验证 将程序下载到硬件平台上作最终验证时,滤波器的输出不一定都有毛刺。但若输出有毛刺,则应将代码中最后的数据输出信号赋值给一中间信号再输出。 5 结束语 本文以低通滤波器为例,描述了IIR数字滤波器从MATLAB设计到FPGA实现的整个过程,讨论了设计中遇到的一些关键性问题,并在MATLAB及modelsim上作了不同层次的仿真,同时在硬件平台上最终验证了滤波器设计的技术指标。基于行为描述方式的递推算法虽然不是最节省FPGA内部资源的算法,但其优点是算法简单清晰,代码简短,可大大节省滤波器设计时间,如果熟练的话,通常十几分钟就可以完成一个满足性能指标的滤波器的设计;此外本设计还有一个特点,就是该算法仅在一个时钟周期内就可以做完一次对输入数据的滤波处理,并得到一个输出。所以,这种滤波算法对那些高频高采样率的输入信号非常有效。 目前,用这一方法设计的带通滤波器已经在LuolanC远程无线导航接收机的前端数字信号处理单元中用于滤除带外噪声,其设计的低通滤波器也在其它一些导航系统中用于信号解调。
|