← ブログ一覧へ
FPGA高云Tang NanoGowinGW1NR-9Capiculanextpnr国产FPGA开源工具PSRAM

开源 FPGA 07|国产 FPGA 上手:高云 Tang Nano 9K 开源工具链全流程

この記事は中国語で書かれ、Google 翻訳で自動翻訳されています。
中国語の原文を見る →
  _____ _    _  __   __ ___ _  _   
 |_   _/_\  | \| |  / _| _ \ \| |  
   | |/ _ \ | .` | | (_ |   / .` |  
   |_/_/ \_\|_|\_|  \___|_|_\_|\_|  

   国产 FPGA 走上开源工具链舞台

系列第 07 篇 · 目标器件:高云半导体 GW1NR-9C(Tang Nano 9K) 工具版本:Yosys 0.40 / nextpnr-gowin 0.7 / apicula 0.8.0 / openFPGALoader 0.12 上一篇:ECP5 + ULX3S:HDMI + SD 卡 + ESP32


0. 这一篇要解决什么问题

前几篇用的都是 Lattice 的芯片(iCE40、ECP5)。但如果你在国内做硬件,Lattice 的芯片价格偏高、供货周期长,有时还有进口合规的麻烦。高云(Gowin) 这几年在国内市场份额快速增长,Tang Nano 系列开发板便宜(9K 版本约 ¥60-80),而且已经有了相对完整的开源工具链支持。

本篇目标:

  1. 搞清楚 Tang Nano 9K 的硬件规格
  2. 用开源工具链(而非高云官方 IDE)跑通点灯 + UART
  3. 实现 PSRAM HyperBus 控制器(Tang Nano 9K 板载 64Mb PSRAM)
  4. HDMI 输出(与 ECP5 篇对比差异)
  5. 摸清 apicula 的现状和局限

本篇不覆盖:

  • 高云官方 IDE(GowinEDA)的详细使用,这是开源工具链系列
  • GW2A/GW5A 等大型高云 FPGA(工具链支持还在进行中)
  • 高云 MCU 软核(相对 VexRiscv 更弱)

1. Tang Nano 9K 硬件规格

Tang Nano 9K — 板卡构成 Sipeed Tang Nano 9K · 21 × 51 mm HDMI Type A TMDS 差分对 ×4 720p @ 60Hz · LVDS25E Gowin GW1NR-9C Arora 系列 · TSMC 55nm 8640 LUT4 · 6480 DFF 468 Kbit BRAM · 60 DSP18 1 PLL · 27 MHz xtal Yosys + nextpnr-gowin / apicula RGB LED ×3 USB-C / BL702 编程器 + 1 路 UART 5V 供电 JTAG → SRAM/Flash openFPGALoader -b tangnano9k PSRAM 64 Mb APS6404L-SQ HyperBus / QSPI 片上集成 · GW1NR 专属 + 64 Mb SPI Flash 40-pin GPIO · TF Card · 2× User Button 36 个用户 IO · 3.3V LVCMOS33 · .cst 约束文件分配 JTAG/UART HyperBus TMDS user IO
Tang Nano 9K 以高云 GW1NR-9C 为核心,片上集成 64 Mb PSRAM,板载 USB-C 编程、HDMI 输出与 40 针 GPIO,约 ¥60–80 即可入手。
参数规格
FPGA高云 GW1NR-9C(Arora 系列,TSMC 55nm)
LUT8640 个 LUT4
DFF6480 个触发器
BRAM468 Kbit(Block RAM)
DSP1860 个(18×18 乘法器)
PLL1 个
板载存储64Mb PSRAM(APS6404L-SQ,HyperBus/SPI 双模式)
时钟27 MHz 晶振(注意:不是 25MHz!)
视频输出HDMI Type A
USBUSB-C(CH552 USB-to-UART,1 路 UART)
存储卡TF 卡槽(SPI 模式)
GPIO36 个用户 IO
价格¥60-80(2024 年淘宝均价)

GW1NR 和 GW1N 的区别: GW1NR 片上集成了 64Mb PSRAM(实际是 APS6404L 芯片,通过内部总线连接),GW1N 没有。Tang Nano 9K 用的是 GW1NR-9C,有 PSRAM。


2. 高云官方工具 vs 开源工具链

对比项GowinEDA(官方)apicula + nextpnr-gowin(开源)
授权费用免费(需注册,有功能限制版)完全免费,无限制
跨平台Windows 主力,Linux 有限Linux/macOS/Windows 全支持
综合引擎高云自研 SynproYosys(开源,可调)
P&R 质量较好(Fmax 稍高)稍低(apicula 时序数据库不完整)
BRAM 支持完整基本支持(primitives 覆盖度约 80%)
PLL 配置图形化 IP Wizard手动实例化原语
DSP18自动推断需要手动实例化(Yosys 推断率低)
SERDES支持(部分型号)不支持(apicula 未逆向)
社区支持官方论坛(中文为主)GitHub Issues(英文为主)
适合场景生产项目,最大化性能学习/实验,CI/CD 集成,开源项目

结论: 对于开源项目或学习目的,nextpnr-gowin 完全够用。对于 Fmax > 100MHz 或需要 DSP/SERDES 密集使用的场景,暂时还是用 GowinEDA 更保险。


3. apicula 项目现状(2024)

apicula(前身叫 nextpnr-gowin)是高云 FPGA 开源工具链的核心。它通过逆向工程的方式,从高云官方 bitstream 中提取芯片的内部结构、时序模型、布线资源描述。

2024 年进展:

  • GW1N-9/GW1NR-9 完整支持(包括 Tang Nano 9K)
  • GW1N-1/GW1N-2/GW1N-4 基本支持
  • GW2A-18(Tang Nano 20K)支持中,覆盖度约 70%
  • GW5A-25(Tang Nano 4K Pro)尚未逆向

时序数据库覆盖度:

GW1NR-9C 时序覆盖(apicula 0.8):
  LUT4 传播延迟:    ✅ 完整(±5% 误差)
  DFF setup/hold:  ✅ 完整
  BRAM 时序:       ✅ 基本完整
  PLL 抖动模型:    ⚠️  保守估计(实际 jitter 可能更小)
  DSP18 流水线:    ⚠️  部分(乘法结果延迟约 ±10% 误差)
  IO buffer 延迟:  ⚠️  近似值(差分 IO 误差较大)

因为时序数据库不完整,nextpnr-gowin 给出的 Fmax 估计比实际偏低约 5-15%。换句话说,如果工具报 80MHz,实际上板可能能跑到 90MHz。但反过来不成立——不要依赖这种不确定性做设计决策。


4. 完整开源工具流程

安装

# OSS CAD Suite(包含 nextpnr-gowin + apicula)
source oss-cad-suite/environment

# 验证 gowin 支持
nextpnr-gowin --help | grep -i "family\|device"
# 应看到 GW1N/GW1NR 等型号

# openFPGALoader(Tang Nano 通过 USB-C 直连,用 CMSIS-DAP 协议)
openFPGALoader --list-boards | grep tang
# tang_nano tang_nano1k tang_nano4k tang_nano9k tang_nano20k

工程目录结构

tang-nano-9k-demo/
├── src/
│   ├── top.v         # 顶层
│   ├── uart_tx.v     # UART 发送
│   └── led_blink.v   # LED 闪烁
├── constraints/
│   └── tangnano9k.cst  # 高云用 .cst(不是 .lpf!)
├── Makefile
└── build/

约束文件(.cst)

高云工具用 .cst(Constraint file)格式,与 Lattice .lpf 不同:

// tangnano9k.cst
// Tang Nano 9K 引脚约束

// 时钟(27 MHz,注意不是 25MHz)
IO_LOC "clk" 52;
IO_PORT "clk" PULL_MODE=UP;

// LED(低电平点亮)
IO_LOC "led[0]" 10;
IO_LOC "led[1]" 11;
IO_LOC "led[2]" 13;
IO_LOC "led[3]" 14;
IO_LOC "led[4]" 15;
IO_LOC "led[5]" 16;
IO_PORT "led[0]" PULL_MODE=UP DRIVE=8;
IO_PORT "led[1]" PULL_MODE=UP DRIVE=8;

// UART(通过 CH552 USB-to-UART)
IO_LOC "uart_rx" 18;
IO_LOC "uart_tx" 17;
IO_PORT "uart_rx" PULL_MODE=UP;
IO_PORT "uart_tx" PULL_MODE=UP;

// HDMI(差分对)
IO_LOC "tmds_clk_p"  33;
IO_LOC "tmds_clk_n"  34;
IO_LOC "tmds_d_p[0]" 35;
IO_LOC "tmds_d_n[0]" 36;
IO_LOC "tmds_d_p[1]" 37;
IO_LOC "tmds_d_n[1]" 38;
IO_LOC "tmds_d_p[2]" 39;
IO_LOC "tmds_d_n[2]" 40;
IO_PORT "tmds_clk_p" IO_TYPE=LVCMOS33D;
IO_PORT "tmds_clk_n" IO_TYPE=LVCMOS33D;
IO_PORT "tmds_d_p[0]" IO_TYPE=LVCMOS33D;
// ... 其余差分对类似

点灯示例(完整 Verilog)

// top.v — Tang Nano 9K 点灯 + UART Hello
module top (
    input  wire       clk,        // 27 MHz
    input  wire       rst_n,      // 按键复位(低有效)
    output reg  [5:0] led,        // 6 个 LED(低电平亮)
    output wire       uart_tx
);
    // PLL:27 MHz → 54 MHz(系统时钟)
    wire clk_sys;
    wire pll_lock;

    // 高云 PLL 原语(rPLL)
    rPLL #(
        .FCLKIN("27"),    // 输入频率 27 MHz
        .IDIV_SEL(0),     // 分频系数 = IDIV_SEL + 1 = 1
        .FBDIV_SEL(1),    // 反馈分频 = FBDIV_SEL + 1 = 2
        .ODIV_SEL(8),     // 输出分频,VCO/8 = 216/4 = 54MHz
        // VCO = 27 × (FBDIV+1) / (IDIV+1) = 27 × 2 = 54 → 216MHz with ODIV=4
        .DYN_SDIV_SEL(2)
    ) pll0 (
        .CLKIN(clk),
        .CLKOUT(clk_sys),
        .LOCK(pll_lock),
        .RESET(~rst_n),
        .RESET_P(1'b0),
        .CLKFB(1'b0),
        .FBDSEL(6'b0),
        .IDSEL(6'b0),
        .ODSEL(6'b0),
        .PSDA(4'b0),
        .DUTYDA(4'b0),
        .FDLY(4'b0)
    );

    // 计数器:约 0.5 秒闪一次(54MHz / 2^25 ≈ 1.6Hz)
    reg [25:0] cnt;
    always @(posedge clk_sys or negedge rst_n) begin
        if (!rst_n)
            cnt <= 0;
        else
            cnt <= cnt + 1;
    end
    always @(*) led = ~{6{cnt[25]}};  // 全亮或全灭

    // UART TX:上电后发送 "Hello Tang Nano 9K!\r\n"
    uart_hello #(.CLK_FREQ(54_000_000), .BAUD(115200)) u_uart (
        .clk(clk_sys), .rst_n(rst_n && pll_lock),
        .tx(uart_tx)
    );
endmodule

// uart_hello.v — 上电发送固定字符串
module uart_hello #(
    parameter CLK_FREQ = 54_000_000,
    parameter BAUD     = 115_200
) (
    input  wire clk,
    input  wire rst_n,
    output reg  tx
);
    localparam CLKS_PER_BIT = CLK_FREQ / BAUD;  // 469

    // 要发送的字符串(ASCII)
    localparam MSG_LEN = 22;
    reg [7:0] msg [0:MSG_LEN-1];
    initial begin
        msg[ 0] = "H"; msg[ 1] = "e"; msg[ 2] = "l"; msg[ 3] = "l";
        msg[ 4] = "o"; msg[ 5] = " "; msg[ 6] = "T"; msg[ 7] = "a";
        msg[ 8] = "n"; msg[ 9] = "g"; msg[10] = " "; msg[11] = "N";
        msg[12] = "a"; msg[13] = "n"; msg[14] = "o"; msg[15] = " ";
        msg[16] = "9"; msg[17] = "K"; msg[18] = "!"; msg[19] = "\r";
        msg[20] = "\n"; msg[21] = 8'd0;
    end

    reg [4:0]  char_idx;
    reg [3:0]  bit_idx;
    reg [9:0]  clk_cnt;
    reg [9:0]  shift_reg;  // start + 8 data + stop
    reg        sending;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            tx       <= 1;
            char_idx <= 0;
            bit_idx  <= 0;
            clk_cnt  <= 0;
            sending  <= 1;
            shift_reg <= 10'h3FF;
        end else if (sending) begin
            if (clk_cnt == CLKS_PER_BIT - 1) begin
                clk_cnt <= 0;
                if (bit_idx == 9) begin
                    bit_idx <= 0;
                    if (char_idx == MSG_LEN - 1)
                        sending <= 0;
                    else begin
                        char_idx  <= char_idx + 1;
                        // 打包:start(0) + data + stop(1)
                        shift_reg <= {1'b1, msg[char_idx+1], 1'b0};
                    end
                end else begin
                    tx      <= shift_reg[0];
                    shift_reg <= {1'b1, shift_reg[9:1]};
                    bit_idx <= bit_idx + 1;
                end
            end else begin
                clk_cnt <= clk_cnt + 1;
                if (bit_idx == 0 && clk_cnt == 0)
                    shift_reg <= {1'b1, msg[char_idx], 1'b0};
            end
        end else
            tx <= 1;
    end
endmodule

综合、布线、烧录

# Makefile
DEVICE  = GW1NR-9C
PACKAGE = QFN88P
CST     = constraints/tangnano9k.cst

build/top.json: src/top.v src/uart_hello.v
	yosys -p "
	  read_verilog -sv $^;
	  synth_gowin -top top -json $@
	"

build/top.pack: build/top.json
	nextpnr-gowin \
	  --device $(DEVICE) \
	  --package $(PACKAGE) \
	  --cst $(CST) \
	  --json $< \
	  --write $@ \
	  --freq 54

build/top.fs: build/top.pack
	gowin_pack -d $(DEVICE) -o $@ $<

prog: build/top.fs
	openFPGALoader -b tangnano9k $<
make prog
# 预期输出:
# Info: Max frequency for clock 'clk_sys': 112.4 MHz (PASS at 54.00 MHz)
# Flashing build/top.fs...
# Programming done.

🚧 避坑 #1:apicula 时序数据库不完整导致 Fmax 偏低

nextpnr-gowin 报告的 Fmax 经常比实际低 5-15%。这不是 bug,而是 apicula 的时序模型是通过逆向工程近似的,保守估计以避免误报通过。如果你的设计目标是 100MHz,工具报 90MHz PASS,不代表实际跑不到 100MHz——但也不能依赖这个去做更激进的设计。建议在 GowinEDA 中做最终的时序收敛确认(用开源工具做开发,用官方工具做验收)。


5. PSRAM HyperBus 控制器

Tang Nano 9K 板载 APS6404L-SQ:64Mb(8MB)PSRAM,支持 HyperBus 和 SPI 双模式。HyperBus 是 Infineon(Cypress)的接口标准,8-bit 数据总线 + 差分时钟,DDR 模式,速率最高 200 MHz × 8bit = 200 MB/s。

HyperBus 命令帧格式(48 bit):

Bit 47:      R/W(1=读,0=写)
Bit 46:      AS(地址空间:0=存储器,1=寄存器)
Bit 45:      Burst(1=线性,0=包绕)
Bit 44-16:   地址(29 bit,字地址,不是字节地址)
Bit 15-3:    保留(0)
Bit 2-0:     地址低 3 bit(字内偏移)
// hyperbus_ctrl.v
// APS6404L PSRAM HyperBus 控制器(简化版:线性读写)

module hyperbus_ctrl (
    input  wire        clk,        // 系统时钟(54 MHz)
    input  wire        rst_n,
    // 用户接口
    input  wire        req,        // 发起请求
    input  wire        rw,         // 1=读,0=写
    input  wire [22:0] addr,       // 字地址(23 bit = 8MB / 2)
    input  wire [15:0] wdata,      // 写数据(16-bit 字)
    output reg  [15:0] rdata,      // 读数据
    output reg         ack,        // 操作完成
    // HyperBus 物理接口
    output reg         hb_ck,      // HyperBus 时钟(差分 P)
    output wire        hb_ckn,     // 时钟 N(由综合工具处理差分)
    output reg         hb_cs_n,    // 片选(低有效)
    inout  wire [7:0]  hb_dq,      // 数据总线(双向)
    output reg         hb_rwds     // 读写数据选通 / 字节掩码
);
    assign hb_ckn = ~hb_ck;

    // 状态机
    localparam IDLE      = 3'd0;
    localparam CA_PHASE  = 3'd1;   // 发送 6 字节命令/地址
    localparam LATENCY   = 3'd2;   // 等待延迟周期(初始延迟 = 6 cycles)
    localparam RWDATA    = 3'd3;   // 读/写数据
    localparam DESEL     = 3'd4;   // 释放 CS

    reg [2:0]  state;
    reg [5:0]  cycle_cnt;
    reg [47:0] ca_shift;   // 命令/地址移位寄存器
    reg [7:0]  dq_out;
    reg        dq_oe;      // 数据总线输出使能

    assign hb_dq = dq_oe ? dq_out : 8'hZZ;

    // 初始延迟:APS6404L 在 54MHz 下 tACC = 40ns → 3 个周期
    // 保守取 6 个 DDR 周期(12 个时钟沿)
    localparam LATENCY_CYCLES = 6;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state    <= IDLE;
            hb_cs_n  <= 1;
            hb_ck    <= 0;
            dq_oe    <= 0;
            ack      <= 0;
        end else begin
            ack <= 0;
            case (state)
                IDLE: begin
                    hb_cs_n <= 1;
                    hb_ck   <= 0;
                    dq_oe   <= 0;
                    if (req) begin
                        // 构造 CA(Command/Address)帧
                        ca_shift <= {rw, 1'b0, 1'b1,     // R/W, Memory Space, Linear
                                     addr[22:0], 3'b0,   // 地址 + 低 3 bit
                                     16'b0};             // 保留
                        hb_cs_n   <= 0;
                        cycle_cnt <= 0;
                        state     <= CA_PHASE;
                        dq_oe     <= 1;
                    end
                end

                CA_PHASE: begin
                    // HyperBus CA 阶段:DDR 发送,每个时钟沿输出 8-bit
                    hb_ck <= ~hb_ck;
                    if (cycle_cnt < 12) begin  // 48 bit / 4-bit DDR = 12 edges
                        dq_out    <= ca_shift[47:40];
                        ca_shift  <= {ca_shift[39:0], 8'h00};
                        cycle_cnt <= cycle_cnt + 1;
                    end else begin
                        cycle_cnt <= 0;
                        state     <= LATENCY;
                        dq_oe     <= 0;
                        hb_rwds   <= 0;
                    end
                end

                LATENCY: begin
                    // 等待初始延迟(6 个时钟周期)
                    hb_ck     <= ~hb_ck;
                    cycle_cnt <= cycle_cnt + 1;
                    if (cycle_cnt == LATENCY_CYCLES * 2 - 1) begin
                        cycle_cnt <= 0;
                        state     <= RWDATA;
                        dq_oe     <= ~rw;  // 写时驱动 DQ
                    end
                end

                RWDATA: begin
                    hb_ck <= ~hb_ck;
                    if (!rw) begin
                        // 写:先输出高字节,再输出低字节(DDR)
                        dq_out <= (cycle_cnt[0] == 0) ?
                                   wdata[15:8] : wdata[7:0];
                        if (cycle_cnt == 1) begin
                            cycle_cnt <= 0;
                            state     <= DESEL;
                        end else
                            cycle_cnt <= cycle_cnt + 1;
                    end else begin
                        // 读:RWDS 为高时数据有效
                        if (hb_rwds) begin
                            if (cycle_cnt == 0)
                                rdata[15:8] <= hb_dq;
                            else begin
                                rdata[7:0] <= hb_dq;
                                ack        <= 1;
                                state      <= DESEL;
                            end
                            cycle_cnt <= cycle_cnt + 1;
                        end
                    end
                end

                DESEL: begin
                    hb_cs_n <= 1;
                    hb_ck   <= 0;
                    dq_oe   <= 0;
                    state   <= IDLE;
                end
            endcase
        end
    end
endmodule

🚧 避坑 #2:PSRAM 初始化必须等 150µs

APS6404L 上电后需要等 150µs 才能接受命令(tPU = 150µs max)。如果你在 rst_n 释放后立刻发命令,PSRAM 可能没初始化完,读出全是 0xFF 或随机数据。在顶层逻辑里加一个计数器:54MHz 时钟,150µs = 8100 个时钟周期。计到 8100 后再拉高 psram_ready 信号,允许控制器开始工作。


6. HDMI 输出:与 ECP5 的差异

高云的差分 IO 原语不叫 ODDRX2F,叫 ELVDS_OBUF + ODDR

// gowin_tmds_out.v
// 高云 GW1NR-9C TMDS 输出原语(与 ECP5 不同)

module gowin_tmds_out (
    input  wire clk_5x,    // 5× 像素时钟
    input  wire clk_pixel,
    input  wire [9:0] tmds,
    output wire tmds_p,
    output wire tmds_n
);
    wire serial_out;

    // 高云 OSER10:10:1 串行化(比 ECP5 的 ODDRX2F 更直接)
    OSER10 #(
        .GSREN("false"),
        .LSREN("true")
    ) ser (
        .D0(tmds[0]),  .D1(tmds[1]),  .D2(tmds[2]),  .D3(tmds[3]),
        .D4(tmds[4]),  .D5(tmds[5]),  .D6(tmds[6]),  .D7(tmds[7]),
        .D8(tmds[8]),  .D9(tmds[9]),
        .FCLK(clk_5x),   // 高速时钟
        .PCLK(clk_pixel), // 像素时钟
        .RESET(1'b0),
        .Q(serial_out)
    );

    // 差分输出缓冲
    ELVDS_OBUF elvds (
        .I(serial_out),
        .O(tmds_p),
        .OB(tmds_n)
    );
endmodule

ECP5 vs 高云 TMDS 输出对比:

原语ECP5(Lattice)GW1NR-9C(高云)
串行化ODDRX2F(4:1 DDR,需要两级)OSER10(10:1,一步到位)
差分输出LVCMOS33D IO + ODDRX2FELVDS_OBUF
PLL 生成 5× 时钟EHXPLLLrPLL
最高支持分辨率1080p@60Hz(ECP5-85F)720p@60Hz(GW1NR-9C,IO 速率限制)
TMDS encoder相同(Verilog 共用)相同(Verilog 共用)

高云的 OSER10 原语非常好用——直接 10:1 串行化,不需要 ECP5 那样两级 DDR 的技巧。TMDS encoder 本身(8b/10b 那部分)两个平台完全相同,可以直接复用上一篇写的 tmds_encoder.v


7. 中国 FPGA 市场格局

厂商代表型号LUT 规模工艺开源工具支持特点
高云半导体(Gowin)GW1N/GW2A/GW5A1K-138KTSMC 55nm/22nm✅ apicula(成熟)价格低,Tang Nano 系列流行,开源生态最好
安路科技(Anlogic)EG4/AL3N2K-32K40nm⚠️ 实验支持主攻 AIoT,有 DSP/NPU IP
紫光同创(Pango Micro)PGL12G/PGL50H12K-1M28nm❌ 无开源面向军工/高端,对标 Xilinx Artix/UltraScale
复旦微电子(FMSH)FMQL45T45K(Zynq 架构)28nm❌ 无开源PS+PL 架构,类 Zynq,面向工业控制
易灵思(Efinix)Trion T8-T1208K-120KTSMC 40nm⚠️ 有 OSS 工具(Oxide)美国公司,但在中国市场有布局

开源生态的差距很明显: 高云是唯一有相对成熟开源工具链的国产 FPGA。其他厂商要么工具完全闭源,要么逆向工程还没人做。

对于想用开源工具的工程师,目前国产 FPGA 的唯一选择就是高云,具体来说是 GW1N/GW1NR/GW2A 系列(apicula 支持的范围)。


8. 验证步骤

# 1. 连接 Tang Nano 9K(USB-C)
lsusb | grep "CherryUSB\|CH552\|Future Technology"
# Bus 001 Device 005: ID 0403:6010 Future Technology Devices International...

# 2. 赋予 USB 权限(Linux)
sudo usermod -aG plugdev $USER
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="0403", MODE="0666"' | sudo tee /etc/udev/rules.d/99-fpga.rules
sudo udevadm control --reload-rules

# 3. 综合 + 烧录
make prog

# 4. 验证 LED 闪烁
# 板上 6 个 LED 应以约 1.6Hz 频率一起闪

# 5. 验证 UART
minicom -D /dev/ttyUSB0 -b 115200
# 应收到: Hello Tang Nano 9K!

# 6. 验证 PSRAM
# 通过 UART 命令接口写入 0x12345678,读回验证
# 使用 uart_hello 改为 psram_test 模式(写-读-比较)

# 7. 验证 HDMI
# 接 HDMI 显示器,应显示 640x480 或 720p 彩条图案

🚧 避坑 #3:openFPGALoader 需要 libusb

在某些精简 Linux 系统(比如 WSL2、Alpine)上,openFPGALoader 找不到 FTDI 设备,报 libusb_init() failed。需要安装 libusb-1.0-0-dev,并且 WSL2 还需要额外配置 USB passthrough(使用 usbipd-win)。Mac 用户通常没有这个问题,但如果报 device not found,先检查 brew install libusb 是否安装。


9. 下一篇预告

下一篇脱离 RTL,进入更高抽象层——HLS(高层次综合)。 用开源工具 Bambu HLS 把 C 语言直接编译成 Verilog,和 Xilinx Vitis HLS 做对比。不花 License 费,能用吗?

开源 FPGA 08:开源 HLS Bambu,C→RTL 不花钱


参考资料

资源链接 / 说明
apicula(高云逆向工程)YosysHQ/apicula
Tang Nano 9K 原理图sipeed/TangNano-9K,Schematic v1.1
GW1NR-9C 数据手册高云半导体官网 DS-GW1NR-9C(含 PLL/IO 原语说明)
APS6404L PSRAM 手册AP Memory APS6404L-SQ-SQ Datasheet v2.0,2020
OSER10 原语高云半导体《用户指南 - 硬件设计》UG702,第 6 章
nextpnr-gowinYosysHQ/nextpnr gowin/
Tang Nano 示例集sipeed/TangNano-9K-example
中国 FPGA 市场报告华经情报网《2024 年中国 FPGA 芯片行业研究报告》