RISC-Vほぼ互換の自作CPU上で動くなにかを作った

はじめに

この記事は 東京高専プロコンゼミ① Advent Calendar 2021 3日目の記事です。

やった事

ChiselでRISC-Vの命令を一通り実装したCPUを作り、そのCPU上で動くOSもどき (コンテキストスイッチ程度のもの) を作りました。

github.com

github.com

CPU自作

RISC-VとChiselで学ぶ はじめてのCPU自作 の内容をほぼ写すような形で実装しました。
この本はChiselでRISC-V互換のCPUを作る事を目的にしていて、rv32i (RISC-Vの最も基本的な命令セット) のほぼ全ての命令の実装が解説されています。
環境構築にDockerイメージが配布されていたり riscv-tests によるテストの手順が説明されていたりしていて、僕のような専門知識のない人間にも優しかったです。(嬉しい)

www.amazon.co.jp

github.com

本で紹介されていなかったいくつかの命令 (sb命令など) は適当に自分で実装しましたが、今回は並列処理を行わないためfence系の命令は実装していません。mretなども未実装です。
また、CPUに雑に手を加えた時にハザード関連でバグらせるのを避けるため、パイプラインを実装していない物を使ってデバッグを行うようにしています。 (本では5段パイプラインでの実装が解説されていました)

OSもどき

ここまで実装するとCの普通のコードが動くようになったので、この上でなにか動かしてみます。
とりあえず今回はコンテキストスイッチがあり、タイマ割り込み処理が走ってプロセス切り替えができるOSもどきを作りたいと思います。

開発環境

Cを書きたくないのでRustを使う事にします。
同じ事をされていた他の方 の記事には公式でサポートされているISAがrv32imacとrv32imcのみと書かれていたのですが、調べてみると現在は公式でrv32iへの対応がされており、targetを適当に指定してあげるだけでrv32iのバイナリを吐かせる事ができました。
それ以外の部分は RustでRISC-V OS自作!はじめの一歩 を参考に整えました。
言語自体の安全性や構文の便利さが活きただけでなく、no_stdでもiteratorやOptionなどの機能が使えてかなり満足です。 unsafeで囲めばインラインアセンブラなどを使ってやりたい放題できるのも嬉しい。

権限とCSR周り

RISC-VのPrivileged ISAに合わせて実装をするととても辛そう & 今回は自前のコードしか動かさない ので、今回はオレオレで権限周りの実装を行っていきます。
こうするとCSRを全く使わなくなるので、元の規則を無視して各CSRに入れる値を独自に定義してしまい、雑に値を読み書きするようにしています。(CSRの各レジスタの権限設定はサボっているため、ユーザープロセスでcsrrwなどを使われるとOSが死にます)
また、独自命令を実装しようとするとコンパイル時のtargetを弄らないといけなくて辛そうなので、手抜きの為にCSRの特定レジスタへの書き込みをフラグにCPU側で特殊な処理を行ったりもしました。

プロセス管理

プロセスを雑に [Option<Process>; 16] で管理し、Process構造体には退避時のレジスタの値やpcなどを格納しています。
各プロセスには固定量のメモリを割り当てていて、CSRの特定の位置にプロセス番号を投げるとCPU側に実装したmmuもどきが対応する物理メモリの値を取ってきてくれます。(ページングは実装していないため、メモリを雑に食べていくとすぐ死にます・・・)

割り込み

CSRにタイマ用のレジスタを用意し、一定サイクルが経過するかタスクが終了した場合にinterrupt関数のアドレスに飛ぶようにします。 遷移前にはCPU側でpcの退避を行い、interrupt関数の中でレジスタの退避や各種無効化を行った後にプロセスの切り替えを行います。
切り替え時には現在実行していないプロセスを優先的に選ぶようにしていますが、真面目にやるならProcess構造体に待ち時間などを保持すると良さそうです。

おわりに

CPU自作パートは本当に楽で特にトラブルもなく動かせました、書籍には本当に感謝・・・
OSもどきはかなり手抜きでメモリの動的な割り当てすらできないような状態なんですが、割と考える事が多く、ちゃんと頭を使っている気分になって楽しかったです。 (デバッグはつらい)
結局FPGAを一切触らずにシミュレーション環境上で動かしたため、いつか時間がある時に実機で動かしたい気分になりました。