Forkしたプロセスの制御 - processクラス

本記事は以下の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)

---

disable forkでは複数のforkしたプロセスをまとめて終了させます。どのプロセスに対する操作かを直接的に指定することはできません。一方processクラスを使用すれば対象のプロセスを直接指定して制御をすることができます。

# processクラス
process クラスは特殊なクラスで、プログラムからnew()することはできません。process::self()で各プロセスへのオブジェクトハンドルを取得し、その後各種操作を行います。
クラスのプロトタイプは以下のようになっています。

```verilog
class process;
  typedef enum { FINISHED, RUNNING, WAITING, SUSPENDED, KILLED } state;
  static function process self();
  function state status();
  function void kill();
  task await();
  function void suspend();
  function void resume();
  function void srandom( int seed );
  function string get_randstate();
  function void set_randstate( string state );
endclass
```

良く使われるメソッドは以下の通りです。
<dl>
<dt>self()</dt>
<dd>現在のプロセスに制御をするためのオブジェクトハンドルを返します</dd>
<dt>status()</dt>
<dd>そのプロセスのステータスを返します。FINISHED, RUNNING, WAITING, SUSPENDED, KILLEDのいずれかです</dd>
<dt>kill()</dt>
<dd>そのプロセスとそこからforkされたサブプロセスを終了させます</dd>
<dt>await()</dt>
<dd>そのプロセスの終了を (ステータスがFINISHEDまたはKILLEDになるのを)待ちます</dd>


# サンプルプログラム
以下がサンプルプログラムと実行結果です。
本例では親子関係にある2つのプロセスをforkしています。便宜的に親をProcess A、子をProcess Bと名付けています。`proc_a =process::self()`でProcess Aの制御に使用するオブジェクトハンドルを獲得します。Process Bも同様です。後はこのハンドルを介してメソッドを呼ぶだけです。status()で随時ステータスを獲得しつつ、3ns後にProcess Aに対してkill()を行っています。Process Aとその子プロセスであるProcess Bが停止します。

```verilog
`timescale 1ns/1ns

module top;
  initial begin
    process proc_a;
    process proc_b;

    $timeformat(-9, 0, " ns", 6);   
    fork
      begin
        proc_a = process::self();
        fork
          begin
            proc_b = process::self();
            delayPrint("Process B checkpoint", 2ns);
            delayPrint("Process B finishing", 2ns);
          end
        join_none
        delayPrint("Process A finishing", 4ns);
      end
    join_none
    #2ns;
    $display("%t: Process A's status is %p", $realtime, proc_a.status());
    $display("%t: Process B's status is %p", $realtime, proc_b.status());
    #1ns;
    $display("%t: Killing Process A",$realtime);
    proc_a.kill();
    $display("%t: Process A's status is %p", $realtime, proc_a.status());
    $display("%t: Process B's status is %p", $realtime, proc_b.status());
  end

  task automatic delayPrint(input string s,
                            input time dly = 0ns);
    #dly;
    $display(s);
  endtask
endmodule
```

```plaintext
# KERNEL:   2 ns: Process A's status is WAITING
# KERNEL:   2 ns: Process B's status is RUNNING
# KERNEL: Process B checkpoint 1
# KERNEL:   3 ns: Killing Process A
# KERNEL:   3 ns: Process A's status is KILLED
# KERNEL:   3 ns: Process B's status is KILLED
# KERNEL: Simulation has finished. There are no more test vectors to simulate.
```

本ソースコードはEDA Playground上に登録されています。
[Process class example](https://www.edaplayground.com/x/6Bw8)

# シミュレータの非互換項目

本機能の再確認のためにEDA Playgroudで動作させてみましたが、複数のシミュレータで対応が不完全なものがありました。最新バージョンでは修正されている、もしくはシミュレータオプションにより回避できるかもしれませんがご注意ください。

* 親プロセスが終了 (FINISHED)して子プロセスのみが残っている時 (RUNNING, WAITING, SUSPENDED)に、親プロセスに対してkill()した場合の動作が正しくないものがあります。本来は子プロセスも停止するべきと筆者は考えますが、停止しないものがあります
* Process classの中でstateのtypedefがされていないシミュレータがあります。この場合status()の戻り値がprocess::state型ではなく、0から4の数値で帰ってきます。

# 参考文献
 "9.7 Fine-grain process control". [1800-2017 - IEEE Standard for SystemVerilog--Unified Hardware Design, Specification, and Verification Language](https://doi.org/10.1109/IEEESTD.2018.8299595). pp. 229-231.