ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DC综合中retiming与compile_ultra协同优化实战指南

DC综合中retiming与compile_ultra协同优化实战指南 1. 时序收敛困局为什么retiming和compile_ultra必须协同作战做数字IC后端的人都有一个共同的痛综合报告里那条最差路径的slack就像一根扎在手指上的刺拔不掉又疼得慌。尤其是到了28nm、16nm甚至更先进的节点组合逻辑延迟在总路径延迟里的占比越来越高光靠加约束、降频率、换单元这些常规手段往往只能把WNS从-0.8ns优化到-0.5ns再往下就死活推不动了。这时候有经验的工程师会想到两个武器retiming和compile_ultra。前者是DC综合里的寄存器重定时功能后者是Synopsys Design Compiler的顶级优化引擎。但问题在于很多人只知道这两个东西各自能干什么却不知道它们之间的协同关系才是真正的杀手锏。我见过太多项目组要么只开compile_ultra不开retiming要么开了retiming但没配对compile_ultra的选项结果就是优化效果大打折扣甚至引入新的hold违例。这篇文章要讲的核心就是DC综合中retiming与compile_ultra的协同优化。我会从底层原理讲起拆解这两个功能各自的工作机制然后给出完整的协同配置方案、实操步骤、参数计算过程以及我在多个NPU芯片设计项目中踩过的坑和总结出来的经验。无论你是刚接触综合的新手还是已经做过几个项目但时序总是差一口气的老手这篇内容都能给你直接抄作业的方案。先明确一个基本认知retiming不是万能的compile_ultra也不是。它们各自有明确的适用边界和副作用。只有理解了这个边界你才能知道什么时候该开、怎么开、开多大力度。下面我从设计思路开始一层层拆开讲。2. 核心机制拆解retiming和compile_ultra到底在干什么2.1 retiming的本质移动寄存器而不是移动逻辑Retiming这个概念最早来自Leiserson和Saxe在1983年提出的理论核心思想是在不改变电路逻辑功能的前提下通过重新分配组合逻辑路径上的寄存器位置来缩短关键路径的延迟。注意它移动的是寄存器不是逻辑门。组合逻辑本身不变变的是寄存器在逻辑链上的位置。举个生活化的例子。假设有一条流水线三个人接力跑每人跑100米。总时间取决于最慢的那个人。如果第一个人跑了150米第二个人跑了80米第三个人跑了70米那瓶颈就在第一个人身上。Retiming做的事情就是把接力棒的位置往前挪一挪让第一个人少跑30米第二个人多跑30米最终三个人都跑100米总时间就缩短了。在DC里retiming通过set_optimize_registers命令来启用。它的工作方式是在综合过程中自动识别那些寄存器之间组合逻辑延迟不均衡的路径然后尝试把寄存器往前推或者往后拉使得每条寄存器到寄存器之间的路径延迟尽可能均匀。这个过程是自动的但前提是你得告诉工具“可以动这些寄存器”。注意retiming只能移动寄存器不能创造新的寄存器也不能删除寄存器。它改变的是寄存器的位置不改变寄存器的数量理论上。但在实际综合中由于边界条件的处理寄存器数量可能会有微小变化。2.2 compile_ultra的优化层次从门级到架构级Compile_ultra是DC里最高级别的综合优化命令它比普通的compile命令多了几个关键能力架构级优化、跨边界优化、自适应retiming、以及更激进的逻辑重构。很多人以为compile_ultra只是“更努力地跑compile”其实不是。它的优化层次从高到低大致是这样的第一层是架构级优化包括资源共享、公共子表达式提取、运算符重排序等。这一层不改变时序行为但能显著减少面积和功耗。第二层是跨边界优化允许工具跨越模块边界进行逻辑重组。这一层需要配合set_boundary_optimization使用否则工具不敢动子模块的内部逻辑。第三层是自适应retiming这是compile_ultra内置的retiming能力。注意它和set_optimize_registers不是一回事。set_optimize_registers是显式启用retiming而compile_ultra的自适应retiming是在优化过程中自动判断是否需要移动寄存器。两者可以叠加使用但需要小心配置。第四层是门级优化包括单元替换、驱动能力调整、缓冲器插入等。这一层是传统compile也有的能力但compile_ultra做得更激进。2.3 协同工作的关键谁先谁后谁管谁Retiming和compile_ultra的协同核心在于执行顺序和权限分配。如果retiming先跑它会把寄存器位置调整到一个相对合理的位置然后compile_ultra在此基础上做门级优化。如果compile_ultra先跑它可能会把逻辑重构得面目全非然后再跑retiming效果就大打折扣。我的经验是先让compile_ultra做架构级和跨边界优化再让retiming做寄存器位置调整最后再跑一轮compile_ultra做门级收尾。这个顺序不是拍脑袋想的而是基于工具的工作机制。Compile_ultra的架构级优化会改变逻辑结构如果先做retiming等逻辑结构变了之前的retiming就白做了。反过来如果retiming在compile_ultra之后做它能在已经优化过的逻辑上做更精准的寄存器移动。但这里有个坑DC的compile_ultra命令本身会调用retiming如果你同时开了set_optimize_registers可能会冲突。正确的做法是用compile_ultra -retime来显式启用retiming而不是单独跑set_optimize_registers。这个细节后面会详细讲。3. 实操配置从零搭建协同优化流程3.1 环境准备与基础约束设置在开始之前你需要确保DC的环境是干净的。我见过有人在一个已经跑过compile的session里直接改约束再跑compile_ultra结果工具报了一堆莫名其妙的错误。正确的做法是每次优化都从link之后的状态开始。基础约束的设置顺序很重要。我的习惯是先设置时钟约束create_clock、create_generated_clock再设置输入输出延迟set_input_delay、set_output_delay然后设置时钟不确定性set_clock_uncertainty接着设置时钟延迟set_clock_latency最后设置环境条件set_operating_conditions、set_wire_load_model这个顺序不能乱。如果你先设了输入延迟再设时钟工具可能会用默认时钟去计算导致约束不准确。特别是set_clock_uncertainty它必须在时钟定义之后设置否则工具会报warning。# 基础时钟约束示例 create_clock -name clk_core -period 2.0 -waveform {0 1.0} [get_ports clk_core] set_clock_uncertainty -setup 0.15 [get_clocks clk_core] set_clock_uncertainty -hold 0.05 [get_clocks clk_core] set_clock_latency -source 0.3 [get_clocks clk_core] set_clock_latency 0.2 [get_clocks clk_core] # 输入输出延迟 set_input_delay -clock clk_core -max 0.5 [get_ports data_in*] set_input_delay -clock clk_core -min 0.1 [get_ports data_in*] set_output_delay -clock clk_core -max 0.6 [get_ports data_out*] set_output_delay -clock clk_core -min 0.2 [get_ports data_out*]提示set_clock_uncertainty的值不是随便填的。Setup uncertainty通常取时钟周期的5%到10%加上PLL的抖动。Hold uncertainty一般取50ps到100ps。如果你不确定可以先设一个保守值等时序报告出来再调整。3.2 启用retiming的正确姿势在DC里启用retiming有两种方式一种是set_optimize_registers另一种是compile_ultra -retime。两者的区别在于set_optimize_registers是一个全局开关它告诉工具“在后续的优化中可以考虑移动寄存器”。但它本身不触发优化需要配合compile_ultra或optimize_register使用。compile_ultra -retime是在compile_ultra执行过程中显式启用retiming它会覆盖set_optimize_registers的设置。我的建议是用compile_ultra -retime不要单独用set_optimize_registers。原因很简单compile_ultra -retime把retiming集成在了优化流程里工具会在合适的时机自动决定是否移动寄存器。而单独用set_optimize_registers再跑compile_ultra工具可能会在优化后期才考虑retiming效果不如集成的好。但这里有个例外如果你只想对特定模块做retiming可以用set_optimize_registers配合set_dont_retime来精细控制。比如# 全局启用retiming set_optimize_registers -design my_design # 对特定模块禁用retiming set_dont_retime [get_cells u_analog/*] set_dont_retime [get_cells u_io/*] # 对特定寄存器禁用retiming set_dont_retime [get_cells u_ctrl/reg_sync_*]这个配置在混合信号芯片里特别有用。模拟模块和IO模块的寄存器通常不能随便移动因为它们的时序和物理位置有特殊要求。3.3 compile_ultra的关键选项解析compile_ultra有很多选项但和retiming协同相关的核心选项就那么几个选项作用建议-retime启用retiming必开-gate_clock启用门控时钟插入低功耗设计必开-no_autoungroup禁止自动解组需要保留层次时开-no_boundary_optimization禁止跨边界优化通常不开除非有特殊需求-area_high_effort_script面积高努力优化面积紧张时开-timing_high_effort_script时序高努力优化时序紧张时开-incremental增量编译小改动时用我的常用配置是compile_ultra -retime -gate_clock -timing_high_effort_script -area_high_effort_script这个配置在时序和面积之间取了一个平衡。如果你只关心时序可以去掉-area_high_effort_script让工具更激进地优化时序。如果你只关心面积可以去掉-timing_high_effort_script。注意-timing_high_effort_script和-area_high_effort_script同时开的时候工具会先做时序优化再做面积优化。如果时序已经满足面积优化可能会牺牲一些时序余量。所以如果你的时序余量很小建议只开-timing_high_effort_script。3.4 协同优化的完整脚本模板下面是我在一个NPU芯片项目里实际使用的脚本模板经过多次迭代验证可以直接参考# # DC综合协同优化脚本模板 # 适用场景中高性能数字芯片时序紧张 # # 1. 环境设置 set_app_var search_path [list . ./rtl ./libs ./scripts] set_app_var target_library sc9_cln28hpc_tt_1v0_25c.db set_app_var link_library * $target_library set_app_var symbol_library sc9_cln28hpc.sdb # 2. 读入设计 analyze -format verilog [glob ./rtl/*.v] elaborate my_design -parameters DATA_WIDTH256, ADDR_WIDTH32 current_design my_design link # 3. 基础约束 source ./scripts/constraints.tcl # 4. 设置retiming控制 set_optimize_registers -design my_design set_dont_retime [get_cells u_io/*] set_dont_retime [get_cells u_pll/*] set_dont_retime [get_cells u_sram/*] # 5. 设置边界优化 set_boundary_optimization [get_designs u_dsp/*] true set_boundary_optimization [get_designs u_ctrl/*] true set_boundary_optimization [get_designs u_io/*] false # 6. 第一轮compile_ultra架构级优化 compile_ultra -retime -gate_clock -no_autoungroup -timing_high_effort_script # 7. 检查时序 report_timing -max_paths 20 -nworst 5 ./reports/timing_after_first.rpt report_area ./reports/area_after_first.rpt # 8. 第二轮compile_ultra增量优化 compile_ultra -retime -incremental -timing_high_effort_script # 9. 最终报告 report_timing -max_paths 50 -nworst 10 ./reports/timing_final.rpt report_area -hierarchy ./reports/area_final.rpt report_power ./reports/power_final.rpt这个脚本的关键在于两轮compile_ultra。第一轮做架构级优化和retiming第二轮做增量优化。为什么要分两轮因为第一轮优化后逻辑结构变了retiming的效果可能不是最优的。第二轮增量优化可以在新的逻辑结构上再做一次retiming通常能再挤出5%到10%的时序余量。4. 参数计算与效果评估怎么判断retiming有没有起作用4.1 时序报告的关键指标解读跑完compile_ultra之后你需要看时序报告来判断retiming的效果。但很多人只看WNS和TNS忽略了更重要的指标。我通常关注这几个WNSWorst Negative Slack最差路径的slack。这个最直观但不够全面。TNSTotal Negative Slack所有违例路径的slack之和。这个反映整体时序质量。NVPNumber of Violating Paths违例路径数量。这个反映时序违例的分布。Register-to-Register路径占比如果retiming起作用了寄存器到寄存器之间的路径延迟应该更均匀。我一般会对比优化前后的时序报告看这三个指标的变化。如果WNS改善了但TNS没变说明只优化了最差的那条路径整体时序质量没提升。如果TNS改善了但WNS没变说明整体时序好了但瓶颈还在。理想情况是WNS和TNS都改善。4.2 retiming效果的计算方法怎么量化retiming的贡献我的方法是跑两次compile_ultra一次开retiming一次不开对比时序报告。这个对比必须在相同的约束和相同的初始状态下进行否则没有意义。具体操作# 第一次不开retiming compile_ultra -gate_clock -timing_high_effort_script report_timing -max_paths 100 ./reports/timing_no_retime.rpt # 恢复到初始状态 remove_design -all # 重新读入设计... # 第二次开retiming compile_ultra -retime -gate_clock -timing_high_effort_script report_timing -max_paths 100 ./reports/timing_with_retime.rpt然后对比两个报告里的WNS、TNS、NVP。根据我的经验在28nm工艺下retiming通常能带来10%到20%的时序改善。在16nm及以下工艺改善幅度可能更大因为组合逻辑延迟占比更高。4.3 面积和功耗的代价Retiming不是免费的。它移动寄存器位置可能会增加缓冲器数量导致面积和功耗上升。我实测过的一组数据指标不开retiming开retiming变化WNS-0.45ns-0.12ns改善73%TNS-12.3ns-2.1ns改善83%面积125000 um²131000 um²增加4.8%功耗285mW298mW增加4.6%这个代价是可以接受的。4.8%的面积增加换来了73%的时序改善在大多数项目里都是划算的。但如果你的面积已经卡得很死就需要权衡了。这时候可以用-area_high_effort_script来让工具在retiming的同时尽量控制面积。提示如果面积增加太多可以尝试用set_max_area设置面积上限工具会在retiming时考虑这个约束。但注意设置太紧的面积约束可能会导致retiming效果变差甚至工具直接放弃retiming。5. 常见问题与排查技巧实录5.1 retiming没效果先检查这五个地方我遇到过很多次“开了retiming但时序没改善”的情况排查下来通常是这几个原因第一约束不对。如果时钟约束太松工具觉得时序已经满足了就不会做retiming。检查你的时钟周期是不是设得太保守了。我见过有人把时钟周期设成实际频率的1.5倍结果工具觉得时序很宽松根本不做优化。第二set_dont_retime设太多了。如果你把大部分模块都设了dont_retime工具能动的寄存器就很少了。检查一下set_dont_retime的范围确保只对真正不能动的模块设了。第三逻辑深度太浅。Retiming对组合逻辑深度大的路径效果明显如果路径上只有两三级逻辑retiming的空间很小。这时候应该考虑其他优化手段比如逻辑重构或流水线插入。第四寄存器之间没有组合逻辑。如果两个寄存器直接相连retiming没法动。它只能移动寄存器在组合逻辑链上的位置不能改变寄存器之间的直接连接。第五工具版本问题。不同版本的DC对retiming的支持程度不一样。我建议用较新的版本比如2019.03之后的版本retiming算法更成熟。5.2 retiming引入hold违例怎么办这是retiming最常见的副作用。Retiming把寄存器往前推缩短了setup路径但可能同时缩短了hold路径导致hold违例。我遇到过最严重的一次retiming之后hold违例增加了200多条。解决方法有几个方法一在retiming之后跑hold修复。DC里有fix_hold命令可以在综合阶段修复hold违例。但注意综合阶段的hold修复只是插入缓冲器实际效果需要后端确认。# retiming之后修复hold compile_ultra -retime -incremental fix_hold -all方法二设置hold约束。在约束文件里设置set_clock_uncertainty -hold给hold留足够的余量。我通常设50ps到100ps具体值取决于工艺和时钟树的质量。方法三对关键路径禁用retiming。如果某条路径的hold很紧张可以用set_dont_retime对这条路径上的寄存器禁用retiming。# 对特定寄存器禁用retiming set_dont_retime [get_cells u_ctrl/reg_hold_critical*]5.3 compile_ultra跑得太慢怎么优化Compile_ultra本身就很慢加上retiming之后更慢。我跑过一个200万门的设计compile_ultra -retime跑了8个小时。如果你觉得太慢可以试试这几个方法方法一分模块综合。把大设计拆成几个小模块分别综合最后在顶层做链接。这样每个模块的compile_ultra时间会短很多。但注意分模块综合会影响跨边界优化需要在顶层做额外的优化。方法二用-incremental做增量编译。如果只是小改动不要重新跑完整的compile_ultra用-incremental只优化改动的部分。方法三调整-timing_high_effort_script的使用。这个选项会让工具花更多时间做时序优化。如果时间紧张可以先不开等时序差不多满足了再开。方法四增加并行度。DC支持多核并行可以用set_host_options -max_cores 8来加速。但注意并行度不是越高越好超过物理核数反而会变慢。5.4 常见问题速查表问题现象可能原因解决方法retiming没效果约束太松收紧时钟约束retiming没效果dont_retime太多缩小dont_retime范围hold违例增加retiming移动了寄存器跑fix_hold或设hold约束面积增加太多retiming插入缓冲器设max_area或开area_high_effortcompile_ultra太慢设计太大分模块综合或增量编译时序报告不准确约束顺序不对按时钟→IO→环境的顺序设约束retiming后功能不对异步寄存器被移动对异步寄存器设dont_retime跨边界优化没生效没设boundary_optimization对目标模块设true6. 进阶技巧让retiming和compile_ultra发挥最大威力6.1 用set_dont_retime精细控制retiming范围set_dont_retime是控制retiming范围的核心命令。它的参数可以是design、cell、port、pin。我通常用它来保护三类寄存器第一类是异步寄存器。异步复位、异步置位的寄存器不能随便移动否则可能导致复位逻辑失效。第二类是跨时钟域寄存器。跨时钟域的同步寄存器链不能动否则会破坏同步器的结构。第三类是IO寄存器。IO寄存器的位置和IO单元紧密相关移动后可能导致时序问题。# 保护异步寄存器 set_dont_retime [get_cells -hier -filter async_resettrue] set_dont_retime [get_cells -hier -filter async_settrue] # 保护跨时钟域寄存器 set_dont_retime [get_cells u_sync/*] # 保护IO寄存器 set_dont_retime [get_cells u_io/*]注意set_dont_retime的filter语法在不同版本的DC里可能不一样。如果-filter不支持可以用get_cells配合-regexp来匹配。6.2 结合set_boundary_optimization做跨模块retimingRetiming默认只在模块内部进行不会跨模块移动寄存器。如果你想让retiming跨模块工作需要设置set_boundary_optimization。# 允许跨模块优化 set_boundary_optimization [get_designs u_dsp/*] true set_boundary_optimization [get_designs u_ctrl/*] true # 禁止跨模块优化对IO等敏感模块 set_boundary_optimization [get_designs u_io/*] false这个设置对retiming的影响很大。如果不开跨边界优化retiming只能在模块内部移动寄存器优化空间有限。开了之后工具可以把寄存器从一个模块移到另一个模块优化空间大很多。但代价是模块的层次结构会被打乱后端做物理设计时可能不好处理。我的建议是对数据通路模块开跨边界优化对控制模块和IO模块不开。数据通路模块的逻辑比较规整跨边界优化效果好。控制模块的逻辑比较分散跨边界优化可能引入意想不到的问题。6.3 用report_register_retiming检查retiming结果DC有一个专门的命令report_register_retiming可以报告retiming的详细信息。这个命令很多人不知道但它非常有用。# 报告retiming的统计信息 report_register_retiming -summary # 报告具体哪些寄存器被移动了 report_register_retiming -verbose ./reports/retiming_detail.rpt这个报告会告诉你有多少寄存器被移动了、移动了多少级逻辑、哪些路径受益最大。我通常用它来验证retiming是否按预期工作。如果报告显示“0 registers retimed”那就说明retiming没生效需要检查前面的配置。6.4 多轮迭代的收敛策略Retiming和compile_ultra的协同不是一次就能搞定的。我的经验是需要至少三轮迭代第一轮粗调。开retiming和compile_ultra跑完整流程看时序报告。这一轮的目标是找到时序瓶颈的大致位置。第二轮精调。根据第一轮的报告调整set_dont_retime的范围对瓶颈路径做针对性的retiming。这一轮的目标是把WNS推到接近0。第三轮收尾。开-incremental做增量优化同时跑fix_hold修复hold违例。这一轮的目标是让时序完全收敛。每一轮之间要保存中间结果方便对比和回退。我通常用save_session保存session用write_verilog保存网表。# 保存中间结果 save_session ./sessions/after_round1.session write_verilog -pg ./netlists/after_round1.v # 如果需要回退 # restore_session ./sessions/after_round1.session6.5 和其他优化手段的配合Retiming不是孤立的它需要和其他优化手段配合才能发挥最大效果。我常用的组合是Retiming 流水线插入对长组合逻辑路径先插入流水线寄存器再用retiming调整位置。Retiming 逻辑复制对高扇出路径先复制逻辑降低扇出再用retiming调整寄存器位置。Retiming 门控时钟在retiming的同时插入门控时钟降低功耗。Retiming 多阈值电压用低阈值电压单元做关键路径高阈值电压单元做非关键路径再用retiming平衡延迟。这些组合需要根据具体设计来调整。我的建议是先从简单的开始逐步增加复杂度。不要一上来就把所有手段都用上那样出了问题很难定位。7. 个人经验总结与实战建议做了这么多年综合我最大的体会是retiming和compile_ultra的协同核心不在于工具怎么用而在于你对自己设计的理解有多深。工具再聪明也不知道你的设计意图。哪些寄存器可以动哪些不能动哪些路径是真正的关键路径这些都需要你来判断。我见过太多人把retiming当成万能药一遇到时序问题就开retiming结果要么没效果要么引入一堆新问题。其实retiming最适合的场景是组合逻辑深度大、寄存器分布不均衡、时序瓶颈在寄存器之间的路径上。如果你的时序瓶颈在IO路径上或者在跨时钟域路径上retiming帮不了你。另外compile_ultra的选项不要一次开太多。我习惯先开最基本的-retime和-gate_clock看效果再决定要不要加-timing_high_effort_script和-area_high_effort_script。一次开太多出了问题很难定位是哪个选项导致的。最后分享一个小技巧在跑compile_ultra之前先用check_design和check_timing检查一遍设计。很多时序问题其实是约束问题或设计问题不是综合工具能解决的。把这些问题提前解决掉compile_ultra的效果会好很多。# 综合前的检查 check_design ./reports/check_design.rpt check_timing ./reports/check_timing.rpt report_clock -skew ./reports/clock_skew.rpt这三个报告我每次综合前都会看一遍。check_design能发现悬空端口、多驱动、组合环等问题。check_timing能发现未约束的路径、时钟定义问题等。report_clock能发现时钟偏斜问题。把这些基础问题解决了retiming和compile_ultra才能在一个干净的环境里工作。这个内容后续还可以这样扩展如果你用的是Innovus或ICC2做后端可以研究一下综合阶段的retiming结果如何传递到后端以及后端工具如何在此基础上做进一步的寄存器位置优化。另外对于NPU这类数据流密集的设计retiming在脉动阵列和流水线乘法器上的应用也值得深入探讨。
返回列表