2. 硬件与软件双引擎:细看 Ultra96 的配置与外设
3. 上手前需要搞懂的三个核心概念
4. 实操:从拿到板子到点亮一个“Hello World”
5. 踩坑记录与排查速查表
5.1 启动异常排查
5.2 显示与网络问题
5.3 电源与散热教训
6. 后续可以往哪些方向挖
说实话,第一次拿到Ultra96的时候,我的第一反应是:这跟树莓派也太像了。同样巴掌大的板子,同样用microSD卡启动,同样有一堆USB口和视频输出口。可等我把电源接上、串口连上、看到bootlog里出现Zynq UltraScale+ MPSoC这几个字之后才发现,这玩意儿的本质跟树莓派完全是两码事。
Ultra96是Avnet和Xilinx合作推出的一块开发板,核心芯片用的是Zynq UltraScale+ MPSoC家族的ZU3EG。它最大的特点在于:一颗芯片里同时包含了一个可以完整跑Linux的四核ARM处理器系统(PS),以及一大片可以随心所欲改电路逻辑的FPGA可编程逻辑(PL)。也就是说,同一块板子既能当一台“电脑”,也能当一块“可以重新改线路的电路板”。
这篇是《Understanding the Ultra96 Board》系列的第一篇,我先不堆细节,重点回答三件事:这板子到底是什么、为什么值得研究、怎么让它跑起来。适合刚接触FPGA异构计算、或者从树莓派等单板计算机转过来想试试MPSoC的读者,已经玩过Zynq-7000系列的老人可以直接跳到第四节看启动细节。
1. Ultra96这块板子,到底特殊在哪
1.1 一颗芯片看懂Ultra96:ZU3EG的双域架构
先看最核心的芯片:Xilinx Zynq UltraScale+ MPSoC,具体型号是ZU3EG。名字里的“EG”代表这是EG系列,也就是说它带了GPU(Mali-400),但没有硬核视频编解码器。如果要一边跑AI推理一边做H.264硬件编解码,就需要换EV系列,那通常是ZU7EV往上走,价格完全不是一个级别了。
这颗芯片从架构上可以分成两大部分:
PS(Processing System,处理器系统):包含四核ARM Cortex-A53(64位)、双核Cortex-R5F实时处理器、Mali-400 GPU,还有DDR内存控制器、USB、UART、SDIO、I2C、SPI、GigE等一系列“电脑该有的外设”。这一整个子系统可以独立启动,跑完整的Linux发行版,比如Ubuntu。对于跑Linux这件事来说,它跟树莓派上的BCM2711没本质区别——都是ARM处理器加一堆控制器,只是A53架构更老一些。
PL(Programmable Logic,可编程逻辑):也就是FPGA的那部分,ZU3EG有大约154K的逻辑单元,换算下来大概相当于20多万个等效门电路。它可以被配置成任意数字逻辑:寄存器、状态机、流水线、自定义的接口协议,甚至是你自己设计的CPU。PL没有固定功能,上电时从配置源读入bitstream,跑起来之后内部就是一片专门为你这版需求定制的电路。
这两部分不是两片芯片封装在一起,而是同一颗die上的两个域,通过内部的AXI总线互联。AXI是ARM定义的AMBA总线协议中的一个分支,Xilinx把PS和PL之间的数据通路做成了标准AXI接口,你只需要把PL逻辑封装成AXI外设,就能被PS里的Linux读写访问,就像访问一个内存映射的寄存器一样。
这一点是Ultra96和其他“能跑Linux的ARM板”的根区别:树莓派的CPU、GPU、外设全部是芯片厂在出厂时固定死的,而Ultra96的PS侧是固定的,PL侧则完全开放。一颗芯片,一半是固定世界,一半是可重构世界,这是异构计算里非常典型的形态。
1.2 板级外设逐个盘点
Ultra96板子本体尺寸是85mm × 54mm左右,确实跟树莓派差不多,但它的外设布局很不一样。我用过的感受是,这块板子的外设不是“给单板电脑用户准备的”,而是“给做嵌入式和FPGA开发的人准备的”。
我手上的这块是V1版,板载外设大概是这样:
- 2GB LPDDR4内存,和PS的DDR控制器直连,带宽够用,跑Ubuntu桌面系统不卡,跑轻量AI推理也勉强可以。
- microSD卡槽,最常用的启动介质。官方镜像直接烧进SD卡,插上就能开机。
- 一个USB 3.0 Type-A口,一个USB Type-C口。Type-C口同时负责供电和DisplayPort视频输出(DP Alt Mode),这点很容易让人困惑,我第一次用还以为是USB转串口,结果那是电源口。
- 板载WiFi/蓝牙模组,走SDIO和UART接口,Ubuntu下可以用网络管理器直接连WiFi。
- MIPI CSI-2摄像头接口和MIPI DSI显示屏接口,用于接图像传感器和屏幕。
- 两个Digilent PMOD兼容扩展口,可以插各种PMOD外设模块,从简单的拨码开关到高速ADC都有。
- 一个板载JTAG/串口调试排针。
这套外设组合的用意很明显:它想让你一个板子同时搞定“Linux应用开发”“FPGA逻辑开发”和“图像视频接口调试”三件事。
1.3 Part 1的目标:先把系统跑起来
这篇Part 1我只讲一件事:让你手里的Ultra96上电、启动、进入Linux,并且理解它和普通单板计算机的启动流程差异。因为很多没玩过Zynq的人,第一次会卡在启动环节——它不是像树莓派那样拷个镜像插电就能跑的,boot流程里面有FSBL、ATF、U-Boot、bitstream这一堆名词,你要是不理解,黑屏都不知道去哪儿查。
等到Part 2,我会专门讲PS和PL之间怎么通过AXI做第一个真正的数据通路,以及如何在Linux里写驱动访问PL侧的寄存器。那是把这块板子的价值真正发挥出来的关键,但前提是先把这套启动流程跑通。
2. 硬件与软件双引擎:细看 Ultra96 的配置与外设
2.1 PS域:四核A53跑Ubuntu只是基本盘
Ultra96的PS侧有一颗四核Cortex-A53,主频最高约1.5GHz,这在今天看起来不算高,但在嵌入式设备里完全够用。它的意义在于:你能在FPGA板卡上跑一个完整的Ubuntu系统,然后直接用gcc编译程序、用Python写脚本、用OpenCV做图像处理。这种开发体验,远不是传统单片机开发那种“编译完烧进去、用串口看printf”能比的。
Cortex-R5F双核实时处理器通常不跑Linux,而是跑裸机代码或FreeRTOS,用于那些对实时性要求高的场景,比如电机控制、协议栈处理。A53跑Linux做应用和业务逻辑,R5F做硬实时控制,PL做高速并行处理,这三层协同是Zynq UltraScale+芯片的核心设计哲学。
我实际用下来的感受是:你在Ubuntu里写程序,跟树莓派上几乎没区别。唯一要注意的是Ultra96只有2GB内存,开个大一点的桌面环境、再跑个浏览器就会很紧张,所以大多数时候我都是通过SSH命令行操作,不接图形界面。
2.2 PL域:能改的电路比跑分更有意思
PL域有154K逻辑单元,放在高性能FPGA里算偏入门的规模,但作为学习平台非常合适。它能让你自己动手搭一个小型软核CPU、做图像处理流水线、加速某个特定算法,这些是ARM芯片永远做不到的。
PL域没有固定的“指令集”,它由查找表(LUT)、触发器(FF)、块RAM(BRAM)、DSP Slice等基本单元组成。你用Verilog或VHDL描述的逻辑,综合工具会把它映射成这些基本单元的连接关系,最后生成bitstream。这个bitstream下载到PL之后,这块FPGA就变成了你设计的那块电路。
我建议新手第一件事不是跑分,而是写一个最简单的计数器模块,接到PMOD引脚的LED上,看着它按你设定的速率闪烁。这件事虽然简单,但能让你真正理解“PL里的逻辑是并行的,多个always语句是同时执行的”这个核心概念。
2.3 双域互联:AXI总线到底是什么
PS和PL之间通过AXI接口通信。Xilinx在这个体系里提供几种接口类型:
- AXI-GP:通用目的接口,带宽不高,适合控制类访问,比如配置寄存器。
- AXI-HP:高性能接口,带FIFO缓冲,适合大批量数据传输,比如DMA往DDR里写数据。
- AXI-ACP:一致性接口,可以直接访问PS侧cache,适合需要缓存一致性的场景。
用生活化的类比来解释:PS和PL是两个国家,各自有自己的地盘和资源。AXI就像一条连接两国的海关通道,GP通道是只允许小包裹通过的普通货运道,HP通道是专门走集装箱的港口码头,ACP通道则是可以免检直达对方仓库的VIP通道。不同类型的业务走不同的通道,效率完全不同。
Ultra96上做第一个PL外设,通常就是从AXI-GP通道接一个AXI GPIO模块开始的。PS把PL侧的寄存器映射到自己的地址空间里,Linux驱动里直接往某个地址写值,PL侧的LED就亮了,这个过程中数据的流向就是通过AXI总线完成的。这个路线非常直观,也最容易验证。
3. 上手前需要搞懂的三个核心概念
3.1 Boot ROM、FSBL和U-Boot各司其职
Zynq芯片上电后,片内的BootROM先运行,然后从启动介质(SD卡、QSPI Flash等)加载FSBL(First Stage Boot Loader)。FSBL的主要工作包括初始化DDR、配置PS侧的时钟、根据需要把PL的bitstream加载进去,最后跳转到下一个启动阶段——也就是ATF(ARM Trusted Firmware)和U-Boot,U-Boot再加载Linux内核。
理解这条链路很重要。因为如果你改了PL侧的启动PL配置,或者是DDR初始化参数有问题,系统可能就卡在FSBL阶段,表现出来就是串口没有任何输出,或者停在某个特定地方不动了。很多新手以为板子坏了,其实只是配置文件不对。
3.2 什么是XSA和硬件平台文件
在Vivado里做完PL设计、生成bitstream之后,还要导出一个叫XSA的文件。XSA就像一张“硬件图纸”,它把你PS侧的配置、PL侧的bitstream、地址映射信息全部打包到一起。Vitis工具链导入这个XSA后,才知道CPU跑在什么频率、DDR怎么初始化、外设地址在哪里、PS和PL之间有哪些接口。
这个文件是整个Vivado/Vitis流程的中枢。我见过太多人卡在“导出到Vitis后找不到硬件平台”的问题,基本就是XSA没有导出或者导错版本了。
3.3 Ultra96到底怎么启动:从SD卡到Linux
以最常见的SD卡启动为例,整个流程是:
- 制作一个包含BOOT.BIN和image.ub等文件的SD卡启动分区。
- 上电后BootROM从SD卡读取启动镜像。
- FSBL初始化DDR、加载PL bitstream。
- U-Boot加载Linux内核和设备树。
- Linux启动,出现登录提示或桌面系统。
BOOT.BIN通常由FSBL、PMU固件、ATF、U-Boot打包而成。如果你用官方提供的Ubuntu镜像,这些都已经帮你做好了。如果你自己从源码构建BSP,就要用PetaLinux或者手动编译来生成这些文件,过程会繁琐不少,但也能学到更多底层细节。
对于多数想快速上手的人来说,直接用官方镜像就是最省事的方式。先跑起来,后面再慢慢深入构建流程。
4. 实操:从拿到板子到点亮一个“Hello World”
4.1 准备清单和工具
需要的硬件:
- Ultra96开发板一块
- USB Type-C电源适配器,规格必须是12V/2A的PD电源。普通5V手机充电头插上去可能没反应,这是很多人的第一个坑
- microSD卡,建议Class 10或A1以上,容量8GB以上就够了
- 一条支持DP Alt Mode的USB Type-C转HDMI线或Type-C转DP线,用来接显示器
- USB转TTL串口模块,用于看启动日志(也可以直接用板载调试串口)
- 如果要用WiFi或蓝牙,盒子里的天线要装好
软件方面,需要准备balenaEtcher之类的烧录工具,和一个终端工具,比如MobaXterm、PuTTY或者macOS/Linux下的minicom。
4.2 烧录官方镜像
去Avnet或者Xilinx的官方资源页找Ultra96的Ubuntu镜像,也可以直接用PYNQ镜像,看你想怎么用。PYNQ的好处是内置了Jupyter Notebook环境,能直接用Python配置PL,很适合快速验证想法。原生Ubuntu则更接近标准Linux开发环境。
烧录步骤很简单:把SD卡插入读卡器,打开balenaEtcher,选择镜像文件,选择SD卡,点Flash。等它写完并校验通过,就完成一大半了。有命令行习惯的话也可以用dd,但Etcher会校验写入结果,更稳妥。
4.3 第一次上电:看启动日志
把烧好的SD卡插入Ultra96,接好串口模块,再把12V PD电源插上。打开串口终端,设置波特率115200,8N1,然后上电。你会看到类似下面的启动日志序列:BootROM -> FSBL -> ATF -> U-Boot -> Linux内核。
正常会走到Ubuntu登录界面,或者串口出现login提示。默认用户名和密码官方文档里有,常见是ultra96或ubuntu等,以你下载的镜像说明为准。进入系统之后,第一件事是ifconfig或ip addr看网络地址,然后SSH远程登录,以后就不用一直挂着串口了。
如果你接的是DP转HDMI显示器,显示器能点亮桌面的话就更直观了。桌面环境跑起来后,打开终端,输入uname -a,看到带xilinx字样内核版本,就说明系统已经正常跑在Ultra96上了。
4.4 第一个PL侧小实验:写一个简单的AXI GPIO控制
系统跑通之后,下一步就是验证PS和PL之间的通路。这一步需要用到Vivado和Vitis,版本建议用2020.1或更新的稳定版本。具体步骤如下:
- 打开Vivado,创建新工程,选择器件
xczu3eg-sfvc784,这是Ultra96所用ZU3EG的封装。 - 创建Block Design,添加
Zynq UltraScale+ MPSoCIP,然后在配置界面选择Ultra96对应的预设。这一步很关键,因为它会自动把DDR、UART、SD等PS外设配置好。 - 在Block Design中添加一个AXI GPIO IP,配置为输出模式,连到一个PMOD引脚上。然后做地址映射,通常默认地址就行。
- 验证设计、生成顶层HDL、生成bitstream。
- 导出XSA文件,打开Vitis,创建新的平台工程并导入XSA。
- 创建一个Application Project,选Hello World模板,然后添加一小段代码,调用XGpio库函数往LED引脚写入数据。
- 运行程序,能看到LED按预期亮灭。
这段流程我建议一定亲手走一遍。因为它涵盖了Vivado的PL设计、PS配置、地址映射、Vitis应用编译、板级运行整个闭环,把这些串起来之后,你再看官方例程会非常顺畅。
如果你觉得第一次就搞Vivado有点重,可以先在Ubuntu里装PYNQ,然后用Python的overlay接口加载官方提供的base.bit,再通过pynq.GPIO直接控制引脚。这样能省掉工具链学习成本,快速看到效果,适合先建立信心。
5. 踩坑记录与排查速查表
5.1 启动异常排查
我把经常遇到的问题整理成一张表,对照着查会快很多:
| 现象 | 可能原因 | 排查方式 |
|---|---|---|
| 上电后完全没有反应 | 供电不足或电源不匹配 | 确认使用12V/2A PD电源,换线试试 |
| 串口没有任何输出 | 串口线接错、波特率不对 | 确认TX/RX交叉,波特率设115200 |
| 启动卡在FSBL阶段 | SD卡镜像损坏或DDR配置错误 | 重新烧录官方镜像,确认XSA和board预设匹配 |
| 启动卡在U-Boot阶段 | boot.scr或启动脚本文件缺失 | 检查SD卡分区有没有BOOT.BIN和image.ub |
| Linux启动后频繁重启 | SD卡速度太慢或供电不稳 | 换A1级别SD卡,检查电源 |
| 显示器黑屏 | Type-C线不支持DP Alt Mode | 换专用的Type-C转DP线 |
5.2 显示与网络问题
DP输出是很多人会卡住的地方。Ultra96的DP输出走的是USB Type-C口,所以你的线材必须支持DP Alt Mode,不是随便一根Type-C转HDMI线都行。有些Type-C转HDMI线只支持“DP to HDMI”方向,插上电脑输出视频正常,但插Ultra96可能没有反应。建议买质量好一些、明确标注支持DP Alt Mode的转接线。
WiFi连接在Ubuntu下基本是开箱即用。如果发现WiFi搜不到,先确认天线已经接好,再检查内核模块是否加载了wl18xx相关的驱动。蓝牙和WiFi是同一个模组,如果WiFi能连而蓝牙没有,多半是蓝牙服务没起来,systemctl status bluetooth看一下。
5.3 电源与散热教训
Ultra96的功耗不算高,但也不是树莓派那种5V直接喂饱的类型。它需要USB-PD协商到12V,普通手机充电头基本不可用。我试过用5V/3A的充电头供电,结果板子完全没反应,折腾了半天才意识到问题在电源上。所以第一件事永远是确认电源。
散热方面,ZU3EG在跑重负载时温度不容小觑。长期压满四核A53再加PL资源利用率高,芯片表面会很烫。建议加一个主动散热风扇或至少贴一片散热片。若是跑模型推理这种重负载任务,温升更快,不加散热经常会在日志里看到CPU降频提示。
还有一个经验:不要在系统运行时带电插拔PMOD外设和MIPI接口设备。PMOD接口本身没有过流保护,热插拔容易烧坏FPGA引脚。我自己因为这个习惯烧过一个PMOD模块,之后所有外设连接都是断电后再操作,这个习惯建议所有新手养成。
6. 后续可以往哪些方向挖
跑通系统之后,Ultra96的乐趣才刚刚开始。我列几个我觉得值得深入的方向:
- PL加速计算:在PL里实现一个简单FIR滤波器或者矩阵乘法单元,用AXI-DMA把DDR里的数据送进去,算完再写回。这个过程会用到大数据量搬运,对AXI-HP接口理解会更深。
- PYNQ生态:PYNQ框架里有很多现成overlay,比如图像处理、DPU、CNN加速器。把它跑起来,用Jupyter Notebook写Python调用硬件加速,体验一下“软件定义硬件”的思路。
- MIPI摄像头接入:接一个MIPI CSI-2摄像头,做实时图像采集和显示。这个方向会用到Vivado里的MIPI CSI-2 IP、VDMA、DP输出,是视频处理链路的全家桶,也最能体现Ultra96这块板子的设计意图。
- Linux设备树与驱动:把PL侧的自定义外设挂到Linux设备树里,写一个简单的字符设备驱动,通过
/dev节点访问PL寄存器。这一步做完,你对“Linux如何管理硬件资源”的理解会有质的提升。
我个人在实际使用中最大的体会是:Ultra96最宝贵的地方不是性能,而是“完整”。它把ARM、FPGA、Linux、外设接口全部放在一个尺寸很小的板子上,让你能在一套环境里把嵌入式系统从硬件到软件全部打通。这种体验,是单纯玩树莓派或者单纯玩FPGA开发板都体会不到的。
如果你也正准备入坑异构计算,我给一个实在的建议:第一周别急着上复杂算法,先把SD卡启动、串口日志、Ubuntu系统、SSH连入这套基础链路跑顺,然后花一个周末把Vivado到Vitis的完整流程走一遍。这两个基础打牢之后,剩下的就是往上叠应用了。
下一篇我会专门写Ultra96上PS和PL之间跑AXI总线的完整流程,包括Block Design里的连线细节、Vitis里驱动代码的框架、以及Linux下设备树的取舍。这部分坑更多,也更有意思。