问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Verilog函数详解:从基础概念到实际应用

创作时间:
作者:
@小白创作中心

Verilog函数详解:从基础概念到实际应用

引用
1
来源
1.
https://www.runoob.com/w3cnote/verilog-function.html

在Verilog中,函数是一种强大的工具,可以将重复性的行为级设计进行提取,并在多个地方调用,从而避免重复代码的多次编写,使代码更加简洁、易懂。本文将详细介绍Verilog中函数的使用方法,包括函数的基本特点、声明格式、调用方式以及不同类型函数的使用场景。

函数

函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。函数主要有以下几个特点:

  • 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进制的数码管译码器,来说明函数可以简化代码的优点。

下图是一个数码管的实物图,可以用来显示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
/*------------ 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

仿真结果如下。由图可知,片选、译码等信号,均符合设计。实际中,4位数字应当在一定的时间内保持不变,而片选信号不停的循环扫描,数码管才能给肉眼呈现一种静态显示的效果。

小结

如果译码器设计没有使用函数dt_translate,则在每个case选项里对信号abcdefg进行赋值时,还需要对single_digit,ten_digit, hundred_digit, kilo_digit进行判断。这些判断语句又会重复4次。虽然最后综合出的实际硬件电路可能是一样的,但显然使用函数后的代码更加的简洁、易读。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号