Moiz's journal

プログラミングやFPGAなどの技術系の趣味に関するブログです

RISCVエミュレータの途中経過

はじめに

前回のエントリーの続きです。

ちょっと今週来週雑用で作業ができなそうなので、忘れないように自分用の進捗メモです。 またしても読者の方々の事はあまり考えていません。

わざわざページ開いてくださった方には、もうしわけありません。いつかまとめてエントリーかきます。

このプロジェクトについて

RISC-Vのエミュレータをゆっくり作っています。最終目標はFPGA上で動作させることです。

github.com

すでに同様のものが多数あるのは知っていますが、自分の趣味と学習が目的ですので特に気にしていません。

命令の追加

命令を多数追加しました。いま実装されている命令は次のとおりです。

命令 タイプ
add rd, rs1, rs2 R
addi rd, rs1, im12 I
and rd, rs1, rs2 R
andi rd, rs1, imm12 I
beq rs1, rs2, offset B
bge rs1, rs2, offset B
bltu rs1, rs2, offset B
bne B
jal rd, offset21 J
jalr rd, offset(rs1) I
lw rd, offset(rs1) I
lui rd, imm24 U
or rd, rs1, rs2 R
ori rd, rs1, imm12 I
sll rd, rs1, rs2 R
slli rd, rs1, shamt I
slt rd, rs1, rs2 R
slti rd, rs1, imm12 I
sltiu rd, rs1, imm12 I
sltu rd, rs1, imm12 R
sra rd, rs1, imm12 R
srai rd, rs1, imm12 I
srl rd, rs1, imm12 R
srli rd, rs1, imm12 I
sub rd, rs1, rs2 R
xor rd, rs1, rs2 R
xori rd, rs1, imm12 I

結構増えました! RV32Iの50命令中、22個実装しています。

実行できるようになったもの。

命令を増やしたので、実行できるアルゴリズムが増えました。 例えばRISC-V原典にのっているインサートソートが実行できます。

// A1 is n and A3 points to array[0]
// A4 is i, A5 is j, A6 is x
addi A3, A0, 4
addi A4, ZERO, 1
// Outer Loop
bltu A4, A1, 8
jalr ZERO, RA, 0
lw A6, A3, 0
addi A2, A3, 0
addi A5, A4, 0
// Inner Loop
lw A7, A2, -4
bgte A6, A7, 20
sw A2, A7, 0
addi A5, A5, -1
addi A2, A2, -4
bne A5, ZERO, -20
// End of Inner Loop
slli A5, A5, 2
add A5, A0, A5
sw A5, A6, 0
addi A4, A4, 1
addi A3, A3, 4
jal ZERO, -64
// End of Outer Loop

レジスターA3にソートする配列へのポインター、A1にソートする要素の数を代入して読み出すとソートしてくれます。 こちら動作結果です。

Before: 116 211 664 486 472 777 429 528 290 433 136 777 502 357 160 481 731 675 802 671 427 21 949 350 997 985 960 483 836 987 126 305 551 790 791 375 919 220 255 209 6 392 339 508 101 499 989 832 174 791 856 953 164 805 655 161 142 616 644 331 955 122 636 506 265 427 233 184 999 489 746 357 881 85 865 982 584 207 167 110 998 23 415 515 180 71 676 675 39 673 6 994 795 642 853 412 421 86 949 772

After: 6 6 21 23 39 71 85 86 101 110 116 122 126 136 142 160 161 164 167 174 180 184 207 209 211 220 233 255 265 290 305 331 339 350 357 357 375 392 412 415 421 427 427 429 433 472 481 483 486 489 499 502 506 508 515 528 551 584 616 636 642 644 655 664 671 673 675 675 676 731 746 772 777 777 790 791 791 795 802 805 832 836 853 856 865 881 919 949 949 953 955 960 982 985 987 989 994 997 998 999

なんだかCPUの動作っぽくなってきました。

テストの追加

複雑になってきたのでテストを追加しました。

今回のコードではテストは主に、アセンブラ作成部分のテストと、CPUの動作のテストに分かれています。

アセンブラ部分のテスト

アセンブラ作成部分では一度符号拡張で見つけにくいエラーをだしてしまったのでカバレッジを上げることにしました。 具体的には以下の部分をランダム化したテストを各命令毎に作成しました。

  • rd
  • rs1
  • rs2
  • immediate

命令タイプによって存在しないものは除きます。 テストの内容は、

  1. ランダム化したパラメータを作成
  2. ランダム化したパラメータからバイナリ命令を作成
  3. アセンブラ機能を使ってパラーメータからバイナリ命令を作成
  4. 2で作成したものと3で作成したものを比較。

というものです。 3でテストを作成したのも自分自身なので、同じ誤解を2回している可能性はありますが、そうではないテクニカルな失敗はかなり防げます。

このテストは実装した全命令に対してそれぞれ100回ずつ行っています。

CPU部分のテスト

CPU側では上記のような命令ごとのユニットテストは難しいのですが、なるべくそれに近いものを作っています。 たとえば、add rd, rs1, rs2のテストでは、次のようなバイナリー・コードを生成します。

addi rs1, ZERO, value1 & 0xFFF
lui rs1, (value1 >> 12)
addi rs2, ZERO, value2 & 0xFFF
lui rs2, (value2 >> 12)
add rd, rs1, rs2
addi A0, rd, 0
jalr ZERO, RA, 0

ここで、value1, value2, rd, rs1, rs2はランダム化されたテストパラメータです。

これでadd rd, rs1, rs2に与えるパラメータ(レジスタの組み合わせ、レジスタの内容)のカバレージはかなり上がると思います。

ランダム化したテストはそれぞれ100回ずつ行っています。

また、同様のテストは他のR Typeの命令(cmd rd, rs1, rs2形式のもの)や、addiなどI Typeの命令(cmd rd, rs1, imm12形式のもの)でも行いました。

これらのユニットテストの他に、前回の1から10まで足し合わせるプログラムと、前述のソートプログラムをテストとして、コードを変更するたびに実行しています。

次のステップ

  • Bタイプ命令(blt等)へのランダム化テストの追加
  • LD/SW命令等の追加(+テスト)
  • 外部ファイル読み取り
  • 最小限のシステムコールのエミュレーション(テキストの表示くらいしたい)
  • システム系命令の対応
  • テストの自動化
  • MMUとか割り込みとかどうしよう(最終目標はハードウェア実装なので)

先は長いですね。

追記

記事のアップロード後早速こんな有用なコメントをいただきました。ありがとうございます。

当該のテストはこちらです。今後のTODOにいれておきます。

https://twitter.com/LDScell/status/1175905081050329088?s=20 github.com