文章来源:FPGA入门到精通
本文收获:
分清 set_false_path / set_clock_groups / set_multicycle_path 三条例外约束的使用边界
搞懂「报告红了就加 false_path」错在哪,CDC 到底该怎么约束
避开 multicycle 最隐蔽的坑:hold 值忘了配,hold 违例被掩盖
做时序收敛久了,你会发现最诱人的一行 XDC 不是 create_clock,是 set_false_path。
原因很简单。
跑完综合,report_timing_summary 一片红,红的路径五花八门,但网上给的答案高度统一:「这是假路径,加个 set_false_path 就行」。
你照着贴一行,报告秒绿。
那一刻的爽感,跟删了堆积一个月的未读邮件差不多。
但这一行恰恰最容易埋雷。
它告诉时序工具:这条路径不用查了。
工具很听话,真的不查了。
报告绿得越干净,你离真实风险越远。
等板子上电偶发出错,回头查,才发现是当初那行「让它变绿」的约束把问题盖住了。
这篇把例外约束三件套讲透:什么时候真的能用 set_false_path,什么时候该用 set_clock_groups,multicycle 那个没人提醒就必踩的 hold 坑在哪。
一、set_false_path 到底在说什么
一句话:让 STA 完全跳过某条路径的 setup/hold 检查。
适合它的只有三类「真不用管」的路径:
# 1. 复位释放路径:复位信号本身不用做时序收敛
set_false_path -to [get_pins-hierarchical-filter{NAME=~*rst_reg*/R}]
# 2. 测试/调试逻辑:比如 JTAG、chipscope 探针
set_false_path -from [get_portstest_mode]
# 3. 经过合格同步器/异步 FIFO 的跨时钟路径(配合 clock_groups 用,见第四节)
注意看第 1 条的范围:*rst_reg*/R,只匹配复位寄存器的 R 脚,不是 *rst* 通配整棵时钟树。范围写多宽,是后面最大的坑之一。

二、最常见的三个错误用法
错误 1:拿它处理所有跨时钟域
这是被引用最多的「标准答案」,也是错得最隐蔽的。
两个时钟域之间有些信号确实跨过去了,但跨的方式不同:有的经过两级同步器,有的直接进了异步 FIFO,有的只是普通寄存器对打。你把 clk_a 到 clk_b 之间一刀切:
# 错误示范:两个时钟域之间所有路径全部豁免
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
工具从此不再报告这两个域之间的任何时序问题,包括你漏同步的那些信号。
CDC 最怕的不是报红,是报了红没人看,更怕的是连红都不报。
跨时钟域的正解是 set_clock_groups,见第四节。

错误 2:范围写得太宽
get_clocks 一写就是一整片。
clk_a 域里可能有 200 个寄存器,其中只有 3 个是真正异步的,剩下 197 个是同步逻辑,跟 clk_b 有真实的数据依赖。
你一条 false_path 全豁免,等于把 197 条本应被检查的路径的检查全关了。
范围越宽,隐患越大。能精确到引脚就精确到引脚,能精确到单元就精确到单元,别偷懒写时钟域。
错误 3:把功能路径误判成假路径
异步 FIFO 的读写指针、握手协议的 req/ack,看着都「跨时钟」,但它们承载的是真实数据流,是设计里最需要时序保证的部分。
把它们 false 掉,FIFO 空满标志错乱、握手丢一拍,都是这种「绿油油的隐患」。
这类路径要么用 multicycle 放宽到真实周期数,要么用 max_delay 限定最大延迟,而不是完全不管。
三、什么时候真的该用
写每一条 set_false_path 之前,先过一道自问:这条路径为什么不需要时序检查?
答得上来的,才写。答不上来的,先别写,去读报告搞清楚它为什么红。
能答上来的典型就三类:复位释放(异步复位本来就不吃时钟沿)、测试/调试逻辑(只在特定模式下生效)、以及确实异步、且已经用同步器或异步 FIFO 正确处理的跨域信号。
这三类之外,false_path 基本都是在掩盖问题。
四、CDC 的正确姿势:set_clock_groups
处理跨时钟域,XDC 里有专门命令,叫 set_clock_groups。
它声明「这两组时钟之间没有确定的相位关系」,工具会自动把组间路径当作异步处理,语义清晰、可维护、报告可读。
单 bit 信号跨域,标准三件套:
# RTL 里:两级同步器
# always @(posedge clk_b) begin
# sync_ff1 <= data_from_clk_a;
# sync_ff2 <= sync_ff1;
# end
# XDC 里:声明两域异步
set_clock_groups -asynchronous \
-group {clk_a} \
-group {clk_b}
# 让 Vivado 把同步器两个触发器紧挨放置,减小亚稳态窗口
set_property ASYNC_REGTRUE [get_cellsu_sync/*]
多 bit 数据跨域,用异步 FIFO(格雷码指针),约束写法一样,把读写时钟划成两组。这里没有 false_path 什么事。
区别记牢:false_path 是豁免某条具体路径,clock_groups 是声明两组时钟无相位关系。
后者是 CDC 的标准做法,前者只是它的一种特例表达,用错了地方就会变成错误 1。

五、那个没人提醒就必踩的坑set_multicycle_path
最后说 multicycle。
它解决的问题是:数据不是每个周期都变,默认按 1 周期检查太严了,放宽到真实周期数。
比如一个模块每 3 个周期才采一次数:
# 正确写法:setup 放宽到 3,hold 必须跟着写 2
set_multicycle_path 3 -setup -from [get_cells u_slow/data_reg] -to [get_cells u_slow/out_reg]
set_multicycle_path 2 -hold -from [get_cells u_slow/data_reg] -to [get_cells u_slow/out_reg]
坑就在这:很多人只写 setup 那条。

