4. 违例原因分析

时序违例,说白了就是信号跑得不够快。但为什么跑不快?原因其实就那么几类。我做了十几年后端,见过的违例,十有八九都逃不出这四个坑:组合逻辑太深、扇出太大、线负载太重、跨时钟域没处理好。

今天咱们就一个一个掰开揉碎了讲。每个坑我都会结合项目里的真实案例,告诉你它长什么样,怎么一眼认出来,以及怎么修。

4.1 组合逻辑过深

这是最经典的时序违例原因。什么叫“过深”?就是两个寄存器之间,串了太多级组合逻辑。你想想看,每一级门都有延迟,串个十几级,路径延迟自然就爆了。

怎么判断?

打开时序报告,看 Data Path 那一栏。如果里面密密麻麻列了十几行甚至几十行 cell,那基本就是组合逻辑过深了。我个人习惯,看到超过 8 级组合逻辑,就会开始警惕。

典型场景: 大的加法器、乘法器、多级 MUX 选择、复杂的比较器。

怎么修?

  • 插寄存器(Retiming / Pipeline): 最直接的办法。把长路径打断,插入一级或多级寄存器。代价是增加 latency,但能大幅提升频率。
  • 逻辑重构: 比如把大的加法器拆成多个小加法器,或者用 CSA(进位保留加法器)代替普通加法器。
  • 综合选项优化: 在综合阶段设置 set_max_fanout 和 set_max_transition,让工具自动优化。
我的经验: 有一次做 AI 加速器,一个 64 位加法器路径延迟直接爆了 500ps。我建议前端同事改成三级流水线加法器,频率从 800MHz 直接干到了 1.2GHz。嗯,有时候修时序,得从 RTL 层面下手。

4.2 扇出过大

扇出,就是一个 cell 的 output 驱动了多少个 cell 的 input。扇出太大,会导致什么?负载电容变大,transition 变慢,延迟自然就上去了。

扇出多少算大?

这个没有标准,跟工艺有关。我个人经验,在 7nm 工艺下,扇出超过 30 就要小心了。在 28nm 工艺下,扇出超过 50 基本就要出问题。

工艺节点 建议最大扇出 典型违例表现
7nm / 5nm 20 - 30 transition 违例,setup 紧张
28nm 40 - 50 信号斜率变缓,hold 也可能出问题
65nm 及以上 60 - 80 主要影响 setup,hold 相对宽松

怎么修?

  • 插入 Buffer Tree: 把一个大扇出拆成多级 buffer 树。比如一个信号要驱动 100 个负载,可以先用一个 buffer 分成两路,每路再驱动 50 个。
  • 复制寄存器(Register Duplication): 对于时钟信号或复位信号,直接复制一个寄存器出来,分担负载。
  • 手动指定 max_fanout: 在 SDC 里设置 set_max_fanout 30 [current_design],让工具帮你自动插 buffer。
注意: 插 buffer 会占用面积和功耗。我曾经在一个项目中,为了修一个扇出违例,插了 200 多个 buffer,结果那片区域密度直接飙到 95%。所以,能少插就少插,优先用 register duplication。

4.3 线负载过重

线负载过重,说白了就是连线太长、绕线太密,导致 RC 延迟太大。这个在先进工艺下越来越严重,因为线宽越来越细,电阻反而变大了。

怎么判断?

看时序报告里的 Net Delay 占比。如果 net delay 占了整个 path delay 的 50% 以上,那基本就是线负载的问题。我见过最夸张的一次,net delay 占了 80%,那根本就是绕线绕成了蜘蛛网。

常见原因:

  • 两个 cell 物理距离太远
  • 绕线层资源紧张,信号被迫绕远路
  • 高扇出信号没有做 buffer tree,导致一根长线拖一堆负载

怎么修?

  • 物理约束: 在 floorplan 阶段就把相关的逻辑摆在一起。比如用 create_bounds 或 create_region 把关键路径上的 cell 圈起来。
  • 手动 placement: 对于特别关键的路径,我会手动指定 cell 的位置,确保它们挨在一起。
  • 使用高层金属绕线: 高层金属电阻小,延迟低。可以在绕线阶段设置 set_preferred_routing_layer,让关键信号走高层。
  • 插入 repeater: 对于超长线,每隔一段距离插一个 buffer 或 inverter,把长线打断成多段短线。
避坑指南: 我曾经在一个 5nm 项目中,一条全局复位信号绕了半个芯片,net delay 直接 1ns。后来我把它改成了分布式复位,每个模块自己产生本地复位信号,问题就解决了。记住,全局信号在先进工艺下是毒药。

4.4 跨时钟域问题

跨时钟域(CDC)问题,是时序分析里最容易忽略、但后果最严重的坑。两个不同频率或不同相位的时钟域之间传数据,如果不做同步处理,就会出现亚稳态,导致数据采样错误。

为什么 CDC 会导致时序违例?

因为 STA 工具默认把所有路径都当作同步路径来分析。如果你没有正确设置 set_false_path 或 set_clock_groups,工具就会去分析那些根本不需要分析的 CDC 路径,结果自然全是违例。

怎么判断?

  • 时序报告里出现大量跨时钟域的违例,而且路径延迟特别大
  • 违例路径的起点和终点分别属于不同的时钟域
  • 这些违例往往在 setup 和 hold 上同时出现

怎么修?

  • 设置 false path: 对于异步 CDC,直接用 set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b] 告诉工具别管这些路径。
  • 设置 clock groups: 对于异步时钟,用 set_clock_groups -asynchronous -group {clk_a} -group {clk_b}。
  • 检查同步器: 确保 CDC 路径上用了正确的同步器(比如双级触发器同步器)。如果同步器没做好,即使 STA 过了,芯片也可能跑飞。
警告: 千万不要为了省事,把所有跨时钟域路径都设成 false path。有些 CDC 是同步的(比如同源分频时钟),设成 false path 会漏掉真正的时序问题。我见过有人这么干,结果芯片流片回来,功能就是不对。

4.5 知识体系总览

下面这张图,是我自己总结的时序违例原因分析框架。每次拿到一个违例报告,我都会按这个思路走一遍,基本不会漏。

时序违例原因分析框架 时序违例 组合逻辑过深 多级门串联,延迟累积 扇出过大 负载电容大,transition 变慢 线负载过重 连线太长,RC 延迟大 跨时钟域问题 异步路径未正确约束 常用修复手段 插寄存器 / Retiming Buffer Tree / 复制寄存器 物理约束 / 高层金属 False Path / Clock Groups

嗯,这四个原因,基本覆盖了 90% 以上的时序违例场景。你拿到一个违例报告,先别急着修,按这个框架走一遍,找到根因,再对症下药。这样修起来,又快又准。

核心总结:
  • 组合逻辑过深 → 插寄存器或逻辑重构
  • 扇出过大 → 插 buffer tree 或复制寄存器
  • 线负载过重 → 物理约束或使用高层金属
  • 跨时钟域问题 → 正确设置 false path 或 clock groups

记住,修时序不是蛮力活,是技术活。找到根因,一刀下去,问题就解决了。