本記事はiimon Advent Calendar 2025 16日目の記事となります!
こんにちは、iimonでエンジニアをしている須藤です。 RISC-V(リスクファイブ)は「シンプルでエレガント」と言われますが、実感が湧いていなかったので、今回はx86との比較を通じて、シンプルさやその設計思想を体感していこうかと思います。
この記事でわかること
- x86とRISC-Vで同じCコードがどう違うアセンブリになるか
- RISC-Vが「シンプル」と言われる設計特徴
- シンプルさがCPUパイプライン効率につながる仕組み
- 現代のx86がRISC的最適化を取り込んでいる理由
※ 後半に発展的な内容(μOPs、OoO実行など)を含みますが、スキップしても本記事の主旨は理解できます。
- RISC-VとISAの前提知識
- RISC-Vアセンブリの読み方
- RISC-V vs x86:コード比較で見る違い
- 例1. 定数乗算
- 例2. 配列アクセス
- 例3. 即値の扱い
- なぜシンプルを選べたのか
- RISC-Vのシンプルな設計特徴
- シンプルさがパイプラインに効く理由
- RISC-Vの現代的意義
- まとめ:RISC-Vがシンプルな理由
- 最後に
- 参考資料
RISC-VとISAの前提知識
ISA(Instruction Set Architecture、命令セットアーキテクチャ)とは
ISA は、プロセッサが持つ「命令」の種類やレジスタ(CPUが直接使う高速な記憶場所)、アドレッシング方式、 各命令のビット列での表現と意味を定めた仕様であり、 ハードウェアとソフトウェアのあいだのインターフェースとなるものです。
同じ ISA を実装したプロセッサであれば、ハードウェアの世代やメーカーが違っても、同じバイナリプログラムをそのまま動かすことができます。
このおかげで、世代交代の早いハードウェアと、その上で動くソフトウェアの互換性を保ちながら開発を進めることができます。
ISA とアセンブリ・マシンコードの関係イメージ
C などの高級言語
↓(コンパイラ)
アセンブリ言語
↓(アセンブラ)
マシンコード(ISA で定義された命令のビット列表現)
RISC(縮小命令セット)とCISC(複雑命令セット)
CISC(1970年代): メモリが高価だった時代、1命令で複雑な処理を行い、命令数を減らすことでメモリアクセスを削減する設計。
RISC(1980年代): 単純な命令を高速に実行し、パイプライン(流れ作業のように命令を並列処理する仕組み)処理を容易にする設計。複雑な処理はコンパイラに任せる。
RISCの「縮小」は命令数ではなく、1命令が行う作業量を縮小するという意味です。
| CISC | RISC | |
|---|---|---|
| 代表的なISA | x86(Intel, AMD) | RISC-V, ARM, MIPS |
| 命令の特徴 | 多様で複雑な命令・アドレッシングモード | 単純で規則的。レジスタ中心・Load/Store型 |
| 命令長 | 可変長(x86は1〜15バイト) | 固定長(多くは4バイト) |
RISC-V(リスクファイブ)とは
RISC-V は、2010年ごろにカリフォルニア大学バークレー校で開発が始まった、 オープンな命令セットアーキテクチャ(ISA)です。
学術研究で扱いやすいことに加えて、組込み機器からサーバ向けプロセッサまで、 広い実用分野で利用できるように設計されています。
モジュラー設計
RISC-Vは「シンプルなベース+必要な拡張」という設計を採用しています。
| 種類 | 例 | 説明 |
|---|---|---|
| ベース | RV32I, RV64I | 最小限の整数命令(約40命令) |
| 標準拡張 | M(乗除算), F/D(浮動小数点), C(圧縮命令) | 用途に応じて追加 |
RV64GC(= RV64IMAFDC ※)がLinux向けの一般的な構成です。本記事ではベースISAを中心に解説します。 (※ 厳密には Zicsr / Zifencei などの基本拡張も含みます)
RISC-Vアセンブリの読み方
ここでは最低限必要なレジスタと命令の形を押さえておきます。
レジスタ
ISAで定義されたレジスタ(物理レジスタではない)でx0〜x31まであり、以下のような用途で使い分けられます。
| レジスタ | ABI名(呼び出し規約での名前) | 用途 |
|---|---|---|
| x0 | zero | 常に0 |
| x10-x17 | a0-a7 | 引数/戻り値 |
引数や戻り値はa0, a1, a2...で表すことがわかります。コードを読むとき、a0を見れば「第1引数か戻り値だな」と判断できます。
x86との対応表(System V ABI)
| RISC-V | x86 | 用途 |
|---|---|---|
| a0-a7 | rdi, rsi, rdx... | 引数 |
レジスタ幅(64bit環境)
本記事のコード例は64bit環境(x86-64、RV64)を前提としています。
C言語のintは通常32bitのため、コンパイラは32bit演算命令を生成します。
命令の形
RISC-V
| 種類 | 命令例 | 意味 |
|---|---|---|
| 算術 | add a, b, c |
a = b + c |
| 即値算術 | addi a, b, 1 |
a = b + 1 |
| メモリ読み | lw a, 0(b) |
a = メモリ[b] |
3オペランド(操作対象)形式: 出力先, 入力1, 入力2
x86
| 種類 | 命令例 | 意味 |
|---|---|---|
| 移動/即値 | mov a, 100 |
a = 100 |
| メモリ読み | mov a, [b] |
a = メモリ[b] |
2オペランド形式: 出力先, 入力
疑似命令
RISC-Vには「疑似命令」があります。これは複数の機械語命令をまとめた略記で、アセンブラが実際の命令に展開します。
| 疑似命令 | 展開後 | 意味 |
|---|---|---|
li a0, 100 |
addi a0, x0, 100 |
即値をロード |
mv a0, a1 |
addi a0, a1, 0 |
レジスタコピー |
設計の意図:
ISAのシンプルさと、アセンブリの使いやすさをレイヤーを分けて両立させています。
RISC-V vs x86:コード比較で見る違い
同じCコードでも、ターゲットISAによってコンパイラが生成するアセンブリは異なります。 コンパイラはISAが提供する命令の中から選択するため、出力を比較することでISAの設計思想の違いが見えます。
では、実際にC言語をコンパイルしてRISC-Vとx86の違いを見てみましょう。 今回はGodbolt Compiler Explorerを使用しました。 https://godbolt.org/
コンパイラの設定
- x86-64 gcc 15.2
- RISC-V(64-bits) gcc 15.2.0
- オプション -O1(ISAの基礎的な違いを見るため)
※ 以下の比較では、戻り命令(
ret)は両者共通のため省略しています。
例1. 定数乗算
int multiply_by_5(int x) { return x * 5; }
| 行 | RISC-V | x86 |
|---|---|---|
| 1 | slliw a5,a0,2 |
lea eax, [rdi+rdi*4] |
| 2 | addw a0,a5,a0 |
- |
| 計 | 2命令 | 1命令 |
- RISC-V: 左シフト2bit(2進数で2桁ずらす = ×4)→ 加算(+1倍)で ×5 を実現
- x86:
lea(Load Effective Address)はアドレス計算用の回路を算術演算として流用するテクニック。メモリにはアクセスせず、乗算と加算を1命令で実行
例2. 配列アクセス
int get_element(int* arr, int index) { return arr[index]; }
| 行 | RISC-V | x86 |
|---|---|---|
| 1 | slli a1,a1,2 |
movsx rsi, esi |
| 2 | add a0,a0,a1 |
mov eax, DWORD PTR [rdi+rsi*4] |
| 3 | lw a0,0(a0) |
- |
| 計 | 3命令 | 2命令 |
RISC-Vではlw(メモリ読み取り)が他の処理と分離されています。
x86ではmovがアドレス計算とメモリ読み取りを同時に行っています。
例3. 即値の扱い
即値(定数)を扱うとき、小さな値と大きな値で挙動の違いを見ていきましょう。
小さな即値
int get_small() { return 100; }
| 行 | RISC-V | x86 |
|---|---|---|
| 1 | li a0, 100 |
mov eax, 100 |
| 計 | 1命令 | 1命令 |
→ どちらも同じ命令数
大きな即値
int get_big() { return 0x12345678; }
| 行 | RISC-V | x86 |
|---|---|---|
| 1 | li a0, 305418240 |
mov eax, 305419896 |
| 2 | addi a0, a0, 1656 |
- |
| 計 | 2命令 | 1命令 |
ここで出ているliはアセンブラの疑似命令で、実際の機械語はU型(lui)+I型(addi)の2命令に展開されています。
→ RISC-Vは2命令に分かれる
ここまで見ると「x86のほうが命令数が少なくて賢い設計では?」と思えます。 しかしRISC-Vの設計者たちは、意図的にシンプルを選びました。 その理由を見ていきましょう。
※ コードサイズが気になる場合、C拡張(圧縮命令)を使うことでx86と遜色ないレベルになります。
なぜシンプルを選べたのか
1970年代はメモリが高価で遅かったため、命令数を減らすCISCが有利でした。 しかし以下の技術進歩により、シンプルな命令セットが現実的な選択肢になりました:
| 要因 | 効果 |
|---|---|
| メモリの高速化・大容量化 | 命令数増加のペナルティ減少 |
| パワーウォール(クロック限界) | 並列処理で性能向上 → シンプルな命令が有利 |
| コンパイラ最適化向上 | 複雑な処理をシンプルな命令に効率変換 |
| 分岐予測精度向上(95%+) | 条件分岐のペナルティ減少 |
※ 分岐予測精度: Dan Luu, Branch Prediction
また、x86は40年以上の歴史の中で、過去の互換性を維持するために継ぎ足しで拡張されてきました(レガシー・バゲッジ)。対してRISC-Vは「過去のしがらみがない(Clean Slate)」状態で、過去40年のCPU研究の成果を最初から反映できた、という後発の優位性があります。
RISC-Vのシンプルな設計特徴
では、RISC-Vの具体的な設計を見ていきましょう。
命令フォーマット
x86の命令フォーマットを見ると
┌────────┬────────┬────────┬─────┬─────────────┬───────────┐
│ Prefix │ Opcode │ ModR/M │ SIB │Displacement │ Immediate │
│ 0-4B │ 1-3B │ 0-1B │0-1B │ 0-4B │ 0-4B │
└────────┴────────┴────────┴─────┴─────────────┴───────────┘
可変長(1〜15バイト)
出典: Intel 64 and IA-32 Architectures Software Developer's Manual
命令長が可変であることがわかります。
次にRISC-Vのフォーマットを見てみましょう。
フィールドの意味:
- rd: 出力先レジスタ(destination)
- rs1, rs2: 入力レジスタ(source)
- imm: 即値(immediate、定数)
- opcode, funct: 命令の種類を指定
31 25 24 20 19 15 14 12 11 7 6 0
┌──────────┬────────┬────────┬──────┬────────┬────────┐
│ funct7 │ rs2 │ rs1 │ func3│ rd │ opcode │ R型
├──────────┴────────┼────────┼──────┼────────┼────────┤
│ imm[11:0] │ rs1 │ func3│ rd │ opcode │ I型
├──────────┬────────┼────────┼──────┼────────┼────────┤
│ imm │ rs2 │ rs1 │ func3│ imm │ opcode │ S型
│ [11:5] │ │ │ │ [4:0] │ │
├──────────┼────────┼────────┼──────┼────────┼────────┤
│ imm │ rs2 │ rs1 │ func3│ imm │ opcode │ B型
│[12|10:5] │ │ │ │[4:1|11]│ │
├──────────┴────────┴────────┴──────┼────────┼────────┤
│ imm[31:12] │ rd │ opcode │ U型
├───────────────────────────────────┼────────┼────────┤
│ imm[20|10:1|11|19:12] │ rd │ opcode │ J型
└───────────────────────────────────┴────────┴────────┘
7bit 5bit 5bit 3bit 5bit 7bit
出典: RISC-V Instruction Set Manual
- ほとんどの算術命令 → R 型 or I 型
- ロード/ストア → I 型 / S 型
- 分岐 → B 型
- 即値ロード/PC 相対 → U 型 / J 型
RISC-Vのフォーマットには、以下の特徴があります:
- すべて32bit固定
- rs2, rs1, rdの位置と幅が固定
これは「人間(アセンブラ)が読むための美しさ」より「電子回路(マルチプレクサ)の都合」を優先した、極めて合理的な設計となっています。
同様に、即値を持つフォーマットでは、即値の符号ビットが常に命令のbit31に配置されています。どの形式でもbit31は必ず即値の符号ビットです。エレガントですね。
符号拡張(12bitの即値を64bitレジスタに格納する際、符号を維持して埋める処理)では、符号ビットの位置が重要です。RISC-Vでは常にbit31なので、opcodeを解読する前から符号拡張の準備を始められ、処理を並列化できます。
例3で小さな値と大きな値で命令が変わった理由
実は小さな値は12bit以下、大きな値は12bitより大きい値を指定していました。 命令フォーマットの即値のビット幅からわかる通り、I型であるaddiは即値が12bitに制限されます。32bit固定長の命令に32bitの即値は収まらないため、U型(lui)で上位20bitを、I型(addi)で下位12bitを分けて表現しています。 ※ 下位12bitの符号ビットによっては、上位20bitの調整が行われることがあります。 逆にx86は可変なのでどちらの命令も1命令に含められています。 これが、固定長のトレードオフとなっています。
Load/Storeアーキテクチャ
RISC-Vのようにメモリアクセスを専用命令(load/store)に限定する設計をLoad/Storeアーキテクチャといいます。
これにより、算術命令は常にレジスタ間操作となり、デコード(命令解読)回路がシンプルになります。
アドレッシングモード
アドレッシングモードとは、命令がオペランド(操作対象のデータ)の場所を指定する方法です。ここではメモリオペランドの主なアドレッシングモードについて見てみましょう。
x86
x86のアドレッシングモードは [base + index*scale + disp] という形式で、各要素の有無により多様なパターンを表現できます:
[rbx]— baseのみ[rbp-8]— base + disp[rbx+rcx*4]— base + index*scale[rbx+rcx*4+32]— フル形式
例2の mov eax, [rdi+rsi*4] はこれを活用しています。
RISC-V
RISC-Vのアドレッシングモードは base + disp の1種類のみです:
8(a1)— a1 + 8
インデックス付きアクセスが必要な場合は、例2のように事前にアドレス計算を行います。
比較
| 観点 | x86 | RISC-V |
|---|---|---|
| デコード | ModR/M + SIB + 可変 disp 解析 | rs1 と即値を抽出するだけ |
| AGU(アドレス計算回路) | base + index*scale + disp を一発で計算 |
rs1 + imm のみ |
| 依存レジスタ数(典型例) | 配列アクセスだと base と index の 2 本 | lw rd, imm(rs1) は rs1 の 1 本 |
ゼロレジスタ(x0)
RISC-Vにはx0(zero)という常に0を返す特殊なレジスタがあります。 書き込みは無視されます。 一見無駄に見えますが、これにより命令を追加せずに多様な操作を実現できます。
| 疑似命令 | 実際の命令 | ゼロレジスタの役割 |
|---|---|---|
| nop | addi x0, x0, 0 | 何も書き込まない |
| li a0, 100 | addi a0, x0, 100 | 0 + 即値 = 即値ロード |
| neg a0, a1 | sub a0, x0, a1 | 0 - x = 否定 |
| j label | jal x0, label | 戻りアドレス破棄 |
| ret | jalr x0, ra, 0 | 戻りアドレス破棄 |
フラグレジスタと実装の複雑性
※ このセクションは発展的な内容です。「フラグレジスタは暗黙の依存を生み、実装を複雑にする」ということが伝われば十分です。
RISC-Vではx0が常に0の読み取り専用ですが、それ以外のx1~x31はソフトウェア側では戻りアドレスや引数などABIでは役割が決まっていますが、CPUデコーダやALUから見ると特別な配線を必要としません。つまり、ほぼすべてGPR(General Purpose Register、汎用レジスタ)という設計になっています。
x86の特殊レジスタと比較してみましょう。代表的な特殊レジスタとしてRFLAGSがあります。フラグの値にはZF(zero)、SF(最上位ビット)、CF(符号なし桁溢れ)、OF(符号あり桁溢れ)、PF(パリティ検査用)などがあり、これらのフラグは多くの算術命令で暗黙に更新されます(どのビットを読む/書くかは命令ごとに少しずつ異なります)。
ALUの計算結果を暗黙的にグローバルなフラグとして特殊レジスタに書き込むことで、命令数を減らし、追加ハードも用意しなくて良くなり、GPRの節約にもなっているように思えます。
cmp rax, rbx ; rax - rbx → ZF/SF/CF/OF 更新 jl L1 ; SF, OF, ZF などの組み合わせで「a < b (符号付き)」判定
条件分岐時は、計算してフラグ更新→フラグから判定して分岐、という2つの命令に分かれます。ただしこれは演算→フラグ更新→分岐がそのままの順序で流れてくることが前提となります。
それに対しRISC-Vでは、演算ではフラグの更新はせず、分岐命令は比較したいレジスタを明示してその場で比較するという設計を取っています。これにより、フラグのような暗黙のグローバル状態がなくなり、依存関係がシンプルになります。結果が(基本的には)レジスタ間のデータフローとして表現されるようになり、OoO実行(Out-of-Order、命令を効率的な順序に並べ替えて実行)やレジスタリネーミング(論理レジスタから物理レジスタへの動的マッピング)の実装が簡潔になります。
毎回変更されるグローバルなフラグを考慮しながら順番を最適化するのは複雑ですが、入力レジスタと出力レジスタを見て同じレジスタを更新するなら順番を保つというのはシンプルでわかりやすいです。純粋関数(副作用のない関数)のようなイメージに近いでしょう。
現代のx86コアでは、古いISA由来のフラグ周りの負債を緩和するために:
- フラグも内部では多数の「物理フラグレジスタ」としてリネーム管理
- 各命令に「どのフラグビットを読む/書くか」の情報を持たせてpartial writeを合成
- コンパイラ側は、なるべく「フルフラグ更新命令→それを読む命令」という1:1のパターンを使用
- 依存を切るためにゼロイディオムやLEAなどを積極的に使用
- 比較命令と分岐命令を内部的に結合し実質1命令として扱うMacro-fusionを行う
…という形でハードウェアとコンパイラが協調して対処しています。
命令とレジスタの直交性
直交性とは「任意の命令と任意のレジスタを自由に組み合わせられる」性質です。
x86では、乗算(MUL)はRAXレジスタを暗黙的に使用し、ループ命令(LOOP)はRCXを暗黙的に使用します。これは「この命令はこのレジスタでしか使えない」という制約を生みます。
RISC-Vでは、すべての算術命令が任意のレジスタで動作します:
mul a0, a1, a2 # どのレジスタでもOK mul t0, t1, t2 # これもOK
この直交性により、以下のメリットが得られます:
比較と分岐の融合
前章で述べたように、RISC-Vでは比較と分岐を1命令で行います:
blt a0, a1, label # a0 < a1 なら分岐
先祖のMIPSでは2命令必要でした:
slt $t0, $a0, $a1 # t0 = (a0 < a1) ? 1 : 0 bne $t0, $zero, label # t0 != 0 なら分岐
x86のフラグ方式、MIPSの一時レジスタ方式、どちらも採用せず、 比較対象を分岐命令に直接埋め込む設計を選んでいます。
これにより、以下のメリットが得られます:
- 命令数が半減
- 一時レジスタが不要
- 暗黙の依存がない(フラグ方式の問題を回避)
シンプルさがパイプラインに効く理由
ここまでRISC-Vの設計を見てきました。では、このシンプルさはパイプライン処理にどう効くのでしょうか?
パイプライン処理
CPUは命令をパイプラインで処理しています。IF(Instruction Fetch:命令取得)、ID(Instruction Decode:命令解読)、EX(Execute:演算実行)、M(Memory:メモリアクセス)、WB(Write Back:結果書き戻し)の5つのステージに分割できます。
サイクル
1 2 3 4 5 6 7 8
┌────┬────┬────┬────┬────┬────┬────┬────┐
Inst1 │ IF │ ID │ EX │ M │ WB │ │ │ │
├────┼────┼────┼────┼────┼────┼────┼────┤
Inst2 │ │ IF │ ID │ EX │ M │ WB │ │ │
├────┼────┼────┼────┼────┼────┼────┼────┤
Inst3 │ │ │ IF │ ID │ EX │ M │ WB │ │
├────┼────┼────┼────┼────┼────┼────┼────┤
Inst4 │ │ │ │ IF │ ID │ EX │ M │ WB │
└────┴────┴────┴────┴────┴────┴────┴────┘
パイプラインを効率的に走らせるための設計
パイプライン処理を困難にする要因:
- 命令の長さが可変 → 「次の命令はどこから?」が分からずFetchが止まる
- 実行時間がバラバラ → 遅い命令が詰まると後続が待たされる
RISC-Vはこれらを設計で解決しています:
| 問題 | RISC-Vの設計 |
|---|---|
| 命令長が可変 | 32bit固定長 → 次の命令は常にPC+4 |
| 実行時間が不均一 | Load/Storeアーキテクチャ → 算術命令の実行時間を均一に |
パイプラインハザード(止まる原因)
パイプラインが止まる原因(ハザード):
- データ依存: 前の命令の結果を待つ必要がある → フォワーディング(結果を後続命令に先回しで渡す)で軽減
- 制御依存: 分岐先が不明 → 分岐予測で軽減
これらはISA設計では完全に解決できず、ハードウェア技術で対処します。
前の命令をデコードしなくても次の命令の位置がわかるということは、前の命令と無関係に次の命令を読めるということです。これはスーパースカラ実行(1サイクルに複数命令を同時処理)で特に重要です。RISC-VはPC, PC+4, PC+8...と決め打ちで命令を切り出せますが、x86は前の命令の長さを確定させないと次の命令の先頭がわからず、切り出しに直列依存が生じます。
同様に、レジスタ位置が固定されているということは、命令内のopcodeデコードと並列・独立してレジスタ番号を読み始められる(投機的レジスタ読み出し)ということです。
以上のことから、デコードの並列化が可能になります。
現代のCPUから見た優位性
※ このセクションは発展的な内容です。「x86は複雑な最適化で対処している」ということが伝われば十分です。
今でもx86のコンピューターは広く使われており、性能もあまり変わらないのではないかと疑問を持つかもしれません。実際、ISAが直接パフォーマンスに直結するわけではないという研究もあります。
では、x86は後方互換性を維持しながらどうやってRISCのメリットを取り込んだのでしょうか?
そこには最適化の歴史があります。x86は可変長命令やフラグレジスタなど、パイプライン処理が難しい設計を持っています。パイプライン効率を上げるために、RISC的な最適化を取り込む必要がありました。
μOPs(マイクロオペレーション)
x86プロセッサは、複雑なx86命令を内部でμOPsというRISC的な小さな命令に分解して実行しています。
+----------+ +----------+ +----------+ +----------+ | x86 inst | → | Decoder | → | μOPs | → | Execute | +----------+ +----------+ +----------+ +----------+
つまり、外から見るとx86、中身はRISC風、という構造です。
x86の最適化を見ることでRISC-Vの良さを反面的に理解していきましょう。
| 番号 | RISC 側(固定長) | x86 側(可変長+μOPs) |
|---|---|---|
| ① | PC / 分岐予測 | PC / 分岐予測 |
| ② | I-TLB | I-TLB |
| ③ | I-Cache | I-Cache(チャンクフェッチ) |
| ④ | フェッチバッファ & アライン | プリデコード & 長さ判定 + バッファ |
| ⑤ | 固定長 N-wide デコーダ | x86 デコーダ群 + microcode ROM |
| ⑥ | 命令キュー / rename 入口 | μOPs Queue(デコード直後) |
| ⑦ | (そのまま rename へ) | μOPs Cache(Op Cache) |
| ⑧ | rename 入口 | μOPs Queue / rename 入口 |
補足:
- ①②③: ここまでは両者ほぼ同じ
- ④: 固定長 vs 可変長の差が露骨に出る
- ⑤: RISC「命令→制御線」、x86「命令→μOPs群」
- ⑦: μOPs Cache(x86ではデコードの重さを隠すために重要な構造。RISC-Vでは必須ではないが、実装によっては搭載される)
- ⑧: ここからOoOバックエンドへ
RISC-Vの場合、命令が32bit、命令境界がその倍数なので、32バイトフェッチして32bitごとにデコーダに投入できます。つまり、デコーダをN本並べれば、ほぼそのままN-wide(N命令デコード)になりやすい設計です。
x86の場合、命令が可変長でプリフィックスも多数あり、μOPsに変換する必要があります。デコード幅を広げると、理想は1サイクルでN個の命令先頭位置を特定してN本のデコーダに入れたいところですが、32バイトフェッチした後、命令の先頭・プリフィックス・オペコードの幅検出ロジックを経て、やっとどこからどこが1命令か決まります。この候補が多いので、優先度エンコーダ/シフタ/クロスバが必要になり、デコード幅を広げたときに候補、選択、それをつなぐ配線やマルチプレクサが増え、複雑性が増します。またμOPs変換を行う際に出てくるμOPsの数が増えるので、フォーマッタ、キュー、配線も増やす必要が出ます。
そこで登場したのがμOPs Cacheです。複雑なデコーダの並列化には限界があるため、一度デコードしたマイクロオペレーション群をIPから参照できるようにキャッシュし、命令フロントエンド(デコード)をスキップすることで実質的なデコード幅を大きくできるようにしました。デコード幅の代わりに、μOPs Cacheから読み出すポートの並列性を上げています。
結局x86はパフォーマンスを改善できましたが、CISC→RISC変換の複雑性は依然として存在しています。
RISC-Vの現代的意義
オープンソース
- ライセンス料がかからない
- 誰でも拡張を提案・追加できる
- 過去2年間(2024年時点)で40件の技術仕様が批准された
カスタマイズ性
- オペコード空間が予約されている
- custom-0〜custom-3(各1024命令分)が標準拡張と衝突しない設計
- GCC/LLVMの
.insnでツールチェーン修正なしに使用可能
採用事例
- Western Digital: 全製品のコントローラをRISC-Vに移行予定(年間10億コア以上)
- Google: Titan M2セキュリティチップを自社でRISC-V設計
- 2024年時点で130億以上のRISC-Vコアが市場に存在
まとめ:RISC-Vがシンプルな理由
本記事では、RISC-Vとx86を比較しながら、RISC-Vの「シンプルさ」の正体を探ってきました。 Cコードの比較から始まり、命令フォーマットやアドレッシングモードの設計、そしてCPUフロントエンドへの影響まで、一連の流れで見てきました。
学んだこと
| 設計 | 効果 |
|---|---|
| 32bit固定長 | 次の命令位置が常にPC+4、デコードの並列化が可能 |
| Load/Storeアーキテクチャ | 算術命令がレジスタ間操作のみになり、デコードがシンプルに |
| アドレッシングモード1種類 | AGU(アドレス計算回路)が単純、依存レジスタ数が少ない |
| フラグレジスタなし | 暗黙の依存がなくなり、OoO実行やレジスタリネーミングが簡潔に |
シンプルさとレイヤー分離
RISC-Vの「シンプルさ」は単なる機能の制限ではありません。
このレイヤー分離により、「シンプル」と「実用性」を両立させています。
トレードオフ
ベースISAだけではコード密度がx86より低くなりますが、C拡張により改善できます。
現代における意義
x86も内部ではμOPsというRISC的な命令に変換して実行しており、RISC思想の有効性がうかがえます。RISC-Vはこの思想を最初からISAレベルで持ち、さらにオープンソースであることから、組み込みからサーバまで幅広い分野で採用が進んでいます。
最後に
今回はRISC-Vの「シンプルさ」に絞って設計を見てきましたが、ハードウェアが行っている処理を意識できるようになり、知見が広がると感じました。パイプライン面でのパフォーマンス向上、コンパイラの性能向上やCPUの最適化も日々進んでいるので、調べてみると面白そうです。興味を持った方は、RISC-Vの公式ドキュメントや回路図を読んでみても面白いかもしれません。
間違っている内容などありましたら、ご指摘お願いします。
次回はかとうさんです。どんな記事を書くのか楽しみですね。
最後までお付き合いいただきありがとうございます! 弊社ではエンジニアを募集しております。 ご興味がありましたらカジュアル面談も可能ですので、下記リンクより是非ご応募ください!