制御レジスタ (CSR) を自動生成する

# 制御レジスタ (CSR) を自動生成する

## はじめに

制御レジスタの実装、手書きですか？実装自体は難しくはないですが、ビット位置など、微妙に異なる単調な記述が続くので、かなり面倒です。こういう手合のものは、自動生成するに限ります。という訳で、自動生成ツールを作りました。

## RgGen

RgGen (https://github.com/rggen/rggen) は、YAML とかExcel とか仕様に近い記述から、

* SystemVerilog/Verilog/VHDL RTL
* シミュレーション用モデル (UVM RAL)
* C ヘッダーファイル
* Markdown

を自動生成するツールです。

こんな入力

* https://github.com/rggen/rggen-sample/blob/master/block_0.yml
* https://github.com/rggen/rggen-sample/blob/master/block_0.xlsx
* https://github.com/rggen/rggen-sample/blob/master/block_0.rb

から、こんな出力を生成します。

* https://github.com/rggen/rggen-sample/blob/master/block_0.sv
* https://github.com/rggen/rggen-sample/blob/master/block_0_ral_pkg.sv
* https://github.com/rggen/rggen-sample/blob/master/block_0.h
* * https://github.com/rggen/rggen-sample/blob/master/block_0.md

また、追加のプラグインを用いれば、Verilog/VHDL/C header を生成することもできます。

* Verilog
    * https://github.com/rggen/rggen-verilog
    * https://github.com/rggen/rggen-sample/blob/master/block_0.v
* VHDL
    * https://github.com/rggen/rggen-vhdl
    * https://github.com/rggen/rggen-sample/blob/master/block_0.vhd

独自ビットフィールドやレジスタバスのプロトコルの追加など、自身の環境に合わせて拡張することも可能です。実際、ビットフィールドや社内仕様のプロトコルを追加して、実業務で運用しています。拡張方法については、以下を参照してください。

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

## インストール

RgGen は [Ruby](https://www.ruby-lang.org/ja/) を使って実装されています。サポートするバージョンは 2.6 以上です。まずは、Ruby を使えるようにしましょう。Ruby のバージョン管理ソフトである `rbenv` を使うのが手っ取り早いです。[この辺](https://qiita.com/tsubu-mustard/items/3f818bf9831a4a934c5a)の記事が参考になると思います。

Ruby が使えるようになったら、RgGen のインストールです。次のコマンドを実行すれば、インストール可能です。

```
$ gem install rggen
```

`rggen -v` を実行してみて、バージョンが表示されれば、インストール成功です。

```
$ rggen -v
RgGen 0.19
```

## 入力ファイル

以下の 2 種類の入力ファイルが必要です。

### コンフィグレーションファイル

データバス幅や、アドレス幅など設定を纏めたファイルです。YAML の連想配列形式を使って記述します。主に、以下の項目を指定します。

* bus_width
    * データバスのビット幅を指定します。
* address_width
    * アドレスのビット幅を指定します。
* protocol
    * レジスタバスのプロトコルをしています。
    * `apb`/`axi4lite`/`wishbone` が指定可能です。
* enable_wide_register
    * `true` を指定した場合、8 bytes 超のレジスタの定義を許可します
        * この場合、C ヘッダーファイルは生成できません
* array_port_format
    * 配列状になっている入出力ポートの形式を指定します。
    * `packed`/`unpakced`/`serialized` が指定可能です。
        * `packed`
            * 配列の形式にパックド配列を用いいます。
            * 未指定の場合、この形式が適用されます。
        * `unpacked`
            * 配列の形式にアンパックド配列を用いいます。
        * `serialized`
            * 配列を 1 つのビット列に纏めます。
    * 幅が `8` 、配列の大きさが `2 * 3` の場合、各形式でのポート宣言は以下のようになります。
        * `packed`
            * `input [1:0][2:0][7:0] i_foo`
        * `unpacked`
            * `input [7:0] i_foo[2][3]`
        * `serialized`
            * `input [47:0] i_foo`

```yaml
bus_width: 32
address_width: 16
protocol: apb
```
### レジスタマップ

アドレスや、ビットフィールドの幅やアクセス属性など、制御レジスタの仕様を纏めたファイルです。YAML/Ruby/Excel で記述可能ですが、本稿では、YAML に絞って説明します。他のフォーマットについては、[サンプル](https://github.com/rggen/rggen-sample)を参照してください。

レジスタマップは、

* レジスタブロック
* レジスタ
* ビットフィールド

の階層から成っています。以下の用に、連想配列の配列が階層状に連なった形に成っています。

```yaml
register_blocks:
  - name: ...
    registers:
      - name: ...
        bit_fields:
          - { name: ... }
          - { name: ... }               
```

全景は、[サンプル](https://github.com/rggen/rggen-sample/blob/master/block_0.yml)を参照してください。

#### レジスタブロック

機能ブロック単位で制御レジスタをまとめる階層です。以下の項目を指定します。また、`registers` に、配下のレジスタの記述を配列で羅列します。

* name
    * レジスタブロックの名前です。
* byte_size
    * レジスタブロックの大きさです。バイト単位で指定します。

```yaml
register_blocks:
  - name: cfg_foo
    byte_size: 256
    registers:
      - ...
      - ...
  - name: cfg_bar
    byte_size: 1024 
    registers:
      - ...
      - ...
```

#### レジスタ

ビットフィールドを機能単位で纏める階層です。以下の項目を指定します。また、`bit_fields` には以下のビットフィールドの記述を羅列します。

`byte_size` を超えない範囲であれば、ビットフィールドの幅に制限はありません。なので、データバス幅を超える幅を持つレジスタを定義することが出来ます。

* name
    * レジスタの名前です。
    * レジスタブロック単位で単一の名前をつけてください。
* offset_address
    * レジスタブロック内のオフセットアドレスです。
    * 単位はバイトです。
    * 未指定の場合、1 つ前のレジスタから計算されます。
    * 以下の場合でアドレスを重複させることが出来ます。
        * アクセス属性が、読み込みのみと書き込みのみの場合
        * レジスタ型が `indirect` の場合
* type
    * レジスタの型を指定します。
    * 無指定
    * external
        * SRAM や他のレジスタブロックを接続するためのレジスタ型です。
        * `size` で指定した範囲を外部に開放します。
    * indirect
        * 指定したビットフィールドを、追加のインデックスとして使用するレジスタ型です。
        * 単一のアドレス範囲に複数のレジスタを配置したい場合に使用します。
            * レジスタ幅分しか、アドレスを専有しません。
        * 以下のように配列で、追加のインデックスの指定を行います。
            * `[indirect, foo.foo_0, foo.foo_1, [bar.bar_0, 0]]`
            * ビットフィールド名のみが指定された場合は、配列の添字として使用されます。
            * ビットフィールド名と値が指定された場合は、ビットフィールドが指定した値になった場合に、有効になります。
    * reserved
        * 指定されたアドレス領域を予約済みにするためのレジスタ型です。
* size
    * 配列形式を使用して指定します。
    * レジスタ型が、無指定/`indirect` の場合
        * 配列の大きさを指定します。
    * レジスタ型が、`external` の場合
        * アドレス方向の大きさを、ワード単位で指定します。
        * バス幅が 32 ビットの場合、4 バイトが 1 ワードになります。

```yaml
registers:
  - name: foo
    offset_address: 0x00
    bit_fields:
      - ...
      - ...

  - name: bar
    offset_address: 0x10
    type: external
    size: [4]

  - name: baz
    offset_address: 0x20
    type: [indirect, foo.foo_0, foo.foo_1, [foo.foo_2, 1]]
    size: [2, 3]
    bit_fields:
      - ...
      - ...
```

#### ビットフィールド

制御レジスタの実体です。以下の項目を指定します。

* name
    * ビットフィールド名です。
    * レジスタ単位で、単一の名前をつけてください。
* bit_assignment
    * 開始ビット位置 (LSB) や幅などを、以下の項目を、連想配列形式で指定します。
        * width
            * ビットフィールドの幅を指定します。
            * 未指定の場合は 1 ビット幅になります。
        * lsb
            * 開始ビット位置を指定します。
            * 未指定の場合は、1 つ前のビットフィールドの値から計算されます。
        * sequence_size
            * いくつ繰り返すかを指定します。
            * 未指定の場合は、繰り返しなしです。
        * step
            * 現在の開始ビット位置と次の開始ビット位置の差分を指定します。
            * 未指定の場合は、`width` になります。
            * `sequense_size` が指定されている場合に有効です。
    * `{ lsb: 0, width: 4, sequence_size: 4, step: 16 }` の場合、実際のビット割当は以下のようになります。
        * [3:0]
        * [19:16]
        * [35:32]
        * [51:48]
* type
    * ビットフィールドの型を指定します。
    * 指定可能なビットフィールド型は[以下](#%E3%83%93%E3%83%83%E3%83%88%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%E5%9E%8B)を参照してください。
* initial_value
    * ビットフィールドの初期値(リセット値)を指定します。
    * `initial_value: VALUE`
        * 指定した値がそのまま反映されます。
    * `initial_value: { default: VALUE }`
        * 初期値はパラメータ化されます。
        * `VALUE` で指定された値が、パラメータの既定値になります。
    * `initial_value: [VALUE_1, VALUE_2,...]`
        * ビットフィールドが繰り返しになっている場合の、各ビットフィールドの初期値です。
        * 繰り返しが `4` でリセット値が `[0, 1, 2, 3]` の場合、各ビットフィールドの初期値は以下のようになります。
            * `bit_field[0]`: 0
            * `bit_field[1]`: 1
            * `bit_field[2]`: 2
            * `bit_field[3]`: 3
* reference
    * 参照するビットフィールドを `register_name.bit_field_name` で指定します。

```yaml
bit_fields:
  - { name: foo, bit_assignment: { lsb: 0 , width: 8 }, type: rw, initial_value 0x00 }
  - { name: bar, bit_assignment: { lsb: 32, width: 8 }, type: ro }
```

##### 指定可能なビットフィールド型

* rc
    * 読み出しのみ可能なビットフィールドです。
    * 読み出し時に 0 クリアします。
    * `reference` が指定された場合、指定されたビットフィールドはマスクとして使用されます。
        * `value & mask` が読み出し値になります。
        * マスクが掛かっていない、素の `value` も外部に出力されます。
    * 割り込みステータスへの使用を想定しています。
* reserved
    * 予約済み領域です。
* ro
    * 読み出しのみ可能なビットフィールドです。
    * `reference` が指定された場合、指定されたビットフィールドの値が読み出し値になります。
        * `rc` 型等でマクスが指定されている場合、マスクが掛かっていない素の値が参照されます。
* rof
    * 読み出しのみ可能なビットフィールドです。
    * `initial_value` で指定した値が読み出されます。
* rs
    * 読み出しのみ可能なビットフィールドです。
    * 読み出し時に 1 にセットされます。
* rotrg
    * 読み出しを示すトリガー出力付きの `ro` ビットフィールド型です
* row0trg/row1trg
    * `ro`/`row[0/1]trg` を合わせたビットフィールド型です
* rowo
    * `ro`/`wo` を合わせたビットフィールド型です
* rowotrg
    * 読み出し/書き込みを示すトリガー出力付きの `rowo` ビットフィールド型です
* rw
    * 読み書き可能なビットフィールドです。
* rwc
    * 読み書き可能なビットフィールドで、クリア入力が付随します。
    * `reference` が指定された場合、指定されたビットフィールドがクリア入力となります。
* rwe
    * 読み書きの可能なビットフィールドで、イネーブル入力が付随します。
    * イネーブル入力が 1 の時に、書き込みができます。
    * `reference` が指定された場合、指定されたビットフィールドがイネーブル入力となります。
* rwl
    * 読み書き可能なビットフィールで、ロック入力が付随します。
    * ロック入力が 1 の場合、書き込みはできません。
    * `reference` が指定された場合、指定されたビットフィールドがロック入力となります。
* rws
    * 読み書き可能なビットフィールドで、セット入力が付随します。
    * セット入力が 1 の場合、外部からの入力データを取り込みます。
    * `reference` が指定された場合、指定されたビットフィールドがセット入力となります。
* rwtrg
    * 読み出し/書き込みを示すトリガー出力付きの `rw` ビットフィールド型です
* w0c/w1c
    * `w0c` の場合は 0 を、`w1c` の場合は 1 を書き込んだ場合に 0 クリアされます。
    * `reference` が指定された場合、指定されたビットフィールドはマスクとして使用されます。
        * `value & mask` が読み出し値になります。
        * マスクが掛かっていない、素の `value` も外部に出力されます。
    * 割り込みステータスへの使用を想定しています。
* w0crs/w1crs
    * `w0crs`の場合は 0 を、`w1crs` の場合は 1 を書き込んだ場合に、0 クリアされます。
    * 読み出し時は、全ビットに 1 が設定されます。
* w0s/w1s
    * `w0s` の場合は 0 を、`w1s` の場合は 1 を書き込んだ場合に 1 にセットされます。
* w0src/w1src
    * `w0crc`の場合は 0 を、`w1src` の場合は 1 を書き込んだ場合に、1 が設定されます。
    * 読み出し時は、全ビットが 0 クリアされます。
* w0trg/w1trg
    * `w0trg` の場合は 0 を、`w1trg` の場合は 1 を書き込んだ場合に、one-shot のトリガー信号が出力されます。
    * 処理開始の通知や、クリア入力への使用を想定しています。
* w1
    * リセット後、1 回だけ書き込めるビットフィールドです。 
* wo
    * 書き込みのみ可能なビットフィールドです。
* wo1
    * `w1` 型の write only 版です。 
* wotrg
    * 書き込みを示すトリガー出力付きの `wo` ビットフィールド型です

## 生成！

`rggen` コマンドにコンフィグレーションファイル、レジスタマップを与えると、SystemVerilog RTL などを生成します。コンフィグレーションファイルは `-c` オプションの引数として与えます。複数個のレジスタマップを一度に与えることも可能です。

```
$ rggen -c config.yml block_0.yml block_1.yml
```

実行ディレクトリに、SystemVerilog RTL/UVM RALモデル/C ヘッダーファイル/Markdown が出力されます。出力されるフィアルの命名規則は以下のとおりです。

| File Type         | Naming Rule             |
|:------------------|:------------------------|
| SystemVerilog RTL | `block_name`.sv         |
| UVM RAL model     | `block_name`_ral_pkg.sv |
| C header file     | `block_name`.h          |
| Markdown          | `block_name`.md         |

出力先を代えたい場合は `-o` オプションで出力先を指定します。

```
$ rggen -c config.yml -o out block_0.yml block_1.yml
```

この場合、`out` ディレクトリにファイルが書き出されます。

## 組み込み

生成された SystemVerilog RTL/UVM RAL model は、共通 RTL モジュール、UVM RAL クラスライブラリを使って構成されています。

* RTL モジュール
    * https://github.com/rggen/rggen-sv-rtl
* UVM RAL クラスライブラリ
    * https://github.com/rggen/rggen-sv-ral

生成物を、実際のデザインや検証環境に組み込むには、上記のリポジトリをクローンする必要があります。また、クローン先を示す環境変数 (`RGGEN_SV_RTL_ROOT`/`RGGEN_SV_RAL_ROOT`) も設定します。

```
$ git clone https://github.com/rggen/rggen-sv-rtl.git
$ export RGGEN_SV_RTL_ROOT=`pwd`/rggen-sv-rtl
$ git clone https://github.com/rggen/rggen-sv-ral.git
$ export RGGEN_SV_RAL_ROOT=`pwd`/rggen-sv-ral
```

シミュレーションの場合、シミュレーションコマンドは次のようになります。

```
$ vcs \
  -full64 -sverilog -ntb_opts uvm \
  -f $RGGEN_SV_RTL_ROOT/compile.f block_0.sv your_design.sv \
  -f $RGGEN_SV_RAL_ROOT/compile.f block_0_ral_pkg.sv your_tb.sv
```

生成た SystemVerilog RTL と UVM RAL モデルを使ったサンプル環境を準備しています。実際に、組み込む際の参考になると思います。

https://github.com/rggen/rggen-sample-testbench

## 質問など

質問やフィードバックがありましたら、以下の連絡先に連絡願います :pray:

* https://github.com/rggen/rggen/issues
* rggen@googlegroups.com
* https://gitter.im/rggen/rggen