HDLBits——Procedures

HDLBits——Procedures

Problem 28: Always blocks(combinational) (Alwaysblock1)

Requirement:

过程块(比如 always 块)提供了一种用于替代 assign 语句描述电路的方法。分别使用 assign 语句和组合 always 块来构建与门。

Alwayscomb.png

Solution:
// 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 @(*) begin
        out_alwaysblock = a & b;
    end

endmodule

PS:

  1. 有两种 always 块是可以综合出电路硬件的:

    综合逻辑:always @(*)

    时序逻辑:always @(posedge clk)

  2. 组合 always 块相当于 assign 语句,因此组合电路存在两种表达方法,具体使用哪个主要取决于使用哪个更方便。过程块内的代码与外部的assign代码不同。过程块中可以使用更丰富的语句(比如 if-then,case),但不能包含连续赋值。

    例如,assign 和组合 always 块描述相同的电路,只要任何输入改变值,两者都将重新计算输出。

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

    对于组合always块,敏感变量列表总是使用(*)。如果把所有的输入都列出来也是可以的,但容易出错(可能少列出了一个),在硬件综合时会忽略少列了一个,仍按原电路综合,但仿真器将会按少列一个来仿真,这导致了仿真与硬件不匹配。

  3. 关于 wire 与 reg 的说明:assign 语句的左侧必须是 net 类型(例如,wire),而过程赋值(在 always 块中)的左侧必须是 variable 类型(例如,reg)。

Timing Diagram:

image-20211128141724081

Problem 29: Always blocks(clocked) (Alwaysblock2)

Requirement:

使用 assign 语句,组合 always 块和时序 always 块这三种方式来构建异或门。 请注意,时序 always 块生成了与另外两个不同的电路,多了一个触发器,因此输出会有一定的延迟。

Alwaysff.png

Solution:
// 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 @(*) begin
        out_always_comb = a ^ b;
    end
    always @(posedge clk) begin
        out_always_ff <= a ^ b;
    end

endmodule

PS:

  1. 上一节提到有两种 always 块,时序 always 块也会像组合 always 块一样生成一系列的组合电路,但同时生成了一组触发器(或寄存器)。该输出在下一个时钟上升沿(posedge clk)后可见,而不是之前的立即可见。

  2. 在 Verilog 中有以下三种赋值方法:

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

    在组合 always 块中,使用阻塞性赋值。在时序 always 块中,使用非阻塞性赋值。具体为什么对设计硬件用处不大,还需要理解Verilog模拟器如何跟踪事件。不遵循此规则会导致极难发现非确定性错误,并且在仿真和综合出来的硬件之间存在差异。

Timing Diagram:

image-20211128142829272

从仿真的波形图可以看出,out_always_ff 比其他两个输出延迟了一个时钟周期,因为上升沿才触发赋值。

Problem 30: If statement(Always if)

Requirement:

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

sel_b1sel_b2out_assign out_always
00a
01a
10a
11b
Solution:
// 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) begin
            out_always = b;
        end
        else begin
            out_always = a;
        end

    end

endmodule

PS:if 语句通常对应一个二选一多路复用器,如果条件为真,则选择其中一个输入作为输出;如果条件为假,则选择另一个输入作为输出,if 语句必须在过程块内使用。与使用**条件运算符(问号冒号语句)+ 连续赋值(assign)**的语句是等价的。

注意:冒号前面是满足条件的结果。

assign 语句也可写作 assign out_assign = sel_b1 ? (sel_b2 ? b:a):a; 即优先判断 sel_b1 的值,sel_b1 为 0 直接输出 a,为 1 的时候再判断 sel_b2 是否为 1,此时,sel_b1 和 b2 均为 1 时,才输出 b,否则均输出 a。但不如上面的简单。

Timing Diagram:

image-20211128143904267

Problem 31: If statement latches(Always if2)

Requirement:

设计电路时除了指定的情况,输出保持不变。这意味着电路需要记住当前状态,从而产生锁存器(latch),组合逻辑不能记住任何状态。除非锁存器是故意生成的,组合电路输出必须在所有输入的情况下都必须有值,这意味着需要 else 子句或输出默认值 default。

以下代码包含生成锁存器的错误,请修复错误,以便真正过热时才关闭计算机,同时使得到达目的地或者需要加油时,停止驾驶。

Always if2.png

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

always @(*) begin
    if (~arrived)
       keep_driving = ~gas_tank_empty;
end
Solution:
// 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 begin
            shut_off_computer = 0;
        end
    end

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

endmodule
//停车也可改为
always @(*) begin
        if (~arrived)
            keep_driving = ~gas_tank_empty;
        else
            keep_driving = ~arrived;
    end
Timing Diagram:

image-20211128152955114

Problem 32: Casestatement(Always case)

Requirement:

创建一个 6 选 1 的多路复用器。当 sel 介于 0 和 5 之间时,选择相应的数据输入。 其他情况输出0。数据输入和输出均为 4 位宽。注意:不要生成锁存器。

Solution:
// 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: begin out = data0; end
        1: begin out = data1; end
        2: begin out = data2; end
        3: begin out = data3; end
        4: begin out = data4; end
        5: begin out = data5; end
        default: begin out = 4'b0000; end
        endcase
    end

endmodule

PS:Verilog 中的 case 语句几乎等同于 if-else if-else 序列,写在逻辑 always 块中,它将一个表达式与其他表达式列表进行比较。它的语法和功能与 C 语言中的 switch 语句稍有不同:

  1. case 语句以 case 开头,每个 case 项以冒号结束。而 switch 语句没有。
  2. 每个 case 项只执行一个语句。 这样就不需要 C 语言中 break 来跳出 switch。但这也意味着如果需要多个语句,则必须使用 begin … end。
  3. case 项允许重复和部分重叠,执行程序匹配到的第一个,而 C 语言不允许重复的 case 项目。

注意:这里一定要用 default 声明一下不在 case 项里的输出,否则会生成不必要的寄存器,影响电路的功能。

Timing Diagram:

image-20211128155439733

Problem 33: Priority encoder(Always case2)

Requirement:

构建一个4位优先编码器。如果输入均为零,则输出零。

优先编码器:组合电路,当给定输入时,输出输入向量中的右边第一个1的位置。例如,输入8’b10010000的,则优先编码器将输出3’d4,因为位[4]是从右数第一个1。出现的第一个数字把后面的数字屏蔽掉了,第一个数字具有较高的优先级,所以叫做优先译码器。

Solution:
// 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
Timing Diagram:

image-20211128161248980

Problem 34: Priority encoder with casez(Always casez)

Requirement:

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

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

    always @(*) begin
        casez (in)
            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

**casez:**如果 case 语句中的 case 项与某些输入无关,就可以减少列出的 case 项(在本题中减少到 4 个)。这就是 casez 的用途:它在比较中将具有值 z 的位视为无关项(即输入01都会匹配到)。

注意:符号 “?” 是 z 的同义词,8’bzzzz1000 也可写作 8’b???1000。

**casex:**与 casez 类似,将输入的 x 和 z 都视为无关。

Timing Diagram:

image-20211128162359141

Problem 35: Always nolatches(Always nolatches)

Requirement:

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

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

所设计的电路有一个 16 位的输入和 4 个输出,请描述此电路,识别这四个按键的扫描码并输出。

Solution:
// 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
        case (scancode)
        16'he06b: begin left = 1'b1;down = 1'b0;right = 1'b0;up = 1'b0; end
        16'he072: begin left = 1'b0;down = 1'b1;right = 1'b0;up = 1'b0; end
        16'he074: begin left = 1'b0;down = 1'b0;right = 1'b1;up = 1'b0; end
        16'he075: begin left = 1'b0;down = 1'b0;right = 1'b0;up = 1'b1; end
        default: begin left = 1'b0;down = 1'b0;right = 1'b0;up = 1'b0; end
    endcase
    end
    
endmodule

PS:always@(*) 综合器会生成一个组合电路,其行为与代码描述相同,硬件不会按顺序“执行”代码。

改进:为避免生成不必要的锁存器,必须在所有条件下为所有的输出赋值(参见Problem 31),导致打很多字,使代码变得冗长。 一个简单的方法是在 case 语句之前为输出分配一个“默认值”,除非 case 语句覆盖赋值,否则这种代码样式可确保在所有可能的情况下输出 0。 此时 case 的 default 项变得不必要,综合器会报一个 case 语句没有 default 的 warning。

// 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
        left = 1'b0;down = 1'b0;right = 1'b0;up = 1'b0;
        case (scancode)
        16'he06b: begin left = 1'b1; end
        16'he072: begin down = 1'b1; end
        16'he074: begin right = 1'b1; end
        16'he075: begin up = 1'b1; end
    endcase
    end
    

endmodule
Timing Diagram:

image-20211128164100480


PS:天气是阴沉的,房间内没有阳光射入,好疲惫。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值