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

0 为什么要有事件调度

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

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

1 几个关键信息

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

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

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

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

2 事件队列

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

下面是大佬总结的图

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

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

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

3 确定性和不确定性

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

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

3.2 不确定性

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

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

assign q = a;

always @(*) begin
    q = b;
end

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

4 阻塞和非阻塞

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

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

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

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的成功赋值

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延迟是处于非活跃事件的,那么我们再做一个小更改

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延迟

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

posted @ 2022-08-14 08:59  行走的BUG永动机  阅读(96)  评论(0编辑  收藏  举报