1. 项目概述与整体设计思路
1.1 核心需求解析
Zynq开发中,MII转GMII的配置和EMIO UART0的配置,属于典型的“FPGA逻辑(PL)与ARM处理器(PS)协同”问题。我在实际调试中遇到这个需求,往往是因为板上PHY芯片的接口模式和PS端默认的MIO管脚分配对不上,或者是串口数量不够用、管脚被占用,需要把UART从MIO挪到EMIO上。
先说清楚这两个东西到底解决什么问题:
MII转GMII:Zynq的GEM(Gigabit Ethernet MAC)控制器本身支持多种接口模式,其中RGMII是板上最常用的,MII是百兆模式,GMII是千兆模式。MII和GMII在数据位宽、时钟频率、管脚数量上完全不同,如果PHY芯片只支持GMII接口而控制器配置成了MII,或者反过来,网络就会彻底不通。所以“MII转GMII”本质上不是硬件上做协议转换,而是在Vivado里把GEM的接口模式配置正确,同时把PHY芯片的工作模式也匹配上,两边对齐了,链路才能起来。
EMIO UART0:Zynq的PS端UART控制器默认可以走MIO管脚,也可以走EMIO管脚。MIO管脚是PS端专用引脚,数量有限(BGA封装一般是54个),而EMIO是PS连接到PL的接口,数量更多(64个),可以灵活约束到FPGA的任意IO上。当MIO资源被其他外设挤占,或者是板卡设计时把串口接到了PL侧引脚上,就必须把UART0从MIO切到EMIO,然后在XDC文件中做管脚约束。
这个需求的典型应用场景包括:自研板卡的串口调试口设计、以太网PHY芯片选型后的接口适配、以及需要同时跑多路串口或以太网的自定义硬件平台。
1.2 方案选型的逻辑推演
为什么要把这两个配置放在一起讨论?因为它们在Zynq开发流程里属于同一层级的操作——都是在Vivado里的Zynq PS配置界面(Zynq Block Design)中完成的,而且都涉及PS-PL接口的打通。
我在多个项目里实测下来,最稳妥的做法是:
- 在Vivado Block Design中添加Zynq PS核,双击打开配置界面
- 在PS-PL Configuration页面中,把UART0的接口从MIO改为EMIO
- 在I/O Configuration页面中,把GEM的接口模式从RGMII切换为MII或GMII
- 生成比特流后,在XDC中额外约束EMIO的管脚位置
这个方案的优点是:完全绕开了硬件改板,纯逻辑层面就能解决问题。缺点是:EMIO的信号路径经过PL内部路由,会引入额外的时延和时序约束要求,对高速接口(如GMII的125MHz时钟)来说,走线约束要格外小心。
对比一下其他方案:有人会倾向于直接用MIO管脚,但MIO数量有限且不可灵活布局;也有人会在PL侧用IP核重新实现一个UART控制器(如AXI UART Lite),但这样会占用额外的LUT资源,而且和PS端的中断、DMA机制对接更麻烦。所以在资源足够、需要PS原生外设功能的场景下,改成EMIO是最合理的。
这里有个容易踩的坑:MII转GMII并不是在PS配置界面里勾选一下就完事的。GEM的接口模式由两个地方共同决定:PS内部的GEM配置,以及外部PHY芯片的接口模式。如果PHY芯片是MII接口,而GEM配成GMII,数据完全对不上,协议层再怎么调都没用。所以先确认PHY芯片型号和支持的接口模式,再动手配置。
1.3 适用人群与前置知识
这篇配置笔记适合以下三类人阅读:
- 正在用Zynq做板卡开发的硬件工程师,遇到了网络不通、串口无输出的问题
- 想把PS端外设映射到PL管脚上的FPGA逻辑工程师
- 从纯软件转过来做Zynq SoC开发的朋友,需要理解PS-PL接口的基本概念
需要具备的前置知识不多,但有几个基础概念必须先弄清楚:MIO和EMIO的区别(这在后面的章节详细说明)、Zynq PS端外设控制器的基本结构、以及Vivado Block Design的基本操作流程。如果你对这些概念不太熟悉,建议先走一遍Vivado的Hello World例程,再回来看这篇配置笔记。
2. MIO与EMIO的核心差异与选择依据
2.1 MIO和EMIO到底是什么
很多刚接触Zynq的人会对MIO和EMIO这两个缩写一头雾水。简单说,MIO(Multiplexed I/O)是PS端专用的多功能引脚,直接连接到PS的SoC内部,不经过PL,所以访问延时低、时序简单;EMIO(Extended Multiplexed I/O)则是PS外设控制器和PL之间的桥接接口,信号从PS侧出去后,要先经过PL内部路由,再约束到PL的IOB上。
拿UART0举例,当配置为MIO模式时,串口的TX、RX两条线直接接到芯片的MIO引脚上,软件直接操作PS寄存器就能收发数据,完全不涉及FPGA逻辑。当配置为EMIO模式时,UART0的TX、RX信号会出现在PL侧的接口列表里,你需要手动在Block Design中创建端口,并将其约束到FPGA的物理引脚上,然后综合、实现、生成比特流。
MIO和EMIO的关键区别,我用一张表列出来:
| 对比项 | MIO | EMIO |
|---|---|---|
| 信号路径 | PS内部直连引脚 | 经过PL内部路由后再到引脚 |
| 引脚数量 | 54个(BGA封装,具体看型号) | 64个(可自由分配到PL IO) |
| 灵活度 | 固定位置,不可自由布局 | 可约束到任意PL引脚 |
| 访问延时 | 低,无需时序约束 | 中,需要XDC约束 |
| 占用FPGA资源 | 不占用 | 占用少量LUT和布线资源 |
| 典型场景 | 调试串口、SDIO、USB等 | MIO被占满或需要灵活布局 |
从这张表能看出来,MIO适合“固定管脚、要求低延时”的场景,EMIO适合“管脚灵活、不受PS约束”的场景。在实际板卡设计中,很多硬件工程师为了布局方便,会把串口、以太网等接口放到PL侧,这时就必须用EMIO。
2.2 为什么以太网接口更需要谨慎选择
以太网接口比串口复杂得多。MII接口需要14根信号线,GMII需要24根信号线,RGMII只要12根,但时序要求更高。如果你在配置时把MII转成GMII,不仅数据线数量翻倍,时钟频率也从25MHz(MII的TX/RX时钟)提升到125MHz(GMII的GTXCLK/RXCLK)。
这里有一个工程上的现实问题:MIO引脚的数量和位置是固定的,你很难在MIO上找到足够多、且走线合理的引脚来布GMII接口。所以GMII接口通常必须通过EMIO来引出,这就意味着以太网信号要经过PL内部布线,在125MHz时钟下,时序收敛会成为一个大问题。
我在做第一个千兆以太网项目时,就遇到过GMII信号经过PL后时序不收敛的情况。后排查下来,问题出在XDC中的管脚约束太随意,没有对GTXCLK做专门的时钟约束,导致综合工具无法正确推断时钟路径。后来在XDC中显式创建了GTXCLK的生成时钟约束,问题才解决。
2.3 MIO与EMIO的选型决策树
在实际项目中怎么快速决定用MIO还是EMIO?我总结了一个简单的决策流程:
- 先查芯片封装,确认MIO引脚数量和现有占用情况
- 看外设接口是否需要高速信号(以太网、USB、SDIO等),如果高速接口数量多,优先考虑走EMIO
- 看板卡布局,如果PHY芯片、串口芯片的位置离PL引脚近,走EMIO可以缩短走线长度
- 如果MIO资源充足,且外设对延时敏感(如调试串口、实时控制信号),优先走MIO
- 如果MIO资源紧张,或者外设信号需要连接到PL内部逻辑(比如UART数据要做协议解析),走EMIO
这个决策树的本质是平衡“性能、灵活度、开发复杂度”三者的关系。MIO是性能最好但最不灵活的方案,EMIO是灵活但需要更多开发工作的方案。
实操心得:如果是调试用的串口,我建议优先用MIO,因为调试串口对管脚位置不敏感,而且MIO模式不需要任何FPGA逻辑参与,出了问题时排查路径更短。只有当MIO确实被占满时,才考虑EMIO方案。
3. MII转GMII的配置实操全流程
3.1 配置前的硬件与工程准备
动手配置之前,先把准备工作做足,免得做到一半才发现环境不对。
首先是确认PHY芯片型号。不同的PHY芯片支持的接口模式不同,有些只支持RGMII,有些是GMII/MII可配置的。常见的有Marvell 88E1512(支持RGMII/SGMII)、Realtek RTL8211E(支持RGMII)、TI DP83867(支持RGMII/SGMII)等。如果你用的是GMII接口的PHY,还需要确认PHY芯片的接口模式配置引脚(如RX_DV、TX_EN等)是否正确设置。
其次是确认Vivado版本。Zynq的Block Design配置界面在不同版本中略有差异,但核心选项位置基本一致。我的测试环境是Vivado 2023.1,配置路径在Zynq PS的I/O Configuration页面中。
最后是确认参考时钟。GMII接口的GEM控制器需要125MHz的参考时钟,这个时钟通常由PS的时钟输出或外部晶振提供。在配置界面里,你需要确认GEM的时钟源选择正确,否则即使接口模式对了,也没有时钟信号。
我做项目时习惯先画一张硬件接口映射表,把PHY芯片的信号名和Zynq的引脚信号一一对应起来,然后检查是否存在信号反转、差分对匹配等问题。这个习惯帮我避免了很多低级错误。
3.2 在Vivado Block Design中配置GEM接口
下面进入核心配置环节。我以Vivado 2023.1为例,详细说明操作步骤。
第一步,创建或打开一个包含Zynq PS核的Block Design。在Diagram窗口中双击Zynq PS核,打开Re-customize IP对话框。
第二步,在左侧导航栏选择I/O Configuration,展开Ethernet选项。你会看到GEM控制器的配置项,不同的Zynq型号在这里的选项名称略有不同。比如Zynq-7000系列通常显示为Gigabit Ethernet Controller,Zynq UltraScale+系列则是GEM。
第三步,在Ethernet接口下,将接口模式从默认的RGMII切换为GMII或MII。注意,这里的选择不是随便选的,必须和PHY芯片的实际接口模式完全一致。如果你的PHY支持MII和GMII双模式,通过PHY芯片的配置引脚来选择。此时,GEM侧的配置必须和PHY的物理配置一致。
第四步,确认GMII接口信号出现在Block Design中。切换模式后,你会看到GEM的接口信号从12根左右的RGMII信号变为24根左右的GMII信号。这些信号包括GTXCLK、GTXEN、GTXD[7:0]、RXDV、RXCLK、RXD[7:0]、RXER、TXER、MDIO、MDC等。
第五步,为GEM接口创建外部端口。在GMII接口的信号上右键选择Make External,创建对应的端口,供后续XDC约束使用。
这里有一个非常重要的细节:GMII的GTXCLK和RXCLK是不同方向的时钟信号。GTXCLK是GEM发送数据时提供给PHY的时钟,频率125MHz(千兆模式)或25MHz(百兆模式);RXCLK是PHY接收数据时提供给GEM的时钟,由PHY根据收到的数据流恢复,频率同样是125MHz或25MHz但来源不同。这两根时钟在XDC中需要分别约束,且RXCLK是外部异步时钟,处理不好会导致时序问题。
3.3 GMII接口的XDC约束详解
配置完Block Design后,生成HDL、综合、布局布线,然后就需要写XDC约束了。GMII接口的XDC约束是很容易被忽略但极其关键的环节。
首先是管脚位置约束。你在Block Design中创建的GMII外部端口,需要一一映射到FPGA的物理引脚上。引脚位置通常由PCB布线决定,所以这一步是照抄板卡的原理图。比如:
# GMII TX接口 set_property PACKAGE_PIN AB12 [get_ports gmii_txd[0]] set_property PACKAGE_PIN AB11 [get_ports gmii_txd[1]] # ... 其他信号其次是电平标准约束。GMII接口的IO电平通常为1.8V或2.5V,取决于PHY芯片的IO电源域电压。Zynq的PL侧IO支持多种电平标准,需要根据PHY芯片的供电电压来设置。如果电平不匹配,轻则信号质量差、偶尔丢包,重则烧毁PHY芯片。
最后是时钟约束。GMII接口的时序约束是整个工程中最容易出问题的部分。GTXCLK是GEM输出给PHY的时钟,如果你在外部端口上把它当作普通信号处理,综合工具无法正确分析这条路径的时序。正确做法是为GTXCLK创建一个生成时钟约束,并将其设为发送数据的参考时钟:
# 假设GTXCLK连接到了某个MMCM的输出,或者是直接的外部时钟 create_generated_clock -name gem_gtxclk -source [get_pins mmcm/CLKOUT0] -divide_by 1 [get_ports gtclk_out]RXCLK的约束更特殊。它是PHY输出的异步时钟,和PS端的系统时钟没有任何相位关系。对于跨时钟域的RX数据,GEM内部会做同步处理,但XDC中必须把这个时钟定义为异步时钟,否则时序分析会报告大量violation:
# 将RXCLK定义为输入时钟 create_clock -name gem_rxclk -period 8.000 [get_ports rxclk] # 设置异步时钟域 set_clock_groups -asynchronous -group [get_clocks -include_generated_clocks gem_rxclk] -group [get_clocks -include_generated_clocks gem_gtxclk]实操心得:第一次做GMII接口时,我因为没有约束RXCLK,导致综合报告里出现几百条时序violation,看着吓人。后来我把RXCLK和GTXCLK设置为异步时钟域后,violation瞬间清零。要知道,GMII的RXCLK和GTXCLK虽然都叫125MHz,但它们是不同晶振/不同PHY恢复出来的时钟,频率可能有几十ppm的偏差,必须按异步处理。
3.4 验证GMII接口是否配置成功
配置完成后,怎么验证接口是否真的通了?我一般分三步做:
第一步,检查phy链路状态。在FSBL(First Stage Boot Loader)阶段或Linux内核启动时,观察GEM的链接状态寄存器(GEM_NWCTRL、GEM_NWSR)的link up位。如果链路没起来,寄存器值不对,说明物理层有问题,可能是接口模式不匹配、XDC约束错误或PHY芯片配置不对。
第二步,检查MAC层通信。在U-Boot中使用ping指令测试网络连通性。U-Boot中的网络驱动会完成GEM的初始化,如果U-Boot下能ping通,说明MAC层和PHY层的配置基本正确。
第三步,跑吞吐率测试。在Linux下使用iperf3测试网络吞吐。如果协商出来是千兆模式,实测吞吐应该能达到900Mbps以上(考虑到协议开销);如果只有百兆模式,吞吐在90Mbps左右。通过这个数据可以反向确认GEM和PHY协商的速率是否符合预期。
有时候U-Boot下ping不通,但Linux下能通,这是因为Linux下的PHY驱动会重新做PHY初始化,覆盖了U-Boot的配置。所以最终验证以Linux下的ethtool信息为准。我遇到过PHY芯片的接口模式引脚被上下拉电阻配置错,导致PHY工作在RGMII模式但GEM配置为GMII的诡异情况,U-Boot直接死机,Linux却启动且自动协商到了百兆模式。这种问题不通过ethtool查看实际协商速率,根本发现不了。
3.5 百兆与千兆模式下的信号时序差异
配置MII和GMII时,还有一个容易忽略的问题:百兆模式和千兆模式的时序完全相同,但频率不同。
- MII模式:TX_CLK和RX_CLK都是25MHz,数据线4位(TXD[3:0]、RXD[3:0]),一个时钟周期传输一个4位数据,组成一个字节需要两个时钟周期。
- GMII模式:GTX_CLK和RX_CLK都是125MHz,数据线8位(TXD[7:0]、RXD[7:0]),一个时钟周期传输一个8位数据。
- RGMII模式:TXC和RXC都是125MHz(千兆)或25MHz(百兆),数据线4位,DDR模式下上下沿各传输一次,一个时钟周期传输一个字节。
看到没有,RGMII和GMII在千兆模式下虽然都是125MHz、8位有效数据,但物理线上的信号数量和边沿触发方式完全不同。RGMII只有4根数据线,用DDR双沿传输;GMII是8根数据线,单沿传输。
如果你把GEM配置为GMII但PHY工作在RGMII模式,那么在GEM看来,TXD[3:0]这4根线上有数据,RXD[7:4]则完全没信号,而且没有有效的TXC/RXC时钟对齐关系,最终表现就是链路起不来,或者协商到最低速率。
这种低级错误在我见过的项目里出现过不止一次。所以配置前一定要打开原理图,确认PHY芯片的接口模式配置引脚(比如一些PHY芯片的MODE[2:0]引脚)在高电平还是低电平,以此判断PHY实际工作在什么模式,然后让GEM的配置跟着走。
4. EMIO UART0的配置实操全流程
4.1 Block Design中启用EMIO UART0
以太网搞定了,我们来看第二半场——把UART0从MIO切换到EMIO。
打开Zynq PS的配置界面,在Peripheral IO Pins标签页里,展开UART选项。你会看到UART0和UART1两个控制器,每个控制器都可以选择使用MIO引脚或EMIO引脚。
默认情况下,UART0和UART1都挂在MIO上。要切换到EMIO,只需要把UART0对应的MIO引脚配置取消勾选,然后在下面的EMIO部分勾选UART0接口。这个操作的直观效果是,Block Design中会出现uart0_tx和uart0_rx两个外部端口(有些版本可能显示为UART0_TX和UART0_RX,大小写因版本而异)。
这里有一个设计上的技巧:如果你不想取消MIO的UART0,而是希望MIO和EMIO同时使用UART0,这种操作是不被支持的。每个UART控制器只能选择MIO或EMIO之一,不能同时使用。如果需要两路串口,就应该用UART0走EMIO、UART1走MIO,或者反过来。
配置完成后,生成Block Design并创建HDL Wrapper。此时你会发现,在顶层模块中多出了两个端口:uart0_rx和uart0_tx。这两个端口就是UART0信号经过PL内部路由后,从PS端引出的接口。
4.2 UART0 EMIO的XDC管脚约束
和GMII接口一样,EMIO UART0也需要管脚约束。
在XDC文件中,将uart0_tx和uart0_rx信号约束到FPGA的PL引脚上:
set_property PACKAGE_PIN L20 [get_ports uart0_tx] set_property IOSTANDARD LVCMOS18 [get_ports uart0_tx] set_property PACKAGE_PIN M20 [get_ports uart0_rx] set_property IOSTANDARD LVCMOS18 [get_ports uart0_rx]注意几个细节:
方向千万别搞错。
uart0_tx是PS输出到外部设备的信号,pin约束为输出或inout;uart0_rx是外部设备发给PS的信号,pin约束为输入。如果方向和板卡上串口芯片的走线接反了,串口无论如何都不会有输出。电平标准必须匹配。如果外部串口芯片是1.8V供电,就设LVCMOS18;如果是3.3V,就设LVCMOS33。Zynq PL侧的IO bank电压决定了支持的电平标准,所以约束前先查一下所在bank的VCCO电压。强制使用错误的IOSTANDARD轻则信号乱码,重则损坏器件。
如果板卡上有上拉或下拉电阻,要注意默认电平状态是否符合UART空闲电平要求。UART的TX和RX在空闲状态下都是高电平(逻辑1),如果外部硬件把RX拉低了,会导致检测到起始位,接收到垃圾数据。这个问题很难查,因为软件层面看不出任何异常。
4.3 串口收发验证与常见现象分析
约束完成后,重新综合、实现、生成比特流,然后就可以进行实机验证了。
第一步,连接串口线。将USB转串口模块的TX连接到Zynq的uart0_rx引脚,USB转串口模块的RX连接到Zynq的uart0_tx引脚。注意是交叉连接,我见过不少人把TX接TX、RX接RX,结果数据全发到了自己身上,串口当然没有输出。
第二步,在终端软件(如MobaXterm、SecureCRT、PuTTY)中新建串口会话,设置波特率、数据位、停止位等参数。Zynq UART默认配置通常是115200、8N1,但有些BSP会把默认波特率改成9600或57600,需要看启动打印信息。
第三步,上电启动,观察串口输出。如果一切正常,你会看到BootROM的启动信息、FSBL的打印信息以及U-Boot的启动日志。
如果串口没有输出,不要慌,按下面的排查顺序来:
- 检查XDC中的管脚方向对不对
- 用万用表测量TX引脚的电压,空闲状态应该是高电平
- 检查终端软件的波特率设置
- 用示波器查看TX引脚是否有信号跳变
我遇到过一个比较刁钻的问题:在Vivado里配置了EMIO UART0,XDC约束也做了,但比特流下载后串口依然无输出。后来发现是Block Design中的EMIO端口没有勾选Enable选项,导致信号根本没有连接到PL。在Vivado的某些版本中,你需要在PS的配置界面的EMIO页面手动勾选UART0的使能选项,否则即使端口出现在接口列表里,也不会有实际信号。
还有一个常见问题是,UART的波特率在EMIO模式下比MIO模式下更容易出错。原因是EMIO路径经过PL内部布线,增加了信号延时和抖动。如果板上有两个串口同时工作,相互之间的干扰可能导致误码。此时可以尝试降低波特率,或者检查PCB的串口走线是否远离高速信号线。
4.4 EMIO UART0的中断与DMA配置
如果你的软件方案中,串口数据量大,需要以中断或DMA方式接收数据,那么还需要在PS端配置相应的中断控制器。
Zynq的UART控制器支持两种数据接收方式:轮询和中断。在EMIO模式下,中断机制和MIO模式完全一样,都是通过UART控制器的中断寄存器触发的,区别只在于物理信号路径。所以从软件角度看,EMIO UART0和MIO UART0对驱动完全透明,驱动不需要做任何修改。
但如果你在PL侧使用了AXI UART Lite,那就完全不是一回事了。AXI UART Lite是一个独立的IP核,有自己的寄存器接口和中断线,它的中断信号通过AXI Interconnect连接到PS的中断控制器(GIC)的PL端中断输入(IRQ_F2P)。在这种架构下,软件的驱动模型就完全不同了,不能在Linux下直接使用/dev/ttyPS0设备节点,而需要为AXI UART Lite编写专门的驱动或者使用uartlite驱动。
所以EMIO UART0的价值就在于:它让你在保持PS原生UART驱动不变的前提下,获得管脚布局的灵活性。软件工作量几乎为零。
4.5 硬件设计中的管脚选择建议
最后聊聊EMIO UART0在硬件设计时的管脚选择。
理论上,EMIO可以连接到PL侧任意一个user IO引脚上。但实际项目中有几个原则需要遵守:
第一,优先选择靠近目标连接器、走线短的引脚。UART信号虽然只有9.6Kbps到4Mbps的速率,不是高频信号,但走线过长仍然会引入串扰和衰减,在高速波特率下会造成误码。
第二,避免选择靠近时钟引脚、差分对的引脚。这些引脚附近的电容耦合效应可能影响UART信号的噪声容限。
第三,避免选择已分配给DDR、PCIe、MIPI等高速接口的引脚。这些引脚通常有特殊的电平标准和阻抗要求,强行用作UART可能违反bank的电气规范。
第四,管脚约束时考虑bank电压。如果选中的PL引脚所在bank的VCCO是1.8V,而外部串口芯片工作在3.3V电平,那么就需要添加电平转换电路,否则无法通信。
我在自研板卡时,一般会预留一组专门的UART测试引脚(通常4~6个,包含TX、RX、RTS、CTS),全部放在一个比较偏的位置,方便后期调试和飞线。这些引脚在原理图上就预留好EMIO的路径,后期软件配置时只需要在XDC中修改管脚约束即可。
5. 常见问题与排查技巧实录
5.1 网络不通的逐步排查法
MII转GMII配置完成后,网络不通是最常见的问题。我把自己实战中的排查顺序整理成了下面的速查表,照着做可以省下大量时间:
| 排查步骤 | 操作内容 | 确认方法 |
|---|---|---|
| 1 | 确认PHY芯片接口模式 | 查看原理图中PHY的MODE引脚配置,确认是MII、GMII还是RGMII |
| 2 | 确认GEM配置和PHY一致 | 在Vivado中查看GEM接口模式,和PHY的模式必须相同 |
| 3 | 确认XDC管脚约束 | 检查GMII所有信号的PACKAGE_PIN是否和原理图一致 |
| 4 | 确认电平标准 | 检查IOSTANDARD是否和PHY的IO电源电压匹配 |
| 5 | 确认时钟约束 | 检查GTXCLK、RXCLK是否正确约束,时钟域是否分组 |
| 6 | 确认PHY复位 | 检查PHY的复位引脚是否被正确释放,有些PHY需要几百毫秒的复位时间 |
| 7 | 检查U-Boot下的link状态 | 在U-Boot中运行mdio read命令读取PHY寄存器,确认link状态位 |
| 8 | 检查Linux下的协商速率 | 在Linux中运行ethtool eth0,确认速率、双工模式 |
这里面有个小细节很多人不知道:PHY芯片的复位不仅仅是一个GPIO控制问题,还牵扯到PHY的配置加载时序。很多PHY芯片在复位释放后,需要从配置引脚或EEPROM中加载工作模式配置,这个过程需要几个毫秒到几十毫秒。如果GEM在PHY还没有完成配置时就开始初始化,可能会读到错误的PHY ID或协商失败。SoC启动时的初始化顺序,寄存器配置不要急着做。
5.2 时序收敛问题的实战解法
GMII接口经过PL内部路由后,时序收敛是最大的难点。我个人的经验是:如果在综合报告中出现大量的GMII时序violation,先别急着调整XDC的约束,而是先检查以下三个地方。
第一,检查GTXCLK的约束方式。GMII的GTXCLK是GEM的输出时钟,它的约束方式直接决定了发送数据的时序分析边界。如果你没有为GTXCLK创建生成时钟,工具会把它当作普通数据信号处理,导致无法正确分析。这个我在前面已经详细说过,不再重复。
第二,检查GMII数据信号与GTXCLK的相位关系。在GMII模式中,发送数据信号GTXD[7:0]和GTXEN以GTXCLK为参考,在时钟上升沿稳定。如果PCB走线长度不一致,或者PL内部路由长度差异大,可能导致数据信号在时钟沿附近变化,造成时序violation。解决方法是调整XDC中的set_output_delay约束,给数据信号和时钟之间留出足够的建立时间窗口。
第三,考虑降低GMII的工作频率。如果你在测试中发现GMII链路不稳定、时序收敛困难,而且你的实际业务对带宽要求不高,可以把GEM配置为百兆模式(MII),这样时钟频率降到25MHz,时序收敛压力大幅减小。我有一次在原型调试阶段,就是用这个方法快速把网络调通,后面再做性能优化。
5.3 串口乱码与无输出的根因分析
串口问题虽然比以太网简单,但排查起来有时候更头疼,因为串口输出“没有”和“乱码”是两种不同的根因方向。
串口完全无输出的排查:
- XDC管脚约束错误:TX、RX方向接错,或者引脚位置不对
- EMIO端口在Block Design中未使能
- 波特率不匹配:软件设置的波特率和实际硬件收到的波特率不一致
- 启动流程问题:FSBL或U-Boot中串口初始化失败
串口乱码的排查:
- 波特率漂移:如果板上晶振精度差,波特率可能出现偏差,导致乱码
- 电平标准不匹配:1.8V和3.3V混用可能导致信号幅度不足
- 地线不稳:串口通信对地线比较敏感,如果USB转串口模块和Zynq板卡的地有压差,容易乱码
- 中断冲突:如果系统中有其他外设抢占CPU资源,UART接收缓冲区溢出,导致数据丢失和乱码
针对乱码问题,我有个小技巧:把USB转串口模块用一根短线独立接地,不要通过电脑的USB地线间接共地。很多廉价USB转串口模块的隔离不好,电脑地线上有噪声会直接影响串口信号质量。
5.4 软硬件协同调试的建议顺序
最后分享一个我自己总结的软硬件协同调试顺序。Zynq开发最怕的是软硬件边界不清,出了问题不知道是硬件问题还是软件问题。我的做法是:
第一步,先验证硬件通路。用纯逻辑的方法测试PL引脚是否正常工作。比如把UART的TX引脚设置为普通GPIO输出,手动拉高拉低,用示波器或万用表测量引脚电压变化。如果引脚电压能跟随GPIO的变化,说明PL引脚到连接器的物理通路是好的。
第二步,验证PS配置。在裸机工程中只初始化UART0和GEM,不加载操作系统。裸机环境排查问题最简单,因为整个系统可控,不需要考虑操作系统调度、驱动框架等干扰因素。
第三步,验证Linux下行为。如果裸机下功能正常但Linux下不正常,问题大概率出在设备树配置或驱动上。此时检查设备树中的UART节点和以太网节点,确认正确的compatible字符串、时钟资源、中断号等。
这个顺序的本质是:先隔离物理层问题,再隔离逻辑层问题,最后才处理软件层问题。每前进一步都确保前面的环节都是正常的,这样排查效率最高。
实操心得:在Zynq开发中,设备树(device tree)中EMIO UART0的配置特别容易出错。因为设备树里并不需要显式指定“EMIO”这个模式,它只需要指定使用的UART控制器编号(如
serial0指向UART0),而UART0走MIO还是EMIO是完全由硬件(比特流)决定的。所以如果比特流中配置了EMIO UART0但设备树中alias没设置好,Linux内核可能默认使用UART1做控制台,导致你看到串口0没有输出。这个细节我吃了不少亏。
6. 经验总结与踩坑备忘
6.1 一定要记住的五个关键点
在完成MII转GMII和EMIO UART0的配置后,我总结了五个最关键的经验,希望你能少走弯路:
第一,先确认PHY芯片接口模式再配置GEM,这是所有网络调试的前提。接口模式不匹配,后面的工作全是白费。
第二,GMII的RXCLK必须按异步时钟处理。这不仅是工具规范问题,更是数据正确性的根基。把RXCLK和GTXCLK设置为异步时钟域,可以减少大量无意义的时序violation,也符合真实硬件的行为。
第三,EMIO UART0的XDC中,方向约束比位置约束更容易出错。TX是输出、RX是输入,这个看起来简单,但在实际项目中因为命名不规范(有人把uart0_tx命名为uart0_tx_from_ps),经常搞混。
第四,设备树中的alias配置直接影响Linux控制台输出。UART0走EMIO后,设备树中必须确保stdout-path指向正确的serial节点,否则Linux启动时控制台可能默默换到了别的串口上。
第五,调试过程中多用示波器和万用表,不要一上来就怀疑软件。Zynq的调试串口信号用逻辑分析仪抓一下,能省去大量猜谜时间。
6.2 值得后续扩展的优化方向
这次配置解决了基础通信问题,但真正要提升系统稳定性和性能,还有几个方向值得深入:
管脚约束的时序优化:GMII的125MHz信号经过PL内部布线时,如果布局工具自动选的路由距离过长,可以手动在XDC中添加区域约束,把GEM的IO逻辑限制在特定bank。
Linux驱动的性能调优:如果网络吞吐不达标,可以检查Linux内核中GEM驱动的tx-ring-size和rx-ring-size参数,适当调大可以提升高吞吐场景下的性能。
多路串口的扩展方案:如果一个UART不够用,可以考虑UART0走EMIO、UART1走MIO,实现双串口调试。如果还不够,才考虑用AXI UART Lite来增加更多串口通道。
6.3 最终实操建议
根据我实际调试Zynq十年左右的经验,最后给几条最实在的建议:
- 做板卡设计时,把调试串口和以太网的信号预留到容易测量的位置,尽量留出测试点。真到调试的时候,这些测试点能救命。
- 每次修改Block Design配置后,保存前都检查一下生成的外部端口数量和信号名,防止漏掉某个信号导致综合报错。
- 遇到问题时保留完整的综合日志和时序报告。很多玄学问题在经过多次综合后,都能从报告中找到线索。
- 在团队协作中,把XDC约束文件和Block Design配置的变更记录保持同步。我们曾经因为同事改了XDC但忘记更新Block Design,导致版本不一致,浪费了一整天的调试时间。
个人在实际操作中还有一个体会:很多Zynq外设配置问题,根因都不在配置本身,而在配置之外的细节。比如PHY的复位时序、电源的上电顺序、时钟芯片的锁定时间,这些硬件层面的因素往往比软件配置更影响系统的稳定性。做嵌入式开发,软硬结合的能力很重要,能心平气和地把信号测量、寄存器读取、日志分析这些基本功做扎实,比会背一百个配置项更有价值。希望这篇配置笔记能帮你少踩几个坑。