1. 为什么Zynq设计里会同时出现MII转GMII和EMIO UART0
我到现在还记得第一次在Zynq上调试MII转GMII时的状态:板子回来,原厂Demo用的PHY芯片是RGMII接口,但我手上这颗PHY只支持MII,我想当然地觉得“MII不就是把GMII的数据位减半吗,直接把引脚接出去不就行了”。结果接是接出去了,PHY的Link状态死活起不来,用示波器看TX_CLK方向才发现,GMII和MII根本不是位宽减半那么简单,两套接口的时钟体系、控制信号对齐方式、甚至数据有效窗口都不一样。
这一章标题里有两个独立配置:一个是MII转GMII的配置,一个是EMIO UART0的配置。它们放在同一章,不是巧合。在Zynq-7000系列里,PS侧外设(GEM以太网控制器、UART串口、SPI、I2C、SDIO等)默认都是走MIO专用引脚,MIO一共54个,分给GEM之后往往就不够用了,以太网PHY又可能只给MII口,这时候就需要用PL侧逻辑做接口转换。而UART0被MIO占用时,也要靠EMIO绕到PL端重新引出去。可以说,这两个配置本质上解决的是同一个问题:PS外设的物理接口不匹配时,怎么用PL把它适配成你需要的形态。
无论你是在做基于Zynq的工业控制板、边缘网关,还是做通信接口测试平台,这一套配置方法都很实用。我尽量把操作链路写得完整,不光是让你能动鼠标,还帮你看懂每一步背后的硬件时序关系。接下来先理清MII和GMII的接口差异,再给配置步骤,然后单独讲EMIO UART0的迁移和验证。
2. 先把MII、GMII这两种接口讲透:位宽、时钟、方向都不同
很多人一看到“MII转GMII”就以为只是把4根数据线变8根,其实错得很远。MII和GMII是两个时代的接口协议,它们的位宽、时钟速率、数据对齐方式、引脚定义都有本质区别。
MII(Media Independent Interface)是10/100Mbps以太网时代的标准MAC-PHY接口。数据线只有4位:TXD[3:0]和RXD[3:0]。发送方向由PHY提供TX_CLK,速率在100Mbps时是25MHz,10Mbps时降到2.5MHz,CPU侧跟着这个时钟把数据打出去。接收方向由PHY提供RX_CLK,同样25MHz或2.5MHz,MAC侧需要跟随这个时钟采数据。MII还有一对很重要的控制线:TX_EN和TX_ER、RX_DV和RX_ER,分别表示数据有效和数据错误。
GMII(Gigabit Media Independent Interface)是千兆以太网时代的接口,数据线变成8位:TXD[7:0]和RXD[7:0],时钟也换成125MHz。但这里有个容易踩坑的点:GMII在1000Mbps下稳定跑125MHz,在100Mbps和10Mbps下,它的数据总线仍然是8位,只是有效数据不是每个周期都有,需要用TX_EN/RX_DV信号来指示,数据实际上是以字节形式批量塞进去的。发送时钟GTX_CLK是从MAC侧输出的125MHz,方向跟MII完全相反。
我用一张表把两个接口的关键参数列出来,方便对照:
| 对比项 | MII | GMII |
|---|---|---|
| 数据位宽 | 4位 | 8位 |
| 支持速率 | 10/100Mbps | 10/100/1000Mbps |
| 发送时钟方向 | PHY提供TX_CLK | MAC提供GTX_CLK |
| 发送时钟频率 | 2.5MHz / 25MHz | 125MHz |
| 接收时钟频率 | 2.5MHz / 25MHz | 125MHz(由PHY恢复) |
| 控制信号 | TX_EN、TX_ER、RX_DV、RX_ER | TX_EN、TX_ER、RX_DV、RX_ER |
| 半双工支持 | 有COL、CRS | 有COL、CRS |
| 典型引脚数 | 约16根 | 约24根 |
做MII转GMII,本质上不是“加速”,而是把一个8位宽、125MHz时钟域的GMII信号,翻译成4位宽、25MHz或2.5MHz时钟域的MII信号。数据宽度变了,时钟域也不一样,这中间必须做跨时钟域缓冲,不能直接把信号拉直连。Xilinx的MII to GMII Converter IP干的就是这件事:它把MAC侧GMII接口和PHY侧MII接口之间的数据宽度、时钟速率、有效信号重新对齐,让一个只支持MII的PHY能接到GMII的MAC上,并且可以正常自协商到10M或100M速率。
再补一句关于接口选择的前因后果。为什么Zynq的GEM外设要配成GMII,而不是直接配成MII?因为Zynq PS侧的GEM虽然理论上支持多种接口模式,但MIO引脚资源非常紧张,MII模式的引脚未必能映射到可用的MIO上,而在PL侧做GMII转MII反而更灵活。另一方面,如果外部PHY是RGMII,那还能用GMII-to-RGMII IP做转换,做法思路差不多,只是RGMII涉及DDR双边沿采样,时序要求更高。本章选择MII场景,主要因为它能帮你把时钟方向和接口转换的底层逻辑理解透,之后切到RGMII也能举一反三。
3. MII转GMII实操:Block Design里的配置顺序与连线
在Vivado里配置MII转GMII,核心步骤是四步:配PS端GEM、添加转换IP、连线、约束引脚。我按工程实操顺序一个个讲,这里用Zynq-7000系列做例子,具体板子上的PHY引脚号请对照你的原理图。
3.1 配置PS端GEM走EMIO并选择GMII
打开你的Block Design,添加ZYNQ7 Processing System IP后,双击进入配置界面。在Peripheral I/O Pins页面里找到Gigabit Ethernet Controller(也就是GEM0或GEM1,取决于你用哪个),把接口类型选为GMII,同时勾选EMIO选项。
这里为什么要走EMIO?因为GMII接口的数据线、控制线加起来二十多根,MIO引脚区根本放不下,而且Zynq的MIO引脚也不是所有外设都能任意映射。勾选EMIO之后,PS侧的GEM GMII信号会全部绕到PL内部,以GEM0_GMII_*的形式出现在Block Design上,这样我们就可以在PL里接IP做转换了。
同时注意,Vivado会自动生成MDIO和MDC引脚,MDIO用于PHY寄存器管理,后面接转换器或PHY时要一起连出去。
3.2 添加MII to GMII Converter IP
在Block Design画布空白处右键,选择Add IP,搜索"MII to GMII Converter"。Vivado自带这个IP,不需要额外下载。
双击IP进入配置界面,重点看几个选项:
- Speed Support:一般选10/100Mbps,因为MII侧本来就不支持千兆。如果你板子的PHY只工作在10/100M,选这个就行。
- Include MDIO:这里有个选择。如果你的PHY需要PS GEM通过MDIO管理,那么建议勾选,让转换器把MDIO透传到PHY侧;如果你直接在PS的MIO上已经有MDIO通道,可能不需要重复引出。
- Slave/Master:通常选Master,由转换器向PHY发起MDIO时钟。
IP添加之后,画布上会出现一个带GMII侧和MII侧信号的模块。GMII侧面向PS GEM,MII侧面向外部PHY。两侧的端口名称不同,很容易混淆,我列一个对应表给你。
| 转换器GMII侧端口 | 方向 | 连接目标 |
|---|---|---|
| gmii_txd[7:0] | 输入 | PS GEM0_GMII_TXD[7:0] |
| gmii_tx_en | 输入 | PS GEM0_GMII_TX_EN |
| gmii_tx_er | 输入 | PS GEM0_GMII_TX_ER |
| gmii_rxd[7:0] | 输出 | PS GEM0_GMII_RXD[7:0] |
| gmii_rx_dv | 输出 | PS GEM0_GMII_RX_DV |
| gmii_rx_er | 输出 | PS GEM0_GMII_RX_ER |
| gmii_rx_clk | 输出 | PS GEM0_GMII_RX_CLK |
| gmii_tx_clk | 输入 | PS GEM0_GMII_GTX_CLK |
| 转换器MII侧端口 | 方向 | 连接目标 |
|---|---|---|
| mii_txd[3:0] | 输出 | 外部PHY的TXD[3:0] |
| mii_tx_en | 输出 | 外部PHY的TX_EN |
| mii_tx_er | 输出 | 外部PHY的TX_ER |
| mii_txd_rx | 输入 | 外部PHY的TXD(对,MII侧发送时钟是从PHY来的) |
| mii_tx_clk | 输入 | 外部PHY的TX_CLK |
| mii_rxd[3:0] | 输入 | 外部PHY的RXD[3:0] |
| mii_rx_dv | 输入 | 外部PHY的RX_DV |
| mii_rx_er | 输入 | 外部PHY的RX_ER |
| mii_rx_clk | 输入 | 外部PHY的RX_CLK |
特别注意MII侧的mii_tx_clk:方向是从PHY输入到转换器,不是从转换器输出。这是因为MII协议规定,发送时钟由PHY负责产生,MAC侧(这里是转换器)必须跟着这个时钟把数据打出去。这个方向和GMII完全相反,很多人第一次就是栽在这里。
3.3 连线与时钟对齐
手动连线时,先把PS的GEM0_GMII_TXD[7:0]等信号接到转换器的gmii_txd[7:0]等端口。时钟部分,把PS的GEM0_GMII_GTX_CLK连到转换器的gmii_tx_clk,这个信号是125MHz的发送时钟。转换器输出的gmii_rx_clk连回PS的GEM0_GMII_RX_CLK。
MII侧则全部做成外部端口(Make External),这些端口最终要约束到FPGA引脚上,连接到PHY芯片。PHY还需要一个复位信号,通常用PL的普通GPIO控制,或者接一个常量。这里建议在PS端留一个GPIO来给PHY复位,方便后面软件里做复位时序。
MDIO/MDC信号也要引出:MDC给PHY提供管理时钟,MDIO是双向数据线。如果你勾选了Include MDIO,那么MDIO信号从转换器引出;如果没勾,就从PS的GEM直接引出。具体看PHY芯片的接线方式,不同板子做法不一样。
3.4 引脚约束
把所有端口从Block Design引出后,打开XDC文件添加物理约束。MII侧信号定义类似这样:
set_property PACKAGE_PIN U18 [get_ports {mii_txd[0]}] set_property IOSTANDARD LVCMOS33 [get_ports {mii_txd[0]}] set_property PACKAGE_PIN U19 [get_ports {mii_txd[1]}] set_property IOSTANDARD LVCMOS33 [get_ports {mii_txd[1]}] set_property PACKAGE_PIN V18 [get_ports {mii_txd[2]}] set_property IOSTANDARD LVCMOS33 [get_ports {mii_txd[2]}] set_property PACKAGE_PIN V19 [get_ports {mii_txd[3]}] set_property IOSTANDARD LVCMOS33 [get_ports {mii_txd[3]}] set_property PACKAGE_PIN W18 [get_ports mii_tx_en] set_property IOSTANDARD LVCMOS33 [get_ports mii_tx_en] set_property PACKAGE_PIN W19 [get_ports mii_tx_clk] set_property IOSTANDARD LVCMOS33 [get_ports mii_tx_clk]引脚号和IO电平都必须以你板卡原理图为准,我这里的引脚只是举例。上板之前重点确认三点:第一,PHY的供电引脚电平,比如常见的3.3V PHY就要配LVCMOS33;第二,MII输入时钟引脚有没有加IBUF,Vivado一般会自动推断,但如果你手动加了引脚约束,注意不要重复约束;第三,PHY复位脚是否需要反极性,很多PHY复位是低有效。
3.5 综合、实现、生成比特流
在Vivado里跑综合和实现,时序报告里重点看mii_tx_clk和mii_rx_clk等输入时钟在IO约束里是否被正确约束。因为这些时钟来自外部PHY,Vivado不知道它们的频率,需要在XDC里补充create_clock约束,否则时序分析会有警告,严重的会导致采样不稳定。
举例:
create_clock -name mii_tx_clk -period 40.0 [get_ports mii_tx_clk] create_clock -name mii_rx_clk -period 40.0 [get_ports mii_rx_clk]40.0对应的就是25MHz时钟周期。如果你的PHY支持10M模式,可以单独加一个2.5MHz的约束,或者用set_clock_groups区分不同模式。这个细节很多教程不提,但实际PHY自协商到10M的时候,时钟频率变了,后面运行时反而容易出问题。
4. UART0从MIO改到EMIO:这不只是改一个勾选框
MII转GMII的配置和EMIO UART0的配置,共同点都是“PS外设往外引”的问题,但侧重点不同。UART0不是接口不匹配,而是引脚被占用或者布局受限,需要绕到PL侧重新走线。
4.1 MIO和EMIO的本质区别
Zynq的PS外设有两种I/O路径:MIO(Multiplexed I/O)和EMIO(Extended Multiplexed I/O)。
MIO是PS专有的物理引脚,共54个,分布在PS I/O banks里,直接连接到PS核心。UART、SPI、I2C、SDIO、USB、GEM等外设都可以通过MIO复用表映射到这些引脚。MIO的好处是零PL资源消耗,延迟低,时序由PS直接保证。坏处是引脚是固定的,比如UART0通常映射到MIO14(TX)和MIO15(RX),一旦这两个引脚被其他外设占用,比如你想在MIO14上接SDIO的时钟,两边就冲突了。
EMIO是从PS延伸到PL的接口。它不直接连接到物理封装外部引脚,而是先进入PL的布线网络,再由FPGA的IO引脚(PL侧的引脚)引出到外部。EMIO的好处是引脚选择非常灵活,你可以通过XDC任意约束到FPGA封装上任何一个可用的IO引脚,相当于把PS外设“接长”到PL。坏处是它消耗PL的布线资源,也增加了引脚约束和电平标准的配置工作。
用表格对比可能更直观:
| 对比项 | MIO | EMIO |
|---|---|---|
| 引脚位置 | PS封装固定引脚 | PL侧任意IO引脚 |
| PL资源消耗 | 无 | 有布线资源和输入输出缓冲消耗 |
| 引脚约束 | 不需要XDC | 必须在XDC中约束 |
| 路由灵活性 | 低 | 高 |
| 延迟 | 低 | 略高,但通常不影响UART功能 |
| 典型场景 | 默认配置、高速要求 | 引脚冲突、特殊布局、电平转换 |
4.2 什么情况下必须把UART0改成EMIO
实际项目里最常见的情况有三种。
第一种,MIO14/15已经被其他外设征用。Zynq的MIO复用表里,SPI、I2C、SDIO几乎都跟UART有引脚交叉,很多工程配置完SDIO和I2C之后,MIO14/15就没有了,这时候UART0必须走EMIO。
第二种,板级布局需要把串口放到特定位置。有些板卡设计希望UART靠近某颗主控芯片,或者板边有特定的接插件,MIO引脚物理位置固定无法改动,只能通过EMIO把信号引到PL端的指定引脚。
第三种,你想在PL里对串口信号做处理。比如加电平转换、做多路选择、加隔离芯片控制,这些逻辑只能在PL端实现,MIO直接连出去就没法插手了。
4.3 Vivado里切换UART0到EMIO的具体操作
在Zynq PS配置界面里,找到Peripheral I/O Pins页面,展开UART0选项,默认会勾选MIO 14和MIO 15两个引脚。取消这两个勾选,然后勾选EMIO那一列。
保存配置后回到Block Design画布,你会看到ZYNQ IP上多出两个端口:UART0_TX和UART0_RX。其中UART0_TX是PS输出到PL的信号,方向是输出,未来要连到PL引脚再到外部;UART0_RX是PL输入到PS的信号,方向是输入,来自外部引脚。
如果芯片支持流控,配置页面里会有UART0的调制解调器信号选项,勾选后还会多出UART0_CTS和UART0_RTS两个端口。CTS和RTS是异步流控信号,CTS是输入,RTS是输出。一般调试不需要流控,可以先不勾。
把UART0_TX和UART0_RX分别做成外部端口。然后在XDC里加物理约束,例如:
set_property PACKAGE_PIN K17 [get_ports uart0_tx] set_property IOSTANDARD LVCMOS33 [get_ports uart0_tx] set_property PACKAGE_PIN K18 [get_ports uart0_rx] set_property IOSTANDARD LVCMOS33 [get_ports uart0_rx]引脚号和IO电平以你的板卡为准。这里有个经常被忽略的问题:引脚位于哪个Bank,Bank的VCCO电压是多少,必须和IO标准匹配。如果你在3.3V的Bank上用了LVCMOS18标准,信号完全不对。
5. EMIO UART0上板验证与串口调试细节
配置改完、比特流生成之后,真正的验证还要在硬件上过一遍。这一节我讲一下从SDK软件驱动到物理连接的完整验证路径。
5.1 生成硬件平台并导入SDK
在Vivado里导出硬件(Export Hardware),勾选Include bitstream,然后Launch Vitis或SDK。创建应用工程时,会看到一个基于当前硬件配置的平台工程,里面已经包含了UART0的驱动。
打开BSP设置,在xparameters.h里能看到UART0的基地址和相关配置。无论UART0走MIO还是EMIO,它的地址都是一样的,驱动代码也完全复用。这也是EMIO的一个隐形好处:软件侧感知不到硬件引脚变化,调试起来省事。
5.2 SDK里初始化UART0并打印
写一个最简单的Hello World验证程序:
#include "xparameters.h" #include "xuartps.h" #include "xil_printf.h" #include "sleep.h" #define UART0_DEVICE_ID XPAR_XUARTPS_0_DEVICE_ID static XUartPs Uart0; int main(void) { XUartPs_Config *Config; int status; Config = XUartPs_LookupConfig(UART0_DEVICE_ID); if (Config == NULL) { return -1; } status = XUartPs_CfgInitialize(&Uart0, Config, Config->BaseAddress); if (status != XST_SUCCESS) { return -1; } XUartPs_SetBaudRate(&Uart0, 115200); xil_printf("EMIO UART0 Test Start\r\n"); while (1) { xil_printf("Hello from Zynq EMIO UART0\r\n"); usleep(1000000); } return 0; }这段代码做的事很简单:查找UART0配置,初始化驱动,设置波特率115200,然后每秒打印一行。
但要注意:xil_printf默认输出到哪个串口,取决于BSP里标准输入输出设备的配置。在Vitis的BSP设置里,确认stdout绑定到了UART0,而不是UART1或者其他外设。如果BSP默认是uart1,打印会没有反应,你还需要用XUartPs_Send直接往UART0寄存器里发数据来验证。
5.3 硬件连接与常见接线错误
EMIO UART0的TX/RX引脚约束到FPGA引脚之后,硬件上需要接一个电平转换板,常见USB转TTL模块就行。
接线的核心原则是:交叉连接。Zynq的uart0_tx要接USB转TTL模块的RXD,Zynq的uart0_rx要接模块的TXD。很多人直接把同名信号连在一起,结果收不到任何数据。
另外,共地一定要接好。USB转TTL模块的GND和Zynq板卡的地必须连通,否则信号电平完全是浮动的,串口助手可能偶尔显示乱码或者无响应。
我用一个表整理常见接线问题:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 串口助手收不到任何数据 | TX/RX接反 | 对调TX/RX再试 |
| 收到乱码 | 波特率不一致或者共地不良 | 确认PS端波特率和PC端一致,检查GND连接 |
| 按复位后能收到一次乱码 | TX引脚悬空导致上电抖动 | 检查UART0_TX引脚约束是否正确,防止浮动 |
| 只在JTAG连接时正常 | USB转TTL模块供电不足 | 给模块独立供电,或换带隔离的模块 |
| 数据显示正常但偶发丢字节 | 使用了流控但没有接CTS/RTS | 关闭流控,或者把CTS拉高 |
5.4 PL内部回环验证的小技巧
上板之前,我特别推荐先在PL内部做一个UART0 TX到RX的回环验证。具体做法:在Block Design里不把UART0_TX和UART0_RX引出到物理引脚,而是直接把这两个端口短接。
这时候跑SDK里的程序,你会看到一个现象:程序里xil_printf打印的内容并没有真正输出到电脑,但是你在代码里用XUartPs_Recv读UART0的接收FIFO,可以读到自己发出去的数据。这说明PS侧UART0的外设链路是通的,问题只出在外部接线。
排除了PS侧故障之后,再把TX/RX解开引出,就能比较有把握地说,剩下的问题一定在PL引脚约束或者外围电路上。这个技巧我每次调试新板卡都用,能省下很多排查时间。
6. 这个组合配置的避坑清单:时钟方向、引脚冲突、软件适配
最后这一部分,我把MII转GMII和EMIO UART0配置过程中最容易踩的坑统一整理一下。这些坑我不是第一次踩了,有些甚至是第二次、第三次踩完才真正记住。
6.1 MII的TX时钟方向问题
这是MII转GMII里最典型的坑。拿到一个MII PHY,第一反应是“MAC提供时钟给PHY”,因为GMII就是这么干的。但MII协议比GMII早,它在100Mbps以太网时代设计的时候,TX时钟就是由PHY产生并回传给MAC的,目的是让MAC侧的部分逻辑可以跟PHY完全同步,简化设计。
这个方向的差异直接决定你的信号连接方案。如果你把MII的mii_tx_clk当作输出去驱动PHY,PHY根本不会响应;反过来,如果你把PHY的mii_tx_clk接错到GMII MAC的GTX_CLK引脚上,两个时钟域完全对不上。记住:GMII时钟由MAC出,MII时钟由PHY出,转换器的职责就是把这两个时钟域之间的数据搬运过去。
6.2 MII转GMII不改变速率上限
不少人在设计评估阶段会有个错觉:既然GMII是千兆接口,那我把MII PHY挂到这个转换器后面,是不是也能自协商到千兆?答案是不行。MII物理层最大速率就是100Mbps,转换器只是把MAC侧的GMII协议降级适配到MII PHY,数据通路仍然受限于PHY的能力。
如果你的产品确实需要千兆吞吐,老老实实选RGMI、SGMII或者直接GMII的PHY。MII转GMII只适合那些“主控已经是GMII/GEM,但PHY只有MII”的存量硬件方案。
6.3 EMIO引脚约束遗漏导致信号悬空
UART0被设置成EMIO之后,如果XDC里漏了某个引脚的约束,Vivado也不会报错,它会在实现阶段把这个引脚放到一个随机位置,或者当成无负载的信号。这种情况最迷惑人:复位之后软件跑起来,串口有时有输出有时没有,似乎看运气。
我的处理方式是,约束文件里把每个EMIO端口都用get_ports单独约束一遍,然后写一个简单的Tcl脚本检查所有端口都有物理位置分配:
set unmapped_ports [get_ports -quiet -filter {PACKAGE_PIN == ""}] if {$unmapped_ports ne ""} { puts "Unmapped ports: $unmapped_ports" }在Vivado的Tcl Console里跑一下,能快速发现遗漏的引脚约束。
6.4 PHY复位和上电时序
MII PHY芯片一般都有复位引脚,很多芯片要求上电后保持低电平至少10ms,再释放复位。如果你直接把PHY复位引脚接到一个常量,或者在上电瞬间复位没拉足够长的时间,PHY内部的自协商可能起不来,表现为MDIO读不到PHY ID,或者MII侧没有任何时钟输出。
正确做法是,把PHY复位引脚连到PS的GPIO EMIO或者PL端GPIO,由软件在初始化早期控制复位时序。比如在GEM驱动初始化之前,先把GPIO拉低,延时50ms,再拉高。
6.5 SDK里标准输入输出绑定错误
UART0走EMIO之后,软件工程里的BSP标准输出默认不一定绑定到UART0。特别是使用Vitis 2023.2之后的版本,平台工程里需要显式配置stdin/stdout设备。很多报“串口没反应”的问题,恰恰是这里配置错了,硬件链路完全正常。
在BSP设置里找到stdout配置项,选择ps7_uart_0,保存后重新生成应用工程。然后重新编译烧写,通常一次就能通过。
6.6 综合时序约束要区分以太网时钟模式
如果你的PHY支持10M和100M两种模式,MII的TX_CLK和RX_CLK可能是25MHz或2.5MHz。在XDC里写死25MHz的create_clock约束,跑10M模式时会留下很大的时序裕量,或者导致Vivado在实现阶段把时序优化到不合适的方向。
我的建议是,用set_clock_groups把两个模式的时钟定义成asynchronous,告诉工具这两个时钟不会同时存在,避免跨时钟域路径被过度约束。如果工程简单,也可以直接用set_false_path跳过MII时钟路径的时序分析,但这种方式对复杂设计不推荐。
7. 一个可复用的集成架构思路
把MII转GMII和EMIO UART0独立验证通过之后,你会发现它们其实可以整合进同一个设计里,而且整合方式非常有规律。
整体框架大致是:Zynq PS作为主控,GEM0通过EMIO引出GMII信号,PL内接MII to GMII Converter IP,转换器MII侧连接到板载PHY芯片;UART0也通过EMIO引出,PL端口约束到串口接插件的TX/RX引脚。PS侧跑Linux或者 bare-metal,用标准的GEM驱动和UART驱动,软件几乎不用改动。
如果你用的是PetaLinux(对应热搜里的PetaLinux 2025.1),还需要在设备树里把GEM和UART0的配置对齐到实际PL设计。设备树里ethernet-phy节点的reg要跟MDIO地址一致,UART0节点确认status = "okay"。这个阶段最容易出现的怪问题是:系统启动后,串口控制台没有任何输出,但SDK里裸机程序明明能打印。通常原因是U-Boot的默认串口配置指向了uart1,而不是uart0,需要改U-Boot环境变量bootargs里的console=ttyPS0。
至于制作SD卡的boot.bin、boot.scr、image.ub这些环节,跟本章的EMIO UART0没有直接关系,但软件引导时串口在哪一路会影响你观察启动日志的位置。我的经验是,在PetaLinux里先把UART0作为stdout-path,这样从FSBL到U-Boot到内核的日志都能打印在同一路串口上,调试事半功倍。
做这个整合设计时,我个人的习惯是按照“先物理引脚,再IP连接,然后软件配置”的顺序推进:先把所有PHY和串口的引脚约束检查一遍;再在Block Design里连线,每连一组信号就核对着方向;等实现通过后,再去Vitis或者PetaLinux里配驱动。每层都确认无误之后,剩下的问题往往只在硬件焊接和信号完整性上,那种问题反而靠示波器比靠软件好解决得多——这也是我做了这么多块板子之后最大的体会。