马上注册,看完整文章,学更多FPGA知识。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
第十三章 FIR滤波器设计
本文档编号:000600000025
需要看对应的视频,请点击视频编号:002700000453
1、本文档讲述FPGA产生两路正弦波数据,一路直接由DA输出,一路经过FIR处理之后输出,然后传到示波器进行观察,从而了解FIR滤波器的效果
2、801开发板使用
第1节 项目背景
1.1 FIR和IIR滤波器
有限冲激响应(Finite Impulse Response, FIR)滤波器,又被称为非递归线性滤波器,是数字信号处理系统中最基本的元件。FIR滤波器的脉冲响应由有限个采样值构成,长度(抽头数)为N、阶数为N−1的FIR系统的转移函数、差分方程和单位冲激响应分别如下列三式所示。 无限冲激响应(Infinite Impulse Response,IIR)滤波器,又被称为递归线性滤波器。顾名思义,FIR和IIR是相对的,相比于IIR,FIR具备线性相位特性。这里解释一下线性相位的概念:如果滤波器的N个实值系数为对称或者反对称结构则该滤波器具有线性相位,即W(n)=±W(N−1−n)W(n)=±W(N−1−n)。此外,FIR更易于设计,但同样指标下FIR滤波器需要更多的参数,即实现时需要消耗更多的计算单元,因而会产生较大的延迟。
1.2 FIR滤波器的原理
FIR滤波器对信号的处理其实就是信号与FIR滤波器的系数进行卷积(即乘累加)的过程。以声音来举例来看一下滤波器的效果,回顾一下日常生活,在聊天时始终以一种音调说话令人感到舒适,但是如果使用了麦克风等收声设备对声音进行收集并且放大后,由于人的音调高低不平,通过麦克风放大后音调高的声音会变得刺耳。如果把音调转换成波形来描述可以得到如下图所示的波形,可以看出整个声音段比较平稳,但是由于声音的变化会出现异常高的两段波,在现实生活中这就代表着出现了两段特别刺耳的声音,即噪声。而滤波器的作用就是将一段波形中突出的部分弱化掉,因此此时就可以使用滤波器来将噪声弱化。
明白了滤波器的作用后来讨论一下具体的工作原理。有些读者认为在上述波形中直接将高出来的波减去一定数值就可以了。那么同学们思考一下如何将高出来的波筛选出来呢?是否可以将其按照固定的数值范围划分呢?答案是否定的。这里需要明确一个概念:不同电压值的波形振幅是不同的。依旧拿声音来举例,如果一个人的说话声音一直维持在40分贝左右,那么当他忽然尖叫声音变为了100分贝时可以试着定义80分贝以上的波需要进行过滤弱化。但是如果另外一个人的说话声音一直维持在85分贝以上,他没有进行忽然的尖叫,声音波形一直是平稳的状态。但根据80分贝以上的波需要进行过滤的定义,一直保持85分贝的这一声音就会被过滤掉。
因此在设计时无法规定一个固定范围,令滤波器将此范围的波形全都过滤掉。同时也可以得出结论:滤波器过滤的对象没有固定数值,而是将一段波中忽高峰值弱化,如果本身波形趋于平稳,波的峰值再高也不会进行滤除。
大家都知道平均数的原理,如果你有7个苹果而我有1个苹果,你拥有的苹果数量比我多出很多。但是将所有苹果放在一起,一人分4个,此时相当于我帮你承担了3个苹果后我们拥有的苹果数量就没有任何的差距了。下图所示的统计图可以更加直观的显示,原本你的苹果数量属于高峰值,我们二人均分之后我们的波形都处于平稳的状态了。同理可知,如果将一个波形中的每个相邻点都进行这样的平均就可以得到一个趋于平滑的波形。
滤波器分为高通、低通和带通等不同类型,即允许不同频率的信号通过滤波器。前文分析可知无法对频率进行临界值的定义,那么如何实现不同频率的值的过滤呢?此时可以采用将相邻两个点的值与过滤器模型值相乘后再进行平均的方法。滤波器的模型值由滤波器自身定义,根据不同的需求进行调整,有可能是一个很大的值,甚至可能是负数以达到过滤的效果。滤波器对信号的处理实际上就是信号与FIR滤波器的系数进行卷积(即乘累加)的过程。
下面通过计算一个简单信号模型来更加深入地了解FIR波形器的原理,本书会将三组信号进行对比,让同学们更加直观的感受到效果。
现在有三组信号,分别是:
信号1:低频信号,即在时域上变化慢的信号,其输入先后顺序为1 1 1 1 2 2 2 2。 信号2:直流信号,其输入先后顺序为1 1 1 1 1 1 1 。 信号3:高频信号,即在时域上变化快的信号,其输入先后顺序为1 2 1 2 1 2 1 2 。
滤波器选定为低通滤波器,其模型值为:1、1。
首先信号1与低通滤波器进行卷积运算,其结果再除以2,如下图所示,第一个信号数字“1”与第二个信号数字“1”和滤波器模型分别相乘求和后再除以2即(1*1+1*1)/2=1。随后每个相邻的信号依次进行同样的操作,处理后得到如下数据:1 1 1 1.5 2 2 2。可以看到低频信号经由低通滤波器处理后各个点仍然保持了其形状,而且在1变成2时变得平缓。
同理,信号2与低通滤波器进行卷积运算,其结果再除以2,如下图所示,第一个信号数字“1”与第二个信号数字“1”和滤波器模型分别相乘求和后再除以2即(1*1+1*1)/2=1。随后每个相邻的信号依次进行同样的操作,处理后得到如下数据:1 1 1 1 1 1 1。可以看到直流信号与输入的信号完全相同。
最后,信号3与低通滤波器进行卷积运算,其结果再除以2,如下图所示,第一个信号数字“1”与第二个信号数字“2”和滤波器模型分别相乘求和后再除以2即(1*1+2*1)/2=1.5。随后每个相邻的信号依次进行同样的操作,处理后得到如下数据:1.5 1.5 1.5 1.5 1.5 1.5 1.5。可以看到高频信号经由低通滤波器处理后已经完成消去了其原本形状,变成了直流信号。
如果此时滤波器模型选为高通滤波器:1 -1,结果又会如何呢?
信号1与高通滤波器进行卷积运算,其结果再除以2,如下图所示,第一个信号数字“1”与第二个信号数字“1”和滤波器模型分别相乘求和后再除以2即[1*1+1*(-1)]/2=0。随后每个相邻的信号依次进行同样的操作,处理后得到如下数据:0 0 0 -0.5 0 0 0。可以看到低频信号经由高通滤波器处理后信号变化基本上消失。
信号2与高通滤波器进行卷积运算,其结果再除以2,如下图所示,第一个信号数字“1”与第二个信号数字“1”和滤波器模型分别相乘求和后再除以2即[1*1+1*(-1)]/2=0。随后每个相邻的信号依次进行同样的操作,处理后得到如下数据:0 0 0 0 0 0 0。可以看到直流信号仍然没有任何变化。 图3.13-7信号2的高通滤波处理结果
信号3与高通滤波器进行卷积运算,其结果再除以2,如下图所示,第一个信号数字“1”与第二个信号数字“2”和滤波器模型分别相乘求和后再除以2即[1*1+2*(-1)]/2= - 0.5。随后每个相邻的信号依次进行同样的操作,处理后得到如下数据:-0.5 0.5 -0.5 0.5 -0.5 0.5 -0.5。可以看到高频信号仍然保持了其变化形态。 图3.13-8信号3的高通滤波处理结果
这里只选取了两个点进行滤波处理,如果按照同样的方法一次进行四、五个信号的卷积运算,滤波器处理后得到的波形会更加平滑。当滤波器的系数不同,抽头个数不同,得到的效果也会不同。
通过上面的案例,相信同学们都已经了解了FIR滤波器的工作原理,简而言之,FIR滤波器的信号处理过程就是信号与FIR滤波器的系数进行卷积(即乘累加)的过程。通过调整滤波器系数、抽头个数等参数可以实现低通、高通、带通等多种滤波器的设计。
1.3 FIR滤波器的设计
1.3.1 matlab产生滤波器系数
打开matlab,在其命令窗口输入fdatool按下回车。 图3.13-9matlab命令窗口输入fdatool
调出FIR滤波器的设计界面如下图所示,在此需要进行对应的参数设计,点击Design Filter即可计算出抽头系数。 图3.13-10MATLAB中FIR滤波器设计界面
在波形设计界面中需要关注以下几个选项:
Response Type:滤波器的类型选择,可以选择lowpass低通滤波器、Highpass高通滤波器、bandpass带通滤波器、bandstop带阻滤波器四个选项,在设计时根据需求进行选择;
Fs(采样频率):即采样速度或者采样率,其定义了每秒从连续信号中提取并组成离散信号的采样个数;
Fstop:信号截止频率
Fpass:信号通过率 Filter Order:设置滤波器的抽头个数设置,可以在specify order中输入个数,也可以选择Minimum order让系统计算满足要求前提下的最小抽头个数。
产生对应系数后点击“file”菜单里的“Export”将系数保存到工作区,如下图所示。
随后直接点击“Export”选项:
打开工作区里的“Num”: 图3.13-13在MATLAB显示滤波器系数文件
将下图第一行的数据复制粘贴到txt文件中: 图3.13-14在MATLAB打开滤波器文件
特别提示:复制后需在两个系数间插入英文状态下的逗号即可得到滤波器的系数,如下图所示: 图3.13-15复制系数到TXT文本
1.3.2 FPGA生成FIR IP核
打开工程后,在“IP catalog”界面中在“DSP”目录下选择“Filter”目录中的“FIR II”选项,如下图所示: 图3.13-16在IP Catalog中找到FIR IP核
在Fitter界面进行如下图所示的设置。
Filter Type:选择滤波器类型 Interpolation Factor:插值因子 Decimation Factor:抽取因子 Max Number of channels:设置通道个数; Clock rate:填写本IP核的工作时钟频率; clock slack:对采样时钟速率、质量的容忍程度,借由对数据的裕量来衡量; Input sample rate (msps):采样率;
点击“coefficients”进入“coefficients”界面后点击“import from file”,找出用matlab生成的系数文件,点击“import”将其导入。导入成功后可以看到“frequency response”界面的波形发生变化,如下图所示。
在coefficient界面也可以对系数的格式、数据位宽等参数进行设置。
在此界面不更改其它选项,按照系统默认进行设置,点击“finish”结束。至此完成了生成 FIR滤波器的主要步骤。
第2节 设计目标
按照至简设计法的设计特色,开始一个新的设计之前首先应明确设计目标。设计目标是整个设计的核心灵魂,后续的每个步骤与操作都是围绕设计目标进行展开的。至简设计法旨在让设计师在设计过程中按照最中的简单快捷的方式实现每个步骤和思路,明确设计目标正是让后面每个阶段的工作都有意义,而不去进行不必要的工程展开,这样一来可以少走很多弯路。对于初学者来说学习阶段养成好习惯可以使之后的工程师生涯受益无穷。所以再次强调在开始设计前一定要将设计目标分析透彻,认真思考本次设计最终想要实现什么目的,达到什么效果,然后再投入到设计中去。
本次设计使用采样率大于100M的双通道的示波器与FPGA开发板相连,其连接示意如下图所示,将示波器的两个通道分别与FPGA的DA通道1和DA通道2相连,从而观察两路DA的输出。
本设计需要FPGA内部产生正弦信号,该信号一路输出给DA通道1,另一路经过FIR滤波器后输出给DA通道2。正弦信号的频率约等于100KHz * (key+1),其受开发板上的3个拨码开关控制,可以用3位信号key表示,即一共可以产生8种频率。例如,当key等于0时可以产生频率约为100KHz的正弦信号;当key等于1时可以产生频率约为200KHz的正弦波;当key等于7时可以产生频率约为800KHz的正弦波。FIR滤波器是低通滤波器,其截止频率是500KHz,由此可见,原则上超过500KHz的信号就会被滤除。FIR滤波器实现架构如下图所示,其输出传送给通道2。
图3.13-21FIR滤波器实现架构
FPGA输出后示波器的显示效果如下所示,其中黄色(上面的波形)是通道1输出的信号,蓝色(下面的波形)是通道2的输出信号。下面带领同学们看一下实际产生的信号效果,想要观看完整版视频演示效果的同学可以登陆网址查看:www.mdy-edu.com/xxxx。
100KHz的信号图如下:
200KHz的信号图如下:
300KHz的信号图如下:
400KHz的信号图如下所示,可以看到已经有了明显的衰减:
500KHz的信号图如下所示,可以看到已经衰减了很多,波形变得非常小:
600KHz的信号图如下所示,可以看到通道2已经几乎没有波形: 700KHz的信号图如下所示,可以看到通道2也同样几乎没有波形:
800KHz的信号图如下所示,可以看到通道2完全趋于没有波形:
第3节 设计实现接下来就进入设计的实现阶段,本书会按照步骤和原理分析与读者分享案例的实现方法,考虑到初学者的需要,此部分的内容会比较详细。基础知识掌握得比较牢靠,只想学习此设计的步骤的情况下可以跳过此部分,后面章节有简化版的步骤分享。在此还是建议初学者不要选择捷径,一定按照详细分析的内容进行学习,只有掌握基础知识、打好基础,才可以从容的独立完成项目设计。
3.1 顶层接口
新建目录:D:\mdy_book\fir_prj。在该目录中,新建一个名为fir_prj.v的文件。用GVIM打开后开始编写代码。这里再次强调,建议初学者按照书中提供的文件路径以及文件名进行设置,避免后续跳出未知错误。
首先来确定顶层信号。分析设计目标可知本设计需要实现以下功能:FPGA产生控制DA9709的信号,令通道A输出未滤波的正弦信号,令通道B输出滤波后的正弦信号,将两种信号进行对比。在此过程中,想要控制DA9709的工作模式则需控制DA9709的MODE、SLEEP管脚;想要控制通道A则需控制AD9280的CLK1、WRT1、DB7~0P1管脚;想要控制通道B则需要控制AD9280的CLK2、WRT2、DB7~0P2管脚。在设计中使用信号clk连接到晶振来表示50M时钟的输入;使用信号rst_n连接到按键来表示复位;使用3位信号key来表示三位拨码开关;将dac_mode信号连接到DA9709的MODE管脚用来控制其工作模式;将dac_sleep信号连接到DA9709的SLEEP管脚用来控制其睡眠模式;将dac_clka信号连接到DA9709的CLK1管脚用来控制通道A的时钟;将dac_wra信号连接到DA9709的WRT1管脚用来控制通道A的写使能;将8位信号dac_da连接到DA9709的DB7~0P1管脚用来控制通道A的写数据;将dac_clkb号连接到DA9709的CLK2脚用来控制通道B时钟;将dac_wrb信号连接到DA9709的WRT2脚用来控制通道B使能;将8位信号dac_db连接到DA9709的DB7~0P2脚用来控制通道B写数据。
综上所述,本工程需要11个信号:时钟信号clk,复位信号rst_n,拨码开关的输入信号key,dac_mode、dac_sleep、dac_clka、dac_wra、dac_da、dac_clkb、dac_wrb和dac_db信号,其中dac_da和dac_db是8位信号,其它都是1位信号。信号和硬件的对应关系如下表所示。 表3.13 - 1信号和管脚关系 将module的名称定义为fir_prj。已知该设计有11个信号:clk、rst_n、key、dac_mode、dac_sleep、dac_clka、dac_wra、dac_da、dac_clkb、dac_wrb和dac_db信号,将与外部相连接的信号写入模块接口列表,具体顶层代码如下所示: | module fir_prj( clk , rst_n , key , dac_mode , dac_sleep , dac_clka , dac_da , dac_wra , dac_clkb , dac_db , dac_wrb ); |
随后声明输入输出属性。这里需要声明这一信号对于FPGA来说属于输入还是输出,如果是输入信号则声明其为input,如果是输出信号则声明其为output。在本设计中,由于clk是外部的晶振输入给FPGA的,因此在FPGA中clk是1位的输入信号input;同样地,rst_n是外部按键输送给FPGA的,因此在FPGA中rst_n也为1位输入信号input;key是3位输入信号input,dac_da和dac_db是8位的输出信号output,dac_mode,dac_clka,dac_wra,dac_sleep,dac_clkb,dac_wrb均为是1位输出信号output。综上所述,补充输入输出端口定义后的代码如下: | input clk ; input rst_n ; input [ 3-1:0] key ; output dac_mode ; output dac_clka ; output [ 8-1:0] dac_da ; output dac_wra ; output dac_sleep ; output dac_clkb ; output [ 8-1:0] dac_db ; output dac_wrb ;
|
3.2 正弦信号设计
将正弦信号命名为sin_data信号,sin_data是从表3.13- 2中选择出来的值,该表一共有128个点。该表的计算方法,在上一章节“信号发生器和DA转换”中有详细描述,这里就不再进行赘述了。 表3.13- 2 DAC输出采样点对应幅度值
定义一个7位的选择信号addr,只要控制好addr信号就可以快速得到sin_data。可以写出下面代码: | always @(*)begin case(addr) 0: sin_data = 8'h7F; 1: sin_data = 8'h85; 2: sin_data = 8'h8C; 3: sin_data = 8'h92; 4: sin_data = 8'h98; 5: sin_data = 8'h9E; 6: sin_data = 8'hA4; 7: sin_data = 8'hAA; 8: sin_data = 8'hB0; 9: sin_data = 8'hB6; 10: sin_data = 8'hBC; 11: sin_data = 8'hC1; 12: sin_data = 8'hC6; 13: sin_data = 8'hCB; 14: sin_data = 8'hD0; 15: sin_data = 8'hD5; 16: sin_data = 8'hDA; 17: sin_data = 8'hDE; 18: sin_data = 8'hE2; 19: sin_data = 8'hE6; 20: sin_data = 8'hEA; 21: sin_data = 8'hED; 22: sin_data = 8'hF0; 23: sin_data = 8'hF3; 24: sin_data = 8'hF5; 25: sin_data = 8'hF7; 26: sin_data = 8'hF9; 27: sin_data = 8'hFB; 28: sin_data = 8'hFC; 29: sin_data = 8'hFD; 30: sin_data = 8'hFE; 31: sin_data = 8'hFE; 32: sin_data = 8'hFE; 33: sin_data = 8'hFE; 34: sin_data = 8'hFE; 35: sin_data = 8'hFD; 36: sin_data = 8'hFC; 37: sin_data = 8'hFA; 38: sin_data = 8'hF8; 39: sin_data = 8'hF6; 40: sin_data = 8'hF4; 41: sin_data = 8'hF1; 42: sin_data = 8'hEF; 43: sin_data = 8'hEB; 44: sin_data = 8'hE8; 45: sin_data = 8'hE4; 46: sin_data = 8'hE0; 47: sin_data = 8'hDC; 48: sin_data = 8'hD8; 49: sin_data = 8'hD3; 50: sin_data = 8'hCE; 51: sin_data = 8'hC9; 52: sin_data = 8'hC4; 53: sin_data = 8'hBE; 54: sin_data = 8'hB9; 55: sin_data = 8'hB3; 56: sin_data = 8'hAD; 57: sin_data = 8'hA7; 58: sin_data = 8'hA1; 59: sin_data = 8'h9B; 60: sin_data = 8'h95; 61: sin_data = 8'h8F; 62: sin_data = 8'h89; 63: sin_data = 8'h82; 64: sin_data = 8'h7D; 65: sin_data = 8'h77; 66: sin_data = 8'h70; 67: sin_data = 8'h6A; 68: sin_data = 8'h64; 69: sin_data = 8'h5E; 70: sin_data = 8'h58; 71: sin_data = 8'h52; 72: sin_data = 8'h4C; 73: sin_data = 8'h46; 74: sin_data = 8'h41; 75: sin_data = 8'h3C; 76: sin_data = 8'h36; 77: sin_data = 8'h31; 78: sin_data = 8'h2C; 79: sin_data = 8'h28; 80: sin_data = 8'h23; 81: sin_data = 8'h1F; 82: sin_data = 8'h1B; 83: sin_data = 8'h17; 84: sin_data = 8'h14; 85: sin_data = 8'h11; 86: sin_data = 8'hE ; 87: sin_data = 8'hB ; 88: sin_data = 8'h9 ; 89: sin_data = 8'h7 ; 90: sin_data = 8'h5 ; 91: sin_data = 8'h3 ; 92: sin_data = 8'h2 ; 93: sin_data = 8'h1 ; 94: sin_data = 8'h1 ; 95: sin_data = 8'h1 ; 96: sin_data = 8'h1 ; 97: sin_data = 8'h1 ; 98: sin_data = 8'h2 ; 99: sin_data = 8'h3 ; 100: sin_data = 8'h4 ; 101: sin_data = 8'h6 ; 102: sin_data = 8'h7 ; 103: sin_data = 8'hA ; 104: sin_data = 8'hC ; 105: sin_data = 8'hF ; 106: sin_data = 8'h12; 107: sin_data = 8'h15; 108: sin_data = 8'h19; 109: sin_data = 8'h1D; 110: sin_data = 8'h21; 111: sin_data = 8'h25; 112: sin_data = 8'h2A; 113: sin_data = 8'h2E; 114: sin_data = 8'h33; 115: sin_data = 8'h38; 116: sin_data = 8'h3E; 117: sin_data = 8'h43; 118: sin_data = 8'h49; 119: sin_data = 8'h4E; 120: sin_data = 8'h54; 121: sin_data = 8'h5A; 122: sin_data = 8'h60; 123: sin_data = 8'h67; 124: sin_data = 8'h6D; 125: sin_data = 8'h73; 126: sin_data = 8'h79; 127: sin_data = 8'h7F; endcase end |
那么addr信号又如何设计呢?在本设计中addr用以控制选择数据的地址,通过控制addr的增加值来产生多种频率的正弦波。以频率为100KHz的正弦信号为例,该正弦信号的周期是10000ns。本工程的工作时钟是20ns,也就是10000/20 = 500个时钟输出一个正弦信号,即500个时钟需要输出128个点,因此每个时钟addr增加的值为128/500 = 0.256。
按同样的分析方法,可以得到其他信号频率的addr增加值如下所示:
100KHz的正弦信号,每个时钟addr增加:128/250= 0.256 200KHz的正弦信号,每个时钟addr增加:128/250= 0.512 300KHz的正弦信号,每个时钟addr增加:128/166.6667 = 0.7679 400KHz的正弦信号,每个时钟addr增加:128/125= 1.024 500KHz的正弦信号,每个时钟addr增加:128/100 = 1.28 600KHz的正弦信号,每个时钟addr增加:128/83.3333= 1.5358 700KHz的正弦信号,每个时钟addr增加:128/71.4286 = 1.792 800KHz的正弦信号,每个时钟addr增加:128/62.5 = 2.048
addr表示的是采样点的值,即为0~127的整数,但是上面计算出的addr的每次增加值包含小数,众所周知FPGA是没有小数的,因此需要将上面的小数乘以1024后取整,得到每次要增加的整数并将该结果保存到addr_tmp中。即:
100KHz的正弦信号,每个时钟addr_tmp增加:0.256*1024 = 262.144 ≈ 262 200KHz的正弦信号,每个时钟addr_tmp增加:0.512*1024 = 524.288 ≈ 524 300KHz的正弦信号,每个时钟addr_tmp增加:0.7679*1024 =786.3296 ≈ 786 400KHz的正弦信号,每个时钟addr_tmp增加:1.024*1024 =1028.576 ≈ 1029 500KHz的正弦信号,每个时钟addr_tmp增加:1.28*1024 =1310.72 ≈ 1311 600KHz的正弦信号,每个时钟addr_tmp增加:1.5358*1024 =1572.6592 ≈1573 700KHz的正弦信号,每个时钟addr_tmp增加:1.792*1024 =1835.008 ≈ 1835 800KHz的正弦信号,每个时钟addr_tmp增加: 2.048*1024 =2097.152 ≈ 2097
前文分析可知:以上8种频率信号都是由拨码信号key进行控制,因此可以写出addr_tmp的代码如下: | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin addr_tmp<= 0; end else if(key==0) begin addr_tmp<= addr_tmp + 262; end else if(key==1) begin addr_tmp<= addr_tmp + 524; end else if(key==2) begin addr_tmp<= addr_tmp + 786; end else if(key==3) begin addr_tmp<= addr_tmp + 1029; end else if(key==4) begin addr_tmp<= addr_tmp + 1311; end else if(key==5) begin addr_tmp<= addr_tmp + 1573; end else if(key==6) begin addr_tmp<= addr_tmp + 1835; end else begin addr_tmp<= addr_tmp + 2097; end end |
由于addr_tmp是小数乘以1024后得到的,因此最终addr_tmp需要除以1024后再赋给addr。除以1024的操作可以用右移10位来实现,addr_tmp向右移10位后保留7位结果赋给addr即可。因此addr_tmp位宽为17位,其代码表示如下: | assign addr = addr_tmp>>10 ; |
3.3 FIR滤波器设计
3.3.1 matlab生成FIR系数打开matlab,在其命令窗口输入fdatool按下回车调出波形设计界面。
在波形设计界面中按照设计目标进行参数选择。
Response Type:案例要求滤波高于500KHz的信号,因此选择lowpass低通滤波器; Fstop:截止频率设为600KHz Fs:采样频率: 12.5MHz(12500Khz)
其它选项默认
最后点击“Design Filter”,波形设计界面如下图所示:
产生系数后点击“file”菜单里的“Export”将系数保存到工作区,如下图所示:
滤波器系数类型和路径界面如下图所示,此界面不进行改变,直接点击“Export”。
打开工作区里的“Num”,如下图所示。
随后将下图第一行的数据复制粘贴到txt文件中:
注意复制后需在两个系数间在英文输入状态下插入逗号得到滤波器系数,如下图所示。
3.3.2 新建FPGA工程
打开软件“Quartus”,点击“File”菜单下的“New Project Wizard”,如下图所示。
弹出“Introduction”界面后点击“Next”,如下图所示。
设置工程目录,工程名,顶层模块名,其中工程目录设置为:D:\mdy_book\fir_prj;工程名设置为:fir_prj;顶层模块名设置为:fir_prj,如下图所示,填写完毕后直接点击“next”。 图3.13-39QUARTUS设置新工程路径和名称
在设置新工程类型界面选择“Empty project”选项建立空白工程,如下图所示,随后点击“Next”。
错误!未找到引用源。接着是添加文件界面,此时不选择任何文件,直接点击“Next”,如下图所示。
在选择芯片型号界面选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,完成后直接点击“Finish”,如下图所示。 图3.13-42QUARTUS设置新工程的芯片型号
3.3.3 FPGA生成FIR IP核
建立工程后,在软件“Quartus”中“IP catalog”这一界面下选择“DSP”目录下“Filter”的“FIR II”选项,如下图所示。
、 图3.13-43在IP Catalog查找FIR IP核
进入此界面后将新生成的FIR滤波器IP核路径设置为:D:\mdy_book\fir_prj\my_fir.v,“IPvariation file name”这一项选择“Verilog”,随后点击“OK”进入FIR滤波器设置界面,如下图所示。
file:///C:/Users/Administrator/Desktop/%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%872/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC.files/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC17755.png
在“Fitter specification”界面需要进行以下设置。
Filter Type:选择Single Rate,表示只采用一种采样率; Clock Rate:50MHz; Input Sample Rate (PSPS):12.5MHz;
其余选项默认参数,然后点击“coefficients”选项卡,如下图所示。
图3.13-45设置FIR IP核类型界面
单击“import from file ”选项,在输出的界面中找出用matlab生成的系数文件:my_fir_coe.txt,点击“import”将其导入。导入成功后可以看到下图的“frequency response”界面的波形发生变化。在“Coefficient Bit Width”选项中填写16,表示每个系数用16比特量化。
在“Input/Output Options”选项卡中,需要做如下设置:
Input Type:选择Signed Binary,表示输入的数据是有符号数(补码形式); Input Width:输入8,表示输入的数据是8位位宽; Output Type:选择Signed Binary,表示输出的数据是有符号数(补码形式); MSB Rounding:选择Truncation。表示输出结果的高位要截断; MSB Bits to Remove:填写3,表示MSB要截取3个符号位; LSB Rounding:选择Truncation,表示输出结果的低位要截断; LSB Bits to Remove:填写19,表示LSB要截断低19位;这样最终输出的结果就是8位。
其它选项默认,点击“Finish”,生成FIR IP核。
生成FIR IP核界面如下:
IP核生成后弹出如下图所示对话框,点击“yes”将此IP核添加进工程。
3.3.4 例化FIR IP核
用软件“GVIM”打开D:\mdy_book\fir_prj\my_fir.v文件,生成的FIR IP核文件如下图所示。
my_fir模块的各个信号的描述见下表: 表3.13- 3 my_fir模块各信号描述 信号名 | | | | | | | 时钟输入信号。在FIR设置时已经填写其为50MHz,所以此时连接50MHz时钟。 | | | | | | | | FIR滤波器输入的数据输入。注意,输入的是有符号数。 | | | | | | | | | | | | | | | | FIR滤波器输出有效指示信号。由于输入始终有效,输出也始终有效,因此此信号可以忽略不用,例化时不进行连接。 | | | | FIR滤波器输出错误指示信号。由于输入没错误,输出也不会有错误,所以可以忽略该信号,例化时不进行连接。 |
由于滤波器的输入数据和输出数据都是有符号数(补码形式,-128~127),但是正弦信sin_data是无符号数(0~255)。因此需要将sin_data变成有符号数后再发送给FIR进行滤波。定义转换后的信号为fir_din,该信号位宽为8位。将sin_data减去128转换成有符号数,即fir_din = sin_data–128。
生成FIR IP核后需要对该IP核进行例化后才可以使用。所谓例化则为把FIR IP核的信号连接到对应的滤波器上。虽然这些信号是一样的,但是在每个模块中信号的命名不同,如果不进行连接,模块与模块之间就会互相不知道对方的存在。因此需要将两个相同的信号连接在一起来达成合作的效果。例化名为u_my_fir,fir的输出数据信号命名为fir_dout,可以写出代码如下: | assign fir_din = sin_data - 128;
my_firu_my_fir( .clk (clk ) , .reset_n (rst_n ) , .ast_sink_data (fir_din ) , .ast_sink_valid (1 ) , .ast_sink_error (0 ) , .ast_source_data (fir_dout) , .ast_source_valid( ) , .ast_source_error( ) ); |
3.4 DA接口信号设计
首先设计信号dac_da。dac_da是直接输出正弦信号的,但由于DA的输出电压与dac_da成反比例线性关系,所以dac_da通过(255-sin_data)得到。写代码时可以调用至简设计法模板,在编辑状态下输入“Shixu2”,将其补充完整得到dac_da的代码表示如下: | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dac_da<= 0; end else begin dac_da<= 255 - sin_data; end end |
接着设计信号dac_sleep,AD一直进行工作,因此dac_sleep始终为0;为了满足tS的时间要求,可以令dac_clka = ~clk,dac_wra信号与dac_clka相同,其具体代码表示如下: | assign dac_sleep = 0 ; assign dac_wra = dac_clka ; assign dac_clka = ~clk ; |
然后设计信号dac_db。dac_db是直接输出滤波后的信号fir_dout。需要注意的是fir_dout是有符号数(范围是-128~127),所以要转为无符号数(0~255)。假设转换后的信号为fir_dout2,则fir_dout2 = fir_dout + 128。由于DA的通道2的输出电压与dac_db是成反比例线性关系,所以dac_db通过(255-fir_dout2)得到。写出dac_db的代码表示如下: | assign fir_dout2 = fir_dout + 128; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dac_db<= 0; end else begin dac_db<= 255 - fir_dout2; end end |
最后设计dac_clkb信号,为了满足tS的时间要求,可以让dac_clkb = ~clk,dac_wrb信号与dac_clkb相同,其具体代码表示如下: | assign dac_wrb = dac_clkb ; assign dac_clkb = ~clk ; |
至此,模块主体已经完成。
3.5 信号定义
接下来将module补充完整,首先来定义信号类型。reg和wire的判断很容易搞不清楚总会有多余的联想,比如认为reg就是寄存器,wire是线;或者认为reg会综合成寄存器,wire不会综合成寄存器。但是这些其实和reg型还是wire型都并无关系,在进行信号类型判断时不需要做任何的联想,只要记住一个规则:“用always实现的是reg型,其他都是wire型”就可以了。
addr是用assign设计的,因此类型为wire。addr值最大为127,需要用7根线表示,即位宽为7。
关于信号位宽的获取,在这里至简设计法分享一个非常实用的技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽。利用这一方法将addr的最大计数值127输入到计算器中,如下图所示,可以看到其位宽为7。
综上所述,addr的定义代码如下: addr_tmp是用always设计的,因此其类型为reg。该信号的位宽是17,代码表示如下: sin_data是用always设计的,因此类型为reg。其最大值为255,要用8根线表示,位宽为8,可以在编辑状态下输入“Reg8”调用至简设计法模板,补充完整后得到代码如下: fir_din是用assign设计的,因此类型为wire。该信号位宽为8,在编辑状态下输入“Wire8”调用至简设计法模板,补充完整后得到代码如下: fir_dout是用例化模块的输出,非always设计因此类型为wire。其位宽为8,依旧在编辑状态下输入“Wire8”调用至简设计法模板,补充完整后得到代码如下: fir_dout2是用assign设计的,非always设计因此类型为wire。其位宽为8,同样在编辑状态下输入“Wire8”调用至简设计法模板,补充完整后得到代码如下: dac_da是用always设计的,因此类型为reg,其位宽为8; dac_sleep是用assign设计的,因此类型为wire,其位宽为1; dac_wra是用assign设计的,因此类型为wire,其位宽为1; dac_clka是用assign设计的,因此类型为wire,其位宽为1; dac_mode是用assign设计的,因此类型为wire,其位宽为1。
依旧在编辑状态下输入“ Reg8”“Wire1”调用至简设计法模板,补充完整后得到相应代码如下: | reg [7:0] dac_da ; wire dac_sleep ; wire dac_wra ; wire dac_clka ; wire dac_mode ; |
dac_db是用always设计的,因此类型为reg,其位宽为8; dac_wrb是用assign设计的,因此类型为wire,其位宽为1; dac_clkb是用assign设计的,因此类型为wire,其位宽为1。
在编辑状态下输入“Reg8”“Wire1”调用至简设计法模板,补充完整后得到相应代码如下: | reg [7:0] dac_db ; wire dac_wrb ; wire dac_clkb ; |
在代码的最后一行写下endmodule 至此,整个代码的设计工作已经完成。完整版的工程代码如下: - module fir_prj(
- clk ,
- rst_n ,
- key ,
- dac_mode ,
- dac_sleep ,
- dac_clka ,
- dac_da ,
- dac_wra ,
- dac_clkb ,
- dac_db ,
- dac_wrb
- );
-
- input clk ;
- input rst_n ;
- input [ 3-1:0] key ;
- output dac_mode ;
- output dac_clka ;
- output [ 8-1:0] dac_da ;
- output dac_wra ;
- output dac_sleep ;
- output dac_clkb ;
- output [ 8-1:0] dac_db ;
- output dac_wrb ;
-
- wire [6:0] addr ;
- reg [16:0] addr_tmp ;
- reg [7:0] sin_data ;
- wire [7:0] fir_din ;
- wire [7:0] fir_dout ;
- wire [7:0] fir_dout2 ;
- reg [7:0] dac_da ;
- wire dac_sleep ;
- wire dac_wra ;
- wire dac_clka ;
- wire dac_mode ;
-
- always @(*)begin
- case(addr)
- 0: sin_data = 8'h7F;
- 1: sin_data = 8'h85;
- 2: sin_data = 8'h8C;
- 3: sin_data = 8'h92;
- 4: sin_data = 8'h98;
- 5: sin_data = 8'h9E;
- 6: sin_data = 8'hA4;
- 7: sin_data = 8'hAA;
- 8: sin_data = 8'hB0;
- 9: sin_data = 8'hB6;
- 10: sin_data = 8'hBC;
- 11: sin_data = 8'hC1;
- 12: sin_data = 8'hC6;
- 13: sin_data = 8'hCB;
- 14: sin_data = 8'hD0;
- 15: sin_data = 8'hD5;
- 16: sin_data = 8'hDA;
- 17: sin_data = 8'hDE;
- 18: sin_data = 8'hE2;
- 19: sin_data = 8'hE6;
- 20: sin_data = 8'hEA;
- 21: sin_data = 8'hED;
- 22: sin_data = 8'hF0;
- 23: sin_data = 8'hF3;
- 24: sin_data = 8'hF5;
- 25: sin_data = 8'hF7;
- 26: sin_data = 8'hF9;
- 27: sin_data = 8'hFB;
- 28: sin_data = 8'hFC;
- 29: sin_data = 8'hFD;
- 30: sin_data = 8'hFE;
- 31: sin_data = 8'hFE;
- 32: sin_data = 8'hFE;
- 33: sin_data = 8'hFE;
- 34: sin_data = 8'hFE;
- 35: sin_data = 8'hFD;
- 36: sin_data = 8'hFC;
- 37: sin_data = 8'hFA;
- 38: sin_data = 8'hF8;
- 39: sin_data = 8'hF6;
- 40: sin_data = 8'hF4;
- 41: sin_data = 8'hF1;
- 42: sin_data = 8'hEF;
- 43: sin_data = 8'hEB;
- 44: sin_data = 8'hE8;
- 45: sin_data = 8'hE4;
- 46: sin_data = 8'hE0;
- 47: sin_data = 8'hDC;
- 48: sin_data = 8'hD8;
- 49: sin_data = 8'hD3;
- 50: sin_data = 8'hCE;
- 51: sin_data = 8'hC9;
- 52: sin_data = 8'hC4;
- 53: sin_data = 8'hBE;
- 54: sin_data = 8'hB9;
- 55: sin_data = 8'hB3;
- 56: sin_data = 8'hAD;
- 57: sin_data = 8'hA7;
- 58: sin_data = 8'hA1;
- 59: sin_data = 8'h9B;
- 60: sin_data = 8'h95;
- 61: sin_data = 8'h8F;
- 62: sin_data = 8'h89;
- 63: sin_data = 8'h82;
- 64: sin_data = 8'h7D;
- 65: sin_data = 8'h77;
- 66: sin_data = 8'h70;
- 67: sin_data = 8'h6A;
- 68: sin_data = 8'h64;
- 69: sin_data = 8'h5E;
- 70: sin_data = 8'h58;
- 71: sin_data = 8'h52;
- 72: sin_data = 8'h4C;
- 73: sin_data = 8'h46;
- 74: sin_data = 8'h41;
- 75: sin_data = 8'h3C;
- 76: sin_data = 8'h36;
- 77: sin_data = 8'h31;
- 78: sin_data = 8'h2C;
- 79: sin_data = 8'h28;
- 80: sin_data = 8'h23;
- 81: sin_data = 8'h1F;
- 82: sin_data = 8'h1B;
- 83: sin_data = 8'h17;
- 84: sin_data = 8'h14;
- 85: sin_data = 8'h11;
- 86: sin_data = 8'hE ;
- 87: sin_data = 8'hB ;
- 88: sin_data = 8'h9 ;
- 89: sin_data = 8'h7 ;
- 90: sin_data = 8'h5 ;
- 91: sin_data = 8'h3 ;
- 92: sin_data = 8'h2 ;
- 93: sin_data = 8'h1 ;
- 94: sin_data = 8'h1 ;
- 95: sin_data = 8'h1 ;
- 96: sin_data = 8'h1 ;
- 97: sin_data = 8'h1 ;
- 98: sin_data = 8'h2 ;
- 99: sin_data = 8'h3 ;
- 100: sin_data = 8'h4 ;
- 101: sin_data = 8'h6 ;
- 102: sin_data = 8'h7 ;
- 103: sin_data = 8'hA ;
- 104: sin_data = 8'hC ;
- 105: sin_data = 8'hF ;
- 106: sin_data = 8'h12;
- 107: sin_data = 8'h15;
- 108: sin_data = 8'h19;
- 109: sin_data = 8'h1D;
- 110: sin_data = 8'h21;
- 111: sin_data = 8'h25;
- 112: sin_data = 8'h2A;
- 113: sin_data = 8'h2E;
- 114: sin_data = 8'h33;
- 115: sin_data = 8'h38;
- 116: sin_data = 8'h3E;
- 117: sin_data = 8'h43;
- 118: sin_data = 8'h49;
- 119: sin_data = 8'h4E;
- 120: sin_data = 8'h54;
- 121: sin_data = 8'h5A;
- 122: sin_data = 8'h60;
- 123: sin_data = 8'h67;
- 124: sin_data = 8'h6D;
- 125: sin_data = 8'h73;
- 126: sin_data = 8'h79;
- 127: sin_data = 8'h7F;
- endcase
- end
-
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- addr_tmp<= 0;
- end
- else if(key==0) begin
- addr_tmp<= addr_tmp + 262;
- end
- else if(key==1) begin
- addr_tmp<= addr_tmp + 524;
- end
- else if(key==2) begin
- addr_tmp<= addr_tmp + 786;
- end
- else if(key==3) begin
- addr_tmp<= addr_tmp + 1029;
- end
- else if(key==4) begin
- addr_tmp<= addr_tmp + 1311;
- end
- else if(key==5) begin
- addr_tmp<= addr_tmp + 1573;
- end
- else if(key==6) begin
- addr_tmp<= addr_tmp + 1835;
- end
- else begin
- addr_tmp<= addr_tmp + 2097;
- end
- end
-
-
- assign addr = addr_tmp>>10 ;
- assign fir_din = sin_data - 128;
-
- my_firu_my_fir(
- .clk (clk ) ,
- .reset_n (rst_n ) ,
- .ast_sink_data (fir_din ) ,
- .ast_sink_valid (1 ) ,
- .ast_sink_error (0 ) ,
- .ast_source_data (fir_dout) ,
- .ast_source_valid( ) ,
- .ast_source_error( )
- );
-
-
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- dac_da<= 0;
- end
- else begin
- dac_da<= 255 - sin_data;
- end
- end
-
-
- assign dac_sleep = 0 ;
- assign dac_wra = dac_clka ;
- assign dac_clka = ~clk ;
-
- assign fir_dout2 = fir_dout + 128;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- dac_db<= 0;
- end
- else begin
- dac_db<= 255 - fir_dout2;
- end
- end
-
- assign dac_wrb = dac_clkb ;
- assign dac_clkb = ~clk ;
-
- endmodule
复制代码
第4节 综合与上板
4.1 添加文件上一节中已经介绍了新建工程的过程,这里就不再赘述了。现在打开软件“Quartus”,在“Project”菜单中选择“Add/Remove File to Project”,如下图所示,随后会弹出文件窗口。
点击右上角的
按钮,在弹出来的窗口中双击选择D:\mdy_book\fir_prj目录下的fir_prj.v、my_fir.qip文件。点击“Add”添加成功后关闭本窗口,添加文件成功界面如下图所示。
4.2 综合
编译界面如下图所示,在菜单栏中选中Processing后选择Start Compilation,开始对整个工程进行编译和综合。
当出现如下图所示界面时说明编译综合成功。
4.3 配置管脚
下面需要对相应管脚进行配置。如下图所示,在菜单栏中,选中“Assignments”,然后选择“Pin Planner”,随后就会弹出配置管脚的窗口。
在配置窗口最下方中的“location”一列,参考表3.2-2信号和管脚关系,按照表3.13- 1中最右两列配置好FPGA管脚。配置管理来源参见管脚配置环节,最终配置的结果如图3.13-58。配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。 表3.13 –1 信号和管脚关系
图3.13-57配置管脚界面
图3.13-58管脚配置成功界面
4.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“Start Compilation”,再次对整个工程进行编译和综合,如下图所示。
当出现图3.2-19QUARTUS编译成功标志,就说明编译综合成功。
4.5 连接开发板
完成编译后开始进行上板调试操作,开发板连接方式如下图所示。将电源接上开发板,USB BLASTER一端连接到JTAG插口,另一端连到PC的USB接口。将开发板上的AD接口和DA与示波器的两个通道相连,连接完成后将电源打开。
4.6 上板
在“Quartus”的“Task”窗口中,右键“Program Device”选择“Open”进入烧录界面,如下图所示。
默认选中文件output/fir_prj.sof,Hardware Setup的旁边会显示USB-Blaster,如下图所示。
当进度条到100%提示成功后即可在示波器上观看相应现象。
下载完成后,如果操作无误此时可以在示波器上看到对应的波形。如果没有显示成功,就需要返回检查一下连接是否到位,有没有写错代码或者软件参数的选择有没有发生错误。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定会达到想要的效果。
第5节 简化版步骤分享
这里依旧会分享简化版的步骤,方便掌握基础原理后进行反复操作复习。
5.1 设计实现
5.1.1 顶层接口
新建目录:D:\mdy_book\fir_prj。在该目录中,新建一个名为fir_prj.v的文件,并用GVIM打开,开始编写代码。
分析设计目标确定顶层信号,信号和硬件的对应关系图见下表所示。 表3.13 –1信号和管脚关系 写出顶层代码: | module fir_prj( clk , rst_n , key , dac_mode , dac_sleep , dac_clka , dac_da , dac_wra , dac_clkb , dac_db , dac_wrb ); |
声明输入输出属性: | input clk ; input rst_n ; input [ 3-1:0] key ; output dac_mode ; output dac_clka ; output [ 8-1:0] dac_da ; output dac_wra ; output dac_sleep ; output dac_clkb ; output [ 8-1:0] dac_db ; output dac_wrb ;
|
5.2 正弦信号设计
设计正弦信号sin_data信号: | always @(*)begin case(addr) 0: sin_data = 8'h7F; 1: sin_data = 8'h85; 2: sin_data = 8'h8C; 3: sin_data = 8'h92; 4: sin_data = 8'h98; 5: sin_data = 8'h9E; 6: sin_data = 8'hA4; 7: sin_data = 8'hAA; 8: sin_data = 8'hB0; 9: sin_data = 8'hB6; 10: sin_data = 8'hBC; 11: sin_data = 8'hC1; 12: sin_data = 8'hC6; 13: sin_data = 8'hCB; 14: sin_data = 8'hD0; 15: sin_data = 8'hD5; 16: sin_data = 8'hDA; 17: sin_data = 8'hDE; 18: sin_data = 8'hE2; 19: sin_data = 8'hE6; 20: sin_data = 8'hEA; 21: sin_data = 8'hED; 22: sin_data = 8'hF0; 23: sin_data = 8'hF3; 24: sin_data = 8'hF5; 25: sin_data = 8'hF7; 26: sin_data = 8'hF9; 27: sin_data = 8'hFB; 28: sin_data = 8'hFC; 29: sin_data = 8'hFD; 30: sin_data = 8'hFE; 31: sin_data = 8'hFE; 32: sin_data = 8'hFE; 33: sin_data = 8'hFE; 34: sin_data = 8'hFE; 35: sin_data = 8'hFD; 36: sin_data = 8'hFC; 37: sin_data = 8'hFA; 38: sin_data = 8'hF8; 39: sin_data = 8'hF6; 40: sin_data = 8'hF4; 41: sin_data = 8'hF1; 42: sin_data = 8'hEF; 43: sin_data = 8'hEB; 44: sin_data = 8'hE8; 45: sin_data = 8'hE4; 46: sin_data = 8'hE0; 47: sin_data = 8'hDC; 48: sin_data = 8'hD8; 49: sin_data = 8'hD3; 50: sin_data = 8'hCE; 51: sin_data = 8'hC9; 52: sin_data = 8'hC4; 53: sin_data = 8'hBE; 54: sin_data = 8'hB9; 55: sin_data = 8'hB3; 56: sin_data = 8'hAD; 57: sin_data = 8'hA7; 58: sin_data = 8'hA1; 59: sin_data = 8'h9B; 60: sin_data = 8'h95; 61: sin_data = 8'h8F; 62: sin_data = 8'h89; 63: sin_data = 8'h82; 64: sin_data = 8'h7D; 65: sin_data = 8'h77; 66: sin_data = 8'h70; 67: sin_data = 8'h6A; 68: sin_data = 8'h64; 69: sin_data = 8'h5E; 70: sin_data = 8'h58; 71: sin_data = 8'h52; 72: sin_data = 8'h4C; 73: sin_data = 8'h46; 74: sin_data = 8'h41; 75: sin_data = 8'h3C; 76: sin_data = 8'h36; 77: sin_data = 8'h31; 78: sin_data = 8'h2C; 79: sin_data = 8'h28; 80: sin_data = 8'h23; 81: sin_data = 8'h1F; 82: sin_data = 8'h1B; 83: sin_data = 8'h17; 84: sin_data = 8'h14; 85: sin_data = 8'h11; 86: sin_data = 8'hE ; 87: sin_data = 8'hB ; 88: sin_data = 8'h9 ; 89: sin_data = 8'h7 ; 90: sin_data = 8'h5 ; 91: sin_data = 8'h3 ; 92: sin_data = 8'h2 ; 93: sin_data = 8'h1 ; 94: sin_data = 8'h1 ; 95: sin_data = 8'h1 ; 96: sin_data = 8'h1 ; 97: sin_data = 8'h1 ; 98: sin_data = 8'h2 ; 99: sin_data = 8'h3 ; 100: sin_data = 8'h4 ; 101: sin_data = 8'h6 ; 102: sin_data = 8'h7 ; 103: sin_data = 8'hA ; 104: sin_data = 8'hC ; 105: sin_data = 8'hF ; 106: sin_data = 8'h12; 107: sin_data = 8'h15; 108: sin_data = 8'h19; 109: sin_data = 8'h1D; 110: sin_data = 8'h21; 111: sin_data = 8'h25; 112: sin_data = 8'h2A; 113: sin_data = 8'h2E; 114: sin_data = 8'h33; 115: sin_data = 8'h38; 116: sin_data = 8'h3E; 117: sin_data = 8'h43; 118: sin_data = 8'h49; 119: sin_data = 8'h4E; 120: sin_data = 8'h54; 121: sin_data = 8'h5A; 122: sin_data = 8'h60; 123: sin_data = 8'h67; 124: sin_data = 8'h6D; 125: sin_data = 8'h73; 126: sin_data = 8'h79; 127: sin_data = 8'h7F; endcase end |
设计addr_tmp信号: | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin addr_tmp<= 0; end else if(key==0) begin addr_tmp<= addr_tmp + 262; end else if(key==1) begin addr_tmp<= addr_tmp + 524; end else if(key==2) begin addr_tmp<= addr_tmp + 786; end else if(key==3) begin addr_tmp<= addr_tmp + 1029; end else if(key==4) begin addr_tmp<= addr_tmp + 1311; end else if(key==5) begin addr_tmp<= addr_tmp + 1573; end else if(key==6) begin addr_tmp<= addr_tmp + 1835; end else begin addr_tmp<= addr_tmp + 2097; end end |
设计addr信号: | assign addr = addr_tmp>>10 ; |
5.2.1 FIR滤波器设计
步骤一:matlab生成FIR系数
打开matlab后在其命令窗口输入fdatool按下回车调出波形设计界面。
图3.13-9matlab命令窗口输入fdatool
点击“Design Filter”设计参数,波形设计界面如下图所示。
点击“file”菜单里的“Export”将系数保存到工作区,如下图所示。
随后直接点击“Export”。
打开工作区里的“Num”,如下图所示。
将下图第一行的数据复制粘贴到txt文件中,得到滤波器系数。
特别提示:复制后需在两个系数间插入英文状态下的逗号即可得到滤波器的系数,如下图所示。
步骤二:新建FPGA工程
打开软件“Quartus”,点击“File”菜单下的“New Project Wizard”。
弹出“Introduction”界面后点击“Next”。
设置工程目录(目录为D:\mdy_book\fir_prj,工程名和顶层名为fir_prj)后点击“Next”。 图3.13-37 QUARTUS设置新工程路径和名称
选择“Empty project”后点击“Next”。
此界面不选择任何文件,点击“Next”。 图3.13-37 QUARTUS添加文件到工程界面
对芯片型号进行选择,在“Device family”选项中选择“Cyclone ⅣE”,“Available devices”选项下选择“EP4CE15F23C8”,随后点击“Next”。 图3.13-37 QUARTUS设置新工程的芯片型号
步骤三:FPGA生成FIR IP核
建立工程后在软件“Quartus”的“IP catalog”界面中选择“DSP”目录下“Filter”的“FIR II”选项,如下图所示。 图3.13-37在IP Catalog查找FIR IP
选择路径为D:\mdy_book\fir_prj\my_fir.v,随后选择“Verilog”并点击“OK”。
随后对IP核类型进行设置。“Filter Type”选择“Single Rate”,“Clock Rate”填写“50MHz”,“Input Sample Rate (PSPS)”填写“12.5MHz”,随后点击“coefficients”选项卡。
单击“import from file”选项,导入文件“my_fir_coe.txt”后点击“import”。
设置“Input/Output Options”选项卡后点击“Finish”。
生成FIR IP核界面如下所示。
IP核生成后会弹出如下图所示的对话框,点击“yes”将此IP核添加进工程。
步骤四:例化FIR IP核
打开shiD:\mdy_book\fir_prj\my_fir.v文件,生成的FIR IP核文件如下图所示。
将FIR IP核例化,具体代码如下: | assign fir_din = sin_data - 128;
my_firu_my_fir( .clk (clk ) , .reset_n (rst_n ) , .ast_sink_data (fir_din ) , .ast_sink_valid (1 ) , .ast_sink_error (0 ) , .ast_source_data (fir_dout) , .ast_source_valid( ) , .ast_source_error( ) ); |
5.2.2 DA接口信号设计
设计信号dac_da: | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dac_da<= 0; end else begin dac_da<= 255 - sin_data; end end |
设计信号dac_sleep、dac_clka、dac_wra: | assign dac_sleep = 0 ; assign dac_wra = dac_clka ; assign dac_clka = ~clk ; |
设计信号dac_db: | assign fir_dout2 = fir_dout + 128; always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dac_db<= 0; end else begin dac_db<= 255 - fir_dout2; end end |
设计信号dac_clkb,dac_wrb: | assign dac_wrb = dac_clkb ; assign dac_clkb = ~clk ; |
至此,模块主体已经完成,接下来将module补充完整。
5.2.3 信号定义
首先定义信号类型,addr的信号定义如下: addr_tmp的信号定义如下: sin_data的信号定义如下: fir_din的信号定义如下: fir_dout的信号定义如下: fir_dout2的信号定义如下: dac_da、dac_sleep、dac_wra、dac_clka、dac_mode的信号定义如下: | reg [7:0] dac_da ; wire dac_sleep ; wire dac_wra ; wire dac_clka ; wire dac_mode ; |
dac_db、dac_wrb、dac_clkb的信号定义如下: | reg [7:0] dac_db ; wire dac_wrb ; wire dac_clkb ; |
在代码的最后一行写下endmodule 至此,整个代码的设计工作已经完成。完整版的工程代码如下: - module fir_prj(
- clk ,
- rst_n ,
- key ,
- dac_mode ,
- dac_sleep ,
- dac_clka ,
- dac_da ,
- dac_wra ,
- dac_clkb ,
- dac_db ,
- dac_wrb
- );
-
- input clk ;
- input rst_n ;
- input [ 3-1:0] key ;
- output dac_mode ;
- output dac_clka ;
- output [ 8-1:0] dac_da ;
- output dac_wra ;
- output dac_sleep ;
- output dac_clkb ;
- output [ 8-1:0] dac_db ;
- output dac_wrb ;
-
- wire [6:0] addr ;
- reg [16:0] addr_tmp ;
- reg [7:0] sin_data ;
- wire [7:0] fir_din ;
- wire [7:0] fir_dout ;
- wire [7:0] fir_dout2 ;
- reg [7:0] dac_da ;
- wire dac_sleep ;
- wire dac_wra ;
- wire dac_clka ;
- wire dac_mode ;
-
-
- always @(*)begin
- case(addr)
- 0: sin_data = 8'h7F;
- 1: sin_data = 8'h85;
- 2: sin_data = 8'h8C;
- 3: sin_data = 8'h92;
- 4: sin_data = 8'h98;
- 5: sin_data = 8'h9E;
- 6: sin_data = 8'hA4;
- 7: sin_data = 8'hAA;
- 8: sin_data = 8'hB0;
- 9: sin_data = 8'hB6;
- 10: sin_data = 8'hBC;
- 11: sin_data = 8'hC1;
- 12: sin_data = 8'hC6;
- 13: sin_data = 8'hCB;
- 14: sin_data = 8'hD0;
- 15: sin_data = 8'hD5;
- 16: sin_data = 8'hDA;
- 17: sin_data = 8'hDE;
- 18: sin_data = 8'hE2;
- 19: sin_data = 8'hE6;
- 20: sin_data = 8'hEA;
- 21: sin_data = 8'hED;
- 22: sin_data = 8'hF0;
- 23: sin_data = 8'hF3;
- 24: sin_data = 8'hF5;
- 25: sin_data = 8'hF7;
- 26: sin_data = 8'hF9;
- 27: sin_data = 8'hFB;
- 28: sin_data = 8'hFC;
- 29: sin_data = 8'hFD;
- 30: sin_data = 8'hFE;
- 31: sin_data = 8'hFE;
- 32: sin_data = 8'hFE;
- 33: sin_data = 8'hFE;
- 34: sin_data = 8'hFE;
- 35: sin_data = 8'hFD;
- 36: sin_data = 8'hFC;
- 37: sin_data = 8'hFA;
- 38: sin_data = 8'hF8;
- 39: sin_data = 8'hF6;
- 40: sin_data = 8'hF4;
- 41: sin_data = 8'hF1;
- 42: sin_data = 8'hEF;
- 43: sin_data = 8'hEB;
- 44: sin_data = 8'hE8;
- 45: sin_data = 8'hE4;
- 46: sin_data = 8'hE0;
- 47: sin_data = 8'hDC;
- 48: sin_data = 8'hD8;
- 49: sin_data = 8'hD3;
- 50: sin_data = 8'hCE;
- 51: sin_data = 8'hC9;
- 52: sin_data = 8'hC4;
- 53: sin_data = 8'hBE;
- 54: sin_data = 8'hB9;
- 55: sin_data = 8'hB3;
- 56: sin_data = 8'hAD;
- 57: sin_data = 8'hA7;
- 58: sin_data = 8'hA1;
- 59: sin_data = 8'h9B;
- 60: sin_data = 8'h95;
- 61: sin_data = 8'h8F;
- 62: sin_data = 8'h89;
- 63: sin_data = 8'h82;
- 64: sin_data = 8'h7D;
- 65: sin_data = 8'h77;
- 66: sin_data = 8'h70;
- 67: sin_data = 8'h6A;
- 68: sin_data = 8'h64;
- 69: sin_data = 8'h5E;
- 70: sin_data = 8'h58;
- 71: sin_data = 8'h52;
- 72: sin_data = 8'h4C;
- 73: sin_data = 8'h46;
- 74: sin_data = 8'h41;
- 75: sin_data = 8'h3C;
- 76: sin_data = 8'h36;
- 77: sin_data = 8'h31;
- 78: sin_data = 8'h2C;
- 79: sin_data = 8'h28;
- 80: sin_data = 8'h23;
- 81: sin_data = 8'h1F;
- 82: sin_data = 8'h1B;
- 83: sin_data = 8'h17;
- 84: sin_data = 8'h14;
- 85: sin_data = 8'h11;
- 86: sin_data = 8'hE ;
- 87: sin_data = 8'hB ;
- 88: sin_data = 8'h9 ;
- 89: sin_data = 8'h7 ;
- 90: sin_data = 8'h5 ;
- 91: sin_data = 8'h3 ;
- 92: sin_data = 8'h2 ;
- 93: sin_data = 8'h1 ;
- 94: sin_data = 8'h1 ;
- 95: sin_data = 8'h1 ;
- 96: sin_data = 8'h1 ;
- 97: sin_data = 8'h1 ;
- 98: sin_data = 8'h2 ;
- 99: sin_data = 8'h3 ;
- 100: sin_data = 8'h4 ;
- 101: sin_data = 8'h6 ;
- 102: sin_data = 8'h7 ;
- 103: sin_data = 8'hA ;
- 104: sin_data = 8'hC ;
- 105: sin_data = 8'hF ;
- 106: sin_data = 8'h12;
- 107: sin_data = 8'h15;
- 108: sin_data = 8'h19;
- 109: sin_data = 8'h1D;
- 110: sin_data = 8'h21;
- 111: sin_data = 8'h25;
- 112: sin_data = 8'h2A;
- 113: sin_data = 8'h2E;
- 114: sin_data = 8'h33;
- 115: sin_data = 8'h38;
- 116: sin_data = 8'h3E;
- 117: sin_data = 8'h43;
- 118: sin_data = 8'h49;
- 119: sin_data = 8'h4E;
- 120: sin_data = 8'h54;
- 121: sin_data = 8'h5A;
- 122: sin_data = 8'h60;
- 123: sin_data = 8'h67;
- 124: sin_data = 8'h6D;
- 125: sin_data = 8'h73;
- 126: sin_data = 8'h79;
- 127: sin_data = 8'h7F;
- endcase
- end
-
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- addr_tmp<= 0;
- end
- else if(key==0) begin
- addr_tmp<= addr_tmp + 262;
- end
- else if(key==1) begin
- addr_tmp<= addr_tmp + 524;
- end
- else if(key==2) begin
- addr_tmp<= addr_tmp + 786;
- end
- else if(key==3) begin
- addr_tmp<= addr_tmp + 1029;
- end
- else if(key==4) begin
- addr_tmp<= addr_tmp + 1311;
- end
- else if(key==5) begin
- addr_tmp<= addr_tmp + 1573;
- end
- else if(key==6) begin
- addr_tmp<= addr_tmp + 1835;
- end
- else begin
- addr_tmp<= addr_tmp + 2097;
- end
- end
-
-
- assign addr = addr_tmp>>10 ;
- assign fir_din = sin_data - 128;
-
- my_firu_my_fir(
- .clk (clk ) ,
- .reset_n (rst_n ) ,
- .ast_sink_data (fir_din ) ,
- .ast_sink_valid (1 ) ,
- .ast_sink_error (0 ) ,
- .ast_source_data (fir_dout) ,
- .ast_source_valid( ) ,
- .ast_source_error( )
- );
-
-
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- dac_da<= 0;
- end
- else begin
- dac_da<= 255 - sin_data;
- end
- end
-
-
- assign dac_sleep = 0 ;
- assign dac_wra = dac_clka ;
- assign dac_clka = ~clk ;
-
- assign fir_dout2 = fir_dout + 128;
- always @(posedge clk or negedge rst_n)begin
- if(rst_n==1'b0)begin
- dac_db<= 0;
- end
- else begin
- dac_db<= 255 - fir_dout2;
- end
- end
-
- assign dac_wrb = dac_clkb ;
- assign dac_clkb = ~clk ;
-
- endmodule
复制代码
5.3 综合与上板
5.3.1 添加文件
上一节已经介绍了新建工程的过程,这里不再赘述了。
打开“Quartus”软件,在“Project”菜单中选择“Add/Remove File to Project”后弹出文件窗口。
点击右上角的
按钮,在弹出来的窗口中双击选择D:\mdy_book\fir_prj目录下的fir_prj.v、fir_prj.qip文件。点击“Add”添加成功后关闭窗口。
5.3.2 综合
在菜单栏中选中“Processing”后选择“Start Compilation”,开始对整个工程进行编译和综合。
当出现下图的界面则说明编译综合成功。
5.3.3 配置管脚
配置管脚界面如下图所示,在菜单栏中选中“Assignments”后选择“Pin Planner”,随后就会弹出配置管脚的窗口。 图3.13-50配置管脚命令界面
在配置窗口“location”配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。
file:///C:/Users/Administrator/Desktop/%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%872/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC.files/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC50238.png 图3.13-50配置管脚界面
5.3.4 再次综合file:///C:/Users/Administrator/Desktop/%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%872/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC.files/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC50347.png 再次打开“QUARTUS”软件,在菜单栏中选中“Processing”后选择“Start Compilation”,再次对整个工程进行编译和综合,如下图所示。 图3.13-50编译命令界面 当出现图3.2-19QUARTUS编译成功标志则说明编译综合成功。file:///C:/Users/Administrator/Desktop/%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%872/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC.files/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC50489.png 图3.13-50编译成功界面
5.3.5 连接开发板file:///C:/Users/Administrator/Desktop/%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%872/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC.files/%E4%B9%A6%E7%B1%8D%E8%BF%9E%E8%BD%BD%E7%AB%A0%E8%8A%82%E4%B8%AD%E8%BD%AC50602.png 开发板连接方式如下图所示。将电源接上开发板,USB BLASTER一端连接到JTAG插口,另一端连到PC的USB接口。将开发板上的AD接口和DA与示波器的两个通道相连,连接完成后将电源打开。
5.3.6 上板
在“Quartus”的“Task”窗口中,右键“Program Device”选择“Open”进入烧录界面,如下图所示。
默认选中文件output/fir_prj.sof,在Hardware Setup旁边会显示USB-Blaster,如下图所示。
进度条提示成功后可在示波器上观察相应现象。
第6节 扩展练习
至此,整个FIR滤波器设计就分享完毕了,希望读者朋友们学会了整个设计之后也可以展开更多思考,在学会低通滤波器的基础上尝试一下高通滤波器的设计。基于原理不变的情况下,多做一些尝试,这样可以帮助同学们更深刻的掌握案例。也欢迎有更多思路和想法的同学前往至简设计法论坛进行交流讨论。
下面是工程使用的系数文件
my_fir_coe.txt
(1.1 KB, 下载次数: 682)
|