[SystemVerilog语法拾遗] 一文讲清楚SystemVerilog中的阻塞赋值与非阻塞赋值

[SystemVerilog语法拾遗] 一文讲清楚SystemVerilog中的阻塞赋值与非阻塞赋值

引言

我们设计的硬件、仿真环境抽象意义上是可以并行运行的。但是在实际仿真时,不过是运行在cpu上的串行执行的程序而已(先不讨论多核)。SystemVerilog专门为这种并行到串行的转换定义了调度机制。但IEEE在制定规范时,并未规定一部分并行的事件执行的先后顺序,这点会引入不确定性(Nondeterminism),这种不确定性往往会体现在不同厂家的仿真器上(因为不同的厂家采用的调度算法是不一样的),最严重的后果就是在不同仿真器上看到的最终结果是不一样的。所以作为一名合格的工程师,应该对sv仿真器的调度机制、确定性与不确定性有基础的了解。(引用 SystemVerilog调度机制与一些现象的思考_uvm_wait_for_nba_region-CSDN博客

本文首先结合SystemVerilog标准中关于语句执行的调度顺序的描述理解一些基本概念,再从Clifford Cummings大神在SNUG-2000上发表的排名第1的文章"Nonblocking Assignments in Verilog Synthesis, Coding Style That Kill!"来讲述使用阻塞赋值和非阻塞赋值应该遵循的一些原则,最后再以项目实例介绍为什么UVM中driver要使用非阻塞赋值而monitor中要使用阻塞赋值,然后引申出SystemVerilog中两种赋值类型使用时的一些注意事项。

《IEEE SystemVerilog 1800-2017》标准相关内容介绍

我们先看下《IEEE SystemVerilog 1800-2017》标准中对于"Scheduling semantics"的一些关键描述。

SystemVerilog is a parallel programming language. The execution of certain language constructs is defined by parallel execution of blocks or processes. It is important to understand what execution order is guaranteed to the user and what execution order is indeterminate.(SystemVerilog是并发执行的程序语言,该语言的执行完全由其定义的并发执行的语句块和进程决定,理解并发执行时哪些执行顺序是确定的、哪些是不确定的,这一点很重要。)
The SystemVerilog language is defined in terms of a discrete event execution model.(SystemVerilog是为离散事件执行模型(discrete event execution)所定义的一种语言。)
A SystemVerilog description consists of connected threads of execution or processes. (SystemVerilog是由一系列相关联的进程所组成的。进程(Processes)是SystemVerilog中并发调度的单元,比如:原语(Primitives)、initial过程块、always过程块、连续赋值(continuous assign)、异步任务(asynchronous tasks)、过程赋值(procedural assignment)等。进程是可以被执行的,有相应的状态标志(state,比如挂起)。进程会响应输入的变化并产生对应的输出。SystemVerilog的描述的对象也正是这些进程。)
Every change in state of a net or variable in the system description being simulated is considered an update event.(仿真中,net或者variable上每次的变化被认为是一次更新事件)
Processes are sensitive to update events. When an update event is executed, all the processes that are sensitive to that event are considered for evaluation in an arbitrary order. The evaluation of a process is also an event, known as an evaluation event.(进程对更新事件敏感!当一个更新事件被执行时,所有对该更新事件敏感的进程都会被求值,但是顺序是任意的。进程的求值本身也被认为是一种事件,即求值事件(evaluation event)。更新事件和求值事件的相互交替执行,推动了仿真时间的前移。)
In addition to events, another key aspect of a simulator is time . The term simulation time is used to refer to the time value maintained by the simulator to model the actual time it would take for the system description being simulated.The term time is used interchangeably with simulation time in this clause.
To fully support clear and predictable interactions, a single time slot is divided into multiple regions where events can be scheduled that provide for an ordering of particular types of execution.
(仿真时间是仿真其用来模拟真实的时间而建模。为了让硬件执行结果更清晰可预见,时间片被拆分成多个区域,这样不同类型的事件能够按照指定的顺序执行)

SystemVerilog中事件调度流程如图1所示。

图1 SystemVerilog中事件调度流程

在这里插入图片描述

Active Region、Inactive Region、NBA Region统称为Active Region set,这是专门为RTL代码执行所设立的区域集合(set),实际上就是上面介绍的verilog代码的区域,只不过在sv中需要限定一下事件是在module中定义的,而不是program中定义的(verilog不存在program)。在sv中,相对于为RTL代码设立的区域,还有专门为验证平台所设计的区域集合,Reactive Region set。包含了Reactive Region、Re-Inactive Region、Re-NBA Region。Observed Region则是专门为断言所设计的区域。关于上面RTL和sv两块region的具体实践,我们后面会用专门的实例来具体讲解。

Nonblocking Assignments in Verilog Synthesis, Coding Style That Kill!
Cummings提出了使用两种赋值类型时要遵循的八大原则:

Guideline #1: When modeling sequential logic, use nonblocking assignments.
Guideline #2: When modeling latches, use nonblocking assignments.
Guideline #3: When modeling combinational logic with an always block, use blocking
assignments.
Guideline #4: When modeling both sequential and combinational logic within the same always
block, use nonblocking assignments.
Guideline #5: Do not mix blocking and nonblocking assignments in the same always block.
Guideline #6: Do not make assignments to the same variable from more than one always block.
Guideline #7: Use $strobe to display values that have been assigned using nonblocking
assignments.
Guideline #8: Do not make assignments using #0 delays.

文中通过具体的代码示例解释了为什么会有上面这“八大方针“。

文章开始提到了,如果不遵守上面这些原则也许并不会影响最后的综合结果,但是可能会影响前仿结果,导致一些无法预见的eda tools相关的问题,增加debug难度。

IEEE标准上列出了在执行SystemVerilog(verilog标准在2005后就停止更新了,统一到SystemVerilog中了,即SystemVerilog也可以编写可综合的rtl代码)代码时哪些语句的顺序是明确的,哪些是不确定的,这些不确定的执行顺序就可能导致竞争的风险,进而在不同的eda仿真工具上表现出不同的执行结果,这是我们需要避免的,这也是本文之所以提出来在使用阻塞非阻塞赋值时为什么要遵循一些使用原则。

阻塞赋值(blocking assignment, 符号为"=")之所以这么叫是因为从它的右式计算到赋值给左边变量之间不允许有任何的延迟,除非手动在中间插入了延迟,关于延迟插入的各种方法可以参考我之前的文章: [SystemVerilog语法拾遗] systemverilog中各种延时方式详解 因此,被begin…end语句块之间的阻塞赋值是顺序执行的。

非阻塞赋值(nonblocking assignment, 符号为"<=")会在active events region集中计算赋值的右式部分(evaluation event),然后再NBA events region再集中进行赋值(update event),因而被begin…end语句块包裹的非阻塞赋值是并行执行的。非阻塞赋值用来对寄存器(reg)类型建模,只能在过程语句中赋值,过程语句包括initial、always、task、function.

我们在时序电路中经常会看到各种边沿沿的采样,@(posedge clk)等,通常我们采样信号一般用阻塞赋值产生,保证我们采样的事件发生在active events region,这样我们后面的赋值可以是阻塞也可以是非阻塞,比如我们通常建模的各种时钟信号都是用一个forever或者always块无限循环产生的,用的就是阻塞赋值,如果用非阻塞赋值,有时就会产生难以debug的错误。关于时钟信号的建模,文章中有如图2所示的描述。

图2 文章中关于时钟信号产生的描述

在这里插入图片描述

文中指出我们应避免使用自触发的always语句块,对于上面这个组合逻辑电路描述,clk既作为always句块需要更新的值,又作为出发时间的敏感量列表变量,由于非阻塞赋值的执行顺序特定,会产生always被连续触发的死循环,导致执行出错,实际仿真会被卡死在0时刻。这样符合我们上面提到的八大方针之一的组合逻辑电路使用阻塞赋值的原则。

而阻塞赋值则不存在这样的问题,文章中详细介绍了语句的执行顺序,这里就不再赘述了。

阻塞赋值和非阻塞赋值综合后可能会是同一个电路,但是在做前仿时仿真结果可能不同,为了设计图3所示的移位寄存器电路,文章中列出了两种设计代码,分别如图4、5所示。

图3 文章中引用的移位寄存器电路

在这里插入图片描述

图4 阻塞赋值的时序逻辑设计实现

在这里插入图片描述

图5 非阻塞赋值的时序逻辑设计实现

在这里插入图片描述

example 7会引入竞争,因为三个赋值语句执行的顺序SystemVerilog语法标准并没有明确规定,因而不同的仿真器实现可能不同,这取决于仿真器的调度算法,而综合后仍然可以产生正确的电路,这就导致综合前后仿真产生不同的结果。

example 11综合前后仿真结果一致,因为非阻塞赋值的特点决定了赋值右式的值在赋值之前就已经确定了,并不会产生update event反作用到evaluation event的串扰情况。

通过这个例子就引出了我们上面八大方针的其中之二:

Guideline #1: When modeling sequential logic, use nonblocking assignments. (建模时序电路使用非阻塞赋值)
Guideline #2: When modeling latches, use nonblocking assignments.(建模锁存器使用非阻塞赋值)

为什么组合逻辑电路时序要使用阻塞赋值(顺序执行)而不是非阻塞赋值(并发执行),用下面三个例子描述:

(1)导致错误的非阻塞赋值

代码截图如图6所示,在a/b/c/d更新后y并不会被更新,因为触发always内事件时,begin…end直接三条语句同步执行,y计算的tmp1和tmp2并不会用最新值,只有下次再次触发always事件才会把这次产生的tmp1和tmp2更新给y,因而y的值永远滞后一拍tmp1和tmp2。

图6 导致错误的非阻塞赋值

在这里插入图片描述

(2)正确但低效的非阻塞赋值(self-triggering always block不推荐)

代码如图7所示,这是一个自触发的always语句块,即把计算值结果放到敏感量列表中,这样可以保证虽然第一次a/b/c/d更新触发的always事件并不会更新y的值,但是由于tmp1或tmp2更新后又会再次触发always事件因而会更新y的值,但是需要触发两次always事件才能将正确值更新,因而结果虽然正确但是比较低效。

图7 正确但低效的非阻塞赋值

在这里插入图片描述

(3)阻塞赋值(正确且高效)

代码如图8所示,由于阻塞赋值的阻塞特点,begin…end之间执行的代码是顺序的,因而a/b/c/d只需触发一次always事件,就能正确更新y的值。

图8 正确且高效的阻塞赋值
在这里插入图片描述

由此我们得到第三个原则。

Guideline #3: When modeling combinational logic with an always block, use blocking assignments.

上面的Guideline #1-#3算是对我们实际工作影响最大的三个原则,至于Guideline #4-#8影响较小,有些已经被仿真器认定为Error或者会报Warning了,通常我们也不会产生困惑,这里只是简单提两句。

Guideline #8: Do not make assignments using #0 delays.

#0相当于在active events region和NBA events region之间强行插入了一个只为#0服务的region,有时候我们使用时不清楚该用阻塞还是非阻塞赋值时会想到这种操作,但是它会加大时序分析的复杂度,并且任何添加#0防止引入竞争的地方都可以使用另外一种更高效的方法来替代。

Guideline #7: Use $strobe to display values that have been assigned using nonblocking assignments.

图1所示的仿真器调度实现里, d i s p l a y 和阻塞赋值都是在 A c t i v e e n v e t s r e g i o n 并发执行的,由于执行顺序的不确定性可能导致打印的值部分是更新后的值部分是没有更新的,并且无法打印非阻塞赋值的更新值,这就会导致 d e b u g 时的困扰。而 display和阻塞赋值都是在Active envets region并发执行的,由于执行顺序的不确定性可能导致打印的值部分是更新后的值部分是没有更新的,并且无法打印非阻塞赋值的更新值,这就会导致debug时的困扰。而 display和阻塞赋值都是在Activeenvetsregion并发执行的,由于执行顺序的不确定性可能导致打印的值部分是更新后的值部分是没有更新的,并且无法打印非阻塞赋值的更新值,这就会导致debug时的困扰。而strobe是在postponed区域执行的,此时所有的update event(阻塞赋值、非阻塞赋值)都已经执行完毕,能够打印所有的更新值,因而推荐使用$strobe打印信号值。

我们关于Cummings的这篇文章就介绍到这里,感兴趣的可以自行查阅完整内容,pdf文档有需要的可以评论留下邮箱。

UVM中的阻塞赋值和非阻塞赋值

我们以一个简单的UVM实例先看看赋值类型对于我们UVM环境运行的影响。

APB协议应用时,apb_master_agent的driver中右如下图所示的一段代码,其中我们队apb_if中的信号赋值都是使用的非阻塞赋值。

图9 apb_master_driver.sv中使用非阻塞赋值

在这里插入图片描述

为了查看DUT采样driver所drive的信号的时间点,我们在tb中增加了如下一段代码来模拟采样psel和penable两个信号,tb是由module … endmodule包裹的顶层模块,本质上跟DUT中的module没有区别,可以模拟DUT中采样信号的行为。

图10 tb中对apb_if中被driver所drive的信号进行采样
在这里插入图片描述

运行结果如图11所示。

图11 非阻塞赋值仿真运行结果波形

在这里插入图片描述

分析图10中的波形可知,driver中我们在T1时刻通过非阻塞赋值将psel信号拉高,而RTL中在T2时刻才能采样到psel得拉高,而penable值的变化在RTL中的采样也会滞后一拍,也就是说我们在driver中对DUT需要采样信号的赋值的行为与RTL中非阻塞赋值的行为无异。pready是dut内部通过组合逻辑产生的信号,目的是输出给apb_master告知apb_master执行的apb行为有没有正确被接收,本实例是apb master/slave一对一操作不存在slave繁忙的情况,因而pready=psel & penable,而driver能够在T3时间点采样到pready为高进而将psel个penable都拉低,由于我们环境中执行的是连续无间隔的apb操作,所以psel一直被拉高,因而只有penable被拉低,T4时driver又再次将penable拉高执行下一次apb操作。

那如果把非阻塞赋值换成阻塞赋值呢?dirver中代码修改如图12所示。

图12 apb_master_driver.sv中使用阻塞赋值

在这里插入图片描述

仿真波形如图13所示。

图13 阻塞赋值仿真运行结果波形
在这里插入图片描述

可以看到T1时刻driver将psel拉高之后psel_sampled也拉高了,也就是说driver中采用阻塞赋值会导致RTL在同一个时刻能采样到该值的变化,进一步说明我们写在class中的这些激励本质上跟RTL中的阻塞赋值是在time slot的同一个时间点执行的,跟我们前面讲verilog的scheduler似乎不太一致,因为通常我们理解的sv的激励和采样都是滞后于RTL之后执行的,SystemVerilog中的阻塞和非赋值应该是在reacvtive和re-NBA region,而RTL中的采样、赋值是在active和NBA region的,时间上应该是RTL执行在前,SystemVerilog执行在后,为什么会出现这种情况呢?

这其实是我们验证方法学发展了,以前我们写激励都是写在program … endprogram中,一些验证检查项也都是通过checker组件来检查,《IEEE SystemVerilog 1800-2017》在4.4.2.6对于reactive events region有如下清晰的定义:

The code specified by blocking assignments in checkers, program blocks and the code in action blocks of concurrent assertions are scheduled in the Reactive region.

也就是说并不是所有的sv语法中的代码都会被安排在reactive到re-NBA区域,sv发展到今天,激励甚至都可以直接写在module包含的模块中了,甚至连verilog的标准都不在更新而同一到SystemVerilog语法标准了,现在还有人把激励写在program语句块中吗?谁做数据、协议检查的时候会去用checker语句块?我们所用的UVM源代码以及扩展的UVM环境都是在module中调用run_test()被执行的,根本没有什么program了,因而SystemVerilog调度器中的reactive之后的event region大家可以不用管了,我们在讨论active和NBA之类的event region时不再区分sv和v,也就是我们写在driver中的赋值语句跟我们写到verilog中的RTL代码在仿真器执行的时候没有任何区别。

这样就能解释为什么我们在driver中drive DUT接口信号必须使用非阻塞赋值了,因为本质上我们所写的driver也是我们对一个真实RTL的模型化抽象,我们使用apb_master_agent,本质上是为了抽象一个真实的apb_master模块,方便我们把一个复杂的系统拆解成独立的模块进行验证,所以我们需要驱动给DUT的信号在driver中也必须使用非阻塞赋值,否则我们simulation就不能建模真实的芯片工作场景。

而在monitor中我们采用的都是阻塞赋值,这是因为monitor并不会驱动实际的RTL信号,它只会sample RTL接口信号,然后再转化成transaction级的描述,采样后的值并不会由被另外的时序电路再次采样使用,因而不需要使用非阻塞赋值,但是语法上monitor中采用非阻塞赋值也不会出错。

除了driver和monitor,我们有时候在写testcase的时候也会通过virtual interface间接驱动DUT的接口信号,这时候也需要注意什么时候使用阻塞赋值和非阻塞赋值,两者如果对其他clk的采样沿,在DUT看来会导致1个clk的差别,非阻塞赋值会滞后一个clk被DUT采样到,这个在使用的时候也需要注意了。

总结

本文介绍了SystemVerilog中关于阻塞赋值和非阻塞赋值的定义以及SystemVerilog中是如何分片进行time slot调度的,随后引用Cummings的一篇文章介绍了我们在使用两种赋值应该遵循哪些原则,最后介绍了UVM中使用两种赋值时需要注意的地方。

通过本文的介绍,相信大家以后无论是写verilog model还是搭建验证环境,能够规避掉不少跟赋值相关的坑。

参考文献

SystemVerilog调度机制与一些现象的思考_uvm_wait_for_nba_region-CSDN博客

《Nonblocking Assignments in Verilog Synthesis, Coding Style That Kill!》——Clifford Cummings

  • 14
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值