FPGA.数字闹钟

用FPGA设计一个数字闹钟应该说是一个比较综合的小系统,包含了按键、数码管、状态机等内容,本文主要是讲述三键输入的数字闹钟总体的设计,整个作品和小时候两三块一个的电子手表十分类似。下面具体讲解…

功能描述

1 用四个数码管正常显示时、分高低位,实现正常显示时间。
2 具备调分调时功能
3 闹钟功能

功能并不复杂,我们现在来分析一下需要哪些模块。
首先是时钟功能,正常显示时间肯定是必须的,为实现这一可以设计一个60进制计数器和一个24进制计数器,当然也可以根据逻辑直接书写出来,但无论是什么办法,这肯定需要一个模块来实现它。

第二就是调分调时了,说白了就是置数,要置数,那么就必须有一个区域去控制数据,也需要一个地方存储数据,然后将置数的数据传给时钟,所以它应该与键盘的联系,内部有存储器。

第三是闹钟,闹钟不难想,比较器,我设定闹钟时间,然后与时钟的时间比较,如果两者相同,输出闹钟信号,就是如此。

最后的便是显示电路,主要是数码管的显示原理,驱动数码管显示时间。

就是这几样部分,貌似这么一说,确实没有什么东西,一个系统就是需要输入和输出相互协调好,这里面的逻辑必须是相互对应的,不出矛盾,个人认为,这是设计的难度所在。

整体设计图

模块讲解

键盘模块(key)

输入 功能说明 输出 功能说明
add_in c_add 时钟加
aub_in c_sub 时钟减
model_in 模式控制端 a_hour 调闹钟小时
clk 时钟 a_minute 调闹钟分钟
rst_n 复位 cnt_control 计数器开关
Display_Model 显示控制
Time_model 调时钟转换信号

细节讲解

model:模式的选择寄存器

整个闹钟系统我设置为五个模式,所以model需要是三位的[2:0]
00:时钟正常走时
01:时钟调分模式,该模式下时钟的计数器停止,时钟是不走的,同时显示模式也会转到调时钟模式。
10:时钟调时模式,与调分模式类似。
11:闹钟调分设置模式,此时时钟走时,显示模式为闹钟模式。

100:闹钟调时模式,与调分时类似。

cnt_control:计数器开关

正常走时和调闹钟模式下,计数器开,cnt_control = 0;
当进入调分和调时模式,计数器关闭,cnt_control = 1。

Time_Model:调时钟转换信号

这个是连接时钟模块(clock)的,是调分模式和调时模式的切换信号。

Display_Model:显示控制

正常走时,进入调分和调时模式时,停止走时,整个过程我设置为同一种显示模式;
闹钟模式下,显示模式转换;
所以一共是两种模式,一根线足以。

代码展示



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
200
201
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
module key(
input clk,
input rst_n,
input add_in,//增加按钮
input sub_in,//减去按钮
input model_in,//模式选择

output reg Display_Model,
//时钟与闹钟显示选择,0 代表时钟,1代表闹钟

output reg cnt_control, //控制定时器
output reg [2:0]Time_model,
output reg c_add, //控制时钟 加
output reg c_sub, //控制时钟 减

output reg a_add, //控制闹钟 加
output reg a_sub //控制闹钟 减
);


/************************************/
parameter T40MS = 20'd40_000;
parameter T1S = 30'd1_000_000;
/************************************/

/// add_in 按键 /////
reg add;
reg [19:0]cnt_a;
reg [1:0]a;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt_a <= 0;
a <= 0;
add <= 1;
end
else
begin
case(a)
0:begin
if(cnt_a < T40MS)
// 按下时间大于40MS 认为有效
begin
if(!add_in)
cnt_a = cnt_a+1'b1;
else
cnt_a = 0;
end
else //counter> 40MS ,说明确实按键是按下了
begin
add = 0;
// 给冲击信号 ,0~1 是上升沿
a = 1;
//确定按键按下,转到状态 1
cnt_a = 0; //计数器清零
end
end
1:begin
add = 1; //产生尖脉冲
if(cnt_a < T40MS)
// 按下时间大于40MS 松开有效
begin
if(add_in)
cnt_a = cnt_a+1'b1;
else
cnt_a = 0;
end
else
begin
a = 0;
// 若松开,回到状态 0 ,等待下次按键到来
cnt_a = 0;
end
end
default : a = 1;
endcase
end
end
//////////////////////////////////////////////////////////

/// sub_in 按键 ///
reg sub;
reg [19:0]cnt_s;
reg [1:0]s;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt_s <= 0;
s <= 0;
sub <= 1;
end
else
begin
case(s)
0:begin
if(cnt_s < T40MS)
// 按下时间大于40MS 认为有效
begin
if(!sub_in)
cnt_s = cnt_s+1'b1;
else
cnt_s = 0;
end
else //counter> 40MS ,说明确实按键是按下了
begin
sub = 0;
// 给冲击信号 ,0~1 是上升沿
s = 1;
//确定按键按下,转到状态 1
cnt_s = 0; //计数器清零
end
end
1:begin
sub = 1; //产生尖脉冲
if(cnt_s < T40MS)
// 按下时间大于40MS 松开有效
begin
if(sub_in)
cnt_s = cnt_s+1'b1;
else
cnt_s = 0;
end
else
begin
s = 0;
// 若松开,回到状态 0 ,等待下次按键到来
cnt_s = 0;
end
end
default : s = 1;
endcase
end
end
////////////////////////////////////////////////////////////

/// model_in 按键 ///
reg model;
reg [19:0]cnt_m;
reg [1:0]m;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt_m <= 0;
m <= 0;
model <= 1;
end
else
begin
case(m)
0:begin
if(cnt_m < T40MS)
// 按下时间大于40MS 认为有效
begin
if(!model_in)
cnt_m = cnt_m+1'b1;
else
cnt_m = 0;
end
else //counter> 40MS ,说明确实按键是按下了
begin
model = 0;
// 给冲击信号 ,0~1 是上升沿
m = 1;
//确定按键按下,转到状态 1
cnt_m = 0; //计数器清零
end
end
1:begin
model = 1; //产生尖脉冲
if(cnt_m < T40MS)
// 按下时间大于40MS 松开有效
begin
if(model_in)
cnt_m = cnt_m+1'b1;
else
cnt_m = 0;
end
else
begin
m = 0;
// 若松开,回到状态 0 ,等待下次按键到来
cnt_m = 0;
end
end
default : m = 1;
endcase
end
end
////////////////////////////////////////////////////////////


/************************************************/
reg [2:0]type;
//00:时钟正常跑模式
//01:时钟调分模式,在该模式时间计数器停止计数
//10: 时钟调时模式,在该模式时间计数器停止计数
//11:闹钟调分模式,在该模式时间计数器正常计数
//100:闹钟调时模式,在该模式时间计数器正常计数
/************************************************/

always @(posedge clk or negedge rst_n)
if(!rst_n)
begin
Display_Model <= 1'b0;
a_add <= 1'b0;
a_sub <= 1'b0;
c_add <= 1'b0;
c_sub <= 1'b0;
Time_model <= 3'b000;
type <= 3'b000;
cnt_control <= 1'b1;//启动计数
end
else
begin
if(!model)
begin
if(type == 3'b100)
type = 3'b000;
else
begin
type = type + 1'b1;
end
end
case(type)
//时钟正常开始跑
3'b000:
begin
Time_model <= 3'b000;
cnt_control <= 1'b1;//启动计数
Display_Model <= 1'b0;
a_add <= 1'b0;
a_sub <= 1'b0;
c_add <= 1'b0;
c_sub <= 1'b0;
end
//调分模式
3'b001:
begin
cnt_control <= 1'b0; //关闭计数
Time_model <= 3'b001;
Display_Model <= 1'b0;
if(!add)//加
begin
c_add <=1'b1 ;
end
else
begin
c_add <= 1'b0;
end

if(!sub)//减
begin
c_sub <= 1'b1;
end
else
begin
c_sub <= 1'b0;
end
end
//调时模式
3'b010:
begin
cnt_control <= 1'b0;//关闭计数
Time_model <= 2'b010;
Display_Model <= 1'b0;
if(!add)//加
begin
c_add <=1'b1 ;
end
else
begin
c_add <= 1'b0;
end
if(!sub)//减
begin
c_sub <= 1'b1;
end
else
begin
c_sub <= 1'b0;
end
end

//调分模式
3'b011:
begin
cnt_control <= 1'b1; //kaijishu
Time_model <= 3'b011;
Display_Model <= 1'b1;
if(!add)//加
begin
a_add <=1'b1 ;
end
else
begin
a_add <= 1'b0;
end
if(!sub)//减
begin
a_sub <= 1'b1;
end
else
begin
a_sub <= 1'b0;
end
end

//调时模式
3'b100:
begin
cnt_control <= 1'b1;//关闭计数
Time_model <= 3'b100;
Display_Model <= 1'b1;

if(!add)//加
begin
a_add <=1'b1 ;
end
else
begin
a_add <= 1'b0;
end

if(!sub)//减
begin
a_sub <= 1'b1;
end
else
begin
a_sub <= 1'b0;
end
end

default:type <= 3'b000;
endcase
end
endmodule


时钟模块(clock)

输入 功能说明 输出 功能说明
c_add hour_h 小时高位
c_sub hour_l 小时低位
Time_model 模式控制端 minute_h 分钟高位
cot_control 计数器开关 minute_l 分钟低位
clk 时钟
rst_n 复位

细节讲解

时钟模块的输入都是来自键盘模块,所以输入不多说了,提一点,我的板子上的输入的时钟(clk),用的是50MHz晶振,而时钟是1Hz,也就是一秒走一下,所以内部必须分频,代码里注意看一下,我为了方便调试,分频并不是分频到1Hz,如果你有需要用到,修改里面的参数就好。

模块输出

模块的输出就是时钟的数据,是传给显示模块去驱动数码管显示的。
小时高位,0~2,三种情况,两根线[1:0]
小时低位,0~0,十种情况,四根线[3:0]
分钟高位,0~5,六种情况,三根线[2:0]
分钟低位,0~9,十种情况,四根线[3:0]

代码展示



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
module clock(
input clk,
input rst_n,
input cnt_control, //控制定时器 ,1启动 ,0停止
input c_add, //控制 加
input c_sub, //控制 减
input [2:0]Time_model,

output [1:0]hour_h, //小时 十位
output [3:0]hour_l, //小时 个位
output [2:0]minute_h, //分钟 十位
output [3:0]minute_l //分钟 个位
);
parameter S= 100000000;
parameter M=60;
/*********************************************************/
//1S计数器
reg [31:0] cnt1s;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt1s <= 15'd0;
else if((cnt1s == S) || (!cnt_control))
cnt1s <= 15'd0;
else if(cnt_control)
cnt1s <= cnt1s + 1'b1;

/*********************************************************/

///////////////////////////////////////////////////////////
// 功能控制 //
//////////////////////////////////////////////////////////
reg [1:0]flag;//用来标志reg1是否到了2,到了2,reg2只能加到4
reg [1:0] reg1;//时的第一位:0~2
reg [3:0] reg2;
//时的第二位:当第一位为0和1时,可以是0~9,当第一位为2时,只能是0~9,
reg [2:0] reg3;//分的第一位:只能是0~5
reg [3:0] reg4;//分的第二位:是0~9

always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
reg1 <= 2'd0; //小时 高位
reg2 <= 4'd0; //小时 低位
reg3 <= 3'd0; //分钟 高位
reg4 <= 4'd0; //分钟 低位
flag <= 2'd0;
end
else
case(Time_model)
//时钟正常开始跑
3'b000:
begin
if(cnt1s == S) //一分钟到了
begin
reg4 <= reg4 + 1'b1;
if(reg4 == 4'd9)
begin
reg4 <= 4'd0;
reg3 <= reg3 + 1'b1;

if(reg3 == 3'd5)
begin
reg3 <= 3'd0;
if(reg1 == 2'd2)
begin
reg2 <= reg2 + 1'b1;
if(reg2 == 4'd3)
begin
reg2 <= 4'd0;
reg1 <= 2'd0;
end
end
else
begin
reg2 <= reg2 + 1'b1;
if(reg2 == 4'd9)
begin
reg2 <= 4'd0;
reg1 <= reg1 + 1'b1;
end
end
end

end
end
end

//调分模式
3'b001:
begin
if(c_add)//加
begin
reg4 <= reg4 + 1'b1;
if(reg4 == 4'd9)
begin
reg4 <= 4'd0;
reg3 <= reg3 + 1'b1;
if(reg3 > 3'd5)
reg3 <= 3'd0;
end
end
else if(c_sub)//减
begin
reg4 <= reg4 - 1'b1;
if(reg4 == 4'd0)
begin
reg4 <= 4'd9;
reg3 <= reg3 - 3'd1;
if(reg3 == 3'd0)
reg3 <= 3'd5;
end
end
end
//调时模式
3'b010:
begin
if(c_add)//加
begin
if(flag == 2'd2)
begin
reg2 <= reg2 + 1'b1;
if(reg2 >= 4'd3)
begin
reg2 <= 4'd0;
reg1 <= 2'd0;
flag <= 2'd0;
end
end
else
begin
reg2 <= reg2 + 1'b1;
if(reg2 == 4'd9)
begin
flag <= flag + 1'b1;
reg2 <= 4'd0;
reg1 <= reg1 + 1'b1;
end
end
end
else if(c_sub)//减
begin
if(flag == 2'd0)
begin
reg2 <= reg2 - 1'b1;
if(reg2 == 4'd0)
begin
reg2 <= 4'd3;
reg1 <= 2'd2;
flag <= 2'd2;
end
end
else
begin
reg2 <= reg2 - 1'b1;
if(reg2 == 4'd0)
begin
flag <= flag - 1'b1;
reg1 <= reg1 - 1'b1;
reg2 <= 4'd9;
end
end
end
end
endcase
end
/************************************************/
assign hour_h = reg1;
assign hour_l = reg2;
assign minute_h = reg3;
assign minute_l = reg4;
/************************************************/
endmodule


闹钟模块(alarm)

输入 功能说明 输出 功能说明
a_hour 闹钟小时控制 a_hour_h 闹钟小时高位
a_minute 闹钟分钟控制 a_hour_l 闹钟小时低位
clk 时钟 a_minute_h 闹钟分钟高位
rst_n 复位 a_minute_l 闹钟分钟低位

细节讲解

闹钟模块与时钟模块的其实没什么区别,就是在调闹钟时,时钟正常运行,时钟调分,调时都会停止运行,也就是计数器不计数,其他的区别不大。

代码展示



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
module alarm(
input clk,
input rst_n,
input c_add, //控制 加
input c_sub, //控制 减
input [2:0]Time_model,

output [1:0]a_hour_h, //闹钟小时 十位
output [3:0]a_hour_l, //闹钟小时 个位
output [2:0]a_minute_h, //闹钟分钟 十位
output [3:0]a_minute_l //闹钟分钟 个位
);
//////////////////////////////////////////////////////////
// 功能控制 //
////////////////////////////////////////////////////////
reg [1:0]flag;//用来标志reg1是否到了2,到了2,reg2只能加到4
reg [1:0] reg1;//时的第一位:0~2
reg [3:0] reg2;
//时的第二位:当第一位为0和1时,可以是0~9,当第一位为2时,只能是0~9,
reg [2:0] reg3;//分的第一位:只能是0~5
reg [3:0] reg4;//分的第二位:是0~9

always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
reg1 <= 2'd0; //闹钟小时 高位
reg2 <= 4'd0; //闹钟小时 低位
reg3 <= 3'd0; //闹钟分钟 高位
reg4 <= 4'd0; //闹钟分钟 低位
flag <= 2'd0;
end
else
case(Time_model)
//闹钟调分模式
3'b011:
begin
if(c_add)//加
begin
reg4 <= reg4 + 1'b1;
if(reg4 == 4'd9)
begin
reg4 <= 4'd0;
reg3 <= reg3 + 1'b1;
if(reg3 > 3'd4)
reg3 <= 3'd0;
end
end
else if(c_sub)//减
begin
reg4 <= reg4 - 1'b1;
if(reg4 == 4'd0)
begin
reg4 <= 4'd9;
reg3 <= reg3 - 3'd1;
if(reg3 == 3'd0)
reg3 <= 3'd5;
end
end
end
//闹钟调时模式
3'b100:
begin
if(c_add)//加
begin
if(flag == 2'd2)
begin
reg2 <= reg2 + 1'b1;
if(reg2 >= 4'd3)
begin
reg2 <= 4'd0;
reg1 <= 2'd0;
flag <= 2'd0;
end
end
else
begin
reg2 <= reg2 + 1'b1;
if(reg2 == 4'd9)
begin
flag <= flag + 1'b1;
reg2 <= 4'd0;
reg1 <= reg1 + 1'b1;

end
end
end
else if(c_sub)//减
begin
if(flag == 2'd0)
begin
reg2 <= reg2 - 1'b1;
if(reg2 == 4'd0)
begin
reg2 <= 4'd3;
reg1 <= 2'd2;
flag <= 2'd2;
end
end
else
begin
reg2 <= reg2 - 1'b1;
if(reg2 == 4'd0)
begin
flag <= flag - 1'b1;
reg1 <= reg1 - 1'b1;
reg2 <= 4'd9;
end
end
end
end
endcase
end
/************************************************/

assign a_hour_h = reg1;
assign a_hour_l = reg2;
assign a_minute_h = reg3;
assign a_minute_l = reg4;

/************************************************/

endmodule


显示模块(display)

输入 功能说明 输出 功能说明
a_hour_h 闹钟小时高位 data 段选信号
a_hour_l 闹钟小时低位 select_wei 位选信号
a_minute_h 闹钟分钟高位 beep 蜂鸣器
a_minute_l 闹钟分钟低位
hour_h 时钟小时高位
hour_l 时钟小时低位
minute_h 时钟分钟高位
minute_l 时钟分钟低位
Display_Model 显示模式控制
clk 时钟
rst_n 复位

显示模块的输入有三种类型:

  1. 闹钟的数据
  2. 时钟的数据
  3. 显示模式控制信号

输出也只有三个

  1. data是八段数码管的段选信号,由于我没用小数点,所以七根线
  2. select_wei数码管位选,四个数码管,四根线
  3. beep链接蜂鸣器的,一根线。

细节讲解

显示模块里用到的主要是数码管动态扫描的知识,扫描频率如何设置,这些问题,查一下数码管的是如何显示就知道了,抓住段选和位选,还有动态扫描的原理,然后分清楚你使用的数码管是共阴还是共阳,这个显示模块就很好理解了。

代码展示



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
200
201
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
module  display(
input clk,
input rst_n,

//时钟模式 双段选数据
input [1:0] hour_h,
input [3:0] hour_l,
input [2:0] minute_h,
input [3:0] minute_l,

//闹钟模式 段选数据
input [1:0] a_hour_h,
input [3:0] a_hour_l,
input [2:0] a_minute_h,
input [3:0] a_minute_l,

input Display_Model,//0:时钟模式,1:秒表模式

output [6:0] data,//数码管段选
output reg[3:0] select_wei, //数码管位选
output reg alarm_out
);

/***********************************/
parameter
SEG_0 = 7'h7e,//c0,
SEG_1 = 7'h30,//f9,
SEG_2 = 7'h6d,//a4,
SEG_3 = 7'h79,//b0,
SEG_4 = 7'h33,//99,
SEG_5 = 7'h5b,//92,
SEG_6 = 7'h5f,//82,
SEG_7 = 7'h70,//F8,
SEG_8 = 7'h7f,//80,
SEG_9 = 7'h7b;//90,

/***********************************/
//时钟数据编码
/***********************************/
wire [6:0]c_data1;
wire [6:0]c_data2;
wire [6:0]c_data3;
wire [6:0]c_data4;

//数码管一要显示的列表数据(0~2)
reg [6:0] data1_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data1_temp <= SEG_0;
else
case(hour_h)
2'd0: data1_temp <= SEG_0;
2'd1: data1_temp <= SEG_1;
2'd2: data1_temp <= SEG_2;
default: data1_temp <= SEG_0;
endcase
/***********************************/
//数码管二要显示的列表数据(0~9)
reg [6:0] data2_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data2_temp <= SEG_0;
else
case(hour_l)
4'd0: data2_temp <= SEG_0;
4'd1: data2_temp <= SEG_1;
4'd2: data2_temp <= SEG_2;
4'd3: data2_temp <= SEG_3;
4'd4: data2_temp <= SEG_4;
4'd5: data2_temp <= SEG_5;
4'd6: data2_temp <= SEG_6;
4'd7: data2_temp <= SEG_7;
4'd8: data2_temp <= SEG_8;
4'd9: data2_temp <= SEG_9;
default: data2_temp <= SEG_0;
endcase
/***********************************/
//数码管三要显示的列表数据 (0~5)
reg [6:0] data3_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data3_temp <= SEG_0;
else
case(minute_h)
3'd0: data3_temp <= SEG_0;
3'd1: data3_temp <= SEG_1;
3'd2: data3_temp <= SEG_2;
3'd3: data3_temp <= SEG_3;
3'd4: data3_temp <= SEG_4;
3'd5: data3_temp <= SEG_5;
default: data3_temp <= SEG_0;
endcase
/***********************************/
//数码管四要显示的列表数据(1~9)
reg [6:0] data4_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data4_temp <= SEG_0;
else
case(minute_l)
4'd0: data4_temp <= SEG_0;
4'd1: data4_temp <= SEG_1;
4'd2: data4_temp <= SEG_2;
4'd3: data4_temp <= SEG_3;
4'd4: data4_temp <= SEG_4;
4'd5: data4_temp <= SEG_5;
4'd6: data4_temp <= SEG_6;
4'd7: data4_temp <= SEG_7;
4'd8: data4_temp <= SEG_8;
4'd9: data4_temp <= SEG_9;
default: data4_temp <= SEG_0;
endcase
/*****************************************/
assign c_data1 = data1_temp;
assign c_data2 = data2_temp;
assign c_data3 = data3_temp;
assign c_data4 = data4_temp;
/*****************************************/

/***********************************/
//闹钟数据编码
/***********************************/
wire [6:0]a_data1;
wire [6:0]a_data2;
wire [6:0]a_data3;
wire [6:0]a_data4;
//数码管一要显示的列表数据(0~5)
reg [6:0] a_data1_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
a_data1_temp <= SEG_0;
else
case(a_hour_h)
3'd0: a_data1_temp <= SEG_0;
3'd1: a_data1_temp <= SEG_1;
3'd2: a_data1_temp <= SEG_2;
3'd3: a_data1_temp <= SEG_3;
3'd4: a_data1_temp <= SEG_4;
3'd5: a_data1_temp <= SEG_5;
default: a_data1_temp <= SEG_0;
endcase
/***********************************/
//数码管二要显示的列表数据(0~9)
reg [6:0] a_data2_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
a_data2_temp <= SEG_0;
else
case(a_hour_l)
4'd0: a_data2_temp <= SEG_0;
4'd1: a_data2_temp <= SEG_1;
4'd2: a_data2_temp <= SEG_2;
4'd3: a_data2_temp <= SEG_3;
4'd4: a_data2_temp <= SEG_4;
4'd5: a_data2_temp <= SEG_5;
4'd6: a_data2_temp <= SEG_6;
4'd7: a_data2_temp <= SEG_7;
4'd8: a_data2_temp <= SEG_8;
4'd9: a_data2_temp <= SEG_9;
default: a_data2_temp <= SEG_0;
endcase
/*****************************************/
//数码管三要显示的列表数据(0~9)
reg [6:0] a_data3_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
a_data3_temp <= SEG_0;
else
case(a_minute_h)
4'd0: a_data3_temp <= SEG_0;
4'd1: a_data3_temp <= SEG_1;
4'd2: a_data3_temp <= SEG_2;
4'd3: a_data3_temp <= SEG_3;
4'd4: a_data3_temp <= SEG_4;
4'd5: a_data3_temp <= SEG_5;
4'd6: a_data3_temp <= SEG_6;
4'd7: a_data3_temp <= SEG_7;
4'd8: a_data3_temp <= SEG_8;
4'd9: a_data3_temp <= SEG_9;
default: a_data3_temp <= SEG_0;
endcase
/***********************************/
//数码管四要显示的列表数据(0~9)
reg [6:0] a_data4_temp;
always @(posedge clk or negedge rst_n)
if(!rst_n)
a_data4_temp <= SEG_0;
else
case(a_minute_l)
4'd0: a_data4_temp <= SEG_0;
4'd1: a_data4_temp <= SEG_1;
4'd2: a_data4_temp <= SEG_2;
4'd3: a_data4_temp <= SEG_3;
4'd4: a_data4_temp <= SEG_4;
4'd5: a_data4_temp <= SEG_5;
4'd6: a_data4_temp <= SEG_6;
4'd7: a_data4_temp <= SEG_7;
4'd8: a_data4_temp <= SEG_8;
4'd9: a_data4_temp <= SEG_9;
default: a_data4_temp <= SEG_0;
endcase
/*******************************************************/
assign a_data1 = a_data1_temp;
assign a_data2 = a_data2_temp;
assign a_data3 = a_data3_temp;
assign a_data4 = a_data4_temp;
/***************************************************/

/******************************************/
parameter shuaxin = 17'h1ffff;
// 数码管扫描频率
reg [19:0] cnt;
reg [1:0] num;//每隔5MS,num加1
always @(posedge clk or negedge rst_n)
if(!rst_n)
begin
select_wei <= 4'd8; // 1000
cnt <= 18'd0;
num <= 2'd0;
end
else if(cnt == shuaxin)
begin
num <= num + 1'b1;
cnt <= 18'd0;
if(select_wei == 4'd1) //0001
select_wei <= 4'd8;
else
select_wei <= {1'b0,select_wei[3:1]}; //右移
end
else
cnt <= cnt + 1'b1;
/******************************************/
//通过Display_Model来确定是要送秒表数据还是闹钟的数据
reg [7:0] data_temp;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_temp <= 7'h7e;
else if(cnt == shuaxin)
case(num)
2'd0: data_temp <= Display_Model ? a_data1:c_data1;//给第一个数码管送数据
2'd1: data_temp <= Display_Model ? a_data2:c_data2;//给第二个数码管送数据
2'd2: data_temp <= Display_Model ? a_data3:c_data3;//给第二个数码管送数据
2'd3: data_temp <= Display_Model ? a_data4:c_data4;//给第二个数码管送数据
endcase
end
assign data = data_temp;
/******************************************/

////// 比较器模块 //////
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
alarm_out <= 0 ;
else if(a_data1 == c_data1 && a_data2 == c_data2 && a_data3 == c_data3 && a_data4 == c_data4)
alarm_out <=1;
else
alarm_out<=0;
end
endmodule


top层

主要是链接,直接上代码吧。

代码展示



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

module top(
input clk,
input rst_n,

input add_in,
input sub_in,
input model_in,

output [6:0] data,//数码管段选数据
output [3:0] select_wei, //数码管位选
output alarm_out


);

wire [1:0] hour_h;
wire [3:0] hour_l;
wire [2:0] minute_h;
wire [3:0] minute_l;

wire [1:0] a_hour_h;
wire [3:0] a_hour_l;
wire [2:0] a_minute_h;
wire [3:0] a_minute_l;

wire Display_Model;
wire [2:0]Time_model;

wire c_add;
wire c_sub;

wire a_add;
wire a_sub;
wire cnt_control; //控制定时器


//
key U1(
//top的input
.clk(clk),
.rst_n(rst_n),

.add_in(add_in),
.sub_in(sub_in),
.model_in(model_in),

//连clock的线
.cnt_control(cnt_control),
.c_add(c_add),
.c_sub(c_sub),
.Time_model(Time_model),

//连alarm的线
.a_add(a_add),
.a_sub(a_sub),

//连display的线
.Display_Model(Display_Model)
);

clock U2(
//input
.clk(clk),
.rst_n(rst_n),

//从key来的线
.c_add(c_add),
.c_sub(c_sub),
.cnt_control(cnt_control),
.Time_model(Time_model),

//output
//连display的线

.hour_h(hour_h),
.hour_l(hour_l),
.minute_h(minute_h),
.minute_l(minute_l)

);
alarm U3(
//input
.clk(clk),
.rst_n(rst_n),

//从key来的线
.c_add(a_add),
.c_sub(a_sub),
.Time_model(Time_model),

//output
//连display的线

.a_hour_h(a_hour_h),
.a_hour_l(a_hour_l),
.a_minute_h(a_minute_h),
.a_minute_l(a_minute_l)

);



display U4(
//input
.clk(clk),
.rst_n(rst_n),

//从clock 来的线
.hour_h(hour_h),
.hour_l(hour_l),
.minute_h(minute_h),
.minute_l(minute_l),

//从alarm 来的线
.a_hour_h(a_hour_h),
.a_hour_l(a_hour_l),
.a_minute_h(a_minute_h),
.a_minute_l(a_minute_l),

//从key 来的线
.Display_Model(Display_Model),

//output
.data(data),
.select_wei(select_wei),
.alarm_out(alarm_out)
);

endmodule


细节讲解

top层的作用是连接各个模块,列出系统的输入输出,然后分清楚中间节点,如果之前没有写过top层,可以先从试着写个小系统,比如:全加器(调用半加器实现),如果你已经是老司机,相信已经驾轻就熟了,不多说了。其实,我这个例化写得有点烦了,例化如果非要分写法可以分为两种,我这是比较烦的,不过我也不改了,╭(╯^╰)╮

写在后面的话

数字钟就讲诉到这里,整个系统不难理解,只是模块之间的联系必须弄清楚,我的观点是:把需要的模块列出来,分析模块之间的联系,再具体看各个模块的内容,单个验证,自顶向下设计,主要还是多琢磨吧,有耐心就好。

关于文章,如果有任何问题都可以在评论区和我交流,如果有错误,欢迎斧正,谢谢了~~

请我喝杯咖啡~
------ 本文结束------
0%