关于verilog里阻塞与非阻塞赋值的个人理解

        最近在做数字的东西,因此一直在学习verilog的语法,看的是夏宇闻老师的《verilog数字系统设计教程》这本书,在看到第14章深入理解阻塞与非阻塞赋值的不同时,结合书后面的誓言RISC_CPU,关于时序问题,产生了一些疑问,因此写了一个简单的程序,探索一下相关的内容,文笔拙劣,理解也并不完全正确,想写出来与大家分享一下,希望能够得到一些指点。

先引用书上的两个例子:

1.采用阻塞赋值,不能自行触发的振荡器

module osc(clk);
  output clk;
  reg clk;
  initial #10 clk = 0;
  always @(clk)
    begin
      $display("At%tns, be excuted.", $time);//just for test.
      #10;
      clk = ~clk;
    end
endmodule

        在initial块中,经过10个单位的延迟,clk被立即阻塞赋值为0。当clk电平从不定态变为0的事件发生时,使always块的@(clk)条件触发,经过10个单位时间的延迟,计算RHS表达式(~clk)得到1,并立即更新LHS的值,clk立即被赋予1。由于在此期间不允许其他语句的干扰,即使always循环回到判断触发条件@(clk),由于此时clk电平已经为1,无法感知从0到1曾经发生过的变化,所以就阻塞在那里,只有等待clk变为0才能进入该always块,因此,这是一个不能自触发的振荡器,不能产生时钟波形。

2. 采用非阻塞赋值的自触发振荡器

module osc(clk);
  output clk;
  reg clk;
  initial #10 clk = 0;
  always @(clk)
    begin
      $display("At%tns, be excuted.", $time);//just for test.
      #10;
      clk <= ~clk;
    end
endmodule

        与上例相比,只是赋值方式有"="变为"<="。@(clk)的第一次触发之后,非阻塞赋值的RHS表达式便计算出来,并把值赋给LHS的事件安排在更新事件队列中。在非阻塞赋值更新事件队列被激活之前,又遇到@(clk)触发语句,并且always块再次对clk的变化产生反应。当非阻塞LHS的值在同一时刻被更新时,@(clk)再一次触发。该例是自触发方式,虽例中的代码能产生周期时钟信号,但在编写仿真测试模块时,不推荐该写法。
 

关于阻塞与非阻塞的实践:

1. 一个简单的时钟分频模块

module delayornot(fetch, clk, rst);
  output fetch;
  input clk, rst;
  reg fetch;
  integer i;
  always @(posedge clk or negedge rst)
    begin
      if (!rst)
        begin
          fetch <= 0;
          i <= 0;
        end
      else
        begin
          if (i == 4)// divided by 5.
            begin
              fetch <= ~fetch;
              i <= 0;
            end
          else
            i <= i+1;
        end
    end 
endmodule

2. 测试模块

module delay_tb();

  wire fetch;
  reg clk, rst;
  reg [3:0] num1;
  reg [3:0] num2;
  reg [3:0] num3;
  
  initial
    begin
      num1 = 4'b0;
      num2 = 4'b0;
      num3 = 4'b0;
      clk = 0;
      rst = 1;
      #80;
      rst = 0;
      #80;
      rst = 1;
      #500;
    end
  
  always #40 clk = ~clk;
    
  delayornot mydelay(fetch, clk, rst);

  always @(posedge fetch)
    begin
      num1 <= num1 + 1;
    end
    
  always @(posedge fetch)
    begin
      if (fetch)
        num2 <= num2 + 1;
    end
    
 always @(posedge clk)
    begin
      $display("At%tns, be excuted.",$time);//just for test.
      if (fetch)
        begin
          num3 <= num3 + 1;
        end
    end

3. 仿真结果

        从仿真图中可以看到,delayornot模块实际上是一个5分频的模块,fetch信号采用非阻塞赋值方式。

        在当fetch信号上升沿到来时,由于num1和num2所在always块触发条件为fetch的上升沿,因此在fetch上升沿,触发了num1和num2所在的always块,因此各自加1。而num3所在的always块触发条件为clk信号的上升沿,注意图中fetch信号上升沿到来的位置。此时,clk信号为上升沿,因此触发了num3的always块。但注意,此时,由于fetch信号在其模块中为非阻塞赋值,即等fetch信号相应always块结束时,才进行赋值操作。因此,此时fetch信号为低电平,即if (fetch)不成立,因此num3未进行加1操作。num3在下一个clk上升沿时才加1,并且在fetch下降时来临时依然加1,原因类似。

       可能有人会疑问,那为什么num2中的if (fetch)就成立了呢?因为,num2所在always块检测的是fetch信号的上升沿,当其上升沿到来时,fetch已经为高电平,因此条件成立。verilog里很重要的一点是,若不加#10等延时命令,则指令执行往往有概念上的先后,而并无实质上的先后。通俗的讲,fetch信号上升沿到来时,num1和num2所在always块才被触发,而num3所在always块检测的是clk的上升沿,也就是说在fetch信号被赋值之前该always块就已经被触发执行了,因此与fetch信号的上升沿无关。

4. 修改num3所在always块代码

always @(*)
    begin
      $display("At%tns, be excuted.",$time);//just for test.
      if (fetch)
        begin
          num3 <= num3 + 1;
        end
    end

        always @(*)的作用与always @(fetch or num3)的作用一致。(*)里面的敏感变量由综合器根据always块里面的输入变量自动添加。

        此时,进行仿真,发现modelsim报错。# ** Error: (vsim-3601) Iteration limit reached at time 520 ns.

        根据$display语句在屏幕的输出发现,程序在520ns的时候一直在触发always块。从上面的波形图可以发现,520ns为fetch信号第一次出现上升沿的位置。此时,通过代码可以发现,fetch信号变化则触发该always块,并且if (fetch)成立,因此num3的值发生变化,进而又触发该always块,陷入死循环。


        如果把代码中的非阻塞赋值改为阻塞赋值,则波形图如图所示。为何不会出现死循环,因为它被阻塞了,原理参考于开篇所给出的采用阻塞赋值不能自行触发振荡器。

        此时由于触发条件为fetch信号或者num3的变化,所以num3与num1和num2同步。如果这里采用always @(posedge clk)以及阻塞赋值的话,则不会同步。

5. 针对出现死循环的问题

module osc(clk);
  output clk;
  reg clk;
  initial #10 clk = 0;
  always @(clk)
    begin
      $display("At%tns, be excuted.", $time);//just for test.
      //#10;
      clk <= ~clk;
    end
endmodule

        修改采用非阻塞赋值的自触发振荡器的代码,将#10注释掉。仿真发现modelsim报同样的错误。# ** Error: (vsim-3601) Iteration limit reached at time 10 ns.

        在同一时间,一直进入该always块,陷入死循环,因而报错。而加上#10延时命令,就可以避免该问题。
 

你可能感兴趣的:(SOC,verilog)