马上注册,看完整文章,学更多FPGA知识。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
温馨提示:明德扬2023推出了全新课程——逻辑设计基本功修炼课,降低学习FPGA门槛的同时,增加了学习的趣味性,并组织了考试赢积分活动
本案例的编号为:002400000076,如果有疑问,请按编号在下面贴子查找答案:MDY案例交流【汇总贴】_FPGA-明德扬科教 (mdy-edu.com)
本文为明德扬原创及录用文章,转载请注明出处
大家好,近期我们会连载《FPGA至简设计原理与应用》一书,有兴趣的同学可以学习,也希望大家可以对我们的书提出宝贵的意见和建议。
《FPGA至简设计原理与应用》书籍连载索引目录
http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=989
读过的朋友可积极在贴后留言,书籍正式出版时,我们会从留言者中挑选20位幸运读者,幸运读者可获潘老师亲笔签名书籍一本。
注:手机浏览可能格式会乱,建议用电脑端进行浏览。
问题答疑
【问题1】本案例中的公式:y=(sin(2π*i/128)+1)*255/2,当i=2时,代入计算器去算,结果为127.6,与书中不符合。答:注意本公式中的π,不是3.14,而是180度,所以要把π换算成180来计算。
第一章 信号发生器和DA转换
本文档编号:000400000024
需要看对应的视频,请点击视频编号:002400000451
1、本文档讲述FPGA产生正弦波数据,通过DA转换,然后传到示波器进行观察
2、801开发板使用
第1节 项目背景
1.1 信号发生器
信号发生器是一种能提供各种频率、输出电平的电信号的设备,又称信号源或振荡器。其在各种电信系统的振幅、频率、传输特性等电参数以及元器件的特性与参数的测量中得到了广泛的应用。
直接数字式频率合成器(Direct Digital Synthesizer,DDS)是一项关键的数字化技术,其将先进的数字处理理论与方法引入频率合成技术,通过数/模转换器将一系列数字量形式的信号转换成模拟量形式的信号。
DDS的输入是频率控制字,其用来控制相位累加器每次增加的相位值,也相当于一个步进值。上图就是一个典型的DDS工程,DDS工程一般可包括相位累加器、信号转换器和DAC三部分,其具体功能为:
相位累加器:每来一个时钟脉冲,在原来相位值的基础上会加上步进得到最新的相位值,随后将新的相位值输出给信号转换器。N位的相位累加器由N位加法器和N位累加寄存器组成,其具体工作过程为:每来一个时钟脉冲,N位加法器将频率控制字K与N位累加寄存器输出的累加相位数据相加,并把相加后的结果送至累加寄存器的输入端。累加寄存器一方面将上一时钟周期作用后产生的新相位数据反馈到加法器的输入端,使加法器在下一时钟的作用下继续与频率控制字K相加;另一方面将这个值作为取样地址送入幅度/相位转换电路,幅度/相位转换电路根据这个地址输出相应的波形数据。最后经D/A转换器和 LPF将波形数据转换成所需要的模拟波形。
信号转换器:一般转换器内部有一片ROM,其事先保存了要产生波形的幅度值。根据输入的相位值可以输出该相位值所对应的信号幅值。例如,可将一个完整周期的正弦波等距离分成128份并保存到转换器的ROM当中,当相位值为0时,则输出相位为0所应对的幅度值,当相位为100时,则输出相位为100所对应的幅度值。
1.2 DA转换
至简设计法教学板上的DAC芯片型号为DA9709,这是一款双通道,位宽8bit的芯片,速率高达125MSPS,能够满足常用信号发生器、滤波信号输出等需求。实际位置如下所示。
该芯片采用48引脚小型LQFP封装,具有高交流、直流性能。DA9709原理图及其与FPGA的连接图如下所示。 图3.12-3教学板DA9709原理图
图3.12-4教学板DA9709与FPGA连接图
其中DAC芯片与FPGA相连的信号为:DA_CLKA、DA_CLKB、DAC_DA7~0、DAC_DB7~0、DA_WRA、DA_WRB 、DAC_MODE和DAC_SLEEP。 表3.12-1DAC芯片与FPGA相连管脚及其作用 DA9709管脚 | | | | | | | DA9709的工作模式。当为高电平时,表示双通道模式,此时通过DA、DB两组信号分别独立控制两个通道。 当为低电平时,表示使用交织模式,此时仅使用一组信号来控制两个通道。 本案例使用双通道模式,也就是固定为高电平。 | | | | 睡眠模式。 当高电平时,DA9709进入睡眠模式,此时不工作。 当低电平时,DA9709是正常工作模式。 本案例使用正常工作模式,固定为低电平。 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
1.3 DA9709的时序
DA9709的控制时序如下图所示。
在双通道模式中通道A和通道B如同两个独立的DA芯片。其中DA_CLKA、DAC_DA7~0、DAC_WR_A用于控制通道A,DA_CLKB、DA_DB7~0、DA_WRB用于控制通道B。
以控制通道A为例,按照时序图的要求先将数据输出到DAC_DA7~0,经过ts时间后将DAC_WRA和DA_CLKA拉高,此时DAC可以将数据锁存,经过一段时间后输出数据所对应的电流,其经过电路转换可以变成对应的模拟电压值。
时序图中要注意以下几点(数据手册有详细说明):
1. DA_CLKA超前或者同时与DA_WRA由0变1。
2. 查询数据手册可知:图中tS(DAC_WRA上升沿前数据保持不变的时间)、tH(DAC_WRA上升沿后数据保持不变的时间)、tLPW(DAC_WRA的高电平时间)、tCPW(DAC_CLKA的高电平时间)等参数如图3.12- 6所示。从图中可以看到tS的时间为至少2ns;tH时间为至少1.5ns;tLPW、tCPW时间为至少3.5ns。在进行设计时可以将这些数值设置的稍大一些从而留有一定的裕量。 通道B的时序要求与通道A相同,仅控制信号有所区别。
在设计之前,除了了解数字接口时序还需要清楚数字量与模拟电压幅值之间的转换关系。至简设计法教学板的DA9709的两个通道均支持0.48~2.2V的电压输出,这一输出电压与输入数据的关系可用下面的公式进行表示:
通道A的输出电压 = -1.72 * (DAC_DA /255) + 2.2 V 通道B的输出电压 = -1.72 * (DAC_DB/255) + 2.2 V 表3.12-2通道输出电压与输入数据的对应关系
由公式可见,输出电压与DAC_DA/B的值成线性反比例关系,最低电压为0.48V,最高为2.2V。需要注意的是,此范围的电压是由于外围硬件电路特性的原因导致的,不同电路对应的电压范围有所不同。
第2节 设计目标
在正式学习本设计之前,带领同学们先来确定设计的功能目标。在此再次强调明确设计目标的重要性,至简设计法旨在用最直接最简洁的方法来达到预期功能。想要实现这一效果就必须非常清晰地明确功能预期。如果不清楚最终想要达到什么效果,那么后续的讨论就没有任何的意义。
本设计中采用采样率大于100M的示波器,将其连接到开发板上,控制DA输出不同频率的正弦波,示波器显示出对应波形。输出方式如下:
连续输出2个周期为6.25MHz的正弦波,每个正弦波输出8个采样点; 连续输出2个周期为3.125MHz的正弦波,每个正弦波输出16个采样点; 连续输出2个周期为1.5625MHz的正弦波,每个正弦波输出32个采样点; 连续输出2个周期为781250Hz的正弦波,每个正弦波输出64个采样点; 连续输出2个周期为390625Hz的正弦波,每个正弦波输出128个采样点; 连续输出2个周期为195312.5Hz的正弦波,每个正弦波输出128个采样点。 正弦波的最高电压是2.2V,最低电压是0.48V。
开发板连接示意图如下所示。
图3.12-7开发板连接方式
示波器的连接示意图如下所示。
图3.12-8信号发生器连接效果图
信号发生器产生正弦波效果图如下所示,每种频率的正弦波连续出现2次且正弦波的周期逐渐变大。
图3.12-9信号发生器产生的正弦波效果图
FPGA内部设定频率会与示波器显示频率存在一定偏差,这属于正常现象。以下是各个频率下的波形显示效果:
示波器显示频率为6.25MHz时正弦波:
图3.12-10f=6.25MHz效果图
示波器显示频率为3.125MHz时正弦波:
图3.12-11f=3.125MHz效果图
示波器显示频率为1.5625MHz时正弦波:
图3.12-12f=1.5625MHz效果图
示波器显示频率为781250Hz时正弦波:
图3.12-13f=781250Hz效果图
示波器显示频率为390625Hz时正弦波:
图3.12-14f=390625Hz效果图
示波器显示频率为195312.5Hz时正弦波:
图3.12-15f=195312.5Hz效果图
第3节 设计实现
本节依旧会分享详细的步骤与解析以便初学者在学会原理的情况下掌握设计方法。希望读者学会的不仅是单个案例的设计,还有操作过程中的一些设计理念及原理。希望同学们可以通过本书掌握独立设计并完成工程的能力,将至简设计法运用到今后的工作中去。已经掌握了原理,只想复习回顾设计步骤的同学也可以选择直接阅读第五节简化版步骤分享。在此读者可根据个人的情况选择学习方式。
3.1 顶层接口
新建目录:D:\mdy_book\dds_da,并该目录中新建一个名为dds_da.v的文件。用GVIM打开后开始编写代码。这里再次强调,建议初学者不要对路径名和文件名进行其它更改,请按照本书提供的名称进行设置。因为更改后可能会出现中文路径、空格路径等非法路径问题,或者有些文件名更改后会出现报错的现象,而初学者并不能很好的发现并解决问题。因此建议先按照要求更名保存,在多做了几个工程、熟悉了各个步骤后,再进行自己更名的操作。希望同学们不要想当然,一时的细心会避免后续出现的一系列问题。
分析设计目标可知在本设计中需要FPGA产生控制DA9709的电压,令DA9709的通道A产生所对应的正弦波,其具体要控制的信号包括MODE、SLEEP、CLK1、WRT1、DB7~0P1管脚。根据设计目标的要求,整个工程需要以下信号:
使用时钟信号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的写数据。
综上所述,本设计一共需要7个信号,时钟信号clk,复位信号rst_n,dac_mode、dac_sleep、dac_clka、dac_wra和dac_da;其中dac_da信号位宽为1,其他信号的位宽都是1。信号和硬件的对应关系如下表所示。 表3.12-3信号和管脚关系 将module的名称定义为dds_da,将以上7个信号写入在模块接口列表中,具体顶层代码如下所示: - module dds_da(
- clk ,
- rst_n ,
- dac_mode ,
- dac_clka ,
- dac_da ,
- dac_wra ,
- dac_sleep
- );
复制代码
随后声明信号的输入输出属性。这里需要声明对于FPGA来说这一信号属于输入还是输出,如果是输入信号,则声明其为input,如果是输出信号,则声明其为output。在本设计中,由于clk是外部的晶振输入给FPGA的,因此在FPGA中clk是1位的输入信号input;同样地,rst_n是外部按键给FPGA的,因此在FPGA中rst_n也是1位的输入信号input;dac_da是FPGA产生用来控制DA9709的信号,因此其为8位的输出信号output,同理,dac_mode,dac_clka,dac_wra,dac_sleep是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 ;
复制代码
3.2 信号设计
首先,来分析一下DAC的输出,以频率为195312.5Hz的正弦波为例进行讲解。在这个频率下,一个完整的正弦波持续时间是T=1/f1/195312.5Hz=0.00000512s=5120ns。设计目标中要求每个波形周期要等间隔输出128个点,因此每隔5120ns/128=40ns就要输出一个点。由于工程输入的时钟是50MHz,其周期是20ns,故每隔40ns/20ns=2个时钟就要输出一个点。确定了输出数据的周期后需要明确每个点输出数值,从而实现输出指定的电压幅值。为了简化思想,可以先假定输出数字值与DAC输出电压成正比。
众所周知,正弦信号的数学表达式是y=sin(x),周期为2π。由于其等间隔分为128个点,所以可表示为y=sin(2π*i/128)。由于这款DAC采用8位原码输入,不能有负数,因此将标准正弦信号向上平移一个坐标将其变为y=sin(2π*i/128)+1。这里还需要注意一点,当输出最大值255时应正好对应最大电压幅值,即保证y的最大值为255,可将公式表示成:y=(sin(2π*i/128)+1)*255/2其中i的范围为0~127,将该公式命名为公式一,按照公式可以得到128个点的幅度值如下表所示。
表3.12-4DAC输出采样点对应幅度值
根据输出电压与输出数字量数值的关系可知两者成反比,因此DAC_DA = 255 - sin_data。这就是最终输出给DA芯片的数据值,即dac_da信号。
综上所述,想要产生频率为195312.5Hz的正弦波,即每隔2个时钟FPGA输出信号dac_da,需要先每隔1个点从表3.12- 4选出sin_data,再通过(255-sin_data)得到dac_da,共输出128个点,组成一个最大电压值2.2V,最小电压值0.48V的正弦波。将同样的操作重复两次即可连续输出2个周期正弦波。
按照相同的方法来分析频率为6.25MHz的正弦波。可知一个频率为6.25MHz的正弦波周期等于1/6.25MHz=0.0000016s=160ns。设计目标中要求一个周期输出8个点,即每隔160ns/8=20ns输出一个点。由于工程的输入时钟是50MHz,周期是20ns,本设计中每隔20ns/20ns=1个时钟就要输出一个点。因此产生频率为6.25MHz的正弦波需要每隔1个时钟输出一个电压值,共输出8个点组成一个正弦波,根据设计要求本设计需要连续产生2个相同频率正弦波。
接下来计算每个点对应的幅度值。开发板的输出电压在0.48~2.2V之间,最低值是0.48V,最高值是2.2V。将一个标准的正弦波向上移动1个单位,使其范围变成0~2后等间隔取8个点采样点i,则采样点间隔为2*π*i/8,使用代码表示为2*pi/8。用8位信号sin_data表示相应采样点的幅度值,其表示方式为:sin_data=(sin(2*pi/8)+1)*(255/2)=(sin(2*pi*16/128) + 1) * (255/2),i的范围为0~7,此处将该公式命名为公式二。
计算出的公式二对应值如下表所示。可以看出:sin_data值在表3.12- 4中有所体现,但并不是表3.12- 4的连续值,而是每间隔16个点进行一次取值,共取8个。观察设计目标可以发现最多需要出现的采样点数量为128,在前文中其对应幅度值均已列出,在整个设计中可以参考此表格,在不同的波形中取对应的采样点。
表3.12-5公式二得到的对应幅度值
综上所述,想要产生频率为6.25MHz的正弦波,即每隔1个时钟FPGA输出信号dac_da。需要先每隔16个点从表3.12- 4取值得到sin_data,再通过(255-sin_data)得到dac_da。共输出8个点组成一个正弦波,每次连续输出2个正弦波。
掌握了设计方法则按照这个思路分析所有需求如下:
连续输出2个周期为195312.5Hz的正弦波,其中每个正弦波输出128个采样点,相当于每隔2个时钟FPGA输出信号dac_da,一共输出128个点组成一个正弦波,每次连续产生2个正弦波。dac_da的产生方式为,从表3.12- 4得到sin_data,再通过(255-sin_data)得到dac_da。
连续输出2个周期为390625Hz的正弦波,其中每个正弦波输出128个采样点,相当于每隔1个时钟FPGA输出信号dac_da,一共输出128个点组成一个正弦波,每次连续产生2个正弦波。dac_da的产生方式为,每隔1个点从表3.12- 4取值得到sin_data,再通过(255-sin_data)得到dac_da。
连续输出2个周期为781250Hz的正弦波,其中每个正弦波输出64个采样点,相当于每隔1个时钟FPGA输出信号dac_da,一共输出64个点组成一个正弦波,每次连续产生2个正弦波。dac_da的产生方式为,每隔2个点从表3.12- 4取值得到sin_data,再通过(255-sin_data)得到dac_da。
依次类推,连续输出2个周期为6.25MHz的正弦波,其中每个正弦波输出8个采样点,相当于每隔1个时钟FPGA输出信号dac_da,一共输出8个点组成一个正弦波,每次连续产生2个正弦波;dac_da的产生方式为,每隔16个点从表3.12- 4取值得到sin_data,再通过(255-sin_data)得到dac_da。
可以看出取值方法是有规律的,按照至简设计法中的变量法思想,可以将上文所述功能概括为:每隔x个时钟输出一个数值,一共输出y个点组成一个正弦波,每次连续产生2个正弦波。根据设计目标可以得知一共需要产生6种不同频率的正弦波,因此需要一个计数器来进行种类计数。
下面按照变量思路来逐句设计相应代码。从“每隔x个时钟输出一个数值”这一规律可知,需要使用一个计数器cnt0来进行时钟计数。
至简设计法中计数器只考虑两个因素:加1条件和计数数量,只要确定相应逻辑,就可以完成计数器代码设计。首先确定计数器cnt0的加1条件:由于该计数器在不停地计数,永远不停止,因此可以认为其加1条件是一直有效的,可写成assign add_cnt0==1。
这里可能会有读者提出疑问:加1条件的概念是什么?这里以停车位来进行比喻,一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。反之则可以认为停车位编号的加1条件就是:对应位置上没有石头,其可以继续的进行编号,即assign add_cnt0 = “没有石头”。因此如果在设计中计数器一直没有阻碍地进行计数工作,就可以认为加1条件是一直有效的。
接下来确定计数器cnt0的计数数量,从前文总结得到的规律“每x个时钟输出一个数值”可知计数器每数x个完成一次计数,因此这里cnt0的计数数量为x。
确定好了加1条件和计数数量后开始进行代码编写。相信各位往常都是一行行输入代码,但是至简设计法有一个小技巧,可以为编写代码省去不少时间,并且一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分做成了模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。
在命令模式下输入“:Mdyjsq”,点击回车后就调出了对应模板,如下图所示。将本案例中的变量填到模板里面,就可以得到完整正确的计数器代码。
图3.12-16至简设计法调用计数器代码模板
补充完整后得到计数器cnt0的代码如下。 - always @(posedge clk or negedge rst_n)begin
- if(!rst_n)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== x-1;
复制代码
由“一共输出y个点”可以确定还需要一个计数器cnt1来计数输出的点数。已知在x个时钟的时候输出对应的y个点,因此cnt1的加1条件是“数到x个时钟”,即end_cnt0。该计数器数完对应的y后就可以进入下一个循环。继续调用至简设计法模板,在命令模式下输入“:Mdyjsq”,点击回车,就调出了对应模板,将“add_cnt1”和“end_cnt1”补充完整,得到计数器cnt2的代码如下: - always @(posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- cnt1 <= 0;
- end
- else if(add_cnt1)begin
- if(end_cnt1)
- cnt1 <= 0;
- else
- cnt1 <= cnt1 + 1;
- end
- end
- assign add_cnt1 = end_cnt0;
- assign end_cnt1 = add_cnt1 && cnt1== y-1;
复制代码
接下来,由“每个要产生要连续产生2个正弦波”可以看出需要一个计数器cnt2来计数2个正弦波。已知一个正弦波由y个点组成,数完y个点后进入到下一个正弦波,因此cnt2的加1条件是“数到y个”,即“end_cnt1”。该计数器一共要数2个正弦波,随后进入到下一个波形,因此其计数数量为2。继续调用至简设计法模板,在命令模式下输入“:Mdyjsq”,点击回车后调出对应模板,将“add_cnt1”和“end_cnt1”补充完整,得到计数器cnt2的代码如下: - always @(posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- cnt2 <= 0;
- end
- else if(add_cnt2)begin
- if(end_cnt2)
- cnt2 <= 0;
- else
- cnt2 <= cnt2 + 1;
- end
- end
- assign add_cnt2 = end_cnt1;
- assign end_cnt2 = add_cnt2 && cnt2== 2-1;
复制代码
cnt2是产生每2个相同波形所需要的计数器,但整个设计从全局来看一共需要产生6种不同频率的正弦波,因此还需要一个计数器cnt3来进行6种正弦波的计数。每产生2个相同正弦波后进入到下一个波形,因此cnt3的加1条件是“产生完2个正弦波”,即“end_cnt2”。由设计目标可知需要数6次才能完成所有的波形,因此其结束条件是“数到6个”。调用至简设计法模板,在命令模式下输入“:Mdyjsq”,点击回车调出对应模板,将“add_cnt1”和“end_cnt1”补充完整,得到计数器cnt3的代码如下: - always @(posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- cnt3 <= 0;
- end
- else if(add_cnt3)begin
- if(end_cnt3)
- cnt3 <= 0;
- else
- cnt3 <= cnt3 + 1;
- end
- end
- assign add_cnt3 = end_cnt2;
- assign end_cnt3 = add_cnt3 && cnt3== 6-1;
复制代码
完成了所有的计数器后来考虑一下其中的变量x和y,前文中变量x和y进行了定义,其中x表示从表中每x个数据后输出一个数据,y表示一个正弦波周期的采样点数。x和y的值都根据正弦波的频率发生变化,即都与计数器cnt3数值有关。根据题意和至简设计法中的变量设计方法可将cnt3与对应的x和y值都列出来,得到x和y的代码如下: - always @(*)begin
- if(cnt3==0)begin
- x = 1 ;
- y = 8 ;
- end
- else if(cnt3==1)begin
- x = 1 ;
- y = 16 ;
- end
- else if(cnt3==2)begin
- x = 1 ;
- y = 32 ;
- end
- else if(cnt3==3)begin
- x = 1 ;
- y = 64 ;
- end
- else if(cnt3==4)begin
- x = 1 ;
- y = 128 ;
- end
- else begin
- x = 2 ;
- y = 128 ;
- end
- end
复制代码
此时可以将其它的信号补充完整。首先来看信号dac_da。前文有讲到dac_da信号都是通过(255-sin_data)得到的,因此可以确定dac_da的代码表示,调用至简设计法模板,在编辑模式下输入“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
复制代码
接下来是sin_data信号。sin_data是根据实际情况计算出来的,如果依次去计算每个信号就比较复杂,因此这里使用查表法,直接将计算好的值列出来,sin_data可以直接在表3.12- 4中进行选择。由于不同的频率值下其选择的方式有所不同,可以定义一个选择信号addr,通过控制好addr从而便捷地获得sin_data的值。sin_data的具体代码表示如下: 那么addr信号又该如何设计呢?逻辑信号addr用以控制选择数据的地址,而不同频率正弦波的地址控制方式不同。根据设计目标可以知道频率为6.25MHz(cnt3=0)是每16个点选择一次;频率为3.125MHz(cnt3=1)是每8个点选择一次;频率为1.5625MHz(cnt3=2)是每4个点选择一次;频率为781250Hz(cnt3=3)是每2个点选择一次;频率为390625Hz(cnt3=4)是每1个点选择一次;频率为195312.5Hz(cnt3=5)是每1个点选择一次,共计128个采样点。
前文设计中已经完善了所有的计数器,可用cnt3表示不同的频率,cnt1表示需要发送的第几个数,其代码表示如下:
当cnt3==0 时,addr = cnt1*16; 当cnt3==1时,addr = cnt1*8; 当cnt3==2时,addr = cnt1*4; 当cnt3==3时,addr = cnt1*2; 当cnt3==4时,addr = cnt1*1; 当cnt3==5时,addr = cnt1*1。
翻译成RTL后可以得到addr的代码表示如下: - always @(*)begin
- if(cnt3==0)
- addr = cnt1 * 16;
- else if(cnt3==1)
- addr = cnt1 * 8;
- else if(cnt3==2)
- addr = cnt1 * 4;
- else if(cnt3==3)
- addr = cnt1 * 2;
- else if(cnt3==4)
- addr = cnt1 * 1;
- else
- addr = cnt1 * 1;
- end
复制代码
最后完成dac_sleep信号的设计。由于DA一直工作,因此dac_sleep始终为0;为了满足tS的时间要求,可以让dac_clka = ~clk;dac_wra信号可以与dac_clka相同;dac_mode信号用于控制DA9709的模式,当其为高电平时表示双通道模式,此时DA、DB两组信号分别独立控制两个通道。至简设计法的理念是:在能实现功能的前提下,设计越简单越好。因此本设计选用双通道模式,令dac_mode始终为1。代码表示如下: - assign dac_sleep = 0 ;
- assign dac_wra = dac_clka ;
- assign dac_clka = ~clk ;
- assign dac_mode = 1 ;
复制代码
至此,主体程序已经完成。
3.3 信号定义
接下来是将module补充完整,首先来定义信号类型。很多读者会搞不清楚reg和wire该如何判断,在进行判断时总产生多余的联想,比如认为reg就是寄存器,wire是线;或者认为reg会综合成寄存器,wire不会综合成寄存器。在此再次强调,这些联想其实和信号是reg型还是wire型都并无关系,因此在进行信号类型的判断时不需要做任何的联想,只要记住一个规则“用always实现的是reg型,其他都是wire型”就可以了。
cnt0是用always产生的信号,因此类型为reg。cnt0计数的最大值为15,需要用5根线表示,即位宽是5位。
关于信号位宽的获取,在这里至简设计法分享一个非常实用的技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽。利用这一方法将cnt0的最大计数值15输入到计算器中,如下图所示,可以看到其位宽为5。
add_cnt1和end_cnt1都是用assign方式设计的,因此其类型都为wire,并且其值都是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码如下: - reg [4:0] cnt0 ;
- wire add_cnt0 ;
- wire end_cnt0 ;
复制代码
cnt1是用always产生的信号,因此其类型为reg。cnt1计数的最大值为127,需要用8根线表示,即位宽是8位。
add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire,并且其值是0或者1,用1根线表示即可。
编辑模式下输入“Reg8”和“Wire1”调用至简设计法模板,补充完整后得到代码如下: - reg [7:0] cnt1 ;
- wire add_cnt1 ;
- wire end_cnt1 ;
复制代码
cnt2是用always产生的信号,因此类型为reg。cnt2计数的最大值为7,需要用3根线表示,即位宽是3位。
add_cnt2和end_cnt2都是用assign方式设计的,因此类型为wire。并且其值是0或者1,用1根线表示即可。
编辑模式下输入“Reg3”和“Wire1”调用至简设计法模板,补充完整后得到代码如下: - reg [2:0] cnt2 ;
- wire add_cnt2 ;
- wire end_cnt2 ;
复制代码
cnt3是用always产生的信号,因此类型为reg。cnt3计数的最大值为5,需要用3根线表示,即位宽是3位。
add_cnt3和end_cnt3都是用assign方式设计的,因此类型为wire。并且其值是0或者1,用1根线表示即可。
编辑模式下输入“Reg3”和“Wire1”调用至简设计法模板,补充完整后得到代码如下: - reg [2:0] cnt3 ;
- wire add_cnt3 ;
- wire end_cnt3 ;
复制代码
变量x,y是用always方式设计的,因此类型为reg;x最大值为2,需要2根线来表示,y最大值为128,需要用8根线表示,即位宽为8。编辑模式下输入“Reg2”“Wire8”调用至简设计法模板,补充完整后得到代码如下: - reg [1:0] x ;
- reg [7:0] y ;
复制代码
addr是用always设计的,因此类型为reg。其值最大为127,需要用7根线表示,即位宽为7,代码表示如下: sin_data是用always设计的,因此类型为reg。其最大值为255,需要用8根线表示,因此位宽为8。编辑模式下输入“Reg8”调用至简设计法模板,补充完整后得到代码如下: 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 ;
复制代码
完成代码后,在最后一行写下endmodule。 至此,整个代码的设计工作已经完成。完整版的工程代码如下:
第4节 综合与上板
4.1 新建工程
打开软件Quartus Ⅱ,点击“File”下拉列表中的New Project Wzard...新建工程选项,如下图所示。
图3.12-18Quartus新建工程
随后会出现Quartus新建工程介绍,如下图所示,直接点击“Next”。
file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml9760\wps13.jpg
此时出现的是工程文件夹、工程名、顶层模块名设置界面,如图3.12- 20所示。设置目录为:D:/mdy_book/dds_da,工程名和顶层名为dds_da。再次强调,为了避免初学者在后续操作中发生程序跳出未知错误的问题,强烈建议设置的文件目录和工程名称与本书保持一致。设置完成后点击“Next”。
图3.12-20QUARTUS新建工程设置名称
新建工程类型设置如下图所示,选择“Empty project”,然后点击“Next”。
图3.12-21QUARTUS新建工程类型
接下来进行文件添加,其界面如下图所示。点击右侧的“Add”按钮,选择之前写好的“dds_da.v”文件,可以看到界面下方会显示出文件,随后点击“Next”。
图3.12-22QUARTUS添加文件
图3.12- 23为芯片选择页面,选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,之后点击“Next”。
图3.12-23QUARTUS选择芯片型号
图3.12- 24为QUARTUS设置工具界面,不必做任何修改,直接点击“Next”。
图3.12-24QUARTUS设置工具界面
QUARTUS新建工程汇总界面如下图所示,可以看到新建工程的汇总情况,点击“Finish”,完成新建工程。
图3.12-25QUARTUS新建工程汇总界面
4.2 综合
新建工程步骤完成后,就会出现如下图所示的 QUARTUS新建工程后界面。
图3.12-26QUARTUS新建工程后界面
点击编译按钮,可以对整个工程进行编译。编译成功的界面如下图所示。
图3.12-27QUARTUS编译后界面
4.3 配置管脚
下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,随后就会弹出配置管脚的窗口。
图3.12-28QUARTUS配置管脚选项
在配置窗口最下方中的“location”一列,参考信号和管脚关系,按照表3.12- 3中最右两列配置好FPGA管脚,配置管理来源参见管脚配置环节,最终配置的结果如图3.12-29。配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。
表3.12-3信号和管脚关系
图3.12-29 QUARTUS配置管脚
4.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“Start Compilation”,再次对整个工程进行编译和综合,如图3.12- 30所示。
图3.12-30QUARTUS编译选项
当出现如下图所示的 QUARTUS编译成功标志,就说明编译综合成功。
图3.12-31QUARTUS编译成功标志
4.5 连接开发板
完成编译后开始进行上板调试操作,按照下图的方式将下载器接入电脑USB接口,接上开发板电源,将开发板ADDA接口与示波器相连接,然后按下开发板下方蓝色开关。
图3.12-32开发板连接图
4.6 上板
打开QUARTUS界面,单击界面中的“file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml9760\wps27.jpg”,则会弹出配置界面。在界面中点击“add file”添加“.sof”文件后点击“Start”,会在“Progress”出现显示进度。
图3.12-33QUARTUS界面
QUARTUS下载程序界面如下图所示,当进度条到100%提示成功后,即可在示波器上观察到相应的现象。
图3.12-34QUARTUS下载程序界面
下载完成后,如果操作无误此时可以在示波器上看到对应的波形。如果没有显示成功,就需要返回检查一下连接是否到位,接口有没有连接错误,代码是否编写正确。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定会达到想要的效果。
第5节 简化版步骤分享
这里依旧会分享简化版的步骤,方便掌握基础原理后进行反复操作复习。
5.1 设计实现
5.1.1 顶层接口
新建目录:D:\mdy_book\dds_da。在该目录中,新建一个名为dds_da.v的文件,用GVIM打开后开始编写代码。
确定顶层信号。信号和硬件的对应关系图见表3.12- 3。 表3.12-3信号和管脚关系 写出顶层代码: - module dds_da(
- clk ,
- rst_n ,
- dac_mode ,
- dac_clka ,
- dac_da ,
- dac_wra ,
- dac_sleep
- );
复制代码
声明输入输出属性: - input clk ;
- input rst_n ;
- output dac_mode ;
- output dac_clka ;
- output [ 8-1:0] dac_da ;
- output dac_wra ;
- output dac_sleep ;
复制代码
5.1.2 信号设计
计算采样点对应幅度值: 表3.12- 4DAC输出采样点对应幅度值
分析设计目标可知:每隔x个时钟输出一个电压值,共输出y个点组成一个正弦波,每次连续产生2个正弦波。
设计表示数了几个时钟的计数器cnt0: - always @(posedge clk or negedge rst_n)begin
- if(!rst_n)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== x-1;
复制代码
设计输出了几个点的计数器cnt1: - always @(posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- cnt1 <= 0;
- end
- else if(add_cnt1)begin
- if(end_cnt1)
- cnt1 <= 0;
- else
- cnt1 <= cnt1 + 1;
- end
- end
- assign add_cnt1 = end_cnt0;
- assign end_cnt1 = add_cnt1 && cnt1== y-1;
复制代码
设计计数2个正弦波的计数器cnt2: - always @(posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- cnt2 <= 0;
- end
- else if(add_cnt2)begin
- if(end_cnt2)
- cnt2 <= 0;
- else
- cnt2 <= cnt2 + 1;
- end
- end
- assign add_cnt2 = end_cnt1;
- assign end_cnt2 = add_cnt2 && cnt2== 2-1;
复制代码
设计产生6种不同频率正弦波的计数器cnt3: - always @(posedge clk or negedge rst_n)begin
- if(!rst_n)begin
- cnt3 <= 0;
- end
- else if(add_cnt3)begin
- if(end_cnt3)
- cnt3 <= 0;
- else
- cnt3 <= cnt3 + 1;
- end
- end
- assign add_cnt3 = end_cnt2;
- assign end_cnt3 = add_cnt3 && cnt3== 6-1;
复制代码
设计变量x和y: - always @(*)begin
- if(cnt3==0)begin
- x = 1 ;
- y = 8 ;
- end
- else if(cnt3==1)begin
- x = 1 ;
- y = 16 ;
- end
- else if(cnt3==2)begin
- x = 1 ;
- y = 32 ;
- end
- else if(cnt3==3)begin
- x = 1 ;
- y = 64 ;
- end
- else if(cnt3==4)begin
- x = 1 ;
- y = 128 ;
- end
- else begin
- x = 2 ;
- y = 128 ;
- end
- end
复制代码
设计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
复制代码
设计sin_data信号: 设计addr信号: - always @(*)begin
- if(cnt3==0)
- addr = cnt1 * 16;
- else if(cnt3==1)
- addr = cnt1 * 8;
- else if(cnt3==2)
- addr = cnt1 * 4;
- else if(cnt3==3)
- addr = cnt1 * 2;
- else if(cnt3==4)
- addr = cnt1 * 1;
- else
- addr = cnt1 * 1;
- end
复制代码
设计dac_sleep、dac_wra、dac_clka、dac_mode信号: - assign dac_sleep = 0 ;
- assign dac_wra = dac_clka ;
- assign dac_clka = ~clk ;
- assign dac_mode = 1 ;
复制代码
至此,主体程序已经完成,接下来是将module补充完整。
5.1.3 信号定义
首先来定义信号类型。cnt0、add_cnt0、end_cnt0的信号定义如下: - reg [4:0] cnt0 ;
- wire add_cnt0 ;
- wire end_cnt0 ;
复制代码
cnt1、add_cnt1和end_cnt1的信号定义如下: - reg [7:0] cnt1 ;
- wire add_cnt1 ;
- wire end_cnt1 ;
复制代码
cnt2、add_cnt2和end_cnt2的信号定义如下: - reg [2:0] cnt2 ;
- wire add_cnt2 ;
- wire end_cnt2 ;
复制代码
cnt3、add_cnt3和end_cnt3的信号定义如下: - reg [2:0] cnt3 ;
- wire add_cnt3 ;
- wire end_cnt3 ;
复制代码
变量x,y的信号定义如下: - reg [1:0] x ;
- reg [7:0] y ;
复制代码
addr的信号定义如下: sin_data的信号定义如下: 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 ;
复制代码
在代码的最后一行写下endmodule 完整版的工程代码如下:
5.2 综合与上板
5.2.1 新建工程
下一步新建工程和上板查看现象。打开软件“Quartus”,在“Project”菜单中选择“Add/Remove File to Project”,弹出文件窗口。
直接点击“Next”。
图3.12-19 Quartus新建工程介绍
此时会出现的是工程文件夹、工程名、顶层模块名设置界面(目录为:D:/mdy_book/dds_da,工程名和顶层名为dds_da),完成设置后点击“Next”。
图3.12-20 QUARTUS新建工程设置名称
选择“Empty project”后点击“Next”。
图3.12-21 QUARTUS新建工程类型
点击右侧的“Add”按钮后选择“dds_da.v”文件,随后点击“Next”完成文件添加。
图3.12-22 QUARTUS添加文件
对芯片型号进行选择,在“Device family”选项中选择“Cyclone ⅣE”,“Available devices”选项下选择“EP4CE15F23C8”随后点击“Next”。
图3.12-23 QUARTUS选择芯片型号
直接点击“Next”。
图3.12-24 QUARTUS设置工具界面
点击“Finish”,完成新建工程。
图3.12-25 QUARTUS新建工程汇总界面
5.2.2 综合
新建工程后界面如下图所示,点击“编译”。
图3.12-26 QUARTUS新建工程后界面
编译成功界面如下图所示。
图3.12-27 QUARTUS编译后界面
5.2.3 配置管脚
进行管脚配置,在菜单栏中点击“Assignments”后点击“Pin Planner”,此时弹出配置管脚的窗口。
图3.12-28 QUARTUS配置管脚选项
在配置窗口“location”根据信号和管脚关系配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。
图3.12-29 QUARTUS配置管脚
5.2.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中选择“Processing”,随后点击“Start Compilation”再次进行综合。
图3.12-30 QUARTUS编译选项
出现 QUARTUS 编译成功标志时表示此次编译成功。 图3.12-31 QUARTUS编译成功标志
5.2.5 连接开发板
按照如下图所示的方法将下载器接入电脑USB接口,接上开发板电源,开发板ADDA接口与示波器相连接,然后按下开发板下方蓝色开关。
图3.12-32开发板连接图
5.2.6 上板
打开 QUARTUS 界面后单击“file:///C:\Users\xkdn\AppData\Local\Temp\ksohtml9760\wps45.jpg
”图标。
图3.12-33QUARTUS界面
点击“add file”,添加.sof文件,完成添加后点击“Start”,在“Progress”会显示进度。进度条显示“100%”为成功,可观察示波器现象。
图3.12-34 QUARTUS下载程序界面
第6节 扩展练习
至此,DA转换设计已经分享完毕,相信同学们已经可以完全掌握这一设计。那么在掌握这项工程后可以多做一些思考,尝试在工程原理不变的基础上进行一定的数据调整,试着改变产生波的周期和采样点等参数,挑战一下独立完成多个设计。也欢迎有更多思路和想法的同学前往至简设计法论坛进行交流讨论。
|