第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/ に配置する想定です。
- WSL2のターミナルで、プロジェクトの
src/フォルダに移動します。cd ~/my_chip_project/src/
- 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つです。
- リセットは負論理の
resetn(resetではない):
PicoRV32本体のポートはinput clk, resetnです。resetnは0でリセット、1で動作の負論理(アクティブLow)です。.reset(...)という名前の入力は存在しません。Tiny Tapeoutのrst_nも負論理なので、反転せずにそのまま.resetn(rst_n)と接続するのが正解です。 ENABLE_REGS_16_31は「1で32本フル、0で16本」:
このパラメータは「レジスタ x16〜x31 を有効化するか」を指定します。既定値は1(=x16〜x31も使える=レジスタ32本フル)。面積を削るために16本(RV32E相当)にしたいなら0を指定します。「1で16本に削減」という説明は意味が逆なので注意してください。※レジスタを16本に減らすと、標準的なRISC-Vツールチェーンが生成するコード(x16以上を使う)はそのままでは動きません。16本で動かすにはコンパイラ側を
rv32eABIにする必要があります。学習目的では、まず32本フル(既定の1)のまま動かすことを推奨します。- バス信号名は教材どおり:
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_module と source_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.yaml の input_files」と「config.json に DIE_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)の実ポートに基づくものです。