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关键字显式赋值
- 在模块例化时中赋值
- 函数返回值或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 II综合时没有发现任何相关的warning和error;而且在FII-PRA 006上进行硬件验证,结果也正常,的确实现了两位全加器的结果。实验时管脚锁定如下:
表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 II和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 II, Vivado 2018.2都很宽松,但在Modelsim的环境下是严格要求的。
- 在begin…end块内变量在声明时初始化:不同的编译器对变量声明的类型有不同要求,在modelsim环境下只有动态变量才能在声明时初始化,而Vivado, Quartus II 在编译时没有严格要求。
- 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
- 仿真结果如图3
图3
从图3的仿真结果可以看出,实体程序中分别在module内声明的reg型变量与在task内声明的变量互不影响。可见在task内声明的变量作用范围,仅限于task内部使用。而且在没有automatic修饰符的情况下默认为静态(static)变量。本例中 last_data0, last_data1最终以锁存器的方式保存变量值。
- 动态(automati)变量测试
修改例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; //不能在声明时赋初值 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变量在声明时可以赋初值。