今さらですが Tang Nano 9K を触ります。
Interface のおかげで全人類 FPGA に入門しているので、この記事では IDE を使わずに全て CLI だけで開発します。
Tang Nano 9K は Gowin GW1NR-LV9QN88PC6/I5 を載せた小さな FPGA ボードです。 8640 LUT4、27 MHz クロック、32 Mbit SPI Flash、HDMI、RGB LCD、SPI LCD、USB-JTAG / USB-UART が載っています。 小さいのにちゃんと遊べる。えらい。
全体の流れ
IDE を使わない場合、だいたい次の流れになります。
| 段階 | ツール | 入出力 |
|---|---|---|
| 合成 | Yosys | *.v → top.json |
| 配置配線 | nextpnr-himbaechel | top.json + *.cst → pnr.json |
| パック | apycula (gowin_pack) | pnr.json → top.fs |
| 書き込み | openFPGALoader | top.fs → FPGA SRAM / Flash |
Gowin FPGA のオープンソース環境は、昔の記事だと nextpnr-gowin と書かれていることがあります。
最近は nextpnr-himbaechel を使うのが本筋っぽいです。
古い記事をコピペすると、デバイス名や family 指定でハマります。
Tang Nano 9K で使う値は次です。
| 項目 | 値 |
|---|---|
| FPGA part | GW1NR-LV9QN88PC6/I5 |
| Gowin family | GW1N-9C |
| openFPGALoader board | tangnano9k |
| パッキング時の device option | GW1N-9C |
いちばん楽な方法
いちばん楽なのは OSS CAD Suite を使う方法です。 Yosys、nextpnr、openFPGALoader などがまとまっています。
tar xf oss-cad-suite-linux-x64-*.tgz
source oss-cad-suite/environment
yosys -V
nextpnr-himbaechel --version
gowin_pack --help
openFPGALoader --version
Docker すら動かすのが面倒な現代っ子なので、まずはこれでよいです。 ただし、ツールの中身を追いたい、最新の Gowin 対応を踏みたい、CI に載せたい、という場合は自分でビルドします。
Yosys
なんでも合成してくれる Yosys 先生。
公式 README では、サブモジュール込みで clone することが推奨されています。
git clone --recurse-submodules https://github.com/YosysHQ/yosys.git
cd yosys
sudo apt update
sudo apt install gawk git make python3 lld bison clang flex \
libffi-dev libfl-dev libreadline-dev pkg-config tcl-dev zlib1g-dev \
graphviz xdot
make config-clang
make
sudo make install
Verilog から Gowin 用の JSON ネットリストを作るには synth_gowin を使います。
yosys -p "read_verilog src/top.v; synth_gowin -top top -json build/top.json"
SystemVerilog を使いたい場合は、対応状況に注意します。 Yosys 標準の Verilog フロントエンドだけで行くなら、まずは Verilog-2005 ぐらいに寄せた方が平和です。
apycula
apycula は Gowin FPGA のビットストリーム形式やチップデータベースを扱うプロジェクトです。
gowin_pack や gowin_bba が入ります。
python3 -m pip install --user apycula
export PATH="$HOME/.local/bin:$PATH"
gowin_pack で .fs ファイルを作ります。
gowin_pack -d GW1N-9C -o build/top.fs build/pnr.json
-d GW1N-9C が Tang Nano 9K の family 指定です。
ここを古い Tang Nano 用の GW1N-1 にすると、当然ながら違うチップ向けのパックになります。
コピペは敵。
nextpnr
これまたおなじみ、なんでも配置配線 (Place and Route) してくれる nextpnr 先生。
Gowin では himbaechel アーキテクチャを有効にしてビルドします。
git clone https://github.com/YosysHQ/nextpnr.git
cd nextpnr
cmake -S . -B build \
-DARCH=himbaechel \
-DHIMBAECHEL_UARCH=gowin
cmake --build build -j"$(nproc)"
sudo cmake --install build
もし gowin_bba が見つからないと言われたら、apycula を入れたパスが通っているか確認します。
which gowin_bba
export PATH="$HOME/.local/bin:$PATH"
Tang Nano 9K の配置配線はこうです。
nextpnr-himbaechel \
--json build/top.json \
--write build/pnr.json \
--device GW1NR-LV9QN88PC6/I5 \
--vopt family=GW1N-9C \
--vopt cst=tangnano9k.cst
.cst はピン配置ファイルです。
トップモジュールのポートを、FPGA の物理ピンに対応させます。
LED を 1 個だけ使うなら、雰囲気はこんな感じです。 実際のピン名は Sipeed のピンマップやサンプルを確認してください。
IO_LOC "led" 10;
IO_PORT "led" IO_TYPE=LVCMOS33;
未割り当てのトップレベルポートがあると nextpnr が怒ります。 怒ってくれるのは助かる。 未接続のピンが勝手に生える方が怖いです。
openFPGALoader
なんでも書き込みしてくれる openFPGALoader 先生。
git clone https://github.com/trabucayre/openFPGALoader.git
cd openFPGALoader
mkdir build
cd build
cmake ..
cmake --build . -j"$(nproc)"
sudo make install
Linux では udev ルールを入れないと一般ユーザから USB-JTAG を触れないことがあります。
sudo cp 99-openfpgaloader.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger
sudo usermod -a "$USER" -G plugdev
このあと一度ログアウト・ログインします。
それが面倒なら、とりあえず sudo openFPGALoader ... で動作確認してもよいです。
接続確認はこれです。
openFPGALoader --detect
SRAM に書くなら -m を付けます。
電源を切ると消えますが、開発中はこれで十分です。
openFPGALoader -m -b tangnano9k build/top.fs
Flash に書くなら -f です。
電源を入れ直しても残ります。
openFPGALoader -f -b tangnano9k build/top.fs
公式ドキュメントでは Gowin の .fs は SRAM と Flash で同じファイルを使い、オプションで書き込み先を変える、という扱いです。
Tang Nano 9K の board name は tangnano9k です。
WSL
WSL2 で USB を使うには、USB デバイスを接続する の手順に従います。
PowerShell を admin で開き、
usbipd wsl list
usbipd wsl attach --busid <BUS-ID>
WSL2 側で lsusb して BL702 っぽいデバイスが見えれば認識されています。
lsusb
openFPGALoader --detect
ここでエラーが出たので、WSL-support に従ってコマンドをたたいたら、いけました。 USB 周りは環境差が大きいです。 動かないときは、Windows 側でデバイスを掴んでいないか、WSL に attach できているか、udev ルールが効いているかを順に見ます。
Windows
Windows で手早く書き込むだけなら MSYS2 を使えます。
- MSYS2 をインストール
- UCRT x64 環境を起動
- openFPGALoader をインストール
pacman -S mingw-w64-ucrt-x86_64-openFPGALoader
あとは同じように実行します。
openFPGALoader -b tangnano9k build/top.fs
合成から全部 Windows ネイティブでやるより、WSL2 + USB passthrough か、OSS CAD Suite を展開して使う方が楽だと思います。
Makefile
最小構成の Makefile はこんな感じです。
TOP := top
SRC := src/top.v
CST := tangnano9k.cst
BUILD := build
DEVICE := GW1NR-LV9QN88PC6/I5
FAMILY := GW1N-9C
BOARD := tangnano9k
.PHONY: all clean load flash
all: $(BUILD)/$(TOP).fs
$(BUILD):
mkdir -p $(BUILD)
$(BUILD)/$(TOP).json: $(SRC) | $(BUILD)
yosys -p "read_verilog $(SRC); synth_gowin -top $(TOP) -json $@"
$(BUILD)/pnr.json: $(BUILD)/$(TOP).json $(CST)
nextpnr-himbaechel \
--json $< \
--write $@ \
--device $(DEVICE) \
--vopt family=$(FAMILY) \
--vopt cst=$(CST)
$(BUILD)/$(TOP).fs: $(BUILD)/pnr.json
gowin_pack -d $(FAMILY) -o $@ $<
load: $(BUILD)/$(TOP).fs
openFPGALoader -m -b $(BOARD) $<
flash: $(BUILD)/$(TOP).fs
openFPGALoader -f -b $(BOARD) $<
clean:
rm -rf $(BUILD)
開発中は make load、完成したら make flash です。
SRAM 書き込みは速いので、L チカを壊しては直す遊びに向いています。
ファイル構成はこうしておくと見通しがいいです。
.
├── Makefile
├── tangnano9k.cst
└── src
└── top.v
src/top.v の最小例です。
27 MHz クロックを分周して LED を点滅させます。
module top(
input wire clk,
output wire led
);
reg [24:0] counter = 0;
always @(posedge clk) begin
counter <= counter + 1'b1;
end
assign led = counter[24];
endmodule
この例では clk と led を .cst に割り当てる必要があります。
ピン番号は基板の版や使う LED によって変わりうるので、Sipeed のピンマップやサンプルプロジェクトを見てください。
ここで適当を書くと、物理世界が爆発します。
Docker 化
というわけで、積み上げた開発環境を爆破し更地にして、Docker 化します。
コマンドたちがさえずる綺麗な環境を守りたい。。。
ただし、USB-JTAG で実機に書き込むところまで Docker に閉じ込めると少し面倒です。 おすすめは、合成・配置配線・パックまでは Docker、書き込みだけホストの openFPGALoader です。
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
clang \
cmake \
curl \
flex \
bison \
gawk \
git \
lld \
make \
ninja-build \
pkg-config \
python3 \
python3-pip \
python3-venv \
tcl-dev \
libffi-dev \
libfl-dev \
libreadline-dev \
zlib1g-dev \
graphviz \
xdot \
&& rm -rf /var/lib/apt/lists/*
RUN git clone --recurse-submodules https://github.com/YosysHQ/yosys.git /opt/yosys \
&& cd /opt/yosys \
&& make config-clang \
&& make -j"$(nproc)" \
&& make install
RUN python3 -m venv /opt/venv \
&& /opt/venv/bin/pip install --upgrade pip \
&& /opt/venv/bin/pip install apycula
ENV PATH="/opt/venv/bin:${PATH}"
RUN git clone https://github.com/YosysHQ/nextpnr.git /opt/nextpnr \
&& cd /opt/nextpnr \
&& cmake -S . -B build -GNinja \
-DARCH=himbaechel \
-DHIMBAECHEL_UARCH=gowin \
&& cmake --build build -j"$(nproc)" \
&& cmake --install build
WORKDIR /work
ビルドします。
docker build -t tangnano9k-toolchain .
プロジェクトをマウントして make します。
docker run --rm -it \
-v "$PWD":/work \
tangnano9k-toolchain \
make
できた build/top.fs をホスト側で書き込みます。
openFPGALoader -m -b tangnano9k build/top.fs
Docker から直接書き込みたい場合は、USB デバイスを渡します。
docker run --rm -it \
--device /dev/bus/usb \
-v "$PWD":/work \
tangnano9k-toolchain \
openFPGALoader -m -b tangnano9k build/top.fs
Linux では権限の都合で --privileged が必要になることもあります。
でも個人的には、書き込みはホストでやる方がトラブルが少ないです。