4. 输入输出延迟约束:set_input_delay / set_output_delay 在收发器中的应用
收发器接口的时序约束,跟普通GPIO完全是两码事。我刚开始做SerDes项目时,就吃过这个亏——以为set_input_delay和set_output_delay跟以前一样用就行,结果仿真全绿,上板子就挂。后来才明白,收发器的数据是跟随恢复时钟的,这玩意儿跟源同步接口有本质区别。
说白了,收发器的输入输出延迟约束,核心在于理解两个东西:时钟是哪里来的,以及数据相对于时钟的相位关系。你想想看,普通接口的时钟是PLL分出来的,收发器的时钟却是CDR从数据流里硬生生恢复出来的。这能一样吗?
4.1 收发器时序分析的特殊性
收发器内部有PMA和PCS两层。PMA负责串并转换,PCS负责编解码和时钟补偿。时序约束主要发生在两个地方:
- FPGA逻辑到收发器TX接口:数据从FPGA fabric送到TX的PCS层
- 收发器RX接口到FPGA逻辑:数据从RX的PCS层送到FPGA fabric
这两个接口的时钟域完全不同。TX侧用的是发送时钟(通常由参考时钟分频得到),RX侧用的是恢复时钟(CDR从数据里提取的)。
核心要点:收发器的输入输出延迟约束,本质上是约束FPGA逻辑与收发器内部PCS接口之间的时序路径。不是约束外部引脚到FPGA逻辑的路径——那是普通IO的玩法。
4.2 set_input_delay 在收发器中的应用
set_input_delay用在RX侧。数据从外部进入收发器,经过CDR和PCS,最终到达FPGA逻辑。我们需要告诉工具:数据相对于恢复时钟的相位关系是怎样的。
我习惯这样写约束:
# 假设恢复时钟是 rx_clk,数据总线是 rx_data[31:0]
# 数据在恢复时钟的上升沿被采样
create_clock -name rx_clk -period 3.2 [get_pins -hierarchical *rx_clk*]
set_input_delay -clock rx_clk -max 0.5 [get_pins -hierarchical *rx_data*]
set_input_delay -clock rx_clk -min -0.2 [get_pins -hierarchical *rx_data*]
嗯,这里要注意:恢复时钟的周期不是固定的。收发器内部有时钟补偿机制,会插入或删除时钟脉冲来匹配数据速率。所以set_input_delay里的时钟周期,应该用标称值,而不是测量值。
我在项目中遇到过一个问题:某次用10G BASE-R协议,恢复时钟的标称周期是3.2ns(322.265625MHz)。我一开始用了3.2ns,时序全过。后来换了更精确的3.1992ns,反而报了一堆违例。为什么?因为工具在做STA时,用的是理想时钟模型,你给个非整数周期,它反而算不准了。
个人经验:对于收发器接口,set_input_delay的时钟周期用标称值取整即可。收发器内部的弹性缓冲会处理时钟补偿,STA不需要关心那点ppm差异。
4.3 set_output_delay 在收发器中的应用
set_output_delay用在TX侧。数据从FPGA逻辑送到TX的PCS层,然后经过PMA串化后发送出去。约束的是FPGA逻辑到TX接口的路径。
典型写法:
# 假设发送时钟是 tx_clk,数据总线是 tx_data[31:0]
# 数据在发送时钟的上升沿被发送
create_clock -name tx_clk -period 3.2 [get_pins -hierarchical *tx_clk*]
set_output_delay -clock tx_clk -max 0.8 [get_pins -hierarchical *tx_data*]
set_output_delay -clock tx_clk -min -0.3 [get_pins -hierarchical *tx_data*]
这里有个坑:收发器的TX接口通常有内置的FIFO。这个FIFO的读写时钟不同步,写时钟是FPGA逻辑时钟,读时钟是发送时钟。set_output_delay约束的是写时钟域到FIFO的路径,不是读时钟域。
我曾经犯过一个错误:把set_output_delay的时钟设成了发送时钟,但实际数据是跟着FPGA逻辑时钟走的。结果时序分析完全乱套,修了三天才发现是时钟选错了。
避坑指南:约束TX接口时,先搞清楚数据是跟着哪个时钟走的。大多数收发器IP核会提供两个时钟:一个给FPGA逻辑用(比如txusrclk),一个给PCS内部用(比如txclk)。set_output_delay应该用FPGA逻辑侧的时钟。
4.4 收发器特有的约束技巧
收发器接口的约束,跟普通IO有几个关键区别:
| 对比项 | 普通IO | 收发器接口 |
|---|---|---|
| 时钟来源 | PLL/MMCM | CDR恢复或参考时钟分频 |
| 数据宽度 | 单bit或窄总线 | 8/10/16/32/64位并行总线 |
| 延迟值 | 通常0.5~2ns | 通常0.1~1ns,跟线延迟有关 |
| 时钟抖动 | 由PLL决定 | 由CDR和参考时钟共同决定 |
我个人习惯的做法是:
- 先看IP核的用户手册,找到PCS接口的时序图。手册里通常会给出setup/hold时间要求。
- 用set_input_delay/set_output_delay模拟这个时序。比如手册说数据在时钟上升沿前0.3ns有效,那set_input_delay -max就设0.3。
- 留余量。收发器接口的线延迟很难精确估算,我一般会加20%~30%的余量。
重要提醒:收发器内部的PCS层有弹性缓冲,可以吸收时钟相位偏移。但弹性缓冲的深度有限,如果set_input_delay/set_output_delay的约束太松,工具可能会把路径优化到弹性缓冲之外,导致实际工作时数据溢出或下溢。
4.5 实战:一个完整的收发器约束示例
假设我们用的是Xilinx的GTH收发器,配置成10G BASE-R模式。数据宽度是32位,内部时钟是322.265625MHz。
# 1. 创建时钟
create_clock -name rx_clk -period 3.2 [get_pins -hierarchical *rx_clk*]
create_clock -name tx_clk -period 3.2 [get_pins -hierarchical *tx_clk*]
# 2. 约束RX输入路径
# 数据在rx_clk上升沿前0.4ns有效,保持0.2ns
set_input_delay -clock rx_clk -max 0.4 [get_pins -hierarchical *rx_data*]
set_input_delay -clock rx_clk -min -0.2 [get_pins -hierarchical *rx_data*]
# 3. 约束TX输出路径
# 数据在tx_clk上升沿后0.3ns输出,保持0.1ns
set_output_delay -clock tx_clk -max 0.7 [get_pins -hierarchical *tx_data*]
set_output_delay -clock tx_clk -min -0.1 [get_pins -hierarchical *tx_data*]
# 4. 异步时钟域约束(如果RX和TX用不同参考时钟)
set_clock_groups -asynchronous -group [get_clocks rx_clk] -group [get_clocks tx_clk]
这个例子里,我特意把RX和TX的时钟设成了异步。为什么?因为10G BASE-R的RX和TX可能来自不同的参考时钟源,它们之间没有确定的相位关系。如果不加set_clock_groups,工具会去分析跨时钟域路径,报一堆假违例。
嗯,最后说一句:收发器的时序约束,没有标准答案。每个IP核的PCS接口时序都不一样,甚至同一个IP核的不同配置,时序要求也不同。我的建议是:拿到IP核后,先跑一个简单的回环测试,用chipscope抓一下数据,看看实际时序余量有多少。然后再调整约束值,做到既不过紧也不过松。
小技巧:如果你不确定set_input_delay/set_output_delay该设多少,可以先设一个保守值(比如0.5ns),然后看时序报告里的slack。如果slack很大,说明约束太松,可以收紧;如果slack为负,说明约束太紧,需要放松。反复迭代几次,就能找到合适的值。
这张图把收发器输入输出延迟约束的整个知识体系串起来了。从PMA/PCS分层,到RX/TX路径,再到具体的约束命令,最后落到实战建议。你写约束的时候,脑子里要有这张图,知道自己在约束哪一段路径。
可可精品资料库