☰
Zynq UltraScale+ SYSMON实战:从结温监控到DDR4调试的完整指南
2026/10/26 9:37:19 网站建设 项目流程

一个多月前,我手头那块Zynq UltraScale+板卡在DDR4训练阶段总是间歇性失败,有时候开机能起来,跑十分钟后读写报错。排查了一圈,最后是SYSMON的温度和电压记录帮了忙——VCC_PSDDR在负载起来的时候掉得厉害。这件事之后我意识到,很多人(包括之前的我)把SYSMON当成一个“能读温度的寄存器”而已,实际上从硬件电路到软件读取,再到报警保护,它是一套完整系统。

这篇文章就把我从零到用起来的一整套流程整理一下。你如果正在做Zynq UltraScale+相关开发,无论你是想监控芯片结温、检查供电电压是否正常,还是想给板卡加上过温保护,这篇文章都适用。我会把SYSMON的硬件架构、Vivado里的配置方式、Linux下的读取方法,以及我实际踩过的几个坑一次讲清楚。内容偏工程实践,不是文档翻译,讲究的是拿来能用。

1. 为什么说SYSMON是Zynq UltraScale+里“自带体检报告”的硬核

1.1 从7系列XADC到UltraScale+ SYSMON:架构演进带来的能力变化

用过7系列FPGA的工程师对XADC应该不陌生,那是集成在芯片内部的模拟采样模块,可以读结温和几路供电电压。到了UltraScale+这一代,Xilinx把它升级成了SYSMON(System Monitor),全称变了,能力也变了。最直观的变化是内部ADC从7系列的12位变成了10位,但采样率维持在1MSPS级别,同时通道数量、报警机制、寄存器映射都做了重新设计。很多老工程师刚上手时拿7系列的习惯去套,结果发现寄存器地址对不上,就是这个原因。

在Zynq UltraScale+ MPSoC上,SYSMON不是一个单一模块,而是分为PS侧和PL侧两部分。PS侧叫PS-SYSMON,挂在APB总线上,由PS系统直接控制,哪怕PL侧没有加载任何逻辑,它也能正常工作。PL侧是Vivado里的一个IP核,通过AXI4-Lite接口或者DRP接口访问。两者都能读芯片结温,但监控的电源通道各有侧重。这个区分一定要先刻在脑子里,否则你会在“为什么Linux里能读温度但读不到VCCINT”这种问题上卡很久。

1.2 它到底能测哪些量:温度、供电、外部模拟通道

SYSMON能测的东西,分三类。

第一类是芯片结温。芯片内部有一个基于PN结的温敏传感器,直接连接到ADC。这个温度是芯片裸片上的实际温度,不是外壳温度,也不是环境温度。搞清楚这一点很重要,下文坑的部分我还会再提。

第二类是片上供电电压。PS侧可以监控VCC_PSINTFP、VCC_PSINTLP、VCC_PSAUX、VCC_PSDDR这些PS电源域;PL侧可以监控VCCINT、VCCAUX、VCCBRAM等PL电源域。这些通道都是芯片内部直接连好的,不需要你外接任何采样电路。

第三类是外部模拟输入。SYSMON有一组差分输入VP/VN和若干VAUX辅助通道,可以接外部传感器或分压网络,用来监控板卡上其他电压,比如12V输入、模拟前端电源、外部温度传感器等。这类通道需要你自己设计外围电路,也是很多硬件工程师容易翻车的地方。

1.3 数据通路:DRP寄存器、APB和AXI

SYSMON的所有配置和采样结果,最终都落到一组16位寄存器上。PL侧通常是通过AXI4-Lite接口访问这些寄存器;PS侧则是通过APB接口。这里有个很容易绕晕的概念:不管你是从AXI还是APB进去的,寄存器内部逻辑都叫DRP(Dynamic Reconfiguration Port)。所以你在Xilinx文档里看到的很多寄存器名,前缀都是“SYSMON_”或者跟DRP相关。

常用寄存器其实不多:0x00是当前温度值,0x04和0x08分别是温度最大值和最小值,后面跟着各路电压通道。每个结果寄存器都是16位,内部10位ADC的转换结果会左移6位对齐到这个16位寄存器里。换算公式也很固定:温度值(°C) = 寄存器值 / 65536 × 503.975 - 273.15,电压值(V) = 寄存器值 / 65536 × 参考电压。这些公式可以直接写进代码里,不需要像某些外置ADC芯片一样查一堆表。

2. 硬件电路设计:供电、参考源和分压网络的处理

2.1 模拟供电与参考源:决定测量精度的第一步

SYSMON说到底是一个模拟采样系统,模拟前端供电的质量直接影响读数稳定性。很多工程师在数字电路里习惯了“电容随便摆摆”的作风,到了SYSMON这里就要改一改。

首先是模拟供电引脚,不同封装的U+系列芯片上名字略有区别,PL侧一般是VCCADC_0这类名字,PS侧则集成在PS域的模拟供电里。这些引脚需要一个干净的低噪声电源,去耦电容不能省。我的习惯是每个模拟供电引脚放一颗0.1µF的陶瓷电容,靠近引脚放置,然后再在稍远处放一颗4.7µF左右的钽电容或者大容量MLCC,用来吸收低频扰动。

其次是参考源。SYSMON支持内部参考和外部参考两种模式,大多数应用直接用内部1.2V参考就够了,因为内部的精度和温漂已经做得很不错。只有当你要追求极高的绝对精度,或者需要用外部参考与其他模拟链路对齐时,才需要接外部参考源。接外部参考时要注意参考芯片的噪声,还有VREFP/VREFN引脚的去耦,这点文档里写得比较散,实际踩过坑的人才知道。

2.2 外部通道分压设计:我推荐怎么算

如果你想用SYSMON监控板卡上某一轨电压,比如12V输入,那就得用分压电阻把它降到SYSMON模拟输入能承受的范围。以内部参考1.2V为例,外部输入建议控制在0到1.2V之间。用12V输入的话,可以选11kΩ和1kΩ的电阻分压,分压后正好是1V,留了20%的余量。用5V输入,可以用4kΩ和1kΩ分压到1V。

这里有个细节很多人会忽略:分压电阻一定要用精度1%以上的,最好用同一批次温漂一致的电阻。因为分压比一旦漂了,你算出来的实际电压就跟着漂,而且这种误差是软件校准救不回来的。另外,分压之后最好加一个RC低通滤波,比如1kΩ串联电阻加10nF对地电容,截止频率大概16kHz,可以把开关电源的高频噪声滤掉不少。RC的位置要靠近FPGA引脚,而不是靠近电源端。

2.3 被忽略的PCB布板规则:采样点要选对

布板时有两个容易犯的错。

第一个是把模拟走线拉得太长,或者贴着开关电源的电感走。SYSMON的外部输入终究是模拟信号,哪怕你前面加了滤波,走线太长照样耦合噪声。我一般要求模拟走线控制在2厘米以内,实在拉不开就加宽间距,绝对不和PWM信号、时钟信号平行走线。

第二个错误是采样点选错。监控DDR4供电,有人直接从电源模块输出端飞线到分压电阻,结果发现SYSMON读出来的电压比万用表量到的低了30mV。原因是PCB走线本身有阻抗,大电流流过时从电源模块到负载之间会形成压降。正确做法是从FPGA的供电引脚附近取采样点,也就是所谓的开尔文连接。如果板子上有专门的电压检测点,优先用检测点。否则你测到的不是芯片真正吃到的电压,而是电源模块输出端的电压。

3. 快速跑通:Vivado里集成SYSMON IP并读第一个温度值

3.1 在Block Design里创建并配置SYSMON IP

打开Vivado,在IP Catalog里搜“sysmon”或者“System Monitor”,就能找到SYSMON IP。双击添加之后,配置界面里会让你选择启用哪些监控通道:Temperature、VCCINT、VCCAUX、VCCBRAM,还有外部的VAUX通道。这里不需要全选,按你的板子实际需求来就行。选太多通道会拉低单个通道的实际采样率,后面读数刷新变慢。

需要特别留意的选项是访问接口。SYSMON IP支持AXI4-Lite接口和DRP接口两种模式。在Block Design里和其他IP互联,最简单的是选AXI4-Lite,这样系统会自动映射出一段寄存器空间,你用AXI总线的读写命令就能访问温度和电压寄存器。如果只是想用一点点逻辑资源做轻量读取,DRP模式更节省资源,但你要自己写DRP时序,工程量大不少。

3.2 回忆一下连接:时钟、复位和模拟电源不能漏

配置完IP之后,Block Design里会自动出现SYSMON的实例。接下来要连三样东西:时钟、复位和模拟电源。

时钟很好理解,AXI总线需要时钟,同时SYSMON内部转换也需要一个参考时钟。你给进来的时钟频率会影响ADC的采样时序,一般按照IP配置界面给的频率范围来就行。复位信号多数情况下用Processor System Reset模块的慢速复位输出。

模拟电源的坑比较隐蔽。SYSMON IP有个模拟供电输入,比如VCCADC引脚,如果你在Block Design里没有把它连接到正确的电压源,仿真可能看不出来,但上板后读出来的数据全是0或者乱跳。我见过不少同事Vivado工程仿真一切正常,下载到板子上温度读出来是零,排查到最后就是模拟电源引脚悬空。Block Design里这个引脚通常要接到一个constant高电平来表示“供电正常”,具体处理方式按IP配置界面提示来,别嫌麻烦。

3.3 裸机读温度:寄存器操作其实不复杂

配置完IP,综合实现生成比特流,在Vitis里做裸机工程时,最快捷的方式是用Xilinx提供的XSysMon驱动。套路是固定的:先查配置,再初始化,然后直接调用读取函数就行。大致写法如下:

#include "xsysmon.h" #include "xparameters.h" XSysMon SysMonInst; int main(void) { XSysMon_Config *CfgPtr; u32 TempRaw; float TempC; CfgPtr = XSysMon_LookupConfig(XPAR_XSYSMON_0_DEVICE_ID); XSysMon_CfgInitialize(&SysMonInst, CfgPtr); XSysMon_Start(&SysMonInst); while (1) { TempRaw = XSysMon_GetTemperature(&SysMonInst); TempC = (TempRaw * 503.975f / 65536.0f) - 273.15f; printf("Temperature: %.2f °C\n", TempC); sleep(1); } return 0; }

如果你不想用驱动,直接读AXI寄存器也一样。SYSMON在地址偏移0x00处就是当前温度寄存器,等待转换完成标志位之后读取即可。我自己的经验是:先用驱动跑通功能,再用裸寄存器写生产代码,这样能少走很多弯路。裸寄存器方式读第一版代码时,记得确认转换完成位,否则会读到上一次的旧数据。

4. Linux下读取PS侧SYSMON:从设备树到sysfs的全链路

4.1 PS侧自带SYSMON:U-Boot阶段就能验证

前面说过,Zynq UltraScale+的PS侧自己带了一个SYSMON,不需要你动PL逻辑就能用。这意味着在你的Linux系统跑起来之前,U-Boot阶段就能读温度和电压,这功能用来做板级验证非常方便。

U-Boot里最简单的做法是用md命令直接读寄存器。PS-SYSMON的基地址在UG1085的地址映射表里有明确标注,通常是在0xFFA50000附近。你读返回的16位寄存器,再套温度换算公式,就算不写一行C代码也能在串口下验证芯片有没有正常启动、温度是否合理。如果U-Boot阶段读到的温度就是八九十度,那大概率是散热没做好,你烧写系统前就该处理了。

4.2 设备树配置和IIO框架

到了Linux内核里,SYSMON是通过IIO(Industrial I/O)子系统管理的。Xilinx官方内核里自带xilinx-sysmon驱动,设备树里要做的就是把PS-SYSMON这个节点声明出来。Xilinx BSP生成的设备树里通常已经有现成节点,结构大致是下面这样:

adc@ffa50000 { compatible = "xlnx,zynqmp-sysmon-1.0"; reg = <0x0 0xffa50000 0x0 0x1000>; /* 具体地址和中断号以你使用的BSP设备树为准 */ };

提醒一句,不同芯片、不同BSP版本,地址和reg大小可能有差异,直接抄网上的地址不如自己打开设备树搜“sysmon”关键字。驱动probe成功后,你会在/sys/bus/iio/devices/下面看到一个iio:deviceX节点。

4.3 用sysfs读温度电压的小脚本

IIO框架暴露出来的接口是一堆sysfs属性,不同内核版本略有差别。较新的Xilinx内核一般会给出in_temp0_raw、in_temp0_offset、in_temp0_scale这样的节点,以及对应的电压通道in_voltageN_raw和in_voltageN_scale。实际温度的计算方式是:

cd /sys/bus/iio/devices/iio:device0 raw=$(cat in_temp0_raw) scale=$(cat in_temp0_scale) offset=$(cat in_temp0_offset 2>/dev/null || echo 0) awk -v r="$raw" -v s="$scale" -v o="$offset" 'BEGIN { printf "%.2f\n", (r + o) * s / 1000 }'

如果你看到的是in_temp0_input,那说明驱动已经帮你换算成毫摄氏度,直接除以1000就是摄氏度。电压通道同样有scale和raw,计算方式类似。我建议你把读温度、读VCC_PSINTFP、读VCC_PSDDR这几个常用通道写成一个shell脚本,放到板子上,调试的时候一条命令就能看全板关键状态,效率高很多。

4.4 什么时候才需要PL侧的SYSMON

跑Linux的典型方案是直接用PS侧SYSMON,因为不占PL资源,也不需要额外驱动移植。但如果你要监控的是PL侧的VCCINT、VCCAUX这些PL电源域,或者你的应用里PL自定义逻辑需要实时读取某些外部模拟通道并做闭环控制,那就需要PL侧的SYSMON IP。这种情况下,PL侧读数可以通过AXI接口给PS的Linux读,也可以直接在PL逻辑里使用。两套SYSMON不冲突,可以同时用,只是注意别在同一个设备树里重复声明重叠的寄存器空间。

5. 从“看数据”到“做保护”:报警阈值、风扇联动与DDR4排查

5.1 温度电压报警阈值:把寄存器值算对

SYSMON不仅是被动测量,它还能主动报警。每个可测通道都有一组上限和下限阈值寄存器,采样结果超过阈值时,SYSMON会拉高报警信号。PL侧有ALARM引脚可以接到中断控制器,PS侧则直接进中断控制器。

设置阈值时要把物理量换算成寄存器值。以85°C温度阈值为例,换算公式是:

// 目标温度 85°C float target_temp_c = 85.0f; uint32_t threshold_reg = (uint32_t)((target_temp_c + 273.15f) * 65536.0f / 503.975f);

电压阈值也类似,比如VCC_PSDDR额定1.2V,你想监测它低于1.14V时报警,那阈值寄存器值就是1.14 / 参考电压 × 65536,多数通道用内部参考1.2V。注意不同通道的满量程参考可能不同,不要一个公式打天下。设计阈值时还要考虑芯片正常工作时电压纹波波动,别把下限设得太贴近额定值,否则负载一波动就误报警。

5.2 风扇控制与保护逻辑的工程实现

温度数据拿到手上,一个很自然的应用是风扇控制。很多板卡上风扇就是全速转,噪声大而且浪费;用SYSMON的结温做PWM调速,效果立竿见影。我不建议在驱动里做太复杂的PID,一个滞回控制器足够:

static int pwm_duty_for_temp(float temp) { if (temp > 80.0f) return 100; else if (temp > 65.0f) return 70; else if (temp > 50.0f) return 40; else return 25; }

滞回的意思是升温到阈值和降温到阈值之间留一个差值,比如65°C启动中速、55°C才降回低速,避免风扇转速在两个阈值附近反复横跳。如果你用的是PL侧SYSMON,报警信号可以直接硬件触发风扇全速,即使PS侧Linux崩溃了保护依然生效。这种协同设计是工业设备值得投入的地方。

5.3 用SYSMON辅助DDR4调试:一个真实场景

回到开头提到的DDR4训练问题。Zynq UltraScale+的PS内建DDR控制器,DDR4接口对电源质量非常敏感,VCC_PSDDR和VCC_PSINTFP这些电压在负载跳变时跌落,轻则训练参数偏差,重则读写报错。

我用SYSMON做的事情很简单:在DDR4压力测试的同时,每秒记录一次VCC_PSDDR和芯片温度,存到日志文件里。压力测试跑半小时后回看数据,发现芯片温度从45°C上升到78°C,同时VCC_PSDDR的读数在某个负载阶段会掉到临界值。没有这个趋势性记录,我会一直怀疑是DDR控制器配置参数的问题,而不是电源。

这里也提醒一句:SYSMON的采样率虽然ADC本身有1MSPS,但多通道轮询分摊下来,每个通道实际刷新率不算高,加上Linux读取和日志写入的开销,你看到的曲线适合判断趋势和秒级波动,不适合观察几十微秒的瞬态跌落。真要看纹波和瞬态,示波器还是不可替代的。SYSMON的价值在于能长时间记录、能自动化报警,这是示波器做不到的。

6. 实战中容易误判的坑和我的排查思路

6.1 温度读数偏高偏低:先确认你测的是“结温”

群里经常有人拿着SYSMON读到的温度去跟红外热像仪对比,发现差个二十度,就怀疑芯片坏了。实际上SYSMON读的是结温,也就是芯片裸片内部的温度,红外热像仪测的是封装外壳表面温度,两者之间隔着封装材料和散热界面,有10到20°C的差值完全正常。我做过的板卡上,结温85°C时外壳可能只有65°C左右。

另一种情况是温度读数漂移。如果你发现待机时温度读数来回跳好几度,首先查模拟供电的电源纹波,其次查内部参考电压引脚上的去耦电容。排除电源问题后,再考虑芯片本身的热阻和散热条件。判断传感器是不是线性正常,可以用热风枪慢慢加热芯片,看读数是否跟随上升;或者对比制冷状态下的读数变化。

6.2 PL侧SYSMON IP读不出数据:按顺序排查

这个问题我在帮朋友调板时遇到过不止一次。现象很一致:裸机里读温度寄存器,永远返回0。排查顺序我建议这样来:

第一查模拟电源引脚有没有接上,这在Block Design阶段最容易漏。第二查转换有没有使能,SYSMON IP默认不会所有通道一起转,需要你在配置界面或者代码里打开转换使能位。第三查时钟,AXI时钟如果没给或者频率不对,寄存器访问都不会有正确响应。第四查ILA抓取寄存器总线的实际返回值,确认是寄存器全0还是读不到地址。

我自己踩得最深的一次,是把模拟电源引脚连到了普通IO的高电平上,结果VCCO电平跟模拟供电要求不匹配,读数全错。后来检查原理图才发现这个引脚需要独立的模拟电源,不是逻辑高电平能代替的。

6.3 设备树没生效:内核配置和节点名字别搞错

Linux下设备树配得对但sysfs里没设备,先别急着怀疑设备树格式,先看内核有没有把XILINX_SYSMON这个驱动编进去。不少人用的是裁剪过的内核,驱动没编译,设备树写了也白搭。

驱动在的情况下,用dmesg | grep -i sysmon看驱动加载时有没有报错,再用ls /sys/bus/iio/devices/看是不是已经注册了设备。如果设备树里寄存器地址写错,驱动会probe失败;如果地址对但时钟没开,读数可能一直是0。这里有一个快速验证方法:直接在U-Boot阶段用md命令读同一基地址的寄存器,看有没有非零值。U-Boot能读出来,说明硬件通路正常,问题出在Linux侧配置;U-Boot也读不出,那基本就是硬件电路或者启动时钟配置的问题。

6.4 烧写实现后读数正常但跟万用表有偏差:先分辨误差来源

有一个很常见的现象:SYSMON读VCC_PSDDR是1.18V,万用表量板子上测试点是1.20V,差值20mV。这不是SYSMON坏了,很可能是采样点不同导致的真实压降。如果确实需要精确对齐,可以在应用层做一次线性校准,用高精度万用表量出基准电压,然后把偏移量写进校准参数。Xilinx的SYSMON内部也预留了校准寄存器,不过我自己更倾向于在软件里做校准,因为这样不影响其他通道,而且校准参数可以跟着配置文件走。

另外,烧写完FPGA之后片上逻辑开始翻转,芯片功耗上升,温度读数比空载时高十几度是正常现象。不要把这种变化当作异常,反而应该用它来判断你的逻辑功耗是否在预期范围内。

最后补一句比较实际的体会:SYSMON是调试工具,但不要把它当示波器用,它能帮你发现趋势性异常、做自动保护、记录长时间数据,但抓毛刺、看纹波必须请示波器。两者配合,才是完整的电源和温度排查思路。每次调试DDR4或者电源问题,我都习惯先把SYSMON这条链路打通,它不花多少资源,但关键时候真能救命。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询