马上注册,看完整文章,学更多FPGA知识。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
温馨提示:明德扬2023推出了全新课程——逻辑设计基本功修炼课,降低学习FPGA门槛的同时,增加了学习的趣味性,并组织了考试赢积分活动
本案例的编号为:000600000078,如果有疑问,请按编号在下面贴子查找答案:MDY案例交流【汇总贴】_FPGA-明德扬科教 (mdy-edu.com)
本文为明德扬原创及录用文章,转载请注明出处
大家好,近期我们会连载《FPGA至简设计原理与应用》一书,有兴趣的同学可以学习,也希望大家可以对我们的书提出宝贵的意见和建议。
《FPGA至简设计原理与应用》书籍连载索引目录
http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=989
读过的朋友可积极在贴后留言,书籍正式出版时,我们会从留言者中挑选20位幸运读者,幸运读者可获潘老师亲笔签名书籍一本。
注:手机浏览可能格式会乱,建议用电脑端进行浏览。
第十四章 插值滤波器设计
本文档编号:002700000026
需要看对应的视频,请点击视频编号:00060000419
1、本文档讲述FPGA产生两路正弦波数据,一路直接由DA输出,一路经过CIC处理之后输出,然后传到示波器进行观察,从而了解CIC滤波器的效果
2、801开发板使用
第1节 项目背景
1.1 多采样率数字滤波器
多采样率,顾名思义即是有多个采样率。上一章节中讲解的FIR、IIR滤波器都只有一个采样频率,其采样率是固定不变的,然而在某些情况的设计中需要不同采样频率下的信号,此时需要对采样率进行转换。以一正弦波模拟信号为例,假定AD采样速率是F1,但现在需要用到的是采样频率为F2的信号,按照传统的速率转换理论则要将经过F1采样后的信号进行DA转换,再将转换后的模拟信号进行以F2采样频率的抽样,最终得到采样率为F2的数字信号,完成采样频率的转换。这种传统方法不仅操作较为复杂,而且通过这一方式处理的信号也可能会由于操作问题受到损伤,因而这一思想现在已经被淘汰,取而代之的采样率转换方法是抽取与内插。
1.2 抽取首先来解释一下抽取的含义:一个有效的正弦波模拟信号经采样频率为F1的抽样信号抽样后得到相应数字信号,很明显这一数字信号序列是在F1频率下得到的。现在如果隔几个点抽取一个信号,假定选定点数为5,同学们来思考一下,每隔5个点抽取一个信号的操作是不是相当于采用1/5倍F1的采样频率对模拟信号进行了采样?可以看出抽取的过程就是降低抽样率的过程,但是这一操作是在时域的抽样,其等于信号在频域波形的周期延拓,相应的周期即为采样频率。因此,为了避免在频域发生频谱混叠,抽样定理也是设计中必须考虑的要素。下面就来具体介绍一下这一定理。
如上图所示,假定所示信号是某一有效信号经采样频率F1抽样得到的频谱,此时的采样频率为8KHz。从图中可以看到从0到F1的这一段距离上有8个空格,对应的每个空格即代表1KHz。有些朋友可能会问:这不是在数字频域吗?单位应该是π,Hz又是从哪里来的呢?没错,这里是数字频域,采样频率F1处对应的是2π,但是为了方便解释以及读者理解,这里用模拟频率来对应数字频率。
综上所述,上图所示的是采样频率为8K的数字信号频域图,现在对这一数字信号进行时域抽取,从而来降低信号的采样率。众所周知,采样率是数字信号频域的波形周期,一旦对数字信号进行时域抽取,则采样率下降,即周期也会下降。因此在进行信号抽取时要把控好尺度,在满足抽样定理的条件下对信号进行抽取,否则就会发生频谱混叠。
图3.14- 2所示的频谱图即是对信号进行了1/5倍的F1采样频率抽取,从图中可见发生了频谱混叠现象。这正是因为1/5倍的F1是1600Hz,而信号的频带是1000Hz,不满足抽样定理从而导致了频谱混叠。为了避免这种情况的发生,除了要满足抽样定理即抽样倍数不能太高之外,还需要将信号的频带设置在F1/2以下,这样才能确保信号不发生频谱混叠。因此,在进行抽取操作之前需要增加一个低通滤波器,又叫做抗混叠低通滤波器,用以限制信号的频带,然后再进行抽取。基于此可以进行如下判断:
低通滤波器的截止频率是1/2倍的经抽取后的采样速率,即fc = 1/2 * (F1/M),其中M是抽取倍数。而1/2*F1对应的数域频率是π,因此可以确定抗混叠低通滤波器的截止频率是π/M。
1.3 内插从上一部分内容可知抽取的过程是降低采样率的过程,那么反过来,插值的过程自然就是提高采样率的过程。同学们可以这样来理解插值的思路:对经F1抽样下得到的数字信号的每两个点之间进行插值,插入的值为0,插值之后信号在单位时间内的采样点数增多,自然而然采样速率也会提升,信号的频谱的周期会有所增加。
需要注意是插值只是在时域信号中间插入了D-1个零值,插值后仅仅是采样率发生了改变,而信号的信息并没有改变。因此,信号频谱的形状是不会改变的,改变的仅仅是周期。如上图所示,F1是插值之前信号的周期,插值之后信号频谱的形状不变,但其周期变为F1*D,其中D是插值倍数。如果直接用F1*D倍的采样率采信号,可以从其得到的频谱看出不会有中间两个波形,也就是说这两个波形是多余的,一般其被叫做镜像频谱。既然这段波形是多余的,那么就可以将其用一个低通滤波器滤掉,对应的低通滤波器就叫做镜像低通滤波器。
同样地,下面来判断一下镜像低通滤波器的截止频率。如图3.14- 4所示,假定内插之后的采样频率为F2 =F1*D,fc = 1/2 *F1,则fc =1/2*(F2/D),而1/2*F2对应的是π。注意,1/2*F2此时对应的是π,而并不是1/2*F1,因为这是完成插值操作采样率增加后的频谱,可以得到镜像低通滤波器的截止频率为:π/D。
有些读者朋友可能还是不太理解插值的概念,这里再通俗的表达一下。大家都知道“点动成线”这一概念,归根结底图像都是由点构成的,波形的显示也一样。但是仔细思考一下可以发现信号转化波形的两个点之间是持续保持上一个点的状态的。如下图所示,在不同点波形有着对应的信号值,但是两个点之间信号值是保持不变的状态,直到第二个点来临才会变为此点对应的信号值,再保持此值到下一个点,实际波形的产生过程中不断重复如此循环。可以发现,这样一来无法判断此波形是正弦波还是三角波,在工作中使用这种波形更是非常的不方便,不能及时发现相应错误。
那么如何解决这一问题呢?所谓点动成线,当点越多越密集的时候,自然而然就可以展现出代表的形状。如果试着在上图波形的基础上多增加一些点,如下图所示,可以看到当增加的点足够多的时候,就可以判断出其代表的是什么图像。
当然在插值过程中并不能盲目的进行加点,首先需要设计每个点之间插入几个点。可以根据现实生活思考一下,当两个点之间距离越近就越能接近想要的形状,当两个点之间距离越远就偏离需要的形状越远。因此可以根据两点之间距离的不同,插入不同数量的点,距离越远需要插入的点越多,距离越近插入的点越少。
根据选取点的多少,可以得出两点之间平均的值并以此作为插入点的值。比如点1为0,点2为100,如果插入一个点1(1),其对应的值即为0+(100-0)/2=50;如果插入3个点1(1)、1(2)、1(3)时,点1(1)的值为0+(100-0)/4=25。
通过在两点之间插入中间点的方法可以得到更加平滑的图像,这即为插值滤波器的原理。当然这一解释只是为了方便同学们更好的明白原理,真正使用的过程中还是需要按照抽取后进行内插的方法进行操作,不可投机取巧。
第2节 设计目标
按照至简设计法的设计特色,开始一个新的设计之前首先应明确设计目标。设计目标是整个设计的核心灵魂,后续的每个步骤与操作都是围绕设计目标进行展开的。至简设计法旨在让设计师在设计过程中按照最中的简单快捷的方式实现每个步骤和思路,明确设计目标正是为了让后面的每个阶段的工作都有意义,而不去进行不必要的工程展开,这样一来可以少走很多弯路。对于初学者来说学习阶段养成好习惯可以使之后的工程师生涯受益无穷。所以再次强调在最开始设计前一定要将设计目标分析透彻,认真思考本次设计最终想要实现什么目的,达到什么效果,然后再投入到设计中去。
本次设计使用采样率大于100M的双通道的示波器,将示波器的两个通道分别与FPGA的DA通道1和DA通道2相连。FPGA内部产生频率为62.5KHz正弦信号,这一正弦信号由8个点组成,该正弦信号一路输出给DA通道1,另一路经过插值滤波器后输出给DA通道2。插值滤波器是4倍的插值,即输入是8个点的正弦波,输出的为32个点的正弦波。插值滤波器的实现结构图如下所示。
FPGA开发板与示波器连接示意图如下,需要将FPGA的DA通道和AD通道与示波器对应通道相连接。
仿真插值滤波器的波形图如下所示,上面的波形代表插值前的信号,下面的波形代表插值后的信号,可以明显看出下面的波形的形状更为光滑。
示波器的最终效果显示如下图所示,黄色线为通道1输出的信号,即未经过插值的信号。蓝色线为通道2的输出的信号,即插值后的信号。想要观看上板演示视频效果的读者朋友可以登陆至简设计法官方网站学习: www.mdy-edu.com/xxxx。
第3节 设计实现
接下来就进入设计的实现阶段,本书会按照步骤和原理分析与读者分享案例的实现方法,考虑到初学者的需要,此部分的内容会比较详细。如果基础知识掌握得比较牢靠,只想学习此设计的步骤的同学可以跳过此部分,后面章节有简化版的步骤分享。在此还是建议初学者不要选择捷径,一定按照详细分析的内容进行学习,只有掌握基础知识、打好基础,才可以从容的独立完成项目设计。
3.1 顶层信号
新建目录:D:\mdy_book\cic_prj。在该目录中,新建一个名为cic_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连接到按键表示复位;将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写数据。
综上所述,本工程需要10个信号:时钟信号clk,复位信号rst_n,dac_mode、dac_sleep、dac_clka、dac_wra、dac_da、dac_clkb、dac_wrb和dac_db信号,其中dac_da和dac_db是8位信号,其它的都是1位信号。信号和硬件的对应关系如下表所示。 表3.14 - 1信号和管脚关系 将module的名称定义为cic_prj,已知该设计有10个信号:clk、rst_n、dac_mode、dac_sleep、dac_clka、dac_wra、dac_da、dac_clkb、dac_wrb和dac_db信号,将与外部相连接的信号写入模块接口列表,具体顶层代码如下所示: | module cic_prj( clk , rst_n , 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;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 ; 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一共有8位,其具体值是从一个正弦信号中按照(2*pi/8)的间隔采样到的。具体的采样算法参见“信号发生器和DA转换”章节,这里不再赘述,最终得出sin_data值如下表所示。
表3.14 –2 不同采样点的sin_data值
定义一个7位的选择信号addr,只要控制好addr,就能方便得到sin_data。可以写出如下代码: | always @(*)begin case(addr) 0: sin_data = 8'h7F; 1: sin_data = 8'hDA; 2: sin_data = 8'hFE; 3: sin_data = 8'HD8; 4: sin_data = 8'h7D; 5: sin_data = 8'h23; 6: sin_data = 8'h1; 7: sin_data = 8'h2A; endcase end |
接下来就来确定一下addr信号。addr是用来控制选择数据的地址的信号,通过控制addr的增加值,就能产生所需要的正弦波。
由设计目标可知需要产生62.5KHz的正弦信号,该正弦信号的周期是为1/62500=16000ns。本工程的工作时钟是20ns,也就是16000/20=800个时钟输出一个正弦信号,即800个时钟完成上表的8个值输出一遍的操作,即每800/8=100个时钟输出一个addr。
每100个时钟输出一个值,那意味着此时需要一个计数器cnt0用来对100个时钟进行计数。之前的章节有讲过,至简设计法的计数器只考虑两个因素:加1条件和计数数量,只要可以确定相应逻辑,就能设计出对应的计数器代码。在本设计中由于计数器cnt0在不停地计数,永远不停止的,因此可以认为其加1条件是一直有效的,可写成:assign add_cnt0==1。通过前面分析可以得出,每数100个时钟后,输出一个值,因此其计数数量为100。
这里有同学会提出疑问:加1条件的概念是什么?这里以停车位来进行比喻,一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。反之则可以认为停车位编号的加1条件就是:对应位置上没有石头,它可以继续的进行编号:assign add_cnt0 = “没有石头”。因此如果在设计中计数器一直没有阻碍地进行计数工作,就可以认为加1条件是一直有效的。
确定好了加1条件和计数数量后开始进行代码编写。相信各位往常都是一行行输入代码,但是至简设计法有一个小技巧,可以为大家编写代码省去不少时间,并且一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分做成了模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。
在命令模式下输入“:Mdyjsq”,点击回车后就调出了对应模板,如下图所示。随后再将本案例中的变量填到模板里面,就可以得到完整正确的计数器代码。
综上所述,计数器cnt0的加1条件是“1”,结束条件是“数到100个”,得到计数器cnt0的完整代码如下: | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin cnt0 <= 0; end else if(add_cnt0) begin if(end_cnt0) cnt0 <= 0; else cnt0 <= cnt0+1 ; end end assign add_cnt0 = 1; assign end_cnt0 = add_cnt0 && cnt0 == 100 -1 ; |
再来确定addr信号的代码表示,已知每100个时钟后addr会加1。因此addr本身其实也是一个计数器,其计数数量为8,由于每数100个时钟addr加1,因此其加1条件是“数到100个时钟”,即end_cnt0。这里继续选择调用至简设计法代码模板,在命令模式下输入“:Mdyjsq”后点击回车,调出计数器模板,将“add_cnt1”和“end_cnt1”补充完整,其具体代码如下: | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin addr<= 0; end else if(add_addr) begin if(end_addr) addr<= 0; else addr<= addr+1 ; end end assign add_addr = end_cnt0; assign end_addr = add_addr&&addr == 8 -1 ; |
3.3 CIC滤波器设计
3.3.1 新建FPGA工程
打开软件“Quartus”,点击“File”菜单下的“New Project Wizard”,如下图所示。
弹出“Introduction”界面后点击“Next”,如下图所示。
设置工程目录,工程名,顶层模块名。工程目录设置为D:\mdy_book\cic_prj,工程名和顶层模块名均为cic_prj,如下图所示。填写完毕后直接点击“Next”。
在设置新工程类型界面,选择“Empty project”选项建立空白工程,如下图所示,随后点击“Next”。 图3.14-15QUARTUS设置新建工程类型界面
错误!未找到引用源。接着是添加文件界面,此时不选择任何文件,直接点击“Next”,如下图所示。 图3.14-16QUARTUS添加文件到新工程界面
在选择芯片型号界面选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,完成后直接点击“Finish”,如下图所示。 图3.14-17QUARTUS设置新工程的芯片类型
3.3.2 FPGA生成CIC IP核建立工程后,在软件“Quartus”中“IP catalog”这一界面下选择“DSP”目录下“Filter”的“FIR II”选项,如下图所示。 图3.14-18IP Catalog中查找CIC IP核
进入此界面后将新生成的fir滤波器IP核路径设置为:D:\mdy_book\cic_prj,“entity name”处填写:my_cic,如下图所示。随后点击“OK”进入FIR滤波器设置界面。
CIC IP核的设置界面如图3.14- 20所示,此界面需要进行以下设置: Filter Type:选择Interpolator,表示插值滤波器; Rate change factor:填写4,表示4倍插值; output Rounding Method:选择Truncation,表示输出的结果要截断; Output data width:选择8。表示输出结果要截断为8位; 其它选项默认,点击窗口右下角的“Generate Hdl”。
选择后会弹出如下窗口,注意选择文件为“Verilog”文件,其余不进行操作,随后点击“Generate”。 图3.14-21设置CIC IP核的文件类型和路径
出现下图提示则表示CIC IP核生成成功,点击Finish关闭CIC滤波器生成窗口。
如果出现以下提示,就表示需要手动将刚才生成的IP核加到本工程。
在“Project”菜单中选择“Add/Remove File to Project”,弹出文件窗口。
点击右上角的
按钮,在弹出来的窗口中,双击选择D:\mdy_book\cic_prj\my_cic\synthesis目录下的my_cic.qip文件。点击“Add”添加成功,关闭本窗口。
3.3.3 例化CIC IP核
用软件“GVIM”打开D:\mdy_book\cic_prj\my_cic\synthesis\my_cic.v文件,生成的FIR IP核文件如下图所示。 图3.14-26CIC IP核的模块和输入输出信号
my_fir模块的各个信号的描述见下表。 表3.14 –3 my_fir模块的各信号描述 信号名 | | | | | | | | | | | | | | | | | | | | | | | 输入数据错误指示信号。实在想不出有啥错误情况,所以此处直接填0。 | | | | | | | | | | | | | | | | FIR滤波器输出错误指示信号。由于输入没错误,输出也不会有错误,所以可以忽略该信号,例化时不连接。 | | | | 下游模块准备好信号。当下游模块准备好时,本IP核才会输出一个数据。 可以利用此信号来控制IP核数据输出的频率。 |
由于滤波器的输入数据和输出数据都是有符号数(补码的形式,-128~127),但是正弦信sin_data是无符号数(0~255)。因此需要将sin_data变成有符号数后再发送给FIR进行滤波。定义转换后的信号为cic_din,该信号位宽为8位。将sin_data减去128转换成有符号数,即cic_din = sin_data - 128。
生成CIC IP核后需要对其进行例化才能使进行使用,例化名为u_my_cic,cic的输出数据信号命名为cic_dout。所谓例化,则是需要把CIC IP核的信号连接到对应的滤波器上。虽然这些信号是一样的,但是其在不同模块上的命名是不同的,如果不进行连接,模块与模块之间互相不知道对方的存在。因此需要将两个相同的信号连接在一起来达成同步工作的效果。
在设计过程中还需要控制CIC IP核的输出,使每个数据都能等间隔输出数据。由于CIC滤波器每100个时钟输入一个数据,CIC是4倍速率,因此其输出是25个时钟一个数据,即每25个时钟向out_ready接口输送一个有效信号。此时需要一个计数器cnt1来计时25个时钟,由于该计数器在不断工作永不停止,因此该计数器加1条件是“1”,结束条件是“数到25个”。这里可以继续调用模板,在命令模式下输入“:Mdyjsq”后点击回车,调出对应模板后将“add_cnt1”和“end_cnt1”补充完整,得到cnt1的代码为: | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin cnt1 <= 0; end else if(add_cnt1) begin if(end_cnt1) cnt1 <= 0; else cnt1 <= cnt1+1 ; end end assign add_cnt1 = 1; assign end_cnt1 = add_cnt1 && cnt1 == 25-1 ; |
有了这些信号后就可以进行CIC IP核的例化。CIC IP核例化具体代码如下: | assign cic_din = sin_data - 128; my_cicu_my_cic( .in_error (0 ), .in_valid (end_cnt0 ), .in_ready ( ), .in_data (cic_din ), .out_data (cic_dout ), .out_error( ), .out_valid (cic_dout_vld), .out_ready (end_cnt1 ), .clk (clk ), .reset_n (rst_n ) ); |
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是直接输出滤波后的信号cic_dout。需要注意的是cic_dout是有符号数(范围是-128~127),所以要转为无符号数(0~255)。假设转换后的信号为cic_dout2,则cic_dout2 = cic_dout + 128。由于DA的通道2的输出电压与dac_db是成反比例线性关系,所以dac_db都是通过(255-cic_dout2)得到。写出dac_db的代码表示如下: | assign cic_dout2 = cic_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 - cic_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型”就可以了。
cnt0是用always产生的信号,因此类型为reg。cnt0计数的最大值为99,需要用7根线表示,即位宽是7位。
关于信号位宽的获取,在这里至简设计法分享一个非常实用的技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽。利用这一方法将cnt0的最大计数值99输入到计算器中,如下图所示,可以看到其位宽为7。
add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [6:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; |
cnt1是用always产生的信号,因此类型为reg。cnt1计数的最大值为24,需要用5根线表示,即位宽是5位。 add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [4:0] cnt1 ; wire add_cnt1 ; wire end_cnt1 ; |
addr是用assign设计的,因此类型为wire。其值最大为7,需要用3根线表示,位宽为3。编辑模式下输入“Reg3”调用至简设计法模板,将其补充完整;
add_addr和end_addr都是用assign方式设计的,因此类型为wire;并且其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | wire [2:0] addr ; wire add_addr ; wire end_addr ; |
sin_data是用always设计的,因此类型为reg;其最大值为255,需要用8根线表示,位宽为8,编辑模式下输入“Reg8”调用至简设计法模板,补充完整后得到代码表示如下: cic_din是用assign设计的,因此类型为wire,其位宽为8,编辑模式下输入“Wire8”调用至简设计法模板,补充完整后得到代码表示如下: cic_dout是例化模块的输出,非always设计的,因此类型为wire。其位宽为8,编辑模式下输入“Wire8”调用至简设计法模板,补充完整后得到代码表示如下: cic_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 cic_prj( clk , rst_n , dac_mode , dac_sleep , dac_clka , dac_da , dac_wra , dac_clkb , dac_db , dac_wrb );
input clk ; input rst_n ; 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 ;
reg [ 6:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; reg [ 4:0] cnt1 ; wire add_cnt1 ; wire end_cnt1 ;
reg [ 2:0] addr ; wire add_addr ; wire end_addr ; reg [7:0] sin_data ; wire [7:0] cic_din ; wire [7:0] cic_dout ; wire [7:0] cic_dout2 ;
reg [7:0] dac_da ; wire dac_sleep ; wire dac_wra ; wire dac_clka ; wire dac_mode ;
reg [7:0] dac_db ; wire dac_wrb ; wire dac_clkb ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin cnt0 <= 0; end else if(add_cnt0) begin if(end_cnt0) cnt0 <= 0; else cnt0 <= cnt0+1 ; end end assign add_cnt0 = 1; assign end_cnt0 = add_cnt0 && cnt0 == 100 -1 ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin addr<= 0; end else if(add_addr) begin if(end_addr) addr<= 0; else addr<= addr+1 ; end end assign add_addr = end_cnt0; assign end_addr = add_addr&&addr == 8 -1 ;
assign cic_din = sin_data - 128;
my_cicu_my_cic( .in_error (0 ), .in_valid (end_cnt0 ), .in_ready ( ), .in_data (cic_din ), .out_data (cic_dout ), .out_error( ), .out_valid (cic_dout_vld), .out_ready (end_cnt1 ), .clk (clk ), .reset_n (rst_n ) );
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin cnt1 <= 0; end else if(add_cnt1) begin if(end_cnt1) cnt1 <= 0; else cnt1 <= cnt1+1 ; end end assign add_cnt1 = 1; assign end_cnt1 = add_cnt1 && cnt1 == 25-1 ;
always @(*)begin case(addr) 0:sin_data = 8'h7F ; 1:sin_data = 8'hDA ; 2:sin_data = 8'hFE ; 3:sin_data = 8'hD8 ; 4:sin_data = 8'h7D ; 5:sin_data = 8'h23 ; 6:sin_data = 8'h01 ; 7:sin_data = 8'h2A ; default:sin_data = 8'h7F; endcase end
assign cic_dout2 = cic_dout + 128;
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_mode = 1; assign dac_clka = ~clk ; assign dac_wra = dac_clka;
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dac_db <= 0; end else begin dac_db <= 255 - cic_dout2; end end
assign dac_clkb = ~clk ; assign dac_wrb = dac_clkb;
endmodule |
第4节 综合与上板
4.1 添加文件
上一节中已经介绍了新建工程的过程,这里就不再赘述了。现在打开软件“Quartus”,在“Project”菜单中选择“Add/Remove File to Project”,如下图所示,随后会弹出文件窗口。
点击右上角的
按钮,在弹出来的窗口中双击选择D:\mdy_book\cic_prj目录下的cic_prj.v文件。点击“Add”添加成功后关闭本窗口,添加文件成功界面如下图所示。
4.2 综合
编译界面如下图所示,在菜单栏中选中Processing后选择Start Compilation,开始对整个工程进行编译和综合。
当出现如下图所示界面时就说明编译综合成功。
4.3 配置管脚
下面需要对相应管脚进行配置。如下图所示,在菜单栏中,选中“Assignments”,然后选择“Pin Planner”,随后就会弹出配置管脚的窗口。
在配置窗口最下方中的“location”一列,参考表3.2-2信号和管脚关系,按照表3.14- 1中最右两列配置好FPGA管脚,配置管理来源参见管脚配置环节,最终配置的结果如图3.13-34。配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。
表3.14–1 信号和管脚关系
图3.14-34配置管脚成功界面
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\cic_prj。在该目录中,新建一个名为cic_prj.v的文件,并用GVIM打开,开始编写代码。
分析设计目标确定顶层信号。信号和硬件的对应关系图见下表所示。 表3.14 - 2信号和管脚关系 写出顶层信号。 | module cic_prj( clk , rst_n , dac_mode , dac_sleep , dac_clka , dac_da , dac_wra , dac_clkb , dac_db , dac_wrb ); |
声明输入输出属性。 | input clk ; input rst_n ; 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.1.2 正弦信号设计
设计正弦信号sin_data信号: | always @(*)begin case(addr) 0: sin_data = 8'h7F; 1: sin_data = 8'hDA; 2: sin_data = 8'hFE; 3: sin_data = 8'HD8; 4: sin_data = 8'h7D; 5: sin_data = 8'h23; 6: sin_data = 8'h1; 7: sin_data = 8'h2A; endcase end |
设计表示输出值的计时器cnt0: | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin cnt0 <= 0; end else if(add_cnt0) begin if(end_cnt0) cnt0 <= 0; else cnt0 <= cnt0+1 ; end end assign add_cnt0 = 1; assign end_cnt0 = add_cnt0 && cnt0 == 100 -1 ; |
设计addr信号: | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin addr<= 0; end else if(add_addr) begin if(end_addr) addr<= 0; else addr<= addr+1 ; end end assign add_addr = end_cnt0; assign end_addr = add_addr&&addr == 8 -1 ; |
5.1.3 CIC滤波器设计
步骤一:新建FPGA工程
打开软件“Quartus”,点击“File”菜单下的“New Project Wizard”,如下图所示。
错误!未找到引用源。 12 QUARTUS新建工程命令界面
弹出“Introduction”界面后点击“Next”:
设置工程目录(目录为D:\mdy_book\cic_prj,工程名和顶层名为cic_prj)后点击“Next”。
选择“Empty project”后点击“Next”。 图3.14-42QUARTUS设置新建工程类型界面
此界面不选择任何文件,点击“Next”。 图3.14-16QUARTUS添加文件到新工程界面
对芯片型号进行选择,在“Device family”选项中选择“Cyclone ⅣE”,“Available devices”选项下选择“EP4CE15F23C8”,随后点击“Next”。 图3.14-17 QUARTUS设置新工程的芯片类型
步骤二:FPGA生成CIC IP核
建立工程后,在软件“Quartus”的“IP catalog”这一界面中选择“DSP”目录下“Filter”的“FIR II”选项,如下图所示。 图3.14-18 IP Catalog中查找CIC IP核
选择路径为D:\mdy_book\cic_prj,在“entity name”处填写my_cic,随后点击“OK”:
随后对IP核类型进行设置。“Filter Type”选择“Interpolator”,“Rate change factor”处填写4,“output Rounding Method”选择“Truncation”,“Output data width”选择8,其它选项默认后点击“Generate Hdl”。
选择文件为“Verilog”文件后点击“Generate”。 图3.14-43设置CIC IP核的文件类型和路径
出现下图提示则表示CIC IP核生成成功,点击Finish关闭CIC滤波器生成窗口。
如果出现以下提示,就表示需要手动将刚才生成的IP核加到本工程。
此时需要在“Project”菜单中选择“Add/Remove File to Project”,随后弹出文件窗口。
点击右上角的
按钮,在弹出来的窗口中双击选择D:\mdy_book\cic_prj\my_cic\synthesis目录下的my_cic.qip文件。点击“Add”添加成功后关闭本窗口。
步骤三:例化CIC IP核
打开D:\mdy_book\cic_prj\my_cic\synthesis\my_cic.v文件,生成的CIC IP核文件如下图所示。 图3.14-26 CIC IP核的模块和输入输出信号
设计表示IP核输出的计数器cnt1: | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin cnt1 <= 0; end else if(add_cnt1) begin if(end_cnt1) cnt1 <= 0; else cnt1 <= cnt1+1 ; end end assign add_cnt1 = 1; assign end_cnt1 = add_cnt1 && cnt1 == 25-1 ; |
将CIC IP核例化: | assign cic_din = sin_data - 128; my_cicu_my_cic( .in_error (0 ), .in_valid (end_cnt0 ), .in_ready ( ), .in_data (cic_din ), .out_data (cic_dout ), .out_error( ), .out_valid (cic_dout_vld), .out_ready (end_cnt1 ), .clk (clk ), .reset_n (rst_n ) ); |
5.1.4 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 cic_dout2 = cic_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 - cic_dout2; end end |
设计信号dac_clkb,dac_wrb: | assign dac_wrb = dac_clkb ; assign dac_clkb = ~clk ; |
至此,模块主体已经完成。接下来将module补充完整。
5.1.5 信号定义
首先来定义信号类型。cnt0、add_cnt0和end_cnt0的信号定义如下: | reg [6:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; |
cnt1、add_cnt1和end_cnt1的信号定义如下: | reg [4:0] cnt1 ; wire add_cnt1 ; wire end_cnt1 ; |
addr、add_addr和end_addr的信号定义如下: | wire [2:0] addr ; wire add_addr ; wire end_addr ; |
sin_data的信号定义如下: cic_din的信号定义如下: cic_dout的信号定义如下: cic_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 cic_prj( clk , rst_n , dac_mode , dac_sleep , dac_clka , dac_da , dac_wra , dac_clkb , dac_db , dac_wrb );
input clk ; input rst_n ; 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 ;
reg [ 6:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; reg [ 4:0] cnt1 ; wire add_cnt1 ; wire end_cnt1 ;
reg [ 2:0] addr ; wire add_addr ; wire end_addr ; reg [7:0] sin_data ; wire [7:0] cic_din ; wire [7:0] cic_dout ; wire [7:0] cic_dout2 ;
reg [7:0] dac_da ; wire dac_sleep ; wire dac_wra ; wire dac_clka ; wire dac_mode ;
reg [7:0] dac_db ; wire dac_wrb ; wire dac_clkb ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin cnt0 <= 0; end else if(add_cnt0) begin if(end_cnt0) cnt0 <= 0; else cnt0 <= cnt0+1 ; end end assign add_cnt0 = 1; assign end_cnt0 = add_cnt0 && cnt0 == 100 -1 ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin addr<= 0; end else if(add_addr) begin if(end_addr) addr<= 0; else addr<= addr+1 ; end end assign add_addr = end_cnt0; assign end_addr = add_addr&&addr == 8 -1 ;
assign cic_din = sin_data - 128;
my_cicu_my_cic( .in_error (0 ), .in_valid (end_cnt0 ), .in_ready ( ), .in_data (cic_din ), .out_data (cic_dout ), .out_error( ), .out_valid (cic_dout_vld), .out_ready (end_cnt1 ), .clk (clk ), .reset_n (rst_n ) );
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin cnt1 <= 0; end else if(add_cnt1) begin if(end_cnt1) cnt1 <= 0; else cnt1 <= cnt1+1 ; end end assign add_cnt1 = 1; assign end_cnt1 = add_cnt1 && cnt1 == 25-1 ;
always @(*)begin case(addr) 0:sin_data = 8'h7F ; 1:sin_data = 8'hDA ; 2:sin_data = 8'hFE ; 3:sin_data = 8'hD8 ; 4:sin_data = 8'h7D ; 5:sin_data = 8'h23 ; 6:sin_data = 8'h01 ; 7:sin_data = 8'h2A ; default:sin_data = 8'h7F; endcase end
assign cic_dout2 = cic_dout + 128;
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_mode = 1; assign dac_clka = ~clk ; assign dac_wra = dac_clka;
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dac_db <= 0; end else begin dac_db <= 255 - cic_dout2; end end
assign dac_clkb = ~clk ; assign dac_wrb = dac_clkb;
endmodule |
5.2 综合与上板
5.2.1 添加文件
上一节中已经介绍了新建工程的过程,这里就不再赘述了。现在打开软件“Quartus”,在“Project”菜单中选择“Add/Remove File to Project”,弹出文件窗口。
点击右上角的
按钮,在弹出来的窗口中,双击选择D:\mdy_book\cic_prj目录下的cic_prj.v文件。点击“Add”添加成功后关闭本窗口。
5.2.2 综合
在菜单栏中选中Processing,然后选择Start Compilation,开始对整个工程进行编译和综合。
当出现下图的界面,则就说明编译综合成功。
5.2.3 配置管脚
配置管脚界面如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,随后就会弹出配置管脚的窗口。
在配置窗口“location”配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。
5.2.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“Start Compilation”,再次对整个工程进行编译和综合,如下图所示。
当出现图3.2-19QUARTUS编译成功标志则说明编译综合成功。
5.2.5 连接开发板
完成编译后开始进行上板调试操作,开发板连接方式如下图所示。将电源接上开发板,USB BLASTER一端连接到JTAG插口,另一端连到PC的USB接口。将开发板上的AD接口和DA与示波器的两个通道相连,连接完成后再将电源打开。
5.2.6 上板
在“Quartus”的“Task”窗口中,右键“Program Device”选择“Open”进入烧录界面,如下图所示。
默认会选中文件output/fir_prj.sof,在Hardware Setup旁边会显示USB-Blaste,如下图所示。
进度条中提示成功后可在示波器中观察相应现象。
第6节 扩展练习
至此,整个插值滤波器设计就分享完毕了,学会了整个设计之后可以展开思考,基于原理不变的情况下多做一些尝试,比如尝试输出三角波,这样帮助同学们更深刻的掌握本案例。也欢迎有更多思路和想法的同学在至简设计法论坛上进行交流讨论。
|