verilog language-Procedures

Problem 28:Always blocks(combination)(Alwaysblock1)

任何电路都可以用 moduleassign 语句组合出来。

这里介绍一种过程块,always 块,提供了一种更便捷地描述电路的方法

有两种 always 块可以综合出电路硬件的

always @(*)//综合逻辑
always @(posedge clk)//时序逻辑
  • 组合 always 块相当于 assign 语句

过程块内的代码与外部的 assign 代码不同。

  • 过程块中,我们可以使用比较丰富的语句(if-then ,case ),但不能包含连续赋值

例如,assign 和组合和 always 块描述相同的电路,两者创造出了相同的组合逻辑电路,只要任何输入(右侧)改变值,两者都将重新计算输出

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

对于组合 always 块,敏感变量列表总是使用 (*)

如果把所有的输入都列出来也是可以的,但容易出错的(可能少列出了一个),并且在硬件综合时会忽略您少列了一个,仍按原电路综合。 但仿真器将会按少列一个来仿真,这导致了仿真与硬件不匹配。(在 SystemVerilog 中,使用 always_comb)

verilog language-Procedures

使用 assign 语句和组合 always 块来构建与门。

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

Problem 29:Always blocks(clocked)(Alwaysblock2)

对于硬件综合来说,存在两种 always 块

always @(*)//组合逻辑
always @(posedge clk)//时序逻辑

时序 always 块也会像组合 always 块一样生成一系列的组合电路,但同时在组合逻辑的输出生成了一组触发器(或寄存器)

  • 通过时序 always 块的输出会在下一个时钟上升沿(posedge clk)后可见。

阻塞性赋值和非阻塞性赋值

在 Verilog 中有三种赋值方法:

连续赋值(assign x=y;):不能在过程块内使用;
过程阻塞性赋值(x=y;):只能在过程块中使用;
过程非阻塞性赋值(x<=y):只能在过程块内使用。

verilog language-Procedures

使用 assign 语句,组合 always 块和时序 always 块,这三种方式来构建异或门。

  • 时序 always 块生成了与另外两个不同的电路,多了一个触发器,因此输出会有一定的延时
// synthesis verilog_input_version verilog_2001
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

verilog language-Procedures

  • 从仿真的波形图可以看出,out_always_ff 比其他两个输出延迟了一个时钟周期,这就是非阻塞性赋值带来的。

Problem 30:If statement(Always if)

  • if 语句通常对应一个二选一多路复用器,如果条件为真,则选择其中一个输入作为输出;反之如果条件为假,则选择另一个输入所谓输出。
  • if 语句必须在过程块内使用。
    always @(*) begin
        if (condition) begin
            out = x;
        end
        else begin
            out = y;
        end
    end
    

verilog language-Procedures

  • 这与下面使用条件运算符连续赋值的语句是等价的:
    assign out = (condition) ? x : y;
    

但是,过程 if 语句使用不当可能会引入新的错误,只有 out 在所有的条件下都被赋值才会生成正确的组合电路,具体的错误下一个训练才会讲到,

构建一个可以在 a 和 b 之间选择的二选一多路复用器。如果 sel_b1 和 sel_b2 都为真,输出 b,其他情况输出 a。请使用两种方法作答,一次使用 assign 赋值,一次使用 if 语句。

sel_b1 sel_b2 out_assign
out_always
0 0 a
0 1 a
1 0 a
1 1 b
// synthesis verilog_input_version verilog_2001
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)
            out_always=b;
        else 
            out_always=a;
    end

endmodule

Problem 31:If statement latches(Always if2)

常见的错误来源:如何避免引入锁存器

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

  1. 我想实现一个逻辑门;
  2. 我想实现一个具有输入并产生输出的组合逻辑块
  3. 我想实现一组组合逻辑,紧接着一组触发器

不要上来就写代码,这样往往与你想象的电路相差甚远!

语法正确的代码不一定会产生合理的电路(组合逻辑 + 触发器)。

在你用 if 语句的时候,Verilog 会在你指定情况之外保持输出不变,这意味着电路需要记住当前状态,从而产生锁存器,组合逻辑并不能记住任何状态,这就会导致错误。

Warning (10240): ... inferring latch(es)

上述这类警告通常情况下代表错误,除非锁存器是故意生成的。组合电路输出必须在所有输入的情况下都有值。这意味着必须需要 else 子句或输出默认值。

示例:以下代码包含生成锁存器的错误,请勿模仿!!!

修复错误,只有当它真的过热时才关闭计算机,真的到达目的地或者需要加油时,才停止驾驶。

verilog language-Procedures

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

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

修改如下:

// 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=~arrived;
    end

endmodule

Problem 32:Casestatement (Always case)

Verilog 中的 Case 语句几乎等同于将一个表达式与其他表达式列表进行比较的一系列 if-elseif-else。

语法示例:

always @(*) begin     // This is a combinational circuit
    case (in)//与C的switch不同
      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 语句比 if 语句更方便。

在本练习中,创建一个 6 选 1 的多路复用器。当 sel 介于 0 和 5 之间时,选择相应的数据输入。 其他情况输出 0。数据输入和输出均为 4 位宽。

注意:不要生成锁存器(Problem 31)

// 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)
            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=4'b0000;//一定要用default声明不在case项里的输出,否则会生成不必要的寄存器
        endcase
    end

endmodule

problem 33:Priority encoder(Always case 2)

优先编码器是组合电路,当给定输入向量时,输出向量中第一个 1 的位置。例如,输入 8'b10010000 的,则优先编码器将输出 3'd4,因为位[4]是从右数第一个 1。

构建一个 4 位优先编码器,如果没有一个输入位,则输出零,

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

always@(*)
    case(in)
    4'b0000: pos = 2'b00;//二进制编码,看起来相对比较直观
    4'b0001: pos = 2'b00;
    4'b0010: pos = 2'b01;
    4'b0011: pos = 2'b00;
    4'b0100: pos = 2'b10;
    4'b0101: pos = 2'b00;
    4'b0110: pos = 2'b01;
    4'b0111: pos = 2'b00;
    4'b1000: pos = 2'b11;
    4'b1001: pos = 2'b00;
    4'b1010: pos = 2'b01;
    4'b1011: pos = 2'b00;
    4'b1100: pos = 2'b10;
    4'b1101: pos = 2'b00;
    4'b1110: pos = 2'b01;
    4'b1111: pos = 2'b00;
    default: pos = 2'b00;
    endcase

endmodule
// synthesis verilog_input_version verilog_2001
module top_module (
    input [3:0] in,
    output reg [1:0] pos  );
always @(*) begin
if (in[0]) pos = 0;//最省代码的写法
else if (in[1]) pos = 1;
else if (in[2]) pos = 2;
else if (in[3]) pos = 3;
else pos = 0;
end
endmodule

Problem 34:Priority encoder with casez(Always casez)

构建一个 8 输入的优先编码器,给定一个 8 位向量,输出输入向量中第一个 1 的位置。如果没有输入,则输出 0。

例如,输入 8'b10010000 应该输出 3'd4,因为位[4]是第一个出现 1 的位置。

case 项是按顺序去检查的,实际上像是生成了一个巨大的真值表和一个门,当输入匹配多个 case 项时,匹配第一个遇到的 case。

如果按照上一个去写 case 语句的话,case 语句中将有 256 个 case 项,引入 casez

如果 case 语句中的 case 项与某些输入无关,就可以减少列出的 case 项

casez:在比较中将具有值 z 的位视为无关项。

上题可用 casez 写为

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
// synthesis verilog_input_version verilog_2001
module top_module (
    input [7:0] in,
    output reg [2:0] pos  );
    always @(*)
        casez (in[7:0])
            8'bzzzzzzz1:pos=3'b000;//这里casez是按照顺序来执行的
            8'bzzzzzz1z:pos=3'b001;//也可以写成8‘bzzzzzz10:pos=3'b001,写逻辑比较清晰且不用看顺序
            8'bzzzzz1zz:pos=3'b010;
            8'bzzzz1zzz:pos=3'b011;
            8'bzzz1zzzz:pos=3'b100;
            8'bzz1zzzzz:pos=3'b101;
            8'bz1zzzzzz:pos=3'b110;
            8'b1zzzzzzz:pos=3'b111;
            default :pos=3'b000;
        endcase//注意这边是endcase
endmodule

Problem 35:Always nolatches(Always nolatches)

假设建立一个电路,去处理游戏 PS/2 键盘扫描码。给出接收到扫描码的最后的两个字节,您需要判断是否有按键被按下。是一个相当简单的映射,可以使用 case 语句或者 if-else 语句实现,一共有如下四种情况。

Scancode [15:0] Arrow key
16'he06b left arrow
16'he072 down arrow
16'he074 right arrow
16'he075 up arrow
Anything else none

16 个输入,4 个输出,描述该电路,并识别这四个按键的扫描码并输出

同时为避免生成了不必要的锁存器,必须在所有条件下为所有的输出赋值(参见 Problem 31: If statement latches(Always if2) )。这可能会多打很多字,使你的代码变得冗长。 一个简单的方法是在 case 语句之前为输出分配一个“默认值”:

always @(*) begin
    up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
    case (scancode)
        ... // Set to 1 as necessary.
    endcase//这种代码格式可以确保“默认值”的输出,default项无关紧要了(前提是“默认值”没有被覆盖)
end
  • 笨办法(最全)
// 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: begin
            up=1'b0;
    		down=1'b0;
    		left=1'b1;
            right=1'b0;end
        16'he072:begin
            up=1'b0;
    		down=1'b1;
    		left=1'b0;
            right=1'b0;end
        16'he074:begin 
            up=1'b0;
    		down=1'b0;
    		left=1'b0;
            right=1'b1;end
        16'he075:begin
            up=1'b1;
    		down=1'b0;
    		left=1'b0;
            right=1'b0;end
        default :begin
            up=1'b0;
    		down=1'b0;
    		left=1'b0;
            right=1'b0;end
    endcase
    end
endmodule
  • 笨办法的简化版
module top_module (
    input [15:0] scancode,
    output reg left,
    output reg down,
    output reg right,
    output reg up  ); 

always@(*)
    casez(scancode)
        16'he06b: begin up = 1'b0; down = 1'b0; left = 1'b1; right = 1'b0; end
        16'he072: begin up = 1'b0; down = 1'b1; left = 1'b0; right = 1'b0; end
        16'he074: begin up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b1; end
        16'he075: begin up = 1'b1; down = 1'b0; left = 1'b0; right = 1'b0; end
        default:  begin up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0; end
    endcase

endmodule
  • 最佳 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;
    casez(scancode)
        16'he06b: left = 1'b1;
        16'he072: down = 1'b1;
        16'he074: right = 1'b1; 
        16'he075: up = 1'b1;
    endcase
end

endmodule
  • 上面的“最佳 solution”会报告一个 case 语句没有 default 的 warning,但综合出的电路应该是和“笨办法”一样的
上一篇:Verilog——if语句的优先级问题


下一篇:基于FPGA的2选1多路选择器设计