RTL で依存性の注入（っぽいこと）をする

# RTL で依存性の注入（っぽいこと）をする

石谷 @[PEZY Computing](https://www.pezy.co.jp/) です。[SystemVerilog Advent Calendar 2020](https://qiita.com/advent-calendar/2020/systemverilog) の２回目です。[１回目](https://qiita.com/taichi-ishitani/private/d5bb34273fce8d4b0385) は `inside` 演算子の解説を行いました。

今回は RTL 中で依存性の注入（っぽいこと）をやってみようと思います。依存性の注入 (Dependency Injection: DI) については、

* https://qiita.com/okadabasso/items/066efc2e728a666b8732
* https://qiita.com/hshimo/items/1136087e1c6e5c5b0d9f

当たりを参考にしてください。

## はじめに

RTL を書いていると、「ほとんどの機能は同じだが、微妙に違う」module が多く出てくると思います。
バスの MUX を例にとると、

* ラウンドロビンな MUX
* 固定優先順位な MUX
* ランダムな調停を行う MUX

のように、調停方法によって、微妙に違う MUX を実装する必要があります。

以下のように、`parameter` でどの調停方法を使うかを指定できるようにし、`generate` 文でその実装を切り替えれるようにすれば、一応の解決を見ます。

```verilog
module bus_mux #(
  parameter ARBITRATION = 0
);
  if (ARBITRATION == 0) begin : g_roundrobin
    //  ラウンドロビンを実装
  end
  else if (ARBITRATION == 1) begin : g_fixed_priority
    //  固定優先順位を実装
  end
  else begin : g_invalid
    //  該当なし
    //  エラー
  end
endmodule
```

ただし、この方法には、

* 調停方法が増えるたびに `bus_mux` を変更する必要がある
* 一品ものの特殊な調停方法に対応しにくい

などの欠点があります。解決するには、調停方法の実装を `bus_mux` 外部に追い出す必要があります。

## 依存性の注入（っぽいこと）を適用してみる

### 「依存性の注入」とは？

使われ方によって変化する機能を、どの実装を使うかの選択も含め、外部に追い出して、機能間を疎結合にし、拡張性やメンテナンス性、テスト容易性を向上させる設計パターンの1つです。

例えば、ある機能 A がログ出力する場合を考えます。ログの出力先として、

* ファイル
* 端末
* テストの用のモック

等々が考えられます。パラメータによって出力先を切り替える場合、先ほどの `bus_mux` の場合と同じ問題にあたってしまいます。
そこで DI の登場です。DI では、

1. 出力先ごとのログ出力を実装したログ出力オブジェクトを定義する
    * 各ログ出力オブジェクトは共通のインターフェースを持つ
2. 機能 A インスタンス時に、所望のログ出力オブジェクトを与える

ようにすることで、機能 A の実装とログ出力の実装を切り離すことができます。
ログの出力先が増えた場合、所望のログ出力オブジェクトを実装し、機能 A に与えることで実現できます。
これは、機能 A の実装を変えることなく実現できます。

### RTL 上でやってみる

SystemVerilog の RTL 上では、

* 機能オブジェクトは interface を使って実装
* 接続には generic interface を使用

することで、依存性の注入（っぽこと）を実現します。

先ほどの `bus_mux` の例を考えます。
調停方法が `bus_mux` から分離したい機能です。なので、interface を使って各調停方法（調停オブジェクトと呼ぶことにします）を実装します。

```verilog
interface roundrobin_arbiter #(
  parameter int ENTRIES = 2
)(
  input var i_clk, input var i_rst_n
);
  logic [ENTRIES-1:0] request;
  logic [ENTRIES-1:0] grant;

  modport arbiter (
    output  request,
    input   grant
  );

  //  ラウンドロビンを実装
endinterface

interface fixed_priority_arbiter #(
  parameter int ENTRIES = 2
);
  logic [ENTRIES-1:0] request;
  logic [ENTRIES-1:0] grant;

  modport arbiter (
    output  request,
    input   grant
  );

  //  固定優先度での調停を実装
endinterface
```

各調停オブジェクトは、共通のインターフェースとして、`modport arbiter` を持ちます。

次に、調停オブジェクトの接続についてです。これは `generic interface` を使って実現します。
`generic interface` を使えば、interface を介して参照する信号や function が存在しさえすれば、任意の interface のインスタンスを接続することができます。
なので、`bus_mux` の調停オブジェクト使用周辺の実装は、以下のようになります。

```verilog
module bus_mux (
  interface.arbiter arbiter
  foo_if.slave      slave_if[2],
  foo_if.master     master_if
);
  logic [1:0] grant;

  always_comb begin
    arbiter.request[0]  = slave_if[0].request;
    arbiter.request[1]  = slave_if[1].request;
    grant               = arbiter.grant;
  end
endmodule
```

最後に `bus_mux` と調停オブジェクトの接続についてです。それぞれをインスタンスして、普通に繋ぐだけです。

```verilog
fixed_priority_arbiter u_arbiter();
bus_mux u_mux (
  .arbiter  (u_arbiter  ),
  // 省略
);
```

調停方法をラウンドロビンにする場合は、`fixed_priority_arbiter` の代わりに、`roundrobin_arbiter` をインスタンスするだけです。

## 実装例

依存性の注入（っぽい）を適用した例を GitHub のリポジトリに置いてあります。
https://github.com/taichi-ishitani/rtl_with_di

[bus_mux.sv](https://github.com/taichi-ishitani/rtl_with_di/blob/master/bus_mux.sv) が MUX の実装です。
調停オブジェクトへの参照を、`interface.arbiter arbiter` として、`generic interface` を使って宣言しています。
調停オブジェクトの要件は、

* 名前が `arbiter` の `modport` を持つこと
* 上記 `modport` は、出力 `request` と、入力 `grant` を持つこと

があげられます。
調停オブジェクトとの接続があるだけで、それ以外の調停に関する記述は MUX の記述中には一切ありません。

[fixed_prioriy_arbiter.sv](https://github.com/taichi-ishitani/rtl_with_di/blob/master/fixed_priority_arbiter.sv) と [roundrobin_arbiter.sv](https://github.com/taichi-ishitani/rtl_with_di/blob/master/roundrobin_arbiter.sv) が調停オブジェクトの実装になります。
別々の interface として定義されていますが、上記の要件に合致する `modport` を持っているので、`bus_mux` の調停オブジェクトとして使うことができます。

[bus_mux_top.sv](https://github.com/taichi-ishitani/rtl_with_di/blob/master/bus_mux_top.sv) が使用例を示すトップモジュールになります。
２つの `bux_mux` のインスタンスがありますが、どの調停オブジェクトを使うかを切り替えることで、調停の種類の切り替えを行っています。

## まとめ

上記の例では、MUX の実装と、調停の実装とを分離できました。
他の調停方法を使いたい場合は、所望の調停方法を実装した、調停オブジェクトを実装するだけでよく、MUX の実装には影響はありません。MUX の実装が変化に対して強くなった、と言えます。
また、調停オブジェクトは MUX だけではなく、他のモジュールにも使えるので、より再利用性が高まったともいえます。

## 再度の宣伝

YAML とか Excel で記述したレジスタマップから、RTL 等を自動生成するツール (RgGen) を作っています。
面倒な CSR のコーディングをせずに済むので、手前味噌ですが、便利なツールです。
よかったら、使ってみてください。

* https://github.com/rggen/rggen

Qiita にも紹介記事を載せてあります。

* https://qiita.com/taichi-ishitani/items/5155b2928b7d85370ae6
* https://qiita.com/taichi-ishitani/items/d89738b5376503c813d8

UVM で記述した AMBA AXI/APB の VIP (モドキ) も公開しています。
こちらも、よければ、見てみてください。

* https://github.com/taichi-ishitani/tvip-axi
* https://github.com/taichi-ishitani/tvip-apb

SystenVerilog で記述した Network On Chip (NoC) も公開しています。
Design Compiler および Vivado でエラボレーションできるところまでは確認しています。
RTL 中で SystemVerilog をどこまで使えるのかの参考になると思います。
(ただし [Quartus は知らない](https://www.intel.com/content/www/us/en/programmable/quartushelp/17.0/mapIdTopics/jka1465580570693.htm)。)
UVM を使った検証環境も同梱しているので、UVM を使った検証環境の参考にもなると思います。

* https://github.com/taichi-ishitani/tnoc