第1章:PicoRV32 徹底分解
基礎編の最後で、私たちは PicoRV32 というRISC-V CPUコアを自分のFPGAに載せ、走らせました。ただしあのときは、コアを「中身を開けないブラックボックス」として、無改造のまま使いました。命令を取りに行く番地(mem_addr)を外からのぞいてPCの歩みを観察した――そこまでが「さわり」でした。本章では、いよいよその箱を開けます。PicoRV32が中で何をどう作っているのかを、本物のソースコードに沿って分解していきます。
※「分解」と「改造」は違います: 本章でコアの中身を読みますが、コードを書き換えるわけではありません。私たちの方針は一貫して「実績あるコアは無改造で使う」です。中身を理解することと、中身を変えることは別物。分解は、SoCを設計するうえで「この部品は何ができて、どんな足を出していて、どこを設定で変えられるのか」を把握するために行います。改造はしません。
※読む対象(現物の入手): 本章が分解するのは、基礎編第6章と同じ公式リポジトリの picorv32.v です。1つのファイルに約3千行。先頭にライセンス表示があり、ISC形式の寛容なライセンス(著作権者の表示を残せば自由に使える類)で公開されています。著作権者は Claire Xenia Wolf 氏(以前の表記 Clifford Wolf と同一人物)。この「現物でライセンスと作者を確かめる」作業こそ、序章で示したIP調達ワークフローのステップ4そのものです。実際の取得コマンドは第2章(IPとして調達する実演)で示します。
1.1 まず全体像 ― 1ファイルに何が入っているか
箱を開けてまず驚くのは、このファイルにはCPU本体だけでなく、いくつもの「別バージョン」や「部品」が同居していることです。中に定義されているモジュール(部品のかたまり)を並べると、こうなっています。
picorv32.v に入っているモジュール(部品)一覧 ─────────────────────────────────────────────────────────────── picorv32 ← CPU本体(素のメモリI/F版)。本章で分解するのはこれ picorv32_regs ← レジスタファイル(差し替え可能な部品) picorv32_pcpi_mul ← 掛け算器(PCPI接続のオプション部品) picorv32_pcpi_fast_mul← 速い掛け算器(同上) picorv32_pcpi_div ← 割り算器(同上) picorv32_axi ← ★CPU本体に AXI4-Lite の殻をかぶせた版 picorv32_axi_adapter ← 素I/F→AXI4-Lite 変換アダプタ picorv32_wb ← ★CPU本体に Wishbone の殻をかぶせた版
★実践編にとって大きな発見: 一番下の picorv32_wb に注目してください。これは PicoRV32本体に、最初からWishboneバスの殻をかぶせてくれている公式モジュールです。本講座はWishboneを主バスに選びました(序章)。つまり、素のメモリI/FをWishboneへ変換する回路を自分で書かなくても、公式に用意されたものが使える可能性が高い、ということです。同様に picorv32_axi(AXI4-Lite版)もあります。この事実は、第2章でSoCの骨格を組むときの重要な出発点になります。※ただし、picorv32_wb が私たちの用途にそのまま使えるかは、第2章で現物を精読して判断します(いまは「公式の選択肢がある」と確認できた段階)。
本章で分解するのは、その大もとである picorv32 本体です。本体の素性が分かれば、_wb 版も _axi 版も「本体に殻を着せただけ」と理解できます。
1.2 心臓部 ― フェッチ・デコード・実行の「ステートマシン」
基礎編で、CPUは「命令を取る→解読する→実行する」を繰り返すだけだと学びました。PicoRV32は、この繰り返しをステートマシン(状態機械)として実装しています。コアの中には「いま自分はどの作業段階にいるか」を覚えている状態レジスタがあり、決められた順に状態を移っていきます。実物では、状態は8つあります。
PicoRV32 の8つの状態(cpu_state)と、命令の流れ
───────────────────────────────────────────────────────────────
fetch 命令を取ってくる(PCの番地から)
│
ld_rs1 1つ目の入力レジスタ(rs1)を読む
│
ld_rs2 2つ目の入力レジスタ(rs2)を読む
│
exec 計算する(ALUで加減算・比較・論理)
├──→ shift シフト命令なら、ここで何回かに分けて桁をずらす
├──→ ldmem ロード命令なら、メモリから読む
├──→ stmem ストア命令なら、メモリへ書く
│
(結果をレジスタに書き、PCを次へ)→ fetch へ戻る(繰り返し)
─────────────────────────────────────────────────────────────
※ trap …… 不正命令などの異常時に入る状態
ここが、PicoRV32が小さい理由の核心です。高性能なCPUは「フェッチ係・デコード係・実行係」を別々の回路として常駐させ、流れ作業(パイプライン)で同時に走らせます。速い代わりに、回路が大きくなります。PicoRV32はその逆で、1つの計算回路(データパス)を、状態を移しながら何度も使い回します。1命令あたり数クロックかかりますが、回路は驚くほど小さく済みます。
パイプライン型(高性能CPU)
各工程の専用回路を並べ、複数の命令を流れ作業で同時並行に処理。速いが、回路が大きい。
マルチサイクル型(PicoRV32)
1組の回路を、状態を切り替えて順番に使い回す。1命令に数クロックかかるが、回路が小さい。だからFPGAに数%で載る。
基礎編とのつながり: 基礎編で7セグに表示したPCの歩み(0000→0004→0008→000C)が「数秒に1歩」とゆっくりだったのは、分周で遅いクロックを与えたからでした。いま分かるのは、1歩進むのにもコアの中ではfetch→ld_rs1→…と複数の状態をくぐっているということ。あの一歩一歩の裏に、この状態遷移が回っていたのです。
1.3 中身の部品 ― レジスタファイル・ALU・シフタ
状態を移りながら、コアは3つの主要部品を使います。レジスタファイル(計算用の小さな記憶)、ALU(足し算・比較などの計算器)、シフタ(桁をずらす器)です。順に分解します。
レジスタファイル ― 32個の作業机
RISC-Vの計算は、メモリと直接やり取りするのではなく、レジスタという高速な小さな記憶を介して行います。PicoRV32は 32個の32ビットレジスタ(x0〜x31)を持ちます。ただし x0 は常にゼロという約束(RISC-Vの決まり)なので、実際に値を覚える必要があるのは31個です。読み出しは2つ同時(rs1とrs2)、書き込みは1つ――これは、ほとんどの命令が「2つ読んで・計算して・1つ書く」形だからです。
分解で見えた工夫: レジスタファイルは picorv32_regs という独立した部品として切り出されています。なぜ分けてあるのか――FPGAやASICによって「速くて小さいメモリの作り方」が違うため、その部分だけを環境に合わせて差し替えられるようにするためです。これは大きな設計の常套手段で、私たちが第3章以降で周辺IPを「差し替え可能な部品」として作るときの、よいお手本になります。
ALU ― 足し算・比較・論理を1か所で
ALU(演算装置)は、加減算・比較・AND/OR/XORといった計算を引き受けます。実物では、命令の種類に応じて出力を選ぶ「切り替え器」になっています。考え方はこうです。
ALUは「計算結果の候補」を用意し、命令に応じて1つを選ぶ
───────────────────────────────────────────────────────────────
加減算 : instr_sub なら op1 - op2、そうでなければ op1 + op2
比較 : 等しい(eq) / 符号付き小なり(lts) / 符号なし小なり(ltu)
論理 : op1 ^ op2(XOR) / op1 | op2(OR) / op1 & op2(AND)
↓
命令の種類で、上のどれを出力するかを選ぶ(マルチプレクサ)
面白いのは、比較(slt や 分岐の判定)も、本質は引き算だという点です。「a が b より小さいか」は「a − b の符号」で分かります。ALUは加減算器を使い回して比較も行います。1つの回路で何役もこなす――ここにも「小さく作る」思想が表れています。
シフタ ― ここに面積と速度の分かれ道がある
シフタ(<< や >> で桁をずらす器)は、PicoRV32で最も「トレードオフ」が見える部品です。作り方が2通りあり、設定で選べます。
反復シフタ(小さい・遅い)=既定
専用の shift 状態に入り、何回かに分けて少しずつ桁をずらします(実物では4ビットずつ、最後に1ビットずつ)。回路は小さいが、シフト量に応じてクロックを消費します。既定(BARREL_SHIFTER=0)はこちら。
バレルシフタ(速い・大きい)
BARREL_SHIFTER=1 にすると、1回の計算で一気に任意ビット数ずらす専用回路が載ります。速い代わりに、回路(LUT)を多く使います。
これが「徹底分解」の収穫: 同じ「シフトする」機能でも、小さく作るか・速く作るかを設計者が選べる。基礎編第6章では BARREL_SHIFTER(0)(小さい方)を選んでいました。CPUを最小構成で載せたかったからです。実践編では、用途に応じてこの選択を自分で判断します。判断材料(実際にどれだけ回路が増えるか)は、本章末の【確認】で実測します。
1.4 外との約束 ― メモリインターフェースのハンドシェイク
コアは命令もデータも外部のメモリから得ます(基礎編で「メモリ係」を自作したのを思い出してください)。そのやり取りの作法=ハンドシェイクを、いま正確に分解します。使う信号はわずかです。
PicoRV32 の素のメモリインターフェース(握手の信号)
───────────────────────────────────────────────────────────────
CPU → mem_valid 「いま要求があります」(1で要求中)
CPU → mem_instr 「これは命令フェッチです」(1なら命令取得)
CPU → mem_addr 要求する番地(命令フェッチ中は、これがPC)
CPU → mem_wdata 書き込むデータ(書き込み時)
CPU → mem_wstrb どのバイトを書くか(4ビット。読み出し時は0)
メモリ→ mem_ready 「用意できました」(1で応答完了)
メモリ→ mem_rdata 返すデータ(命令またはデータ)
───────────────────────────────────────────────────────────────
流れ: CPUが mem_valid=1 で要求 → メモリが mem_ready=1 で応答
→ CPUが mem_rdata を受け取り、握手成立。次へ進む。
この「要求(valid)を出して、応答(ready)を待つ」という握手は、ハードウェア設計のもっとも基本的で重要なパターンです。速い相手(すぐ ready を返すRAM)にも、遅い相手(時間のかかる周辺機器)にも、同じ作法で繋げます。基礎編で書いた「メモリ係」は、まさにこの mem_valid を見て mem_ready を返す回路でした。いま、その正体が握手プロトコルだと分かります。
※書き込みの粒度(mem_wstrb): mem_wstrb は4ビットで、32ビット語のうちどのバイトを書き換えるかを指定します。バイト書き込み(sb)・ハーフ書き込み(sh)・ワード書き込み(sw)を、この4ビットで表し分けます。読み出しのときは mem_wstrb=0 です。第2章でメモリやMMIOを設計するとき、この粒度を正しく扱うことが鍵になります。
※もう一つの口(Look-Ahead): 実物には、素のI/Fに加えて Look-Ahead(先読み)インターフェース(mem_la_*)も用意されています。これは「次に何を要求するか」を1歩早く外に出すことで、高速なメモリと繋ぐときの待ち時間を減らす仕組みです。本講座では、まず分かりやすい素のI/Fを使い、Look-Aheadは必要になった段で扱います(いまは「速度を詰める口も別にある」とだけ把握)。
★第2章への橋: いま見た「valid を出して ready を待つ」握手は、Wishboneバスの握手(cyc/stb で要求し ack で応答)と、考え方がそっくりです。だから、素のメモリI/FをWishboneに翻訳する picorv32_wb が成立するのです。第2章では、この対応関係を手がかりにバスを組みます。
1.5 設定で姿を変える ― パラメータと面積・性能のトレードオフ
PicoRV32の大きな魅力は、たくさんのパラメータ(設定スイッチ)で、コアの姿を用途に合わせて変えられることです。すべてを「全部入り」にすれば高機能ですが回路は大きくなり、削れば小さく軽くなります。主なスイッチと、その効き目を分解します。
主なパラメータ(設定スイッチ)と、面積・性能への効き目 ─────────────────────────────────────────────────────────────── ENABLE_REGS_16_31 レジスタを32本にするか16本に削るか(命令セットの素性に関わる) BARREL_SHIFTER シフタを「速い・大きい」にするか「小さい・遅い」既定にするか TWO_STAGE_SHIFT 既定の反復シフタを少し速くする(4ビット+1ビットずつ) TWO_CYCLE_ALU ALUを2クロックに分け、動作周波数を上げやすくする(面積↔速度) TWO_CYCLE_COMPARE 比較を2クロックに分ける(同上、タイミング改善向け) COMPRESSED_ISA 圧縮命令(RV32C)対応。コードが小さくなるが、回路は増える ENABLE_MUL / DIV 掛け算・割り算を載せる(PCPI経由のオプション部品を接続) ENABLE_IRQ 割り込みに対応する(周辺機器を活かすなら効いてくる) CATCH_ILLINSN 不正命令を検出してtrapする(安全側。基本は有効が安心) PROGADDR_RESET リセット直後に最初に実行する番地(プログラムの置き場に合わせる)
基礎編第6章でCPUをインスタンス化したとき、私たちは次の設定を与えていました。いま、その一つひとつの意味が読み解けます。
あのとき「オフ」にしたもの
BARREL_SHIFTER(0)=小さい反復シフタ、ENABLE_MUL(0)・ENABLE_DIV(0)=掛け算/割り算なし、COMPRESSED_ISA(0)=圧縮命令なし、ENABLE_IRQ(0)=割り込みなし。すべて「まず最小構成で載せる」ための判断でした。
実践編で「オン」を検討するもの
周辺機器を活かすなら ENABLE_IRQ(割り込み)、計算を増やすなら ENABLE_MUL、コード量が増えてきたら COMPRESSED_ISA。用途に応じて、面積と相談しながら足していきます。
1.6 PicoRV32の素性 ― RV32I という土台
最後に、PicoRV32が話す「言葉」=命令セットを確認します。PicoRV32は RV32I(32ビットRISC-Vの基本整数命令セット)を土台にしています。掛け算・割り算(M拡張)は前述のオプション部品(PCPI経由)で足し、圧縮命令(C拡張)はパラメータで足す――つまり「素のRV32Iを核に、必要な拡張を選んで盛る」構造です。
※先輩CH32V003との違い(素性の比較): 序章で見たCH32V003は RV32EC――レジスタを16本に削ったE(Embedded)に、圧縮Cを載せた素性でした。PicoRV32も ENABLE_REGS_16_31 を0にすれば16本に近づけられますが、本講座は素直にRV32I(32本)を土台にします。なぜなら、レジスタが多いほうがコンパイラが素直なコードを出しやすく、学習にも応用にも見通しが良いからです。命令セットの選択は、後のツールチェーン(コンパイラ)の設定(第11章)に直結します。ここで決めた素性が、ソフト側の足場を決めるわけです。
1.7 補足 ― 「AI協力」で開発するとは、どういうことか
本講座では随所で「AIの協力を得て進める」と述べますが、これはAIが自動でSoCを完成させてくれるという意味ではありません。これからの設計の核心は、「AIによる高速なコード生成」と「人間によるフィジカルな実機検証」を素早く往復させるループにあります。役割は、おおよそ三つに分かれます。
① AIにコードを支援させる
人間(アーキテクト)が仕様と構造を言語化し、AIに Verilog RTL・テストベンチ・制御用Cの雛形を生成させます。ゼロから書く負担を大きく減らせます。ただしそれは静的な予測にすぎず、正しさは次の段階で確かめます。
② シミュ/実機で「裏取り」する
生成したコードを、まず論理シミュレーション(Icarus Verilog 等)で検証し、続いて Vivado で FPGA(Basys 3)に書き込んで実際に動かします。意図した波形が出るか、物理の現実と照らし合わせます。
③ 結果を返して改善する
シミュや実機で出たエラー・タイミング・「動かない」という現象(ログや波形)を、そのままAIに突き返します。AIはその現実のデータを踏まえて修正する――この往復こそが肝です。
思想は人間、実装はAI、最終審判は物理(実機)。 コード生成が得意なAIを助手として使い倒し、人間は「実機で測った数字」を見て設計判断を下す。実際、本講座の専用ビルド工程もこのサイクルで進めています――AIに骨格を書かせ、論理シミュレーションで検証し、確定しきれない値は「要・実機確認」として実機へ送る、その往復です。次節の【確認】から、このループを実際に回し始めます。
1.8 【確認】パラメータを変えて、回路の大きさを実測する
分解で学んだ「面積と性能のトレードオフ」を、机上の話で終わらせず、自分のFPGAで実測します。基礎編で使ったVivadoの利用率レポート(Report Utilization)を、ここで活かします。
- 最小構成で合成: 基礎編第6章の設定(
BARREL_SHIFTER(0)・乗除算オフ)のままpicorv32本体を合成し、LUT・FFの利用数を記録します。 - バレルシフタを入れて比較:
BARREL_SHIFTER(1)に変え、もう一度合成。LUTがどれだけ増えるかを見ます。「速さを買うと、面積をどれだけ払うのか」が数字で見えます。 - 掛け算を入れて比較:
ENABLE_MUL(1)を加えて合成し、増分を記録します。 - 表にまとめる: 構成ごとのLUT/FFを並べ、自分の手元のデータとしてトレードオフを把握します(数値は版・合成条件で変わるので、必ず自分のレポートで確認)。
※この実測が、後の設計判断の土台になります: 「割り込みを足したい」「掛け算がほしい」と思ったとき、それがFPGAの資源をどれだけ食うかを、あなたは自分の数字で知っていることになります。アーキテクト(設計判断をする人)にとって、これは強力な武器です。AIに「どの構成が良い?」と尋ねることはできますが、最終的な取捨は、自分で測った数字を見て自分で決める――序章で掲げた流儀の、最初の実践です。
1.9 この章のまとめ
- 箱を開けた:
picorv32.vにはCPU本体に加え、Wishbone版(picorv32_wb)やAXI版、差し替え可能なレジスタファイルや乗除算器が同居していることを確認した。 - 心臓部を見た: PicoRV32はマルチサイクル型のステートマシン(fetch→ld_rs1→ld_rs2→exec→…)で、1組の回路を使い回すから小さい、と理解した。
- 部品を分解した: レジスタファイル(32本・x0はゼロ)、ALU(加減算を使い回す)、シフタ(小さい反復/速いバレルの選択)を読んだ。
- 外との約束を読んだ: メモリI/Fは「valid→ready」の握手で、これがWishboneにつながることを見た。
- 素性と設定を把握した: 土台はRV32I。パラメータで面積と性能を選べることを知り、その効き目を実測する道筋を立てた。
- AIと実機の往復を知った: AIに骨格(Verilog/C)を生成させ、シミュと実機で裏取りし、その結果で改善する――この往復が本講座の設計の肝だと確認した。
コアの中身が腹に落ちました。次の第2章では、この理解を土台に、PicoRV32を「最初に調達するIP」として迎え入れ、Wishboneバスでまわりに部品を繋いでいく――SoCの骨格づくりに入ります。序章で掲げたIP調達ワークフロー(7ステップ)を、いよいよ実演します。