自动变量有什么好处?
What is the benefit of automatic variables?
我正在 Systemverilog 中寻找 "automatic" 的好处。
我一直在看 "automatic" 阶乘示例。但我无法理解它们。有谁知道我们为什么使用 "automatic"?
传统上,Verilog 一直用于 RTL 和门级抽象的硬件建模。由于 RTL 和门级抽象都是 static/fixed(非动态),Verilog 仅支持静态变量。因此,例如,Verilog 中的任何 reg 或 wire 在仿真开始时都将是 instantiated/mapped 并且将保留在仿真内存中的映射直到仿真结束。因此,您可以将任何 wire/reg 转储为波形,并且 reg/wire 从开始到结束都有一个值,因为它始终被映射。从程序员的角度来看,这些变量被称为 static。在 C/C++ 世界中,要声明这样一个变量,您将不得不使用 storage class specifier static。在 Verilog 中,每个变量都是隐式静态的。
请注意,在 SystemVerilog 出现之前,Verilog 仅支持静态变量。尽管 Verilog 也支持一些用于行为抽象建模的构造,但由于缺少 automatic 存储 class.
,支持受到限制
automatic(在软件世界中称为auto)存储class变量映射到栈上。调用函数时,函数中声明的所有局部(非静态)变量都映射到堆栈中的各个位置。由于此类变量只存在于栈中,一旦函数执行完毕,它们就不存在了,栈也相应缩小。
除其他优势外,此存储 class 实现的一种可能性是 递归函数 。在 Verilog 世界中,函数不能重入。在自动存储 class 不可用的世界中,递归(或可重入)函数没有任何用处。要理解这一点,您可以将可重入函数想象成动态地对其自身进行多次递归实例化的函数。每个实例都将其 automatic 变量映射到堆栈上。随着递归的进行,堆栈会增长,每个函数都可以使用自己的变量集进行计算。当函数调用 return 时,将整理计算值并提供最终结果。只有静态变量,每个函数调用都会将变量值存储在相同的公共位置,从而消除了多次调用(实例化)的任何好处。
来到阶乘算法,将阶乘概念化为递归算法相对容易。在数学中我们写 factorial(n) = n(factial(n-1))*。所以你需要计算阶乘(n-1)才能知道阶乘(n)。请注意,没有终止情况就无法完成递归,在阶乘的情况下为 n=1.
function automatic int factorial;
input int n;
if (n > 1)
factorial = factorial (n - 1) * n;
else
factorial = 1;
endfunction
没有自动存储class,因为当我们从阶乘内部调用阶乘(n-1)时,函数中的所有变量都将映射到固定位置(n),递归调用将覆盖调用者上下文中的任何变量。在上面代码段定义的阶乘函数中,如果我们不指定存储class为automatic,那么n和结果 factorial 将被对 factorial(n-1) 的递归调用覆盖。结果变量 n 将被连续覆盖为 n-1, n-2, n-3 依此类推,直到达到n = 1的终止条件。对 factorial 的终止递归调用会将值 1 分配给 n 并且当递归展开时, factorial(n-1) * n 将在每个阶段评估为 1.
有了自动存储class,每个递归函数调用都会在内存中有自己的位置(实际上在堆栈上)来存储变量n。因此,对阶乘的连续调用不会覆盖调用者的变量 n。结果,当递归展开时,我们将得到 factorial(n) 的正确值 n*(n-1)(n-2) .. *1.
请注意,也可以使用迭代来定义阶乘。这可以在不使用 自动 存储 class 的情况下完成。但在许多情况下,递归使用户能够以更直观的方式编写算法代码。
另一个例子是在for循环中使用fork join -
如果不使用 automatic,for 循环中的 fork join 将无法正常工作。
for (int i=0; i<`SOME_VALUE ; i++) begin
automatic int id=i;
fork
task/function using the id above ;
...
join_none
end
我建议 1 个示例如下(使用 fork...join_none):
Ex.1(未使用 automatic):值输出将为“3 3 3 3”。因为 i 在退出 for 循环后取最新值,所以 i 存储在静态内存位置。这可能是您代码中的错误。
initial begin
for( int i =0; i<=3 ; i++)
fork
$write ("%d ", i);
join_none
end
Ex.2(使用 automatic):值输出将为“0 1 2 3”。因为在每个循环中,i 的值被复制到 k,并且 fork..join_none 产生一个具有每个值 k(每个循环会为k定位1个内存space : k0, k1, k2, k3):
initial begin
for( int i =0; i<=3 ; i++)
fork
automatic int k = i;
$write ("%d ", k);
join_none
end
我正在 Systemverilog 中寻找 "automatic" 的好处。 我一直在看 "automatic" 阶乘示例。但我无法理解它们。有谁知道我们为什么使用 "automatic"?
传统上,Verilog 一直用于 RTL 和门级抽象的硬件建模。由于 RTL 和门级抽象都是 static/fixed(非动态),Verilog 仅支持静态变量。因此,例如,Verilog 中的任何 reg 或 wire 在仿真开始时都将是 instantiated/mapped 并且将保留在仿真内存中的映射直到仿真结束。因此,您可以将任何 wire/reg 转储为波形,并且 reg/wire 从开始到结束都有一个值,因为它始终被映射。从程序员的角度来看,这些变量被称为 static。在 C/C++ 世界中,要声明这样一个变量,您将不得不使用 storage class specifier static。在 Verilog 中,每个变量都是隐式静态的。
请注意,在 SystemVerilog 出现之前,Verilog 仅支持静态变量。尽管 Verilog 也支持一些用于行为抽象建模的构造,但由于缺少 automatic 存储 class.
,支持受到限制automatic(在软件世界中称为auto)存储class变量映射到栈上。调用函数时,函数中声明的所有局部(非静态)变量都映射到堆栈中的各个位置。由于此类变量只存在于栈中,一旦函数执行完毕,它们就不存在了,栈也相应缩小。
除其他优势外,此存储 class 实现的一种可能性是 递归函数 。在 Verilog 世界中,函数不能重入。在自动存储 class 不可用的世界中,递归(或可重入)函数没有任何用处。要理解这一点,您可以将可重入函数想象成动态地对其自身进行多次递归实例化的函数。每个实例都将其 automatic 变量映射到堆栈上。随着递归的进行,堆栈会增长,每个函数都可以使用自己的变量集进行计算。当函数调用 return 时,将整理计算值并提供最终结果。只有静态变量,每个函数调用都会将变量值存储在相同的公共位置,从而消除了多次调用(实例化)的任何好处。
来到阶乘算法,将阶乘概念化为递归算法相对容易。在数学中我们写 factorial(n) = n(factial(n-1))*。所以你需要计算阶乘(n-1)才能知道阶乘(n)。请注意,没有终止情况就无法完成递归,在阶乘的情况下为 n=1.
function automatic int factorial;
input int n;
if (n > 1)
factorial = factorial (n - 1) * n;
else
factorial = 1;
endfunction
没有自动存储class,因为当我们从阶乘内部调用阶乘(n-1)时,函数中的所有变量都将映射到固定位置(n),递归调用将覆盖调用者上下文中的任何变量。在上面代码段定义的阶乘函数中,如果我们不指定存储class为automatic,那么n和结果 factorial 将被对 factorial(n-1) 的递归调用覆盖。结果变量 n 将被连续覆盖为 n-1, n-2, n-3 依此类推,直到达到n = 1的终止条件。对 factorial 的终止递归调用会将值 1 分配给 n 并且当递归展开时, factorial(n-1) * n 将在每个阶段评估为 1.
有了自动存储class,每个递归函数调用都会在内存中有自己的位置(实际上在堆栈上)来存储变量n。因此,对阶乘的连续调用不会覆盖调用者的变量 n。结果,当递归展开时,我们将得到 factorial(n) 的正确值 n*(n-1)(n-2) .. *1.
请注意,也可以使用迭代来定义阶乘。这可以在不使用 自动 存储 class 的情况下完成。但在许多情况下,递归使用户能够以更直观的方式编写算法代码。
另一个例子是在for循环中使用fork join -
如果不使用 automatic,for 循环中的 fork join 将无法正常工作。
for (int i=0; i<`SOME_VALUE ; i++) begin
automatic int id=i;
fork
task/function using the id above ;
...
join_none
end
我建议 1 个示例如下(使用 fork...join_none):
Ex.1(未使用 automatic):值输出将为“3 3 3 3”。因为 i 在退出 for 循环后取最新值,所以 i 存储在静态内存位置。这可能是您代码中的错误。
initial begin
for( int i =0; i<=3 ; i++)
fork
$write ("%d ", i);
join_none
end
Ex.2(使用 automatic):值输出将为“0 1 2 3”。因为在每个循环中,i 的值被复制到 k,并且 fork..join_none 产生一个具有每个值 k(每个循环会为k定位1个内存space : k0, k1, k2, k3):
initial begin
for( int i =0; i<=3 ; i++)
fork
automatic int k = i;
$write ("%d ", k);
join_none
end