和函數(shù)一樣,任務(wù)(task)可以用來描述共同的代碼段,并在模塊內(nèi)任意位置被調(diào)用,讓代碼更加的直觀易讀。函數(shù)一般用于組合邏輯的各種轉(zhuǎn)換和計算,而任務(wù)更像一個過程,不僅能完成函數(shù)的功能,還可以包含時序控制邏輯。下面對任務(wù)與函數(shù)的區(qū)別進行概括:
比較點 | 函數(shù) | 任務(wù) |
---|---|---|
輸入 | 函數(shù)至少有一個輸入,端口聲明不能包含 inout 型 | 任務(wù)可以沒有或者有多個輸入,且端口聲明可以為 inout 型 |
輸出 | 函數(shù)沒有輸出 | 任務(wù)可以沒有或者有多個輸出 |
返回值 | 函數(shù)至少有一個返回值 | 任務(wù)沒有返回值 |
仿真時刻 | 函數(shù)總在零時刻就開始執(zhí)行 | 任務(wù)可以在非零時刻執(zhí)行 |
時序邏輯 | 函數(shù)不能包含任何時序控制邏輯 | 任務(wù)不能出現(xiàn) always 語句,但可以包含其他時序控制,如延時語句 |
調(diào)用 | 函數(shù)只能調(diào)用函數(shù),不能調(diào)用任務(wù) | 任務(wù)可以調(diào)用函數(shù)和任務(wù) |
書寫規(guī)范 | 函數(shù)不能單獨作為一條語句出現(xiàn),只能放在賦值語言的右端 | 任務(wù)可以作為一條單獨的語句出現(xiàn)語句塊中 |
任務(wù)在模塊中任意位置定義,并在模塊內(nèi)任意位置引用,作用范圍也局限于此模塊。
模塊內(nèi)子程序出現(xiàn)下面任意一個條件時,則必須使用任務(wù)而不能使用函數(shù)。
Verilog 任務(wù)聲明格式如下:
task task_id ;
port_declaration ;
procedural_statement ;
endtask
任務(wù)中使用關(guān)鍵字 ?input
?、?output
?和 ?inout
?對端口進行聲明。?input
?、?inout
?型端口將變量從任務(wù)外部傳遞到內(nèi)部,?output
?、?inout
?型端口將任務(wù)執(zhí)行完畢時的結(jié)果傳回到外部。
進行任務(wù)的邏輯設(shè)計時,可以把 ?input
?聲明的端口變量看做 wire 型,把 ?output
?聲明的端口變量看做 ?reg
?型。但是不需要用 ?reg
?對 ?output
?端口再次說明。
對 ?output
?信號賦值時也不要用關(guān)鍵字 ?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,因為輸出默認(rèn)是reg
endtask
任務(wù)在聲明時,也可以在任務(wù)名后面加一個括號,將端口聲明包起來。
上述設(shè)計可以更改為:
task xor_oper_iner(
input [N-1:0] numa,
input [N-1:0] numb,
output [N-1:0] numco ) ;
#3 numco = numa ^ numb ;
endtask
任務(wù)可單獨作為一條語句出現(xiàn)在 ?initial
?或 ?always
?塊中,調(diào)用格式如下:
task_id(input1, input2, …,outpu1, output2, …);
任務(wù)調(diào)用時,端口必須按順序?qū)?yīng)。
輸入端連接的模塊內(nèi)信號可以是 ?wire
?型,也可以是 ?reg
?型。輸出端連接的模塊內(nèi)信號要求一定是 ?reg
?型,這點需要注意。
對上述異或功能的 ?task
?進行一個調(diào)用,完成對異或結(jié)果的緩存。
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 //任務(wù)調(diào)用
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 ; //數(shù)據(jù)緩存
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
對上述異或功能設(shè)計進行簡單的仿真,testbench 描述如下。
激勵部分我們使用簡單的 ?task
?進行描述,激勵看起來就更加的清晰簡潔。
其實,?task
?最多的應(yīng)用場景還是應(yīng)用于 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
仿真結(jié)果如下。
由圖可知,異或輸出邏輯結(jié)果正確,相對于輸入有 3ns 的延遲。
且連接信號 a,b,co_t 與任務(wù)內(nèi)部定義的信號 numa,numb,numco 狀態(tài)也保持一致。
因為任務(wù)可以看做是過程性賦值,所以任務(wù)的 ?output
?端信號返回時間是在任務(wù)中所有語句執(zhí)行完畢之后。
任務(wù)內(nèi)部變量也只有在任務(wù)中可見,如果想具體觀察任務(wù)中對變量的操作過程,需要將觀察的變量聲明在模塊之內(nèi)、任務(wù)之外,可謂之"全局變量"。
例如有以下 2 種嘗試?yán)?nbsp;?task
?產(chǎn)生時鐘的描述方式。
//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;
仿真結(jié)果如下。
第一種描述方式,雖然任務(wù)內(nèi)部變量會有賦值 0 和賦值 1 的過程操作,但中間變化過程并不可見,最后輸出的結(jié)果只能是任務(wù)內(nèi)所有語句執(zhí)行完畢后輸出端信號的最終值。所以信號 ?clk_test1
?值恒為 1,此種方式產(chǎn)生不了時鐘。
第二種描述方式,雖然沒有端口信號,但是直接對"全局變量"進行過程操作,因為該全局變量對模塊是可見的,所以任務(wù)內(nèi)信號翻轉(zhuǎn)的過程會在信號 ?clk_test2
?中體現(xiàn)出來。
和函數(shù)一樣,Verilog 中任務(wù)調(diào)用時的局部變量都是靜態(tài)的??梢杂藐P(guān)鍵字 ?automatic
?來對任務(wù)進行聲明,那么任務(wù)調(diào)用時各存儲空間就可以動態(tài)分配,每個調(diào)用的任務(wù)都各自獨立的對自己獨有的地址空間進行操作,而不影響多個相同任務(wù)調(diào)用時的并發(fā)執(zhí)行。
如果一任務(wù)代碼段被 2 處及以上調(diào)用,一定要用關(guān)鍵字 ?automatic
?聲明。
當(dāng)沒有使用 ?automatic
?聲明任務(wù)時,任務(wù)被 2 次調(diào)用,可能出現(xiàn)信號間干擾,例如下面代碼描述:
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)
仿真結(jié)果如下。
?en_cnt
?為高時,任務(wù) (1) 中信號 en 有效, cnt1 能輸出正確的邏輯值;
此時任務(wù) (2) 中信號 en 是不使能的,所以 cnt2 的值被任務(wù) (1) 驅(qū)動的共用變量 ?cnt_temp
?覆蓋。
?en_cnt
?為低時,任務(wù) (2) 中信號 en 有效,所以任務(wù) (2) 中的信號 cnt2 能輸出正確的邏輯值;而此時信號 cnt1 的值在時鐘的驅(qū)動下,一次次被任務(wù) (2) 驅(qū)動的共用變量 ?cnt_temp
?覆蓋。
可見,任務(wù)在兩次并發(fā)調(diào)用中,共用存儲空間,導(dǎo)致信號相互間產(chǎn)生了影響。
其他描述不變,只在上述 ?task
?聲明時加入關(guān)鍵字 ?automatic
?,如下所以。
task automatic test_flag ;
此時仿真結(jié)果如下。
en_cnt
?為高時,任務(wù) (1) 中信號 cnt1 能輸出正確的邏輯值,任務(wù) (2) 中信號 cnt2 的值為 X;
en_cnt
?為低時,任務(wù) (2) 中信號 cnt2 能輸出正確的邏輯值,任務(wù) (1) 中信號 cnt1 的值為 X;
可見,任務(wù)在兩次并發(fā)調(diào)用中,因為存儲空間相互獨立,信號間并沒有產(chǎn)生影響。
點擊這里下載源碼
更多建議: