ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Zynq UltraScale+ MPSoC SYSMON实战:结温与电源监控全解析

Zynq UltraScale+ MPSoC SYSMON实战:结温与电源监控全解析

很多第一次拿到 Zynq UltraScale+ MPSoC 开发板的工程师,开机第一件事往往是点个灯、跑个 Hello World,然后就不知道该干点什么了。其实这块片子最值钱的内设之一,是它自带的一套片上监控系统——SYSMON。它干什么用?一句话:实时告诉你芯片的结对温度是多少、VCCINT/VCCAUX/PS 各电源轨电压稳不稳,外部模拟通道还能挂几路传感器。做工业控制、车载、医疗、通信板卡的工程师,迟早会跟它打交道。

这套系统不是锦上添花,而是可靠性设计的底座。7nm 工艺器件的热密度很高,十几路电源轨一路一路地分压供电,任何一路掉链子,轻则跑飞,重则烧毁。SYSMON 的价值就是让你在芯片内部直接读到结温和电源电压,不用外挂一大排 ADC,也不用在 PCB 上拉一堆采样线。这篇文章我就按自己实际调板的经验,把 Zynq UltraScale+ MPSoC 的 SYSMON 从原理到落地讲一遍:怎么选通路、怎么读数据、怎么换算、怎么设报警,以及常见的坑。

1. 为什么一片SoC要内置一套“体检系统”

先想一个问题:为什么不在板子上放一个外部 ADC,再用 I2C 去读温度传感器?成本倒还在其次,关键是精度和响应。外部传感器贴在 PCB 上、封装外面,离真正的热源——芯片内部晶体管——隔了一层封装和 PCB,温度差个 10°C 都是正常的。SYSMON 把温度传感器直接做在芯片内部,靠近最热的核心区域,测到的是结温(Junction Temperature),这才是真正决定器件寿命和稳定性的物理量。电压也一样,外部用万用表量到的只是 PCB 上的电压,而芯片内部核心电压因为 IR drop 可能已经掉了几十毫伏。

1.1 SYSMON不是ADC,是片上健康管理单元

很多人把 SYSMON 理解成“一颗内置 ADC”,这个理解太窄了。它实际上是一个完整的监测子系统,包含几块东西:一是温度传感器,分布在硅片关键位置;二是电压采样通道,通过片内分压网络把各路电源引到 ADC 输入;三是一颗 12 位 SAR ADC,负责把模拟量换成数字量;四是一组报警比较器,可以设置高低温、高低压阈值,超过阈值直接产生中断;五是寄存器接口,让 PL、PS、JTAG 三条通路都能访问。

我习惯把它类比成人的体检设备:温度通道是体温计,电压通道是血压计,报警逻辑就是异常告警的护士。平常你不用盯着,它自己在那儿周期性地量;一旦超标,它主动喊你。这个“主动喊”的能力在工程上特别重要,因为很多故障要等到系统已经挂了你才反应过来,而 SYSMON 可以在故障发生早期就给你中断。

1.2 都哪些场景在靠它“续命”

Zynq UltraScale+ MPSoC 被用在哪些场景?我自己接触过和看到的典型应用包括:工业机器视觉与运动控制、医疗影像设备、汽车智驾域控制器、5G 小站和回传设备、数据中心智能网卡、以及一些仿真加速平台。这些场景有个共同点:器件长时间高负载运行,环境温度可能很恶劣——工业现场 60°C 的环境温度不稀奇,车载舱内更是如此。这时候如果没有结温监控,你根本不敢把器件推到标称性能。

另外一个高频问题就是 DDR4。很多人搜“zynq ddr4参数详细参数”,把地址映射、数据位宽、时序参数调得很仔细,却忽略了 DDR4 供电轨的稳定性。PS 侧 DDR 控制器对电源纹波和压降极其敏感,高温下 1.2V 的 VCC_PSDDR 一旦掉到阀值以下,就会出现随机 ECC 错误或者系统死机。用 SYSMON 的对应电压通道去监控这个轨,是排查这类问题最直接的手段。所以别把 SYSMON 当成调试期的玩具,量产设备里它是必不可少的“看门人”。

2. 架构与关键参数:动手前必须先弄懂的几件事

2.1 12位SAR ADC,精度到底够不够

SYSMON 内部这颗 ADC 是 12 位逐次逼近型(SAR),采样率最高 1MSPS。第一次看手册的人都会问:监控温度电压而已,为什么不用 16 位甚至 24 位的?

道理很简单:监控和测量是两码事。测量仪器追求绝对精度,监控系统追求的是趋势判断和阈值比较。12 位分辨率下,温度通道的量化步进大约是 0.123°C,电压通道的分辨率取决于全量程(Full Scale Range,FSR),一般每 LSB 对应不到 1mV。这个精度做报警和趋势分析绰绰有余。而且 SAR ADC 结构简单、转换延迟低、功耗小,适合持续采样。

当然,12 位是原始分辨率,实际有效位数(ENOB)会因为噪声、参考电压抖动而略低。所以读出来的数据不要过度解读小数点后面,你要关注的是变化趋势和越限行为,而不是追求绝对精确到 0.01°C。

2.2 内部电源通道、温度通道和外部模拟通道怎么分工

SYSMON 的通道可以分成三类。第一类是温度通道,直接测量芯片结温;第二类是内部电源通道,包括 VCCINT、VCCAUX、VCCBRAM,以及 PS 侧的 VCC_PSINTLP、VCC_PSINTFP、VCC_PSAUX、VCC_PSDDR 等;第三类是外部模拟输入通道,通常叫 VAUXP/VAUXN,是一组差分输入引脚,可以接外部传感器或分压后的被测电压。

内部电源通道的测量范围是出厂校准好的,你只需要读原始码再按公式换算。外部通道则要注意输入电压不能超过引脚允许范围,超过就要用电阻分压。关于内部通道,不同器件、不同封装的全量程值会略有差异,一定要以 UG580(UltraScale Architecture System Monitor 用户指南)里面的表格为准,不要拿着 7 系列 XADC 的手册数据硬套。

通道类型测量对象说明
温度芯片结温传感器在片内,直接读
VCCINTPL 核心逻辑电源片内分压后采样
VCCAUX辅助电源同上
VCCBRAMBlock RAM 电源同上
PS 电源轨VCC_PSINTLP/VCC_PSINTFP/VCC_PSAUX/VCC_PSDDR由 PS 侧 SYSMON 实例监控
VAUX 外部通道用户自定义模拟量差分输入,注意量程

2.3 PL与PS两套监控链路,千万别搞混

Zynq UltraScale+ MPSoC 里其实有不止一个 SYSMON 实例。PL 侧有一个,负责监控 FPGA 逻辑相关的电源轨和温度;PS 侧也有一个,挂在 PMU(Platform Management Unit)下面,负责监控 PS 电源轨、PS 温度,以及部分片上传感器。这两个实例彼此独立,寄存器空间也不同。

这个区别在实际开发中特别容易踩坑。你如果在 Linux 下读到了温度,那大概率是 PS 侧 SYSMON 的数据;而 PL 侧的温度,要么通过 PS 去访问 DRP 寄存器,要么在 PL 里例化 System Management Wizard 用 AXI 去读。两边都有温度通道,但读数可能不一样——PL 和 PS 的发热源不同,硅片位置也不同。所以做监控面板时要标注清楚你读的是哪个实例的数据,不要混在一起做成一条曲线。

3. 三种通路实测:Linux、裸机与PL侧IP

3.1 准备一块能跑的基础平台与烧写流程

我以最常见的 ZCU104 或者自研的 Zynq UltraScale+ 板卡为例。硬件上你需要:一块能正常启动的板子、一条 JTAG 下载器、一张 SD 卡(如果走 SD 启动)。软件上建议用 Vivado + Vitis 2021.1 以后的版本,内核用 Xilinx 官方 BSP 或者你自己编译的 Linux。

启动镜像的老问题——也就是网上总有人搜“zynq烧写”——流程通常是这样的:先做一个 BOOT.BIN,里面包含 FSBL、PMUFW、ATF、U-Boot,然后再加上内核镜像和设备树,做成 SD 卡启动或者烧到 QSPI Flash。这里提醒一句:很多人在 Vitis 里改完裸机程序,发现板上跑的还是旧程序,原因就是新 ELF 没有打进 BOOT.BIN,或者 BOOT.BIN 虽然更新了但 Flash 没擦干净。做 SYSMON 实验也一样,确认你调试的监控程序确实被加载了,再开始看数据。

3.2 Linux方案:一条cat命令看到结温

如果目标板跑的是 Linux,读 PS 侧 SYSMON 最简单。Xilinx 内核里有一个 IIO 驱动,设备树里一般会有一个 SYSMON 节点。

/ { sysmon: sysmon { compatible = "xlnx,zynqmp-sysmon-1.0"; }; };

实际 BSP 里节点位置和 compatible 可能随版本不同,以你的内核设备树为准。驱动加载成功后,会在/sys/bus/iio/devices/下生成设备节点,然后直接读:

ls /sys/bus/iio/devices/ cat /sys/bus/iio/devices/iio:device0/in_temp0_input cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw

in_temp 开头的通道一般返回的是已经处理过的温度值,单位是毫摄氏度。也就是说45250表示 45.25°C。电压通道可能同时提供 raw 和 processed 两种属性,raw 是 ADC 原始码,processed 是直接换算好的电压值。用 cat 跑一下就能看到,非常直观。

在生产环境里,我更推荐用脚本周期性地把这些值拉出来,写进日志或者推给监控平台。比如 5 秒读一次温度、10 秒记录一次电压,连续跑几个小时,基本就能画出这块板子的热特性曲线。

3.3 BareMetal方案:在FSBL或独立应用里拿数据

Linux 还没起来,或者系统不允许跑 Linux 的时候,就要用裸机驱动。Xilinx 的 BSP 里提供 PS SYSMON 驱动,在 Vitis 里创建 standalone 工程后,可以在 BSP 里勾选xsysmonps,然后这样初始化并读取:

#include "xsysmonps.h" static XSysmonPs SysMonInst; int SysMon_Init(void) { XSysmonPs_Config *CfgPtr; CfgPtr = XSysmonPs_LookupConfig(XPAR_XSYSMONPS_0_DEVICE_ID); if (CfgPtr == NULL) { return XST_FAILURE; } XSysmonPs_CfgInitialize(&SysMonInst, CfgPtr); return XST_SUCCESS; } float ReadTemp(void) { return XSysmonPs_GetTemperature(&SysMonInst); } float ReadVccInt(void) { return XSysmonPs_GetVoltage(&SysMonInst, XSYSMONPS_CH_VCCINT); }

注意,不同 Vitis 版本的 BSP 里,枚举名和函数名可能会有细微差别,编译报错时打开xsysmonps.h对照一下就行。裸机方式的优点是可以放在 FSBL 阶段就跑起来,在 Linux 或你的应用程序启动之前先完成一轮自检:温度是否过高、关键电源轨是否正常。不过,任何不通过,就不继续启动后面的步骤。这个逻辑在车载和工业设备上是硬性要求。

3.4 PL方案:System Management Wizard与AXI读取

要在 PL 侧做监控,最常见的是在 Vivado 里例化 System Management Wizard 这个 IP。配置的时候有几个关键点:一是选择你要启用的通道,内部电源通道按需勾选,外部通道如果用过要单独 Enable;二是采样模式建议选 continuous,让 ADC 自己一直循环采样,而不是靠软件逐个触发;三是如果你要接外部传感器,差分输入引脚要在约束文件里分配好。

IP 例化后会自动生成一个 AXI 接口,你也可以用 DRP 接口让 PS 来读。假设你把 IP 挂到某个 AXI 从端口,基地址是 0xA0000000,那么在裸机或 Linux 驱动里就可以这样读:

#include "xil_io.h" #define SYSMON_BASEADDR 0xA0000000 #define SYSMON_REG_TEMP 0x00 #define SYSMON_REG_VCCINT 0x04 static u32 SysMon_ReadChannel(u32 Offset) { u32 val = Xil_In32(SYSMON_BASEADDR + Offset); return (val & 0xFFF0) >> 4; } float SysMon_ReadTempC(void) { u32 raw = SysMon_ReadChannel(SYSMON_REG_TEMP); return (raw * 503.975f) / 4096.0f - 273.15f; } float SysMon_ReadVccInt(void) { u32 raw = SysMon_ReadChannel(SYSMON_REG_VCCINT); return (raw * 1.0f) / 4096.0f; }

注意上面代码里的 12 位数据是左对齐放在寄存器的 [15:4] 位,所以要先右移四位,这个对齐方式在多数 XADC/System Monitor 移植版本上是一致的,但保险起见还是要看你 Vivado 生成的 IP 手册里的寄存器说明。VCCINT 的换算我在代码里用了 1.0V 做示例,实际全量程值要查 UG580,我后面会再说。

PL 侧方案适合做 FPGA 逻辑自己的健康信息上报,比如把温度、电压打包进 PCIe 的配置空间,或者通过 UART 直接输出到调试串口。它不依赖 PS 操作系统,PL 上电配置完成后就能工作。

3.5 报警阈值与中断联动

SYSMON 的报警功能是它区别于普通 ADC 的核心竞争力。报警的思路是:预设一组上限和下限,比如温度上限 100°C、下限 -10°C;VCCINT 上限 0.95V、下限 0.70V。采样结果不断和这组阈值比较,一旦越限,就置位报警寄存器并产生中断。

以 PS 侧裸机为例,设置报警阈值通常是类似这样的调用:

XSysmonPs_SetThreshold(&SysMonInst, XSYSMONPS_ALARM_TEMP_HIGH, 100.0f); XSysmonPs_EnableAlarm(&SysMonInst, XSYSMONPS_ALARM_TEMP_HIGH);

然后在中断处理函数里去读报警状态寄存器,判断是哪个通道、哪一类报警,再执行风扇加速、频率降级或者直接整机断电。我建议报警阈值在开发阶段不要设得太激进。比如室温 25°C 时结温可能在 40°C 左右,你如果设 45°C 报警,那整个调试期间就一直在响。先记录一段时间正常运行的温度峰值,再在这个峰值上加 20~30°C 的裕量作为正式阈值,这个做法比较稳妥。

4. 换算、标定与精度实战

4.1 温度换算公式与一次实测核对

SYSMON 温度通道的原始码和温度之间,遵循 PTAT 传感器的一阶线性关系。官方手册给出的换算公式是:

[ T(°C) = \frac{Raw \times 503.975}{4096} - 273.15 ]

举个例子:我某次在室温 26°C 的实验室里跑一个视频编解码负载,从寄存器读到 Raw = 2500。代入公式:

[ \frac{2500 \times 503.975}{4096} - 273.15 = 307.60 - 273.15 = 34.45°C ]

这个 34.45°C 是结温,比板载测温电阻贴在散热器上读到的 29°C 高出 5°C 左右,完全合理。所以不要拿 SYSMON 的结温去和外壳温度做严格对比,它们的物理含义不同。你要做的是建立一条“结温-负载-环境温度”的对应关系,后续通过结温来推断系统热状态。

4.2 电压换算与外部通道分压设计

电压通道的换算比温度简单,就是比例关系:

[ V = \frac{Raw \times FSR}{4096} ]

难点在于 FSR 到底是多大。UG580 里有一张表,列出了每个内部通道的全量程电压。我强烈建议你打开手册,找到你的具体型号那一行,把每个通道的 FSR 抄下来写进头文件,而不是凭感觉填。拿 VCCINT 举例,如果 FSR 是 1.0V,某次读到 Raw = 3446,那么电压就是 3446 × 1.0 / 4096 ≈ 0.841V,和标称 0.85V 的差异来自电源纹波和 ADC 量化,属于正常。

外部通道的设计要更小心。VAUX 差分输入的量程通常较小,被测信号如果来自 5V 或 12V 的分压网络,必须仔细算电阻比例。我的习惯是:先留足余量,让被测电压在加到引脚前被限制到量程的 80% 以内;再在 ADC 引脚前加一个 RC 低通滤波,截止频率不要太高,几十 kHz 就够,用来滤掉电源开关噪声;最后在输入端串联一个几百欧姆的保护电阻,防止带电插拔时损伤引脚。

4.3 出厂标定的几个土办法

SYSMON 出厂时内部通道已经校准过,但对精度要求高的应用,我还是建议做一次板级标定。方法不复杂:拿一块已经上电的板子,用高精度万用表去量对应电源轨的测试点电压,同时读 SYSMON 的原始码,算出实际 FSR 和手册标称值的偏差。如果偏差是一个固定的比例系数,就可以在软件里做增益修正;如果偏差随温度漂移,那就需要在高温和低温两个点分别标定,做两点校正。

外部通道更要标定。因为分压电阻本身有 1% 甚至 5% 的误差,电阻温漂也会带来读数偏移。我的做法是:在 PCIe 板卡或设备整机测试阶段,用 CNC 或手动模式加载一个已知电压,记录读数,生成一张“实测-读数”对照表,最后在固件里做线性校正。别嫌麻烦,批量生产的板子,电阻批次一换,不标定的话电压读数能偏出 2% 以上,这在某些电源监控场景是不可接受的。

5. 现场问题排查与经验备忘

5.1 高频问题速查表

下面这些是我在实际调板过程中遇到过的、以及在社区里看到高频出现的问题,整理成速查表,方便你现场对照。

现象可能原因处理办法
温度读数一直是 0xFFF 或满码采样通道未使能或转换未启动检查 IP 配置是否开启 channel,检查连续采样模式
温度读数跳变剧烈,波动几度ADC 转换结果未做平均软件里做多次采样取平均,或开启过采样
电压读数比万用表低几十毫伏板上走线压降或 FSR 配置错误核对 UG580 的 FSR,用高精度表标定
Linux 下找不到 IIO 设备节点设备树节点缺失或驱动未加载检查内核是否启用 xlnx-zynqmp-sysmon 驱动
PS 和 PL 温度读数不一致两个不同实例,测量位置不同区分两个实例的数据,不要混用
外部通道悬空时读满码引脚浮空,导致采样输入不确定外部通道不用时禁用,使用时接好差分对
报警在启动阶段误触发阈值寄存器默认值或者上电瞬间电源未稳启动完成后延迟一段时间再使能报警

5.2 我踩过的几个坑

第一个坑:在开发板的 PMU 固件里使能了温度报警,但阈值设得太低。结果板子一加载大负载,PMU 直接触发报警,系统重启。当时排查了半天,最后发现是报警阈值设置的是 60°C,而这个板子满载时结温轻松到 75°C。所以报警阈值必须基于实测数据来定,不能拍脑袋。

第二个坑:外部通道没有用的引脚,我没有在 IP 里禁用,导致它悬空。悬空引脚读出来的电压码是 0xFFF,车里系统监控面板上多了一路 1.8V 的假报警。后来把所有不用的外部通道全部禁用,问题消失。这个细节非常容易被人忽略。

第三个坑:跟“zynq ddr4参数详细参数”相关。调试一块自研板时,DDR4 在常温下完全正常,一跑高温环境就出 ECC 错误。用 SYSMON 量 VCC_PSDDR,发现温度上来后该轨电压下降了接近 80mV,已经低于 DDR4 规范允许的纹波范围。问题根源在供电模块的高温降额,而不是 DDR 控制器参数。后来换了更大电流的 LDO 并调整 SYSMON 报警阈值,问题解决。通过这件事我也意识到:DDR4 的详细参数调试,不能只看控制器侧,还要看供电侧的实时监控。

第四个坑:烧写镜像后监控程序不生效。有一次我在 Vitis 里重新编译了一个包含物理层监控的应用,准备通过“zynq烧写”流程把它烧到 QSPI Flash。结果重新启动后跑的还是旧程序,读出来的温度和电压一直是初始化默认值。后来发现烧写工具只更新了应用分区,而 BOOT.BIN 里的 FSBL 和 PMUFW 没有重新打包。PS 侧很多功能——包括 SYSMON 的初始化——依赖 PMUFW 的配合,PMUFW 不更新,监控初始化路径就不对。改完重新打包烧写,一次通过。

5.3 监控数据还能怎么用

温度和电压数据读出来了,别只停留在“看一眼”的层面。我做过几个比较实用的扩展:第一是风扇调速,用结温做闭环输入,替代固定转速,温度低时安静,温度高时全速运转,对设备噪音和寿命都有好处;第二是动态降频,在温度接近报警阈值时,通过 Linux 的 CPUFreq 或 PS 的 DVFS 接口把频率降一档,让系统在不过热的前提下继续运行,而不是直接关机;第三是寿命预测,连续记录结温和时间,做一个简单的 Arrhenius 寿命估算模型,配合 RTC 时间戳,可以预估器件在设备中的老化速度。

数据上报那块也值得一提。如果你做的是远程运维设备,把 SYSMON 数据通过 UART、以太网或者 PCIe 传到上位机或云端,就可以在设备还没彻底挂掉之前提前预警。我见过不少设备故障,日志里最后一条就是 SYSMON 报出的电压越限记录,这一条数据往往能省下好几天的排查时间。

我在实际做这套东西的时候,最大的体会是:SYSMON 的数据不复杂,难的是把它当成一个系统来用。你先得确认自己读的是 PL 还是 PS 的实例,再花半天时间把每个通道的 FSR、换算公式、报警阈值全部记录成一张表,最后把这些值和中断、风扇、降频、日志联动起来。这样一套下来,你的板子才算真正有了“自我感知”的能力。下次调板子遇到高温死机或者电源异常的疑难问题,不妨先看一眼 SYSMON——很多时候,答案早就写在寄存器里了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进