RISC-Vベースの自作CPUに自作OSを実装(helloコマンドしかできない)
こんにちは,めぶきぶです.
この記事は,大学院生の春休みに就活をほったらかして作成した,自作CPUに自作OSを実装する,その記録になります.
タイトルは「RISC-Vベースの自作CPUに自作OSを実装(helloコマンドしかできない)」ですが,もう少し詳しく説明すると,「RISC-VとChiselで学ぶ はじめてのCPU自作」の内容をVerilogに移植し,「Writing an OS in 1,000 lines」という記事中のhelloコマンドまでを,移植したCPU上で動作させるという意味です.完全に自作というわけではないです.
1.完成したもの
TeraTermを用いてUART通信を行っています.
コマンドを入力し,その応答が返ってきます.
↓動画です(私のポスト)
https://x.com/MebuKibu/status/1773163575554592995?s=20
2.システムの全体像
構築したシステムは図のようになっています.
動作の流れ
①C言語にてOSを作成
②RISC-VコンパイラでOSをコンパイルし,バイナリファイルを作成
③バイナリファイルから,命令部分のみを抽出し,16進数に変換
④CPUのmemory.vに16進数形式のファイルを書き込む
⑤Vivadoを用いて,Verilogで作成したCPUをbitstreamファイルに変換
⑥FPGAボードに書き込み,CPUを起動
⑦TeraTermを用いてUART通信を行い,helloコマンドを入力し「Hello World」
2.1.FPGAボード
今回使用したFPGAボードはDigilent社の「ARTY S7-50」です.
数年前に,秋月電子通商で購入しました.そのときは,13,000円くらいだったと記憶していますが,今見たら22,440円になってました.びっくり
購入理由としては,「作ろう!CPU」のサポートページ において,このボードを用いたサンプルを見つけたためです.
Vivadoのインストールからシミュレーションの方法,FPGA書き込みまでサポートしているので,初心者の方にはおすすめです.
2.2.CPU
自作CPUは,「RISC-VとChiselで学ぶ はじめてのCPU自作――オープンソース命令セットによるカスタムCPU実装への第一歩」という本で作成するCPUをVerilogに移植したものです.
私の認識では,ChiselはVerilogよりも高位なハードウェア記述言語です.おそらく,ChiselコードをVerilogコードに変換することも可能ですが,より低位な視点で1からCPUを作成したいと思ったので,Verilogで書くことにしました.
この本は,かなりボリューミーで,簡単なCPUの実装から,パイプライン処理,ベクトル命令,カスタム命令について書かれています.その中から,「簡単なCPUの実装」の部分だけ使用し,Verilogに移植しました.
自作したCPUの命令セットは,RV32Iです.これは,RISC-V 32bitの基本整数命令セットIであり,乗算,除算命令は含みません.
2.3.OS
自作OSは,「Writing an OS in 1,000 lines」という記事のhelloコマンドまでを自作CPUに合うように変更しました.
operating-system-in-1000-lines.vercel.app
この記事は,全18章で構成され,章ごとに少しずつ実装していく形式になっています.一歩ずつ進めていくので,OSを勉強する上でおすすめです.
全18章のうち,15章のシステムコールまでを対象としました.16章以降は,ホストOS(自分のPCのOS)との通信するという内容でした.私は,FPGAボードに実装が目標だったため,内容が違うなと思い,16章以降は見送りました.
「Writing an OS in 1,000 lines」のOSと私の自作OSの変更点は以下です.
・OpenSBIを用いない
OpenSBIとは,RISC-VのオープンソースなSモードインターフェースです.自作CPUは,一から構築するため,これは使用できません.QEMUでのシミュレーションでは,-bias none としています.
・文字入出力はメモリマップドなUARTモジュール
OpenSBIが使用できないと,文字の入出力も一苦労です.ここでは,xv6-riscvで使用されているUARTモジュールを参考にして,実装することにしました.
xv6は,MIT(マサチューセッツ工科大学)の授業で用いられているOSです.元々x86 CPU用に実装されたxv6をRISC-V CPU用に実装し直したものがxv6-riscvです.ソースコードは,GitHubで公開されています.
・ページングSV32が実装できなかったので,ページングなし
ページングSV32に設定するには,CSRのSATPの32bit目の立てればよいと思っていたのですが,この処理を行うと例外が発生してしまいます.色々調べたのですが,解決方法が見つからず,ページングはなしとなりました.
shell.cはユーザアプリケーションで,ベースアドレスは仮想アドレスを指定しています.しかし,ページングがないため,仮想アドレスから物理アドレスへの変換ができません.そのため,一度コンパイルしてから,その結果をダンプしてアプリケーションのベース物理アドレスを確認し,その物理アドレスをリンカスクリプトで指定してから,再びコンパイルするという方法でなんとか実装しました.(アプリケーションとは)
そもそも,自作CPUではCSRは存在するだけで,ほぼ機能していないので,ユーザモードとか意味ないです(泣)
2.4.UART通信
メモリマップドなUARTモジュールについて少し説明します.
メモリマップドとは,あるアドレスに対するメモリの読み書きが,他のデバイスの読み書きと対応する形式のことを指します.
私の自作CPUの場合だと,アドレス0x1000に対してメモリの読み書きを行うと,そのアドレスにはUARTモジュールが接続されているので,UARTモジュールに対して読み書きが行われます.
この手法の利点は,CPU側はメモリの読み書きをするだけでよく,他のデバイスのことを考える必要がないことです.
UARTのOS側動作(C言語)は,前述のようにxv6-riscvを参考にしています.
一方,CPU側(Verilog)では,主に以下の記事を参考にしました.
この記事は,UARTの送信側だけですが,受信側も検索すれば沢山出てきます.
3.まとめ
今回,RISC-Vベースの自作CPUに,helloコマンドができる自作OSを実装し,FPGAボードで動作させました.
今後は,まずページングをなんとかすることと,ファイルシステムにも挑戦してみたいと思います.