picoRV32 + RAM + UART の最小 SoC を Tang Nano 9K(GW1NR-9)へ実装する流れを、
**学習用の実習ステップとして整理した“完全版カリキュラム”**です。
初心者〜中級者が
「Yosys → nextpnr-gowin → Gowin Programmer」
まで一通り習得できる内容になっています。
? 全体の流れ(Tang Nano 9K 向け)
(1) RTL設計(Verilog)
▼
(2) Yosys 合成(synth_gowin)
▼
(3) nextpnr-gowin 配置配線
▼
(4) gowin_pack で bitstream 生成
▼
(5) Gowin Programmer で書き込み
? 全体を学習する“実習カリキュラム”
? STEP 0:開発環境の準備(実習)
使用ツール
-
Yosys(gowin 対応ビルド)
-
nextpnr-gowin
-
gowin_pack(Gowin SDK 付属)
-
Gowin Programmer
※ Tang Nano 9K(GW1NR-9C)向け。
? STEP 1:SoC(CPU+RAM+UART)の RTL を理解する(実習)
前の回答で作成した SoC:
-
picorv32.v
-
simple_ram.v
-
simple_uart.v
-
soc_top.v
を教材として利用。
実習内容
-
各モジュールの I/O 図を紙に書く
-
バス結線(mem_valid, mem_addr, mem_wstrb など)を理解
-
RAM を 4KB → 8KB に変更してみる
-
UART のアドレスを 0x10000000 → 0x20000000 に変更してみる
? STEP 2:Tang Nano 9K のピン配置を学ぶ(実習)
Tang Nano 9K の主なポイント:
-
50MHz クロックが外部にある
-
UART TX を GPIO ピンへ割り当てる
-
リセットはボタン(または電源 ON リセット)で作る
実習課題:
クロック → soc_top(clk)
UART_TX → ボード上のピン名(例:GPIO0)
RESET → DIPSW / ボタン
Tang Nano 9K のピン名例(例として):
| 信号 | ピン名(例) |
|---|---|
| CLK | 15 |
| UART TX | 25 |
| RESET | BUTTON |
? STEP 3:Tang Nano 9K 用 TOP(FPGA ピン結線)を作成(実習)
src/top_fpga.v
module top_fpga(
input clk_50m,
input reset_btn,
output uart_tx
);
wire reset = reset_btn;
soc_top u_soc(
.clk(clk_50m),
.reset(reset),
.uart_tx(uart_tx)
);
endmodule
? STEP 4:Tang Nano 9K 用 Yosys スクリプト(実習)
run_9k.ys
# --- RTL 読み込み ---
read_verilog src/picorv32.v
read_verilog src/simple_ram.v
read_verilog src/simple_uart.v
read_verilog src/soc_top.v
read_verilog src/top_fpga.v
# --- トップモジュール指定 ---
hierarchy -top top_fpga -check
# --- Gowin 用合成 ---
synth_gowin -top top_fpga -json build/soc_9k.json
合成結果:build/soc_9k.json
? STEP 5:nextpnr-gowin で配置配線(実習)
Tang Nano 9K のデバイスは:
--device GW1NR-9C
nextpnr-gowin \
--json build/soc_9k.json \
--write build/soc_9k_routed.json \
--device GW1NR-9C \
--freq 50
? STEP 6:gowin_pack で bitstream 化(実習)
gowin_pack -d GW1NR-9C \
-o build/soc_9k.fs \
build/soc_9k_routed.json
生成物:soc_9k.fs
? STEP 7:ボード書き込み(実習)
Gowin Programmer でbuild/soc_9k.fs を書き込むだけ。
? STEP 8:UART 動作確認(実習)
UART 出力(UART TX)を USB–シリアルへ接続
PC 側で:
baud: 9600
data: 8bit
stop: 1bit
ファームウェアを書いていない場合は出力無し。
次のステップでファームをロード可能にする。
? STEP 9:ソフトウェア実行方法を学ぶ(実習)
方法 A:RAM に BOOT ROM を内蔵する
simple_ram に初期化ファイルを追加:
$readmemh("firmware.hex", mem);
方法 B:UART ローダーを SoC に追加
もっと高度ですが学習可。
? STEP 10:学習用ミニプロジェクト(実習)
最後に、学習成果として:
⭐ 実習プロジェクト案
-
UART で “Hello World” を表示(RISC-V ソフト)
-
GPIO LED を点滅させる
-
メモリマップ I/O を追加して制御
-
4KB の RAM を 16KB に拡張
-
50MHz 以外のクロック(PLL)を使用
-
SoC に SPI Flash ローダーを追加
これで 「RTL → 合成 → P&R → FPGA 実装 → 動作確認」
まで一連の流れを完全に理解できます。
? 必要なら追加可能
-
Tang Nano 9K 向け RAM 初期化ツール
-
RISC-V 簡易アセンブラテンプレート
-
“Hello World” 用 RISC-V C プロジェクト
-
Yosys / nextpnr の内部構造解説
-
UART ローダーの追加方法
-
Block RAM(SPRAM)を使う構成例
必要なら Tang Nano 9K の具体的なピン配置(pinout)に合わせた .pcf(constraints ファイル) の作成も追加します。
ボード写真 or 使用したいピンを教えてください。
? 1. プロジェクトのフォルダ構成(例)
project/
├── src/
│ ├── picorv32.v
│ ├── simple_ram.v
│ ├── simple_uart.v
│ ├── soc_top.v
│ └── top_fpga.v
├── constr/
│ └── tangnano9k.pcf
├── firmware/
│ ├── firmware.S
│ └── firmware.hex
├── build/
├── run_9k.ys
├── place_9k.sh
└── pack_9k.sh
? 2. RTL データファイル例
? (A) picorv32.v(公式のオリジナル)
これは公式のまま置くだけなので省略
(実際は https://github.com/cliffordwolf/picorv32 にある1ファイル)
? (B) simple_ram.v(4KB RAM)
module simple_ram #(
parameter ADDR_BITS = 12,
parameter DATA_BITS = 32
)(
input clk,
input [ADDR_BITS-1:0] addr,
input [DATA_BITS-1:0] wdata,
input wen,
output reg [DATA_BITS-1:0] rdata
);
reg [DATA_BITS-1:0] mem [(1<<ADDR_BITS)-1:0];
initial begin
$readmemh("firmware/firmware.hex", mem);
end
always @(posedge clk) begin
if (wen)
mem[addr] <= wdata;
rdata <= mem[addr];
end
endmodule
? (C) simple_uart.v(TX 専用)
module simple_uart(
input clk,
input reset,
input [7:0] data,
input send,
output reg tx,
output reg busy
);
localparam DIV = 5208; // 9600bps at 50MHz
reg [12:0] divcnt = 0;
reg [3:0] bitpos = 0;
reg [9:0] shifter = 10'b1111111111;
always @(posedge clk) begin
if (reset) begin
tx <= 1;
busy <= 0;
divcnt <= 0;
bitpos <= 0;
end else begin
if (!busy && send) begin
shifter <= {1'b1, data, 1'b0};
busy <= 1;
bitpos <= 0;
divcnt <= 0;
end else if (busy) begin
if (divcnt == DIV) begin
divcnt <= 0;
tx <= shifter[0];
shifter <= {1'b1, shifter[9:1]};
bitpos <= bitpos + 1;
if (bitpos == 10)
busy <= 0;
end else begin
divcnt <= divcnt + 1;
end
end
end
end
endmodule
? (D) soc_top.v(CPU + RAM + UART 結合)
module soc_top(
input clk,
input reset,
output uart_tx
);
wire mem_valid;
wire mem_instr;
wire mem_ready;
wire [31:0] mem_addr;
wire [31:0] mem_wdata;
wire [3:0] mem_wstrb;
wire [31:0] mem_rdata;
picorv32 #(
.ENABLE_MUL(0),
.ENABLE_DIV(0),
.ENABLE_IRQ(0)
) cpu (
.clk(clk),
.resetn(!reset),
.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)
);
wire ram_sel = (mem_addr[31:12] == 20'h00000);
wire [31:0] ram_rdata;
simple_ram ram (
.clk(clk),
.addr(mem_addr[13:2]),
.wdata(mem_wdata),
.wen(|mem_wstrb & mem_valid & ram_sel),
.rdata(ram_rdata)
);
wire uart_sel = (mem_addr == 32'h1000_0000);
wire uart_busy;
wire uart_send = mem_valid && (|mem_wstrb) && uart_sel;
simple_uart uart(
.clk(clk),
.reset(reset),
.data(mem_wdata[7:0]),
.send(uart_send),
.tx(uart_tx),
.busy(uart_busy)
);
assign mem_rdata =
ram_sel ? ram_rdata :
uart_sel ? {31'b0, uart_busy} :
32'h00000000;
assign mem_ready = mem_valid;
endmodule
? (E) top_fpga.v(Tang Nano 9K ピン結線)
module top_fpga(
input clk_50m,
input reset_btn,
output uart_tx
);
soc_top u_soc(
.clk(clk_50m),
.reset(reset_btn),
.uart_tx(uart_tx)
);
endmodule
? 3. Tang Nano 9K ピン配置ファイル (PCF) — tangnano9k.pcf
※ UART TX を PIN25、50MHz を PIN15 とした例
set_io clk_50m 15
set_io reset_btn 23
set_io uart_tx 25
? 4. Yosys スクリプト(run_9k.ys)
# ---- 読み込み ----
read_verilog src/picorv32.v
read_verilog src/simple_ram.v
read_verilog src/simple_uart.v
read_verilog src/soc_top.v
read_verilog src/top_fpga.v
# ---- トップ指定 ----
hierarchy -top top_fpga -check
# ---- Tang Nano 9K 向け合成 ----
synth_gowin -json build/soc_9k.json -top top_fpga
? 5. nextpnr 実行スクリプト(place_9k.sh)
#!/bin/bash
nextpnr-gowin \
--json build/soc_9k.json \
--write build/soc_9k_routed.json \
--device GW1NR-9C \
--freq 50 \
--cst constr/tangnano9k.pcf
? 6. gowin_pack スクリプト(pack_9k.sh)
#!/bin/bash
gowin_pack -d GW1NR-9C \
-o build/soc_9k.fs \
build/soc_9k_routed.json
? 7. RISC-V “Hello World” ファームウェア
? firmware.S(UART に文字送信)
.section .text
.global _start
_start:
la t0, UART_ADDR
loop:
li t1, 'H'
sb t1, 0(t0)
li t1, 'i'
sb t1, 0(t0)
j loop
UART_ADDR = 0x10000000
? firmware.hex 生成方法
riscv64-unknown-elf-as firmware.S -o firmware.o
riscv64-unknown-elf-objcopy -O verilog firmware.o firmware.hex
? 8. 実行の流れ
yosys run_9k.ys
./place_9k.sh
./pack_9k.sh
Gowin Programmer で build/soc_9k.fs を書き込み
UART に
HiHiHiHi...
が流れ続ければ成功!
? この一式で学べる内容(学習ステップ)
-
RTL(CPU+RAM+UART)構成
-
SoC 上のバス結合
-
Tang Nano 9K へのピン結線
-
Yosys での合成(gowin flow)
-
nextpnr(配置配線)
-
gowin_pack(bitstream 化)
-
FPGA 書き込み
-
RISC-V アセンブリ → HEX → BRAM 初期化
必要であれば:
-
Picorv32 の割込み付き SoC
-
UART RX の追加
-
SPI Flash ブート
-
LED/GPIO マップ
-
Block RAM(SPRAM)使用版
なども追加でテンプレートを作成できます。