Verilog 中的Behave建模和时序


Verilog 中的Behave模型包含程序语句,这些语句控制模拟并操作数据类型的变量。所有这些语句都包含在程序中。每个过程都有一个与其关联的活动流。

在Behave模型的模拟过程中,由“始终”和“初始”语句定义的所有流程在模拟时间“零”处一起开始。初始语句执行一次,always 语句重复执行。在此模型中,寄存器变量 a 和 b 在仿真时间“零”时分别初始化为二进制 1 和 0。然后初始语句完成,并且在模拟运行期间不会再次执行。该初始语句包含语句的开始-结束块(也称为顺序块)。在这个开始-结束类型块中,首先初始化a,然后初始化b。

Behave建模示例

module behave; 
reg [1:0]a,b; 

initial 
begin 
   a = ’b1; 
   b = ’b0; 
end 

always 
begin 
   #50 a = ~a; 
end 

always 
begin 
   #100 b = ~b; 
end 
End module 

程序分配

程序赋值用于更新 reg、整数、时间和内存变量。程序分配和连续分配之间存在显着差异,如下所述 -

连续赋值驱动网络变量,并在输入操作数更改值时进行评估和更新。

过程赋值在围绕寄存器变量的过程流结构的控制下更新寄存器变量的值。

过程赋值的右侧可以是任何计算结果的表达式。但是,右侧的部分选择必须具有恒定的索引。左侧表示从右侧接收赋值的变量。程序分配的左侧可以采用以下形式之一 -

  • 寄存器、整数、实数或时间变量 - 对这些数据类型之一的名称引用的赋值。

  • 寄存器、整数、实数或时间变量的位选择 - 对单个位的赋值,使其他位保持不变。

  • 寄存器、整数、实数或时间变量的部分选择 - 两个或多个连续位的部分选择,其余位保持不变。对于部分选择形式,只有常量表达式是合法的。

  • 记忆元素- 记忆的单个字。请注意,位选择和部分选择在内存元素引用上是非法的。

  • 上述任何形式的串联 - 可以指定前四种形式中任何一种的串联,这有效地对右侧表达式的结果进行分区,并将分区部分按顺序分配给串联的各个部分。

分配延迟(不适用于综合)

在延迟赋值中,在执行语句和进行左侧赋值之前会经过 Δt 时间单位。在分配内延迟的情况下,会立即评估右侧,但在将结果放入左侧分配之前会有 Δt 的延迟。如果另一个过程在 Δt 期间改变右侧信号,则不会影响输出。综合工具不支持延迟。

句法

  • 过程赋值变量 = 表达式

  • 延迟赋值#Δt变量=表达式;

  • 赋值内延迟变量 = #Δt 表达式;

例子

reg [6:0] sum; reg h, ziltch; 
sum[7] = b[7] ^ c[7]; // execute now. 
ziltch = #15 ckz&h; /* ckz&a evaluated now; ziltch changed 
after 15 time units. */ 

#10 hat = b&c; /* 10 units after ziltch changes, b&c is
evaluated and hat changes. */ 

阻塞分配

阻塞过程赋值语句必须在顺序块中执行其后面的语句之前执行。阻塞过程赋值语句不会阻止并行块中跟随它的语句的执行。

句法

阻塞过程赋值的语法如下 -

<lvalue> = <timing_control> <expression>

其中,左值是对过程赋值语句有效的数据类型,= 是赋值运算符,时序控制是可选的内部赋值延迟。时序控制延迟可以是延迟控制(例如,#6)或事件控制(例如,@(thoughtge clk))。该表达式是模拟器分配给左侧的右侧值。阻塞过程赋值所使用的 = 赋值运算符也可用于过程连续赋值和连续赋值。

例子

rega = 0; 
rega[3] = 1;            // a bit-select 
rega[3:5] = 7;          // a part-select 
mema[address] = 8’hff;  // assignment to a memory element 
{carry, acc} = rega + regb;  // a concatenation 

非阻塞 (RTL) 分配

非阻塞程序分配允许您在不阻塞程序流的情况下安排分配。每当您想要在同一时间步内进行多个寄存器分配时,都可以使用非阻塞过程语句,而不考虑顺序或相互依赖。

句法

非阻塞过程赋值的语法如下 -

<lvalue> <= <timing_control> <expression>

其中左值是对过程赋值语句有效的数据类型,<= 是非阻塞赋值运算符,时序控制是可选的内部赋值时序控制。时序控制延迟可以是延迟控制或事件控制(例如@(thoughtge clk))。该表达式是模拟器分配给左侧的右侧值。非阻塞赋值运算符与模拟器用于小于等于关系运算符的运算符相同。当您在表达式中使用 <= 运算符时,模拟器会将其解释为关系运算符;而当您在非阻塞过程赋值构造中使用 <= 运算符时,模拟器会将其解释为赋值运算符。

模拟器如何评估非阻塞过程分配当模拟器遇到非阻塞过程分配时,模拟器分两步评估并执行非阻塞过程分配,如下所示 -

  • 模拟器评估右侧并安排新值的分配在程序时序控制指定的时间进行。模拟器评估右侧并安排新值的分配在程序时序控制指定的时间进行。

  • 在时间步结束时,即给定的延迟已到期或适当的事件已发生,模拟器通过将值分配给左侧来执行分配。

例子

module evaluates2(out); 
output out; 
reg a, b, c; 
initial 

begin 
   a = 0; 
   b = 1; 
   c = 0; 
end 
always c = #5 ~c; 
always @(posedge c) 

begin 
   a <= b; 
   b <= a; 
end 
endmodule 

状况

条件语句(或 if-else 语句)用于决定是否执行语句。

形式上,语法如下 -

<statement> 
::= if ( <expression> ) <statement_or_null> 
||= if ( <expression> ) <statement_or_null> 
   else <statement_or_null> 
<statement_or_null> 

::= <statement> 
||= ; 

<表达式> 被求值;如果为真(即具有非零已知值),则执行第一条语句。如果为 false(值为零或值为 x 或 z),则不执行第一个语句。如果存在 else 语句并且 <表达式> 为 false,则执行 else 语句。由于 if 表达式的数值被测试为零,因此某些快捷方式是可能的。

例如,以下两个语句表达了相同的逻辑 -

if (expression) 
if (expression != 0) 

由于 if-else 的 else 部分是可选的,因此当从嵌套的 if 序列中省略 else 时可能会出现混乱。如果缺少 else,则始终将 else 与最近的前一个相关联可以解决此问题。

例子

if (index > 0) 
if (rega > regb) 
   result = rega; 
   else // else applies to preceding if 
   result = regb; 

If that association is not what you want, use a begin-end block statement 
to force the proper association 

if (index > 0) 
begin 

if (rega > regb) 
result = rega; 
end 
   else 
   result = regb; 

构造:if-else-if

以下结构经常出现,因此值得单独进行简短的讨论。

例子

if (<expression>) 
   <statement> 
   else if (<expression>) 
   <statement> 
   else if (<expression>) 
   <statement> 
   else  
   <statement>

这种 if 序列(称为 if-else-if 构造)是编写多路决策的最通用方法。表达式按顺序求值;如果任何表达式为 true,则执行与其关联的语句,从而终止整个链。每个语句要么是单个语句,要么是语句块。

if-else-if 构造的最后一个 else 部分处理“以上都不是”或其他条件均不满足的默认情况。有时默认情况下没有明确的操作;在这种情况下,可以省略尾随的 else,或者可以将其用于错误检查以捕获不可能的条件。

案例陈述

case 语句是一种特殊的多路决策语句,它测试一个表达式是否与多个其他表达式之一匹配,并进行相应的分支。例如,case 语句对于描述微处理器指令的解码很有用。case 语句具有以下语法 -

例子

<statement> 
::= case ( <expression> ) <case_item>+ endcase 
||= casez ( <expression> ) <case_item>+ endcase 
||= casex ( <expression> ) <case_item>+ endcase 
<case_item> 
::= <expression> <,<expression>>* : <statement_or_null> 
||= default : <statement_or_null> 
||= default <statement_or_null> 

case 表达式按照给出的确切顺序进行计算和比较。在线性搜索期间,如果其中一个 case 项表达式与括号中的表达式匹配,则执行与该 case 项关联的语句。如果所有比较都失败,并且给出了默认项,则执行默认项语句。如果未给出默认语句,并且所有比较都失败,则不会执行任何 case item 语句。

除了语法之外,case 语句在两个重要方面与多路 if-else-if 构造不同 -

  • if-else-if 构造中的条件表达式比将一个表达式与其他几个表达式进行比较(如 case 语句)更为通用。

  • 当表达式中有 x 和 z 值时,case 语句提供明确的结果。

循环语句

循环语句有四种类型。它们提供了一种控制语句执行零次、一次或多次的方法。

  • forever 连续执行一条语句。

  • 重复执行语句固定次数。

  • while 执行一条语句,直到表达式变为 false。如果表达式以 false 开头,则根本不会执行该语句。

  • for 通过三步过程控制其关联语句的执行,如下所示 -

    • 执行通常用于初始化控制循环执行次数的变量的赋值

    • 计算表达式 - 如果结果为零,则 for 循环退出,如果结果不为零,则 for 循环执行其关联的语句,然后执行步骤 3

    • 执行通常用于修改循环控制变量值的赋值,然后重复步骤 2

以下是循环语句的语法规则 -

例子

<statement> 
::= forever <statement> 
||=forever 
begin 
   <statement>+ 
end  

<Statement> 
::= repeat ( <expression> ) <statement> 
||=repeat ( <expression> ) 
begin
   <statement>+ 
end  

<statement> 
::= while ( <expression> ) <statement> 
||=while ( <expression> ) 
begin 
   <statement>+ 
end  
<statement> 
::= for ( <assignment> ; <expression> ; <assignment> ) 
<statement> 
||=for ( <assignment> ; <expression> ; <assignment> ) 
begin 
   <statement>+ 
end

延迟控制

延迟控制

可以使用以下语法来延迟控制过程语句的执行 -

<statement> 
::= <delay_control> <statement_or_null> 
<delay_control> 
::= # <NUMBER> 
||= # <identifier> 
||= # ( <mintypmax_expression> )

以下示例将分配的执行延迟 10 个时间单位 -

#10 雷加 = regb;

接下来的三个示例提供了数字符号 (#) 后面的表达式。赋值的执行会延迟表达式值指定的模拟时间量。

事件控制

通过使用以下事件控制语法,程序语句的执行可以与网络或寄存器上的值更改或声明的事件的发生同步 -

例子

<statement> 
::= <event_control> <statement_or_null> 

<event_control> 
::= @ <identifier> 
||= @ ( <event_expression> ) 

<event_expression> 
::= <expression> 
||= posedge <SCALAR_EVENT_EXPRESSION> 
||= negedge <SCALAR_EVENT_EXPRESSION> 
||= <event_expression> <or <event_expression>> 

*<SCALAR_EVENT_EXPRESSION> 是解析为一位值的表达式。

网络和寄存器上的值变化可以用作事件来触发语句的执行。这称为检测隐式事件。Verilog 语法还允许您根据变化的方向检测变化,即朝向值 1 (spokege) 或朝向值 0 (negedge)。posege 和 negedge 对于未知表达式值的Behave如下 -

  • 从 1 到未知以及从未知到 0 的转换中检测到负边沿
  • 在从 0 到未知以及从未知到 1 的转换中检测到 posege

过程:始终和初始块

Verilog 中的所有过程均在以下四个块之一内指定。1) 初始块 2) 始终块 3) 任务 4) 功能

初始和始终语句在模拟开始时启用。初始块仅执行一次,并且当语句完成时其活动终止。相反,always 块会重复执行。仅当模拟终止时,其活动才会终止。模块中可以定义的初始块和始终块的数量没有限制。任务和功能是从其他过程中的一个或多个位置启用的过程。

初始块

初始语句的语法如下 -

<initial_statement> 
::= initial <statement>

以下示例说明了如何使用初始语句在仿真开始时初始化变量。

Initial 
Begin 
   Areg = 0; // initialize a register 
   For (index = 0; index < size; index = index + 1) 
   Memory [index] = 0; //initialize a memory 
   Word 
End

初始块的另一个典型用途是波形描述的规范,该波形描述执行一次以向被仿真电路的主要部分提供激励。

Initial 
Begin 
   Inputs = ’b000000; 
   // initialize at time zero 
   #10 inputs = ’b011001; // first pattern 
   #10 inputs = ’b011011; // second pattern 
   #10 inputs = ’b011000; // third pattern 
   #10 inputs = ’b001000; // last pattern 
End 

总是阻塞

“always”语句在整个模拟运行过程中不断重复。下面给出了always语句的语法

<always_statement> 
::= always <statement> 

由于其循环性质,“always”语句仅在与某种形式的定时控制结合使用时才有用。如果“always”语句没有提供时间提前的方法,则“always”语句会创建模拟死锁条件。例如,以下代码创建无限零延迟循环 -

Always areg = ~areg; 

为上述代码提供时序控制会创建一个潜在有用的描述 - 如下例所示 -

Always #half_period areg = ~areg;