前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从事件调度理解阻塞和非阻塞

从事件调度理解阻塞和非阻塞

原创
作者头像
行走的BUG永动机
发布2022-08-14 09:08:35
4110
发布2022-08-14 09:08:35
举报
文章被收录于专栏:工作学习工作学习

0 为什么要有事件调度

我们知道Verilog是一种并行编程语言,然而Verilog是通过计算机执行的,那么必然要遵循计算机顺序执行的逻辑

当多条语句都被触发时,我们如何确定语句的执行顺序就需要一种规则来做出限定

1 几个关键信息

仿真的代码是由一个个离散事件组成,运行Verilog也就是执行一个个时间和线程

进程包括UDP、module、initial块、always块、连续赋值语句、异步任务和过程赋值语句

在进行仿真时,所有线网、变量和命名块发生变化时,都被认为是更新事件,而进程对更新事件是敏感的,更新事件执行时,所有对该实践敏感的进程都会按照任意顺序进行评估

仿真时间用来模拟被仿真电路所需的实际时间

2 事件队列

Verilog事件队列被分为五个区域:活跃事件、非活跃事件、非阻塞赋值更新时间、监视事件和将来事件

下面是大佬总结的图

在执行顺序上:活跃事件 -> 非活跃事件 -> 非阻塞赋值更新事件 -> 监控事件 -> 将来事件

不过这五个事件内包含的操作,它们的执行顺序是随机的

我对当前仿真时间的理解是当T,将来仿真时间是次T

3 确定性和不确定性

3.1 确定性
  1. begin...end中的语句都是按顺序执行的
  2. 非阻塞赋值的执行顺序也是按照语句出现的顺序执行
代码语言:javascript
复制
 initial begin
    a <= 0;
    a <= 1;
 end

以上面这个例子来说,根据确定性可知,两条语句的执行按顺序执行的,在进入非阻塞赋值更新时,变量a的值先被更新为0,后被更新为1

3.2 不确定性

在确定性中指出,在一个block中,所有语句的执行都是顺序执行的,那如果语句不在同一个block中呢?

对于不同的block,具体是哪个block先执行,这个行为是不确定的,比如下面这个例子

代码语言:javascript
复制
 assign q = a;
 
 always @(*) begin
     q = b;
 end

两条语句在不同的block中,而这两个block我们没办法预知到底是哪个block先执行,因此最终变量q是a的值还是b的值是不确定的,这也是为什么在学习Verilog时一直在强调,同一个变量不能在多个block中进行赋值

4 阻塞和非阻塞

从上面的调度表可以看出,阻塞赋值在活跃事件中;非阻塞的右式计算在活跃事件中,而更在非阻塞赋值更新事件中

由于事件队列的执行是顺序执行的,当仿真进入当前仿真时间时,先执行活跃事件,对于阻塞和非阻塞来说,当进入活跃事件时,阻塞赋值进行右式计算,并将计算的结果赋值给左式,而对于非阻塞赋值,此时只进行右式的计算,但不会立即将结果更新到左式,直到事件队列进入非阻塞赋值更新事件时,才更新非阻塞赋值的左式

说的可能不好理解,我们用例子来做说明

代码语言:javascript
复制
 module test;
     reg [1:0] a,b;
 
     initial begin
          a = 2'b11;
     end
 
     initial begin
         b <= 2'b1;
     end
 
     initial begin
         $display($time, ,"\$display: a-> %b", a);
         $display($time, ,"\$display: b-> %b", b);
         #10;
         $finish;
     end
 
 endmodule

从上面的事件调度表中可以知道,在这个例子中,阻塞赋值、非阻塞赋值的右式计算和$display都属于活跃事件

那么对于这个例子可以做出提前预测结果,变量a成功赋值,变量b没有完成赋值,来看看运行结果

可以看到运行结果与我们的猜测一致

同样的,如果我们加入监控事件,那么在监控事件中可以看到变量b的成功赋值

代码语言:javascript
复制
 module test;
     reg [1:0] a,b;
 
     initial begin
          a = 2'b11;
     end
 
     initial begin
         b <= 2'b1;
     end
 
     initial begin
         $display($time, ,"\$display: a-> %b", a);
         $display($time, ,"\$display: b-> %b", b);
         #10;
         $finish;
     end
 
     initial begin
         $strobe($time, ,"\$strobe: a-> %b", a);
         $strobe($time, ,"\$strobe: b-> %b", b);
     end
 endmodule

可以看到,在$strobe中,变量b成功赋值

回观调度表,有一个显示零延迟(#0),而#0延迟是处于非活跃事件的,那么我们再做一个小更改

代码语言:javascript
复制
 module test;
     reg [1:0] a,b;
 
     initial begin
          #0 a = 2'b11;
     end
 
     initial begin
         b <= 2'b1;
     end
 
     initial begin
         $display($time, ,"\$display: a-> %b", a);
         $display($time, ,"\$display: b-> %b", b);
         #10;
         $finish;
     end
 
     initial begin
         $strobe($time, ,"\$strobe: a-> %b", a);
         $strobe($time, ,"\$strobe: b-> %b", b);
     end
 endmodule

可以看到,当我们给阻塞赋值加上#0延迟后,$display不在显示变量a的赋值,因为加上#0延迟后,阻塞赋值进入非活跃事件队列,而处于活跃事件队列的$display自然无法打印变量a的值

同时监控事件$strobe能够打印变量a的值,说明加上#0延迟后,阻塞赋值还是属于当前仿真时间中,并未进入将来仿真事件,而如果我们给阻塞赋值加入一个非0延迟

代码语言:javascript
复制
 module test;
     reg [1:0] a,b;
 
     initial begin
          #1 a = 2'b11;
     end
 
     initial begin
         b <= 2'b1;
     end
 
     initial begin
         $display($time, ,"\$display: a-> %b", a);
         $display($time, ,"\$display: b-> %b", b);
         #10;
         $finish;
     end
 
     initial begin
         $strobe($time, ,"\$strobe: a-> %b", a);
         $strobe($time, ,"\$strobe: b-> %b", b);
     end
 endmodule

当加入非0延迟,$strobe便无法获取变量a的赋值

参考资料

( Verilog HDL分层事件队列_formerman的博客-CSDN博客

verilog-std-1364-2005

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0 为什么要有事件调度
  • 1 几个关键信息
  • 2 事件队列
  • 3 确定性和不确定性
    • 3.1 确定性
      • 3.2 不确定性
      • 4 阻塞和非阻塞
      • 参考资料
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档