赞
踩
数字钟 是大二小学期数字电路课程设计的题目
本代码在对应开发板和环境下验证无误,详细见开头的资源。
此外,我还想声明的是,如果能直接用到其他环境最好,但这个源代码只是作为无偿的分享,来提供思路。
课设的东西还是要自己做的,希望大家在评论区能多讨论一下跟知识本身有关的事情。
Thanks!
首先要说的是,在Verilog中的always
模块是并行的,即always
模块执行的次序与其在module
中的位置无关。
那么就引来了一个问题,我怎么确定要执行哪一个模块?
答案是:
flag
(标志)和if
判断来确定执行哪一个模块。always
中为reg
类型),后执行的模块对先执行的模块的输出敏感。简单来说:
always
模块通过输出和敏感条件相连接,组成逻辑上串行的大模块always
模块的敏感条件为主时钟(或其替代),通过if
实现大模块的并行也就是说,我们可以通过敏感变量的设置和flag
的设置来控制数字系统的结构。
理解上面这些是理解下面代码的基础!!!
针对这个数字钟系统,整个系统具有三种模式(互为并行关系),其相应的flag
要求如下:
set_mod==1
set_mod==0
且 set_alarm==0
set_mod==0
且 set_alarm==1
接下来我会按模块讲解,当然整个源码会放到最后。
功能:
对开发板的50MHz时钟信号,取50M分频,得到频率为1s的时钟信号
模块用到的输入:
input clk
模块的敏感条件:
posedge clk
模块的输出:
clk_div
(reg
类型,中间变量)模块框图:
模块代码:
//---------------------分频开始----------------------- parameter m=49999999; integer div_cnt=0; reg clk_div; always@(posedge clk) begin if(div_cnt==m) begin clk_div<=1'b1; div_cnt<=0; end else begin clk_div<=1'b0; div_cnt<=div_cnt+1; end end //---------------------分频结束-----------------------
parameter
类型的 m
,加1
即为分频的数目。
m=49999999;
表示每 50M 个clk
的边沿,会对应1个clk_div
的边沿。
分频的核心是一个if
语句和一个计数器div_cnt
,即:
div_cnt==m
时,表示已经记录到 50M 个clk
的上升沿==>对clk_div
置1
div_cnt<=m
时,表示还未记录到 50M 个clk
的上升沿==>对clk_div
置0
另外需要强调的一点:分频后的时钟信号的占空比 不要求 为 1:1。也就是说我只要求在一个周期内有一个有效的触发边沿(上边沿或下边沿),而对于这个有效的触发边沿是不是出现在周期的正中间则不做要求。
功能:
实现 00:00:00
==> 23:59:59
的循环的时间计时
模块用到的输入:
clk_div
:分频后的时钟信号,频率1Hz(周期 T=1s)。set_mod
:区分自动计时(set_mod==1
)和手动计时(set_mod==0
)。set_alarm
:区分设定闹钟(set_alarm==1
)和手动计时(set_alarm==0
)再结合我之前提到的:并行的模块由flag
和if
来控制。
大家对并行模块的控制的理解应该更直观和深刻了。
模块的敏感条件:
posedge clk_div
模块的输出:
secL_1
(秒的个位,reg
类型,中间变量)secH_1
(秒的十位,reg
类型,中间变量)minL_1
(分的个位,reg
类型,中间变量)minH_1
(分的十位,reg
类型,中间变量)hourL_1
(时的个位,reg
类型,中间变量)hourH_1
(时的十位,reg
类型,中间变量)至于为什么要把secL_1
、secH_1
、minL_1
、minH_1
、hourL_1
、hourH_1
都定义为reg
(寄存器类型,中间变量)而不定义为直接的输出output
,原因会在 模块4:计时输出模块 解释。
模块框图:
对flag
的要求:
set_mod==1
末状态清零:
当时间为 23:59:59
时,在下一秒时(clk_div
的posedge
触发该模块)对secL_1
、secH_1
、minL_1
、minH_1
、hourL_1
、hourH_1
进行清零操作,重置为 00:00:00
考虑进位关系:
自动计时模块中,我们考虑 秒、分、时 之间的 进位关系 (这是与手动计时的重大区别)。
嵌套的if-else
的作用是:当该位需要接收进位时,判断该位是否达到其上限(比如秒的低位最大为9
,秒的高位最大为5
)。
如果达到上限,则该位清零并向上进位;如果未达到上限,则该位+1。
对 flag
的要求:
set_mod==0
&& set_alarm==0
至于为什么不加,其实加不加都行。
为什么要继承手动计时的结果:
手动计时可以视为对数字钟的 校准。
也就是说当自动计时出现误差的时候,可以用手动校时来消除误差。
手动校时的输出为准确结果,应当作为自动计时模块的新的初值。
模块代码:
//------------------自动计数开始---------------------- reg [3:0]secL_1; reg [3:0]secH_1; reg [3:0]minL_1; reg [3:0]minH_1; reg [3:0]hourL_1; reg [3:0]hourH_1; always@(posedge clk_div) begin if(set_mod==1) begin //末状态清零 if(hourH_1==4'b0010 && hourL_1==4'b0011 && minH_1==4'b0101 && minL_1==4'b1001 && secH_1==4'b0101 && secL_1==4'b1001) begin secL_1<=4'b0000; secH_1<=4'b0000; minL_1<=4'b0000; minH_1<=4'b0000; hourL_1<=4'b0000; hourH_1<=4'b0000; end else //非末状态计数 if(secL_1==9) begin secL_1<=4'b0000; if(secH_1==5) begin secH_1<=4'b0000; if(minL_1==9) begin minL_1<=4'b0000; if(minH_1==5) begin minH_1<=4'b0000; if(hourL_1==9) begin hourL_1<=4'b0000; hourH_1<=hourH_1+1; end else hourL_1<=hourL_1+1; end else minH_1<=minH_1+1; end else minL_1<=minL_1+1; end else secH_1<=secH_1+1; end else secL_1<=secL_1+1; end else if(set_mod==0 && set_alarm==0) begin //继承手动的结果 secL_1<=secL_2; secH_1<=secH_2; minL_1<=minL_2; minH_1<=minH_2; hourL_1<=hourL_2; hourH_1<=hourH_2; end end //------------------自动计数结束----------------------
该模块由两个串行的模块组成(注意接口问题):
需要注意的是,在 模块3.2 手动调整模块 中,不考虑时、分、秒之间进位(时、分、秒是相互独立的)!!!
也就是说:
当 模式 设定为 调秒 时,只有秒的低位(secL_2
)和秒的高位(secH_2
)可以手动调节,而 分、时 均保持不变。(这一点是手动计数和自动计数的重大区别)
功能:
确定手动计时的位置为 时 或 分 或 秒
模块用到的输入:
set_option
:每有一个上升沿,则更改一次手动计数的位置(秒 --> 分 --> 时 的循环顺序)。模块的敏感条件:
posedge set_option
模块的输出:
option_1
(reg
类型,中间变量)option_1==0
==> 手动计数位置为 sec
option_1==1
==> 手动计数位置为 min
option_1==2
==> 手动计数位置为 hour
模式设定模块源代码:
//模式设定开始
//option_1==0表示手动调秒; option_1==1表示手动调分; option_1==2表示调时
reg [1:0]option_1;
always@(posedge set_option)
begin
if(option_1==3)
option_1<=0;
else
option_1<=option_1+1;
end
//模式设定结束
功能:
根据模式设定模块的输出option_1
指定的位置(时or分or秒),在调节开关time_add
下调数。
模块用到的输入:
time_add
:每有一个time_add
的posedge
,被调位置的数字 +1set_mod
和 set_alarm
:set_mod==0
且 set_alarm==0
表示 手动模式on + 闹钟设定模式off,对应手动校时功能set_mod==0
且 set_alarm==1
表示 手动模式on + 闹钟设定模式on,对应闹钟设置功能option_1
:option_1==0
==> 手动计数位置为 sec
option_1==1
==> 手动计数位置为 min
option_1==2
==> 手动计数位置为 hour
模块的敏感条件:
posedge time_add
模块输出:
均定义为reg
类型,作为中间变量。
secL_2
;secH_2
;minL_2
;minH_2
;hourL_2
;hourH_2
;模块源代码:
//手动调整开始 reg [3:0]secL_2; reg [3:0]secH_2; reg [3:0]minL_2; reg [3:0]minH_2; reg [3:0]hourL_2; reg [3:0]hourH_2; always@(posedge time_add) begin //手动校时模式 if(set_mod==0 && set_alarm==0) //手动模式on + 闹钟设定模式off begin //末状态清零 if(hourH_2==4'b0010 && hourL_2==4'b0011 && minH_2==4'b0101 && minL_2==4'b1001 && secH_2==4'b0101 && secL_2==4'b1001) begin secL_2<=4'b0000; secH_2<=4'b0000; minL_2<=4'b0000; minH_2<=4'b0000; hourL_2<=4'b0000; hourH_2<=4'b0000; end else //非末状态计数 //调秒 if(option_1==0) begin if(secL_2==9) begin secL_2<=4'b0000; if(secH_2==5) secH_2<=4'b0000; else secH_2<=secH_2+1; end else secL_2<=secL_2+1; end //调分 if(option_1==1) begin if(minL_2==9) begin minL_2<=4'b0000; if(minH_2==5) minH_2<=4'b0000; else minH_2<=minH_2+1; end else minL_2<=minL_2+1; end //调时 if(option_1==2) begin if(hourL_2==9) begin hourL_2<=4'b0000; hourH_2<=hourH_2+1; end else hourL_2<=hourL_2+1; end end //闹钟设定模式 else if(set_mod==0 && set_alarm==1) //手动模式on + 闹钟设定模式on begin //末状态清零 if(alarm_hourH==4'b0010 && alarm_hourL==4'b0011 && alarm_minH==4'b0101 && alarm_minL==4'b1001 && alarm_secH==4'b0101 && alarm_secL==4'b1001) begin alarm_secL<=4'b0000; alarm_secH<=4'b0000; alarm_minL<=4'b0000; alarm_minH<=4'b0000; alarm_hourL<=4'b0000; alarm_hourH<=4'b0000; end else //非末状态计数 //调秒 if(option_1==0) begin if(alarm_secL==9) begin alarm_secL<=4'b0000; if(alarm_secH==5) alarm_secH<=4'b0000; else alarm_secH<=alarm_secH+1; end else alarm_secL<=alarm_secL+1; end //调分 if(option_1==1) begin if(alarm_minL==9) begin alarm_minL<=4'b0000; if(alarm_minH==5) alarm_minH<=4'b0000; else alarm_minH<=alarm_minH+1; end else alarm_minL<=alarm_minL+1; end //调时 if(option_1==2) begin if(alarm_hourL==9) begin alarm_hourL<=4'b0000; alarm_hourH<=alarm_hourH+1; end else alarm_hourL<=alarm_hourL+1; end end
关于 末状态清零 和 非末状态计数,原理和 自动计时 一样,这里就不再赘述。
其实如果有了自动计时模块的思路,很容易想到把自动计时模块的思路,原封不动地搬到手动调时模块,即依旧考虑秒分时之间的进位。
但这样会产生问题。
首先一个小问题是:
按自动计时模块的思路,我要给时hour
、分min
、秒sec
各配备一个调整开关(开关每有一个posedge
则相应位置的数字 +1),也就是得到三个调整开关hour_add
、min_add
、sec_add
。之后我要把这三个调整开关接到开发板的三个不同的pins
上。
其实这样是有些多余的,因为这三个调整开关,在功能上是大同小异的,在本质上是一样的,都是拨动一下则相应位置的数 +1。
也就是说,通过合理的优化,是可以把三个调整开关合并为一个调整开关的。
另外,如果功能需要扩展,我要去手动调整更多位置的数(比如说年、月、日),那我岂不是要用更多的调整开关,接到开发板更多的不同的pins
上?
这样就会有两个问题:
然后还有一个严重的问题:
对于变量的赋值只能出现在一个always
中
按照自动计时的思路,我必须在一个always
中完成对时hour
、分min
、秒sec
的赋值,也就是说这个always
的敏感条件有三个边沿——hour_add
、min_add
、sec_add
(always@(posedge hour_add or posedge min_add or posedge sec_add)
)。
问题时这样写,会出现编译错误。因为一个always
模块中只能出现一个主时钟。而这样写的话,hour_add
、min_add
、sec_add
都是主时钟信号(非控制端信号)。
经查阅资料是编译时无法区分三个posedge
中哪个是时钟信号,哪个是控制信号。
所以:
用 “不考虑时、分、秒的进位” 替代 “考虑时、分、秒的进位”,有以下好处:
time_add
,再引入1个 模式设定开关set_option
。(输入接口个数减少,输入接口类型不再冗余和重复)always
中的敏感条件由分别调整时分秒的三个posedge
,减少为一个posedge
—— time_add
//------------------手动计数开始---------------------- //模式设定开始 //option_1==0表示手动调秒; option_1==1表示手动调分; option_1==2表示调时 reg [1:0]option_1; always@(posedge set_option) begin if(option_1==3) option_1<=0; else option_1<=option_1+1; end //模式设定结束 //手动调整开始 reg [3:0]secL_2; reg [3:0]secH_2; reg [3:0]minL_2; reg [3:0]minH_2; reg [3:0]hourL_2; reg [3:0]hourH_2; always@(posedge time_add) begin //手动校时模式 if(set_mod==0 && set_alarm==0) //手动模式on + 闹钟设定模式off begin //末状态清零 if(hourH_2==4'b0010 && hourL_2==4'b0011 && minH_2==4'b0101 && minL_2==4'b1001 && secH_2==4'b0101 && secL_2==4'b1001) begin secL_2<=4'b0000; secH_2<=4'b0000; minL_2<=4'b0000; minH_2<=4'b0000; hourL_2<=4'b0000; hourH_2<=4'b0000; end else //非末状态计数 //调秒 if(option_1==0) begin if(secL_2==9) begin secL_2<=4'b0000; if(secH_2==5) secH_2<=4'b0000; else secH_2<=secH_2+1; end else secL_2<=secL_2+1; end //调分 if(option_1==1) begin if(minL_2==9) begin minL_2<=4'b0000; if(minH_2==5) minH_2<=4'b0000; else minH_2<=minH_2+1; end else minL_2<=minL_2+1; end //调时 if(option_1==2) begin if(hourL_2==9) begin hourL_2<=4'b0000; hourH_2<=hourH_2+1; end else hourL_2<=hourL_2+1; end end //闹钟设定模式 else if(set_mod==0 && set_alarm==1) //手动模式on + 闹钟设定模式on begin //末状态清零 if(alarm_hourH==4'b0010 && alarm_hourL==4'b0011 && alarm_minH==4'b0101 && alarm_minL==4'b1001 && alarm_secH==4'b0101 && alarm_secL==4'b1001) begin alarm_secL<=4'b0000; alarm_secH<=4'b0000; alarm_minL<=4'b0000; alarm_minH<=4'b0000; alarm_hourL<=4'b0000; alarm_hourH<=4'b0000; end else //非末状态计数 //调秒 if(option_1==0) begin if(alarm_secL==9) begin alarm_secL<=4'b0000; if(alarm_secH==5) alarm_secH<=4'b0000; else alarm_secH<=alarm_secH+1; end else alarm_secL<=alarm_secL+1; end //调分 if(option_1==1) begin if(alarm_minL==9) begin alarm_minL<=4'b0000; if(alarm_minH==5) alarm_minH<=4'b0000; else alarm_minH<=alarm_minH+1; end else alarm_minL<=alarm_minL+1; end //调时 if(option_1==2) begin if(alarm_hourL==9) begin alarm_hourL<=4'b0000; alarm_hourH<=alarm_hourH+1; end else alarm_hourL<=alarm_hourL+1; end end end //手动调整结束 //------------------手动计数结束----------------------
这里要解释一下,为什么自动计时和手动调时的输出设置为reg
类型而不是output
类型。(注意以下的“输出
”是相对“输入
”来说的,与output
类型并不是一个概念)
假设我将其设定为output
类型,那么把自动计时的输出和手动调时的输出放到一起,就有了2×6=12个output
。
考虑到输出可视化,我需要把自动计时的输出或手动调时的输出放到数码管上。
当我采用自动计时模式时,数码管上显示的时间是自动计时模式的输出。此时我并不关心手动调时的输出是什么(因为数字钟完成了正确显示时间的功能)。
当我采用手动调时模式时,数码管上显示的时间是手动调时模式的输出。此时我并不关心自动计时的输出是什么(当我需要手动调时的时候,说明自动计时模块的结果已经不正确,没有意义了)。
而且开发板上的数码管最多只能显示8个数字。
这也就意味着,在任意时刻,只有自动计时模式的输出和手动调时模式的输出二者其一起作用,而另一个可有可无。或者换句话说,在任意时刻,在12个output
中总有6个output
可有可无。
在这种情况下,仅仅将真正起作用的输出作为output
,将不起作用的作为reg
,显然是更好的选择。
功能:
将模块2自动计时模块和模块3手动计时模块的reg
类型的输出中,起作用的reg
变为output
类型的输出。
模块用到的输入:
secL_1
secH_1
minL_1
minH_1
hourL_1
hourH_1
secL_2
secH_2
minL_2
minH_2
hourL_2
hourH_2
模块的敏感条件:
当自动计时模块和手动调时模块的输出有一个发生变化的时候,即触发该模块。
因为自动计时模块的输出每1s就变动一次,所以该模块的输出至少1s更新一次,数码管上的示数也是至少1s更新一次。
secL_1
secH_1
minL_1
minH_1
hourL_1
hourH_1
secL_2
secH_2
minL_2
minH_2
hourL_2
hourH_2
模块输出:
均定义为output
类型。
secL
secH
minL
minH
hourL
hourH
模块代码:
//---------计数结果从中间变量变为最终结果开始--------- always@(secL_1 or secH_1 or minL_1 or minH_1 or hourL_1 or hourH_1 or secL_2 or secH_2 or minL_2 or minH_2 or hourL_2 or hourH_2) begin //自动计数模式 if(set_mod==1) begin secL<=secL_1; secH<=secH_1; minL<=minL_1; minH<=minH_1; hourL<=hourL_1; hourH<=hourH_1; end else //手动计数模式 if(set_mod==0 && set_alarm==0) //手动模式on + 闹钟设定模式off begin secL<=secL_2; secH<=secH_2; minL<=minL_2; minH<=minH_2; hourL<=hourL_2; hourH<=hourH_2; end end //---------计数结果从中间变量变为最终结果结束---------
简单来说,就是:
当set_mod==1
(自动计时模块)时,output
类型的 secL
等变量,取 reg
类型的 secL_1
(自动计时模块的输出) 等变量的值。
当set_mod==0 && set_alarm==0
(手动调时模块)时,output
类型的 secL
等变量,取 reg
类型的 secL_2
(手动调时模块的输出) 等变量的值。
而当secL_1
or secH_1
or minL_1
or minH_1
or hourL_1
or hourH_1
or secL_2
or secH_2
or minL_2
or minH_2
or hourL_2
or hourH_2
有任意一个变化时,该模块就会被触发。从而保证了输出的时效性。
功能:
从59:50
到00:00
,产生以1
为起始的0-1
序列,对应指示灯间隔为1s
的闪烁。
模块用到的输入:
minH
:output
类型minL
:output
类型secH
:output
类型secL
:output
类型模块的敏感条件:
secL
: 时间每变化一次就触发一次。因为时间变化的同时,秒的个位secL
必然变化,故省略对secH
、minL
、minH
的敏感(待核)模块输出:
blink
:output
类型模块代码:
//-------------------报时开始-------------------------
always@(secL)
begin
if(minH==5 && minL==9 && secH==5 && secL>=0)
blink<=~blink;
else
blink=1'b0; //保持常灭状态
end
//-------------------报时结束-------------------------
在硬件中,没有给定初值的变量,初值视为0
。所以我们通过 变量取反 来构造闪烁的0-1
序列。
功能:
实现 9-0
的倒计时
当set_recnt==1
时,开始倒计时;set_recnt==0
时,将数字重置为9
。
模块用到的输入:
clk_div
:主时钟。分频后的时钟信号,频率1s。set_recnt
:控制端。倒计时开始与否的flag
模块的敏感条件:
posedge clk_div
:作为该模块的主时钟,决定倒计时的频率为1snegedge set_recnt
:作为控制端,所以需要该模块对set_recnt
的变化敏感。模块输出:
cnt
:reg
类型recnt
:output
类型模块代码:
//------------------倒计时开始------------------------ reg[3:0]cnt; always@(posedge clk_div or negedge set_recnt) begin if(!set_recnt) begin cnt<=4'b0000; end else begin if(cnt==4'b1001) cnt<=4'b0000; else cnt<=cnt+1; end case(cnt) 4'b0000:recnt<=4'b1001; 4'b0001:recnt<=4'b1000; 4'b0010:recnt<=4'b0111; 4'b0011:recnt<=4'b0110; 4'b0100:recnt<=4'b0101; 4'b0101:recnt<=4'b0100; 4'b0110:recnt<=4'b0011; 4'b0111:recnt<=4'b0010; 4'b1000:recnt<=4'b0001; 4'b1001:recnt<=4'b0000; default:recnt<=4'b0000; endcase end
首先是在手动计时模块中提到的问题:
当有两个及以上的边沿作为always
的敏感条件时,要通过合理的操作,来区分主时钟和控制端。
在本模块我们通过增加if(!set_recnt)-else
来表明set_recnt
为控制端而另一个信号clk_div
为主时钟。
always@(posedge clk_div or negedge set_recnt)
的模块,在当set_recnt==0
时,将数字重置为9
;set_recnt==1
时,生成0-9
的循环序列。
模块中output
类型的recnt
,为reg
类型的cnt
的反序。
cnt | recnt |
---|---|
0 | 9 |
1 | 8 |
2 | 7 |
3 | 6 |
4 | 5 |
5 | 4 |
6 | 3 |
7 | 2 |
8 | 1 |
9 | 0 |
通过case
语句,由cnt
计算recnt
。
模块代码:
always@(recnt) begin case(recnt) 4'b0000:qout_recnt<=7'b1000000; 4'b0001:qout_recnt<=7'b1111001; 4'b0010:qout_recnt<=7'b0100100; 4'b0011:qout_recnt<=7'b0110000; 4'b0100:qout_recnt<=7'b0011001; 4'b0101:qout_recnt<=7'b0010010; 4'b0110:qout_recnt<=7'b0000010; 4'b0111:qout_recnt<=7'b1111000; 4'b1000:qout_recnt<=7'b0000000; 4'b1001:qout_recnt<=7'b0010000; default:qout_recnt<=7'b1111111; endcase end //------------------倒计时结束------------------------
数码管输出模块对recnt
的值敏感。
将recnt
的值,放到数码管输出模块中,便可在数码管上显示相应的倒计时数字。
这是非常固定的代码,直接拿来用就好了。这里也不做讲解。
功能:
手动模式来设定闹钟。
模块用到的输入:
time_add
:手动计时的时钟模块的敏感条件:
posedge time_add
:手动拨动一次开关,则对应模式(调时or调分or调秒)相应位置的数字+1模块输出:
闹钟设定的时间,均为output
类型
alarm_secL
alarm_secH
alarm_minL
alarm_minH
alarm_hourL
alarm_hourH
这个小模块被整合到模块3:手动模块中
需要注意的是,手动校时模式和闹钟设定模式都在模块3:手动模块中,我们通过设置一个flag
——set_alarm
来区分这两种模式。
当set_mod==0
且set_alarm==0
(手动调节模式on
+ 闹钟设定模式off
)时,功能为手动校时。
当set_mod==0
且set_alarm==1
(手动调节模式on
+ 闹钟设定模式on
)时,功能为闹钟设定。
闹钟设定模式,依旧采用时分秒独立的思想,即不考虑时分秒的进位。这一点同 手动校时模式 完全一样。
闹钟设定模式下,末状态清零和非末状态计数,在 模块3 中已经讲解,这里不再重复。
模块代码:
见 模块3
功能:
在闹钟设定的时间,触发指示灯亮。
模块用到的输入:(待改,见文章头)
secL
:时间每变动一次,则触发一次该模块。secL
必然变化,故省略对secH
、minL
、minH
的敏感(待核)模块的敏感条件:
secL
:时间每变动一次,则触发一次该模块。模块输出:
alarm_sound
:到达设定时间则亮(值为1
),否则不亮(值为0
)模块代码:
//闹钟触发开始:判断闹钟设定时间 是否等于 现在的时间,如果是则灯亮 //reg [3:0]alarm_cnt; always@(posedge clk_div) begin alarm_sound=1'b0; if(secL==alarm_secL && secH==alarm_secH && minL==alarm_minL && minH==alarm_minH && hourL==alarm_hourL && hourH==alarm_hourH) begin alarm_sound=1'b1; end /* else begin alarm_sound=1'b0 end */ end //闹钟触发结束 //----------------闹钟模块结束------------------------
//----------------闹钟模块开始------------------------ //闹钟设定模块在 模块3 中 //闹钟触发开始:判断闹钟设定时间 是否等于 现在的时间,如果是则灯亮 //reg [3:0]alarm_cnt; always@(secL) begin alarm_sound=1'b0; if(secL==alarm_secL && secH==alarm_secH && minL==alarm_minL && minH==alarm_minH && hourL==alarm_hourL && hourH==alarm_hourH) begin alarm_sound=1'b1; end /* else begin alarm_sound=1'b0 end */ end //闹钟触发结束 //----------------闹钟模块结束------------------------
这里只以secL
和alarm_sec
为例,其他的secH
和alarm_secH
同理。
功能:
将需要显示的secL
或alarm_secL
,以数字形式显示在数码管上。
模块用到的输入:(待改,见文章头)
secL
alarm_secL
模块的敏感条件:
secL or alarm_secL
:两者有其一变化,即触发该模块。模块输出:
qout_1
:数码管亮灭信号整个系统需要在数码管上显示的时间有计时的时间和闹钟设置的时间(不考虑倒计时),而且二者在数码管显示是要占用相同的位置。
所以我们要判断起作用的是计时模块还是闹钟设置模块。判断的方法则是通过set_alarm
。
set_alarm==0
时为计时模块。
set_alarm==1
时为闹钟设置模块。
当然大家如果细心的话,会发现secL
这一组output
,和alarm_secL
这一组output
,总是会有一组不被用到。
为什么这里不像计时模块一样,将两组output合并来减少输出接口数呢?
原因是:
自动计时模块和手动调时模块都是计时模块,二者在功能上相互配合才能正确地完成计时功能。所以将自动计时模块和手动调时模块的集成为一个大模块,合并其输出接口是一个很合理很顺其自然的做法。
而计时模块和闹钟模块,二者在功能的实现上基本独立,并没有集成到一起的必要。硬要把二者结合到一起,反而会使两个独立模块搅在一起,破坏系统的条理性和模块之间的独立性。
模块代码:
always@(secL or alarm_secL) begin if(set_alarm==0) begin case(secL) 4'b0000:qout_1<=7'b1000000; 4'b0001:qout_1<=7'b1111001; 4'b0010:qout_1<=7'b0100100; 4'b0011:qout_1<=7'b0110000; 4'b0100:qout_1<=7'b0011001; 4'b0101:qout_1<=7'b0010010; 4'b0110:qout_1<=7'b0000010; 4'b0111:qout_1<=7'b1111000; 4'b1000:qout_1<=7'b0000000; 4'b1001:qout_1<=7'b0010000; default:qout_1<=7'b1111111; endcase end else if(set_alarm==1) begin case(alarm_secL) 4'b0000:qout_1<=7'b1000000; 4'b0001:qout_1<=7'b1111001; 4'b0010:qout_1<=7'b0100100; 4'b0011:qout_1<=7'b0110000; 4'b0100:qout_1<=7'b0011001; 4'b0101:qout_1<=7'b0010010; 4'b0110:qout_1<=7'b0000010; 4'b0111:qout_1<=7'b1111000; 4'b1000:qout_1<=7'b0000000; 4'b1001:qout_1<=7'b0010000; default:qout_1<=7'b1111111; endcase end end
module clk( input clk, input set_mod, //低电平表示手动set模式(连switch)==>SW17(完成) input set_option, //选择调秒、调分或调时(连switch)==>KEY3(完成) input time_add, //拨动开关以计数(连switch)==>KEY2(完成) input set_recnt, //低电平重置为9,高电平倒计时(连switch)==>SW0(完成) input set_alarm, //==>SW16 高电平开始设置闹钟,需与set_mod配合来判断是调时钟or调闹钟 + 区分闹钟和时间的可视化(完成) //时分秒的数码管输出 output reg [6:0]qout_1, //==>HEX0(完成) output reg [6:0]qout_2, //==>HEX1(完成) output reg [6:0]qout_3, //==>HEX2(完成) output reg [6:0]qout_4, //==>HEX3(完成) output reg [6:0]qout_5, //==>HEX4(完成) output reg [6:0]qout_6, //==>HEX5(完成) //作为数码管的输入 output reg [3:0]secL, output reg [3:0]secH, output reg [3:0]minL, output reg [3:0]minH, output reg [3:0]hourL, output reg [3:0]hourH, //整点报时的可视化 output reg blink, //==>LEDR17(完成) //倒计时的可视化 output reg [3:0]recnt, output reg [6:0]qout_recnt, //==>HEX7(完成) //闹钟设定时间的可视化 output reg [3:0]alarm_secL, output reg [3:0]alarm_secH, output reg [3:0]alarm_minL, output reg [3:0]alarm_minH, output reg [3:0]alarm_hourL, output reg [3:0]alarm_hourH, output reg alarm_sound //到闹钟设定时间亮10秒==>LEDR15(完成) ); //---------------------分频开始----------------------- parameter m=49999999; integer div_cnt=0; reg clk_div; always@(posedge clk) begin if(div_cnt==m) begin clk_div<=1'b1; div_cnt<=0; end else begin clk_div<=1'b0; div_cnt<=div_cnt+1; end end //---------------------分频结束----------------------- //------------------自动计数开始---------------------- reg [3:0]secL_1; reg [3:0]secH_1; reg [3:0]minL_1; reg [3:0]minH_1; reg [3:0]hourL_1; reg [3:0]hourH_1; always@(posedge clk_div) begin if(set_mod==1) begin //末状态清零 if(hourH_1==4'b0010 && hourL_1==4'b0011 && minH_1==4'b0101 && minL_1==4'b1001 && secH_1==4'b0101 && secL_1==4'b1001) begin secL_1<=4'b0000; secH_1<=4'b0000; minL_1<=4'b0000; minH_1<=4'b0000; hourL_1<=4'b0000; hourH_1<=4'b0000; end else //非末状态计数 if(secL_1==9) begin secL_1<=4'b0000; if(secH_1==5) begin secH_1<=4'b0000; if(minL_1==9) begin minL_1<=4'b0000; if(minH_1==5) begin minH_1<=4'b0000; if(hourL_1==9) begin hourL_1<=4'b0000; hourH_1<=hourH_1+1; end else hourL_1<=hourL_1+1; end else minH_1<=minH_1+1; end else minL_1<=minL_1+1; end else secH_1<=secH_1+1; end else secL_1<=secL_1+1; end else if(set_mod==0 && set_alarm==0) begin //继承手动的结果 secL_1<=secL_2; secH_1<=secH_2; minL_1<=minL_2; minH_1<=minH_2; hourL_1<=hourL_2; hourH_1<=hourH_2; end end //------------------自动计数结束---------------------- //------------------手动计数开始---------------------- //模式设定开始 //option_1==0表示手动调秒; option_1==1表示手动调分; option_1==2表示调时 reg [1:0]option_1; always@(posedge set_option) begin if(option_1==3) option_1=0; else option_1<=option_1+1; end //模式设定结束 //手动调整开始 reg [3:0]secL_2; reg [3:0]secH_2; reg [3:0]minL_2; reg [3:0]minH_2; reg [3:0]hourL_2; reg [3:0]hourH_2; always@(posedge time_add) begin if(set_mod==0 && set_alarm==0) //手动模式on + 闹钟设定模式off begin //option=2; //调试时指定option //末状态清零 if(hourH_2==4'b0010 && hourL_2==4'b0011 && minH_2==4'b0101 && minL_2==4'b1001 && secH_2==4'b0101 && secL_2==4'b1001) begin secL_2<=4'b0000; secH_2<=4'b0000; minL_2<=4'b0000; minH_2<=4'b0000; hourL_2<=4'b0000; hourH_2<=4'b0000; end else //非末状态计数 //调秒 if(option_1==0) begin if(secL_2==9) begin secL_2<=4'b0000; if(secH_2==5) secH_2<=4'b0000; else secH_2<=secH_2+1; end else secL_2<=secL_2+1; end //调分 if(option_1==1) begin if(minL_2==9) begin minL_2<=4'b0000; if(minH_2==5) minH_2<=4'b0000; else minH_2<=minH_2+1; end else minL_2<=minL_2+1; end //调时 if(option_1==2) begin if(hourL_2==9) begin hourL_2<=4'b0000; hourH_2<=hourH_2+1; end else hourL_2<=hourL_2+1; end end end //手动调整结束 //------------------手动计数结束---------------------- //---------计数结果从中间变量变为最终结果开始--------- always@(secL_1 or secH_1 or minL_1 or minH_1 or hourL_1 or hourH_1 or secL_2 or secH_2 or minL_2 or minH_2 or hourL_2 or hourH_2) begin //自动计数模式 if(set_mod==1) begin secL<=secL_1; secH<=secH_1; minL<=minL_1; minH<=minH_1; hourL<=hourL_1; hourH<=hourH_1; end else //手动计数模式 if(set_mod==0 && set_alarm==0) //手动模式on + 闹钟设定模式off begin secL<=secL_2; secH<=secH_2; minL<=minL_2; minH<=minH_2; hourL<=hourL_2; hourH<=hourH_2; end end //---------计数结果从中间变量变为最终结果结束--------- //-------------------报时开始------------------------- always@(posedge clk_div) begin if((minH==5 && minL==9 && secH==4 && secL==9) || (minH==5 && minL==9 && secH==5 && secL>=0)) blink<=~blink; else blink=1'b0; //保持常灭状态 end //-------------------报时结束------------------------- //------------------倒计时开始------------------------ reg[3:0]cnt; always@(posedge clk_div or negedge set_recnt) begin if(!set_recnt) begin cnt<=4'b0000; end else begin if(cnt==4'b1001) cnt<=4'b0000; else cnt<=cnt+1; end case(cnt) 4'b0000:recnt<=4'b1001; 4'b0001:recnt<=4'b1000; 4'b0010:recnt<=4'b0111; 4'b0011:recnt<=4'b0110; 4'b0100:recnt<=4'b0101; 4'b0101:recnt<=4'b0100; 4'b0110:recnt<=4'b0011; 4'b0111:recnt<=4'b0010; 4'b1000:recnt<=4'b0001; 4'b1001:recnt<=4'b0000; default:recnt<=4'b0000; endcase end always@(recnt) begin case(recnt) 4'b0000:qout_recnt<=7'b1000000; 4'b0001:qout_recnt<=7'b1111001; 4'b0010:qout_recnt<=7'b0100100; 4'b0011:qout_recnt<=7'b0110000; 4'b0100:qout_recnt<=7'b0011001; 4'b0101:qout_recnt<=7'b0010010; 4'b0110:qout_recnt<=7'b0000010; 4'b0111:qout_recnt<=7'b1111000; 4'b1000:qout_recnt<=7'b0000000; 4'b1001:qout_recnt<=7'b0010000; default:qout_recnt<=7'b1111111; endcase end //------------------倒计时结束------------------------ //----------------闹钟模块开始------------------------ //手动设定闹钟时间开始 always@(posedge time_add) begin if(set_mod==0 && set_alarm==1) //手动模式on + 闹钟设定模式on begin //option_2=0; //调试时指定模式 //末状态清零 if(alarm_hourH==4'b0010 && alarm_hourL==4'b0011 && alarm_minH==4'b0101 && alarm_minL==4'b1001 && alarm_secH==4'b0101 && alarm_secL==4'b1001) begin alarm_secL<=4'b0000; alarm_secH<=4'b0000; alarm_minL<=4'b0000; alarm_minH<=4'b0000; alarm_hourL<=4'b0000; alarm_hourH<=4'b0000; end else //非末状态计数 //调秒 if(option_1==0) begin if(alarm_secL==9) begin alarm_secL<=4'b0000; if(alarm_secH==5) alarm_secH<=4'b0000; else alarm_secH<=alarm_secH+1; end else alarm_secL<=alarm_secL+1; end //调分 if(option_1==1) begin if(alarm_minL==9) begin alarm_minL<=4'b0000; if(alarm_minH==5) alarm_minH<=4'b0000; else alarm_minH<=alarm_minH+1; end else alarm_minL<=alarm_minL+1; end //调时 if(option_1==2) begin if(alarm_hourL==9) begin alarm_hourL<=4'b0000; alarm_hourH<=alarm_hourH+1; end else alarm_hourL<=alarm_hourL+1; end end end //手动设定闹钟时间结束 //闹钟触发开始:判断闹钟设定时间 是否等于 现在的时间,如果是则灯亮10秒 //reg [3:0]alarm_cnt; always@(posedge clk_div) begin alarm_sound=1'b0; if(secL==alarm_secL && secH==alarm_secH && minL==alarm_minL && minH==alarm_minH && hourL==alarm_hourL && hourH==alarm_hourH) begin alarm_sound=1'b1; end /* else begin alarm_sound=1'b0 end */ end //闹钟触发结束 //----------------闹钟模块结束------------------------ //------------时分秒的数码管显示开始------------------ always@(secL or alarm_secL) begin if(set_alarm==0) begin case(secL) 4'b0000:qout_1<=7'b1000000; 4'b0001:qout_1<=7'b1111001; 4'b0010:qout_1<=7'b0100100; 4'b0011:qout_1<=7'b0110000; 4'b0100:qout_1<=7'b0011001; 4'b0101:qout_1<=7'b0010010; 4'b0110:qout_1<=7'b0000010; 4'b0111:qout_1<=7'b1111000; 4'b1000:qout_1<=7'b0000000; 4'b1001:qout_1<=7'b0010000; default:qout_1<=7'b1111111; endcase end else if(set_alarm==1) begin case(alarm_secL) 4'b0000:qout_1<=7'b1000000; 4'b0001:qout_1<=7'b1111001; 4'b0010:qout_1<=7'b0100100; 4'b0011:qout_1<=7'b0110000; 4'b0100:qout_1<=7'b0011001; 4'b0101:qout_1<=7'b0010010; 4'b0110:qout_1<=7'b0000010; 4'b0111:qout_1<=7'b1111000; 4'b1000:qout_1<=7'b0000000; 4'b1001:qout_1<=7'b0010000; default:qout_1<=7'b1111111; endcase end end always@(secH or alarm_secH) begin if(set_alarm==0) begin case(secH) 4'b0000:qout_2<=7'b1000000; 4'b0001:qout_2<=7'b1111001; 4'b0010:qout_2<=7'b0100100; 4'b0011:qout_2<=7'b0110000; 4'b0100:qout_2<=7'b0011001; 4'b0101:qout_2<=7'b0010010; 4'b0110:qout_2<=7'b0000010; 4'b0111:qout_2<=7'b1111000; 4'b1000:qout_2<=7'b0000000; 4'b1001:qout_2<=7'b0010000; default:qout_2<=7'b1111111; endcase end else if(set_alarm==1) begin case(alarm_secH) 4'b0000:qout_2<=7'b1000000; 4'b0001:qout_2<=7'b1111001; 4'b0010:qout_2<=7'b0100100; 4'b0011:qout_2<=7'b0110000; 4'b0100:qout_2<=7'b0011001; 4'b0101:qout_2<=7'b0010010; 4'b0110:qout_2<=7'b0000010; 4'b0111:qout_2<=7'b1111000; 4'b1000:qout_2<=7'b0000000; 4'b1001:qout_2<=7'b0010000; default:qout_2<=7'b1111111; endcase end end always@(minL) begin if(set_alarm==0) begin case(minL) 4'b0000:qout_3<=7'b1000000; 4'b0001:qout_3<=7'b1111001; 4'b0010:qout_3<=7'b0100100; 4'b0011:qout_3<=7'b0110000; 4'b0100:qout_3<=7'b0011001; 4'b0101:qout_3<=7'b0010010; 4'b0110:qout_3<=7'b0000010; 4'b0111:qout_3<=7'b1111000; 4'b1000:qout_3<=7'b0000000; 4'b1001:qout_3<=7'b0010000; default:qout_3<=7'b1111111; endcase end else if(set_alarm==1) begin case(alarm_minL) 4'b0000:qout_3<=7'b1000000; 4'b0001:qout_3<=7'b1111001; 4'b0010:qout_3<=7'b0100100; 4'b0011:qout_3<=7'b0110000; 4'b0100:qout_3<=7'b0011001; 4'b0101:qout_3<=7'b0010010; 4'b0110:qout_3<=7'b0000010; 4'b0111:qout_3<=7'b1111000; 4'b1000:qout_3<=7'b0000000; 4'b1001:qout_3<=7'b0010000; default:qout_3<=7'b1111111; endcase end end always@(minH) begin if(set_alarm==0) begin case(minH) 4'b0000:qout_4<=7'b1000000; 4'b0001:qout_4<=7'b1111001; 4'b0010:qout_4<=7'b0100100; 4'b0011:qout_4<=7'b0110000; 4'b0100:qout_4<=7'b0011001; 4'b0101:qout_4<=7'b0010010; 4'b0110:qout_4<=7'b0000010; 4'b0111:qout_4<=7'b1111000; 4'b1000:qout_4<=7'b0000000; 4'b1001:qout_4<=7'b0010000; default:qout_4<=7'b1111111; endcase end else if(set_alarm==1) begin case(alarm_minH) 4'b0000:qout_4<=7'b1000000; 4'b0001:qout_4<=7'b1111001; 4'b0010:qout_4<=7'b0100100; 4'b0011:qout_4<=7'b0110000; 4'b0100:qout_4<=7'b0011001; 4'b0101:qout_4<=7'b0010010; 4'b0110:qout_4<=7'b0000010; 4'b0111:qout_4<=7'b1111000; 4'b1000:qout_4<=7'b0000000; 4'b1001:qout_4<=7'b0010000; default:qout_4<=7'b1111111; endcase end end always@(hourL) begin if(set_alarm==0) begin case(hourL) 4'b0000:qout_5<=7'b1000000; 4'b0001:qout_5<=7'b1111001; 4'b0010:qout_5<=7'b0100100; 4'b0011:qout_5<=7'b0110000; 4'b0100:qout_5<=7'b0011001; 4'b0101:qout_5<=7'b0010010; 4'b0110:qout_5<=7'b0000010; 4'b0111:qout_5<=7'b1111000; 4'b1000:qout_5<=7'b0000000; 4'b1001:qout_5<=7'b0010000; default:qout_5<=7'b1111111; endcase end else if(set_alarm==1) begin case(alarm_hourL) 4'b0000:qout_5<=7'b1000000; 4'b0001:qout_5<=7'b1111001; 4'b0010:qout_5<=7'b0100100; 4'b0011:qout_5<=7'b0110000; 4'b0100:qout_5<=7'b0011001; 4'b0101:qout_5<=7'b0010010; 4'b0110:qout_5<=7'b0000010; 4'b0111:qout_5<=7'b1111000; 4'b1000:qout_5<=7'b0000000; 4'b1001:qout_5<=7'b0010000; default:qout_5<=7'b1111111; endcase end end always@(hourH) begin if(set_alarm==0) begin case(hourH) 4'b0000:qout_6<=7'b1000000; 4'b0001:qout_6<=7'b1111001; 4'b0010:qout_6<=7'b0100100; 4'b0011:qout_6<=7'b0110000; 4'b0100:qout_6<=7'b0011001; 4'b0101:qout_6<=7'b0010010; 4'b0110:qout_6<=7'b0000010; 4'b0111:qout_6<=7'b1111000; 4'b1000:qout_6<=7'b0000000; 4'b1001:qout_6<=7'b0010000; default:qout_6<=7'b1111111; endcase end else if(set_alarm==1) begin case(alarm_hourH) 4'b0000:qout_6<=7'b1000000; 4'b0001:qout_6<=7'b1111001; 4'b0010:qout_6<=7'b0100100; 4'b0011:qout_6<=7'b0110000; 4'b0100:qout_6<=7'b0011001; 4'b0101:qout_6<=7'b0010010; 4'b0110:qout_6<=7'b0000010; 4'b0111:qout_6<=7'b1111000; 4'b1000:qout_6<=7'b0000000; 4'b1001:qout_6<=7'b0010000; default:qout_6<=7'b1111111; endcase end end //-------------时分秒的数码管显示结束----------- --- endmodule
全部源码和报告见:
链接:https://pan.baidu.com/s/1pyKZoCeeQNJL2FxeajPnjg?pwd=6z4k
提取码:6z4k
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。