实验三 分秒数字钟的设计与实现
实验资料 实验源码

实验目的

  1. 掌握基于 SystemVerilog HDL 的时序逻辑电路建模方法;
  2. 掌握计数器设计方法,并能够使用计数器设计使能时钟(用于时钟分频);
  3. 掌握移位寄存器设计方法,并能够利用移位寄存器设计边沿检测电路;
  4. 掌握 7 段数码管的动态显示。

实验内容

基于 SystemVerilog HDL 设计并实现一个分秒数字钟。整个工程的顶层模块 如图 3-6 所示,输入/输出端口如表 3-1 所示。使用 4 个七段数码管显示当前的 计时。其中,两个数码管对应“分”,另两个数码管对应“秒”。通过 1 个拨动开关 对数字钟进行复位控制。使用 1 个按键对数字中进行“暂停/计时”控制,按键每按 下一次,进行暂停和计时的切换,即暂停时,按下按键启动计时;计时过程中, 按下按键暂停计时。

实验原理

详情见实验资料中实验指导书

实验步骤

1

  1. 首先实现边沿检测电路,命名为chk_asc模块。阅读实验指导书可知,i_start可看作一个按钮,每按一次改变一次当前电路的状态。所以chk_asc模块中需要使用到2位移位寄存器,来存储两个状态,可看作现态和次态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
`timescale 1ns / 1ps

module chk_asc(
input sys_clk,
input sys_rst_n,
input cin,
output logic cout
);

logic [1:0]temp=0;
logic en=0;
always_ff @(posedge sys_clk) begin
if(sys_rst_n==1) begin
temp={cin,temp[1]};
if(temp[1]==1&&temp[0]==0) en=~en;
end
else begin
temp=0;
en=0;
end
end
assign cout=(en==1?1:0);

endmodule
  1. 然后实现使能时钟生成器,命名为en_clock模块。其中时钟周期为40ns,而七段数码管动态扫描周期为1ms,所以可以得出每经过25000个时钟周期,七段数码管会进行一次扫描。建模时可以使用一个计数器来实现这个功能。同时,分秒数字时钟一共需要4个七段数码管,所以输出an应该有4个状态。
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
`timescale 1ns / 1ps

module en_clock(
input sys_clk,
input sys_rst_n,
output logic [3:0]cout
);

integer r_cnt=0;
always_ff @(posedge sys_clk) begin
if(sys_rst_n==1) begin
if(r_cnt==24999) begin
if(cout==4'b0000) cout=4'b0001;
else if(cout==4'b0001) cout=4'b0010;
else if(cout==4'b0010) cout=4'b0100;
else if(cout==4'b0100) cout=4'b1000;
else if(cout==4'b1000) cout=4'b0001;
r_cnt=0;
end
else begin
r_cnt=r_cnt+1;
end
end
else begin
r_cnt=0;
cout=4'b0;
end
end

endmodule
  1. 之后实现计时电路,命名为cnt_clock模块。分秒时钟每加1秒需要经过2千500万个40ns的时钟周期。与使能时钟生成器类似,可以使用计数器实现此功能。
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
`timescale 1ns / 1ps

module cnt_clock(
input sys_clk,
input sys_rst_n,
input en,
output logic [7:0]min,
output logic [7:0]sec
);

integer cnt=0;
integer CNT=0;
always_ff @(posedge sys_clk) begin
if(sys_rst_n==1) begin
if(en==1) begin
if(cnt==24999) begin
cnt=0;
CNT=CNT+1;
if(CNT==999) begin
CNT=0;
sec=sec+1;
if(sec==60) begin
min=min+1;
sec=0;
end
end
end
else begin
cnt=cnt+1;
end
end
end
else begin
cnt=0;
CNT=0;
min=0;
sec=0;
end
end

endmodule
  1. 还需要写一个BCD码转换为七段数码管信号的模块,命名为BCD_2_7Dig模块。使用case语句即可完成。
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
`timescale 1ns / 1ps

module BCD_2_7Dig(
input [3:0] BCD,
output logic [7:0] a_to_g
);

always @(*) begin
case (BCD)
// gfedcba
4'b0000:a_to_g=8'b11000000;
4'b0001:a_to_g=8'b11111001;
4'b0010:a_to_g=8'b10100100;
4'b0011:a_to_g=8'b10110000;
4'b0100:a_to_g=8'b10011001;
4'b0101:a_to_g=8'b10010010;
4'b0110:a_to_g=8'b10000010;
4'b0111:a_to_g=8'b11011000;
4'b1000:a_to_g=8'b10000000;
4'b1001:a_to_g=8'b10010000;
default: a_to_g=8'b11111111;
endcase
// 8'b11111111:nothing
// 8'b01111111:P(dot)
// 8'b10111111:G
// 8'b11011111:F
// ?
// 8'b11101111:E
// 8'b11110111:D
// 8'b11111011:C
// 8'b11111101:B
// 8'b11111110:A
// ?
end

endmodule
  1. 完成以上4个模块后,可以在dig_clock中进行实例化了。

需要:

  • 1个cn_clock模块:每个7段数码管扫描周期提供一个使能信号,即输出an,判断当前需要控制哪一个7段数码管;

  • 1个chk_asc模块:通过i_start判断当前时钟的状态;

  • 1个cnt_clock模块:当使能端有效时,进行计时;

  • 2个bin2bcd_0模块(由老师提供):将cnt_clock模块得到的分和秒的二进制数转换为BCD码;

  • 4个BCD_2_7Dig模块:将所得到的BCD码分成四部分再转换为7段数码管的信号;

    此外,由于每次输出a_to_g只能控制一个7段数码管(即实验报告中所提到的,4个7段数码管并不是同时亮或灭,是有规律地高频的切换),所以要根据cn_clock模块得到的使能信号,给a_to_g赋值。

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
`timescale 1ns / 1ps

module dig_clock(
input sys_clk,
input sys_rst_n,
input i_start,
output logic [3 : 0] an,
output logic [7 : 0] a_to_g
);

// 输出七段数码管的使能端
logic [3:0] en;
en_clock en_clk(sys_clk,sys_rst_n,en);
assign an=en;
// 输出计时器状态,开始/暂停
logic i_start_en;
chk_asc chkasc(sys_clk,sys_rst_n,i_start,i_start_en);
// 输出计时器的时间,分+秒
logic [7:0]min;
logic [7:0]sec;
cnt_clock cnt_clk(sys_clk,sys_rst_n,i_start_en,min,sec);
// Binary to BCD
logic [7:0]minBCD;
logic [7:0]secBCD;
bin2bcd_0 minute(min,minBCD);
bin2bcd_0 second(sec,secBCD);
// BCD to a_to_g
logic [7:0] a_to_g_min_01;
BCD_2_7Dig min_01(minBCD[3:0],a_to_g_min_01);
logic [7:0] a_to_g_min_10;
BCD_2_7Dig min_10(minBCD[7:4],a_to_g_min_10);
logic [7:0] a_to_g_sec_01;
BCD_2_7Dig sec_01(secBCD[3:0],a_to_g_sec_01);
logic [7:0] a_to_g_sec_10;
BCD_2_7Dig sec_10(secBCD[7:4],a_to_g_sec_10);
integer r_cnt=0;
always @(*) begin
a_to_g=8'b11111111;
if(i_start_en==1) begin
if(en==4'b0000) a_to_g=8'b11111111;
else if(en==4'b0001) a_to_g=a_to_g_sec_01;
else if(en==4'b0010) a_to_g=a_to_g_sec_10;
else if(en==4'b0100) begin
a_to_g=a_to_g_min_01;
a_to_g[7]=0;
end
else if(en==4'b1000) a_to_g=a_to_g_min_10;
end
end

endmodule
  1. 实验主要工作完成,过程中用到了很多仿真文件进行检查。之后进行仿真,运行,生成bin文件,在实验平台运行即可。

实验过程中所用到的所有完整代码均已存放到git仓库中

实验结果

波形图

2

FPGA

测试截图

3

测试视频

实验问题

  1. 画出分秒数字钟电路的原理图(模块级别即可,如使能时钟模块、边沿 检测模块等)

  2. 分秒数字钟电路中一共使用了几个计数器,作用分别是什么?

答:2个。

第一个用来将时钟频率分频为7段数码管扫描的频率;

第二个用来将时钟频率分频为1Hz,实现计时功能,每秒+1。