第四章:统一功耗格式(UPF)详解

各位同学,今天我们来聊聊UPF。说实话,UPF这东西刚出来的时候,我还挺抵触的——又得学一套新语法。但做了几个低功耗项目之后,我彻底服气了。没有UPF,你根本没法系统性地管理芯片的功耗意图。

UPF全称是Unified Power Format,统一功耗格式。它最早由Synopsys和ARM等公司推动,现在已经是IEEE 1801标准了。目前主流用的是UPF 2.0和UPF 3.0。我个人习惯用UPF 2.1,兼容性最好,工具支持也最成熟。

4.1 UPF 2.0 vs UPF 3.0:到底差在哪?

先说说这两个版本的区别。UPF 2.0是2013年左右的版本,它把电源域、电平转换器、隔离单元这些基础概念都定义清楚了。UPF 3.0呢,主要增加了对复杂电源状态机的支持,比如多电压域的动态电压频率调整(DVFS)。

我举个例子你就明白了:

  • UPF 2.0:适合做静态的低功耗设计,比如always-on域和关断域
  • UPF 3.0:适合做动态的功耗管理,比如CPU核在不同负载下切换电压

嗯,这里要注意:如果你的芯片只用到了电源关断和简单的电压切换,UPF 2.0完全够用。别为了追新而用3.0,工具支持反而可能出问题。

4.2 电源域定义:create_power_domain

电源域是UPF的核心概念。说白了,就是把芯片上需要独立供电的区域圈出来。语法很简单:

create_power_domain PD_CPU \
  -elements {u_cpu_top} \
  -supply {primary VDD_CPU} \
  -supply {secondary VDD_MEM}

这里我解释一下几个关键参数:

  • -elements:指定这个域包含哪些模块。可以是实例名,也可以是层次路径
  • -supply:定义这个域用什么电源。primary是主电源,secondary是备用电源
我的经验:定义电源域时,尽量把相关的逻辑放在同一个域里。我曾经在一个项目里把时钟管理单元和CPU核分到了不同域,结果跨域时钟同步搞得我头大。后来我学乖了,时钟相关的逻辑尽量和它服务的模块放在一起。

4.3 电源状态表:add_port_state 与 create_pst

电源状态表,说白了就是告诉工具:我的芯片在什么状态下,各个电源域应该是什么电压。先看端口状态定义:

add_port_state VDD_CPU -state {ON 1.0} -state {OFF 0.0}
add_port_state VDD_GPU -state {ON 0.9} -state {OFF 0.0}
add_port_state VDD_MEM -state {ON 1.2} -state {RET 0.6}

然后创建电源状态表:

create_pst chip_pst -supplies {VDD_CPU VDD_GPU VDD_MEM}
add_pst_state sleep -pst chip_pst -state {OFF OFF RET}
add_pst_state active -pst chip_pst -state {ON ON ON}
add_pst_state gpu_only -pst chip_pst -state {OFF ON RET}

你看,这样定义之后,工具就能理解:

  • 在sleep模式下,CPU和GPU都关断,内存保持
  • 在active模式下,所有域都正常工作
  • 在gpu_only模式下,只有GPU工作,CPU关断,内存保持
避坑指南:我曾经犯过一个错误——在电源状态表里定义了某个域为OFF,但实际设计中这个域还有信号需要输出到其他域。结果综合工具报了一堆hold violation。后来我才意识到,关断的域必须用隔离单元把输出信号钳住。

4.4 电平转换器:跨电压域的桥梁

当两个电源域电压不同时,信号从一个域传到另一个域,就需要电平转换器。UPF里用set_level_shifter来指定:

set_level_shifter LS_CPU2GPU \
  -domain PD_CPU \
  -source PD_CPU \
  -sink PD_GPU \
  -applies_to outputs \
  -rule low_to_high \
  -location self

这里几个参数要注意:

  • -source-sink:指定源域和目标域
  • -applies_to:可以选outputs、inputs或both。我一般选outputs,在源域输出端做转换
  • -rule:low_to_high表示从低压到高压,high_to_low反之
  • -location:self表示放在源域内,parent表示放在父域
我的习惯:电平转换器尽量放在源域。为什么?因为源域知道自己的电压,转换逻辑更简单。如果你放在目标域,还得考虑目标域是否已经上电。嗯,这个细节在低功耗验证里特别容易出问题。

4.5 隔离单元:关断域的守门员

当一个域关断时,它的输出信号会变成未知态(X)。如果不做隔离,这些X会传播到其他域,导致功能错误。隔离单元就是干这个的:

set_isolation ISO_CPU2GPU \
  -domain PD_CPU \
  -source PD_CPU \
  -sink PD_GPU \
  -applies_to outputs \
  -clamp_value 0 \
  -isolation_supply VDD_GPU

关键参数:

  • -clamp_value:隔离时输出钳位到0还是1。我一般选0,因为低功耗设计里0更省电
  • -isolation_supply:隔离单元用什么电源供电。注意,隔离单元必须用目标域的电源,否则它自己也会关断
避坑指南:隔离单元的供电电源一定要选对。我曾经在一个项目里,隔离单元用了源域的电源,结果源域关断后隔离单元也掉了,输出直接变成X。那一次debug花了我整整两天。后来我养成了习惯:隔离单元永远用目标域的电源。

4.6 实战中的UPF设计流程

好了,理论讲完了,我给大家梳理一下实际项目中的UPF设计流程:

  1. 第一步:划分电源域。根据芯片架构,把需要独立供电的模块圈出来。比如CPU、GPU、内存控制器、外设等
  2. 第二步:定义电源端口。用create_supply_port和create_supply_net定义每个域的电源网络
  3. 第三步:创建电源状态表。用add_port_state和create_pst定义所有可能的电源模式
  4. 第四步:添加电平转换器和隔离单元。用set_level_shifter和set_isolation处理跨域信号
  5. 第五步:验证。用Synopsys的VC LP工具跑一遍UPF检查,确保没有遗漏

嗯,这里我要强调一下:UPF不是写一遍就完事的。随着设计迭代,UPF也要不断更新。我建议你把UPF和RTL代码一起做版本管理,每次修改都要做回归验证。

4.7 小结

今天的内容就到这里。UPF这东西,刚开始学觉得语法多、规则复杂,但用熟了之后你会发现,它其实是低功耗设计里最值得花时间学的东西。没有UPF,你根本没法系统性地管理芯片的功耗意图。

下一章我们会讲综合阶段的低功耗优化,到时候会用到今天学的UPF知识。建议大家先把UPF的语法练熟,尤其是电源状态表和隔离单元的配置,这是最容易出错的地方。

好,今天就到这里。有问题随时找我。