☰
FPGA内部温度监控实战:XADC IP核DRP模式读取结温详解
2026/10/6 18:06:29 网站建设 项目流程

1. 为什么我要用XADC读FPGA内部温度

做FPGA项目时间长了,你会发现一个很现实的问题:板子跑起来之后,芯片到底热不热?尤其是在一些密闭机箱、无风扇散热或者高温工业环境下,FPGA结温一旦超过安全阈值,轻则时序违例、逻辑跑飞,重则直接烧片。我踩过最惨的一次坑,是一块7K325T在夏天高温环境里连续跑了六个小时,突然开始出现偶发数据错误,排查了半天才发现是芯片温度飙到了将近100度,时序余量被温度吃光了。

后来我就养成了一个习惯:只要板子有资源余量,一定把XADC用起来,实时监控芯片温度和供电电压。Xilinx 7系列FPGA内部集成了一个叫做XADC的模块,全称是Xilinx Analog-to-Digital Converter,它是一个双通道12位、1MSPS的ADC,硬核集成在芯片里,不占用任何逻辑资源。它能采集片上温度传感器、供电电压,也能通过外部模拟输入通道采集板级模拟信号。

这篇文章我打算把用Vivado调用XADC IP核读取内部温度的完整流程讲透,包括IP核配置、DRP接口时序、代码实现、实测数据验证,以及我在实际项目中踩过的坑。适合已经有基本FPGA开发经验、用过Vivado但还没碰过XADC的朋友,也适合想深入理解DRP动态重配置接口时序的同行。读完你至少能做到两件事:第一,独立完成XADC读温度的工程搭建;第二,看懂DRP接口的时序波形,自己会改代码读其他通道。

2. XADC IP核的整体设计与选型思路

2.1 为什么选XADC而不是外置温度传感器

很多人第一反应是:我外挂一个I2C温度传感器不就行了?比如TMP102、LM75这些,便宜又好用。但实际项目里,外置传感器测的是PCB板面温度,不是FPGA芯片结温。这两者差距可能非常大,尤其在芯片功耗较高、散热路径不好的情况下,结温和板面温度差二三十度都很正常。你要监控的是芯片内部那个真正会烧掉的结点,外置传感器给不了你这个数据。

XADC的片上温度传感器直接集成在FPGA的硅片里,测的就是结温,精度典型值±4度,分辨率12位,对于工程监控来说完全够用。而且它不需要任何外部器件、不占PCB面积、不占逻辑资源,唯一要做的就是通过DRP或者JTAG把数据读出来。从成本和可靠性角度看,没有理由不用它。

2.2 XADC IP核的两种使用模式

Vivado里调用XADC IP核,本质上是对这个硬核做了一层封装。它提供两种主要的使用方式:

第一种是独立模式,IP核自己完成采样、转换、报警判断,你只需要读寄存器就行。这种模式最简单,适合纯监控场景。

第二种是DRP模式,也就是Dynamic Reconfiguration Port,动态重配置端口。你可以通过这个接口在运行时读写XADC内部的寄存器,改变采样通道、调整报警阈值、读取转换结果。DRP是一个同步的读写接口,有自己的地址、数据、使能信号,时序上跟BRAM的接口有点像。

我这次用的是DRP模式,原因有两个:一是DRP能让我精确控制什么时候读哪个通道,方便做多通道轮询;二是理解了DRP时序之后,你就能举一反三,去操作XADC的其他寄存器,比如设置报警阈值、配置平均次数等。独立模式虽然简单,但灵活性差很多。

2.3 IP核配置的关键参数

在Vivado里配置XADC IP核的时候,有几个参数必须搞清楚,不然生成出来的核可能根本读不到你想要的数据。

Interface Selection:这里选DRP。选完之后IP核会暴露daddr、den、di、do、dwe、drdy这一组DRP信号。

Startup Channel Selection:启动通道选择。XADC有一个通道序列器,上电后会自动按顺序采样。这里可以选默认的Temperature、VCCINT、VCCAUX等。我一般保持默认,因为后面通过DRP可以手动切换。

Sequencer Mode:序列器模式。有Continuous、Single Channel、Default等选项。如果选Continuous,XADC会不停地循环采样所有使能的通道;如果选Single Channel,只采一个通道。我建议选Continuous,让它在后台自己跑,你需要读哪个通道的时候通过DRP去读对应的状态寄存器就行。

Acquisition and Conversion Time:采样和转换时间。这个跟采样时钟有关,默认设置一般没问题。但如果你对采样率有要求,需要根据时钟频率算一下。

Alarm Settings:报警阈值。温度上限默认是85度,可以改。我一般会把它设到比实际安全阈值略低一点,比如80度,这样留出反应时间。

Control/Status Ports:建议把channel、eoc、eos、busy、alarm这些状态信号都使能,调试的时候非常有用。

配置完之后,IP核的端口大概长这样:左边是DRP接口和时钟复位,右边是状态输出和报警输出。接下来重点讲DRP接口怎么用。

3. DRP接口时序详解与代码实现

3.1 DRP接口的信号定义

先把DRP这组信号一个个说清楚,这是后面写代码的基础。

信号名方向位宽含义
daddr输入7位寄存器地址,决定读/写哪个寄存器
den输入1位DRP使能,高有效
di输入16位写入数据
do输出16位读出数据
dwe输入1位写使能,1为写,0为读
drdy输出1位数据就绪,读操作完成时拉高一个周期
dclk输入1位DRP时钟,一般用50MHz或100MHz
reset输入1位复位

读操作的基本流程是:在dclk上升沿,把daddr和den拉高、dwe拉低;XADC在接下来的几个周期内完成内部读取,然后在drdy拉高的那个周期,do上就是有效数据。写操作类似,只是dwe拉高,di上放要写的数据。

3.2 读温度寄存器的完整时序

温度寄存器的地址是7'h00。这个地址在XADC的文档里有明确说明,0x00就是片上温度传感器的状态寄存器。

一次完整的读操作,从时序上看是这样的:

第一个时钟上升沿:den拉高,dwe拉低,daddr设为7'h00。此时XADC开始处理读请求。

接下来大概2到3个时钟周期:XADC内部完成地址译码和数据准备。这段时间den可以拉低,也可以保持,但一般建议只拉高一个周期。

然后drdy会在某个上升沿拉高一个周期,表示do上的数据有效。你需要在drdy为高的时候把do锁存下来。

这里有个关键点:drdy不是固定延迟几个周期就来的,它取决于XADC内部的转换状态。如果你在XADC正忙的时候发读请求,drdy可能会晚几个周期才来。所以正确的做法是用状态机等待drdy,而不是用固定计数器去数周期。

3.3 温度数据的换算方法

读出来的do是16位数据,但温度值只占高12位。具体格式是这样的:do[15:4]是12位温度ADC码,do[3:0]是状态位,一般忽略。

温度换算公式在Xilinx文档UG480里写得很清楚:

温度(摄氏度)= (ADC码 × 503.975) / 4096 - 273.15

举个例子,如果读到的ADC码是0x0A5C,也就是十进制的2652,那么温度 = 2652 × 503.975 / 4096 - 273.15 = 326.3 - 273.15 = 53.15度。

这个公式我实测过,跟JTAG读出来的温度基本一致,误差在2度以内,完全满足工程监控需求。

3.4 Verilog代码实现

下面是我实际项目里用的读温度模块,简化了一下,去掉了多通道轮询部分,只保留读温度的核心逻辑。

module xadc_temp_read ( input wire clk, // 50MHz input wire rst_n, output reg [11:0] temp_code, // 原始ADC码 output reg [15:0] temp_value, // 换算后的温度,放大100倍 output reg temp_valid ); // DRP接口信号 reg [6:0] daddr; reg den; reg [15:0] di; wire [15:0] do; reg dwe; wire drdy; // 状态机 localparam IDLE = 2'd0; localparam READ = 2'd1; localparam WAIT = 2'd2; localparam DONE = 2'd3; reg [1:0] state; reg [15:0] do_reg; // XADC IP核例化 xadc_wiz_0 u_xadc ( .dclk (clk), .reset (~rst_n), .daddr (daddr), .den (den), .di (di), .do (do), .dwe (dwe), .drdy (drdy) ); always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; daddr <= 7'd0; den <= 1'b0; dwe <= 1'b0; di <= 16'd0; temp_code <= 12'd0; temp_value <= 16'd0; temp_valid <= 1'b0; end else begin case (state) IDLE: begin temp_valid <= 1'b0; state <= READ; end READ: begin daddr <= 7'h00; // 温度寄存器地址 den <= 1'b1; dwe <= 1'b0; // 读操作 state <= WAIT; end WAIT: begin den <= 1'b0; if (drdy) begin do_reg <= do; state <= DONE; end end DONE: begin temp_code <= do_reg[15:4]; // 温度换算:code * 503.975 / 4096 - 273.15 // 为避免浮点,先算 code * 503975 / 4096,再减27315,最后除以100 temp_value <= (do_reg[15:4] * 503975 / 4096 - 27315); temp_valid <= 1'b1; state <= IDLE; end endcase end end endmodule

这段代码有几个地方需要说明。第一,温度换算我用了整数运算,先把503.975放大1000倍变成503975,算完之后再减27315(273.15放大100倍),最后结果就是温度放大100倍。这样避免了浮点运算,在FPGA里更高效。第二,状态机在WAIT状态等drdy,而不是固定等几个周期,这样更可靠。第三,每次读完回到IDLE,下一个周期又发起新的读请求,形成连续监控。

3.5 实测波形分析

我用Vivado的ILA抓了一组实际波形,读温度的完整过程大概是这样:

den拉高一个周期后,drdy并没有马上来,而是等了大概8个时钟周期才拉高。这8个周期里XADC在忙内部的事情。drdy拉高的那个周期,do上出现了0x0A5C,也就是53.15度。整个过程从发起到拿到数据,大概10个时钟周期,在50MHz时钟下就是200纳秒。这个速度对于温度监控来说绰绰有余。

如果你发现drdy一直不来,或者do上一直是0,大概率是IP核配置有问题,或者daddr设错了。温度寄存器地址是0x00,这个别搞错。

4. 常见问题与排查技巧实录

4.1 drdy不拉高怎么办

这是最常见的问题。我遇到过好几次,总结下来原因无非这几个:

IP核没使能DRP接口。在Vivado里配置XADC的时候,Interface Selection必须选DRP,否则根本不会生成drdy信号。这个错误很隐蔽,因为IP核照样能生成,只是端口不对。

daddr设成了保留地址。XADC的寄存器地址不是随便设的,0x00到0x3F有明确的定义。如果你设了一个保留地址,XADC不会响应,drdy自然不来。温度是0x00,VCCINT是0x01,VCCAUX是0x02,这几个常用地址记牢。

den拉高的时间不对。den必须在dclk上升沿之前稳定,如果跟时钟边沿太近,可能采不到。建议用寄存器输出den,保证时序。

复位没释放。XADC的reset信号如果一直有效,整个模块不工作。检查一下复位逻辑,确保上电后reset已经拉低。

4.2 读出来的温度明显不对

有一次我读出来是负一百多度,明显离谱。排查后发现是换算公式用错了,把do[15:4]当成了有符号数。实际上温度ADC码是无符号的,直接按无符号算就行。

还有一种情况是读到了其他通道的数据。比如你daddr设的是0x00,但XADC的序列器正在采其他通道,do上可能是上一个通道的残留数据。解决办法是等drdy拉高再取数,drdy为高时do一定是当前请求通道的数据。

4.3 温度跳动很大怎么处理

XADC的原始温度数据确实会有几度的跳动,这是ADC噪声和芯片内部温度梯度造成的,正常现象。如果你需要更稳定的读数,可以开启XADC内部的平均功能。在IP核配置里有个Averaging选项,可以设成16次、64次或256次平均。开启之后温度会平滑很多,代价是响应变慢。

我一般设16次平均,兼顾稳定性和响应速度。如果只是做报警监控,256次平均也没问题。

4.4 常见问题速查表

现象可能原因解决方法
drdy一直不拉高IP核未使能DRP重新配置IP核,Interface选DRP
drdy一直不拉高daddr为保留地址确认地址,温度用0x00
do数据为0den时序不对用寄存器输出den
温度值离谱换算公式错误确认用无符号运算
温度跳动大未开启平均开启16次或64次平均
读到的不是温度通道未切换等drdy后再取数
温度偏高很多芯片确实热检查散热,不是代码问题

4.5 几个实操心得

第一,调试阶段一定要把ILA用起来。把daddr、den、drdy、do都抓到ILA里,一眼就能看出时序对不对。比看代码猜问题快十倍。

第二,先读VCCINT验证通路。VCCINT的地址是0x01,正常值应该在1.0V左右,对应的ADC码大概是0x0AAA左右。如果你读VCCINT能读到合理值,说明DRP通路没问题,再去读温度就顺了。

第三,温度报警阈值别设太高。我一般设80度报警,85度强制降频或关机。7系列FPGA的结温上限是125度,但超过100度之后时序余量会明显恶化,不要等到那时候才处理。

第四,多通道轮询时加个延时。如果你要轮流读温度、VCCINT、VCCAUX,每次读之间最好隔几个周期,让XADC有时间切换内部通道。我一般隔16个周期,实测很稳。

5. 工程扩展与多通道监控思路

5.1 从单温度扩展到全芯片监控

温度只是XADC能读的其中一个参数。实际上XADC内部有一个完整的通道序列器,可以监控VCCINT、VCCAUX、VCCBRAM等供电电压,还能通过外部模拟输入通道监控板级电压。我在实际项目里一般会做一个轮询状态机,依次读温度、VCCINT、VCCAUX,把数据打包通过UART或者以太网上报给上位机。

轮询的实现很简单,把上面读温度的状态机扩展一下,加一个通道计数器,每读完一个通道就切换到下一个地址。地址依次是0x00、0x01、0x02。注意每次切换通道后要等drdy,不要用固定延时。

5.2 报警逻辑的设计

XADC IP核本身有报警输出,alarm信号会在温度或电压超过阈值时拉高。但我建议在逻辑里再做一层判断,因为IP核的报警阈值是固定的,而你可能需要根据工作模式动态调整。

我的做法是:把读到的温度值跟一个寄存器里的阈值比较,超过就拉高一个自定义的alarm信号。这个阈值可以通过DRP动态写入,也可以在逻辑里直接改。这样更灵活,也方便做分级报警,比如80度警告、90度降频、100度关机。

5.3 温度数据的平滑处理

如果你要做温度曲线显示,原始数据跳动会很难看。除了开启XADC内部平均,还可以在逻辑里做滑动平均。比如维护一个16深度的FIFO,每次新数据进来就丢掉最老的,算平均值。这样温度曲线会非常平滑,适合上位机显示。

滑动平均的代价是要用一点BRAM或者寄存器资源,但对于7系列FPGA来说这点资源微不足道。我一般用16深度,效果很好。

5.4 跨平台注意事项

XADC是7系列和UltraScale系列都有的模块,但UltraScale的叫SYSMON,寄存器地址和接口时序基本兼容,但有些细节不一样。如果你从7系列迁移到UltraScale,代码大体不用改,但IP核要重新生成,地址要对照新文档确认。

另外,Zynq系列里XADC的用法稍有不同,因为PS端也能访问XADC。如果你用Zynq,可以通过PS的XADCPS寄存器直接读,不一定非要用PL的DRP。这个看具体需求,纯PL方案更通用。

5.5 一个完整的监控模块框架

最后给一个我在项目里常用的监控模块框架,把温度、电压、报警、平滑都整合在一起:

module sys_monitor ( input wire clk, input wire rst_n, output reg [15:0] temp_value, output reg [15:0] vccint_value, output reg [15:0] vccaux_value, output reg alarm ); // 通道轮询状态机 // 0: 温度 0x00 // 1: VCCINT 0x01 // 2: VCCAUX 0x02 reg [1:0] ch_sel; // ... DRP读写逻辑 // ... 数据换算 // ... 报警比较 // ... 滑动平均 endmodule

这个框架的核心就是通道轮询加数据换算,把前面讲的读温度逻辑复制三份,改一下地址和换算公式就行。报警逻辑用一个比较器,滑动平均用一个移位寄存器组。整体资源消耗很小,7K325T上跑几百个都不成问题。

我在实际使用中发现,XADC读温度的精度和稳定性完全能满足工程需求,关键是DRP时序要吃透,drdy一定要等,不能想当然地用固定延时。另外,温度监控最好从项目一开始就加上,不要等到板子出问题了才想起来。这个模块一旦调通,后面所有项目都能复用,性价比极高。

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

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

立即咨询