跳转到主要内容
瑞苏盈科专栏
我与瑞苏盈科板卡的开发故事

set_false_path 别乱用:XDC 例外约束避坑

judy 提交于

文章来源: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 那条。