【FPGA基础】Latch基础
“本文主要转载自https://zhuanlan.zhihu.com/p/93133247,高屋建瓴。其中关于如何会产生latch的部分,转载自https://zhuanlan.zhihu.com/p/34408492?from_voters_page=true。”
在FPGA的设计中,避免使用锁存器是几乎所有FPGA工程师的共识,Xilinx和Altera也在手册中提示大家要慎用锁存器,除非你明确知道你确实需要一个latch来解决问题。要想理解latch是什么,就需要分别理解锁存器、触发器和寄存器的概念。
一、锁存器、触发器和寄存器的原理和区别
锁存器、触发器和寄存器它们的英文分别为:Latch、Flip-Flop、Register。本文不会详细介绍锁存器和触发器内部的电路,只对输入输出的波形分析。
1、锁存器 Latch
锁存器用来存储状态信息,就是将这个状态一直保持。锁存器对脉冲的电平敏感,也就是电平触发,在有效的电平下,锁存器处于使能状态,输出随着输入发生变化,此时它不锁存信号,就像一个缓冲器一样;在锁存器没有使能时,则数据被锁住,输入信号不起作用,此时输出一直为锁存的状态信息。我们常见的锁存器有SR锁存器、D锁存器、JK锁存器等。
我们以最简单的D锁存器为例来说明锁存器的工作过程,D锁存器有3个接口,也可以认为是4个,因为输出的两个和只是单纯的反向关系。
其中D为输入信号,当E为高时,输出Q即为输入的D;当E为低时,Q保持E为高时的最后一次状态,也就是锁存过程。
为什么锁存器不好?
从上面的图中可以看出,锁存器很容易在信号上产生毛刺;而且也没有时钟信号,不容易进行静态时序分析。正是因为这两个原因,我们在FPGA设计时,尽量不用锁存器。
2、触发器
触发器(Flip-Flop,简写为 FF),也叫双稳态门,又称双稳态触发器。在中国台湾及中国香港译作“正反器”,是一种具有两种稳态的用于储存的组件,可记录二进制数字信号“1”和“0”。D触发器是我们平时写程序中用到最多的element。除了D触发器,常见的触发器还有T触发器、SR触发器、JK触发器等。触发器对脉冲边沿敏感,其状态只在时钟脉冲的上升沿或下降沿的瞬间改变。
我们以D触发器为例来说明触发器的工作过程,D触发器接口如下:
触发器只在时钟边沿时起作用,所以哪怕输入的信号中有毛刺,输出还是比较干净的。
触发器会有个CE的端口,如果CE为低,输出保持不变,这样也算是锁存,但不会用到锁存器,只要不用锁存器,就不会有上述锁存器带来的问题。
3、锁存器和触发器的对比
避免使用D锁存器,尽量使用D触发器
// D锁存器 module test_latch( input a , input b ,</span><span style="color: #0000ff;">output</span><span style="color: #000000;"> y
);
always @(a or b) begin
if (a == 1'b1)
y = b;
endendmodule
// D触发器 module test_d( input clk , input a , input b ,</span><span style="color: #0000ff;">output</span><span style="color: #000000;"> y
);
always @(posedge clk) begin
if (a == 1'b1)
y = b;
endendmodule
在D触发器中,信号a被综合成D触发器的使能端,只有在时钟上沿到来且a为高时,b信号的值才能传递给a;只要在时钟上升沿期间信号b是稳定,即使在其他时候b还有毛刺,经过D触发器后数据是稳定的,毛刺被滤除。
4、寄存器
寄存器是用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果,它被广泛的用于各类数字系统和计算机中。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储1位二进制数,所以由N个锁存器或触发器可以构成N位寄存器。 工程中的寄存器一般按计算机中字节的位数设计,所以一般有8位寄存器、16位寄存器等。
还有一点需要了解的是,FPGA中最小的单元是门电路,门电路又组成了锁存器,锁存器组成了寄存器。
二、如何产生锁存器以及如何避免锁存器
一般来说,在组合逻辑中,如果条件描述不全就会容易产生Latch
1. if-else 和case-default必须配套,也就是出现if 必须出现else与之配套;有case必须在后面写一个default,针对case语句也可以增加综合指令 //synopsys full_case指令省略default语句。
2.在所有条件下,对所有信号都进行赋值,同时单个always模块尽量只对单一变量进行赋值。
在时序电路的if语句中,即使没有else,也不会综合出Latch,因为时序电路综合出来的是Flip-flop;
上面这两种写法容易出现在什么地方呢?最常见的就是在写状态机时,case语句中没有给出变量的全部情况。
最后就SIRF 2008年面试题为例进行说明
“下面哪种写法会产生latch?为什么?”
(1) always @(*) begin if(d) a = b; end(2)
always @(*) begin
if(d)
a = b;
else
a = a;
end(3)
always @ (b or d)
case(d)
2'b00: a=b>>1;
2'b11: c=b>>1;
default: begin
a=b;
c=b;
end
endcase(4)
always @(b or d) begin
a = b;
c = b;
case (d)
2'b00: a = b >> 1;
2'b11: c = b >> 1;
endcase
end(5)
always@(b or d) begin
case (d) //synopsys full_case
2'b00: a = b >> 1;
2'b11: c = b >> 1;
endcase
end
代码(1)中由于缺少else分支,故而会产生锁存器;
代码(2)if-else结构完整但由于为组合逻辑,而组合逻辑要想产生记忆功能(a锁存),只能综合成锁存器;
代码(3)中由于并未在所有情况下对所有信号值赋值故会产生latch,当d为0时并没有对c赋值;
代码(4)中由于在选择语句之前给信号赋一个初值,故不会产生latch。但该种代码风格是按照软件的思维方式书写,故而不推荐使用;
代码(5)中虽然有 //synopsys full_case但是依旧并未在所有情况下对所有信号值赋值故会产生latch( //synopsys full_case只能省掉default)。
三、FPGA中的Latch资源
网上有一种说法是:FPGA中只有LUT和FF的资源,没有现成的Latch,所以如果要用Latch,需要更多的资源来搭出来。这种说法是错误的,因为在Xilinx的FPGA中,6 系列之前的器件中都有Latch;6系列和7系列的FPGA中,一个Slice中有50%的storage element可以被配置为Latch或者Flip-Flop,另外一半只能被配置为Flip-Flop。比如7系列FPGA中,一个Slice中有8个Flip-Flop,如果被配置成了Latch,该Slice的另外4个Flip-Flop就不能用了。这样确实造成了资源的浪费。
在UltraScale的FPGA中,所有的storage element都可以被配置成Flip-Flop和Latch。
我们以下面的代码来说明Flip-Flop和Latch在Ultrascale的FPGA中Implementation后的结果。
//Flip-Flop代码module FF_top(
input clk,
input [3:0] data_i,
input data_ie, //enable
output reg [3:0] o_latch
);always @ ( posedge clk )
begin
if(data_ie)
o_latch <= data_i;
endendmodule
//Latch代码
module latch_top(
input [7:0] data_i,
input data_ie, //enable
output reg [7:0] o_latch
);always @ *
begin
if(data_ie)
o_latch[3:0] <= data_i[3:0];
endendmodule
Flip-Flop实现后的Schematic和Device如下:
Latch实现后的Schematic和Device如下:
可以看出(左下角红圈),在使用Flip-Flop时,storage element被综合成了FDRE,也就是触发器;当使用Latch电路时,storage element被综合成了LDCE。
所以,FPGA中没有Latch的说法在Xilinx的FPGA中是不对的。
既然Latch有这么多的问题,那为什么FPGA中还要保留?首先就是因为FPGA电路的灵活性,保留Latch并不影响FPGA的资源,因为storage element可以直接被配置为Flip-Flop。
其次就是有些功能是必须要使用Latch的,比如很多处理器的接口就需要一个Latch来缓存数据或地址。
最后要说明的一点是:锁存器虽然在FPGA中不怎么被使用,但在CPU中却很常见,因为锁存器比Flip-Flop快很多。