← 講義ポータルへ戻る 第3回・第4回講義:SoCの骨格を組む

第2章:Wishbone SoCの骨格 ― アーキテクチャ設計とIP調達の初実演

第1章で、PicoRV32の中身を分解しました。コアは「外部のメモリと、valid→readyの握手でやり取りする」ことが分かりました。本章では、この理解を土台に、いよいよSoC(System on Chip)の骨格を組みます。骨格とは、「CPUと、いくつもの部品(メモリや周辺機器)を、共通の通り道でつなぐ仕組み」のこと。その共通の通り道がバスです。本章では、なぜバスを揃えるのか、なぜWishboneを選ぶのかを腑に落とし、序章で掲げたIP調達ワークフロー(7ステップ)を、PicoRV32を題材に初めて通して実演します。

2.1 なぜ「共通のバス」に揃えるのか

SoCには、CPUのほかに、メモリ・タイマー・通信・表示……といった部品(IP)が次々に加わります。もし部品ごとに繋ぎ方がバラバラだったら、新しい部品を足すたびに専用の配線を考えなければならず、すぐに破綻します。そこで、「部品はみんな、同じ作法の口を持つ」と決めておきます。これがバスです。

バスがないと(自由配線)

部品ごとに繋ぎ方が違う。1つ足すたびに専用配線を設計。部品が増えるほど、組み合わせが爆発して手に負えなくなる。

バスがあると(共通の口)

全部品が同じ作法の口(バスインターフェース)を持つ。新しい部品も、その口さえ備えればパズルのように差し込める。これがSoCを育てられる理由。

たとえるなら: バスは「電源タップ」のようなものです。コンセントの形(作法)が世界共通だから、新しい家電を買っても、そのまま挿せば動きます。もし家電ごとに差込口の形が違ったら、毎回専用の工事が要ります。SoCのバスは、回路の部品にとっての「共通コンセント」です。

2.2 なぜ Wishbone を選ぶのか ― 主バスの決定

共通バスの「作法」には、いくつかの規格があります。代表的なのが WishboneAXI4-LiteAPB です。本講座は Wishbone を主バスに選びました。その理由を、正直に並べます。

Wishbone(本講座の主役)

オープンソースハードウェアの事実上の共通語。ロイヤリティフリーで、握手が cyc/stb/ack とシンプル。OpenCores由来の既製IPに採用例が多く、「IPをパズルのように繋ぐ」目的に最適。教えやすく、回路も軽い。

AXI4-Lite

業界で広く使われる強力な規格。読み・書きのチャネルが分かれ高性能だが、信号が多く握手が複雑。学習の最初の一歩としては重い。本講座では、AXIのIPを使いたいときにブリッジで取り込む方針(第5章)。

APB

低速な周辺機器向けの簡素な規格。シンプルさは魅力だが、採用IPの広がりではWishboneに一日の長。これもブリッジで取り込めます。

★決め手は「公式の後押し」もあった: 第1章の分解で、picorv32.v の中に picorv32_wb(公式のWishbone殻)が同梱されているのを見つけました。主役のCPUが最初からWishbone対応の口を用意してくれている――これは、Wishboneを主バスに選ぶ強い後押しになります。本章2.4で、この picorv32_wb の中身を現物確認し、採用を確定します。

2.3 【IP調達・初実演】最初のIPは、CPUそのもの

序章で「IPが登場するたびに7ステップの道順を見せる」と約束しました。記念すべき最初のIPは、SoCの主役――PicoRV32(CPUコア)そのものです。基礎編では「公式から取ってきて使う」とだけ書きましたが、ここではなぜそれを選び、どう確かめたかを、7ステップで丁寧にたどります。

   IP調達ワークフロー:PicoRV32 を例に
 ───────────────────────────────────────────────────────────────
   1. 要求を言語化   ・RV32Iの32bit RISC-Vコア
                     ・FPGAに数%で載る小ささ(基礎編で確認済み)
                     ・標準バス(できればWishbone)に繋げる口がある
                     ・組み込み可能なライセンス
   2. 探索先         RISC-V公式の周辺, GitHub("picorv32"), 各種解説・論文
   3. 候補を絞る     PicoRV32 / VexRiscv / Ibex 等。本講座は実績と小ささ・
                     学習しやすさで PicoRV32 を第一候補に
   4. 現物を確認     公式リポジトリの picorv32.v を取得し自分の目で:
                     ・先頭のライセンス=ISC形式(寛容)、著作権者 Claire Xenia Wolf
                     ・picorv32_wb(Wishbone殻)が同梱されている ← 要求3に合致!
   5. 採否を判断     採用。理由=小ささ・実績・Wishbone殻の同梱・寛容ライセンス
   6. 統合           picorv32_wb を SoC のバスマスタとして据える(2.4〜2.6)
   7. 検証           最小SoCで動かし、シミュ→FPGAで確認(2.7〜2.8)

ステップ4で実際に行う取得は、基礎編第6章と同じ一行です(無改造で使うので、取ってきてそのまま読みます)。

# CPUコア本体+各種ラッパ(picorv32_wb 含む)を公式リポジトリから取得(無改造で使用)
curl -O https://raw.githubusercontent.com/YosysHQ/picorv32/master/picorv32.v

※ステップ4が要(AIの提案を鵜呑みにしない): 「PicoRV32にはWishbone殻がある」という情報は、AIや解説記事からも得られます。しかし本講座では、必ず自分でファイルを開いて確かめます。実際、第1章で picorv32_wb モジュールの存在と、その中の握手変換FSMを、現物で確認しました。固有名(モジュール名・作者名・ライセンス)は取り違えが起こり得ます。釣った魚は、自分の目で見てから料理する――これが規律です。

2.4 【現物確認】picorv32_wb の中身 ― 採用を確定する

採用の前に、picorv32_wb が「本当に素直なWishboneマスタか」を中身で確かめます。読んでみると、構造は明快でした。内部でCPU本体(素I/F版)を1個インスタンス化し、その素の握手を、小さな状態機械でWishboneの握手へ翻訳しているだけです。

   picorv32_wb の中の翻訳 FSM(素I/F → Wishbone)
 ───────────────────────────────────────────────────────────────
   IDLE     CPUが mem_valid=1 にしたら、
            番地/データ/書込許可/バイト選択を Wishbone 信号へ写し、
            stb_o=1, cyc_o=1 を立てて要求開始 → WBSTART へ
   WBSTART  スレーブが ack_i=1 を返したら、
            dat_i を mem_rdata に取り込み、mem_ready=1 でCPUへ返す。
            stb_o/cyc_o を下ろして → WBEND へ
   WBEND    後始末(mem_ready を戻す)→ IDLE へ
   ───────────────────────────────────────────────────────────────
   ・we_o(書込)= wstrb のいずれかが立っていれば1
   ・sel_o(バイト選択)= mem_wstrb をそのまま
   ・リセットは wb_rst_i(押す=1の正論理。Basys 3 の btnC にそのまま合う)

第1章で見た「valid→ready」の握手が、ここで「cyc/stback」の握手へ、きれいに置き換わっています。一度に1件ずつ確実に処理する素直な作りで、教材SoCにそのまま使えます採用を確定します。これでCPUは「Wishboneマスタ」として、バスの主人公になりました。

※この章の現物確認の結論(実験ログ): 「素I/FをWishbone化する回路を自作するか、公式の picorv32_wb を使うか」という、序章以来の保留(設計の分岐)に、ここで結論が出ました。公式の picorv32_wb を採用します。自作の薄ブリッジは不要です。実績ある公式実装に乗るほうが、検証の手間も少なく、堅実です。

2.5 Wishbone B4 の最小作法 ― 握手の信号

CPU(マスタ)と部品(スレーブ)が、Wishboneでやり取りする際の信号を、最小限おさえます。難しくありません。第1章の握手に、いくつか名前が増えただけです。

   Wishbone の主な信号(マスタ → スレーブ/スレーブ → マスタ)
 ───────────────────────────────────────────────────────────────
   マスタ → スレーブ
     cyc   「いまバス取引が進行中」(取引のあいだ1に保つ)
     stb   「この相手に有効な要求を出している」(ストローブ)
     adr   要求する番地
     dat_o 書き込むデータ(書き込み時)
     we    書き込みか読み出しか(1=書き込み)
     sel   どのバイトを対象にするか(4ビット)
   スレーブ → マスタ
     ack   「要求を受理し、用意できた」(これで1取引が成立)
     dat_i 返すデータ(読み出し時)
   ───────────────────────────────────────────────────────────────
   1取引の流れ: cyc=1 & stb=1 で要求 → スレーブが ack=1 で応答 → 成立

本講座が使うのは、この「1取引ずつ、確実に」進める素直なやり方(クラシックなWishbone)です。高速化のためにパイプライン化する方式もありますが、まずは分かりやすさを優先します。picorv32_wb も、この素直な方式で作られていました。

2.6 部品を住み分ける ― アドレスデコーダとメモリマップ

バスにはCPU(マスタ)1人に対し、メモリやLED、UARTなど複数の部品(スレーブ)がぶら下がります。CPUが「この番地のデータがほしい」と要求したとき、どの部品が応えるべきかを振り分ける係が要ります。それがアドレスデコーダです。そして、どの番地にどの部品を割り当てるかの一覧表がメモリマップです。

本講座のSoCのメモリマップを、まず設計します(番地の割り当ては設計判断。サイズや具体値は実装で詰めます)。

   本SoCのメモリマップ(設計案。上位ニブル adr[31:28] で住み分け。番地・サイズは実装で確定)
 ───────────────────────────────────────────────────────────────
   0x0000_0000 〜   RAM(本章は命令+データ) ← PROGADDR_RESET をここに置く(第4章でFlash XIPへ移行)
   0x1000_0000 〜   LED(MMIO)              ← まずはここに書いてLEDを点ける
   0x2000_0000 〜   GPIO(第3章で自作)       ← いまは予約(席だけ用意)
   0x3000_0000 〜   タイマ(第3章で自作)     ← いまは予約
        :              :(PWM=0x4 / 外部Flash=0x5 / UART=0x6 … も後章で予約)
   ───────────────────────────────────────────────────────────────
   ※ 上位ニブル(adr[31:28])で「どの部屋か」を見分ける。本章で使うのは 0x0 と 0x1 だけ。

アドレスデコーダの考え方は単純です。CPUが出した番地の上位ビットを見て、「これはRAMの部屋」「これはLEDの部屋」と判断し、その部品にだけ stb を渡します。応答(ack)と読み出しデータ(dat_i)は、選ばれた部品のものをCPUへ返します。骨組みだけ示すと、次のイメージです。

// ※これは骨格イメージ(未検証)。完全な実装は 2.8 のビルド工程で組み立て・検証します。
// 上位ビットで住み分け(例:adr[31:28])
wire sel_ram  = (wbm_adr[31:28] == 4'h0);   // 0x0xxx_xxxx → RAM
wire sel_led  = (wbm_adr[31:28] == 4'h1);   // 0x1xxx_xxxx → LED
// 選ばれた部品にだけ stb を渡す
assign ram_stb = wbm_stb & sel_ram;
assign led_stb = wbm_stb & sel_led;
// 応答(ack)と読み出しデータを、選ばれた部品から集めてCPUへ返す
assign wbm_ack = (sel_ram & ram_ack) | (sel_led & led_ack);
assign wbm_dat_i = sel_ram ? ram_dat : led_dat;

※これは「考え方」を示す骨格です(未検証): 上の断片は、住み分けの発想を伝えるためのスケッチです。実際に動かすには、Wishboneの作法(cycの扱い、複数スレーブの選択、未割り当て番地への応答など)を漏れなく実装し、シミュレーションで確かめる必要があります。完全なコードは、勝手に進めず、2.8で設計合意のうえ一括して組み立て・検証します。本講座の流儀(設計を固めてからコード)どおりに進めます。

2.7 最小SoC を設計する ― CPU+RAM+出力で「バスが通った」を見る

骨格の部品がそろいました。最初のゴールは大げさなものではありません。「CPUが、Wishbone越しに、メモリと出力にちゃんと届く」――この一点を、最小の構成で確かめます。具体的には、(1) Wishboneマスタ=picorv32_wb、(2) プログラムを置くRAM(Wishboneスレーブ)、(3) 出力=LED(MMIO)。この3つをデコーダでつなぎます。

   最小SoC の構成(まず「バスが通る」ことを確かめる)
 ───────────────────────────────────────────────────────────────
   ┌─────────────┐  Wishbone   ┌──────────────┐
   │ picorv32_wb │ ──────────► │ アドレス      │ ──► RAM(0x0000_0000〜)
   │ (CPUマスタ) │ ◄────────── │ デコーダ      │ ──► LED(0x1000_0000)
   └─────────────┘   握手       └──────────────┘
        clk = 100MHz(W5), rst = btnC(U18) → wb_rst_i(押す=1)
   ───────────────────────────────────────────────────────────────
   動作:RAM上の小さなプログラムが、LEDの番地へ書き込む → LEDが点く
        =「CPU→バス→デコーダ→MMIO」の経路が、端から端まで通った証拠

CPU(picorv32_wb)の置き方は、第1章・2.4で読んだポートに素直につなぐだけです。骨格を示します(ポート名は現物のとおり。これも完全な実装は2.8で検証込みに組みます)。

// ※骨格イメージ(未検証)。picorv32_wb を SoC のバスマスタとして置く
picorv32_wb #(
    .BARREL_SHIFTER(0), .COMPRESSED_ISA(0),
    .ENABLE_MUL(0), .ENABLE_DIV(0), .ENABLE_IRQ(0),   // まず最小構成(第1章の判断)
    .PROGADDR_RESET(32'h0000_0000)                    // RAM先頭から実行開始
) cpu (
    .trap      (),
    .wb_clk_i  (clk),          // 100MHz(必要なら分周)
    .wb_rst_i  (rst),          // btnC:押す=1(正論理。そのまま合う)
    .wbm_adr_o (wbm_adr),
    .wbm_dat_o (wbm_dat_o),
    .wbm_dat_i (wbm_dat_i),
    .wbm_we_o  (wbm_we),
    .wbm_sel_o (wbm_sel),
    .wbm_stb_o (wbm_stb),
    .wbm_ack_i (wbm_ack),
    .wbm_cyc_o (wbm_cyc),
    .irq       (32'b0),
    .mem_instr ()
    // PCPI・trace 等は未使用(開けておく)
);

※「Hello相当」をどう見せるか(設計判断・実験ログ): ロードマップでは最小SoCに「UARTでHello相当」と書いていました。ただしUARTは、序章の規律に従えば第5章でIP調達7ステップを通して正式に迎えるのが筋です。そこで本章では、「バスが端まで通った」ことを示す出力として、まずMMIOのLED書き込み(基礎編第6章でやった sw 命令の発展)を使います。文字を送る本物のUARTは第5章で。本章のゴールは「Wishboneの経路が通る」ことに絞り、通信は適切な章へ送ります。この線引きでよいか、最後にご確認ください。

2.8 【確認】最小SoC を、設計合意のうえで組み立て・検証する

ここまでで、最小SoCの設計(バス方式・メモリマップ・各部品の役割・骨格)が固まりました。本講座の流儀は「設計を固めてからコード」。次の一歩は、この設計に基づいて完全なRTLを一括で組み立て、シミュレーションとFPGAで検証することです。検証は、序章で示した段階どおりに進めます。

  1. RTLを一式そろえる: picorv32.v(公式・無改造)/最小SoCトップ(デコーダ+RAMスレーブ+LED)/RAMに置く小さなプログラム(LED番地へ書き込む)。
  2. シミュレーションで確かめる: Verilator か Icarus Verilog で、CPUがRAMから命令を読み、LED番地への書き込みが ack で成立するまでの握手の波形を見ます(第12章で本格化)。
  3. FPGA(Basys 3)で確かめる: 合成・書き込みののち、LEDが点けば「CPU→Wishbone→デコーダ→MMIO」の経路が実機で通った証拠です。
  4. 記録する: 利用率(CPU+バス+RAMでどれだけ使うか)と、つまずいた点を実験ログとして残します。

※完全なコードは、次の一手で: 本章は設計の合意までを担います。完全な最小SoCのRTL(cat一発のフルコード)と、その構文・シミュ確認は、ご了承いただいた次の工程で一括して用意します(勝手に進めず、確認を取ってから)。これが本講座の「実験の並走記録」としての誠実な進め方です。

2.9 この章のまとめ

SoCの骨格ができました。次の第3章では、この骨格に自作の周辺IP(GPIO・タイマ・PWM)を、Wishboneスレーブとして足していきます。「コアは無改造、まわりをMMIOで足す」という基礎編第6章の思想を、いよいよ本格的に展開します。