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