今天在刷HDLBits的时候遇到了这个比较难的关于12小时时钟设计的问题,故开个笔记记录一下自己的思路。
首先看下这个题目,要求我们设置一个计时12小时并可以表征上半天、下半天的时钟。这种计时问题在前面也遇到过,本质是一个分频器的问题,或者说,是一个改装计数器的问题。 也就是说将自定义不同进制的计数器,并将其串联,根据上下进位的原则来赋予各个计数器不同的权重。而就这道题而言,因为小时的范围是1到12,分钟和秒的范围是0到59,那么问题就转化成了把题目所给的两位BCD(hh, ss, mm)改装成12进制和59进制的计数器的问题。
当然,让我们先回到问题本身。从顶层模块看,一共有三个输入,四个输出;其中 clk为时钟,而 reset和ena都是受时钟控制的同步信号。
对于这两个信号而言,从题目给的波形图示例可以看出,reset是高电平有效的置零信号,且具有最高优先级;一旦reset=1,hh会被置为12,mm与ss都会被置为0。而ena是高电平有效的使能信号,当reset不为1的时候,ena控制ss能否递增;如果ena变为0,则ss保持现有值不变。
然后我们再来关注下各个输出量之间的关系。根据题目信息,不会出现hh=0的情况,也就是说,这个计时器表示的时间范围是从12:00:00到1:00:00在递增到11:59:59,下一刻回到12:00:00并且pm翻转。而另外几个量跟前面的一千赫兹分频器原理类似,当ss到达59时下一ss归0且mm进1,ss和mm均达59时下一刻二者归零且hh进1。也就是说,我们可以用两个容量为60的计数器来分别表示输出mm和ss,再用一个容量为12的计数器来表示hh。三个计数器成串联关系,从低到高依次进位,体现为某个计数器的使能信号来源于上一个计数器输出为计满值的情况,而对于最底层的ss而言,它的使能信号就是ena。
按照层次化的设计方法,在顶层模块中我们只需要对pm的情况做阐释,即每当reset信号生效时,pm为0;当reset=0时,pm在每次时间达到11:59:59时翻转;这个用一个比较简单的if-else语句就可以写出来;同时我们在顶层模块中需要实例化三个计数器。
module top_module(
input clk,
input reset,
input ena,
output pm,
output [7:0] hh,
output [7:0] mm,
output [7:0] ss);
count60 u1 (.clk(clk), .reset(reset), .en(ena), .cnt_out(ss));
count60 u2 (.clk(clk), .reset(reset), .en(ena&&ss==8'h59), .cnt_out(mm));
count12 u3 (.clk(clk), .reset(reset), .en(ena&&(ss==8'h59)&&(mm==8'h59)), .cnt_out(hh));
always @(posedge clk)
begin
if (reset)
pm <= 0;
else if (ss==8'h59&&mm==8'h59&&hh==8'h11)
pm <= ~pm;
else;
end
endmodule
这个地方需要注意的是每个计数器的使能信号;是ena和下层模块进位的相与;假设在下层模块将要进位时,时钟上升沿传入信号,但是ena恰为0,这时候如果不把ena考虑进上层模块的使能信号中的话,会造成仅有ss受ena控制不递增而上层模块递增的错误情况。
然后就是底层模块部分,先写mm和ss的60进制计数器。首先考虑的仍然是reset,而后是en,需要考虑进位关系。这里比较关键的是,进位关系实际上要考虑两点;首先当然是作为计数器计满置0的情况,另外一个是因为hh和mm都是两位BCD码,也即一共8位二进制码,每四位表示一个十进制数;所以这个时候需要从十进制的角度来考虑进位;对于hh和mm,需考虑当低四位表示的数字等于9时就存在低四位的置零和高四位进1;而当低四位表示的数字小于9时,高四位不变而低四位进1,这个用if-else语句也可实现。
module count60 (input clk, input reset, input en, output reg [7:0]cnt_out);
always @(posedge clk)
begin
if (reset)
cnt_out <= 0;
else if (en)
if (cnt_out == 8'h59)
cnt_out <= 0;
else if (cnt_out[3:0]==9)
begin
cnt_out[3:0] <= 0;
cnt_out[7:4] <= cnt_out[7:4]+1;
end
else cnt_out[3:0] <= cnt_out[3:0]+1;
end
endmodule
当然,这个地方不要忘了在if-else语句中若有多个变量的赋值需要加上begin和end。
然后进行12进制计数器的编写,跟上面的60进制基本相同,不同的一方面是当reset产生时回到12,还有当计数达到12时下一次置1。也就是说计满回归的值和置零信号产生后回归的值不同。同时也需要注意BCD码的进位情况。
module count12 (input clk, input reset, input en, output reg [7:0]cnt_out);
always @(posedge clk)
begin
if (reset)
cnt_out <= 8'h12;
else if (en)
if (cnt_out == 8'h12)
cnt_out <= 1;
else if (cnt_out[3:0]==9)
begin
cnt_out[3:0] <= 0;
cnt_out[7:4] <= cnt_out[7:4] +1;
end
else
cnt_out[3:0] <= cnt_out[3:0]+1;
end
endmodule
以上就是12小时计数器的题目分析,这个题综合性比较强,算是HDLBits里面比较难的题,融合了层次化的设计方法、串联进位计数器的设计、BCD计数的设置,值得反复学习。