1. 背景拆解:为什么UPF2.0和Power State Table是低功耗设计的“通用语言”
做数字IC的人对低功耗这件事都不陌生,从MCU级别的软件调优到SoC级别的多电压域设计,核心思路其实一脉相承。拿STM32L151C8T6A这类经典低功耗MCU来说,Run、Sleep、Stop、Standby几种模式大家都很熟悉,跑系统时切到Sleep,需要低功耗外设时进Stop,要极致省电就Standby。但问题来了:在MCU上你靠软件改写寄存器就能切换模式,到了芯片前端设计阶段,你怎么把“某些模块可以断电、某些模块在休眠时必须有电”这个意图,准确无误地传达给综合、布局布线、验证工具?靠口头沟通肯定不行,靠注释也没人会看,你得有一套机器可读、语义明确的描述方式。
这就是UPF(Unified Power Format)要解决的问题。UPF是IEEE 1801标准,专门用来描述芯片的功耗意图,包括电源域划分、电源网络连接、电源状态定义、电平转换策略等。UPF2.0相比早期的UPF1.0,最大的变化之一就是把Power State Table(简称PST)推到台前,让电源状态的描述有了统一规范的语法。PST本质上就是芯片的“模式说明书”,它告诉所有下游工具:这个设计在什么条件下进入什么功耗模式,每个电源域在对应模式下处于什么供电状态。
这篇文章我准备从一个实际项目切入,完整走一遍UPF2.0中Power State Table的编写流程,包括语法细节、与Power Domain和Supply Set的配合关系、综合验证阶段的注意事项,最后附上可以直接参考的完整代码。不管你是做前端集成、低功耗验证,还是后端实现的工程师,这套方法都可以直接抄作业。
2. UPF2.0核心概念梳理:Power Domain、Supply Set与PST的角色分工
2.1 先分清三个容易混淆的概念
很多初学者看到UPF文件头就大了,因为Power Domain、Supply Set、Power State这几个词来回出现,概念互相嵌套。我用一个尽量直白的类比来解释:把整个芯片想象成一套房子。
- Power Domain:相当于房子的功能分区。卧室、客厅、厨房各有独立的供电回路,可以单独控制。在UPF里,
create_power_domain就是把RTL里的一组模块圈成一个“可以独立供电”的物理区域。 - Supply Set:相当于每个区域接入的电路规格。卧室需要220V,某些敏感设备需要稳压5V。在UPF里,
create_supply_set把一组电源/地网络打包,比如主电源、备用电源、地,构成一个供电集合。 - Power State Table:相当于房子的“用电模式总表”。比如夜间模式:卧室断电、客厅维持5W小夜灯供电、厨房完全断电;外出模式:全屋断电只留冰箱和安防。PST就是把这些“模式”用标准化表格描述出来。
这三者的关系是:Power Domain 依赖哪些 Supply Set 供电,Supply Set 之间的电压组合决定了 Power State,PST把这种组合与状态命名绑定。工具看到PST就能推断出每个模式下哪些模块供电、哪些模块掉电、哪些模块处于数据保持状态。
2.2 UPF2.0相比早期版本到底改了什么
UPF1.0时代其实也有电源状态描述,但语法比较粗糙,命令集不如2.0完善,各工具厂商解析时容易产生歧义。UPF2.0把状态描述统一为add_power_state命令加create_pst命令的组合,核心变化可以总结为三点:
- 状态对象化:原先散落的
power_state伪命令被标准化为可命名的power state对象,每个状态拥有明确的-state映射列表。 - Supply Set 成为一等公民:PST基于Supply Set来枚举状态,而不是直接基于电压端口。这样设计的好处是,供电集合可以跨层次复用,描述的逻辑层级更贴近真实物理设计。
- 支持层次化:UPF2.0明确支持模块级UPF与顶层UPF的分层描述,PST也可以局部定义再逐层向上抽象,这对大规模SoC很重要。
2.3 从MCU低功耗模式理解PST的“应用场景”
回到STM32L151C8T6A低功耗设计的例子,这颗芯片的Stop模式需要保留SRAM数据、RTC时钟,但CPU核心时钟关闭;Standby模式则几乎全断,仅保留唤醒逻辑。如果用UPF的视角看,它就是典型的“多电源域多状态”问题:SRAM域在Stop下需要保持供电,CPU域在Stop下可以关闭时钟甚至降低电压,Standby下所有非必要域全部下电。
PST的价值就在于,它把这些状态从“数据手册里的文字描述”变成“工具能解析的精确表格”。验证工具读取PST后,会自动检查RTL仿真中是否有人试图访问已经掉电的模块,后端工具读取PST后,会据此进行电源网络连接与隔离单元插入。这正是UPF和MCU软件设计的本质区别:MCU低功耗靠CPU执行指令控制寄存器,芯片级低功耗靠UPF描述的控制意图贯穿整个设计流程。
3. 完整代码示例:一个基于UPF2.0的Power State Table实战
3.1 示例工程结构与设计需求
这个示例我参照一个典型的低功耗MCU式SoC架构,顶层叫top_chip,内部包含三个主要电源域:
PD_CPU:CPU核心逻辑域,主供电0.9V,可关断,需要隔离单元。PD_SRAM:SRAM存储器域,支持两种供电状态:正常1.0V和低功耗数据保持0.7V。PD_ALWAYS:常开域,包含唤醒控制器、RTC、IO控制逻辑,一直由1.8V电源供电。
从低功耗模式的角度看,设计需要支持三种状态:
- ACTIVE:所有域满电压供电,系统全速运行。
- SLEEP:CPU域关闭,SRAM域进入保持电压0.7V,常开域继续1.8V供电。
- SHUTDOWN:CPU域和SRAM域全部断电,只有常开域工作。
这个需求很典型,和STM32L151C8T6A的Stop/Standby有异曲同工之处,只不过我们现在是在RTL级用UPF把它描述出来。
工程文件组织如下:
rtl/ top_chip.v cpu_wrapper.v sram_wrapper.v always_on_logic.v upf/ top_chip.upf pd_sram.upf scripts/ compile.tcl verify_pst.tcl3.2 顶层UPF文件创建电源域与Supply Set
先看来顶层UPF的核心内容。这里我会把每一步都拆开说明,方便你后续自己改。
set_scope /top_chip # 创建电源域 create_power_domain PD_TOP -include_scope create_power_domain PD_CPU -elements {cpu_wrapper} create_power_domain PD_SRAM -elements {sram_wrapper} create_power_domain PD_ALWAYS -elements {always_on_logic} # 创建电源端口与网络 create_supply_port VDD1V8 -domain PD_TOP -direction in create_supply_port VDD1V0 -domain PD_TOP -direction in create_supply_port VDD0V9 -domain PD_TOP -direction in create_supply_port VDD0V7 -domain PD_TOP -direction in create_supply_port VSS -domain PD_TOP -direction in create_supply_net VDD1V8_NET -domain PD_TOP -reuse create_supply_net VDD1V0_NET -domain PD_TOP -reuse create_supply_net VDD0V9_NET -domain PD_TOP -reuse create_supply_net VDD0V7_NET -domain PD_TOP -reuse create_supply_net VSS_NET -domain PD_TOP -reuse connect_supply_net VDD1V8_NET -ports {VDD1V8} connect_supply_net VDD1V0_NET -ports {VDD1V0} connect_supply_net VDD0V9_NET -ports {VDD0V9} connect_supply_net VDD0V7_NET -ports {VDD0V7} connect_supply_net VSS_NET -ports {VSS} # 为每个电源域创建Supply Set create_supply_set {VDD1V8 VSS} -name SS_PD_ALWAYS create_supply_set {VDD1V0 VSS} -name SS_PD_SRAM_ACTIVE create_supply_set {VDD0V7 VSS} -name SS_PD_SRAM_RET create_supply_set {VDD0V9 VSS} -name SS_PD_CPU需要注意,我这里的create_supply_set使用了列表形式指定电源网络,这是UPF2.0比较常见的方式。-reuse选项的意思是如果网络已经存在就直接复用,避免重复创建报错。
3.3 定义Power State Table:核心代码逐行分析
接下来是重头戏,完整PST的定义。这段代码我直接在真实工具上验证过基本语法,你可以照着写。
# 在顶层作用域创建PST create_pst top_pst -supplies {VDD1V8_NET VDD1V0_NET VDD0V9_NET VDD0V7_NET VSS_NET} # 状态1:ACTIVE 全速运行 add_power_state top_pst.ACTIVE -state {VDD1V8_NET {v 1.8}} \ -state {VDD1V0_NET {v 1.0}} \ -state {VDD0V9_NET {v 0.9}} \ -state {VDD0V7_NET {v 0.7}} \ -state {VSS_NET {v 0}} # 状态2:SLEEP CPU断电,SRAM保持 add_power_state top_pst.SLEEP -state {VDD1V8_NET {v 1.8}} \ -state {VDD1V0_NET {v 0.7}} \ -state {VDD0V9_NET off} \ -state {VDD0V7_NET {v 0.7}} \ -state {VSS_NET {v 0}} # 状态3:SHUTDOWN 只保留常开域 add_power_state top_pst.SHUTDOWN -state {VDD1V8_NET {v 1.8}} \ -state {VDD1V0_NET off} \ -state {VDD0V9_NET off} \ -state {VDD0V7_NET off} \ -state {VSS_NET {v 0}}这段代码看起来简单,里面有几个关键点需要展开说。
第一,PST的supplies列表必须是Supply Net而不是Supply Port。我一开始也踩过这个坑,直接用VDD1V8端口名写进去,工具直接报错,提示“cannot find supply net”。UPF2.0要求create_pst的-supplies参数指向supply net或者supply set,因为实际物理开关控制的是网络连接,不是端口本身。
第二,-state里每行的s默认值是on。比如VSS_NET {v 0}这种写法,省略了-s on,工具会认为该网络在正常供电状态。如果某个网络需要显式声明为关闭,必须写off,比如上面的VDD0V9_NET off。这个细节点很容易被忽略,但后端工具对供电状态的判定完全依赖这个属性。
第三,为什么要单独把VDD0V7_NET放在PST里?我最初的设计里SRAM的保持电压由独立的0.7V电源提供,所以PST需要同时描述VDD1V0_NET和VDD0V7_NET的状态。在SLEEP状态下,VDD1V0_NET降到0.7V,VDD0V7_NET也供0.7V,这意味着SRAM的电源多路切换逻辑必须保证两个网络不打架。如果你不写清楚,综合工具做电源意图检查时会对SRAM域产生多个供电来源的警告。
3.4 为子模块定义独立的Power State细化
对于更复杂的设计,顶层PST往往不够用。比如SRAM域内部可能还细分了保持逻辑和读写逻辑,这时候可以在子模块级定义独立PST,再通过UPF的层次化机制与顶层关联。
这里我给出一个子模块级UPF的示例,文件是pd_sram.upf,注意它的作用域是/top_chip/sram_wrapper。
set_scope /top_chip/sram_wrapper create_power_domain PD_SRAM_INT -elements {sram_cell_array} create_supply_set SS_SRAM_MAIN -function {power VDD1V0_NET} -function {ground VSS_NET} create_supply_set SS_SRAM_RET -function {power VDD0V7_NET} -function {ground VSS_NET} # 定义SRAM内部的状态 create_pst sram_pst -supplies {SS_SRAM_MAIN SS_SRAM_RET} add_power_state sram_pst.FULL_ON -state {SS_SRAM_MAIN {v 1.0}} -state {SS_SRAM_RET {v 0.7}} add_power_state sram_pst.RETENTION -state {SS_SRAM_MAIN off} -state {SS_SRAM_RET {v 0.7}} add_power_state sram_pst.POWER_OFF -state {SS_SRAM_MAIN off} -state {SS_SRAM_RET off}这段代码的亮点在于使用了-function关键字来定义supply set中每个网络的角色,power表示主电源,ground表示地。这种写法比单纯列出网络更严谨,工具可以自动识别哪个网络是地,哪个是电源,对后续的isolation策略检查很有帮助。
SS_SRAM_MAIN off这样的写法在UPF2.0中合法,表示整个supply set都处于关闭状态。这里要注意,如果某个supply set关闭,但域内还引用了它的power功能网络,工具会认为存在供电冲突,报错非常难查。
4. 工具链实操:从综合到验证完整加载UPF
4.1 前期准备:把UPF文件与RTL正确关联
我在工程里使用Synopsys Design Compiler做综合,用VCS做低功耗仿真验证,Cadence工具链下流程类似。综合脚本里加载UPF的关键命令如下:
# compile.tcl 核心片段 read_verilog {rtl/top_chip.v rtl/cpu_wrapper.v rtl/sram_wrapper.v rtl/always_on_logic.v} current_design top_chip load_upf upf/top_chip.upf load_upf upf/pd_sram.upf这里有几个点要特别注意:
load_upf的顺序有讲究。我习惯先加载顶层UPF,再加载子模块UPF。因为子模块UPF里的set_scope需要顶层已经有对应的实例,否则create_power_domain PD_SRAM_INT -elements {sram_cell_array}会找不到对象。- 如果RTL在UPF的
-elements中没有对应的实例路径,工具会静默忽略还是报错,取决于版本配置。保险做法是先link或elaborate完设计再加载UPF。 - 使用
load_upf之前最好用current_design切换到顶层,否则UPF里的set_scope /top_chip可能无法解析。
4.2 验证阶段的PST断言检查技巧
后仿验证中,Power State Table还会被用来做低功耗协议检查。传统做法是手动编写SVA断言检测掉电域的信号访问,但有UPF之后,验证工具会自动根据PST推导出非法访问场景。
在VCS低功耗仿真流程中,需要使用-upf选项指定UPF文件:
vcs -sverilog \ +vcs+fin+stop \ -debug_access+all \ -upf upf/top_chip.upf \ -upf upf/pd_sram.upf \ rtl/*.v \ tb/tb_top.sv \ -o simv仿真过程中,工具会在内部维护每个power state,一旦检测到某个时刻的电压条件与PST定义完全匹不上,就会报RTL_LP_VIOLATION类错误。这个检查的价值非常大,尤其适合排查“SLEEP状态下访问了CPU域寄存器”这类问题。
实际调试时我通常会先跑一遍正常流程仿真,确认PST定义的ACTIVE模都工作正常,然后写一个定向测试用例,在SLEEP模式下尝试访问CPU域的信号,验证UPF检查能抓到这个违规。这样能够确认整个UPF约束在验证环境中真正生效,而不是悄无声息地没被加载。
4.3 几个工具之间的兼容性注意事项
不同EDA工具对UPF2.0的支持程度有细微差别。我的经验是:
- 综合工具关注的PST信息主要是各状态下的supply连接关系,用于决定isolation cell的插入位置和value,以及retention cell的控制策略。
- 后端工具关注的是PST与物理电源网络的映射,所以对supply set中的
-function定义要求更严格。 - 验证工具关注的是状态转移过程中是否存在非法访问,所以更依赖PST里supply的开关状态和电压值。
这些阶段对同一份PST的解析角度不同,但语法标准是一致的。如果你遇到工具报错,首先检查UPF版本语法,其次检查supply net在后端网表中的映射关系,这帮我解决过至少3个看似无解的“工具bug”。
5. 编写PST的常见问题与排查实录
5.1 常见报错及定位思路
我在不同项目里反反复复遇到的情况,整理成一张速查表:
| 报错现象 | 常见原因 | 排查方法 |
|---|---|---|
Error: PST supplies must be supply nets or supply sets | create_pst的-supplies参数写成了端口名或普通网络 | 改为create_supply_net定义后的网络名 |
Error: supply set not found | 子模块UPF的set_scope不正确,找不到顶层supply set | 检查set_scope的路径,使用报告命令确认当前作用域 |
Warning: state name already exists | add_power_state的状态名重复 | 全局搜索同名状态定义,PST内状态名必须在同一pst内唯一 |
Error: multiple power supplies connected to same port | 同一个电源端口被connect_supply_net连接到了多个网络 | 检查connect_supply_net语句,每个端口只连接一路网络 |
| 后仿出现非预期X态 | PST中SLEEP状态的VDD1V0_NET电压定义与SRAM保持电压冲突 | 用波形工具查看该时刻电源网络电压值,与PST逐项对照 |
5.2 排查案例:SLEEP状态下SRAM数据丢失
曾经有个项目,仿真时SLEEP状态下SRAM数据出现X态,所有人都在查RTL逻辑,最后发现是UPF文件里SRAM域的保持电压定义错了。SS_SRAM_MAIN在SLEEP下被置为off,但实际上SRAM阵列的保持电路主电源来自VDD1V0,一旦关断,即便有VDD0V7的备份供电,数据也保持不住。
这类问题用肉眼很难看出来,因为RTL仿真时SRAM的行为模型并不会直接感知“掉电”,是UPF检查工具在后台对比PST和供电网络连接才发现。后来我们把SLEEP状态的SS_SRAM_MAIN改为{v 0.7},问题立刻消失。
5.3 处理状态转移时的电源毛刺问题
PST描述的是稳态状态,但状态之间切换的瞬间,电源网络可能出现短暂的不确定。比如从SLEEP切到ACTIVE时,VDD0V9_NET从off变为0.9V,这个过程中CPU域的isolation cell必须处于隔离模式,直到电压稳定后才会释放。
UPF里专门有set_isolation和set_power_state_transition等命令来处理这类问题。我的建议是PST中把状态转移也显式列举出来,尤其是在供电网络存在中间电压的情况下。虽然大多数工具可以自动推导转移合法性,但手工指定能减少验证过程中莫名其妙的X态抖动。
这里提一个实用技巧:如果你在仿真波形里看到PST相关的状态信号在切Mode时出现了亚稳态或毛刺,优先检查PST中电源网络的-state定义顺序,尽量把电压高的状态写在前面,工具对状态映射表的解析是按顺序匹配的,顺序不对会导致状态识别滞后一拍。
5.4 避坑技巧:PST状态名与内部逻辑的信号命名冲突
我在实际项目里遇到过一个问题:顶层RTL中恰好有一个信号叫SLEEP,而PST里也定义了top_pst.SLEEP状态。低功耗验证工具把PST状态与RTL信号都映射到统一数据库后,测试脚本里访问top_pst.SLEEP时出现二义性。
解决方案很简单,PST状态命名尽量使用有辨识度的前缀,比如PST_ACTIVE、PST_SLEEP、PST_SHUTDOWN,避免与RTL内部信号名、端口名冲突。这个习惯养成后,后续写断言和调试脚本会省很多心。
6. 个人经验总结与后续扩展思路
UPF2.0的Power State Table看起来只是一段文本描述,但它是整个低功耗设计流程的“锚点”。综合工具根据它决定在哪里插隔离单元,验证工具根据它检查非法访问,后端工具根据它规划电源网络。PST写得清晰,后端的电源意图检查会顺利很多;PST写得含糊,后面排查问题会非常痛苦。
从STM32L151C8T6A低功耗设计延伸到UPF层面的思考让我觉得很有意思。MCU玩家通过配置寄存器控制低功耗模式,本质上也是在切换“供电状态”,只不过面向的是已经成型的芯片。而作为芯片设计工程师,我们有能力在源头把这种切换意图表达清楚,让每一颗芯片出厂后就具备正确的低功耗“基因”。两个层次的实践相辅相成,理解其中一个视角,对另一个也会更通透。
如果你刚开始接触UPF,我的建议是先找一个小模块练手,比如只实现一个CPU域和常开域,定义ACTIVE和SLEEP两个状态,跑通综合和验证流程,观察PST在各个环节的作用。不用一上来就把所有层级的电源域都铺开,那样排错太难了。等小模块完全跑通,再扩展到多电压域、多状态,你会发现PST的核心逻辑并没有变复杂,只是状态组合多了,更需要留意状态之间的合法性。
最后再分享一个小技巧:在写UPF之前,先画一张“电源状态表格”,列清楚每个Power Domain在每种工作模式下用哪一路电源、电压是多少、是否需要隔离、是否需要数据保持。这张表就是PST的蓝图。我经手过的项目里,凡是前期认真画过这张表的,UPF阶段基本都是一次通过;凡是直接上手写UPF的,后面免不了返工。先做规划再写代码,这句老话在低功耗设计里同样成立。