目录
简介... 3
滤波器简介... 3
FIR数字滤波器简介... 3
设计说明... 4
设计任务... 5
分工... 5
详细设计:... 6
执行模块设计... 6
MATLAB部分:... 6
FPGA部分:... 10
参数量化... 10
确定编码... 10
输入输出定义... 10
电路结构... 11
流水线结构简介:... 11
数据流动模型:... 11
加法器和乘法器:... 12
流水线的基本单元... 13
测试模块设计... 15
数据对比验证:... 15
算法正确性验证... 16
分析及实验总结:... 18
附录... 19
Verilog:... 19
MATLAB. 27
C++. 27
简介
滤波器简介
滤波器用最简单的话来说,就是是对输入信号进行处理,得到我们想要的信号。
从现实应用来说,一维角度它可以应用于比如语音处理,把一段语音当中的高频率衰减,使我们听到更平和的声音。二维角度,他可应用于图像处理,例如图像去噪等等
按照功能分类,大致可以分为高通滤波器,低通滤波器,带通滤波器等等。
本次设计的低通滤波器的作用是允许低频率信号通过,衰减高频率信号。
从处理信号的角度可分为模拟滤波器和数字滤波器,对模拟信号进行处理的是模拟滤波器,对离散信号进行处理的是数字滤波器。从数学角度来说,本质去别是,数字滤波器是通过运算电路对离散信号进行运算得到新的信号。而模拟滤波器的是通过滤波电路对原信号进行处理。
FIR数字滤波器简介
FIR数字滤波器,全称为有限冲击响应滤波器,是最简单的数字滤波器。
有限冲击响应:即数字滤波器的响应系数序列,由于它的结构,一般也叫抽头系数序列,有限说明它的序列系数是有限的。通过对这个系数序列做DFT运算,就得到了这个数字滤波器的功能特征。因为时域的卷积等于频率内做乘法,即这个系数序列做DFT运算是从频谱的角度来看,它对输入信号频率的处理程度。
从数学的角度来说,它的本质功能其实就是对响应和输入信号进行卷积运算,得到新的信号。
示例为一个5抽头fir滤波器的结构图:
设计说明
数字滤波器是一种很常见的数字电路。为何要联合MATLAB?原因主要有以下几个:
抽头系数产生方便:数字滤波器一个很关键的问题就是抽头系数。如果我用纯FPGA实现,我必须要根据滤波器的频率响应设计一个逆DFT运算电路来产生抽头系数。这个电路实现非常的复杂,所以采用MATLAB软件来产生抽头系数。
原始信号数据产生方便:FIR滤波器所需要处理的信号是按照一定采样频率的正弦信号。这其中涉及一个DDS电路。这个电路实现非常的复杂,所以采用MATLAB软件来产生原始信号数据。
方便验证:如何知道设计的数字电路是否能够得到正确的输出?简单的方法就是让MATLAB软件和FPGA电路分别处理相同的原始信号数据,比对误差。
补充说明:本次设计的滤波器远远达不到实际运用的要求,再三权衡下设计均为最简单的方法,并不强调性能,所以滤波器的阶数不高,数据的位宽也很低,重在体现fir数字滤波器电路的设计方法。而且阶数低,位宽低,非常方便验证数字电路的正确性。
设计任务
本次的目标是联合MATLAB和FPGA设计一个最简单的fir的低通滤波器并验证其的正确性。
原始数据:
以产生1个采样频率为100hz,原sin信号为频率5hz相位为0的原始信号作为原始数据。
FIR参数:
采样频率Fs=100Hz
通带频率Fpass=10Hz
阻带频率Fstop=20Hz
阶数:15阶,即有16个抽头。
系数位宽:16位。(因为你要对应到具体数字电路上,系数必须量化,而且最好采用定点型方便fpga实现)
分工
MATLAB主要负责产生滤波器系数,产生原始输入信号数据。
FPGA上主要实现卷积运算。
证明
如果我们能证明5HZ正弦信号,在经过滤波器的卷积运算后,只是每个信号简单的相同比例放大,即说明没有衰减。因为信号之间的相对大小是不变的。
详细设计:
执行模块设计
MATLAB部分:
抽头系数产生
我采用的是MATLAB内置的工具FDATOOL。
设计出来后的频率响应如图:
可以看到10HZ前几乎没有衰减,在20HZ后进入旁瓣,符合设计目标。
系数产生:这里要注意一个问题是MATLAB产生的抽头系数你要调成定点型。其实MATLAB和赛灵思公司有合作,可以直接产生COE文件(COE是一个XILINX公司专用的文件格式),我产生的系数如下(可以看到位宽位16位):
原测试数据产生
一共分为四个个步骤,采样,浮点定点转换,放大,进制转换。我们以产生1个采样频率为100hz,原sin信号为频率5hz相位为0幅度为1,采样两个周期为例说明。
原信号:
频率为5hz,200ms一个周期,故400ms应该共有两个周期。
采样:
采样频率100,即1s采样100个点,400ms内应采样40个点,再算上0点有41个点与图吻合。
量化转换:
为何要量化,这与计算机的结构有关。计算机里面有FPU可以很好的实现浮点运算。所以我们采集的数据是浮点类型的。但是从FPGA内部的角度来说,实现浮点运算是一件非常困难的事情。所以我们要进行浮点数与定点数的转换。这采用了MATLAB内置的量化函数quantizer()和quantize()。转换后为定点小数。定点小数你就得确定多少位是整数位,多少位是符号位。我的转化方案是整数位1位,小数位有15位,共计16位。
放大:
为何要对采样后的数据放大?这也是为了方便实现做出的权衡。我们的目的就是把上一步转换后得到的定点小数是整数运算容易还是小数运算容易?我想对fpga来说显然是整数容易,对于整数来说加法和减法都只需要求补运算。那么如何进行定点小数与整数的转换。这其实非常简单了,方法是算术左移。你只需要保持最高位符号位不变。一个有效的方法是乘以2的N次方,这里我选择了乘以2的15次方,即算术左移15位,本质是把小数位全部移到整数位。
放大后的输入信号数据:
进制转换:
其实当我们把信号放大完后理论上产生输入信号的数据已经我们的工作已经完成了。但遗憾的是还包含最后一步。在FPGA里面是不认识十进制的,FPGA的仿真文件只认识二进制和十六进制。即使你的十进制数字确实是固定位宽位16的2进制数的表达。所以你要做的最后一件事,是吧放大后的信号数据变为2进制或十六进制并写入文件。MATLAB提供了内置的转换函数NUM2HEX(),我转换为了十六进制。转换后如下:
FPGA部分:
在得到系数后就可以开始FPGA实现了。要很清楚在FPGA部分我们要完成的只有两件事,卷积运算实现和仿真。卷积运算其实就是一条数学公式 ,期中h()序列为我们的抽头系数序列,x序列为我们的输入信号序列。y()为我们的输出信号序列。N为我们的滤波器阶数。我们实现它为15阶。 。
参数量化
用MATLAB联合FPGA设计滤波器首先问题就是参数量化问题,我们要实现的数学公式如下 。
我们知道在数字系统中,一切运算都是量化的。所以第一步就是要定义公式中信号的位宽。位宽是一个可以体现精度的概念。通用计算机都是以8的倍数为单位的。所以h()序列的宽度为16位。输入信号x()的位宽也为16位。因为输出信号y()为h()和x()的卷积,所以它的位宽为35位;为何为35位?根据上面的公式y()的每一个输出为15个x()·h()的累加。我们先不看符号位。x()·h()为1个30位的数字。15个这样的数字相加,因为2的4次方为16,所以最大的输出是会小于30位的16倍,即再多4位,再加上一位符号位,所以我设置y()的输出位35位,理论上35位其实就够了,但是现在你要与任何设备通信位宽一般都是8的2次幂倍数,所以y()我最后会符号扩展为64位。
确定编码
这是一个不可避开的问题,卷积运算说白了就是加法和乘法的组合。但是从底层硬件上来说加法和乘法的硬件实现根据编码的表示方式而不同。在FPGA上面我采用的是补码。即数是有符号的。
输入输出定义
我们定义的输入信号有位宽为16的输入序列信号,时钟信号,复位信号,还有输出信号为63位。
电路结构
对于实现这个卷积运算,我采取的是流水线结构的。流水线的电路RTL级转换后大致如下
流水线结构简介:
什么叫流水线结构,其实就是移位寄存器的高位别版本。我们使用一个16位的寄存器。回顾一下下图这个结构 。这个图很像我设计的实际的结构。
数据流动模型:
15阶就是有15拍的延迟的意思。假设x是我们的输入信号,我们在每个时钟的上升沿,使信号流向下一个D触发器,使信号向左流动一拍。即从最右端流入,最左端流出。即每个信号15个周期后进入最左端。 。
为此我们需要定义内部寄存器变量 。对应代码如下。
加法器和乘法器:
基于模块化的设计,我设计了两个加法器模块和乘法器模块,它们的定义非常简单,在这里先标注你要先注意位宽。它们对应如下:
流水线的基本单元
每一级的流水线单元是类似的。对应的电路如图。
我们先解析乘法器mul的输入发现它一个输入信号是接地的。为何接地呢。还记得吗?我们的卷积运算 。h()抽头序列的系数应该是已知的。那么就直接在FPGA内配置它为固定变量。
乘法器的固定系数那段就是接的h()抽头序列的系数。还有一段接的就是我们的D触发器。也就是我们流水线上的数据。接着我们再来看加法器。
以add1为例子它的两个输入分别是第一个乘法器和第二个乘法器的输出。
再来看看add2,他的输入为上一步的add1的输出和mul3,mul3为卷积乘法的第三项。
注意事项
这里为了防止数据溢出一定要进行--符号扩展。
测试模块设计
以下的仿真都是电路综合后的行为仿真。
数据对比验证:
这是第一步,你必须保证你MATLAB产生的原始数据能顺利传送到FPGA仿真文件上。
我们的MATLAB上的原始数据为
我们在仿真testbench时用memory来读取它仿真得到数据如下:
为了方便观察,我们调用$write格式化输出到屏幕上。
对照MATLAB得波形和坐标验证为原始数据正确。
算法正确性验证
你要特别注意 我们要得到41个输出需要的不仅仅需要41个采样样本而是56个,你要在前面补15个为0的输入。
Fpga仿真数据
以下仿真为综合后的行为仿真。我们在仿真testbench时仿真得到数据如下为了方便观察,我们调用$write格式化输出到屏幕上。
软件数据
因为MATLAB对底层运算不是很方便。所以我用C++实现了卷积运算。在C++层面上其实就是一个二重循环的事情,对应代码如下
得到的数据为:
分析及实验总结:
最后对比分析,发现软件产生的数据和FPGA上得到的数据一致。证明FPGA上的卷积运算正确。由数据分析得到的数据确实是离散的正弦信号。我们现在来对比输入和输出。
输入:
输出:
证明:
一个好的方法是从输出反推输入证明我们设计的滤波器只是对正弦信号简单的放大。并没有衰减。正确算法是 。即拿输出的第一个非0数据除以输入的第一个非0数据。得到放大比例。之后拿所有输出除以这个比例得到反推数据。反推数据如下。
它和原始数据一致,证明我们的滤波器只是简单的放大,设计成功。
总的来说,硬件实现算法时,底层的编码,时序是一个非常有难度且必要的问题。
附录
Verilog:
源文件
module lowpass_filter
(
clk,
reset,
filter_in,
filter_out,
);
input clk;
input reset;
input signed [15:0] filter_in;
output signed [63:0] filter_out;
parameter signed [15:0] coeff1 = 16'b0000010001000001;
parameter signed [15:0] coeff2 = 16'b1111111000110111;
parameter signed [15:0] coeff3 = 16'b1111011101001001;
parameter signed [15:0] coeff4 = 16'b1111001110010011;
parameter signed [15:0] coeff5 = 16'b1111110001010100;
parameter signed [15:0] coeff6 = 16'b0001010010011011;
parameter signed [15:0] coeff7 = 16'b0011001111110111;
parameter signed [15:0] coeff8 = 16'b0100101000100010;
parameter signed [15:0] coeff9 = 16'b0100101000100010;
parameter signed [15:0] coeff10 = 16'b0011001111110111;
parameter signed [15:0] coeff11 = 16'b0001010010011011;
parameter signed [15:0] coeff12 = 16'b1111110001010100;
parameter signed [15:0] coeff13 = 16'b1111001110010011;
parameter signed [15:0] coeff14 = 16'b1111011101001001;
parameter signed [15:0] coeff15 = 16'b1111111000110111;
parameter signed [15:0] coeff16 = 16'b0000010001000001;
reg signed [15:0] delay_pipeline [0:15] ;
wire signed [31:0] product1;
wire signed [31:0] product2;
wire signed [31:0] product3;
wire signed [31:0] product4;
wire signed [31:0] product5;
wire signed [31:0] product6;
wire signed [31:0] product7;
wire signed [31:0] product8;
wire signed [31:0] product9;
wire signed [31:0] product10;
wire signed [31:0] product11;
wire signed [31:0] product12;
wire signed [31:0] product13;
wire signed [31:0] product14;
wire signed [31:0] product15;
wire signed [31:0] product16;
wire signed [34:0] symbol_extension_product1;
wire signed [34:0] symbol_extension_product2;
wire signed [34:0] symbol_extension_product3;
wire signed [34:0] symbol_extension_product4;
wire signed [34:0] symbol_extension_product5;
wire signed [34:0] symbol_extension_product6;
wire signed [34:0] symbol_extension_product6;
wire signed [34:0] symbol_extension_product7;
wire signed [34:0] symbol_extension_product8;
wire signed [34:0] symbol_extension_product9;
wire signed [34:0] symbol_extension_product10;
wire signed [34:0] symbol_extension_product11;
wire signed [34:0] symbol_extension_product12;
wire signed [34:0] symbol_extension_product13;
wire signed [34:0] symbol_extension_product14;
wire signed [34:0] symbol_extension_product15;
wire signed [34:0] symbol_extension_product16;
wire signed [34:0] sum1;
wire signed [34:0] sum2;
wire signed [34:0] sum3;
wire signed [34:0] sum4;
wire signed [34:0] sum5;
wire signed [34:0] sum6;
wire signed [34:0] sum7;
wire signed [34:0] sum8;
wire signed [34:0] sum9;
wire signed [34:0] sum10;
wire signed [34:0] sum11;
wire signed [34:0] sum12;
wire signed [34:0] sum13;
wire signed [34:0] sum14;
wire signed [34:0] sum15;
/*reg signed [34:0] sum15_1;
reg signed [34:0] sum15_2;实际应用应该打两拍不过为了验证功能没有打*/
multiplier_sixteen mul1(.A(delay_pipeline[0]),.B(coeff1),.P(product1));
multiplier_sixteen mul2(.A(delay_pipeline[1]),.B(coeff2),.P(product2));
multiplier_sixteen mul3(.A(delay_pipeline[2]),.B(coeff3),.P(product3));
multiplier_sixteen mul4(.A(delay_pipeline[3]),.B(coeff4),.P(product4));
multiplier_sixteen mul5(.A(delay_pipeline[4]),.B(coeff5),.P(product5));
multiplier_sixteen mul6(.A(delay_pipeline[5]),.B(coeff6),.P(product6));
multiplier_sixteen mul7(.A(delay_pipeline[6]),.B(coeff7),.P(product7));
multiplier_sixteen mul8(.A(delay_pipeline[7]),.B(coeff8),.P(product8));
multiplier_sixteen mul9(.A(delay_pipeline[8]),.B(coeff9),.P(product9));
multiplier_sixteen mul10(.A(delay_pipeline[9]),.B(coeff10),.P(product10));
multiplier_sixteen mul11(.A(delay_pipeline[10]),.B(coeff11),.P(product11));
multiplier_sixteen mul12(.A(delay_pipeline[11]),.B(coeff12),.P(product12));
multiplier_sixteen mul13(.A(delay_pipeline[12]),.B(coeff13),.P(product13));
multiplier_sixteen mul14(.A(delay_pipeline[13]),.B(coeff14),.P(product14));
multiplier_sixteen mul15(.A(delay_pipeline[14]),.B(coeff15),.P(product15));
multiplier_sixteen mul16(.A(delay_pipeline[15]),.B(coeff16),.P(product16));
assign symbol_extension_product1=$signed ({{3{product1[31]}},product1});
assign symbol_extension_product2=$signed ({{3{product2[31]}},product2});
assign symbol_extension_product3=$signed ({{3{product3[31]}},product3});
assign symbol_extension_product4=$signed ({{3{product4[31]}},product4});
assign symbol_extension_product5=$signed ({{3{product5[31]}},product5});
assign symbol_extension_product6=$signed ({{3{product6[31]}},product6});
assign symbol_extension_product7=$signed ({{3{product7[31]}},product7});
assign symbol_extension_product8=$signed ({{3{product8[31]}},product8});
assign symbol_extension_product9=$signed ({{3{product9[31]}},product9});
assign symbol_extension_product10=$signed({{3{product10[31]}},product10});
assign symbol_extension_product11=$signed({{3{product11[31]}},product11});
assign symbol_extension_product12=$signed({{3{product12[31]}},product12});
assign symbol_extension_product13=$signed({{3{product13[31]}},product13});
assign symbol_extension_product14=$signed({{3{product14[31]}},product14});
assign symbol_extension_product15=$signed({{3{product15[31]}},product15});
assign symbol_extension_product16=$signed({{3{product16[31]}},product16});
adder_thirty_five add1(.A(symbol_extension_product1),.B(symbol_extension_product2),.S(sum1));
adder_thirty_five add2(.A(sum1),.B(symbol_extension_product3),.S(sum2));
adder_thirty_five add3(.A(sum2),.B(symbol_extension_product4),.S(sum3));
adder_thirty_five add4(.A(sum3),.B(symbol_extension_product5),.S(sum4));
adder_thirty_five add5(.A(sum4),.B(symbol_extension_product6),.S(sum5));
adder_thirty_five add6(.A(sum5),.B(symbol_extension_product7),.S(sum6));
adder_thirty_five add7(.A(sum6),.B(symbol_extension_product8),.S(sum7));
adder_thirty_five add8(.A(sum7),.B(symbol_extension_product9),.S(sum8));
adder_thirty_five add9(.A(sum8),.B(symbol_extension_product10),.S(sum9));
adder_thirty_five add10(.A(sum9),.B(symbol_extension_product11),.S(sum10));
adder_thirty_five add11(.A(sum10),.B(symbol_extension_product12),.S(sum11));
adder_thirty_five add12(.A(sum11),.B(symbol_extension_product13),.S(sum12));
adder_thirty_five add13(.A(sum12),.B(symbol_extension_product14),.S(sum13));
adder_thirty_five add14(.A(sum13),.B(symbol_extension_product15),.S(sum14));
adder_thirty_five add15(.A(sum14),.B(symbol_extension_product16),.S(sum15));
always @(posedge clk, posedge reset)
if(reset==1'b1)begin
delay_pipeline[15] <= 0;
delay_pipeline[14] <= 0;
delay_pipeline[13] <= 0;
delay_pipeline[12] <= 0;
delay_pipeline[11] <= 0;
delay_pipeline[10] <= 0;
delay_pipeline[9] <= 0;
delay_pipeline[8] <= 0;
delay_pipeline[7] <= 0;
delay_pipeline[6] <= 0;
delay_pipeline[5] <= 0;
delay_pipeline[4] <= 0;
delay_pipeline[3] <= 0;
delay_pipeline[2] <= 0;
delay_pipeline[1] <= 0;
delay_pipeline[0] <= 0;
end
else begin
delay_pipeline[15] <= filter_in;
delay_pipeline[14] <= delay_pipeline[0];
delay_pipeline[13] <= delay_pipeline[1];
delay_pipeline[12] <= delay_pipeline[2];
delay_pipeline[11] <= delay_pipeline[3];
delay_pipeline[10] <= delay_pipeline[4];
delay_pipeline[9] <= delay_pipeline[5];
delay_pipeline[8] <= delay_pipeline[6];
delay_pipeline[7] <= delay_pipeline[7];
delay_pipeline[6] <= delay_pipeline[8];
delay_pipeline[5] <= delay_pipeline[9];
delay_pipeline[4] <= delay_pipeline[10];
delay_pipeline[3] <= delay_pipeline[11];
delay_pipeline[2] <= delay_pipeline[12];
delay_pipeline[1] <= delay_pipeline[13];
delay_pipeline[0] <= delay_pipeline[14];
end
/* always @(posedge clk)
begin
sum15_1<=sum15;
sum15_2<=sum15_1;
end实际应用应该打两拍不过为了验证功能没有打*/
assign filter_out = $signed({{29{sum15[34]}},sum15});
endmodule
测试文件
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: /07/24 16:51:33
// Design Name:
// Module Name: lowpass_filter_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module lowpass_filter_tb(
);
reg clk;
reg reset;
reg signed [15:0] memory[0:40];
wire signed [63:0] filter_out;
reg signed [15:0] filter_in;
reg[9:0] n;
lowpass_filter DUT(.clk(clk),.reset(reset),.filter_in(filter_in),.filter_out(filter_out));
initial begin
clk=0;
reset=0;
$readmemh("H:/matlab/five_hz_sin_data_in.txt",memory);
for(n=0;n<=56;n=n+1)
begin
#10
if(n<15)
filter_in=0;
if(n>15)//跳过开始的0
begin
filter_in=memory[n-15];
$write("%d ",filter_out);
end
if(((n-15)%7==0)&&(n>15))
$write("\n");
end
$write("%d ",filter_out);
end
always #5 clk=~clk;
endmodule
MATLAB
clear;
clc;
format long;
fs=100;
dt=1/fs; f=5;
t=0:dt:2*1/f;
y=sin(2*pi*f*t);
q = quantizer('fixed', 'Ceiling', 'Saturate', [16 0]);
z=y*32768;
test = num2hex(q,z);
f = fopen('H:\matlab\five_hz_sin_data_in.txt' , 'w');
for i=1:41
for j=1:4
fprintf(f ,'%s' , test(i,j));
end
fprintf(f ,'\r\n');
end
fclose(f);
figure(1);
stem(t.*1000,z);
title('·Å´óºóµÄÀëÉ¢5HzÕýÏÒÐźÅ');
axis([-inf,+inf,-32768,+32768]); xlabel('t/ms','FontName','ËÎÌå','FontWeight','normal','FontSize',14);
ylabel('ÐźÅÇ¿¶È','FontName','ËÎÌå','FontWeight','normal','FontSize',14);
C++
int main()
{
long long coffee[16];
coffee[0] = 0b0000010001000001;
coffee[1] = 0b1111111000110111;
coffee[2] = 0b1111011101001001;
coffee[3] = 0b1111001110010011;
coffee[4] = 0b1111110001010100;
coffee[5] = 0b0001010010011011;
coffee[6] = 0b0011001111110111;
coffee[7] = 0b0100101000100010;
coffee[8] = 0b0100101000100010;
coffee[9] = 0b0011001111110111;
coffee[10] = 0b0001010010011011;
coffee[11] = 0b1111110001010100;
coffee[12] = 0b1111001110010011;
coffee[13] = 0b1111011101001001;
coffee[14] = 0b1111111000110111;
coffee[15] = 0b0000010001000001;
long long sin_data[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10126, 19261, 26510, 31165, 32767, 31165, 26510, 19261, 10126, 1,
-10125,-19260,-26509,-31164,-32768,-31164,-26509,-19260,-10125, 0,
10126, 19261, 26510, 31165, 32767, 31165, 26510, 19261, 10126, 0,
-10125,-19260,-26509,-31164,-32768,-31164,-26509,-19260,-10125, 0};
long long out_data[41] = { 0 };
long long reset_data[41] = { 0 };//反推原始数据
for(int i=0;i<=40;++i)
for (int j = 0; j <=15; ++j)
{
out_data[i]=coffee[j] * sin_data[i + j];
}
/*for (int i = 0; i <= 40; ++i)
{
//std::cout << out_data[i] << " ";
printf("%d ", out_data[i]);
if (i % 8 == 0&&i!=0)
std::cout << '\n';
}*/
for (int i = 0; i <= 40; ++i)
{
reset_data[i] = out_data[i] / (out_data[1] / 10126);
printf("%d ", reset_data[i]);
if (i % 8 == 0 && i != 0)
std::cout << '\n';
}
}