SV的仿真调度机制以及阻塞非阻塞赋值的区别(用例子说明)

一、疑问

1、为什么要有Verilog仿真调度机制

Verilog语言本身就有并发的特性,所有的描述语句(连续赋值语句,行为语句块:always和initial,模块实例化等)均是并行发生的。而我们用于仿真的计算机是串行执行的,所以Verilog中的并行行为其实是用计算机的串行来模拟的(就像多任务操作系统,某时刻只能执行一个,而不同任务之间看起来就像并行执行的)。因此并行性在仿真器中仿真其实也是按照一定的顺序一条一条语句执行的,分时执行,而在并行的进程全部执行完之前,仿真时间是不会往前推进的。

因此,如果不对这些并行的线程的执行顺序做一个安排,就会产生不确定性,使得不同厂家的仿真器出现不同的仿真结果。

2、为什么要进一步扩充Verilog仿真调度机制?

因为在Verilog的仿真调度机制中,存在测试平台和设计之间的竞争状态。比如:在同一个时间片内(如100ns),一个信号同时被读取和写入,那么读取的数值是旧数值还是刚写入的新数值?对设计的输出信号的采样存在着同样的问题。你希望在时钟沿到来之前的最后时刻捕获数据,但是你不能在出现时钟边沿的时候采样,因为设计的输出值可能已经变了,应当在时钟沿到来之前的Tsu时间上采样。

出现竞争的根源在于设计和测试平台的事件(event)混合在同一个时间片(time slot)内,即使在纯RTL程序中也会发生相同的问题。**这就需要将两种事件进行分开调度。**SV中引入一种新的时间片划分方式,如下图。在verilog中,大多数的事件在有效区域(active region)执行。而测试平台的代码在reactive区执行。这样就可以保证设计和验证环境之间有清晰的事件发生顺序,避免两者的竞争问题。

在这里插入图片描述

二、Verilog和SV的仿真调度机制

1、Verilog的仿真调度机制:

在这里插入图片描述

2、SystemVerilog的仿真调度机制:在这里插入图片描述

1、time-slot时间片是仿真时间中的一个抽象单位Ts,该单位中所有的线程(always、initial、assertion等)和数据的阻塞、非阻塞赋值有各自的执行优先级。

2、区域说明:

  • Preponed:当前时间片的入口;
  • active、inactive和NBA区域属于仿真模块中的设计部分的线程,可以参考图8-6分层事件队列的活跃事件、非活跃事件和非阻塞赋值更新事件。注意,在NBA区域中,如果有一些非阻塞触发了别的线程,那么被触发的线程要被迁移到active区域(可以参考下文图7.8的 tb,可以看到有两个active区);
  • Observed区:在设计部分的线程执行完毕后,则进入observed区,主要执行属性断言(property assertion)。由于断言中需要监测设计中的变量,必须等到所有数据被赋予最终值,所以放在了设计区域结束之后,这样可以避免设计采样的变量不稳定而导致断言检查报错。该区域使用接口和程序块中的采样操作,是的采集到的数据是该时间片的最终值。
  • Reactive区:执行testbench代码(线程)。
  • Postpone区:在分别经历了与设计、testbench有关的区域后,当前Ts进入与postpone。该区域内的值保持稳定,且与下一个Tsprepone的值一致。同时该区域也作为SV PLI/DPI的回调函数点,使得在SV外部的调用语言(如C)在使用SV变量使仍然可以用到最新的数值。
    在这里插入图片描述
    3、概念说明
  • #1step:1step是仿真器在时间上进行调度的最小单位,也就是说,在1step的delay里是不存在事件的。注意,step和ps、fs这些时间单位不一样,它是仿真器为了解决采样问题引入的调度的最小单位,为时间片的单位;
    时钟块的默认时序就是在#1step的延时后采样输入信号,而#0延时后驱动输出信号(所以#1step在prezoned区执行,#0延迟在inactive区执行,采样早于驱动)。1step的延时规定了信号在前一个时间片的postponed区域(或者当前时间片的preponed区域)在设计有任何新的动作之前被采样。这样可以在时钟改变之前捕获输出值,这样可以保证采样到的数据是上一个时钟周期的数据。

三、例子说明

接下来通过一个例子以及不同的变形来理解仿真调度机制以及阻塞、非阻塞赋值的差别。

module counter(input clk);
  bit [3:0] cnt;

  always @(posedge clk) begin
    cnt <= cnt + 1;
    $display("@%0t DUT cnt = %0d", $time, cnt);
  end
endmodule

module tb1;
bit clk1;
bit [3:0] cnt;

  initial begin
    forever #5ns clk1 <= !clk1;
  end

  counter dut(clk1);

  always @(posedge clk1) begin
    $display("@%0t TB cnt = %0d", $time, dut.cnt);
  end
endmodule

module tb2;
bit clk1;
bit clk2;
bit [3:0] cnt;

  initial begin
    forever #5ns clk1 <= !clk1;
  end

  always @(clk1) begin
    clk2 <= clk1;
  end

  counter dut(clk1);

  always @(posedge clk2) begin
    $display("@%0t TB cnt = %0d", $time, dut.cnt);
  end
endmodule

运行结果:
在这里插入图片描述
解释:
1、对于tb1,由于DUT和TB对dut.cnt的采样都发生在active区域(因为display语句在active区域执行),而cnt<= cnt+1为非阻塞赋值,发生在active区域之后的NBA区,所以都采样到了dut.cnt变化前的数值。
2、对于tb2,在active区里,dut里先对cnt采样了,所以是之前的值;紧接着在NBA区执行cnt<=cnt+1,cnt值被更新。同时,tb在NBA区得到clk2,然后才进行active区的dut.cnt采样,此时已经是更新后的值了。
在这里插入图片描述

变形1:

将tb2中的clk2 <= clk1改为clk2 = clk1;
那么仿真结果如下:

# @5 DUT cnt = 0
# @5 TB cnt = 0
# @15 DUT cnt = 1
# @15 TB cnt = 1
# @25 DUT cnt = 2
# @25 TB cnt = 2

也就是和tb1完全相同。这是因为采用了阻塞赋值后,clk2较clk1的从active区到NBA区的延迟就不存在了,因此相同。

变形2:cnt的赋值由非阻塞变为阻塞

module counter(input clk);
  bit [3:0] cnt;

  always @(posedge clk) begin
    //cnt <= cnt + 1;
    cnt = cnt + 1;
    $display("@%0t DUT cnt = %0d", $time, cnt);
  end
endmodule

tb1的结果如下:

# @5 DUT cnt = 1
# @5 TB cnt = 1
# @15 DUT cnt = 2
# @15 TB cnt = 2
# @25 DUT cnt = 3
# @25 TB cnt = 3

在这里插入图片描述
分析:
①由于DUT和TB的dut.cnt采样都发生在active区域,所以值都一样;
②因为cnt=cnt+1和dut.cnt同时发生在active区,所以采样到的dut.cnt是变化后的数值。

tb2的结果如下:

# @5 DUT cnt = 1
# @5 TB cnt = 1
# @15 DUT cnt = 2
# @15 TB cnt = 2
# @25 DUT cnt = 3
# @25 TB cnt = 3

分析:
在这里插入图片描述
只要你把cnt的赋值写成了阻塞赋值cnt=cnt+1,那么cnt的更新和dut.cnt的采样将会同时发生在active区。此时虽然clk2相对于clk1还是存在延迟,但是dut.cnt采样到的值都一样了,都是更新后的值。只不过是dut相比于tb更早得到更新后的数值而已。

一道检验题

可以用下面这个简单的例子来看看自己是否掌握了最基础的阻塞、非阻塞赋值以及Verilog的调度。看的时候可以挡住波形和电路,根据代码来推出波形、电路。
下面例题中,b初始值为0。
在这里插入图片描述
分析:
①begin…end语句块中,语句顺序执行。但是非阻塞赋值的右式计算优先。由于赋值还没发生,因此右边的值还为原来的值,因此b为0,a为0。之后,来到NBA区,将1,b和a同时赋值给b,a和c。因此第一个时钟周期里,a和c的值为0,b为1;第二个时钟周期也一样,非阻塞赋值的右式计算优先,会将b=1,和a=0分别赋值给a和c,所以结束后a为1,c为0;第三个时钟上升沿之后,c才会最终变为1.(注意,其实b在这里不应该在时钟上升沿附近变化,不符合建立保持时间的要求。)
②begin…end语句块中,语句顺序执行。时钟上升沿时,因为是阻塞赋值,因此顺序执行这三个语句,第一个时钟周期结束时,abc全相等。
③组合逻辑,与时钟无关。代码等价于一条导线,所以一直相等。

想自己跑跑仿真的可以参考下面这位老哥的代码。
参考:verilog阻塞非阻塞赋值在always语句中的表现

其他内容补充参考:
!SystemVerilog调度机制与一些现象的思考
!第四章-连接设计和测试平台
SystemVerilog LRM 学习笔记 – SV Scheduler仿真调度
!测试平台与设计间的时序问题
SystemVerilog中scheduler(调度)

  • 19
    点赞
  • 127
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
SystemVerilog和Verilog都是硬件描述语言,用于描述数字电路的行为和结构。它们在仿真调度机制方面有一些相似之处。 在Verilog中,所有的描述语句(连续赋值语句、行为语句块、模块实例化等)都是并行发生的。然而,由于仿真器是串行执行的,Verilog中的并行行为实际上是通过串行执行来模拟的。这意味着在仿真过程中,仿真器会按照一定的顺序逐条执行语句,分时执行。在所有并行进程执行完之前,仿真时间不会向前推进。\[3\] SystemVerilog也具有类似的并行行为和仿真调度机制。它引入了调度器(scheduler)的概念,用于控制并发执行的顺序。调度器根据一定的规则和优先级来决定哪些并发块应该被执行。这样可以模拟出更复杂的并行行为。\[1\]\[2\] 总结来说,SystemVerilog和Verilog都使用了仿真调度机制来模拟并行行为。Verilog中的并行行为是通过串行执行来模拟的,而SystemVerilog引入了调度器来控制并发执行的顺序。 #### 引用[.reference_title] - *1* [SV仿真调度机制](https://blog.csdn.net/weixin_39060517/article/details/115909613)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [SV仿真调度机制以及阻塞阻塞赋值区别(用例子说明)](https://blog.csdn.net/dinghj3/article/details/122513314)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hardworking_IC_boy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值