RTL表达式和运算符

描述

数字硬件建模SystemVerilog-循环语句

经过几周的更新,SV核心部分用户自定义类型和包内容已更新完毕,接下来就是RTL表达式和运算符。

马上HDLBits-SystemVerilog版本也开始准备了,基本这一部分完成后就开始更新~

编程

循环语句允许多次执行编程语句或begin-end语句组。SystemVerilog中的循环语句有:for、repeat、while、do..while、foreach和forever。其中,所有综合编译器只支持for和repeat循环。其他类型的循环可能由一些综合编译器支持,但这些限制限制了这些循环的用途。本系列重点介绍所有综合编译器都支持的for和repeat循环。

for循环语句

for循环的一般语法是:

编程

循环开始时,initial_assignment只执行一次。

end_expression在循环第一次通过之前进行计算。如果表达式为true,则执行语句或语句组。如果表达式为false,则循环退出。

在每次循环结束时执行step_assignment。再次计算end_expression。如果为真,则循环重复,否则退出循环。

下面的代码片段演示了使用for循环的一个简单示例,该示例使用b_bus中的反向位位置对a_bus的每个位进行异或。对于4位总线,a_bus[0]与b_bus[3]进行异或,a_bus[1]与b_bus[2]进行异或,以此类推。

编程

综合编译器“展开”循环体来实现循环,这意味着循环中的语句或begin…end语句组被复制到循环迭代的次数。在上面的代码片段中,赋值语句被复制了四次,因为循环从0迭代到3。综合时展开循环后看到的代码是:

编程

循环将执行的迭代次数必须是固定的次数,以便综合器进行循环展开。迭代次数固定的循环称为静态循环。

循环的优势在迭代次数越多时越明显,如果a和b在上面的for循环片段中是64位总线,则需要64行代码来手动异或两条64位总线,对于for循环,无论总线的向量大小如何,只需要两行代码。

示例6-7展示了上述代码片段的完整参数化模型,图6-7显示了综合该模型的结果。

示例6-7:使用for循环对向量位进行操作

//`begin_keywords "1800-2012" // use SystemVerilog-2012 keywords
module bus_xor
#(parameter N = 4)            // bus size
(input  logic [N-1:0] a, b,   // scalable input size
 output logic [N-1:0] y       // scalable output size
);
  timeunit 1ns; timeprecision 1ns;

  always_comb begin 
    for (int i=0; i
编程 图6-7:示例6-7的综合结果:循环对向量位进行操作

 

在图6-7中可以看到,for循环的四次迭代是如何展开的,以及如何成为异或操作的四个实例。

静态循环与依赖数据的循环 (Static loops versus data-dependent loops)

静态循环,也称为数据独立循环,在这种循环中,可以确定迭代次数,而不必知道任何变量网络的值。for (int i=0;i <= 3;i++)是一个静态循环。可以确定循环将迭代4次(i=0  到i = 3),这种不依赖于其他信号,就能确定循环迭代次数的循环就是静态循环。

依赖数据的循环(data-dependent loop)是一种非静态循环,需要评估网络或变量的值,以确定循环将执行多少次。for (int i=0; i<=count; i++)依赖于count具体的数值,因为在不知道count值的情况下,无法确定循环将迭代多少次。

零延迟和定时循环(Zero-delay and timed loops)

零延迟循环不包含任何形式的时序。零延迟循环代表组合逻辑。在仿真中,零延迟循环会立即执行。在由综合器生成的门级电路实现中,零延迟循环在单个时钟周期内执行。前例6-7中所示的for循环是零延迟静态循环。

定时循环是需要消耗时间来执行循环的每个过程。定时循环并不代表组合逻辑的行为,因为循环的执行可能需要超过一个时钟周期才能完成。

最佳实践指南6-3
for循环是静态的、零延迟的循环,迭代次数固定。

为了展开循环,综合编译器需要能够静态地确定循环迭代次数。虽然有些for循环代码写的是静态循环,并且仿真也是正确的,但是可能是不可综合的。这方面的一个例子是:

编程

代码片段的目的是遍历数据向量,以找到为1的最低编号位。循环从数据的最低有效位0开始,并向上迭代,直到数据中的一位为l。通过修改end_count(循环结束条件)的值,找到第一个为l的位后,循环立即终止。虽然在循环开始之前结束计数被初始化为32,但它的值可以随着循环的执行而改变。

综合编译器在这个代码片段中遇到的问题是,不可能静态地确定循环将迭代多少次,因为循环的结束条件可能会根据输入的数据值(data值)发生变化而变化。为了展开循环,综合需要循环执行固定的次数。

无需依赖数据即可退出循环的可综合方式。示例6-8显示了前面代码段的可综合编码样式。示例6-8使用一个执行固定次数的静态循环,避免不是在循环结束时提前终止循环,而不是根据数据的值(data值)来确定循环的结束。

当找到最低的为1的位时,循环对剩余的迭代不做任何操作,图6-8显示了综合该示例的结果。在本例中,数据的总线大小是参数化的,并设置为4位宽,以便减小综合后的原理图的大小。

例6-8;使用for循环查找向量中为1的最低位

//`begin_keywords "1800-2012" // use SystemVerilog-2012 keywords
module find_lowest_bit
#(parameter N = 4)            // bus size
(input  logic [N-1:0]         data,
 output logic [$clog2(N):0]  low_bit
);
  timeunit 1ns; timeprecision 1ns;

  logic        done;  // local flag
  
  always_comb begin 
    // find lowest bit that is set in a vector
    low_bit = '0;
    done = '0;
    for (int i=0; i<=N-1; i++) begin 
      if (!done) begin 
        if (data[i]) begin 
          low_bit = i;
          done = '1;
        end 
      end 
    end 
  end 
  
endmodule: find_lowest_bit
//`end_keywords
图6-8:示例6-8的综合结果 编程

 

最佳实践指南6-4
以固定的迭代大小对所有循环进行编码,这种编码风格确保循环可以展开,并且将得到所有综合编译器的支持。

循环迭代器变量寿命和可见性(For-loop iterator variable lifetime and visibility)

用于控制for循环的变量称为循环迭代器变量。通常,循环迭代器变量被声明为initial assignment(初始赋值)的一部分,如下所示:

编程

当作为初始赋值的一部分声明时,循环迭代器变量是for循环的局部变量,不能在循环外引用。循环迭代器变量是自动生成的,这意味着该变量在循环开始的时间创建,并在循环退出时消失。

循环迭代器变量也可以在for循环之外声明,例如在模块级别或在命名的begin-end组中声明。外部声明的循环迭代器变量在循环退出后仍然存在,可以在声明变量的同一范围内的其他地方使用。当循环退出时,外部变量的值将是在结束条件评估为false之前,赋值步骤所指定的最后一个值。

Repeat循环

Repeat循环执行循环一定次数。Repeat循环的一般语法是:

编程

以下示例使用Repeat循环将data信号提高到3的幂(数据立方)。

编程

SystemVerilog有一个指数幂运算符,但一些综合编译器不支持该运算符。上面的代码片段显示了如何使用Repeat循环算法执行指数运算(将一个值与自身重复相乘)。

与for循环一样,如果循环的边界是静态的,则Repeat循环是可综合的,这意味着循环迭代的次数要求是固定的,并且不依赖于运行过程中可能发生变化的值。

示例6-9显示了上述指数运算片段的完整示例。在本例中,数据输入的宽度和指数或幂运算被参数化,以使示例更通用。这些参数在编译时是固定的常量。因此,使用参数作为迭代次数的Repeat循环是可综合的静态循环。这个模型的输出q是时序逻辑,因此q要使用非阻塞赋值,循环中的迭代是组合逻辑,其最终结果记录在阻塞赋值的临时变量中,因此,它的新值可用于循环的下一次迭代。

示例6-9:使用Repeat循环实现幂运算

//`begin_keywords "1800-2012" // use SystemVerilog-2012 keywords
module exponential
#(parameter E = 3,    // power exponent
  parameter N = 4,    // input bus size
  parameter M = N*2   // output bus size
)
(input  logic         clk,
 input  logic [N-1:0] d,
 output logic [M-1:0] q
);
  timeunit 1ns; timeprecision 1ns;

  always_ff @(posedge clk) begin: power_loop
    logic [M-1:0] q_temp; // temp variable for inside the loop
    if (E == 0)
      q <= 1;  // do to power of 0 is a decimal 1
    else begin 
      q_temp = d;
      repeat (E-1) begin 
        q_temp = q_temp * d;
      end 
      q <= q_temp;
    end 
  end: power_loop
  
endmodule: exponential
//`end_keywords 

 

图6-9显示了示例6-9的综合结果,当E的值为3时,Repeat循环执行2次,综合结果创建了乘法器的2个实例。输出向量q的每一位都由一个通用触发器进行赋值,图中只显示了第一个输出寄存器触发器,

编程 图6-9:示例6-9的综合结果:Repeat循环实现幂运算

综合时间考虑。静态、零延迟的循环或Repeat循环将综合为组合逻辑。如果该组合逻辑的输出被记录在触发器中,那么由循环推断的组合逻辑的总传播延迟必须小于一个时钟周期。

笔记
每个特定ASIC或FPGA设备的功能和限制可能会有很大的不同。使用乘法、除法、模和幂运算符的RTL模型应与目标设备的功能相匹配。

注意,在图6-9中,示例6-9中Repeat循环推断的乘法器是级联的。乘法器链的总传播延迟需要小于等于一个时钟周期,以便在输出触发器中记录有效且稳定的结果。一些综合编译器可以进行寄存器重定时,插入或移动寄存器,以在组合逻辑中创建流水。寄存器重定时是综合编译器的一项功能,不在本文的范围内。有关此主题的更多信息,请参阅综合编译器的文档。

如果寄存器重定时不可用,则不满足设计时钟周期的循环将需要重新编码为流水或状态机形式,手动将循环展开为多个时钟周期。

While和do-While循环

最佳实践指南6-5
使用for循环和repeat循环进行RTL建模。不要使用while和Do-while循环。

尽管许多综合编译器都支持这些循环,但它们有一些限制,比如使代码难以维护和重用,这就限制了它们在RTL建模中的实用性。相反,使用for循环或repeat循环,由于循环迭代的次数是静态的,所以增加了它们在RTL建模中的实用性。为了完整起见,本文简单介绍了while和do-while循环,但不推荐使用。

while循环执行编程语句或begin-end语句组,直到end_expression变为false。在循环的顶部计算结束表达式(end_expression)。如果第一次输入循环时结束表达式为false,则根本不执行语句或语句组。如果结束表达式为true,则执行语句或语句组,然后循环返回顶部并再次计算结束表达式(end_expression)。

do-while循环也执行编程语句或begin-end语句组,直到end_expression变为false。通过do-while循环,结束表达式(end_expression)在循环的底部进行计算。因此,第一次必进入循环。如果循环到达底部时结束表达式(end_expression)为false,则循环退出。如果结束表达式(end_expression)为true,循环将返回顶部并再次执行语句或语句组,

下面的代码显示了一个使用while循环的不可综合示例:

编程

此示例统计16位data信号中有多少位被设置为l。data值被复制到名为temp的临时变量中。如果设置了temp的位0为l,则num_ones计数器将递增。然后将temp变量右移一次,这将移出位0,并将位0移到位15。只要至少有一位temp被设置为1,temp的计算结果为true,循环就会继。当temp的计算结果为false时,循环退出。temp中的某个值在某些位中有X或Z,但没有将任何位设置为1,这也会导致while循环退出。

本示例不可综合,因为循环执行的次数取决于data,不是静态的,如上一节所述。综合无法明确地确定循环将执行多少次,因此无法展开循环,就无法综合。

For each循环和通过向量的循环

For each循环遍历未压缩数组的所有维度。未压缩数组是网络或变量的集合,其中集合可以通过使用数组名称作为一个整体进行操作,或者数组的单个元素可以使用数组中的索引进行操作。数组的元素可以是任何数据类型和向量大小,但数组的所有元素必须是相同的类型和大小。数组可以有任意数量的维度。数组声明的一些示例如下:

编程

可以使用[ starting_address:ending_address]样式,如上面的mem数组,或使用[dimension_sizel风格,与查找表数组一样,前面更详细地讨论了声明和使用未压缩数组。

foreach循环用于迭代数组元素,foreach循环将自动声明其循环控制变量,自动确定数组的开始和结束索引,并自动确定索引的方向(增加或减少循环控制变量)。

下面的示例遍历一个二维数组,该数组表示带有一些数据的查找表。对于数组中的每个元素,都会调用一个函数来对该值进行某种操作(函数未显示)。

编程

请注意,i和j变量没有声明——foreach循环会在内部自动声明这些变量。也不需要知道数组的每个维度的边界。foreach循环会自动从每个维度的最低索引值迭代到最高索引值。

在整理这个系列时,一些综合编译器不支持foreach循环。在RTL模型中使用之前,工程师应该确保项目中使用的所有工具都支持哪种循环类型。

笔记
迭代数组所有维度的另一种编码方式是使用for循环。前面的示例可以使用所有综合编译器支持的静态for循环重写。

编程

请注意,在这个嵌套for循环示例中,每个数组维度的大小及其起始和结束索引值必须进行硬编码(即需要明确的数值),以匹配数组声明的大小。SystemVerilog还提供数组查询系统功能,适用于不同大小或参数化大小的数组,可使for循环更通用。前面的例子可以写成:

编程

笔记
在编写本文时,一些综合编译器不支持数组查询系统函数。在RTL模型中使用之前,工程师应该确保项目中使用的所有工具都支持这些功能。

以下是数组查询系统功能的简要说明。有关这些查询功能的更多信息,请参阅IEEE 1800 SystemVerilog语言参考手册。

(数组名,维度)-返回指定维度的最右边索引号。维度以数字1开头,从最左边的未压缩维度开始。在最右边的未压缩维度之后,维度编号与最左边的压缩维度继续,并以最右边的压缩维度结束。

(数组名,维度)-返回指定维度最左边的索引号。尺寸标注的编号与相同。

(数组名,维度)-如果大于或等于,则返回1;如果小于,则返回-1。

(数组名,维度)-返回指定维度的最低索引号,可以是左索引或右索引。

(数组名,维度)-返回指定维度的最高索引号,可以是左索引或右索引。

(数组名,维度)-返回指定维度中的元素总数(与-+1相同)。

(数组名)返回数组中的维度数,包括压缩维度和未压缩维度,  

      审核编辑:彭静
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分