UPF 2.1用的人越来越多,但真正到了Hard Macro和Top-Level Port这一层,很多工程师容易踩坑。刚接触低功耗流程时,我也以为UPF只是写几行约束,后来在项目中处理SRAM宏的电源域划分、顶层端口隔离时,才明白这里面的细节远不止语法本身。本文就结合我实际项目中的经验,把UPF 2.1中Hard Macro与Top-Level Port的电源意图处理讲透,包括命令写法、边界建模思路、常见坑点和后端签核经验。
1. 思路先行:Hard Macro与Top-Level Port为什么总是“翻车重灾区”
1.1 两个边界难点的本质
先说Hard Macro。所谓Hard Macro,指的是已经完成布局布线、拥有固定物理版图的宏单元,例如SRAM编译器生成的内存阵列、PLL、DLL、USB PHY、DDR PHY等。它们的共同特点是:内部电路不可见,也没有可综合的RTL。对UPF而言,这意味着工具无法像普通标准单元那样,从逻辑网表里自行推断每个cell属于哪个电压域、电源引脚接在哪条电源网络上。
很多工程师第一次处理这类宏时,习惯性做法是认为“单元库里有PG pin定义,工具会自己处理”。但实际跑下来会发现,综合工具和后端PR工具对硬宏的电源理解,完全取决于UPF里有没有显式声明。你不告诉工具这个宏的VDD接哪条supply net,工具在电源连接检查阶段就会报出成片的“unconnected power pin”告警。更隐蔽的是,仿真时这些悬空引脚会显示X态,排查起来极其耗时。
Top-Level Port则相反。顶层端口不隶属于任何标准单元,它本身没有电源引脚,但它在低功耗设计中承担着跨电压域信号交互的职责。一个输入端口可能来自另一个电压域,进入本域之前要不要隔离?一个输出端口可能要被送到一个经常关断的模块,在关断时输出信号怎么处理?这些都需要通过UPF显式建模,否则综合结果里根本不会插入隔离单元。
1.2 动手之前先回答三个问题
我在处理任何Hard Macro或Top-Level Port之前,会强制自己先整理一份“电源意图清单”。对于Hard Macro,核心问题是:
- 这个宏内部到底有几个电压域?不同电压域的供电网络分别对应引脚是哪些?
- 宏的外部边界有没有需要特殊处理的信号,比如需要电平转换或隔离的信号?
- 宏有没有厂商提供的UPF模板?如果有,它覆盖到哪一层,还需要我自己补什么?
对于Top-Level Port,核心问题是:
- 这个端口是输入还是输出?它的方向决定了隔离单元的插入位置和方式。
- 这个端口在芯片正常工作模式下,属于哪个电压域管辖?它的参考地是谁?
- 当某个相关电压域关断时,这个端口应该保持什么状态?高阻、钳位到0还是钳位到1?
不要小看这几个问题,它们直接决定了后面UPF命令的参数选择。比如隔离单元的clamp value设成0还是1,如果你没有提前想清楚端口在低功耗模式下的期望值,后面仿真阶段一定会出现莫名其妙的X态传播。
2. Hard Macro电源意图:构建可综合、可验证的UPF描述
2.1 拿到一个Hard Macro,先在周边定好供电边界
处理Hard Macro的第一个核心动作,是明确它所属的power domain。实践中,我建议先通过create_power_domain命令,把宏实例本身划入目标电压域,然后再逐条连接宏的电源引脚。
举个例子。假设设计里有一颗经过编译器生成的SRAM宏,实例名为SRAM0,工作电压1.0V(VDD),接地为VSS。顶层已经建好了PD_TOP域,供电网络是VDD_TOP和VSS_TOP。最基础的UPF写法是这样:
create_power_domain PD_SRAM -elements {SRAM0} create_supply_port VDD_SRAM -direction in create_supply_net VDD_SRAM -domain PD_SRAM create_supply_net VSS_SRAM -domain PD_SRAM connect_supply_net VDD_SRAM -ports {SRAM0/VDD} connect_supply_net VSS_SRAM -ports {SRAM0/VSS} set_domain_supply_net PD_SRAM -primary_power_net VDD_SRAM -primary_ground_net VSS_SRAM这段命令看起来简单,但这背后有几个关键点值得说明。第一,create_supply_port的作用是在当前UPF作用域下创建一个输入类型的供电端口,它代表“这个电压从外部供给进来”的抽象边界。对于Hard Macro,这个端口可以理解成宏的“供电入口”,它必须存在,否则工具不知道VDD_SRAM电压从哪里引入。
第二,connect_supply_net到宏的物理引脚。这里的端口名SRAM0/VDD必须和单元库中实际定义的引脚名完全一致。不同厂家的SRAM库,电源引脚命名差别很大,有叫VDD的、有叫VDD1的、还有叫VDDA的。如果名字对不上,UPF解析阶段工具就会报错。所以拿到宏库后,第一件事是用report_cell或lib文件搜索确认电源引脚的确切名称,不要凭经验猜。
第三,set_domain_supply_net指定了这个域的primary power和primary ground。这一步相当于告诉工具:这个域里的标准单元和宏,默认参考的电源网络是哪条。如果不设置,部分工具会使用全局默认的VDD/VSS,一旦顶层域名不一致,就会出现电源网络对不上的问题。
2.2 用supply net“显式声明”替代“隐式继承”
我在项目里见过最多的错误,就是工程师试图让工具“隐式继承”宏的电源连接关系。具体表现是:只写了create_power_domain,没有写任何connect_supply_net,然后指望综合工具自动把宏的VDD连到顶层VDD上。这个想法在半定制流程中是行不通的。
原因在于,Hard Macro没有内部网表。工具看不到宏内部cell的电源逻辑,它也判断不了宏的VDD引脚应该挂在哪条网络上。你必须在UPF里显式声明这是一条物理连接路径。这正是“电源意图”四个字的意义——你不是在描述电路功能,而是在描述供电连接关系。
还有一种更隐蔽的情况:宏的某个电源引脚虽然有默认连接,但宏内部其实包含多个电压域。举个例子,一个带retention功能的SRAM,通常会有两个电源引脚:一个主逻辑电源VDD,一个保持电源VDD_MEM或VCC_RET。在正常工作模式下两个电压相同,但在低功耗模式下,主逻辑电源关断,保持电源继续供电,这样SRAM内容才能保住。
这种场景下,你需要在宏的边界上同时定义两个supply set,分别对应保持域的非关断电源和逻辑域的关断电源。UPF中通过supply set来表达“同一电压域在不同状态下的供电组合”,示例如下:
set_domain_supply_net PD_SRAM -primary_power_net VDD_SRAM -primary_ground_net VSS create_supply_port VDD_RET -direction in create_supply_net VDD_RET -domain PD_SRAM connect_supply_net VDD_RET -ports {SRAM0/VDD_RET} create_supply_set SS_RET -function {power VDD_RET} -function {ground VSS}这里有一个容易忽略的细节:当一个power domain内有多个supply net时,set_domain_supply_net指定的primary power决定仿真和综合时该域的标准单元使用哪个电压源。而retention功能则通过宏内部特殊的PG逻辑以及隔离单元来实现。UPF里单纯的connect_supply_net并不能让工具自动理解retention行为,你还需要配合set_isolation和case宏的retention信号。
在写这类命令时,我的习惯是参考宏的datasheet里关于power up/down sequence的描述,而不是只看UPF模板。很多IP厂商提供的UPF模板只是覆盖了基本连接,并没有覆盖特殊工况。把宏手册上的引脚说明和UPF逐条对照,虽然费时间,但能省掉后面的大量返工。
2.3 不同Hard Macro类型的处理差异
不同功能的Hard Macro,电源意图处理的侧重点完全不同,这里用表格总结一下我实际工作中常用到的处理思路。
| Hard Macro类型 | 典型电源特征 | 主要UPF处理动作 | 最容易踩的坑 |
|---|---|---|---|
| SRAM编译器宏 | 有VDD和VSS,可能带retention引脚 | 创建domain、连接电源引脚、为retention域配置独立supply set | 忽略retention引脚,导致低功耗下数据丢失 |
| PLL/DLL | 模拟电路通常需要独立低位噪声电压 | 建立独立power domain,连接高洁净度模拟电源,考虑隔离 | 与数字电源共用域,噪声串扰导致锁相环性能恶化 |
| USB/PCIe PHY | 多个模拟电压域,如VDD_PLL、VDD_IO、VDD_CORE | 按IP手册拆分多个supply domain,逐条连接电源引脚 | 输入信号跨域时未做隔离/电平转换 |
| 第三方逻辑IP宏 | 可能是数字逻辑RTL,也可能提供pre-layout netlist | 作为普通black box处理,关注是否包含电源开关单元 | 宏内部电源关断逻辑和顶层UPF冲突 |
PLL这类模拟宏值得单独多说两句。模拟宏对电源噪声极其敏感,所以通常需要独立的供电网络,而且这个网络在物理实现时一般要加宽走线、加多decap电容。在UPF层面,处理动作是把该宏划入独立power domain,并让这个域的supply network和数字主电源域物理分离。PLL的VDD引脚不要和数字逻辑共用同一条supply net,否则IR drop分析阶段很容易发现PLL供电电压不符合spec要求。
对于第三方IP,有一点经验非常关键:不要盲目信任IP厂商的UPF模板。我遇到过厂商模板里电源网络名称和自家集成环境命名不一致的情况,结果后端PR阶段大量连接错误。拿到模板后先做一次全局替换,把厂商的电源网络名称映射到项目统一的命名规则上,再进一步检查引脚连接是否完整。
3. Top-Level Port的电源意图:把边界当“一等公民”来建模
3.1 端口隔离是“一步都不能少”的环节
Top-Level Port的处理,核心是隔离。为什么端口要隔离?因为芯片外部信号或相邻电压域的信号,在串联进芯片内部低功耗域时,可能会在某个域关断后失去驱动源,导致内部电路出现不确定状态。如果不插隔离单元,这个不确定性会像多米诺骨牌一样传播到整个低功耗域。
举一个真实例子。芯片有两个电压域,PD_DSP和PD_CPU。PD_DSP经常在待机时关断,但PD_CPU有一个输出端口连接到PD_DSP内部的某个模块。当PD_DSP关断时,如果该端口还在持续翻转信号,就会对PD_DSP内部的保持状态造成影响,甚至导致漏电增加。正确的UPF写法是在PD_DSP域边界对该输入端口做隔离控制:
set_isolation ISO_DSP_INPUT -domain PD_DSP \ -ports {dsp_data_in[3:0]} -clamp_value 0 \ -isolation_control input -isolation_signal iso_en这里的isolation_control和isolation_signal配合,决定了隔离单元的使能信号来源。当iso_en有效时,端口被钳位到clamp_value定义的值上,从而隔离外部信号对关断域的影响。很多初学者只写了-domain和-ports,没有指定clamp_value,默认情况下综合工具采用的钳位值可能并不符合设计期望,这会在后仿真阶段带来麻烦。
对于输出端口,隔离位置和输入略有不同。如果芯片输出端口指向一个经常关断的外部模块,通常是在输出端口所属域内、靠近输出pad的位置插入隔离单元。需要注意的是,输出隔离单元的供电不能依赖关断域自身的电源,否则一旦该域关断,隔离单元自身也无法工作。实践中,这类隔离单元一般挂在always-on域上,UPF里可以用set_domain_supply_net把隔离单元的供电域设定为始终开启的域,或者通过物理实现阶段指定voltage area来实现。
Top-Level Port的隔离是UPF中最容易写漏的部分。我建议在项目开始时建一个端口清单表,标注每个端口与此芯片内部各电源域的关系。当UPF写完跑完vcs低功耗仿真后,把输出X态网络的报错逐一和这个端口清单交互检查,就能快速定位哪条信号路径缺少隔离。
3.2 输入端口如何关联supply set和上电顺序
输入端口本身没有电源引脚,但它关联的“信号逻辑电平参考域”是存在的。这个参考域,就是驱动这个端口的逻辑所在的电源域。UPF里可以通过把端口加入某个power domain的scope来声明归属,例如:
create_power_domain PD_EXT_IN -elements {ext_data_in}但在5nm、7nm的项目里,很多顶层输入端口实际上处于“悬置”状态,即芯片内部没有对应域显式包含它。这时UPF工具无法确定该端口的逻辑参考电压,隔离单元插入后仿真时可能直接报三态。为了规避这种情况,我通常会在UPF中为悬置输入端口单独建立一个“空域”,只包含该端口而不包含任何物理实例,并指定该域的supply net属性,这样工具对于该端口连接到的隔离单元可以获得合法的供电参考。
上电顺序对输入端口的影响同样不可忽视。假设某个输入端口在芯片启动期间先于其参考电压域上电,那么信号就会在一个不可靠的电压状态下翻转,可能引发闩锁。UPF本身不能直接约束上电顺序,上电顺序通常由电源管理和功率切换逻辑控制。但UPF可以通过写电源域的supply set依赖关系来影响工具对电源网络开关的理解。比如在upf里为某输入相关域设置一个supply set,要求它的供电来自always-on电源,从而避免掉电后输入端口悬置。
3.3 Level Shifter与Always-on Buffer的选择
跨电压域的信号,除了隔离之外,还要考虑电平转换。Level Shifter的插入位置是一个经典问题:应该放在驱动器所在域,还是接收器所在域?这个问题没有统一答案,取决于标准单元库中level shifter cell的供电类型和实现细节。
UPF2.1中通过set_level_shifter命令控制插入位置。例如:
set_level_shifter LS_EXT_OUT -domain PD_DSP -applies_to output \ -location self -threshold 0.7-location参数支持self、fanout、driver、receiver等取值。在实际项目中,我建议在早期先采用-location self,让工具尽可能在边界处插入。但如果库中的level shifter需要高侧供电,而高侧电压域在边界的物理位置没有可用voltage area,工具就会自动推导到内部逻辑或其他域中插入。此时如果-location设得不合理,容易出现重复插入或是level shifter和前级逻辑距离过远的问题。
关于Always-on buffer,我需要特别强调。在功耗管理的控制逻辑中,隔离使能信号、状态保持信号、上电顺序控制信号等,必须保证在相应电压域掉电期间还能有效传递。这些信号一般由always-on域产生,但目标单元却可能在任意掉电域内。简单的办法是用always-on buffer将控制信号fanout到各掉电域边界,这些buffer本身挂在always-on域电源上,不会随着目标域一起掉电。但在UPF里显式声明这些buffer所属域的写法,往往被忽略。如果你希望工具在综合时正确推断buffer插入位置,推荐使用supply set的pg_type来标记这些信号的参考电压等级,而不是只依赖物理实现阶段的手动约束。
4. 验证与Signoff:别让UPF“写的时候爽,验的时候哭”
4.1 从UPF到网表的电源连接检查
UPF写完后,后端流程会对UPF进行编译,并将电源意图映射到物理网表上。这一阶段最常见的检查项包括:
- 每个power domain内包含的元素是否合理。如果宏单元被划分进两个域,工具会报错。
- 每条supply net是否连接到正确的电源引脚。常见错误是在多个域中重名命名的supply net造成错误匹配。
- 隔离单元和level shifter是否被正确插入。这一步通常由形式验证工具在低功耗模式下检查,确认设计在电气逻辑上符合UPF描述。
实际操作中,我建议进行三遍检查。第一遍,跑upf编译,看日志里有没有解析错误和warning。第二遍,跑flatten之后spyglass规则检查,主要看电源域crossing有没有遗漏隔离或电平转换。第三遍,跑门级低功耗仿真,把每个上断电序列都跑一遍,重点关注X态传播和时序异常。三遍检查侧重点不同,能互补覆盖绝大部分问题。
有一个容易遗漏的地方是VCS低功耗仿真时的UPF加载。门级仿真中UPF文件会和SDF一起读入仿真器,仿真器通过UPF来建模不同电源域的开关行为。如果你的UPF中某个Hard Macro的电源引脚连接没有写全,仿真器会默认为该引脚在所有状态下都保持上电,这可能导致仿真结果的乐观偏差——仿真通过了,真实芯片却出了问题。所以对于Hard Macro,我会额外写一段UPF测试用例,单独把各电压域的开关状态遍历一遍,确认宏的供电引脚行为符合预期。
4.2 常见问题与排查技巧实录
下面这个表格是我在实际项目中整理出的高频问题和排查思路,不只是理论推导,每条都经过了真实项目的检验。
| 现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| UPF编译阶段找不到Hard Macro的电源引脚 | 单元库中该宏的电源引脚名和UPF中写的名字不一致 | 打开.lib文件或SPICE netlist,确认宏的实际引脚名;使用add_power_pin显式建立引脚和supply net的映射 |
| 隔离单元插入后仿真出现X态 | 隔离使能信号在掉电时失效,或者clamp值设错 | 检查isolation control信号是否来自always-on域;对照functional specification确认clamp_value期望值 |
| 顶层输入端口在低功耗模式下悬空 | 端口未归属任何power domain,或该端口所属域被误关断 | 用create_power_domain -ports显式声明端口归属;使用always-on的supply set管理输入参考电压 |
| Level Shifter重复插入或遗漏 | 多级UPF中不同层级都写了set_level_shifter,且location设置冲突 | 在顶层统一管理level shifter约束,子块UPF中不重复设置;用report_level_shifter检查插入结果 |
| IR drop分析时Hard Macro内部电压分布异常 | 宏内部缺少PG连接信息,物理实现层无法准确建模 | 从IP厂商获取宏的abstract view或供电网格模型;在后端create_voltage_area时为宏建立完整供电环 |
| 综合结果中Hard Macro的PG pin悬空 | connect_supply_net只连接了宏的一个电源引脚,其他引脚漏连 | 逐个对照宏引脚清单和UPF中的connect_supply_net,特别关注retention、isolation特殊用电源引脚 |
还有一个新手很容易忽略的细节:UPF中supply net的命名与网表中物理电压域名称之间存在映射关系。很多时候,UPF解析失败不是因为逻辑关系不对,而是因为命名风格不统一。比如UPF里叫VDD_SRAM_1V0,网表里叫VDDS,形式检查工具就无法自动建立关联。解决办法是尽量使用set_equivalent_supply_net或统一的命名映射表来规范连接关系,而不是让工具去“猜”。
4.3 实操心得:UPF文件组织与版本管理
UPF文件不像RTL那样有良好的版本对比机制,但它同样需要版本管理。一个复杂项目的UPF往往有几千行,如果把所有约束都写在单一文件里,后期查问题和ECO都会非常痛苦。我的习惯是拆分成三个文件:
- macro_power_intent.upf:存放所有Hard Macro相关的power domain、supply set和电源引脚连接。
- port_boundary_intent.upf:存放顶层端口的隔离、电平转换和supply set声明。
- top_power_intent.upf:存放顶层电源域划分、主电源网络定义以及上述两个文件的include关系。
文件拆分之后,还有一个额外好处。IP厂商更新宏版本时,通常只需替换macro_power_intent.upf文件;而上电时序变更时,只需要修改port_boundary_intent.upf。减少了改错、引入了风险的可能。
另外一点是UPF加载顺序问题。UPF2.1中,命令的执行顺序很重要,必须先在当前作用域创建domain和supply net,然后做连接,最后定义isolation和level shifter策略。如果顺序颠倒,工具会直接报“unknown object”。在拆分文件时也要保持这种顺序逻辑。我的做法是:先在top_power_intent.upf里完成顶层域的声明,再include两个子文件,最后在顶层文件末尾统一做supply set等效声明和隔离策略确认。这样可以减少依赖顺序带来的错误。
最后再分享一个实际项目的经验
之前在一个28nm的低功耗SoC项目中,项目初期大家都没太在意Hard Macro的UPF连接,以为所有宏都有厂家模板可以一步到位。结果在PR阶段做功耗分析时,发现一个SRAM宏的retention引脚根本没有连上电源,导致低功耗模式下SRAM内容保持不了。定位这个问题的过程很煎熬,影像仿真通过、功能仿真通过,但硬件实测时就是无法恢复现场。后来深挖,发现根因是UPF里对宏的retention域建模缺失。
那次之后我定下了一条规矩:所有Hard Macro在上项目前,必须由后端工程师和IP集成工程师共同review一份“宏电源连接清单”,逐条对照宏手册和UPF脚本确认电源引脚、地引脚、特殊功能引脚全部覆盖到位。这个过程不能省,也绝不能只依赖厂商一句话。低功耗设计的好与坏,往往不在于用了多少高级的UPF命令,而在于边界条件想得有多清楚。把Hard Macro和Top-Level Port这两个边界处理扎实了,后面的综合、验证、签核流程会顺畅很多。