
1. 从一次芯片收敛失败说起为什么我们需要关注电压缩放芯片功耗一直是后端实现里的老大难尤其是先进工艺节点下功耗、时序、电压三者纠缠在一起任何一环出问题都会让项目卡在收敛阶段。我印象很深的一次经历某款SoC在跑完PrimeTime时序收敛、DRC也全部清掉之后在post-silicon的voltage margining测试中却出现了异常。芯片在标称电压下工作完全正常但只要电压往下调5%关键路径上的setup就崩了系统直接死机。当时团队里第一反应是“hold没修好”或者“工艺角选得太激进”结果查了一圈发现都不是——问题出在我们用PrimeTime做signoff的时候压根没有把**电压缩放voltage scaling**这个变量正确带进时序分析导致静态时序分析STA得到的裕量跟芯片实际表现对不上。那次之后我系统性整理了在PrimeTime里做voltage scaling的完整流程也在后续几个多电压域项目中验证了整套方法。这篇文章就把这套思路、命令、脚本和踩坑经验完整写出来希望能帮到正在做多电压域项目、或者被低功耗时序收敛卡住的朋友。2. 为什么要做voltage scaling低功耗设计的必然选择2.1 多电压域成为标配电压不再是单一常数过去做一颗芯片全芯片统一供电后端时序分析时给一个固定的工作电压比如1.8V或0.9V所有单元库的延迟查表都基于这同一个电压。设计简单、分析也简单。但现在不一样了。移动设备、AI加速器、IoT芯片几乎都在用多电压域设计核心逻辑跑在低电压域0.6V~0.8V追求能效比IO和模拟单元跑在高电压域1.2V~1.8V保证信号完整性和驱动能力某些模块会在runtime时动态调整电压实现DVFS动态电压频率调节。于是“电压”从单一常数变成了一个分布在不同模块、不同时段都会变化的变量。如果STA阶段还是按一个固定电压去分析后果就是高估某些路径的时序、低估另一些路径的时序最后signoff结果和实际芯片行为脱节。2.2 电压变化对单元延迟的影响远比你想象的大在标准单元库里单元延迟通常用非线性延迟模型NLDM或者复合电流源模型CCS去描述延迟值主要受三个因素影响输入转换时间input transition time输出负载电容output load capacitance工作电压supply voltage前面两个是后端工程师天天都在调的东西但电压对延迟的影响其实更“隐蔽”。以40nm工艺的一个标准反相器为例当电压从1.1V降到0.9V延迟可能增加30%~50%如果电压继续往下降接近单元的“最小工作电压”附近时延迟会呈指数级恶化这已经不是查表能精确拟合的范畴了。所以在低功耗多电压域设计中电压缩放不是可选项而是必须做对的基础工作。否则你签核signoff时给的时序报告到了芯片实硅上根本站不住脚。提示并不是所有库都能做voltage scaling。SC 9mm库和部分特殊单元库可能只有单一电压模型做多电压域分析之前一定要先确认库的charactersiticPVT信息里电压维度是否可伸缩。3. 最常见的三种voltage scaling场景动手操作之前先分清你要做的是哪一种。我在项目中遇到过、也实际处理过的主要是下面三种3.1 场景一统一电压缩放Uniform Voltage Scaling整颗芯片只有一个电压域但由于工艺偏差或系统功耗策略实际工作电压会在一定范围内浮动。比如芯片目标电压1.0V但实际可能从0.95V到1.05V浮动。对应到PrimeTime里就是让工具基于一个“电压缩放系数”重新计算约束和延迟通常配合update_timing和set_voltage来实现。这种场景最简单常见于良率分析和功耗-性能折中调优。3.2 场景二多电压域静态分析Multi-Voltage Static Analysis芯片内部有多个电压域每个域有固定的供电电压比如CPU域0.75V、GPU域0.85V、IO域1.8V域与域之间通过level shifter或者isolation cell做接口。做STA时每个单元必须使用对应电压域的库模型跨域路径需要特殊处理。这是所有多电压域项目的“基本功”PVPhysical Verification阶段会专门检查是否所有单元都处于正确的voltage area内。3.3 场景三动态电压频率调整DVFS下的分析DVFS是目前移动和高性能芯片中最复杂的低功耗策略。运行时某个模块的电压会随负载动态变化频率也随之调整。芯片往往工作在“电压-频率”的多个组合点上。设计师需要在设计阶段就分析多个电压-频率组合下的时序收敛情况确保每一个工作点都满足时序要求。有些项目甚至会使用“电压感知时序分析”Voltage-Aware STA技术让工具在lib库中根据电压自动插值计算延迟而不是简单地选择某一个角的库。我当时处理的那颗SoC就是典型的DVFS场景模块在0.65V/400MHz和0.95V/1.2GHz两个工作点之间切换结果我只分析了0.85V最差情况5%电压下降时setup崩掉。回头看其实只要在每一个电压-频率组合下分别做STA问题就能提前暴露。4. PrimeTime做voltage scaling的完整实操流程接下来是重头戏我把在PrimeTime里做voltage scaling的流程拆成四个阶段讲按顺序做基本不会漏项。4.1 第一步读入库与设计建立电压感知环境PrimeTime在分析多电压时要能识别电压设置第一步是读库的时候就要“告诉”工具哪个库属于哪个电压域。典型的PrimeTime脚本长这样# 读入Liberty库注意要带setup条件 set_app_var search_path [list . /home/design/libs /home/design/netlist] set_app_var target_library [list slow_0.63V_n40.lib slow_0.72V_n40.lib slow_0.85V_n40.lib] # 读入netlist和约束 read_verilog ../netlist/top.v current_design top # 创建电压域并关联单元 create_voltage_area -name VA_CORE -coordinate {0 0 200 200} create_voltage_area -name VA_IO -coordinate {200 0 400 200} set_voltage 0.75 -object_list [get_voltage_areas VA_CORE] set_voltage 1.80 -object_list [get_voltage_areas VA_IO] # 更新Timing环境 set_operating_conditions -library slow_0.75V_n40 -voltage 0.75 update_timing注意这里的set_voltage是给电压域分配电压参考值不是主流工具里的set_voltage -object_list语法在每个版本里都完全一样实际用的时候先man set_voltage查一下当前版本支持的关键字。在DC综合阶段也有一份对应的UPF文件如果你用UPF流程建议直接把create_voltage_area和set_voltage从UPF里读进来避免手动写两遍load_upf ../upf/top.upf4.2 第二步为不同电压域设置Operating Conditions多电压域设计里不同库文件对应不同的电压PrimeTime允许每个电压域单独指定operating condition。这里有一个非常关键的点PrimeTime的全局operating condition只作用于单电压域设计多电压域时要用per-voltage-area的方式。正确的做法是通过set_operating_conditions -voltage或者利用set_min_library分别设定wc和bc库# 为每个电压域设置最差情况WC和最好情况BC库 set_operating_conditions -name OC_WC_CORE \ -library slow_0.75V_n40 \ -voltage 0.75 \ -analysis_type on_chip_variation set_operating_conditions -name OC_WC_IO \ -library slow_1.80V_n40 \ -voltage 1.80 \ -analysis_type on_chip_variation # 指定哪个单元属于哪个operating condition默认按library自动匹配 set_voltage_area_operating_conditions -voltage_area VA_CORE -operating_condition OC_WC_CORE set_voltage_area_operating_conditions -voltage_area VA_IO -operating_condition OC_WC_IO如果你不想手动给每个domain指定operating condition也可以在读库时利用库自带的“operating condition”信息自动匹配前提是库里正确定义了operating_conditions组。在大规模SoC上我习惯最后用一条命令检查是否所有单元都被正确分到了预期电压域report_voltage_area如果输出中还有单元落在unconstrained区域说明电压域划分有遗漏必须补上不然后面report timing结果是错的。4.3 第三步处理跨电压域路径——Level Shifter与Isolation Cell单域设计不需要操心跨域路径多电压域必须处理。跨电压域会产生两个问题低电压域到高电压域的上升沿可能不足需要level shifter提高驱动高电压域到低电压域的漏电和闩锁风险需要isolation cell或者always-on buffer。PrimeTime分析跨域路径时会自动识别level shifter和isolation cell的插入方式但前提是你在create_voltage_area阶段就指定了power switch或者level shifter的cell。实战中我踩过一个坑某次项目中综合阶段插了level shifter但PrimeTime里没有定义level_shifter_voltage_area结果工具把跨域路径当成普通路径分析延迟全算错了。后来在PT脚本里加上set_level_shifter_strategy -rule low_to_high -voltage_area VA_CORE -cells level_shift_lh set_level_shifter_strategy -rule high_to_low -voltage_area VA_IO -cells level_shifter_hl重新跑分析时序报出来的裕量才和实际行为吻合。4.4 第四步执行时序分析并解读结果环境搭好后跑时序分析本身不复杂# 约束 read_sdc ../constraints/top.sdc # 时序分析 update_timing -full report_timing -path_type full -delay_type max -nworst 10 -slack_lesser_than 0.0 report_power -voltage_area all这里要特别提醒一个细节update_timing后如果改了电压设置必须重新update一次。因为电压变化会改变单元延迟时序图timing graph中的延迟弧需要刷新。另外在做multi-corner分析时建议用report_timing -group按电压域分别出报告而不是只盯着全局最差路径。否则你看到的“最差路径”可能全集中在高电压域低电压域的真实风险反而被掩盖了。5. 结合电压缩放的库里到底有什么秘密5.1 Liberty库中电压相关属性解读要用好PrimeTime做voltage scaling你至少得看得懂.lib文件里的电压相关字段。典型的一段NLDM库描述长这样cell (INV_X1) { pin (Y) { timing () { related_pin : A; timing_sense : negative_unate; cell_rise (scalar) { values (0.01, 0.02, 0.03); } rise_transition (scalar) { values (0.005, 0.01, 0.02); } } } }乍一看好像没有电压信息确实NLDM查表时通常只包含输入转换时间和输出负载两个维度。电压对延迟的影响是通过在不同电压条件下生成多个时序库来体现的——同样的单元0.72V的库和0.85V的库delay和transition的数值完全不同。所以库这个层面其实已经隐含了“电压-延迟”关系。PrimeTime在单点分析single-point模式下直接用对应库的延迟值在电压缩放模式下会利用不同电压库之间的差异做插值。5.2 PrimeTime如何用多个电压库做插值在DVFS分析中我们经常要分析0.72V和0.85V两个工作点但如果库只提供了这0.72V和0.85V两个点的延迟而实际工作电压可能是0.78V怎么办两种方案重新采集库准确但成本高让PrimeTime根据已有库插值fast estimation精度取决于库的线性程度。PrimeTime提供了一种特殊模式它在分析时能基于已加载的多组电压库对延迟进行线性插值linear interpolation对应变量是delay_calc_with_si下基于负载电容的CCI插值或者是传统LVFlibrary voltage format还未完全普及。但在实际项目里我更推荐另一种做法在STA脚本里对多个电压角分别跑完整分析然后把结果合起来看。用命令set_app_var timing_analysis_mode multi_corner set_operating_conditions -name WC_0P75V_SS -library ss_0p75v -voltage 0.75 set_operating_conditions -name WC_0P85V_SS -library ss_0p85v -voltage 0.85Multi-corner模式比插值更稳健每个corner都是完整的库模型无需担心插值误差。插值模式更适合做探索性分析或者早期架构评估。提示不要指望插值结果和实测完全一致。在0.72V到0.85V这种宽范围内做线性插值误差可能达到5%-10%。如果时序裕量本来就小于这个值插值结果只能给你一个“趋势”不能作为signoff依据。5.3 电压缩放对噪声和功耗的影响除了延迟电压缩放还会影响功耗和噪声分析。功耗方面动态功耗跟V²成正比所以电压微调对功耗影响极大。PrimeTime在做report_power时如果电压已经设对了它计算出来的动态功耗才会合理。否则会出现“报出来的功耗比实测低30%”这种离谱情况。噪声IR-drop和crosstalk也与电压强相关。做signal integritySI分析时PrimeTime需要用到set_voltage信息来计算受害者与攻击者的voltage margin。如果电压设错了SI结果同样不可信。6. 动态电压频率调整DVFS下的PrimeTime实战6.1 定义电压-频率工作点DVFS设计的特点是多个“电压-频率”对分析时要遍历所有工作点。例如Workload电压V频率MHz高性能0.951200平衡0.85800低功耗0.72400每个工作点对应一组约束时钟频率和一组库电压。在PrimeTime里需要分别创建不同的分析view或者session。推荐做法是# Workload 1: 高性能 set_app_var current_session HO_PERF set_operating_conditions -name WC_0P95V -library ss_0p95v -voltage 0.95 set_clock_period -name clk 0.833 update_timing report_timing -path_type full ./report/perf_wc.rpt report_power ./report/perf_power.rpt # Workload 2: 平衡 set_app_var current_session BAL set_operating_conditions -name WC_0P85V -library ss_0p85v -voltage 0.85 set_clock_period -name clk 1.25 update_timing report_timing -path_type full ./report/bal_wc.rpt report_power ./report/bal_power.rpt6.2 DVFS下最容易漏掉的两类检查实际项目中DVFS最怕两类问题第一类是电压切换期间的时序问题。当电源管理单元从0.95V切到0.72V时电压不是瞬间跳变的而是有斜坡时间。在这个过渡期间单元延迟处于“中间状态”既不是高压库也不是低压库。如果此时时钟还在跑就可能出现竞争冒险。解决办法在SDC里对电源切换相关信号设置set_case_analysis或者用pwr_switch相关约束约束时序弧。最保险的做法是在切换期间让模块暂停时钟这也是大多数芯片选择的做法。第二类是跨电压域路径在DVFS工作点下的优先检查。不同电压域的电压可能同时切换切换速度不一致时跨域路径上的setup/hold可能同时恶化。在分析时必须把跨域路径单独拎出来仔细看。6.3 我用的一个实用脚本片段下面这个脚本是我在多电压DVFS项目里用的基础框架你可以在其基础上加自己的约束检查# 载入库和设计 set_app_var search_path [list . /home/design/libs] set_app_var target_library [list ss_0p72v.lib ss_0p85v.lib ss_0p95v.lib] read_verilog ../netlist/top.v current_design top link_design # 载入UPF定义电压域 load_upf ../upf/top.upf # 定义operating condition set_operating_conditions -name WC_0P72V -library ss_0p72v -voltage 0.72 set_operating_conditions -name WC_0P85V -library ss_0p85v -voltage 0.85 set_operating_conditions -name WC_0P95V -library ss_0p95v -voltage 0.95 # 按照UPF中的电压域指定每个域的OC foreach {va oc} {VA_CORE WC_0P85V VA_IO WC_0P95V VA_SRAM WC_0P72V} { set_voltage_area_operating_conditions -voltage_area $va -operating_condition $oc } # 读入约束 read_sdc ../constraints/top.sdc # 按工作点遍历 foreach {sess freq} {PERF 0.833 BAL 1.250 LP 2.082} { set_app_var current_session $sess set_clock_period -name clk $freq update_timing -full report_timing -path_type full -nworst 20 -delay_type max ./report/${sess}_wc.rpt report_timing -path_type full -nworst 20 -delay_type min ./report/${sess}_bc.rpt report_power -voltage_area all ./report/${sess}_power.rpt }这段脚本直接就能跑通你在实际项目里替换掉库名、路径和时钟周期即可。跑完后别忘了把多份报告里的“最差最差的极限情况”最差setup slack最差hold slack汇总到一个表方便对比。7. 常见问题与排查技巧实录7.1 跨电压域路径分析异常这是被问得最多的问题。症状是跨域路径的setup slack特别差甚至为负但同域路径余量都很好。排查思路先确认level shifter是否正确插入在原理图里看跨域路径上是否出现了level shifter单元。如果没有回溯综合脚本看是约束没传到位还是UPF漏定义了level shifter策略。确认isolation cell和level shifter是否被正确“识别”在PrimeTime里用report_delay_calculation或者report_timing -through看路径经过哪些单元类型。检查两个电压域的operating condition是否设置正确错误地让低电压域的IO单元用高电压库会产生虚假的极差延迟。7.2 电压改了但时序结果不变如果你执行了set_voltage和update_timing但是report_timing的延迟完全没变大概率是operating condition没改。电压值和库是绑定的你只改了电压参考值库里延迟表没有切换工具内部仍然在用旧的延迟信息。解决办法同时设置set_operating_conditions的库和电压并且用report_operating_conditions确认当前生效的OC到底是哪个。7.3 功耗报告明显离谱动态功耗过高或过低且排除了vector和switching activity问题后强烈建议先查电压域设置。功耗公式里最重要的项是C * V² * f如果V设成了1.8V但设计实际是0.75V计算出来的功耗会高好几倍。用report_voltage_area逐域检查电压值这比逐个单元查功耗可靠得多。7.4 多工作点分析速度太慢DVFS分析需要跑多个工作点如果每个工作点都做full chip的OCV片上变化计算时间会成倍增加。建议把分析拆细早期做快速抽查时用set_app_var timing_update_mode incremental只重算变化了的路径Signoff阶段再做全量OCV并且开多线程加速set_app_var multicore_enable true如果是差异化较明显的模块可以用set_disable_timing屏蔽无关宏单元的时序弧只分析关键模块。8. 几种保存分析结果与归档的小技巧项目上不止一次遇到这样的情况时序报告跑出来了但别人甚至你自己两周后回看时完全想不起当时用的是哪套库、哪个电压、哪版约束。所以我的习惯是每跑一轮电压缩放分析都把环境信息一起打印归档report_operating_conditions report_voltage_area report_clock -attributes report_analysis_type然后把这份报告跟timing/power报告放在同一个文件夹命名规则类似top_WC_0P85V_perf_20250115。这种“结果环境”双报告的方式后面做ECO或者接收其他团队审查时都会轻松很多。再补充一点如果你用的PrimeTime较新比如2022以后的版本可以考虑用write_session保存当前分析session后续直接restore省去重新读库读网表的时间。尤其在大规模SoC上这一步能帮你把单次分析的启动时间从半小时压缩到一两分钟。9. 实际项目中我总结的几条经验最后讲几段比较“体感”的经验都是踩过坑换来的。第一库的电压覆盖范围一定要在项目早期确认。如果你只拿到一个0.85V的库却要分析0.72V~0.95V的DVFS范围后面无论脚本写得再漂亮都不过是“无米之炊”。项目启动阶段就跟库团队确认好哪些电压点会出库、精度多高直接决定后续STA的玩法。第二跨电压域路径必须手工过一遍。不是说不信任工具而是工具能自动处理“标准”情况但总会有某些特殊路径——比如时钟树上的缓冲器、测试逻辑里的扫描链——可能跨域但没被正确插上level shifter。我会每年每个tapeout轮次专门跑一个脚本把design里所有起点和终点电压域不同的path打出来人工核对一遍这个习惯救过我至少三次。第三电压缩放和IR-drop分析要联动看。静态STA假设每个单元都拿到名义电压但真正到芯片上由于电源网络电阻的存在实际供电电压会低于名义值IR-drop。如果你在STA里已经把时序裕量压得很紧一旦IR-drop超过预期芯片就可能跑不到目标频率。所以我用PrimeTime做电压缩放分析时会预留至少IR-drop degradation的margin具体数值取决于是用静态IR分析还是动态IR分析。第四别在最后的DVFS signoff阶段才想起做电压缩放。最理想的情况是在综合阶段就开始约束多电压域在布局布线阶段做初步分析在signoff阶段做完整验证。拖得越晚发现问题后要改的东西越多代价呈指数增长。我刚工作的时候总觉得STA就是跑一条命令看slack后来做了低功耗项目才知道电压、功耗、时序这几条线是缠在一起的。把这些变量理清楚、分析做扎实signoff才有底气。提示至于更进阶的“实际电压降感知时序收敛”等方法属于后端低功耗与IR分析的融合方向需要在项目中把Power Intent、UPF和PrimeTime的设置一体考虑这会是一篇单独的长文。先把这个基础流程跑通后面的路就好走了。