Verilog变量声明与基本数据类型等章节中介绍变量的声明,也简单介绍了变量的使用及使用情况,随着学习和理解的逐渐深入,有必要将变量的类型及使用范围作更深入的探讨。
-
wire型变量声明及使用范围
wire型变量在module内是全局变量,可以在module内任何地方被引用,但对wire型变量的赋值却有不同的要求。
-
- wire型变量声明,在module的变量声明区域内完成,但不能在initial,always, function,task 等过程或函数内声明。
- wire类型变量使用范围,在module范围内wire型变量在声明后是全局可见的,即一次赋值全局使用。但wire型变量在module范围内只能在声明之后可见,因此使用范围为变量声明到module结束。如果在module内先使用后声明,不同的编译器可能会给出不同的结果,见例1的测试结果。
- wire型变量不能在initial,always,function,task 等过程或函数内对该变量的赋值。
- wire类型变量赋值可以有如下三种形式:
- 声明时赋初值,
- 用assign关键字显式赋值,
- 在模块例化时中赋值,但只能对module的输入部分赋值,
- 函数返回值或task调用等赋值,
- wire型变量只能赋值一次。
- 端口中的output如果是wire类型,则在整个module内是全局的。
例1:
module test_wire
(
input [1:0] a,
input [1:0] b,
output [2:0] c
);
assign c = tmp0 + tmp1; //tmp0,tmp1未声明先使用, 建议先声明后使用
wire [1:0] tmp0;
wire [1:0] tmp1;
assign tmp0 = a;
assign tmp1 = b;
endmodule
例1中赋值语句“assign c = tmp0 + tmp1;”在变量声明前就已经使用,在Quartus综合时没有发现任何相关的warning和error;而且在FII-PRA006上进行硬件验证,结果也正常,的确实现了两位全加器的结果。实验时管脚锁定如下:
表1
| Verilog name | a[0] | a[1] | b[0] | b[1] | c[0] | c[1] | c[2] |
| pra006 name | sw0 | sw1 | sw2 | sw3 | LED5 | LED6 | LED7 |
| pin number | 80 | 83 | 86 | 87 | 75 | 76 | 77 |
硬件实验结果如图1,

图1
图1,实现了2 + 2 = 4的结果,经过测试,各个输入组合,完全符合2位全加器的结果。
但在Modelsim编译仿真的过程中,却发现有如下错误,如图2

图2
Vivado18.2下编译也没有问题,仅给出一个警告,指示在11行的语句中(assign c = tmp0 + tmp1;)tmp0,tmp1在没有声明前使用。
“identifier tmp0 is used before its declaration [D:/FPGA_DDR3_IO/test_auto_func/test_auto_func.srcs/sim_1/new/test_wire.v:11]”
但在Vivado下仿真结果是正确的。如图2,
测试程序如下:
timescale 1 ns /1 ps
module tb1;
reg [1:0] a,b;
wire [2:0] c;
initial begin
a = 0;
b = 0;
#10
a = 1;
b = 0;
#10
a = 2;
b = 0;
#10;
a = 3;
b = 0;
#10;
a = 0;
b = 1;
#10;
a = 1;
b = 1;
#10;
a = 2;
b = 1;
#10;
a = 3;
b = 1;
#10;
a = 0;
b = 2;
#10;
a = 1;
b = 2;
#10;
a = 2;
b = 2;
#10;
a = 3;
b = 2;
#10;
a = 0;
b = 3;
#10;
a = 1;
b = 3;
#10;
a = 2;
b = 3;
#10;
a = 3;
b = 3;
#10;
end
test_wire test_wire_inst
(
.a (a),
.b (b),
.c (c)
);
endmodule

图2
虽然在Quartus 和Vivado 18.2下综合都没有问题,但Modelsim下却报语法错误,因此为了得到不同平台下输出结果一致,强烈建议在Verilog编程开发中,变量按照先声明后使用的原则。
2. reg型变量的声明及使用范围
相比较wire类型变量,reg类型变量使用要复杂了许多。reg型变量跟据使用范围可以分为全局变量和局部变量,根据变量的可见性又可以分为静态变量和动态变量。由于整形(integer),字符串,存储类型等最终都可以归结为reg类型,因此也遵循reg类型的共同特性。下面就reg类型的新特性加以介绍并举例说明。
- reg型变量的声明可以在modules的声明区域进行,也是遵循与wire同样的使用范围,即从声明的位置开始到 module结束。
- reg型变量可以在过程或函数内声明,如在always,function,task,initial等内部定义,在过程或函数内定义的变量其可见范围限定在过程或函数内,而且也是从声明的位置开始到过程结束有效。
- reg型变量也可以在任何含有begin…end的范围内定义及使用。其可见范围只在begin…end的范围内可见。
- reg型变量的可见性:
reg类型变量按照可见性又分为static和automatic两种。在变量声明时如果没有指明automatic类型,都默认为static类型。
automatic类型具有时效性,例如在过程或函数中声明的automatic变量在每次进入时重新初始化赋值。
static变量只初始化一次,在退出过程或函数时内部变量不可见,但在重新进入过程或函数时该变量又重新可见,而且保留上次退出时的值,因此static变量初始化只有一次,即第一次进入时对该变量初始化,之后再进入该过程将恢复上次退出时的值。
- 在begin…end块内部声明变量时要求begin关键字之后声明块的名称,如例2的实体程序中所示的 begin:fibo。这个要求在Quartus,Vivado 2018.2都很宽松,但在Modelsim的环境下是严格要求的。
- 在begin…end块内变量在声明时初始化:不同的编译器对变量声明的类型有不同要求,在Modelsim环境下只有动态变量才能在声明时初始化,而Vivado,Quartus在编译时没有严格要求。
- reg使用范围及静态(static)变量可见性测试。
- 实体程序如下:
module task_fibonacci
(
input rst,
input i,
input [ 7: 0 ] in_data,
output reg [ 15: 0 ] out_data,
output reg [ 15: 0 ] last_data0, last_data1
);
task fibo ( input rst, input integer i, input[ 7: 0 ] in_data, output [ 15: 0 ] out_data );
begin:fibo_body
reg [ 15: 0 ] last_data0, last_data1;
if ( rst )
begin
last_data0 = 0;
last_data1 = 0;
end
if ( in_data < 2 )
out_data = in_data;
else
out_data = last_data0 + last_data1;
last_data1 = last_data0;
last_data0 = out_data;
end
endtask
always@( i, in_data, rst )
begin
if ( rst )
begin
last_data0 = 0;
last_data1 = 0;
end
fibo( rst, i, in_data, out_data );
last_data0 = last_data0 + 1;
last_data1 = last_data0;
end
endmodule
- 仿真程序
`timescale 1ns /1ps
module tb;
reg [ 7: 0 ] in_data;
reg rst;
integer i;
wire [ 15: 0 ] fibo;
wire [ 15: 0 ] last_data0, last_data1;
initial
begin
rst = 1'b1;
i = 0;
in_data = 0;
#10
rst = 0;
in_data = 1;
while ( i < 256 )
begin
#10
$display ( “sequence = % 4d fibonacci = % d”, i + 1, fibo );
i = i + 1;
in_data = i;
end
#10;
end
task_fibonacci fibonacci_inst
(
.rst ( rst ),
.i ( i ),
.in_data ( in_data ),
.out_data ( fibo ),
.last_data0 ( last_data0 ),
.last_data1 ( last_data1 )
);
endmodule
2.仿真结果如图3

图3
从图3的仿真结果可以看出,实体程序中分别在module内声明的reg型变量与在task内声明的变量互不影响。可见在task内声明的变量作用范围,仅限于task内部使用。而且在没有automatic修饰符的情况下默认为静态(static)变量。本例中 last_data0,last_data1最终以锁存器的方式保存变量值。
- 动态(automatic)变量测试
修改例2中的实体程序如下:
module task_fibonacci
(
input rst,
input i,
input [ 7: 0 ] in_data,
output reg [ 15: 0 ] out_data,
output reg [ 15: 0 ] last_data0, last_data1
);
task fibo ( input rst, input integer i, input[ 7: 0 ] in_data, output [ 15: 0 ] out_data );
begin :fibo
automatic reg [ 15: 0 ] last_data0, last_data1;
if ( rst )
begin
last_data0 = 0;
last_data1 = 0;
end
if ( in_data < 2 )
out_data = in_data;
else
out_data = last_data0 + last_data1;
last_data1 = last_data0;
last_data0 = out_data;
end
endtask
always@( i, in_data, rst, last_data0, last_data1 )
begin
if ( rst )
begin
last_data0 = 0;
last_data1 = 0;
end
else
begin
fibo( rst, i, in_data, out_data );
last_data0 = last_data0 + 1;
last_data1 = last_data0;
end
end
endmodule

图3
从图3中显示的结果可以看出,由于task内的自动(automatic)变量没有初始化,因此last_data0,last_data1将X值传递给out_data,而外部的last_data0,last_data1值没有受到影响。修改上面的程序如例4。
例4:
module task_fibonacci
(
input rst,
input i,
input [ 7: 0 ] in_data,
output reg [ 15: 0 ] out_data,
output reg [ 15: 0 ] last_data0, last_data1
);
task fibo ( input rst, input integer i, input[ 7: 0 ] in_data, output [ 15: 0 ] out_data );
begin :fibo
automatic reg [ 15: 0 ] last_data0 = 0, last_data1 = 0;
if ( rst )
begin
last_data0 = 0;
last_data1 = 0;
end
if ( in_data < 2 )
out_data = in_data;
else
out_data = last_data0 + last_data1;
last_data1 = last_data0;
last_data0 = out_data;
end
endtask
always@( i, in_data, rst, last_data0, last_data1 )
begin
if ( rst )
begin
last_data0 = 0;
last_data1 = 0;
end
else
begin
fibo( rst, i, in_data, out_data );
last_data0 = last_data0 + 1;
last_data1 = last_data0;
end
end
endmodule
仿真结果如图4

图4
从图4可以看出,除了in_data为1时,in_data直接赋值给out_data外,其它时刻在调用task时,由于”out_data = last_data0 + last_data1;” , out_data返回结果都为0; 可见使用自动变量,每次调用task都会对自动变量赋初值。
例5:
module tb2;
reg [ 6: 0 ] a;
reg [ 15: 0 ] acc;
initial
begin
a = 0;
#10 a = 10;
#10 a = 20;
#10 a = 30;
#10 a = 40;
#10 a = 50;
#10 a = 60;
#10 a = 70;
#10 a = 80;
#10 a = 90;
#10 a = 100;
#10;
end
always@( * )
begin :acc_sum
reg [ 6: 0 ] tmp; //modelsim不能在声明时赋初值
tmp = 0;
acc = 0;
while ( tmp <= a )
begin
acc = acc + tmp;
tmp = tmp + 1'b1;
end
end
endmodule

图5
例5程序中在always内定义了tmp变量,初始化并参与运算可以有较好的封装性,由于该变量在always快外不可见,可以自由使用不会与always快外的同名变量冲突或赋值的相互覆盖。
3.reg变量声明和使用总结如下(以modelsim编译器为准):
- always内声明变量有如下特点:
- always内声明变量只能static类型变量,而不能声明automatic类型变量。
- always块内的begin之后要跟块的名称,如:begin :acc_sum。
- always块内声明的变量只在该always块内可见。
- always块内声明的reg变量不能在声明时赋初值,只能在声明后通过显示赋值
- automatic变量的声明及使用范围:
- automatic只能在function或task内声明和使用,不能always,initial过程内使用。
- 声明时赋初值
- 在module声明时可以赋初值。
- automatic变量在声明时可以赋初值。
对应视频:
学习本节课后有几点收获,1、变量要先声明后使用;2、wire型变量的声明、赋值、使用范围;3、reg型变量区分为static、automatic两种类型;4、automatic变量的声明只可用在function和task内部,并且需要初始化赋值;
本篇文章非常详细具体的讲解了wire、reg类型变量的声明方式、赋值方式、使用范围等知识,阅读后对变量的声明及使用有了更深入的了解,文中有很多值得深思揣摩的观点,相信对自己今后的工作会有很大益处!