SoC

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

を教材として利用。

実習内容

  1. 各モジュールの I/O 図を紙に書く

  2. バス結線(mem_valid, mem_addr, mem_wstrb など)を理解

  3. RAM を 4KB → 8KB に変更してみる

  4. 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:学習用ミニプロジェクト(実習)

最後に、学習成果として:

⭐ 実習プロジェクト案

  1. UART で “Hello World” を表示(RISC-V ソフト)

  2. GPIO LED を点滅させる

  3. メモリマップ I/O を追加して制御

  4. 4KB の RAM を 16KB に拡張

  5. 50MHz 以外のクロック(PLL)を使用

  6. 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...

が流れ続ければ成功!


? この一式で学べる内容(学習ステップ)

  1. RTL(CPU+RAM+UART)構成

  2. SoC 上のバス結合

  3. Tang Nano 9K へのピン結線

  4. Yosys での合成(gowin flow)

  5. nextpnr(配置配線)

  6. gowin_pack(bitstream 化)

  7. FPGA 書き込み

  8. RISC-V アセンブリ → HEX → BRAM 初期化


必要であれば:

  • Picorv32 の割込み付き SoC

  • UART RX の追加

  • SPI Flash ブート

  • LED/GPIO マップ

  • Block RAM(SPRAM)使用版

なども追加でテンプレートを作成できます。