fork/join是Verilog中常用的语句。该语法在SystemVerilog中添加了join_any和join_none两个关键字,使fork的行为发生了变化。本文将比较全面的介绍fork的用法,其中不使用join_any和join_none关键字的时候,其用法和Verilog中一致。
在fork块中,begin和end之间的语句会顺序执行,如果没有begin和end,则各条语句会并发执行。看下面的例子。在fork块中有A、B两个任务,由于fork中么有begin/end块,这两个任务是并发执行的,我们看看打印结果。
module tb(
);
reg sigout=0;
initial begin
fork
A();
B();
join
// $display("Started!!");
// $display("Finished!!");
end
task A;
integer i=0;
repeat (2) begin
#10;
$display("Number %d.", i);
i+=2;
end
endtask : A
task B;
integer j=1;
#5;
repeat (4) begin
#10;
$display("Number %d.", j);
j+=2;
end
endtask : B
endmodule
打印结果如下,输出是从0 1 2 3 5 7。A任务和B任务的前两次repeat输出了0 1 2 3,此时A任务结束,B任务继续输出最后两次repeat的值,即5 7。
如果将A和B任务用begin/end块包含起来,这两个任务将会顺序执行,输出就会变成0 2 1 3 5 7,即A任务执行并输出完毕后,B任务才开始执行。如下图。
先看看SystemVerilog 3.1a版对于上述关键字的描述。
使用join时,该fork块将阻塞进程,直到所有在fork中所有的语句都执行完毕;使用join_any时,该fork块将阻塞进程,直到fork块中任意一个进程结束;使用join_none时,该fork块不会阻塞,并会和其他进程一起并发执行。但是这些并发的进程将在遇到第一个阻塞语句时才开始执行。
使用join时的用法和Verilog是一致的,不再赘述。
为了测试join_any,把上文的代码修改一下。在fork外加了两个打印语句。
module tb(
);
reg sigout=0;
initial begin
fork
A();
B();
join_any
$display("Started!!");
$display("Finished!!");
end
task A;
integer i=0;
repeat (2) begin
#10;
$display("Number %d.", i);
i+=2;
end
endtask : A
task B;
integer j=1;
#5;
repeat (4) begin
#10;
$display("Number %d.", j);
j+=2;
end
endtask : B
endmodule
以下是打印输出。可以看到,fork阻塞了进程,任务A首先完成,输出了0和2。此时由于A任务结束,fork不再阻塞进程,所以可以看到2输出后,紧接着输出了fork外面的两天打印语句。然后任务B接着输出3 5 7。
首先把用于测试join_any的代码中的join_any修改为join_none,其输出如下。
两条打印语句Started和Finished首先输出,有点出乎意料。这是由于fork块后面没有任何阻塞语句,而join_none不会阻塞下一条阻塞语句之前的所有进程。由于任务A和B中都添加了延时,所以Started和Finished被首先打印,然后才轮到任务A和B输出。为了更清晰的理解join_none,将测试程序进行修改,添加一个阻塞赋值语句,同时增加输出信号。
module tb(
);
reg sigout=0;
reg a_end, b_end;
initial begin
a_end = 0;
b_end = 0;
fork
A();
B();
join_none
$display("Started!!");
#20 sigout = 1;
$display("Finished!!");
end
task A;
integer i=0;
repeat (2) begin
#10;
$display("Number %d.", i);
i+=2;
end
a_end = 1;
endtask : A
task B;
integer j=1;
#5;
repeat (4) begin
#10;
$display("Number %d.", j);
j+=2;
end
b_end = 1;
endtask : B
endmodule
首先在Started和Finished两条打印语句之间插入了一个赋值语句,同时定义了三个新信号:a_end、b_end、sigout。该程序打印输入如下。fork内部的语句将和#20 sigout = 1之前的语句同时开始并发执行,但是该执行被挂起,直到遇到了#20 sigout = 1之后,才会执行。所以我们看到,Started的打印是0延时的,所以首先输出。接着是输出0和1,然后是20ns之后的sigout有效和Finished打印语句。
在看输出的波形,sigout和a_end都在20ns时输出,b_end在45ns时输出,符合预期。