Menu Close

变量类型及使用范围深入探讨

Verilog变量声明与基本数据类型等章节中介绍变量的声明,也简单介绍了变量的使用及使用情况,随着学习和理解的逐渐深入,有必要将变量的类型作更深入的探讨。

  1. 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,

%title插图%num

图1

图1,实现了2+2=4的结果,经过测试,各个输入组合,完全符合2位全加器的结果。

但在Modelsim编译仿真的过程中,却发现有如下错误,如图2

%title插图%num

图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

 

%title插图%num

图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

 

  1. 仿真程序
`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

 

  1. 仿真结果如图3

%title插图%num

图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

 

%title插图%num

图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

%title插图%num

图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

 

%title插图%num

图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变量在声明时可以赋初值。

 

 

Posted in FPGA, Quartus II, Verilog, Verilog, 教材与教案, 文章, 编程语言

发表评论

相关链接