RISC-V RV32I CPU ASFRV32Iの解説

# ASFRV32I 
[ASFRV32I](https://github.com/asfdrwe/ASFRV32I)は1ファイル189行のVerilogで実装したシングルサイクルのRISC-V RV32IのCPUです。[RISC-V Unpriviledged ISA 20191213](https://riscv.org//wp-content/uploads/2019/12/riscv-spec-20191213.pdf) に準拠しています。

この記事ではASFRV32Iの使い方を説明していきます。ASFRV32Iの設計と実装の詳細は[別の記事](https://qiita.com/asfdrwe/items/595c871611e6603741fa)を参照してください。

## ASFRV32Iの動かし方
[Icarus verlog](http://iverilog.icarus.com/)でシミュレーションできます。Linuxなら大抵のディストリビューションでバイナリが用意されていると思います。[Windows版](http://bleyer.org/icarus/)もあります。

### ダウンロード
gitでダウンロードしてください。

```
$ git clone https://github.com/asfdrwe/ASFRV32I.git
```

### 論理合成
Icarus verlogで論理合成します。

  ```
iverilog -o RV32I RV32I.v RV32I_test.v
  ```

### シミュレーション実行
test.hexを読み込んで実行します。

  ```
./RV32I
  ```

### プログラム(test.hex)の書き方
ASFRV32Iのメモリは命令データ兼用で4KBです。1行が8bit16進数のテキスト形式のtest.hexを読み込み実行します。RISC-Vバイナリを実行するためにはこのテキスト形式に変換する必要があります。

#### ハンドアセンブルの場合
RV32Iは4バイト固定長です。ASFRV32Iではリトルエンディアンで書きます。
lui x1, 0x20000(x1レジスタの上位20bitに0x20000を代入し下位12bitに0x000を代入)ならば機械語は

  ```
0b_0010_0000_0000_0000_0000_00001_0110111(2進数)
0x_20_00_00_B7(16進数)
  ```
なのでtest.hexは次のようになります。
 
  ```
B7
00
00
20
  ```

#### gcc
[RISC-Vのツールチェイン](https://github.com/riscv/riscv-gnu-toolchain) を取得してビルドしてください。

ファイル取得 
 
  ```
$ git clone https://github.com/riscv/riscv-gnu-toolchain
$ cd riscv-gnu-toolchain
$ git submodule update --init --recursive
  ```

ビルドしてパスを通します。

  ```
$ ./configure --prefix=/opt/riscv
$ make linux
$ make install
$ export PATH=/opt/riscv/bin:$PATH
  ```

ASFRV32IはOSなしのベアメタルです。ASFRV32Iはプログラムを0番地から実行するので下記のlink.ldのようなリンカスクリプトで実行コード(.textセクション)を0番地に配置しています。

[link.ld](mytest/link.ld)

  ```
OUTPUT_ARCH( "riscv" )
ENTRY(_start)

SECTIONS
{
  . = 0x00000000;
  .text.init : { *(.text.init) }
  . = ALIGN(0x0100);
  .tohost : { *(.tohost) }
  . = ALIGN(0x0100);
  .text : { *(.text) }
  . = ALIGN(0x0100);
  .data : { *(.data) }
  .bss : { *(.bss) }
  _end = .;
}
  ```

##### アセンブリ
アセンブリで記述する場合は次のように実装します。_startをエントリポイントとして0番地に配置され_startからプログラムが実行されます。
[test.S](mytest/test.S) 

  ```
.globl _start

_start:
  lui x1, 0x50000
  addi x1, x1, 0xca
_loop:
  jal x2, _loop

.data
        .align 4
testdata:
        .dword 41
  ```

コンパイルは次のように行います。 

  ```
$ riscv64-unknown-elf-gcc -o test.bin test.S -march=rv32g -mabi=ilp32 -nostdlib -nostartfiles -T ./link.ld
  ```

##### バイナリの変換
elf形式のバイナリを1行8bit16進数のテキストファイルに変換します。[freedom-bin2hex.py](https://github.com/sifive/elf2hex/blob/master/freedom-bin2hex.py) をダウンロードしてください。実行にはpythonが必要です。

objcopyでbinaryをダンプしてfreedom-bin2hex.pyで1行8bit16進数のテキストファイルに変換します。

  ```
$ riscv64-unknown-elf-objcopy -O binary test test.bin
$ python freedom-bin2hex.py -w 8 test.bin test.hex
  ```

##### c言語
ベアメタルなのでint main()は使えません。printf等も使えません。スタックポインタを設定するスタートアップルーチンがないとサブルーチンの呼び出しができないので、0x0800をスタックに設定したstart.Sをスタートアップルーチンとします。start.Sは_mainをエントリポイントとしています。

[start.S](mytest/start.S)  

  ```
.globl _start

_start:
  li sp, 0x0800
  jal _main
  j
  ```

Cでのコード例  
[test2.c](mytest/test2.c)

  ```
void _main()
{
  static int a, b, c;
  a = 1;
  b = 2;
  c = 1 + 2;
  int d, e, f, h;
  d = 10;
  e = 15;
  f = e - d;
  h = a - 10;
  return;
}
  ```

コンパイルは次のようにstart.Sも一緒にアセンブル＆リンクして-nostdlib -nostartfilesを指定してください。

ASFRV32Iでは未対応命令はNOP扱いなので掛け算等はNOPになるはずです(未確認)。
-march=rv32iを指定してlibgccをリンクすれば掛け算もRV32Iの範囲でなんとかしてくれるとは思いますが確認していません。

  ```
$ riscv64-unknown-elf-gcc -o test2.bin start.S test2.c -march=rv32g -mabi=ilp32 -nostdlib -nostartfiles -T ./link.ld
  ```

バイナリからtest.hexへの変換はアセンブリのときと同じです。  

  ```
$ riscv64-unknown-elf-objcopy test2 -O binary test2.bin
$ freedom-bin2hex.py -w 8 test2.bin test.hex
  ```

#### riscv-tests
[riscv-tests](https://github.com/riscv/riscv-tests) を使うことでRV32Iに正しく準拠しているか確認できます。

ファイル取得  

  ```
$ git clone https://github.com/riscv/riscv-tests
$ cd riscv-tests
$ git submodule update --init --recursive
$ autoconf
  ```

ASFRV32Iはメモリが4KBで割り込みやCSR関係を実装していないので、0番地から実行するように修正しCSR命令と割り込み関連を削除します。

risc-testsのトップディレクトリで [riscv-tests.patch](riscv-tests.patch) を当ててください。

  ```
$ patch -p1 < ASFRV32Iの場所/riscv-tests.patch
  ```

hex形式にしたものとコンパイル後に逆アセンブルしたもの(*.dump)を[test](test/)以下に用意してあります。ASFRV32Iではecallが0番地への無条件分岐なのことに気をつけて動作確認してみてください。