HDLBits答案(10)_D触发器、同步与异步复位、脉冲边沿检测
D触发器、同步与异步复位、脉冲边沿检测
D触发器
基础知识介绍
如果把数字电路比作一个人的身体,那么触发器(Flip-Flop)就是这个身体的”记忆细胞”。它让数字电路有了记忆能力,这也是时序逻辑电路区别于组合逻辑电路的关键!
什么是D触发器?
D触发器(D Flip-Flop)是最常用的触发器之一。这里的”D”代表”Data”(数据)或”Delay”(延迟),因为它的功能就是在时钟边沿到来时,将D输入端的数据保存到输出端Q。
生活中的类比:
想象一个拍照的场景:
- D输入端就像是相机镜头前的场景
- 时钟信号就像是你按下快门的那一刻
- Q输出端就像是拍出来的照片
只有在你按下快门(时钟边沿)的瞬间,镜头前的场景(D输入)才会被保存下来变成照片(Q输出)。在其他时间,无论场景怎么变化,照片都不会改变!
核心概念定义:
- 边沿触发:触发器只在时钟信号的上升沿(0→1跳变)或下降沿(1→0跳变)时才会更新状态
- 状态保持:在非时钟边沿时刻,无论输入怎么变化,输出都保持不变
- 次态(Next State):时钟边沿之后触发器的新状态,记为Q’
- 现态(Current State):时钟边沿之前触发器的旧状态,记为Q
为什么边沿触发很重要?
在时钟电平有效的期间(比如时钟为高电平时),D输入的数据可能会发生多次变化。如果使用电平触发,输出也会跟着变来变去。而边沿触发只在时钟跳变的瞬间采样一次,这样大大提高了电路的稳定性和抗干扰能力!
边沿D触发器的功能表:
| D | CLK | Q | QN | 说明 |
|---|---|---|---|---|
| 0 | 时钟上升沿 | 0 | 1 | 置0 |
| 1 | 时钟上升沿 | 1 | 0 | 置1 |
| × | 0 | last Q | last QN | 保持(时钟为低) |
| × | 1 | last Q | last QN | 保持(时钟为高) |
特性方程:1
Q' = D (在时钟上升沿时)
这个方程很简单,意思就是:时钟边沿到来后,Q变成D的值。

同步复位与异步复位
基础知识介绍
在实际的数字系统中,我们经常需要让电路回到一个已知的初始状态,这就需要复位信号(Reset)。复位可以让触发器回到预设的状态(通常是0),这样系统才能从一个确定的状态开始工作。
复位主要有两种类型:同步复位和异步复位。
生活中的类比:
想象一个电梯的控制系统:
- 同步复位:就像是电梯只有在到达某一层楼停下时(时钟边沿),才会响应”回到一楼”的按钮
- 异步复位:就像是电梯的紧急停止按钮,无论电梯在哪里、在做什么,只要按下这个按钮,电梯立刻停止
同步复位
什么是同步复位?
“同步”的意思是复位信号需要和时钟信号同步才能生效。换句话说,复位信号只有在时钟边沿到来时,才会被电路识别和处理。
Verilog代码模板:
1 | always @(posedge clk) begin |
同步复位的优缺点:
✅ 优点:
- 可以滤除复位信号上的毛刺(因为只在时钟边沿采样)
- 有利于静态时序分析(STA)
- 不会出现异步复位释放时的亚稳态问题
❌ 缺点:
- 需要时钟信号才能复位(如果时钟不工作,就无法复位)
- 复位信号必须持续足够长的时间,保证能被时钟采样到
- 会消耗更多的组合逻辑资源
异步复位
什么是异步复位?
“异步”的意思是复位信号不受时钟信号的控制。无论时钟边沿是否到来,只要复位信号有效,电路就会立即复位!
Verilog代码模板:
1 | always @(posedge clk or negedge rst_n) begin |
注意:在敏感列表中,我们同时列出了时钟边沿和复位边沿!
异步复位的优缺点:
✅ 优点:
- 不需要时钟信号就能立即复位(即使时钟不工作也能复位)
- 响应速度快,立即生效
- 通常不需要额外的组合逻辑
❌ 缺点:
- 复位信号上的毛刺可能导致误复位
- 异步复位释放时(复位信号从有效变无效),如果刚好在时钟边沿附近,可能导致亚稳态
如何选择同步复位还是异步复位?
这是一个经典的问题,答案是:看具体需求!
在通信IC设计中,笔者的经验是:
- 如果系统对复位的响应速度要求很高,或者需要在没有时钟的情况下也能复位 → 选择异步复位
- 如果系统对可靠性要求很高,或者复位信号可能有毛刺 → 选择同步复位
入门者避坑指南
时序逻辑设计中有很多容易踩坑的地方,下面我们总结几个最常见的错误。
错误1:在时序逻辑中使用阻塞赋值
错误表现:1
2
3
4
5// ❌ 错误示例:在时序always块中使用阻塞赋值=
always @(posedge clk) begin
a = b;
c = a; // 这里的a是上面刚更新的值!
end
错误原因分析:
在Verilog中,有两种赋值方式:
- 阻塞赋值(Blocking Assignment)
=:立即执行,会阻塞后续语句的执行 - 非阻塞赋值(Non-Blocking Assignment)
<=:并行执行,不会阻塞后续语句
记住这个铁律:
- 组合逻辑用阻塞赋值
= - 时序逻辑用非阻塞赋值
<=
正确做法对比:1
2
3
4
5// ✅ 正确示例:在时序always块中使用非阻塞赋值<=
always @(posedge clk) begin
a <= b;
c <= a; // 这里的a是时钟边沿之前的值!
end
调试技巧:
- 如果不确定该用哪种赋值,先想想这是组合逻辑还是时序逻辑
- 写代码时,把”时序逻辑用<=,组合逻辑用=”当成口头禅
错误2:异步复位的敏感列表写错
错误表现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// ❌ 错误示例1:异步复位没有写在敏感列表中
always @(posedge clk) begin
if (rst) begin // rst是异步复位,但没有写在敏感列表里
q <= 1'b0;
end
else begin
q <= d;
end
end
// ❌ 错误示例2:敏感列表中写了rst,但没有写边沿
always @(posedge clk or rst) begin // 应该是posedge rst或negedge rst
if (rst) begin
q <= 1'b0;
end
else begin
q <= d;
end
end
错误原因分析:
对于异步复位,敏感列表中必须包含复位信号的边沿(posedge或negedge),否则综合器可能无法正确识别这是一个异步复位。
正确做法对比:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// ✅ 正确示例:高电平有效的异步复位
always @(posedge clk or posedge rst) begin
if (rst) begin
q <= 1'b0;
end
else begin
q <= d;
end
end
// ✅ 正确示例:低电平有效的异步复位
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q <= 1'b0;
end
else begin
q <= d;
end
end
错误3:在异步复位的if-else中放组合逻辑
错误表现:1
2
3
4
5
6
7
8
9
10// ❌ 错误示例:在异步复位的else分支中使用了组合逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q <= 1'b0;
end
else begin
// 这里不能用assign!
assign q = a & b;
end
end
错误原因分析:
在时序always块中,我们应该只对寄存器进行赋值,不要在里面写assign语句或复杂的组合逻辑。组合逻辑应该放在单独的always块中,或者用连续赋值语句。
正确做法对比:1
2
3
4
5
6
7
8
9
10
11// ✅ 正确示例:把组合逻辑和时序逻辑分开
wire q_next = a & b; // 组合逻辑用连续赋值
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q <= 1'b0;
end
else begin
q <= q_next; // 时序逻辑只负责更新寄存器
end
end
错误4:锁存器(Latch)的意外生成
错误表现:1
2
3
4
5
6
7// ❌ 错误示例:组合逻辑always块中没有写完整的if-else
always @(*) begin
if (ena) begin
q = d;
end
// 缺少else分支!当ena=0时,q保持原值,生成锁存器
end
错误原因分析:
在组合逻辑always块中,如果某个信号在某些条件下没有被赋值,综合器就会认为这个信号需要”保持原值”,从而生成一个锁存器(Latch)。锁存器是电平敏感的存储元件,在设计中我们通常要避免意外生成锁存器。
正确做法对比:
如果确实需要锁存器(比如题目7明确要求),可以这样写:1
2
3
4
5
6
7// ✅ 正确示例:有意设计锁存器
always @(*) begin
if (ena) begin
q = d;
end
// 没有else,这时候确实需要锁存器
end
如果不需要锁存器,一定要确保所有信号在所有条件下都有赋值:1
2
3
4
5
6
7
8
9// ✅ 正确示例:避免生成锁存器
always @(*) begin
if (ena) begin
q = d;
end
else begin
q = 1'b0; // 给q一个默认值
end
end
错误5:在同一个always块中混合使用同步和异步复位
错误表现:1
2
3
4
5
6
7
8
9
10
11
12// ❌ 错误示例:想同时用同步和异步复位,结果逻辑混乱
always @(posedge clk or posedge rst1) begin
if (rst1) begin // 异步复位
q <= 1'b0;
end
else if (rst2) begin // 同步复位
q <= 1'b0;
end
else begin
q <= d;
end
end
错误原因分析:
虽然从语法上讲上面的代码是可以综合的,但这会让复位策略变得混乱。在一个设计中,建议统一使用一种复位方式,要么全同步,要么全异步,不要混用。
正确做法对比:
选择一种复位方式,坚持使用:1
2
3
4
5
6
7
8
9// ✅ 正确示例:统一使用异步复位
always @(posedge clk or posedge rst) begin
if (rst) begin
q <= 1'b0;
end
else begin
q <= d;
end
end
D触发器巩固练习
题目1:基本D触发器
题目描述:创建一个D触发器。
Solution1:
1 | module top_module ( |
小贴士:
记住!时序逻辑的always块使用非阻塞赋值 <=。
题目2:8位D触发器
题目描述:创建8个D触发器,每个都由时钟的上升沿触发。
Solution2:
1 | module top_module ( |
注意:
在Verilog中,向量赋值会自动对应到每一位,所以上面的代码等价于实例化了8个独立的D触发器。
题目3:带同步复位的8位D触发器
题目描述:创建8个D触发器与高电平有效同步复位。所有D触发器由clk的上升沿触发。
Solution3:
1 | module top_module ( |
题目4:带同步复位的下降沿触发D触发器
题目描述:创建8个D触发器与高电平有效同步复位。触发器必须被重置为0x34,而不是0。所有D触发器应由clk的下降沿触发。
Solution4:
1 | module top_module ( |
题目5:带异步复位的8位D触发器
题目描述:创建8个D触发器与高电平有效异步复位。所有D触发器应由clk的上升沿触发。
Solution5:
1 | module top_module ( |
提示:
使用posedge areset时,在if条件中应该使用if(areset)来判断,而不是if(!areset),否则逻辑会相反!
题目6:带字节使能的16位D触发器
题目描述:创建一个16位D触发器,有时我们仅需要修改部分触发器中的值。字节使能信号控制当前时钟周期中16个寄存器中哪个字节需被修改。byteena[1]控制高字节d[15:8],而byteena[0]控制低字节d[7:0]。
resetn是一个同步低复位信号。
所有的D触发器由时钟的上升沿触发。
Solution6:
1 | module top_module ( |
题目7:D锁存器
题目描述:实现下面的电路(锁存器)

Solution7:
1 | module top_module ( |
注意:
锁存器和触发器是不同的!锁存器是电平敏感的,而触发器是边沿敏感的。在大多数现代设计中,我们推荐使用触发器,但了解锁存器也很重要。
题目8:带异步复位的D触发器
题目描述:实现下面的电路

Solution8:
1 | module top_module ( |
题目9:带同步复位的D触发器
题目描述:实现下面的电路

Solution9:
1 | module top_module ( |
题目10:简单的有限状态机
题目描述:实现下面的电路

Solution10:
1 | module top_module ( |
题目11:带多路选择器的触发器子模块
题目描述:考虑下图的电路:

假设要为这个电路实现分层的Verilog代码,使用一个子模块的三个实例,该子模块中有一个触发器和多路选择器。为这个子模块编写一个名为top_module的Verilog模块(包含一个触发器和多路选择器)。
Solution11:
1 | module top_module ( |
题目12:移位寄存器的一个阶段
题目描述:考虑下图的n-bit移位寄存器

为该电路的一个阶段编写一个Verilog模块顶层模块,包括触发器和多路选择器。
Solution12:
1 | module top_module ( |
题目13:三个触发器的有限状态机
题目描述:给定如图所示的有限状态机电路,假设D触发器在机器开始之前被初始重置为零。

Solution13:
1 | module top_module ( |
题目14:用D触发器实现JK触发器
题目描述:JK触发器有下面的真值表。只使用D触发器和逻辑门实现JK触发器。注:Qold是时钟上升沿前的D触发器的输出。
| J | K | Q | 说明 |
|---|---|---|---|
| 0 | 0 | Qold | 保持 |
| 0 | 1 | 0 | 置0 |
| 1 | 0 | 1 | 置1 |
| 1 | 1 | ~Qold | 翻转 |
Solution14:
1 | module top_module ( |
脉冲边沿检测
基础知识介绍
在数字系统中,我们经常需要检测信号的变化——比如信号什么时候从0变成1,或者从1变成0。这就是边沿检测(Edge Detection)。
什么是边沿?
边沿就是信号电平发生跳变的瞬间:
- 上升沿(Rising Edge):信号从0变成1(0→1)
- 下降沿(Falling Edge):信号从1变成0(1→0)
生活中的类比:
想象你在看一条波浪线:
- 上升沿就像波浪从谷底升到波峰
- 下降沿就像波浪从波峰降到谷底
边沿检测就是要找出这些”转折点”!
为什么需要边沿检测?
在通信系统中,边沿检测非常重要:
- 检测数据帧的开始和结束
- 同步时钟和数据
- 识别控制信号的变化
- 捕捉短暂的脉冲信号
边沿检测的基本原理
边沿检测的核心思想很简单:比较信号的当前值和前一个值。
- 如果之前是0,现在是1 → 检测到上升沿
- 如果之前是1,现在是0 → 检测到下降沿
- 如果之前和现在一样 → 没有边沿
如何实现?
我们需要用一个D触发器来保存信号的前一个值,然后把当前值和前一个值进行比较:
1 | // 保存前一个值 |
这个逻辑是不是很直观?
边沿检测电路的时序图
让我们用一个简单的时序图来理解边沿检测:
1 | 时钟: ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ |
从这个时序图可以清楚地看到:
rise_edge只在in从0变1后的那个时钟周期为1fall_edge只在in从1变0后的那个时钟周期为1
带滤波的边沿检测
在实际的硬件环境中,信号上可能会有毛刺(Glitch)——这是一种短暂的、不希望出现的电平跳变。如果我们直接用简单的边沿检测,可能会把这些毛刺误判为真实的信号跳变。
如何解决?
我们可以用多个级联的触发器来对信号进行采样,然后判断信号是否真的发生了稳定的跳变。
这是一个实际工程中常用的例子:
1 | module edge_detector ( |
这个电路的原理是:只有当信号稳定地从1变成0(连续两个周期都是1,然后连续两个周期都是0),我们才认为是真正的下降沿。这样可以有效滤除毛刺!
边沿检测巩固练习
题目1:上升沿检测
题目描述:对于8位向量中的每一位,检测输入信号何时从一个时钟周期的0变化到下一个时钟周期的1(正边缘检测)。输出位应该在发生0到1转换后的周期输出1,如下示意图所示:

Solution1:
1 | module top_module ( |
题目2:任意边沿检测
题目描述:对于8位向量中的每一位,检测输入信号何时从一个时钟周期变化到下一个时钟周期(检测脉冲边沿)。输出位应该在发生转换后的周期输出1。

Solution2:
1 | module top_module ( |
题目3:下降沿捕获(带保持)
题目描述:对于32位向量中的每一位,当输入信号从一个时钟周期的1变化到下一个时钟周期的0时捕获(捕捉下降沿),”捕获”意味着输出将保持1直到被reset(同步重置)。
每个输出位的行为就像一个SR触发器:输出位应该在发生1到0转换后的周期被设置(为1)。当复位为高时,输出位应该在正时钟边缘复位(为0)。如果上述两个事件同时发生,则reset具有优先级。
在下面示例波形的最后4个周期中,”reset”事件比”set”事件早一个周期发生,因此这里不存在冲突。

Solution3(方法1:用case语句):
1 | module top_module ( |
Solution3(方法2:更简洁的写法):
1 | module top_module ( |
题目4:时钟双沿触发器
题目描述:实现一个在时钟上升沿和下降沿都能采样的触发器。
注意:不能直接写 always @(posedge clk or negedge clk),这在Verilog中是不允许的。
Solution4(方法1:使用两个触发器和多路选择器):
1 | module top_module ( |
Solution4(方法2:使用异或电路):
1 | module top_module ( |
小贴士:
我们知道一个数异或另一个数再异或同一个数,会得到原来的数。这就是最简单的加密与解密原理!
第二种方法相较于第一种方法,不需要用clk信号进行选择,这样可以避免在输出多路选择器处产生毛刺。在实际工程中,推荐使用第二种方法。
总结
在这篇文章中,我们深入学习了时序逻辑设计的核心内容:
- D触发器:数字电路的”记忆细胞”,在时钟边沿保存数据
- 同步复位与异步复位:两种复位方式各有优缺点,需要根据具体需求选择
- 入门者避坑指南:总结了5个最常见的错误,包括阻塞/非阻塞赋值、敏感列表、锁存器生成等
- D触发器巩固练习:通过14道练习题,掌握各种触发器的设计方法
- 脉冲边沿检测:检测信号的上升沿、下降沿,以及带滤波的边沿检测
- 边沿检测巩固练习:通过4道练习题,掌握边沿检测电路的设计
从通信IC设计的角度来看,触发器和边沿检测是非常基础但又极其重要的内容。在通信芯片中,我们需要处理大量的时序逻辑,从简单的数据缓存到复杂的协议状态机,都离不开触发器。而边沿检测则在数据恢复、时钟同步、信号识别等场景中有着广泛的应用。
希望这篇文章能帮助你打好时序逻辑设计的基础!




