Verilog 中的行为建模和时间

Verilog 中的行为模型包含程序语句,用于控制模拟和操作数据类型的变量。所有这些语句都包含在程序中。每个程序都有一个与之关联的活动流。

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

行为建模示例

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 

程序分配

程序分配用于更新寄存器、整数、时间和内存变量。程序分配和连续分配之间存在显著差异,如下所述 −

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

程序分配在围绕它们的程序流构造的控制下更新寄存器变量的值。

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

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

  • 寄存器、整数、实数或时间变量的位选择 − 对单个位的分配,其他位保持不变。

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

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

  • 以上任何内容的连接 −可以指定前四种形式的任何一种连接,这样可以有效地对右侧表达式的结果进行分区,并按顺序将分区部分分配给连接的各个部分。

分配延迟(不用于合成)

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

语法

  • 程序分配变量 = 表达式

  • 延迟分配#Δt 变量 = 表达式;

  • 分配内延迟变量 = #Δt 表达式;

示例

reg [6:0] sum; reg h, ziltch;
sum[7] = b[7] ^ c[7]; // 立即执行。
ziltch = #15 ckz&h; /* ckz&a 现在求值;ziltch 在 15 个时间单位后发生变化。 */

#10 hat = b&c; /* ziltch 变化 10 个单位后,b&c 求值,hat 发生变化。 */

阻塞赋值

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

语法

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

<lvalue> = <timing_control> <expression>

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

示例

rega = 0;
rega[3] = 1; // 位选择
rega[3:5] = 7; // 部分选择
mema[address] = 8'hff; // 赋值给内存元素
{carry, acc} = rega +regb; // 连接

非阻塞 (RTL) 赋值

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

语法

非阻塞程序分配的语法如下 −

<lvalue> <= <timing_control> <expression>

其中 lvalue 是程序分配语句有效的数据类型,<= 是非阻塞分配运算符,时序控制是可选的分配内时序控制。时序控制延迟可以是延迟控制或事件控制(例如,@(posedge 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> 
||= ; 

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

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

if (expression)
if (expression != 0)

由于 if-else 的 else 部分是可选的,因此当从嵌套 if 序列中省略 else 时可能会造成混淆。通过始终将 else 与缺少 else 的最近的上一个 if 相关联,可以解决这个问题。

示例

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 结构)是编写多路决策的最通用方式。表达式按顺序进行评估;如果任何表达式为真,则执行与其关联的语句,这将终止整个链。每个语句要么是单个语句,要么是语句块。

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

Case 语句

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 项语句。

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

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

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

循环语句

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

  • forever 连续执行语句。

  • repeat 以固定次数执行语句。

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

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

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

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

    • 执行通常用于修改 loopcontrol 变量值的赋值,然后重复步骤 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 rega = 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 (posedge) 或朝向值 0 (negedge)。未知表达式值的 posedge 和 negedge 的行为如下 −

  • 在从 1 到未知和从未知到 0 的转换中检测到 negedge
  • 在从 0 到未知和从未知到 1 的转换中检测到 posedge

程序:Always 和 Initial 块

Verilog 中的所有程序都在以下四个块之一中指定。 1) 初始块 2) 始终块 3) 任务 4) 函数

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

初始块

initial 语句的语法如下 −

<initial_statement>
::= initial <statement>

以下示例说明了在模拟开始时使用 initial 语句初始化变量。

Initial
Begin
    Areg = 0; // 初始化寄存器
    For (index = 0; index < size; index = index +1)
    Memory [index] = 0; //初始化内存
    Word
End

initial 块的另一个典型用途是指定执行一次的波形描述,以向被模拟电路的主要部分提供刺激。

Initial
Begin
    Inputs = 'b000000;
    // 在零时间初始化
    #10 输入 = 'b011001; // 第一个模式
    #10 输入 = 'b011011; // 第二个模式
    #10 输入 = 'b011000; // 第三个模式
    #10 输入 = 'b001000; // 最后一个模式
End

Always 块

"always"语句在整个模拟运行过程中不断重复。always 语句的语法如下

<always_statement>
::= always <statement>

由于"always"语句具有循环特性,因此只有与某种形式的时间控制结合使用时才有用。如果"always"语句没有提供时间推进的手段,则"always"语句会创建模拟死锁条件。例如,以下代码创建了一个无限的零延迟循环 −

Always areg = ~areg;

为上述代码提供时间控制可以创建一个可能有用的描述——如以下示例所示 −

Always #half_period areg = ~areg;