概述
Verilog HDLBits--Count Clock
这篇文章主要讲述HDLBits的基础练习中,有关 Verilog 时钟计数器的问题。计数器的常用功能之一就是数字时钟,而在二进制存储的FPGA中,十进制的显示问题往往成为每个使用者都需要解决的问题。
笔者在使用常规 one-digit BCD编码计数器将各位(如分钟的个位、十位等)分开单独显示 方法后,固执地设计了一种十进制计数直接转BCD显示的方法。后者在本题中应用时虽有些小瑕疵,但不失为该类问题的一种规范化思路,尤其对十进制转BCD编码类问题,具有重要的参考意义。为不让文章显得冗长复杂,本文将只对第一种方法进行介绍,第二种方法将在下一篇详细分析。
0. HDLBits简介
有关该Verilog在线学习网站的安利,见本专栏第一篇文章。
1. Problem105: Count Clock
要求: 设计一个满足12小时计时的时钟系列计数器(包括am/pm指示)。该计数器由一个fast-running clk驱动,每当时钟需要增加计时时,ena信号则会发生脉冲信号(如:每秒一次)。 reset信号复位时钟到12:00 AM。pm信号为0时指示AM,为1时指示PM。hh、mm、ss都为两个BCD digits(8 bits),分别表示小时(01-12)、分钟(00-59)、秒(00-59)。reset 比 enable信号优先级更高,即使未使能,也要执行复位操作。 下面的时序图展示了时钟从11:59:59 AM 到12:00:00 PM的信号变化,以及同步复位和使能操作结果。
hint: 注意11:59:59 PM 在下一个时钟周期应为12:00:00 AM ;12:59:59 PM 在下一时钟周期应为 01:00:00 PM 。没有00:00:00这种形式。
1.1 正确答案
1 module top_module(
2 input clk,
3 input reset,
4 input ena,
5 output pm,
6 output [7:0] hh,
7 output [7:0] mm,
8 output [7:0] ss);
9 wire ena_ss_tens, ena_mm_ones, ena_mm_tens, ena_hh; //enable signals for every single bit
10 wire ss_zero;
11 //ss ones bit
12 m_s_one_bit_cont ss_ones(.clk(clk), .reset(reset), .ena(ena), .zero(4'b1001), .one(ss[3:0]));
13 //ss tens bit
14 assign ena_ss_tens =(ss[3:0]==4'd9)? 1'b1 : 1'b0;
15 m_s_one_bit_cont ss_tens(.clk(clk), .reset(reset), .ena(ena & ena_ss_tens), .zero(4'b0101), .one(ss[7:4]));
16 //mm ones bit
17 assign ss_zero = ss[7:4]==4'd5 & ss[3:0]==4'd9;
18 assign ena_mm_ones = (ss_zero) ? 1'b1: 1'b0;
19 m_s_one_bit_cont mm_ones(.clk(clk), .reset(reset), .ena(ena & ena_mm_ones), .zero(4'b1001), .one(mm[3:0]));
20 //mm tens bit
21 assign ena_mm_tens =(mm[3:0]==4'd9 & ss_zero)? 1'b1 : 1'b0;
22 m_s_one_bit_cont mm_tens(.clk(clk), .reset(reset), .ena(ena & ena_mm_tens), .zero(4'b0101), .one(mm[7:4]));
23 //hh two bits
24 assign ena_hh = (mm[7:4]==4'd5 & mm[3:0]==4'd9 & ss_zero) ? 1'b1 : 1'b0;
25 h_two_bit_cont hh_2bits(.clk(clk), .reset(reset), .ena(ena & ena_hh), .hh(hh));
26 //pm
27 always@(posedge clk)
28 if (reset)
29 pm = 1'b0;
30 else if(hh=={4'd1, 4'd1} & mm=={4'd5, 4'd9} & ss=={4'd5, 4'd9} )
31 pm = ~pm;
32 else
33 pm = pm;
34 endmodule
35 //hour counter: 1-12
36 module h_two_bit_cont (
37 input clk,
38 reset,
39 ena,
40 output[7:0] hh);
41 reg[3:0] one_dec;// decimal number
42 always@(posedge clk) begin
43 if (reset)begin
44 hh = {4'b0001, 4'b0010};
45 one_dec = 4'b1100;
46 end
47 else if(~ena)begin
48 hh = hh;
49 one_dec = one_dec;
50 end
51 else if (one_dec == 4'b1100) begin
52 hh = {4'b0000, 4'b0001};
53 one_dec = 4'b0001;
54 end
55 else
56 one_dec = one_dec + 1'b1;
57 if(one_dec >= 4'b1010)
58 hh = {4'b0001, one_dec - 4'd10};
59 else
60 hh = {4'b0000, one_dec};
61 end
62 endmodule
63 //minute and second one-digit counter
64 module m_s_one_bit_cont (
65 input clk,
66 reset,
67 ena,
68 input[3:0] zero,//return 'zero' bit
69 output[3:0] one);
70 always@(posedge clk)
71 if (reset)
72 one = 4'b0000;
73 else if(~ena)
74 one = one;
75 else if (one == zero)
76 one = 4'b0000;
77 else
78 one = one + 1'b1;
79 endmodule
题目解析:
正如题目名称:计数时钟,实质就是用于时钟显示的计数器设计。由于FPGA内部变量都是二进制存储,因此像数字时钟、秒表等的十进制数字显示问题成为设计者值得思考的一类问题。目前,人们普遍解决该类问题的方法是利用one-digit BCD编码计数器巧妙地将各个位上的数字分割开来单独显示。我想这也是HDLBits作者在前两题:第103-104题就已经传达给读者的设计思想。
而深受软件编程思路影响的童鞋,这里可能会有疑问:既然是将时钟的各个位分割开来,为什么不直接使用“ / ”、“ % ”语句来的更简单些呢?认真了解过Verilog语法的伙伴们都清楚,在所有Verilog的语法中,大约只有30%可用于综合电路的设计,其余70%只能用于testbench编写。而“ / ”、“ % ”也归于70%那一类。
代码分析:
1. 设计one-digit BCD模块(代码第64-79行-- module m_s_one_bit_cont),通过调用该模块,实现“分”、“秒”计数器个位、十位(不涉及百位)数字的单独定义。
2. 考虑到“时”计数器本质上属十二进制,不能与“分”、“秒”计数器同用一种BCD模块,因此,代码第36-62行对“时”计数器(module h_two_bit_cont)进行了单独设计。
3. top_module中,实例化下层模块,设计各模块enable信号电路及pm 指示电路。
设计重点:
- “分”、“秒”时钟计数器 one-digit BCD模块设计;
- “时”计数器(十二进制)显示模块设计; - 各个位计数器使能信号设计 ;
注:
代码第12行,时钟的秒计数器,个位使能信号ena直接设计为top_module的ena输入信号,依靠时钟上升沿的驱动按需计数,以推动整个时钟计数器的运行。
- pm指示信号的电路设计;
1.2 电路图
以上为1.1中代码综合后生成的电路,为清楚表达设计思路、描述设计重点,同时为方便读者查阅,图中略去了部分信号的连接情况,只留下顶层模块和实例化模块间的信号传输关系。若小伙伴想仔细了解该电路的RTL Schematic,可自行复制代码到 ISE 或 Vivado 中查看。
1.3 设计详解
在这一部分,笔者将结合电路图,详细说明该电路的设计流程和设计重点(思路清晰的小伙伴可自行对照代码和综合后电路的对应关系)。
1.3.1 时 /分 /秒计数器及下层模块设计
- ss_tens 、ss_ones 两模块分别为秒计数器的十位、个位数字计数器,通过实例化 m_s_one_bit_cont module 实现。
其中m_s_one_bit_cont module 为 one-digit BCD 计数器,与常规设计不同的是,此模块增加了zero[3:0] 输入端口,即归零比较值。该端口的设计是为了兼容秒/分计数器个位和十位的不同计数范围(个位0-9;十位0-5),以减少自定义模块个数。
- mm_tens 、mm_ones 则为分计数器的十位、个位数字计数器,原理同秒计数器。
- hh_2bits 模块则是为十二进制的时计数器单独设计的 two-digit BCD 计数器。不同于分/秒计数器的各位分开定义,时计数器的计数范围要求1--12,归零比较值为12,即在下一个ena信号到来时,时计数器复位为1。
注:
相比 one-digit BCD 设计,two-digit BCD 需要额外的十进制计数器进行时钟计数,然后直接将该十进制数字转换成BCD编码形式。该类十进制计数直接转BCD显示的思想同样适用于分、秒计数器设计,笔者特地私下做了一番验证,将在下一篇文章中详细说明(附在本篇中显得过于冗长)。
1.3.2 各ena及pm指示信号电路设计
- ss_ones_ena = top_module_ena //秒-个位ena信号
- ss_tens_ena =top_module_ena & (ss_ones==4'd9) //秒-十位ena信号
- mm_ones_ena = top_module ena & (ss=={4'd5 , 4'd9})
- mm_tens_ena = top_module ena & (mm_ones == 4'd9) & (ss=={4'd5 , 4'd9})
- hh_ena = top_module ena & (mm == {4'd5 , 4'd9}) & (ss == {4'd5 , 4'd9})
注:
各模块ena信号需同时考虑到 top_module 的 ena 信号,这是容易被忽略的一点。
- pm指示信号,则根据题目hint部分的提示即可完成设计——11:59:59 PM 在下一个时钟周期应为12:00:00 AM。每个 11:59:59 时刻,pm进行一次翻转。一开始会多想,总觉得没这么简单。但只要记住一天一半的时间是AM,另一半的时间是PM,而时钟每计到 11:59:59,则为12小时(半天),因此,直接翻转即可。
最后给两点小建议:
- 每个信号,既相互联系,又互相独立。想清楚信号自身的逻辑功能,避免多种信号关联设计,将大大提高代码的写作效率,同时也能避免不必要的bug。
- 模块化设计、模块化设计、模块化设计……真香!
最后
以上就是谨慎果汁为你收集整理的b 计数器位选信号 verilog_?HDLBits--(Verilog在线学习)--"105: Count Clock"的全部内容,希望文章能够帮你解决b 计数器位选信号 verilog_?HDLBits--(Verilog在线学习)--"105: Count Clock"所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复