HDLBits-Verilog学习小结(五)Alwaysblock

1 Always blocks

由于数字电路是由与导线连接的逻辑门组成,因此任何电路都可以表示为模块分配语句的某种组合。但是,有时这不是描述电路的最方便的方法。
Procedures过程(always blocks为例)为描述电路提供了另一种语法。
对于综合硬件,两种类型的always blocks是相关的:

  • Combinational: always @(*)
  • Clocked: always @(posedge clk)

1.1 Combinational

Combinational always blocks等同于赋值语句,因此总可以用两种方式表示组合电路,使用哪种选择主要是哪种语法更方便的问题。

程序块内部代码的语法与外部代码不同。程序块具有更丰富的语句集(例如if-then,case),不能包含连续的赋值,但是还引入了许多新的非直观的错误处理方式。

例如,assign和combinational always block描述了同一电路。两者都创建相同的组合逻辑块。每当任何输入(右侧)更改值时,两者都将重新计算输出:

  • assign out1 = a & b | c ^ d;
  • always @(*) out2 = a & b | c ^ d;

对于combinational always blocks,请始终使用灵敏度列表(*)。

明确列出信号很容易出错(如果您错过了一个信号),并且在硬件综合中会被忽略。如果您明确指定了灵敏度列表(*)并错过了信号,则合成的硬件仍会像指定了一样运行,但是模拟不会与硬件的行为匹配。

关于wire vs. reg的注释:assign语句的左侧必须为网络类型(例如,wire),而过程赋值的左侧(始终在块中)必须为变量类型(例如reg)。这些类型(wire与reg)与合成的硬件无关,而仅仅是Verilog用作硬件仿真语言时留下的语法。

question:
使用assign语句和组合的always块构建AND门。

solution:

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

endmodule

1.2 Clocked

Clocked always blocks会产生组合逻辑块,就像combinational always blocks一样,但还会在组合逻辑块的输出处创建一组触发器(或“寄存器”)。而不是立即看到逻辑团(the blob of logic)的输出,而是仅在下一个(posege clk)之后立即显示输出。

Verilog中有三种类型的分配

  1. 连续分配(assign x = y;)。仅在不在过程内部时使用(“始终阻止”)。连续分配(分配x = y;)。仅在不在过程内部时使用(“always block”)。
  2. 程序阻塞分配(Procedural blocking assignment):(x = y;)。只能在过程内使用。
  3. 程序非阻塞分配(Procedural non-blocking assignment):(x <= y;)。只能在过程内使用。

在combinational always block中,使用阻止分配(blocking assignments)。在 clocked always block中,使用非阻塞分配(non-blocking assignments)。不遵循该规则将导致极难发现不确定性的错误,并且仿真和综合硬件之间的错误也不同。

question:
使用assign语句,组合的Always块和Clocked Always块三种方式构建XOR门。请注意,clocked 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 @(*)
        out_always_comb = a ^ b;
    always @(posedge clk)
        out_always_ff = a ^ b;

endmodule

2 Statement

2.1 If statement

if语句通常创建2对1多路复用器,如果条件为true,则选择一个输入,如果条件为false,则选择另一个输入。

if

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

这等效于对条件运算符使用连续赋值:

assign out = (condition) ? x : y;

但是,过程式if语句提供了一种新的出错方法:仅当out是always assigned的值时,电路才是组合电路。

question:
构建一个在a和b之间选择的2对1多路复用器。如果sel_b1和sel_b2均为true,则选择b。否则,选择a。执行两次,一次使用assign语句,一次使用过程式if语句。

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_b1&sel_b2) begin
            out_always = b;
        end
        else begin
            out_always = a;
        end
    end
        
endmodule

2.2 If statement latches

设计电路时,必须首先考虑电路:

  • 我想要这个逻辑门
  • 我想要一个具有这些输入并产生这些输出的组合逻辑块
  • 我想要一组组合的逻辑后跟一组触发器(flip-flops)

一定不要做的是先编写代码,然后希望它生成正确的电路。

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

语法正确的代码不一定会导致电路合理(组合逻辑+触发器)。通常的原因是:“在您指定的情况以外的情况下会发生什么?”。 Verilog的答案是:保持输出不变

这种“保持输出不变”的行为意味着需要记住当前状态,从而产生一个锁存器。组合逻辑(例如逻辑门)无法记住任何状态。

注意警告:Warning (10240): ... inferring latch(es)" messages.
除非有意使用闩锁,否则它几乎总是表示错误。在所有条件下,组合回路必须为所有输出分配一个值。这通常意味着您总是需要else子句或分配给输出的默认值。

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

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

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

latches
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)
           shut_off_computer = 1;
        else
            shut_off_computer = 0;
    end

    always @(*) begin
        if (arrived | gas_tank_empty)
           keep_driving = 0;
        else
            keep_driving = 1;
    end

endmodule

2.3 Case statement

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”以冒号结尾。没有“开关”。
  • 每个案例项目都可以执行一条语句。这使得C中使用的“中断”变得不必要。但这意味着如果您需要多个语句,则必须使用begin … end。
  • 允许重复(和部分重叠)的案例项目。使用第一个匹配的。 C不允许重复的案例项目。

question:
如果存在大量案例,**Case语句比if语句更方便。**因此,在本练习中,创建6对1多路复用器。当sel在0到5之间时,选择相应的数据输入。否则,输出0。数据输入和输出均为4位宽。(注意推断闩锁)

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 = 0;
        endcase
    end

endmodule

3 Priority encoder

3.1 Priority encoder

question:
构建一个4位优先级编码器。

优先级编码器是一种组合电路当给定输入位向量时,输出该向量中前1位的位置。例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为bit [4]是高的第一位。
对于此问题,如果所有输入位都不为高(即输入为零),则输出零。请注意,一个4位数字具有16种可能的组合。

module top_module (
    input [3:0] in,
    output reg [1:0] pos  );
    always @(*) begin
        case(in)
            4'h0:pos=0;
            4'h1:pos=0;
            4'h2:pos=1;
            4'h3:pos=0;
            4'h4:pos=2;
            4'h5:pos=0;
            4'h6:pos=1;
            4'h7:pos=0;
            4'h8:pos=3;
            4'h9:pos=0;
            4'ha:pos=1;
            4'hb:pos=0;
            4'hc:pos=2;
            4'hd:pos=0;
            4'he:pos=1;
            4'hf:pos=0;
            default:pos=0;
         endcase
      end

endmodule

3.2 Priority encoder with casez

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

根据上一个练习(Priority encoder),case语句中将有256个案例。如果case语句中支持的case项无关位,我们可以减少这种情况(减少到9个case)。
这就是casez的目的:在比较中,将值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 item。选择第一个匹配项(因此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) //casez instead of case
            8'bzzzzzzz1:pos=0;
            8'bzzzzzz10:pos=1;
            8'bzzzzz100:pos=2;
            8'bzzzz1000:pos=3;
            8'bzzz10000:pos=4;
            8'bzz100000:pos=5;
            8'bz1000000:pos=6;
            8'b10000000:pos=7;
            default:pos=0;
        endcase
    end
endmodule

3.3 Avoiding latches

假设您正在建立一个电路来处理游戏的PS / 2键盘的扫描码。给定收到的扫描码的最后两个字节,您需要指示是否已按下键盘上的箭头键之一。这涉及到相当简单的映射,可以将其实现为具有四个案例的case语句(或if-elseif)。
nolatches
电路具有一个16位输入和四个输出。构建识别这四个扫描代码并断言(asserts)正确输出的电路。

为避免产生锁存器,必须在所有可能的情况下为所有输出分配一个值。

仅具有默认情况是不够的。我们必须在所有四种情况和默认情况下为所有四个输出分配一个值。这可能涉及很多不必要的输入。解决此问题的一种简单方法是在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语句覆盖该分配。这也意味着默认:case项变得不必要。

注意:逻辑合成器生成一个组合电路,其行为与代码描述的等效。硬件不会按顺序“执行”代码行。

solution:

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;
            16'he072: down=1;
            16'he074: right=1;
            16'he075: up=1;
        endcase
    end

endmodule
  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值