📜  Verilog中的行为建模和计时

📅  最后修改于: 2020-11-22 17:18:12             🧑  作者: Mango


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

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

程序分配

程序分配用于更新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是对过程赋值语句有效的数据类型,=是赋值运算符,定时控制是可选的内部赋值延迟。定时控制延迟可以是延迟控制(例如#6)或事件控制(例如@(posege 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是对过程赋值语句有效的数据类型,其中<=是非阻塞赋值运算符,时序控制是可选的内部赋值时序控制。时序控制延迟可以是延迟控制,也可以是事件控制(例如@(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语句)用于决定是否执行语句。

正式地,语法如下-

 
::= if (  )  
||= if (  )  
   else  
 

::=  
||= ; 

被评估;如果为true(即,具有一个非零的已知值),则执行第一条语句。如果为假(具有零值或值为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 () 
    
   else if () 
    
   else if () 
    
   else  
   

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

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

案例陈述

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

 
::= case (  ) + endcase 
||= casez (  ) + endcase 
||= casex (  ) + endcase 
 
::=  >* :  
||= default :  
||= default  

案例表达式将按照给出的确切顺序进行评估和比较。在线性搜索过程中,如果一个案例项目表达式与括号中的表达式匹配,则将执行与该案例项目关联的语句。如果所有比较都失败,并且给出了默认项目,则执行默认项目语句。如果未提供默认语句,并且所有比较均失败,则不会执行任何case item语句。

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

  • 如case语句中所示,if-else-if构造中的条件表达式比将一个表达式与多个表达式进行比较更为通用。

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

循环语句

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

  • 永远不断地执行一条语句。

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

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

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

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

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

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

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

 
::= forever  
||=forever 
begin 
   + 
end  

 
::= repeat (  )  
||=repeat (  ) 
begin
   + 
end  

 
::= while (  )  
||=while (  ) 
begin 
   + 
end  
 
::= for (  ;  ;  ) 
 
||=for (  ;  ;  ) 
begin 
   + 
end

延迟控制

延迟控制

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

 
::=   
 
::= #  
||= #  
||= # (  )

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

#10 rega = regb;

接下来的三个示例在数字符号(#)后面提供了一个表达式。通过表达式的值指定的仿真时间来执行分配延迟。

事件控制

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

 
::=   

 
::= @  
||= @ (  ) 

 
::=  
||= posedge  
||= negedge  
||=  > 

* 是一个解析为一位值的表达式。

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

  • 在从1到未知以及从未知到0的转换中检测到忽略
  • 在从0到未知以及从未知到1的过渡中检测到姿势

过程:始终块和初始块

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

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

初始块

初始语句的语法如下-

 
::= initial 

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

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  

由于“ always”语句具有循环特性,因此仅在与某种形式的时序控制结合使用时才有用。如果“总是”语句没有提供前进的时间,则“总是”语句会创建模拟死锁条件。例如,以下代码创建一个无限零延迟循环-

Always areg = ~areg; 

为上述代码提供时序控制会创建可能有用的描述-如以下示例所示-

Always #half_period areg = ~areg;