← 講義ポータルへ戻る 第7回〜第10回 実験講義用

第5章:PicoRV32を用いたRISC-Vマクロの統合とSoC化(計算機アーキテクチャ実験対応編)

本章では、前章までの「Lチカ専用の固定回路」から大きくステップアップし、プログラム(ソフトウェア)によって自由に変幻自在な動きをさせることができる「CPU(プロセッサ)コア」をシリコン上に構築します。世界標準のオープンソース命令セットアーキテクチャ(ISA)であるRISC-V(リスク・ファイブ)の軽量コア「PicoRV32」を扱い、初学者が直面するRTL統合の罠と、LibreLaneでの配置配線パラメータ制御を学びます。

※本章は現行版(ttsky25b / LibreLane 3.0.3)に準拠し、かつPicoRV32公式リポジトリ(YosysHQ/picorv32)の実際のポート定義に合わせて全面改訂しています。 旧版にあったポート名・パラメータ・多重ドライブの誤りを修正済みです(詳細は各所の注記)。

5.1 【理論】計算機アーキテクチャとメモリマップドI/O(MMIO)

CPUは単体ではただ計算をするだけの箱です。外部の物理LEDを光らせたり、スイッチの入力を読み取ったりするには、周辺回路とCPUを接続する道路であるバス(Bus)や、メモリマップドI/O(MMIO)という仕組みが必須です。

CPUは、メモリ(RAM/ROM)へのアクセスと、外部の周辺機器へのアクセスを区別しません。特定の「番地(アドレス:本実習では 0x4000_0000)」を狙ってデータを書き込む命令(ストア命令)を発行すると、アドレスデコーダがそれを検知して、物理的な出力ピンのレジスタにデータをラッチ(保存)するように回路を組みます。

5.2 【Step 1】PicoRV32 CPUコア本体の取得

まずは、オープンソースで公開されている実績豊富なCPUコアのソースコードを取得します。新規プロジェクトのテンプレート(ttsky-verilog-template)の src/ に配置する想定です。

  1. WSL2のターミナルで、プロジェクトの src/ フォルダに移動します。
    cd ~/my_chip_project/src/
  2. GitHubの公式リポジトリから、PicoRV32の本体Verilogファイル(picorv32.v)を curl コマンドで直接取得します。
    curl -O https://raw.githubusercontent.com/YosysHQ/picorv32/master/picorv32.v

5.3 【最重要】PicoRV32を統合する際に必ず押さえる「3つの事実」

PicoRV32を自作SoCトップに組み込むとき、公式の picorv32.v のポート定義・パラメータ定義を正しく理解していないと、合成は通っても全く動かない、あるいは合成エラーになります。実際のソース(YosysHQ/picorv32)に基づく要点は次の3つです。

  1. リセットは負論理の resetnreset ではない):
    PicoRV32本体のポートは input clk, resetn です。resetn0でリセット、1で動作の負論理(アクティブLow)です。.reset(...) という名前の入力は存在しません。Tiny Tapeoutの rst_n も負論理なので、反転せずにそのまま .resetn(rst_n) と接続するのが正解です。
  2. ENABLE_REGS_16_31 は「1で32本フル、0で16本」:
    このパラメータは「レジスタ x16〜x31 を有効化するか」を指定します。既定値は 1(=x16〜x31も使える=レジスタ32本フル)。面積を削るために16本(RV32E相当)にしたいなら 0 を指定します。「1で16本に削減」という説明は意味が逆なので注意してください。

    ※レジスタを16本に減らすと、標準的なRISC-Vツールチェーンが生成するコード(x16以上を使う)はそのままでは動きません。16本で動かすにはコンパイラ側を rv32e ABIにする必要があります。学習目的では、まず32本フル(既定の 1)のまま動かすことを推奨します。

  3. バス信号名は教材どおり:
    mem_valid / mem_instr / mem_ready / mem_addr / mem_wdata / mem_wstrb / mem_rdata はPicoRV32の実ポート名と一致しています。trap 出力は未使用なら空接続(.trap())で構いません。

5.4 【Step 2】Tiny Tapeout用RISC-VミニマムSoCトップ回路の記述

PicoRV32をTiny Tapeoutの極小領域に収めるため、「乗算器・除算器ハードウェアの排除」などでエリアを節約したSoCトップ回路(src/tt_um_riscv_soc.v)を作成します。前節の3つの事実を反映した、そのまま合成・動作する正しい記述を示します。

※旧版からの修正点:(1) .reset(reset).resetn(rst_n)(負論理を直結、内部の wire reset = !rst_n; は不要)、(2) ENABLE_REGS_16_31(1) のコメント「16本に削減」は誤りなので、ここではレジスタ32本フル(既定)で記述、(3) uo_out への多重代入(multiple driver)を解消。旧版の assign uo_out = led_reg;assign uo_out[7:1] = 7'b0; の併記は、同じ信号を2回駆動するためエラー(または一方が無視)になります。下記のように1本のassignに統合します。

`default_nettype none

module tt_um_riscv_soc (
    input  wire [7:0] ui_in,    // 汎用入力ピン
    output wire [7:0] uo_out,   // 汎用出力ピン (uo_out[0]にLEDを接続)
    input  wire [7:0] uio_in,   // 双方向ピン入力
    output wire [7:0] uio_out,  // 双方向ピン出力
    output wire [7:0] uio_oe,   // 双方向ピン出力有効化
    input  wire       ena,      // チップ有効化
    input  wire       clk,      // クロック (50MHz)
    input  wire       rst_n     // リセット (低アクティブ:0でリセット)
);

    // PicoRV32 ネイティブ・バス信号の定義
    wire        mem_valid;
    wire        mem_instr;
    reg         mem_ready;
    wire [31:0] mem_addr;
    wire [31:0] mem_wdata;
    wire [3:0]  mem_wstrb;
    reg  [31:0] mem_rdata;

    reg [7:0] led_reg; // LED状態保持用MMIOレジスタ

    // 【PicoRV32 CPU コアのインスタンス化】
    // 注意:PicoRV32のリセットは負論理 resetn。Tiny Tapeoutの rst_n をそのまま接続する。
    picorv32 #(
        .ENABLE_COUNTERS(0),
        .ENABLE_COUNTERS64(0),
        .ENABLE_REGS_16_31(1), // 1 = レジスタ x16〜x31 も有効(=32本フル)。学習用は既定の1のままが安全
        .TWO_STAGE_SHIFT(1),
        .BARREL_SHIFTER(0),
        .COMPRESSED_ISA(0),
        .ENABLE_MUL(0),        // 乗算器を排除(エリア節約)
        .ENABLE_FAST_MUL(0),
        .ENABLE_DIV(0),        // 除算器を排除(エリア節約)
        .ENABLE_IRQ(0)
    ) cpu_core (
        .clk         (clk),
        .resetn      (rst_n),  // ★負論理を直結(反転しない)
        .trap        (),       // 未使用:空接続
        .mem_valid   (mem_valid),
        .mem_instr   (mem_instr),
        .mem_ready   (mem_ready),
        .mem_addr    (mem_addr),
        .mem_wdata   (mem_wdata),
        .mem_wstrb   (mem_wstrb),
        .mem_rdata   (mem_rdata)
    );

    // 【バス・アドレスデコーダおよびレジスタ制御ロジック】
    // rst_n は負論理なので !rst_n でリセット判定する
    always @(posedge clk) begin
        if (!rst_n) begin
            led_reg   <= 8'b0;
            mem_ready <= 1'b0;
            mem_rdata <= 32'b0;
        end else begin
            mem_ready <= 1'b0; 
            
            if (mem_valid && !mem_ready) begin
                // アドレス判定:CPUが「0x4000_0000番地」にアクセスしてきたか?
                if (mem_addr == 32'h4000_0000) begin
                    if (mem_wstrb[0]) led_reg <= mem_wdata[7:0];
                    mem_ready <= 1'b1; // アクセス完了をCPUに通知
                end else begin
                    // それ以外の番地は NOP 命令を返すダミー応答(学習用の最小構成)
                    mem_rdata <= 32'h0000_0013; // RISC-Vの NOP命令 (addi x0, x0, 0)
                    mem_ready <= 1'b1;
                end
            end
        end
    end

    // 物理ピンとのマッピング(多重ドライブを避け、1本のassignに統合)
    // LEDは led_reg[0] を uo_out[0] に出し、残りのビットは 0 に固定
    assign uo_out  = {7'b0, led_reg[0]};
    assign uio_out = 8'b0;
    assign uio_oe  = 8'b0;

    // 未使用入力を明示的に捨てて lint 警告を抑制(任意)
    wire _unused = &{ena, ui_in, uio_in, mem_addr[31:2], 1'b0};

endmodule

※この最小SoCの位置づけ: 上記は「PicoRV32が正しくリセット解除され、0x4000_0000 へのストアでLEDレジスタが書き換わる」ことを確認するための最小構成です。実際にプログラムを動かすには命令メモリ(ROM/RAM)の実装とリセットベクタ(PROGADDR_RESET)の設定が必要です。ここではまず合成→配置配線→物理検証のフロー全体をCPU規模の回路で通すことを目標にします。メモリ実装は発展課題(第8章方向)とします。

5.5 【Step 3】プロジェクト設定:info.yaml と LibreLane設定の役割分担

ここは初学者が最も混乱する箇所です。Tiny Tapeoutのプロジェクトでは、設定ファイルが役割の異なる2種類に分かれています。どちらに何を書くかを正しく理解してください。

【重要】2種類の設定ファイルの役割分担

① プロジェクト記述ファイル(info.yaml

「このプロジェクトのトップモジュール名は何か」「どのVerilogファイルを使うか」といったプロジェクトのメタ情報を書きます。具体的には top_modulesource_files です。

② LibreLane設定(ユーザーコンフィグ)

DIE_AREA(チップ面積)、PL_TARGET_DENSITY_PCT(セル配置密度)、CLOCK_PERIOD(クロック周期)といった物理設計(配置配線)のパラメータを書きます。これは ./tt/tt_tool.py --create-user-config を実行すると生成されるLibreLane設定ファイルで指定します。

※注意: テンプレートの世代によって、プロジェクト設定ファイルのキー名・ファイル形式(info.yaml / config.yaml / config.json)に差があります。必ず自分が使っている ttsky-verilog-template に同梱された設定ファイルの実物を開き、そこにあるキー名に合わせて編集してください。 下記は ttsky 系テンプレートで一般的な info.yaml の例です。

info.yaml(プロジェクト記述)の例。RISC-VコアはVerilogファイルが2つになる点に注意します。

# info.yaml(プロジェクトのメタ情報)
project:
  title:        "RISC-V Mini SoC"
  description:  "PicoRV32-based minimal SoC for Tiny Tapeout"
  top_module:   "tt_um_riscv_soc"
  source_files:
    - "tt_um_riscv_soc.v"
    - "picorv32.v"          # ★CPU本体も必ず列挙する(2ファイル)

※旧版では「info.yamlinput_files」と「config.jsonDIE_AREA を追記」という記述が混在し、どちらに何を書くか不明瞭でした。現行では上記のようにキー名は source_files / top_module(テンプレート実物に従う)、物理パラメータはLibreLaneのユーザーコンフィグ側、と切り分けます。

5.6 【Step 4】配置配線パラメータの設定とハードニング実行

CPUのように高密度な回路は、初期設定のままだと配線エリアがパンクして Congestion エラーになります。チップ面積とセル密度を調整して、配線の隙間を確保します。

まず、LibreLane用の設定ファイルを生成します。

cd ~/my_chip_project
./tt/tt_tool.py --create-user-config

生成されたLibreLableのユーザーコンフィグに、面積と密度のパラメータを設定します。Tiny Tapeoutでは設計を「タイル数」で広げる仕組みもありますが、ここでは面積と密度の考え方を示します。

【重要パラメータ】面積とセル配置密度の考え方

チップ面積(DIE_AREA)は回路規模に応じて広げます。Lチカでは小さくて済みますが、PicoRV32は数百〜数千セル規模になるため、十分な面積が必要です。

// LibreLaneユーザーコンフィグ(JSON形式の例)
"DIE_AREA": "0 0 320 300",        // 規模に応じて面積を調整(単位: um)
"PL_TARGET_DENSITY_PCT": 45       // セル配置密度の目標(%)。45〜48%程度に下げると配線の隙間を確保しやすい

※密度パラメータのキー名はLibreLane(OpenLane 2系)では PL_TARGET_DENSITY_PCT(パーセント指定)です。旧OpenLane 1系の PL_TARGET_DENSITY(0〜1の小数指定)とは名前・単位が異なります。使用中のLibreLaneのドキュメントで現行キー名を確認してください。

※Tiny Tapeoutでは DIE_AREA を直接指定せず「タイル数」で面積を決める設定方式の場合があります。テンプレート実物の設定に従ってください。

設定が完了したら、ハードニングを実行します。

# 設定を変えたら必ず再生成してからハードニング
./tt/tt_tool.py --create-user-config
./tt/tt_tool.py --harden

# 合成・クロック警告のチェック(推奨)
./tt/tt_tool.py --print-warnings

裏側でYosys(合成)とOpenROAD(配置配線)が連携し、CPUのレイアウトデータが生成されます。成果物は runs/wokwi/final/ 以下に出力されます。完了したら次章(第6章)へ進み、サインオフ物理検証を行いましょう。

※参考: オープンソースEDAによるRISC-V実装やセルライブラリ研究については、第8章でオープンソースのセルライブラリ自動構築研究(libretto等)を参考文献として正式に紹介します。本章のコードはPicoRV32公式(YosysHQ/picorv32)の実ポートに基づくものです。