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

2.8 数字时钟设计

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

本节的文档编号:001600000019

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

1.至简原理与应用配套的案例文档
2.使用6个数码管实现数字时钟功能,与数字时钟相同,该功能可以显示00:00:00到23:59:59范围的时间。
3. 这是ALTERA入门学习案例文档

1项目背景

数字时钟用数字电路技术实现时、分、秒计时显示的装置。用数字同时显示时,分,秒的精确时间,并能实现准确校时。与传统表盘式机械式时钟相比,具有更高的准确性和直观性,且无机械装置,具有更长的使用寿命。不仅如此,还具备体积小、重量轻、抗干扰能力强、对环境要求高、高精确性、容易开发等特性。本案例将详细介绍用至简设计法实现数字时钟的功能。


2 设计目标

本工程使用6个数码管实现数字时钟功能。该功能与数字时钟相同,即从00:00:00一直到23:59:59

上板效果图如下图所示。

288


3 设计实现

3.1 顶层信号

新建目录:D:mdy_bookmy_shizhong在该目录中,新建一个名为my_shizong.v的文件,并用GVIM打开,开始编写代码。

我们要实现的功能,概括起来就是控制8个数码管,让其中2个数码管常灭,其他6个数码显示不同的数字。要控制8个数码管,就需要控制位选信号,即FPGA要输出一个8位的位选信号,设为seg_sel,其中seg_sel[0]对应数码管0seg_sel[1]对应数码管1,以此类推,seg_sel[7]对应数码管7

要显示不同的数字,就需要控制段选信号,不需要用到DP,一共有7根线,即FPGA要输出一个7位的段选信号,设为seg_mentseg_ment[6]~segm_ment[0]分别对应数码管的abcdefg(注意对应顺序)。

我们还需要时钟信号和复位信号来进行工程控制。

综上所述,我们这个工程需要4个信号,时钟clk,复位rst_n,输出的位选信号seg_sel和输出的段选信号seg_ment

器件

信号线

信号线

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_shizhong。并且我们已经知道该模块有4个信号:clkrst_nseg_selseg_ment,代码如下:

1

2

3

4

5

6

module my_shizhong(

clk     ,

rst_n   ,

seg_sel ,

seg_ment

);

其中clkrst_n1位的输入信号,seg_sel8位的输出信号,seg_ment7位的输出信号,根据此,补充输入输出端口定义。代码如下:

1

2

3

4

input       clk     ;

input       rst_n   ;

output[7:0] seg_sel ;

output[6:0] seg_ment;


3.2 信号设计

我们先分析要实现的功能,我们用m_gm_sf_gf_ss_gs_s分别表示秒钟个位、秒钟十位、分钟个位、分钟十位、小时个位和小时十位所表示的数字值,m_g_sm_s_sf_g_sf_s_ss_g_ss_s_s分别表示秒钟个位、秒钟十位、分钟个位、分钟十位、小时个位和小时十位所表示数码管段选值。

数码管0显示的是秒个位值,则翻译成信号就是seg_sel的值为8’b1111_1110seg_ment的值为:m_g_s。数码管1显示秒十位,也就是说seg_sel的值为8’b1111_1101seg_ment的值为m_s_s。以此类推,数码管5显示小时十位,也就是seg_sel的值为8’b1101_1111seg_ment的值为s_s_s

289

那么seg_mentseg_sel多久变化一次呢?我们就需要用到数码管动态扫描原理了。

数码管动态显示接口是应用最为广泛的一种显示方式之一,动态驱动是将所有数码管的8个显示笔划"a,b,c,d,e,f,g,dp"的同名端连在一起,另外为每个数码管的公共极COM增加位选通控制电路,位选通由各自独立的I/O线控制,当要输出字形码时,所有数码管都接收到相同的字形码,但究竟是哪个数码管会显示出字形,取决于单片机对位选通COM端电路的控制,所以我们只要将需要显示的数码管的选通控制打开,该位就显示出字形,没有选通的数码管就不会亮。通过分时轮流控制各个数码管的的COM端,就使各个数码管轮流受控显示,这就是动态驱动。在轮流显示过程中,每位数码管的点亮时间为12ms,由于人的视觉暂留现象及发光二极管的余辉效应,尽管实际上各位数码管并非同时点亮,但只要扫描的速度足够快,给人的印象就是一组稳定的显示数据,不会有闪烁感,动态显示的效果和静态显示是一样的,能够节省大量的I/O端口,而且功耗更低。

也就是说,只要刷新时间为1~2ms,那么人眼看起来就似乎是所有数码管都在显示。因为我们把这个刷新时间定为2ms,也就是如下图。

290

由波形图可知,我们需要1个计数器用来计算2毫秒的时间。本工程的工作时钟是50MHz,即周期为20ns,计数器计数到2_000_000/20=100_000个,我们就能知道2毫秒时间到了。另外,由于该计数器是不停地计数,永远不停止的,可以认为加1条件一直有效,可写成:assign add_cnt0==1。综上所述,该计数器的代码如下。

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 ;

再次观察波形图,我们发现依次是显示的是m_g_sm_s_sf_g_sf_s_ss_g_ss_s_s,一共6个,循环进行显示。这说明还需要另外一个计数器来表示第几个。该计数器每隔2ms1,因此加1条件为end_cnt0。该计数器一共要数6次。所以代码为:

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次的时候输出值为8’hfe;在第2次的时候输出值为8’hfd;以此类推,在第6次的时候输出值为8’hdf。我们用信号cnt1来代替第几次,也就是:当cnt1==0的时候,输出值为8’hfe;在cnt1==1的时候输出值为8’hfd;以此类推,在cnt1==5的时候输出值为8’hdf。再进一步翻译成代码,就变成如下:

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

或者可以简写成:

1

2

3

4

5

6

7

8

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<

end

end

对上面代码解释一下,第131行是指先将8’b1向左移位,再取反后的值,赋给seg_sel。假设此时cnt1等于0,那么8’b1<<0的结果是8’b0000_0001,取反的值为8’hfe;假设cnt1等于3,那么8’b1<<3的结果为8’b000_1000,取反后的结果为8’b1111_0111,即8’hf7。与第一种写法的结果都是相同的。

我们来思考输出信号seg_ment的变化。seg_ment的值,是m_gm_sf_gf_ss_gs_s等数值分别译码成数码管显示的信号m_g_sm_s_sf_g_sf_s_ss_g_ss_s_s。我们一种做法,是将数值分别转成译码信号,然后再挑选出来给seg_ment。如下面的原理图:

291

我们还可以是先可以选择哪一组数据,选出来后再做译码,其原理图如下:

292

对比两个原理图,可以发现,实现同样的功能,第二个原理图比第一个,少了5个译码电路。这是一个简单的例子,说明实现一个功能,可以有很多种方法,方法各有优势,能用较少的资源、较快的速度实现功能,这就是设计师的能力,也是FPGA设计的魅力所在。

我们采用第二种实现方案。设计一个信号sel_data,表示从6个数据中选择的信号,之后再做译码。波形图更新如下:

293

sel_data的值有可能为0~9中的任意数字,这些数字都要转成数码管的段选信号。下表列出不同数字对应的段选信号值。

数字

共阳abcdefg

2进制

共阳abcdefg

16进制

共阴abcdefg

2进制

共阴abcdefg

16进制

0

7b0000001

7h01

7b 1111110

7h7e

1

7b 1001111

7h4f

7b 0110000

7h30

2

7b 0010010

7h12

7b 1101101

7h6d

3

7b 0000110

7h06

7b 1111001

7h79

4

7b 1001100

7h4c

7b 0110011

7h33

5

7b 0100100

7h24

7b 1011011

7h5b

6

7b 0100000

7h20

7b 1011111

7h3f

7

7b 0001111

7h0f

7b 1110000

7h70

8

7b 0000000

7h00

7b 1111111

7h7f

9

7b 0000100

7h04

7b 1111011

7h7b

明德扬开发板使用的是共阳数码管。根据上表,可写出下面代码。

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  @(posedgeclk or negedgerst_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_datam_gm_sf_gf_ss_gs_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_gm_sf_gf_ss_gs_s信号,按照常识,秒个位m_g的变化规律如下:

294

秒个位的数据是012~90这样有规律的加1变化,很明显可以看出这个m_g其实就是一个计数器,并且这个计数器每隔1秒时间才变化加1一次。1秒时间计时也需要一个计数器,设为cnt2,则cnt2是一直加1的,即可以写成assign add_cnt2 = 1cnt2要数1秒时间,本工程的工作时钟是50MHz,即周期为20ns,计数器计数到1_000_000_000/20=50_000_000个,我们就能知道1秒时间到了,所以cnt1的周期是 50_000_000个。有了cnt1,我们就知道m_g这个计数器的加1条件是1秒时间到了,即end_cnt2==1,要计数10个。综上所述,可以写出cnt2m_g的代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedgeclk or negedgerst_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 ;


1

2

3

4

5

6

7

8

9

10

11

12

13

always @(posedgeclk or negedgerst_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_sm_s的变化情况如下:

295

m_s秒十位的指示,很明显每隔10秒就会加1,很明显,m_s也是一个计数器,加1条件是10秒时间到,也就是end_m_g。该计数器周期性是数6个。所以代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedgeclk or negedgerst_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_gf_g的变化情况如下:

296

f_g分个位的指示,很明显每隔1分钟即60秒就会加1,很明显,f_g也是一个计数器,加1条件是60秒时间到,也就是end_m_s。该计数器周期性是数10个。所以代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

always @(posedgeclk or negedgerst_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_sf_s的变化情况如下:

297

f_s分十位的指示,很明显每隔10分钟就会加1,很明显,f_s也是一个计数器,加1条件是10分钟到,也就是end_f_g。该计数器周期性是数6个。所以代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedgeclk or negedgerst_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_gs_g的变化情况如下:

298

s_g小时个位的指示,很明显每隔1小时即6分钟就会加1,很明显,s_g也是一个计数器,加1条件是1小时也就是60分钟时间到,也就是end_f_g

接下来我们需要好好思考这个小时个位,它的周期是多少。有读者认为是10个,因为是小时个位会从0~9这样变化;有读者认为是24个,因为一共是24小时;有读者认为是4个,因为小时个位会从0~4变化。

认为是24小时的读者,没有认识到我们只是看小时个位的变化,而不是小时个位和小时十位整体,整体才是24小时。我们盯着小时个位,会发现它是这么一个规律:0~90~90~30~90~90~3。因此正确的答案是,有时候周期是10,有时候是4,也就是说周期性会变的。按照明德扬的变量法,设其周期是x,则可以写出s_g的代码如下,而x是怎么来,我们先不考虑,后期再说。

1

2

3

4

5

6

7

8

9

10

11

12

13

always @(posedgeclk or negedgerst_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_ss_s的变化情况如下:

299

s_s小时十位的指示,很明显当小时个位要清零时(注意不是每隔10小时),s_s就会加1,很明显,s_s也是一个计数器,加1条件是小时个位要清零了,也就是end_s_g。该计数器周期性是数3个。所以代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedgeclk or negedgerst_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~90~90~30~90~90~3,也就是周期性是10或者是4。至于是10还是4,则取决于小时十位,即s_s。当时钟的小时十位显示为2时,小时个位只要数到3就行了,没有25点的。综上所术,发s_s2时,x=4,否则x=10。所以x的代码如下:

1

2

3

4

5

6

always  @(*)begin

if(s_s==2)

x = 4;

else

x = 10;

end

此次,主体程序已经完成。接下来是将module补充完整。


3.3 信号定义

接下来定义信号类型。

cnt0是用always产生的信号,因此类型为regcnt0计数的最大值为100_000,需要用17根线表示,即位宽是17位。add_cnt0end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [16:0]     cnt0     ;

wire            add_cnt0 ;

wire            end_cnt0 ;

cnt1是用always产生的信号,因此类型为regcnt1计数的最大值为6,需要用3根线表示,即位宽是3位。add_cnt1end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者11根线表示即可。因此代码如下:

1

2

3

reg  [2:0]      cnt1    ;

wire            add_cnt1;

wire            end_cnt1;

seg_sel是用always方式设计的,因此类型为reg,其一共有8根线,即位宽为8。因此代码如下:

1

reg  [ 7:0]  seg_sel  ;

seg_ ment是用always方式设计的,因此类型为reg,其一共有7根线,即位宽为7。因此代码如下:

1

reg   [ 6:0]  seg_ment ;

sel_data是用always设计的,所以类型为wire。其最大值为9,所以需要4位位宽。代码如下:

1

reg  [ 3:0]   sel_data  ;

cnt2是用always产生的信号,因此类型为regcnt2计数的最大值为50_000_000,需要用26根线表示,即位宽是26位。add_cnt2end_cnt2都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [25:0]     cnt2     ;

wire            add_cnt2 ;

wire            end_cnt2 ;

m_g是用always产生的信号,因此类型为regm_g计数的最大值为9,需要用4根线表示,即位宽是4位。add_m_gend_m_g都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [ 3:0]     m_g      ;

wire            add_m_g  ;

wire            end_m_g  ;

m_s是用always产生的信号,因此类型为regm_s计数的最大值为5,需要用3根线表示,即位宽是3位。add_m_send_m_s都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [ 2:0]     m_s      ;

wire            add_m_s  ;

wire            end_m_s  ;

f_g是用always产生的信号,因此类型为regf_g计数的最大值为9,需要用4根线表示,即位宽是4位。add_f_gend_f_g都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [ 3:0]     f_g      ;

wire            add_f_g  ;

wire            end_f_g  ;

f_s是用always产生的信号,因此类型为regf_s计数的最大值为5,需要用3根线表示,即位宽是3位。add_f_send_f_s都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [ 2:0]     f_s      ;

wire            add_f_s  ;

wire            end_f_s  ;

s_g是用always产生的信号,因此类型为regs_g计数的最大值为9,需要用4根线表示,即位宽是4位。add_s_gend_s_g都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [ 3:0]     s_g      ;

wire            add_s_g  ;

wire            end_s_g  ;

s_s是用always产生的信号,因此类型为regs_s计数的最大值为2,需要用2根线表示,即位宽是2位。add_s_send_s_s都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

3

reg  [ 1:0]    s_s      ;

wire            add_s_s  ;

wire            end_s_s  ;

x是用always产生的信号,因此类型为regx计数的最大值为10,需要用4根线表示,即位宽是4位。

1

reg  [ 3: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

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 @(posedgeclk or negedgerst_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 @(posedgeclk or negedgerst_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  @(posedgeclk or negedgerst_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  @(posedgeclk or negedgerst_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 @(posedgeclk or negedgerst_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 @(posedgeclk or negedgerst_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 @(posedgeclk or negedgerst_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 @(posedgeclk or negedgerst_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 @(posedgeclk or negedgerst_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 @(posedgeclk or negedgerst_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 @(posedgeclk or negedgerst_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 新建工程

首先在d盘中创建名为“my_shizhong”的工程文件夹,将写的代码命名为“my_shizhong.v,顶层模块名为“my_shizhong”。

300

301

然后打开QuartusⅡ,点击File下拉列表中的New Project Wzard...新建工程选项。

302

3.再出现的界面中直接点击Next

303

4.之后出现的是工程文件夹、工程名、顶层模块名设置界面。按照之前的命名进行填写,然后点击Next,在出现的界面选择empty project

304

305

5.之后是文件添加界面。添加之前写的“my_shizhong.v”文件,点击右侧的“Add”按钮,之后文件还会出现在下方大方框里,之后点击“Next”。

306

器件型号选择界面。在上方红色箭头处选择Cyclone E,在中间红色箭头处选择EP4CE15F23C8,然后点击“Next”。

307

EDA工具界面。直接点击“Next”。

308

8.之后出现的界面,点击“Finish”。

309


4.2 综合

1.新建工程步骤完成后,就会出现以下界面。选中要编译的文件,点击编译按钮。

310

2.编译成功后会出现一下界面。

311


4.3 配置管脚

312

在菜单栏中,选中Assignments,然后选择Pin Planner,就会弹出配置管脚的窗口。

313

在配置窗口最下方中的location一列,参考下表中最右两列配置好FPGA管脚。

器件

信号线

信号线

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

配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。


4.4 再次综合

314

在菜单栏中,选中Processing,然后选择Start Compilation,再次对整个工程进行编译和综合。

315

出现上面的界面,就说明编译综合成功。


4.5 连接开发板

图中,下载器接入电脑USB接口,电源接入电源,然后摁下蓝色开关。

316


4.6 上板

1.双击Tasks一栏中”Program Device”。

317

2.会出现如下界面,点击add file添加.sof文件,点击“Start”,会在“Progress”出显示进度。

318

3.进度条中提示成功后,即可在开发板上观察到相应的现象。


   拓展阅读