Lingkaの宝藏之地
首页项目归档杂谈说说照片墙音乐友链关于
封面

Vivado 10G/25G Ethernet Subsystem Example Design 的 XDC 约束问题整理

2026-07-04 21:04:47
# FPGA
# 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

十二、经验教训

  1. XDC 虽然长得像 Tcl,但不能完全当成普通 Tcl 脚本使用。
  2. get_clocks 在综合阶段和实现阶段能查到的对象可能不同。
  3. -quiet 不能解决空对象问题,只是隐藏部分 warning。
  4. set_clock_groups 的 -group 不能为空。
  5. 对 GT、Ethernet IP 这类复杂 IP,派生时钟经常在实现阶段才更完整。
  6. 如果某条约束只适合实现阶段,应单独放在 Implementation-only XDC 中。
  7. 对 10G Ethernet Example Design,dclk 和 GT user clocks 一般应按异步时钟域处理。
  8. Timing Failed 不一定代表 RTL 写错,也可能是 CDC 约束没有正确设置。
  9. 查时钟名时,最好在 implemented design 打开后查询。
  10. 不要只看 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

avatar

Lingka

一个在硬件、MCU、FPGA和ARM Linux SoC之间来回折腾的开发者,目标是让每一块板子都乖乖跑起来。

2026年7月

一
二
三
四
五
六
日
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Recent Records

AXU9EGB 双 SFP+(XCZU9EG-FFVB1156-2-I)GT Quad / Lane 选择调试记录

2026-06-25 20:04:14

博客缓存与音乐播放问题总结

2026-06-16 14:42:48