HDLBits练习汇总-04-Verilog语言--程序部分(二)

简介: HDLBits练习汇总-04-Verilog语言--程序部分

if语句出现锁存器


错误的常见来源:如何避免出现锁存器

设计电路时,你必须首先想到在电路方面:

  • 我想要这个逻辑门
  • 我想要一个具有这些输入并产生这些输出的逻辑组合块
  • 我想要一个逻辑组合块,然后是一组触发器
    你不能做的是先写代码,然后希望它生成一个合适的电路。If (cpu_overheated) then shut_off_computer = 1;If (~arrived) then keep_driving = ~gas_tank_empty;语法正确的代码不一定会产生合理的电路(组合逻辑 + 触发器)。通常的原因是:“在您指定的情况之外的情况下会发生什么?”。Verilog 的答案是:保持输出不变。

这种“保持输出不变”的行为意味着需要记住当前状态,从而产生一个锁存器。组合逻辑(例如,逻辑门)无法记住任何状态。注意警告(10240):…推断闩锁(es)“消息。除非闩锁是故意的,否则它几乎总是表明存在错误。组合电路必须在所有条件下为所有输出分配一个值。这通常意味着您总是需要else子句或分配给输出的默认值。

练习


以下代码包含创建闩锁的错误行为。修复错误,以便您仅在计算机确实过热时才关闭计算机,并在到达目的地或需要加油时停止驾驶。

image.png

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

Module Declaration

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

答案


// synthesis verilog_input_version verilog_2001
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)
           shut_off_computer = 1;
        else
            shut_off_computer = 0;
    end
    always @(*) begin
        if (~arrived)
           keep_driving = ~gas_tank_empty;
        else
           keep_driving = 0; 
    end
endmodule

case语句


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 >1 statement
            end
      1'b0: out = 1'b0;
      default: out = 1'bx;
    endcase
end
  • case 语句以case开头,每个“case item”以冒号结尾。没有“switch”。
  • 每个 case 项只能执行一个语句。这使得 C 中使用的“break”变得不必要。但这意味着如果您需要多个语句,则必须使用begin … end。
  • 允许重复(和部分重叠)的case项。使用第一个匹配的。C 不允许重复的 case 项。

练习


如果有大量case项,Case 语句比 if 语句更方便。因此,在本练习中,创建一个 6 对 1 多路复用器。当sel在0到5之间时,选择对应的数据输入。否则,输出 0。数据输入和输出均为 4 位宽。

Module Declaration

// synthesis verilog_input_version verilog_2001
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   );

答案


// synthesis verilog_input_version verilog_2001
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)
            0:out = data0;
            1:out = data1;
            2:out = data2;
            3:out = data3;
            4:out = data4;
            5:out = data5;
        default:out = 4'b0000;
        endcase
    end
endmodule

case语句2


甲优先级编码器是一个组合电路,给定一个输入位向量时,输出第一的位置1中的向量位。例如,给定输入8’b100 1 0000的 8 位优先级编码器将输出3’d4,因为 bit[4] 是第一个高位。

练习


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

Module Declaration

// synthesis verilog_input_version verilog_2001
module top_module (
    input [3:0] in,
    output reg [1:0] pos  );

答案


// synthesis verilog_input_version verilog_2001
module top_module (
    input [3:0] in,
    output reg [1:0] pos  );
  always@(*) begin  // This is a combinational circuit
        case(in)
            0:pos = 0;
            1:pos = 0;
            2:pos = 1;
            3:pos = 0;
            4:pos = 2;
            5:pos = 0;
            6:pos = 1;
            7:pos = 0;
            8:pos = 3;
            9:pos = 0;
            10:pos = 1;
            11:pos = 0;
            12:pos = 2;
            13:pos = 0;
            14:pos = 1;
            15:pos = 0;
        default:pos = 4'b0000;
        endcase
    end
endmodule

casez语句


从上一个练习,case 语句中有 256 个 case。如果 case 语句中的 case 项支持 don’t-care 位,我们可以减少这种情况(减少到 9 个 case)。这就是z 的情况:它在比较中将具有z值的位视为不关心的。

例如,这将实现上一个练习中的 4 输入优先级编码器:

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是一样的。

练习


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

Module Declaration

// synthesis verilog_input_version verilog_2001
module top_module (
    input [7:0] in,
    output reg [2:0] pos  );

答案


// synthesis verilog_input_version verilog_2001
module top_module (
    input [7:0] in,
    output reg [2:0] pos  );
  always @(*) begin
    casez (in)
        8'bzzzz_zzz1: pos = 0;   
        8'bzzzz_zz1z: pos = 1;
        8'bzzzz_z1zz: pos = 2;
        8'bzzzz_1zzz: pos = 3;
        8'bzzz1_zzzz: pos = 4;   
        8'bzz1z_zzzz: pos = 5;
        8'bz1zz_zzzz: pos = 6;
        8'b1zzz_zzzz: pos = 7;
        default: pos = 0;
    endcase
end
endmodule

避免锁存器


假设您正在构建一个电路来处理来自游戏的 PS/2 键盘的扫描码。给定接收到的最后两个字节的扫描码,您需要指出是否按下了键盘上的箭头键之一。这涉及一个相当简单的映射,它可以实现为具有四个 case 的 case 语句(或 if-elseif)。

image.png

您的电路有一个 16 位输入和四个输出。构建这个电路来识别这四个扫描码并断言正确的输出。

为避免创建锁存器,必须在所有可能的条件下为所有输出分配一个值。仅仅有一个默认情况是不够的。您必须在所有四种情况和默认情况下为所有四个输出分配一个值。这可能涉及许多不必要的打字。解决此问题的一种简单方法是在 case 语句之前为输出分配一个“默认值” :

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

这种代码风格确保在所有可能的情况下为输出分配一个值(0),除非 case 语句覆盖分配。这也意味着default: case 项变得不必要了。逻辑合成器生成一个组合电路,其行为与代码描述的相同。硬件不会按顺序“执行”代码行。

Module Declaration

// synthesis verilog_input_version verilog_2001
module top_module (
    input [15:0] scancode,
    output reg left,
    output reg down,
    output reg right,
    output reg up  ); 

练习答案


// synthesis verilog_input_version verilog_2001
module top_module (
    input [15:0] scancode,
    output reg left,
    output reg down,
    output reg right,
    output reg up  ); 
    always @(*) begin
        up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
        case (scancode)
            16'he06b: left = 1'b1;
            16'he072: down = 1'b1;
            16'he074:right = 1'b1;
            16'he075: up = 1'b1;
        endcase
    end
endmodule
目录
相关文章
HDLBits练习汇总-04-Verilog语言--程序部分(一)
HDLBits练习汇总-04-Verilog语言--程序部分
116 0
HDLBits练习汇总-04-Verilog语言--程序部分(一)
|
芯片
HDLBits练习汇总-01-Verilog语言--基础部分
HDLBits练习汇总-01-Verilog语言--基础部分
126 0
HDLBits练习汇总-01-Verilog语言--基础部分
HDLBits练习汇总-05-Verilog语言--拓展部分
HDLBits练习汇总-05-Verilog语言--拓展部分
86 0
HDLBits练习汇总-03-Verilog语言--模块层次结构(二)
HDLBits练习汇总-03-Verilog语言--模块层次结构
294 0
HDLBits练习汇总-03-Verilog语言--模块层次结构(二)
|
编译器
HDLBits练习汇总-03-Verilog语言--模块层次结构(一)
HDLBits练习汇总-03-Verilog语言--模块层次结构
136 0
HDLBits练习汇总-03-Verilog语言--模块层次结构(一)
HDLBits练习汇总-02-Verilog语言--向量部分(二)
HDLBits练习汇总-02-Verilog语言--向量部分
114 0
HDLBits练习汇总-02-Verilog语言--向量部分(二)
|
索引
HDLBits练习汇总-02-Verilog语言--向量部分(一)
HDLBits练习汇总-02-Verilog语言--向量部分
167 0
HDLBits练习汇总-02-Verilog语言--向量部分(一)