
1. 背景拆解为什么UPF2.0和Power State Table是低功耗设计的“通用语言”做数字IC的人对低功耗这件事都不陌生从MCU级别的软件调优到SoC级别的多电压域设计核心思路其实一脉相承。拿STM32L151C8T6A这类经典低功耗MCU来说Run、Sleep、Stop、Standby几种模式大家都很熟悉跑系统时切到Sleep需要低功耗外设时进Stop要极致省电就Standby。但问题来了在MCU上你靠软件改写寄存器就能切换模式到了芯片前端设计阶段你怎么把“某些模块可以断电、某些模块在休眠时必须有电”这个意图准确无误地传达给综合、布局布线、验证工具靠口头沟通肯定不行靠注释也没人会看你得有一套机器可读、语义明确的描述方式。这就是UPFUnified 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 StatePST把这种组合与状态命名绑定。工具看到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_CPUCPU核心逻辑域主供电0.9V可关断需要隔离单元。PD_SRAMSRAM存储器域支持两种供电状态正常1.0V和低功耗数据保持0.7V。PD_ALWAYS常开域包含唤醒控制器、RTC、IO控制逻辑一直由1.8V电源供电。从低功耗模式的角度看设计需要支持三种状态ACTIVE所有域满电压供电系统全速运行。SLEEPCPU域关闭SRAM域进入保持电压0.7V常开域继续1.8V供电。SHUTDOWNCPU域和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} # 状态1ACTIVE 全速运行 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}} # 状态2SLEEP 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}} # 状态3SHUTDOWN 只保留常开域 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.7VVDD0V7_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. 工具链实操从综合到验证完整加载UPF4.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 \ vcsfinstop \ -debug_accessall \ -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 setscreate_pst的-supplies参数写成了端口名或普通网络改为create_supply_net定义后的网络名Error: supply set not found子模块UPF的set_scope不正确找不到顶层supply set检查set_scope的路径使用报告命令确认当前作用域Warning: state name already existsadd_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的后面免不了返工。先做规划再写代码这句老话在低功耗设计里同样成立。