🎄

エミュレータから始めるRISC-V

2022/12/27に公開

お世話になってます、くらげです。

僕も所属する東京大学工学部電気電子工学科・電子情報工学科では、B3の秋に、後期実験という「より専門性の高いテーマに集中して取り組む」実験があります。僕はその中で「マイクロプロセッサの設計・実装」という実験に割り当てられ、VerilogでRISC-Vが動くCPUを自作する実験を行いました。

この実験では基本的な整数演算用の仕様であるRV32Iを動作させるプロセッサを作りました。テストコードを命令メモリに読み込みVivadoのウィンドウ上に表示される波形を見ながらデバッグする作業は、普段触れているプログラミングとは異なる趣があり非常に歯ごたえがありました。
実験を通してRISC-Vの仕様に明るくなり、より発展的なCPUの作成をしてみたいと思ったんですが、一方、Vivado & Verilogによるハードウェア記述言語での開発はテスト・デバッグ作業が辛く、自分ではあまりやりたくないと感じたのも事実です。そこで、今回はこれらのツールを離れ、より慣れた開発言語でRISC-Vのエミュレータを作成し、簡単なテストコードが動作することを確認しました。

この記事はEEIC Advent Calendar 2022の25日目の記事です。
今年も多数のTwitterオタクたち学生の参加、ありがとうございました!
大トリなのに遅刻してごめんなさい!

https://adventar.org/calendars/7892

作成したエミュレータ

これです

https://github.com/hamadatakaki/kuragemu-riscv

よくある1ステージのCPUを模したエミュレータをRustで作成しました。

Rustの環境があるコンピュータで cargo run とすると動くと思います。

テストする

正しく動作するかテストするために実験ではCoremarkというベンチマークを動かしていましたが、そこまで対応する時間はとれなかったため、今回は自分で用意した挿入ソートを行うテストコードをエミュレーションしてみました。

以下に手書きしたアセンブリ言語でテストコードを示します。コメントアウトは対応するプログラムカウンタの値と、ジャンプを行う命令におけるPCの差分です。

initialize:
    addi    a3, x0, 0x1c    # 00
    addi    a1, x0, 1       # 04
    sw      a1, 0(a3)       # 08
    addi    a1, x0, 2       # 0c
    sw      a1, -4(a3)      # 10
    addi    a1, x0, 5       # 14
    sw      a1, -8(a3)      # 18
    addi    a1, x0, 6       # 1c
    sw      a1, -c(a3)      # 20
    addi    a1, x0, 7       # 24
    sw      a1, -10(a3)     # 28
    addi    a1, x0, 3       # 2c
    sw      a1, -14(a3)     # 30
    addi    a1, x0, 4       # 34
    sw      a1, -18(a3)     # 38
    addi    a1, x0, 8       # 3c
    sw      a1, -1c(a3)     # 40
    addi    a3, x0, 4       # 44
insert_sort:
    addi    a4, x0, 1       # 48
outer_loop:
    bltu    a4, a1, outer_loop_2   # 4c (+8)
exit_loop:
    j       end             # 50 (+40)
outer_loop_2:
    lw      a6, 0(a3)       # 54
    addi    a2, a3, 0       # 58
    addi    a5, a4, 0       # 5c
inner_loop:
    lw      a7, -4(a2)      # 60
    bge     a6, a7, exit_inner_loop # 64 (+14)
    sw      a7, 0(a2)       # 68
    addi    a5, a5, -1      # 6c
    addi    a2, a2, -4      # 70
    bne     a5, x0, inner_loop      # 74 (-14)
exit_inner_loop:
    slli    a5, a5, 2       # 78
    add     a5, a0, a5      # 7c
    sw      a6, 0(a5)       # 80
    addi    a4, a4, 1       # 84
    addi    a3, a3, 4       # 88
    j       outer_loop      # 8c (-40)
end:
    jal     x0, 0           # 90

このアセンブリをRISC-V命令セットの形に変換し16進数として記述したものがこれです。

挿入ソートの動作例としてはWikipediaの挿入ソートの記事から拝借しました。initializeラベルの部分でaddi命令とsw命令により主記憶を初期化する部分で挿入ソート前の配列を初期化しています。

実行結果

cargo run とすると各ステップごとのログを出力し、プログラム終了後に各レジスタの値と主記憶の最初の10バイトを出力します。

main: [8, 4, 3, 7, 6, 5, 2, 1, 0, 0] # ソート前
main: [1, 2, 3, 4, 5, 6, 7, 8, 0, 0] # ソート後

確かにソートされており(少なくともソートで使った命令の範囲では)テストが動いたことを確かめられました!

おわりに

ぎりぎりまで記事のテーマが決まらず簡単な内容になってしまいましたが、とりあえず動くものが作れてよかったです。

プログラムを書いてから記事を書く前にほかの参加者の記事を読んだんですが、低レアプログラミングやエミュレータに近い内容の記事を出してる人が多くて「被った?」と焦りました。
完全にかぶっていればこの記事はお蔵入りでアドカレはバックレようと思いましたが、ギリギリ何とかかぶって無さそうで安堵しています。

今年も多数の参加ありがとうございました!来年もよろしくお願いします!!

Discussion