在 Verilog 中,可以利用任務(wù)(關(guān)鍵字為 task)或函數(shù)(關(guān)鍵字為 function),將重復(fù)性的行為級設(shè)計(jì)進(jìn)行提取,并在多個(gè)地方調(diào)用,來避免重復(fù)代碼的多次編寫,使代碼更加的簡潔、易懂。
函數(shù)
函數(shù)只能在模塊中定義,位置任意,并在模塊的任何地方引用,作用范圍也局限于此模塊。函數(shù)主要有以下幾個(gè)特點(diǎn):
1)不含有任何延遲、時(shí)序或時(shí)序控制邏輯
2)至少有一個(gè)輸入變量
3)只有一個(gè)返回值,且沒有輸出
4)不含有非阻塞賦值語句
5)函數(shù)可以調(diào)用其他函數(shù),但是不能調(diào)用任務(wù)
Verilog 函數(shù)聲明格式如下:
function [range-1:0] function_id ;
input_declaration ;
other_declaration ;
procedural_statement ;
endfunction
函數(shù)在聲明時(shí),會(huì)隱式的聲明一個(gè)寬度為 range、 名字為 function_id 的寄存器變量,函數(shù)的返回值通過這個(gè)變量進(jìn)行傳遞。當(dāng)該寄存器變量沒有指定位寬時(shí),默認(rèn)位寬為 1。
函數(shù)通過指明函數(shù)名與輸入變量進(jìn)行調(diào)用。函數(shù)結(jié)束時(shí),返回值被傳遞到調(diào)用處。
函數(shù)調(diào)用格式如下:
function_id(input1, input2, …);
下面用函數(shù)實(shí)現(xiàn)一個(gè)數(shù)據(jù)大小端轉(zhuǎn)換的功能。
當(dāng)輸入為 4’b0011 時(shí),輸出為 4’b1100。例如:
module endian_rvs
#(parameter N = 4)
(
input en, //enable control
input [N-1:0] a ,
output [N-1:0] b
);
reg [N-1:0] b_temp ;
always @(*) begin
if (en) begin
b_temp = data_rvs(a);
end
else begin
b_temp = 0 ;
end
end
assign b = b_temp ;
//function entity
function [N-1:0] data_rvs ;
input [N-1:0] data_in ;
parameter MASK = 32'h3 ;
integer k ;
begin
for(k=0; k< N; k=k+1) begin
data_rvs[N-k-1] = data_in[k] ;
end
end
endfunction
endmodule
函數(shù)里的參數(shù)也可以改寫,例如:
defparam data_rvs.MASK = 32'd7 ;
但是仿真時(shí)發(fā)現(xiàn),此種寫法編譯可以通過,仿真結(jié)果中,函數(shù)里的參數(shù) MASK 實(shí)際并沒有改寫成功,仍然為 32’h3。這可能和編譯器有關(guān),有興趣的學(xué)者可以用其他 Verilog 編譯器進(jìn)行下實(shí)驗(yàn)。
函數(shù)在聲明時(shí),也可以在函數(shù)名后面加一個(gè)括號(hào),將 input 聲明包起來。
例如上述大小端聲明函數(shù)可以表示為:
function [N-1:0] data_rvs (
input [N-1:0] data_in
......
);
常數(shù)函數(shù)
常數(shù)函數(shù)是指在仿真開始之前,在編譯期間就計(jì)算出結(jié)果為常數(shù)的函數(shù)。常數(shù)函數(shù)不允許訪問全局變量或者調(diào)用系統(tǒng)函數(shù),但是可以調(diào)用另一個(gè)常數(shù)函數(shù)。
這種函數(shù)能夠用來引用復(fù)雜的值,因此可用來代替常量。
例如下面一個(gè)常量函數(shù),可以來計(jì)算模塊中地址總線的寬度:
parameter MEM_DEPTH = 256 ;
reg [logb2(MEM_DEPTH)-1: 0] addr ; //可得addr的寬度為8bit
function integer logb2;
input integer depth ;
//256為9bit,我們最終數(shù)據(jù)應(yīng)該是8,所以需depth=2時(shí)提前停止循環(huán)
for(logb2=0; depth >1; logb2=logb2+1) begin
depth = depth > > 1 ;
end
endfunction
automatic函數(shù)
在 Verilog 中,一般函數(shù)的局部變量是靜態(tài)的,即函數(shù)的每次調(diào)用,函數(shù)的局部變量都會(huì)使用同一個(gè)存儲(chǔ)空間。若某個(gè)函數(shù)在兩個(gè)不同的地方同時(shí)并發(fā)的調(diào)用,那么兩個(gè)函數(shù)調(diào)用行為同時(shí)對同一塊地址進(jìn)行操作,會(huì)導(dǎo)致不確定的函數(shù)結(jié)果。
Verilog 用關(guān)鍵字 automatic 來對函數(shù)進(jìn)行說明,此類函數(shù)在調(diào)用時(shí)是可以自動(dòng)分配新的內(nèi)存空間的,也可以理解為是可遞歸的。因此,automatic 函數(shù)中聲明的局部變量不能通過層次命名進(jìn)行訪問,但是 automatic 函數(shù)本身可以通過層次名進(jìn)行調(diào)用。
下面用 automatic 函數(shù),實(shí)現(xiàn)階乘計(jì)算:
wire [31:0] results3 = factorial(4);
function automatic integer factorial ;
input integer data ;
integer i ;
begin
factorial = (data >=2)? data * factorial(data-1) : 1 ;
end
endfunction // factorial
下面是加關(guān)鍵字 automatic 和不加關(guān)鍵字 automatic 的仿真結(jié)果。
由圖可知,信號(hào) results3 得到了我們想要的結(jié)果,即 4 的階乘。
而信號(hào) results_noauto 值為 1,不是可預(yù)知的正常結(jié)果,這里不再做無用分析。
數(shù)碼管譯碼
上述中涉及的相關(guān)函數(shù)知識(shí)似乎并沒有體現(xiàn)出函數(shù)的優(yōu)越性。下面設(shè)計(jì)一個(gè) 4 位 10 進(jìn)制的數(shù)碼管譯碼器,來說明函數(shù)可以簡化代碼的優(yōu)點(diǎn)。
◆一個(gè)數(shù)碼管的實(shí)物圖,可以用來顯示 4 位十進(jìn)制的數(shù)字。 在比賽計(jì)分、時(shí)間計(jì)時(shí)等方面有著相當(dāng)廣泛的應(yīng)用。
◆數(shù)碼管控制示意圖如下。
每位數(shù)碼顯示端有 8 個(gè)光亮控制端(如圖中 a-g 所示),可以用來控制顯示數(shù)字 0-9 。
而數(shù)碼管有 4 個(gè)片選(如圖中 1-4),用來控制此時(shí)哪一位數(shù)碼顯示端應(yīng)該選通,即應(yīng)該發(fā)光。倘若在很短的時(shí)間內(nèi),依次對 4 個(gè)數(shù)碼顯示端進(jìn)行片選發(fā)光,同時(shí)在不同片選下給予不同的光亮控制(各對應(yīng) 4 位十進(jìn)制數(shù)字),那么在肉眼不能分辨的情況下,就達(dá)到了同時(shí)顯示 4 位十進(jìn)制數(shù)字的效果。
◆下面,我們用信號(hào) abcdefg 來控制光亮控制端,用信號(hào) csn 來控制片選,4 位 10 進(jìn)制的數(shù)字個(gè)十百千位分別用 4 個(gè) 4bit 信號(hào) single_digit, ten_digit, hundred_digit, kilo_digit 來表示,則一個(gè)數(shù)碼管的顯示設(shè)計(jì)可以描述如下:
module digital_tube
(
input clk ,
input rstn ,
input en ,
input [3:0] single_digit ,
input [3:0] ten_digit ,
input [3:0] hundred_digit ,
input [3:0] kilo_digit ,
output reg [3:0] csn , //chip select, low-available
output reg [6:0] abcdefg //light control
);
reg [1:0] scan_r ; //scan_ctrl
always @ (posedge clk or negedge rstn) begin
if(!rstn)begin
csn <= 4'b1111;
abcdefg <= 'd0;
scan_r <= 3'd0;
end
else if (en) begin
case(scan_r)
2'd0:begin
scan_r <= 3'd1;
csn <= 4'b0111; //select single digit
abcdefg <= dt_translate(single_digit);
end
2'd1:begin
scan_r <= 3'd2;
csn <= 4'b1011; //select ten digit
abcdefg <= dt_translate(ten_digit);
end
2'd2:begin
scan_r <= 3'd3;
csn <= 4'b1101; //select hundred digit
abcdefg <= dt_translate(hundred_digit);
end
2'd3:begin
scan_r <= 3'd0;
csn <= 4'b1110; //select kilo digit
abcdefg <= dt_translate(kilo_digit);
end
endcase
end
end
/*------------ translate function -------*/
function [6:0] dt_translate;
input [3:0] data;
begin
case(data)
4'd0: dt_translate = 7'b1111110; //number 0 - > 0x7e
4'd1: dt_translate = 7'b0110000; //number 1 - > 0x30
4'd2: dt_translate = 7'b1101101; //number 2 - > 0x6d
4'd3: dt_translate = 7'b1111001; //number 3 - > 0x79
4'd4: dt_translate = 7'b0110011; //number 4 - > 0x33
4'd5: dt_translate = 7'b1011011; //number 5 - > 0x5b
4'd6: dt_translate = 7'b1011111; //number 6 - > 0x5f
4'd7: dt_translate = 7'b1110000; //number 7 - > 0x70
4'd8: dt_translate = 7'b1111111; //number 8 - > 0x7f
4'd9: dt_translate = 7'b1111011; //number 9 - > 0x7b
endcase
end
endfunction
endmodule