
RGMII这个接口说它是FPGA工程师的必修课一点不为过。但凡做过以太网通信的项目十有八九绕不开它。但真正让人头疼的不是RGMII协议本身有多复杂而是它的时序约束——尤其是input delay和output delay这两个参数填错了要么功能直接跑不通要么跑通了但偶尔丢包更麻烦的是温度一变、批次一换就出问题。我见过太多项目在实验室跑得好好的一到现场就间歇性通信异常最后查来查去就是RGMII的时序余量不够。这篇内容面向的是已经上手FPGA、正在调试或即将调试RGMII接口的工程师。我会从RGMII的接口特性讲起把input/output delay约束的计算逻辑拆开揉碎然后给出Xilinx和Intel两大平台上的具体约束写法最后聊一聊时序收敛的实操技巧和常见坑。不管你是用Artix-7、Kintex-7还是Cyclone系列核心思路是通用的。1. RGMII接口的时序特征与约束难点1.1 RGMII和GMII的本质区别GMII接口大家应该不陌生8位数据宽度的并行接口发送和接收各用一路125MHz时钟数据在时钟的上下沿都不采样只在单沿采样时序关系非常清晰。RGMII做的事情很简单——把8位数据砍成4位但速率不能降怎么办用双沿采样DDR。125MHz时钟的上下沿各传4位等效还是8位速率维持1000Mbps。这个砍位宽、提频率的操作带来了几个直接后果。第一时钟和数据的关系从单沿对齐变成了双沿对齐时序窗口直接减半。第二PCB走线的不对称性影响被放大因为数据和时钟之间的偏斜skew在双沿采样下更容易吃掉建立/保持余量。第三FPGA内部需要用IDDR来接收、用ODDR来发送这引入了额外的内部延迟。RGMII标准里其实定义了两种模式一种是时钟和数据同相输出接收端需要把时钟延迟约2ns再采样另一种是FPGA内部对时钟做90度相移让时钟边沿对准数据窗口中心。实际项目中PHY芯片通常支持内部延迟配置FPGA侧也可以做延迟具体谁来做、做多少这就是时序约束要解决的问题。1.2 为什么RGMII的时序约束容易出错很多人第一次写RGMII约束的时候直接抄了别人的set_input_delay和set_output_delay数值结果发现能用但不知道为什么能用。换一个PHY芯片或者换一块板子同样的约束就出问题了。根本原因在于input/output delay的数值不是凭空来的它取决于三个因素——PHY芯片的时序参数、PCB走线延迟、以及FPGA内部触发器的建立保持时间。这三个因素中任何一个变了约束就要跟着调整。更麻烦的是RGMII是DDR接口上升沿和下降沿的时序可能不对称。PHY芯片的数据手册里通常会给出Tskew数据相对时钟的偏斜、Tsetup建立时间、Thold保持时间这些参数但很多工程师不会去仔细看这些参数直接用一个经验值就上了。实验室常温下能跑通不代表全温度范围、全电压范围、所有芯片批次都能跑通。还有一个常见的误区认为约束只要满足时序报告不报红就行。实际上时序报告不报红只说明在当前约束下没有违规但如果约束本身写得不对比如input delay设小了时序报告可能显示满足但实际硬件上就是采不到正确数据。1.3 约束前必须搞清楚的三件事在动手写约束之前有三件事必须确认清楚第一PHY芯片的工作模式。有些PHY芯片默认开启了内部延迟比如Marvell 88E1512的RGMII模式可以通过寄存器配置延迟有些则需要FPGA侧来做。如果PHY已经加了2ns延迟FPGA侧再加延迟就会导致采样点偏移过大。第二PCB走线等长情况。RGMII的时钟和数据走线是否做了等长控制通常要求时钟和数据之间的偏斜控制在±50mil以内对应约±7.6ps的延迟差以FR4板材、微带线约150ps/inch估算。如果走线偏斜很大约束里需要把这个偏斜考虑进去。第三FPGA内部是否使用了IDELAY/ODELAY。7系列FPGA的IOB里集成了可编程延迟单元可以用来微调采样点。如果用了IDELAY约束的写法会有所不同。提示建议在项目初期就把PHY芯片的RGMII时序参数表找出来把Tsetup、Thold、Tskew这些关键值记下来。后面计算input/output delay的时候直接用比凭经验猜靠谱得多。2. Output Delay约束的计算逻辑与实操2.1 Output delay的物理含义Output delay约束告诉综合工具FPGA输出的数据到达外部PHY芯片时相对于随路时钟数据需要提前多少时间稳定建立时间要求以及需要保持多少时间保持时间要求。对于RGMII发送方向FPGA通过ODDR输出4位数据TXD[3:0]和一路发送时钟TXC。PHY芯片在TXC的上升沿和下降沿采样TXD。所以output delay的参考时钟是TXC被约束的对象是TXD。set_output_delay的数值计算基于PHY芯片的接收端时序要求。具体来说最大延迟max对应建立时间要求。数据必须在时钟边沿到来之前至少Tsetup时间稳定。所以max output delay Tsetup - Tskew如果数据比时钟晚到Tskew为正。最小延迟min对应保持时间要求。数据必须在时钟边沿之后至少Thold时间保持不变。所以min output delay -Thold - Tskew。这里的Tsetup和Thold是PHY芯片接收端的参数Tskew是PCB走线导致的时钟与数据之间的偏斜。2.2 一个具体的计算实例假设我们用的PHY芯片数据手册给出以下参数以1000Mbps RGMII为例参数符号典型值单位数据建立时间Tsetup1.0ns数据保持时间Thold1.0ns时钟到数据偏斜Tskew0.2ns时钟周期Tcyc8.0nsPCB走线方面假设TXC和TXD做了等长处理偏斜控制在±20mil以内对应约±3ps可以忽略不计。那么output delay的计算如下max output delay Tsetup - Tskew 1.0 - 0.2 0.8nsmin output delay -Thold - Tskew -1.0 - 0.2 -1.2ns在Xilinx的XDC约束中写法是这样的# RGMII发送方向output delay约束 set_output_delay -clock [get_clocks rgmii_txc] -max 0.8 [get_ports {rgmii_txd[*]}] set_output_delay -clock [get_clocks rgmii_txc] -min -1.2 [get_ports {rgmii_txd[*]}]注意这里用的是TXC时钟作为参考时钟。在Xilinx的约束中需要先创建TXC的时钟约束create_clock -name rgmii_txc -period 8.0 [get_ports rgmii_txc]如果是DDR接口还需要加上-clock_fall选项因为数据在时钟的上升沿和下降沿都会变化set_output_delay -clock [get_clocks rgmii_txc] -max 0.8 [get_ports {rgmii_txd[*]}] -clock_fall -add_delay set_output_delay -clock [get_clocks rgmii_txc] -min -1.2 [get_ports {rgmii_txd[*]}] -clock_fall -add_delay2.3 Intel平台上的写法差异Intel原Altera的SDC约束语法和Xilinx的XDC有些不同。同样的参数在Intel Quartus的SDC中写法如下# 创建TXC时钟 create_clock -name rgmii_txc -period 8.0 [get_ports rgmii_txc] # 设置output delay set_output_delay -clock rgmii_txc -max 0.8 [get_ports {rgmii_txd[*]}] set_output_delay -clock rgmii_txc -min -1.2 [get_ports {rgmii_txd[*]}] # DDR情况下需要分别约束上升沿和下降沿 set_output_delay -clock rgmii_txc -max 0.8 [get_ports {rgmii_txd[*]}] -clock_fall set_output_delay -clock rgmii_txc -min -1.2 [get_ports {rgmii_txd[*]}] -clock_fallIntel的SDC中-add_delay选项不是必须的因为Quartus会自动处理多边沿约束的叠加。但Xilinx的XDC中如果不加-add_delay后面的约束会覆盖前面的。2.4 实操中容易忽略的细节第一ODDR的约束。在7系列FPGA中RGMII发送通常用ODDR原语实现。ODDR的D1和D2分别对应上升沿和下降沿的数据。约束的时候set_output_delay是针对端口port的不是针对ODDR内部的。但综合工具需要知道ODDR的时钟关系所以TXC的create_clock必须正确。第二时钟的相移。如果FPGA内部对TXC做了90度相移通过MMCM/PLL那么create_clock的时候需要反映这个相移。比如create_clock -name rgmii_txc -period 8.0 -waveform {2.0 6.0} [get_ports rgmii_txc]这里-waveform {2.0 6.0}表示上升沿在2ns下降沿在6ns相当于相移了90度。第三输出延迟的符号。min output delay通常是负值因为保持时间要求数据在时钟边沿之后还要保持一段时间。如果算出来是正值说明数据需要在时钟边沿之前就变化这在物理上是不合理的需要检查参数是否搞反了。注意output delay的数值不是越大越好也不是越小越好。max值设得太大工具会拼命优化数据路径让它更早到达可能导致保持时间违规min值设得太小太负工具会拼命优化数据路径让它更晚变化可能导致建立时间违规。两个值要匹配PHY芯片的实际要求。3. Input Delay约束的推导与板级验证3.1 Input delay和output delay的对称性Input delay约束的是FPGA接收外部数据的时序。对于RGMII接收方向PHY芯片发送RXC接收时钟和RXD[3:0]接收数据FPGA用IDDR在RXC的上下沿采样RXD。Input delay的物理含义是外部数据到达FPGA端口时相对于RXC时钟数据已经提前了多少时间对应max或者还需要保持多少时间对应min。计算逻辑和output delay是对称的但方向相反max input delay数据在时钟边沿之前到达的最大时间。对应PHY芯片的输出建立时间加上PCB走线延迟。min input delay数据在时钟边沿之后到达的最小时间。对应PHY芯片的输出保持时间加上PCB走线延迟。具体公式max input delay Tco_max Tpcb_skewmin input delay Tco_min - Tpcb_skew其中Tco是PHY芯片输出数据相对于输出时钟的延迟Tpcb_skew是PCB走线偏斜。3.2 从PHY数据手册提取关键参数继续用前面的PHY芯片举例假设数据手册给出参数符号最小值最大值单位输出时钟到数据延迟Tco0.52.5ns输出时钟周期Tcyc8.08.0ns输出时钟占空比—45%55%—PCB走线方面假设RXC和RXD之间的偏斜为±30mil对应约±4.5ps可以忽略。那么input delay的计算如下max input delay Tco_max 2.5nsmin input delay Tco_min 0.5ns在Xilinx XDC中的写法# 创建RXC时钟 create_clock -name rgmii_rxc -period 8.0 [get_ports rgmii_rxc] # 设置input delay set_input_delay -clock [get_clocks rgmii_rxc] -max 2.5 [get_ports {rgmii_rxd[*]}] set_input_delay -clock [get_clocks rgmii_rxc] -min 0.5 [get_ports {rgmii_rxd[*]}] # DDR情况下需要加上clock_fall set_input_delay -clock [get_clocks rgmii_rxc] -max 2.5 [get_ports {rgmii_rxd[*]}] -clock_fall -add_delay set_input_delay -clock [get_clocks rgmii_rxc] -min 0.5 [get_ports {rgmii_rxd[*]}] -clock_fall -add_delay3.3 板级验证约束写完只是开始约束写完了时序报告也过了但这不代表硬件上一定能正常工作。板级验证是必不可少的一步。我通常的做法是先用一个简单的回环测试loopback验证RGMII的基本通信功能。具体来说FPGA发送已知的数据包PHY芯片回环到接收端FPGA检查接收到的数据是否和发送的一致。如果一致说明基本的时序是OK的。然后进行压力测试连续发送大量数据包统计误码率。如果误码率不为零说明时序余量不够需要调整约束或者使用IDELAY/ODELAY来微调采样点。7系列FPGA的IDELAY原语可以以约78ps的步进调整延迟参考时钟200MHz时。通过扫描IDELAY的值找到误码率最低的采样点然后把这个值固定下来。这个过程叫做眼图扫描。// IDELAYE2原语示例 IDELAYE2 #( .IDELAY_TYPE(VAR_LOAD), .IDELAY_VALUE(0), .REFCLK_FREQUENCY(200.0), .DELAY_SRC(IDATAIN) ) idelay_rxd0 ( .IDATAIN(rgmii_rxd[0]), .DATAOUT(rxd_delayed[0]), .C(clk_200m), .CE(1b0), .INC(1b0), .LD(load_delay), .CNTVALUEIN(delay_value), .CNTVALUEOUT() );扫描的时候从0到31逐个尝试IDELAY值记录每个值对应的误码率。通常会发现一个窗口在这个窗口内误码率为零。取窗口的中心值作为最终的IDELAY配置。3.4 温度漂移和电压变化的影响实验室常温下扫描出来的最佳IDELAY值到了高温或低温环境下可能就不适用了。这是因为PHY芯片和FPGA的IO延迟都会随温度变化。如果项目要求工业级温度范围-40°C到85°C建议在高温和低温下都做一次眼图扫描取交集作为最终值。如果交集为空说明时序余量本身就不够需要从PCB设计或PHY配置上想办法。比如调整PHY的内部延迟设置或者优化PCB走线。提示眼图扫描是个耗时的工作但非常值得做。我一般会在项目初期花半天时间把IDELAY窗口扫出来后面调试就省心多了。扫描的时候记得用PRBS伪随机二进制序列图案比固定数据更能暴露时序问题。4. 时序收敛的实战技巧与常见坑4.1 时序报告怎么看时序收敛的第一步是看懂时序报告。Vivado的时序报告里RGMII相关的路径通常出现在Inter-Clock Paths或者Input/Output Ports部分。重点看两个指标WNS最差负裕量和WHS最差保持裕量。WNS为负说明建立时间违规WHS为负说明保持时间违规。对于RGMII接口建立时间违规比保持时间违规更常见因为数据路径通常比时钟路径长。如果WNS是负的但很小比如-0.1ns可以尝试通过布局布线优化来解决。如果WNS负得比较多超过-0.5ns就需要检查约束是否合理了。一个常见的误区是看到WNS为负就拼命加约束、改代码但其实问题可能出在约束本身。比如input delay设得过大工具会认为数据到达得太晚拼命优化数据路径但实际硬件上数据并没有那么晚。这时候应该重新核对PHY数据手册确认参数是否正确。4.2 约束的优先级和覆盖关系在Xilinx的XDC中约束是有优先级的。后写的约束会覆盖先写的除非加了-add_delay。这个特性很容易导致问题如果你在多个XDC文件里都写了RGMII的约束后面的可能会覆盖前面的导致实际生效的约束和你以为的不一样。我的建议是把所有RGMII相关的约束放在同一个XDC文件里并且加上注释说明每个数值的来源。这样后面维护的时候一目了然。另外set_input_delay和set_output_delay的-clock选项必须引用一个已经创建的时钟。如果时钟名字写错了约束不会报错但也不会生效。Vivado会给出warning但很容易被忽略。建议在约束文件里加上report_clocks命令确认时钟已经正确创建。4.3 多时钟域和跨时钟域处理RGMII的接收时钟RXC是从PHY芯片恢复出来的和FPGA内部的系统时钟是异步的。所以RGMII接收数据必须做跨时钟域处理。通常的做法是用RXC把数据写入一个异步FIFO然后用系统时钟读出。这里有一个坑很多人忘了对RXC做时钟约束导致Vivado把RXC当作普通信号处理时序分析完全不准。RXC必须用create_clock约束即使它的频率和TXC相同。# RXC时钟约束 create_clock -name rgmii_rxc -period 8.0 [get_ports rgmii_rxc] # 异步时钟域声明 set_clock_groups -asynchronous -group [get_clocks rgmii_rxc] -group [get_clocks sys_clk]set_clock_groups -asynchronous告诉工具这两个时钟域是异步的不需要做时序分析。如果不加这个约束工具会尝试分析RXC和sys_clk之间的路径可能产生大量虚假的时序违规。4.4 常见问题排查表现象可能原因排查方法通信完全不通约束缺失或时钟未创建检查report_clocks确认TXC/RXC已约束偶尔丢包时序余量不足做眼图扫描调整IDELAY高温下通信异常温度漂移导致采样点偏移高低温下分别扫描IDELAY时序报告WNS为负约束过紧或布局不佳核对PHY参数尝试重新布局换板子后通信异常PCB走线偏斜不同重新测量走线延迟更新约束100Mbps模式正常1000Mbps异常时序窗口减半导致余量不够重点优化1000Mbps下的时序4.5 一个真实的踩坑案例我之前做过一个项目用的是Kintex-7加Marvell 88E1512的方案。实验室调试的时候一切正常常温下跑24小时压力测试零误码。结果小批量试产的时候有大约10%的板子出现间歇性丢包。排查过程很曲折。首先怀疑是焊接问题重新植球后问题依旧。然后怀疑是PHY芯片批次问题换了一批PHY还是一样。最后用示波器抓RXC和RXD的波形发现不同板子之间的时序偏斜差异很大最大的差了将近500ps。根本原因是PCB厂家的阻抗控制不够精确导致走线延迟不一致。解决办法是在FPGA侧使用IDELAY但IDELAY的值需要根据每块板子的实际情况来定。这就麻烦了总不能每块板子都手动扫描一遍。最后的方案是在FPGA里做一个简单的自适应算法上电时发送已知图案扫描IDELAY值找到误码率最低的点然后锁定。这个算法用了一个小的状态机实现占用资源很少但彻底解决了批次一致性问题。这个案例给我的教训是RGMII的时序约束不能只考虑典型情况必须考虑最坏情况。如果项目对可靠性要求高IDELAY自适应几乎是必须的。4.6 关于时序约束的几点个人体会做了这么多年的FPGA我越来越觉得时序约束不是写完就行的事情。它更像是一个需要持续验证和调整的过程。我的习惯是第一约束文件里每个数值都要有出处。要么来自数据手册要么来自实测不能凭感觉写。第二约束写完只是第一步板级验证才是关键。眼图扫描虽然耗时但能发现很多约束看不出来的问题。第三留足余量。RGMII的时序窗口本来就不大如果约束算出来余量只有几十皮秒那实际硬件上大概率会出问题。我一般会要求至少留出20%的余量。第四不同批次的PHY芯片和PCB板可能有差异如果项目量大建议在FPGA里预留IDELAY调整的接口方便后期调试。RGMII的时序约束说到底就是算准参数、写对约束、验证到位这三件事。算准参数靠的是仔细阅读数据手册和实测写对约束靠的是理解约束的物理含义验证到位靠的是系统的板级测试。这三件事都做到了RGMII的时序收敛就是水到渠成的事情。