fork/join是Verilog中常用的语句。该语法在SystemVerilog中添加了join_any和join_none两个关键字,使fork的行为发生了变化。本文将比较全面的介绍fork的用法,其中不使用join_any和join_none关键字的时候,其用法和Verilog中一致。

1. fork块中的begin/end块

    在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。

systemverilog 函数接收 队列_verilog

    如果将A和B任务用begin/end块包含起来,这两个任务将会顺序执行,输出就会变成0  2  1  3  5  7,即A任务执行并输出完毕后,B任务才开始执行。如下图。

systemverilog 函数接收 队列_verilog_02

2. join/join_any/join_none

    先看看SystemVerilog 3.1a版对于上述关键字的描述。

systemverilog 函数接收 队列_fpga/cpld_03

    使用join时,该fork块将阻塞进程,直到所有在fork中所有的语句都执行完毕;使用join_any时,该fork块将阻塞进程,直到fork块中任意一个进程结束;使用join_none时,该fork块不会阻塞,并会和其他进程一起并发执行。但是这些并发的进程将在遇到第一个阻塞语句时才开始执行。

    使用join时的用法和Verilog是一致的,不再赘述。

2.1 join_any

    为了测试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。

systemverilog 函数接收 队列_fpga/cpld_04

2.2 join_none

    首先把用于测试join_any的代码中的join_any修改为join_none,其输出如下。

systemverilog 函数接收 队列_SystemVerilog_05

下一条阻塞语句之前的所有进程。由于任务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

同时开始并发执行,但是该执行被挂起,直到遇到了#20 sigout = 1之后,才会执行。所以我们看到,Started的打印是0延时的,所以首先输出。接着是输出0和1,然后是20ns之后的sigout有效和Finished打印语句。

systemverilog 函数接收 队列_systemverilog_06

    在看输出的波形,sigout和a_end都在20ns时输出,b_end在45ns时输出,符合预期。

systemverilog 函数接收 队列_Verilog_07