多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Xilinx FPGA原语详解:从分类到实战,覆盖时钟、IO与高速接口

Xilinx FPGA原语详解:从分类到实战,覆盖时钟、IO与高速接口 前阵子调试一块带PCIe和千兆以太网接口的板子同事问我“这个OBUF原语到底是干嘛的直接写assign不就行了”我当时愣了一下因为这个问题其实问到了FPGA设计里一个很底层但又特别容易被忽视的点。今天就把Xilinx原语这件事从头到尾捋一遍从分类、常用原语到实际工程里的调用方式覆盖时钟、IO、高速接口等常见场景包括PCIe和1588时间同步这类对时钟和IO要求苛刻的工程顺便把我这些年踩过的坑一起交代清楚。1. 动手之前先搞清楚Xilinx原语到底分几类1.1 原语不是黑魔法它就是FPGA里的“积木块”很多刚接触FPGA的朋友会把原语想得很神秘其实原语就是Xilinx在芯片里预先做好的、可以被设计直接实例化的硬件功能模块。你可以把FPGA芯片想象成一个巨大的乐高套装CLB可配置逻辑块、IOB输入输出块、BRAM、DSP Slice都是乐高零件而原语就是已经拼好的功能件比如时钟缓冲器、差分输入缓冲器、高速串行收发器。为什么要用原语因为有些硬件功能你用RTLVerilog/VHDL是描述不出来、也综合不出来的。比如你想把一个时钟信号从芯片外部引脚送到内部的全局时钟网络这个“送”的动作涉及芯片内部专门的金字塔形时钟布线资源普通的assign语句只能走通用布线资源延迟大、偏斜大根本满足不了时序要求。这种时候就必须用IBUFG、BUFG这类时钟原语告诉综合工具和布线工具“我要用全局时钟网络”。原语和IP核最大的区别在于IP核是一大块封装好的功能比如DDR控制器、千兆以太网MAC里面调用了很多原语你只需要配置GUI界面原语则是单层的、原子级的硬件模块直接实例化没有多余的分包包装也不占额外的逻辑资源。用原语的好处一个是确定性高行为完全可控另一个是资源开销最小不引入IP核那些额外的接口逻辑和复位逻辑。1.2 按功能划分时钟、IO、布线、存储、DSP、高速串行从实际使用的频率来看Xilinx原语可以粗略分成六大类时钟类IBUF、IBUFG、BUFG、BUFGCE、MMCME2_ADV、PLLE2_ADV、BUFIO、BUFRIO类IBUF、OBUF、IOBUF、IBUFDS、OBUFDS、OBUFTDS、IDDR、ODDR、IDELAYE2、ISERDESE2、OSERDESE2布线类BUFG、BUFH、BUFGMUX还有跨时钟域的CDC专用原语存储类RAMB36E1、RAMB18E1、FIFO18E1不过很多场景下用Block Memory Generator IP更方便DSP类DSP48E1、DSP48E2乘加运算的硬核高速串行类GTH/GTY/GTP收发器GTXE2_COMMON、GTXE2_CHANNEL等这里我要强调一个容易混淆的点IBUF和IBUFG。IBUF是普通的输入缓冲用于通用IO引脚的输入IBUFG是专门接全局时钟引脚的输入缓冲它的输出只能接到BUFG或者MMCM的时钟输入。很多初学者搞不清这两个东西结果把系统时钟接到普通IO上再用IBUFBUFG转接虽然能用但走线绕了远路时钟质量打了折扣。正确做法是系统时钟必须接在MRCC或者SRCC专用时钟引脚上然后用IBUFG或IBUFDS差分时钟接收再接BUFG或直接进MMCM/PLL。还有一个分类维度是Vivado使用级别原语分为“Primitive”和“Macro”。Primitive是真正的硬件单元一个原语对应芯片上一个具体的物理结构比如LUT6、FDRE、OBUFMacro是由多个Primitive组合成的功能块比如LUTRAM、SRL16它们没有对应的独立物理结构。工程里我们用的绝大多数原语都是Primitive级别的比如OBUFDS、ISERDESE2这类。2. 最常用的几个原语拆解BUFG、OBUF、OBUFDS、MMCM、IDELAYE22.1 BUFG和BUFGCE全局时钟网络的控制开关BUFG是全局时钟缓冲器它的输入可以来自IBUFG、MMCM/PLL的时钟输出、或者普通逻辑信号输出接到全局时钟网络可以把时钟送到FPGA内部几乎每一个触发器的时钟端口。BUFG的延迟很小而且从BUFG输出到片内所有触发器的时钟偏斜控制得非常好这是普通布线完全做不到的。BUFGCE就是带时钟使能的BUFG多了一个CE端口高电平有效。当CE为低时输出被强制拉低不是保持是拉低所以它常用于时钟的“软开启”比如通过配置寄存器控制CE实现时钟树上电后先稳定再给逻辑送时钟。但有一个坑如果你用BUFGCE做时钟动态切换要小心毛刺问题。尽量不要在CE上使用组合逻辑对时钟做gating最好的做法是使用BUFGMUX或者BUFGCE_1原语它们专门做了防毛刺处理。BUFGMUX可以在两个时钟源之间无毛刺切换CE已经内置了切换逻辑只是行为模型写得比较绕建议看官方UG4727系列时钟资源用户指南的时序图。我通常在两种场景下会手动例化BUFG一种是用普通IO接外部时钟时一种是需要对内部产生的高速时钟做扇出平衡时。有些场景下综合工具会自动插入BUFG你不用管。比如你写了一个always (posedge clk)综合器识别出时钟信号后会自动推断BUFG。但差分时钟输入、手动MMCM输出分配等场景工具往往依赖你手工的原语例化来理解你的意图。2.2 OBUF和OBUFDS数字信号的“最后一道闸门”再回到开头同事的那个问题直接写assign led cnt[23];综合器其实会自动推断出OBUF但为什么有时候还要手动例化OBUF手动例化OBUF的理由主要有两个第一控制IO标准比如LVCMOS33、LVCMOS18、SSTL15等你在XDC约束文件里也能通过set_property IOSTANDARD LVCMOS33 [get_ports led]来设但有些复杂场景比如需要动态配置IO标准或内部走线特殊处理手动例化更直接第二让代码的表达更接近硬件实际方便仿真和调试。OBUFDS是差分输出缓冲输入一个单端逻辑信号输出两个互补信号O和OB形成差分对。差分信号抗干扰能力强适合高速信号传输比如千兆以太网的GMII接口虽然本身是单端的但很多PHY芯片连接FPGA的MDI差分对时就需要OBUFDS。这里要特别提醒一个非常容易踩的坑OBUFDS的输出引脚必须约束到FPGA芯片上真正的差分引脚对比如P和N相邻的两个引脚不是随便两个IO都能给OBUFDS用的。在XDC里要这样约束set_property PACKAGE_PIN AB12 [get_ports data_p] set_property PACKAGE_PIN AB11 [get_ports data_n] set_property IOSTANDARD LVDS [get_ports data_p] set_property IOSTANDARD LVDS [get_ports data_n]如果你把data_p和data_n分别约束到两个不相邻的引脚布线工具会报错或者在时序报告里出现非常明显的延迟差异。这个错误我见过很多次每次都是少年激情澎湃改完代码上板结果示波器上看到的波形完全不是那么回事。2.3 MMCM/PLL时钟频率怎么算出来的Xilinx 7系列里面的MMCME2_ADV和PLLE2_ADV几乎是所有时钟方案的灵魂。MMCM混合模式时钟管理器内部有VCO压控振荡器输出频率的计算公式是Fout Fin × M / (D × O)其中M是倍频系数CLKFBOUT_MULT_FD是输入分频DIVCLK_DIVIDEO是输出分频CLKOUT0_DIVIDE_F等Fin是输入参考时钟频率Fout是最终输出频率。VCO的频率范围是确定的7系列MMCM的VCO范围大致在600MHz到1440MHz根据速度等级略有不同K7一般是600-1200MHzV7可以到1440MHz。举个例子输入时钟是125MHzQSFP参考时钟我需要产生一个100MHz的逻辑时钟和一个250MHz的DDR接口时钟。计算过程如下VCO频率选125×81000MHz在600-1200MHz范围内CLKFBOUT_MULT_F设8DIVCLK_DIVIDE设1100MHz输出CLKOUT0_DIVIDE_F设为10250MHz输出CLKOUT1_DIVIDE设为4如果输入时钟是33.333MHzPCIe参考时钟100MHz不适用这里举例PCIe的33MHz本地时钟VCO频率选33.333×18600MHz在边界上也不算太舒服。一般我选600-800MHz左右的一档留点裕量VCO抖动性能在这个区间比较稳定。MMCM和PLL的区别MMCM功能更全面支持动态相位调整、支持分数分频CLKOUT_DIVIDE_F可以是小数PLL只是整数分频。DDR接口的时钟-数据相位对齐、SerDes的位对齐这些场景基本都要靠MMCM。PLL用在纯时钟频率变换且不需要动态相位调整的场景。遇到MMCM锁定失败或者输出毛刺先看VCO频率是否在范围内其次看输入时钟是否符合PLL的最低频率要求K7的PLL输入范围一般是32MHz到800MHz但上限受速度等级限制。2.4 IDELAYE2和ISERDESE2/OSERDESE2IO时序的精细调节手段IDELAYE2是输入延迟原语可以在输入路径上插入可编程的延迟步进。7系列器件每个IO都有独立的IDELAYE2tap数可配范围0到31每个tap大约延迟78ps到90ps取决于进程角。这个原语在源同步接口比如SDRAM、以太网RGMII、ADC采样接口里几乎是标配。为什么需要IDELAY举个例子FPGA接收外部芯片送来的数据和时钟时时钟和数据到达FPGA引脚的相对相位不是确定的可能因为PCB走线长度差、芯片输出偏斜等原因数据和时钟之间存在固定偏差。这个时候要么用IODDRISERDES在相位上做对齐要么用IDELAY调整数据路径延迟让数据出现在时钟采样窗口的中央。RGMII接口就是一个经典案例RX时钟由PHY芯片与数据一起发出但RGMII标准要求时钟偏斜控制在2ns以内FPGA接收端要正确处理IDELAY设置才能保证不同批次的PHY芯片都能稳定收数据。我曾经在调试一块基于RGMII的以太网板卡时一开始把IDELAY设置为0结果宽温测试下丢包率飙升后来扫了31个tap的延迟值找到FIFO裕量最大的一档问题迎刃而解。ISERDESE2和OSERDESE2实现串并转换分别把高速串行数据转换成并行数据和把并行数据转成高速串行。千兆以太网GMII的RX/TX路径上由于GMII本身是8bit并行125MHz通常不需要SerDes但RGMII把数据宽度减半然后DDR发送就需要IDDR/ODDR或ISERDES/OSERDES的帮助。3. 结合高频场景的实战千兆以太网、PCIe、1588的时钟原语组合3.1 千兆以太网GMII接口中OBUF和OBUFDS的实际用法千兆以太网的物理接口标准常见GMII8bit并行125MHz、RGMII4bit DDR125MHz、SGMII1bit SERDES1.25Gbps。如果在FPGA上自研MAC逻辑GMII与外部PHY相连时FPGA作为MAC侧要输出TXD[7:0]、TX_EN、TX_ER以及GTXCLK125MHz。这些信号的输出都要经过OBUF但一般你用约束文件设了IOSTANDARD后综合器自动就帮你做好了OBUF的插入不需要手动写。手动例化OBUF的场景更多出现在数据输出需要做比较特殊的处理时。比如某个项目要求MAC输出时钟和数据的相位差精确控制在某个范围你需要在输出时钟路径上专门加一个BUFG或ODDR来赋值这时就会手动例化ODDR而ODDR的输出再接OBUF。注意FPGA输出时钟的专用做法是使用ODDR原语把内部时钟copy一份从IO输出而不是直接把BUFG输出连到IO端口——在Vivado里直接连接也不允许综合会报错“cannot place clock onto general routing”。OBUFDS在以太网里的应用通常是PHY芯片和FPGA之间使用MDI差分信号时。此外RGMII接口的PHY和MAC之间也有用LVDS差分传输的变体比如一些工业级的PHY芯片提供RGMII-LVDS接口。处理差分IO时除了输出用OBUFDS输入要用IBUFDS。3.2 PCIe参考时钟与本地时钟的工程关系GT时钟、差分缓冲、BUFGPCIe是Xilinx FPGA的一个大头应用。xilinx pcie相关的设计里除了PCIe硬核比如7系列的Integrated Block for PCIe本身的使用还有一个经常被忽略的点PCIe参考时钟PCIe REFCLK是100MHz差分时钟直接接到FPGA的GT参考时钟专用引脚上通过GTXE2_COMMON内部的专用时钟网络分发到各个收发器通道。虽然这部分通常在IP核里被透明处理了但如果你要对PCIe的用户逻辑提供本地时钟就需要额外处理。PCIe IP核输出的user clock比如user_clk通常250MHz、user_clk_div2125MHz这些时钟实际上是由IP核内部从GT恢复时钟得到的你也可以在用户逻辑中直接使用。但如果你的用户逻辑需要与PCIe时钟域异步的另一个时钟比如DDR控制器时钟、MAC层的时钟就需要MMCM生成这时要特别注意时钟域交叉的问题比如异步FIFO。在1588 PTP时间同步场景中xilinx 1588 timer syncerFPGA要提供高精度的时间戳功能。1588协议要求MAC在发送和接收数据包的精确时刻打时间戳这个时刻的精度依赖于一个高稳定的本地时钟计数通常是100MHz或者125MHz而这个时钟通常从MAC的时钟域中获得。调试1588时我有个经验驱动PTP时间计数器的时钟最好直接用MMCM的BUFO输出而不要通过BUFG再转一手因为BUFG和BUFO的相位关系虽然固定但引入的延迟可能会破坏时间戳的精度实测下来同一个设计从BUFG取时钟和从BUFO取时钟时间戳抖动可能会有几百皮秒的差距对1588要求纳秒级精度的场景来说不能忽视。3.3 时钟管理原语在PCIe和DDR等复杂系统中的应用链路一个典型的高速数据采集系统时钟树可能是这样的外部100M差分时钟进IBUFDS然后分成两路一路直接接到GT参考时钟引脚用于PCIe收发器另一路经过MMCM倍频出200MHz给DDR4控制器出125MHz给以太网MAC出100MHz给PTP时间计数器。每条时钟路径上还可能有BUFG进行扇出。这里要特别说明PCIe参考时钟和系统普通时钟的区别PCIe REFCLK对相位噪声要求极高必须走GT专用引脚和GT专用时钟路由不能随便用普通IO接收再进BUFG否则会导致PCIe链路训练失败或者误码率升高。有些设计为了节省成本想用FPGA内部的PLL输出一个100MHz给GT做参考时钟这在理论上可以通过GTREFCLK的内部选择但抖动指标一般不够好所以我不建议对性能有要求的场合这么干。涉及DDR控制器时虽然IP核会帮你例化好MMCM和IDELAY但你还是要知道内部链路DDRPHY模块通过IDELAYE2调整DQS和DQ的相对延迟通过MMCM产生DDR时钟和系统时钟通过OSERDESE2输出DQ/DQS通过ISERDESE2读取返回的读数据。DDR的调校calibration之所以复杂本质上就是在寻找时序裕量最佳的那个延迟组合。4. 原语例化的常见问题与排查技巧实录4.1 综合不通过这些报错信息你看懂了吗综合或实现阶段原语相关的报错是比较常见的。这里列几个我实际遇到过的方便你对号入座“ERROR: [Synth 8-3331] design logic has unconnected port I on instance OBUF” —— IO原语悬空。OBUF输入没有接驱动信号检查是不是端口连接写错了。“ERROR: [Vivado 12-1345] Cannot mix IBUF and OBUF on the same I/O port” —— 同一个IO口既接了IBUF又接了OBUF这是因为IOBUF才是双向口原语你把IBUF和OBUF同时连接到同一个top端口上综合器无法确定最终选哪个。“ERROR: [Place 30-574] Invalid IOCTL setting for LVDS pair” —— 差分引脚对配置错误检查P/N引脚位置和IOSTANDARD是否匹配。还有一类问题是MMCM和PLL的使用限制某个时钟输出没有连接任何负载或者M和D参数的乘积使VCO频率超出范围这些在实现阶段都会报错。我的经验是遇到这类报错先看两个东西一是原语的例化参数是否正确二是该原语是否受到某些物理限制比如某些bank的MRCC引脚不支持某种IO标准。4.2 仿真波形正常板子点不亮原语时序和XDC约束的配合仿真正常而板级失败的情况多半出在IO约束和时钟约束上。仿真时IBUF、OBUF只是理想缓冲不带有实际的延迟、电压转换、传输特性。板级运行时输出端口的驱动能力、IO标准、上升时间、负载电容都会影响信号质量。有个经典案例FPGA输出一个50MHz时钟通过OBUF驱动一个外部芯片仿真里波形干干净净板子上用示波器一看边沿有明显的振铃。原因就是OBUF的SLEW属性默认是“SLOW”上升时间太长在阻抗不匹配的情况下反射叠加变成了振铃。改法很简单在XDC里加set_property SLEW FAST [get_ports clk_out]或者手动例化OBUF时把SLEW改为“FAST”。这就是为什么说原语不光是“接个线”的事它的属性和FPGA物理结构绑定在一起你配置错了就真的会影响电磁兼容和信号完整性。另外FFFlip-Flop的时钟必须来自全局时钟资源或区域时钟资源这是时序约束检查的重点。如果你的代码里把一个普通信号用作了时钟Vivado会报“clock has non-clock source”的警告你可能需要检查你的设计是否真正需要这个“门控时钟”。很多新人设计里会有类似wire gated_clk clk enable;的写法这在真正的ASIC设计里也许是省功耗的手段但在Xilinx FPGA上绝不是一个好习惯。如果功耗确实有要求应该用BUFGCE或者CLOCK GATING相关的专用原语。4.3 一个容易忽视的坑ODDR在输出时钟时的特殊处理想把任意一个时钟从通用IO输出最标准的方法是使用ODDR原语ODDR #( .DDR_CLK_EDGE(SAME_EDGE), .INIT(1b0), .SRTYPE(SYNC) ) ODDR_clkout ( .Q(clk_out), .C(clk_in), .CE(1b1), .D1(1b1), .D2(1b0), .R(1b0), .S(1b0) );这样Q端输出其实是clk_in的频率但相位与内部时钟相同。为什么不用assign clk_out clk_in因为普通的assign输出走的是IOB里的OBUF普通路径而ODDR方式利用了IODDR内部的专用路径输出时钟与内部时钟的偏斜更可控。但ODDR输出时钟也有一个局限它不能直接接任意引脚必须接在HR或HP bank的IO上而且某些bank的引脚不支持时钟输出。这跟芯片的IO资源映射有关查手册或者用Vivado的引脚规划工具就能看到。4.4 实操心得一套能提升调试效率的原语使用习惯自从吃过几次苦头后我总结了一套原语使用的基本习惯这里分享出来。第一优先使用IP核但要知道IP核内部用了什么原语。Xilinx提供了很多带GUI的IP核如Clocking Wizard、SelectIO Interface Wizard它们会帮你生成原语的例化模板和约束。直接用IP核的另一个好处是它会帮你处理好输入输出端口的IOSTANDARD省心不少。但你应该在生成的HDL文件里看看它到底例化了哪些原语、参数是多少这对理解自己的时钟架构非常有帮助。第二手写原语时打开Vivado的“Language Templates”里面有所有原语的例化模板。不要在记忆里背代码也别去网上复制不知道哪个老版本的旧代码特别是7系列和UltraScale的模板有差异。第三全局时钟资源是有限的BUFG一共就几十个MMCM/PLL每个bank区域还有限制不要随意挥霍。在资源规划阶段就要估算我这个设计需要几个全局时钟、几个区域时钟、几个MMCM、几个BUFGCE。我曾经接过一个同事的项目里面为了不同模块的时钟各搞了一个MMCM结果最后布局时发现MMCM不在同一个bank区域时钟资源互相冲突费了好大劲才把时钟树重新规划了一遍。第四原语不是越多越好尽量让综合器自动推断只在关键路径上手动干预。比如always (posedge clk)里的时钟综合器能自动加BUFG你就别自己先例化BUFG再把它接到触发器的时钟端口那样反而会打乱时钟网络的规划。5. 原语代码实战一段能直接复用的例化模板5.1 差分时钟输入到内部时钟的整体例化下面是一个用差分时钟125MHz作为系统时钟输出100MHz和200MHz两个时钟的完整示例包含了IBUFDS、MMCME2_BASE和BUFG的使用。module clk_gen ( input wire clk_p, input wire clk_n, input wire rst_n, output wire clk_100m, output wire clk_200m, output wire mmcm_locked ); wire clk_in; wire clk_fb; wire clk_100m_bufg; wire clk_200m_bufg; // 差分输入缓冲125MHz 差分时钟转单端 IBUFDS #( .DIFF_TERM (TRUE), .IOSTANDARD (LVDS) ) ibufds_125m ( .O (clk_in), .I (clk_p), .IB (clk_n) ); // MMCM频率规划 // VCO 125MHz * 10 1250MHz // clk_100m 1250 / 12.5 100MHz这里用CLKOUT0_DIVIDE_F12.5 // clk_200m 1250 / 6.25 200MHz这里用CLKOUT1_DIVIDE_F6.25 MMCME2_BASE #( .CLKIN1_PERIOD (8.000), .CLKFBOUT_MULT_F (10.000), .DIVCLK_DIVIDE (1), .CLKOUT0_DIVIDE_F (12.500), .CLKOUT1_DIVIDE (6.250), .CLKOUT2_DIVIDE (1), .CLKOUT3_DIVIDE (1), .CLKOUT4_DIVIDE (1), .CLKOUT5_DIVIDE (1), .CLKOUT6_DIVIDE (1) ) mmcm_inst ( .CLKOUT0 (clk_100m_bufg), .CLKOUT1 (clk_200m_bufg), .CLKFBOUT (clk_fb), .CLKIN1 (clk_in), .CLKFBIN (clk_fb), .PWRDWN (1b0), .RST (~rst_n), .LOCKED (mmcm_locked) ); // 输出时钟接入全局时钟网络 BUFG bufg_100m (.I(clk_100m_bufg), .O(clk_100m)); BUFG bufg_200m (.I(clk_200m_bufg), .O(clk_200m)); endmodule这个例子里有几个细节需要注意CLKIN1_PERIOD是输入时钟周期单位是ns125MHz对应8.000nsMMCME2_BASE和MMCME2_ADV的区别在于ADV多了一些动态相位调整端口一般固定频率转换用BASE就够了CLKOUT0_DIVIDE_F支持小数但CLKOUT1_DIVIDE在MMCME2_BASE里不支持小数所以这里6.25实际上是在ADV里才能用BASE只能用整数6对应208.33MHz。如果你的设计用到了小数分频一定要查清楚是ADV还是BASE或者直接看Clocking Wizard生成的代码。5.2 ISERDESE2和IDELAYE2的千兆以太网RGMII输入示例RGMII接收端是典型的使用ISERDES和IDELAY的场景。PHY芯片输出125MHz的RXCLK和4bit DDR数据FPGA要用RXCLK作为源同步时钟采样数据。这里做一个简化示例// 简化版单bit数据通道的IDELAY ISERDES接收 // 数据 rxd0_p 已经通过IBUFDS转成单端 rxd0 IDELAYE2 #( .IDELAY_TYPE (FIXED), .DELAY_SRC (IDATAIN), .IDELAY_VALUE (12), // tap delay, 常规配置范围0-31 .HIGH_PERFORMANCE_MODE(TRUE) ) idelay_rxd0 ( .IDATAIN (rxd0), .DATAOUT (rxd0_delayed), .C (rxclk), .CE (1b0), .INC (1b0), .CINVCTRL(1b0), .CNTVALUEIN(5d0), .CNTVALUEOUT(), .LD (1b0), .LDPIPEEN(1b0), .REGRST (~rst_n) ); ISERDESE2 #( .DATA_WIDTH (4), .DATA_RATE (DDR), .INTERFACE_TYPE(NETWORKING) ) iserdes_rxd0 ( .D (rxd0_delayed), .CLK (rxclk), .CLKB (~rxclk), .CE1 (1b1), .CE2 (1b1), .RST (~rst_n), .Q1 (rxd0_bit3), .Q2 (rxd0_bit2), .Q3 (rxd0_bit1), .Q4 (rxd0_bit0), .SHIFTOUT1 (), .SHIFTOUT2 (), .SHIFTIN1 (1b0), .SHIFTIN2 (1b0), .O (), .CLKDIV (rxclk_div), .OCLK (1b0), .DYNCLKDIVSEL(1b0), .DYNCLKSEL (1b0) );这段代码的IDELAY_VALUE在真实工程里往往不是随便设的最好通过时序校准模块动态调整或者借助Vivado的硬件管理器用JTAG在线读一下眼图裕量。如果设计里用了RGMII IDELAY控制器IP它会在初始化阶段自动扫描tap值。ISERDESE2的DATA_WIDTH和DATA_RATE要和接口协议匹配RGMII是4bit DDR所以DATA_WIDTH4、DATA_RATEDDRCLKDIV是分频后的时钟用来把并行数据送到内部逻辑频率等于rxclk的一半。6. 常见问题速查表问题现象可能原因排查思路MMCM未锁定波形输出乱VCO频率超范围、输入时钟不稳定检查CLKFBOUT_MULT_F和DIVCLK_DIVIDE组合用ILA或VIO看LOCKED信号时序OBUFDS输出波形错误差分引脚对匹配错误、IOSTANDARD不匹配查询引脚规划工具确认P/N在同一差分对上且IOSTANDARD一致时钟输出有毛刺使用了组合逻辑gating时钟改为BUFGCE或ODDR方案避免用assign做时钟门控RGMII接口误码IDELAY设置不合适采样不在眼图中央扫描IDELAY_VALUE观察误码率或使用眼图测试工具综合报原语端口未连接例化的原语端口有悬空输入或输出打开Language Templates逐项核对特别是未用端口要接固定电平GT参考时钟无法使用参考时钟接错引脚或未用IBUFDS_GTE2PCIe REFCLK必须接GT专用引脚并使用专用原语不能走普通BUFG路径时序报告中延迟偏大没有使用全局时钟网络路径确认时钟是否经过BUFG或MMCM输出观察时钟路径报告中是否有BUFG排查工具方面Vivado的Device View可以直观看到BUFG、MMCM的物理位置和连接关系配合原理图视图Schematic检查原语之间的连接。遇到复杂的时钟问题先看时钟网络报告Clock Networks Report它会列出所有时钟源和经过的原语路径。7. 最后再聊几句原语设计思路我自己用原语用过这么多年最大的体会是原语手册永远值得反复翻。很多东西你以为记住了换一个芯片系列就变了。7系列和UltraScale的GT原语、BUFGCE_1、MMCME4_ADVUltraScale里的MMCM改名为MMCME4_ADV的端口和参数都不一样比如IDELAYE3与IDELAYE2的端口命名就不同。写代码前先确认你用的什么芯片再查对应手册。还有一点想提醒的是原语的仿真行为。行为级仿真模型里IBUF和OBUF基本等效于导线什么延迟、抖动、毛刺都没有。但只要你在实现后的仿真post-implementation timing simulation里跑就能看到原语模型带来的真实延迟。很多设计在行为仿真里跑得飞快一到后仿真问题全暴露基本都跟原语相关的时序细节有关。调试这类问题最好的工具是Vivado的逻辑分析仪ILA和虚拟IOVIO在板上抓到真实波形后再回头改代码比反复仿真有效得多。工具链本身也在不断吸收这些原语的使用经验。Vivado的SmartConnect、Clocking Wizard这类IP已经帮你封装好了原语有些甚至会在综合时自动优化原语的参数。但底层逻辑永远不变原语是FPGA的“物理层接口”你越懂它设计越主动。
返回列表