0%

Verilog之易错易忘知识点

本文主要记录在Verilog学习的过程中容易出错、容易遗忘的知识点,会不定期更新

组合逻辑易错点

1.always块语句

  • 在Verilog中可以使用两种方法描述组合逻辑:使用always组合块或assign语句

    • 当使用always组合块描述时,变量需声明为reg类型(此时必须用 = 赋值,否则综合后可能会出错)
    • 当使用assign语句描述时,变量需声明为wire类型
  • 在always块内部开始时为变量分配默认值,可以确保综合后不会生成锁存器

    • 以if_else为例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      reg [1 : 0] cnter, cnter_nxt;

      always @(*) begin
      //put all default values here
      cnter_nxt = cnter;
      //describe the equation in an algorithmic manner
      if(reset_cnter)
      cnter_nxt = 'd0;
      else if(cnter == 2'b11)
      cnter_nxt = 'd0;
      else
      cnter_nxt = cnter + 1'b1;
      end
    • 以case为例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      always @(*) begin
      state_nxt = state; //declare the default value in the beginning
      case(state)
      IDLE: begin
      if(start_write)
      start_nxt = WRITE;
      else if(start_read)
      start_nxt = READ;
      //else start_nxt = state; already covered in beginning
      end
      WRITE: begin
      if(end_write)
      state_nxt = IDLE;
      //else start_nxt = state; already covered in beginning
      end
      READ: begin
      if(end_read)
      state_nxt = IDLE;
      //else start_nxt = state; already covered in beginning
      end
      default: begin
      end
      endcase
      end

2.移位操作符

  • 算数左移<<

    1
    2
    wire [7 : 0] a, b, c;
    assign c = a << b; //将a向左移b位,低位用0填充
    • 在不发生溢出时,左移可以用来进行某些乘法运算,这里的溢出,指的是a最大能表示的范围,如果a左移后的值超过a能表示的最大范围,还用左移来代替乘法运算,就会出错!!!
    • 例如a = 8’b11000011(195),将a左移2位后(就是乘以4),c = 8’b00001100,最高两位就丢失了(因为195*4后是780,而8bit无符号数最多表示255,故会发生溢出)
  • 算数右移>>

    1
    2
    wire [7 : 0] a, b, c;
    assign c = a >> b; //将a向右移b位,高位用0填充

函数与任务

  • 函数function
    • 函数可以用来生成可综合的组合逻辑。当同一逻辑功能在多处使用时,可以采用Verilog函数来描述其功能,上层电路可以调用该函数,这样有利于提高代码的可读性和电路的可重用性
    • 函数内部所描述的逻辑功能是可综合的,综合后得到的是组合逻辑,其内部不能出现与时间控制相关的语句,如wait,@或 #

Verilog中的`ifdef

  • 在模块内部,设计者可以在ifdef和对应的else后面插入一些语句。综合工具进行RTL代码编译处理时,将首先判断是否对其后面的字符串进行了定义,如果进行了定义,那么将其后面的语句插入电路代码中,否则它将else后面的语句插入电路代码中。有时ifdef和else后面没有需要插入的语句

  • 下面是一个具体的例子:

    • 创建一个含有`define定义的头文件:chiptop_defines.vh

      1
      2
      //`define CHIP_XYZ_SEL1
      //`define CHIP_XYZ_SEL2
    • 使用`include语句包含.vh文件

      1
      `include "chiptop_defines.vh"
    • ifdef用法举例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      `ifdef CHIP_XYZ_SEL1
      assign d_nxt = a + b;
      assign e_nxt = a + c;
      `elsif CHIP_XYZ_SEL2
      assign d_nxt = a - b;
      assign e_nxt = a - c;
      `else
      assign d_nxt = a * b;
      assign e_nxt = a * c;
      `endif
      • 上面是一个具有多个条件编译分支的例子。如果在chiptop_defines.vh文件中对CHIP_XYZ_SEL1进行了定义,编译后将在电路代码中插入d_nxt = a + b;e_nxt = a + c;否则将判断是否定义了CHIP_XYZ_SEL2,如果没有定义,那么编译器将进入最后一个分支。
      • 注意:是``elsif,不是else if(这么写会报错)【《Verilog高级数字系统设计计数与实例分析》这本书咋这么多错误,还得自己敲敲代码】
    • 仿真结果如下:

      image-20240517164526920

  • `ifdef的用法与mux语句的区别:

    • 在一个mux语句中,不同的分支都会出现在综合后的网表中,根据select信号(选择控制信号)的值决定执行其中的一条路路径
    • 在`ifdef中,只有某一个分支对应的代码会在网表中实际出现
    • 如果设计者需要在芯片中保留两条路径,并且可以根据需要进行选择,那么应使用mux而不是`ifdef

用于验证的Verilog语法

1.$random

  • $random可以产生一个有符号的32bit随机整数,一般的用法为:

    1
    num = $random % b;
    • num的范围在-(b-1)~(b-1)的随机数
  • 产生随机正整数:

    1
    num = {$random} % b;
    • num的范围在0~(b-1)中的随机数
  • 产生一个在min,max之间随机数

    1
    num = min + {$random} % (max - min + 1);

2.while循环

  • while循环语句在执行时,只要其后的表达式为真,则循环执行后面对应的语句。当表达式的值为假时,退出该循环

  • 在while循环内,建议加入与定时控制有关的语句,以免形成定时环路,造成死循环

  • 下面是一个while循环的例子,在这段代码中,需要从某个基地址开始,按照一定的地址增量产生新的地址对存储器进行访问,当地址大于预先设定的最大地址时,跳出while循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    module while_tb;

    parameter INIT_ADDR = 32'h0000_0020,
    MAX_ADDR = 32'h0000_003C,
    ADDR_INCR = 8'h04;

    reg clk;
    reg [32 : 0] mem_addr;

    always #5 clk = ~clk;

    initial begin
    clk = 1;
    end

    initial begin
    mem_addr = INIT_ADDR;
    while(mem_addr <= MAX_ADDR) begin
    @(posedge clk);
    //---do the task here
    //---end of task
    $display("memory address = %h", mem_addr);
    mem_addr = mem_addr + ADDR_INCR;
    end
    end

    endmodule
    • 仿真结果:

      image-20240527110056644

多个if综合效果

  • (20240604)附:第一次知道原来一个always中多个if并行,编译器不会编译成多个mux并行(针对在多个if中对同一个变量赋值的情况),还是级联的mux,最后一个if的优先级最高(越靠近输出)(在我另一篇文章中也有记录FPGA数字信号处理之高速度结构设计 | ssy的小天地 (ssy1938010014.github.io)

  • 可参考:Verilog中单if语句、多if语句和case语句与优先级的关系_verilog if else if 执行顺序-CSDN博客

  • 我当时是测试了这样一段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    always @(posedge clk) begin
    begin
    if(cfar_flag[0]) begin
    object <= 1'b1;
    end
    if(cfar_flag[1]) begin
    object <= 1'b1;
    end
    if(cfar_flag[2]) begin
    object <= 1'b1;
    end
    if(cfar_flag[3]) begin
    object <= 1'b1;
    end
    if(cfar_flag[4]) begin
    object <= 1'b1;
    end
    if(cfar_flag[5]) begin
    object <= 1'b1;
    end
    if(cfar_flag[6]) begin
    object <= 1'b1;
    end
    if(cfar_flag[7]) begin
    object <= 1'b1;
    end
    end
    end
  • 综合结果:(圈起来的优先级最高cfar_flag[7])

    image-20240604111607659

  • 如果把if放在不同always中,那么就会综合出并行的效果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    genvar i;
    generate
    for(i = 0; i < 8; i = i + 1) begin
    always @(posedge clk) begin
    if(cfar_flag[i]) begin
    object <= 2'd2;
    end
    end
    end
    endgenerate
  • 综合结果:

    image-20240604114309065
  • 但这种方式综合会报CW(critical warning),布线会报错

    image-20240604120117359


ILA与总线模式

  • 若测试接口用总线模式输出,此时若ila单独一根一根线拉出去观察,就会把之前总线覆盖,所以此时ila也要用总线模式

    • 错误的:

      image-20240624134811042
    • 正确的:

      image-20240624134844215
欢迎来到ssy的世界