从事件调度理解阻塞和非阻塞
0 为什么要有事件调度
我们知道Verilog是一种并行编程语言,然而Verilog是通过计算机执行的,那么必然要遵循计算机顺序执行的逻辑
当多条语句都被触发时,我们如何确定语句的执行顺序就需要一种规则来做出限定
1 几个关键信息
仿真的代码是由一个个离散事件组成,运行Verilog也就是执行一个个时间和线程
进程包括UDP、module、initial块、always块、连续赋值语句、异步任务和过程赋值语句
在进行仿真时,所有线网、变量和命名块发生变化时,都被认为是更新事件
,而进程对更新事件是敏感的,更新事件执行时,所有对该实践敏感的进程都会按照任意顺序进行评估
仿真时间
用来模拟被仿真电路所需的实际时间
2 事件队列
Verilog事件队列被分为五个区域:活跃事件、非活跃事件、非阻塞赋值更新时间、监视事件和将来事件
下面是大佬总结的图
在执行顺序上:活跃事件 -> 非活跃事件 -> 非阻塞赋值更新事件 -> 监控事件 -> 将来事件
不过这五个事件内包含的操作,它们的执行顺序是随机的
我对当前仿真时间的理解是当T,将来仿真时间是次T
3 确定性和不确定性
3.1 确定性
- begin...end中的语句都是按顺序执行的
- 非阻塞赋值的执行顺序也是按照语句出现的顺序执行
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