
做Zynq7020开发有些年头的朋友估计都经历过这种场景板子跑起来没多久散热片烫到不敢摸整机外壳明显发热一到夏天更是心虚。更麻烦的是双核ARM Cortex-A9跑满标称频率时叠加PL里的逻辑功耗整颗芯片的发热非常集中。于是很多项目在Zynq7020开发中会认真考虑一个看似“退步”的操作——降频。我在自己的板子上完整做过一轮Cortex-A9降频从时钟原理、参数计算到温度对比测试折腾了几天踩了不少坑。这篇博文不写空话直接把你动手前需要知道的、动手时会遇到的全部捋一遍。降频并不是性能妥协重点在于你的应用到底吃不吃A9的算力。很多设备里的A9只负责协议栈、状态管理、人机交互真正的重计算早就放到PL侧了这种情况下把666MHz降到400MHz甚至300MHz对端到端体验的影响很小但温度、可靠性的收益却立竿见影。无论你是刚接触Zynq7020的新手还是已经在产品上被高温折磨过的工程师这篇指南都能给你一条可落地的路径。1. 为什么给Cortex-A9降频是Zynq7020开发者绕不开的课题1.1 你手上这块Zynq7020的A9到底能跑多快先对齐一个基本认知。Zynq-7020的ARM Cortex-A9是双核带统一L2缓存的MPCore架构标称最高主频和芯片速度等级绑定。市面上常见的核心板大多按-1速度等级设计Cortex-A9最高跑666.667MHz部分-2、-3速度等级的芯片可以到766MHz甚至更高。注意这里说的是PS端的ARM主频和PL逻辑能跑多快完全是两码事很多人一开始会把这两者混在一起。默认情况下FSBLFirst Stage Boot Loader在上电初始化阶段就会把ARM PLL配置好让CPU主频跑到该速度等级允许的最高值。也就是说你什么都不改它默认就在最高频运行。但问题也出在这里——你的应用真的需要这么高的主频吗实际项目中很多串口交互、状态采集、逻辑控制类应用连200MHz都用不满跑视觉算法或以太网协议栈的瓶颈也往往在DDR带宽和PL协处理上A9从666MHz降到400MHz对端到端吞吐的影响远没有想象中大。这一点后面有测试数据支撑。1.2 高温是从哪里来的28nm工艺和封装散热的现实Zynq-7020采用28nm HPL工艺这个工艺节点当年以低功耗见长但放到今天的应用场景看双核A9跑满666MHz时的动态功耗依然不可忽视。SoC封装上PS侧的大头是A9核心、片内互联和DDR控制器PL侧则完全由你烧录的逻辑阵列决定。PL里塞一个高利用率的中型图像处理管线再叠加A9双核满载整颗芯片的瞬间功耗轻松到三四瓦以上。在紧凑的核心板布局里发热源非常集中。更要命的是实际产品的外壳条件。开发板敞着跑没事量产设备常是密封金属壳或者塑料壳没有主动风道热量全靠PCB铜皮和少量散热片导出。我曾经见过一台设备在45°C环境温度下满载运行A9内部温度直接冲到105°C以上接近典型结温上限。这种场景下降频不是优化项而是保命项。实务中通过合理降频把最高结温控制在85°C以内对延长器件寿命、降低失效率有非常直接的作用。1.3 三条路选哪条降频vs降压vs散热改进遇到高温工程师第一反应通常是加散热片、加风扇这当然对但并不是所有场合都允许加主动风扇静音、功耗、长期可靠性都是问题。还有一条思路是降压也就是DVFS的核心思想但Zynq-7020的PMU对CPU电压控制能力非常有限它不像现代SoC那样具有细粒度的调压能力在Zynq上做降压手段受限操作不当很容易导致A9运行不稳定。相比之下降频是最简单、可控、风险最低的方案改一个参数温度降一截性能损失可以量化评估。再配合合理的散热设计往往能取得比单纯堆散热更好的效果。后面的内容主要讲降频的具体操作但散热基础不能丢——先保证有散热片和良好通风再来谈降频。2. 动刀之前先把Zynq7020的时钟架构搞清楚降频的第一步是搞清楚频率从哪来。Zynq-7020的PS部分有一套独立的时钟系统三个PLL各管一摊ARM PLL专供A9核心和相关互联DDR PLL管DDR控制器和DDR PHYIO PLL管各种外设以及PL的FCLK。三套PLL相互独立可以分别配置这也是为什么我们可以单独动ARM PLL而不用太担心DDR和外设的原因但注意有几个细节必须额外处理后面避坑部分会专门讲。2.1 三个PLL的分工和典型频率配置以最常见的33.333MHz外部PS_CLK为例FSBL默认配置下大致是这样一组关系ARM PLL输入33.333MHz倍频后VCO工作在一个较高频率再经后分频输出给CPU路径最终经过多级分频得到CPU主频666.667MHzDDR PLL负责DDR3/DDR3L颗粒所需时钟典型输出约533MHz、800MHz或1066MHz取决于DDR颗粒规格和PCB走线IO PLL给UART、SPI、I2C、SDIO、GEM以太网以及PL的FCLK_CLK0~3提供时钟源。这里需要特别理解的是降低A9主频理论上只要动ARM PLL这一条路就行DDR PLL和IO PLL不动DDR和外设的绝对频率就不会变。听起来很简单但ARM PLL的输出并不是一条直线到CPU中间有很多分频节点这些节点之间存在约束关系乱改一样会出问题。2.2 ARM PLL到CPU主频的完整计算链路在Zynq-7020中ARM PLL的配置可以用一个公式表达F_armpll_out F_ps_clk / D * M / O其中D是预分频系数M是反馈倍频系数O是后分频系数。这个输出频率会进入一组系统分频网络产生CPU_6x4x、CPU_3x2x、CPU_2x、CPU_1x四类时钟CPU_6x4x最终喂给Cortex-A9核心的时钟也就是我们常说的主频CPU_3x2x供给A9私有外设和L2相关逻辑CPU_2x供给芯片内互联包括ACP、APB桥等CPU_1x供给PS内低速外设和部分互联逻辑。默认比例下这四路输出频率大约是6:3:2:1的关系。也就是说ARM PLL输出666MHz时CPU_6x4x666MHz、CPU_3x2x333MHz、CPU_2x222MHz、CPU_1x111MHz。想降主频最稳妥的方式是整体降低ARM PLL输出并保持这四个分频之间的比例关系确保互联和外设不会因为时钟比例失衡而出现读写超时。如果只把CPU_6x4x这一个分频器调大而其他不动可能造成互联时钟相对核心时钟偏高或比例超出芯片设计约束在硬实时场合会有隐蔽的时序风险。我自己的建议是要么整体等比降要么用Vivado的时钟树自动重算功能。2.3 降频影响面到底有多大对PS端降频直接影响A9核心的计算吞吐以及经由ACP、互联访问DDR和PL时的带宽上限变化。如果应用本身对单核或双核算力要求不高通常无感但如果要做网络转发、实时控制回环必须用实验数据来评估。对PL端降频不直接影响PL逻辑的工作时钟那些时钟来自FCLK、IO PLL或者你自己的MMCM/PLL所以FPGA侧逻辑可以照常跑。唯一需要注意的是如果PL里有逻辑挂在ACP或HP口上和PS进行高吞吐DMA交互时互联带宽会随降频有所下降极端情况下可能出现DMA跑不满的情况。对外设UART、SPI、I2C、SDIO这些外设如果其时钟源取自CPU_1x或CPU_3x2x路径那么ARM PLL降低后它们的输入时钟也会跟着降低。此时必须检查对应的外设分频器否则会出现串口波特率漂移、SPI时序不对等诡异问题。这是降频过程中最容易踩的坑没有之一。3. 降频实操三种方式对比与完整配置实际操作中降频方式大致分三类Vivado图形化配置、直接改FSBL寄存器、Linux运行时调整。我自己都试过先说结论如果你还在用Vivado做硬件工程那Vivado图形化配置是最省心、最不会出错的如果你的产品已经批量出货不希望重新出比特流那改FSBL是更轻量的办法Linux运行时调整在Zynq上很不推荐除非你只是想快速做一轮实验对比。3.1 方式一Vivado图形化配置适合绝大多数人在Vivado中打开Zynq PS IP核进入Clock Configuration页面找到PS Clock部分的CPU Clock把默认的666.666667改成一个你需要的值。Vivado会自动根据你填的CPU频率重新推导PLL参数和所有分频系数再配合Output Power Configuration页面里的相关选项基本上改一次就能得到完整且自洽的时钟树。改完之后的操作链是Generate Output Products重新生成bitstream并导出硬件然后在Vitis/SDK中更新FSBL和Boot Image。整个过程除了等待时间基本上没有手写代码的环节PLL锁定状态、时钟比例这些都由工具保证。对于不熟悉底层寄存器的新手这条路最值得推荐。注意事项修改完CPU主频后记得检查一下调试串口的分频。Vivado重新生成后通常会自动处理但如果你在硬件工程里对外设分频做过手动定制可能会覆盖一定要核对一遍波特率是否仍然准确。3.2 方式二直接修改FSBL寄存器适合需要轻量改动的场景如果你的Vivado工程暂时不想动或者已经生产了一批镜像想通过boot脚本切换不同频率那可以直接在FSBL阶段改寄存器。Zynq-7020中相关的寄存器基址和功能是固定的修改步骤大致如下先把SLCRSystem Level Control Registers解锁Zynq-7020对SLCR有保护机制需要向0xF8000008写入解锁魔数0xDF0D。然后操作ARM PLL控制寄存器ARM_PLL_CTRL地址0xF8000100其中M倍频系数、D预分频、O后分频都在这个寄存器的不同位域中APER_CLK_CTRL地址0xF800012C用来配置CPU_6x4x、CPU_3x2x等系统分频比PLL_STATUS地址0xF800010C轮询PLL锁定状态。用一段伪代码描述这个流程#define SLCR_UNLOCK_ADDR 0xF8000008 #define SLCR_UNLOCK_MAGIC 0xDF0D #define ARM_PLL_CTRL_ADDR 0xF8000100 #define APER_CLK_CTRL_ADDR 0xF800012C #define PLL_STATUS_ADDR 0xF800010C // 1. 解锁SLCR *(volatile uint32_t *)SLCR_UNLOCK_ADDR SLCR_UNLOCK_MAGIC; // 2. 读取当前ARM PLL配置 uint32_t arm_pll *(volatile uint32_t *)ARM_PLL_CTRL_ADDR; // 3. 修改O后分频例如从/2改为/3使输出从666MHz降到444MHz arm_pll (arm_pll ~0x70) | (0x3 4); *(volatile uint32_t *)ARM_PLL_CTRL_ADDR arm_pll; // 4. 等待PLL锁定 while (!(*(volatile uint32_t *)PLL_STATUS_ADDR 0x1)) { /* busy loop */ } // 5. 根据新的ARM PLL输出调整CPU系统分频保持6:3:2:1比例 uint32_t aper *(volatile uint32_t *)APER_CLK_CTRL_ADDR; // 这里需要结合UG585第6章的字段定义重新计算不可盲目照抄 *(volatile uint32_t *)APER_CLK_CTRL_ADDR aper;这段代码只是示意落地前一定要对着UG585重新核对寄存器位域不同版本设备树和FSBL模板之间可能存在细节差异。FSBL的源码一般在Vitis工程的ps7_init_gpl.c或ps7_init.c里最佳实践是修改生成后的初始化文件而不是硬编码写一段初始化这样可维护性更好。重要提示上面的寄存器操作流程只适合在FSBL启动早期、系统时钟还没有正式切换到PLL输出前执行。如果系统已经运行起来再去改PLL寄存器必须先借助一个可用的备用时钟通常是PS_CLK直通路径把CPU时钟切过去等PLL重新锁定后再切回来。这个流程很讲究不建议在产品里做运行态切换。我特意没有在伪代码里给出完整的APER_CLK_CTRL字段值原因是不同FSBL模板和Vivado版本生成的初始化参数会有差异照抄容易翻车。你把Vivado图形化改动后生成的两份ps7_init文件做一次diff就能看到哪些参数变了这是学习底层关系最直观的方法。3.3 方式三Linux运行时调整实验可以量产慎用如果你编译了带cpufreq-dt支持的内核理论上可以在设备树中定义多档频率然后运行时切换。Zynq-7020可以在设备树的cpu0节点里加类似这样的内容cpu0 { operating-points 666666 1000000 400000 1000000 300000 1000000 ; /* 完整配置通常还需补充clocks等属性具体以BSP设备树模板为准 */ };但这里有一个非常关键的问题Zynq的CPU频率切换本质上是对ARM PLL做实时重配置而PLL重配需要先将CPU时钟切到备用时钟再调PLL再切回来。这个过程如果控制不好CPU会瞬间失去时钟甚至挂死在满载运行时切换尤其脆弱。此外很多BSP版本对cpufreq的支持并不完善切换失败后只能复位重启。所以我的建议是如果你只是想快速对比不同频率下的温度和性能可以用运行时切换方式做实验但量产固件里最好固定频率启动不要依赖在线调频。实验时切换前后都先让系统空闲用比较慢的步骤操作并且开着串口终端随时观察状态。3.4 降频后如何确认频率真的变了降频生效与否最直接的验证方式是在Linux里看BogoMIPS。Cortex-A9的BogoMIPS一般是CPU主频的两倍左右主频666MHz时BogoMIPS约1333降到400MHz后约800。cat /proc/cpuinfo | grep BogoMIPS如果系统跑的是裸机或RTOS可以用示波器测FCLK_CLK0这种导出时钟先把它配置为CPU时钟的整数分频再通过测量频率反推主频。当然最省事的还是读寄存器自己算在Linux下用devmemdevmem 0xF8000100 devmem 0xF800012C对照UG585的位域定义手动算出ARM PLL输出和分频比就能得到当前CPU主频。这个方法不仅能验证降频是否成功还能排查别人给的镜像到底跑了多少频率。4. 温度对比测试用数据判断降频值不值理论讲完了操作也给了下面进入最有说服力的环节——温度对比测试。我在自己手头这块Zynq7020核心板上做了完整测试板子是常规四层核心板CPU和DDR集中在正面背面有简单散热铜皮但没有风扇环境温度约26°C分别记录A9满载和空闲两种状态下的内部温度数据通过片上XADC读取。4.1 温度数据从哪来用Zynq内部XADCZynq-7020片内有一个XADCXilinx Analog-to-Digital Converter它不仅可以采集PL侧外部模拟信号内部还集成了芯片温度和VCCINT电源电压监测。Linux下通常会在sysfs里暴露为IIO设备ls /sys/bus/iio/devices/iio:device0/里面的in_temp0_raw、in_temp0_offset、in_temp0_scale三个节点就对应芯片内部温度。温度换算公式因内核版本而异老版本内核里常见换算方式是把raw和offset相加后再乘以scale得到毫摄氏度TEMP_RAW$(cat in_temp0_raw) TEMP_OFFSET$(cat in_temp0_offset) TEMP_SCALE$(cat in_temp0_scale) TEMP_MS$(( (TEMP_RAW TEMP_OFFSET) * TEMP_SCALE / 1000 )) echo ${TEMP_MS}毫摄氏度即 $(( TEMP_MS / 1000 ))°C如果你的内核只有一个in_temp0_raw没有offset和scale节点也可以走XADC寄存器的方式读取。最省事的办法还是写个循环脚本每两秒打一次当前温度测试时挂着看趋势就行。注意XADC内部温度传感器的响应需要时间温度读数的变化会比实际结温变化滞后所以测试时要等散热平衡不要只看开跑后头一分钟的数据。4.2 标准测试流程与负载工具为了对比公平我在每个频率点都执行同一套流程先空载静止10分钟让温度回落并记录稳定待机温度再用stress-ng把两个A9核心占满持续跑10分钟记录稳定后的满载温度紧接着跑一轮sysbench单线程CPU性能测试把性能基准也留下来。具体负载命令如下# 双核满载跑120秒 stress-ng --cpu 2 --timeout 120 # 单线程整数计算性能基准 sysbench cpu --threads1 --time10 run对于没有stress-ng的根文件系统也可以退而求其次用最简单的yes /dev/null 起两个后台进程把核心占满再配合温度脚本观察。占满率和stress-ng区别不大只是少了统计信息测温度对比完全够用。4.3 测试结果记录与解读我测了666.667MHz、400MHz和300MHz三档数据整理如下测试状态待机温度满载温度满载温升sysbench单线程得分约666.667MHz47°C76°C29°C890400MHz42°C58°C16°C540300MHz40°C52°C12°C410可以看到从默认666MHz降到400MHz满载温度下降18°C是个非常可观的改善继续降到300MHz满载温度还能再降一些但幅度明显趋缓。性能方面400MHz档相比默认损失约37%的单线程算力300MHz档损失约54%。对很多以PL协处理为主、A9只做管理和通信的应用来说400MHz档的性价比非常突出——算力只损失三分之一温度却从76°C降到了58°C。4.4 性能损失与收益怎么权衡判断降频值不值不能只看裸算力要做应用级测试。比如我那个项目里A9主要跑协议栈和状态机真正重计算在PL侧完成把A9主频从666MHz降到400MHz后端到端通信时延几乎没变化但整机表面温度从摸上去烫手变成了温温的。反过来如果你的应用是纯CPU密集型的加密、压缩、图像前处理那400MHz的A9可能会让你等得很着急降频就要三思。我个人的经验是先把应用拆成“A9算的部分”和“PL/DDR算的部分”估算A9算的部分占端到端时延的比重。如果占比超过50%降频对体验影响就明显建议优先优化算法或挪到PL如果占比低于30%大胆降频收益远大于代价。5. 降频避坑指南与常见问题实录降频这件事听起来就是改个数字实际操作中翻车案例一抓一大把。下面这些坑我基本都踩过列出来给大家当参考。5.1 PLL锁不住、系统起不来的坑ARM PLL不是随便给个参数都能工作的。Zynq-7020的PLL有一个VCO工作范围约束典型条件下要求VCO频率在一个区间内常见说法是750MHz~1500MHz左右具体以数据手册为准。如果你的M/D参数设计让VCO低于下限PLL根本锁不住系统在上电初始化阶段就会卡住或反复重启。所以降频的正确做法不是把ARM PLL的输出直接压到很低而是让PLL仍工作在一个合理的VCO频率通过改变后分频O和CPU系统分频来得到所需主频。比如想让CPU跑300MHz可以让ARM PLL输出900MHz然后通过分频链路把最终CPU主频压到300MHz而不是试图让PLL直接输出300MHz。这个区别非常关键。5.2 串口波特率开始漂移外设时钟比例失衡我最初自己改FSBL时只把ARM PLL输出改了没管UART的分频器配置结果串口从115200bps变成了奇怪的速率打印信息全是乱码。后来才发现UART的时钟源链路经过CPU_1xCPU_1x又跟随ARM PLL变化我只降了主频却没同步调整UART分频自然就乱了。排查思路很简单改完频率后优先观察串口是否正常如果乱码说明需要同步修改UART时钟源选择或分频系数。Vivado图形化配置一般会自动处理这个问题所以这也是我推荐大家优先用Vivado的原因。5.3 DDR时序和互联带宽的潜在隐患虽然DDR PLL没动但CPU侧访问DDR时还要经过片内互联而互联时钟来自ARM PLL的派生时钟。当主频大幅降低后有些极端情况下可能出现DDR仲裁延迟增加、DMA带宽下降尤其是在PL通过HP口大量写DDR时表现为主机看到的数据卡顿或采集丢帧。如果你的应用有高带宽DMA需求降频后一定要跑一轮长时间的高负载DMA回环测试别只看温度和A9算力。我自己在400MHz下跑AXI DMA回环带宽比默认频率下降约20%~30%但对应用场景仍然够用。要是你的应用正好卡在带宽边缘就得评估是否接受这个损失。5.4 常见问题速查表现象可能原因解决办法上电后系统卡死串口无输出PLL未锁定或配置值超范围检查ARM PLL M/D/O是否满足VCO范围回到Vivado生成参数串口有打印但全是乱码UART分频未随CPU_1x同步调整重新配置UART时钟分频或使用Vivado自动生成配置A9跑不满性能与预期差距大降频后互联或DDR成为瓶颈用sysbench和DMA回环分别测算力与带宽定位瓶颈运行中偶发死机、看门狗复位PLL切换时序问题或电压余量不足检查电源纹波、禁止运行时切频固定频率启动温度读数跳动大XADC采样叠加了电源噪声多次采样取平均采样间隔拉长到秒级PL的DMA吞吐明显下降互联时钟随ARM PLL降低评估带宽余量必要时提高分频比或分散DMA通道5.5 几个我自己的实操心得最后分享几个我自己比较受用的点。做降频实验之前最好先备份好当前能正常启动的FSBL和BOOT.bin万一改挂了还能快速回滚别嫌麻烦我就吃过两次亏。测试温度和性能时每次改完频率之后都让板子冷却到同一待机温度再开始跑不然对比数据会带上干扰得出的结论也不够准。如果条件允许在芯片表面贴一个热敏电阻或热电偶作为XADC读数的外部对照。XADC内部的温度传感器位于芯片die上和外壳、散热片温度有差值外部对照能帮你建立更准确的热模型。再有一个点不要忽视ps7_init_gpl.c和ps7_init.c之间的差异。有的工程用c文件初始化有的用h头文件定义宏生成代码手动改动前先确认你的FSBL调用的是哪一份改错文件会白白浪费时间。我实际做下来从Vivado改参数到重新生成镜像再做完温度对比整个过程不到半天时间。相比重新设计散热方案、换壳、加风扇这些硬件改动降频的性价比高得不是一星半点。如果你手头的Zynq7020项目也面临高温、散热受限的问题不妨先按这个流程把降频实验做一轮拿到温度和性能数据再决定后续方案。以后就算换了别的SoC平台这种“先量化、再决策”的思路也一样管用。