马上注册,看完整文章,学更多FPGA知识。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
温馨提示:明德扬2023推出了全新课程——逻辑设计基本功修炼课,降低学习FPGA门槛的同时,增加了学习的趣味性,并组织了考试赢积分活动
本案例的编号为:001600000117,如果有疑问,请按编号在下面贴子查找答案:MDY案例交流【汇总贴】_FPGA-明德扬科教 (mdy-edu.com)
本文为明德扬原创及录用文章,转载请注明出处
大家好,近期我们会连载《FPGA至简设计原理与应用》一书,有兴趣的同学可以学习,也希望大家可以对我们的书提出宝贵的意见和建议。
《FPGA至简设计原理与应用》书籍连载索引目录
http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=989
读过的朋友可积极在贴后留言,书籍正式出版时,我们会从留言者中挑选20位幸运读者,幸运读者可获潘老师亲笔签名书籍一本。
注:手机浏览可能格式会乱,建议用电脑端进行浏览。
第三篇 FPGA至简设计项目实践
第七章 数字时钟
本文档编号:001600000019
需要看对应的视频,请点击视频编号:001600000042
1.至简原理与应用配套的案例文档
2.使用6个数码管实现数字时钟功能,与数字时钟相同,该功能可以显示00:00:00到23:59:59范围的时间。
3. ALTERA入门学习案例文档
第1节 项目背景
数字时钟是采用数字电路技术实现时、分、秒计时显示的装置,可以用数字同时显示时,分,秒的精确时间并实现准确校时,具备体积小、重量轻、抗干扰能力强、对环境要求高、高精确性、容易开发等特性。与传统表盘式机械时钟相比,数字时钟具有更高的准确性和直观性,由于没有机械装置,其使用寿命更长。本设计基于FPGA开发板的数码管功能进行数字时钟的设计,在前面章节中已经详细讲解了数码管的工作原理,这里就不再进行赘述,有需要详细学习可以回到前面章节进行学习。本案例将详细介绍用至简设计法实现数字时钟功能的设计。
第2节 设计目标
按照至简设计法的设计特色,在正式学习本设计之前首先应明确设计目标。设计目标是整个设计的核心灵魂,后续的每个步骤与操作都是围绕设计目标进行展开。至简设计法的中心思想很简单,就是用最直接简洁的方法来进行工程设计。而明确设计目标则是为了让后续的每个阶段都有意义,避免进行不必要的工程工程,少走弯路。对于初学者来说,更应该在学习的过程中养成好习惯,才能在职业生涯中受益。所以再次强调在开始设计前一定要将设计目标分析透彻,认真思考本次设计的最终目的和实现效果,然后再围绕目标展开设计。 本工程使用6个数码管实现数字时钟功能。与数字时钟相同,其可以显示00:00:00到23:59:59范围的时间。 数字时钟的上板效果如下图所示,想要观看上板演示视频效果的读者朋友可以登陆至简设计法官方网站学习:www.mdy-edu.com/xxxx。
图3.7-1数字时钟效果图
第3节 设计实现
接下来进入设计的实现阶段,本书会按照步骤和原理分析分享案例的具体实现方法,考虑到初学者的需要,该部分的内容会比较详细。如果基础知识掌握得比较牢靠,只想学习此设计步骤,也可以跳过此部分,直接进入第五节的简化版步骤分享。但建议初学者一定按照详细分析的内容进行学习,不要选择捷径,只有打好基础,掌握基础知识,才可以从容的独立完成项目设计。
3.1 顶层信号
新建目录:D:\mdy_book\my_shizhong,并在该目录中,新建一个名为my_shizong.v的文件。用GVIM打开后开始编写代码。这里建议初学者按照本书中提供的文件路径以及文件名进行设置,避免后续出现未知错误。 第一步应确定顶层信号。开发板上共8个数码管,分析设计目标可知,本次设计的功能是控制其使其中2个数码管常灭,其余6个数码显示不同的数字。FPGA通过控制位选信号来控制8个数码管,即应输出一个8位的位选信号,将其设定为seg_sel。其中seg_sel[0]对应数码管0,seg_sel[1]对应数码管1,以次类推,seg_sel[7]对应数码管7。同样地,数码管控制方法中讲解过:只要控制每个数码管上的7个子段就可以实现数码管上不同数字的显示,即控制段选信号实现数码管的数字显示。本设计中不需要用到h子段,共需要7个子段,因此FPGA需要输出一个7位的段选信号,将其设定为seg_ment,seg_ment[6]~segm_ment[0]分别对应数码管的abcdefg(注意对应顺序)。当然,除位选信号和段选信号外,设计中进行工程控制的时钟信号和复位信号也同样必不可少。 综上所述,本设计一共需要4个信号:时钟信号clk,复位信号rst_n,输出的位选信号seg_sel和输出的段选信号seg_ment。信号和硬件的对应关系如下表所示。 表3.7 - 1信号和管脚关系
将module的名称定义为my_shizhong,已知该模块有4个信号:clk、rst_n、seg_sel和seg_ment,在顶层信号代码中需要将与外部相连接的输入/输出信号列出,从而实现信号与管脚的连接。具体顶层代码如下: | module my_shizhong( clk , rst_n , seg_sel , seg_ment ); |
随后声明信号的输入输出属性,在模块中需要声明这一信号对于FPGA来说属于输入信号还是输出信号。信号若为输入的话则声明其为input,若为输出声明则声明其为output。在本设计中,由于clk是外部的晶振输入给FPGA的,因此在FPGA中clk为1位的输入信号input;同样地,rst_n是外部按键给FPGA的,因此在FPGA中rst_n也为1位的输入信号input;seg_sel是FPGA控制数码管亮灭的信号,因此seg_sel是8位的输出信号output;seg_ment是FPGA控制数码管显示数字内容的信号,因此seg_ment是7位的输出信号output。按照以上分析将输入输出端口定义补充完整,其具体代码如下: | input clk ; input rst_n ; output[7:0] seg_sel ; output[6:0]seg_ment; |
3.2 信号设计在设计信号之前,先按照至简设计法的思路来进行架构设计。根据设计目标先来分析要实现的功能,8个数码管中有6个一直处于亮的状态,其余2个为灭的状态。在不同的时刻时,相应位数的数码管会一直变化,比如每一秒时间过后,表示秒钟个位的数字发生变化。每十秒之后,表示秒钟十位的数字会发生变化。每60秒之后,表示分钟个位的数字发生变化,以此类推。随着时间的变化这些数字按照既定规律进行变化,从而达到显示正确时间的效果。
从分析中可以看出每个数字的变化条件是不同的,有的数字是每秒都在变化,有的数字则一小时才变化一次。对于初学者来说这项设计可能实现起来比较复杂,但只要遵循至简设计法的思想就可以简化成最直接最简单的思路。下面请各位将时钟的概念抛开,将每个数码管单独看作是不同时间不同位置显示的数码管,比如数码管0显示秒钟个位,数码管1显示秒钟十位,数码管2显示分钟个位。在不同的时刻,数码管显示的数字不同,这样一来就很好理解了。
本设计用m_g,m_s,f_g,f_s,s_g,s_s分别表示秒钟个位、秒钟十位、分钟个位、分钟十位、小时个位和小时十位所表示的数字值,m_g_s,m_s_s,f_g_s,f_s_s,s_g_s,s_s_s分别表示秒钟个位、秒钟十位、分钟个位、分钟十位、小时个位和小时十位所表示数码管的段选值。
数码管0显示的是秒钟个位值,翻译成信号即seg_sel的值为8’b1111_1110,seg_ment的值为:m_g_s。数码管1显示秒钟十位,即seg_sel的值为8’b1111_1101,seg_ment的值为m_s_s。以此类推,数码管5显示小时十位,即seg_sel的值为8’b1101_1111,seg_ment的值为s_s_s。输出信号的时序波形图如下所示。
图3.7-2输出信号时序
从波形图中可以看出在不同时刻下,seg_ment和seg_sel的值会发生变化。那么多久会变化一次呢?这里需要学习一个新知识,即数码管动态扫描原理。
数码管动态显示接口是应用最为广泛的显示方式之一。动态驱动是将数码管的8个显示笔划"a,b,c,d,e,f,g,h"的同名端连在一起,每个数码管的公共极COM需增加由各自独立I/O线控制的位选通控制电路。当要输出某一字形码时,所有数码管都会接收到相同的字形码,但究竟是哪个数码管会显示出字形取决于单片机对位选通COM端电路的控制。只需将显示数码管的选通控制打开,该位就会显示出字形,而没有选通的数码管并不会点亮。综上所述,动态驱动是通过分时轮流控制各数码管的COM端,使各个数码管轮流受控显示。
通俗来讲就是将8个数码管一样的端口连在一起,通过一个信号进行控制。即若传输一个控制数码管a段点亮的信号,8个数码管也都会收到这一信号。发送信号时令需要点亮的数码管显出字形,不需要的数码管不显示字形。例如需要数码管0显示数字“5”,就应发出显示数字“5“对应的段选信号,这时8个数码管都会收到数字“5”的段选信号,此时只将数码管0的选通控制打开,就只有数码管0显示了数字“5”,其余的数码管没有选通就不会亮。在不同时刻轮流控制各数码管,就可以达到数码管轮流受控显示的效果,这也就是数码管动态扫描的原理。
那么这种轮流显示的模式下如何实现6个数码管的同时显示呢?在轮流显示的过程中,每位数码管的点亮时间为1~2ms。由于人的视觉暂留现象及发光二极管的余辉效应,尽管各数码管并非同时点亮,但只要扫描的速度足够快,人们观察到的就是一组稳定的显示数据,而不会有闪烁感。在设计中将数码管点亮时间设置为1~2ms,就可以达到动态显示和静态显示相同的效果,但动态显示不仅能够节省大量的I/O端口,而且功耗更低。
综上所述,本设计中将数码管的刷新时间定为2ms,定义时间后的输出时序波形图如下所示: 图3.7-3带时间信息的输出时序
根据波形图分析可以得到本设计的计数器架构:本设计一共需要两个计数器,一个计时器用来计算2ms时间,另一个计数器用来计算显示次数。
先来讨论计算2ms时间的计数器cnt0。至简设计法的设计规则中有讲过:计数器的设计只考虑两个因素:加1条件和计数数量。只要确定相应逻辑,就能完成计数器代码设计。
首先确定计数器cnt0的加1条件:由于该计数器在不停地计数,永不停止,因此可以认为其加1条件一直有效,可写成:assignadd_cnt0==1。
此处可能有同学存在疑惑加1条件的概念是什么?这里以停车位来进行比喻,一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。反之则可以认为停车位编号的加1条件就是:对应位置上没有石头,其可以继续的进行编号,即assign add_cnt0 = “没有石头”。因此如果在设计中计数器一直没有阻碍地进行计数工作,就可以认为加1条件是一直有效的。
接着讨论计数器cnt0的计数数量:本工程的工作时钟是50MHz,即周期为20ns,计数器计数到第2_000_000/20=100_000个时就代表2毫秒时间到了。
确定好了加1条件和计数数量后开始进行代码编写。相信各位往常都是一行行输入代码,但是至简设计法有一个小技巧,可以节省编写代码的时间,并且一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分做成了模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。
打开GVIM,在命令模式下输入“:Mdyjsq”,点击回车后调出了对应模板,如下图所示。之后再将本案例中的变量填到模板里面,就可以得到了完整正确的代码。
图3.7-4至简设计法调用计数器代码模板
补充完整后得到计数器cnt0的代码如下所示。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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==100_000-1 ; |
接着讨论表示显示次数的计数器cnt1,根据波形图可以发现,完成2毫秒计数就进行下一次显示,因此该计数器的加1条件为end_cnt0。每次循环中,6个段选信号m_g_s、m_s_s、f_g_s、f_s_s、s_g_s和s_s_s依次显示,再进入下一个循环,因此该计数器要数6次。继续调用至简设计法模板,在命令模式下输入“:Mdyjsq”,点击回车就调出了对应模板,将“add_cnt1”和“end_cnt1”补充完整,得到该计数器的代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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==6-1 ; |
确定两个计数器后来思考一下输出信号seg_sel的变化。根据设计目标可知,在第1次显示时,FPGA输出值为8’hfe;第2次显示时,FPGA输出值为8’hfd;以此类推,在第6次显示时,FPGA输出值为8’hdf。本设计中用信号cnt1来代替第几次显示,即当cnt1==0的时候,输出值为8’hfe;当cnt1==1的时候输出值为8’hfd;以此类推,当cnt1==5的时候输出值为8’hdf。根据分析将其翻译成代码,可以调用至简设计法模板,在编辑模式下输入“Shixu2”后将其补充完整,得到信号seg_sel的代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_sel<= 8'hfe; end else if(cnt1==0) begin seg_sel<= 8'hfe; end else if(cnt1==1) begin seg_sel<= 8'hfd; end else if(cnt1==2) begin seg_sel<= 8'hfb; end else if(cnt1==3) begin seg_sel<= 8'hf7; end else if(cnt1==4) begin seg_sel<= 8'hef; end else if(cnt1==5) begin seg_sel<= 8'hdf; end end |
以上代码可以正确实现seg_sel的信号功能,不论是从实现角度还是资源角度来说都可以满足设计要求。然而,在这里可以将其进行进一步的概括,得到化简版的代码。
根据设计目标可知,第1个数码管0亮时seg_sel输出为b8’b1111_1110;第2个数码管1亮时seg_sel输出为b8’b1111_1101;第3个数码管2亮时seg_sel输出为b8’b1111_1011;第4个数码管3亮时seg_sel输出为b8’b1111_0111;第5个数码管4亮时seg_sel输出为b8’b1110_1111;第6个数码管5亮时seg_sel输出为b8’b1101_1111;根据规律可以发现每一个数码管点亮信号对应的数字“0”都比前一个信号向左移1位。在此可统一设为将8’b1向左移位后再取反的值,将该值赋给seg_sel。通过观察可以看出每次的左移位数与cnt1的值相等,因此可以得到以下代码,其中第6行即表示将8’b1向左移位后再取反的值赋给seg_sel。
读者朋友也可以代入数值计算一下,假如此刻cnt1等于0,那么8’b1<<0的结果是8’b0000_0001,取反的值为8’hfe即b8’b1111_1110;假如cnt1等于3,那么8’b1<<3的结果为8’b000_1000,取反后的结果为8’hf7即8’b1111_0111。该设计方法与第一种设计得出的结果相同,但代码经过了简化更加清晰。这里同样可以在编辑模式下输入“Shixu2”,调出至简设计法模板,补充完整后得到代码如下: | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_sel<= 8'hfe ; end else begin seg_sel<= ~(8'b1<<cnt1) ; end end |
下面来讨论一下输出信号seg_ment的变化。seg_ment的值是m_g、m_s、f_g、f_s、s_g和s_s等数值分别译码成数码管显示的信号m_g_s、m_s_s、f_g_s、f_s_s、s_g_s和s_s_s。需要根据不同的显示次数选择相应的数码管信号,可以采用两种设计方法实现这一功能。
第一种方法是将各数值均转换成译码信号,再根据cnt1选择相应的信号赋值给seg_ment,其原理图如下图所示。可以看到设计中先将m_g、m_s、f_g、f_s、s_g和s_s全都译码成对应的数码管显示信号m_g_s、m_s_s、f_g_s、f_s_s、s_g_s和s_s_s,随后再根据对应的cnt1进行选择,确定所需的seg_ment值。
图3.7-5译码电路实现方法一
译码电路实现的另一种方法是在译码之前先选择需要的数据,只对需要的数据进行译码从而得到seg_ment值,其原理图如下图所示。先用cnt1进行选择,只将选择出的信号译码成数码管信号。
图3.7-6译码电路实现方法二
对比上面两种方法的原理图可以发现:虽然两种可以实现同样的功能,但是第二种方法比第一种少了5个译码电路。由此也可以看出,在设计中想要实现一个功能可以使用很多方法。但设计师需要做的,就是使用最少的资源、较快的速度来实现同样的功能,这也是至简设计法基本的设计理念,同样是FPGA设计的魅力所在。 经过思考分析,本设计选择第二种设计方案,那么就需要设计一个信号sel_data来表示从6个数据中选择进行译码的信号。将sel_data信息补充后的波形图如下所示。 图3.7-7补充sel_data信号时序图
sel_data的值有可能为0~9中的任意数字,这些数字都要转成数码管的段选信号。前面章节有讲过显示数字对应的段选信号值,如下表所示。 表3.7- 20~9对应的数码管段选信号值
至简设计法开发板使用的是共阳数码管。根据上表可写出如下代码。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_ment<= 7'h01; end else if(sel_data==0)begin seg_ment<= 7'h01; end else if(sel_data==1)begin seg_ment<= 7'h4f; end else if(sel_data==2)begin seg_ment<= 7'h12; end else if(sel_data==3)begin seg_ment<= 7'h06; end else if(sel_data==4)begin seg_ment<= 7'h4c; end else if(sel_data==5)begin seg_ment<= 7'h24; end else if(sel_data==6)begin seg_ment<= 7'h20; end else if(sel_data==7)begin seg_ment<= 7'h0f; end else if(sel_data==8)begin seg_ment<= 7'h00; end else if(sel_data==9)begin seg_ment<= 7'h04; end end |
当然这一代码也可以写为case的形式,其具体代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin segment<= 7'h00; end else begin case (sel_data) 0 : segment <= 7'h01; 1 : segment <= 7'h4f; 2 : segment <= 7'h12; 3 : segment <= 7'h06; 4 : segment <= 7'h4c; 5 : segment <= 7'h24; 6 : segment <= 7'h20; 7 : segment <= 7'h0f; 8 : segment <= 7'h00; 9 : segment <= 7'h04; default : segment <= 7'h00; endcase end end |
根据波形图可以分析出sel_data从m_g、m_s、f_g、f_s、s_g和s_s中选取。当cnt1=0,即数码管0显示时,sel_data的值为m_g;当cnt1=1,即数码管1显示时,sel_data的值为m_s;当cnt1=2,即数码管2显示时,sel_data的值为f_g;当cnt1=3,即数码管3显示时,sel_data的值为f_s;当cnt1=4,即数码管4显示时,sel_data的值为s_g;当cnt1=5,即数码管5显示时,sel_data的值为s_s。综上所述,sel_data的代码为: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(*)begin if(cnt1==0) sel_data = m_g; else if(cnt1==1) sel_data = m_s; else if(cnt1==2) sel_data = f_g; else if(cnt1==3) sel_data = f_s; else if(cnt1==4) sel_data = s_g; else sel_data = s_s; end |
接下来设计m_g、m_s、f_g、f_s、s_g和s_s信号,根据常识可知代表秒钟个位的m_g信号值每过一秒会加1,其值在数字0~9中进行循环,具体变化规律如下图所示。 图3.7-8秒钟个位的时序图
分析秒钟个位的波形图可知,秒钟个位数据是0、1、2、3、4、5、6、7、8、9、0、1……这样有规律的进行加1变化。回想一下计数器需要的条件:加1条件和计数数量,可以发现m_g其实可以设计成一个计数器,且该计数器每隔1秒时间加1一次,这里的1秒时间计时也需要一个计数器,可以将其设为cnt2。
首先来讨论用来1秒计时的计数器cnt2。可以看到cnt2是一直在不停计数,永不停止的,即加1条件一直有效,可以写成assign add_cnt2= 1。cnt2要数1秒时间,本工程的工作时钟是50MHz,即周期为20ns,计数器计数到第1_000_000_000/20=50_000_000个时,就表示1秒时间到了,所以cnt2的计数数量是 50_000_000个。在命令模式下输入“:Mdyjsq”,点击回车,就调出了至简设计法计数器模板,补充完整得出cnt2代码表示如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 = 1; assign end_cnt2 = add_cnt2 && cnt2==50_000_000-1 ; |
接着讨论m_g,可以将其视作另外一个计数器。其每隔1秒变化一次,因此它的加1条件是1秒计时完成,即end_cnt2==1。前面中分析过m_g需要进行0~9的循环计数,即计数数量为10个。在命令模式下输入“:Mdyjsq”后点击回车调出至简设计法计数器模板,将其补充完整后得到m_g代码表示如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin m_g<= 0; end else if(add_m_g) begin if(end_m_g) m_g<= 0; else m_g<= m_g+1 ; end end assign add_m_g = end_cnt2; assign end_m_g = add_m_g&&m_g == 10 -1 ; |
在m_g的基础上来思考秒钟十位m_s的变化。根据常识可知秒钟个位进行一个循环后(即0~9秒完成后)秒钟十位的信号值会加1,因此秒钟十位m_s的信号波形图变化情况如下所示。
图3.7-9秒钟十位的时序图
分析波形图可以看到,秒钟十位m_s每隔10秒就会加1,即可将m_s也看做是一个计数器。其加1条件是10秒时间结束,由于m_g的一个循环是十秒,因此m_s的加1条件可写为end_m_g。这里需要注意一下计数器m_s的计数数量并不是10,可以思考一下现实生活中的钟表,一分等于60秒,那么秒钟十位的变化是0、1、2、3、4、5,因此该计数器进行6次计数就会进入到下一个循环,其计数数量为6。综上所述,在命令模式下输入“:Mdyjsq”,点击回车就调出了至简设计法计数器模板,补充完整得出m_s代码表示如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin m_s<= 0; end else if(add_m_s)begin if(end_m_s) m_s<= 0; else m_s<= m_s + 1; end end
assign add_m_s = end_m_g; assign end_m_s = add_m_s&&m_s==6-1 ; |
接下来在秒钟十位m_s的基础上讨论分钟个位f_g,根据实际情况可以得知,每隔60秒即增加一分钟,因此分钟个位f_g的变化波形图如下所示。
图3.7-10分钟个位的时序图
分析波形图可以看出,每隔1分钟即60秒后分钟个位f_g的信号值就会加1。很明显,f_g也是一个计数器,其加1条件是60秒时间结束,即end_m_s。分钟个位的计数器数10个会进入到下一个循环,即计数数量为10。在命令模式下输入“:Mdyjsq”,点击回车,调用至简设计法计数器模板,补充完整得出f_g代码表示如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin f_g<= 0; end else if(add_f_g) begin if(end_f_g) f_g<= 0; else f_g<= f_g+1 ; end end assign add_f_g = end_m_s; assign end_f_g = add_f_g&&f_g == 10 -1 ; |
接下来根据分钟个位f_g思考分钟十位f_s的情况,根据常识可知,每过十分钟分钟十位的信号值会加1,其变化波形图如下所示。
图3.7-11分钟十位的时序图
根据波形图可以看出,f_s分钟个位也是一个计数器,其加1条件为10分钟结束,即end_f_g;分钟十位的计数器计数数量与秒钟十位类似,1小时等于60分,因此分钟十位计数为0、1、2、3、4、5,该计数器数的计数数量为6。综上所述,在命令模式下输入“:Mdyjsq”后点击回车,调用至简设计法计数器模板,补充完整得出f_s代码表示如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin f_s<= 0; end else if(add_f_s)begin if(end_f_s) f_s<= 0; else f_s<= f_s + 1; end end
assign add_f_s = end_f_g; assign end_f_s = add_f_s&&f_s==6-1 ; |
接着根据分钟十位f_s思考小时个位s_g,小时个位s_g的变化波形图情况如下所示。
图3.7-12小时个位的时序图
分析波形图可以明显看出:小时个位s_g每隔1小时即60分钟就会加1,因此s_g也是一个计数器,其加1条件是60分钟时间结束,即end_f_g。
确定了加1条件,接着需要讨论计数数量。根据实际情况可知一天为24小时,可以发现此时小时的个位周期循环并不是前面的0~6,那么小时个位的周期到底应该是多少呢?可能有同学认为是10个,因为小时个位和前面分钟个位、秒钟个位一样有0~9的变化;可能有同学认为是24个,因为一天为24个小时,每天都是24的循环;也可能有同学则会认为是4个,因为小时个位是0~3变化。
下面一起来思考一下现实情况,本书的前文部分中有讲解过:为了简单直接的设计,只单个讨论每一个数码管的显示变化。而这里讨论的是小时的个位,如果此时还认为其周期是24个小时则代表观念还没有转变过来,没有透彻的理解至简设计法的设计理念。
24小时考虑的是小时个位和小时十位两个数码管的显示内容,而在此单纯分析小时个位会发现它以这样的规律存在:0~9,0~9,0~3,0~9,0~9,0~3……。可以看出小时个位的计数数量,有时候是10,有时候是4,即其计数数量在不断发生变化。这里按照至简设计法的变量法,设其周期是x,其具体定义和取值在后文中会进行描述。打开GVIM,在命令模式下输入“:Mdyjsq”,点击回车,调用至简设计法计数器模板,补充完整得出的s_g代码如下所示。 1 2 3 4 5 6 7 8 9 10 11 12 13 | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin s_g<= 0; end else if(add_s_g) begin if(end_s_g) s_g<= 0; else s_g<= s_g+1 ; end end assign add_s_g = end_f_s; assign end_s_g = add_s_g&&s_g == x -1 ; |
最后根据小时个位s_g来思考小时十位s_s,小时十位s_s的变化波形图情况如下所示。
图3.7-13小时十位的时序图
根据波形图可以看出,当小时个位s_g要清零时(注意不是每隔10小时),小时十位s_s会加1,因此s_s也是一个计数器,该计数器加1条件是小时个位清零,即end_s_g。注意这里本书描述的计数器加1条件是s_g清零,而不是每隔10小时。回忆一下s_g的计数数量,其有时是0~9一个周期进行循环,计数数量是10;有时是0~3一个周期循环,计数数量是4,因此该计数器的加1条件是小时个位清零。关于小时十位s_s计数器的计数数量,通过分析可知小时十位每数0、1、2后,计数器进入到下一个循环,因此该计数器的计数数量为3。综上所述,在命令模式下输入“:Mdyjsq”,点击回车,调用至简设计法计数器模板,补充完整得出s_s代码表示如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin s_s<= 0; end else if(add_s_s)begin if(end_s_s) s_s<= 0; else s_s<= s_s + 1; end end
assign add_s_s = end_s_g; assign end_s_s = add_s_s&&s_s==3-1 ; |
最后不要忘记对前文中表示小时个位计数数量的x进行设计。小时个位循环是按照0~9,0~9,0~3,0~9,0~9,0~3……的规律,即其计数单位是10或者是4。那么计数单位什么时候是10,什么时候是4呢?思考一下现实情况,小时个位的情况取决于小时十位的显示,因为一天只有24小时,因此只有当时钟的小时十位为2的时候,小时个位的计数数量为4。即当s_s为2时,x=4,否则x=10。根据以上分析得出x的代码如下所示:
| always @(*)begin if(s_s==2) x = 4; else x = 10; end |
此次,主体程序已经完成。
3.3 信号定义
接下来将module补充完整,首先进行定义信号类型。再次强调,在进行reg和wire的判断的时候,总容易存在多余的联想,比如认为reg就是寄存器,wire是线;或者认为reg会综合成寄存器,wire不会综合成寄存器。但是这些其实和reg型还是wire型都并无关系,在信号类型的判断时不需要做任何的联想,只要记住一个规则“用always实现的是reg型,其他都是wire型”就可以了。
进行信号定义时还需要定义信号的位宽,至简设计法在这里们分享一个非常实用的位宽获取技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽。
图3.7-14通过计算器获取信号位宽
cnt0是用always产生的信号,因此类型为reg。cnt0计数的最大值为100_000,如上图所示,通过计算器可以得知需要用17根线表示,即位宽是17位。
add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可。在编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [16:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; |
cnt1是用always产生的信号,因此类型为reg。cnt1计数的最大值为6,需要用3根线表示,即位宽是3位。编辑模式下输入“Reg3”调用至简设计法模板并补充完整;
add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板并补充完整。
补充后的cnt1、add_cnt1和end_cnt1的定义代码如下所示: | reg [2:0] cnt1 ; wire add_cnt1; wire end_cnt1; |
seg_sel是用always方式设计的,因此类型为reg,其共有8根线,即位宽为8。编辑模式下输入“Reg8”调用至简设计法模板,补充完整后得到代码表示如下:
seg_ment是用always方式设计的,因此类型为reg,其用7根线表示,即位宽为7。代码表示如下:
sel_data是用always设计的,所以类型为wire。其最大值为9,即位宽为4。编辑模式下输入“Wire4”调用至简设计法模板,补充完整后得到代码表示如下:
cnt2是用always产生的信号,因此类型为reg。cnt2计数的最大值为50_000_000,使用计算机可以算出需要用26根线表示,即位宽是26位。
add_cnt2和end_cnt2都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [25:0] cnt2 ; wire add_cnt2 ; wire end_cnt2 ; |
m_g是用always产生的信号,因此类型为reg。m_g计数的最大值为9,需要用4根线表示,即位宽是4位; add_m_g和end_m_g都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可; 编辑模式下输入“Reg4”和“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [ 3:0] m_g ; wire add_m_g ; wire end_m_g ; |
m_s是用always产生的信号,因此类型为reg。m_s计数的最大值为5,需要用3根线表示,即位宽是3位; add_m_s和end_m_s都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可; 编辑模式下输入“Reg3”和“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [ 2:0] m_s ; wire add_m_s ; wire end_m_s ; |
f_g是用always产生的信号,因此类型为reg。f_g计数的最大值为9,需要用4根线表示,即位宽是4位; add_f_g和end_f_g都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可; 编辑模式下输入“Reg4”和“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [ 3:0] f_g ; wire add_f_g ; wire end_f_g ; |
f_s是用always产生的信号,因此类型为reg。f_s计数的最大值为5,需要用3根线表示,即位宽是3位; add_f_s和end_f_s都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可; 编辑模式下输入“Reg3”和“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [ 2:0] f_s ; wire add_f_s ; wire end_f_s ; |
s_g是用always产生的信号,因此类型为reg。s_g计数的最大值为9,需要用4根线表示,即位宽是4位; add_s_g和end_s_g都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可; 编辑模式下输入“Reg4”和“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [ 3:0] s_g ; wire add_s_g ; wire end_s_g ; |
s_s是用always产生的信号,因此类型为reg。s_s计数的最大值为2,需要用2根线表示,即位宽是2位;
add_s_s和end_s_s都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可。因此代码如下:
编辑模式下输入“Reg2”和“Wire1”调用至简设计法模板,补充完整后得到代码表示如下: | reg [ 1:0] s_s ; wire add_s_s ; wire end_s_s ; |
x是用always产生的信号,因此类型为reg。x计数的最大值为10,需要用4根线表示,即位宽是4位。编辑模式下输入“Reg4”调用至简设计法模板,补充完整后得到代码表示如下:
至此,整个代码的设计工作已经完成,完整代码如下所示。此设计看起来比较复杂,但是认真思考会发现其原理非常基础。如果朋友还有不理解的地方,一定要反复推敲,弄懂之后会发现本设计还是非常有趣的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | module my_shizhong( clk , rst_n , seg_sel , seg_ment );
input clk ; input rst_n ; output[7:0] seg_sel ; output[6:0] seg_ment;
reg [ 7:0] seg_sel ;
reg [ 6:0] seg_ment ;
reg [16:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; reg [2:0] cnt1 ; wire add_cnt1; wire end_cnt1;
reg [25:0] cnt2 ; wire add_cnt2 ; wire end_cnt2 ;
reg [ 3:0] m_g ; wire add_m_g ; wire end_m_g ;
reg [ 2:0] m_s ; wire add_m_s ; wire end_m_s ;
reg [ 3:0] f_g ; wire add_f_g ; wire end_f_g ;
reg [ 2:0] f_s ; wire add_f_s ; wire end_f_s ;
reg [ 3:0] s_g ; wire add_s_g ; wire end_s_g ;
reg [ 1:0] s_s ; wire add_s_s ; wire end_s_s ; reg [ 3:0] sel_data ;
reg [ 3:0] x ;
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==100_000-1 ;
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==6-1 ;
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_sel<= 8'hfe; end else if(cnt1==0)begin seg_sel<= 8'hfe; end else if(cnt1==1)begin seg_sel<= 8'hfd; end else if(cnt1==2)begin seg_sel<= 8'hfb; end else if(cnt1==3)begin seg_sel<= 8'hf7; end else if(cnt1==4)begin seg_sel<= 8'hef; end else if(cnt1==5)begin seg_sel<= 8'hdf; end end
always @(*)begin if(cnt1==0) sel_data = m_g; else if(cnt1==1) sel_data = m_s; else if(cnt1==2) sel_data = f_g; else if(cnt1==3) sel_data = f_s; else if(cnt1==4) sel_data = s_g; else sel_data = s_s; end
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_ment<= 7'h01; end else if(sel_data==0)begin seg_ment<= 7'h01; end else if(sel_data==1)begin seg_ment<= 7'h4f; end else if(sel_data==2)begin seg_ment<= 7'h12; end else if(sel_data==3)begin seg_ment<= 7'h06; end else if(sel_data==4)begin seg_ment<= 7'h4c; end else if(sel_data==5)begin seg_ment<= 7'h24; end else if(sel_data==6)begin seg_ment<= 7'h20; end else if(sel_data==7)begin seg_ment<= 7'h0f; end else if(sel_data==8)begin seg_ment<= 7'h00; end else if(sel_data==9)begin seg_ment<= 7'h04; end end
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 = 1; assign end_cnt2 = add_cnt2 && cnt2==50_000_000-1 ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin m_g<= 0; end else if(add_m_g) begin if(end_m_g) m_g<= 0; else m_g<= m_g+1 ; end end assign add_m_g = end_cnt2; assign end_m_g = add_m_g&&m_g == 10 -1 ;
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin m_s<= 0; end else if(add_m_s)begin if(end_m_s) m_s<= 0; else m_s<= m_s + 1; end end
assign add_m_s = end_m_g; assign end_m_s = add_m_s&&m_s==6-1 ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin f_g<= 0; end else if(add_f_g) begin if(end_f_g) f_g<= 0; else f_g<= f_g+1 ; end end assign add_f_g = end_m_s; assign end_f_g = add_f_g&&f_g == 10 -1 ;
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin f_s<= 0; end else if(add_f_s)begin if(end_f_s) f_s<= 0; else f_s<= f_s + 1; end end
assign add_f_s = end_f_g; assign end_f_s = add_f_s&&f_s==6-1 ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin s_g<= 0; end else if(add_s_g) begin if(end_s_g) s_g<= 0; else s_g<= s_g+1 ; end end assign add_s_g = end_f_s; assign end_s_g = add_s_g&&s_g == x -1 ;
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin s_s<= 0; end else if(add_s_s)begin if(end_s_s) s_s<= 0; else s_s<= s_s + 1; end end
assign add_s_s = end_s_g; assign end_s_s = add_s_s&&s_s==3-1 ;
always @(*)begin if(s_s==2) x = 4; else x = 10; end
endmodule |
接下来是新建工程和上板查看现象。
第4节 综合与上板
4.1 新建工程
打开软件Quartus Ⅱ,点击“File”下拉列表中的New ProjectWzard...新建工程选项,如下图所示。
图3.7-15Quartus新建工程
随后会出现Quartus新建工程介绍,如下图所示,直接点击“Next”。
图3.7-16Quartus新建工程介绍
此时会出现工程文件夹、工程名、顶层模块名设置界面,如图3.7- 17所示。设置目录为:D:/mdy_book/my_shizhong,工程名和顶层名为my_shizhong。再次强调,为了避免初学者在后续操作中出现报错情况,强烈建议设置的文件目录和工程名称与本书保持一致。设置完成后点击“Next”。
图3.7-17QUARTUS新建工程设置名称
新建工程类型设置选择“Empty project”,然后点击“Next”。
图3.7-18QUARTUS新建工程类型
文件添加界面如图3.7- 19所示,点击右侧的“Add”按钮,添加已经写好的“my_shizhong.v”文件,点击右侧的“Add”按钮,可以看到界面下方会显示出文件,之后点击“Next”。
图3.7-19QUARTUS添加文件
芯片型号选择界面如图3.7-20所示,选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”,之后点击“Next”。
图3.7-20QUARTUS选择芯片型号
图3.7- 21为QUARTUS设置工具界面,不必做任何修改,直接点击“Next”。
图3.7-21QUARTUS设置工具界面
下图为新建工程汇总界面,可以看到新建工程的汇总情况,点击“Finish”,完成新建工程。
图3.7-22QUARTUS新建工程汇总界面
4.2 综合
新建工程步骤完成后,就会出现下图所示的QUARTUS新建工程后界面。
图3.7-23QUARTUS新建工程后界面
点击编译按钮,可以对整个工程进行编译。编译成功的界面,如图3.7- 24所示。
图3.7-24QUARTUS编译后界面
4.3 配置管脚
下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,随后就会弹出配置管脚的窗口。
图3.7-25QUARTUS配置管脚选项
在配置窗口最下方中的“location”一列,参考信号和管脚关系,按照表3.7- 1中最右两列配置好FPGA管脚,配置管理来源参见管脚配置环节,最终配置的结果如图3.7-26。配置完成后关闭Pin Planner,软件自动会保存管脚配置信息。 表3.7 - 1信号和管脚关系
图3.7-26 QUARTUS配置管脚
4.4 再次综合
再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“StartCompilation”,再次对整个工程进行编译和综合,如图3.7- 27所示。
图3.7-27QUARTUS编译选项
当出如下所示的编译成功标志时,就说明编译综合成功。
图3.7-28QUARTUS编译成功标志
4.5 连接开发板
完成编译后开始进行上板调试操作,按照下图的方式,将下载器接入电脑USB接口,接上开发板电源后按下开发板下方蓝色开关,硬件连接完毕。
图3.7-29开发板连接图
4.6 上板打开QUARTUS 界面,单击界面中的“
”,会弹出配置界面。在界面中点击“add file ”添加“.sof ”文件后点击“Start ”,会在“Progress ”出现显示进度。
图3.7-30QUARTUS界面
QUARTUS下载程序界面如下图所示,当进度条到100%提示成功后,可在开发板上观察相应的现象。
图3.7-31QUARTUS下载程序界面
操作完成后可以在开发板上观察现象,如果操作没有错误,此刻可以看到数码管上显示出时间并且在开始不断计数。如果显示的方向或者时间表示出现错误,就需要从头开始进行错误排查。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定有更多的收获。
第5节 简化版步骤分享
下面会分享简化版的步骤,方便掌握基础原理后进行反复操作复习。
5.1 设计实现
5.1.1顶层信号
新建目录:D:\mdy_book\my_shizhong。在该目录中,新建一个名为my_shizong.v的文件,用GVIM打开后开始编写代码。 确定顶层信号,信号和硬件的对应关系见表3.7- 1。
表3.7 -1信号和管脚关系
写出顶层代码。 | module my_shizhong( clk , rst_n , seg_sel , seg_ment ); |
声明输入输出属性。 | input clk ; input rst_n ; output[7:0] seg_sel ; output[6:0]seg_ment; |
5.1.2信号设计
进行架构设计。根据设计目标得出波形图如下所示。
图3.7-3带时间信息的输出时序 设计计数器架构,设计表示2ms时间的计数器cnt0代码如下。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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==100_000-1 ; |
设计表示第几次显示的计数器cnt1代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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==6-1 ; |
设计输出信号seg_sel代码如下: | always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_sel<= 8'hfe ; end else begin seg_sel<= ~(8'b1<<cnt1) ; end end |
设计输出信号seg_ment代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin segment<= 7'h00; end else begin case (sel_data) 0 : segment <= 7'h01; 1 : segment <= 7'h4f; 2 : segment <= 7'h12; 3 : segment <= 7'h06; 4 : segment <= 7'h4c; 5 : segment <= 7'h24; 6 : segment <= 7'h20; 7 : segment <= 7'h0f; 8 : segment <= 7'h00; 9 : segment <= 7'h04; default : segment <= 7'h00; endcase end end |
设计sel_data信号代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(*)begin if(cnt1==0) sel_data = m_g; else if(cnt1==1) sel_data = m_s; else if(cnt1==2) sel_data = f_g; else if(cnt1==3) sel_data = f_s; else if(cnt1==4) sel_data = s_g; else sel_data = s_s; end |
秒钟个位m_g的时序图:
图3.7-8秒钟个位的时序图
设计计数秒钟个位m_g的计数器cnt2代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 = 1; assign end_cnt2 = add_cnt2 && cnt2==50_000_000-1 ; |
设计秒钟个位m_g代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin m_g<= 0; end else if(add_m_g) begin if(end_m_g) m_g<= 0; else m_g<= m_g+1 ; end end assign add_m_g = end_cnt2; assign end_m_g = add_m_g&&m_g == 10 -1 ; |
秒钟十位m_s的时序图:
图3.7-9秒钟十位的时序图
设计秒钟十位m_s代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin m_s<= 0; end else if(add_m_s)begin if(end_m_s) m_s<= 0; else m_s<= m_s + 1; end end
assign add_m_s = end_m_g; assign end_m_s = add_m_s&&m_s==6-1 ; |
分钟个位f_g的时序图:
图3.7- 10 分钟个位的时序图
设计分钟个位f_g代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin f_g<= 0; end else if(add_f_g) begin if(end_f_g) f_g<= 0; else f_g<= f_g+1 ; end end assign add_f_g = end_m_s; assign end_f_g = add_f_g&&f_g == 10 -1 ; |
分钟十位f_s的时序图:
图3.7- 11分钟十位的时序图
设计分钟十位f_s代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin f_s<= 0; end else if(add_f_s)begin if(end_f_s) f_s<= 0; else f_s<= f_s + 1; end end
assign add_f_s = end_f_g; assign end_f_s = add_f_s&&f_s==6-1 ; |
小时个位s_g时序图:
图3.7- 12 小时个位的时序图
设计小时个位s_g代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 | always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin s_g<= 0; end else if(add_s_g) begin if(end_s_g) s_g<= 0; else s_g<= s_g+1 ; end end assign add_s_g = end_f_s; assign end_s_g = add_s_g&&s_g == x -1 ; |
小时十位s_s时序图:
图3.7- 13 小时十位的时序图
设计小时十位s_s代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | always @(posedge clk or negedge rst_n)begin if(!rst_n)begin s_s<= 0; end else if(add_s_s)begin if(end_s_s) s_s<= 0; else s_s<= s_s + 1; end end
assign add_s_s = end_s_g; assign end_s_s = add_s_s&&s_s==3-1 ; |
设计变量x代码如下: | always @(*)begin if(s_s==2) x = 4; else x = 10; end |
至此,主体程序已经完成,下面将module补充完整。
5.1.3信号定义
首先定义信号类型,cnt0、add_cnt0和end_cnt0的信号定义如下: | reg [16:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; |
cnt1、add_cnt1和end_cnt1的信号定义如下: | reg [2:0] cnt1 ; wire add_cnt1; wire end_cnt1; |
seg_sel的信号定义如下:
seg_ment的信号定义如下:
sel_data的信号定义如下:
cnt2、add_cnt2和end_cnt2的信号定义如下: | reg [25:0] cnt2 ; wire add_cnt2 ; wire end_cnt2 ; |
m_g、add_m_g和end_m_g的信号定义如下: | reg [ 3:0] m_g ; wire add_m_g ; wire end_m_g ; |
m_s、add_m_s和end_m_s的信号定义如下: | reg [ 2:0] m_s ; wire add_m_s ; wire end_m_s ; |
f_g、add_f_g和end_f_g的信号定义如下: | reg [ 3:0] f_g ; wire add_f_g ; wire end_f_g ; |
f_s、add_f_s和end_f_s的信号定义如下: | reg [ 2:0] f_s ; wire add_f_s ; wire end_f_s ; |
s_g、add_s_g和end_s_g的信号定义如下: | reg [ 3:0] s_g ; wire add_s_g ; wire end_s_g ; |
s_s、add_s_s和end_s_s的信号定义如下: | reg [ 1:0] s_s ; wire add_s_s ; wire end_s_s ; |
x的信号定义如下:
至此,整个代码的设计工作已经完成,完整的设计代码如下所示。 | module my_shizhong( clk , rst_n , seg_sel , seg_ment );
input clk ; input rst_n ; output[7:0] seg_sel ; output[6:0] seg_ment;
reg [ 7:0] seg_sel ;
reg [ 6:0] seg_ment ;
reg [16:0] cnt0 ; wire add_cnt0 ; wire end_cnt0 ; reg [2:0] cnt1 ; wire add_cnt1; wire end_cnt1;
reg [25:0] cnt2 ; wire add_cnt2 ; wire end_cnt2 ;
reg [ 3:0] m_g ; wire add_m_g ; wire end_m_g ;
reg [ 2:0] m_s ; wire add_m_s ; wire end_m_s ;
reg [ 3:0] f_g ; wire add_f_g ; wire end_f_g ;
reg [ 2:0] f_s ; wire add_f_s ; wire end_f_s ;
reg [ 3:0] s_g ; wire add_s_g ; wire end_s_g ;
reg [ 1:0] s_s ; wire add_s_s ; wire end_s_s ; reg [ 3:0] sel_data ;
reg [ 3:0] x ;
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==100_000-1 ;
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==6-1 ;
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_sel<= 8'hfe; end else if(cnt1==0)begin seg_sel<= 8'hfe; end else if(cnt1==1)begin seg_sel<= 8'hfd; end else if(cnt1==2)begin seg_sel<= 8'hfb; end else if(cnt1==3)begin seg_sel<= 8'hf7; end else if(cnt1==4)begin seg_sel<= 8'hef; end else if(cnt1==5)begin seg_sel<= 8'hdf; end end
always @(*)begin if(cnt1==0) sel_data = m_g; else if(cnt1==1) sel_data = m_s; else if(cnt1==2) sel_data = f_g; else if(cnt1==3) sel_data = f_s; else if(cnt1==4) sel_data = s_g; else sel_data = s_s; end
always @(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin seg_ment<= 7'h01; end else if(sel_data==0)begin seg_ment<= 7'h01; end else if(sel_data==1)begin seg_ment<= 7'h4f; end else if(sel_data==2)begin seg_ment<= 7'h12; end else if(sel_data==3)begin seg_ment<= 7'h06; end else if(sel_data==4)begin seg_ment<= 7'h4c; end else if(sel_data==5)begin seg_ment<= 7'h24; end else if(sel_data==6)begin seg_ment<= 7'h20; end else if(sel_data==7)begin seg_ment<= 7'h0f; end else if(sel_data==8)begin seg_ment<= 7'h00; end else if(sel_data==9)begin seg_ment<= 7'h04; end end
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 = 1; assign end_cnt2 = add_cnt2 && cnt2==50_000_000-1 ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin m_g<= 0; end else if(add_m_g) begin if(end_m_g) m_g<= 0; else m_g<= m_g+1 ; end end assign add_m_g = end_cnt2; assign end_m_g = add_m_g&&m_g == 10 -1 ;
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin m_s<= 0; end else if(add_m_s)begin if(end_m_s) m_s<= 0; else m_s<= m_s + 1; end end
assign add_m_s = end_m_g; assign end_m_s = add_m_s&&m_s==6-1 ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin f_g<= 0; end else if(add_f_g) begin if(end_f_g) f_g<= 0; else f_g<= f_g+1 ; end end assign add_f_g = end_m_s; assign end_f_g = add_f_g&&f_g == 10 -1 ;
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin f_s<= 0; end else if(add_f_s)begin if(end_f_s) f_s<= 0; else f_s<= f_s + 1; end end
assign add_f_s = end_f_g; assign end_f_s = add_f_s&&f_s==6-1 ;
always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin s_g<= 0; end else if(add_s_g) begin if(end_s_g) s_g<= 0; else s_g<= s_g+1 ; end end assign add_s_g = end_f_s; assign end_s_g = add_s_g&&s_g == x -1 ;
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin s_s<= 0; end else if(add_s_s)begin if(end_s_s) s_s<= 0; else s_s<= s_s + 1; end end
assign add_s_s = end_s_g; assign end_s_s = add_s_s&&s_s==3-1 ;
always @(*)begin if(s_s==2) x = 4; else x = 10; end
endmodule |
5.2 综合与上板
5.2.1新建工程
接下来是新建工程和上板查看现象。首先打开QuartusⅡ,点击File下拉列表中的New Project Wzard...新建工程选项。
图3.7-15Quartus新建工程
直接点击Next。
图3.7- 16 Quartus新建工程介绍
如下图所示设置工程文件夹、工程名、顶层模块名(目录为:D:/mdy_book/my_shizhong,工程名和顶层名为my_shizhong),完成设置后点击“Next”。
图3.7- 17 QUARTUS新建工程设置名称
选择“Empty project”后点击“Next”。
图3.7- 18 QUARTUS新建工程类型
点击右侧“Add ”选项-,添加“my_shizhong.v”文件后点击“Next”。
图3.7- 19 QUARTUS添加文件
对芯片型号进行选择:“Device family”选项中选择“Cyclone ⅣE”,“Available devices”选项下选择“EP4CE15F23C8”,随后点击“Next”。
图3.7- 20 QUARTUS选择芯片型号
直接点击“Next”。
图3.7- 21 QUARTUS设置工具界面
点击“Finish”,完成新建工程。
图3.7- 22 QUARTUS新建工程汇总界面
5.2.2综合新建工程后界面如下图所示,点击“编译”。
图3.7- 23 QUARTUS新建工程后界面
编译成功界面如下图。
图3.7- 24 QUARTUS编译后界面
5.2.3配置管脚
在菜单栏点击“Assignments”后点击“Pin Planner”,此时会弹出配置管脚的窗口。
图3.7- 25 QUARTUS配置管脚选项
在配置窗口“location”根据信号和管脚关系配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。
图3.7- 26 QUARTUS配置管脚 5.2.4再次综合
打开“QUARTUS”软件,在菜单栏中选择“Processing”,点击“StartCompilation”。
图3.7- 27 QUARTUS编译选项
出现QUARTUS编译成功标志,表示此次编译成功。
图3.7- 28 QUARTUS编译成功标志
5.2.5连接开发板
下载器接入电脑USB接口,将开发板接上电源后按下蓝色开关。
图3.7-29开发板连接图
5.2.6上板
打开QUARTUS界面,单击“ ”图标:
图3.7- 30 QUARTUS界面
点击“add file”,选择“.sof”文件,随后点击“Start”。在“Progress”中会显示进度,当进度条显示“100%”表示成功,可在开发板上观察现象。
图3.7- 31 QUARTUS下载程序界面
第6节 扩展练习
至此,整个数字时钟的工程设计就分享完毕了。这里只是给读者朋友们展示了一个案例,掌握了原理的读者朋友可以试着进行设计的延伸与扩展。比如更改开发板上数码管的显示位置或者尝试将24小时时钟改为12小时时钟。这样举一反三,既可以考察自己的知识掌握程度,又能强化自己的设计能力。也欢迎读者朋友对此设计进行扩展性的思考和讨论,有更好的思路可以前往至简设计法论坛进行交流。
|