在 Verilog 中,可以利用任務(關鍵字為 task)或函數(關鍵字為 function),將重復性的行為級設計進行提取,并在多個地方調用,來避免重復代碼的多次編寫,使代碼更加的簡潔、易懂。
函數
函數只能在模塊中定義,位置任意,并在模塊的任何地方引用,作用范圍也局限于此模塊。函數主要有以下幾個特點:
1)不含有任何延遲、時序或時序控制邏輯
2)至少有一個輸入變量
3)只有一個返回值,且沒有輸出
4)不含有非阻塞賦值語句
5)函數可以調用其他函數,但是不能調用任務
Verilog 函數聲明格式如下:
function [range-1:0] function_id ;
input_declaration ;
other_declaration ;
procedural_statement ;
endfunction
函數在聲明時,會隱式的聲明一個寬度為 range、 名字為 function_id 的寄存器變量,函數的返回值通過這個變量進行傳遞。當該寄存器變量沒有指定位寬時,默認位寬為 1。
函數通過指明函數名與輸入變量進行調用。函數結束時,返回值被傳遞到調用處。
函數調用格式如下:
function_id(input1, input2, …);
下面用函數實現一個數據大小端轉換的功能。
當輸入為 4’b0011 時,輸出為 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
函數里的參數也可以改寫,例如:
defparam data_rvs.MASK = 32'd7 ;
但是仿真時發現,此種寫法編譯可以通過,仿真結果中,函數里的參數 MASK 實際并沒有改寫成功,仍然為 32’h3。這可能和編譯器有關,有興趣的學者可以用其他 Verilog 編譯器進行下實驗。
函數在聲明時,也可以在函數名后面加一個括號,將 input 聲明包起來。
例如上述大小端聲明函數可以表示為:
function [N-1:0] data_rvs (
input [N-1:0] data_in
......
);
常數函數
常數函數是指在仿真開始之前,在編譯期間就計算出結果為常數的函數。常數函數不允許訪問全局變量或者調用系統函數,但是可以調用另一個常數函數。
這種函數能夠用來引用復雜的值,因此可用來代替常量。
例如下面一個常量函數,可以來計算模塊中地址總線的寬度:
parameter MEM_DEPTH = 256 ;
reg [logb2(MEM_DEPTH)-1: 0] addr ; //可得addr的寬度為8bit
function integer logb2;
input integer depth ;
//256為9bit,我們最終數據應該是8,所以需depth=2時提前停止循環
for(logb2=0; depth >1; logb2=logb2+1) begin
depth = depth > > 1 ;
end
endfunction
automatic函數
在 Verilog 中,一般函數的局部變量是靜態的,即函數的每次調用,函數的局部變量都會使用同一個存儲空間。若某個函數在兩個不同的地方同時并發的調用,那么兩個函數調用行為同時對同一塊地址進行操作,會導致不確定的函數結果。
Verilog 用關鍵字 automatic 來對函數進行說明,此類函數在調用時是可以自動分配新的內存空間的,也可以理解為是可遞歸的。因此,automatic 函數中聲明的局部變量不能通過層次命名進行訪問,但是 automatic 函數本身可以通過層次名進行調用。
下面用 automatic 函數,實現階乘計算:
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
下面是加關鍵字 automatic 和不加關鍵字 automatic 的仿真結果。
由圖可知,信號 results3 得到了我們想要的結果,即 4 的階乘。
而信號 results_noauto 值為 1,不是可預知的正常結果,這里不再做無用分析。
數碼管譯碼
上述中涉及的相關函數知識似乎并沒有體現出函數的優越性。下面設計一個 4 位 10 進制的數碼管譯碼器,來說明函數可以簡化代碼的優點。
◆數碼管控制示意圖如下。
每位數碼顯示端有 8 個光亮控制端(如圖中 a-g 所示),可以用來控制顯示數字 0-9 。
而數碼管有 4 個片選(如圖中 1-4),用來控制此時哪一位數碼顯示端應該選通,即應該發光。倘若在很短的時間內,依次對 4 個數碼顯示端進行片選發光,同時在不同片選下給予不同的光亮控制(各對應 4 位十進制數字),那么在肉眼不能分辨的情況下,就達到了同時顯示 4 位十進制數字的效果。
◆下面,我們用信號 abcdefg 來控制光亮控制端,用信號 csn 來控制片選,4 位 10 進制的數字個十百千位分別用 4 個 4bit 信號 single_digit, ten_digit, hundred_digit, kilo_digit 來表示,則一個數碼管的顯示設計可以描述如下:
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