ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Zynq-7020降频实战:从FSBL到Linux的功耗与散热优化

Zynq-7020降频实战:从FSBL到Linux的功耗与散热优化 Zynq-7020双核ARM Cortex-A9默认跑在667MHz在绝大多数评估板上这是出厂设定但落到实际产品里这个频率往往不是最优解。我上一个项目用的是Zynq-7020做数据采集与边缘预处理整机装在无风扇的密封铝壳里板卡上还有FPGA和模拟前端散热预算抠得相当紧。系统跑起来后A9核满载几分钟外壳温度直接逼近设计上限而且模拟采样通道的温漂也开始变得不可接受。于是我把A9核从667MHz一路压到了400MHz换来了将近三分之一的芯片功耗下降同时保留FPGA逻辑侧的并行处理能力整体功能一点没打折。这篇文章就把整个降频过程完整记录下来覆盖FSBL固件层修改、U-Boot二次确认、Linux设备树与cpufreq限频验证以及期间踩过的坑和排查思路给同样被功耗和散热卡住的朋友一个可落地的参考。1. 降频前必须搞懂的时钟结构否则后面全白做一开始我也以为降频就是把芯片主频改低这么简单真正动手才发现Zynq-7020的PS时钟树比想象中复杂一些而且各个时钟之间存在倍数关系牵一发动全身。先花点时间把这块吃透后面改起来才能心里有数。1.1 Zynq-7020的三路PLL和CPU时钟路径Zynq-7020的PS端有三个PLL分别是ARM PLL、DDR PLL和IO PLL。其中ARM PLL专门给CPU域提供时钟DDR PLL管DDR控制器和内存时序IO PLL负责各种外设和静态时钟。三者并不是完全独立的比如经常有人会问“我降了CPU频率DDR要不要跟着改”答案是看总线配置但在最常见的FSBL默认配置下你只动ARM PLL这条链路DDR频率不会跟着变这个后面会展开。CPU时钟不是直接从PLL输出拿的中间还经过CPU_6x2x和CPU_3x2x两条路径。名字里的6x2x你可以理解成ARM PLL先除以6再乘以某个系数送到CPU实际效果相当于ARM PLL除以2这也是为什么默认配置下ARM PLL跑1333MHzA9核得到667MHz。CPU_3x2x路径则是ARM PLL除以3后再做处理用得相对少。在FSBL生成的ps7_init文件里通常会看到针对这两条路径的寄存器初始化代码不需要手动改驱动。搞清楚这个结构后的第一反应特别重要降频时优先动的是ARM PLL的频率还是CPU那两条路径的分频系数两种方案都能达到降频目的但影响范围不一样。改ARM PLL的好处是整条CPU时钟域同步缩放稳定性最好改分频路径则会更灵活比如可以单独在某条路径上做调整。对大多数场景我建议优先改ARM PLL因为后续验证简单结论也直观。1.2 一个具体案例算给你看:667MHz到500MHz再到400MHz我用最标准的33.333MHz PS_CLK来算一笔账。默认FSBL里面ARM PLL的倍频数是40所以ARM PLL输出就是约1333MHz再通过CPU_6x2x路径除以2得到CPU主频667MHz。如果想降到500MHz最简单的办法是把ARM PLL从1333MHz压到1000MHzCPU频率自然就变成500MHz。1000除以33.333约等于30也就是说把倍频数从40改成30就行了。如果进一步压到400MHz那就需要把ARM PLL压到800MHz倍频数约等于24CPU频率就是400MHz。这两个目标频率都是整数关系寄存器里配置不产生小数误差适合做对比测试。但需要注意降低ARM PLL后CPU_6x2x路径下对应的中间时钟也会按比例降低比如L2缓存、SCU等都在这个域里整体性能会同步缩水。还有一个容易忽略的问题是BootROM阶段的启动速度。BootROM本身会用较低频率初始化FSBL起来之后才会把PLL配置到目标值。你把FSBL里的频率改低之后FSBL阶段本身耗时可能会略有变化但影响很小。真正要注意的是不要低于某些外设的时钟要求尤其是DDR控制器和UART不过这两块分别由DDR PLL和IO PLL提供只要你不去动它们问题不大。2. FSBL固件层降频实操这才是最根源的改动FSBL是Zynq上电后第一个运行在A9核上的裸机代码它的作用之一就是把PS端的时钟初始化到目标值。在FSBL里改时钟相当于从启动源头就把频率定死后面无论U-Boot还是Linux只要不自行重配时钟都会沿用这个频率。反过来如果只在Linux设备树里改FSBL阶段还是会先跳到667MHz中间这段高速运行的时间虽然短但对某些对功耗敏感的场景来说依然不可接受所以从FSBL改起最彻底。2.1 在Vivado/Vitis里找到并读懂ps7_init文件我在Vivado里新建了一个最小的硬件工程把Zynq-7020的PS配置导出到SDK/Vitis然后创建一个FSBL模板工程。生成完之后关键的源码在名叫ps7_init_gpl.c的文件里如果没有开GPL也可能叫ps7_init.c。这个文件其实就是一堆寄存器初始化表其中PLL相关的初始化函数名为ps7_pll_init_data_3_0这里面存放了ARM PLL、DDR PLL和IO PLL三组寄存器地址和写入值。实际操作时我习惯先用文本编辑器直接搜ARMPLL或者0xF8000100这个地址。0xF8000100就是ARMPLL_CTRL寄存器的地址。在这个数组里你会发现类似下面这样的结构一列是地址一列是寄存器值每一组代表一次写操作。最需要注意的是它的寄存器值通常不是我们手工算好的最终值而可能带有一些保留位或使能位直接整个替换容易翻车。正确做法是只修改位域把其他位原样保留或者严格按照Xilinx文档里的位定义重新计算完整值。我项目里的ps7_pll_init_data_3_0数组第一行大概长这样const unsigned long ps7_pll_init_data_3_0[] { {0xF8000100, 0x00224008}, /* ARMPLL: 1333.333 MHz */ ... };注意这个0x00224008并不是40左移20位那么简单其中还包含了DIVCFG、锁定使能等选项。所以我建议在修改前先把位数拆开确认每一位的含义而不是盲目套公式。2.2 拆解ARMPLL_CTRL寄存器并计算新配置值ARMPLL_CTRL寄存器的位定义里位[25:20]是PLL_FDIV也就是倍频数位[13:12]是DIVCFG用于配置PLL的后分频典型情况下可以是除以1、2或3其他位还有锁定标志、使能位等。我用的默认配置里PLL_FDIV是40DIVCFG所对应的后分频让PLL输出为1333MHz然后CPU路径再决定最终的A9主频。为了降到500MHz我要把倍频数改成30。原寄存器值0x00224008的高位部分对应倍频数40如果直接按位替换新的寄存器值应该是把40换成30也就是把位[25:20]从101000改成011110其他位保持不变。我建议你在Excel或者计算器里先把原值写成二进制然后把对应bit位置换这样最稳妥。替换后得到的值大约是0x001A4008具体以你实际读出的寄存器字段为准。如果进一步降到400MHz倍频数取24那么位[25:20]就是011000新值大约是0x00184008。改完之后重新编译FSBL工程生成BOOT.bin烧录到QSPI Flash或者SD卡里启动。第一次启动后先在U-Boot提示符下用md命令看一下ARMPLL_CTRL寄存器当前值或者直接在Linux下用devmem读取确认频率确实改过来了。2.3 重新编译FSBL并验证启动链路FSBL工程重新编译后生成fsbl.elf这个文件并不会自动覆盖你板上现有的BOOT.bin需要手动替换。常规做法是在Vitis里重新生成BOOT.bin把fsbl.elf、设计文件的bitstream、U-Boot的u-boot.elf一起打包进去顺序不能乱一般是FSBL在最前bitstream跟着最后才是U-Boot。打包顺序错了可能导致启动到一半挂掉。我第一次只改了FSBL没有检查后续启动阶段结果发现开机之后Linux里面显示CPU频率还是667MHz。原因就是U-Boot在启动过程中会依据自己的时钟配置把PLL重新写了一遍FSBL的修改被覆盖了。这个问题很典型所以千万不要改完FSBL就以为万事大吉必须继续排查U-Boot这一层。3. 排查U-Boot二次提速别让FSBL白改U-Boot这一层是Zynq降频绕不开的坎。Xilinx官方U-Boot在启动时会读取设备树中的时钟节点调用相应的时钟驱动重新初始化PS端的时钟。如果设备树里没写任何降频信息U-Boot会沿用其默认配置把PLL恢复到667MHz附近你的FSBL改动等于被清零。3.1 确认U-Boot版本与时钟驱动行为我用的U-Boot版本是从Xilinx官方仓库拉的里面默认启用了CONFIG_CLK和Zynq时钟驱动。启动日志里能看到类似“clock: ... 667000000”之类的信息这就是U-Boot在启动过程中对时钟做了一次重新配置。解决办法有两个方向一种是把U-Boot设备树里的CPU频率节点改成和FSBL一致另一种是直接在U-Boot配置里禁用时钟驱动让它沿用FSBL设置。我个人更推荐前者因为禁用时钟驱动可能会有副作用比如某些外设的时钟时序依赖U-Boot的初始化贸然关掉容易带来新问题。改设备树的话需要找到arch/arm/dts/zynq-7000.dtsi或者你自己的板级dts文件查看cpu节点的clock-frequency属性。我把它改成了500MHz对应的值这样U-Boot起来时会按照预设的时钟树生成频率不再覆盖FSBL的降频结果。3.2 在U-Boot里直接验证寄存器值如果改完设备树后还是怀疑U-Boot在搞鬼最好在U-Boot命令行下直接读取寄存器确认。U-Boot的md命令可以读物理地址比如md.l 0xF8000100 1。把读到的值和FSBL里写入的目标值比对一下就能判断时刻是哪个阶段把时钟改回去了。我当时的排查过程就是这样先读寄存器发现是667MHz对应的值立刻锁定是U-Boot的问题。除了设备树外U-Boot也有可能通过zynq_slcr或clock驱动的代码路径去写PLL这时候需要查U-Boot源码里对应的时钟配置函数把初始化的目标频率改掉或者找到对应的kconfig屏蔽掉。代码层面的排查相对枯燥但胜在彻底能够保证从U-Boot起到Linux起来之前频率都不变。实际开发中如果项目时间紧也可以在U-Boot环境变量里加一个自定义启动脚本启动时用mw.l命令强制再写一次PLL寄存器相当于在bootcmd的最前面做个兜底。3.3 记住一个原则:从FSBL到U-Boot再到Linux频率目标必须一致这个原则说起来简单但实际操作中很容易出现“FSBL改了U-Boot没改Linux又显示原频率”的情况。比较稳妥的做法是一改到底FSBL的ps7_init改一遍U-Boot设备树改一遍Linux设备树如果定义了CPU频率也要同步。只有三层目标一致开机到Linux整个过程的功耗曲线才是平滑的。否则看似降频成功实际上在启动早期仍然存在短暂的高速运行段对于工业现场的功耗墙判断来说会有偏差。4. Linux侧限频用cpufreq实现动态变频和锁定上限固件层面把频率定死后Linux侧还可以做得更细致。Zynq的A9双核跑Linux时默认不一定启用cpufreq驱动但如果你需要运行时动态调整频率或者希望CPU在负载低时自动降频那就得在内核和设备树里同时加上cpufreq支持。我实测下来这种方式最灵活也最适合做功耗调优因为并不是每个时刻都需要400MHz有些任务跑满667MHz反而能更快结束整体功耗更低。4.1 设备树里配置CPUFreq操作点Zynq-7020的设备树中CPU节点的基本结构是这样cpus { #address-cells 1; #size-cells 0; cpu0 { compatible arm,cortex-a9; device_type cpu; reg 0; clocks clkc 3; clock-latency 500000; operating-points 667000 1000000 500000 950000 400000 900000 ; }; };其中operating-points每一行的第一列是频率单位kHz第二列是电压单位uV。Zynq-7020的核心电压通常在1.0V附近具体以你的电源设计为准。由于我们已经在FSBL和U-Boot里把最高频率定在了500MHz这里我保留了667MHz的OPP但实际不会用到也可以直接删掉667MHz那一项让内核只认为最高就是500MHz。不过保留的话切换自由度更大只要主板散热跟得上运行时也可以临时拉回667MHz应急。需要注意的是Zynq的cpufreq驱动在较新内核里推荐使用cpufreq-dt你需要在内核配置里打开CONFIG_CPUFREQ_DT和对应的调节器比如CONFIG_CPU_FREQ_GOV_USERSPACE和CONFIG_CPU_FREQ_GOV_ONDEMAND。我的经验是先把所有常见的governor都编译进去后续用命令行切换最方便。4.2 内核与用户空间的限频命令内核启动之后cpufreq节点会出现在/sys/devices/system/cpu/cpu0/cpufreq/下面。常用的读取和修改方式如下cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies echo 400000 /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq echo userspace /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor echo 400000 /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed如果系统里装了cpupower工具用起来会更方便cpupower frequency-info cpupower frequency-set -g userspace -f 400MHz我在实测中发现Zynq-7020的cpufreq在切换频率时因为PLL重新锁定需要时间个别线程可能出现短暂停顿所以如果是实时性要求较高的场景建议用userspace或performancegovernor并固定频率而不是让内核频繁动态切换。实时任务和动态调频这两个需求本身就有点矛盾需要根据项目取舍。4.3 没启用cpufreq时怎么临时验证如果内核没编译cpufreq支持又不想重新编译内核我还有个土办法直接通过devmem写硬件寄存器来切换ARM PLL倍频数实现临时降频。这个操作适合做快速验证比如我先在Linux下执行devmem 0xF8000100 32 0x001A4008把倍频数改成30CPU瞬间就切到500MHz。注意devmem写寄存器有一定风险不建议在产品代码里用但作为验证手段非常高效。我在做对比测试时就是靠这个方法在不同频率之间快速切换不用反复烧写BOOT.bin。5. 降频后的完整验证流程数据说话才有说服力把频率降下来只是第一步真正重要的是验证降频后系统的稳定性、功耗改善和性能损失。做这一轮验证时最好准备一台能实时监测整机功耗的电源比如支持电脑联机读数的可编程电源或者至少也要有几块高精度万用表。同时监控核心温度和外壳温度有条件的话用热像仪看板卡表面温度分布这些数据对于判断降频幅度是否合理非常关键。5.1 查看当前频率是否真正生效验证第一步是确认系统当前运行频率。在Linux下可以用下面几种方法交叉验证cat /proc/cpuinfo输出里可以看到BogoMIPS该值在ARM上并不严格等于CPU频率但能提供参考。更直接的方式是cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq如果cpufreq不可用就回到devmem读寄存器devmem 0xF8000100 32读出来的寄存器值按位域解析一下就能知道ARM PLL当前的倍频数。我习惯用这三个方法同时确认因为曾经遇到过cpufreq显示了预期频率但寄存器实际不是那个值的诡异情况交叉验证能避免这种错觉。5.2 性能对比测试与功耗实测记录我在默认667MHz、降频500MHz、降频400MHz三档各跑了一轮性能测试和功耗记录。性能测试工具用的是标准的dhrystone和tinymembench前者测CPU整数性能后者测内存带宽和延迟。功耗记录则是让板卡运行同样的业务负载比如持续不停地进行双核浮点运算加FPGA侧的数据搬运观察整机功耗稳定值。从实测结果看CPU整数性能基本和频率成正比667降到400大约损失40%的CPU理论算力这在预期内。但内存带宽的下降比例并不完全与CPU频率同步因为DDR频率独立由DDR PLL控制CPU频率降低之后访存延迟反而略有下降整体吞吐下降幅度在20%到30%之间。这说明如果业务对内存带宽敏感降CPU频率的损失没那么大而对纯计算密集的任务就要谨慎评估。5.3 温度与稳定性的长期观察降频后整机功耗在我这个项目里下降了大约30%外壳温度降低明显。我单独做了一轮72小时连续运行测试在密封铝壳内、室温30度的环境下400MHz档位的核心温度稳定在60度左右满载跑也不超过70度而原来667MHz满载时核心温度会一路冲到90度已经接近官方规格极限。这个结果坚定了我用400MHz作为产品档位的决定。稳定性测试方面除了经典的压力测试之外我额外加了针对FPGA与PS交互的压力场景让PL侧不断发起DMA搬运同时CPU持续跑网络服务连续跑了几天没有发现死机、挂起或者数据错误。这里有个经验Zynq的A9核降频后最常出现问题的地方反而不是CPU本身而是DMA和中断控制器所以验证的时候务必带上高负载的DMA场景。6. 常见坑与排查技巧都是亲手踩过的教训降频过程中我踩了不少坑有些问题在网上查不到直接答案只能靠读寄存器、看源码慢慢定位。这里整理几个高频问题希望能帮你少走弯路。6.1 改完FSBL启动直接挂死这是最常见的问题通常原因是PLL配置值里有保留位或者锁定位被破坏了。Xilinx的PLL寄存器不是简单的“倍频数左移20位”写错任何一个保留位都可能导致PLL无法锁定启动卡死在FSBL阶段。如果遇到这种情况先回读UART启动日志看卡在哪个阶段再用JTAG连接Vitis查看寄存器当前值。我一般会在修改前把原始ps7_init_gpl.c的完整数组备份一份计算软件也要能解析原来的十六进制数用位域编辑工具确认每一位的含义避免手工拼接出错。6.2 Linux显示频率还是667MHz这种问题十有八九是U-Boot覆盖了FSBL的配置。排查方法我已经在前面说过核心是在U-Boot命令行下读ARMPLL_CTRL寄存器确认U-Boot起来时频率已经是目标值。如果不是那就去U-Boot设备树和源码里继续追。还有一种情况是Linux设备树里显式定义了clock-frequencyLinux启动时会按这个值去设置也会覆盖掉之前的配置所以Linux那边的内核DTS同样要排查。6.3 降频后DMA传输出现偶发错误这个问题比较隐蔽我当时排查了很久。A9核降频后如果PS端的总线时钟频率和PL侧的FCLK之间比例变化DMA跨时钟域传输时可能出现建立时间不足的问题表现为偶发的数据错位或者丢数。解决方向有两个一是检查PS到PL的接口时钟配置是否满足Xilinx文档里的频率比例要求二是在FPGA逻辑里增加跨时钟域的同步FIFO深度或者降低PL侧FCLK频率。我最后把FCLK从150MHz降到了100MHz问题彻底消失代价是PL侧逻辑处理吞吐略微下降但换来了系统稳定性。6.4 cpufreq切换频率后系统突然卡顿如果启用了cpufreq切换频率时出现短暂卡顿通常是时钟切换过程里中断响应被暂时延迟导致的。我的建议是对实时性敏感的任务直接把governor设成performance固定在一个频率不要用ondemand。如果必须动态调频那就把频率变化范围缩小比如只在400MHz和500MHz之间切换避免PLL锁定过程中出现太长的空窗期。6.5 快速检查表我最后把排查过程中常用的命令和寄存器整理成一张速查表方便现场调试检查项命令或寄存器预期结果FSBL是否写入目标频率UART日志 JTAG查看ps7_pll_init_data_3_0写入值与目标一致U-Boot是否覆盖频率U-Boot下运行: md.l 0xF8000100 1读回目标寄存器值Linux当前频率cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq目标频率Linux实际硬件频率devmem 0xF8000100 32解析后等于目标倍频DDR是否受影响devmem 0xF8000110 32保持默认DDR PLL配置DMA稳定性高负载DMA压力测试72小时无丢数、无错位这张表基本覆盖了从启动到运行再到压力的全链路检查点每次调完频率照着跑一遍能省掉大量现场排查时间。关于Zynq-7020降频我自己最大的体会是频率不是越高越好而是越贴合系统需求越好。这个降频思路不仅仅适用于发热严重的设备对于电池供电的便携设备、对电磁干扰敏感的采集系统甚至只是为了降低长期运行故障率都值得一试。而且降频并不等于性能妥协只要把FPGA侧和PS侧的分工重新调整很多场景下整体的系统处理能力反而更均衡。希望这篇实录能帮到你也欢迎在实际调频过程中回来交流你们遇到的新坑。
返回列表