FPGA驱动AD9226高速ADC采集:从时序分析到Verilog实现与调试 📅 发布时间:2026/9/9 8:56:40 👁 浏览次数: 简介面向FPGA学习者与硬件工程师这份工程围绕AD9226高速ADC14位、125MSPS在FPGA平台上的接口控制与数据采集实现是小梅哥、黑金风格教程配套的AC620_ACM9226示例项目适合希望借助完整Demo掌握高速采样时序与数字信号处理基础的人群。资源共246个文件压缩包仅6.5MB除Verilog源码v/tdf外还包含Quartus工程数据库cdb/hdb/ammdb、综合与布局布线报告rpt/qmsg、配置文件qpf/qsf、sof烧写文件、mif初始化数据及readme说明基本保留了一个可复现工程的关键产物。已有2648人学习下载学时可重点读状态机、时钟与同步设计、SPI或并行接口时序结合文档梳理AD9226启动转换、数据读取及误差校正思路同时可借助原有工程文件直接改版验证减少从零搭建时间。整体属于中等偏基础难度的FPGAADC入门参考包。 AD9226这个片子差点把第一批做数据采集的同学劝退了。上个月帮一个朋友调板子他自己画的采集卡FPGA用的Xilinx Artix-7ADC就是AD9226。原理图看起来没什么问题代码也是从开源仓库里抄的“通用驱动”结果插上信号源就是读不出正确波形ILA里面看到的12位数据完全是乱的。折腾了一晚上最后发现栽在时钟相位上——不是代码逻辑错了是对AD9226的输出时序理解错了。这篇文章就把AD9226模块从时钟生成、数据采样、FIFO缓存到引脚约束、板级调试的完整链路讲清楚。我会把Verilog代码直接给出来把XDC约束里几个容易忽略的点说明白再把调试阶段踩过的坑按排查思路复盘一遍。不管你是刚接触FPGA数据采集的新手还是已经把系统搭起来但波形不对、数据偶发错位的老手这篇内容都能帮上忙。1. AD9226和FPGA搭档到底解决了什么问题AD9226是ADI出的12位、65 MSPS单通道并行ADC采样率最高可以跑到65M输入带宽在500MHz左右。这个参数在很多场合是“够用但不出彩”比不上高速ADC常用的JESD204B接口但又比STM32内置ADC和ADS1256这类低速串行ADC快了一个数量级。实际做数字示波器、激光雷达信号采集、超声回波处理、通信中频解调这些项目时AD9226都是性价比非常高的选择。为什么这种并行ADC天然适合配FPGA而不是单片机关键在接口形态。AD9226的数据输出是12根并行数据线加上一路采样时钟每一个采样周期数据线上就更新一次结果。主控需要做的事情就是“在这些数据有效的时候把它们抓进来”。对FPGA来说这几乎是零压力——一片普通的Artix-7或者Cyclone IV随便用几个IO引脚就能把这12位数据并行收进来然后做后续的数字信号处理。而STM32如果要用AD9226就得靠FSMC或者FMC总线去模拟并行时序虽然技术上可行但采样率的瓶颈往往不在ADC本身而在MCU处理数据的实时性上整体也浪费了并行接口的优势。我们用FPGA做AD9226采集实际上做了三件事给AD9226提供干净的采样时钟决定“什么时候采样”在正确的时钟沿把12位数据稳定地采进FPGA内部把数据缓存下来再交给后级串口、DDR、以太网、显示等。这三件事看起来简单每一件都有细节。尤其是第二件很多代码写不好就是因为没有吃透AD9226手册里那个时序图。这一块放到下一节详细说。2. 读懂AD9226时序才能写出能用的采样逻辑如果只是照着别人的模块代码例化一遍大概率会遇到“为什么数据对不上”的问题。AD9226的数据手册里有一个参数叫数据输出延迟tOD中文手册里通常叫“数据输出延迟时间”这个参数决定了采样时钟沿和数据有效窗口之间的时间关系。AD9226是超前进位pipeline架构内部经过固定周期的流水线延迟后结果才送到输出寄存器。数据更新不是在采样时钟的上升沿立即发生的而是采样时钟上升沿之后经过一个tOD时间数据才稳定更新。手册上给的tOD范围通常是3.5ns到6ns左右依温度和供电电压变化。这带来的直接后果是如果FPGA直接用采样时钟的上升沿去打拍AD9226的输出数据很容易采到数据线上的跳变过程而不是稳定的数据电平。当采样率较低比如1MHz时这个问题不明显数据建立时间足够长一旦采样率跑到25M、50M甚至65M数据有效窗口可能只有几纳秒这时候采样沿稍微偏一点结果就是整个波形上出现周期性毛刺或者数据完全错乱。解决思路有两个我实测过都可以用使用采样时钟的下降沿去采数据。AD9226在采样时钟上升沿更新输出数据经过tOD后数据稳定那么在下降沿处数据通常已经稳定了半个周期建立时间相对充裕。更稳妥的做法是用PLL把采样时钟做一次相移一般是移相-90度也就是延迟四分之一周期然后用相移后的时钟去采数据。这样数据窗口中心正好落在采样沿附近无论tOD飘到最小值还是最大值都不会采错。我自己的习惯是用PLL相移方案特别是在采样率超过25M时必须这么做。下降沿方案在低速时省资源、逻辑简单但到了高速段板级走线的延迟、FPGA IO的输入延迟都会被拉进来余量不一定够。AD9226还有一个很容易被忽略的引脚——FLAG它是输入过范围指示。当模拟输入超过满量程范围时FLAG会变高。这个引脚在调试前端增益时非常有用后面调试部分我会再提。AD9226的输出数据格式和接口模式由MODE引脚配置。MODE接低电平时输出CMOS电平、二进制偏移码MODE接高电平时输出LVDS电平、二进制补码。大多数FPGA评估板都用CMOS模式数据引脚直接进FPGA的Bank供电3.3V或2.5V具体看Bank的VCCO设置。代码里拿到数据后如果要做有符号运算需要自己做一次偏移二进制到二进制补码的转换这一步初学者容易漏后面代码里我会写清楚。3. 驱动代码的完整实现时钟生成、数据采样与FIFO缓存下面这段Verilog代码是一套可直接综合的AD9226采集模块包含PLL时钟配置、ODDR输出采样时钟、数据采样打拍、异步FIFO缓存和UART发送链路。为方便阅读我把核心模块拆开说明。3.1 顶层模块框架module ad9226_top( input sys_clk_p, // 差分系统时钟或普通单端时钟 input sys_clk_n, input rst_n, // AD9226 接口 output adc_clk, // 送给AD9226的采样时钟 input [11:0] adc_data, // AD9226 并行数据输出 input adc_flag, // 过范围标识 // UART调试输出 output uart_tx );系统时钟我一般用100M或50M通过PLL同时产生两路时钟一路是内部逻辑时钟clk_logic另一路是采样时钟clk_adc。clk_adc的频率就是AD9226的实际采样率比如25M、50M、65M根据工程需求定。3.2 ODDR原语输出采样时钟很多人习惯直接把PLL输出连到引脚上比如assign adc_clk clk_adc;这种做法在低速时能跑但到了高速段很容易出问题。PLL输出走的是全局时钟网络从BUFG出来之后到IO引脚之间会有额外的时钟延迟而且普通的assign语句综合后只是一个直接连线不具备驱动大负载的能力时钟边沿会被拉坏。推荐做法是把采样时钟通过ODDR原语输出到引脚。ODDR的本质是把一个时钟的上升沿和下降沿组合成完整的输出时钟它能让时钟边沿与IOBIO Block的逻辑对齐边沿质量比直接assign好很多。// ODDR用于产生ADC采样时钟输出 ODDR #( .DDR_CLK_EDGE(SAME_EDGE), .INIT(1b0), .SRTYPE(SYNC) ) u_oddr_adc_clk ( .Q(adc_clk), .C(clk_adc), .CE(1b1), .D1(1b1), .D2(1b0), .R(1b0), .S(1b0) );这段代码的作用是在clk_adc的上升沿输出1在clk_adc的下降沿输出0这样引脚上就看到一个和clk_adc同频率的方波。这个方波就是ADC的采样时钟。3.3 采样时钟相移与数据打拍PLL输出给ODDR的clk_adc是“发射时钟”同时PLL还输出一个相移版本clk_adc_shift专门用来作为FPGA内部采样数据的时钟。具体配置在Xilinx的Clocking Wizard IP里只需要把第二路输出的Phase Shift设为-90度延迟90度即延迟四分之一周期。在50M采样率时一个周期20ns-90度就是5ns。reg [11:0] adc_data_r1; reg [11:0] adc_data_r2; reg adc_flag_r1; always (posedge clk_adc_shift or negedge rst_n) begin if (!rst_n) begin adc_data_r1 12d0; adc_data_r2 12d0; adc_flag_r1 1b0; end else begin adc_data_r1 adc_data; adc_data_r2 adc_data_r1; adc_flag_r1 adc_flag; end end打两拍是跨时钟域处理的标准做法。虽然clk_adc_shift和adc_data在某种意义上“同源”但数据从外部引脚进来再到内部寄存器路径上存在IO延迟打两拍可以进一步降低亚稳态概率。实际调试中只打一拍也能工作但数据偶尔会有毛刺打成两拍更稳。3.4 偏移二进制码转二进制补码AD9226在CMOS模式下默认输出偏移二进制码。所谓偏移二进制码就是把零电平映射到中间值204812位满量程正端是4095满量程负端是0。而在FPGA做信号处理时通常需要的是有符号数所以这里要做一次转换// 偏移二进制码转二进制补码 wire signed [11:0] adc_data_s $signed(adc_data_r2 - 12h800);如果直接把原始数据当成有符号数用零电平会变成-2048而且波形的正负方向是反的后续做FFT、滤波、峰值检测全都会出错。这一步虽然简单但非常关键。3.5 FIFO缓存与UART发送数据以采样率持续产生而UART的发送速率远低于采样率所以中间必须加FIFO做速率匹配。这里用Xilinx FIFO Generator IP配置写时钟为clk_adc_shift50M读时钟为clk_logic50M数据位宽16bit深度1024标准FIFO模式。因为采样时钟和逻辑时钟都是同一个PLL出来的频率相同、相位不同属于同源时钟同步FIFO也勉强能用但异步FIFO更安全后面如果要改成65M采样、100M逻辑频率异步FIFO就不用动结构。wire [15:0] fifo_din {4d0, adc_data_s}; wire fifo_wr_en 1b1; // 连续写入 wire fifo_rd_en uart_rd_en; wire [15:0] fifo_dout; wire fifo_empty; wire fifo_full; wire fifo_valid; fifo_ip u_fifo_1024 ( .wr_clk(clk_adc_shift), .rd_clk(clk_logic), .din(fifo_din), .wr_en(fifo_wr_en), .rd_en(fifo_rd_en), .dout(fifo_dout), .full(fifo_full), .empty(fifo_empty), .valid(fifo_valid) );把12位有符号数据放到16位FIFO里高位补0是示意图写法实际要发到电脑进行波形显示时通常会把符号位扩展到高4位或者保留原始12位数据、用额外一个字节发送状态信息。我常用的做法是低12位放ADC原始数据高4位放{adc_flag, 3b000}这样上位机不仅能看波形还能知道有没有发生过量程。UART发送模块每次读一个16位数据拆成两个字节发出去波特率115200或921600看需求。3.6 XDC引脚约束与set_input_delay引脚约束是AD9226工程里最能体现经验的环节。下面是一份最小可用的XDC约束片段# 系统时钟 create_clock -period 10.000 -name sys_clk [get_ports {sys_clk_p}] # ADC采样时钟由PLL生成不需要在XDC中重新create_clock create_clock -period 20.000 -name adc_clk [get_pins {clk_wiz_0/inst/CLKOUT3}] # AD9226数据引脚 set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS33} [get_ports adc_data[0]] set_property -dict {PACKAGE_PIN T4 IOSTANDARD LVCMOS33} [get_ports adc_data[1]] # ... 其余引脚依原理图分配 set_property -dict {PACKAGE_PIN U4 IOSTANDARD LVCMOS33} [get_ports adc_clk] set_property -dict {PACKAGE_PIN V4 IOSTANDARD LVCMOS33} [get_ports adc_flag]关键是set_input_delay如果不设置Vivado默认外部数据与时钟同时到达时序分析完全不准确。查AD9226手册tOD的最小典型最大值可以查到比如最小3.5ns、最大6ns。在50MHz采样率周期20ns下可以这样约束set_input_delay -clock [get_clocks adc_clk] -max 6.0 [get_ports {adc_data[*]}] set_input_delay -clock [get_clocks adc_clk] -min 3.5 [get_ports {adc_data[*]}]这行约束告诉VivadoADC数据相对于采样时钟沿有3.5~6ns的延迟。加上你用的相移时钟时序分析工具会自动计算余量。这样做完静态时序分析如果出现建立时间违例再加大相移角度如果保持时间违例减小相移角度。这个“试相移”的过程就是调高速ADC采集的常态。4. 引脚分配与板级联调的细节决定模块稳不稳定软件代码写对了板级设计跟不上照样翻车。这里说几个我实测中踩过或者见别人踩过的板级坑。4.1 电平匹配要严格对应AD9226的CMOS输出电平取决于其供电引脚而FPGA对应Bank的VCCO必须一致。很多开发板AD9226的数字部分供电是3.3V那FPGA接收数据的Bank就必须是3.3VLVCMOS33不能放在1.8V Bank里。如果Bank电压不匹配FPGA内部钳位二极管会导通轻则数据错误重则烧引脚。有一天晚上查一个“数据偶尔对偶尔乱”的问题最后发现就是对端Bank的VCCO跳线帽掉了电平不匹配导致。4.2 采样时钟质量直接影响ADC信噪比AD9226对采样时钟的抖动比较敏感时钟抖动大信噪比就下降。用FPGA的ODDR输出普通IO时钟抖动会比专用时钟芯片大不少但做一般的数据采集够用。如果想跑满65MSPS并追求信噪比建议用独立的低抖动有源晶振直接给AD9226的CLK引脚FPGA内部不产生采样时钟只负责接收数据采样时钟和接收数据时钟再通过PLL做相位对齐。代价是PCB上多一个晶振但信号质量提升非常明显。4.3 数据线等长和串阻AD9226的数据线建议在PCB上做等长处理尤其是采样率在50M以上时。等长误差控制在1mm以内基本就没问题。每条数据线上串一个22欧姆左右的电阻可以抑制振铃但要注意串阻会和FPGA输入电容形成低通滤波器串阻太大了会拖慢边沿。22欧姆是我比较常用的值实测在50M采样率下边沿还能保持得很好。4.4 模拟输入前端AD9226的模拟输入可以配置成单端、差分等几种模式具体由SENSE引脚接法决定。针对最常见的单端输入接法输入信号进来后建议加一级运放做阻抗变换和电平搬移把信号电平搬到AD9226的共模范围内。千万不要把超过满量程的信号直接灌进AD9226ADC不是运放过压会损坏输入级。FLAG引脚此时可以用来做保护判断一旦FLAG拉高说明输入过范围FPGA端就该调整前端增益这个机制在自动增益控制里特别有用。5. 我把调试过程踩过的坑按排查链路整理了一遍这部分是本文的重头戏。每个问题都是真实调板过程我尽量把排查思路写出来方便你照着定位自己的问题。5.1 现象ILA里看到的数据杂散得像噪声第一次上板ILAVivado集成逻辑分析仪抓到的adc_data完全像随机数正弦波输入的时候数据也在0到4095之间乱跳。排查链路先看采样时钟是否输出正常用示波器量adc_clk引脚50M方波没问题。再看数据线连接是否正确万用表量原理图到FPGA引脚的通断没问题。最后想到时序问题我把clk_adc_shift的相移从0度改成-90度问题立刻消失波形恢复正常。原因分析直接用采样时钟上升沿打拍正好打在AD9226输出数据更新的窗口上。tOD是3.5~6ns而50M周期是20ns上升沿之后数据还在变化过程中FPGA采到的就是中间态。相移-90度之后采样沿推迟5ns数据已经完全稳定。5.2 现象正弦波变梯形波每隔一段时间跳一下采集正弦波整体波形看起来对但波形上有周期性“跳变”像是采样率不对。排查链路先用信号发生器给一个已知的1kHz正弦波采样率50M理论上一个周期能采到50000个点显示出来应该是平滑正弦。但实际波形在固定间隔出现台阶。后来用ILA抓数据发现数据本身是连续的问题出在UART发送链路我的UART发送模块没有处理好FIFO读使能时序导致上位机拼数据时偶发丢失一个字节数据帧错位。修复方法检查UART发送模块确保每次从FIFO读出的16位数据被完整拆成两个字节发送后再请求下一个数据中间不能有其他逻辑插入。这种“软件层丢帧”的问题用ILA往往是看不到的需要在上位机或者串口调试助手里观察原始字节流。5.3 现象短时间采集正常长时间运行偶发错码系统跑了一段时间后偶然会出现一个错误采样点看起来像是毛刺或偶发干扰。排查链路先排除电源纹波用示波器看ADC供电引脚纹波在可接受范围。然后用ILA长时间抓取发现错误点并不是随机的而是每次都在同一类数据值附近出现。最后定位到是板级串扰AD9226的数据线和采样时钟线在PCB上平行走了一段时钟边沿通过耦合干扰了数据线。修复方法PCB改版时把数据线和时钟线之间拉开距离加地线隔离。如果是已经做好的板子可以用软件规避对ADC数据做中值滤波或者连续性检查异常点剔除。这个方法虽然不治本但在产品原型阶段能应急。5.4 现象FLAG一直为高数据异常输入一个小的直流信号FLAG居然一直为高ADC数据也不对。排查链路用示波器看模拟输入引脚信号幅度没问题。翻手册发现FLAG是否起作用和AD9226的MODE配置有关FLAG的阈值和满量程设置都受SENSE引脚控制。问题出在SENSE引脚悬空导致内部参考电压和满量程设置不对。修复方法把SENSE引脚按手册要求接到正确的参考电平。AD9226的SENSE引脚接法决定了内部参考、外部参考和满量程范围务必定下来之后再写软件否则调了半天其实是硬件配置不对。5.5 现象时序分析报保持时间违例Vivado综合实现后时序报告里出现保持时间违例集中在ADC数据输入路径上。排查方法保持时间违例本质上是数据变化太快触发器采样沿抓不住上一个数据。解决办法有两个方向一是把采样时钟的相移调大让采样沿更晚留给数据保持更多时间二是加set_input_delay -min的值告诉工具数据不会那么早变化给工具更多优化空间。这两个方法我经常配合着用。注意如果是在低温度下测试时出现保持时间违例那大概率是数据线走线太短加上FPGA IO delay太小需要从板级想办法。6. 如果想让模块跑得更快更准下一步可以这样改这一节写给已经跑通基础采集、想进一步优化的人。6.1 从CMOS模式切换到LVDS差分模式AD9226如果MODE引脚配置为高电平数据输出就变成LVDS差分信号。差分信号抗干扰能力强适合长距离传输和更高采样率。代价是FPGA上要用的差分引脚数量翻倍12位数据要24根线引脚资源紧张时不划算。但如果你在做一个模拟前端和FPGA分板的系统差分模式值得考虑。6.2 数据直接进DDR用DMA搬运采集数据一直往UART发速率上限大概在100kSps左右这根本发挥不了AD9226的性能。正确的做法是把数据通过AXI4接口或者简单的DDR控制器写到DDR3/DDR4的大缓冲里采集完一段再批量处理。Xilinx平台可以用MicroBlaze或者直接在逻辑里写一个简易的DDR写控制器把FIFO的数据连续搬到DDR。这样采样率可以跑满50M甚至65M存储深度也不受FIFO限制。6.3 和STM32配合用FMC高速取数如果你的系统是STM32H743加FPGA的结构FPGA可以只做数据采集和格式转换STM32通过FMC接口像读外部存储器一样把数据读走。这种架构把实时性要求高的采样任务交给FPGA把界面、联网、存储这些复杂逻辑交给MPU是一种很成熟的分工模式。需要注意FMC的读时序必须和FPGA内的寄存器访问逻辑严格匹配否则也会出现类似AD9226采数采错的现象——本质都是建立/保持时间问题。6.4 采到的数据要不要再滤波AD9226的采样率较高数据里如果混有带外噪声直接做FFT会把噪声折返到通带内。建议根据信号带宽在FPGA里加一个FIR低通滤波器采样率降到合适的范围再往外发。Xilinx的FIR Compiler IP可以自动生成系数和硬件输入IQ数据后输出滤波结果这个组合我在多个项目里用过稳定可靠。最后再分享一个小技巧在调试AD9226模块时别直接在ILA里看原始波形先给一个已知直流电平确认12位码值和理论值一致再给一个低频正弦波看包络对不对然后才上高速信号。这个从简到繁的验证顺序能帮你把问题快速二分——是采样逻辑的问题还是前端模拟链路的问题一次就定位清楚。另外强烈建议在工程里预留一个调试寄存器用串口命令动态切换PLL相移角度比如-60度、-75度、-90度、-105度这样在调时序裕量时不用每次改代码重新综合直接在板上扫一遍就知道最佳相位窗口在哪个位置。这个方法我用了好几年每次都能省掉至少半天调试时间。本文还有配套的精品资源点击获取