ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA时序约束实战:set_input_delay推导与DDR接口调试

FPGA时序约束实战:set_input_delay推导与DDR接口调试 做FPGA开发的人大概都有过这种经历逻辑功能明明仿真全对综合实现也都通过结果板子一跑起来DDR接口的数据就是不对读出来一片乱码用逻辑分析仪抓波形又看不出明显毛病。这种时候十有八九不是RTL写错了而是时序约束没写好具体一点说就是set_input_delay这类输入约束没有准确描述外部器件和FPGA之间的真实时序关系。我自己在项目里调DDR接口、RGMII接口踩过不少坑这篇就专门聊聊set_input_delay怎么用、怎么推导以及如何用它搞定DDR接口的setup/hold问题。这套东西适合谁如果你正在做FPGA和外部DDR颗粒通信、接DDR PHY芯片或者RGMII以太网接口或者只是想把时序约束从“抄例程”变成“真正理解”这篇文章都值得看完。我会从DDR接口为什么特殊讲起给出手把手推导input_delay的方法再附上Vivado里的实际写法和问题排查经验。1. 先搞清楚为什么DDR接口的时序这么难搞1.1 从一次“编译通过但上板乱码”的经历说起印象很深的一次是给一块板子写DDR3读写逻辑。当时功能仿真做了好几轮读出来的数据和写入完全一致心里想着“稳了”结果上了板子之后连续读写几百个地址就开始偶尔出错用示波器去抓引脚波形看起来也像模像样。折腾了两天最后老老实实打开Vivado的时序报告才发现输入路径上setup和hold全都有红色violation。后来查原因问题出在DQS和DQ在PCB上的走线并没有完全等长而我压根没写set_input_delay约束。Vivado在没有任何输入延迟约束的时候是按照“数据在时钟沿处同时到达”来分析的这跟实际的时序关系完全不吻合自然就给出了一个过于乐观的分析结果。实际板子上的数据窗口根本没有FPGA内部触发器要求的那么宽裕于是读写出现随机错误。那次之后我养成了个习惯只要FPGA引脚上接了外部有时序要求的信号无论接的是DDR、RGMII PHY还是ADC第一时间把input_delay算清楚、写进XDC而不是等出了问题再去猜。这个习惯救了我后面好几个项目。1.2 DDR接口到底特殊在哪儿双沿采样、源同步、窗口极小DDR的全称是Double Data Rate核心特性就是时钟的上升沿和下降沿都采样数据。也就是说外部器件在同一个时钟周期内会送出两位数据。比如DDR3-800接口时钟频率400MHz周期2.5ns但数据率是800Mbps等效数据位宽变成了时钟周期的1/2。这对时序分析的影响是巨大的。普通SDR信号只有一个采样沿只要你把setup/hold余量留够就完事。DDR信号则要求我们同时去盯两个沿的约束而且下降沿采样时用的不是完整周期而是半周期留给建立时间的时间窗口更窄。另一个关键点是DDR接口属于源同步接口。什么意思就是数据跟随路时钟DQS是从同一个源器件发出来的FPGA自身并不产生那个采样时钟。在源同步场景下时序分析的参照基准不是FPGA内部的系统时钟而是外部器件同时发出的DQS。这就要求约束文件里必须用DQS作为参考时钟来约束DQ而不是随便拿一个内部PLL时钟凑合。再加上DDR的数据有效窗口本身就很小。DDR3颗粒的DQ相对于DQS的偏斜通常在几百皮秒量级PCb上哪怕只是走线差了30mil等效延迟差就有约5ps多条走线累积起来已经能让原本就紧张的时序余量雪上加霜。这也是为什么DDR接口对PCB等长要求那么苛刻等长设计就是为了保证DQS和DQ到达FPGA引脚的相对相位尽量一致从而给FPGA内部的set_input_delay约束提供一个小而确定的输入窗口。1.3 set_input_delay在整个时序分析里扮演什么角色set_input_delay是输入路径约束的核心命令它描述的是外部数据信号相对于FPGA捕获时钟沿在FPGA引脚上出现的延迟时间。你可以把它理解为“外部世界写给时序引擎的一封信”告诉时序引擎外部数据到底什么时候到达引脚。这封信写准了Vivado或Quartus才能正确计算FPGA内部的建立时间和保持时间余量。建立时间对应的是数据在采样沿之前必须稳定的时间保持时间对应的是采样沿之后数据必须维持稳定的时间。两项检查分别对应两条路径setup路径关心的是数据到达得太晚导致采样失败hold路径关心的是数据变化得太早导致还没来得及采到就被冲掉。在DDR接口里setup和hold检查同样存在只不过采样沿变成了两个。set_input_delay配合参数-min和-max分别约束数据最早到达和最晚到达的时间窗口时序引擎拿这组数据再去和FPGA内部触发器本身的建立保持时间做比较。换言之set_input_delay不是凭空生成的它的每一档数值都应该对应外部芯片手册里的Timing参数和PCB的实际走线延迟。2. 从芯片手册到XDCset_input_delay的推导方法2.1 建立时间与保持时间的物理含义别只会背定义很多初学者背得下定义建立时间就是数据在时钟沿之前必须稳定的时间保持时间就是数据在时钟沿之后必须稳定的时间。但没有多少人真去想过为什么会有这两个参数。FPGA内部触发器Flip-Flop并非理想器件。在时钟沿到来的一瞬间它内部的传输门和锁存结构需要一点时间才能准确读取输入数据。数据必须在时钟沿之前提前到达并稳定一小段时间这就是setup而在时钟沿之后内部锁存结构还在完成数据捕捉数据也不能立刻变化这就是hold。这两个时间由芯片制造工艺决定写死在Xilinx或Intel的芯片数据手册里普通开发者改不了。以Xilinx 7系列为例内部触发器的setup时间大约在0.1~0.3ns这一档hold时间通常在0.05~0.2ns。当外部DDR接口的数据率很高时留给整个数据路径的裕量本来就只有一两百皮秒如果内部触发器本身还要吃掉一部分设计难度自然就上来了。set_input_delay跟setup/hold的关系是这样的时序引擎会把外部输入延迟和FPGA内部从引脚到触发器的路径延迟加起来再跟时钟周期以及触发器的setup/hold要求做差。如果外部信号到达得偏晚内部路径又长setup就容易被击穿。如果外部信号变化太早hold要求又严格hold也可能出问题。所以set_input_delay的max值管setupmin值管hold这对关系后面排查violation时要时刻记住。2.2 推导输入数据窗口Tco、PCB走线延迟、时钟偏斜现在重点来了set_input_delay到底怎么算。以FPGA接收外部DDR器件发来的数据为例一条完整的数据通路是这样的外部器件在时钟或DQS沿到来后经过一定的时钟到输出延迟Tco把数据送到器件引脚数据再经过PCB走线到达FPGA引脚。与此同时时钟或DQS信号也经过PCB走线到达FPGA引脚。在FPGA引脚上数据相对于时钟沿的延迟可以用一个公式表达最大输入延迟max Tco_max 数据走线最大延迟 - 时钟走线最小延迟 时钟抖动 最小输入延迟min Tco_min 数据走线最小延迟 - 时钟走线最大延迟 - 时钟抖动那些只写一半的教程通常忽略掉走线差异但实际工程中PCB走线的长度差往往是压垮时序的最后一根稻草。比如FR4板材上信号传播速度约为150~170ps/inch折算下来约6mil/ps。也就是说如果DQS比DQ长12mil数据就会比时钟晚约2ps到达。单独看这点延迟微不足道但DDR3-1066的数据有效窗口可能只有500ps左右任何一点多余的偏斜都会侵蚀余量。时钟抖动也要算进去PLL或外部晶振的抖动会让时钟沿的到达时间发生随机偏移。在约束里体现为对max值的加项和对min值的减项本质上就是给时序引擎一个更真实的“最坏情况”。2.3 一个DDR3-800接口的完整计算实例用一个简化但贴近实际的例子来算一遍。假设外部DDR器件的工作参数如下参数最小值最大值Tco时钟到数据输出延迟0.10ns0.45nsPCB DQ与DQS走线延迟差-20ps20ps时钟抖动-0.03ns代入公式最大输入延迟 0.45 0.02 0.03 0.50ns 最小输入延迟 0.10 - 0.02 - 0.03 0.05ns也就是说在FPGA引脚上数据最早在时钟沿之后0.05ns就到达最晚在时钟沿之后0.5ns才到达这是一个宽度为0.45ns的输入数据窗口。接下来写约束。假设DQS是DDR接口的随路时钟频率400MHz周期2.5nscreate_clock -name ddr_dqs -period 2.5 [get_ports dqs_p] set_input_delay -clock ddr_dqs -max 0.50 [get_ports {dq[*]}] set_input_delay -clock ddr_dqs -min 0.05 [get_ports {dq[*]}] set_input_delay -clock ddr_dqs -clock_fall -max 0.50 [get_ports {dq[*]}] set_input_delay -clock ddr_dqs -clock_fall -min 0.05 [get_ports {dq[*]}]DQS本身是差分信号在XDC里还要先创建差分时钟通常写成create_clock -name ddr_dqs -period 2.5 [get_ports {dqs_p}]这里有个容易被忽略的点DQS的低电平期间和数据的高低电平也有对齐关系如果你把DQS当作普通单端时钟来约束Vivado可能无法正确处理差分输入缓冲。建议在约束前先确认时钟引脚通过IBUFDS进入FPGA内部后生成的内部时钟名是否正确。计算出来的数值记得留一点裕量。工程上我习惯在计算值基础上再多留50~100ps比如上面算出来max是0.50ns实际约束写成0.55nsmin再往下压到0.02ns。原因很简单电源噪声、温度变化、连接器接触阻抗这些因素芯片手册上不会写得很清楚多留一点余量能显著提高板子在不同环境下的稳定性。2.4 为什么DDR约束都要带-clock_fall那两条DDR的数据在上升沿和下降沿都被采样所以必须为下降沿单独做一组约束。-clock_fall告诉时序引擎下面这组input_delay是针对下降沿采样的检查。很多初学者会把这组约束漏掉只写了上升沿的两条结果Vivado在下一次布局布线时下降沿路径被当成“不确定”或者“无约束”来处理综合出的时序报告看起来全都满足实际上板子一样乱码。我之前给RGMII接口写约束时就干过这事只写了-clock_max和-min忘了加-clock_fall后来看到时序报告里有一条“no input delay constraint for falling edge”的警告才反应过来。为什么下降沿约束通常和上升沿一样理论上下降沿采样需要的时间裕量是半周期减去数据窗口宽度但由于约束中max和min已经描述了数据相对于时钟沿的绝对位置上升沿和下降沿的输入延迟在数值上往往是相同或接近的。少数特殊接口会要求上升沿和下降沿的数据偏斜不同比如某些DDR3器件在tDQSL和tDQSH参数上略有差异这时候就必须分别给出两组数值不能直接复制。实操上有几个选择如果DQS差分信号进入FPGA后通过BUFIO直接驱动ISERDES那么约束以DQS为参考时钟就够精确如果DQS经过MMCM/PLL移相后才作为采样时钟那么要把相移量考虑进去否则input_delay的参考点就错了。真做了相移之后约束的基准时钟应该改成MMCM输出时钟而不是引脚上的DQS这一点务必留意。3. 实操把set_input_delay写对、写全3.1 Vivado下创建时钟与set_input_delay的完整写法Vivado的XDC约束本质上是一套Tcl脚本set_input_delay命令本身不复杂但完整、可落地的写法需要考虑工程里的时钟结构。最常见的源同步DDR接口约束模板是这样# 1. 先创建随路时钟 create_clock -name ddr_dqs -period 2.500 [get_ports {dqs_p}] # 2. 约束差分时钟输入的内部时钟 # 如果dqs_p/dqs_n经过IBUFDS后得到ddr_dqs_ibuf set_property LOC IBUFDS_X0Y1 [get_cells i_ibufds_dqs] ;# 仅示意不需要手动指定 # 3. 对上升沿做input delay set_input_delay -clock ddr_dqs \ -max 0.55 [get_ports {dq[*]}] set_input_delay -clock ddr_dqs \ -min 0.02 [get_ports {dq[*]}] # 4. 对下降沿做input delay set_input_delay -clock ddr_dqs -clock_fall \ -max 0.55 [get_ports {dq[*]}] set_input_delay -clock ddr_dqs -clock_fall \ -min 0.02 [get_ports {dq[*]}]如果DQ总线上还有数据掩码DM引脚或者DDR接口还有地址命令总线别忘记单独写约束。地址命令总线在DDR3里通常是SDR单沿信号由CLK上升沿采样但它们的input_delay同样要计算不能漏。还有一种情况是DQS和DQ不是直接从引脚进入FPGA寄存器而是先进IDELAY做可调延迟。这时候input_delay的参考点依然是引脚处的时序关系IDELAY的移相在功能实现上起作用但XDC约束本身不会因为加了IDELAY就自动修改input_delay的数值。有件事要特别提醒set_input_delay是对端口的约束如果你已经把DQS和DQ通过IBUFDS/IBUF变成了内部信号再对内部信号写set_input_delay会直接报错。约束对象必须是最顶层的input port也就是FPGA的物理引脚。3.2 等长设计与input_delay的联动调整PCB等长设计直接决定了input_delay计算结果里的走线偏差项。之前那张表里我把走线延迟差算成±20ps这不是拍脑袋定的而是从PCB的等长约束指标里反推出来的。假设PCB的Layout指南要求DQS与DQ之间等长控制在±30mil内按6mil/ps估算对应的延迟偏差就是±5ps。这5ps看着不大但对于高速DDR接口来说已经是整个时序预算里不可忽略的部分。反过来如果你的PCB等长做得比较松比如差了100mil那么input_delay的max要加约16psmin要减约16ps输入窗口变窄FPGA内部时序裕量就被压缩。所以做DDR接口项目时我通常先跟PCB工程师确认等长规则拿到具体的走线长度表之后再算input_delay。不能等板子回来了才想起来要算约束那样出了问题很难定位是板子走线问题还是约束问题。还有一个工程上实用的技巧在Vivado里可以直接用get_property DELAY [get_nets dq[0]]之类的方式获取布线后的实际时钟路径延迟偏差拿它去反推模型估算值和真实值的差距。不过这是后端收尾阶段的验证方法前端约束时还是以PCB设计规则和芯片手册为准。3.3 约束值合不合理用这三种方法验证写完set_input_delay之后怎么判断数值设得对不对我一般用三种方式层层验证。第一看综合和实现后的时序报告。如果setup/hold的裕量都是正数且留有余地说明约束方向基本正确。如果明明板子上跑得好好的时序报告却大片红色violation那很可能是约束过紧比如max值给得太大把真实数据到达时间描述得太晚。反之如果报告全绿但板子乱码多半是约束过于乐观真实数据窗口比约束描述的还要恶劣。第二做输入路径时序仿真。仿真时把外部器件的时序模型加上直接验证FPGA引脚上数据相对时钟的相位关系再对照约束里的数值是否一致。Vivado的仿真库里通常有DDR颗粒模型搭个简单的testbench就能看到波形上的数据有效窗口。第三也是最实战的方法上板后用ILA抓内部采样数据。在FPGA内部对DQ做一次固定值读回测试连续跑几千次通过调节IDELAY的步进值扫描数据采样点。如果某些延迟档位下数据稳定正确某些档位下出错就能反推出真实的数据窗口边界再回来修正set_input_delay。这个方法在调试DDR接口时几乎是必用的Vivado里可以通过VIO在线改IDELAY的tap值非常方便。4. 常见问题与排查技巧实录4.1 读懂setup/hold violation报告时序报告里出现setup violation翻译成人话就是数据到达FPGA内部触发器的时间太晚了导致在当前时钟沿采样时数据还没有稳定。报告中通常会给出几个关键数值Slack裕量、Data Path Delay、Clock Path Delay和Required Time。排查setup violation的顺序我是这样固定的先看Required Time是由哪条时钟路径算出来的再看Data Path Delay有没有异常大的组合逻辑延迟最后检查input_delay的max值是不是偏大。如果问题出在输入路径上多半是set_input_delay max值给得过大或者参考时钟的周期被约束得太短。hold violation则相反它表示数据变化得太早在时钟沿之后没能保持足够长的时间。对于输入路径hold violation通常因为set_input_delay的min值设得太小数据在约束中被描述成“最早到达”。注意这不一定说明实际板子真的保证了hold可能只是约束数值没有反映真实的最早到达时间。看到违例先别急着改代码打开Schematic或ChipScope看实际布线很多时候是约束写错而不是逻辑写错。我记得有一次排查DDR读数据错乱的问题折腾了半天发现是DQS的时钟约束周期写成了2.5ns但实际DQS频率是400MHz没错却忘了差分对另一根引脚也要定义导致时序引擎对下降沿的分析直接缺位。4.2 实战中遇到的典型时序违例与处理手段分享一个真实案例。某块FPGA板上设计了DDR3-800接口DQS和DQ从外部DDR PHY芯片出来到FPGAPCB走线基本等长。最初约束用的是供应商参考设计里的默认值max1.2ns、min0.2ns结果时序报告一片飘红setup裕量是-0.3ns。当时第一反应是参考设计有错后来仔细看外部芯片手册它的Tco max只有0.35nsPCB走线偏差也不超过20ps理论上max应该只有0.4ns左右。参考设计里那个1.2ns是针对另一颗PHY芯片写的参数完全不适用。改回按自己推导的0.45ns之后setup立即转绿。这个案例说明一个关键道理参考设计的约束只能作为格式参考数值必须根据你手上的芯片手册自己算。不同型号的外部DDR器件甚至同一颗粒在不同温度下的Tco参数都有差异抄别人的数值就是把别人的风险也抄了进来。另一个常见问题是hold violation。处理手段不像setup那样方便通过修改input_delay解决因为在现实中数据最早到达时间由物理特性决定。如果约束的min值已经偏大却依然违例往往说明数据在引脚上变化得太快需要让FPGA内部路径变长一些。Vivado的布局布线器会自动在数据路径上插入延迟单元来满足hold但效果有限更主动的做法是在RTL里对输入数据打一拍再做逻辑处理或者用IDELAY的可调延迟给数据增加固定延迟。4.3 从DDR3到DDR4、再到RGMII约束思路的迁移DDR3和DDR4在约束核心思路上没有本质区别都是源同步接口都是DQS随路时钟都要分别约束上升沿和下降沿。区别主要在速率和具体时序参数上。DDR4的接口时钟频率更高数据率动辄2400MT/s以上此时数据有效窗口更窄对PCB等长精度、input_delay计算精度和FPGA内部I/O延迟的控制要求全部上升一个量级。这时候有个很现实的问题手工约束DDR4颗粒的读写接口单靠set_input_delay已经不足以覆盖所有细节Xilinx官方基本都要求使用MIG IP核或UltraScale系列自带的DDR PHY硬核。MIG内部会生成一套非常严格的跨时钟域和数据对齐约束普通开发者自己写控制器很难保证每条时序路径都被正确约束。RGMII接口则是另一个很好的练习对象。RGMII是DDR信号时钟125MHz数据率250Mbps时钟和数据由PHY芯片同时发出标准规定时钟相对于数据的skew为1.5~2.0ns。约束时通常写成create_clock -name rgmii_rxc -period 8.000 [get_ports rgmii_rxc] set_input_delay -clock rgmii_rxc -max 2.0 [get_ports {rgmii_rd[*]}] set_input_delay -clock rgmii_rxc -min 1.5 [get_ports {rgmii_rd[*]}] set_input_delay -clock rgmii_rxc -clock_fall -max 2.0 [get_ports {rgmii_rd[*]}] set_input_delay -clock rgmii_rxc -clock_fall -min 1.5 [get_ports {rgmii_rd[*]}]RGMII在FPGA内部通常配合IDELAY把数据延迟到时钟中心附近采样。调试时用ILA观察数据采样点然后逐步调整IDELAY步进找到稳定的采样窗口再反推约束值。这套调试流程跟DDR接口完全一致理解了DDR的约束原理RGMII基本就是换个参数再来一遍。调DDR接口这几年下来我的体会是set_input_delay表面上只是一条约束命令背后却是对整个外部时序周境的建模能力。写约束前一定要先读懂芯片手册的时序参数再结合PCB走线情况计算数据窗口最后上板用ILA实测校准。靠猜、靠抄最终都会在某一批板子上翻车。如果你正准备开始搞DDR接口别急着写Verilog先把时序模型在纸上画出来把input_delay算明白再动手不迟。
返回列表