命令セット自作のすすめ

この記事はISer Advent Calendar 2020の22日目として書かれたものです。昨日はKazuki Otaさんのp5.jsからPythonの関数を呼び出す。でした。

こんにちは。IS20erのRisebbit1と申します。自作CPUがなんとか完動したのでこの記事を書いています。
IS(東京大学理学部情報科学科)といえばCPU実験ですね。この実験では学生が3~4人の班に分かれてCPUやコンパイラなどを作ります。私が担当するコア係は土台となるハードウェアの部分であるプロセッサコアを作るのが仕事です。命令セットを設計して、その命令セットを動作させる回路を設計したらVerilog HDLに書き起こすというのが大まかな流れになります。

さて、そんなわけでまず最初にやるのは命令セットの設計です。せっかくオリジナルのCPUを作るわけですから、命令セットもオリジナルな物を作りたいですよね?...ね?
ところが結局、今年命令セットを1から作ったのは私の班だけでした。えぇ...。他の班はRISC-VやMIPSをベースに作っているようです。しかしこれは寂しいのでオリジナル命令セットを作る方が1人でも多くなることを期待してこの記事を書くことにしました。

自作しようと思った理由

自作と言ってもアセンブリの書き方はほとんどRISC-Vと同じです。大きく異なるのは各命令に対応する機械語のフォーマットです。以下に示すのがRISC-VのR形式のフォーマットです。f:id:Risebbit:20201219165645p:plain これを見ると分かるのですが、パーツがやたら多いです。アセンブリでは例えば add a0, a1, a2 と書いているので、この命令に対応する機械語は4つのパーツがあれば十分なはずです。functなんて使わずに全部opcodeにまとめてしまいたい、そう思ったのが自作のきっかけです2。もちろんRISC-Vがこのような設計になったのは理由があるはずで、高性能なハードウェアの実現に役立っていることでしょう。しかし、複雑なフォーマットであることも事実で、初めて作るCPUはより単純なフォーマットにしたいと考えました。他にも、自分で設計したものは自分が一番良く分かっているので改良がしやすいというメリットもあります。
また、仕様書を読むのが苦手で既存のフォーマットを理解して再現するぐらいなら自分で設計したほうが楽しいと思ったのも理由の一つです。今まで他人が決めたルール(言語仕様やアーキテクチャ)の上で動くものをさんざん作ってきたわけです。このCPU実験はそんなルールを決める側になる数少ない機会です。コンパイラは自分が決めたルールを守ってくれます。世界のルールを決定する神になる、そんな貴重な機会を逃すわけにはいきません。

自作命令セットの紹介

そうは言っても、課題プログラムがどのようなものかよく分からない状態で完璧な命令セットを作るというのは無理な話で、私自身完動するまでに命令セットの改良を何度も行いました。
設計の方針としてはまず最初に必要そうな命令を列挙して、オペランドの数や種類を見て形式を分けます。オペランドレジスタ3つの命令とオペランドに即値がある命令は別の形式にしたほうが良さそうだなーといった具合です。そしてそれぞれの形式の中身を決めていきます。後で必要と分かった命令が増えることもあり得るので余裕を持たせておくと良いです。こうして一番最初に作ったフォーマットはこのようなものになりました。 f:id:Risebbit:20201219173043p:plain MSB側の3ビットは命令形式を識別するために使っています。nは即値、r0, r1, r2はレジスタを指定するフィールドです。上述の通りopecodeを左側に1つにまとめ、右側から即値、レジスタを並べて間を0で埋めるというシンプルな構造です。しかしこのフォーマットには重大な欠点がありました。命令の形式が分からないとアクセスすべきレジスタの番号が分からないのです。これは普段ハードウェア設計をしている方ならすぐに気づきそうなことですが、初めてだと気が付きませんでした。レジスタを指定するフィールドを同じ場所に置かなかったのが原因で、これによってレジスタフェッチまでに1クロック多く使ってしまうのです3。気をつけることはこれぐらいで、これさえ守っていれば基本的にはうまくいくと思われます。(というかこれを守らなかったとしても単位を取るには十分なコアができる気がします。)
この後D形式が必要ないことに気がついたり、メモリアクセスをワード(4バイト)単位にしたりといった仕様変更を経て、完動したときのフォーマットはこのようなものになりました。 f:id:Risebbit:20201219180441p:plain レジスタの位置を揃えた点を除いて初期のフォーマットとあまり変化はありません。opecodeを左側に1つにまとめるというコンセプトは初期から全く変わっていません。この分かりやすいフォーマットにより、命令デコーダアセンブラの実装が楽になったと考えています。また、拡張性などを無視して必要最低限の命令が実装できれば良いので、RISC-Vなどに比べて即値により多くのビット数を割り当てることができました。
ちなみに形式ごとのフォーマットを決めたら個々の命令を作るのは簡単でopの部分が重複しないように並べていけば良いです。並べ方によってデコーダの書きやすさが変わってきますが、それは書いてみれば分かります。具体的な命令の例を少し示します。 f:id:Risebbit:20201219182212p:plain

終わりに

ここまで読んでいただきありがとうございました。命令設計がそんなに難しいものではなくむしろ他の実装を楽にしてくれるものであり、ルールを作る喜びを味わえるものであるということが伝われば幸いです。
CPU実験のコア係が作るコアは、FPGAの上で動かすことになるのでFPGAの制約を守る必要があります。逆に言えばそれさえ守っていればどんなに変なアーキテクチャにしても良いということです4。今後オリジナルな命令セットや特殊なアーキテクチャが多く作られることを期待したいですね。
私の班の2ndアーキテクチャはまだ設計中ですが、少し珍しいものになりそうです。成功するかどうかは分かりませんが、失敗しても許されるという学生の特権を存分に利用してCPU実験を最後まで楽しみたいです。


  1. ライズビットと読みます。

  2. これはRISC-Vの例ですが、MIPSも似たようなものだったはずです。

  3. レジスタのフィールドを同じにすれば命令がやってくるのと同じクロックでレジスタ番号を指定できますが、このフォーマットでは命令を見てから次のクロックでレジスタ番号を指定しなければなりません。

  4. 課題プログラムの動作が物理的な不可能なものでない限り。