4. 综合进阶:时序约束(SDC)编写、综合优化技巧、面积与速度的权衡、综合报告解读

综合这一步,说白了就是把 RTL 代码翻译成门级网表。但光翻译出来没用,你得告诉工具——「我想要什么样的芯片」。这个「告诉工具」的过程,就是写 SDC。

我刚开始做后端时,总觉得 SDC 是前端的事。后来有一次,综合出来的网表时序惨不忍睹,前端说「我约束写好了」,结果一查,时钟定义错了。嗯,从那以后,我都是自己亲手过一遍 SDC。

4.1 SDC 到底在约束什么?

SDC 的全称是 Synopsys Design Constraints。它定义了芯片的时序环境。工具根据 SDC 来优化电路。

核心约束就这几类:

  • 时钟定义:频率、波形、时钟组
  • 输入输出延迟:信号从片外来、往片外去的 timing budget
  • 时序例外:false path、multicycle path
  • 设计规则:max_transition、max_capacitance、max_fanout

核心原则:SDC 要「紧」但不要「死」。太松了工具不优化,太紧了工具过度优化导致面积爆炸。

4.2 时钟约束——最基础也最容易翻车

时钟是时序的脉搏。脉搏乱了,整个芯片就乱了。

# 创建一个 100MHz 的时钟,占空比 50%
create_clock -name clk -period 10.0 [get_ports clk]

# 生成时钟(分频时钟)
create_generated_clock -name clk_div2 \
  -source [get_ports clk] -divide_by 2 \
  [get_pins u_div/clk_out]

我见过最典型的翻车案例:有人把 create_clock 写成了 create_generated_clock,结果综合工具认为两个时钟完全异步,跨时钟域的路径全被忽略。流片回来,跨时钟域的数据采样出错。

注意:生成时钟的 source 必须是时钟源 pin,不是 clock port。写错了工具不会报错,但时序分析会漏掉路径。

4.3 输入输出延迟——把芯片放进系统里看

芯片不是孤岛。你得告诉工具:片外信号什么时候来,片内信号什么时候要走。

# 输入延迟:片外信号在时钟沿后 2ns 到达片内
set_input_delay -clock clk -max 2.0 [get_ports data_in]

# 输出延迟:片内信号需要在时钟沿前 3ns 到达片外
set_output_delay -clock clk -max 3.0 [get_ports data_out]

这里有个坑:set_input_delay 是相对于时钟沿的延迟。如果写反了符号,工具会认为你有 8ns 的 slack,实际上根本跑不到 100MHz。

我的习惯:先画一张时序图,把时钟沿、数据窗口、输入输出延迟都标出来。画清楚了再写 SDC,基本不会错。

4.4 时序例外——别让工具做无用功

不是所有路径都需要在一个时钟周期内完成。有些路径天生就是假的,有些路径需要多个周期。

# False path:异步时钟域之间的路径
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

# Multicycle path:数据需要 2 个周期才能稳定
set_multicycle_path -setup 2 -from [get_pins u_slow/data_reg/CK] \
  -to [get_pins u_slow/data_reg/D]

为什么要设 false path?你想想看,两个异步时钟域之间的路径,工具非要把它优化到 10ns 以内,结果就是面积白白增加,时序还未必能收敛。

我曾经接手一个项目,综合出来的面积比预期大了 30%。查了半天,发现是跨时钟域的路径没设 false path,工具把所有力气都花在了不可能收敛的路径上。

4.5 综合优化技巧——让工具听你的话

综合工具不是万能的。你得告诉它哪些路径重要,哪些路径可以牺牲。

优化方向 命令/策略 适用场景
面积优先 set_max_area 0 成本敏感、时序宽松
速度优先 group_path -name critical -weight 5 高频模块、关键路径
功耗优先 set_max_dynamic_power 移动设备、低功耗场景
平衡策略 默认设置 + 手动调整权重 大多数项目

我个人习惯的做法是:先跑一版默认策略,看哪些路径是瓶颈。然后针对瓶颈路径单独设 group_path,把权重调高。这样不会全局过度优化。

4.6 面积与速度的权衡——没有免费的午餐

这是数字后端永恒的话题。面积小、速度快、功耗低——三者最多取其二。

为什么面积和速度会冲突?说白了,速度快的电路需要更大的驱动能力,更大的驱动能力意味着更大的晶体管,更大的晶体管就是更大的面积。

  • 逻辑复制:把一个大扇出的逻辑复制成多份,减少负载,提升速度。代价是面积翻倍。
  • 单元升级:把低驱动单元换成高驱动单元,速度提升 10%,面积增加 20%。
  • 路径重定时:把组合逻辑在寄存器之间重新分配,不增加面积但可能影响布局。

经验法则:综合阶段不要追求极限速度。留 10%-15% 的时序余量给后端布局布线。否则后端一插时钟树,时序就崩了。

4.7 综合报告解读——别只看 slack

综合跑完了,工具会吐一堆报告。很多人只看 slack 是不是正的,其实远远不够。

我一般会看这几份报告:

  1. 时序报告:最差路径的 slack、路径深度、单元类型
  2. 面积报告:组合逻辑面积、时序逻辑面积、物理面积
  3. 功耗报告:动态功耗、静态功耗、漏电功耗
  4. 设计规则报告:max_transition、max_capacitance 违规
  5. 利用率报告:标准单元利用率、宏单元利用率

举个例子,如果时序报告显示最差路径的 slack 是 -0.5ns,但路径深度只有 10 级,那说明单元驱动能力不够。如果路径深度有 30 级,那说明逻辑级数太多,需要改 RTL。

避坑指南:我曾经只看 slack 为正就认为综合通过了。结果后端布局布线后,时序全面崩溃。原因是综合时没设 set_max_transition,工具用了超大驱动单元,后端根本布不通。

4.8 综合流程中的 SDC 检查清单

每次综合前,我都会过一遍这个清单:

检查项 检查内容 常见问题
时钟完整性 所有时钟是否正确定义? 漏定义生成时钟
IO 约束 输入输出延迟是否合理? 符号写反、数值过大
时序例外 跨时钟域是否设 false path? 漏设导致面积爆炸
设计规则 max_transition 是否设置? 未设导致后端无法收敛
时钟组 异步时钟是否分到不同组? 同组导致过度约束

嗯,这个清单我用了好几年,每次都能帮我发现一两个遗漏。

4.9 综合进阶知识体系

下面这张图总结了本章的核心脉络:

综合进阶知识体系 SDC 约束输入 时钟约束 IO 延迟约束 时序例外 综合优化引擎 面积优先 速度优先 功耗优先 综合报告解读(时序/面积/功耗/DRC) 核心目标:在面积、速度、功耗之间找到最优平衡点

从这张图可以看得很清楚:SDC 约束是输入,综合引擎根据约束做优化,优化过程中需要在面积、速度、功耗之间做权衡,最后通过报告来验证结果。

每一步都有坑,每一步都有技巧。但只要你把基础打牢了,这些都不是问题。

最后说一句:综合是后端流程的第一步,也是最重要的一步。综合做不好,后面布局布线再怎么折腾也救不回来。花时间把 SDC 写对、把优化策略调好,后面会省很多事。