HDLBits中文版,标准参考答案 | 2 Verilog Language 2.4 Procedures 过程块

关注 望森FPGA  查看更多FPGA资讯

这是望森的第 6 期分享

作者 | 望森
来源 | 望森FPGA

目录

1 Always blocks (combinational) | Always过程块(组合逻辑)

2 Always blocks:(clocked) | Always过程块(时序逻辑)

3 lf statement | If语句

4 lf statement latches | If 语句闩锁

5 Case statement | Case语句

6 Priority encoder | 优先编码器

7 Priority encoder with casez | Casez 实现的优先编码器

8 Avoiding latches | 避免闩锁


文中的代码都能够正常运行,请放心食用😋~

练习的官方网站是:https://hdlbits.01xz.net/

注:作者将每个练习的知识点都放在了题目和答案之后


过程块包括always, initial, task, and function块。过程块允许使用顺序语句(不能在程序之外使用)来描述电路的行为。


1 Always blocks (combinational) | Always过程块(组合逻辑)

题目:

请使用assign赋值语句以及组合always块两种方法分别构建一个 AND 门。

答案:

// 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

知识点:

由于数字电路由通过电线连接的逻辑门组成,因此任何电路都可以表示为模块和赋值语句的某种组合。但是,有时这并不是描述电路最方便的方式。过程块(其中一个例子是 always 块)提供了描述电路的另一种语法。

对于综合硬件,有两种类型的 always 块是相关的:

  • 组合逻辑:always @(*)

  • 时序逻辑:always @(posedge clk)

组合 always 块等同于赋值语句,因此总有一种方式可以以两种方式表达组合电路。选择使用哪种方式主要取决于哪种语法更方便。过程块内部的代码语法与外部的代码不同。过程块具有更丰富的语句集(例如,if-then、case),不能包含连续赋值*。(*程序连续赋值确实存在,但与连续赋值略有不同,并且不可综合。)

例如,assign 和组合 always 块描述相同的电路。两者都创建相同的组合逻辑块。每当任何输入(右侧)的值发生变化时,两者都会重新计算输出。

assign out1 = a & b | c ^ d;

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

对于组合式 always 块,始终使用敏感度列表 (*)。明确列出信号容易出错(如果遗漏了一个信号),并且会被忽略以进行硬件综合。

关于 wire 与 reg 的说明:assign 语句的左侧必须是网络类型(例如 wire),而过程赋值(在 always 块中)的左侧必须是变量类型(例如 reg)。这些类型(wire 与 reg)与合成的硬件无关,只是 Verilog 作为硬件模拟语言使用时遗留下来的语法。


2 Always blocks:(clocked) | Always过程块(时序逻辑)

题目:

请使用赋值语句、组合 always 块和时钟 always 块三种方式构建 XOR 门。

请注意,时钟 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 statement
    assign out_assign = a ^ b;
    
    //combinational always block
    always@(*)begin
        out_always_comb = a ^ b;
    end
    
    //clocked always block
    always@(posedge clk)begin
        out_always_ff <= a ^ b;
    end
    
endmodule

知识点:

对于硬件综合,有两种类型的always块:

  • 组合逻辑:always @(*)

  • 时序逻辑:always @(posedge clk)

时序always块与组合always块一样,会创建组合逻辑块,但也会在该组合逻辑块的输出端创建一组触发器(或“寄存器”)。逻辑块的输出并非立即可见,而是在下一个(posedge clk)之后立即可见。

阻塞与非阻塞赋值

Verilog 中有三种类型的赋值:

  • 连续赋值(assign x = y;)。只能在不在程序内部时使用(“始终阻塞”)。

  • 程序阻塞赋值:(x = y;)。只能在程序内部使用。

  • 程序非阻塞赋值:(x <= y;)。只能在程序内部使用。

在组合式 Always 块中,使用阻塞分配。在时钟式 Always 块中,使用非阻塞分配。


3 lf statement | If语句

题目:

构建一个 2 对 1 多路复用器,在 a 和 b 之间进行选择。
如果 sel_b1 和 sel_b2 都为真,则选择 b。否则,选择 a。
执行两次相同的操作,一次使用赋值语句,一次使用过程 if 语句。

答案:

// 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
    assign out_assign = (sel_b1 & sel_b2) ? b : a;
    //always
    always@(*)begin
        if (sel_b1 & sel_b2)
            out_always = b;
        else
            out_always = a;
    end
    
endmodule

知识点:

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

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

这相当于使用带有条件运算符的连续赋值:
assign out = (condition) ? x : y;
但是,程序性 if 语句提供了一种新的出错方式。只有当 out 始终被赋值时,电路才是组合的。
(意味着每个if都需要写else)


4 lf statement latches | If 语句闩锁

题目:

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

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

知识点:

常见错误来源:如何避免制作闩锁

设计电路时,你必须首先从电路的角度来思考:

  • 我想要这个逻辑门

  • 我想要一个具有这些输入并产生这些输出的组合逻辑块

  • 我想要一个组合逻辑块,后面跟着一组触发器

你一定不能做的是先写代码,然后希望它能生成正确的电路。语法正确的代码不一定会产生合理的电路(组合逻辑 + 触发器)。

通常的原因是:“除了您指定的情况之外,会发生什么情况?”。Verilog 的答案是:保持输出不变。

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


5 Case statement | Case语句

题目:

当有大量案例时,case 语句比 if 语句更方便。

因此,在本练习中,用case 语句创建一个 6 对 1 多路复用器。当 sel 介于 0 和 5 之间时,选择相应的数据输入。否则,输出 0。数据输入和输出都是 4 位宽。

请勿设计出锁存器(参见 .always_if2)

答案:

// 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'd0 : out = data0;
            3'd1 : out = data1;
            3'd2 : out = data2;
            3'd3 : out = data3;
            3'd4 : out = data4;
            3'd5 : out = data5;
            default out = 0;
        endcase
    end
 
endmodule

知识点:

Verilog 中的 Case 语句几乎等同于将一个表达式与一系列其他表达式进行比较的 if-else if-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 项”以冒号结尾。没有“switch”。

  • 每个 case 项只能执行一个语句。这使得 C 中使用的“break”变得没有必要。但这意味着如果您需要多个语句,则必须使用 begin ... end。

  • 允许重复(和部分重叠)的 case 项。使用第一个匹配的项。C 不允许重复的 case 项。


6 Priority encoder | 优先编码器

题目:

优先级编码器是一种组合电路,当给定一个输入位向量时,它会输出向量中第一个 1 bit的位置。
例如,给定输入 8'b10010000 的 8 位优先级编码器将输出 3'd4,因为 bit[4] 是第一个高位。
请构建一个 4 位优先级编码器,如果没有一个输入位是高位(即输入为零),则输出零。

注意,4 位数有 16 种可能的组合。

答案:

// 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
        casex(in)
            4'bxxx1 :         pos = 2'd0;
            4'bxx10 :         pos = 2'd1;
            4'bx100 :         pos = 2'd2;
            4'b1000 :         pos = 2'd3;
            default         pos = 2'd0;
        endcase
    end
    
endmodule

知识点:

错解分析:【本题逻辑为关注第一个1以及其低位的0,忽略高位数据】
// 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
        casex(in)
            4'b0001 :         pos = 2'd0;
            4'b001x :         pos = 2'd1;
            4'b01xx :         pos = 2'd2;
            4'b1xxx :         pos = 2'd3;
            default         pos = 2'd0;
        endcase
    end
    
endmodule

7 Priority encoder with casez | Casez 实现的优先编码器

题目:

为 8 位输入构建优先级编码器。
给定一个 8 位向量,输出应报告向量中第一个(最低有效)位为 1。如果输入向量没有高位,则报告零。

例如,输入 8'b10010000 应输出 3'd4,因为 bit[4] 是第一个高位。

答案:

// synthesis verilog_input_version verilog_2001
module top_module (
    input [7:0] in,
    output reg [2:0] pos );
 
    always@(*) begin  // This is a combinational circuit
        casez(in)
            8'bzz_zzz_zz1 :         pos = 3'd0;
            8'bzz_zzz_z10 :         pos = 3'd1;
            8'bzz_zzz_100 :         pos = 3'd2;
            8'bzz_zz1_000 :         pos = 3'd3;
            8'bzz_z10_000 :         pos = 3'd4;
            8'bzz_100_000 :         pos = 3'd5;
            8'bz1_000_000 :         pos = 3'd6;
            8'b10_000_000 :         pos = 3'd7;
            default                         pos = 3'd0;
        endcase
    end
    
endmodule

知识点:

从上一个练习(always_case2)来看,case 语句中有 256 个案例。如果 case 语句中的 case 项支持无关位,我们可以将其减少(减少到 9 个案例)。这就是 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 项目。第一个匹配项被选中(因此 4'b1111 匹配第一个项目,out = 0,但不匹配任何后续项目)。

  • 还有一个类似的 casex,它将 x 和 z 都视为无关项,两者在使用时区别不大。

  • 字符 ? 是 z 的同义词。因此 2'bz0 与 2'b?0 相同

明确指定优先级行为可能比依赖案例项的排序更不容易出错。例如,如果对某些案例项进行了重新排序,以下行为仍将以相同的方式运行,因为任何位模式最多只能匹配一个案例项:

casez (in[3:0])
4'bzzz1: ...
4'bzz10: ...
4'bz100: ...
4'b1000: ...
default: ...
endcase


8 Avoiding latches | 避免闩锁

题目:

假设您正在构建一个电路来处理 PS/2 键盘的扫描码,用于游戏。根据收到的扫描码的最后两个字节,您需要指示键盘上的某个箭头键是否被按下。这涉及一个相当简单的映射,可以将其实现为包含四个 case 的 case 语句(或 if-elseif)。

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

答案:

// 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  // This is a combinational circuit
        up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
        case(scancode)
            16'he06b :         left         = 1'd1;
            16'he072 :         down         = 1'd1;
            16'he074 :         right         = 1'd1;
            16'he075 :         up                 = 1'd1;
        endcase
    end
    
endmodule

知识点:

为了避免创建锁存器,必须在所有可能的条件下为所有输出分配一个值(另请参阅 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
end

这种代码样式可确保在所有可能的情况下为输出分配一个值(0),除非 case 语句覆盖了赋值。这也意味着 default: case 项变得没有必要。


- END -

微信公众号/CSDN/EETOP搜索【望森FPGA】,查看更多FPGA资讯~

相关推荐文章,点击跳转:

FPGA新手必用,Verilog HDL编程学习网站推荐 —— HDLBits
Getting Started| HDLBits启航篇!
HDLBits中文版,标准参考答案 | 2 Verilog Language 2.2 Vectors

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值