Verilog 時序檢查

2022-05-20 14:45 更新

關(guān)鍵詞: setup hold recovery removal width period

指定路徑延遲,目的是讓仿真的時序更加接近實(shí)際數(shù)字電路的時序。利用時序約束對數(shù)字設(shè)計(jì)進(jìn)行時序仿真,檢查設(shè)計(jì)是否存在違反(violation)時序約束的地方,并加以修改,也是數(shù)字設(shè)計(jì)中不可或缺的過程。

Verilog 提供了一些系統(tǒng)任務(wù),用于時序檢查。這些系統(tǒng)任務(wù)只能在 specify 塊中調(diào)用。下面就介紹 6 種常用的用于時序檢查的系統(tǒng)任務(wù):$setup, $hold, $recovery, $removal, $width 與 $period。

$setup, $hold

系統(tǒng)任務(wù) $setup 用來檢查設(shè)計(jì)中元件的建立時間約束條件,$hold 用來檢查保持時間約束條件。其用法格式如下:

$setup(data_event, ref_event, setup_limit);

  • ?data_event?: 被檢查的信號,判斷它是否違反約束
  • ?ref_event?: 用于檢查的參考信號,一般為時鐘信號的跳變沿
  • ?setup_limit?: 設(shè)置的最小建立時間

如果 ?T( ref_event - data_event) < setup_limit?, 則會打印存在 ?violation ?的報告。

$hold(ref_event, data_event, hold_limit);

  • ?data_event?: 被檢查的信號,判斷它是否違反約束
  • ?ref_event?: 用于檢查的參考信號,一般為時鐘信號的跳變沿
  • ?hold_limit?: 設(shè)置的最小保持時間

如果 ?T( data_event - ref_event ) < hold_limit?, 則會打印存在 ?violation ?的報告。

注意: ?$setup? 和 ?$hold? 輸入端口的位置是不同的。

Verilog 提供了同時檢查建立時間和保持時間的系統(tǒng)任務(wù):

$setuphold (ref_event, data_event, setup_limit, hold_limit);

下面完成一個數(shù)乘以 15 的操作,來說明 ?$setup? 和 ?$hold? 的用法。

Verilog 中,一個變量乘以常數(shù)一般用移位相加的方法來完成,例如對變量 num 乘以 15 的操作可以表示為:

num x 15 = (num << 3) + (num << 2) + (num << 1) + num

此操作需要 3 個加法器。下面對加法器進(jìn)行建模,并指定路徑延遲。

全加器功能描述可參考《Verilog 教程》《Verilog 連續(xù)賦值》。

//單 bit 全加器,指定路徑延遲
module full_adder1(
   input   Ai, Bi, Ci,
   output  So, Co);

   assign So = Ai ^ Bi ^ Ci ;
   assign Co = (Ai & Bi) | (Ci & (Ai | Bi));

   specify
      (Ai, Bi, Ci *> So) = 1.1 ;
      (Ai, Bi     *> Co) = 1.3 ;
      (Ci         => Co) = 1.2 ;
   endspecify
endmodule

//8bit 位寬加法器例化
module full_adder8(
     input [7:0]   a ,   //adder1
     input [7:0]   b ,   //adder2
     input         c ,   //input carry bit
     output [7:0]  so ,  //adding result
     output        co    //output carry bit
     );

   wire [7:0]      co_temp ;
   full_adder1  u_adder0(
                         .Ai     (a[0]),
                         .Bi     (b[0]),
                         .Ci     (c==1'b1 ? 1'b1 : 1'b0),
                         .So     (so[0]),
                         .Co     (co_temp[0]));

   genvar          i ;
   generate
      for(i=1; i<=7; i=i+1) begin: adder_gen
         full_adder1  u_adder(
                              .Ai     (a[i]),
                              .Bi     (b[i]),
                              .Ci     (co_temp[i-1]),
                              .So     (so[i]),
                              .Co     (co_temp[i]));
      end
   endgenerate
   assign co    = co_temp[7] ;
endmodule

8bit 位寬的觸發(fā)器描述如下。觸發(fā)器中指定路徑延遲,并加入建立時間和保持時間的時序檢查。

建立時間設(shè)置為 2ns,保持時間設(shè)置為 3ns。

module D8(
    input       [7:0]   d ,
    input               clk ,
    output reg [7:0]    q);

   always @(posedge clk)
     q <= d ;
   specify
      $setup(d, posedge clk, 2);
      $hold(posedge clk, d, 3);
      (d,clk *> q) = 0.3 ;
   endspecify
endmodule

在 testbench 里完成乘以 15 的操作,并在一個周期內(nèi)輸出給下一級寄存器。

`timescale 1ns/1ns
module test ;
   reg  [3:0]   a ;
   reg  [3:0]   b ;
   wire [3:0]   so ;
   wire         co ;

   parameter    CYCLE_10NS = 10ns;
   reg          clk ;
   initial begin
      clk = 0 ;
      # 111 ;
      forever begin
          #(CYCLE_10NS/2) clk = ~clk ;
      end
   end

   //需要乘以 15 的數(shù)
   reg [7:0]    num = 0 ;
   always @(posedge clk) begin
      num[3:0] <= num[3:0] + 1 ;
   end

   // num * 8 + num * 4
   wire [7:0]    adder1 ;
   full_adder8  u1_adder8(
               .a      (num<<2),
               .b      (num<<3),
               .c      (1'b0),
               .so     (adder1),
               .co     ());
   //num * 2 + num
   wire [7:0]    adder2 ;
   full_adder8  u2_adder8(
               .a      (num<<1),
               .b      (num),
               .c      (1'b0),
               .so     (adder2),
               .co     ());
   //num x 15
   wire [7:0]    adder3 ;
   full_adder8  u3_adder8(
               .a      (adder1),
               .b      (adder2),
               .c      (1'b0),
               .so     (adder3),
               .co     ());
   
   //store the result
   wire [7:0]    res_mul15 ;
   D8   data_store(
               .d       (adder3),
               .clk     (clk),
               .q       (res_mul15));

   initial begin
      forever begin
         #100;
         if ($time >= 1000)  $finish ;
      end
   end
endmodule // test

仿真報告中則出現(xiàn)了帶有 setup/hold violation 的打印信息,部分截圖如下。


截取出現(xiàn) violation 時間的波形圖,如下所示。


分析如下:

  • 建立時間和保持時間均出現(xiàn)了 violation,雖然仿真中變量 num 乘以 15 以后延遲一個時鐘周期的輸出結(jié)果是正確的,但實(shí)際電路是很危險的。
  • 波形中信號的建立時間 166-164.4 = 1.6 ns,小于設(shè)置的 2ns,所以會報告 violation。
  • 波形中信號的保持時間 168.2-166 = 2.2 ns,小于設(shè)置的 3ns,所以會報告 violation。
  • 圖中紅色部分是信號 d 變化的中間過程,因?yàn)樾盘柛?nbsp;bit 延遲不同,所以中間可能會出現(xiàn)多個不同的結(jié)果。

時序優(yōu)化

保持時間的時序優(yōu)化,在 RTL 層級描述上一般不好控制,這屬于后端設(shè)計(jì)工程師的工作范疇,這里不做討論。

本次主要簡單探討建立時間不滿足約束條件時的優(yōu)化問題。由上一節(jié)《Verilog 建立時間和保持時間》中可知建立時間約束表達(dá)式為:

Tcq + Tcomb + Tsu <= Tclk + Tskew (1)

  • ?Tcq?: 寄存器 clock 端到 Q 端的延遲;
  • ?Tcomb?: data path 中的組合邏輯延遲;
  • ?Tsu?: 建立時間;
  • ?Tclk?: 時鐘周期;
  • ?Tskew?: 時鐘偏移。

優(yōu)化此不等式可從以下幾個方面考慮:

  • 選取時序較好的工藝原件,其 Tcq 和 Tsu 值越小越好;
  • 優(yōu)化組合邏輯,使組合邏輯延遲 Tcomb 越小越好;
  • 降低工作時鐘頻率,增大工作時鐘周期 Tclk;
  • 增加時鐘偏移 Tskew,但時鐘偏移過大又會造成其他問題,例如保持時間可能不滿足,功能邏輯錯誤等。

從 RTL 層次進(jìn)行時序優(yōu)化時,只能考慮方法(2)(3)。

例如,將上述仿真中的工作時鐘周期由 10ns 改為 20ns,則不會出現(xiàn) setup violation。

或者,調(diào)整邏輯,一個周期內(nèi)完成 3 次加法運(yùn)算,改為分散到兩個周期內(nèi)完成,中間增加一級寄存器進(jìn)行緩沖,來減少時序上的壓力。同時,變量 num 的變化周期也應(yīng)該變?yōu)樵瓉淼?nbsp;2 倍時長。

testbench 修改如下:

`timescale 1ns/1ns
`define LOGIC_BUF
module test ;
   parameter    CYCLE_10NS = 10ns;
   reg          clk ;
   initial begin
      clk = 0 ;
      # 111 ;
      forever begin
          #(CYCLE_10NS/2) clk = ~clk ;
      end
   end

   reg          slow_flag = 0 ;
   always @(posedge clk) begin
`ifdef LOGIC_BUF
      slow_flag <= ~slow_flag ;
`else
      slow_flag <= 1'b1 ;
`endif
   end

   reg  [7:0]    num = 0 ;
   always @(posedge clk) begin
      if(slow_flag)
        num[3:0] <= num[3:0] + 1 ;
   end

   wire [7:0]    adder1 ;
   full_adder8  u1_adder8(
               .a      (num<<2),
               .b      (num<<3),
               .c      (1'b0),
               .so     (adder1),
               .co     ());

   wire [7:0]    adder2 ;
   full_adder8  u2_adder8(
               .a      (num<<1),
               .b      (num),
               .c      (1'b0),
               .so     (adder2),
               .co     ());

   //====== for better time=========
   //adding buffer
   wire [7:0]    adder1_r, adder2_r ;
   D8   adder1_buf(
               .d       (adder1),
               .clk     (clk),
               .q       (adder1_r));
   D8   adder2_buf(
               .d       (adder2),
               .clk     (clk),
               .q       (adder2_r));

`ifdef LOGIC_BUF
   wire [7:0]         adder1_t       = adder1_r ;
   wire [7:0]         adder2_t       = adder2_r ;
`else
   wire [7:0]         adder1_t       = adder1 ;
   wire [7:0]         adder2_t       = adder2 ;
`endif

   wire [7:0]    adder3 ;
   full_adder8  u3_adder8(
               .a      (adder1_t),
               .b      (adder2_t),
               .c      (1'b0),
               .so     (adder3),
               .co     ());

   wire [7:0]    res_mul15 ;
   D8   data_store(
               .d       (adder3),
               .clk     (clk),
               .q       (res_mul15));

   initial begin
      forever begin
         #100;
         if ($time >= 1000)  $finish ;
      end
   end

endmodule // test

此時仿真報告中不再有 violation,仿真截圖如下。

由圖可知,信號提前到達(dá)并保持不變的時間可達(dá) 8.6 ns,完全滿足建立時間的時序要求。

此種方法的根本原理,是將信號多次變化的時序,分散在多個周期內(nèi),來滿足時序約束的要求。此外,流水線設(shè)計(jì),并行設(shè)計(jì)等都可以優(yōu)化時序。


$recovery, $removal

建立時間和保持時間的概念都是出現(xiàn)在同步電路的設(shè)計(jì)中。

對于異步復(fù)位的觸發(fā)器來說,異步復(fù)位信號也需要滿足 recovery time(恢復(fù)時間)和 removal time(去除時間),才能有效的復(fù)位和釋放復(fù)位,防止出現(xiàn)亞穩(wěn)態(tài)。

釋放復(fù)位時,復(fù)位信號在時鐘有效沿來臨之前就需要提前一段時間恢復(fù)到非復(fù)位狀態(tài),這段時間為 recovery time。類似于同步時鐘下觸發(fā)器的 setup time。

復(fù)位時,復(fù)位信號在時鐘有效沿來臨之后,還需要在一段時間內(nèi)保持不變,這段時間為 removal time。類似于同步時鐘下觸發(fā)器的 hold time。

recovery 與 removal time 示意圖如下所示。


系統(tǒng)任務(wù) ?$recovery? 與 ?$removal? 分別用于 recovery 和 removal time 的檢查,用法如下:

$recovery (ref_event, data_event, recovery_limit) ;

  • ?ref_event?: 用于檢查的參考信號,一般為清零或復(fù)位信號跳變沿;
  • ?data_event?: 被檢查的信號,一般為時鐘信號跳變沿。
  • ?recovery_limit?:設(shè)置的最小 recovery time。

當(dāng) ?ref_event (reset) < data_event (clock)? 且 ?T(data_event - ref_event) < recovery_limit? 時,即復(fù)位信號在時鐘信號到來之前如果不滿足 ?recovery time?,則報告中會打印 ?violation?

$removal (ref_event, data_event, removal_limit) ;

  • ?ref_event?: 用于檢查的參考信號,一般為清零或復(fù)位信號跳變沿;
  • ?data_event?: 被檢查的信號,一般為時鐘信號跳變沿。
  • ?removal_limit?:設(shè)置的最小 removal time。

當(dāng) ?ref_event (reset) > data_event (clock)? 且 ?T(ref_event - data_event) > removal_limit? 時,即復(fù)位信號在時鐘信號到來之后如果不滿足 ?removal time?,則報告中會打印 ?violation?。

Verilog 提供了同時檢查 revomal 和 recovery 的系統(tǒng)任務(wù):

$recrem (ref_event, data_event, recovery_limit, removal_limit);

$width, $period

有些數(shù)字設(shè)計(jì),例如 flash 存儲器,還需要對脈沖寬度或周期進(jìn)行檢查,為此 Verilog 分別提供了系統(tǒng)任務(wù) $width 和 $period。用法如下:

$width(ref_event, time_limit) ;

  • ?ref_event?: 邊沿觸發(fā)事件
  • ?time_limit?: 脈沖的最小寬度

?$width? 用于檢查邊沿觸發(fā)事件 ?ref_event ?到下一個反向跳變沿之間的時間,常用于脈沖寬度的檢查。如果兩次相反跳邊沿之間的時間小于 ?time_limit?,則會報告 ?violation?。

$period(ref_event, time_limit) ;

?$period? 用于檢查邊沿觸發(fā)事件 ?ref_event ?到下一個同向跳變沿之間的時間,常用于時鐘周期的檢查。如果兩次同向跳邊沿之間的時間小于 ?time_limit?,則報告中會打印 ?violation?。


檢查信號 CLK 寬度和周期的 specify 塊描述如下:

   specify
      $width(posedge CLK, 10);  
      $period(posedge CLK, 20);
   endspecify

點(diǎn)擊這里下載源碼


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號