避免锁存器

1 锁存器介绍

        锁存器(Latch)是最为基本的时序元件,是一种对脉冲电平敏感的存储单元电路。简单来说,锁存器可以存储电路当前的状态使其保持不变,仅在外部控制信号的驱动下才能更改内部的信息。

锁存器寄存器
基本存储单元基本存储单元
电平触发边沿触发

组合逻辑意外产生的

(违背组合逻辑不存储状态的定义,想要的是导线)

在时序电路中使用,由时钟触发产生的

        在 Verilog 中,一个变量如果声明为寄存器类型(reg),它既可以被综合成组合逻辑的导线,也可能被综合成时序逻辑中的“寄存器”或“锁存器”。在使用 always @(*) 组合逻辑块语句时,我们会希望变量被综合成导线,但是有时候由于代码书写问题,它会被综合成我们不希望的锁存器结构,进而对电路带来危害。主要有:

  • 电路的输出状态可能发生多次变化,增加了下一级电路的不确定性;(由于有毛刺,而锁存器又对电平敏感,所以会导致意外跳变。)
  • 在大部分 FPGA 的设计里,锁存器结构会消耗更多的电路资源;
  • 锁存器导致电路不能按照我们预期的方式工作,在调试时带来额外的问题

        latch 的主要危害:其会产生毛刺(glitch),这种毛刺对下一级电路是很危险的。并且其隐蔽性很强,不易查出。因此,在设计中,应尽量避免 latch 的使用。

        因此,我们在代码书写时需要格外注意,应当避免出现锁存器。一个简单且好记的原则是:组合逻辑中不应出现记忆电路,即电路不能保存自身的状态。违反了这一原则的组合逻辑电路往往就会产生锁存器

        下面是一些常见的语句不完整引起latch例子。


2 if-else 逻辑缺陷

        在组合逻辑电路中,不完整的 if-else 结构会产生锁存器。例如下面这段 Verilog 代码:

module Latch1(
    input               data,
    input               en,
    output reg          q
);

always @(*) begin    只有en和data产生电平变化时,才会进入always。也就是信号有翻转的时候
    if (en) 
        q = data;
end
endmodule

        我们来分析一下:

  • 若en是0→1 进入过程赋值语句always中,那么可以进入if条件语句中,此时q的值更新为data的值。
  • 若en是1→0或data任意电平翻转 进入过程赋值语句always中,那么不可以进入if条件语句中,此时q的值为之前保存的值。(注意0时刻开始前,编译器会进入always内。写文章-优快云创作中心)

        为啥是之前的值?

        因为由于 always 语句里的 if 缺少对应的 else 分支,因此编译器默认 else 的分支下寄存器 q 的值保持不变。此时电路应当具有存储数据的功能,所以变量 q 会被综合成锁存器结构。

  1. 锁存器生成条件

    • 在组合逻辑(always @(*))中,未覆盖所有输入分支(如缺少 else)时,工具必须生成锁存器以保持未赋值情况下的值。

    • 此代码中,en = 0 时 q 未被赋值,因此需要锁存器存储 q 的旧值。

  2. 锁存器本质:锁存器是电平敏感存储单元,由 en 控制:

    • en = 0q 保持最后一次 en = 1 时的 data 值。

    • en = 1q 透明传递 data(类似导线)。

        如何避免?        

        避免此类锁存器的方法主要有 2 种:1、补全 if-else 结构;2、对信号赋初值。例如,上面的代码可以改为以下两种形式:

// 方法一:补全 if-else 分支结构    
always @(*) begin
    if (en)  
        q = data;
    else
        q = 1'b0;
end

//方法二: 为 q 赋初值
always @(*) begin
    q = 1'b0;
    if (en)
        q = data;
end

        方法二中由于内部都是对同一个变量的阻塞赋值,因此 always 中的语句是顺序执行的。执行时首先将 q 赋值为 0。如果信号 en 有效,则改写 q 的值为 data,否则 q 会保持为 0(而不是自己先前的存储的值,值进行了更新)。因此,这里 q 要么取值为 data,要么取值为 0,不会出现保持自身数值不变的情况,所以不会产生锁存器。

         注意:在时序逻辑中,不完整的 if-else 结构不会产生锁存器,例如下面这段 Verilog 代码:

module module_ff(
    input           clk,
    input           en,
    input           data, 
    output reg      q
);
always @(posedge clk) begin
    if (en)
        q <= data;
end
endmodule

        这是因为,时序逻辑中的变量 q 会被综合生成寄存器,而非锁存器,其数值仅在时钟的边沿到来时才会改变,这正是寄存器的特性。具有边沿触发敏感的存储功能是我们时序逻辑电路正好想要的。

        另外还有一种比较隐蔽的情况:当条件语句中有很多条赋值语句时,每个分支条件下的逻辑不完整也是会产生锁存器的。例如:

module Latch2(
    input               data1,
    input               data2,
    input               en,
    output reg          q1,
    output reg          q2
);

always @(*) begin
    if (en)
        q1 = data1;
    else
        q2 = data2;
end
endmodule

        这段代码看起来 if 和 else 都有了,但 if 部分里没有对 q2 赋值,else 部分里没有对 q1 赋值。从每个信号各自的的逻辑来看,这实际上也相当于是 if-else 结构不完整,相关信号缺少在其他条件下的赋值行为。所以本质上,这还是语法书写不规范造成的错误。

        这种情况也可以通过补充完整赋值语句or赋初值来避免产生锁存器。例如:

// 补全 if-else 分支结构    
always @(*) begin
    if (en)  begin
        q1 = data1;
        q2 = 1'b0;
    end
    else begin
        q1 = 1'b0;
        q2 = data2;
    end
end

// 为 q1、q2 赋初值
always @(*) begin
    q1 = 1'b0;
    q2 = 1'b0;
    if (en)
        q1 = data1;
    else
        q2 = data2;
end

2 case 逻辑缺陷

        case 语句产生锁存器的原理几乎和 if 语句一致。在组合逻辑中,当 case 选项列表不全且没有加 default 关键字,或有多个赋值语句不完整时,也会产生锁存器。例如:

module Latch3(
    input               data1,
    input               data2,
    input [1:0]         sel,
    output reg          q
);

always @(*) begin
    case(sel)
        2'b00:  q = data1;
        2'b01:  q = data2;
    endcase
end
endmodule

        这段代码之所以会产生锁存器,是因为在 sel 为 2'b10、2'b11 时,case 语句中并没有给出 q 的赋值结果,进而会被默认为保持原先的值不变。

        同样地,消除此种锁存器的方法也是 2 种:1、将 case 选项列表补充完整(可以罗列所有的选项结果,也可以用 default 关键字来代替其他选项结果。);2、或对信号赋初值。

// 枚举完整补充逻辑
always @(*) begin
    case(sel)
        2'b00:          q = data1;
        2'b01:          q = data2;
        2'b10, 2'b11:   q = 1'b0;
    endcase
end

// 使用default以补充逻辑
always @(*) begin
    case(sel)
        2'b00:    q = data1;
        2'b01:    q = data2;
        default:  q = 1'b0;
    endcase
end

// 使用默认赋初值
always @(*) begin
    q = 1'b0;
    case(sel)
        2'b00:  q = data1;
        2'b01:  q = data2;
    endcase
end

        更特别地,当 if 和 case 组合起来时,我们往往就容易出现逻辑遗漏。例如下面这段 Verilog 代码:

reg [3:0] value;
always @(*) begin
    case (state)
        2'b00: value = 1;
        2'b01: begin
            if (signal)
                value = 2;
        end
        2'b10: begin
            if (signal)
                value = 3;
        end
        default: value = 0;
    endcase
end

        这段代码中尽管有default语句,但依然会产生锁存器,因为case分支中的 if 逻辑不完整。

        一种比较好的策略是:在 always 语句块的一开始就进行默认赋值。这样可以避免潜在的逻辑不完整风险(减少你书写代码时容易写出不完整语句而导致的错误)

reg [3:0] value;
always @(*) begin
    value = 0;        //赋初值
    case (state)
        2'b00: value = 1;
        2'b01: begin
            if (signal)
                value = 2;
        end
        2'b10: begin
            if (signal)
                value = 3;
        end
        default: value = 0;
    endcase
end

3 自赋值与判断

        在组合逻辑中,如果一个信号的赋值源头有其信号本身,或者判断条件中有其信号本身的逻辑,也会产生锁存器。因为此时的信号也需要具有存储功能,才能够获得先前时刻该信号的数值。此类问题在 if 语句、case 语句、问号表达式中都可能出现,例如:

1-自己作为判断条件
reg a, b;
always @(*) begin
    if (a & b)  
        a = 1'b1;   // a 会生成锁存器
    else 
        a = 1'b0;
end

2-自增?
reg a, en;
always @(*) begin
    if (en)
        a = a + 1;  // a 会生成锁存器
    else
        a = 1'b0;
end

3-条件表达式也要小心
wire d, sel;
assign d = (sel2 && d) ? 1'b0 : 1'b1;  // d 会生成锁存器

        避免此类锁存器的方法只有一种,就是在组合逻辑中避免这种写法。时刻提醒自己:信号不要给信号自己赋值;不要用赋值信号本身参与判断条件逻辑

        例1代码中的 a = a & b 会导致组合反馈环路,这在硬件中不可实现。

另一种解法:

        如果不要求下一时刻信号立刻输出,我们可以将信号进行一个时钟周期的延时后再接入组合逻辑。例如上面的例1可以改写为:

reg a, b;
reg a_r;        //作为中间媒介

always @(posedge clk)        //综合为D触发器
    a_r <= a;   //存储a先前的值

always @(*) begin           //编译器优化直接综合为与门,并非选择器
    if (a_r & b)
        a = 1'b1;
    else 
        a = 1'b0;
end

        这段代码不会生成锁存器,因为我们人为引入了 a_r 作为寄存变量,用于存储变量 a 先前的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值