虽然设计的代码在仿真器中理论上来说是可以并行执行的,但是在实际仿真中,代码都是运行在CPU上的一些程序而已。SV为代码的执行顺序定义了调度机制,最大限度的减少不确定性的产生。
SV被定义为一种基于离散事件执行模型的语言。换句话说,仿真是离散的,是基于时间片进行且只对特定的时刻点进行仿真的。
这里说的时间片其实没有时间概念,纯粹是工具为了仿真效果而提出的不同任务工作的区域,在一个时钟边沿有效,可以理解为一个时钟边沿上的进程,但不涉及时间的推进,只是进程顺序执行。只有在当前时间片上的所有仿真全部完成,仿真器才会进入下一个时间片。当然,每个时间片还会细分为多个区域,如下图所示:
抛开那些用来调用其他语言函数接口的PLI(ProgrammingLanguage Interface),我们只讨论方框区域。
preponed区域:这个区域中的数值是上一时间片中最终的稳定值。断言所需的数据就是在这个区域采样的。
active区域:断言所需数据采样完成以后,就进入本区域了,但是只执行阻塞赋值语句,连续赋值语句,非阻塞赋值中“<=”符号右边的计算,原语计算以及调用系统函数(如$display)等。需要说明的是,不同线程中的上述语句执行顺序是不确定的,仿真结果和仿真器相关。
inactive区域:该区域用的比较少,只有当线程被加上#0延迟才会进入该区域,当active区域中的操作全部执行完,本区域中的零延时线程才会进行操作。因此,零延时操作会延缓线程的操作时间,使用时应当注意,可以用在验证中对事件的执行先后顺序进行调度。
NBA区域:当前面的几个区域都完成以后,就进入NBA区域了,本区域做的事情就是把在active区域计算的非阻塞赋值右侧表达式的值赋给左侧。
前面这几个区域其实在Verilog中就定义了,而且基本没有变化,这是专门为RTL代码执行所设立的区域,但是在SV中,则增添了几个区域,专门为验证平台所设计,如下所示:
observed区域:此区域的主要功能是使用在preponed区域中采样的值来评估并发断言中的属性是否成立。属性评估在任何一个时间片中只发生一次。
reactive区域:在上一区域对断言属性进行评估后,本区域对断言表达式中的代码进行操作,看是否成功。当然,本区域还会执行program块中的连续赋值,阻塞赋值,非阻塞赋值的右式计算等。同样需要注意的是,上述这些语句的执行顺序同样是不确定的。
re_inactive区域:同inactive区域类似,执行program块中的#0零延迟阻塞赋值。
re_NBA区域:同NBA区域类似,执行program块中的非阻塞赋值左值更新。
postponed区域:同下一时钟片中的preponed区域一样的值,代表本时钟片中的最终稳定值。
在SV中还引入了#1 step的概念,这个在前面的时钟块中讲到过,时钟块里面默认的输入偏移就是#1step,而输出偏移是0。那么这个#1step怎么理解呢?实际上,它就是在当前时间片的preponed区域进行采样,也就是说在当前时间片还未进行任何操作时采样,和断言时采样是在同一个区域。请注意的是,step并不是我们在代码中定义的时间单位。它是仿真器为了解决采样问题而引入的调度的最小单位,是时间片的单位。但是我们能看到的最小时间就是时间精度指定的,所以这点好像没法很好的证明。只是看LRM手册是这么说的,应该是没什么联系的。
关于调度的例子。上次发过一篇文章。systemverilog之program与module ,从中可以看出区别,在此不再赘述。
针对SV的调度机理,提出如下建议:
时序逻辑使用非阻塞赋值,这样才可以保证时序逻辑的代码在NBA区域执行。
用always块写组合逻辑使用阻塞赋值。这样可以保证代码是在active 区域执行。
不要在多个always块中对同一个变量赋值。这样会引起冲突,导致最终结果的不确定性。
在设计代码中,在过程赋值时不要使用#0的延迟语句。
End