
Vivado 10G/25G Ethernet Subsystem Example Design 的 XDC 约束问题整理
一、工程背景
本工程使用的是 AMD/Xilinx 的 10G/25G Ethernet Subsystem IP,主要配置如下:
Select Core = Ethernet MAC + PCS/PMA 64-bit
Speed = 10.3125G
Data Path Interface = AXI Stream
Num of Cores = 2
Base R/KR Standard = BASE-R
GT RefClk = 156.25 MHz
GT Lane = X1Y4、X1Y6
Control Interface = Control and Status Vectors
测试目标是让 FPGA 板卡上的两个 SFP+ 口互相通信:
SFP0 / GT X1Y4 <---- DAC/光纤 ----> SFP1 / GT X1Y6
Example Design 顶层主要包括:
GT TX/RX 高速收发口
GT 156.25 MHz 参考时钟
INIT_CLK / dclk
sys_reset
pkt_gen_mon 发包检测模块
rx_gt_locked_led
rx_block_lock_led
completion_status
本次问题主要集中在 Vivado XDC 约束文件中,尤其是 GT/MAC 派生时钟和 dclk 之间的异步时钟约束问题。
二、遇到的问题 1:XDC 中使用 concat 报错
1. 报错信息
[Designutils 20-1307] Command 'concat' is not supported in the xdc constraint file.
2. 原因分析
一开始在 XDC 文件中写了类似下面的语句:
set gt_user_clks [get_clocks -quiet -of_objects [concat $gt_rx_userclk_pins $gt_tx_userclk_pins]]
问题在于:
XDC 文件虽然看起来像 Tcl 文件,
但不能把它当成完整 Tcl 脚本使用。
concat 属于普通 Tcl 命令,在 Vivado 的 XDC 约束文件中不能这样直接使用。
3. 解决方法
XDC 文件中尽量不要写复杂 Tcl 逻辑,例如:
concat
if
foreach
proc
XDC 中应尽量只写标准约束命令,例如:
create_clock
create_generated_clock
set_property
set_false_path
set_max_delay
set_clock_groups
如果确实需要复杂 Tcl 逻辑,应该单独写 .tcl 脚本,而不是直接写在 .xdc 文件中。
三、遇到的问题 2:XDC 中使用 if 报错
1. 报错信息
[Designutils 20-1307] Command 'if' is not supported in the xdc constraint file.
2. 原因分析
当时为了判断 clock 是否存在,写了类似下面的判断:
if {[llength $gt_user_clks] > 0} {
set_clock_groups -asynchronous \
-group [get_clocks dclk] \
-group $gt_user_clks
}
但是该写法不适合直接放在 XDC 约束文件中。
3. 解决方法
不要在普通 XDC 文件中写 if 判断。
正确思路是:
简单约束写在 XDC;
复杂判断逻辑写在单独 Tcl 脚本中。
四、遇到的问题 3:set_clock_groups 找不到对象
1. 报错信息
[Vivado 12-4739] set_clock_groups: No valid object(s) found for '-group'
或者:
[Vivado 12-4739] set_clock_groups: No valid object(s) found for '-group [get_clocks {rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1}]'
2. 原来的约束写法
一开始写的是:
set_clock_groups -asynchronous \
-group [get_clocks dclk] \
-group [get_clocks {rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1}]
3. 原因分析
这些时钟:
rx_core_clk_0
rx_core_clk_1
tx_clk_out_0
tx_clk_out_1
是 10G Ethernet / GT 相关的派生用户时钟。
在综合阶段,Vivado 可能还没有生成或识别这些时钟对象,所以:
get_clocks {rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1}
返回的是空对象。
即使加了 -quiet:
get_clocks -quiet xxx
也只能让 get_clocks 本身不打印 warning,不能解决外层命令的问题。
因为外层仍然相当于执行了:
set_clock_groups -group 空对象
所以还是会报错:
No valid object(s) found for '-group'
五、为什么 Tcl Console 能查到 clock,但 XDC 中仍然报错?
1. 在 Tcl Console 中查询
打开 implemented design 后执行:
open_run impl_1
get_clocks
可以查到类似下面的时钟:
gt_refclk_p
init_clk
dclk
qpll0clk_in[0]
qpll0refclk_in[0]
rxoutclk_out[0]
rxoutclkpcs_out[0]
txoutclk_out[0]
rxoutclk_out[0]_1
rxoutclkpcs_out[0]_1
txoutclk_out[0]_1
rx_core_clk_0
rx_core_clk_1
tx_clk_out_0
tx_clk_out_1
并且执行:
get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]
能正常返回:
rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1
2. 关键区别
这里要注意:
Tcl Console 中能查到,是因为已经打开了 implemented design。
也就是说,这些时钟在实现后的设计中是存在的。
但是普通 XDC 文件在综合阶段也会被读取。
综合阶段这些时钟对象可能还没有建立,所以会报错。
因此,本质问题是:
XDC 约束读取阶段 和 clock 对象生成阶段 不一致。
六、遇到的问题 4:Implementation 完成但 Timing Failed
1. 报错信息
[Timing 38-282] The design failed to meet the timing requirements.
Timing Summary 中可以看到类似路径:
dclk -> rx_core_clk_0
dclk -> rx_core_clk_1
dclk -> tx_clk_out_0
dclk -> tx_clk_out_1
并且 WNS/TNS 很差。
2. 原因分析
dclk 是初始化、控制、状态相关的 free-running clock。
而下面这些时钟:
rx_core_clk_0
rx_core_clk_1
tx_clk_out_0
tx_clk_out_1
是 10G Ethernet / GT 相关的用户时钟。
它们之间本来就不是同一个同步时钟域,而是异步时钟域。
如果没有正确添加异步时钟组约束,Vivado 就会尝试检查:
dclk -> rx_core_clk
dclk -> tx_clk_out
这类跨时钟域路径的 setup/hold timing。
于是就会出现 Timing Failed。
3. 结论
这个 Timing Failed 不一定是 RTL 写错了,而是:
dclk 和 GT user clocks 之间的 CDC 约束没有正确设置。
七、最终解决方案:单独建立 Implementation-only XDC
1. 核心思路
不要把下面这条约束放在普通 XDC 中:
set_clock_groups -asynchronous \
-group [get_clocks -quiet dclk] \
-group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]]
原因是普通 XDC 会在综合阶段读取,而综合阶段这些 clock 可能还不存在。
正确做法是:
把 dclk 与 GT user clocks 的异步时钟组约束,
单独放到一个只用于 Implementation 的 XDC 文件中。
八、具体操作步骤
第一步:删除或注释原 XDC 中的错误约束
在原来的:
xxv_ethernet_0_example_top.xdc
中,把最后类似下面的约束删除或注释掉:
# set_clock_groups -asynchronous \
# -group [get_clocks -quiet dclk] \
# -group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]]
注意:
这条约束不能放在综合阶段也会读取的普通 XDC 文件里。
第二步:新建 Implementation-only XDC 文件
新建文件:
async_clk_impl.xdc
文件内容如下:
########################################
# async_clk_impl.xdc
# Only used in Implementation
# dclk vs GT user clocks
########################################
set_clock_groups -asynchronous \
-group [get_clocks -quiet dclk] \
-group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]]
第三步:设置该 XDC 只用于 Implementation
在 Vivado 左侧 Sources 中选中:
async_clk_impl.xdc
然后在 Source File Properties 中设置:
USED_IN_IMPLEMENTATION = 勾选
USED_IN_SYNTHESIS = 不勾选
也可以用 Tcl 命令设置:
set_property USED_IN {implementation} [get_files async_clk_impl.xdc]
这样综合阶段不会读取这条约束,Implementation 阶段才会使用它。
九、检查真实 clock 名的方法
如果不确定真实时钟名,可以先打开 implemented design:
open_run impl_1
然后查询所有时钟:
get_clocks
也可以模糊查询:
get_clocks *clk*
get_clocks *dclk*
get_clocks *rx*
get_clocks *tx*
本工程中查询到的关键时钟包括:
dclk
rx_core_clk_0
rx_core_clk_1
tx_clk_out_0
tx_clk_out_1
进一步验证:
get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]
如果返回:
rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1
说明这些 clock 名是有效的。
十、重新运行流程
修改完成后,建议重新跑完整流程:
Reset Synthesis Run
Run Synthesis
Run Implementation
Report Timing Summary
如果 Timing 通过,再生成 bitstream:
Generate Bitstream
十一、本次问题总结
问题 1:XDC 里用了 concat
错误原因:
XDC 中不适合直接使用普通 Tcl 的 concat 命令。
解决方法:
不要在 XDC 里写复杂 Tcl 逻辑。
问题 2:XDC 里用了 if
错误原因:
XDC 中不适合直接写普通 Tcl 的 if 判断。
解决方法:
不要在 XDC 中写 if 判断;
复杂判断逻辑应放到单独 Tcl 脚本中。
问题 3:set_clock_groups 找不到对象
错误原因:
综合阶段相关 clock 对象还不存在,get_clocks 返回空。
解决方法:
把相关 set_clock_groups 约束放到 Implementation-only XDC 中。
问题 4:Implementation Timing Failed
错误原因:
dclk 和 GT/MAC 用户时钟之间的异步路径没有正确切掉。
解决方法:
set_clock_groups -asynchronous \
-group [get_clocks -quiet dclk] \
-group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]]
并且这条约束应该放在:
async_clk_impl.xdc
中,同时设置:
USED_IN_IMPLEMENTATION = true
USED_IN_SYNTHESIS = false
十二、经验教训
- XDC 虽然长得像 Tcl,但不能完全当成普通 Tcl 脚本使用。
get_clocks在综合阶段和实现阶段能查到的对象可能不同。-quiet不能解决空对象问题,只是隐藏部分 warning。set_clock_groups的-group不能为空。- 对 GT、Ethernet IP 这类复杂 IP,派生时钟经常在实现阶段才更完整。
- 如果某条约束只适合实现阶段,应单独放在 Implementation-only XDC 中。
- 对 10G Ethernet Example Design,
dclk和 GT user clocks 一般应按异步时钟域处理。 - Timing Failed 不一定代表 RTL 写错,也可能是 CDC 约束没有正确设置。
- 查时钟名时,最好在 implemented design 打开后查询。
- 不要只看 Tcl Console 能不能查到 clock,还要注意该约束在哪个阶段被读取。
十三、最终推荐的 XDC 文件结构
建议把 XDC 文件分开管理:
constraints/
├── xxv_ethernet_0_example_top.xdc
│ ├── INIT_CLK_P/N 约束
│ ├── gt_refclk_p/n 约束
│ ├── 官方 example design 自带 set_false_path / set_max_delay / waiver
│
├── sfp.xdc
│ ├── SFP0 GT TX/RX 管脚约束
│ ├── SFP1 GT TX/RX 管脚约束
│ ├── SFP TX_DISABLE / LED / Reset 等板级 GPIO 约束
│
└── async_clk_impl.xdc
└── dclk 与 GT user clocks 的异步时钟组约束
仅用于 Implementation
十四、最终 async_clk_impl.xdc 内容
########################################
# async_clk_impl.xdc
# Only used in Implementation
# dclk vs GT user clocks
########################################
set_clock_groups -asynchronous \
-group [get_clocks -quiet dclk] \
-group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]]
文件属性设置:
USED_IN_IMPLEMENTATION = true
USED_IN_SYNTHESIS = false
十五、上板后优先检查的信号
bitstream 生成后,下载到 FPGA 板卡。
两个 SFP+ 口用 DAC 线或光纤互连。
优先观察下面这些信号:
rx_gt_locked_led_0
rx_gt_locked_led_1
rx_block_lock_led_0
rx_block_lock_led_1
completion_status[4:0]
正常情况下希望看到:
rx_gt_locked_led_0 = 1
rx_gt_locked_led_1 = 1
rx_block_lock_led_0 = 1
rx_block_lock_led_1 = 1
1. rx_gt_locked_led = 1 的含义
说明 GT 接收侧已经锁定。
也就是 GT 物理层收发器本身基本起来了。
2. rx_block_lock_led = 1 的含义
说明 10GBASE-R PCS 已经完成 block lock。
也就是 10GBASE-R 的 64b/66b 码流已经能被正确识别,链路基本起来。
3. 如果 rx_gt_locked_led = 1,但 rx_block_lock_led = 0
这种情况说明:
GT 本身已经锁定,
但是 10GBASE-R 码流没有锁住。
此时需要重点检查:
SFP 线缆或光模块是否正常
TX_DISABLE 是否拉低
GT TX/RX 管脚是否约束正确
P/N 极性是否接反
GT Lane 是否对应 X1Y4 和 X1Y6
156. 25 MHz GT 参考时钟是否正确
两端速率和编码方式是否一致
十六、最终结论
本次 XDC 问题的核心不是 10G/25G Ethernet Subsystem IP 配置错误,而是:
XDC 约束的读取阶段 和 clock 对象生成阶段 不一致。
综合阶段找不到下面这些派生时钟:
rx_core_clk_0
rx_core_clk_1
tx_clk_out_0
tx_clk_out_1
所以不能把相关 set_clock_groups 约束放在普通 XDC 文件里。
最终解决方法是:
单独创建 async_clk_impl.xdc;
只用于 Implementation;
在里面约束 dclk 与 GT user clocks 为异步时钟组。
这样可以同时解决两个问题:
1. 避免综合阶段 clock 对象为空导致报错;
2. 解决 Implementation 阶段 dclk -> rx_core_clk / tx_clk_out 的 Timing Failed 问题。
最终推荐约束如下:
set_clock_groups -asynchronous \
-group [get_clocks -quiet dclk] \
-group [get_clocks -quiet [list rx_core_clk_0 rx_core_clk_1 tx_clk_out_0 tx_clk_out_1]]
该约束应放在:
async_clk_impl.xdc
并设置为:
USED_IN_IMPLEMENTATION = true
USED_IN_SYNTHESIS = false