锁存器与D触发器的Verilog语言描述与电路特点
1 锁存器 Latch
在 Verilog 中,锁存器(Latch) 是一种电平敏感的存储元件,它在使能信号有效时(通常是高电平)将输入数据传递到输出,使能信号无效时保持输出不变。在组合逻辑 always 块中,意外产生锁存器是一个常见的设计问题,因为它可能导致不可预测的硬件行为、功耗增加和时序问题。
1.1 锁存器结构
电路结构如下图所示,原理详见数字电路课程

1.2 锁存器产生的原因
当一个 always 块描述组合逻辑(通常使用 always @(*))时,如果在某些输入条件下,块内没有为某个输出变量赋值,综合工具就会推断出一个锁存器来保持该变量的旧值。也就是说,变量的值在某些条件下是“不确定”或“保持不变”,这正是锁存器的行为。
- if 语句缺少 else 分支
分析:当 enable 为 0 时,data_out 的值没有被明确指定。为了在 enable 为 0 时保持 data_out 的值不变,综合工具会生成一个锁存器。
always @(*) begin
if (enable) data_out = data_in;
end - case 语句没有覆盖所有可能的值
分析:sel 的值可能为 2'b11,但是没有处理这种情况,因此综合工具会生成一个锁存器来保持 y 的值不变。
always @(*) begin // 组合逻辑
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c; // 没有处理 sel == 2'b11 的情况!
endcase
end - 原信号赋值或者判断
分析:a 的值作为条件判断
always @(*) begin
if (a & b) a = 1'b1; // a -> Latch
else a = 1'b0;
end - 敏感信号列表不完整 如果组合逻辑中 always@() 块内敏感信号列表不完整,该触发的时候没有触发,那么相关寄存器还是会保存之前的输出结果。因而会生成锁存器
1.3 锁存器的避免
- 为所有 if 分支提供 else
确保在 if 语句的每个分支(包括 if 和 else)中都为所有相关的输出变量赋值。
always @(*) begin
if (enable) data_out = data_in;
else data_out = 1'b0;
end - 在 case 语句中使用 default
为 case 语句提供 default 分支,并确保 default 分支中为所有相关的输出变量赋值。
always @(*) begin
case (sel)
2'b00: y = a;
2'b01: y = b;
2'b10: y = c;
default: y = 1'b0; // 添加默认分支
endcase
end - 不要将赋值信号放在赋值源头,或条件判断中
reg a_r;
always @(posedge clk) a_r <= a;
always @(*) begin
if (a_r & b) a = 1'b1;
else a = 1'b0;
end - 敏感信号列表建议多用 always@(*)
2 D触发器
2.1 D触发器的结构
D触发器(D Flip-Flop)是一种电平敏感的存储元件,它可以将输入数据传递到输出,并保持输出不变,直到下一次输入数据改变。D触发器的结构如下图所示,原理详见数字电路课程

2.2 D触发器和锁存器的区别
- 触发方式:D触发器是边沿触发的,锁存器是电平触发的。
- 内部结构:D触发器通常由两个锁存器(主从结构)级联而成。第一个锁存器在时钟边沿前一个时刻(例如下降沿)锁存 D 值,第二个锁存器在时钟边沿(例如上升沿)接收第一个锁存器的输出;这种结构确保了只在时钟边沿时刻对 D 采样。锁存器结构相对简单,通常由门电路(如 NAND、NOR 门)构成,直接响应使能信号电平。
2.3 D触发器的Verilog语言描述
module D_FF(D, clk, rst, Q);
input D, clk, rst;
output reg Q;
always @(posedge clk or posedge rst) begin
if (rst) Q <= 1'b0;
else Q <= D;
end
endmodule