第13章:锁存器
本章对应原书第 343~345 页,属于「第四篇 数字电路篇」。
本章在讲什么?(先看这个)
前几章的组合逻辑电路有个共同点:没有记忆——输出只取决于当前输入,跟过去发生了什么完全无关。从本章开始进入时序逻辑电路的世界,第一个主角是锁存器。这一章有两层意义:一是理解锁存器(Latch)这种能"记住"数据的电路;二是——更重要——学会识别和避免 Verilog 代码中意外产生的锁存器。很多初学者的代码综合出了 latch 自己却不知道,它隐蔽性极强,会带来时序问题,所以本章是数字电路设计里非常关键的一课。
核心概念拆解
锁存器简介
锁存器(Latch)是一种对脉冲和电平敏感的存储单元电路,可以在特定输入脉冲或电平作用下改变状态。"锁存",就是把信号暂存以维持某种电平状态。
锁存器是电平触发的存储单元:数据存储的动作取决于输入时钟(或使能)信号的电平值。只有当锁存器处于使能状态时,输出才会随数据输入发生变化;一旦锁存信号起作用,数据就被"锁住",输出不再随输入变化。
锁存器不同于触发器:不锁存数据时,输出端信号随输入信号变化,就像信号通过一个缓存器;锁存时数据被固定住。因此锁存器也称为透明锁存器——不锁存时输出对输入是"透明"的。
锁存器的分类包括 RS 锁存器、门控 RS 锁存器和 D 锁存器,其中 D 锁存器最常用。
D 锁存器的工作原理
D 锁存器是一种具有数据存储功能的时序逻辑电路,核心功能是:在使能信号有效期间,把输入数据 D 实时传递到输出端 Q;使能信号无效时,保持最后时刻的数据状态。这种电平触发的存储机制区别于边沿触发的 D 触发器,典型应用包括数据暂存、异步信号同步等。

从电路图看,D 锁存器由两部分组成: - G1、G2 两个与非门组成 RS 锁存器; - G3、G4 两个与非门组成控制电路,C 为控制信号,控制 G3 和 G4 的激励输入。
工作过程分析: - 当控制信号 C=0 时:无论 D 输入什么,RD 和 SD 同时为 1。根据与非门组成的 RS 锁存器逻辑,RD=SD=1 时输出 Q 维持原状态不变——数据被"锁住"; - 当控制信号 C=1 时:如果 D=0,SD=1、RD=0,锁存结果为 0 状态;如果 D=1,RD=1、SD=0,锁存结果为 1 状态。也就是说此时锁存器的状态完全由 D 决定,D 是什么,输出就是什么。
由此可以推出 D 锁存器的特性表(Qn 是触发前的当前状态,Qn+1 是触发后的状态):
| C | D | Qn | Qn+1 |
|---|---|---|---|
| 0 | X | Qn | Qn(保持) |
| 1 | 0 | X | 0 |
| 1 | 1 | X | 1 |
可以看出:当 C 为 1 时,Qn+1 与 D 完全一样(D=0 则 Qn+1=0,D=1 则 Qn+1=1)。

波形图看得更直观:D 是输入信号,C 是控制信号,Q 是输出。当 C 为高电平时,Q 跟随 D 变化(图中虚线框内 Q 的波形等于 D 的波形);当 C 从高变低的瞬间,D 当时的状态被锁存——虚线处 D 为低,Q 就锁存为低;最后两条虚线处 D 为高,Q 锁存为高。
锁存器的危害
这一小节是本章的重点。在绝大多数设计中我们都要避免产生锁存器,它会使设计出现时序问题,且隐蔽性很强,新人很难查出问题源头。
锁存器最大的设计风险在于其电平敏感特性,会导致两个关键问题: 1. 输入信号的任何毛刺都可能直接传播到输出端; 2. 静态时序分析工具难以准确评估其保持时间要求。
这些特性会对后续电路级联造成严重的信号完整性风险。因此数字电路设计的基本原则是:凡是能够使用触发器实现的场合,就应当避免采用锁存器结构。

从示意图可以看出:锁存器没有时钟信号,只有输入数据 data、使能信号 enable 和输出数据 q。没有时钟,就意味着无法对这种器件进行时序分析——这在时序电路里非常危险,可能引起时序不满足,导致电路功能实现有问题。
实验任务
使用 Verilog 代码设计一个锁存器电路——没错,这次是故意"造"出锁存器,让你亲眼看到它是怎么被综合出来的。
程序设计
先记住结论:代码里出现 latch 的两个原因——在组合逻辑中,if 或者 case 语句描述不完整:if 缺少 else 分支,case 缺少 default 分支。解决办法就是 if 必须带 else 分支,case 必须带 default 分支。
注意一个细节:只有不带时钟的 always 块(组合逻辑)中 if 或 case 不完整才会产生 latch;带时钟的时序逻辑里不存在这个问题。
情况一:if 缺少 else
module latch(
input a, //输入a
input b, //输入b
output reg y //输出y
);
// 当a = 1 时,将b的值赋值给y
always@(*) begin
if(a == 1)
y = b;
end
endmodule
用 Vivado 打开 RTL 视图(RTL ANALYSIS):

视图中显示了 RTL_LATCH——这就是被综合出来的锁存器。原因也好理解:a=0 时代码没告诉电路 y 应该是多少,电路只能"记住"y 之前的值,这就是锁存行为。
把 else 补全:
module latch(
input a, //输入a
input b, //输入b
output reg y //输出y
);
always@(*) begin
if(a == 1) // 当a = 1 时,将b的值赋值给y
y = b;
else
y = 0; // 其余条件下y的值都为0
end
endmodule

补上 else 后,RTL 视图变成一个 mux 选择电路,latch 消失了。
情况二:case 缺少 default
module latch(
input a, //输入a
input b, //输入b
output reg y //输出y
);
// 当a = 0 时,将b的值赋值给y
always@(*) begin
case (a)
0: y = b;
endcase
end
endmodule

RTL 视图中又出现了 RTL_LATCH。
补上 default:
module latch(
input a, //输入a
input b, //输入b
output reg y //输出y
);
always@(*) begin
case (a)
0: y = b; // 当a = 0 时,将b的值赋值给y
default : y = 0; // 其余条件下y的值都为0
endcase
end
endmodule

补上 default 后,电路又变回了 mux 选择电路,没有 latch。
本章总结
本章的核心要点只有一条,但极其重要:组合逻辑代码里出现 latch 的原因是 if 或 case 语句描述不完整(if 缺 else、case 缺 default),导致综合器推断出锁存器。解决办法:if 必须带 else 分支,case 必须带 default 分支。同时要理解锁存器的危害根源——电平敏感、无时钟、无法做时序分析、毛刺直通输出。
图片对照
本章图片已按讲解位置分散插入各小节,包括 D 锁存器电路图、波形图、锁存器示意图以及四组对比用的 RTL 视图。
初学者容易踩的坑
- 以为锁存器"有用"而故意保留:latch 看似能省寄存器,但无法进行时序分析、毛刺直通,工程上应一律避免——能用触发器实现的场合就不用锁存器。
- 只在组合逻辑里防 latch,却不知道范围:只有不带时钟的 always 块中 if/case 不完整才产生 latch,带时钟的时序 always 块没有这个问题。
- if 链条中间断了:不只是完全不写 else,哪怕是 if-else if 链的最后没有 else 兜底,同样会产生 latch。
- case 只列举了部分条件:枚举型信号只写了几种取值、没写 default,是新手最常见的 latch 来源。
- 看不到问题就不检查:latch 的危害(时序违例、毛刺)往往在后期或上板时才暴露,所以要养成写完组合逻辑就去 RTL 视图确认没有 RTL_LATCH 的习惯。
小结 & 下一步
本章认识了 D 锁存器的电平触发原理,更重要的是掌握了"意外 latch"的成因与对策:组合逻辑中 if 补 else、case 补 default。建议动手做两件事:一是写一个带时钟的 always 语句(时序逻辑电路),综合后观察电路结构,体会它与组合逻辑的区别;二是翻出自己以前写的代码,检查有没有不完整的 if/case。