Verilog函数调用是一种在Verilog中实现复杂功能的方法,它可以帮助我们更好地组织代码,提高代码的可读性和可重用性。Verilog函数调用是一个独立的子过程,它可以接受一个或多个参数,并返回一个值。
Verilog函数调用的语法如下:
function_name(parameter_list);其中function_name是函数名,parameter_list是传递给函数的参数列表。在Verilog中,函数必须在被调用之前声明。声明语法如下:
function function_name; //declaration of function input [width-1:0] parameter_list; //input parameters output [width-1:0] output_variable; //output variable begin //body of the function end endfunction其中function_name是函数名,parameter_list是传递给函数的参数列表,output_variable是函数返回值。
在 Verilog 中,可以利用任务(关键字为 task
)或函数(关键字为 function
),将重复性的行为级设计进行提取,并在多个地方调用,来避免重复代码的多次编写,使代码更加的简洁、易懂。
函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。函数主要有以下几个特点:
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
在 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 进制的数码管译码器,来说明函数可以简化代码的优点。
下图是一个数码管的实物图,可以用来显示 4 位十进制的数字。在比赛计分、时间计时等方面有着相当广泛的应用。
每位数码显示端有 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
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
仿真结果如下
由图可知,片选、译码等信号,均符合设计。实际中,4 位数字应当在一定的时间内保持不变,而片选信号不停的循环扫描,数码管才能给肉眼呈现一种静态显示的效果。
如果译码器设计没有使用函数 dt_translate
,则在每个 case
选项里对信号 abcdefg
进行赋值时,还需要对 single_digit
,ten_digit
, hundred_digit
, kilo_digit
进行判断。这些判断语句又会重复 4 次。虽然最后综合出的实际硬件电路可能是一样的,但显然使用函数后的代码更加的简洁、易读。
点击这里下载源码
progress基础库 1.0.0 开始支持,低版本需做兼容处理。进度条。组件属性的长度单位默认为px,2.4.0起支持传入单位(rpx/px)。属性...
wx.stopBackgroundAudio(Object object)从基础库1.2.0开始,本接口停止维护,请使用wx.getBackgroundAudioManager代替停止播放音...
https://www..cn/verilog2/verilog2-zcte3o4h.html?RECACHE=1...
正则表达式是烦琐的,但它是强大的,学会之后的应用会让你除了提高效率外,会给你带来绝对的成就感。只要认真阅读本教程,加上应...