前言
本系列整理数字系统设计的相关知识体系架构,为了方便后续自己查阅与求职准备。对于FPGA和ASIC设计中,避免使用Latch(锁存器)一直是个绕不开的话题,本文结合网上的文章,并根据示例介绍如何在实际设计中避免Latch。
锁存器:组合电路与时序电路的桥梁
在网上看到一个帖子说了这样一个说辞,我觉得很不错,分享给大家:锁存器不就是组合逻辑电路与时序逻辑电路的桥梁么?
其实仔细想想也是,之前功利性的学习根本没有仔细考虑为什么拿到数电基础的书后,目录设计总是按照组合逻辑、锁存器、时序逻辑去安排章节。
现在思考后我觉得很有道理(听我开始瞎掰)最初数字电路的组合逻辑解决了很多问题,但是却没有很好的解决如何将这个状态做存储,有些设计情况下不得不将一些信号的状态存储,所以就使用逻辑门构建了锁存器的概念,设计的灵活性得到进一步提升,但是同时又存在了另外一个问题,锁存器就像一个阀门,但是由于这个阀门打开后内部是随时可以变化的,这也给如何检验设计的正确性带来了难度,所以急需一个器件或者设计去解决这个问题,进而有了时序逻辑电路中触发器的概念,触发器也就随着 “需求和发展” 出现。
因此,组合逻辑、锁存器、时序逻辑,这样安排更像说明了数字电路的发展和迭代的历史,虽然锁存器的一般内容比较少,但不可否认,锁存器就是组合逻辑电路与时序逻辑电路的桥梁。
锁存器概念
锁存器( latch)是电平触发的存储单元,数据存储的状态取决于输入时钟(或者使能)信号的电平值,尽当锁存器处于使能状态时,输出才会随着数据输入发生变化。
锁存器不同于触发器,锁存器在不锁存数据时,输出端的信号随输入信号变化,就像信号通过一个缓存器一样;一旦锁存信号起锁存作用,则数据被锁住,输入信号不起作用。因此锁存器也称为透明锁存器, 指的是不锁存时输出对输入是透明的。
锁存器的分类包括 RS 锁存器、门控 RS 锁存器和 D 锁存器, 此处介绍下 D 锁存器。
D 锁存器就是能够将输入的单路数据 D 存入到锁存器中的电路,D锁存器的电路图如下图所示。
从 D 锁存器的电路图中可以看出,该电路主要是由两个部分组成,第一个部分是由 G1、 G2两个与非门组成的 RS 锁存器,第二个部分是由 G3、 G4 两个与非门组成的控制电路。 C 为控制信号,用来控制 G3 和 G4 的激励输入。
下面来分析下 D 锁存器的工作原理:
当控制信号 C=0 时,根据与非门的逻辑定律,无论 D输入什么信号, RD 和 SD 信号都会输出为1。 RD 和 SD 都同时等于 1 的话,锁存器的输出端 Q 将维持原状态不变。
当控制端 C=1 时,如果此时 D=0,SD 就等于1, RD 就等于 0,根据 RS 锁存器的逻辑规律,电路的结果就为 0 状态;如果 D =1,那么 RD 就等于 1,SD 也就等于 0,锁存器的结果就为 1 状态,也就是说,此时锁存器的状态是由激励输入端 D 来确定的,D 等于什么,锁存器的状态就是什么。
根据上面的描述,可以推出 D 锁存器的特性表, Qn 是指触发器当前逻辑状态也即触发前的状态, Qn+1 是指触发后的状态。
C | D | Qn | Qn+1 |
---|---|---|---|
0 | X | 0 | 0 |
0 | X | 1 | 1 |
1 | 0 | X | 0 |
1 | 1 | X | 1 |
通过这个表格,可以看出,当 C 为 1 时, D 的状态和 Qn+1 的状态完全一样,当 D=0 时, Qn+1=0,当 D=1 时, Qn+1=1。进一步画出 D 锁存器的波形图。
从 D 锁存器的波形图可以看出, D 是锁存器的输入信号, C 是锁存器的控制信号,Q 是锁存器的输出信号。当控制信号 C 为高电平时,输出信号 Q 将跟随输入信号 D 的变化而变化。
为什么要避免锁存器?(Latch的危害)
前面虽然提到锁存器是组合逻辑电路与时序电路的桥梁,但是其灵活性很难保证在设计中进行精准分析。在实际使用中,Latch 多用于门控时钟(clock gating)的控制,在绝大多数设计中我们要避免产生锁存器。它会让设计的时序出问题,并且它的隐蔽性很强, 新人很难查出问题。
- 毛刺敏感:锁存器最大的危害在于不能过滤毛刺和影响工具进行时序分析。这对于下一级电路是极其危险的。这也就是为什么寄存器不用电平触发而选择使用边沿触发的原因。所以,只要能用触发器的地方,就不用锁存器。
- 不能异步复位: 由于其能够储存上次状态的原因,上电后Latch处于不定态。
- 占用更多资源: 对于绝大多数当前主流的FPGA架构资源中,基本是不包含Latch,这也就意味着需要更多的资源去实现搭建Latch。在早些版本的某些FPGA内部包含了Latch。
- 使时序分析变得复杂: 锁存器没有时钟信号,只有数据输入和使能以及输出 q 端,没有时钟信号也就说明没有办法对这种器件进行时序分析, 这个在时序电路里面是非常危险的行为,因为可能引起时序不满足导致电路功能实现有问题。
- 额外的延时: 在ASIC设计中,锁存器也会带来额外的延时和DFT,并不利于提高系统的工作频率。
结构完整就能避免Latch?
大多数的人首先想到的是由于verilog代码中在if-else结构中缺少else或case结构中缺少default所导致,因此也往往在设计中要求if-else结构和case结构要写完整。但结构完整就真能完全避免Latch的产生吗?
module demo(
input wire [1:0] en,
output reg[3:0] latchtest
);
always @(*) begin
if (en ==1) begin
latchtest = 'd6;
end
else if (en ==2) begin
latchtest = 'd3;
end
else begin
latchtest = latchtest;
end
end
endmodule
图上示例这种情况,虽然逻辑完整但也会生成latch,其原因是针对该逻辑信号进行了保持,构成了逻辑环路。
同样下图中的设计也会产生锁存器,原因是虽然逻辑完备但是没有针对单一信号进行完备逻辑的处理,在部分逻辑信号进行了保持,所以逻辑仍生成了latch。
//demo1
reg latchtest1,latchtest2;
always @(*)begin
if(enable)
latchtest1 = in;
else
latchtest2 = in;
end
下面的设计是我认为比较隐晦的latch。乍一看,分支完备,case语句也有default,但在EDA工具进行分析后仍出现latch
module demo(
input wire [1:0] en,
output reg[3:0] latchtest
);
always @(*) begin
case (en)
0:latchtest[0] = 'd1;
1:latchtest[1] = 'd1;
2:latchtest[2] = 'd1;
3:latchtest[3] = 'd1;
default:latchtest = 'd1;
endcase
end
分析其根本原因仍然是,针对相关的分支条件该信号的部分bit位进行了保持。
此外,三目运算符在组合逻辑设计时进行逻辑保持,同样会引入latch。
锁存器产生的根本原因
生成latch的例子很多,从例子来看不论是单个信号或者是一个信号某些位,只要在组合逻辑设计时他存在逻辑保持都有可能生成latch,但说锁存器产生的根本原因,即为代码设计时的逻辑:需要在组合逻辑中避免保持的操作。当组合逻辑需要保持时,就会综合出锁存器。
避免Latch的解决方法
整理常见出现组合逻辑保持的情况,也即能得到避免生成latch的相关方法。
- if else分支结构不完整
- 组合逻辑中case结构不完整
- 组合逻辑设计时使用三目运算符存在逻辑保持
- 使用原信号进行赋值或者使用原信号进行条件判断
- 敏感信号列表不完整
所以避免latch的方法就是,要对以上或者其他未列举的组合逻辑中存在逻辑保持的情况进行改写和规避,即可避免头疼的latch生成。简单来说就是:优化代码逻辑,避免组合逻辑的逻辑保持。 当遇到逻辑保持的情况时,要对存在逻辑保持的分支进行补全,或者使用赋初值的方法进行覆盖逻辑保持时的情况。
// 补全条件分支结构
always @(*) begin
if (en)
dout = in ;
else
dout = 'd0 ;
end
//赋初值
always @(*) begin
dout = 'd0 ;
if (en)
dout = data ; //如果en有效,改写q的值,否则q会保持为0
end
必要的逻辑保持怎么处理?
可能对于有些情况无法完全避免掉组合逻辑设计的逻辑保持,对于这种情况,可以在这个组合逻辑路径中插入一级寄存器,寄存器的输出替代原来逻辑保持的部分,示例如下:
wire [DATA_WIDTH-1:0] accsum_in = (ab_sum_valid)?sum_in:dout;
always @(posedge clk or negedge rst_n) begin
if(rst_n == 'd0)begin
dout <= 'd0;
end
else if(adder1_outvld == 1)begin
dout <= sum_in ;
end
end
Reference
- 【FPGA】Verilog中锁存器(Latch)原理、危害及避免
- 菜鸟教程 - 6.5 Verilog 避免 Latch
- Verilog专题(三)如何在组合逻辑中避免latch的产生
- Latch的产生和避免