Verilog -- 阻塞与非阻塞的仿真与综合

Verilog -- 阻塞与非阻塞的仿真与综合

目录

  • Verilog -- 阻塞与非阻塞的仿真与综合

    • 基本概念
    • Verilog层积事件列(stratified event queue)
      • 示例:自触发的always块
    • 阻塞和非阻塞的综合问题
    • 非阻塞赋值和$display
    • 0延时
    • 建议

参考 Clifford E. Cummings, Sunburst Design, Inc. "Nonblocking Assignments in Verilog Synthesis, CodingStyles That Kill!"

前段时间为了探究阻塞和非阻塞的进行过简单测试,当时觉得对阻塞与非阻塞的理解已经可以了,直到发现了Sunburst的这篇论文(这个机构真的很nb,很多verilog,SV的经典教程都出自它),才算真的明白了阻塞和非阻塞的原理。

基本概念

RHS(right-hand-side): 指等式右边的表达式或者变量(RHS expression or RHS variable)

LHS(left-hand-side):指等式左边的表达式或者变量(RHS expression or RHS variable)

竞争条件:Verilog竞争条件发生在当两个或多个语句被安排在相同的仿真时间步长中执行时,当语句执行的顺序改变时,会产生不同的结果。

阻塞赋值:当没有其它的Verilog描述可以打断“阻塞赋值”时,操作将会估计RHS的值并完成赋值。“阻塞”即是说在当前的赋值完成前阻塞其它类型的赋值任务。

  • “阻塞赋值“可以看作一步进程(one-step process): 当没有其它可以打断赋值的描述时,估计等式右边(RHS)的指并赋予左边(LHS)。 在同一个always块里面,阻塞赋值结果将一直持续下去直到赋值结束。

非阻塞赋值:在一个时间步(time step)的开始估计RHS expression的值并在这个时间步(time step)结束时用等式右边的值更新取代LHS。在估算RHS expression和更新LHS expression的中间时间段,其它的对LHS expression的非阻塞赋值可以被执行。即是说“非阻塞赋值”从估计RHS开始并不阻碍执行其它的Verilog描述。

  • “非阻塞赋值”可以看作二步进程(one-step process):

      1. 在时间步开始估计RHS;
      1. 在时间步结束时更新LHS;

Verilog层积事件列(stratified event queue)

层积事件列(stratified event queue)是一个概念模型,每个仿真器都有不同的实现方式。“层积事件列”逻辑上划分为四个不同的队列,分别用于当前的仿真时间和未来的仿真时间。

活跃事件列(Active Events)是(最多的被预备执行的Verilog事件)包括:

  • 阻塞赋值
  • 连续赋值
  • $display命令
  • 估出“非阻塞赋值”的RHS expressions
  • 计算初原元件(primitive)的输入和更改实例(instance)的输出

注意“非阻塞赋值”的LHS不在“活跃事件列”里更新。

非活跃事件列(Inactive event): #0延时的阻塞赋值。

非阻塞赋值更新事件列(Thenonblocking assign updates event queue)即是“非阻塞赋值”的LHS expression被安排更新赋值的那些事件。在一个仿真时间步(simulation time step)的开始,“RHS expression 的估值”与其它被激活事件是以任意的次序进行的。

monitor事件列是由那些被安排的“\(strobe”和“\)monitor”显示命令带来的。$strobe 和 $monitor 用于显示一个仿真时间步结束时变量更新后的值(这时该仿真时间步里所有的赋值分配都已经完成)

注意:事件可以被加到任意的事件列里(由IEEE标准强制约束的),但是只可能从“活跃事件列”里被移出。其它事件列里的事件最终总是要成为“激活事件”的(或者提升为“活跃事件)。

示例:自触发的always块

---- Non-Self Triggered ------
module osc1 (clk);
output clk;
reg clk;
initial #10 clk = 0;
always @(clk) #10 clk = ~clk;
endmodule
----------------------------------------

------- Self Triggered ------
module osc2 (clk);
output clk;
reg clk;
initial #10 clk = 0;
always @(clk) #10 clk <= ~clk;
endmodule
----------------------------------------
  • 第一种写法使用阻塞赋值。这样的话RHS估值和LHS赋值是不被打断地执行,此时不允许其他语句的干扰。阻塞赋值必须在@(clk)边沿触发到来时刻之前完成。即在边沿事件之前,对clk的赋值已经完成。所以, 没有“触发事件”(@(clk))来触发always块里面的触发事件。因此不能自触发。
  • 第二种写法使用非阻塞赋值,在第一个@(clk)触发之后按照事件的执行顺序可以做如下排序:
    • 10ns时进入触发always块,执行always中语句
    • 非阻塞赋值的RHS expression被估值,并且LHS值被送入“非阻塞赋值更新”事件列。
    • RHS估值完毕,等待LHS进行
    • 非阻塞LHS的值更新,同时又遇到了@(clk)触发语句,always块再次对clk的值变化产生反应。@(clk)再一次触发。
    • 非阻塞赋值的RHS再次估值...

      因此可以进行自触发。

阻塞和非阻塞的综合问题

一般都建议使用阻塞逻辑描述组合逻辑,非阻塞逻辑描述时序逻辑。

但阻塞赋值就一定不能描述时序逻辑吗?答案是否定的,如果仔细安排阻塞赋值的顺序,也是可以进行时序电路的描述,并能通过仿真和综合(或者可以通过综合)。

以一个三级流水线为例:

首先我们直到,采用非阻塞赋值,仿真和综合肯定没问题。那么如果使用阻塞赋值呢?

always @(posedge clk) begin
    q1 = d;
    q2 = q1;
    q3 = q2;
  end

采用上面这种方式,肯定不是我们要的结果,而是将会只综合出一个寄存器。这肯定是不可行的。

而如果换一下顺序:

always @(posedge clk) begin
    q3 = d2;
    q2 = q1;
    q1 = q;
  end

阻塞赋值被仔细地排序,以使仿真能够像寄存器一样正确地工作。这样的写法无论是仿真还是综合都是正确的,但是不建议这样做。

另外,如果将一个always块拆分成三个写,也就是:

always @(posedge clk) q1=d;
always @(posedge clk) q2=q1;
always @(posedge clk) q3=q2;

这样Verilog标准允许以任意的次序来仿真执行3个always块,这也许会使得该流水线仿真结果产生错误,因为这产生了Verilog竞争条件。由不同的always块执行顺序会产生不同的结果。尽管这样,它的综合结果将是正确的!这就意味着综合前仿真综合后仿真不匹配。

另外,在组合逻辑中,可以用非阻塞赋值来描述吗?

答案是肯定的,但是也需要一番波折,还是以一个例子来看:

always @(a or b or c or d) begin
   tmp1 <= a & b;
   tmp2 <= c & d;
   y    <= tmp1 | tmp2;
 end

上面这段组合逻辑采用非阻塞赋值来描述,可以发现,y的值没法被正确更新,因为当abcd改变时tmp与y赋值的RHS同时估值,所以y的LHS更新时使用的还是旧的tmp,导致功能不正确。要解决这一问题也不麻烦:

always @(a or b or c or d or tmp1 or tmp2) begin
   tmp1 <= a & b;
   tmp2 <= c & d;
   y    <= tmp1 | tmp2;
 end

在“非阻塞赋值更新事件队列”中当非阻塞赋值更新LHS变量时,always块将会“自触发”并使用最新的tmp1和tmp2来更新y输出。现在y输出值正确了,但时代价是因为增加了两条 passes贯穿整个always块。使用太多的pass来贯穿always块等于降低仿真器的性能。因此虽然能获得正确功能,但不建议使用。

非阻塞赋值和$display

非阻塞赋值在$display命令之后才被更新赋值,因此,display如果紧跟非阻塞赋值则无效

initial $monitor("\$monitor: a = %b", a);
initial begin
 $strobe ("\$strobe : a = %b", a);
 a = 0;
 a <= 1;
 $display ("\$display: a = %b", a);
 #1 $finish;
 end

输出:

\(display: a = 0
\)monitor: a = 1

$strobe : a = 1

0延时

在层积事件列中可以发现,#0延时有一个专门的非活跃事件列,因此,零延迟#0 使得赋值事件处于“非激活事件列”,也就是非阻塞赋值LHS更新之前。

initial begin
 a = 0;
 b = 1;
 a <= b;
 b <= a;
 $monitor ("%0dns: \$monitor: a=%b b=%b", $stime, a, b);
 $display ("%0dns: \$display: a=%b b=%b", $stime, a, b);
 $strobe ("%0dns: \$strobe : a=%b b=%b\n", $stime, a, b);
 #0 $display ("%0dns: #0 : a=%b b=%b", $stime, a, b);
end

结果:

0ns: $display: a=0 b=1

0ns: #0 : a=0 b=1

0ns: $monitor: a=1 b=0

0ns: $strobe : a=1 b=0

从上面可以看到,使用#0延时后,display语句进入非活跃事件,在非阻塞赋值之前执行。

建议

  • 当为时序逻辑建模,使用“非阻塞赋值”。
  • 当为锁存器(latch)建模,使用“非阻塞赋值”。
  • 当用always块为组合逻辑建模,使用“阻塞赋值”
  • 当在同一个always块里面既为组合逻辑又为时序逻辑建模,使用“非阻塞赋值”。
  • 不要在同一个always块里面混合使用“阻塞赋值”和“非阻塞赋值”。
  • 不要在两个或两个以上always块里面对同一个变量进行赋值。
  • 使用$strobe以显示已被“非阻塞赋值”的值。
  • 不要使用#0延迟的赋值。

原文地址:https://www.cnblogs.com/lyc-seu/p/12701860.html

时间: 2024-09-27 04:32:54

Verilog -- 阻塞与非阻塞的仿真与综合的相关文章

verilog中阻塞与非阻塞

越是看似简单.经常接触的.我们越是不知其所以然.这就是我写本文的原因.阻塞和非阻塞赋值一般使用在进程中,包括always和initial进程.assign赋值等操作中. 阻塞赋值与非阻塞赋值 always @(event-expression) begin     <LHS1=RHS1> <LHS2=RHS2> ...... end 同样可将采用非阻塞赋值方式的always进程块写成下面的形式: always @(event-expression) begin     <LH

同步与异步、阻塞与非阻塞

"阻塞"与"非阻塞"与"同步"与"异步"不能简单的从字面理解,提供一个从分布式系统角度的回答.1.同步与异步同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回.但是一旦调用返回,就得到返回值了.换句话说,就是由*调用者*主动等待这个*调用*的结果. 而异步则是相反

阻塞、非阻塞、同步、异步

同步和异步关注的是消息通信机制. 同步 调用者主动等待调用结果返回,没有得到结果之前,该调用不返回. 异步 调用者不必马上等待返回结果,执行部件通过通知.状态或回调函数来返回结果给调用者. 阻塞和非阻塞关注的是程序等待调用结果的状态. 阻塞 调用返回之前,当前线程挂起.调用线程只有等待结果之后才返回. 非阻塞 它不会阻塞当前线程.

同步与异步,阻塞与非阻塞基础

1.同步与异步 同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication) 同步,在发出一个调用时,在没有得到结果之前,该调用就不返回.但是一旦调用返回,就得到返回值了.是由调用者主动等待这个调用的结果. 异步,调用在发出之后,这个调用就直接返回了,所以没有返回结果.当一个异步过程调用发出后,调用者不会立刻得到结果.而是在调用发出后,被调用者通过状态.通知来通知调用者,或通过回调函数处理这个调用. 2.阻塞与非阻

Linux设备驱动中的阻塞和非阻塞I/O

[基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到条件满足. 2.非阻塞 非阻塞操作是指在进行设备操作是,若操作条件不满足并不会挂起,而是直接返回或重新查询(一直占用CPU资源)直到操作条件满足为止. 当用户空间的应用程序调用read(),write()等方法时,若设备的资源不能被获取,而用户又希望以阻塞的方式来访问设备,驱动程序应当在设备驱动层的

socket阻塞与非阻塞,同步与异步、I/O模型,select与poll、epoll比较

1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 同步/异步主要针对C端: 同步:      所谓同步,就是在c端发出一个功能调用时,在没有得到结果之前,该调用就不返回.也就是必须一件一件事做,等前一件做完了才能做下一件事. 例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事 异步:      异步的概念和同步相对.当c端一个异步过程调用发出后,调

IO-同步,异步,阻塞,非阻塞

IO-同步,异步,阻塞,非阻塞1.什么是IO数据在系统内核(kernel)和用户进程之间的传递,称为IO. 2.IO操作步骤以read为例,涉及两个系统对象,调用IO的process(or thread),即用户进程:另一个为系统内核(kernel).当用户进程调用recvfrom操作时,会经历两个阶段1)等待数据准备2)将数据从内核拷贝至进程中 3.IO模型根据用户进程在IO操作时的状态,可以分为5中IO类型:blocking IO:阻塞IOnon-blocking IO:非阻塞IOIO mu

阻塞IO,非阻塞IO,异步IO和非异步IO 的区别

最近在研究java IO.NIO.NIO2(或者称AIO)相关的东西,有些概念还是要明确下. 按照<Unix网络编程>的划分,IO模型可以分为:阻塞IO.非阻塞IO.IO复用.信号驱动IO和异步IO,按照POSIX标准来划分只分为两类:同步IO和异步IO. 如何区分呢?首先一个IO操作其实分成了两个步骤: 1.发起IO请求 2.实际的IO操作 阻塞和非阻塞IO:在于第一个步骤是否会会被阻塞,如果会则是阻塞IO,否则是非阻塞IO. 异步和非异步(同步)IO:在于第二个步骤是否会阻塞,如果实际的I

同步和异步、阻塞和非阻塞

首先说明我对这些概念也不是很清楚,以下内容是我做的一些理事. 同步和异步.阻塞和非阻塞这是两组概念,说的是不同的事情,同步和阻塞没有必然的联系,异步和非阻塞也没有必然的联系.同步和异步是只跟IO操作过程中进程的状态变化有关.阻塞和非阻塞就是进程的两种状态.比如你去银行,排除的话就是一种同步的方式,叫号的话就是异步的方式.排队必须自己看着什么时候轮到自己,而叫号则不必,轮到你的时候会触发一个事件,或者说你会收到一个信号,别人会叫你.不管是排除还是叫号,如果你在等待的过程中不能做其他事情,那就是阻塞