相关阅读
SystemVerilog基础https://blog.csdn.net/weixin_45791458/category_12517449.html?spm=1001.2014.3001.5482
有关Verilog中顺序块和并行块的相关内容已经在之前的Verilog基础的文章讲过,如下所示。
Verilog基础:块语句https://blog.csdn.net/weixin_45791458/article/details/132561861?spm=1001.2014.3001.5502 但在SystemVerilog中,并行块除了fork-join这种形式之外,还多出了两个变种,fork-join_any和fork-join_none,本文将对此进行讨论。
下面是Verilog中并行块语法的BNF范式,有关BNF范式相关内容,可以之前的文章。
Verilog基础:巴科斯范式(BNF)https://blog.csdn.net/weixin_45791458/article/details/132567389?spm=1001.2014.3001.5502
下面是SystemVerilog中并行块语法的BNF范式,可以看到其中变动较大的就是,并行块结尾的关键词可以是join、join_any、join_none之间的任何一个。
SystemVerilog中的并行块具有以下几个性质:
1、块内的语句在进入并行块后同时执行(同时执行不代表连续执行,并行块执行过程中可能会发生进程挂起与切换,这点在之后说明)。
2、块内各语句的过程赋值延迟全部是相对于进入并行块时而言的(有关过程赋值延迟的内容,可以看Verilog基础:延时模型)。
3、块内各语句的过程赋值延迟可以提供语句赋值的时间顺序。
4、根据join keyword关键词的不同,控制流可能以不同的方式传出并行块(这与Verilog中的并行块不一样)。
首先回顾一下Verilog中的并行块,在并行块开始执行时,块内语句同时执行(这个同时不是严格意义的同时,指的是同一个仿真时间),在被调度到仿真时间最后的语句执行完后,则并行块整体执行完毕。
下面的代码片是一个例子,显示了并行块语句的执行情况。
//例1
`timescale 1ns/1ns
initial begin
#2 $display("Enter fork at %t", $time);
fork
#1 $display("Statement1 execute at %t", $time);
#2 $display("Statement2 execute at %t", $time);
#3 $display("Statement3 execute at %t", $time);
join
$display("Exit fork at %t", $time);
end
输出
Enter fork at 2
Statement1 execute at 3
Statement2 execute at 4
Statement3 execute at 5
Exit fork at 5
但是要注意的是,对于有内嵌延迟的过程赋值语句,一旦右端的表达式的值计算完毕,即视作此表达式执行完毕,详细请看下面的例子。
//例2
reg [1:0]a;
initial begin
#2 $display("Enter fork at %t", $time);
$monitor("a = %d at %t", a, $time);
fork
a <= #1 1;
a <= #2 2;
a <= #3 3;
join
$display("Exit fork at %t", $time);
end
输出:
Enter fork at 2
Exit fork at 2
a = x at 2
a = 1 at 3
a = 2 at 4
a = 3 at 5
可以看到进入并行块和退出并行块的仿真时间是相同的,因为非阻塞赋值的内嵌延迟并不会阻塞控制流执行并行块内的其他非阻塞赋值语句,而真正的赋值需要等到3、4、5ns时。
将上例中的非阻塞赋值改为阻塞赋值后会发生什么?下面给出了这种情况下的输出。
//例3
reg [1:0]a;
initial begin
#2 $display("Enter fork at %t", $time);
$monitor("a = %d at %t", a, $time);
fork
a = #1 1;
a = #2 2;
a = #3 3;
join
$display("Exit fork at %t", $time);
end
输出:
Enter fork at 2
a = x at 2
a = 1 at 3
a = 2 at 4
Exit fork at 5
a = 3 at 5
尽管并行块内各个阻塞赋值语句在同一仿真时间的执行顺序不同,但有内嵌延迟的阻塞赋值必须等待延迟时间后才视为执行完毕,三条阻塞赋值语句同时执行,则延迟最长的那条阻塞赋值语句执行完毕后退出并行块,即2+3=5ns时退出并行块。
对于fork-join的讨论到此为止,下面是对于fork-any的讨论。当并行块末尾关键词使用fork-any时,当并行块中有任意一条语句执行完毕,控制便可以跳出并行块执行之后的语句(当然,对于并行块中其他尚未执行的语句,它们仍然会在被调度的仿真时间执行),下面举例说明。
//例4
reg [1:0]a;
initial begin
#2 $display("Enter fork at %t", $time);
$monitor("a = %d at %t", a, $time);
fork
#1 a = 1;
#2 a = 2;
#3 a = 3;
join_any
$display("Exit fork at %t", $time);
end
输出:
Enter fork at 2
a = x at 2
Exit fork at 3
a = 1 at 3
a = 2 at 4
a = 3 at 5
可以看到,如果fork-join_any块中的任何一个语句执行完毕,控制就可以跳出并行块了,即在3ns时就退出并行块了,但并行块内部的后两个赋值仍然会在其被调度的时间执行,所以在4ns和5ns时,a的值发生了改变。
这样就会出现一种问题:当fork-join块内的语句和块后的语句被调度到同一时间执行时,它们的执行顺序有保证吗?我们知道begin end块能保证顺序块内语句依次执行,但fork-join_any块使得在结束一个并行块前,可以执行后面的语句,下面看一个例子。
//例5
reg [1:0]a;
initial begin
#2 $display("Enter fork at %t", $time);
$monitor("a = %d at %t", a, $time);
fork
#1 a = 1;
#2 a = 2;
join_any
$display("Exit fork at %t", $time);
#1 a = 0;
end
输出:
Enter fork at 2
a = x at 2
Exit fork at 3
a = 1 at 3
a = 0 at 4
上例中,当a=1在3ns时执行完毕后,控制即可跳出并行块,但在随后1ns,仿真器就要面临一个选择,是先执行a=2还是a=0,begin-end块的性质是否能保证a=2一定在a=0之前执行?答案是否定的,虽然结果表明a=2在a=0之前执行(最终结果为0),但这不是一定的,比如下面的反例。
//例6
reg [1:0]a;
initial begin
#2 $display("Enter fork at %t", $time);
$monitor("a = %d at %t", a, $time);
fork
a = 1;
#1 a = 2;
join_any
$display("Exit fork at %t", $time);
#1 a = 0;
end
输出:
Enter fork at 2
Exit fork at 2
a = 1 at 2
a = 2 at 3
由输出可以清晰地看出,a=2时在a=0后发生(最终结果为2)。即begin-end块只能保证在同一仿真时间,fork块中的第一条执行的语句是在fork块后语句之前的,而无法保证其他同一仿真时间的语句的执行顺序。