做过多通道高速采集的朋友应该都有体会,FPGA端真正麻烦的往往不是算法本身,而是ADC芯片的配置、高速串行数据的接收、跨时钟域的处理这些"地基活"。这篇聊聊我用Vivado做的一个AD9653四通道Verilog工程,采样率125M,涉及SPI配置和LVDS接收,整个代码工程是从零写的,不是拿厂商例程改的,跑通后稳定采集了几个月。内容会包含架构拆解、每一块的原理和关键代码思路,以及我在调板过程中踩过的坑,希望对正在做类似ADC采集的人有帮助。
1. 项目背景与整体架构:为什么要用多片AD9653拼四通道
先说需求来源。某次接到的任务是要做一套四通道同步采集系统,输入信号带宽几十兆,动态范围要求比较高,所以ADC位数不能低于14位。板卡面积和功耗都有严格限制,不能用太夸张的高端器件。综合对比了ADI家的几款ADC,最终定了AD9653,采样率125M,14位分辨率。这里有个细节容易踩坑:单颗AD9653是双通道的,要凑四通道就得用两片,所以FPGA工程里所有逻辑都是围绕“两片ADC、四个通道”设计的。
选AD9653还有一个原因:它输出的是LVDS接口,而且自带数据时钟DCO,属于源同步传输。源同步虽然对布线长度和时钟质量有要求,但比JESD204B那种高速串行协议简单得多,不需要复杂的链路训练,也不依赖专用参考时钟,在中低规模采集板卡上非常实用。整个信号链可以从头到尾控制在合理的功耗和成本范围内。
系统的整体架构分成三块:
- 模拟前端与ADC:两片AD9653,每片两组差分输入,输出LVDS数据线和DCO时钟。
- FPGA采集与缓存:LVDS接收、串并转换、并行数据对齐、异步FIFO、AXI4-Stream输出。
- 上位机传输链路:通过USB3.0桥接芯片把数据包上传,这部分我直接用现成IP,工程里不做重点。
这个结构最大的好处是每一层都能独立调试:先把SPI配置跑通,再抓LVDS波形,再调数据对齐,最后才接FIFO和上位机。千万别想着一次全通,分层验证能让你少掉一半头发。
关键参数表:
| 模块 | 参数 | 说明 |
|---|---|---|
| 采样率 | 125 MSPS | ADC输出DCO频率=125M |
| 分辨率 | 14 bit | 每位通过LVDS差分对传输 |
| 通道数 | 4 通道 | 由两片双通道AD9653构成 |
| LVDS形态 | 源同步并行 | 每一通道一组数据线+DCO |
| FPGA | 7系列中端器件 | 使用BUFIO、ISERDESE2、IDELAYE2 |
2. SPI配置模块:寄存器时序与状态机设计
2.1 AD9653的SPI协议基础
AD9653的寄存器配置走三线/四线SPI,具体由CFG_PIN决定,我用的三线模式:CSB、SCLK、SDIO三根线。数据帧固定32位,包括8位指令和24位数据。指令部分最高位是读/写标志,接着是两位无关位,低五位是寄存器地址。数据部分则是16位地址的填充位,实际寄存器数据在低8位。真正写寄存器时,发完8位指令后跟一个无关字节,然后是8位有效数据。
刚开始写SPI驱动时最容易犯的错是纠结高8位还是低8位。实际上很简单:有效寄存器数据在24位payload的最低8比特,前面16位全部填0。例如写地址0x00、数据0x01,指令就是写标志加地址00,payload就是0x000001。SPI时序和普通EEPROM差不多:CSB拉低,SCLK时钟沿采样,SDIO按高位先出,写完一帧拉高CSB。
2.2 上电初始化状态机设计
配置状态机我写了单独模块,核心状态包括上电等待、寄存器写入序列、锁相环稳定等待、配置完成。之所以要拉一个上电等待,是因为ADC的AVDD和DVDD上电后有稳定时间,我配置为等待5ms。寄存器写入序列用了一个case语句生成地址和数据,例如写入0x00、0x01芯片软复位,然后通过0x08配置内部基准、0x0A设置LVDS输出电流等。初始化完成后置位cfg_done信号,LVDS接收模块才开始工作。
这里有个很实用的经验:配置完成后,一定要把SPI模块的时钟停掉或让片选保持高电平。我遇到过ADC正常输出,但SCLK上持续有毛刺导致偶发寄存器误写的情况。后来在cfg_done后把SCLK拉到固定电平、CSB拉高,问题立刻消失。如果条件允许,还可以回读寄存器验证,AD9653支持读操作,三线模式下读帧同样是32位,把SDIO切到输出即可。
2.3 配置不生效的排查手段
SPI配置不生效时,不要一上来就怀疑代码,先用逻辑分析仪抓CSB、SCLK、SDIO三根线,确认帧格式对不对。最常见的两个问题:一个是SCLK空闲极性搞反,AD9653要求SCLK空闲为低、数据在上升沿被采样;第二个是片选低电平期间SCLK多翻转了一次,造成CSB和SCLK的相对位置不对。这两个问题用示波器一目了然。
3. LVDS接收链路:BUFIO加ISERDESE2的双沿采样实现
3.1 为什么LVDS接收要单独处理
AD9653在125M采样率下,14位数据加上DCO,每通道相当于15对LVDS信号。如果直接把差分对接到普通IO,靠内部逻辑时钟去采,时序根本收不拢。因为差分数据和DCO在板级走线上存在skew,而且DCO的频率是125MHz,逻辑时钟要采到全部数据位,时序余量极小。
稳妥的方案是使用FPGA内部的专用IO资源:DCO差分时钟经过IBUFDS转单端后,由BUFIO直接驱动ISERDESE2的采样时钟;数据线经过IBUFDS之后,由IDELAYE2做精细延迟,再进入ISERDESE2做串并转换。BUFIO的优势在于它是从IO Bank直接取时钟,不经过全局时钟网络,延迟最小,而且可以保证与数据在同一个时钟区域内对齐。
3.2 ISERDESE2参数配置与位宽展开
ISERDESE2我配置成双数据率模式,factor=8。这样每个周期14位数据被拆成两个并行拍:第一个DDR采样,第二个DDR采样,各自得到7位,合起来就还原了14位。实际代码里每个通道例化8个ISERDESE2,分别对应14个数据位和1个DCO的DDR位?不对,DCO不做串并转换,只需要触发FIFO或作为BUFIO时钟源。数据位才是串并转换的对象。
代码里最核心的部分是IDELAYE2的tap值调整。AD9653手册给出的数据与DCO之间的建立保持时间窗口约为几百皮秒,而FPGA内部IDELAY一个tap的延迟大约在30到80皮秒,具体由IDELAYCTRL的参考时钟决定。调试时我先把所有数据位的tap设为固定值0x20,先看能不能解出基本帧,再用vio核动态微调,直到误码降为零。如果使用自动训练,就把对齐使能放到约束中,复位释放后自动扫描。
3.3 双沿采样与bit对齐的根本问题
ISERDESE2拿到的是差分线上的串行比特流,必须正确知道哪一个bit是LSB,哪个是MSB。AD9653默认输出格式中DCO的上升沿对应高字节位或低字节位,不同通道之间还有可能因为LVDS极性接反造成整体bit翻转。这就需要在接收端做一个训练对齐过程:ADC输出已知的测试pattern,FPGA端检测pattern的起始位置,然后通过ISERDESE2的bitslip信号移动位边界。
实际工程中,我在上电初始化后,先配置AD9653输出全0、全1、交替pattern,FPGA端检测每个通道1的计数是否等于零或满量程,这一步能快速判断LVDS数据线有没有错位、短路、接反。确认无误后再配置成正常数据模式,检查每通道数据高14位是不是有效。如果发现所有通道都错了固定几位,基本就是bitslip没有对齐;如果只是某一通道错,那就查那个通道的硬件连接。
4. 多片ADC同步与跨时钟域数据流
4.1 为什么四通道数据会不同步
两片AD9653虽然有同一个采样时钟输入,但每片内部的采样时钟相位不一定完全一致,上电复位的时刻也不一样,所以四通道样本在时间上天然存在偏差。这个问题对要求多通道严格同步的应用非常致命。解决办法有两种:一是配置AD9653的采样同步功能,让两片ADC同时复位采样时钟;二是在FPGA端,把四通道数据统一纳入同一个FIFO读时钟域,依靠FIFO写侧的使能对齐逻辑,保证每个通道的样本序号一致。
我采用的是第二种。四通道LVDS接收模块各自解出并行数据,然后统一打拍进入各自的独立FIFO,FIFO的读时钟是同一个,读使能由统一的samples_valid控制。这样即使各通道写入FIFO的时刻有微小差异,读出时也是按同一节奏读,做到样本级对齐。需要特别注意,FIFO初始化时一定要将所有通道的复位释放时刻对齐,否则复位移相会造成一次永久性的偏置。
4.2 异步FIFO与AXI-Stream打包
每通道数据是14位,加符号位或状态位之后凑成16位,四个通道合起来一个采样时刻就是64位。用异步FIFO把数据从125M采样时钟域搬到传输接口时钟域,例如100M的USB3接口时钟。FIFO深度我留了4K,采样率长期高于传输速率时负责缓冲,突发模式下不要让它写满。
AXI-Stream打包的逻辑比较简单:四个通道的数据并成一行,附带帧头字节,上位机端按帧头定位采样点即可。这里有个值得分享的细节:FIFO的读写指针在跨时钟域时用的是格雷码,Vivado的标准异步FIFO IP会自动处理,但如果用源码,务必保证写指针读侧同步和读指针写侧同步,否则空满标志很容易误判,导致丢数或错位。
4.3 跨时钟域的最佳实践
在处理LVDS接收数据时,任何跨时钟域信号都不能直接打一拍了事。ISERDESE2输出的并行数据是DCO时钟域的,FIFO写侧是采样时钟域,FIFO本身完成了异步转换,这是正确的做法。但不要因为偷懒,把并行数据直接接到普通寄存器去采,那时序必然出问题。我在工程里用了一个标志位来对齐通道:当一线通道解出帧头后,拉高sop信号,其他通道的sop通过fifo内的特殊字来识别,然后在读侧统一做一次流水同步。
5. 约束文件与板级要点:管脚分配、电平标准和时钟区域
5.1 LVDS管脚分配的硬性规则
在Vivado中约束LVDS管脚,必须成对使用,而且对IO Bank的电平标准要正确设置。7系列FPGA中LVDS电平支持HP Bank和HR Bank,工作电压也不一样:HP Bank是1.8V,HR Bank是2.5V或3.3V。AD9653的LVDS输出供电是DVDD,典型1.8V,所以我把ADC的LVDS输出连接到了FPGA的HP Bank,电平标准选LVDS_25或LVDS_18需确认外部端接。
更关键的是DCO时钟必须分配到Bank内的MRCC或SRCC管脚,否则没法用BUFIO驱动同一Bank内的ISERDESE2。如果DCO和任意一组数据线在同一个Bank,用相邻的MRCC管脚最好。我在第一版PCB上因为布局关系把DCO分配到了SRCC,导致BUFIO无法输出到所有需要的数据IOB,后来被迫改板。FPGA选型时务必先查封装Pinout,把所有LVDS对分配到有足够采样时钟资源的Bank。
5.2 xdc约束示例
我整理一段简化的xdc约束:
set_property PACKAGE_PIN AH5 [get_ports adc_dco_p] set_property PACKAGE_PIN AG5 [get_ports adc_dco_n] set_property IOSTANDARD LVDS_25 [get_ports adc_dco_p] set_property IOSTANDARD LVDS_25 [get_ports adc_dco_n] set_property PACKAGE_PIN AJ5 [get_ports adc_data_p[0]] set_property PACKAGE_PIN AK5 [get_ports adc_data_n[0]] set_property IOSTANDARD LVDS_25 [get_ports adc_data_p[*]] set_property IOSTANDARD LVDS_25 [get_ports adc_data_n[*]]当然最终约束要根据实际PCB的引脚连接来定。这里要提醒的是,LVDS输入不需要额外加DIFF_TERM,如果加了,反而可能影响共模电压,除非器件手册明确要求片内端接。
5.3 时钟区域规划
采集既然涉及125M的DCO,建议所有LVDS I/O尽量分布在相邻的IO Bank,DCO的MRCC所在的Bank能覆盖的数据位数量是有限的,Bank越大能接收的LVDS位越多。如果四通道数据分别在不同Bank,每个Bank需要有独立的DCO资源。一个常见做法是每片AD9653的通道数据放在同一Bank,DCO走该Bank的MRCC,这样两片ADC用两个独立Bank,避免跨Bank时钟域问题。
6. 仿真验证与实测调板踩坑记录
6.1 写仿真模型逼真一点
纯靠上板调试验证太慢。我用了一组与AD9653行为相近的仿真模型,模拟LVDS时序:产生125M DCO,14位数据按帧输出,随机模式。测试台里从SPI配置完成开始延时,再打开LVDS输出,检查ISERDESE2解出的并行数据是否和输入的pattern一致。仿真阶段的重点不是数据值完全一致,而是确认时序窗口能否满足开发需求。比如DCO频率是125MHz,周期8ns,仿真模型里故意加入0.3ns的数据延迟抖动,看FPGA端采样是否有误码。
6.2 板级实测中的三个路径问题
第一个是LVDS数据线某一根虚焊。现象是单通道数据偶发大跳变,用Vivado的debug插入ILA抓数据,发现某一bit长时间表现为固定电平,用万用表量LVDS对地电压发现浮空,补焊后正常。
第二个是DCO时钟的极性反了。ISERDESE2在DDR模式下,上升沿采到的是数据位的高半部分,下降沿是低半部分,如果DCO极性反了,解出来的14位数据就会变成一组交错错位的值。这个可以靠bitslip恢复,但更规矩的做法是查PCB上DCO和数据的走线长度和极性,尽量从源头上避免。
第三个是数据对齐失败时值看似正常。有些pattern不敏感,比如满量程正弦输入时,即使bit偏移一位,低两位变化也不明显。所以调试时最好用满速率的伪随机码或方波作为输入,只有这种信号才能暴露位对齐问题。
6.3 几个可以复用的调试技巧
一是使用Vivado的VIO核在运行时动态修改IDELAYE2的tap值,这比每次改了约束重新综合快得多。把tap值做成可写寄存器,扫描范围0到31,逐档看ILA中的数据是否稳定,就能快速找到最佳tap区间。
二是使用集成逻辑分析仪ILA,触发条件设置为帧头匹配,抓取四个通道的并行数据。注意ILA本身也会消耗时钟资源,数据率太高时不要长时间全速抓取,用少量深度采样即可。
三是把SPI配置结果回读校验做成自动流程。上电后先写配置,再读关键寄存器,比如0x08、0x0A等,比对写入值与回读值,不一致就自动设错误标志,方便批量测试和老化验证时快速定位。
7. 整套工程的实际工程量与后续改进空间
整个工程代码量不算大,核心模块包括SPI配置、LVDS接收、FIFO、AXI4-Stream输出,总计约三千行Verilog,综合后占用资源比较低,7系列中端器件完全够用。最花时间的其实是前期的接口验证和板级调试,代码本身在架构设计合理的情况下并不会反复修改太多。
个人认为这套架构最大的扩展方向是可以平滑迁移到更高采样率和更多通道。比如换成双通道250M的ADC,只需要把ISERDESE2的factor参数和FIFO位宽调整一下,整体架构不用动;如果要扩到八通道,那就把四通道例化为一组,再做一个上层同步模块,数据流路径几乎可以复用。另一个改进方向是增加动态IDELAY训练逻辑,目前我是在初始化阶段手动扫描tap值,后续可以做成上电自校准,完全不需要人为干预。
如果大家手头正在做类似的ADC采集项目,建议先把SPI配置、LVDS接收、数据对齐这三块单独验证通过,再往下做传输协议。地基稳了,后面所有调试都省心。我这套工程里裁剪掉了一些板级特定配置和上位机相关代码,但核心逻辑是可以直接沿用的。如果评论区有做同步多通道采集的朋友,欢迎交流你们在LVDS对齐上的做法,我这边后来也整理了一些关于DCO采样窗口的测试数据,有时间会再单独写一篇。