本题是一道经典的12小时制时钟设计,私以为是继双边沿触发器之外,时序逻辑前半篇幅中较为精妙的一题,故将题目中关键要素与解题思路记录如下。欢迎大家批评指正!
1. 12小时制:A.M./P.M.的转换时机
12小时制作为本题的背景,是形成解题思路的第一个门槛。其核心要素有二:其一是12小时制表示的时间范围,这关系到时钟在关键节点处能否正确进位;其二是作为12小时制标志的A.M.与P.M.的转换时机,这关系到时钟能否正确输出当前时间下对应的标志。
- 首先要明确的是,12小时制是围绕着“午时之前/之后”这个概念设计的时间系统。一天24小时由中午(或午夜,这里采用中午进行区分)前12个小时与中午后12个小时均分,并由A.M.(拉丁文ante meridiem表示中午之前,注意这里不是英文的after…)和P.M.(post meridiem,表示中午之后)标志区分是午前还是午后。无论是哪一半时间,小时数都在1~12之间进行变化,故“00:mm:ss”这样的时间表示形式并不属于12小时制。
- 通过想象一个12小时制时钟运行一天来确认它表示的时间范围:当时间来到午夜11:59:59 P.M.,秒针前进1s,时间变为12:00:00A.M.,进入属于中午之前的12个小时,此时时间标志从P.M.切换为A.M.,接着时间快进到12:59:59A.M.,秒针前进1s,时间变为01:00:00A.M.,之后重复这一步骤,直到11:59:59A.M.,秒针前进1s,时间变为12:00:00P.M.,进入属于中午之后的12个小时,此时时间标志从A.M.切换为P.M.,接着时间快进到12:59:59P.M.,秒针前进1s,时间变为01:00:00P.M.,之后重复这一步骤,直到时间来到午夜11:59:59 P.M.……
- 从上面时钟运行的描述中我们可以得知,当时间穿越正午与午夜时刻时,当前时间所属的时间半区将会发生变化。从数字变化情况上来讲,也就是hh从11变为12时,时间标志就要发翻转。同时可知,如果不为时间后缀标志,在12:00:00时将无法区分是正午还是午夜(
tips:可以让计算机抬头看天),是12小时制的一个弊端。
以上说明解释了题目所给时序图中,在时间hh:mm:ss为11:59:59之后,下一个时钟周期上升沿,hh由11进位到12时,时间标志位pm发生了翻转。由此在电路设计中,我们将清楚地知道这一步骤的实现应放在时序逻辑的哪一位置上。
2. BCD数字:事实上的伪十六进制
hh, mm, and ss are two BCD (Binary-Coded Decimal) digits each for hours (01-12), minutes (00-59), and seconds (00-59).
根据题目描述,对于时钟“hh:mm:ss”的每一位,都是一个BCD数字,即对于时、分、秒的描述,均是由两个BCD数字实现的。明白了BCD数字的进位机制,时钟各位之间的进位逻辑也就不难搞懂了。BCD(Binary-Coded Decimal),直译为用二进制编码的十进制,其本质上是一种伪十六进制。
- 在数字电路中,一位16进制数如0xc(HDL语言描述为4’hc)与1100b(HDL语言描述为4’b1100)是等价的,可称前者由后者表示。对于BCD数来说,由于其也是用四位2进制数来表示一位10进制数,这就使十进制数的二进制描述与十六进制数的二进制描述形成了子集关系——一位BCD数字二进制表示的0-9,是十六进制数字二进制表示的0-f的一部分。当BCD数字9再加1时,不是本位变为a,而是向高位进位,本位清零,相当于是一个限定特定位置进位的十六进制数字。
- 明确了BCD数字与二进制、十六进制数字的对应关系后,我们可以用三组8位的寄存器分别表示时钟的时、分和秒位,每组寄存器中以4位表示一个数字。如
reg [7:0] s
用来表示秒位,其中reg [3:0] s
是秒位两位数字中的低位,而reg [7:4] s
则是两位数字中的高位,以此类推,分别用HDL描述出时分秒的三组8位BCD寄存器。各位进位的限制也依时钟的要求具体设定,如分位与秒位的高位不能超过BCD数字“5”(也就是4’b0101)。
3. 进位逻辑:先低位后高位,进位后先复位
通过对以上12小时制中时间标志转换和BCD数字进位的理解,我们可以很自然地得到时钟的进位逻辑:对于时钟的8个BCD数字,由低到高依次进行累加与进位,当低位满足进位要求后,向高位进位并将低位清零。这里低位向高位进位分为两种情况。
- 秒位(对于分位、时位也是如此)的中低位BCD数字对高位BCD数字的进位:当秒位低位BCD数字不满足小于4’h9的条件时,向高位进位的同时低位清零;
- 秒位对分位(分位对时位也是如此)的进位:当秒位两个BCD数字不满足小于8’h59的条件时,向分位进位的同时秒位清零;
- 对于时位要注意从11变为12时(也就是11:59:59变为12:00:00时),时间所属的半区会发生变化,要在这时对描述时间标志的寄存器进行翻转;同时要注意12的下一个时间1(时间在1-12之间循环)。所以时位部分的逻辑要将11与12这两个事件节点单拎出来描述逻辑,在1-11之间则按照时钟要求下BCD数字进位限制进行进位即可。
4. 语言描述:一个时钟周期前进一位
最后将本题完整的代码贴在下方,可以看出在每一个时钟的上升沿,非复位的情况下,代码所描述的电路按照上述规则进行数字的累加、清零与进位。
module top_module(
input clk,
input reset,
input ena,
output pm,
output [7:0] hh,
output [7:0] mm,
output [7:0] ss);
reg p;
reg [7:0] h;
reg [7:0] m;
reg [7:0] s;
always @(posedge clk)
if(reset) begin
p <= 1'b0;
h <= 8'h12;
m <= 8'h00;
s <= 8'h00;
end
else
if(ena)
if(s < 8'h59)
if(s[3:0] < 4'h9)
s[3:0] <= s[3:0] + 1'h1;
else begin
s[3:0] <= 4'h0;
s[7:4] <= s[7:4] + 4'h1;
end
else begin
s <= 8'h00;
if(m < 8'h59)
if(m[3:0] < 4'h9)
m[3:0] <= m[3:0] + 4'h1;
else begin
m[3:0] <= 4'h0;
m[7:4] <= m[7:4] + 4'h1;
end
else begin
m <= 8'h00;
if(h == 8'h11)
p <= !p;
if(h < 8'h12)
if(h[3:0] < 4'h9)
h[3:0] <= h[3:0] + 4'h1;
else begin
h[3:0] <= 4'h0;
h[7:4] <= h[7:4] + 4'h1;
end
else
h <= 8'h01;
end
end
assign pm = p;
assign hh = h;
assign mm = m;
assign ss = s;
endmodule
5.总结
通过对本题关键要素的分析可以逐步推导出一个不使用子模块实现的12小时制时钟HDL语言描述。子模块的设计思想固然先入为主且较为直观,但是如果能够善于分析具体问题,总结利用本题的设计思想,有可能设计出代码形式上逻辑更为清晰直观、电路实现上资源更为精简的电路,不仅有利于电路设计、更能节省硬件成本。如果能做到这一点,对各类题目抽丝剥茧地分析与记录的目的也就达到了。