做集成电路、电源或者混合信号仿真的人,应该都有过这种经历:HSPICE 跑了好几个小时,波形也出来了,本来以为万事大吉,结果一量延迟和功耗,数据怎么都对不上。前阵子我帮同事复核一条 buffer 链的性能,用 Custom Waveform 从同一个 .tr0 文件里取数,和当初仿真脚本里写的 .meas 结果差了将近一倍。当时第一反应以为是电路出了问题,后来折腾了半天才发现,问题出在测量窗口、阈值定义和电流方向这些看似不起眼的细节上。
这篇文章就把我这次排查过程中踩过的坑整理一下。内容围绕 HSPICE 瞬态仿真后,怎么用 Custom Waveform 把延迟和功耗测准这件事展开,包含表达式设置、窗口选择、时间步长影响,以及一组实测数据对比。适合刚接触 Custom Waveform 的人,也适合那些已经用了很久但总觉得数据“差点意思”的工程师。
1. 延迟和功耗为什么必须统一“测量口径”再谈准确性
1.1 我踩过的现场:同一份波形,三个数
先说那天到底发生了什么。被测电路是一串 5 级反相器组成的驱动链,输出端接了 1pF 的负载电容,电源电压 1.8V,输入是 5MHz 的方波。仿真脚本里我用 .meas 语句分别量了 t_pLH、t_pHL 和平均功耗,结果分别为 4.87ns、4.52ns 和 118.6uW。后来同事用 Custom Waveform 打开同一个波形文件,直接拖了两条 cursor 过去量,出来却是 t_pLH=5.63ns,平均功耗变成了 139.2uW。
差这么多,谁看了都会慌。我先是怀疑模型库加载有问题,又检查了电源和地网络,最后甚至重跑了一遍仿真。直到我把两个测量方式的阈值对比放在一起,才真相大白:Custom Waveform 默认的延迟阈值是 1.2V,而我 .meas 里用的是 0.9V,因为我们的 VDD 是 1.8V,50% 阈值就是 0.9V。阈值不一样,延迟自然就不一样,尤其是上升沿比较缓的节点,差了 0.7ns 一点不奇怪。
这类问题在团队协作里特别常见。做过时序分析的人都知道,延迟不是“波形上随便找一个点”,而是一个有明确约定、有阈值、有边沿定义的量。只要阈值、边沿、窗口三者任何一个没对齐,结果就没有可比性。所以谈 Custom Waveform 怎么用之前,首先要养成一个习惯:所有测量都必须先明确定义,再去看数字。
1.2 延迟/功耗测量的通用口径
延迟最通用的口径是传输延迟(propagation delay)。输入端信号到达 50% VDD 的时刻,到输出端信号到达 50% VDD 的时刻,两者的差值就是这一级电路的传输延迟。上升沿对应的叫 t_pLH,下降沿对应的叫 t_pHL。有些场景还会区分 rise time 和 fall time,但那是信号边沿速率,不是延迟,容易混。
功耗测量则复杂一点。瞬态仿真里我们能拿到的功耗主要有几个层面:静态功耗、短路功耗、开关功耗,以及峰值功耗。平均功耗一般用一段时间内的能量积分除以时间长度得到;峰值功耗则是在某个时刻或非常短的时间窗内,电源电压和电源电流乘积的最大值。这里特别容易出问题的是“时间长度”该取多少,以及电流方向怎么算。
延迟和功耗看似两个独立指标,实际上对测量参数的要求是一致的:必须有一个稳定的、可复现的时间窗口。延迟要看稳态下的信号翻转,功耗同样要看稳态下的周期性行为。如果一上来就把整个 tran 波形全选进来,电源启动阶段的电流尖峰会被算进平均功耗里,延迟测量也容易把第一个边沿的“冷启动”状态包含进去。这就是很多“奇怪数据”的真正来源。
2. Custom Waveform 量延迟:阈值、边沿和波形索引位一个都不能错
2.1 延迟量测的设置步骤
Custom Waveform 打开 .tr0 文件后,最常规的做法是直接选中信号 v(in) 和 v(out),然后在波形窗口的测量菜单里选择 Transition Delay。这里一定要手动配置三个参数:阈值电压、信号边沿、以及测量起点。
阈值电压建议直接输入 VDD*50% 的计算结果。继续用 1.8V 的例子,阈值就是 0.9V。如果电路有不同电压域,每个电压域的阈值都要单独设,不要图省事把两个信号都设成同一个数值,除非它们确实是同一个电源域。边沿选择要看你是测上升沿传输还是下降沿传输。测 t_pLH 时,输入信号选 rise,输出信号也选 rise;测 t_pHL 时两边都选 fall。这听起来简单,但在波形上如果存在多次过阈值,工具会默认选择第一个满足条件的点,容易把前一个周期的残留翻转也算进去。
这时候就需要“波形索引位”出场了。Custom Waveform 的测量配置里一般都有 occurrence 或者 edge number 这样的选项,意思是“从哪个边沿开始测”。比如你想测稳态的第 10 个上升沿,就把输入边沿索引填 10,输出边沿索引也填 10。如果不填,工具可能默认从波形文件最开始计算,而波形最开始往往是从 0 开始上电,那个边沿根本不是正常的逻辑翻转。
我个人的习惯是先让仿真跑到至少 2 到 3 个周期后再开始取数。比如 5MHz 方波,周期是 200ns,那我仿真至少跑 2us,然后取第 8 到第 10 个周期之间的波形作为测量对象。这样既能避开上电瞬态,又能拿到足够多的稳态样本。
2.2 延迟误差容易被放大的情形
延迟误差最容易被放大的地方,是信号边沿不够陡的时候。同样的阈值偏差,如果信号翻转斜率是 0.5ns 内从 0V 到 1.8V,阈值算错 0.1V 只会带来几十皮秒的误差;但如果遇到高阻节点或大电容负载,边沿可能长达 5ns,阈值算错 0.1V 就能带来几百皮秒甚至 1ns 以上的偏差。
换句话说,阈值设在 1.2V 还是 0.9V,对慢边沿信号的影响会被成倍放大。很多人在 Custom Waveform 里量的数据“偏大”,不一定是工具错了,而是阈值定得过高,导致测量点在边沿上往后移动了一大截。
另一个容易踩的坑是信号名带总线位。比如out<5>、out[5]、v(out:5)这三种写法在不同版本的工具里都可能出现。量错信号名不会报错,但会把一条完全不相干的波形拉进来。我吃过一次亏,量出来的延迟是 0.1ps,检查了半天才发现把v(out:0)输成了v(out:O),字母 O 当成数字 0 了。这个只能靠每次测量前放大波形确认高电平、低电平数值来兜底。
还有一点值得注意:Custom Waveform 里看到的波形是仿真数据点之间的线性插值结果。如果输出信号在阈值附近有抖动,比如在 0.9V 附近反复穿越,工具默认会用“第一次穿越”来定义延迟。这种抖动往往来自仿真器内部的时间步长过大,不是电路的正常行为。解决办法不是去改测量设置,而是回头把 .tran 的 maxstep 减小,从源头上把波形跑得更干净。
3. 功耗测量没那么简单:窗口选择、电流方向和平均值陷阱
3.1 平均功耗和峰值功耗定义
在 HSPICE 瞬态仿真中,想看功耗,最直观的办法是把电压源支路电流和电源电压的乘积画出来。这个乘积就是瞬时功耗曲线。平均功耗通常是对某个时间窗口内的瞬时功耗做积分,再除以窗口长度;峰值功耗则是窗口内瞬时功耗的最大值。
用 Custom Waveform 实现时,常见的表达式大概是下面这种风格:
avg( v(vdd) * i(v_vdd) ) max( v(vdd) * i(v_vdd) )如果工具支持带窗口的测量,可以写成:
avg( v(vdd) * i(v_vdd), 2u, 6u ) max( v(vdd) * i(v_vdd), 2u, 6u )注意i(v_vdd)的写法取决于电压源在网表里的名字。比如说网表里是v_vdd vdd 0 1.8,那么电流函数的写法就是i(v_vdd)。还有一个很容易忽略的问题:HSPICE 里无源元件的电流方向默认是从正端流入,从负端流出。如果你是按“从电源流出到电路”的方向来思考的,那么i(v_vdd)的数值可能是负的,乘上 v(vdd) 以后,功耗曲线会变成一个负数。负功耗在数学上不是不能积分,但很容易让人混淆。
我通常在表达式里加一个绝对值,或者反过来把电流支路的方向调成从地到电源,也就是在网表里写成v_vdd vdd 0 1.8,然后测量时用i(v_vdd)但先通过波形确认它到底是正还是负。确认方向这件事,用光标点一下波形上的电流值就可以,不花时间,但能避免后续所有数据偏差。
3.2 为什么“全窗口取数”会高估功耗
这是功耗测量里最隐蔽的一个问题。Custom Waveform 默认的测量区间一般是从波形文件的第一个数据点到最后一个数据点。如果仿真从 0 时刻开始,电源一上电的瞬间会有很大的充电电流,这个电流尖峰持续时间可能只有几纳秒,但幅值是正常工作电流的几十倍甚至上百倍。
你今天看到 139.2uW,而稳态平均功耗其实只有 118.6uW,就是因为那个上电尖峰被无差别地平均进了整个窗口。上电瞬间发生的翻转次数是有限的,但能量却很大,把平均值拉高。仿真跑得越长,单个上电尖峰的影响越小,但如果你把仿真时间缩短到几个微秒,影响就会非常明显。
正确做法是先把功率波形画出来,用光标确认波形从什么时候开始进入周期性稳态。然后再把这个区间作为测量窗口。比如我这次仿真从 2us 开始,波形已经稳定成 5MHz 的周期信号,那窗口就取 2us 到 6us。取四个完整周期,时间上足够平均,又不至于把上电阶段卷进来。
同样道理,测峰值功耗时也不能直接取整个时间轴上的最大值。上电阶段的浪涌电流往往会创造出一个超级夸张的“峰值功耗”,这个值对电路的实际工作状态没有参考意义。如果要评估正常工作时的峰值功耗,就一定先限定时间窗口。
3.3 峰值的“假尖峰”过滤
还有一个峰值的“假尖峰”问题。瞬态仿真里,如果时间步长设置不够密,电源电流波形可能会出现非常窄的高幅值毛刺。这个毛刺可能是真实存在的,比如输出级同时导通造成的瞬时短路电流;也可能是数值噪声,尤其是迭代收敛不充分时,电流值会在某几个点突然跳变。
区分真假毛刺的办法很简单:把毛刺附近的时间轴拉到足够细,看它是不是由多个连续的数据点组成。如果只有一个孤立的样本点高得离谱,前后都是正常值,那大概率是数值噪声。这时候不要急着记录这个“峰值功耗”,先把仿真步长或收敛精度调高,重跑后再看。
我在项目里会把峰值功耗的统计窗口设置成包含一个完整上升沿加下降沿的时间范围,比如 1us 到 1.5us,而不是只取单个时刻。这样即使有短暂的短路电流,也能反映真实状态,不会被孤点干扰。
4. 实测对比:同一段 tran 仿真,Custom Waveform 与 .meas 的结果差异
4.1 测试条件
为了把问题说得更直观,这里列一组我实际跑过的数据。电路结构是前面提到的 5 级反相器链,输出负载 1pF,VDD=1.8V,输入 5MHz 方波,仿真总时长 10us。HSPICE 脚本里同时写了 .meas 语句做参考,Custom Waveform 里则分两种方式取数:一种是不改任何默认配置直接量;另一种是把阈值、窗口、边沿全部按统一口径设置后重新量。
参考 .meas 语句大致如下:
.meas tran tpLH TRIG v(in) VAL=0.9 RISE=10 TARG v(out) VAL=0.9 RISE=10 .meas tran tpHL TRIG v(in) VAL=0.9 FALL=10 TARG v(out) VAL=0.9 FALL=10 .meas tran avg_power AVG v(vdd)*i(v_vdd) FROM=2u TO=6u .meas tran peak_power MAX v(vdd)*i(v_vdd) FROM=2u TO=6u这里的 RISE=10 和 FALL=10 就是前面说的波形索引位,表示取第 10 个上升沿或下降沿作为测量点。阈值取 0.9V,功耗窗口取 2us 到 6us。
4.2 结果对照表
下面这张表是最终的测量结果:
| 测量项 | 参考 .meas 结果 | Custom Waveform 默认取数 | Custom Waveform 按统一口径取数 |
|---|---|---|---|
| t_pLH | 4.87ns | 5.63ns | 4.86ns |
| t_pHL | 4.52ns | 4.49ns | 4.51ns |
| 平均功耗 | 118.6uW | 139.2uW | 118.7uW |
| 峰值功耗 | 1.03mW | 1.72mW | 1.04mW |
第一列是 .meas 的正确结果。第二列是 Custom Waveform 默认取数的结果,此时阈值不是 0.9V,窗口也没有限制,导致 t_pLH 偏大 0.76ns,平均功耗偏大 17.4%,峰值功耗甚至偏大了 67%。第三列是把阈值改为 0.9V、边沿索引调整为第 10 个边沿、功耗窗口限制为 2us 到 6us 之后的结果,这时所有数字和 .meas 基本吻合,差异都在 1% 以内。
t_pHL 在默认取数时看起来反而接近正确值,这是巧合。因为下降沿的阈值偏差方向可能和边沿速率相互抵消,但并不能因此就说默认设置“没问题”。经验不足的人看到 t_pHL 对得上,就容易忽略其他三项的严重偏差,最后稀里糊涂地把错误数据提交出去。
4.3 修正后一致性为负的原因
其实修正后的测量结果并不是刚好完全相等的,还有 0.01ns 到 0.02ns 左右的小差异。这来自两种测量机制的点位插值方式不同。HSPICE 的 .meas 语句在计算阈值穿越时间时,会利用仿真器内部的时间步长信息做更精细的反插值,而 Custom Waveform 读取的是已经被写到 .tr0 文件里的离散样本点,它看到的分辨率取决于文件里保存了多少个点。
如果 .tr0 文件的采样间隔比较粗,Custom Waveform 的插值误差就会明显一些。想缩小这个差异,有两条路:一是把仿真输出的时间间隔设得更密,也就是减小 .tran 的 maxstep;二是在 Custom Waveform 的显示设置里开启“线性回插”或“高精度插值”选项,让它尽量还原原始波形。
但要注意,后一条路只是让数据显示更平滑,并不能凭空恢复 .tr0 文件里没有存储的高频信息。真正靠谱的办法还是从仿真源头控制输出密度,后面第 5 部分会专门展开讲。
5. 时间步长与收敛性:测量精度藏在仿真设置里
5.1 maxstep 设置对延迟的影响
很多人做瞬态仿真时,习惯只写总时长,不写 maxstep,觉得 HSPICE 会自动控制步长。对简单电路来说,HSPICE 确实会自动加密时间步;但对大负载、慢边沿、强反馈这类结构,自动步长控制可能不够密,导致波形里出现肉眼可见的“折线段”。
延迟测量本质上是在找输入、输出两个信号穿越阈值的时间差。如果时间步长很粗,比如 1ns 一个点,而信号刚好在两点之间穿越了 0.9V,那么工具只能根据前后两个样本点做线性插值来估算穿越时刻。这个估算和真实穿越时刻之间的误差,最大可以达到半个步长。换句话说,maxstep=1ns 时,延迟误差可能到 ±500ps。这个量级对高速接口来说已经非常致命。
我这次测试分别用了三组参数:
| .tran 设置 | t_pLH 测量值 | 与基准差异 |
|---|---|---|
tran 0 10u | 5.76ns | +0.89ns |
tran 100p 10u | 4.99ns | +0.12ns |
tran 10p 10u | 4.87ns | 基准 |
可以看到,不写 maxstep 的默认结果,和 10ps 步长的基准结果差了快 1ns。这完全是采样点太稀造成的假象,不是电路延迟真的变大了。所以我做延迟敏感仿真时,会让 maxstep 至少小于预期延迟的 1/20。预期延迟 5ns,那 maxstep 取 200ps 以内比较安全。
5.2 收敛抖动与“假延迟”
时间步长只是问题的一半,另一半是收敛性。瞬态仿真每个时间点都是一个非线性方程组的迭代过程,如果迭代没有真正收敛,得到的电压值可能偏离真实解,反映在波形上就是阈值附近出现抖动。
收敛抖动对延迟测量的影响尤其恶劣。信号在 0.9V 附近抖动时,波形会短时间内多次穿越阈值,工具如果默认取第一次穿越,测到的延迟可能比真实值提早很多。比如输出从低到高,如果它在 0.7V 先冲了一下到 0.95V,再掉回 0.85V,然后再稳定冲到 1.8V,工具会在第一次上冲到 0.95V 时就认为“已经到阈值”,而这个时刻实际只是噪声过冲,不是真正的逻辑翻转。
遇到这种情况,我建议回到仿真参数里检查两件事:一个是.option里有没有开启accurate=1,另一个是收敛相关的容差设置是否过宽。对模拟电路仿真,runlvl和abstol的默认值在很多版本里对功耗和延迟测量偏宽松。可以试着加上:
.option accurate=1 .option runlvl=6这样会带来一些仿真时间开销,但只要不是超大电路,都值得打开。等电路真正到了超大规模后仿阶段,再根据实际情况调高收敛容差也不迟。
5.3 其他精度选项
除了 maxstep 和收敛设置,还有两个输出选项会影响 Custom Waveform 的读数。一个是.option post,它控制是否把波形数据写入 .tr0 文件,默认一般打开;另一个是.option probe,它可以让你只保存指定信号,大幅缩小文件体积。如果文件太大,Custom Waveform 加载和缩放都会卡顿,反而容易让人不想精细测量。
我通常在仿真脚本里加一段:
.option post .option probe .save v(in) v(out) v(vdd) i(v_vdd)这样 .tr0 文件里只保留我关心的信号,加载快很多。测量完成后如果发现需要别的中间节点,再临时改脚本重跑。对于动辄几 GB 波形的后仿场景,这种按需保存的方式能节省不少时间。
6. 我沉淀的一套 Custom Waveform 测量固定流程
6.1 测量模板
经过这些坑之后,我固定形成了一套流程,每次拿到新的瞬态波形,都按这个顺序来操作,基本不会再出现“数据对不上”的情况。
第一步,检查波形是否满足稳态条件。先把 v(in) 和 v(out) 都画出来,缩放到完整时间范围,确认上电暂态在哪个时刻结束。如果波形还在缓慢爬升,先延长时间重跑,不要急着测量。
第二步,设置阈值。延迟测量用 0.9V,不是 1.2V,更不是默认的“最大值的 50%”那种模糊概念。这里直接计算 VDD 的 50%,填数值,不用百分比。因为不同电压域混用时,百分比容易指定到错误的电源域。
第三步,设置边沿索引。测第 10 个上升沿,就两边都填 10。如果输入波形前面有几个毛刺,可能需要把边沿索引进一步提高,选一个信号干净的区域。
第四步,设置功耗窗口。用v(vdd)*i(v_vdd)画出瞬时功耗曲线,光标找到进入稳态周期的起点,把窗口取到包含至少 4 个完整周期。同时看一眼电流方向,必要时在表达式里加绝对值。
第五步,对照 .meas 数据。不要只测一组就出报告,先和 HSPICE 脚本里的 .meas 对比一次,差异在 1% 以内才认为可信。超过 1% 就回头检查阈值和窗口,而不是调大插值精度。
这套流程看起来繁琐,实际上熟练之后不到两分钟就能完成一组测量。比起“先量一个数,出了报告再被质疑,最后花半天重新核实”,这点时间投入非常划算。
6.2 一点使用习惯
最后再说一个额外的使用习惯:把测量配置保存下来。Custom Waveform 一般支持保存波形工程文件,可以把已经设置好的信号、表达式、测量结果都存在同一个文件里。我每次做完一组测量,都会另存为一份带日期和工艺角名称的工程文件,比如delay_power_tt_20250114.cwm。下次要复测同一个模块,直接加载工程文件,数据、曲线、表达式全部还原,不用重新配置。
另外,输出报告时我会同时附上一行“测量条件”说明,包括阈值电压、边沿索引、功耗窗口、仿真步长。这行说明看起来是小事,但在跨部门协作或者写设计文档时,能帮其他人避免重复我踩过的坑。很多时候,延迟和功耗数据看起来“对不上”,不是因为电路变了,而是因为测量口径变了。把这些信息写清楚,比在邮件里来回解释半天要高效得多。
回到开头那个案例,那次数据偏差的最后结论其实很简单:Custom Waveform 的默认阈值和脚本里的 .meas 不一致。找到原因后,所有数字迅速回归一致,电路本身没有任何问题。这件事给我的影响却留了很久,之后每做一次测量,我都会先问自己一句:我量到的,到底是不是我真正想定义的那个量?