Verilog基础学习二

Verilog基础学习二



一、always 块

我们知道数字电路是由导线连接的逻辑门组成,因此任何电路都可以表示为moduleassign语句的某种组合。但是,有时候这不是描述电路最简便的方法。过程块(比如always块)提供了一种用于替代assign语句描述电路的方法。

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

综合逻辑:always @(*)
时序逻辑:always @(posedge clk)

1.阻塞性赋值和非阻塞性赋值

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

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

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

示例代码如下:

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

二、条件语句

1.if 语句 基本用法

基本if语句:

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

条件(三元)运算符:

assign out = (condition) ? x : y;

condition ? if_true : if_false

2.避免引入锁存器

组合电路输出必须在所有输入的情况下都有值。这意味着必须需要else子句或着输出默认值。

3.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项目

4.casez 语句

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

还有一个类似的casex,将输入的x和z都视为无关。不认为casex比casez有什么特别的好处。
(作者个人感觉没必要用casex,z和x状态的问题涉及电路的基本知识)
符号"?" 是z的同义词,所以2’bz0与2’b?0相同。

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语句之前为输出分配一个“默认值”

除非case语句覆盖赋值,否则这种代码样式可确保在所有可能的情况下输出0。 这也意味着case的default项变得不必要。

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

三、归约运算符(Reduction Operators)

归约运算符(Reduction Operators)可以对向量的每一位位进行AND,OR和XOR,产生一位输出:

&a [30] // AND:a[3]&a[2]&a[1]&a [0]相当于(a[3:0]== 4'hf)
|b [30] // OR: b[3]|b[2]|b[1]|b [0]相当于(b[3:0]!= 4'h0)
^c [20] // XOR:c[2]^c[1]^c[0]

四、for循环

for循环(组合always块或者generate块)在处理重复动作的时候很有用。

模块实例化时必须使用generate块

 always@(*)begin
        for (int i=0;i<=99;i=i+1)begin
            out[i]=in[99-i];
        end
    end

Problem : Combinational for-loop: 255-bit population count

设计电路来计算输入矢量中 ’1‘ 的个数,题目要求建立一个255bit输入的矢量来判断输入中 ’1‘ 的个数。

module top_module( 
    input [254:0] in,
    output [7:0] out );

    integer i;
    always @ (*)
        begin
            out = 8'b0000_0000;     //**为了后面的计数累加,此处先初始化为0.**
            for (i=0; i<255; i++)
                begin
                    if(in[i] == 1'b1)
                        out = out + 1'b1;
                    else
                        out = out + 1'b0;
                end
        end
endmodule
  • 循环里面的变量一定要初始化

Problem : Generate for-loop: 100-bit binary adder 2

通过实例化100个全加器来实现一个100bit的二进制加法器。该加法器有两个100bit的输入和cin,输出为sum与cout。为了鼓励大家使用实例化来完成电路设计,我们同时需要输出每个全加器的cout。
故cout[99]标志着全加器的最终进位。

  • ​ 有好多加法器需要实例化,可采用实例化数组或generate语句来实现。

​ 考虑到for循环中只有cin与cout是变化的,每次计算中cout是本次计算的输出,也是下次计算的输入(cout就是下次计算的cin)。故我们先计算出cout[0]sum[0]

assign cout[0] = a[0] & b[0] | a[0] & cin | b[0] & cin;
assign sum[0]  = a[0] ^ b[0] ^ cin;
//一定要记得赋初值,然后再开始循环

代码如下

module top_module( 
    input [99:0] a, b,
    input cin,
    output [99:0] cout,
    output [99:0] sum );

    integer i;
    always @(*) begin 
        cout[0] = a[0]&b[0] | a[0]&cin | b[0]&cin;
    	sum[0] = a[0] ^ b[0] ^ cin;
        
        for(i=1;i<100;i++) begin
            sum[i] = a[i] ^ b[i] ^ cout[i-1];
            cout[i] = a[i]&b[i] | a[i]&cout[i-1] | b[i]&cout[i-1];
        end
    end
endmodule

五、Generate 块

什么是generate语句?

​ 生成语句可以动态的生成verilog代码,当对矢量中的多个位进行重复操作时,或者当进行多个模块的实例引用的重复操作时,或者根据参数的定义来确定程序中是否应该包含某段Verilog代码的时候,使用生成语句能大大简化程序的编写过程。

​ 使用关键字generateendgenerate来指定范围。generate语句有generate-for、generate-if、generate-case三种语句,本题中我们使用generate-for语句。

​ generate-for语句:

(1) 必须有genvar关键字定义for语句的变量。

(2)for语句的内容必须加begin和end**(即使就一句)**。

(3)for语句必须有个名字

//创建一个2进制转换器

Module gray2bin
#(parameter SIZE = 8)
(
  input [SIZE-1:0] gray,
  output [SIZE-1:0] bin
)

Genvar gi;  //在generate语句中采用genvar声明
    generate 
  for (gi=0; gi<SIZE; gi=gi+1) 
      begin : genbit    //for语句必须有名字
        assign bin[i] = ^gray[SIZE-1:gi];
      end
    endgenerate 
endmodule

Problem : Generate for-loop: 100-digit BCD adder

​ 本题已经提供了一个名为bcd_fadd的BCD一位全加器,他会添加两个BCD码和一个cin,并产生一个cout和sum。
每个bcd_fadd都是含有两个四位的输入(a,b),一个四位的输出(sum)

module bcd_fadd {
    input [3:0] a,
    input [3:0] b,
    input     cin,
    output   cout,
    output [3:0] sum );

我们需要实例化100个bcd_fadd来实现100位的BCD进位加法器。该加法器应包含两个100bit的BCD码(包含在400bit的矢量中)和一个cin,
输出产生sum 和 cout。

module top_module( 
    input [399:0] a, b,
    input cin,
    output cout,
    output [399:0] sum );

    wire [399:0] cout_temp;
    genvar i;

    bcd_fadd inst1_bcd_fadd (
        .a(a[3:0]),
        .b(b[3:0]),
        .cin(cin),
        .cout(cout_temp[0]),
        .sum(sum[3:0])
    );
    //与上题同理,还是先计算cout[0],我声明一个wire型的cout_temp来存放每次计算后cout的值。
    //类似矢量运算中赋初值,实例的初始化也是必不可少的
    
    generate
        for(i=4; i<400; i=i+4)
            begin: bcd
                bcd_fadd inst_bcd_fadd(
                    .a(a[i+3:i]), 
                    .b(b[i+3:i]), 
                    .cin(cout_temp[i-4]), //上次计算输出的cout
                    .cout(cout_temp[i]), //本次计算输出的cout,在下次计算中变为cin
                    .sum(sum[i+3:i])
                );
            end
    endgenerate 

    assign cout = cout_temp[396];   

endmodule

总结

本次学习了always 块的用法,包含里面的if、case、casez、for语句的具体用法;学习了归约运算符(Reduction Operators)的操作,简化打字量;最后学习了Generate 块的用法,今后在实践中具体操作会有更深刻的理解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值