HDLBits答案(4)_如何避免生成锁存器?

HDLBits_Verilog Language_Procedures

HDLBits链接


组合逻辑的always块

[David说]:

考虑到硬件的可综合性,有两种always块是有意义的:

  • 组合:always @(*)
  • 时序:always @(posedge clk)

组合逻辑的always块和assign赋值是等价的,使用哪一种完全看哪一种更方便。always块内可有更丰富的状态,如if-then,case等,但不能有连续赋值语句assign。

如果在always块内指定了特定的信号,但没有的话,和always(*)综合结果相同,此时功能仿真结果和硬件结果就有所差异。

assign赋值语句的左边一般为wire类型,always块中左边的变量一般为reg类型。这些类型与硬件综合无关,仅是verilog语法的要求。

题目描述

使用赋值语句和组合always块两种方式构建与门。

Solution

module top_module(
    input a, 
    input b,
    output wire out_assign,
    output reg out_alwaysblock
);
    assign out_assign = a & b;
    always @(*) begin
        out_alwaysblock = a & b; 
    end
endmodule

时序逻辑的always块

[David说]:时序always块可像组合always块那样生成电路,同时也会生成一系列的触发器,寄存器等,因为输出要等到下个时钟延才能输出。

阻塞赋值 vs 非阻塞赋值

verilog中有三种赋值方式:

  • 连续赋值(assign x=y;),只能在always块外使用。
  • 阻塞赋值(x=y;),只能在always块内使用。
  • 非阻塞赋值(x<=y;)只能在always块内使用。

组合逻辑的always块中(always @(*))使用阻塞赋值语句;

时序逻辑的always块中(always @(posedge clk))使用非阻塞赋值语句。

题目描述

使用赋值语句、组合always块和时序always块三种方式构建一个异或门。

Solution:

module top_module(
    input clk,
    input a,
    input b,
    output wire out_assign,
    output reg out_always_comb,
    output reg out_always_ff   );
	assign out_assign = a ^ b;
    always @(*) begin
        out_always_comb = a ^ b;
    end
    always @(posedge clk) begin
        out_always_ff <= a ^ b;
    end
endmodule

IF选择器

[David说]:

一个if语句会产生一个2选1的数据选择器,需注意的是,并不是if选择的那路数据才被实现成电路模式,而是if和else两路都被实现为电路形式然后用选择器选择输出。

1.png

if的两种方式:

always @(*) begin
    if (condition) begin
        out = x;
    end
    else begin
        out = y;
    end
end
assign out = (condition) ? x : y;

题目描述

2-1选择器

sel_b1sel_b2out_assign/out_always
00a
01a
10a
11b

Solution:

module top_module(
    input a,
    input b,
    input sel_b1,
    input sel_b2,
    output wire out_assign,
    output reg out_always   ); 
    assign out_assign=(sel_b1&sel_b2)?b:a;
    always @(*) begin
        if(sel_b2&sel_b1) begin
            out_always = b;
        end
        else begin
            out_always = a;
        end
    end
endmodule

IF中锁存器问题

[David说]:

如何避免在使用if语句时生成锁存器?

tip:锁存器与触发器的区别?

  • 锁存器是一种对脉冲电平(也就是0或者1)敏感的存储单元电路,而触发器是一种对脉冲边沿(即上升沿或者下降沿)敏感的存储电路。

当我们在设计电路时,不能直接先写成代码然后期望它直接生成为合适的电路,如下典型错误所示:

  • If (cpu_overheated) then shut_off_computer = 1;
  • If (~arrived) then keep_driving = ~gas_tank_empty;

语法上正确的代码并不意味着设计成的电路也是合理的。我们来思考这么一个问题,如上图的错误示例,如果if条件不满足,输出如何变化呢?Verilog给出的解决方法是:保持输出不变。因为组合逻辑电路不能记录当前的状态,所以就会综合出锁存器。

所以当我们使用if语句或者case语句时,我们必须考虑到所有情况并给对应情况的输出进行赋值,就意味着我们要为else或者default中的输出赋值。

题目描述:找BUG,解决下面的代码中包含的创建锁存器的不正确行为。

always @(*) begin
    if (cpu_overheated)
       shut_off_computer = 1;
end

always @(*) begin
    if (~arrived)
       keep_driving = ~gas_tank_empty;
end

2.png

Solution

module top_module (
    input      cpu_overheated,
    output reg shut_off_computer,
    input      arrived,
    input      gas_tank_empty,
    output reg keep_driving  ); //

    always @(*) begin
        if (cpu_overheated) begin
           shut_off_computer = 1;
        end
        else begin
            shut_off_computer = 0;
        end
    end

    always @(*) begin
        if (~arrived) begin
           keep_driving = ~gas_tank_empty;
        end
        else begin
           keep_driving = 0;
        end
    end
    
endmodule

Case语句

[David说]:

在Verilog中,case语句与if-elseif-else相近,与c语言中的switch差别较大,示例如下:

always @(*) begin     // This is a combinational circuit
    case (in)
      1'b1: begin 
               out = 1'b1;  // begin-end if statement >1
            end
      1'b0: out = 1'b0;
      default: out = 1'bx;
    endcase
end
  • case语句以case开始,每个case的选项以分号结束。
  • 每个case的选项中只能执行一个statement,所以就无需break语句。但如果我们想在一个case选项中执行多个statement,就需要使用begin...end
  • case中可以有重复的case item,首次匹配的将会被执行。

题目描述:6-1数据选择器

Be careful of inferring latches!(需添加Default)

Solution

module top_module ( 
    input [2:0] sel, 
    input [3:0] data0,
    input [3:0] data1,
    input [3:0] data2,
    input [3:0] data3,
    input [3:0] data4,
    input [3:0] data5,
    output reg [3:0] out   );//

    always@(*) begin  // This is a combinational circuit
        case(sel)
            3'b000:out=data0;
            3'b001:out=data1;
            3'b010:out=data2;
            3'b011:out=data3;
            3'b100:out=data4;
            3'b101:out=data5;
            default:out=3'b0;
        endcase
    end

endmodule

优先编码器

题目描述:优先编码器是一种组合电路,当给定输入位向量时,输出该向量中第一个1的位置。 例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为bit [4]是高的第一位。

构建一个4位优先级编码器。 对于此问题,如果所有输入位都不为高(即输入为零),则输出零。 请注意,一个4位数字具有16种可能的组合。

Solution

1、根据惯性思维使用case语法,列出每一种情况,然后列出其对应的输出。

case(input)

4'b0001: output = 1;

.......

4'b1111:output = 1;

default: output = 0;

endcase

2、如果按上述思路来写,那么更多位的优先编码器如何实现呢?其实有更简单的方法,目前既不用casex也不用casez,这两位要在后面出场。来看另一种思路:

module top_module (
    input [3:0] in,
    output reg [1:0] pos  );
    always @(*) begin
        case(1)
            in[0]:pos = 0;
            in[1]:pos = 1;
            in[2]:pos = 2;
            in[3]:pos = 3;
            default:pos = 0;
        endcase
    end
endmodule

根据上面所说的case的性质,case中可以有重复的case item,但首次匹配的才会被执行。再看上面的case语句,即从低到高位去比较in中是否有数据位为1,下面即使有重复为1的也只会执行首次匹配的操作。如此,N位优先编码器只需要N个case分支即可,大大简化代码量。


casez实现优先编码器

题目描述

为8位输入构建优先级编码器。当给定输入位向量时,输出该向量中第一个1的位置。如果输入向量没有高位,则报告为零。 例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为bit [4]是高的第一位。

[David说]:由上一个练习我们知道,case语句中将有256种case item,使用casez以后,我们可以减少需比较的case item,这就是casez的目的,在比较中,将值z的位视作无关位。

所以上题的另一种解法为:

always @(*) begin
    casez (in[3:0])
        4'bzzz1: out = 0;   // in[3:1] can be anything
        4'bzz1z: out = 1;
        4'bz1zz: out = 2;
        4'b1zzz: out = 3;
        default: out = 0;
    endcase
end

case语句的行为就好像是按顺序检查每个项一样(实际上,它所做的事情更像是生成一个巨大的真值表,然后进行门操作)。注意某些输入(例如4’b1111)是如何匹配多个case项的。选择第一个匹配项(因此4’b1111匹配第一个项目,out = 0,但不匹配后面的任何项目)。

  • 还有类似的casex,它将x和z均无视掉,但用它来代替casez的意义不大。
  • 符号?是z的同义词,所以2‘bz0=2’b?0

Solution

module top_module (
    input [7:0] in,
    output reg [2:0] pos  );
    always @(*) begin
        casez(in)
            8'bzzzzzzz1:pos=0;
            8'bzzzzzz1z:pos=1;
            8'bzzzzz1zz:pos=2;
            8'bzzzz1zzz:pos=3;
            8'bzzz1zzzz:pos=4;
            8'bzz1zzzzz:pos=5;
            8'bz1zzzzzz:pos=6;
            8'b1zzzzzzz:pos=7;
            default:pos=0;
        endcase
    end
endmodule

该题也可以按Priority encoder中的解法2来写,复杂度甚至更低。


去除锁存器

题目描述

假设构建一个电路来处理游戏的PS/2键盘上的扫描代码。对于收到的最后两个字节的扫描码,我们需要指示是否按下了键盘上的一个方向键。这涉及到一个相当简单的映射,它可以实现为一个case语句(或if-elseif),包含四个case。

电路有一个16位输入和四个输出。建立能识别这四种扫描码并正确输出的电路。

Scancode[15:0]Arrow key
16’he06bleft arrow
16’he072down arrow
16’he074right arrow
16’he075up arrow
Anything elsenone

[David说]:为避免生成锁存器,所有的输入情况必须要被考虑到。但仅有一个简单的default是不够的,我们必须在case item和default中为4个输出进行赋值,这会导致很多不必要的代码编写。

一种简单的方式就是对输出先进行赋初值的操作,这种类型的代码确保在所有可能的情况下输出都被赋值,除非case语句覆盖了赋值。这也意味着不再需要缺省的default项。如下面示例代码:

always @(*) begin
    up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
    case (scancode)
        ... // Set to 1 as necessary.
    endcase
end

Solution

module top_module (
    input [15:0] scancode,
    output reg left,
    output reg down,
    output reg right,
    output reg up  ); 
    always @(*) begin
        left=0;down=0;right=0;up=0;
        case(scancode)
            16'he06b:left=1;
            16'he072:down=1;
            16'he074:right=1;
            16'he075:up=1;
        endcase
    end
endmodule

总结:

  • 学习了组合和时序两种always块,并知晓了两种类型的always块中变量的类型即赋值方式。何时使用wire与reg,何时用阻塞和非阻塞赋值。

  • 学习了如何在使用if和case语句时避免生成锁存器

    将所有状态均考虑到并为其输出赋值,考虑不全可用else/default。

    在case之前为所有输出赋初值,这样可以不用使用default,除非满足case item进行覆盖赋值,否则仍保持初值。

  • 学习了case语句的妙用,以及使用casez忽略无关位,更简便的实现优先编码器。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日拱一卒_未来可期

若复习顺利望有闲钱的同学支持下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值