fork joinとその仲間たち

本記事は以下の3部構成の一部です。

1. [fork joinとその仲間たち](https://qiita.com/triggerfish/items/055464c8b46825f0af49)
2. [Forkしたプロセスの制御 - disable fork](https://qiita.com/triggerfish/items/13f6d2e7a8b86aede37e)
3. [Forkしたプロセスの制御 - processクラス](https://qiita.com/triggerfish/items/4894f6be377a10d24292)

---

verilogには複数のプロセスを同時に実行する機能としてfork joinがありますが、System Verilogでは拡張され、fork join_anyとfork join_noneが仲間に加わりました。どちらも便利な機能で検証で頻繁に使用されますので紹介したいと思います。
全て複数のプロセスを同時に実行するというforkの部分は同じなのですが、どのタイミングで次のステートメントの実行に移るかのjoinの部分の振る舞いが異なります。

# fork join
最初にverilog時代からあったfork joinの復習です。fork joinの中のプロセス(begin endで囲ったブロック)が並列に実行されます。全てのプロセスの実行が終了するとjoinの次のステートメントに実行が移ります。
以下がサンプルプログラムと実行結果です。forkしたプロセスが全て終了した20nsでjoinの次のステートメントが実行されています。

```verilog
`timescale 1ns/1ns

program top();
  initial begin
    $timeformat(-9, 0, " ns", 6);
    fork
      begin
        #10ns $display("Process A Finished at %t", $realtime());
      end
      begin
        #20ns $display("Process B Finished at %t", $realtime());
      end
    join
    $display("Current time is %t", $realtime());
  end
endprogram
```



```plaintext
# KERNEL: Process A Finished at  10 ns
# KERNEL: Process B Finished at  20 ns
# KERNEL: Current time is  20 ns
# RUNTIME: Info: RUNTIME_0068 $finish called.
```

用途としてはLRMにもあるように、順番の不定な複数のイベントの終了を待つのに使用できます。以下の例ではevent\_aとevent\_b両方の発生後に次に進みます (本例のようにプロセスの単位が1ステートメントの場合はbegin endで囲う必要はありません)。

```verilog

fork
  @event_a;
  @event_b;
join
$display("Both evant_a and event_b have occurred.");
```


# fork join_any

join\_anyでは同時実行されたプロセスのうちどれか一つでも終了したら次のステートメントの実行に移ります。終了していないプロセスはそのまま実行が継続されます。
以下がサンプルプログラムと実行結果です。Process Aの終了時間である10nsでjoin\_anyの次のステートメントが実行されていることがわかります。

```verilog
`timescale 1ns/1ns

program top();
  initial begin
    $timeformat(-9, 0, " ns", 6);
    fork
      begin
        #10ns $display("Process A Finished at %t", $realtime());
      end
      begin
        #20ns $display("Process B Finished at %t", $realtime());
      end
    join_any
    $display("Current time is %t", $realtime());
    #15ns;
    $display("Current time is %t", $realtime());
  end
endprogram
```



```plaintext
# KERNEL: Process A Finished at  10 ns
# KERNEL: Current time is  10 ns
# KERNEL: Process B Finished at  20 ns
# KERNEL: Current time is  25 ns
# RUNTIME: Info: RUNTIME_0068 $finish called.
```

用途としてはステータスポーリングのタイムアウトが挙げられます。下記例では1 usをタイムアウトとし、REGから非0の値が読めるまでデータを読み出し続けます。例えREGS.REG.read()がハングして値を返さなくても、確実にタイムアウトが可能です。

```verilog
bit is_timeout = 1'b0;

fork
  begin
    int v;
    // Keep reading until non-zero is read from the register
    do (REGS.REG.read(v))
      ;
    while (!v);
  end
  #1us is_timeout = 1'b1;
join_any
disable fork;
if (is_timeout) begin
  `uvm_error("Timeout during reading REG");
end
```


# fork join_none

join\_noneではプロセスをforkしたら即座に次のステートメントの実行に移ります。
以下がサンプルプログラムと実行結果です。fork直後にjoin\_noneの次のステートメントが実行されていることがわかります。

```verilog
`timescale 1ns/1ns

program top();
  initial begin
    $timeformat(-9, 0, " ns", 6);
    fork
      begin
        #10ns $display("Process A Finished at %t", $realtime());
      end
      begin
        #20ns $display("Process B Finished at %t", $realtime());
      end
    join_none
    $display("Current time is %t", $realtime());
    #35ns;
    $display("Current time is %t", $realtime());
  end
endprogram
```


```plaintext
# KERNEL: Current time is   0 ns
# KERNEL: Process A Finished at  10 ns
# KERNEL: Process B Finished at  20 ns
# KERNEL: Current time is  35 ns
# RUNTIME: Info: RUNTIME_0068 $finish called.
```

本機能は検証環境内でDUTと信号レベルでやりとりをする部分に使用することが多いです。
例えばDUTの信号のモニタなどに使用できます。下記のようにfork join\_noneで独立プロセスとしてモニタを実行し、後はDUTからのイベントドリブンでステートメントを実行します。

```verilog
  fork
    forever begin
      @vif.signal_a;
      $display("signal_a value changed %x",@vif.signal_a);
    end
  join_none
```

# 参考文献
 "9.3.2 Parallel blocks". [1800-2017 - IEEE Standard for SystemVerilog--Unified Hardware Design, Specification, and Verification Language](https://doi.org/10.1109/IEEESTD.2018.8299595). pp.210-212.