第15章:计数器
本章对应原书第 359~362 页,属于「第四篇 数字电路篇」。
本章在讲什么?(先看这个)
上一章的寄存器只会"存",这一章让它学会"数"。计数器 = 寄存器 + 加法器,是逻辑设计中使用频率最高的时序电路之一,可以实现对脉冲个数的计数,进而实现测量、计数、分频和控制等功能——后面章节的流水灯、呼吸灯、频率计,全都建立在计数器之上。本章先用 3 个 D 触发器搭出一个 3 位计数器,从电路层面理解"每来一个时钟上升沿就加 1"到底是怎么实现的,再用 Verilog 设计一个 3bit 计数器并仿真验证。
核心概念拆解
计数器简介
计数是最简单最基本的运算,计数器就是实现这种运算的逻辑电路。下面以一个 3 位计数器为例,从电路结构入手学习。

这个电路由 3 个 D 触发器(F1、F2、F3)、2 个异或门(G1、G2)和 1 个与门(G3)组成(触发器的复位信号图中没有画出)。上电复位后 Q0、Q1、Q2 的初始状态是 000。
上电后各触发器的输入分析:
- F1:输入 D 由"Q 非"反馈回来。Q 当前是 0,"Q 非"就是 1,反馈给 F1 的输入 D,所以 F1 的输入是 1;
- F2:输入是 Q0 和 Q1 经过异或门后的结果。Q0=0、Q1=0,异或门"两个输入相同输出 0、不同输出 1",所以 F2 的输入是 0;
- F3:输入是 Q0 与 Q1 经过与门后的输出,再与 Q2 异或。Q0 和 Q1 相与还是 0,这个 0 再与 Q2=0 异或,输出 0,所以 F3 的输入是 0。
来了第一个时钟上升沿:3 个 D 触发器同时触发,输入值被锁存,下一刻输出变为 Q2Q1Q0 = 001。如果把 Q2 当作二进制最高位、Q0 当作最低位,计数器输出的就是二进制 001,即十进制 1。
第二个上升沿到来时输出 010(十进制 2)……每多来一个时钟上升沿,计数器就加 1。计到第 8 个脉冲时,电路状态由 111 变回 000,完成一个循环周期——所以该电路也称为模 8 同步加法计数器。
所谓"同步",是指电路中 3 个边沿型 D 触发器共用同一个时钟脉冲 CLK,时钟上升沿到来时同时触发(而不是前一级的输出当后一级的时钟)。
3bit 计数器的真值表:
| 时钟CLK | 当前 Q2 Q1 Q0 | 下一刻 Q2 Q1 Q0 |
|---|---|---|
| 上升沿 | 0 0 0 | 0 0 1 |
| 上升沿 | 0 0 1 | 0 1 0 |
| 上升沿 | 0 1 0 | 0 1 1 |
| 上升沿 | 0 1 1 | 1 0 0 |
| 上升沿 | 1 0 0 | 1 0 1 |
| 上升沿 | 1 0 1 | 1 1 0 |
| 上升沿 | 1 1 0 | 1 1 1 |
| 上升沿 | 1 1 1 | 0 0 0 |
从真值表可以看出:每个时钟上升沿之后计数器值加 1,计满 8 个时钟周期后从 111 溢出回 000,循环往复。
实验任务
使用 Verilog 语言设计一个 3bit 的计数器电路。
程序设计——模块设计
模块命名为 counter。因为是时序逻辑,输入肯定有时钟和复位;输出为一个 3bit 的计数值。

| 信号名 | 方向 | 端口说明 |
|---|---|---|
| sys_clk | 输入 | 系统时钟,50MHz |
| sys_rst_n | 输入 | 系统复位按键,低电平有效 |
| cnt | 输出 | 3bit的计数器 |
绘制波形图
每当一个时钟周期到来,寄存器就累加 1:

cnt 循环从 0 计数到 7,每当计到最大值时,就从零开始重新累加。
编写代码
module counter(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位
output reg [2:0] cnt //输出计数器
);
//*****************************************************
//** main code
//*****************************************************
//描述计数器
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 3'd0;
else
cnt <= cnt + 3'd1;
end
endmodule
代码解读:
- 第 5 行定义了一个 3bit 的 cnt;
always@(posedge sys_clk or negedge sys_rst_n):时钟上升沿触发、低电平复位信号异步复位;- 系统复位信号 sys_rst_n 为低电平时,cnt 清零;否则每个时钟上升沿执行
cnt <= cnt + 3'd1循环累加。
对比上一章的电路图你会发现:电路版的 3 位计数器需要 3 个触发器加 3 个门电路,而 Verilog 一句 cnt <= cnt + 1 就描述完了——综合工具会自动生成对应的加法器和寄存器。这就是硬件描述语言的威力。
仿真验证——编写 TB 文件
计数器模块的输入只有时钟和复位,TB 只需对这两个信号激励:
`timescale 1ns / 1ns //仿真单位/仿真精度
module tb_counter();
//reg define
reg sys_clk ; //系统时钟
reg sys_rst_n; //系统复位
//wire define
wire [2:0] cnt ; //cnt计数器
//信号初始化
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#201
sys_rst_n <= 1'b1;
end
//生成50MHz系统时钟
always #10 sys_clk <= ~sys_clk;
//例化计数器模块
counter counter_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.cnt (cnt )
);
endmodule
TB 结构与上一章相同:初始复位 201ns 后释放;always #10 sys_clk <= ~sys_clk; 生成 50MHz 时钟。
仿真验证
打开 Modelsim 运行 10us:

仿真波形与我们绘制的波形图一致:cnt 从 0 逐次累加到 7,然后归零重新开始,说明 3bit 计数器设计正确。
本章总结
本章讲解了后续学习和设计中最常用的计数器:从 3 个 D 触发器组成的模 8 同步计数器电路理解了计数原理,再掌握了用 Verilog 描述计数器的标准写法——"复位清零 + 时钟沿累加"。计数器是后面所有延时、分频、状态机的基础,务必通过后续章节的训练熟练掌握。
图片对照
本章原文图片包括 3 位计数器电路图、模块框图、波形图和仿真波形,均已插入对应小节。
初学者容易踩的坑
- 计数器位宽不够:3bit 最大只能计到 7,如果计数范围更大要相应加宽位宽,否则会提前溢出(有时候溢出正是我们要的,比如本章的模 8 循环,但要"有意为之"而不是"意外发生")。
- 忘加复位:计数器没有复位的话,上电后 cnt 的初值不确定(可能是 x 态),上板和仿真都会出问题。
- 复位分支漏写或写错极性:
if(!sys_rst_n)对应低电平有效的复位,写成if(sys_rst_n)逻辑就反了。 - 误以为计数器是"异步"的:本章电路是同步计数器,所有触发器共用一个时钟;还有一种行波(异步)计数器把前级输出当后级时钟,各级延迟会累积,FPGA 设计中一般用同步计数器。
- 混淆"计数值"和"计时值":cnt 是对时钟脉冲的计数,不是秒数;要变成时间得知道时钟周期(见拓展训练的换算)。
小结 & 下一步
本章掌握了计数器的电路原理和 Verilog 标准写法:复位清零、时钟沿循环累加。建议动手拓展:把现有例程稍作修改,设计一个"1 秒钟"的计数器。提示:板载晶振是 50MHz,一个时钟周期为 1/(50×10^6) s = 0.000_000_02s,所以 1 秒钟需要 50_000_000 个脉冲周期——计数器需要在 50MHz 时钟下计 50_000_000 次,想想计数器位宽要多少位才装得下。