这几天一直在纠结阻塞与非阻塞的问题,到现在基本弄清楚了。在纠结这个问题的时候,还顺便弄清楚了前仿真与后仿真,Verilog的分层事件队列,使用系统任务的一些原则等。这些问题以后再说,现在只谈一下我对阻塞与非阻塞的理解。
概念这东西,还是引用教材中的比较好。
关于阻塞:计算RHS并更新LHS,此时不能允许有来自任何其他Verilog语句的干扰。 所谓阻塞的概念是指在同一个always块中,其后面的赋值语句从概念上(即使不设定延迟)是在前一句赋值语句结束后再开始赋值的。
这段代码实现的功能就是,在clk上升沿到来的时候,把a的值赋给b,再把b的值赋给c,并显示a、b的值。当条件符合时,执行上述操作。在把a的值赋给b的这个过程中,其他的语句都“被阻塞”,被迫停下来,结束之后,进入下一句,直到执行完begin---end中语句。所以相当于把a的值通过b传递给c。
这里所有的测试文件用的都是这一个testbench :
仿真结果:
综合之后只有一级寄存器。相当于把a的值直接传递给c。而b只是中间变量而已,并没有实际意义。
如果把上述代码中的两个赋值语句相互交换,如下所示,结果就不同了
在第一个clk上升沿到来时,由于b的值未知,赋给c之后,c也为未知值;紧接着,把a的值给b,由于a的值已经给出,所以,结束之后,a、b的值相同,c为x。
综合之后,生成两级移位寄存器。
关于非阻塞:
1) 在赋值时刻开始时,计算非阻塞赋值RHS表达式。
2) 在赋值时刻结束时,更新非阻塞赋值LHS表达式。
这段代码在posedge clk到来时,计算所有的RHS(Right Hand Side)的值,此时,a的值为3,b的值为x,这是同时进行的,没有先后顺序;然后更新LHS(left Hand Side)的值,结束之后,b的值变为3,c的值为前一时刻b的值,即x。如波形图所示:
综合之后生成两级移位寄存器:
如果把上述代码中的两个赋值语句相互交换,如下所示,结果和上面是一样的。所以在一个begin---end中的非阻塞语句并不会因为放置的位置不同,出现不同的结果。
关于非阻塞的例子,分析的貌似蛮有道理,但是从脚本上却看出了问题,第一个clk上升沿到来之后,a的值为3,而b、c的值却为x,就是说,并没有执行操作,但是从波形图看,确实在posedge clk的时候,把a的值传递给了b。到底怎么回事呢?直到看了一篇博文(http://blog.sina.com.cn/s/blog_58649eb30100biox.html),想起了Verilog的层次化事件队列的概念。再一翻书,明白了。原来是系统任务用的不对,$display命令的执行是安排在活动时间队列中,但排在非阻塞赋值数据更新事件之前。这就解释了为什么b的值是3,显示的却是x。换成$strobe之后,和预想的就相当一致了。
这下就对了。用$strobe系统任务来显示,应该用非阻塞赋值的变量值。
再了解一下Verilog的层次化事件队列有助于理解阻塞与非阻塞。阻塞赋值、计算非阻塞赋值语句右边的表达式、连续赋值、执行$display命令都放在动态事件队列中。而非阻塞赋值语LHS的更新却不放在这里,排在其他队列中的事件要被排入动态事件队列中后,才能等待执行。所以,排在动态事件队列中的事件优先级最高。
$strobe显示命令是排列在监控事件队列中,在仿真结束的时候,所有的值都赋完之后,$strobe才会显示出要求显示的变量的值。解释了应该用$srobe来显示非阻塞赋值的结果。
最后还剩下到现在都没想明白的两个问题:
单从代码中可以看出,当posedge clk到来时,发生了两次移位,至于哪次在前哪次在后并不能确定,顺序是随机的。唯一可以确定的就是:确实发生了两次移位。从仿真结果来看,当条件满足时,先执行的是b = a,接着是c = b,和阻塞赋值的第一种情况一样。
但是却综合出来了一个两级移位寄存器的RTL级视图:
把两条语句交换后如下:
仿真结果应该和上面是一样的才对,但很明显不一样,结果显示,第一次posedge clk结束时b的值为3,c的值为x,合理的解释是:clk上升沿到来,先执行的是c = b,然后才是b = a。而这种结果也应该是不确定的,但做了多次之后,每次结果都是这样。
综合后出现的RTL级视图和上面是一样的。
这两个例子综合后出现的两级移位寄存器说明,在每个clk上升沿,确确实实有两次移位,并且这两次移位是顺序进行的,所以出现了两个寄存器,而移位的先后顺序却不同,所以仿真的结果也就不同。但是既然仿真结果不一样,也不确定,怎么会出现相同的RTL级视图呢?怎么会综合出正确的结果呢?
好吧,只好用教材上的两段句来解释这个问题(虽然我还是看不太明白):
阻塞赋值分别被放在不同的always块里。仿真时,这些块的先后顺序是随机的,因此可能出现错误的结果。这是Verilog中的竞争冒险,按不同的顺序执行这些块将导致不同的结果。但是,这些代码的综合结果却是正确的流水线寄存器。也就是说,前仿真和后仿真的结果可能会不一致。
把always块的顺序稍作变动,也可以被综合成正确的移位寄存器逻辑,但仿真结果可能不正确。