尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于OSS CAD Suite与Tang Nano 9K的开源FPGA开发全流程实战

基于OSS CAD Suite与Tang Nano 9K的开源FPGA开发全流程实战 1. 项目概述当开源EDA遇上高性价比FPGA最近在捣鼓一个需要用到FPGA的小项目手头正好有一块Sipeed的Tang Nano 9K开发板。这块板子以其极致的性价比在开源硬件圈里小有名气核心是一颗来自高云的GW1NR-LV9 FPGA。但和传统FPGA开发动辄几个G的闭源IDE不同这次我想尝试点不一样的完全使用开源工具链——OSS CAD Suite简称YosysHQ工具链来搞定从设计到上板的全过程。这听起来像是一次“硬核”的回归毕竟在商业工具大行其道的今天用开源工具玩转一块国产FPGA本身就是件挺酷的事。Tang Nano 9K搭载的GW1NR-LV9属于高云小蜜蜂家族逻辑资源约9K LUTs对于学习、验证算法或做一些轻量级的接口转换比如把并行数据转成MIPI信号来说资源是足够的。而OSS CAD Suite则是一套集成了Yosys综合、nextpnr布局布线、gtkwave波形查看等核心工具的完整开源EDA套件。它的目标很明确为开源硬件和FPGA爱好者提供一个免费、透明且可脚本化的开发环境。对于想深入理解FPGA开发底层流程或者单纯不想被许可证束缚的开发者来说这无疑是一条值得探索的路径。那么这套组合能做什么简单说你可以用标准的Verilog或SystemVerilog编写代码然后通过开源工具链综合、布局布线最终生成可以烧录到Tang Nano 9K上的比特流文件。无论是实现一个简单的流水灯、UART通信还是更复杂的图像处理流水线、数字滤波器如IIR甚至是尝试一些通信协议如SPI、I2C的FPGA端实现这套环境都能支持。它特别适合学生、硬件黑客、以及希望将FPGA开发流程集成到CI/CD中的工程师。接下来我就带你一步步搭建环境并完成一个从代码到点灯的完整流程过程中会分享我踩过的坑和总结的技巧。2. 环境搭建与工具链深度解析2.1 OSS CAD Suite的获取与安装首先我们需要获取OSS CAD Suite。最推荐的方式是直接从YosysHQ的GitHub Release页面下载对应你操作系统的预编译包。对于Linux和macOS用户来说这通常是最无痛的方式。Windows用户同样有预编译版本可用但可能需要处理一下系统路径和环境变量。以Linux为例我通常这样做访问GitHub仓库找到最新的稳定版发布包比如oss-cad-suite-linux-x64-*.tgz。使用wget或curl下载到本地。解压到一个你喜欢的目录比如~/opt/下。tar -xzf oss-cad-suite-linux-x64-*.tgz -C ~/opt/最关键的一步设置环境变量。你需要将工具链的bin目录加入PATH并且通常还需要设置LD_LIBRARY_PATHLinux或DYLD_LIBRARY_PATHmacOS指向其lib目录。一个简单粗暴的方法是在你的 shell 配置文件如~/.bashrc或~/.zshrc里添加export OSS_CAD_SUITE/home/yourname/opt/oss-cad-suite export PATH$OSS_CAD_SUITE/bin:$PATH export LD_LIBRARY_PATH$OSS_CAD_SUITE/lib:$LD_LIBRARY_PATH保存后执行source ~/.bashrc使其生效。注意预编译包包含了几乎所有依赖避免了从源码编译各种库如Boost、TCL的麻烦。但请确保你的系统有基础的构建工具如make和gcc。安装完成后在终端输入yosys -V和nextpnr-gowin -V来验证核心工具是否可用。如果看到版本号输出恭喜你开源工具链的主体就绪了。2.2 Tang Nano 9K的硬件识别与驱动接下来是让电脑认识你的Tang Nano 9K。这块板子使用了一颗CH552芯片作为USB转JTAG/UART桥接器。在Linux系统下内核通常已经自带了ch34x或ch341驱动插入USB线后板子应该能被自动识别。你可以通过lsusb命令查看如果看到类似1a86:5512的ID南京沁恒微电子的VID/PID就说明设备已被识别。串口设备通常会映射为/dev/ttyUSB0或/dev/ttyACM0。使用dmesg | grep tty命令可以查看最新的串口设备注册信息。对于Windows用户可能需要手动安装CH552的驱动程序。你可以从Sipeed的官网或Wiki找到相应的驱动下载链接。安装成功后在设备管理器中会看到新的串行端口COM口和可能的USB设备。实操心得在Linux下有时普通用户没有访问/dev/ttyUSB*设备的权限。你需要将你的用户加入到dialout或uucp组或者创建一个udev规则。我更喜欢后者一劳永逸。创建一个文件如/etc/udev/rules.d/99-tangnano.rules加入一行SUBSYSTEMtty, ATTRS{idVendor}1a86, ATTRS{idProduct}5512, MODE0666。然后重新插拔设备即可。2.3 项目目录结构与Makefile自动化一个清晰的项目结构能极大提升开发效率。我建议为每个FPGA项目创建一个独立的目录并采用如下结构my_fpga_project/ ├── src/ # 存放所有Verilog源代码 │ ├── top.v # 顶层模块 │ ├── clk_gen.v # 时钟生成/管理模块 │ └── ... # 其他子模块 ├── constraints/ # 约束文件 │ └── tangnano9k.cst # 管脚和时钟约束 ├── build/ # 编译输出目录可.gitignore │ ├── obj/ # 中间文件 │ └── bin/ # 最终比特流文件 ├── sim/ # 仿真文件可选 │ └── tb_top.v # 测试平台 ├── scripts/ # 脚本文件 │ └── program.sh # 烧录脚本 └── Makefile # 自动化构建脚本核心是Makefile它能把繁琐的命令行操作自动化。下面是一个极简但可用的Makefile示例用于综合、布局布线并生成比特流PROJECT top SOURCES $(wildcard src/*.v) CONSTRAINTS constraints/tangnano9k.cst DEVICE GW1NR-LV9QN88PC6/I5 all: build/bin/$(PROJECT).fs # 综合 build/obj/$(PROJECT).json: $(SOURCES) mkdir -p build/obj yosys -p read_verilog -sv $(SOURCES); synth_gowin -top $(PROJECT) -json $ # 布局布线并生成比特流 build/bin/$(PROJECT).fs: build/obj/$(PROJECT).json $(CONSTRAINTS) mkdir -p build/bin nextpnr-gowin --json $ --write build/obj/$(PROJECT)_pnr.json \ --device $(DEVICE) --cst $(CONSTRAINTS) \ --freq 27 gowin_pack -d $(DEVICE) -o $ build/obj/$(PROJECT)_pnr.json # 清理 clean: rm -rf build # 伪目标 .PHONY: all clean这个Makefile做了几件事yosys读取所有Verilog源文件指定顶层模块为top进行综合synth_gowin并输出一个网表JSON文件。nextpnr-gowin读取这个JSON和约束文件针对具体的器件型号GW1NR-LV9QN88PC6/I5进行布局布线并可以指定时序约束--freq 27表示期望时钟频率27MHz。最后gowin_pack将布局布线后的结果打包成高云专用的.fs比特流文件。在项目根目录下只需执行make命令就会自动完成整个流程在build/bin/下生成top.fs文件。执行make clean则清理编译产物。3. 从Verilog到比特流核心开发流程实战3.1 编写第一个可综合的Verilog模块让我们从一个最经典的项目开始流水灯。这能验证整个工具链和硬件是否工作正常。首先在src/目录下创建顶层文件top.v。module top ( input wire clk, // 板载27MHz时钟输入 output reg [5:0] led // Tang Nano 9K有6个用户LED ); // 内部信号定义 reg [23:0] counter; // 24位计数器用于分频 // 计数器逻辑每个时钟上升沿加1 always (posedge clk) begin counter counter 1; end // LED流水逻辑利用计数器的高位控制LED亮灭形成流水效果 always (posedge clk) begin led {counter[23], counter[22], counter[21], counter[20], counter[19], counter[18]}; end endmodule这个模块非常简单。它实例化了一个24位计数器时钟每来一个上升沿计数器加1。然后我们将计数器的第18到23位counter[23:18]分别赋值给6个LED。由于计数器一直在循环累加这些高位比特会以不同的频率周期性变化位越高变化越慢从而在LED上形成一种看似“流水”的动态效果。注意事项在Verilog中对于时序逻辑用always (posedge clk)描述的块必须使用非阻塞赋值。这确保了在时钟沿到来时所有寄存器同时更新其值模拟真实的触发器行为。如果错误地使用了阻塞赋值在综合时可能不会报错但仿真和实际硬件行为会与预期不符这是一个常见的坑。3.2 约束文件编写连接物理世界代码描述了逻辑功能但哪个端口对应板子上的哪个物理引脚这就需要约束文件.cst来定义。在constraints/tangnano9k.cst中我们需要指定时钟和LED的引脚。# Tang Nano 9K 约束文件示例 # 时钟引脚板载27MHz晶振连接到FPGA的B7引脚 set_pin -pin B7 -type clk -freq 27 # 或者更精确的写法指定IO标准 # create_clock -name clk -period 37.037 -waveform {0 18.5185} [get_ports clk] # set_property -dict {PACKAGE_PIN B7 IOSTANDARD LVCMOS33} [get_ports clk] # LED引脚根据Tang Nano 9K原理图6个LED阴极通过电阻连接到FPGA阳极接3.3V。 # 因此FPGA输出低电平时LED亮高电平时LED灭。 # LED0 - Pin L14 # LED1 - Pin N13 # LED2 - Pin M12 # LED3 - Pin P11 # LED4 - Pin N10 # LED5 - Pin P10 set_pin -pin L14 -type io -io std -name led[0] set_pin -pin N13 -type io -io std -name led[1] set_pin -pin M12 -type io -io std -name led[2] set_pin -pin P11 -type io -io std -name led[3] set_pin -pin N10 -type io -io std -name led[4] set_pin -pin P10 -type io -io std -name led[5] # 可选设置IO电平标准默认为LVCMOS33 # set_io_standard -name LVCMOS33 [get_ports {led[*]}]约束文件的语法可能因工具链版本略有不同。上面示例使用了set_pin这种相对简洁的语法。-type clk告诉工具这是一个时钟端口-freq 27给出了频率信息单位MHz这对于时序分析很重要。LED引脚则被定义为普通的IO-type io。核心细节解析理解硬件连接至关重要。Tang Nano 9K的LED是共阳极接法意味着LED的正极阳极已经接到了3.3V电源上。负极阴极通过一个限流电阻连接到FPGA的引脚。所以当FPGA引脚输出逻辑低电平0V时电流从3.3V经LED和电阻流向FPGA引脚LED点亮。输出高电平3.3V时引脚与阳极电压相等没有电流LED熄灭。因此在我们的代码里led信号为0时灯亮为1时灯灭。流水灯代码中我们将计数器的高位赋给led所以你会看到LED随着高位比特的翻转而亮灭。3.3 综合、布局布线与比特流生成有了代码和约束就可以运行之前写好的Makefile了。在终端项目根目录下输入make命令。综合Yosysyosys会首先被调用。它会解析所有Verilog文件检查语法进行高层次综合HLS本例中很简单然后将你的行为级描述转换为由FPGA底层基本单元查找表LUT、触发器FF等组成的门级网表。这个网表以JSON格式输出。你可以观察Yosys的输出日志它会报告资源使用情况例如使用了多少个LUT和FF。对于我们的流水灯用量会非常少。布局布线nextpnr-gowinnextpnr-gowin读取网表JSON和约束文件。它的任务非常关键布局决定网表中的每一个逻辑单元如LUT、FF应该被放置到FPGA芯片的哪个物理位置。好的布局能减少信号延迟和布线拥堵。布线用芯片内部的金属连线资源将所有已经放置好的逻辑单元按照网表要求连接起来。时序分析根据你提供的时钟频率约束--freq 27工具会分析信号在路径上的延迟是否满足建立时间和保持时间的要求。如果失败它会尝试优化布局布线或者最终报出时序违例。对于27MHz的低频时钟和如此简单的设计时序通常都能轻松满足。打包gowin_packnextpnr输出的是布局布线后的、更详细的网表。gowin_pack是这个工具链中专用于高云FPGA的“比特流生成器”。它读取布局布线结果根据高云芯片的底层架构如Block RAM、DSP、IO Bank的配置生成最终的.fs文件。这个文件包含了配置FPGA内部所有可编程点开关、LUT内容等的二进制信息。如果一切顺利你会在build/bin/目录下看到top.fs文件。这就是可以烧录到Tang Nano 9K里的“程序”。4. 烧录、调试与进阶技巧4.1 使用openFPGALoader进行烧录生成比特流后我们需要将其烧录到FPGA的配置存储器中。Tang Nano 9K板载了一颗SPI Flash通常为W25Q16来存储比特流FPGA上电时会自动从Flash加载配置。我们将使用开源烧录工具openFPGALoader。首先确保你已经安装了openFPGALoader。许多Linux发行版的仓库里都有也可以用包管理器安装或者从源码编译。安装后连接Tang Nano 9K的USB口到电脑。基本的烧录命令如下openFPGALoader -b tangnano9k build/bin/top.fs-b tangnano9k指定板卡类型openFPGALoader内置了多种开发板的配置文件其中包含了Flash型号和JTAG链信息。build/bin/top.fs指定要烧录的比特流文件路径。执行命令后工具会通过USB-JTAG桥接器先配置FPGA本身然后将比特流写入到外部的SPI Flash中。写入完成后你可以按一下板子的复位键或重新上电FPGA便会从Flash加载新的配置此时应该能看到6个LED开始呈现流水灯效果。实操心得openFPGALoader的-b参数非常方便。如果不指定你可能需要手动提供一堆参数比如--cable、--fpga-part、--flash-size等。使用板卡预设值能避免很多麻烦。如果烧录失败可以尝试添加-v参数查看详细日志排查是驱动问题、连接问题还是Flash识别问题。4.2 仿真验证用Icarus Verilog和GTKWave在直接上板之前对复杂设计进行仿真是极其重要的。它能帮你发现逻辑错误节省调试时间。OSS CAD Suite通常包含了iverilogIcarus Verilog仿真编译器和gtkwave波形查看器。我们在sim/目录下创建一个测试平台文件tb_top.vtimescale 1ns / 1ps module tb_top; // 输入 reg clk; // 输出 wire [5:0] led; // 实例化被测模块 top uut ( .clk(clk), .led(led) ); // 生成时钟周期约37ns (对应27MHz) initial clk 0; always #18.5185 clk ~clk; // 半个周期延时 // 初始化与测试过程 initial begin // 初始化信号可选 // 记录波形文件 $dumpfile(tb_top.vcd); $dumpvars(0, tb_top); // 0表示记录所有层次的信号 // 运行足够长时间观察 #1000000; // 仿真1,000,000个时间单位1ms $finish; end endmodule然后使用以下命令进行编译和仿真cd sim iverilog -o tb_top.vvp -s tb_top ../src/top.v tb_top.v vvp tb_top.vvp gtkwave tb_top.vcdiverilog将源文件和测试平台编译成一个可执行的仿真映像.vvp。vvp运行这个映像执行仿真并根据$dumpfile和$dumpvars的指令生成波形文件.vcd。gtkwave打开波形文件你可以添加信号到查看窗口观察clk和led信号随时间的变化验证流水逻辑是否正确。4.3 进阶设计结构化建模与参数化当项目变大时良好的代码组织至关重要。Verilog的generate语句和parameter参数化设计能极大提升代码的复用性和可维护性。例如我们可以将之前的计数器流水灯改造成一个参数化的呼吸灯模块module pwm_breath #( parameter WIDTH 24, // 计数器位宽 parameter INVERT 0 // 输出是否取反 )( input wire clk, output reg pwm_out ); reg [WIDTH-1:0] counter; reg [WIDTH-1:0] threshold {WIDTH{1b0}}; // 初始为0 reg direction 0; // 0: 增加阈值1: 减少阈值 always (posedge clk) begin counter counter 1; // 三角波生成阈值 if (direction 0) begin if (threshold) // 如果阈值全为1则调转方向 direction 1; else threshold threshold 1; end else begin if (|threshold) // 如果阈值非全0则减少 threshold threshold - 1; else direction 0; end // 比较器产生PWM输出 pwm_out (counter threshold) ^ INVERT; end endmodule然后在顶层模块中实例化多个驱动不同的LED可以创造出复杂的灯光效果module top ( input wire clk, output wire [5:0] led ); // 使用generate循环实例化6个参数略有不同的呼吸灯模块 genvar i; generate for (i0; i6; ii1) begin: breath_led_gen pwm_breath #( .WIDTH(24 i), // 每个LED的计数器位宽递增使呼吸节奏不同 .INVERT(1) // 因为LED低电平亮所以输出取反 ) u_breath ( .clk(clk), .pwm_out(led[i]) ); end endgenerate endmodule这种结构化、参数化的设计方法使得代码清晰修改方便比如想增加LED数量只需修改循环上限是FPGA开发中必须掌握的核心技能。5. 常见问题排查与深度优化指南5.1 编译与烧录问题速查在实际操作中你可能会遇到各种问题。下面是一个常见问题与解决方法的速查表问题现象可能原因排查步骤与解决方案make失败Yosys报语法错误1. Verilog代码语法错误。2. 使用了工具链不支持的SystemVerilog语法。1. 仔细检查Yosys输出的错误行号和信息。常见的如缺少分号、模块端口列表不匹配。2. OSS CAD Suite对SystemVerilog的支持是子集。尽量使用经典的Verilog-2001语法。对于logic类型、always_comb等可尝试换成reg/wire和always (*)。nextpnr-gowin布局布线失败1. 约束文件错误如引脚名拼写错误。2. 设计资源超限。3. 时序无法满足频率设太高。1. 检查.cst文件确保-name后的端口名与顶层模块的端口名完全一致大小写敏感。2. 查看Yosys综合报告中的资源使用量对比GW1NR-LV9的总资源约8640 LUTs。3. 尝试降低--freq参数值或检查代码中是否存在过长的组合逻辑路径。openFPGALoader找不到设备1. 驱动未正确安装。2. 设备权限问题Linux。3. 板卡未上电或USB线故障。1. Windows检查设备管理器Linux运行lsusb查看是否有1a86:5512设备。2. Linux下参考2.2节配置udev规则或用户组。3. 确认板子电源灯亮尝试更换USB口或USB线。烧录成功但板子无反应1. 比特流未正确写入Flash。2. 约束文件中引脚分配错误。3. 硬件连接问题如LED损坏。1. 尝试用openFPGALoader -b tangnano9k --verify build/bin/top.fs进行校验。2.重点检查确认约束文件中的引脚编号与原理图一致。Tang Nano 9K的引脚编号是“物理引脚号”如B7而非逻辑Bank号。3. 用万用表测量FPGA引脚在程序运行时的电压是否在变化。仿真iverilog行为与上板不一致1. 测试平台未充分模拟实际环境如上电复位。2. 代码中存在异步逻辑或未初始化的寄存器在仿真和综合后行为不同。1. 在测试平台中加入复位信号的模拟并确保时钟生成符合实际频率。2. 对于所有寄存器变量在声明时或复位逻辑中赋予明确的初值。综合会忽略初始值但上电后寄存器状态是不确定的。5.2 时序约束与性能优化对于简单的流水灯时序约束可能不是问题。但当你设计更复杂的系统比如处理图像1280x1024 60Hz或实现高速通信时时序就成了关键。在nextpnr-gowin中我们通过--freq给出了全局时钟约束。但这只是最基本的。更精确的约束需要写在约束文件里使用SDCSynopsys Design Constraints风格不过目前OSS CAD Suite对高云FPGA的SDC支持还在完善中。优化建议流水线设计这是提高系统时钟频率最有效的方法。将一段长的组合逻辑路径拆分成多个时钟周期完成。例如一个32位加法器如果关键路径太长可以将其拆成两个16位加法用触发器隔开。// 非流水线可能成为频率瓶颈 always (posedge clk) begin result a b c; end // 二级流水线 reg [31:0] sum_ab; always (posedge clk) begin sum_ab a b; // 第一级计算ab result sum_ab c; // 第二级计算 (ab)c end寄存器输出模块的输出信号尽量用寄存器打一拍再输出。这不仅能改善时序还能将关键路径限制在模块内部有利于布局布线工具优化。合理使用(* keep *)属性在Yosys中你可以使用Verilog属性来阻止综合器优化掉某些信号或层次结构这对于调试和保持特定结构很有用。例如(* keep *) wire debug_signal;。关注nextpnr报告布局布线后nextpnr会输出详细的时序报告。关注“最差负裕量”Worst Negative Slack, WNS。如果WNS为负说明有时序违例。报告里会列出违例的路径你可以据此优化代码。5.3 资源利用分析与调试技巧Yosys的综合报告会给出初步的资源估计。nextpnr在布局布线后也会给出更精确的资源使用报告。对于Tang Nano 9K的9K LUTs你需要心中有数。调试技巧LED和GPIO是你的朋友在调试初期不要吝啬使用板载LED和空闲的GPIO引脚。将内部的关键状态信号如状态机状态、计数器值、数据有效标志输出到GPIO用逻辑分析仪甚至另一个单片机来捕捉是最直接的调试方法。软核调试如果你在FPGA里运行了一个软核处理器如RISC-V可以借助其调试模块和UART打印信息这与软件调试非常相似。静态代码分析养成好的编码习惯使用lint工具如verilator的--lint-only模式在综合前检查代码中的潜在问题如锁存器推断、多驱动、位宽不匹配等。增量编译对于大型设计修改一小部分代码后重新全编译可能很耗时。虽然开源工具链的增量编译支持不如商业IDE成熟但你可以通过编写更精细的Makefile只重新综合和布局布线修改过的模块及其上级模块来节省时间。将Sipeed Tang Nano 9K与OSS CAD Suite结合你拥有的不仅是一套免费的FPGA开发环境更是一个深入理解从硬件描述语言到实际电路映射全过程的绝佳平台。它可能没有商业工具那样华丽的界面和一键式操作但每一步的透明性和可控性正是学习和研究的价值所在。从点灯开始逐步尝试实现UART、SPI、PWM再到更复杂的图像处理流水线或软核系统每一步的挑战和成功都会让你对数字电路设计的理解更加深刻。这套开源工具链社区活跃不断有新的设备和特性被加入值得持续关注和探索。
返回列表