關鍵詞:任務

任務與函數的區別

和函數一樣,任務(task)可以用來描述共同的代碼段,並在模塊內任意位置被調用,讓代碼更加的直觀易讀。函數一般用於組合邏輯的各種轉換和計算,而任務更像一個過程,不僅能完成函數的功能,還可以包含時序控製邏輯。下麵對任務與函數的區別進行概括:

比較點函數任務
輸入函數至少有一個輸入,端口聲明不能包含 inout 型任務可以沒有或者有多個輸入,且端口聲明可以為 inout 型
輸出函數沒有輸出任務可以沒有或者有多個輸出
返回值函數至少有一個返回值任務沒有返回值
仿真時刻函數總在零時刻就開始執行任務可以在非零時刻執行
時序邏輯函數不能包含任何時序控製邏輯任務不能出現 always 語句,但可以包含其他時序控製,如延時語句
調用函數隻能調用函數,不能調用任務任務可以調用函數和任務
書寫規範函數不能單獨作為一條語句出現,隻能放在賦值語言的右端任務可以作為一條單獨的語句出現語句塊中

任務

任務聲明

任務在模塊中任意位置定義,並在模塊內任意位置引用,作用範圍也局限於此模塊。

模塊內子程序出現下麵任意一個條件時,則必須使用任務而不能使用函數。

  • 1)子程序中包含時序控製邏輯,例如延遲,事件控製等
  • 2)沒有輸入變量
  • 3)沒有輸出或輸出端的數量大於 1

Verilog 任務聲明格式如下:

task       task_id ;
    port_declaration ;
    procedural_statement ;
endtask

任務中使用關鍵字 input、output 和 inout 對端口進行聲明。input 、inout 型端口將變量從任務外部傳遞到內部,output、inout 型端口將任務執行完畢時的結果傳回到外部。

進行任務的邏輯設計時,可以把 input 聲明的端口變量看做 wire 型,把 output 聲明的端口變量看做 reg 型。但是不需要用 reg 對 output 端口再次說明。

對 output 信號賦值時也不要用關鍵字 assign。為避免時序錯亂,建議 output 信號采用阻塞賦值。

例如,一個帶延時的異或功能 task 描述如下:

實例

task xor_oper_iner;
    input [N-1:0]   numa;
    input [N-1:0]   numb;
    output [N-1:0]  numco ;
    //output reg [N-1:0]  numco ; //無需再注明 reg 類型,雖然注明也可能沒錯
    #3  numco = numa ^ numb ;
    //assign #3 numco = numa ^ numb ; //不用assign,因為輸出默認是reg
endtask

任務在聲明時,也可以在任務名後麵加一個括號,將端口聲明包起來。

上述設計可以更改為:

實例

task xor_oper_iner(
    input [N-1:0]   numa,
    input [N-1:0]   numb,
    output [N-1:0]  numco  ) ;
    #3  numco       = numa ^ numb ;
endtask

任務調用

任務可單獨作為一條語句出現在 initial 或 always 塊中,調用格式如下:

task_id(input1, input2, …,outpu1, output2, …);

任務調用時,端口必須按順序對應。

輸入端連接的模塊內信號可以是 wire 型,也可以是 reg 型。輸出端連接的模塊內信號要求一定是 reg 型,這點需要注意。

對上述異或功能的 task 進行一個調用,完成對異或結果的緩存。

實例

module xor_oper
    #(parameter         N = 4)
     (
      input             clk ,
      input             rstn ,
      input [N-1:0]     a ,
      input [N-1:0]     b ,
      output [N-1:0]    co  );
 
    reg [N-1:0]          co_t ;
    always @(*) begin          //任務調用
        xor_oper_iner(a, b, co_t);
    end
 
    reg [N-1:0]          co_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            co_r   <= 'b0 ;
        end
        else begin
            co_r   <= co_t ;         //數據緩存
        end
    end
    assign       co = co_r ;
 
   /*------------ task -------*/
    task xor_oper_iner;
        input [N-1:0]   numa;
        input [N-1:0]   numb;
        output [N-1:0]  numco ;
        #3  numco       = numa ^ numb ;   //阻塞賦值,易於控製時序
    endtask
 
endmodule

對上述異或功能設計進行簡單的仿真,testbench 描述如下。

激勵部分我們使用簡單的 task 進行描述,激勵看起來就更加的清晰簡潔。

其實,task 最多的應用場景還是應用於 testbench 中進行仿真。task 在一些編譯器中也不支持綜合。

實例

`timescale 1ns/1ns
 
module test ;
    reg          clk, rstn ;
 
    initial begin
        rstn      = 0 ;
        #8 rstn   = 1 ;
        forever begin
            clk = 0 ; # 5;
            clk = 1 ; # 5;
        end
    end
 
    reg  [3:0]   a, b;
    wire [3:0]   co ;
    initial begin
        a         = 0 ;
        b         = 0 ;
        sig_input(4'b1111, 4'b1001, a, b);
        sig_input(4'b0110, 4'b1001, a, b);
        sig_input(4'b1000, 4'b1001, a, b);
    end
 
    task sig_input ;
        input [3:0]       a ;
        input [3:0]       b ;
        output [3:0]      ao ;
        output [3:0]      bo ;
        @(posedge clk) ;
        ao = a ;
        bo = b ;
    endtask ; // sig_input
 
    xor_oper         u_xor_oper
    (
      .clk              (clk  ),
      .rstn             (rstn ),
      .a                (a    ),
      .b                (b    ),
      .co               (co   ));
 
    initial begin
        forever begin
            #100;
            if ($time >= 1000)  $finish ;
        end
    end
 
endmodule // test

仿真結果如下。

由圖可知,異或輸出邏輯結果正確,相對於輸入有 3ns 的延遲。

且連接信號 a,b,co_t 與任務內部定義的信號 numa,numb,numco 狀態也保持一致。

任務操作全局變量

因為任務可以看做是過程性賦值,所以任務的 output 端信號返回時間是在任務中所有語句執行完畢之後。

任務內部變量也隻有在任務中可見,如果想具體觀察任務中對變量的操作過程,需要將觀察的變量聲明在模塊之內、任務之外,可謂之"全局變量"。

例如有以下 2 種嚐試利用 task 產生時鍾的描述方式。

實例

//way1 to decirbe clk generating, not work
task clk_rvs_iner ;
        output    clk_no_rvs ;
        # 5 ;     clk_no_rvs = 0 ;
        # 5 ;     clk_no_rvs = 1 ;
endtask
reg          clk_test1 ;
always clk_rvs_iner(clk_test1);

//way2: use task to operate global varialbes to generating clk
reg          clk_test2 ;
task clk_rvs_global ;
        # 5 ;     clk_test2 = 0 ;
        # 5 ;     clk_test2 = 1 ;
endtask // clk_rvs_iner
always clk_rvs_global;

仿真結果如下。

第一種描述方式,雖然任務內部變量會有賦值 0 和賦值 1 的過程操作,但中間變化過程並不可見,最後輸出的結果隻能是任務內所有語句執行完畢後輸出端信號的最終值。所以信號 clk_test1 值恒為 1,此種方式產生不了時鍾。

第二種描述方式,雖然沒有端口信號,但是直接對"全局變量"進行過程操作,因為該全局變量對模塊是可見的,所以任務內信號翻轉的過程會在信號 clk_test2 中體現出來。

automatic 任務

和函數一樣,Verilog 中任務調用時的局部變量都是靜態的。可以用關鍵字 automatic 來對任務進行聲明,那麼任務調用時各存儲空間就可以動態分配,每個調用的任務都各自獨立的對自己獨有的地址空間進行操作,而不影響多個相同任務調用時的並發執行。

如果一任務代碼段被 2 處及以上調用,一定要用關鍵字 automatic 聲明。

當沒有使用 automatic 聲明任務時,任務被 2 次調用,可能出現信號間幹擾,例如下麵代碼描述:

實例

task test_flag ;
        input [3:0]       cnti ;
        input             en ;
        output [3:0]      cnto ;
        if (en) cnto = cnti ;
endtask

reg          en_cnt ;
reg [3:0]    cnt_temp ;
initial begin
        en_cnt    = 1 ;
        cnt_temp  = 0 ;
        #25 ;     en_cnt = 0 ;
end
always #10 cnt_temp = cnt_temp + 1 ;

reg [3:0]             cnt1, cnt2 ;
always @(posedge clk) test_flag(2, en_cnt, cnt1);       //task(1)
always @(posedge clk) test_flag(cnt_temp, !en_cnt, cnt2);//task(2)

仿真結果如下。

en_cnt 為高時,任務 (1) 中信號 en 有效, cnt1 能輸出正確的邏輯值;

此時任務 (2) 中信號 en 是不使能的,所以 cnt2 的值被任務 (1) 驅動的共用變量 cnt_temp 覆蓋。

en_cnt 為低時,任務 (2) 中信號 en 有效,所以任務 (2) 中的信號 cnt2 能輸出正確的邏輯值;而此時信號 cnt1 的值在時鍾的驅動下,一次次被任務 (2) 驅動的共用變量 cnt_temp 覆蓋。

可見,任務在兩次並發調用中,共用存儲空間,導致信號相互間產生了影響。

其他描述不變,隻在上述 task 聲明時加入關鍵字 automatic,如下所以。

task automatic test_flag ;

此時仿真結果如下。

  • en_cnt 為高時,任務 (1) 中信號 cnt1 能輸出正確的邏輯值,任務 (2) 中信號 cnt2 的值為 X;
  • en_cnt 為低時,任務 (2) 中信號 cnt2 能輸出正確的邏輯值,任務 (1) 中信號 cnt1 的值為 X;

可見,任務在兩次並發調用中,因為存儲空間相互獨立,信號間並沒有產生影響。

源碼下載

Download