32bit CPUを自作した
ハードウェア未経験者が簡単なCPUを自作した日記です。
FPGAの上にMIPSのサブセットを実装しました。
この1年ちょっと、OSやCPUといった低レイヤーの勉強をしてきたのですが、CPU編の一つの目標であったgccでコンパイルしたバイナリを動かすという目標が達成できました。
CPUといっても、とりあえずレジスタとメモリの読み書きができて、足し算引き算ができる程度のめちゃくちゃしょぼいCPUです。
ISAはMIPSのサブセットです。
以下のような特徴・制限があります。
- MIPSのごく一部の命令をサポート(とりあえずテストコードから生成されたバイナリが動く)
- マルチサイクル(現在は1命令=7clk)
- パイプラインなし
- 乗除算・シフタなし(当然HI/LOレジスタも実装なし)
- 浮動小数点演算のサポートなし
- メモリはワード(4byte)アクセスのみサポート、バイトアクセスはサポートなし
- 割り込みやMMUはサポートなし
- 外部とのIOなし
とりあえずテスト用のコードが動くように都度都度実装したので、対応した命令はすごく少ないです。
対応命令
デモ
関数再帰で10番目のフィボナッチ数を計算する動画をYoutubeに上げました。(QiitaでYoutube埋め込みできるようになってほしい...)
自作 32bit CPU
Verilogで書いたCPUを論理合成して、DE0-CV上で動作させました。
7セグの真ん中の2つが、$v0
レジスタの下位16bitを示していて、10番目のフィボナッチ数 0x37(=55)を求めて終了です。
(ちなみに7セグのうち、左の2つは$ra
レジスタ、右の2つはプログラムカウンタです)
低クロックで動作させているので、計算終了まで1分半もかかります。
HDLはここで公開しています。
kanade32
動作させたコード
mips_mainがメイン関数です。
int fibonacci(int n){
if(n == 0) return 0;
else if(n == 1) return 1;
return(fibonacci(n - 1) + fibonacci(n - 2));
}
int mips_main(void){
return fibonacci(10);
}
スタックポインタなどの初期化などが必要なので、以下のようなスタートアップルーチンをアセンブラで書いて、リンクしています。
.extern mips_main
.global start
start:
nop
main:
addi $sp, $zero, 0x400
nop
jal mips_main
add $v1, $zero, $v0
nop
j end
end:
nop
j end
nop
start
がエントリポイントで、スタックポインタ($sp
レジスタ)を初期化後、Cで書いたmips_main
関数にジャンプします。
mips_main
の戻り地は$v0
レジスタに入るので、それを$v1
レジスタに転送して終了しています。
今回作ったCPUはMIPSのバイナリを実行しますが、遅延スロットは持っていないので、分岐前にnop
を入れて対応しています。
MIPSをターゲットにクロスコンパイルして、リンクします。
mips-linux-gnu-as -o crt0.o crt0.asm
mips-linux-gnu-gcc -c -mno-abicalls -o fibonacci.o fibonacci.c
mips-linux-gnu-ld -e start -Ttext=0 -o out.o crt0.o fibonacci.o
mips-linux-gnu-objcopy --only-section=.text -O binary out.bin
とりあえずテキストセクションのバイナリだけを引っこ抜いて、CPUのメモリに読み込ませて動作します。
ちなみにローダー的なプログラムは書いていないので、elfのような実行形式は扱えません。
したがって、メモリ上にプログラムやデータの再配置もできないので、.rodataや.bssセクションは扱えません。
(CPUにメモリのバイトアクセスを実装して、俺々実行ファイル形式とローダーを作ればできるはずたぶん)
ブロック図
すべての信号線は書ききれないので、かなり簡易化した図です。
黒線がデータ信号、青線が制御信号です。
いわゆるノイマンアーキテクチャで、命令メモリのアドレス空間とデータメモリのアドレス空間は別れていません。
マルチサイクルで動作するCPUで、1命令を5つのステージに分けて処理します。
ステージは以下のようになっています。
- IF:命令フェチ
- ID:命令デコードとレジスタ読み取り
- EX:演算実行
- MEM:メモリアクセスと次の命令アドレスのセット
- WB:レジスタへの演算結果のライトバック
命令種別でクロック数は変わりません。
すべての命令は7クロックで処理されます。
図の中でステージレジスタ(stage reg)と読んでいるのが、ステージ間で情報を保持する素子です。
ステージ間(実際にステージを実行するところ)は組み合わせ回路で構築しています。
基本は1クロック、メモリアクセスは2クロックで、左から右にステージレジスタを1つずつ進んでいきます。
勉強したこと
CPU自作にあたって、最初に参考にした記事が以下です。。
ハード素人が32bit CPUをFPGAで自作して動かすまで読んだ本のまとめ - スティルハウスの書庫の書庫
紹介されている書籍を何冊か読んで、実際にMIPSの命令をいつくか実装して、フィボナッチ数計算のコードを動作するところまでたどり着けました。
読んだ本
自作エミュレータで学ぶx86アーキテクチャ
CPUに興味持つきっかけはこの本でした。
もともと30日でできる! OS自作入門を読む前にx86を少し勉強しておこうと思って読んだ本です。
そもそもCPUがどうやって計算を行っているのか、実際に簡単なエミュレータを自作して理解を深めることができます。
あくまでもエミュレータなので、ハードウェアの知識は得られないのですが、ユーザーから見えるCPUの動作を理解できます。
低レイヤーの入り口として良い本だと思います。
CPUの創りかた
ハードウェア的な面で、CPU自作の入門本といえばこの一冊。
電子回路の基礎(オームの法則くらい)を理解していれば、読んで4bit CPUを作れるようになります。
シングルサイクルで足し算しかできないCPUですが、基礎の基礎がかなりつまっており、CPUの構成要素や動作概念を理解できます。
実際にロジックICを組み合わせて作ったのが1年ほど前。
本自体が少し古いこともあり入手困難なICも含まれています。
そのため、入手できるICを組み合わせて、同じ論理を実現するなど試行錯誤したおかげで、論理回路の筋トレにもなりました。
この本を読んでから、OSなど他の領域の勉強などをしていて、数か月時間があいた後、再びこの本に戻りFPGAで再実装もしました。
FPGAボードで学ぶ 組込みシステム開発入門
FPGAとVerilog入門。
あまり内容見ずに買ってしまったのですが、途中からNiosIIの上で動くアプリケーションを作ったり、avalonバスに接続する周辺回路を作る内容になりました。
前半だけでも、Quartusでの開発方法、SignalTapでのデバッグ方法、Verilogでの論理回路記述、簡単なステートマシンの実装などの勉強になりました。
僕が作りたいのは、CPUのコア部分なので、後半は読んでいません。
この本を読んで、FPGAで最初に作ったのはi2cのマスターデバイスでした。
(まともに設計せずに書き始めたので非常にきたない。。)
とりあえずFPGAを温度センサーとつないで情報取れたのが嬉しかった。
Verilog HDL&VHDLテストベンチ記述の初歩
ある程度の規模の回路を作ろうと思うので、シミュレーションは必須です。
いきなりFPGAにコンフィグレーションして開発すると論理合成自体に時間がかかるので、単純に開発の効率も落ちます。
論理が正しいところまでは、シミュレーションで確認することで、コンパイル時間も短く、信号も好きなタイミングで見られるのでデバッグも楽に進められまます。
最初は実機にコンフィグレーションして、LEDや7セグ、SignalTapなどを使ってデバッグしていたのですが、正直やってられません。
シミュレーションを覚えてから、開発効率が100倍になりました。(誇張なし)
コンピュータの構成と設計 第5版 上
パタヘネ。
コンピュータアーキテクチャの基礎から入ってCPUの実装までわかります。
2進数での論理の演算、整数・小数の算術演算などの基礎から入り、それぞれ回路実装まで解説があります。
(HDLでは演算子を書けば勝手に合成してくれますが、具体的に論理回路としてどう計算されるのかを理解できました)
CPUに関してはMIPSを具体例として、実装が解説がされています。
(まだ上巻の途中までしか読めていませんが、一部の命令は解説がないので、雰囲気で実装しました)
今回MIPSのISAを実装するにあたって、一番参考になりました。
シングルサイクルからいきなりパイプライン化の話になるので、今そこで理解に苦しんでいます。
頑張って読み切ろう。
今後
やること、というよりは今時点で理解が不足していることです。
シフタと乗除算の実装
マルチサイクル化したので、1クロックで終わらない計算も実装できます。
シフタと加減算器を組み合わせて複数クロックのかかる乗除算を実装できます。
(単純に組み合わせ回路を合成すれば1クロックで計算可能ですが、回路規模が大きくなってしまうので悩みどころ)
HI/LOレジスタ周りが複雑ときくので、制御信号も増えそう。。。
FPUの実装
今回作ったのは整数の加算・減算・論理演算のみのALUです。
浮動小数点については、専用の回路設計が必要になるのでひとまず省略しました。
これまで、浮動小数点はソフトウェア上で扱うだけでも苦手意識が強かったのですが、パタヘネを読んで少し興味が出てきました。
基礎知識が全然足りていないので、どのくらい大変なのか未知数です。
RISC-V(rv32i)の実装
最近流行りのRISC-Vの最も基本になる命令セットです。
聞くところによると、MIPSと同じがそれ以上に命令セットがシンプルらしいです。
(乗除算すらも標準拡張Mの範囲で、rv32i自体には含まれていません)
まずは仕様書を読むところから。。。
感想
ハードウェアまじで何もわからないので、今回のCPU自作は楽しくもあり、同時に苦しかったです。
C言語で書いたコードをコンパイラして動いたときは本当に嬉しかったです。
今はまだ低レイヤーのどこに浸かるか、まだ色々体験入学している状態です。
CPUでもやりたいことはまだまだあるのですが、まだ手をつけていないコンパイラの勉強に行きたい気持ちもあり、たぶん↑に上げた課題は放り投げることになります。