
上个月调一块Zynq板子卡在两个看起来特别基础、实际上坑特别多的配置上一个是PS端的GEM得接一颗百兆PHY要把MII信号从EMIO引出来另一个是板载调试串口不够用想把UART0也放到PL侧走EMIO。两个需求单看都不难但串在一起做的时候MII和GMII的引脚映射、MIO和EMIO的选择逻辑、顶层端口命名、XDC约束怎么写每一步都有知识点。这篇文章就把GEM的MII模式配置和EMIO UART0的配置完整走一遍重点放在“为什么这么配”和“实际踩过的坑”上适合正在用Vivado做Zynq硬件平台、裸机或Linux底板的工程师参考也适合刚接触Zynq PS外设引脚规划的新手。1. 先搞明白GMII、MII、RGMII的引脚与时序配置GEM之前建议先把MAC和PHY之间的接口关系理清楚。很多人在Vivado里直接选MII但对MII和GMII引脚的变化没有概念最后布线时把TX_CLK和RX_CLK搞反或者把本来由PHY提供的时钟当成输出来做约束白白折腾一整天。这里花点篇幅把三种接口的引脚和时序讲透。1.1 三种接口的引脚对比GMII是8位并行接口主要用于千兆以太网数据线分TXD[7:0]和RXD[7:0]还带TX_EN、TX_ER、RX_DV、RX_ER、GTX_CLK、RX_CLK、CRS、COL、MDC、MDIO。这里有个容易记混的点GMII的发送时钟叫GTX_CLK由MAC输出频率固定125MHz不管实际跑的是千兆、百兆还是十兆GTX_CLK都是125MHz低速时候靠TX_EN配合重复编码来降速不像MII那样把时钟降下来。RGMII是GMII的DDR降脚版本数据线收窄到RXD[3:0]和TXD[3:0]时钟还是125MHz但数据在时钟上下沿都采样能实现千兆。RGMII引脚少很多PCB走线压力小所以现代板卡上用得多。MII是传统的10/100M接口数据线TXD[3:0]和RXD[3:0]。MII的发送时钟叫TX_CLK注意这个方向和GMII完全相反它是由PHY提供的频率100M时25MHz10M时2.5MHz。RX_CLK同样由PHY提供频率和TX_CLK一致。整个接口除了数据线还有TX_EN、RX_DV、CRS、COL、MDC、MDIO。接口类型数据宽度发送时钟来源发送时钟频率典型速率信号数量GMIITXD[7:0]/RXD[7:0]MAC输出GTX_CLK125MHz固定10/100/1000M24根左右RGMIITXD[3:0]/RXD[3:0]MAC输出TX_CLK125MHz固定10/100/1000M14根左右MIITXD[3:0]/RXD[3:0]PHY输出TX_CLK100M时25MHz10M时2.5MHz10/100M16根左右1.2 GMII转MII到底转什么“MII转GMII”这个说法听起来像是在说需要做一个协议转换但对Zynq来说更常遇到的情况是GEM控制器本身支持GMII和MII模式软件配置好之后硬件引脚上直接出对应的MII信号。也就是说你不需要在外面的逻辑里做数据转换GEM内部已经把8位数据通路、时钟分频、TX时钟方向这些都处理好了。那什么时候才真的需要“转换”只有一种情况你的板子Layout已经把MAC侧接口固定成了GMII的引脚位置而外接PHY只支持MII这时候才需要在PL里做一个GMII到MII的桥接。这种桥接逻辑的核心有几点数据路径从8位收窄到4位发送时每个时钟周期取TXD[3:0]接收时把RXD[3:0]扩展到RXD[7:0]。时钟频率从125MHz降到25MHz/2.5MHz同时TX时钟方向要反过来GMII下是MAC输出GTX_CLKMII下是PHY输出TX_CLK桥接逻辑里要把这个方向处理好。控制信号要做相应映射GMII的TX_ER/RX_ER在MII里对应TXD[3]/RXD[3]在特定条件下的复用。不过从我实际经验看除非是改板兼容老PHY否则没必要走这条弯路。Zynq的GEM直接在寄存器里选择通过EMIO引出MII信号该出的信号全部在PL侧出现省时省力。1.3 MII模式下的关键时序参数MII模式下重点关注的时序参数其实不多但每一个都可能影响硬件是否跑得通TX_CLK/RX_CLK频率100M时25MHz10M时2.5MHz误差按IEEE 802.3要求控制在±100ppm内。TX数据输出相对于TX_CLK上升沿的建立时间不同PHY要求不太一样一般不小于5ns。RX数据相对于RX_CLK上升沿的建立保持时间通常在5ns/5ns左右具体看PHY数据手册。在实际做XDC约束的时候这些时序参数一般不需要手动填写因为GEM的MII信号通过EMIO引到PL后Vivado会按普通IO时序去分析。但如果EMIO走线特别长或者经过了多级组合逻辑最好在XDC里对时钟路径加set_max_delay之类的约束避免时序收敛问题。2. MIO和EMIO的区别以及为什么这个项目选了EMIOZynq的外设引脚分成MIO和EMIO两种通路很多人容易混淆。理解这两者的本质区别是配置GEM和UART的前提。2.1 MIO与EMIO的本质区别MIO是PS专用引脚数量固定总共54个分成Bank 0/1/2/3。MISO直接连接到PS内部的外设控制器上电后由PS侧的配置决定功能。它的好处是延迟低、不占用PL资源、不需要FPGA逻辑参与但缺点是引脚位置固定一旦PCB布局和Bank电压不合适改起来很麻烦。EMIO则是把PS外设的控制信号引到PL内部的“虚拟引脚”上可以理解为PS外设通过PL借道输出。使用EMIO后信号不会自动出现在物理引脚上你必须在PL顶层看到这些端口然后用XDC约束到实际的FPGA引脚。这个过程既占用了PL的布线资源也会增加一些信号延迟但灵活性大幅提升。从使用复杂度上对比对比项MIOEMIO物理位置PS专用引脚固定54个通过PL逻辑引出到FPGA引脚PL资源占用无占用PL引脚与布线资源上电可用性配置即用需要PL配置成功后才有效延迟低比MIO稍高取决于布线路径灵活性低引脚固定高可自由选择FPGA引脚配置复杂度相对简单需要XDC约束需注意IO标准2.2 GEM的MII模式为什么必须关注EMIOZynq-7000的GEM控制器在RGMII模式下GEM0可以走MIO 16到MIO 27这12个引脚不需要PL参与。但如果你要使用MII模式情况就不一样了MII信号需要通过EMIO引出。原因是MII模式需要的信号数量多而且GEM的硬件引脚定义里MII模式只支持从EMIO通道输出。实际在Vivado的PS配置里可以看到选择ENET0之后下面还有一层接口模式选择里面有RGMII、GMII、MII。当你选择MII时原来的MIO引脚选项会消失取而代之的是一组出现在PL侧的信号包括ENET0_TX_CLK、ENET0_TXD[3:0]、ENET0_TX_EN、ENET0_RX_CLK、ENET0_RXD[3:0]、ENET0_RX_DV、ENET0_CRS、ENET0_COL以及MDC/MDIO。这意味着你必须到PL侧把这些信号接到物理引脚上再连到PHY。2.3 UART0的MIO和EMIO选择逻辑UART0默认的MIO位置是MIO14和MIO15分别对应TX和RX这也是调试串口最常见的接法。如果板子上MIO14/15被其他功能占了或者你希望把串口接到FPGA一侧方便布线就可以使用EMIO方式。需要注意UART0走EMIO时只有两个信号UART0_TX和UART0_RX没有RTS/CTS。如果要用流控需要在PL里额外设计逻辑或者改用UART1的MIO。另外同一个外设不建议同时启用MIO和EMIO两套引脚因为这会在软件侧产生冲突驱动也不知道往哪边发。回到我这次的项目MIO区域已经被NAND、SD、还有其他外设占得差不多了UART0的默认MIO引脚也分给了其他功能所以UART0走EMIO是唯一的合理选择。而GEM0要接的百兆PHY是MII接口也必须走EMIO。于是两个外设的信号都放在了PL侧接下来要处理的事情就变成了在Vivado里正确配置PS然后在XDC里把EMIO信号约束到实际的FPGA引脚。3. Vivado实操GEM的MII模式配置与引脚约束下面进入正题用Vivado实际操作一遍。我这里用的是Vivado 2023.1版本不同版本界面文字可能稍有差异但配置路径基本一致。3.1 Vivado工程与PS配置入口新建工程后添加ZYNQ7 Processing System IP。双击打开PS配置界面左侧导航栏第一个大项就是Peripheral I/O Pins所有PS引脚的复用配置都在这里。假如你还没有启用ENET默认情况下ENET0显示为灰色未选择状态。点击ENET0前的复选框选中之后下方会出现接口模式选择。这里要注意Vivado的逻辑是“先勾选使能再选接口模式”很多人在第一步就漏了展开二级菜单导致后面找不到EMIO信号。3.2 GEM接口模式选择MII/GMII/RGMII展开ENET0后你会在它下面看到ENET0接口模式的选项。可以选RGMII、GMII或者MII。具体选择要跟硬件设计一致如果你的PHY是RMII或MII接口就选MII如果PHY是RGMII接口选RGMII如果PHY走GMII选GMII。选MII之后你会看到Peripheral I/O Pins界面里MIO那个区域的ENET0相关引脚选项被清空了而右侧PS-PL Configuration里面多出一组ENET0的信号。这些信号就是EMIO引出的MII接口信号。生成的Block Design里Zynq PS IP上会多出一组接口名字一般是类似MDIO、ENET0_MII这样的接口里面包含TXD[3:0]、RXD[3:0]、TX_CLK、RX_CLK、TX_EN、RX_DV、CRS、COL、MDC、MDIO等信号。Create HDL Wrapper之后顶层模块里会出现一组端口。不同Vivado版本里这组端口可能叫enet0_mii_rtl_0_tx_clk、enet0_mii_rtl_0_txd、enet0_mii_rtl_0_rx_clk等也有可能叫ENET0_TX_CLK、ENET0_TXD这种更直白的名字。建议打开生成的顶层Wrapper文件确认一下不要凭记忆写XDC。3.3 从EMIO到物理引脚的XDC约束这一步是最容易出问题的地方。EMIO信号到了PL顶层之后如果不在XDC里约束Implementation会报一堆IO not placed错误。MII接口信号比较多建议在XDC里把整组信号一次性列出。以顶层端口名enet0_mii_rtl_0_*为例约束写法大概是这样set_property PACKAGE_PIN AB12 [get_ports {enet0_mii_rtl_0_tx_clk}] set_property IOSTANDARD LVCMOS33 [get_ports {enet0_mii_rtl_0_tx_clk}] set_property PACKAGE_PIN AB13 [get_ports {enet0_mii_rtl_0_txd[0]}] set_property PACKAGE_PIN AB14 [get_ports {enet0_mii_rtl_0_txd[1]}] set_property PACKAGE_PIN AB15 [get_ports {enet0_mii_rtl_0_txd[2]}] set_property PACKAGE_PIN AB16 [get_ports {enet0_mii_rtl_0_txd[3]}] set_property IOSTANDARD LVCMOS33 [get_ports {enet0_mii_rtl_0_txd[*]}]实际引脚号以你的原理图为准别照抄我的示例。有一点必须特别注意MII模式下TX_CLK是PHY输出的输入信号也就是FPGA里的input方向更准确说是PS的GEM接收PHY的时钟所以不要在端口方向里把它声明成output。在XDC里一般不需要显式设置端口方向但你自己写顶层HDL的时候容易搞混。还有CRS和COL这两个信号。MII标准里这两个是半双工模式用的很多PHY芯片也会把它们引出来但如果你做的是全双工网络这两个信号可以不接。不过在某些GEM配置下不连接CRS/COL可能会导致控制器误判链路状态建议按PHY手册操作。如果你用的PHY没有CRS/COL引出可以看GEM是否允许禁用相关检测或者在Block Design里把对应端口悬空并加注释说明。3.4 时序约束经验EMIO路径天然比MIO路径长因为信号要先经过PL内部布线再出去。如果MII时钟频率不高25MHz的时序压力不大一般都能收敛。但如果遇到时序违例优先检查是不是IO约束里的负载电容没设对或者参考电压的IOSTANDARD不对。还有一个我踩过的坑如果MII信号经过了一个外部PHY而PHY的时钟是25MHz晶振直接供给的那么TX_CLK和RX_CLK之间存在相位差是正常的GEM能容忍一定范围内的相位差。不要因为这个原因去调整XDC里的输入延迟约束否则可能适得其反。4. Vivado实操EMIO UART0的配置与验证UART0的EMIO配置比GEM简单很多但同样有几个细节需要处理。4.1 UART0的EMIO信号与配置步骤还是在Peripheral I/O Pins界面找到UART0勾选启用。此时注意右侧PS-PL Configuration中会出现UART0的EMIO信号包括UART0_TX和UART0_RX。如果你同时还想让UART1工作那又是另一组信号不要混。点击UART0时下方还有MIO引脚的选择如果你需要走EMIO不要勾选MIO14/MIO15对应的选项。启用了EMIO后MIO对应的UART0相关引脚会变灰。Create HDL Wrapper之后顶层端口会有一组uart0信号比如uart0_rtl_0_tx和uart0_rtl_0_rx也可能是UART0_TX、UART0_RX。老规矩打开Wrapper确认命名。XDC约束示例set_property PACKAGE_PIN AE6 [get_ports {uart0_rtl_0_tx}] set_property IOSTANDARD LVCMOS33 [get_ports {uart0_rtl_0_tx}] set_property PACKAGE_PIN AE7 [get_ports {uart0_rtl_0_rx}] set_property IOSTANDARD LVCMOS33 [get_ports {uart0_rtl_0_rx}]UART不管波特率多少信号都是单端电平约束比较简单。但要注意IOSTANDARD必须和连接的电平转换芯片或UART转USB芯片的电平匹配常见的是LVCMOS33或LVCMOS18选错会导致烧录器读不到电平变化。4.2 验证UART0 EMIO是否工作正常配置完成后裸机环境下直接用Xilinx SDK或Vitis的Hello World模板选择UART0作为stdout。如果没有任何输出先别急着查代码按这个顺序排查确认Block Design里Zynq PS的UART0已经勾选EMIO而不是MIO。确认顶层Wrapper里确实看到了uart0_rtl_0_tx和uart0_rtl_0_rx端口。确认XDC里已经约束了这两个端口并且综合后没有IO not placed的警告。用示波器或逻辑分析仪看uart0_rtl_0_tx引脚有没有波形翻跳。如果没有说明信号根本没从GEM里出来回第一步检查。Linux环境下UART0走EMIO的话设备树里仍然使用seriale0000000这个节点不需要特殊修改。唯一的区别是引脚地址被映射到了PL侧设备树看不到具体引脚也不需要加pinctrl。但有一个前提PL侧的约束必须保证信号能到实际引脚否则Linux启动时串口完全无输出。实际验证时我习惯先让系统在u-boot阶段打印如果u-boot能通过UART0输出说明硬件链路已经通了后面Linux的问题大概率是设备树或内核配置问题。5. 常见问题与排查速查表配置上踩过的坑我整理成一个速查表方便以后遇到类似问题直接对号入座。5.1 以太网MII配置的典型问题选了MII后EMIO信号在顶层看不到。这个大概率是Block Design没有重新生成Wrapper或者Vivado版本里端口名挂在子模块里。先Validate Design然后右键Block Design点Create HDL Wrapper选择Let Vivado manage wrapper更新一下再看。TX_CLK方向搞反。MII模式下TX_CLK是输入经常有人当成输出。接线和约束之前先看PHY手册确认到底是PHY提供TX_CLK还是MAC提供。标准MII是PHY提供但个别PHY可以配成MAC时钟模型不要想当然。CRS/COL影响链路状态。部分PHY和GEM组合下CRS/COL不接会导致link up后无法收发。如果PHY没有引出CRS/COL建议看GEM寄存器里是否有强制忽略相关信号的选项。时序不过。EMIO路径长MII时钟虽然只有25MHz但如果中间穿过太多组合逻辑建立时间可能不满足。可以加一个寄存器级打拍或者调整布局布线。PHY地址和MDIO没问题但PHY不工作。检查PHY的硬件配置引脚有没有设好比如百兆模式匹配电阻以及PHY复位引脚是否长时间处于复位状态。这是硬件问题不是软件配置问题。5.2 UART0 EMIO的典型问题顶层没有uart0_rtl端口。检查是否在Vivado界面里确实勾选了UART0并使能EMIO重新生成Wrapper。串口无输出但MIO方式正常。确认硬件上是不是把UART0的TX/RX接到了PL引脚对应的位置以及电平转换芯片的方向TX要接输入侧RX接输出侧接反也会没数据。波特率不对。走EMIO不影响波特率但如果PS时钟频率配置改动过要确认UART的输入时钟是否还在正确分频范围否则纯软件层面会出现乱码。运行Linux时console设置无效。检查内核参数里console ttyPS0,115200是否匹配设备树以及u-boot启动参数中的bootargs是否正确。5.3 排查顺序与工具使用建议遇到外设完全不工作我的排查顺序一般是先看硬件链路。EMIO信号有没有出现在物理引脚上用万用表或示波器测电平不要一上来就改代码。再查配置。把Block Design里PS配置逐项截图和硬件原理图对照确认MII接口模式、UART0的EMIO路径都没选错。再看约束。综合报告里有没有unplaced IO、有没有Unconstrained Ports警告。最后才是软件。裸机用最简单的Hello WorldLinux用最小设备树减小变量。在PL侧做调试时强烈建议把MII的关键信号比如TX_CLK、TX_EN、TXD[3:0]以及UART的TX、RX用ILA或者Debug Hub工具抓一下。EMIO信号在PL内部走ILA可以直接看到比示波器量引脚方便得多尤其当PCB走线短、引脚不好夹探头的时候。带着ILA跑一次复位观察UART0 TX在发送数据时有没有电平翻转观察MII接口在PHY上报link up后有没有TX_EN脉冲。这两个信号没问题基本可以确定PS外设配置和EMIO路径已经走到了正确位置剩下的问题要么在PHY芯片本身要么在软件协议栈。我个人在实际项目里还有一个习惯在顶层设计里把EMIO信号集中命名比如统一加emio_前缀这样无论代码还是XDC都一目了然。另外EMIO引脚的Bank电压要和实际连接的外设一致MII接口PHY电平是2.5V还是3.3V直接影响XDC里的IOSTANDARD设置这个必须跟硬件原理图确定好。最后补一个和后续开发强相关的建议如果你准备用PetaLinux做系统镜像GEM和UART0的EMIO配置会影响设备树生成但前提还是Vivado工程里硬件设计正确。很多人Boot.bin生成出来以后串口无输出最后发现是硬件配置阶段就把UART0放到了EMIO却没约束引脚导致PL加载前完全没有打印。所以我的做法是调试阶段除了EMIO串口尽量在板上保留一个MIO串口做早期引导打印等PL加载完成后再切换到应用串口处理起问题会从容很多。