1. 程式人生 > >關於verilog裡阻塞與非阻塞賦值的個人理解

關於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延時命令,就可以避免該問題。