这几年做数字芯片,功耗已经不是一个“能省则省”的加分项,而是很多项目的硬性必选项。我最早接触UPF(Unified Power Format,统一电源格式)是在一个低功耗MCU项目的后端阶段,那时候项目里要支持多种工作模式,待机电流要求压到微安级别,光靠架构层做降频省出来的功耗远远不够,电源域划分、多电压、电源关断全部都得安排上,而UPF就是把这些电源意图用标准格式写给EDA工具看的桥梁。
这篇文章适合三类人看:做数字前端和RTL设计的工程师,被后端和物理实现逼着改UPF的验证工程师,以及刚接触低功耗SoC项目、想搞清楚UPF和低功耗设计之间关系的学生。我尽量少讲空泛的概念,多写实际项目里能用得上的语法、流程和踩坑经验,争取你看完之后能直接上手写自己的UPF文件,哪怕只是一个最小规模的模块,也能理解它在整个设计流程里是怎么流转的。
1. 低功耗为什么成为芯片设计的头等大事
1.1 功耗问题的来源
芯片功耗分为动态功耗和静态功耗两大类。动态功耗主要来自两个部分:一个是CMOS电路翻转时对负载电容充放电产生的功耗,另一个是PMOS和NMOS在翻转瞬间同时导通形成的短路电流功耗。一个比较直观的公式是 P_dyn = αCV²f,其中α是翻转活动因子,C是节点电容,V是工作电压,f是工作频率。电压在公式里是平方项,所以电压降一点,动态功耗降得非常明显,这也是为什么多电压域和动态电压频率调节(DVFS)会成为低功耗设计的核心手段。
静态功耗则主要来源于漏电流,包括亚阈值漏电、栅极漏电和阱漏电等。随着工艺制程不断微缩,阈值电压持续降低,亚阈值漏电呈指数级上升,在先进工艺下静态功耗已经占到芯片总功耗的很大一部分,甚至在一些待机场景下占据绝对主导。
拿我前两年接触的一个用国产MCU做可穿戴设备传感器的项目举例,系统在大部分时间处于低频待机状态,动态功耗已经压得很低,但漏电依然让电池跑不到标称时长。芯片端的低功耗设计要解决的,就是动态和静态两个战场同时下手——动态靠降压降频,静态靠电源关断和低漏电工艺库。
1.2 低功耗设计的三道门槛
第一道是续航门槛。无论是智能手表、蓝牙耳机还是工业传感器节点,电池容量被物理尺寸卡死,用户还要越来越长的续航,这几乎是所有终端设备项目都要面对的死命题。第二道是散热和封装门槛。性能不断提高,芯片功耗升高,热密度跟着上去了,散热成本、封装成本都在涨,功耗控制不好,产品的整体成本也要失控。第三道是可靠性和市场门槛。功耗过高带来的高温会加速器件老化,影响可靠性,同时电磁兼容、噪声问题也会加重。
这也是为什么这两年低功耗MCU产品层出不穷,比如华大半导体的HC32系列、Nordic的nRF系列,大家都把待机电流从微安级往纳安级压。实际开发中我发现,光靠掉电模式、中断唤醒这些MCU层面的软件调度还不够,芯片本身必须在硬件设计上就具备多电源域和电源关断能力,否则软件再优化也突破不了物理底座的天花板。硬件底子好,软件才能在上面做好功耗调度。
1.3 低功耗设计需要一套统一的“电源意图”描述
过去做低功耗设计,很多团队靠的是Excel表格和PPT——电源域有哪些、每个域正常工作电压多少、关断之后需要哪些隔离策略、哪些寄存器要保留,全部记在文档里。画图一时爽,等到流转给后端和验证的时候,每个人的理解都会有偏差,工具也不知道你的电源意图,只能靠人肉在门级网表里手动插入电平转换器和隔离单元,改一版迭代一遍,效率极低而且容易漏。
UPF要解决的就是这个问题。它用一套标准化语法把电源域的划分、供电网络的连接、电源开关策略、隔离策略、电平转换策略以及寄存器的保持策略全部描述出来,前端、后端、验证、签核各环节都能读同一份文件,工具可以直接基于UPF来做自动化插入和检查。本质上,UPF就是把工程师脑子里的“电源设计意图”变成机器可解析的“电源规格书”,是打通前后端功耗设计的关键枢纽。
2. 低功耗设计技术栈与UPF的位置
2.1 常见的低功耗设计手段
时钟门控是门槛最低、应用最普遍的低功耗手段。很多模块在大部分时间并不需要时钟,比如一个等待外设事件唤醒的接口控制器,通过时钟门控把Clock切掉,该模块的动态功耗几乎归零,但状态存储仍然保持,恢复也快,成本非常低。它的本质是降低活动因子α。
多电压域和DVFS是另一层思路。芯片里不同的模块对性能要求不一样,CPU核心可能需要0.8V高压跑高频,而GPIO接口、低速外设可能0.6V就足够。DVFS还能在运行过程中动态调节电压,典型场景是手机SoC里的大小核搭配,按负载自动切频切压,省下来的电量肉眼可见。
电源关断(Power Gating)是更激进的手段,把空闲模块的电源直接切掉,静态漏电被彻底掐断。但电源切断了,数据就丢了,所以需要配合保留寄存器把关键状态数据留住,还需要隔离单元防止悬浮信号传到还在供电的模块里产生漏电路径。
隔离单元的作用是当某个域的电源关断后,把输出信号钳制到一个固定的安全电平,避免关断域和常开域之间出现不受控的电流通路。电平转换器则是用来处理不同电压域之间的信号传输,比如0.8V域的信号要进入到1.2V域,如果不做转换,驱动可能不足,逻辑无法正确识别。
最后还有存储器层面的处理,比如SRAM工作电压单独供电、深度睡眠模式的数据驻留控制,以及低功耗状态下的分块读写策略。这些都属于低功耗设计范式里一环扣一环的局部优化。
2.2 为什么不用CPF而用UPF
低功耗设计意图的标准,行业里其实有两家流派。一个是Synopsys主推的UPF,目前已经是IEEE 1801标准;另一个是早期Cadence推的CPF(Common Power Format)。最终市场选择了UPF作为更通用的标准,原因在于UPF允许在RTL的不同层级灵活设定Scope和策略,跟RTL设计结合的粒度更自由,而且UPF通过“电源状态表”(Power State Table)来描述多域工作模式的兼容性,在处理复杂多电压域场景时表达能力更强。
实际项目里,几乎不会有人再拿CPF做新项目了,UPF已经成为主流EDA工具链的默认支持格式。无论是前端仿真、逻辑综合、DFT插入还是后端布局布线,UPF文件在每一个环节都有对应的处理方式,这也是它能被串联进全流程的根本原因。
2.3 UPF在整个IC设计流程中的流转
一份UPF文件,起步时由设计架构师或者前端设计工程师编写,描述的是功能级的电源意图,不涉及具体SoC物理实现的细节。逻辑综合阶段,综合工具读入UPF和RTL,根据UPF定义插入隔离单元、电平转换器和保留寄存器,同时完成电源开关逻辑的推断。综合之后的网表带上了电源管理单元,UPF也跟着更新成更贴近门级网表约束的格式。
到了后端布局布线阶段,后端工程师把综合后生成的UPF作为约束给到APR工具,工具会根据UPF中的供电网络关系来做电源网络规划、放置电源开关单元、连接隔离和电平转换单元,并检查电源域的物理实现是否满足要求。功耗分析阶段,工具会结合翻转率信息和UPF中描述的电压、工作模式计算各电源域的动态和静态功耗。
验证环节也有专门的UPF仿真。仿真器会根据UPF描述在RTL仿真阶段模拟电源的关断、恢复、隔离行为,检查设计在电源状态切换时功能是否正确。一条链路下来,UPF文件虽然一直在演化,但核心的电源意图从RTL到网表保持一致,这就是UPF价值的放大效应所在。
3. 读懂UPF核心语法:从电源域到电源开关
3.1 最小可用的UPF文件长什么样
写UPF第一步总是定义电源域。电源域是一组逻辑模块的集合,它们共享同一组供电网络。看一个最简单的例子:
set_scope rtl_top create_power_domain PD_CPU -include_scope create_power_domain PD_ALWAYS_ON create_supply_port VDD_CORE -domain PD_CPU create_supply_port VDD_AON -domain PD_ALWAYS_ON create_supply_net VDDN_CORE -domain PD_CPU create_supply_net VDDN_AON -domain PD_ALWAYS_ON create_supply_net VSSN connect_supply_net VDDN_CORE -ports VDD_CORE connect_supply_net VDDN_AON -ports VDD_AON connect_supply_net VSSN -ports VSS set_domain_supply_net PD_CPU -primary_power_net VDDN_CORE -primary_ground_net VSSN set_domain_supply_net PD_ALWAYS_ON -primary_power_net VDDN_AON -primary_ground_net VSSN这段代码的意思是:在rtl_top这个模块下创建两个电源域,PD_CPU和PD_ALWAYS_ON。PD_CPU是可以通过电源门控关断的域,PD_ALWAYS_ON是常开域。接着定义两个Supply Port(外部供电端口)和两组Supply Net(内部供电网络),再用connect_supply_net把端口和网络连起来,最后通过set_domain_supply_net指定每个域的主电源网络和主地网络。
很多初学者会忽略set_domain_supply_net这一步,只建立网络不指定域的供电关系,工具在后续处理时往往会报错或者默认推断出错误结果。我写UPF的习惯是,每创建完一个域,马上就跟上set_domain_supply_net,把供电关系一口气定义清楚,避免后面遗漏。
3.2 隔离单元和电平转换的表达方式
电源域关断后,如果输出信号没有被钳制,输出会悬空,下游常开域电路可能因为输入端电压不稳定形成贯通电流,甚至不止是漏电的问题,严重的会影响常开域的逻辑状态。所以必须给关断域的输出加隔离单元。UPF里用set_isolation来约束:
set_isolation iso_cpu -domain PD_CPU \ -isolation_power_net VDDN_AON \ -clamp_value 0 set_isolation_control iso_cpu -domain PD_CPU \ -isolation_signal iso_ctrl \ -isolation_sense high \ -location self第一句告诉工具,PD_CPU域需要加隔离单元,隔离单元本身由常开电源VDDN_AON供电,输出在隔离状态下钳到0。第二句指定隔离控制信号是iso_ctrl,高电平有效,且隔离单元放在PD_CPU域内部边界。
为什么特别强调隔离单元的供电电源是VDDN_AON而不是VDDN_CORE?如果隔离模块跟着关断域一起断电,那它自己都失电了,还怎么承担钳制任务?所有需要常开域在关断期间保持功能的逻辑,供电都必须挂在常开电源网络上。这是一个非常容易踩的坑。
电平转换器放在电压不同的两个域之间。UPF里的描述是:
set_level_shifter ls_cpu_aon -domain PD_CPU \ -applies_to outputs \ -location self \ -threshold 0.72这条命令说的是PD_CPU域的输出在送出域之前要插入电平转换器,转换阈值是0.72V。当两个相邻域的工作电压差超过一定数值时,工具就会根据threshold参数决定是否需要插入level shifter。阈值设置要参考标准单元库中电平转换单元的转换范围,设得太激进会导致工具插一堆没必要的单元,浪费面积和延迟;设得太宽松又会漏插,后续时序分析导出大量违例。
3.3 电源开关和保留寄存器
电源门控的电源开关单元在UPF里通过create_power_switch来声明:
create_power_switch sw_cpu \ -domain PD_CPU \ -input_supply_port {in_power VDDN_AON} \ -output_supply_port {out_power VDDN_CORE} \ -control_port {sleep_en sleep_en} \ -on_state {on_state in_power {!sleep_en}}这个命令把PD_CPU域里的内部供电网络VDDN_CORE和外部常开电源网络VDDN_AON通过一个受sleep_en控制的开关连接起来。当sleep_en为低,开关导通,VDDN_AON给VDDN_CORE供电;当sleep_en拉高,开关断开,PD_CPU域彻底断电。
电源关断后,如果有些关键状态还需要在唤醒后恢复,就需要保留寄存器。UPF里用set_retention描述:
set_retention ret_cpu -domain PD_CPU \ -retention_power_net VDDN_AON \ -retention_control {ret_ctrl high}这里指定PD_CPU域的保留寄存器由VDDN_AON供电,ret_ctrl为高时进入保持状态。保留寄存器本质上是一个带影子锁存的寄存器,主电源掉电后,数据被转移到由常开电源供电的影子锁存器里,唤醒时再恢复到主寄存器。代价是面积变大、时序变差,所以只对关键状态寄存器使用,不能无差别替换。
我见过一个项目把整块RAM的地址寄存器全做了retention,面积暴涨近20%,实际上很多状态完全可以靠软件在唤醒后重新配置,根本不需要硬件保持,做了纯粹是浪费。
3.4 多电压域之间的供电关系
真实项目的电源域往往不止两个,比如主控域、IO域、模拟域、射频域、备份域等。UPF通过电源状态表(Power State Table)描述多个电源域在不同工作模式下的电压和状态组合:
create_pst pst_main -supplies {VDDN_CORE VDDN_AON} add_pst_state active -pst pst_main -state {0.8 1.2} add_pst_state standby -pst pst_main -state {OFF 1.2}这段定义了两种工作状态:active状态下VDDN_CORE为0.8V、VDDN_AON为1.2V;standby状态下VDDN_CORE完全关断(OFF),VDDN_AON保持1.2V。电源状态表的完备性非常重要,如果状态之间的转换没有覆盖,后端工具做电源网络分析和验证时就会漏掉某些工作模式的检查。我建议把所有产品定义的工作模式列成矩阵,再逐一对应到PST状态里,确保没有遗漏。
4. UPF落地实操:从RTL到签核
4.1 UPF和RTL的配合方式
UPF文件在项目里一般和RTL同步维护。常见做法是RTL顶层下有一个专门的目录存放UPF文件,并同步到验证环境里。前端设计在创建电源域时,需要和RTL中模块的实例名严格对应,最好的统一方式是直接用模块名来定义域范围,避免工程名字对不上。
UPF和RTL的配合还有一个关键点:供电网络在不同层次Scope中的可见性。UPF的set_scope命令把后续命令的作用范围统一切到某个设计层次,后续创建的域、网络、隔离约束都默认在该Scope下。如果RTL层次有较大调整,比如模块下沉、改名,UPF里的Scope引用就会失效,工具直接报找不到对象。
有个项目经历让我印象深刻,重构了子系统模块的层级后,UPF里引用的旧层次还在生效,仿真初始阶段一切正常,一到电源切换测试就报信号找不到。排查了大半天,最后发现是两个UPF文件引用了同一逻辑功能但不同层次的模块,这种问题靠抽检很难发现,最好能写一个简单的脚本来检查UPF中引用的层次是否都能在RTL里找到。
4.2 逻辑综合阶段的UPF应用
综合工具读入UPF和RTL后,会根据UPF描述自动插入隔离、电平转换、保留寄存器和电源开关相关的控制逻辑。综合时要注意的几点:
第一,综合约束文件(SDC)里要把控制隔离和保留信号的时钟约束设对。隔离控制信号和保留控制信号往往在关断域掉电之后才有效,这就意味着它们必须在常开域里稳定不变,创建时钟树时要把这些信号当作与时钟同级的重要网络来处理,否则时序分析会出大问题。
第二,UPF中声明的电源开关会在综合时推断成实际的电源开关单元,这些单元和普通标准单元的时序模型不一样,综合工具在优化逻辑时可能不会把它们当成普通逻辑来处理,所以一定要设置好综合工具的低功耗选项,让工具自动处理。
第三,综合后的网表和UPF文件要一起交付给DFT和后端,千万不要只交付网表不交付UPF。一旦后端的UPF和综合用的UPF不一致,所有和电源相关的DFT测试、扫描链生成都会出问题。
4.3 UPF仿真:功能验证里的电源切换演练
UPF仿真在当前主流仿真器里基本都支持,比如Synopsys VCS、Mentor Questa这些工具都能做UPF-aware仿真。仿真器的做法是先解析UPF建立供电网络和逻辑域的关系,然后在仿真模型里加入电源行为,比如某个域关断后,域内寄存器的值会变成X态,直到恢复电源并完成复位才重新有效。
写电源切换测试序列时,最重要的一条原则是:测试用例必须验证“隔离控制信号要早于电源关断生效”,以及“电源恢复稳定之后才能释放隔离控制信号”。这两个时序关系搞反了,芯片在实际运行时会有不确定状态传播,功能测试却可能一点问题都发现不了。
具体仿真序列一般是这样的:先让系统进入正常态,然后断言隔离控制信号,等隔离控制生效后拉高sleep_en关断电源,模拟一段时间的掉电状态,再拉低sleep_en恢复电源,等待电源稳定后释放隔离控制信号,最后检查保留寄存器的值和唤醒后的执行路径。整个过程需要带时序信息地在波形上确认,不能用功能级代码完全替代。
我踩过一次坑,只验证了隔离信号钳住之后关电源,但没验证“隔离信号的撤销必须先于电源恢复”这个反向时序。后来在后端时序分析阶段发现一个寄存器存在数据保持路径违例,回头看仿真波形才意识到问题出在隔离撤销得太早,导致数据在电源尚未完全稳定时被读走。所以仿真用例最好同时覆盖隔离与关断、恢复与撤销这四种组合的时序关系。
4.4 功耗分析中的UPF输入
做功耗分析时,除了芯片在真实场景里的翻转率(Switch Activity)文件,UPF提供的电源状态定义还会直接影响功耗计算的准确性。功耗分析工具会根据PST里的状态组合计算各域的动态功耗和静态功耗,在standby状态下工具会把对应域的静态功耗按OFF值处理,而不只是把动态功耗清零。
综合和APR后做功耗优化时,我通常的做法是先跑一遍基于UPF的功耗分析,看哪个电源域的静态功耗占比异常,再针对性检查是不是隔离单元供电接错、电源开关默认状态不对、或者某些本该属于关断域的逻辑被错误挂到常开电源上。这些问题在UPF文件里埋得很深,如果分析时没有按照PST做多状态功耗计算,很难暴露出来。
5. 常见问题与排查技巧实录
5.1 UPF仿真中的典型问题
仿真中遇到最多的一类问题是掉电域内信号出现X态扩散。现象是关断域电源切掉后,X态从寄存器传到常开域逻辑里,导致常开域出现大量未知值。排查时先确认隔离单元有没有正确插入,其次看隔离控制信号是不是在电源关断前就已经稳定。如果隔离单元没有按预期生效,很可能是set_isolation的domain范围没设置正确,隔离单元被放在了错误的位置。
还有一类问题信号名颠倒导致的保留控制失效。UPF里retention_control的信号方向必须和RTL里电源管理单元的控制信号一致,经常有工程师把高电平有效写成低电平有效,仿真时看保留寄存器在掉电期间就被改写,或者恢复后数据变成X态。排查时直接在波形里找到retention信号和对应寄存器数据,对比时间点就能确认问题。
5.2 综合和后端阶段的高频坑
综合阶段见到最多的错误是两个域之间缺电平转换器。工具默认只在电压差超过一定阈值时插入,但如果后端的电压降分析结果显示某些路径上的压降超过阈值,level shifter可能就不够。遇到这种问题,建议一是检查UPF里的threshold设置,二是通过后端功耗分析结果反馈回来调整UPF策略。
电源开关控制信号跨域是另一个大麻烦。控制sleep_en的信号如果从常开域传到关断域,中间没有任何隔离和同步处理,电源关断瞬间控制信号抖动一下就可能造成开关误动作。规范的做法是:电源开关控制信号在常开域内做同步处理,然后通过隔离单元传递,且UPF中要用特定的约束声明这个信号不需要插入level shifter,否则工具会自动插入一个电平转换器,导致时序裕量不足。
5.3 我个人的UPF管理习惯
UPF文件和RTL一样需要版本管理,而且最好和RTL在同一次提交里一起更新,避免出现UPF和RTL不同步的情况。每次修改UPF之后,先跑一遍UPF语法检查和UPF-aware仿真,再往下流转到综合,不要指望后端和验证帮你去发现UPF的问题。
另外,我在项目里习惯用脚本自动检查UPF引用的信号是否都在RTL中存在。这个脚本不复杂,提取UPF里的信号名,和RTL的端口、寄存器列表做比对,跑完能节约大量手工排查时间。还有一个小技巧,在综合和仿真之前先把UPF里的create_power_domain列成清单,和设计规格书里的电源域定义逐一打勾,确认没有多余或遗漏的域,这一步虽然琐碎,但每次都能拦住一些低级错误。
写在最后的一点体会
UPF初学的时候看着像一堆命令行拼凑出来的约束,觉得比写RTL简单,但真正上手做项目才发现,它的难点不在语法,而在对芯片电源管理架构的理解。你只有清楚知道每个电源域什么时候通电、什么时候关断、哪些信号需要隔离、哪些寄存器必须保留,才能写出一份所有工具和团队都满意的UPF。这份文件写得好不好,直接体现在后端物理实现的质量和验证团队的返工次数上。
我个人实际项目里的建议是:不要等RTL全部写完再补UPF,最好在模块级规格确定后就开始搭UPF框架,用最简版本把电源域和供电网络定义出来,随着设计成熟再逐步补充隔离、电平和保留策略。UPF做得越早,设计里能被低功耗手段优化的空间就越大,后端和验证团队的日子也越好过,最后芯片的功耗数据才真正扛得住产品需求。