關鍵詞:時延控製,事件觸發,邊沿觸發,電平觸發

Verilog 提供了 2 大類時序控製方法:時延控製和事件控製。事件控製主要分為邊沿觸發事件控製與電平敏感事件控製。

時延控製

基於時延的時序控製出現在表達式中,它指定了語句從開始執行到執行完畢之間的時間間隔。

時延可以是數字、標識符或者表達式。

根據在表達式中的位置差異,時延控製又可以分為常規時延與內嵌時延。

常規時延

遇到常規延時時,該語句需要等待一定時間,然後將計算結果賦值給目標信號。

格式為:#delay procedural_statement,例如:

reg  value_test ;
reg  value_general ;
#10  value_general    = value_test ;

該時延方式的另一種寫法是直接將井號 # 獨立成一個時延執行語句,例如:

#10 ;
value_ single         = value_test ;

內嵌時延

遇到內嵌延時時,該語句先將計算結果保存,然後等待一定的時間後賦值給目標信號。

內嵌時延控製加在賦值號之後。例如:

reg  value_test ;
reg  value_embed ;
value_embed        = #10 value_test ;

需要說明的是,這 2 種時延控製方式的效果是有所不同的。

當延時語句的賦值符號右端是常量時,2 種時延控製都能達到相同的延時賦值效果。

當延時語句的賦值符號右端是變量時,2 種時延控製可能會產生不同的延時賦值效果。

例如下麵仿真代碼:

實例

`timescale 1ns/1ns
 
module test ;
    reg  value_test ;
    reg  value_general, value_embed, value_single ;
 
    //signal source
    initial begin
        value_test        = 0 ;
        #25 ;      value_test        = 1 ;
        #35 ;      value_test        = 0 ;        //absolute 60ns
        #40 ;      value_test        = 1 ;        //absolute 100ns
        #10 ;      value_test        = 0 ;        //absolute 110ns
    end
 
    //(1)general delay control
    initial begin
        value_general     = 1;
        #10 value_general  = value_test ; //10ns, value_test=0
        #45 value_general  = value_test ; //55ns, value_test=1
        #30 value_general  = value_test ; //85ns, value_test=0
        #20 value_general  = value_test ; //105ns, value_test=1
    end
 
    //(2)embedded delay control
    initial begin
        value_embed       = 1;
        value_embed  = #10 value_test ; //0ns, value_test=0
        value_embed  = #45 value_test ; //10ns, value_test=0
        value_embed  = #30 value_test ; //55ns, value_test=1
        value_embed  = #20 value_test ; //85ns, value_test=0
    end
 
    //(3)single delay control
    initial begin
        value_single      = 1;
        #10 ;
        value_single = value_test ; //10ns, value_test=0
        #45 ;
        value_single = value_test ; //55ns, value_test=1
        #30 ;
        value_single = value_test ; //85ns, value_test=0
        #20 ;
        value_single = value_test ; //105ns, value_test=1
    end
 
    always begin
        #10;
        if ($time >= 150) begin
            $finish ;
        end
    end
 
endmodule

仿真結果如下,由圖可知:

  • (1)一般延時的兩種表達方式執行的結果都是一致的。
  • (2)一般時延賦值方式:遇到延遲語句後先延遲一定的時間,然後將當前操作數賦值給目標信號,並沒有"慣性延遲"的特點,不會漏掉相對較窄的脈衝。
  • (3)內嵌時延賦值方式:遇到延遲語句後,先計算出表達式右端的結果,然後再延遲一定的時間,賦值給目標信號。

下麵分析下內嵌延時的賦值過程:

value_embed  = #10 value_test ; //0ns, value_test=0

0ns 時,執行此延時語句。

先將 0 賦值給信號 value_embed, 延遲 10ns 輸出為 0;

value_embed  = #45 value_test ; //10ns, value_test=0

10ns 時,執行此延時語句。

由於此時 value_test 仍然為 0,所以 value_embed 值不變。

即到 55ns 時,value_embed 值仍然保持為 0。

value_embed  = #30 value_test ; //55ns, value_test=1

同理,55ns 時,value_test 值為 1,將其賦值給 value_embed 並延遲 30ns 輸出。

所以 85ns 時,value_embed 輸出為 1。

value_embed  = #20 value_test ; //85ns, value_test=0

同理,105ns 時,value_embed 輸出為 0。

邊沿觸發事件控製

在 Verilog 中,事件是指某一個 reg 或 wire 型變量發生了值的變化。

基於事件觸發的時序控製又主要分為以下幾種。

一般事件控製

事件控製用符號 @ 表示。

語句執行的條件是信號的值發生特定的變化。

關鍵字 posedge 指信號發生邊沿正向跳變,negedge 指信號發生負向邊沿跳變,未指明跳變方向時,則 2 種情況的邊沿變化都會觸發相關事件。例如:

實例

//信號clk隻要發生變化,就執行q<=d,雙邊沿D觸發器模型
always @(clk) q <= d ;                
//在信號clk上升沿時刻,執行q<=d,正邊沿D觸發器模型
always @(posedge clk) q <= d ;  
//在信號clk下降沿時刻,執行q<=d,負邊沿D觸發器模型
always @(negedge clk) q <= d ;
//立刻計算d的值,並在clk上升沿時刻賦值給q,不推薦這種寫法
q = @(posedge clk) d ;      

命名事件控製

用戶可以聲明 event(事件)類型的變量,並觸發該變量來識別該事件是否發生。命名事件用關鍵字 event 來聲明,觸發信號用 -> 表示。例如:

實例

event     start_receiving ;
always @( posedge clk_samp) begin
        -> start_receiving ;       //采樣時鍾上升沿作為時間觸發時刻
end
 
always @(start_receiving) begin
    data_buf = {data_if[0], data_if[1]} ; //觸發時刻,對多維數據整合
end

敏感列表

當多個信號或事件中任意一個發生變化都能夠觸發語句的執行時,Verilog 中使用"或"表達式來描述這種情況,用關鍵字 or 連接多個事件或信號。這些事件或信號組成的列表稱為"敏感列表"。當然,or 也可以用逗號 , 來代替。例如:

實例

//帶有低有效複位端的D觸發器模型
always @(posedge clk or negedge rstn)    begin      
//always @(posedge clk , negedge rstn)    begin      
//也可以使用逗號陳列多個事件觸發
    if! rstn)begin
        q <= 1'b ;      
    end
    else begin
        q <= d ;
    end
end

當組合邏輯輸入變量很多時,那麼編寫敏感列表會很繁瑣。此時,更為簡潔的寫法是 @*@(*),表示對語句塊中的所有輸入變量的變化都是敏感的。例如:

實例

always @(*) begin
//always @(a, b, c, d, e, f, g, h, i, j, k, l, m) begin
//兩種寫法等價
    assign s = a? b+c : d ? e+f : g ? h+i : j ? k+l : m ;
end

電平敏感事件控製

前麵所討論的事件控製都是需要等待信號值的變化或事件的觸發,使用 @+敏感列表 的方式來表示的。

Verilog 中還支持使用電平作為敏感信號來控製時序,即後麵語句的執行需要等待某個條件為真。Verilog 中使用關鍵字 wait 來表示這種電平敏感情況。例如:

實例

initial begin
    wait (start_enable) ;      //等待 start 信號
    forever begin
        //start信號使能後,在clk_samp上升沿,對數據進行整合
        @(posedge clk_samp)  ;
        data_buf = {data_if[0], data_if[1]} ;      
    end
end

源碼下載

Download