计数器是时序电路中比较简单的模型,但对于Verilog语法的训练和复习起到不可或缺的作用。计数器设计中有16进制计数器,10进制计数器(BCD码计数器),60进制计数器,30进制计数器等不同的模型,这些模型在数字电路中学习中已经有较为详细的介绍。本节内容主要是利用Verilog描述语言对这些计数器进行描述,从而学会Verilog基础语法在时序电路设计中的应用。
1.二进制计数器
二进制计数器是指将reg类型的向量按照整体使用,向量的权值都是2的幂次方。对于4位二进制组成的向量可以表达的范围0-9 a,b,c,d,e,f 总共16个数值,因此也把二进制计数器称为16进制计数器。
例1:
module count16
(
input rst,
input clk,
output reg [15:0] count_hex
);
always@(posedge clk or posedge rst)
if(rst)
begin // 异步复位
count_hex <= 0;
end
else //同步释放
count_hex <= count_hex + 1;
endmodule
在时钟的使用及触发器模型 中我们介绍了在时钟边沿的驱动下,reg型变量可以通过触发器模型来分析。对于D触发器,有D端,Q端,时钟端。如图1:

图 1
(1) 触发器的时钟端
时钟端比较好理解,因为在always过程中有posedge 关键字,所以我们知道所有寄存器(count_hex[15:0])对应触发器的clk端全部连在一起组成同步时序电路。
(2) 触发器的D端和Q端
对于count_hex<=count_hex+1; 这样的赋值语句,左边和右边都有寄存器变量,到底哪一边是触发器的D端,哪一边是Q端?
我们可以这样分析,在赋值语句的左边count_hex是被赋值变量,而触发器的Q端是输出端,如果左端是Q,就会给输出端赋值,这不合理,因此赋值语句左边的count_hex只能是触发器的D端。赋值语句右边希望能在稳定输出的前提下评估组合逻辑运算,将运算的结果赋给D端,从这一点看赋值语句右边count_hex只能是Q端,这和数字电路中现态次态分析是一致的。
因次结论如下:对于类似的count_hex<=count_hex+1;赋值语句,赋值语句左边对应的触发器的D端,赋值语句右边对应的是触发器的Q端的现态。
另外,如果在仿真波形上或signal_tap,ILA等调试工具上看到的寄存器的值(触发器模型),都是触发器的Q端的值。
因此例1的程序可以用图2-1的模型来理解。在Vivado中经过analysis & elaboration 后的模型如图2-2所示。
图2-1

图2-2
2.十进制计数器(BCD码计数器)
十进制计数器就是逢9进1,当多位十进制计数器级联时,处在不同位置的寄存器向量的权值不同。如例2中的向量a,b,c,d, a代表个位,b为十位,c为百位,d为千位。因此如果b要实现加1,a需要产生进位,即当a==9时,如果a再出现加1的情况下,a将回归到0,而b将实现加1操作。b到c,c到d的计数及进位操作与a到b的情况类似。与2进制计数器不同,进位按照2进制的规律(16进制的规律)可以自动实现,因此各级的回归于进位需要if或case等分支判断语句帮助实现。
例2:
十进制计数器程序
module BCD_counter
(
input clk,
input rst,
output reg [3:0] a,
output reg [3:0] b,
output reg [3:0] c,
output reg [3:0] d
);
always@(posedge clk or posedge rst)
if(rst)
begin
a <= 0;
b <= 0;
c <= 0;
d <= 0;
end
else
begin
if(a == 9)
begin
a <= 0;
if(b == 9)
begin
b <= 0;
if(c == 9)
begin
c <= 0;
if(d == 9) d <= 0;
else d <= d + 1;
end
else c <= c + 1;
end
else
b <= b + 1;
end
else
a <= a + 1;
end
endmodule
testbench 仿真程序 tb.v
`timescale 1ns/1ps
module tb
(
);
parameter PERIOD = 10;
reg clk;
initial
begin
clk = 1'b0;
# (PERIOD/2)
forever
# (PERIOD/2)
clk=~clk;
end
wire [3:0] a, b, c, d;
reg rst;
initial
begin
rst = 1;
#20
rst = 0;
end
BCD_counter BCD_counter_inst
(
.rst (rst),
.clk (clk),
.a (a),
.b (b),
.c (c),
.d (d)
);
endmodule
仿真波形如下:

图3

图4
在10进制计数器中,if(a==9)的语句中,a的模型如果为D触发器,那么这里a是D端还是Q端?由于D端接收的是组合逻辑,在整个周期内D的是不稳定的。而Q值在上升沿后到下一个上升沿之前都是稳定的,因此这里的a指的是a的Q端。
3. 60进制计数器
上面解决了D触发器模型的问题后,60进制计数器就显得比较简单了。
例4:
module counter60
(
input clk,
input rst,
output reg [3:0] a,
output reg [3:0] b
);
always@(posedge clk or posedge rst)
if(rst)
begin
a <= 0;
b <= 0;
end
else
begin
if(a == 9)
begin
a <= 0;
if(b == 5) b <= 0;
else b <= b + 1;
end
else
a <= a + 1;
end
endmodule
4. 12进制计数器
12进制计数器由两个部分构成:
- 在a, b 组合中不满足十进制的12,即不满足(a==2)&&(b==1)的情况下,a,b组合仍然满足10进制的计数规律。
- 当满足(a==2)&&(b==1)条件时,高位清零,低位置1。即b<=0; a<=1;
12进制的Verilog程序如下:
module counter12
(
input clk,
input rst,
output reg [3:0] a,
output reg [3:0] b
);
always@(posedge clk or posedge rst)
if(rst)
begin
a <= 1;
b <= 0;
end
else
begin
if((a == 2)&&(b == 1))
begin
a <= 1;
b <= 0;
end
else if(a == 9)
begin
a <= 0;
b <= b + 1;
end
else
a <= a + 1;
end
endmodule

图5
5. 24进制计数器
24进制计数器与12进制计数器非常相似,可以直接12进制的方式进行分析和设计,24进制的Verilog程序如下:
module counter24
(
input clk,
input rst,
output reg [3:0] a,
output reg [3:0] b
);
always@(posedge clk or posedge rst)
if(rst)
begin
a <= 0;
b <= 0;
end
else
begin
if((a == 3)&&(b == 2))
begin
a <= 0;
b <= 0;
end
else if(a == 9)
begin
a <= 0;
b <= b + 1;
end
else
a <= a + 1;
end
endmodule

图6
其它的计数器如30进制,31进制,28进制,29进制,7进制等,这里就不一一介绍了,可以参照复杂数字钟设计部分内容。
思考题:试分析以下语句有何区别?(a为4位 reg类型)
a <= a + 1;
a <= a + 1’b1;
a <= a + 4’b1;
a <= a + 4’h1;
a <= a + 32’b1;
a <= a + ‘b1;
a <= a + ‘h1;
