Verilog連續賦值、過程賦值、過程連續賦值總結

NullBeer發表於2024-07-28

最近總是遇到systemverilog的賦值問題,檢視了一下手冊發現SV的賦值方式總的還是繼承了verilog的賦值方式,而且verilog賦值方面的資料比較多,所以就寫了先寫一篇關於verilog的賦值總結。

連續賦值

連續賦值就是一旦賦值,輸出將隨輸入改變而變化,一旦修改輸入則立刻體現在輸出上。

input a,b;
wire out;

assign out = a & b;
  • 要求賦值左側必須為net型
  • 關鍵詞assign
  • 賦值過程類似於物理場景的導線連線,out將跟隨a異或b的電路變化而變化,一旦a或b有修改則立刻體現在輸出上。
  • 連續賦值屬於併發執行,與書寫順序無關,不論在何時賦值都會在一開始執行。
  • 對於同一引數多次賦值,視情況和強度而定,但不要進行該操作。
  • 常用於組合邏輯

過程賦值

過程賦值只會在賦值時刻進行改變,其餘時間保持不變,而且根據使用的賦值符號=<=,分為阻塞賦值和非阻塞賦值。

阻塞賦值 =

//例1
initial begin
      ai            = 4'd1 ; 	  	//(1)
      ai            = 4'd2 ;   		//(2)
      //ai 最此刻為 2
      #20 ;                    		//(3)
      ai            = 4'd3 ;     	//(4)
      bi            = 4'd4 ;     	//(5)
      value_blk     = ai + bi ;  	//(6)
      //value_blk 最終為7
        #40;
    end
  • 要求賦值左側必須為reg、integer, real, time-variable, or memory(等非net型);
  • 賦值過程在initial或always塊中;
  • 阻塞賦值類似於C語言的賦值方式,後面的賦值會覆蓋掉前面的賦值,執行順序為1->2->...->6,類似軟體程式設計的賦值方式,隨時賦值隨時開始。
  • 最終結果與書寫順序相關.
  • 常用於testbench測試用例/組合邏輯.

非阻塞賦值 =>

//例1
initial begin
      ai            = 4'd1 ; 	  	//(1)
      bi            = 4'd2 ;   		//(2)
      //ai和bi的初始值分別為1,2
      #20 ;                    		//(3)
      //非阻塞賦值
      ai            <= 4'd3 ;     	//(4)
      bi            <= 4'd4 ;     	//(5)
      value_nonblk     <= ai + bi ;  	//(6)
      #40;
      //value_blk 最終為3
end

image

  • 要求賦值左側必須為reg型
  • 關鍵詞為initial或always
  • 非阻塞賦值類似於物理電路中的時序電路,其中可以這樣理解程式碼執行順序,1->2->3之後其他的非阻塞賦值(4)(5)(6)不著急執行,而是列入到"事件佇列"中,一直存到#40需要被執行前,即下一個時刻需要執行前,(4)(5)(6)將會被同時執行,此時對於value_nonblk而言其對應的ai和bi依舊是1和2,因此結果為3,可用於時序電路建模。
  • 最終結果與書寫順序相關
  • 常用於時序邏輯

下面使用阻塞賦值和非阻塞分別描述構成的組合邏輯和時序邏輯。

module top_module(
input a, output reg out1, output reg out2);
always@(*)	begin
		out1 = a;       //a的值直接到out1
    out2 = out1;    //當前out1直接賦給out2
end                 //也就是說a的值直接out2
endmodule

阻塞賦值在always(*)塊下,直接綜合時形成了如下的組合邏輯電路。
image

module top_module(
input a, input clk, output reg out1, output reg out2);
always@(posedge clk)	begin
		out1 <= a;       //a的值賦給out1
    out2 <= out1;    //此時左側的out1並不是a的大小,而是上一週期的值,因此這時是將上一週期的out1的值賦給out2
end                                                  
endmodule           

而非阻塞賦值在always(posedge clk)塊下,在綜合時形成了如下時序電路。
image

⚠️注意
雖然阻塞賦值always@(*) out_block = a & b;和連續賦值assign out = a & b;賦值方式不同,而且左側採用的分別為reg型別和net型別,但最終綜合出來卻是一樣的組合邏輯電路,都不會出現暫存器,這說明宣告的型別與合成的硬體無關。
同樣賦值過程並完全等同於硬體實現過程 always@(posedge clk) a <= 1;always@(posedge clk) a = 1; 兩者也並沒有區別,a作為輸出都有暫存器的出現,也就是賦值方式與硬體實現過程並不相同這主要是Verilog語法的歷史遺留問題, 只採用宣告的型別和賦值方式很難區分,而systemverilog乾脆直接用logic代替了wire和reg兩種型別,對宣告的型別不做區分【難辦,難辦就別辦了~~】,因此瞭解綜合後的硬體電路,要重點關注語法本身是組合還是時序。

因此作為一個ICer,不能為了炫技而採用各種花裡胡哨的技巧,而是採用一些原則來保證自己的程式碼可以更便於觀察。因此為了更清晰的描述出確定的電路, 在使用非阻塞與阻塞賦值時最好遵循以下原則:

  1. 時序電路建模時,使用非阻塞賦值 ;
  2. 鎖存器電路建模時,使用非阻塞賦值 ;
  3. 使用always塊寫組合邏輯電路時,採用阻塞賦值;
  4. 在同一個always塊中同時建立時序和組合邏輯電路時,用非阻塞賦值 ;
  5. 在同一個always塊中不要同時使用非阻塞賦值和阻塞賦值 ;
  6. 不要在多個always塊中,為一個變數賦值;
  7. 用$strobe系統任務來顯示用非阻塞賦值的變數值;
  8. 在賦值時不要使用#0延遲【有時間了再去聊原因】;

遵循以上邏輯可以消除90%-100%的在模擬中產生的競爭冒險現象,有助於正確編寫可綜合硬體。
image


過程連續賦值

連續賦值透過assign語句驅動net型別變數,而過程賦值透過initial和always塊驅動reg型別的變數。這兩種驅動方式基本上可以構成常用的電路,但verilog又給出了一種賦值方式,即過程連續賦值,可以透過覆蓋現有的賦值,並在一定時期內驅動net、reg資料變數。
主要有兩對過程連續賦值:assign-deassignforce-release。但這種賦值方式最好不要用於設計,會使得程式碼難以理解,更多用於testbench,在測試時,為出現對應結果,將設計中某些必要的訊號強制賦為期待值。

assign-deassign

assign透過在有一段時間內覆蓋掉現有的過程賦值控制的reg變數的值。並在執行deassign之後,就會釋放掉過程連續賦值,但可以保持一段時間,直到其他賦值進行修改。

module assign_deassign_ex;
  reg [3:0] d1;
  initial begin
    $monitor("At time T = %0t: d1 = %0d", $time, d1);
    d1 = 5;
    #20 d1 = 7;
  end
  
  initial begin
    #5;
    assign d1 = 3;	//直接覆蓋掉d1從5->3
    #5 deassign d1; //d1釋放之後,d1維持一段時間,在#20時刻從5變為7
    $display("At time T = %0t: deassign d1", $time);
  end
endmodule

//輸出結果
At time T = 0: d1 = 5
At time T = 5: d1 = 3
At time T = 10: deassign d1
At time T = 20: d1 = 7

force-release

force 和 release 語句透過在一段時間內覆蓋現有的過程、連續或過程連續賦值來控制 net 和 reg 資料型別變數值,force的許可權比assign要高,可以覆蓋掉其他的賦值方式。release同deassign一樣,可以釋放掉賦值,但對於過程賦值和過程連續賦值前可以保持前一個值。而對於net型別,直接恢復到前一個連續賦值的值。

module assign_deassign_ex;
  reg [3:0] d1;
  wire [3:0] d2;
  
  assign d2 = 2;
  initial begin
    $monitor("At time T = %0t: d1 = %0d, d2 = %0d", $time, d1, d2);
    d1 = 5;
    #20 d1 = 7;
  end
  
  initial begin
    #5;
    $display("At time T = %0t: force d1 and d2", $time);
    force d1 = 3;		//強制將d1賦值為3
    force d2 = 4;		//d2賦值為4
    #5 release d1;	//d1可以保持為3,一直到#20恢復為7
    release d2;			//d2一旦釋放則恢復到2
    $display("At time T = %0t: release d1 and d2", $time);
  end
endmodule

//輸出
At time T = 0: d1 = 5, d2 = 2
At time T = 5: force d1 and d2
At time T = 5: d1 = 3, d2 = 4
At time T = 10: release d1 and d2
At time T = 10: d1 = 3, d2 = 2
At time T = 20: d1 = 7, d2 = 2

參考文獻
[1] Procedural continuous assignments - VLSI Verify-教程類
[2] Verilog Assignments-教程類
[3] Verilog 過程連續賦值-教程類
[4] How does SystemVerilog force work? -問答類

相關文章