数字硬件建模SystemVerilog-程序块 (procedural blocks)
经过几周的更新,SV核心部分用户自定义类型和包内容已更新完毕,接下来就是RTL编程语句。
程序块是编程语句的容器。程序块的主要目的是控制何时应执行编程语句,例如每当时钟上升沿出现时,或每当信号或总线改变值时。SystemVerilog有两种主要类型的程序块: initial 程序块和always 程序块。
initial 程序是一种验证结构;综合编译器不支持。但是有一个例外是,综合编译器支持使用readmemh系统任务加载内存块或分配给特定内存地址的 initial 程序。FPGA综合器可能还允许使用 initial 程序对设备通电状态进行建模,本文不讨论或使用 initial 程序,因为它们不用于对RTL功能进行建模。
过程是无限循环。它们执行编程语句,完成后自动重新开始。一般的概念是,当电源打开时,硬件在做一些连续的事情。这种连续行为是使用always 程序建模的。
SystemVerilog有四种类型的always 程序:使用关键字always的通用过程和使用关键字always_ff, always_comb 和 always_latch的专用always 过程。
always 程序块可用于多种类型建模,包括可综合RTL模型、抽象行为模型(如不会综合的RAM)以及验证代码(如时钟振荡器或连续响应检查器)。虽然通用always程序的灵活性使其在各种建模和验证项目中都很有用,但同样的灵活性意味着软件工具不知道always的预期用途是什么,什么时候用于可综合的RTL模型。为了将RTL模型准确地转换为ASIC或FPGA设备,综合器对通用always程序设置了许多编码限制。
专用的RTL程序。Always_ff、always_comb和always_latch专用always程序块的行为与通用always程序块相同,但会施加综合所需的特殊编码限制。这些额外的限制有助于确保RTL仿真的行为与实际ASIC或FPGA的门级行为相匹配。正如这些专用程序的名称所表明的,Always_ff对仿真触发器等时序逻辑器件施加了某些综合限制。Always_comb为建模组合逻辑(如解码器)施加了某些综合限制,always_latch为建模锁存行为施加了某些综合限制。后面会详细说明每个过程块功能及验证。
敏感列表
always 程序告诉仿真,应该“always”评估被建模的功能(一个无限循环),但仿真和综合都需要了解更多信息,以便准确地建模硬件行为。这些工具还需要知道何时执行程序块中的语句。对于RTL建模,时间要么在表示时序逻辑的时钟边沿上,要么在表示组合逻辑或锁存逻辑的过程更改值所使用的任何信号上。
为了控制在可综合RTL模型中何时执行编程语句,程序是以敏感列表开始,敏感列表是一个信号列表,值的变化将触发程序的执行。通用always和RTL特定always_ff程序要求RTL设计工程师明确规定灵敏度列表。RTL特定的always_comb和always_latch程序将推断出一个隐式灵敏度列表。
显式指定的敏感度列表与@标记一起引入,口头上称为“at”。在可综合RTL建模中,灵敏度列表包含一个或多个网络或变量名的列表。名称可以用逗号(,)或关键字”or”分隔。
以下两个明确的敏感度列表功能相同:
在敏感度列表的上下文中,or关键字只是一个分隔符:它不是or操作。逗号与关键字or的使用取决于用户的偏好。一种风格在功能上并不优于另一种风格。
灵敏度列表还可以指定标量(l位)信号的特定边沿,该边沿将触发always 程序。边沿由关键词posedge和negedge指定,边沿灵敏度对于基于时钟的功能非常重要:
always @(posedge elk or negedge rstN)…
posedge关键字是“正边沿”的缩写,negedge是“负边沿”的缩写。正边沿是任何可能被硅晶体管感知为正向过渡的过渡。因此,posedge将在0-to-1、0-to-z、0-to-x、z-to-1、x-to-l、z-to-x和x-to-z转换时触发,相反,negedge将在1-to-0、1-to-z、1-to-x、z-to-0、x-to-0、z-to-x和x-to-z转换时触发.
时序逻辑灵敏度。时序逻辑元件,如触发器,在时钟边沿触发,通常是该时钟的上升沿。(一些ASIC和FPGA设备具有在时钟下降沿触发的组件,很少有在时钟两侧触发的组件。)为了指示always 程序代表时钟触发的时序逻辑行为,always或always_ff关键字后跟:
@ (posedge
例如:
always_ff @(posedge clk) q <= d; //时序逻辑触发器
一些时序元件具有异步输入,例如set或reset控制。这些异步信号也会影响仿真或综合时评估always程序的运行时间,因此也应该包括在灵敏度列表中。
后面章节更详细地讨论了时序逻辑的建模,包括同步和异步set、enable控制,以及正确使用通用always和专用always_ff程序块的指南。
组合逻辑灵敏度。组合逻辑(如加法器或解码器)的输出反映了该逻辑块当前输入值的组合。因此,每当组合逻辑的任何输入值改变时(即敏感度列表),就需要重新评估组合逻辑中的编程语句。为了仿真这种行为,always关键字后面是一个明确的敏感度列表,其中包括该逻辑块读取的所有信号,其形式为:
@(
例如:
always @(a, b) sum = a + b;
always_comb专用always程序的一个特点是,它自动推断出一个合适的组合逻辑灵敏度列表。上述加法器代码使用always_comb建模为:
always_comb @(a, b) sum = a + b;
后面章节将更详细地讨论组合逻辑建模,以及always和always_comb程序块的正确使用指南。
latch逻辑灵敏度。锁存是组合逻辑块的一种形式,可以存储其当前状态,建模锁存行为遵循与建模组合逻辑行为相同的敏感度列表规则。Always_latch关键字后面是一个灵敏度列表,其中包括该逻辑块读取的所有信号,格式为:
@(
如下所示:
Always_latch专用always程序自动推断出正确的组合逻辑灵敏度列表。
后续章节将更详细地讨论了锁存逻辑的建模,包括使用always和always_latch程序块的最佳实践编码指南。
不可综合的敏感度列表。从语法上讲,灵敏度列表可以包含操作,例如@(a+b)或iff保护条件,posedge和negedge限定符也可以用于大于1位宽的向量,但只使用向量的最低有效位(最右边的位),向量中其他位的更改不会触发敏感度列表,RTL综合编译器通常不支持操作:iff和向量边沿(posedge和negedge)。
begin-end语句组
所有形式的程序块都可以包含一条语句或一组语句。语句组包含在关键字begin和end之间,可以包含任意数量的语句,包括none语句。下面的代码片段显示了一个包含单个语句的always 程序和一个包含begin end组的always 程序。
一条语句可以嵌套在另一条语句中,如:
在前面的代码段中,外部语句是always 程序中的单个语句,因此不需要begin-end语句组。
可以使用以下语法命名begin-end语句组:
begin:
命名语句组可以包含局部变量声明,局部变量可以在语句组内使用,但不能在可综合RTL模型的组外引用,(SystemVerilog的更高版本增加了在未命名的begin端组中声明局部变量的功能,但在编写本文时,大多数综合编译器都不支持这种功能。)
也可以选择命名组的匹配端。命名语句组的结尾可以帮助直观地匹配嵌套的语句组。SystemVerilog要求用于开始和结束的名称必须完全匹配。
局部变量的使用有助于确保在某些情况下得到适当的综合结果。在时序always程序中计算并由另一个程序使用的临时中间变量可能在仿真中起作用,但可能综合出与RTL仿真行为不匹配的门级功能,在过程中声明局部变量将防止此编码错误-无法从过程外部访问局部变量,
下面的示例声明了一个临时变量,该变量位于always_ff 过程的局部。临时变量用于计算中间结果,然后用于计算最终结果(本例中的计算特意保持简单,以便专注于局部变量的声明,而不是一些可能需要中间计算的复杂算法)
请注意,冒号前后允许有空白,如上面的begin后面所示。但是,end后面不能有空白,如上图所示。使用空格有助于使复杂的代码更易于阅读。
在程序块中使用变量和网络
程序赋值的左侧只能是变量类型,包括基于变量的用户自定义类型。在运算符或赋值语句更新之前,变量仍然保持其先前的值,变量的这种特性会影响仿真和综合。
在下面的代码段中,sum必须声明为变量类型,因为它位于过程赋值的左侧。有关RTL建模中可使用的可综合变量类型的讨论,请参见之前的文章。
只有程序赋值的左侧必须是变量。赋值的右侧可以使用变量、网络、参数或文字值。
编辑:黄飞
评论