你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

锁存器Latch的电路结构、特点以及如何在用Verilog时避免锁存器的生成

2021/11/17 20:23:49

锁存器、触发器和寄存器的比较

锁存器(latch):是电平触发的存储单元,数据存储的动作(状态转换)取决于输入时钟(或者使能)信号的电平值,尽当锁存器处于使能状态时,输出才会随着数据输入发生变化。
  分为普通锁存器门控锁存器,普通锁存器无控制信号,输出状态始终直接由输入决定。在实际的数字系统中,为了协调各部分的工作,往往需要有一个特定的控制信号去控制锁存器状态转换的时间,在控制信号无效时,输出保持不变,不随输入变换;当控制信号有效时,输出由输入决定,跟随输入变化。
①正是因为门控锁存器在控制信号有效的期间内,都可以接收输入信号,所以,激励信号的任何变化,都将直接引起锁存器输出状态的改变。这时输入信号若发生多次变化,输出状态也可能发生多次变化,这一现象称为锁存器的空翻。
②其次,当门控锁存器的控制信号有效时,锁存器就变成了一个组合电路,时序逻辑电路的模型就等效为两个各组合电路互为反馈的反馈系统,因此,系统有可能会因为瞬态特性不稳定而产生振荡现象。

 触发器(flip-flop)是边沿敏感的存储单元,数据存储的动作(状态转换)由某一信号 的上升或者下降沿进行同步的(限制存储单元状态转换在一个很短的时间内)。(钟控D触 发器其实就是 D 锁存器,边沿 D 触发器才是真正的 D 触发器)
触发器分为两种,一种是主从触发器和边沿触发器。主从触发器在时钟有效期内(主触 发器)接收数据,在时钟边沿输出状态转换。 边沿触发器在时钟边沿期间, 触发器才接收数 据并使输出状态转换。 目前,主从触发器基本上已经很少见了,实际使用的大都是边沿触发器。

寄存器(register):用来暂时存放参与运算的数据和运算结果。在实际的数字系统中, 通常把能够用来存储一组二进制代码的同步时序逻辑电路称为寄存器。 区别与联系:由于触发器内有记忆功能,因此利用触发器可以方便地构成寄存器。由于 一个触发器能够存储一位二进制码,所以把 n 个触发器的时钟端口连接起来就能构成一个存 储 n 位二进制码的寄存器。
   从寄存数据的角度来讲,寄存器和锁存器的功能是相同的;它们的区别在于寄存器是同 步时钟控制,而锁存器是电位信号控制。 一般的设计规则是:在绝大多数设计中避免产生锁存器。它会让您设计的时序完蛋,并 且它的隐蔽性很强,非老手不能查出。

锁存器具备下列特点:
(1)对毛刺敏感(使能信号有效时,输出状态可能随输入多次变化,产生空翻,对下一级电路很危险),不能异步复位,因此在上电后处于不确定的 状态。
(2)锁存器会使静态时序分析变得非常复杂,不具备可重用性。 (首先, 锁存器没有时 钟参与信号传递,无法做 STA;其次,综合工具会将 latch 优化掉,造成前后仿真结果不一 致)
(3)在FPGA中基本的单元是由查找表和触发器组成的,若生成锁存器反而需要 更多的资源。根据锁存器的特点可以看出,在电路设计中,要对锁存器特别谨慎,如果设计 经过综合后产生出和设计意图不一致的锁存器,则将导致设计错误,包括仿真和综合。因此, 在设计中需要避免产生意想不到的锁存器。 如果组合逻辑的语句完全不使用 always  语句块,就可以保证综合器不会综合出锁存器。
(4)但如果锁存器和触发器两者都由与非门搭建的话,锁存器耗用的逻辑资源要比D触发器少(D触发器需要12个MOS管,锁存器只需6个MOS管),锁存器的集成度更高。所以在的ASIC设计中会用到锁存器。但锁存器对毛刺敏感,无异步复位端,不能让芯片在上电时处在确定的状态;另外,锁存器会使静态时序分析变得很复杂,不利于设计的可重用,所以,在ASIC设计中,除了CPU这高速电路,或者RAM这种对面积很敏感的电路,一般不提倡用锁存器。 


锁存器的出现以及解决办法
在基于always的组合逻辑描述语句中容易综合出锁存器的地方:1:if 语句的使用中缺 少else语句(前提是不是始终边沿触发);2:case 语句中没有给出全部的情况。
(1)时序电路中if ....else 不完整不会生成锁存器,组合逻辑中会产生锁存器:

DC综合结果如下:

上 边的电路是时序逻辑,生成触发器,而触发器是有使能端(enable)的,使能端无效时就可以保存数据,无需锁存器
下 边的电路是组合逻辑,在en为低电平时,输出q1要保持不变,而组合逻辑没有存储元件,只能生成锁存器来保持数据。所以 在时序逻辑中,不完整的 if…else… 结构并不会生成锁存器,而组合逻辑中不完整的 if…else… 结构就会生成锁存器 。为了避免我们设计的组合逻辑中出现不想要的锁存器,在使用 if…else… 结构时一定要写完整。
(2)c中enable为1位,未形成锁存器,d中enaable为2位,形成锁存器



(3)形成锁存器,如果给q赋初值,不会形成锁存器
module a0(
input  clk     ,
input  [3:0] data,
input   [1:0] enable  ,
output reg q
);
always @(*)begin
       if(enable==0) q = data[3];
       else if(enable==1) q = data[2];
//       else if(enable==2) q = data[1];
//       else if(enable==3) q = data[0];
end
endmodule

(4)其他的情况(摘自他人博客,我均用DC综合过,结果正确)

case1(有锁存器):

module mux_latch
(
    input   [3:0] data,
    input   [1:0] valid,
    input   flag,
    output reg valid_data
);

always @ (*)
    begin
        if(valid==2'd0) valid_data = data[3];
        if(valid==2'd1) valid_data = data[2];
        if(valid==2'd2) valid_data = data[1];
        if(valid==2'd3) valid_data = data[0];
    end

endmodule

//---------------------------------------------------------

case 2解决办法1:加else与if配对

always @ (*)
    begin
        if(valid==2'd0) valid_data = data[3];else
        if(valid==2'd1) valid_data = data[2];else
        if(valid==2'd2) valid_data = data[1];else
        if(valid==2'd3) valid_data = data[0];else
                     valid_data = 1'b0;
    end

//-----------------------------------------------------------

case3解决办法2:赋初始值

always @ (*)
    begin

   valid_data = 1'b0
        if(valid==2'd0) valid_data = data[3];
        if(valid==2'd1) valid_data = data[2];
        if(valid==2'd2) valid_data = data[1];
        if(valid==2'd3) valid_data = data[0];
    end

//----------------------------------------------------------

case 4(有锁存器)

always @ (*)
begin
    case(valid)
        2'b00 : begin if(flag) valid_data = data[0];end
        2'b01 : begin if(flag) valid_data = data[1];end
        2'b10 : begin if(flag) valid_data = data[2];end
        2'b11 : begin if(flag) valid_data = data[3];end
    endcase

//-------------------------------------------------------------

case 5(有锁存器)

always @ (*)
begin
    case(valid)
        2'b00 : begin if(flag) valid_data = data[0];end
        2'b01 : begin if(flag) valid_data = data[1];end
        2'b10 : begin if(flag) valid_data = data[2];end
        2'b11 : begin if(flag) valid_data = data[3];end
        default:valid_data=1'b0;
    endcase
end

//--------------------------------------------------------------

case 6解决办法---赋初始值

always @ (*)
begin
   valid_data=1'b0;
    case(valid)
        2'b00 : begin if(flag) valid_data = data[0];end
        2'b01 : begin if(flag) valid_data = data[1];end
        2'b10 : begin if(flag) valid_data = data[2];end
        2'b11 : begin if(flag) valid_data = data[3];end
    endcase
end

//----------------------------------------------------------------

case7解决办法-- 加else

always @ (*)
begin
    case(valid)
        2'b00 : begin if(flag) valid_data = data[0];else valid_data = 1'b0;end
        2'b01 : begin if(flag) valid_data = data[1];else valid_data = 1'b0;end
        2'b10 : begin if(flag) valid_data = data[2];else valid_data = 1'b0;end
        2'b11 : begin if(flag) valid_data = data[3];else valid_data = 1'b0;end
    endcase
end