LJgibbs · 2020年09月24日

HDLBits:在线学习Verilog(七 · Problem 30-34)

转载自:知乎
本系列文章将和读者一起巡礼数字逻辑在线学习网站 HDLBits 的教程与习题,并附上解答和一些作者个人的理解,相信无论是想 7 分钟精通 Verilog,还是对 Verilog 和数电知识查漏补缺的同学,都能从中有所收获。

附上传送门:Always if - HDLBits

Problem 30: If statement(Always if)

if语句通常对应一个二选一多路复用器,如果条件为真,则选择其中一个输入作为输出;反之如果条件为假,则选择另一个输入所谓输出。if语句必须在过程块内使用。

下面给出了一个基本的if语句和其综合出来的电路。

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

这与下面使用条件运算符连续赋值的语句是等价的:

assign out = (condition) ? x : y;

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

牛刀小试

构建一个可以在a和b之间选择的二选一多路复用器。如果sel\_b1和sel\_b2都为真,输出b,其他情况输出a。请使用两种方法作答,一次使用assign赋值,一次使用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 out_assign = sel_b1? sel_b2? b:a :a;

    always@(*)
        if(sel_b1&sel_b2)
            out_always = b;
        else
            out_always = a;

endmodule

Problem 31: If statement latches(Always if2)

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

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

1、我想实现一个逻辑门;

2、我想实现一个具有输入并产生输出的组合逻辑块;

3、我想实现一组组合逻辑,紧接着一组触发器。

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

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

除了你指定的情况以外,会发生些什么,答案是什么也不会发生,输出保持不变。而这往往就导致了电路的错误,所以说语法正确的代码不一定能产生合理的电路(组合逻辑+触发器)。

输出保持不变,这就意味着电路需要记住当前状态,从而产生锁存器。组合逻辑(比如逻辑门)不能记住任何状态。

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

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

牛刀小试

示例:以下代码包含生成锁存器的错误,请勿模仿!!! 修复错误,只有当它真的过热时才关闭计算机,真的到达目的地或者需要加油时,才停止驾驶。

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

1、case语句以case开头,每个case项以冒号结束。而switch语句没有。

2、每个case项只执行一个语句。 这样就不需要C语言中break来跳出switch。但这也意味着如果您需要多个语句,则必须使用begin ... end。

3、case项允许重复和部分重叠,执行程序匹配到的第一个,而C语言不允许重复的case项目。

牛刀小试

如果存在大量的case项,则case语句比if语句更方便。 因此,在本练习中,创建一个6选1的多路复用器。当sel介于0和5之间时,选择相应的数据输入。 其他情况输出0。数据输入和输出均为4位宽。

注意:不要生成锁存器(详见:Problem 31: If statement latches(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'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;
        endcase
    end

endmodule

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

Problem 33: Priority encoder(Always case2)

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

牛刀小试

构建一个4位优先编码器。如果没有输入均为零,则输出零。请注意,4位数字有16种输入发的可能。

小提示:使用十六进制(4'hb)或十进制(4'd11)与二进制(4'b1011)相比可以节省打字量。

解答与分析

这是二进制编码的写法,看起来相对直观一点。

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

作者提供了16进制的编码的写法,他觉得这样打字打的少,2333333。

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

    always @(*) begin            // Combinational always block
        case (in)
            4'h0: pos = 2'h0;    // I like hexadecimal because it saves typing.
            4'h1: pos = 2'h0;
            4'h2: pos = 2'h1;
            4'h3: pos = 2'h0;
            4'h4: pos = 2'h2;
            4'h5: pos = 2'h0;
            4'h6: pos = 2'h1;
            4'h7: pos = 2'h0;
            4'h8: pos = 2'h3;
            4'h9: pos = 2'h0;
            4'ha: pos = 2'h1;
            4'hb: pos = 2'h0;
            4'hc: pos = 2'h2;
            4'hd: pos = 2'h0;
            4'he: pos = 2'h1;
            4'hf: pos = 2'h0;
            default: pos = 2'b0;    // Default case is not strictly necessary because all 16 combinations are covered.
        endcase
    end
    
endmodule

当然,有更简单的写法,那就看下一题吧。

不过还是先谈谈优先译码器,可能在学数字电路的时候优先译码器是左边第一个为0的数字,在本题中是右面第一个。两者都可以叫做优先译码器,出现的第一个数字把后面的数字屏蔽掉了,第一个数字具有较高的优先级,所以叫做优先译码器。

与优先译码器相对应的简单译码器,简单译码器的输入要求只能有一个1。出现多个1的时候视不同情况有不同的处理方式。

Problem 34: Priority encoder with casez(Always casez)

牛刀小试

构建一个8输入的优先编码器。给定一个8位向量,输出输入向量中左数第一个1的位置。如果输入均为0,则输出零。例如,输入8'b10010000应该输出3'd4,因为位[4]是第一个出现1的位置。

如果还按上一个练习(Problem 33: Priority encoder(Always case2))写case语句的话,case语句中将有256个case项。如果case语句中的case项与某些输入无关,就可以减少列出的case项(在本题中减少到9个)。这就是casez的用途:它在比较中将具有值z的位视为无关项(即输入01都会匹配到)。

例如:下面的代码就是上一个联系中的4输入优先编码器:

always @(*) begin
    casez (in[3:0])
        4'bzzz1: out = 0;   // in[3:1]输入什么都可以
        4'bzz1z: out = 1;
        4'bz1zz: out = 2;
        4'b1zzz: out = 3;
        default: out = 0;
    endcase
end

case项是按顺序检查的(实际上,它更像是生成一个巨大的真值表然后生成超大的门)。注意有输入(例如,4'b1111)匹配多个case项。选择第一个匹配(因此4'b1111匹配第一个case项,out = 0)。

还有一个类似的casex,将输入的x和z都视为无关。不认为casex比casez有什么特别的好处。(作者个人感觉没必要用casex,z和x状态的问题涉及电路的基本知识)

符号"?" 是z的同义词,所以2'bz0与2'b?0相同。

解答与分析

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

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

endmodule

嗯,用casez就方便多了。。。

推荐阅读

关注此系列,请关注专栏FPGA的逻辑
推荐阅读
关注数
10605
内容数
562
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息