SystemVerlg : wait fork和disable fork的作用范围

wait fork:
会引起调用进程阻塞直到它的所有子进程结束,
一般用来确保所有子进程(调用进程产生的进程)执行都已经结束

disable fork:
用来终止调用进程的所有活跃进程, 以及子进程的所有子进程

注意:wait fork作用的父进程下的子进程,而不包括子进程下的子进程,而disable fork则是作用于父进程下的所有进程,包括子进程的子进程;

wait fork和disable fork的作用和其名字一样清晰,一个是用来等待挂起的进程结束,一个是用来终止未结束的进程。一般简单的情况,上面的描述足够用来分析仿真环境的运行逻辑。但是如果同时挂起的进程太多而且wait fork或者disable fork也调用了多次,那就需要清楚这两个进程控制语句在何处地方起到控制作用。
上述描述都提到了调用进程和其子进程,调用进程即调用wait fork或者disable fork的进程,或者叫做父进程,而在父进程中创建的其它进程即子进程。从下面这个例子可以分析出这点:

https://www.chipverify.com/systemverilog/systemverilog-wait-fork

module tb_top;
 
  initial begin
    // Fork off 3 sub-threads in parallel and the currently executing main thread
    // will finish when any of the 3 sub-threads have finished.
    fork
 
      // Thread1 : Will finish first at time 40ns
      #40 $display ("[%0t ns] Show #40 $display statement", $time);    
 
      // Thread2 : Will finish at time 70ns
      begin
        #20 $display ("[%0t ns] Show #20 $display statement", $time);
 
        #50 $display ("[%0t ns] Show #50 $display statement", $time);
      end
 
      // Thread3 : Will finish at time 60ns
      #60 $display ("[%0t ns] TIMEOUT", $time);
    join_any
 
        // Display as soon as the fork is done
        $display ("[%0t ns] Fork join is done, wait fork to end", $time);
 
        // Fork two more processes
        fork
          #10 $display ("[%0t ns] Wait for 10", $time);
          #20 $display ("[%0t ns] Wait for 20", $time);
        join_any
 
        // Wait until ALL forked processes are over
        wait fork;
        $display ("[%0t ns] Fork join is over", $time);
   end
endmodule
[20 ns] Show #20 $display statement
[40 ns] Show #40 $display statement
[40 ns] Fork join is done, wait fork to end
[50 ns] Wait for 10
[60 ns] TIMEOUT
[60 ns] Wait for 20
[70 ns] Show #50 $display statement
[70 ns] Fork join is over

首先需要知道的是,能够建立进程的只有initial块,always块,以及fork语句(也许还有其它的,我SV也没有了解透彻,欢迎指出问题)。 inital、always块、fork join创建静态进程, fork join_any 和 fork join_none创建动态进程
了解这点之后我们就可以知道父进程是哪个范围,以及它有哪些子进程。在上述例子中, 调用进程就是initial挂起的一个begin块,它有哪些子进程?fork join_any的特点不在赘述,第一个fork join_any创建了三个进程(三条并行语句),第二个fork join_any创建了两个进程,一共五个。父进程调用的wait fork于是需要等待这五个进程全部结束之后才会打印后面的display,这五个进程结束得最晚的是Thread2, 花费70个仿真时间所以最后一个display打印时间是70 ns。
如果在wait fork之前打印一次时间,应该是50 ns,这个第一个和第二个fork join_any种的最短进程的花费时间之和。
再看看比较特殊一点的情况

class A;
	string name;
	function new (string name ="");
		this.name = name;
	endfunction
	task run (int delay,  string mark);
		fork
			# delay;
		join_none
		$display ("@%0t-----------%s_run_1", $time,  mark);
		wait fork;
		$display ("@%0t-----------%s_run_2", $time,  mark);
	endtask
enclass
module Top;
	A a = new ("a");
	initial begin
		fork 
			# 60;
		join_none;

	   fork 
	       a.run (5, "thread1");
	       a.run (6, "thread2");
	   join_none
	   fork 
	       a.run (2, "thread3");
	   join_none
	   begin 
	       a.run (1, "thread4");
	   end
	   
	   wait fork;
	   
		$display ("@%0t-----------%s", $time, "wait 1");
	    fork 
	       a.run (10, "thread5");
	       a.run (20, "thread6");
	   join_none
	   a.run (10, "thread7");
	   
	   wait fork;
	   
	   $display ("@%0t-----------%s", $time, "wait 2");
    end
endmodule
@0-----------thread1_run_1
@0-----------thread2_run_1
@0-----------thread3_run_1
@2-----------thread3_run_2
@2-----------thread4_run_1
@5-----------thread1_run_2
@6-----------thread2_run_2
@60-----------thread4_run_2
@60-----------wait 1
@60-----------thread7_run_1
@60-----------thread5_run_1
@60-----------thread6_run_1
@70-----------thread5_run_2
@80-----------thread6_run_2
@80-----------thread7_run_2
@80-----------wait 2

这个例子可能写得有些复杂,但是可以看出关键问题。
首先有多个fork join_none创建了多个子进程,以第二个fork_none为例,里面调用了两个任务,任务体都可以视为一个begin块, 所以就产生了两个子进程(父进程是initial块),而在这个子进程即任务体中,又使用fork join_none 创建了一个子进程,其父进程则是这个任务体,它是一个子进程的子进程。
wait fork只会阻塞调用它的父进程,所以看打印时间,thread1, thread2, thread3的run_1都是在0时刻打印,而run_2则分别在5, 6, 2时刻打印,而这正是三次调用任务的传递的delay参数。这说明了wait fork不会等待属于父进程的父进程的子进程,只会等待自己的调用进程的子进程结束
知道这点和就好理解为什么thread4的run_2是在60这个时刻打印的了,run(1, “thread4”)是在一个begin块中被调用了,而wait fork又是在这个任务体中被调用了,但即使有一个begin end,还有一个task endtask封装,这个wait fork的调用进程还是intial块。因为能够创建进程的只有initial块,always块和fork语句。所以thread4的wait fork语句并不处于一个iniital块的子进程中,其父进程还是initial块,自然地,它阻塞了initial继续运行,等待前面挂起的子进程结束,前面挂起的最长的子进程时间是60,所以thread4_run_2的打印时间就是60。
后面的thread7的run_2打印时间是80的原因也是如此,它需要等待新创建的thread5和thread6进程的结束。
我还设想过wait fork是fork join/join_any/join_none的一条并行语句的情况,还是上面的代码,新的initial块如下

initial begin
	fork 
		# 30;
	join_none
	fork 
		a.run (10, "thread1");
		begin
			a.run (20, "thread2");
		end
		wait fork;
	join
	$display ("@%0t-----------%s", $time, "test");
end
@0-----------thread1_run_1
@0-----------thread2_run_1
@10-----------thread1_run_2
@20-----------thread2_run_2
@20-----------test

test的打印时间是20,也就是说fork join只在第二个run任务运行完后就结束了,其中的wait fork语句没有因为前面的fork join_none语句而阻塞。

进程process 和 线程 thread

SystemVerilog 为每一个initial块, always块和fork join/join_any/join_none的每一条并行语句以及每一个动态进程创建了一个执行线程,每条连续赋值语句也可以认为是它自己的线程。我对线程process和进程Thread的概念区别尚不清楚,也没有进行研究,但是根据上述描述两者基本是一致的。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值