做PCIe低功耗测试,最难的不是读数,而是你根本不知道电流波形对应的是链路的哪个状态。尤其是L1子状态,特别是L1.2,Refclk和发送端都处于关闭状态,示波器上看不到任何信号翻转,电流偏偏就趴在几十毫安以下;这时候如果只靠一台示波器看电流,很容易把一次失败的Link Down当成成功的低功耗状态记录在案。这个项目要解决的问题就是这个:用PCIe CrossSync PHY方案把链路的物理层行为——CLKREQ#、Refclk、Lane0的收发信号——和功耗测量放在同一时间轴上,真正搞清楚L1子状态进入、驻留、退出的全过程,把L1.1/L1.2的功耗测准、测明白。下面我把整个方案从头拆开讲。
1. 项目整体设计与思路拆解
1.1 这个测试为什么绕不开CrossSync
如果只是想知道“L1子状态功耗是多少”,理论上拿个功率计串在供电回路上等电流稳定读数就行。但实际做过一次就知道,问题出在你根本不确定“电流稳定”的那些时间点,链路是不是真的在目标状态。
PCIe链路有L0、L0s、L1、L1.1、L1.2等多个低功耗状态,它们在物理层上表现差异很大。L0有持续的数据或控制信号翻转,L0s可以在几纳秒内进入并且保持物理层静默,L1把发送器和数据链路时钟都关了,L1.2进一步把Refclk和Common Mode都停掉。问题在于,从示波器上看,L0s、L1、L1.1这几个状态下链路信号都是“没有翻转”的,仅凭一根电流波形根本分不清。而CrossSync的价值恰好在这里:它能把CLKREQ#、Refclk、Lane0信号分散在多个通道上同步采集,配合电流探头,把所有跟状态判定相关的信号和功耗放在同一个时间轴上。
1.2 方案分层与测点设计
整个项目我分了三层来做:链路状态观测层、功耗测量层、时间对齐层。
链路状态观测层用的是CrossSync同步的两台示波器。一台看低速控制信号,包括CLKREQ#、PERST#、Refclk;另一台接高速差分探头看Lane0的收发信号。通过CrossSync连接之后,两台示波器共享同一个时基和触发系统,相当于变成了一台更多通道的大示波器,通道数量和带宽两边都保住了。
功耗测量层我专门用的SMU模块(电源测量单元),把给Endpoint供电的12V电源轨串进测量回路,用开尔文四线法采集电压和电流。相比示波器加电流探头的方案,SMU在小电流下精度高得多,而且它可以实现无缝量程切换,不用在L0和L1.2之间手动切档位。这个细节我在后面专门讲,它决定了你是不是能拿到一条完整的功耗曲线。
时间对齐层是很多人忽略但必须做的一件事。仪器之间哪怕各自时间轴都很准,触发起点不一致,后面叠加波形就是一个错位的状态。这里我依靠CrossSync的统一触发和时间戳机制,再在每个通道上做一次Deskew校准,把探头和线缆的延迟差异消掉,确保电流波形上某个跳变,能在链路信号上精确到纳秒级对应到是哪个状态切换。
1.3 关键指标与预期结果
这个项目交付的指标主要有三个维度:
- 各状态的稳态功耗,也就是L0、L0s、L1、L1.1、L1.2下的平均电压、电流、功率;
- 状态切换的能量和时间,即从L0进入L1.2需要多长时间、花了多少能量,退出L1.2回到L0的恢复时间是多少;
- 异常行为记录,包括进入失败、退出失败、链路重新训练等在时间轴上对应的位置和现象。
为什么这三个维度比单纯测一个“L1.2功耗”价值大?因为单纯一个数没办法回答“为什么是这么大”。有了时间对齐的波形,你可以看到进入L1.2的瞬间是不是有电源管理芯片的尖峰电流,退出时恢复时钟的时间是否拖累了整体功耗,这些细节才是低功耗调优真正需要的东西。
2. 核心细节解析与实操要点
2.1 L1子状态里的L1.1和L1.2到底差在哪
看任何PCIe功耗测试项目,先把状态机制看明白。L1 PM Substates是PCIe Base Spec 3.1里正式引入的机制,在此之前不同厂商有自己的私有实现。它最重要的特征是引入CLKREQ#来协商参考时钟的管理:如果Endpoint支持并把CLKREQ#拉低,Host就可以认为时钟管理的责任在Endpoint;当进入L1.2时,CLKREQ#可以被释放,Refclk彻底关掉,链路两端的PHY模拟前端都进入低功耗漏电模式,这就是L1.2功耗能低到毫瓦级甚至更低的根本原因。
| 状态 | 数据链路活动 | Refclk状态 | 发送器/CM Keeper | 典型功耗范围 |
|---|---|---|---|---|
| L0 | 持续活动 | 正常 | 打开 | 数瓦级别 |
| L0s | 无,可快速恢复 | 正常 | 发送器关闭,CM Keeper保持 | 数百毫瓦到1瓦 |
| L1 | 无 | 正常 | 主要发送器关闭,PLL可保持 | 数十毫瓦到数百毫瓦 |
| L1.1 | 无 | 可降频或关闭(受CLKREQ#控制) | CM Keeper保持 | 数毫瓦到数十毫瓦 |
| L1.2 | 无 | 可完全关闭 | CM Keeper关闭,漏电流为主 | 毫瓦级或更低 |
这个表格里的功耗数值是工程上的典型范围,不是规范强制值。具体值和工艺、平台、PHY设计关系很大,但趋势是一致的:L1.2相对L1通常能降低一到两个数量级。
实际测试时,我判断链路是否进入L1.2,依据是三个现象同时出现:CLKREQ#释放(拉高或变为高阻,跟实现有关)、Refclk幅度降低到接近0、Lane0上没有任何差分活动。三个条件缺一不可。如果只有CLKREQ#释放但Refclk还在,那可能是L1.1而不是L1.2。这就是为什么要同时抓多个信号,单看一个容易误判。
2.2 CrossSync的接线、触发与时间对齐
CrossSync的核心思想一句话说完:用一台主示波器作为时间基准,另外一台示波器作为从机,两台设备通过同步线缆连接,共享触发和时基,使得两台机器上所有通道的采样点都在同一个时间坐标系里。听起来像玄学,实际用起来就是把两台示波器变成一台更多通道的示波器。
接线时我把通道规划成这样:
- 主示波器CH1:CLKREQ#,用无源探头,注意CLKREQ#通常有上拉到3.3V,高阻探头就行,但要确认探头地线最短,避免共模噪声;
- 主示波器CH2:PERST#,用于定位链路复位和供电就绪;
- 主示波器CH3:Refclk,优先用差分探头接Refclk对的正端,如果只有单端探头可以取参考时钟单端信号,但要注意幅度判断;
- 主示波器CH4:Lane0的RX差分信号,用高带宽差分探头,带宽至少覆盖被测速率,Gen3用4GHz以上,Gen4建议8GHz以上;
- 从示波器CH1:电流探头或SMU电流输出的模拟监控波形。
通道分配完成后,必须做Deskew。我用主示波器自带的1kHz方波校准信号,分别接到所有通道,用Deskew功能把各通道之间因为探头不同、线缆长度不同引入的延迟校准掉。CrossSync本身只能保证时基同步,通道间的偏斜还得靠Deskew解决,这一步做不好,后面看触发沿对齐就是糊涂账,几百纳秒的错位会让你误判整个时序关系。
触发我一般选CLKREQ#的上升沿或下降沿,下降沿捕获进入低功耗的起点,上升沿捕获退出的起点。采样率方面,低速控制信号用2GSa/s足够,高速差分通道如果同样开2GSa/s,记录长度撑不了几秒,这是通道数的矛盾。我的做法是:低速通道开长时间记录,1到2秒,2GSa/s下对应2到4Gpts;高速通道用短记录但打开触发延时,保证它只记录进入或退出那一小段窗口。这样既能看到L1.2驻留的完整过程,又能保证高速通道的信号细节没丢。
2.3 功耗测量时“动态范围”这个绕不开的坑
L1.2要测到毫瓦级,而L0可能有几安电流,这两者差了1000倍以上。如果用一个固定量程的电流探头从头测到尾,小电流下噪声直接淹没信号;如果用高精度万用表,量程切换又会打断连续性,正好错过状态切换瞬间的电流变化。
这就是我在项目里坚持用SMU的原因。以我用的SMU模块为例,它可以做到从微安到安培的无缝量程切换,有些方案叫Seamless Measurement,切换过程中没有测量盲区,这正好匹配L0到L1.2这种大动态范围、切换频繁的场景。如果预算有限,也可以用示波器加差分探头测串联采样电阻的压降,但采样电阻值要折中:电阻太小,L1.2下的压降信号淹没在噪声里;电阻太大,L0下压降直接把系统供电电压拉崩。
折中方案是:用两个不同阻值的采样电阻串联,比如10mΩ和1Ω,分别用两路差分探头同时测。L0和L0s时读10mΩ那一路,L1.2时读1Ω那一路,然后在脚本里合并。这个方案成本低,但两路探头的增益误差比较麻烦,需要校准。还有一个更麻烦的点:串联1Ω电阻在L0大电流下压降可观,必须设计继电器或电子开关把大电流旁路掉,否则系统无法工作。所以这个方案我只有在手里没有SMU模块时才用,有SMU就老老实实用SMU,省事太多。
这里提一个很多人容易犯的错:采样电阻的取点位置。图省事直接串在12V供电插座的线上,测出来的电压包含线缆压降和接地回路的噪声,数据完全没法用。正确做法是把采样电阻放在电源入口的接口端子上,用开尔文四线法引出Sense点,而且地线一定要单独连到电阻的电流端,不能在电源线上随便找个点就近夹地。这种细节直接决定你测出来的是“整条电源线的损耗”还是“真实的Endpoint功耗”。
3. 实操过程与核心环节实现
3.1 平台搭建与前置检查
我用一块FPGA开发板做PCIe Endpoint,主板插槽作为Root Complex。选FPGA而不是直接拿NVMe盘或网卡来测,有两个原因。一是FPGA板上可以编译额外的调试逻辑,比如把LTSSM当前状态、LTR值通过GPIO实时输出,对定位问题帮助很大;二是便于把某个电源轨单独引出来测量,不用拆盘体、也不用担心固件对低功耗状态的私有控制策略。
上电之前,几个前置检查点必须过一遍:
- 确认链路枚举正常。开机后进系统,lspci能看到Endpoint,并且链路宽度和速率正确。x4还是x8、Gen3还是Gen4,必须有记录,因为不同链路配置下功耗差异很大;
- 检查PCIe AC耦合电容的摆放位置。规范要求AC耦合电容放在发送端一侧,一般建议靠近连接器,走线长度在规范允许范围内。如果电容被放错到了接收端附近或者距离过大,链路勉强能训练,但进入L1.2再恢复时容易出现恢复训练失败,这个坑后面细说;
- 检查CLKREQ#是否正确连接到插槽对应引脚,并且有合适的上下拉。L1子状态必须依赖CLKREQ#参与时钟协商,如果这根线悬空或者被拉死,后面的测试就不用做了。
软件侧,在BIOS里把PCI Express Link State Power Management设置为允许,一般有Disabled、L0s、L1、L1 Substates几个档位,直接选L1 Substates。进入Linux后把ASPM策略设为允许L1。如果用的是Windows,电源选项里把PCIe链接状态电源管理设置为“最大电源节省量”。LTR如果BIOS支持也一并打开,这会让系统更快进入L1.2。
3.2 CrossSync示波器配置步骤
环境准备好后,按下面顺序配置CrossSync:
- 用同步线连接主从示波器,打开主示波器的CrossSync enable,从机被锁定为主机的时基扩展;
- 进入Deskew校准,把方波校准信号接到每个通道,逐一校零,确保通道间偏斜在可接受范围;
- 把CLKREQ#接主示波器CH1,PERST#接CH2,Refclk接CH3,Lane0 RX差分探头接CH4;
- 从示波器接电流探头的模拟输出,或SMU的模拟监控输出,采样率保持在100kSa/s以上,用于电流变化趋势观察;
- 触发源选CH1的CLKREQ#,触发类型选边沿,先用下降沿,触发延迟设成记录长度的80%,这样能看到进入前和进入后的完整过程;
- 记录长度根据实际需要设置,我通常用1到2秒,低速通道足够覆盖两次进入和退出的循环。
配置里最容易忽略的是触发延迟设置。如果你只触发记录当前时刻之后的波形,看到的只有进入L1.2后的稳态部分,前面的L0到L1.2切换过程全部丢失。把触发延迟设置到总记录长度的80%,相当于记录窗口大部分落在触发事件之前,这样从触发时刻往回能看到完整的切换过程。这个思路对任何低功耗状态切换测试都适用。
3.3 功耗测量接线
我用的是SMU模块的电源输出功能,同时把电压和电流采样值通过模拟输出引到从示波器通道,这样功耗数据也和链路信号在同一条时间轴上。实际接线如下:
- 把PCIe插槽的12V供电从主板供电端断开,改为由SMU输出提供;
- SMU用Force和Sense四线法接线,Sense线走开尔文连接,远离大电流路径;
- 设置限流1A左右,具体看Endpoint满载功耗,防止上电瞬间损坏;
- 开启无缝量程切换模式,让电流从L0跳到L1.2时不会被量程切换打断;
- 把PERST#信号作为供电就绪标志同步到示波器,方便和SMU时间轴对齐。
如果不用SMU,替代方案是串联采样电阻。电阻值建议不要只用单个,而是两个不同阻值搭配。10mΩ用于L0和L0s,1Ω用于L1.2小电流。两个电阻串联或者说用继电器切换,然后用两路差分探头同时测。这个方案调试起来比较麻烦,继电器切换的瞬间会引入毛刺,而且切换逻辑本身可能干扰链路状态,所以只能在应急场景用。
3.4 完整测试流程
整个测试我按下面的流程走:
- 系统启动进入桌面,加载PCIe驱动并确认枚举正常;
- 设置ASPM策略允许L1并打开LTR,如果BIOS支持;
- 执行一个空闲脚本,让Endpoint退出所有业务流量;
- 用示波器记录模式,触发记录CLKREQ#下降沿,采集进入L1.2的全过程;
- 保持空闲约60到120秒,让链路在L1.2稳定,然后通过软件发起一次小流量,比如读一次配置空间ID,唤醒链路;
- 记录退出L1.2恢复到L0的波形,同时观察SMU电流波形;
- 重复步骤4到6至少十次,每次记录保存为独立文件,并且记录实时链路状态和时间戳。
整个流程看起来简单,但节奏问题很关键。如果空闲时间太短,链路可能还在L1.1尚未进入L1.2你就发起了唤醒,数据里就少了一段L1.2的稳态波形。如果空闲时间太长,测试时间成本高,而且有些平台在L1.2待久了会因为其他原因触发链路重新训练,得到一堆异常数据。实测下来60到120秒是“足够进入L1.2又不至于过度等待”的区间,具体还要看系统的低功耗进入策略。
唤醒流量最好选择不经过驱动复杂路径的操作,比如直接读Endpoint的配置空间ID。配置空间读总是被允许的,不会因为驱动状态机而阻塞;如果用DMA或网络流量唤醒,中间多了不少环节,容易掩盖真实的退出时序,测量结果里分不清哪些时间花在链路恢复上,哪些时间花在驱动软件上。
3.5 数据处理:把波形变成功耗结论
拿到波形后,我用Python脚本做状态窗口划分和功耗统计。下面是一个简化版本,把CSV数据读进来,按照CLKREQ#、Refclk、Lane活动状态给每个采样点打标签,然后按状态分组统计平均功率和功率中位数。
import pandas as pd df = pd.read_csv('pcie_pwr_log.csv') # 关键列:time, current_A, voltage_V, clkreq_high, # refclk_active, lane0_active df['power_W'] = df['current_A'] * df['voltage_V'] def state_label(row): if row['lane0_active']: return 'L0_active' if row['refclk_active'] and not row['clkreq_high']: return 'L0s_or_L1' # 需要结合协议状态进一步区分 if not row['refclk_active'] and not row['clkreq_high']: return 'L1.1_like' # Refclk关闭但CLKREQ#仍有效 if not row['refclk_active'] and row['clkreq_high']: return 'L1.2_like' # Refclk与CLKREQ#均释放 return 'unknown' df['state'] = df.apply(state_label, axis=1) for state, grp in df.groupby('state'): print(state, f'samples={len(grp)}', f'mean_power_mW={grp["power_W"].mean()*1000:.3f}', f'median_power_mW={grp["power_W"].median()*1000:.3f}')脚本里的状态判据并不绝对精确,比如L0s和L1都可能满足“refclk_active为真且CLKREQ#为低”,要区分它们需要进一步考察链路活动或调用FPGA内部抓的LTSSM状态。所以实际项目中我还会在FPGA里把LTSSM状态机的当前状态通过GPIO输出到示波器,用这个作为状态真值,脚本里的信号判据只做备份。这个做法强烈推荐,它能避免你在波形堆里反复猜状态。
统计口径上,我建议看中位数而不是均值,尤其是做稳态功耗对比。因为每个状态窗口内总是会有切换瞬间的尖峰电流混进去,均值会被尖峰抬高,而中位数能更干净地反映稳态水平。如果非要看能量切换,就单独统计从进入L1开始到稳定L1.2这一小段窗口的电流积分,乘以电压得到能量,这才算有效能量指标。
4. 常见问题与排查技巧实录
4.1 链路就是不进L1:从软件到硬件查一遍
这类问题我遇到过不少,第一个要检查的是ASPM策略是否真的生效。Linux下可以看/sys/module/pcie_aspm/parameters/policy,看active策略是什么;如果policy是powersupersave,一般不会禁止L1,但如果系统厂商覆盖了BIOS设置,ASPM可能被强制关闭。Windows下则要看电源计划的“PCI Express”子项,有些主板驱动会重新覆盖这个设置。
第二个检查点是设备驱动。部分网卡和存储控制器的驱动为了延迟指标会主动禁用ASPM,甚至在运行时把配置空间里的L1使能位清掉。这种情况看驱动日志不一定有提示,直接用setpci查Link Control寄存器最直接。PCIe配置空间里Link Control寄存器在偏移0x10,bits [1:0]是ASPM控制位,bit1对应L1使能,确认它被置1。如果被驱动清掉了,就要去驱动配置里找ASPM相关选项打开。
硬件侧容易忽略的是CLKREQ#的电气连接。CLKREQ#协议上是OD门信号,靠上拉电阻维持高电平,被测Endpoint必须有能力拉低它来请求时钟管理。如果你的测试板把CLKREQ#固定拉低,或者干脆没接,那链路压根不会认为自己有资格进入L1.2,可能只会停在L1。我踩过一次:某块转接卡把CLKREQ#接到了固定低,BIOS里却显示支持L1 Substates,结果功耗怎么测都是L1级别的数值,查了半天才找到原因。测CLKREQ#要不要上拉、上拉到哪,直接决定了整个低功耗测试做不做得成。
4.2 能进L1但进不了L1.2:寄存器、LTR与时钟
链路停在L1而不是L1.2的时候,先看L1 PM Substates功能是否在设备能力寄存器里正确声明。在PCIe配置空间的扩展配置空间里找L1 PM Substates Capability,确认它的能力位包含L1.2支持,控制寄存器里的使能位也要是打开的。很多FPGA IP的L1.2支持需要正确配置和license许可,配置不对就会被静默忽略,最后表现为链路只能进入L1。
第二个高频原因和LTR有关。OS在决定要不要进L1.2时,会参考Endpoint声明的LTR(Latency Tolerance Reporting)。如果你没有实现LTR,或者LTR值太大,OS会认为进出一趟L1.2的恢复延迟不可接受,宁可停在L1也不进L1.2。这属于“软件不敢用”而不是“硬件不能用”。稳妥的办法是在BIOS或驱动里把LTR设为较小的值,比如32us,实测很多平台就愿意进L1.2了。
第三个原因最隐蔽:CLKREQ#没有和Refclk关闭动作联动。有些Host即便看到CLKREQ#释放,也因为对端没有按预期把时钟关闭而不进入L1.2完整流程。这种问题要靠CrossSync波形来判断:观察CLKREQ#释放后Refclk是否真的掉到0。如果Refclk迟迟不掉,就要查Endpoint的参考时钟电路是否支持被CLKREQ#关断。
4.3 时间轴对不齐:Deskew和采样率的坑
CrossSync虽然能把两台示波器同步起来,但通道间偏斜如果没校准,看出来的状态切换会有几十纳秒甚至几百纳秒的错位。有一次我同时看CLKREQ#下降沿和电流探头波形,CLKREQ#已经掉下去了,电流却还维持在L0水平好几微秒,乍一看以为是进入时序慢,实际上是电流探头的模拟输出延迟比其他通道大了一截。后来在Deskew里把电流探头接入的通道也参与校准,全通道偏斜校准后时序才正确。
采样率也会带来假象。低速通道如果开2GSa/s、高速通道开20GSa/s,CrossSync在做插值时会以最小时基为基准,但通道间插值方式不同可能造成微小的沿位置差异。我一直建议所有和时间对齐相关的低速信号通道用同一个采样率,这样戳出来的沿位置才是可信的。真要跨采样率看细节,就先在低速通道上做时长统计,不在高速通道上做精细信号分析,避免把插值误差当成真实时序。
4.4 退出L1.2时间太长甚至链路挂死
在L1.2里待久了再唤醒,偶尔会遇到链路重新训练失败或者恢复时间远大于预期。用CrossSync抓到退出波形以后,要重点量三个时间:CLKREQ#释放后到Refclk恢复的时间、Refclk恢复到链路发送训练序列的时间、最后到L0正常数据的时间。这三段任何一段过长,都值得单独查。
Refclk恢复慢,最常见的原因是参考钟电路上电或锁定太慢,检查PLL上电时序和去耦电容;训练序列发送晚,往往是MAC层没有及时响应物理层信号,检查FPGA里PCIe IP的时钟恢复逻辑;最后一段慢,可能是对端Host的仲裁逻辑慢。遇到这种问题,只靠功耗曲线完全看不出来,CrossSync的三段式时间分析几乎是唯一的定位手段。
4.5 耦合电容摆放位置这个前置坑
前面说过AC耦合电容必须放在发送端一侧,规范要求在TX端放置,典型值75nF到200nF,常见用100nF。位置要靠近发送端,摆放不当会影响信号完整性。这里我补充一个实际案例:有一批测试板把PCIe Gen3的AC耦合电容放在接收端附近,正常跑L0和L0s倒看不出来,但一进L1.2再唤醒,链路就频繁在Polling阶段反复重试,功耗曲线出现一个接一个的退出-重新训练尖峰。后来分析下来,是电容摆放导致信号路径等效长度超限,在长时间低功耗后PHY没有保持住均衡状态,恢复训练时信号裕量不足。
这个问题的教训是:做L1子状态功耗测试前,不但要确认链路能正常枚举,还要确认它能在目标速率下稳定运行一段时间后再进入低功耗。我会在测试开始前先做一轮“低功耗唤醒稳定性”预测试:在L0下跑满带宽流量5分钟,暂停流量进入L1.2,再发起流量,循环100次。如果这个测试里出现任何一次训练失败,回头查信号完整性和耦合电容摆放,别急着测功耗。否则测出来的L1.2功耗大概率包含大量异常重新训练成分,没有参考意义。
4.6 一个验证L1.2测量值可信的小技巧
最后分享一个验证测量可信的小技巧:同时评估一下PHY在L1.2下的漏电流是否在合理范围。具体做法是用CrossSync观测Refclk关闭后没有差分活动,然后看电流波形是不是“真正稳定”而不是持续缓慢下降。L1.2稳态电流应该是几乎不变的,主要就是漏电流;如果电流在几秒窗口内还有明显漂移,说明链路可能仍然在某些模拟电路之间震荡,或者外部电路有非预期漏电。这种情况下算出来的功耗中位数再漂亮,也不代表真正的L1.2功耗。
我自己做这套测试最大的体会是,CrossSync这类同步仪器真正解决的不是“采集”问题,而是“状态判定”问题。PCIe的低功耗状态是协议层、链路层、物理层和供电网络共同作用的结果,任何单一信号都说明不了全貌;只有把CLKREQ#、Refclk、链路差分信号和电源电流放在同一个时间轴上,才能把每一个功耗数字都钉在对应的协议状态上。后面如果再扩展,我会把这套方法直接搬到其他高速串行接口的低功耗测试上,逻辑一模一样:先把“链路状态”变成可观测的物理信号,再谈功耗测量。
最后再分享一个实用的收尾建议:保存所有原始波形的时候,一定把示波器时间戳和SMU时间戳都记录成UTC格式的绝对时间,处理数据时先做一次同步偏移校准。别小看这一步,等你要把十几次实验的数据拼接对比时,就知道它多么省事。数据几百兆也不怕,脚本批量处理,绝对时间对得上,功耗窗口就能和协议状态准确对应,整个项目的可复现性也就稳了。