官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师

3.6秒表功能--明德扬科教(mdy-edu.com)

发布时间:2021-08-22   作者:admin 浏览量:

本文的文档编号:001600000018

需要看对应的视频,请点击视频编号:001600000100

1.至简原理与应用配套的案例文档
2.设计中只需要使用开发板上的一个数码管0来实现秒表功能,具体为:复位后,数码管0显示数字0并持续1秒;随后显示数字1并持续2秒;然后显示数字2并持续3秒;以此类推,最后显示数字9并持续10秒。之后再次回到显示数字0并持续1秒的循环。
3. ALTERA入门学习案例文档

篇 FPGA至简设计项目实践   


     第六章 秒表功能

第1节 项目背景

上一章节中详细的讲解了数码管的显示原理和控制方式,本章不再进行赘述。通过相关原理可知FPGA通过控制数码管的对应管脚,从而实现数码管显示的控制。在这一结论的基础上,本章将进行更深一步的数码管显示设计。

第2节 设计目标

在正式学习本设计之前先来确定一下设计的功能目标。在这里再次强调明确设计目标的重要性,至简设计法旨在用最直接简洁的方法来进行工程设计,想要达到这一效果,设计中的每一个步骤和代码都应是有用的。只有每一步操作都是为了最终的设计目标,才可以做到“最直接”,才能少走弯路,而这也是至简设计法的特别之处。因此在进行设计前需要彻底理清每一个设计目标,围绕这个设计目标展开后续的每一个步骤设计。如果最终想要达到的效果都无法确定,那么后续的讨论就没有任何的意义。

开发板上有 8 位数码管,在本次设计中只需要使用1个数码管(数码管0)来实现秒表功能,其具体设计效果为:复位后,数码管0显示数字0并持续1秒;随后显示数字1并持续2秒;然后显示数字2并持续3秒;以此类推,最后显示数字9并持续10秒。之后再次回到显示数字0并持续1秒的循环中。
秒表功能的上板效果如下图所示,想要观看上板演示视频效果可以登陆至简设计法官方网站进行学习:old.mdy-edu.com/xxxx

3.6-1秒表实现效果图

第3节 设计实现

本节会分享详细的步骤与解析,以便初学者可以在学会原理的情况下掌握设计方法。在学会每一个案例的基础上希望同学们将设计思想融会贯通,最终可以掌握完成独立完成工程设计的能力,可以将至简设计法运用到今后的工作中去。如果已经掌握了原理、只想复习回顾设计步骤,也可以选择直接阅读第五节简化版步骤进行实操练习。请各位根据个人的实际情况进行选择。

3.1 顶层信号

新建目录:D:mdy_bookmy_time,并在此目录中,新建一个名为my_time.v的文件。用GVIM打开后开始编写代码。在这里建议初学者按照本书提供的路径名以及文件名进行设置,不要自行进行修改。因为在更改后可能会出现中文路径、空格路径等非法路径的问题,也可能会出现文件名更改后报错的现象。而对于初学者来说,并不能很好的发现并解决问题。因此建议初学者先按照要求更名保存,在进行多个工程设计,熟悉了各个步骤后再进行更名的操作。在此不要想当然,一时的细心可以避免后续问题的出现。

分析设计目标可知,本次设计要实现的功能是控制数码管0根据不同时间显示不同的数字并持续一定的时间。在这个过程中,8个数码管中只有数码管0进行显示变换,其他数码管不亮。上一章中对数码管的控制方法进行了讲解,如果有需要可以回到上一章复习。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.6 - 1信号和管脚关系
  
器件
  
信号线
信号线
FPGA管脚
内部信号
  
U6,U7
  
SEG_E
SEG0
Y6
seg_ment[2]
SEG_DP
SEG1
W6
未用到
SEG_G
SEG2
Y7
seg_ment[0]
SEG_F
SEG3
W7
seg_ment[1]
SEG_D
SEG4
P3
seg_ment[3]
SEG_C
SEG5
P4
seg_ment[4]
SEG_B
SEG6
R5
seg_ment[5]
SEG_A
SEG7
T3
seg_ment[6]
DIG1
DIG_EN1
T4
seg_sel[0]
DIG2
DIG_EN2
V4
seg_sel[1]
DIG3
DIG_EN3
V3
seg_sel[2]
DIG4
DIG_EN4
Y3
seg_sel[3]
DIG5
DIG_EN5
Y8
seg_sel[4]
DIG6
DIG_EN6
W8
seg_sel[5]
DIG7
DIG_EN7
W10
seg_sel[6]
DIG8
DIG_EN8
Y10
seg_sel[7]
  
X1
  

SYS_CLK
G1
clk
  
K1
  

SYS_RST
AB12
rst_n

将module的名称定义为my_time,已知该模块有4个信号:clk、rst_n、seg_sel和seg_ment。在顶层信号代码中需要将与外部相连接的输入/输出信号列出,从而实现信号与管脚的连接,其具体代码如下:
1
  
2
  
3
  
4
  
5
  
6
module  my_time(
  
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。综上所述,完成输入输出端口定义,其具体代码如下:
1
  
2
  
3
  
4
input           clk  ;
  
input           rst_n  ;
  
output   [7:0]   seg_sel   ;
  
output   [6:0]   seg_ment  ;

3.2 信号设计

在设计信号之前,先按照至简设计法的思路来进行架构设计。根据设计目标先来分析要实现的功能,8个数码管中数码管0显示数字,其余数码管不显示。数码管0的具体显示顺序为:显示数字“0”持续1秒;显示数字“1”持续2秒;显示数字“2”持续3秒;显示数字“3”持续4秒;显示数字“4”持续5秒;显示数字“5”持续6秒;显示数字“6”持续7秒;显示数字“7”持续8秒显示数字“8”持续9秒;显示数字“9”持续10秒。至此一个循环结束,再从显示数字“0”开始进入下一个循环。

将现象翻译成信号表示如下:
数码管0显示数字“0”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b000_0001,持续1秒;
数码管0显示数字“1”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b100_1111,持续2秒;
数码管0显示数字“2”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b001_0010,持续3秒;
数码管0显示数字“3”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b000_0110,持续4秒;
数码管0显示数字“4”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b100_1100,持续5秒;
数码管0显示数字“5”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b010_0100,持续6秒;
数码管0显示数字“6”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b010_0000,持续7秒;
数码管0显示数字“7”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b000_1111,持续8秒;
数码管0显示数字“8”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b 0000000,持续9秒;
数码管0显示数字“9”,即seg_sel的值为8’b1111_1110,seg_ment的值为7’b000_0100,持续10秒。
随后从显示数字“0”开始循环往复。

总结规律可以发现seg_sel信号的值不发生变化,始终为8’hfe;seg_ment隔一段时间后会变化,其波形示意图如下图所示:

图3.6-2秒表的波形图

根据设计目标可以发现数码管0每隔一段时间发生变化,即seg_ment信号每隔一段时间后会变化,且变化的间隔时间不同。第一次显示时,信号seg_sel=8’hfe,seg_ment=7’h01,持续1秒;在第二次显示时,seg_sel=8’hfe,seg_ment=7’h4f,持续2秒;以此类推,第十次显示,seg_sel=8’hfe,seg_ment=7’h04,持续10秒。以此为规律在波形图上补充时间信息,补充后的波形图如下图所示:
图3.6-3秒表的实现架构图

根据波形图的分析可以得到本设计的计数器架构:本设计一共需要两个计数器,一个计数器用来计算时间,如1秒、2秒等;另一个计数器用来计算在目前处在第几次的显示阶段。

来思考一下:为什么不能只采用一个时间计数器,按照1秒、3秒、6秒、10秒……这样增加秒数的方法来计数呢?实际上增加计数器的操作正是运用了至简设计法的道理,让信号代码更加有条理并便于设计师确定位置。如果采用一个计数器的计数方法,虽然减少了一个计数器,但在实际的代码操作中却非常麻烦。

举个生活中常见的例子,如下图所示,将每一次的循环看做楼层,每一次的数码管显示数字的持续时间记作门牌号,即1秒、2秒、3秒在一楼中对应1号2号3号,以此类推。如果只用一个计数器的话,那么一楼的门牌号为1、2、3、4、5、6、7、8,二楼的则为9、10、11、12、13、14、15、16、17、18,三楼以此类推。这种只有用一种计数单位的方法,开始确实没有太大问题,但是随着楼层的变高,这种计数方式的弊端就会显露出来。比如在这种情况下,如果想要找76号,就可能需要很久才能找到。

图3.6-4单一门牌号计数模式

如果在一个计数单位的基础上再加一个计数单位,即采用两种技术模式,一个记楼层,一个记门牌号,如下图所示。在同样的门牌号计数中,可以记为一楼的1、2、3、4、5、6、7、8号,二楼的1、2、3、4、5、6、7、8、9、10号,以此类推,每一层都有对应的房间号。在这种计数模式下,如果想要找到七层6号房间,不需要多做思考就可以一下定位到正确位置。

图3.6-5两种门牌号复合计数模式

通过案例可以确定两种计数器复合计数才是最简单的计数模式,因此本设计中使用一个计算时间的计数器,一个表示当前显示次数(1到10)的计数器。这样如果后续遇到问题,也可以快速的定位到相应位置,避免了很多麻烦。

此外,在以上门牌号计数案例中,如果想要寻找每一层的固定位置房间,按照两种计数单位复合的模式,用cnt0来表示房间号,其范围是0-9,用cnt1来表示楼层号,其范围是0-1。通过这种方法,可以利用cnt0和cnt1来找到任何一个房间。当然,如果想找同一个位置的房间,也可以直接用cnt0来表示。例如cnt0==4可以统一表示每层楼的四号房间。但如果只有房间号这一计数模式而没有楼层的话,想表示每层楼的四号房间,则表示方式为“cnt0==4”、“cnt0=12”,更高楼层以此类推。两种表现形式的难易程度显而易见。

可见两个计数器复合计数的方式并不是多此一举,反而是最适合本设计的计数器方案。不论是简单的还是复杂的设计,至简设计法都会全面的考虑设计需求,在每一个环节都会采用最适合的设计方案,尽量为后续的步骤减少不必要的麻烦。

因此,在本设计中选用了两个计数器:一个记录时间,一个记录循环次数,下面就分别讨论两个计数器的实现。首先讨论表示时间的计数器,其用cnt0表示。至简设计法的设计规则中有讲过,计数器的设计只考虑两个因素:加1条件和计数数量。只要确定相应逻辑,就能完成计数器代码设计。
首先讨论计数器cnt0的加1条件:由于该计数器在不停地计数,永不停止,因此可以认为其加1条件是一直有效的,可以写为:assign add_cnt0==1。

此处可能有同学存在疑惑加1条件的概念是什么?这里以停车位来进行比喻,一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。反之则可以认为停车位编号的加1条件就是:对应位置上没有石头,其可以继续的进行编号,即assign add_cnt0 = “没有石头”。因此如果在设计中计数器一直没有阻碍地进行计数工作,就可以认为加1条件是一直有效的。

确定加1条件后来讨论计数器cnt0的计数数量。本设计的工作时钟是50MHz,即周期为20ns,当计数器数到第1_000_000_000/20=50_000_000个时,就代表1秒计时结束;当计数器计数到第2_000_000_000/20=100_000_000个时,就代表2秒计时结束;当计数器数到第150_000_000个时,就代表3秒计时结束;以此类推,当计数器数到第500_000_000个时,就代表10秒计时结束。由于每次计算的时间不同,因此可以考虑使用变量法。至简设计法中选用变量法的逻辑思维非常简单,只要循环的周期个数不同,就可以选择变量法。设定x表示计数器cnt0的计数数量,后文中会对x的值进行详细展开。

确定好加1条件和计数数量后开始进行代码编写。相信各位往常都是一行行输入代码,但是至简设计法有一个小技巧,可以为大家编写代码省去不少时间,并且一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分做成了模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这里就可以用模板编写计数器代码,感受一下这个炫酷的功能。

打开GVIM工具,在命令模式下输入“:Mdyjsq”后点击回车,就调出了对应模板,如下图所示。之后再将本案例中的变量填到模板里面,就可以得到完整正确的计数器代码。

图3.6-6至简设计法调用计数器代码模板

补充完整后得到计数器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== x-1 ;

下面来设计表示第几次显示的计数器,将该计数器命名为cnt1。分析波形图可以看出,每个阶段完成后,该计数器的值加1。因此加1条件是cnt0结束,代码表示为end_cnt0。该计数器一共要数10次,即计数数量为10。这里继续选择调用至简设计法代码模板,在命令模式下输入“: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== 10-1 ;


确定完两个计数器后来思考输出信号seg_sel的值。通过波形图可以看出:在整个设计中,该信号始终不变一直是8’hfe,所以代码直接写成:
1
assign  seg_sel = 8'hfe ;


再来思考输出信号seg_ment的变化。分析设计目标可知在第一次显示时,seg_ment输出值为7’h01;在第二次显示时,输出值为7’h4f;以此类推,在第十次显示时,输出值为7’h0f。计时器cnt1来表示第几次显示,即cnt1==0时,输出值为7’h01;cnt1==1的时输出值为7’h4f;以此类推,在cnt1==9时,输出值为7’h04。再进一步翻译成代码,打开GVIM,在编辑模式下输入“Shixu2”,调出至简设计法模板,补充完整后的代码如下所示:
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(cnt1==0)begin
  
seg_ment<=  7'h01;
  
    end
  
    else if(cnt1==1)begin
  
seg_ment<=  7'h4f;
  
    end
  
    else if(cnt1==2)begin
  
seg_ment<=  7'h12;
  
    end
  
    else if(cnt1==3)begin
  
seg_ment<=  7'h06;
  
    end
  
    else if(cnt1==4)begin
  
seg_ment<=  7'h4c;
  
    end
  
    else if(cnt1==5)begin
  
seg_ment<=  7'h24;
  
    end
  
    else if(cnt1==6)begin
  
seg_ment<=  7'h20;
  
    end
  
    else if(cnt1==7)begin
  
seg_ment<=  7'h0f;
  
    end
  
    else if(cnt1==8)begin
  
seg_ment<=  7'h00;
  
    end
  
    else if(cnt1==9)begin
  
seg_ment<=  7'h04;
  
    end
  
end

最后再来思考一下变量x。
正如前文中楼房门牌号的例子,假定第一层为8个房间,第二层为10个房间,第三层为15个房间,第四层为20个房间。若用代码将这种情况表示出来,即在编辑模式下输入“Zuhe”后回车,可以得到至简设计法模板,填写相应条件后得到最终代码如下:
1
  
2
  
3
  
4
  
5
  
6
  
7
  
8
  
9
  
10
  
11
  
12
  
13
  
14
always  @(*)begin
  
    if(cnt1==0)begin
  
        x = 8 ;
  
    end
  
    else if(cnt1==1)begin
  
        x = 10 ;
  
    end
  
    else if(cnt1==2)begin
  
        x = 15 ;
  
    end
  
    else begin
  
        x = 20 ;
  
    end
  
end


同样的道理,在讨论计数器cnt0的时候曾经得出结论:“当计数器数到第1_000_000_000/20=50_000_000个时,就代表1秒计时结束;当计数器计数到第2_000_000_000/20=100_000_000个时,就代表2秒计时结束;当计数器数到第150_000_000个时,就代表3秒计时结束;以此类推,当计数器数到第500_000_000个时,就代表10秒计时结束。”可以看出,cnt0的计数数量与cnt1有关。即第1次显示数50_000_000个,第2次数100_000_000个……,按照这样的规律可以将10次对应的x表示出来。

在编辑模式下输入“Zuhe”后回车,调出至简设计法模板,填写对应条件后得到表示x的代码如下所示。这里是使用至简设计法写出来的代码,需要着重学习一下,可以看到该代码非常整洁,逻辑思维一目了然。这种写法不会出现BUG,并且可以做到资源最优,在进行实操时可以使用这种写法。

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
always  @(*)begin
  
    if(cnt1==0)begin
  
        x = 50_000_000;
  
    end
  
    else if(cnt1==1)begin
  
        x = 100_000_000;
  
    end
  
    else if(cnt1==2)begin
  
        x = 150_000_000;
  
    end
  
    else if(cnt1==3)begin
  
        x = 200_000_000;
  
    end
  
    else if(cnt1==4)begin
  
        x = 250_000_000;
  
    end
  
    else if(cnt1==5)begin
  
        x = 300_000_000;
  
    end
  
    else if(cnt1==6)begin
  
        x = 350_000_000;
  
    end
  
    else if(cnt1==7)begin
  
        x = 400_000_000;
  
    end
  
    else if(cnt1==8)begin
  
        x = 450_000_000;
  
    end
  
    else if(cnt1==9)begin
  
        x = 500_000_000;
  
    end
  
end


至此,主体程序已经完成。回顾一下设计过程会发现:每一步设计都围绕设计目标逐步展开,看似在讨论一个个小问题,但都是始终围绕设计目标来进行讨论,这也正是最开始提到的制定和理解设计目标的重要性。

3.3 信号定义

接下来需要将module补充完整,首先来定义信号类型。reg和wire的判断很容易搞不清楚总会有多余的联想,比如认为reg就是寄存器,wire是线;或者认为reg会综合成寄存器,wire不会综合成寄存器。但是这些其实和reg型还是wire型都并无关系,在信号类型的判断时不需要做任何的联想,只要记住一个规则“用always实现的是reg型,其他都是wire型”就可以了。

cnt0是用always产生的信号,因此类型为reg。cnt0计数的最大值为500_000_000,需要用29根线表示,即位宽是29位。

至简设计法在这里们分享一个非常实用的位宽获取技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入进去,就会获得对应的信号位宽。以cnt0信号为例,在计算器中输入500_000_000,即可得到对应的位宽29,如下图所示。

图3.6-7通过计算器获取信号位宽

因此cnt0的定义代码如下:
1
reg    [28:0]   cnt0  ;


add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板,补充完整后得到代码表示如下:
1
  
2
wire     add_cnt0   ;
  
wire     end_cnt0   ;


cnt1是用always产生的信号,因此类型为reg。cnt1计数的最大值为9,需要用4根线表示,即位宽是4位。编辑模式下输入“Reg4”调用至简设计法模板并补充完整;
add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可。编辑模式下输入“Wire1”调用至简设计法模板并补充完整。
补充后的cnt1、add_cnt1和end_cnt1的定义代码如下所示:
1
  
2
  
3
reg    [3:0]            cnt1        ;
  
wire                     add_cnt1    ;
  
wire                     end_cnt1    ;


seg_sel是用assign方式设计的,因此类型为wire,其共有8根线,即位宽为8。编辑模式下输入“Reg8”调用至简设计法模板,补充完整后得到代码表示如下:
1
wire    [7:0]   seg_sel   ;


seg_ment是用always方式设计的,因此类型为reg,其用7根线表示,即位宽为7。代码表示如下:
1
reg   [6:0]   seg_ment   ;


x是用always方式设计的,因此类型为reg,其位数与cnt0一致,都是20位位宽。x的定义代码表示如下:
1
reg   [28:0]   x   ;


至此,整个代码的设计工作已经完成。整体代码如下:
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
   

  
module  miaobiao(
  
clk  ,
  
rst_n  ,
  
seg_sel,
  
seg_ment
  
    );
  
  
  
  
    input               clk  ;
  
    input               rst_n  ;
  
  
  
    output     [7:0]  seg_sel   ;
  
    output    [6:0]  seg_ment   ;
  
  
  
    reg           [28:0]      cnt0;
  
    reg           [3:0]       cnt1;
  
    wire       add_cnt0;
  
    wire       end_cnt0;
  
    wire       add_cnt1;
  
    wire       end_cnt1;
  
    wire     [7:0]  seg_sel   ;
  
    reg    [6:0]  seg_ment   ;
  
  
    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== 50000000-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==10-1 ;
  
    assign seg_sel  = 8'hfe;
  
always  @(posedge clk or negedge rst_n)begin
  
    if(rst_n==1'b0)begin
  
seg_ment<=  7'h01;
  
    end
  
    else if(cnt1==0)begin
  
seg_ment<=  7'h01;
  
    end
  
    else if(cnt1==1)begin
  
seg_ment<=  7'h4f;
  
    end
  
    else if(cnt1==2)begin
  
seg_ment<=  7'h12;
  
    end
  
    else if(cnt1==3)begin
  
seg_ment<=  7'h06;
  
    end
  
    else if(cnt1==4)begin
  
seg_ment<=  7'h4c;
  
    end
  
    else if(cnt1==5)begin
  
seg_ment<=  7'h24;
  
    end
  
    else if(cnt1==6)begin
  
seg_ment<=  7'h20;
  
    end
  
    else if(cnt1==7)begin
  
seg_ment<=  7'h0f;
  
    end
  
    else if(cnt1==8)begin
  
seg_ment<=  7'h00;
  
    end
  
    else if(cnt1==9)begin
  
seg_ment<=  7'h04;
  
    end
  
end
  
always  @(*)begin
  
    if(cnt1==0)begin
  
        x = 50_000_000;
  
    end
  
    else if(cnt1==1)begin
  
        x = 100_000_000;
  
    end
  
    else if(cnt1==2)begin
  
        x = 150_000_000;
  
    end
  
    else if(cnt1==3)begin
  
        x = 200_000_000;
  
    end
  
    else if(cnt1==4)begin
  
        x = 250_000_000;
  
    end
  
    else if(cnt1==5)begin
  
        x = 300_000_000;
  
    end
  
    else if(cnt1==6)begin
  
        x = 350_000_000;
  
    end
  
    else if(cnt1==7)begin
  
        x = 400_000_000;
  
    end
  
    else if(cnt1==8)begin
  
        x = 450_000_000;
  
    end
  
    else if(cnt1==9)begin
  
        x = 500_000_000;
  
    end
  
end
  
endmodule

整个代码的设计工作已经完成,接下来将新建工程并上板查看现象。在上板之前,建议再检查一遍有没有遗漏或者不懂的地方。授人以鱼不如授人以渔,本书希望在此分享朋友的不止是一个设计,更想让各位学会至简设计法的设计方法与思路,逐渐掌握独立思考和设计工程的技能。掌握了一个设计会带来阶段性的便捷,但是学会了这项技能可以终身受益。所以希望读者遇到不理解的地方时一定多多钻研,直到真正弄懂弄通为止。

第4节 综合与上板

4.1 新建工程

打开软件Quartus Ⅱ,点击“File”下拉列表中的New Project Wzard...新建工程选项,如下图所示。

图3.6-8Quartus新建工程
随后会出现Quartus新建工程介绍,如下图所示,直接点击“Next”。

图3.6-9Quartus新建工程介绍

此时会出现工程文件夹、工程名、顶层模块名设置界面,如图3.6- 10所示。设置目录为:D:/mdy_book/miaobiao,工程名和顶层名为miaobiao。再次强调,为了避免初学者在后续操作中出现报错情况,强烈建议设置的文件目录和工程名称与本书保持一致。设置完成后点击“Next”。

图3.6-10QUARTUS新建工程设置名称

新建工程类型设置选择“Empty project”,如下图所示,然后点击“Next”。

图3.6-11QUARTUS新建工程类型

文件添加界面如图3.6- 12所示,点击右侧的“Add”按钮,添加已经写好的“miaobian.v”文件,可以看到界面下方会显示出文件,随后点击“Next”。

图3.6-12QUARTUS添加文件

芯片型号选择界面如图3.6- 13所示,选择“Cyclone ⅣE”,在芯片型号选择处选择“EP4CE15F23C8”后点击“Next”。

图3.6-13QUARTUS选择芯片型号

图3.6- 14为QUARTUS设置工具界面,不必做任何修改,直接点击“Next”。

图3.6-14QUARTUS设置工具界面

新建工程汇总情况如下图所示,点击“Finish”后完成新建工程。

图3.6-15QUARTUS新建工程汇总界面
4.2 综合

新建工程步骤完成后会出现如下所示的QUARTUS界面。

图3.6-16QUARTUS新建工程后界面

点击编译按钮,可以对整个工程进行编译。编译成功的界面如图3.6- 17所示。

图3.6-17QUARTUS编译后界面


4.3 配置管脚

下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,随后会弹出配置管脚的窗口。

图3.6-18QUARTUS配置管脚选项

在配置窗口最下方中的“location”一列,参考信号和管脚关系,按照表3.6- 1中最右两列配置好FPGA管脚。配置管理来源参见管脚配置环节,最终配置的结果如图3.6- 19。配置完成后,关闭“Pin Planner”,软件自动会保存管脚配置信息。

表3.6 - 1信号和管脚关系
  
器件
  
信号线
信号线
FPGA管脚
内部信号
  
U6,U7
  
SEG_E
SEG0
Y6
seg_ment[2]
SEG_DP
SEG1
W6
未用到
SEG_G
SEG2
Y7
seg_ment[0]
SEG_F
SEG3
W7
seg_ment[1]
SEG_D
SEG4
P3
seg_ment[3]
SEG_C
SEG5
P4
seg_ment[4]
SEG_B
SEG6
R5
seg_ment[5]
SEG_A
SEG7
T3
seg_ment[6]
DIG1
DIG_EN1
T4
seg_sel[0]
DIG2
DIG_EN2
V4
seg_sel[1]
DIG3
DIG_EN3
V3
seg_sel[2]
DIG4
DIG_EN4
Y3
seg_sel[3]
DIG5
DIG_EN5
Y8
seg_sel[4]
DIG6
DIG_EN6
W8
seg_sel[5]
DIG7
DIG_EN7
W10
seg_sel[6]
DIG8
DIG_EN8
Y10
seg_sel[7]
  
X1
  

SYS_CLK
G1
clk
  
K1
  

SYS_RST
AB12
rst_n


图3.6-19 QUARTUS配置管脚
4.4 再次综合

再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“Start Compilation”,再次对整个工程进行编译和综合,如下图所示。

图3.6-20QUARTUS编译选项

当出现如下所示的编译成功标志时则说明编译综合成功。


图3.6-21QUARTUS编译成功标志
4.5 连接开发板

完成编译后开始进行上板调试操作,按照下图的方式,将下载器接入电脑USB接口,接上开发板电源后按下开发板下方蓝色开关,硬件连接完毕。


图3.6-22开发板连接图

4.6 上板

打开QUARTUS界面,单击界面中的“ ”,会弹出配置界面。在界面中点击“add file”添加“.sof”文件后点击“Start”,会在“Progress”出现显示进度。

图3.6-23QUARTUS界面
当进度条到100%时提示成功,如下图所示,此时即可在开发板上观察到相应的现象。

图3.6-24QUARTUS下载程序界面
如果操作没有错误,此时可以观察到数码管像秒表计时一样显示数字,如果显示的时间间隔或者数字发生错误,则需要从头开始进行错误排查。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定会有更多的收获。

第5节 简化版步骤分享

这里依旧会分享简化版的步骤,方便掌握基础原理后进行反复操作复习。


5.1 设计实现

5.1.1 顶层信号

新建目录:D:mdy_bookmy_time。在该目录中,新建一个名为my_time.v的文件,用GVIM打开后开始编写代码。
确定顶层信号。工程信号和硬件的对应关系如下表所示:
表3.6 - 1信号和管脚关系
  
器件
  
信号线
信号线
FPGA管脚
内部信号
  
U6,U7
  
SEG_E
SEG0
Y6
seg_ment[2]
SEG_DP
SEG1
W6
未用到
SEG_G
SEG2
Y7
seg_ment[0]
SEG_F
SEG3
W7
seg_ment[1]
SEG_D
SEG4
P3
seg_ment[3]
SEG_C
SEG5
P4
seg_ment[4]
SEG_B
SEG6
R5
seg_ment[5]
SEG_A
SEG7
T3
seg_ment[6]
DIG1
DIG_EN1
T4
seg_sel[0]
DIG2
DIG_EN2
V4
seg_sel[1]
DIG3
DIG_EN3
V3
seg_sel[2]
DIG4
DIG_EN4
Y3
seg_sel[3]
DIG5
DIG_EN5
Y8
seg_sel[4]
DIG6
DIG_EN6
W8
seg_sel[5]
DIG7
DIG_EN7
W10
seg_sel[6]
DIG8
DIG_EN8
Y10
seg_sel[7]
  
X1
  

SYS_CLK
G1
clk
  
K1
  

SYS_RST
AB12
rst_n

写出顶层信号代码:
1
  
2
  
3
  
4
  
5
  
6
module  my_time(
  
clk  ,
  
rst_n  ,
  
seg_sel,
  
seg_ment
  
    );

声明输入输出属性:
1
  
2
  
3
  
4
input           clk  ;
  
input           rst_n  ;
  
output   [7:0]   seg_sel   ;
  
output   [6:0]   seg_ment  ;

5.1.2 信号设计

进行架构设计。根据设计目标,得出波形图示意如下:

图3.6-3 秒表的实现架构图

设计计数器架构,表示时间的计数器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== x-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== 10-1 ;

设计输出信号seg_sel,其代码如下:
1
assign  seg_sel = 8'hfe ;

设计输出信号seg_ment,其代码如下:
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(cnt1==0)begin
  
seg_ment<=  7'h01;
  
    end
  
    else if(cnt1==1)begin
  
seg_ment<=  7'h4f;
  
    end
  
    else if(cnt1==2)begin
  
seg_ment<=  7'h12;
  
    end
  
    else if(cnt1==3)begin
  
seg_ment<=  7'h06;
  
    end
  
    else if(cnt1==4)begin
  
seg_ment<=  7'h4c;
  
    end
  
    else if(cnt1==5)begin
  
seg_ment<=  7'h24;
  
    end
  
    else if(cnt1==6)begin
  
seg_ment<=  7'h20;
  
    end
  
    else if(cnt1==7)begin
  
seg_ment<=  7'h0f;
  
    end
  
    else if(cnt1==8)begin
  
seg_ment<=  7'h00;
  
    end
  
    else if(cnt1==9)begin
  
seg_ment<=  7'h04;
  
    end
  
end

设计变量x,其代码如下:
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
always  @(*)begin
  
    if(cnt1==0)begin
  
        x = 50_000_000;
  
    end
  
    else if(cnt1==1)begin
  
        x = 100_000_000;
  
    end
  
    else if(cnt1==2)begin
  
        x = 150_000_000;
  
    end
  
    else if(cnt1==3)begin
  
        x = 200_000_000;
  
    end
  
    else if(cnt1==4)begin
  
        x = 250_000_000;
  
    end
  
    else if(cnt1==5)begin
  
        x = 300_000_000;
  
    end
  
    else if(cnt1==6)begin
  
        x = 350_000_000;
  
    end
  
    else if(cnt1==7)begin
  
        x = 400_000_000;
  
    end
  
    else if(cnt1==8)begin
  
        x = 450_000_000;
  
    end
  
    else if(cnt1==9)begin
  
        x = 500_000_000;
  
    end
  
end
至此,主体程序已经完成,下面将module补充完整。
5.1.3 信号定义
首先定义信号类型,cnt0的信号定义如下:
1
reg    [28:0]   cnt0  ;

add_cnt0和end_cnt0的信号定义如下:
1
  
2
wire     add_cnt0   ;
  
wire     end_cnt0   ;

cnt1、add_cnt1和end_cnt1的信号定义如下:
1
  
2
  
3
reg    [3:0]            cnt1        ;
  
wire                     add_cnt1    ;
  
wire                     end_cnt1    ;

seg_sel的信号定义如下:
1
wire    [7:0]   seg_sel   ;

seg_ment的信号定义表示如下:
1
reg   [6:0]   seg_ment   ;

x的信号定义表示如下:
1
reg   [28:0]   x   ;

至此,整个代码的设计工作已经完成。整体代码如下:
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
   

  
module  miaobiao(
  
clk  ,
  
rst_n  ,
  
seg_sel,
  
seg_ment
  
    );
  
  
  
  
    input               clk  ;
  
    input               rst_n  ;
  
  
  
    output     [7:0]  seg_sel   ;
  
    output    [6:0]  seg_ment   ;
  
  
  
    reg           [28:0]      cnt0;
  
    reg           [3:0]       cnt1;
  
    wire       add_cnt0;
  
    wire       end_cnt0;
  
    wire       add_cnt1;
  
    wire       end_cnt1;
  
    wire     [7:0]  seg_sel   ;
  
    reg    [6:0]  seg_ment   ;
  
  
    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== 50000000-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==10-1 ;
  
    assign seg_sel  = 8'hfe;
  
always  @(posedge clk or negedge rst_n)begin
  
    if(rst_n==1'b0)begin
  
seg_ment<=  7'h01;
  
    end
  
    else if(cnt1==0)begin
  
seg_ment<=  7'h01;
  
    end
  
    else if(cnt1==1)begin
  
seg_ment<=  7'h4f;
  
    end
  
    else if(cnt1==2)begin
  
seg_ment<=  7'h12;
  
    end
  
    else if(cnt1==3)begin
  
seg_ment<=  7'h06;
  
    end
  
    else if(cnt1==4)begin
  
seg_ment<=  7'h4c;
  
    end
  
    else if(cnt1==5)begin
  
seg_ment<=  7'h24;
  
    end
  
    else if(cnt1==6)begin
  
seg_ment<=  7'h20;
  
    end
  
    else if(cnt1==7)begin
  
seg_ment<=  7'h0f;
  
    end
  
    else if(cnt1==8)begin
  
seg_ment<=  7'h00;
  
    end
  
    else if(cnt1==9)begin
  
seg_ment<=  7'h04;
  
    end
  
end
  
always  @(*)begin
  
    if(cnt1==0)begin
  
        x = 50_000_000;
  
    end
  
    else if(cnt1==1)begin
  
        x = 100_000_000;
  
    end
  
    else if(cnt1==2)begin
  
        x = 150_000_000;
  
    end
  
    else if(cnt1==3)begin
  
        x = 200_000_000;
  
    end
  
    else if(cnt1==4)begin
  
        x = 250_000_000;
  
    end
  
    else if(cnt1==5)begin
  
        x = 300_000_000;
  
    end
  
    else if(cnt1==6)begin
  
        x = 350_000_000;
  
    end
  
    else if(cnt1==7)begin
  
        x = 400_000_000;
  
    end
  
    else if(cnt1==8)begin
  
        x = 450_000_000;
  
    end
  
    else if(cnt1==9)begin
  
        x = 500_000_000;
  
    end
  
end
  
endmodule


5.2 综合与上板


5.2.1 新建工程
接下来新建工程并上板查看现象。首先打开软件Quartus Ⅱ,点击“File”下拉列表中的New Project Wzard...新建工程选项。

图3.6-8Quartus新建工程
直接点击“Next”。

图3.6- 9 Quartus新建工程介绍
此时出现的是工程文件夹、工程名、顶层模块名设置界面,设置目录为:D:/mdy_book/miaobiao,工程名和顶层名为miaobiao,完成设置后点击“Next”。

图3.6- 10 QUARTUS新建工程设置名称
选择“Empty project”后点击“Next”。

图3.6- 11 QUARTUS新建工程类型

点击右侧的“Add”按钮,选择“miaobian.v”文件,添加文件后点击“Next”。

图3.6- 12 QUARTUS添加文件

对芯片型号进行选择:“Device family”选项中选择“Cyclone ⅣE”,“Available devices”选项下选择“EP4CE15F23C8”,随后点击“Next”。

图3.6- 13 QUARTUS选择芯片型号

直接点击“Next”。

图3.6- 14 QUARTUS设置工具界面

点击“Finish”,完成新建工程。

图3.6- 15 QUARTUS新建工程汇总界面


5.2.2 综合
新建工程后界面如下图所示,点击“编译”。

图3.6- 16 QUARTUS新建工程后界面
编译成功界面如下图。

图3.6- 17QUARTUS编译后界面

5.2.3 配置管脚
在菜单栏点击“Assignments”后点击“Pin Planner”,此时会弹出配置管脚的窗口。

图3.6- 18 QUARTUS配置管脚选项

在配置窗口“location”根据信号和管脚关系配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。

图3.6- 18 QUARTUS配置管脚

5.2.4 再次综合
打开“QUARTUS”软件,在菜单栏中选择“Processing”,点击“Start Compilation”。

图3.6- 19 QUARTUS编译选项

出现 QUARTUS 编译成功标志则表示此次编译成功。

图3.6- 20 QUARTUS编译成功标志
5.2.5 连接开发板
下载器接入电脑 USB 接口,开发板接上电源后按下蓝色开关。

图3.6- 21 开发板连接图

5.2.6 上板
打开 QUARTUS 界面,单击“  ”图标。

图3.6- 22 QUARTUS界面
点击“add file”,添加.sof文件,随后点击“Start”。在“Progress”中会显示进度,当进度条显示“100%”表示成功,可在开发板上观察现象。

图3.6- 23 QUARTUS下载程序界面


第6节 扩展练习
至此,通过数码管实现秒表功能的设计已经分享完毕,相信读者朋友已经可以完全掌握这一工程。在掌握工程的基础上可以多做一些思考,在工程原理不变的基础上进行一定的数据调整,比如对计数时间进行改变或尝试改变显示数码管个数等,挑战独立完成多个设计。也欢迎有更好思路和想法的同学前往至简设计法论坛上进行讨论。

   拓展阅读