未闻verilog---分层事件队列
分层事件队列
当我们在谈到非阻塞赋值时都会说非阻塞赋值不对LHS立刻进行赋值,它在time-step的最后进行赋值。但是time-step的最后又是什么时候?就需要讨论Verilog的分层事件队列。
Verilog描述了电路的行为,是并行进行的,在仿真的时候我们把每一条语句每一个代码块都看作并行执行的,但是对于仿真器来说,他运行在计算机上,计算机在执行仿真器的代码时是串行进行的,所以对于Verilog代码来说,他的执行有前有后,到底哪一条语句先执行,哪一条语句后执行,都是需要按照一定的规则的,这些规则就是Verilog仿真的参考模型和事件队列。
verilog仿真的参考模型
Verilog仿真时在进行事件调度时按照下面的参考模型进行调度(下面是将0延时部分拿去的简化版本)
“T”代表当前的仿真时间
参考模型如下:
while(队列中有事件需要处理){
//以下是激活事件的过程
if(没有活跃事件)
{
if(有非活跃事件)
{激活所有的非活跃事件;}
else if(有非阻塞赋值的更新事件)
{激活所有非阻塞赋值的更新事件;}
else if(有monitor事件)
{激活所有monitor事件}
else
{将T推进到下一个事件时间;
激活时间T的所有非活跃时间;}
}
//以下是执行时间的过程
E=任何活跃事件;
if(E是一个更新事件)
{更新需修改对象;
将产生的计算事件增加进事件队列;}
else //上面是赋值,下面是计算右边得到右值
{/* 应该是一个计算事件 */ //先计算得到右值,再更新左值
计算这个进程;
将计算产生的更新事件加入事件队列;}
}//end while
从上面可以看出,先执行活跃事件,活跃事件执行完之后再执行非活跃事件,再执行非阻塞赋值的更新事件,再执行所有的monitor事件,如果当前时刻的所有事件都执行完,推进仿真事件T前进。
对于活跃事件来说,先进行计算事件的进行,再进行更新事件的进行。
分层事件队列
仿真器首先按照仿真时间对事件进行排序,而在当前仿真时间里,则按照事件的优先级进行排序。
活跃事件是优先级最高的事件。在活跃事件之间,它们执行的顺序是随机的,阻塞赋值(=),连续赋值(assign),以及非阻塞赋值的右式计算等,都属于活跃事件。
当活跃事件全部执行完时,系统会按照仿真参考模型,将其他队列中的事件激活为活跃事件,加入活跃事件队列中执行。事件是根据一定的规则加入到5个区域中任何一个的,但是只从其中的活跃事件队列区域离开。
其他将来仿真时间的所有事件(比如延时后赋值计算)暂时存放在将来事件队列。当仿真器前进到某个时刻,该时刻的所有事件也就被分类到当前仿真时间事件队列中。仿真时刻未到的事件仍然留在将来事件队列中。
事件的激活会触发其他事件:
活动事件如阻塞赋值和连续赋值,可以触发额外的赋值和过程块,从而导致更多的活动时间和非阻塞赋值更新事件在同一个time-step中被调度。在这种情况下,新的活跃事件将会在激活非阻塞赋值更新事件之前被执行。
激活非阻塞赋值事件意味着取出所有的非阻塞赋值的更新事件,然后把它们放入到活跃事件队列。当这些激活的事件被执行的时候,他们还会触发其他的进程,导致更多的活跃事件,导致更多的非阻塞赋值更新事件在当前的time-step被调度。当前的time-step的活动持续进行,直到当前的time-step的所有事件被执行完,而且不再有能够导致更多事件被调度的进程触发。就此所有的$monitor
和$strobe
命令显示他们各自要输出的数值,然后仿真时间T可以前进。如果在当前的仿真时间里还有活动时间和非阻塞赋值更改事件,那么仿真时间不会向前。
仿真队列的确定性与不确定性
确定性
仿真队列的确定性在于begin...end块中的语句应该按照他们在块中的顺序执行,因为模型中其他的进程(比如@,#,或者wait)在执行begin...end块中的语句时,中间可能会被挂起,转而执行其他进程,但是最后begin...end块中的语句必须按照他们在块中呈现的顺序执行。
对于阻塞赋值,语句的执行就是在begin...end块中按从上到下的顺序执行,但是对于非阻塞赋值来说,举个例子
initial
begin
a <= 0;
a <= 1;
end
当执行这个块的时候,第一条语句a <= 0;
的右值计算部分先被放入活跃队列,更新部分被放入非阻塞赋值更新队列,第二条语句a <= 1;
的右值计算部分之后被放入活跃队列,更新部分被放入非阻塞赋值更新队列,第一条语句的更新事件排在第二条语句之前,则当把更新事件从队列中被调度出来时,先调度第一条语句的更新事件,a被赋值为0,再把第二条语句的更新事件调度出来,a的值被赋值为1。
所以仿真队列的确定性就在于统一begin...end块中语句运行的顺序,在前面的先运行,在后面的后运行。
不确定性
仿真队列的不确定性在于活跃队列语句的执行没有顺序规定,活跃队列语句执行的顺序是随机的。
举个例子,当两个进程对一个变量同时有read又有write可能会发生竞争。
always @(posedge clk)
x = y;
always @(posedge clk)
y = z;
由于阻塞赋值位于活跃队列,上面两条阻塞赋值语句没有begin...end块的约束,他们在活跃队列中的运行顺序是随机的,是不确定的,当上面的语句先运行时,x的值为y原来的值,当下面的语句先运行时,x的值为z的值。所以这种运行顺序的不确定性导致了竞争。
如果把上面的阻塞赋值转换为非阻塞赋值,就可以正常运行了。因为非阻塞赋值先进行右值的计算,这是要赋值给x和y的值就已经确定,之后在非阻塞赋值更新事件激活时,x被赋予y原来的值,y被赋予z的值,没有发生竞争。(这就是为什么建议在时序逻辑中采用非阻塞赋值)
如果两个进程同时对一个变量进行write
always @(posedge clk)
x <= y;
always @(posedge clk)
x <= z;
不管采用阻塞赋值还是非阻塞赋值,都会出现运行顺序的不确定(最后x的值为y还是z是不确定的),会导致竞争。(所以我们不建议在不同的块中同时对一个变量进行赋值)
仿真队列的不确定性还来源于行为块中没有时序控制(例如@,#或wait)的语句不用必须在一个事件中连续执行完。在执行一条语句时,仿真器可以挂起执行,把部分执行完的语句作为挂起事件放到事件队列中,这么做就是允许进程交替执行,但是交替执行的顺序是不确定的,而且不再用户的控制下。
比如计算表达式和更新线网可能是交替的,运行顺序的随机性就会导致竞争的发生。
assign p =q;
initial
begin
q = 1;
#1 q = 0;
$display(p);
end
那么display的结果是0还是1是不确定的,当执行完#1 q=0;
这条语句时,会导致对p的更新事件的发生,那么接下来执行assign p=q;
还是执行diaplay是不确定的,他们都处于活跃事件队列,那么他们执行顺序的不确定就会导致最后结果的不同。
参考
《Verilog编程艺术》
《轻松成为设计高手》