-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreport.tex
More file actions
687 lines (644 loc) · 27.7 KB
/
Copy pathreport.tex
File metadata and controls
687 lines (644 loc) · 27.7 KB
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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
\documentclass[a4paper]{report}
\usepackage{xeCJK}
\usepackage{float}
\usepackage{fancyhdr}
\usepackage{titlesec}
\usepackage{graphicx}
\setCJKmainfont [
Path = fonts/,
Extension = .ttf,
UprightFont = *-Regular,
ItalicFont = *-Regular,
AutoFakeBold = true] {LXGWWenKaiMono}
\setCJKmonofont[
Path = fonts/,
Extension = .ttf,
UprightFont = *-Regular,
ItalicFont = *-Regular,
AutoFakeBold = true] {LXGWWenKaiMono}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[C]{数字电子技术基础课程设计——多功能数字时钟}
\fancyfoot[C]{\thepage}
\fancypagestyle{plain}{
\fancyhf{}
\fancyhead[C]{数字电子技术基础课程设计——多功能数字时钟}
\fancyfoot[C]{\thepage}
}
\titleformat{\chapter}[display] {\normalfont\bfseries}{}{0pt}{\Huge}
\titlespacing*{\chapter}{0pt}{-30pt}{20pt}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
\renewcommand{\contentsname}{目录}
\usepackage[colorlinks=true, linkcolor=black, urlcolor=blue]{hyperref}
\begin{document}
\pagenumbering{roman}
\tableofcontents
\newpage
\clearpage
\pagenumbering{arabic}
\chapter{课程设计题目}
本次课程设计题目为“多功能数字时钟”。
\section{功能要求}
本项目的功能要求如下:
\begin{itemize}
\item 进行正常的时、分、秒计数, 使用24小时制显示时间
\item 利用按键实现“校时”“校分”功能:
\begin{enumerate}
\item 按下“校时”键时, 计数器迅速递增, 并按24小时循环
\item 按下“校分”键时, 计数器迅速递增, 并按60分钟循环
\item 按下“清零”键时,计时器全部清零
\end{enumerate}
\item 利用扬声器做整点报时,报时五声
\item 实现闹时功能,可选择:
\begin{enumerate}
\item 预置固定的闹时时间的方法
\item 利用系统的输入数据随时设定闹时时间
\end{enumerate}
\end{itemize}
\section{仿真方案与设计方法选择}
考虑到要求的功能比较简单,本项目采用如下架构:
\begin{itemize}
\item 使用Verilog HDL语言进行设计和TestBench编写
\item 采用IVerilog进行仿真验证
\item 如果需要网表,采用yosys进行综合
\item 网表和波形文件的可视化采用VSCode的Digital IDE插件实现
\end{itemize}
由于清零和复位本质上是一回事,本项目不设置单独的清零键,而是使用“复位”键作为清零键。\par
考虑到“闹时”这一功能的实用性,本项目额外增加一个“停止闹钟”键,用于停止闹时,同时利用系统的输入数据随时设定闹时时间。\par
由于Digital IDE等波形查看工具无法解码数码管的段码并显示,且使用Verilog HDL语言将二进制转换到数码管的段码本质上视需要一个组合逻辑的switch就可以实现,为了方便调试,本项目不再将输出转译到数码管的段码,而是直接显式二进制数据。 \par
本项目将使用自底向上的设计方法,先设计每个模块的功能,再将这些模块组合起来实现整个系统的功能。
\chapter{设计思路及详细实现}
首先对项目进行功能模块拆分,将其拆解为方便测试的、容易复用的小模块。对于本项目,模块拆分如下:
\begin{itemize}
\item 时钟分频模块:参数化的计数分频器,将系统时钟分频到1Hz,使用参数配置分频系数
\item 时间计数模块:本质上是一个参数化的模N计数器,带有计数使能和进位输出,使用参数配置模值
\item 时钟核心顶层模块:将时钟分频模块和时间计数模块组合起来,实现时、分、秒的计数,同时为扩展模块提供必要的信号
\item 闹钟模块:根据系统输入数据,在当前时刻大于等于设定的闹时时间时,触发蜂鸣器输出
\item 整点报时模块:当当前时刻的分和秒都为0时,触发五声整点报时
\end{itemize}
\section{时钟分频模块}
\begin{table}[h]
\centering
\begin{tabular}{|c|c|c|}
\hline
端口名 & 方向 & 备注 \\
\hline
InputCLK & 输入 & 输入时钟 \\
\hline
SysRST & 输入 & 系统复位信号 \\
\hline
OutputCLK & 输出 & 输出时钟 \\
\hline
\end{tabular}
\caption{时钟分频模块端口表}
\end{table}
我们给它一个参数DivideRatio,用于配置分频系数。并通过综合器的编译期函数实现自动位宽计算,关键代码如下:
\begin{verbatim}
module ClockDivider #(parameter integer DivideRatio = 32768) (
input wire SysRST,
input wire InputCLK,
output wire OutputCLK
);
localparam integer FlipCount = DivideRatio / 2;
localparam integer DivideCounterWidth = $clog2(FlipCount);
reg ClockBuffer;
reg[DivideCounterWidth-1:0] DivideCounter;
\end{verbatim}
接下来就是实现计数分频器的逻辑了,它的算法如下:
\begin{itemize}
\item 当SysRST为高电平时,将分频计数器清零,输出时钟也置为低电平
\item 当SysRST为低电平时,正常计数
\begin{itemize}
\item 当分频计数器未达到FlipCount-1时,正常增长
\item 当分频计数器达到FlipCount-1时,将输出时钟取反,同时将分频计数器清零
\end{itemize}
\end{itemize}
\newpage
这部分算法的实现比较简单,故而直接给出Verilog HDL代码,不再做过多多余的解释:
\begin{verbatim}
assign OutputCLK = ClockBuffer;
always @(posedge InputCLK or posedge SysRST) begin
if(SysRST) begin
ClockBuffer <= 1'b0;
DivideCounter <= {DivideCounterWidth{1'b0}};
end else begin
if(DivideCounter < FlipCount - 1) begin
DivideCounter <= DivideCounter + 1;
end else begin
DivideCounter <= {DivideCounterWidth{1'b0}};
ClockBuffer <= ~ClockBuffer;
end
end
end
\end{verbatim}
\section{时间计数模块}
\begin{table}[hpb]
\centering
\begin{tabular}{|c|c|c|}
\hline
端口名 & 方向 & 备注 \\
\hline
SysCLK & 输入 & 计数时钟 \\
\hline
SysRST & 输入 & 系统复位信号 \\
\hline
Increase & 输入 & 计数使能信号 \\
\hline
CarryOutput & 输出 & 进位输出信号 \\
\hline
Count & 输出 & 计数输出信号 \\
\hline
\end{tabular}
\caption{时间计数模块端口表}
\end{table}
由于该模块同时被用作时、分和秒的计数,我们需要将它的模值参数化,我们使用一个ModValue作为参数,并自动计算内部寄存器的位宽。再定义一些内部需要使用到的信号,代码如下:
\begin{verbatim}
module TimeCounter #(parameter integer ModValue = 24) (
input wire SysCLK,
input wire SysRST,
input wire Increase,
output wire [$clog2(ModValue) - 1:0] Count,
output wire CarryOutput
);
reg[$clog2(ModValue) - 1:0] CountRegister;
assign Count = CountRegister;
assign CarryOutput = (CountRegister == ModValue - 1) && Increase;
\end{verbatim}
\par
这里采用组合逻辑生成进位信号,因为我们希望在计数到$\mathrm{ModValue}-1$时,能够立即触发进位,而不是等待下一个时钟周期。否则将导致级联模块时,进位信号延迟一个时钟周期,导致计数错误。\newpage
接下来我们需要实现这个模块的逻辑, 首先我们理清楚这个模块的算法:
\begin{itemize}
\item 当复位信号SysRST为高电平时,将计数器清零
\item 当SysRST为低电平时,正常计数
\begin{itemize}
\item 当计数使能信号Increase为高电平时,使能计数
\begin{itemize}
\item 当计数器未达到ModValue-1时,正常增长
\item 当计数器达到ModValue-1时,将计数器清零
\end{itemize}
\item 当计数使能信号Increase为低电平时,保持当前计数不变
\end{itemize}
\end{itemize}
将这部分思路转换到Verilog HDL,我们就可以写出如下代码:
\begin{verbatim}
always @(posedge SysCLK or posedge SysRST) begin
if(SysRST) begin
CountRegister <= {$clog2(ModValue) {1'b0}};
end else begin
if(Increase) begin
if(CountRegister < ModValue - 1) begin
CountRegister <= CountRegister + 1;
end else begin
CountRegister <= {$clog2(ModValue) {1'b0}};
end
end else begin
// Doing Nothing
end
end
end
\end{verbatim}
这里稍微解释一下为什么虽然Increase的信号为低电平时我们什么都没有做,但笔者还是写了else block。这是因为Verilog HDL的组合逻辑综合时,如果else block为空,综合器会插入一个电平敏感的Latch,这通常不是我们期望的行为。所以笔者在写Verilog HDL时,无论必要与否总是有if必有else,避免某天在真的写出了Latch导致时序错误。
\section{时钟核心顶层模块}
\begin{table}[h]
\centering
\begin{tabular}{|c|c|c|}
\hline
端口名 & 方向 & 描述 \\
\hline
SysCLK & 输入 & 系统时钟 \\
\hline
SysRST & 输入 & 系统复位信号 \\
\hline
AdjustMinute & 输入 & 校分信号 \\
\hline
AdjustHour & 输入 & 校时信号 \\
\hline
ClockPPS & 输出 & 秒脉冲信号,来自SysCLK分频 \\
\hline
DayChanged & 输出 & 日期变更信号,用于闹钟 \\
\hline
ClockHour & 输出 & 时钟小时输出 \\
\hline
ClockMinute & 输出 & 时钟分钟输出 \\
\hline
ClockSecond & 输出 & 时钟秒输出 \\
\hline
\end{tabular}
\caption{时钟核心顶层模块端口表}
\end{table}
\newpage
这部分实际上没有什么好说的,只是将之前的模块组合起来,同时添加同于校时和校分的组合逻辑。我们直接给出代码:
\begin{verbatim}
module ClockCore #(parameter integer SystemClockFrequency) (
input wire SysCLK,
input wire SysRST,
input wire AdjustMinute,
input wire AdjustHour,
output wire ClockPPS,
output wire DayChanged,
output wire [4:0] ClockHour,
output wire [5:0] ClockMinute,
output wire [5:0] ClockSecond
);
ClockDivider #(SystemClockFrequency) InputDivider (
.SysRST(SysRST),
.InputCLK(SysCLK),
.OutputCLK(ClockPPS)
);
wire SecondCarryOutput, MinuteCarryOutput, HourCarryOutput;
wire MinuteIncrease, HourIncrease;
assign MinuteIncrease = AdjustMinute | SecondCarryOutput;
assign HourIncrease = AdjustHour | MinuteCarryOutput;
assign DayChanged =
(ClockHour == 5'd23) && (ClockMinute == 6'd59)
&& (ClockSecond == 6'd59);
TimeCounter #(60) SecondCounter (
.SysRST(SysRST),
.SysCLK(ClockPPS),
.Increase(1'b1),
.CarryOutput(SecondCarryOutput),
.Count(ClockSecond)
);
TimeCounter #(60) MinuteCounter (
.SysRST(SysRST),
.SysCLK(ClockPPS),
.Increase(MinuteIncrease),
.CarryOutput(MinuteCarryOutput),
.Count(ClockMinute)
);
TimeCounter #(24) HourCounter (
.SysRST(SysRST),
.SysCLK(ClockPPS),
.Increase(HourIncrease),
.CarryOutput(HourCarryOutput),
.Count(ClockHour)
);
endmodule
\end{verbatim}
\newpage
\section{闹钟模块}
\begin{table}[h]
\centering
\begin{tabular}{|c|c|c|}
\hline
端口名 & 方向 & 描述 \\
\hline
SysCLK & 输入 & 系统时钟 \\
\hline
SysRST & 输入 & 系统复位信号 \\
\hline
ClockPPS & 输入 & 秒脉冲信号,来自SysCLK分频 \\
\hline
StopAlarm & 输入 & 停止闹钟 \\
\hline
DayChanged & 输入 & 日期变更信号,用于闹钟 \\
\hline
AlarmHour & 输入 & 闹钟小时设置 \\
\hline
AlarmMinute & 输入 & 闹钟分钟设置 \\
\hline
ClockHour & 输入 & 当前小时数 \\
\hline
ClockMinute & 输入 & 当前分钟数 \\
\hline
ClockSecond & 输入 & 当前秒数 \\
\hline
AlarmPulse & 输出 & 闹钟脉冲信号 \\
\hline
\end{tabular}
\caption{闹钟模块端口表}
\end{table}
这个模块的逻辑相对复杂,我们慢慢拆解它的代码。首先是模块的端口定义:
\begin{verbatim}
module Alarm (
input wire SysCLK,
input wire SysRST,
input wire ClockPPS,
input wire StopAlarm,
input wire DayChanged,
input wire [4:0] AlarmHour,
input wire [5:0] AlarmMinute,
input wire [4:0] ClockHour,
input wire [5:0] ClockMinute,
input wire [5:0] ClockSecond,
output wire AlarmPulse
);
\end{verbatim}
然后是一些内部的信号和寄存器的定义:
\begin{verbatim}
reg AlarmActive, AlarmActivated;
wire [16:0] AlarmTime;
wire [16:0] ClockTime;
assign AlarmPulse = AlarmActive & ClockPPS;
assign AlarmTime = {AlarmHour, AlarmMinute, 6'd0};
assign ClockTime = {ClockHour, ClockMinute, ClockSecond};
\end{verbatim}
这里的AlarmActive是闹钟激活标识,用于使能闹钟脉冲输出。AlarmActivated是当前日期闹钟已激活标识,用于防止重复激活闹钟。
可以看到我们实际上将闹钟触发时间和当前时间都拼接成了两个17位的二进制信号,这是为了方便比较。如果不拼接,比较时间的逻辑将比较复杂。
而闹钟的脉冲输出这来自于AlarmActive和ClockPPS两个信号的逻辑与,这是因为我们希望闹钟发出的是嘀嘀声,而非连续的蜂鸣音。
\newpage
最后就是这个模块最核心的always块:
\begin{verbatim}
always @(posedge SysCLK or posedge SysRST) begin
if(SysRST) begin
AlarmActive <= 1'b0;
AlarmActivated <= 1'b0;
end else begin
if (StopAlarm) begin
AlarmActive <= 1'b0;
end else if(DayChanged) begin
AlarmActivated <= 1'b0;
end selse
if((~AlarmActivated) && (ClockTime >= AlarmTime)) begin
AlarmActive <= 1'b1;
AlarmActivated <= 1'b1;
end
end
end
\end{verbatim}
这里实际上看着代码就可以直接得到它的逻辑:
\begin{itemize}
\item 当系统复位时,复位所有状态。
\item 当系统正常运行时,闹钟模块按如下逻辑运行:
\begin{itemize}
\item 当当前时间大于等于闹钟时间时,激活闹钟,并设置已激活标识
\item 当StopAlarm信号为高电平时,停用闹钟,清除闹钟激活标识
\item 当日期变更时,清除闹钟已激活标识
\end{itemize}
\end{itemize}
\section{整点报时模块}
\begin{table}[h]
\centering
\begin{tabular}{|c|c|c|}
\hline
端口名 & 方向 & 描述 \\
\hline
SysCLK & 输入 & 系统时钟 \\
\hline
SysRST & 输入 & 系统复位信号 \\
\hline
ClockPPS & 输入 & 秒脉冲信号,来自SysCLK分频 \\
\hline
ClockHour & 输入 & 当前小时数 \\
\hline
ClockMinute & 输入 & 当前分钟数 \\
\hline
ClockSecond & 输入 & 当前秒数 \\
\hline
ChimePulse & 输出 & 整点报时脉冲信号 \\
\hline
\end{tabular}
\caption{整点报时模块端口表}
\end{table}
\textbf{注意}:当前模块的实现实际上有一定瑕疵:其在五声报时结束后会额外输出一个SysCLK周期长度的高电平脉冲。但由于修复这部分需要付出额外的LUT资源,同时SysCLK通常频率很高,笔者选择将这个缺陷视为一个可接受的trade-off。
\newpage
这部分的核心逻辑实际上也就是一个比较,即: 当前分钟数为0,且秒数小于5时,整点报时激活信号被置为高电平,否则为低电平。再用激活信号与秒脉冲信号进行逻辑与,实现脉冲输出。\\
这部分的代码如下:
\begin{verbatim}
odule Chime #(parameter integer PulseCount = 5)(
input wire SysCLK,
input wire SysRST,
input wire ClockPPS,
input wire [4:0] ClockHour,
input wire [5:0] ClockMinute,
input wire [5:0] ClockSecond,
output wire ChimePulse
);
reg ChimeActive;
assign ChimePulse = ChimeActive & ClockPPS;
always @(posedge SysCLK or posedge SysRST) begin
if(SysRST) begin
ChimeActive <= 1'b0;
end else begin
if((ClockMinute == 6'd0) & (ClockSecond <= 6'd5)) begin
ChimeActive <= 1'b1;
end else begin
ChimeActive <= 1'b0;
end
end
end
endmodule
\end{verbatim}
\section{整体顶层模块}
\begin{table}[h]
\centering
\begin{tabular}{|c|c|c|}
\hline
端口名 & 方向 & 描述 \\
\hline
SysCLK & 输入 & 系统时钟 \\
\hline
SysRST & 输入 & 系统复位信号 \\
\hline
AdjustMinute & 输入 & 校分信号 \\
\hline
AdjustHour & 输入 & 校时信号 \\
\hline
StopAlarm & 输入 & 停止闹钟信号 \\
\hline
AlarmHour & 输入 & 闹钟小时设置 \\
\hline
AlarmMinute & 输入 & 闹钟分钟设置 \\
\hline
ClockHour & 输出 & 当前小时数 \\
\hline
ClockMinute & 输出 & 当前分钟数 \\
\hline
ClockSecond & 输出 & 当前秒数 \\
\hline
Buzzer & 输出 & 蜂鸣器输出信号 \\
\hline
\end{tabular}
\caption{整体顶层模块端口表}
\end{table}
\newpage
与时钟核心顶层模块类似,这部分几乎没有逻辑,就是将各个模块的输出连接起来,故直接给出代码:
\begin{verbatim}
module MultiFunctionalClock #(parameter integer SysCLKFreq)(
input wire SysCLK,
input wire SysRST,
input wire AdjustMinute,
input wire AdjustHour,
input wire StopAlarm,
input wire [4:0] AlarmHour,
input wire [5:0] AlarmMinute,
output wire [4:0] ClockHour,
output wire [5:0] ClockMinute,
output wire [5:0] ClockSecond,
output wire Buzzer);
wire ClockPPS, DayChanged, AlarmPulse, ChimePulse;
assign Buzzer = AlarmPulse | ChimePulse
ClockCore #(SystemClockFrequency) ClockCore_Instance (
.SysCLK(SysCLK),
.SysRST(SysRST),
.AdjustMinute(AdjustMinute),
.AdjustHour(AdjustHour),
.ClockPPS(ClockPPS),
.DayChanged(DayChanged),
.ClockHour(ClockHour),
.ClockMinute(ClockMinute),
.ClockSecond(ClockSecond));
Alarm Alarm_Instance (
.SysCLK(SysCLK),
.SysRST(SysRST),
.ClockPPS(ClockPPS),
.StopAlarm(StopAlarm),
.DayChanged(DayChanged),
.AlarmHour(AlarmHour),
.AlarmMinute(AlarmMinute),
.ClockHour(ClockHour),
.ClockMinute(ClockMinute),
.ClockSecond(ClockSecond),
.AlarmPulse(AlarmPulse));
Chime #(5) Chime_Instance (
.SysCLK(SysCLK),
.SysRST(SysRST),
.ClockPPS(ClockPPS),
.ClockHour(ClockHour),
.ClockMinute(ClockMinute),
.ClockSecond(ClockSecond),
.ChimePulse(ChimePulse));
endmodule
\end{verbatim}
\chapter{功能验证与仿真结果}
由于TestBench代码实在没有什么逻辑可言,本章不会给出测试代码,而只给出波形图及其解释。若对于测试代码感兴趣,可以在\href{https://github.com/ZhiYi-R/DigitalCircuitCourseDesign_MultiFunctionalClock}{本项目的GitHub仓库}找到。
\section{单元测试}
本节将测试各个单元模块,并给出对应的测试波形及其解释。
\subsection{时钟分频模块}
我们在TestBench中例化一个分频系数为500的时钟分频模块,并给它一个时钟输入,其波形如图:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/ClockDivider.png}
\caption{时钟分频模块测试波形}
\end{figure}
可以看到我们从高频时钟信号中生成了低频时钟信号,那么这个分频出来的信号是否是正确的呢?我们将这个波形放大来查看时钟跳变的边沿:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/ClockDivider_Detail_Rising.png}
\caption{时钟上升沿测试波形}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/ClockDivider_Detail_Falling.png}
\caption{时钟下降沿测试波形}
\end{figure}
可以看到计数器每个SysCLK周期增加1,而无论是输出时钟上升沿还是下降沿,都是在计数器递增到249时,即经过了250个SysCLK周期后,输出时钟翻转,符合我们的预期。\newpage
\subsection{时钟计数模块}
由于本模块实际上需要级联使用,故而在测试用例中例化两个模数为10的时钟计数模块,并将它们级联起来。
其测试波形如图:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/TimeCounter.png}
\caption{时钟计数模块测试波形}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/TimeCounter_Detail.png}
\caption{时钟计数模块级联进位测试波形}
\end{figure}
可以看到两级模块均正常工作,且级联进位功能也正常。符合我们的预期,测试通过。
\subsection{闹钟模块}
这部分测试比较复杂,我们需要测试闹钟的各种功能:
\begin{enumerate}
\item 是否能正常响铃
\item 手动停止后是否会被异常激活
\item 日期变更后是否能重复当前闹钟
\end{enumerate}
测试波形如下:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/Alarm.png}
\caption{闹钟模块测试波形}
\end{figure}
可以看到,当前时间大于设置时间后,会触发闹钟,并开始响铃。而当手动停止后,闹钟不再响铃,且不再被重复激活。当日期变更信号触发后,闹钟又在判定当前时间大于设置时间后触发,并开始响铃。符合我们的预期,测试通过。
\newpage
\subsection{整点报时模块}
这部分由于实现的关系,我们要模拟一个真实的“秒数”信号给本模块,其测试波形如下:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/Chime.png}
\caption{整点报时模块测试波形}
\end{figure}
可以看到,当分和秒都为0时,整点报时模块会触发一次,输出五个脉冲。符合我们的预期,测试通过。\\
\textbf{注意}:这里看起来输出了六个脉冲是因为TestBench中直接将SysCLK作为ClockPPS,额外的一个SysCLK周期长度的脉冲看起来就像“第六声”。在实际使用中,这个额外的脉冲会相当短,人耳几乎无法感知。
\section{集成测试}
这部分我们将测试两个顶层模块的功能,并给出对应的测试波形及其解释。
\subsection{时钟核心顶层模块}
在本测试中我们将测试时钟核心顶层模块的如下功能:
\begin{enumerate}
\item 秒、分钟、小时是否能正常计数
\item 校时与校分功能是否正常
\item 用于连接扩展模块的信号是否正常
\end{enumerate}
本测试整体波形如下:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/ClockCore.png}
\caption{时钟核心顶层模块测试波形}
\end{figure}
\newpage
我们放大几处关键的测试波形如下:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/ClockCore_Detail_SecondCarry.png}
\caption{秒进位与校分测试波形}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/ClockCore_Detail_MinuteCarry.png}
\caption{分钟进位与校时测试波形}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/ClockCore_Detail_DayChanged.png}
\caption{日期变更测试波形}
\end{figure}
可以看到,当时、分、秒计数器满时,都会输出一个进位脉冲,同时自身计数归零;当校时与校分功能被触发时,对应的计数器数值每秒递增1。而当时间到达23:59:59时,会触发一次日期变更信号,输出一个脉冲。符合我们的预期,测试通过。
\newpage
\subsection{整体顶层模块}
在这个测试中,我们将全面的测试多功能时钟的所有功能。首先给出整体的测试波形:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/MultiFunctionalClock.png}
\caption{整体顶层模块测试波形}
\end{figure}
我们首先观察复位与整点报时功能,放大波形如下:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/MultiFunctionalClock_Detail_ResetAndChime.png}
\caption{复位与整点报时测试波形}
\end{figure}
可以看到复位功能正常,整点报时功能正常。(前文提到的额外一个SysCLK长度的脉冲在此处体现出区别)接下来我们来观察校时和校分功能,放大波形如下:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/MultiFunctionalClock_Detail_AdjustHour.png}
\caption{校时测试波形}
\end{figure}
\newpage
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/MultiFunctionalClock_Detail_AdjustMinute.png}
\caption{校分测试波形}
\end{figure}
可以看到校时与校分功能正常。然后我们来观察闹钟的基本功能,放大波形如下:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/MultiFunctionalClock_Detail_Alarm.png}
\caption{闹钟测试波形}
\end{figure}
可以看到,闹钟在设置时间后会触发,且在手动停止成功。最后我们来看闹钟的重复触发功能,放大波形如下:
\begin{figure}[H]
\centering
\includegraphics[width=1.0\textwidth]{images/MultiFunctionalClock_Detail_DayChangedAndAlarmRetrig.png}
\caption{闹钟重复触发测试波形}
\end{figure}
可以看到,当日期变更信号触发后,闹钟在满足触发条件后再次触发。至此,所有的模块都符合我们的预期,测试通过。
\chapter{心得与体会}
本次数字电子技术基础课程设计的题目是“多功能数字时钟”,通过使用 Verilog HDL 进行设计与实现,我不仅加深了对数字逻辑电路的理解,也提升了自己在实际工程开发中的动手能力和解决问题的能力。
首先,在设计初期,我深入学习了数字时钟的基本原理,包括计时模块、计数器、分频器、数码管驱动等关键组成部分。通过对这些模块的组合与调试,我更加清晰地认识到模块化设计在数字系统开发中的重要性。每个模块独立设计、仿真验证后,再进行整体集成,不仅提高了开发效率,也便于后期的调试与维护。
其次,在使用 Verilog HDL 进行设计的过程中,我深刻体会到硬件描述语言与传统编程语言的差异。Verilog 更加注重并行逻辑与时序控制,这要求我在编写代码时必须时刻关注信号的时序关系与触发条件。通过多次仿真与调试,我逐渐掌握了如何编写稳定、可综合的代码,并学会了使用 iVerilog 等仿真工具进行功能验证。
在调试过程中,我也遇到了不少困难,例如计数器进位异常、闹钟重复触发逻辑处理不当等问题。每一次问题的出现都促使我重新审视设计的逻辑结构,并通过查阅资料、翻阅教材,最终找到解决方案。这些经历让我明白,工程开发不仅仅是技术的积累,更是思维方式和解决问题能力的锻炼。
此外,本次设计也让我认识到团队协作与项目管理的重要性。虽然项目是个人完成,但在设计过程中与同学交流思路、分享经验,极大地拓宽了我的视野,也帮助我更快地发现并解决问题。
总的来说,这次多功能数字时钟的设计项目不仅让我巩固了课堂所学的数字电路知识,也让我初步体验了从需求分析、模块设计、代码实现到仿真调试的完整开发流程。未来我将继续深入学习数字系统设计与硬件描述语言,争取在更复杂的项目中不断提升自己的专业能力。
\end{document}