进程
进程或线程是作为独立实体执行的任何代码片段。fork-join块创建并行运行的不同线程。在下面的图-1中,可以看到进程的类型和进程控制。
| 序号 | 进程 | 描述 | 
|---|---|---|
| 1. | fork-join | 只有所有子线程执行完毕时,父线程才会执行。 | 
| 2. | fork-join_any | 只有任何一个子线程执行完毕时,父线程才会执行。 | 
| 3. | fork-join_none | 父线程与子线程并行执行。 | 
| 4. | wait fork | 使父线程等待所有子线程执行完毕。 | 
| 5. | disable fork | 当执行disable fork时,会终止所有子线程的执行。 | 
| 6. | 细粒度进程控制 | 这些用于控制进程,并提供进程/线程的状态。 | 
进程或线程
有3种类型的线程/进程
- fork-join
- fork-join_any
- fork-join_none
fork-join
SystemVerilog通过fork-join结构支持并行线程。在fork-join进程中,只有当所有子线程完成执行时,父线程才会执行。
语法:-
fork  线程1  线程2  线程3
join代码片段:-
$display("[%0t] Thread_T1: a的值为%0d,b的值为%0d,c的值为%0d,d的值为%0d",$time,a,b,c,d);fork:FORK_F1  begin:BEGIN_B2  #1 a <= b;  b <= 7;  $monitor("[%0t] Thread-T2: a的值为%0d,b的值为%0d,c的值为%0d,d的值为%0d",$time,a,b,c,d);  #1 ->e1;  c = b;  end:BEGIN_B2  begin:BEGIN_B3  wait(e1.triggered);  $display("[%0t] 事件已触发",$time);  begin:BEGIN_B4  #1 d = c;  end:BEGIN_B4  end:BEGIN_B3  join:FORK_F1 $display("[%0t] Thread_T3: a的值为%0d,b的值为%0d,c的值为%0d,d的值为%0d",$time,a,b,c,d); 
输出:
 在图-2中,我们可以看到Thread_T1首先在#0模拟时间执行,但是Thread_T3将在所有子线程执行完毕后才执行,子线程将根据时间延迟执行。

在图-3中,可以清楚地了解关于fork-join代码的整个工作方式,以及与时间表区域相关的调度原理。
- 变量的取样将在提前的区域中完成。
- 所有阻塞赋值将在活动区域执行,所有非阻塞赋值将在活动区域中评估。
- 事件将在活动区域执行。
- $display语句将在活动区域执行。
- 所有#0延迟语句将在非活动区域中执行。
- 评估的非阻塞赋值将在NBA区域中执行。
- $monitor语句将在推迟的区域中执行。

fork-join_any
当任何一个子线程完成执行时,父线程将执行。这意味着如果fork-join_any块中有2个或更多线程,并且每个线程需要不同的时间完成。在这种情况下,无论哪个线程先完成,fork-join_any都将退出该块,并开始执行模拟中的下一个父线程/语句。这并不意味着剩余的子线程将被模拟自动丢弃。这些线程将在后台运行。
语法:-
fork  Thread 1  Thread 2  Thread 3  
join_any  
代码片段:-
$display("[%0t] Thread_T1: Starting of fork_join_any",$time);a = "Kapu";
c = "Malpe";fork:FORK_F1  begin:BEGIN_B2  #0 $display("[%0t] Thread_T2: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);  begin:BEGIN_B3  b <= a;  #1 $display("[%0t] Thread_T3: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);  end:BEGIN_B3  end:BEGIN_B2  fork:FORK_F2  begin:BEGIN_B4  #3 -> e1;  $display("[%0t] Thread_T4: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);  end:BEGIN_B4  join:FORK_F2  join_any:FORK_F1#1 $display("[%0t] Thread_T5: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);begin:BEGIN_B5wait(e1.triggered);d = "Kodi";$monitor("[%0t] Thread_T6: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);
end:BEGIN_B5
输出:-
 在下图中,我们可以看到父线程Thread_T1在#0处执行,子线程Thread_T3在#1处执行,然后只有父线程Thread_T5将在#2处执行。


fork-join_none
父线程与子线程并行执行。这意味着在fork-join_none外部的线程不会等待fork-join_none内部的任何线程完成,它们只是并行执行。这并不意味着模拟会自动丢弃其余的子线程。这些线程将在后台运行。
语法:-
fork Thread 1  Thread 2  Thread 3  
join_none 
代码片段:-
$display("[%0t] Thread_T1: Starting of fork_join_none",$time);a = "Kapu";
c = "Malpe";fork:FORK_F1  begin:BEGIN_B2  #1 $display("[%0t] Thread_T2: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);      b <= a;  #1 $display("[%0t] Thread_T3: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);  end:BEGIN_B2  fork:FORK_F2  #1 -> e1;  $display("[%0t] Thread_T4: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);  join:FORK_F2  join_none:FORK_F1#1 $display("[%0t] Thread_T5: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);wait(e1.triggered);
d = "Kodi";$monitor("[%0t] Thread_T6: Values of a =%0s,b =%0s,c =%0s,d =%0s",$time,a,b,c,d);
输出:在下图中,在#0处执行父线程Thread_T1和子线程Thread_T4,然后在#1处同时执行父线程Thread_T5和子线程Thread_T2,以此类推。


进程控制
System Verilog提供了允许一个进程终止或等待其他进程完成的构造。
- wait fork
- disable fork
- 细粒度进程控制
wait fork
wait fork语句用于确保所有子进程(由调用进程创建的进程)都已完成执行。它将等待直到所有fork进程完成执行。
代码片段:-
#1 $display("[%0t] Thread_T1: values of a = %0s,b = %0s,c = %0s",$time,a,b,c);fork:FORK_F1  #2 b <= "Delta";  #0 $display("[%0t] Thread_T2: values of a = %0s,b = %0s,c = %0s",$time,a,b,c);  begin:BEGIN_B2  #1 -> e1;  c = "Hoode";  #1 $display("[%0t] Thread_T3: values of a = %0s,b = %0s,c = %0s",$time,a,b,c);  end:BEGIN_B2  fork:FORK_F2  wait(e1.triggered);  #2 $display("[%0t] Thread_T4: values of a = %0s,b = %0s,c = %0s",$time,a,b,c);  join:FORK_F2  #1 $display("[%0t] Thread_T5: values of a = %0s,b = %0s,c = %0s",$time,a,b,c);  join_none:FORK_F1  wait fork;  
#0 $monitor("[%0t] Thread_T6: values of a = %0s,b = %0s,c = %0s",$time,a,b,c);  
输出:-
在下图中,我们看到在#1处,父线程Thread_T1将被执行,并且有一个#0语句将在非活动区域中工作,语句将在相应区域中执行。尽管我们使用了fork-join_none,但$monitor语句将等待所有子线程被执行。

disable fork
执行disable fork时,所有活动的进程都将被终止。
代码片段:-
#0 $display("[%0t] Thread_T1: Values of a = %0s,b = %0s,c = %0s",$time,a,b,c);fork:FORK_F1  #3 b <= "Delta";  #4 $display("[%0t] Thread_T2: Values of a = %0s,b = %0s,c = %0s",$time,a,b,c);  begin:BEGIN_B2  #1 -> e1;  c = "Hoode";  #1 $display("[%0t] Thread_T3: Values of a = %0s,b = %0s,c = %0s",$time,a,b,c);  end:BEGIN_B2  fork:FORK_F2  @(e1.triggered);  #1 $display("[%0t] Thread_T4: Values of a = %0s,b = %0s,c = %0s",$time,a,b,c);  join:FORK_F2  #1 $display("[%0t] Thread_T5: Values of a = %0s,b = %0s,c = %0s",$time,a,b,c);  join_any:FORK_F1  disable fork;  
#1 $display("[%0t] Thread_T6: ending of fork-join",$time);   
输出:-
在下图中,在#0处,我们正在等待事件被触发,#0语句将在活动区域中执行,因为它是$display语句。 在#1处,它正在触发事件e1,并且一个子线程Thread_T5将被执行,然后由于使用了fork-join_any,它将转到父线程并触发disable fork语句,然后所有剩余的子线程将被终止。

细粒度进程控制
SystemVerilog有一个内置的名为Process的类,允许一个进程(例如,像fork_join)访问和控制进程/线程。当我们分叉出任何线程时,会在那时创建Process类的新对象。该对象包含有关该线程的状态信息。

| 序号 | 任务 | 描述 | 
|---|---|---|
| 1. | self() | 用于创建进程的ID/对象。 | 
| 2. | status() | 用于返回当前线程的模式。 | 
| 3. | kill() | 用于终止线程。 | 
| 4. | await() | 用于等待当前线程完成其他线程。 | 
| 5. | suspend() | 用于暂停线程一段时间。 | 
| 6. | resume() | 用于从暂停状态恢复线程。 | 
self()
它为Process类创建对象/ID。该对象用于访问Process类的所有预定义方法。对象包含所有线程的状态信息。
语法:-
process p_handle1,p_handle2;  
initial begin p_handle1 = process :: self();  p_handle2 = process :: self(); 
end  
代码片段:-
fork:FORK_F1  $display("[%0t] Entered into fork-join and started first check for the process",$time);  #1 ->e1;  begin:BEGIN_B2  wait(e1.triggered);  if(p1 == null)  $display("[%0t] Not created",$time);  else  $display("[%0t] Created",$time);  ->e3;  #1 ->e2;  end:BEGIN_B2  #2 p1 = process :: self();  begin:BEGIN_B3  wait(e2.triggered);$display("[%0t] Started second check for the process",$time);  if(p1 == null)$display("[%0t] Not created",$time);else$display("[%0t] Created",$time);->e4;end:BEGIN_B3fork:FORK_F2begin:BEGIN_B4wait(e3.triggered);$display("[%0t] first check for the process done",$time);end:BEGIN_B4begin:BEGIN_B5wait(e4.triggered);$display("[%0t] Second check for the process done",$time);end:BEGIN_B5join:FORK_F2join:FORK_F1
在上面的代码片段中,您可以看到在#0模拟时间时声明了进程类的句柄。在下面的图中,
- 在#1模拟时间时,我们正在检查是否创建了对象p1,然后它显示“未创建”。
- 在#2模拟时间时,我们为进程p1创建一个对象。
- 在#3模拟时间时,我们检查对象p1,显示“已创建”。


Status()
它将显示进程ID的状态/模式。有不同的模式,如已完成(Finished)、运行中(Running)、等待中(Waiting)、已挂起(Suspended)、已终止(Killed)。
语法:-
process p_handle;
initial beginbegin  p_handle = process :: self();  $display("status : %s",p_handle.status());  end
end 
代码片段:-
$display("[%0t] Seeking status:",$time);  fork:FORK_F1  begin:BEGIN_B2  p1 = process :: self();  #1 $display("[%0t] I am in process p1",$time);  $display("[%0t] Initial status of p1: %s",$time,p1.status());  #1 $display("[%0t] Still working in p1",$time);  ->e1;  ->e2;  end:BEGIN_B2  begin:BEGIN_B3  p2 = process :: self();  wait(e2.triggered);  #1 $display("[%0t] I am in process p2",$time);  $display("[%0t] Initial status of p2: %s",$time,p2.status());  $display("[%0t] Still working in p2",$time);  ->e3;  end:BEGIN_B3  begin:BEGIN_B4  wait(e1.triggered);  $display("[%0t] Final status of p1: %s",$time,p1.status());  end:BEGIN_B4  begin:BEGIN_B5  wait(e3.triggered);  $display("[%0t] Final status of p2: %s",$time,p2.status());  end:BEGIN_B5  fork:FORK_F2  p3 = process :: self();  #1 $display("[%0t] I am in process p3",$time);  #1 $display("[%0t] status of p3: %s",$time,p3.status());  #1 ->e4;  join:FORK_F2  join_any:FORK_F1  wait(e4.triggered);  
#1 $display("[%0t] Final status of p3: %s",$time,p3.status());  
在下图中,
- 您可以看到一些字符串是大写的,这些是进程p1和p2的状态。
- 在不同的模拟时间,进程/线程的状态将根据它们的执行而改变。  

kill()
kill()函数终止进程及其所有子进程。如果进程没有被阻塞(由于等待语句、延迟或等待事件触发),则它将在当前时间戳被终止。
语法:-
Process p_handle1; 
initial begin fork p_handle1 = process :: self();  p_handle1.kill(); join_any  
end 
代码片段:-
$display("[%0t] Seeking status:",$time);fork:FORK_F1  begin:BEGIN_B2  p1 = process :: self();  #1 $display("[%0t] I am in process p1",$time);  $display("[%0t] Initial status check of p1: %s",$time,p1.status);  ->e1;  if(p1.status() != process :: FINISHED)  p1.kill();  $display("hi i am working");  $display("what about you?");  end:BEGIN_B2  begin:BEGIN_B3  wait(e1.triggered);  #1 $display("[%0t] Status of p1 before killing: %s",$time,p1.status());  end:BEGIN_B3  join:FORK_F1  
在上述代码片段中,您可以看到在#0模拟时间时创建了进程p1的进程类对象。 在下图中,
- 在#1模拟时间时,进程p1的状态为RUNNING。
- 在使用kill()方法后的#2模拟时间时,进程p1的状态为KILLED。


await()
这种方法用于允许一个进程等待另一个进程/线程完成。
语法:-
Process p_handle1,p_handle2;  
initial begin  fork beginp_handle1 = process :: self(); p_handle2.await(); end begin p_handle2 = process :: self();endjoin 
end
代码片段:-
在上述代码片段中,我们试图使进程p1等待直到进程p2完成。 在下面的图8中,您可以看到:
- 在#1模拟时间之前使用await()方法之前,p1的状态为RUNNING。
- 在使用await()方法后的#2模拟时间时,p1的状态为WAITING。
- 一旦p2的状态为FINISHED,则p1的状态也为FINISHED。


suspend()
这个方法用于暂停进程/线程的执行。它可以暂停自己或其他进程的执行。执行将暂停,直到遇到resume()方法。 如果进程没有被阻塞(由于等待语句、延迟或等待事件触发),则它将在当前时间戳被暂停。
语法:-
Process p_handle1;  
initial begin  fork beginp_handle1 = process :: self();p_handle1.suspend();  endjoin_none 
end 
代码片段:-
在上述代码片段中,我们试图使进程p1永久暂停。 在下面的图10中,您可以看到:
- 在#1模拟时间之前,暂停p1的状态为RUNNING。
- 在#3模拟时间之后,暂停p1的状态为SUSPENDED。


resume()
这个方法用于重新启动被暂停的进程。如果暂停的进程在被阻塞时(由于等待语句、延迟或等待事件触发),则恢复该进程将重新初始化到事件表达式或等待条件为真,或等待延迟到期。
语法:-
Process p_handle1,p_handle2;
initial begin fork  begin  p_handle1 = process :: self();  p_handle1.suspend();  end  begin p_handle2 = process :: self();p_handle1.resume();  end  join_none  
end
代码片段:-
$display("[%0t] Seeking status:",$time);  fork:FORK_F1  begin:BEGIN_B2  p1 = process :: self();  #1 $display("[%0t] I am in process p1",$time);  $display("[%0t] Initial status of p1: %s",$time,p1.status());  ->e1;  if(p1.status() != process :: FINISHED)  begin:BEGIN_B3  #1 $display("[%0t] Status of p1 before suspending: %s",$time,p1.status());  p1.suspend();  $display("[%0t] Status of p2 in p1 block: %s",$time,p2.status());  end:BEGIN_B3  end:BEGIN_B2  begin:BEGIN_B4  wait(e2.triggered);  $display("[%0t] Status of p1 before resuming: %s",$time,p1.status());  p1.resume();  #1 $display("[%0t] Status of p1 after resuming: %s",$time,p1.status());  ->e3;  end:BEGIN_B4  begin:BEGIN_B6  p2 = process :: self();  #1 $display("[%0t] I am in process p2",$time);  $display("[%0t] Initial status of p2: %s",$time,p2.status());  if(p1.status() == process :: SUSPENDED)  #1 ->e2;  end:BEGIN_B6  begin:BEGIN_B7  wait(e3.triggered);  #1 $display("[%0t] Final status of p1: %s",$time,p1.status());  $display("[%0t] Final status of p2: %s",$time,p2.status());  end:BEGIN_B7  join:FORK_F1
在上述代码片段中,我们试图在进程p2中恢复进程p1。 在下面的图12中,您可以看到:
- 在#1模拟时间时,p1的状态为RUNNING。
- 在使用resume()方法之前的#2模拟时间时,p1的状态为SUSPENDED。
- 在使用resume()方法后的#3模拟时间时,p1的状态为FINISHED。


进程常见问题
- fork_join、fork_join_any 和 fork_join_none 之间的区别
| fork_join | fork_join_any | fok_join_none | 
|---|---|---|
| 在 fork_join 中,主(父)线程在 fork_join 中的所有线程(子线程)执行完毕后才会执行 | 在 fork_join_any 中,如果任何一个子线程执行,则主(父)线程执行 | 在 fork_join_none 中,子线程和主(父)线程同时执行 | 
- 我们可以在 fork_join 中使用 wait_fork 吗?
我们知道,在 fork_join 中,仅当 fork_join 中的所有线程执行完毕时,主线程才会执行,因此不需要使用 wait_fork。 我们可以在 fork_join_any 或 fork_join_none 语句后使用 wait fork,以等待 fork-join_any 或 fork_join_none 中的所有线程完成。 因此,在 fork_join 中不需要 wait_fork。
- 阻塞和非阻塞赋值的区别
| 阻塞 | 非阻塞 | 
|---|---|
| 在阻塞赋值中,一条语句执行完毕后,下一条语句将执行,即右侧表达式的第一个表达式被评估并立即分配给左侧变量 | 在非阻塞赋值中,对当前时间单位的所有右侧表达式进行评估,并在时间单位结束时分配给左侧变量 | 
| 由 " = " 表示 | 由 " <= " 表示 | 
| 它按顺序执行 | 它并行执行 | 
| 阻塞用于组合逻辑 | 非阻塞用于时序逻辑 | 
- wait event 和 @ event 之间的区别
如果我们在相同的延迟下触发 wait 和 @,那么 wait 语句会被执行,因为 wait 捕获速度比 @ 快。
- 我们可以使用不同延迟执行 wait 和 @ 吗?
module tb;    event e;  initial begin  #20 ->e;  $display($time,"thread1");  end  initial   begin  #25 @e;  $display($time,"thread2");  end  initial   begin  #15 wait(e.triggered);  $display($time,"thread3");  end 
endmodule  
在上面的例子中,我们可以看到事件、wait 和 @ 的延迟是不同的。我们还可以看到这里 @ 的延迟大于事件的延迟,而 wait 的延迟小于事件的延迟,所以这里只有 wait 语句与事件的延迟一起执行。因此,在下面的图中,我们可以看到线程 1 和线程 3 使用相同的延迟(#20)执行。
