Verilog语言中阻塞和非阻塞赋值的区别

2021-08-17 17:24 来源:电子说

来源:《Verilog数字系统设计(夏宇闻)》

阻塞和非阻塞赋值的语言结构是Verilog语言中最难的概念之一。即使是一些经验丰富的Verilog设计工程师也不能完全理解什么时候使用非阻塞赋值,什么时候使用阻塞赋值来设计符合要求的电路。

他们也没有完全理解为什么在电路结构的设计中,也就是综合风格的Verilog模块的设计中使用非阻塞赋值,以及符合IEEE标准的Verilog模拟器如何处理非阻塞赋值的仿真。

本节旨在尽可能详细地解释阻塞和非阻塞赋值的含义,并明确提出在Verilog模块编程中使用赋值操作时应注意的要点。根据这些关键点编写代码,可以避免Verilog仿真中的风险和竞争现象。我们之前提到过以下两点:

组合逻辑的电路结构是通过在描述组合逻辑的总块中使用块赋值来合成的。

时序逻辑的电路结构是通过在描述时序逻辑的总是块中使用非块赋值来合成的。

我们为什么要这么做?答案是,这是因为合成前模拟和合成后模拟要一致。如果不按照以上两点编写Verilog代码,有可能合成正确的逻辑,但仿真结果会不一致。

为了更好地理解以上几点,我们需要深刻理解Verilog语言中阻塞赋值和非阻塞赋值在函数和执行时间上的差异。为了解释这个问题,两个缩写定义如下:

RHS方程右手方向的表达式或变量可分别缩写为RHS表达式或RHS变量。

LHS等式左侧的表达式或变量可以分别缩写为LHS表达式或LHS变量。

IEEE Verilog标准定义了一些语句有确定的执行时间,而另一些语句没有确定的执行时间。如果有两个或两个以上的语句准备同时执行,由于语句的排列顺序不同(这是IEEE Verilog标准允许的),会产生不同的输出结果。这就是为什么Verilog模块具有风险性和竞争性。为了避免竞争,了解阻塞分配和非阻塞分配在执行时间上的差异非常重要。

阻塞赋值阻塞赋值运算符由等号(即=)表示。为什么这个赋值叫做阻塞赋值?这是因为先计算等号右边方向(RHS)部分的值,然后赋值语句不允许其他Verilog语句的任何干扰,也不允许其他赋值语句执行到当前赋值完成时间,也就是RHS赋给LHS的时间。

通常,合成阻塞分配操作不能在RHS中设置延迟(甚至不允许零延迟)。理论上讲,它与下面的赋值语句只有一个概念顺序,没有实质性的延迟。如果RHS增加了延迟,延迟期间会阻止赋值语句的执行,延迟后会执行赋值。这个赋值语句不全面,这种风格的代码不能用在需要集成的模块设计中。

阻塞分配的执行可以被认为是一步操作:

计算RHS并更新LHS。此时不允许任何其他Verilog语句的干扰。阻塞的概念是指在同一个始终阻塞中,它后面的赋值语句在前一个赋值语句概念上结束后开始赋值(即使没有设置延迟)。

如果一个进程块中的RHS变量阻塞赋值恰好是另一个进程块中的LHS变量阻塞赋值,并且这两个进程块是由同一个时钟沿触发的,那么阻塞赋值操作就会出现问题,即阻塞赋值的顺序安排不好,就会出现竞争。如果这两个阻塞分配操作由同一时钟沿触发,则无法确定执行顺序。下面的例子可以说明这个问题:

[示例1]。具有阻塞分配的反馈振荡器

模块fbosc1 (y1,y2,clk,rst);

输出y1、y2;

输入clk,rst

reg y1,y2;

总是@(posedge clk或posedge rst)

如果(rst)y1=0;//重置

否则y1=y2

总是@(posedge clk或posedge rst)

if(rst)y2=1;//预设

否则y2=y1

末端模块

根据IEEE Verilog标准,无论顺序如何,上述示例中的两个始终块都是并行执行的。如果前一个总是块的复位信号首先到达0,y1和y2都取1,而如果下一个总是块的复位信号首先到达0,y1和y2都取0。这清楚地表明,这个Verilog模块是不稳定的,会导致风险和竞争。

非阻塞赋值非阻塞赋值运算符由小于或等于符号(即“=”)表示。为什么这个赋值叫做非阻塞赋值?这是因为非阻塞赋值器的RHS表达式是在赋值操作时间开始时计算的,而LHS是在赋值操作时间结束时更新的。

其他Verilog语句,包括其他Verilog非阻塞赋值语句,可以同时计算RHS表达式和更新LHS。非阻塞赋值允许其他Verilog语句同时操作。非阻塞赋值的操作可以看作是一个两步过程:

在赋值时间开始时,计算非阻塞赋值RHS表达式。

在赋值时间结束时,更新非阻塞赋值LHS表达式。

非阻塞赋值操作只能用于对寄存器类型变量进行赋值,因此只能用在“initial”块和“always”块等过程块中。非阻塞赋值不允许用于连续赋值。下面的例子可以说明这个问题:

[例2]。 用非阻塞赋值的反馈振荡器

module fbosc2 (y1, y2, clk, rst);

output y1, y2;

input clk, rst;

reg y1, y2;

always @(posedge clk or posedge rst)

if (rst) y1 《= 0; // reset

else y1 《= y2;

always @(posedge clk or posedge rst)

if (rst) y2 《= 1; // preset

else y2 《= y1;

endmodule

同样,按照IEEE Verilog 的标准,上例中两个always块是并行执行的,与前后次序无关。无论哪一个always块的复位信号先到, 两个always块中的非阻塞赋值都在赋值开始时刻计算RHS表达式,而在结束时刻才更新LHS表达式。所以这两个always块在复位信号到来后,在always块结束时 y1为0而y2为1是确定的。从用户的角度看这两个非阻塞赋值正好是并行执行的。

Verilog模块编程要点:

下面我们还将对阻塞和非阻塞赋值做进一步解释并将举更多的例子来说明这个问题。在此之前,掌握可综合风格的Verilog模块编程的八个原则会有很大的帮助。在编写时牢记这八个要点可以为绝大多数的Verilog用户解决在综合后仿真中出现的90-100% 的冒险竞争问题。

时序电路建模时,用非阻塞赋值。

锁存器电路建模时,用非阻塞赋值。

用always块建立组合逻辑模型时,用阻塞赋值。

在同一个always块中建立时序和组合逻辑电路时,用非阻塞赋值。

在同一个always块中不要既用非阻塞赋值又用阻塞赋值。

不要在一个以上的always块中为同一个变量赋值。

用$strobe系统任务来显示用非阻塞赋值的变量值

在赋值时不要使用 #0 延迟

Verilog的新用户在彻底搞明白这两种赋值功能差别之前,一定要牢记这几条要点。照着要点来编写Verilog模块程序,就可省去很多麻烦。

编辑:jq

延伸 · 阅读