はじめに
前日の投稿は @takahoyo さんの Naumachiaを使ったペネトレーションテストのトレーニング環境構築 でした。
今回は RISC-V の最も基本的な命令セットである RV32I を実装しようと思います。
RISC-V とは
RISC-V(リスクファイブ)は、UC Berkeley が発表した RISC 命令セットの5番目のメジャーバージョンです。
RISC(Reduced Instruction Set Computer、りすく)は、コンピュータのプロセッサの命令セットアーキテクチャ (ISA) の設計の方向性として、命令セットの複雑さを減らすことすなわち、命令の総数や種類を減らし、それぞれの命令が行う処理を単純なものにし、命令フォーマットの種類を減らし、オペランドのアドレッシングを単純化する、などといった方向性により「命令セットを縮小して」設計されたコンピュータ(プロセッサ)である。この方向性が新しいものとして提案された際、従来のその逆の方向性を指すレトロニムとしてCISCという語が同時に提案された。
(Wikipedia より https://ja.wikipedia.org/wiki/RISC)
仕様がオープンソースとして再利用可能な形で公開されているため、オレオレ RISC-V 実装を楽しむことができます。
(「リバースエンジニアリングで命令セットの解析結果が公開されているやつもあるじゃん!」とか、「そもそも命令セットを自分で作れば良いじゃん!」とかは無しで)
命令セットの考え方などは『RISC-V 原典』という本が詳しいので是非呼んでください。
(末尾に読んだ感想を書きました。)
なぜ RISC-V なのか
最近は RISC-V が色々な文脈で注目を浴びています。
- 中国がアメリカからの制裁を逃れるために独自コア開発に RISC-V を採用した
- RISC-V はライセンスフリーだから他のコアで実装するより安い
- RISC-V をいじってオレオレ命令を組み込んだら速くなった
- RISC-V を実験的な素材で作ってみた
- などなど
というわけで、ひとまず自分でも触ってみようかなと思い立ったのがきっかけです。
RISC-V の特徴:命令の拡張性
RISC-V の特徴として命令の拡張性が挙げられます。
以下のようにいくつかの命令が拡張機能ごとにまとめられており、コアの実装者はベースとなる命令セット(ベース命令セット)と、それ以外に必要となる拡張を選択して実装することで、必要最低限の命令のみを実装することができます。
これにより、実装が単純化するだけでなく、回路面積の減少やそれに伴う動作周波数の向上、省電力化などが期待されます。
表: ベース命令セットの一覧
命令セット名 | 内容 | Frozen |
---|---|---|
RV32I | 32 bit のベース命令セット | Yes |
RV32E | 32 bit の組み込み向けベース命令セット | No |
RV64I | 64 bit のベース命令セット | Yes |
RV128I | 128 bit のベース命令セット | No |
Frozen が Yes のものついては仕様が凍結されており、今後の仕様変更はないものとされます。
表: 拡張命令セットの一覧
拡張名 | 内容 |
---|---|
M | 乗算・除算命令 |
A | アトミック命令 |
F | 浮動小数点 |
D | 倍精度浮動小数点 |
Q | 4倍精度浮動小数点 |
L | 10進数浮動小数点 |
C | 圧縮命令 |
B | ビット操作 |
J | 動的翻訳言語サポート(予約されているだけで実態はない) |
T | トランザクションメモリサポート(予約されているだけで実態はない) |
P | Packed SIMD 命令 |
V | ベクトル演算 |
N | ユーザレベル割り込み |
また、仕様で定義されているこれらの命令の他に、ユーザ独自の命令を追加することも可能です。
RV32I の命令セット
RV32I は RISC-V 命令セットの中で 32 bit 整数の基本的な命令の集合です。
それぞれの命令を表にすると以下のようになります。
また、RV32I は下図のような 32 本の 32 bit レジスタを持ちます。
(図は The RISC-V Instruction Set Manual Volume I: User-Level ISA から引用しています)
実装方針
命令セットの opcode などの実態がわかったので、あとは実装していきます。
以下のような実行ステージを持った CPU を実装していきます。
(今回は簡単のために MMU だとか キャッシュだとか、命令パイプライニングなどはすっ飛ばします)
- Fetch
- Decode
- Exec
- Write back
実装
それぞれの命令ステージごとに回路を実装します
Fetch
プログラムカウンタ (pc) の位置にある命令を命令レジスタに持ってきます。
Decode
デコードでは、前段のフェッチで取得した命令を後段の Exec が解釈できるようにします。
Exec の動作を決定するには、命令の Opcode と Funct を見れば良いので、Opcode ごとにどの命令なのかをパースします。
また、命令ごとに操作に必要なデータもここでパースします。即値以外にもレジスタの値についてもここで取得してしまいます。
Exec
Exec では、前段のデコードの結果(命令の種類と操作に必要な値)をもとに命令を実行します。
Calc, Branch, Shift の3つの回路を、このステージで操作する回路として作成します。
全ての回路に入力を与え、前段のデコードの結果によって出力をマルチプレクサで決定します。
(ソフトウェアエミュレータとして実装する場合は、デコードの結果から switch-case 文などで実行する命令を変化させれば良さそうですね。)
Branch, Load/Store についてはこのステージでは何もせず、次の Write back でレジスタやメモリへの読み書きを行います。
Write back
Register やメモリ、pc などへの書き込みを行う。
キャッシュや MMU なんてものはないので、指定されたところに指定された値を書き込むだけ。
操作が終了するまでは CPU をストールさせます。
参考
RISC-V 命令セットの仕様書
RISC-V 命令セットの仕様書は pdf で公開されています。
The RISC-V Instruction Set Manual Volume I: User-Level ISA
RISC-V のリファレンス実装
RISC-V のリファレンスとなる実装は数多くあります。
RISC-V 原典は面白い!
他のアーキテクチャの歴史を検証しながら「だから RISC-V ではこういう風にしたんです。」というような説明が続きます。
内容はおおよそ150ページほどで、他は各命令の詳細という構成なので、すぐに読み終わると思います。
印象的だったのは、本の冒頭で「RISC-V は優雅だ」というような記述があるのですが、読み終わった時には私も同じ感覚になったことです。
最初見たときは「文学的表現を使うなんて...」と思ったのですが、最終的には私もそういう表現が相応しいなと思いました。