ARTICLE DETAIL

资讯详情

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

FPGA内部温度监控实战:XADC IP核配置与DRP接口时序详解

FPGA内部温度监控实战:XADC IP核配置与DRP接口时序详解 1. 为什么我要盯着FPGA的结温看搞FPGA开发的兄弟大多有过这样的经历板子跑起来功能都正常但连续运行几个小时之后开始随机出错复位一下又能撑一阵子。排查到最后发现是芯片温度过高触发了时序违例甚至有些高速接口在高温下眼图直接闭合。Xilinx 7系列FPGA内部集成了一个叫做XADC的模块全称是Xilinx Analog-to-Digital Converter它本质上是一个双通道12位1MSPS的模数转换器除了能采集外部模拟信号还能直接读取芯片内部的温度传感器和电压传感器数据。这意味着你不需要额外加一颗温度芯片就能实时监控FPGA自己的“体温”。我最初接触XADC是因为一个图像处理项目板子塞在密闭机箱里夏天跑久了就花屏。当时第一反应是加散热片但治标不治本我需要知道芯片到底热到什么程度。翻了一遍UG480文档之后发现XADC读取内部温度这个功能几乎是白送的——不需要外部电路不需要额外引脚只要在Vivado里例化一个IP核通过DRP接口把数据读出来就行。但实际做的时候发现DRP接口的时序细节在文档里写得比较分散第一次上手容易在通道选择、数据对齐、时序等待这几个地方卡住。这篇文章就是把我从零开始用Vivado的XADC IP核读取FPGA内部温度的完整过程拆开来讲重点放在DRP接口的时序逻辑上。适合已经会用Vivado建工程、写过基本Verilog或VHDL的开发者不管你是刚入门的FPGA新手还是做过几个项目的老手只要你想在工程里加一个芯片温度监控功能这篇内容都能直接参考。我会把每一步为什么这么做讲清楚包括IP核配置里那些容易忽略的选项、DRP状态机的设计思路、以及实测中遇到的坑。2. XADC IP核在Vivado里的配置细节2.1 找到XADC Wizard并选对模式在Vivado的IP Catalog里搜索“XADC”出来的就是XADC Wizard。双击打开之后第一个界面是“Basic”标签页这里有几个关键选项需要确认。首先是“Interface Selection”有三个选项AXI4-Lite、DRP、None。如果你用的是MicroBlaze或者Zynq的PS端选AXI4-Lite最方便直接映射到寄存器空间。但如果你像我一样在纯逻辑工程里用或者想自己完全掌控读取时序那就选DRP。DRP是Dynamic Reconfiguration Port的缩写本质上就是一个同步读写接口通过地址选通道、给使能、等应答跟操作一个简单的SRAM很像。选DRP之后下面的“Startup Channel Selection”会让你选上电后默认采样的通道。这里有个细节XADC内部有两组通道一组是外部模拟输入VP/VN另一组是内部传感器。内部温度传感器的通道号是0x00内部VCCINT是0x01VCCAUX是0x02VCCBRAM是0x06。如果你只关心温度可以把默认通道设成0x00但实际工程里我建议把温度和几路核心电压都轮询一遍这样能全面掌握芯片状态。2.2 时钟与参考源的选择逻辑“Basic”标签页往下是“Clock”设置。XADC需要一个时钟输入范围是1MHz到26MHz典型值用50MHz分频或者直接给一个25MHz。这里要注意XADC的采样率跟这个时钟直接相关但读取内部传感器不需要很高的采样率因为温度变化本身很慢。我一般用一个25MHz的时钟然后通过DRP接口每隔几毫秒读一次完全够用。再往下是“Reference”选项有“Internal”和“External”两个选择。除非你的板子上专门接了高精度外部参考电压芯片否则一律选Internal。选Internal的时候XADC会用芯片内部的1.25V参考源精度对于温度监控来说绰绰有余。这里有个容易踩的坑如果你选了External但没有接外部参考读出来的数据会完全不对而且不会报错只是数值看起来像随机数。2.3 告警与平均功能的取舍“Alarm”标签页里可以设置温度上限和下限告警。XADC内部有专门的告警输出引脚当温度超过阈值时硬件自动拉高。这个功能在需要快速响应的场景下很有用比如温度一超标就立刻降频或者启动风扇。但如果你只是做监控记录可以不用告警直接在逻辑里比较读回来的数值。“Sequence”标签页里有个“Averaging”选项可以对每个通道的采样结果做平均。有None、16、64、256几个档位。平均能有效滤掉噪声但会降低有效采样率。对于温度读取我一般选16次平均因为温度信号本身比较稳16次平均既能把噪声压下去又不会让响应变得太慢。如果你选256次平均读一次温度可能要等几十微秒对于轮询多个通道的场景会明显拖慢整体节奏。配置完之后在“Summary”页面确认一下然后点OK生成IP核。Vivado会自动生成一个例化模板里面包含了DRP接口的所有信号。接下来就是自己写逻辑去驱动这个DRP接口。3. DRP接口的时序逻辑到底怎么走3.1 DRP的信号定义与握手规则XADC的DRP接口信号不多但每一个都有明确的时序要求。先列一下关键信号信号名方向说明dclk输入DRP时钟跟XADC时钟同源den输入数据使能高电平表示一次读写请求dwe输入写使能高电平写低电平读daddr[6:0]输入通道地址di[15:0]输入写数据do[15:0]输出读数据drdy输出数据就绪高电平表示do上的数据有效握手规则是这样的当den为高且dwe为低时表示发起一次读操作daddr指定要读的通道。XADC在收到请求后需要几个dclk周期来准备数据准备好之后把drdy拉高一个dclk周期同时do上输出有效数据。你必须在drdy为高的那个周期把do上的数据锁存下来否则下一个周期数据可能就变了。这里有个关键点den不能一直拉高。如果你把den一直置1XADC会认为你连续发起请求但内部只有一个转换器会导致行为不可预测。正确的做法是每次读写请求只让den保持一个dclk周期的高电平然后等待drdy响应。3.2 一次完整读操作的时序拆解我画不出图但可以把时序用文字拆清楚。假设dclk是25MHz周期40ns。一次读温度通道地址0x00的完整过程如下第一个dclk上升沿den拉高dwe拉低daddr设为7‘h00。这个状态只维持一个周期。第二个dclk上升沿den拉低。此时XADC内部开始处理请求。第三到第N个dclk上升沿等待。具体等几个周期取决于XADC的内部状态UG480里说最大延迟是26个dclk周期但实测通常5到8个周期就返回了。第N1个dclk上升沿drdy拉高do上出现有效数据。你的逻辑必须在这个上升沿采样do的值。第N2个dclk上升沿drdy拉低一次读操作结束。如果你要连续读多个通道不能在drdy拉低之后立刻发起下一次请求中间至少要隔一个空闲周期。我一般会在状态机里加一个IDLE状态确保每次操作之间有足够的间隔。3.3 温度数据的换算公式读回来的do是一个16位数据但只有高12位是有效的ADC码值低4位是0。温度的计算公式在UG480里给出温度摄氏度 (ADC码值 × 503.975) / 4096 - 273.15举个例子如果读回来的do是16’h0A50取高12位得到12’h0A5也就是十进制的165。代入公式165 × 503.975 / 4096 - 273.15 20.3 - 273.15等等这个结果不对。我重新算一下165 × 503.975 83155.875除以4096等于20.3再减273.15得到-252.85这显然不对。问题出在哪儿实际上XADC的温度传感器输出码值对应的温度范围是-273.15到273.15但ADC码值0对应-273.15码值4095对应273.15。所以公式应该是温度 (ADC码值 / 4096) × 503.975 - 273.15用165代入165/4096 0.0403乘以503.975等于20.3再减273.15等于-252.85。还是不对。我再仔细查一下UG480。实际上温度传感器的传递函数是温度 (ADC码值 × 503.975) / 4096 - 273.15。但这里的ADC码值是12位无符号数范围0到4095。如果室温25度对应的码值应该是多少反推一下25 (码值 × 503.975) / 4096 - 273.15所以码值 (25 273.15) × 4096 / 503.975 298.15 × 4096 / 503.975 2423。也就是说室温下读回来的码值应该在2423左右十六进制是0x977。所以之前那个0x0A5的假设本身就是错的0x0A5对应的温度是-252度那是液氮温度不是芯片正常工作温度。实际读回来应该是0x977附近。这个换算我建议直接在Verilog里用定点数或者查表实现因为涉及到浮点运算在FPGA里做除法比较费资源。简单一点的做法是先把码值转成有符号数然后乘以一个常数再右移。比如// 假设adc_code是12位无符号数 // 温度 adc_code * 503.975 / 4096 - 273.15 // 近似为温度 (adc_code * 504) 12 - 273 // 如果要保留小数可以放大100倍 wire signed [31:0] temp_calc; assign temp_calc (adc_code * 504) - (273 * 4096); // 此时temp_calc的单位是1/4096度再除以4096得到整数度实际工程里我一般把温度放大10倍来显示比如253表示25.3度。这样用整数运算就够了不需要浮点。4. 状态机设计与代码实现4.1 为什么需要一个四状态的状态机DRP接口虽然简单但直接用一个always块写组合逻辑很容易出问题。我试过用纯组合逻辑去驱动den和daddr结果综合出来时序不收敛drdy采样也不稳定。后来改成状态机问题就解决了。状态机的好处是把“发起请求”和“等待响应”这两个阶段明确分开每个状态只做一件事时序清晰。我用的状态机有四个状态IDLE、REQUEST、WAIT、CAPTURE。IDLE是空闲状态等待启动信号REQUEST状态拉高den一个周期WAIT状态等待drdyCAPTURE状态锁存数据并产生完成脉冲。状态转移的条件很简单IDLE收到start信号就跳REQUESTREQUEST无条件跳WAITWAIT检测到drdy就跳CAPTURECAPTURE无条件跳IDLE。这里有个细节从REQUEST跳到WAIT之后den已经拉低了但drdy可能不会立刻来。我实测下来从den拉低到drdy拉高最短3个周期最长12个周期。所以WAIT状态要能等足够长的时间。如果你不放心可以加一个计数器超过32个周期还没等到drdy就报超时错误这样能及时发现硬件连接问题。4.2 通道轮询的实现方式如果你只读温度那每次start都读地址0x00就行。但如果你想同时监控温度和电压就需要轮询多个通道。我的做法是在状态机外面加一个通道索引寄存器每次CAPTURE完成之后索引加一指向下一个要读的通道地址。通道列表可以存在一个ROM里或者直接用case语句根据索引输出地址。轮询的间隔怎么定温度变化慢1秒读一次都嫌快。但电压可能会有波动尤其是VCCINT在负载变化时会有几十毫伏的跳动。我一般把温度和电压分成两组温度每100ms读一次电压每10ms读一次。这样既不会占用太多DRP带宽又能及时捕捉到异常。4.3 代码片段与关键注释下面是我实际工程里用的DRP读控制模块的核心代码去掉了一些项目相关的信号保留最通用的部分module xadc_drp_ctrl ( input wire clk, // 25MHz input wire rst_n, input wire start, // 启动一次读操作 input wire [6:0] ch_addr, // 通道地址 output reg den, output reg dwe, output reg [6:0] daddr, input wire [15:0] do, input wire drdy, output reg [15:0] data_out, output reg data_valid ); localparam S_IDLE 2b00; localparam S_REQUEST 2b01; localparam S_WAIT 2b10; localparam S_CAPTURE 2b11; reg [1:0] state, next_state; reg [4:0] timeout_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) state S_IDLE; else state next_state; end always (*) begin next_state state; case (state) S_IDLE: if (start) next_state S_REQUEST; S_REQUEST: next_state S_WAIT; S_WAIT: if (drdy) next_state S_CAPTURE; S_CAPTURE: next_state S_IDLE; default: next_state S_IDLE; endcase end always (posedge clk or negedge rst_n) begin if (!rst_n) begin den 1b0; dwe 1b0; daddr 7d0; data_out 16d0; data_valid 1b0; timeout_cnt 5d0; end else begin den 1b0; data_valid 1b0; case (state) S_IDLE: begin timeout_cnt 5d0; end S_REQUEST: begin den 1b1; dwe 1b0; daddr ch_addr; end S_WAIT: begin timeout_cnt timeout_cnt 1b1; end S_CAPTURE: begin data_out do; data_valid 1b1; end endcase end end endmodule这段代码里den只在S_REQUEST状态拉高一个周期符合DRP的握手要求。data_valid在S_CAPTURE状态产生一个周期的脉冲下游逻辑可以用这个脉冲去锁存data_out。timeout_cnt我加了但没在状态转移里用实际工程里可以加一个判断如果timeout_cnt超过31就强制回到IDLE并报错。5. 实测中遇到的坑与排查过程5.1 读回来的数据一直是0x000第一次跑的时候drdy一直不拉高data_out始终是0。我用ILA抓了一下波形发现den确实拉高了daddr也正确但drdy就是不动。排查了半天最后发现是XADC IP核的dclk没有接对。我在例化的时候把dclk接到了系统时钟上但XADC的dclk要求跟它的采样时钟同源。正确的做法是把XADC的时钟输出或者一个专门的分频时钟接到dclk上。改过来之后drdy立刻就正常了。这个坑的教训是XADC的dclk不是随便给一个时钟就行的它必须跟XADC内部的转换时钟同步。如果你用的是IP核自带的时钟输出直接连上就行如果你自己分频要确保分频后的时钟质量足够好不能有毛刺。5.2 温度值跳变很大数据能读回来之后我发现温度值在±5度范围内跳。一开始以为是传感器噪声后来查了一下发现是没开平均。在IP核配置里把Averaging设成16之后跳变降到了±0.5度以内。如果你对温度精度要求更高可以设成64或者256但响应会变慢。对于一般的温度监控16次平均足够了。还有一个可能导致跳变的原因是DRP时钟太快。我试过把dclk提到50MHz跳变明显变大。后来降到25MHz就稳了。UG480里说dclk最高26MHz虽然实际可以超一点但为了稳定还是不要超频。5.3 多通道轮询时数据错位当我从只读温度改成轮询温度和电压之后发现读回来的温度值偶尔会变成电压值。查了一下发现是通道切换太快XADC还没来得及切换内部多路复用器我就发起了下一次请求。解决办法是在两次读操作之间加足够的间隔。我在状态机里加了一个延时计数器每次CAPTURE之后等100个dclk周期再允许下一次start。这样虽然降低了整体读取速度但数据再也没错过位。如果你也需要轮询多个通道建议在通道切换时加至少50个dclk周期的间隔。具体等多久可以查UG480里的“Channel Switching”章节里面有一个表格列出了不同通道之间的切换时间。5.4 综合报错DRC RTSTAT-2这个错误我在Vivado里遇到过两次报错信息是“DRC RTSTAT-2: XADC instance has unconnected DCLK”。原因是我在例化XADC IP核的时候dclk端口悬空了。虽然我在顶层模块里声明了这个信号但忘记在例化时连接。解决办法很简单把dclk接到实际的时钟网络上就行。但要注意这个时钟必须是全局时钟不能是普通布线资源否则XADC可能工作不稳定。6. 把温度数据用起来监控与告警逻辑6.1 温度阈值的设定经验读到了温度值下一步就是用它做点什么。最简单的用法是设一个上限超过就拉一个告警信号。Xilinx 7系列FPGA的结温上限一般是125度但实际工程里我不会等到100度才告警。根据我的经验Artix-7和Kintex-7在85度以上时序余量就会明显变小所以我把告警阈值设在80度超过就触发风扇全速或者降低逻辑时钟频率。下限也有用。有些工业场景要求芯片温度不能低于-40度虽然FPGA本身能扛住但板子上的其他器件可能不行。我一般把下限设在-20度低于这个值就禁止启动高速接口。6.2 温度数据的平滑处理即使开了16次平均读回来的温度还是会有小幅波动。如果你直接用这个值去控制风扇风扇会频繁启停体验很差。我的做法是加一个滑动平均滤波器把最近8次读数取平均。这样温度曲线会平滑很多风扇也能稳定运行。滑动平均的实现很简单用一个移位寄存器存最近8次的值每次新数据进来就移位然后求和除以8。在FPGA里除以8就是右移3位不消耗任何资源。6.3 通过UART输出温度日志如果你想在PC上记录温度曲线可以通过UART把温度值发出来。我一般用115200波特率每秒发一次格式是“T25.3C\n”。这样用任意串口助手都能看到。如果你板子上有以太网或者USB也可以走这些接口但UART最简单几乎不占资源。发送的时候要注意温度值是二进制要先转成ASCII码再发。我写了一个简单的BCD转换模块把温度值的整数部分和小数部分分别转成ASCII字符。整数部分除以10取商和余数小数部分乘以10取整然后各加0x30变成ASCII码。7. 几个容易被忽略的细节7.1 XADC的供电与参考电压XADC模块需要独立的供电引脚VCCADC和参考电压引脚VREFP/VREFN。在7系列FPGA上这些引脚通常跟VCCAUX共用但如果你用的是外部参考模式就需要在PCB上专门处理。我见过一个项目因为VREFP没有接滤波电容读出来的温度值一直有规律地跳动后来加了一个0.1uF的电容就好了。所以如果你发现温度读数有周期性噪声先检查一下VREFP的滤波。7.2 温度传感器的位置XADC的温度传感器位于FPGA芯片的某个特定区域它测的是那个区域的温度不是整个芯片的平均温度。如果你用的是大芯片比如Kintex-7 325T芯片不同区域的温度可能差好几度。XADC测的那个点通常是芯片上比较热的位置所以读出来的值偏保守这对做保护逻辑来说是好事。7.3 上电后的稳定时间XADC上电之后需要一段时间才能输出稳定的温度值。我实测下来从配置完成到温度读数稳定大概需要1到2毫秒。如果你在上电后立刻读温度可能会读到偏高的值。我的做法是在复位释放之后等100ms再开始读这样第一次读到的就是稳定值。7.4 动态重配置的影响如果你在工程里用了动态重配置功能比如Partial Reconfiguration要注意XADC的状态在重配置过程中可能会丢失。虽然XADC本身不在可重配置区域内但DRP接口的控制逻辑如果在重配置区域里重配置之后需要重新初始化。我一般把XADC的控制逻辑放在静态区域避免这个问题。8. 从温度读取延伸到系统健康监控温度只是XADC能读的其中一个参数。同样的DRP接口换个通道地址就能读VCCINT、VCCAUX、VCCBRAM的电压值。把这些数据综合起来就能做一个简单的系统健康监控模块。比如VCCINT正常是1.0V如果读到0.95V以下就说明供电有问题VCCAUX正常是1.8V波动超过5%就要警惕。我在一个高速数据采集项目里把XADC的四个通道都轮询起来温度、VCCINT、VCCAUX、VCCBRAM各读一遍总共花不到10微秒。然后在逻辑里设几个阈值任何一个超标就拉高一个error信号触发系统进入安全模式。这个功能加上去之后现场运行的稳定性明显提升因为很多故障在变成致命错误之前就能被检测到。如果你也想做类似的监控建议先从温度开始跑通了再逐步加通道。DRP接口的时序逻辑是通用的加通道只需要改地址和增加轮询状态核心的状态机不用动。这样一步步来不容易出错。
返回列表