[Verilog16]HDLBits习题_Procedures

HDLBits习题讲解_Procedures

always语句块

  • 由于数字电路的实际实现,均是由基础的逻辑门搭建而成,因此任何电路都可以表示为模块和分配语句的某种组合。然而,有时这不是描述电路的最方便的方式。alwaysinitialtaskfunction为述电路提供了替代语法。
  • 对于always语句块而言,考虑到硬件的可综合性,有两种always块是有意义的:
  1. 组合:always @(*)
  2. 时序:always @(posedge clk)
  • 组合逻辑的always块和assign赋值是等价的,使用哪一种完全看哪一种更方便。always块内可有更丰富的状态,如if-then,case等,但不能有连续赋值语句assign。assign赋值语句的左边一般为wire类型,always块中左边的变量一般为reg类型。这些类型与硬件综合无关,仅是verilog语法的要求。
  • 时序逻辑的always块会生成一系列的触发器,即时序敏感型,只在时钟边沿到来时,才会对输出数据进行更新,否则则会对现有数据进行保持。在Verilog语法中,过程赋值语句用于对寄存器类型(reg)进行赋值,有着两种赋值方式:阻塞赋值与非阻塞赋值。
  1. 阻塞赋值方式:赋值符号为“=”,如b = a; 。阻塞赋值在该语句结束时就完成赋值操作,即b的值在该赋值语句结束后立刻改变。 如果在一个块语句中,有多条阻塞赋值语句,那么在前面的赋值语句没有完成之前,后面的语句就不能执行,就像被阻塞(blocking)了一样,因此称为阻塞赋值方式。
  2. 非阻塞赋值方式:赋值符号为“<=”,如b <= a;。非阻塞赋值在块结束时才完成赋值操作,即b的值并不是立即就改变的,而要等到该always代码块结束时才将值赋值给目标对象。所以,若是在一个always代码块中对一个变量进行多次非阻塞赋值,则只有最后一次赋值将生效。
  • 在描述组合逻辑的always块中用阻塞赋值,则综合成组合逻辑的电路结构;在描述时序逻辑的always块中用非阻塞赋值,则综合成时序逻辑的电路结构。

Always blocks (combinational)

  • 题目要求,使用赋值语句和组合always块两种方式构建与门。代码如下:
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 blocks (clocked)

  • 题目要求,使用赋值语句、组合always块和时序always块三种方式构建一个异或门。
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
  • 观察时序图,我们可以发现,out_always_ff的波形滞后于out_assignout_always_comb一个时钟周期,这是由于在时序逻辑电路中,触发器是时钟边沿敏感型的,仅在时钟边沿到来时,才更新输出。

if-else语句

  • 其格式与C语言中if-else语句类似,使用方法有以下3种。系统对表达式的值进行判断,若为0,x,z,按“假”处理;若为“1”,按“真”处理,执行指定语句。 语句可是单句,也可是多句,多句时用“begin-end”语句括起来
if(表达式) 语句1;
//////////////////////////////////
if(表达式) 语句1;
else   语句2;
/////////////////////////////////
if(表达式1) 语句1;
else if(表达式2) 语句2;
……
else if(表达式n) 语句n;
else 语句n+1;
  • __使用条件语句注意事项:__在使用条件语句时,应注意列出所有条件分支,否则,编译器认为条件不满足时,会引进一个触发器保持原值。这可用于设计时序电路,例如在计数器的设计中,条件满足则加1,否则保持不变;而在组合电路设计中,应避免这种隐含触发器的存在。为避免偶然生成锁存器的错误。如果用到if语句,最好写上else项。如果用case语句,最好写上default项。遵循上面两条原则,就可以避免发生这种错误。

If statement

  • 题目要求,构件一个2-1选择器,该器件的真值表如下:
sel_b1 sel_b2 out_assign out_wire
0 0 a a
0 1 a a
1 0 a a
1 1 b b
  • 代码如下:
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 statement latches

  • 题目要求,找出代码中的BUG,解决下面的代码中包含的创建锁存器的不正确行为。
always @(*) begin
    if (cpu_overheated)
       shut_off_computer = 1;
end

always @(*) begin
    if (~arrived)
       keep_driving = ~gas_tank_empty;
end
  • 代码修改如下:
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 语句

  • 当敏感表达式的值为值1时,执行语句1;为值2时,执行语句2;依此类推,如果敏感表达式的值与上面列出的值都不相等的话,则执行default后面的语句。
  • case语句中,敏感表达式与值1~值n间的比较是一种全等比较,必须保证两者的对应位全等。 在casez语句中,如果分支表达式某些位的值为高阻z,那么对这些位的比较就不予考虑,因此只须关注其他位的比较结果。而在casex语句中,则把这种处理方式进一步扩展到对x的处理,即如果比较的双方有一方的某些位是x或z,那么这些位的比较就都不予考虑。
  • case中可以有重复的case item,首次匹配的将会被执行。
  • case语句的使用格式如下:
case(敏感表达式)
    值1:语句1; // case分支项
    值2:语句2;
    ……
    值n:语句n;
    default:语句n+1; // default语句可以省略
endcase
  • __使用条件语句注意事项:__在使用条件语句时,应注意列出所有条件分支,否则,编译器认为条件不满足时,会引进一个触发器保持原值。这可用于设计时序电路,例如在计数器的设计中,条件满足则加1,否则保持不变;而在组合电路设计中,应避免这种隐含触发器的存在。为避免偶然生成锁存器的错误。如果用到if语句,最好写上else项。如果用case语句,最好写上default项。遵循上面两条原则,就可以避免发生这种错误。

Case statement

  • 题目要求,使用case语句创建一个6-1多路复用器。当sel在 0 到 5 之间时,请选择相应的数据输入。否则,输出 0。数据输入和输出均为 4 位宽。
  • 代码如下:
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'b0;
        endcase
    end
            
endmodule

Priority encoder

  • 优先编码器是一种组合电路,当给定输入位向量时,输出该向量中第一个1的位置。 例如,给定输入8'b10010000的8位优先级编码器将输出3'd4,因为bit [4]第一个高位。
  • 题目要求,构建一个4位优先级编码器。 对于此问题,如果所有输入位都不为高(即输入为零),则输出零。 请注意,一个4位数字具有16种可能的组合。
  • 代码如下:
module top_module (
    input [3:0] in,
    output reg [1:0] pos  );
	always @(*) begin
        case (in)
            4'b0000:pos = 0;
            4'b0001:pos = 0;
            4'b0010:pos = 1;
            4'b0011:pos = 0;
            4'b0100:pos = 2;
            4'b0101:pos = 0;
            4'b0110:pos = 1;
            4'b0111:pos = 0;
            4'b1000:pos = 3;
            4'b1001:pos = 0;
            4'b1010:pos = 1;
            4'b1011:pos = 0;
            4'b1100:pos = 2;
            4'b1101:pos = 0;
            4'b1110:pos = 1;
            4'b1111:pos = 0;
            default:pos = 0;
            
        endcase
    end
endmodule

Priority encoder with casez

  • 为8位输入构建优先级编码器。当给定输入位向量时,输出该向量中第一个1的位置。如果输入向量没有高位,则报告为零。 例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为bit [4]是高的第一位。由上一个练习我们知道,case语句中将有256种case item,使用casez以后,我们可以减少需比较的case item,这就是casez的目的,在比较中,将值z的位视作无关位。
  • 代码如下:
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

Avoiding latches

  • 假设构建一个电路来处理游戏的PS/2键盘上的扫描代码。对于收到的最后两个字节的扫描码,我们需要指示是否按下了键盘上的一个方向键。这涉及到一个相当简单的映射,它可以实现为一个case语句(或if-elseif),包含四个case。
  • 电路有一个16位输入和四个输出。建立能识别这四种扫描码并正确输出的电路。
Scancode[15:0] Arrow key
16'he06b left arrow
16'he072 down arrow
16'he074 right arrow
16'he075 up arrow
Anything else none
  • 一种简单的方式就是对输出先进行赋初值的操作,这种类型的代码确保在所有可能的情况下输出都被赋值,除非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
  • 代码如下:
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

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