FPGA内嵌XADC硬核:基于Verilog的温度与电压监控模块设计
发布时间:2026/9/20 11:25:06来源:尧图网络
简介面向Zynq系列FPGA开发者的XADC 12位ADC采样Verilog模块已在xc7z020平台验证运行调用Xilinx自带XADC IP核实现模数转换最高采样率达1Msps输入时钟100MHz可在IP核内灵活修改。包内提供完整可综合工程与顶层模块xadc_top覆盖从IP例化、约束到在线调试的完整链路资源共62个文件压缩包29.42MB以24个vh文件为源码主体另有11个v文件及xdc约束、xci配置、veo例化模板、dcp网表、vhdl仿真文件等多种辅助设计文件同时附带docx说明文档便于查阅。工程已在xc7z020上实测通过其他Xilinx器件可通过IP report升级适配目前已有173人学习具备较强参考价值。设计上拆分为xadc_wiz_0采样核心与ila_xadc调试链路两部分借助ILA逻辑分析仪可实时观测采样波形与数值结果大幅降低XADC调试门槛适合嵌入式与硬件开发者快速集成或二次开发。 最近在调一块板卡需要实时监控FPGA的结温和供电电压本来打算外挂一颗ADC芯片后来发现Xilinx FPGA里早就集成了XADC硬核12位精度、最高1MSPS采样率既能测芯片温度又能测电压还能扩展16路外部模拟输入。之前总觉得XADC用起来有点绕DRP寄存器配置、原语例化、时序控制资料看着多但真正要快速跑通还是得自己踩一遍。这次我抽时间把整套逻辑整理成了一个可复用的Verilog模块通过DRP接口读写配置实测在Xilinx 7系列芯片上稳定运行。这篇文章把模块的设计思路、Verilog代码、温度换算、外部通道扩展和调试中遇到的各种坑都过一遍给想用上XADC但不想从头啃文档的开发者做个参考。1. XADC硬核速览为什么项目里值得用它1.1 一个被你忽略的板级监控利器XADC这个硬核在Xilinx 7系列及之后的FPGA里都有它本质上就是一颗完整的12位SAR型ADC内部还集成了温度传感器和多个供电电压监控通道。很多人做板卡设计的时候习惯性忽略它觉得FPGA里的ADC肯定不如外置的靠谱。实际上对于温度监控、电压监控、慢速模拟量采集这类场景XADC足够用了而且省掉一颗外置ADC的BOM成本和PCB面积更不用操心I2C或SPI总线的时序对接。更关键的是XADC是硬核不占用FPGA逻辑资源只有DRP接口的控制逻辑会占一点LUT而且支持上电自动采样就算用户逻辑还没跑起来XADC内部状态机已经在循环采集温度和电压了。这一点在板卡调试阶段特别有用——板子一上电就能看到芯片温度不用等软件初始化。1.2 XADC能测什么内置传感器与外部通道XADC硬核的结构可以理解为两个12位ADC内核ADC A和ADC B共用一组逻辑内部有模拟多路开关来选择当前转换的通道。支持以下测量对象测量对象数量说明芯片结温1路内置温度传感器典型范围-40°C到125°C供电电压3~6路VCCINT、VCCAUX、VCCBRAM等具体看器件系列专用模拟输入1对VP/VN差分输入满量程1V辅助模拟输入16对VAUXP/VAUXN差分输入满量程1V可配置使能外部MUX扩展最多16路配合MUXADDR信号扩展模拟通道内部通道的满量程是3V外部辅助通道的满量程是1V这个在计算电压时要注意区分我见过的很多第一版代码温度算错其实就是满量程搞混了。2. Verilog模块整体设计从DRP接口入手2.1 模块顶层接口怎么定要在FPGA逻辑里控制XADC唯一通道就是DRP接口Dynamic Reconfiguration Port。用户逻辑通过DRP往配置寄存器写值决定XADC采样哪个通道、什么模式转换结果则保存在状态寄存器里用户逻辑通过DRP读回来。设计模块之前先想清楚接口我最终定的顶层接口很简单一个工作时钟、一个复位一个12位温度数据输出、一个12位电压数据输出加上一个数据有效信号。我没有把DRP的DEN、DWE、DADDR这些信号直接暴露到顶层而是封装在模块内部这样对上层逻辑更友好。如果你的项目需要同时读多个通道可以在这个模块基础上增加一个地址选择输入或者把输出改成一组寄存器。2.2 DRP读写机制和状态机设计DRP接口的时序核心就几个信号DCLK是DRP时钟DEN是使能信号拉高一个周期表示发起一次读写DWE是写使能高表示写、低表示读DADDR是寄存器地址写数据从DI进入读数据从DO输出DRDY拉高表示数据有效。很多人在DRP时序上栽跟头主要问题是DEN拉高后没有正确等待DRDY导致读到的是旧数据。DRP一次读操作至少需要几个DCLK周期才能完成中间不能连续发起下一次操作。我的做法是在状态机里设置一个等待状态DRDY不拉高就不跳转这样最稳妥。状态机设计上我用了四个状态IDLE、READ、WAIT_DRDY、DONE。IDLE状态下发起读请求DEN拉高、DWE拉低、DADDR指向要读的寄存器READ状态把DEN拉低然后进入WAIT_DRDY等待数据DRDY拉高后锁存DO数据回到IDLE开始下一轮读取。2.3 寄存器映射需要记住的几个地址XADC的寄存器空间分两类配置寄存器地址0x40起和状态寄存器地址0x00起DRP读操作同时支持这两个区域的访问。我自己实际使用中需要记住的地址不多寄存器地址含义说明0x00温度状态寄存器12位温度码存放在DO[15:4]0x01VCCINT状态寄存器12位电压码存放位置同上0x02VCCAUX状态寄存器12位电压码0x03VCCBRAM状态寄存器7系列专用0x42配置寄存器2bit[4:0]选择当前转换通道0x43配置寄存器3bit[15:0]对应VAUX0-15通道使能这里有个容易踩的坑状态寄存器地址和配置寄存器地址不是连续的状态寄存器从0x00开始配置寄存器从0x40开始中间不要搞混。另外温度数据是12位存放在16位寄存器的bit[15:4]低4位永远是0如果直接把整个DO值当成温度码去换算是会出问题的。3. 完整代码XADC温度采集模块实战3.1 XADC原语例化关键端口说明XADC原语的名字就叫XADC在Vivado的UNISIM库里。例化的时候有几个端口需要注意DCLK必须给时钟否则DRP操作不会生效RESET可以不用因为XADC上电默认就工作EOC和EOS是转换完成信号做监控类应用时可以用也可以不用因为状态寄存器是持续更新的随时去读都是最新值。直接给出原语例化代码XADC #( .INIT_40(16h0000), // 配置寄存器0默认自动采样模式 .INIT_41(16h0000), // 配置寄存器1 .INIT_42(16h0000), // 配置寄存器2默认温度通道 .INIT_43(16h0000), // 配置寄存器3外部通道关闭 .SIM_MONITOR_FILE(xadc_sim.txt) // 仿真监控文件可留空 ) u_xadc ( .DCLK(clk), // DRP时钟 .RESET(1b0), // 不使能复位 .DEN(den), // DRP使能 .DWE(dwe), // DRP写使能0读1写 .DADDR(daddr), // DRP地址 .DI(di), // DRP写数据 .DO(do), // DRP读数据 .DRDY(drdy), // DRP数据有效 .EOC(eoc), // 转换结束 .EOS(eos), // 序列转换结束 .BUSY(), // 忙标志未使用 .ALM(), // 报警输出未使用 .OT(), // 过温标志未使用 .CHANNEL(), // 当前转换通道号未使用 .MUXADDR(), // 外部MUX地址未使用 .VP(1b0), // 专用模拟输入P不用接地 .VN(1b0), // 专用模拟输入N不用接地 .VAUXP(16h0000), // 外部辅助通道P全接地 .VAUXN(16h0000) // 外部辅助通道N全接地 );3.2 状态机怎么写DRP读时序的Verilog实现下面这段是我整理好的DRP读取状态机功能很简单循环读取0x00地址的温度寄存器每读一次更新一次温度输出。核心就是前面说的四个状态DEN只拉高一个周期然后死等DRDY。module xadc_temp_reader ( input wire clk, input wire rst_n, output reg [11:0] temp_code, output reg data_valid ); reg den; reg dwe; reg [6:0] daddr; reg [15:0] di; wire [15:0] do; wire drdy; localparam S_IDLE 2d0; localparam S_READ 2d1; localparam S_WAIT 2d2; localparam S_DONE 2d3; reg [1:0] state; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_IDLE; den 1b0; dwe 1b0; daddr 7d0; di 16d0; temp_code 12d0; data_valid 1b0; end else begin data_valid 1b0; case (state) S_IDLE: begin den 1b1; dwe 1b0; daddr 7h00; // 读温度寄存器 state S_READ; end S_READ: begin den 1b0; state S_WAIT; end S_WAIT: begin if (drdy) begin temp_code do[15:4]; // 取高12位 data_valid 1b1; state S_DONE; end end S_DONE: begin state S_IDLE; // 循环读取 end endcase end end XADC #() u_xadc ( .DCLK(clk), .RESET(1b0), .DEN(den), .DWE(dwe), .DADDR(daddr), .DI(di), .DO(do), .DRDY(drdy), .EOC(), .EOS(), .BUSY(), .ALM(), .OT(), .CHANNEL(), .MUXADDR(), .VP(1b0), .VN(1b0), .VAUXP(16h0000), .VAUXN(16h0000) ); endmodule这段代码的实现方式是把读操作连续循环起来大约每几微秒更新一次温度数据。对温度监控来说这个更新频率完全够用不会给DRP总线造成压力。如果后续要读多个通道只需要把地址改成对应状态寄存器的地址其他逻辑不用动。3.3 温度换算从12位码到摄氏度拿到12位温度码之后要换算成实际的摄氏度公式来自UG480T(°C) ADC_CODE × 503.975 / 4096 - 273.15如果直接浮点运算在FPGA里没必要。我常用的定点近似方法是把公式简化成整数运算输出结果是毫摄氏度m°C误差在0.1°C以内完全满足监控需求。wire signed [27:0] temp_milli; assign temp_milli (temp_code * 123) - 273150; // 单位: 0.001 °C验证一下25°C左右时温度码约为2423代入计算得2423×123 - 273150≈ 24879即24.879°C非常接近实际值。如果你的系统对温度精度要求不高可以直接输出这个整数或者直接把原始code交给上位机去算FPGA内部不做换算。我个人推荐后者因为上位机算起来更灵活而且省DSP乘法器。4. 扩展外部通道让XADC帮你看板级模拟量4.1 使能外部通道与通道选择配置单纯做温度和板卡供电监控默认配置就够了。但如果你想用XADC采集外部传感器或者电位器这一类慢速模拟信号就需要用VAUX辅助通道。配置外部通道的流程比默认模式多两步一是配置寄存器30x43里使能对应通道二是配置寄存器20x42里选择当前转换的通道。以VAUX0为例需要发起两次DRP写操作第一次写0x43寄存器把bit[0]置1使能VAUX0第二次写0x42寄存器把bit[4:0]设置为01000即8表示选择VAUX0通道。在自动采样模式下使能外部通道后XADC会自动把该通道加入轮询序列你不用管内部状态机是怎么调度的。写配置寄存器的状态机和读寄存器类似区别是DWE拉高、DI写入要配置的值。这里提醒一下写配置寄存器之后建议等EOS信号序列转换结束再开始读数据确保配置已经生效。如果配置完马上读拿到的可能是上一个通道的数据。4.2 读外部通道数据与引脚约束外部通道的数据寄存器地址从0x14开始VAUX0对应0x14VAUX1对应0x15以此类推。读的时候地址改成对应的0x14就能拿到VAUX0的12位转换结果。这里要特别强调满量程问题内部温度通道的满量程是3V其他内部电压通道也是3V但VAUX外部通道的模拟输入范围是0到1V换算公式是V ADC_CODE × 1.0 / 4096习惯上大家会把ADC的值都直接乘3结果外部通道算出来全部偏高3倍这个坑我至少见过三次。实际开发中建议用万用表测量一下输入电压和代码算出来的结果做交叉验证确保换算系数是对的。引脚约束方面外部模拟输入引脚需要像普通IO一样在XDC里指定位置和电平标准不同的是它们没有数字输入缓冲不能当普通GPIO用。如果你在综合时看到和VAUX引脚相关的错误检查一下约束文件里是否把所有VAUXP和VAUXN引脚都定义清楚了。5. 常见问题与调试经验5.1 读不到数据 / 数据恒为0如果模块跑起来但DO一直是0优先查三件事第一DCLK有没有给上XADC原语的DCLK如果悬空DRP总线完全不工作第二DEN信号有没有只拉高一个周期如果DEN一直拉高DRP时序会错乱第三检查状态机里是否把DWE拉低了如果DWE误拉到高电平读操作就变成了写操作。我这里再补充一个排查技巧在Vivado的Hardware Manager里打开XADC界面可以直观看到温度、电压这些测量值同时也能看到XADC到底有没有在工作。如果Hardware Manager能读到值说明XADC硬件没问题问题出在你的Verilog模块上如果Hardware Manager也读不到值就要怀疑芯片配置或者引脚问题了。5.2 温度读数离谱温度读数偏高或者偏低大部分情况下是换算公式用错了。先确认高12位数据提取是否正确——寄存器里DO[15:4]才是有效数据如果你把整个DO值或者DO的低12位拿去算结果一定不对。还有一种情况是用了从网上抄的换算公式参数不对。我验证过的公式就是前面给出的那个T code × 503.975 / 4096 - 273.15或者定点近似code×123 - 273150。另外一个容易被忽略的因素是芯片的散热和功耗。有些板卡FPGA满载运行结温到70、80度很正常不要觉得读数高就是采错了。如果怀疑有系统误差可以用JTAG方式读XADC对照一下或者用一个已知温度的设备做参考点。5.3 仿真模型行为差异很多人在仿真阶段发现XADC读出来的数值和真实硬件对不上这个太正常了。XADC原语的仿真模型默认输出的是室温附近的一个模拟值它本身不是用来精确模拟硬件行为的。所以在写testbench时更好的思路是验证DRP读写时序是否正确而不是纠结仿真里的温度数值准不准。仿真时还需要注意Xilinx的仿真库版本对XADC支持程度不同有些版本仿真模型需要额外加参数。如果仿真时XADC不输出或者报错建议先单独仿真一个最小系统确定原语仿真模型跑通了再接你的状态机代码避免两个问题叠在一起不好排查。5.4 XADC在不同FPGA系列上的差异XADC从7系列到UltraScale系列都有但寄存器地址和特性有一些细微差异。UltraScale系列多了VCCPINT、VCCPAUX这些电压监控通道状态寄存器0x10到0x12的含义不一样7系列没有这些电压那几个地址是保留区或者不可用状态。如果你是从旧工程移植代码到新系列一定要打开对应系列的UG480核对寄存器表别照抄之前系列的设计。此外不同封装的FPGAXADC引脚的物理位置完全不同甚至有些型号的VAUX引脚数量和可用通道数也有限制。选型阶段建议先看封装引脚图确认需要的模拟通道在目标封装上是支持可用的不然等PCB画完才发现某个VAUX引脚没引出来就麻烦了。最后说一个实操中的小技巧。XADC的报警功能ALM信号很多项目都没用到但其实配合温度监控特别好用。你可以把OT过温信号接一个LED或者中断源当结温超过安全阈值默认125°C时立刻报警。配置报警阈值只需要在配置寄存器里写好上下限XADC内部会自动比对不需要软件不断轮询状态寄存器而且这个报警判断是硬件实时完成的不占FPGA逻辑资源。我做板卡可靠性测试的时候就把OT信号接到了系统复位逻辑上温度过高直接拉复位配合看门狗实现了一套很简单的过温保护策略。本文还有配套的精品资源点击获取
网站建设高端定制企业官网