简介:面向具备一定FPGA开发经验、希望打通高速ADC采集链路的工程师,这份PDF聚焦Vivado平台下AD9680的Verilog测试程序实现,围绕1G采样率、Lane4的JESD204B接收场景,解决寄存器配置、采样时钟生成与链路同步等关键问题。资源包共1个文件,为111KB的PDF文档,集中呈现AD9680寄存器初始化、基于PLL的稳定时钟配置以及采用状态机组织的JESD204B接收模块等核心代码片段。已有135人学习,文中还穿插时钟相位对齐、SPI配置时序、JESD204B链路同步等调试经验与常见问题处理思路,便于读者对照自身采集系统快速定位配置要点、理解接收流程并提升调试效率,适合在实践中结合具体应用场景调整优化。
1. 从一次链路失锁说起:这套 1G 采样率 Lane4 接收方案要解决什么
调试一块高速采集板时,最让人难受的不是代码编译不过,而是上电后 JESD204B 链路时好时坏:频谱上偶尔干净,重启几次又全是杂散。AD9680 这类 14 位 1GSPS 双通道 ADC,配上 Kintex-7 或 KU040 级别的 FPGA,硬件本身没问题,翻车的往往是配置顺序、时钟相位和 Lane 对齐这三件事。这套基于 Vivado 的 Verilog 测试程序,目标很明确:把 AD9680 配到 1G 采样率、4 Lane 输出,让 JESD204B 接收端稳定拿到数据,并且留出可观测、可复位的调试接口。它适合正在做高速采集毕设、课设,或者要交付一版 ADC 接口验证方案的人,照着结构改参数就能跑。
2. 需求与总体架构:三个子系统如何串成一条数据链
先把需求拆成可验收的条目,再谈模块划分。这个项目不是“写个 SPI 读寄存器”那么简单,它要同时满足采样率、通道数、协议层对齐和可调试性四个约束。我一般会把整份方案画成一条从模拟输入到 FPGA 内部数据流的链路,而不是一上来就堆代码。
2.1 功能需求与非功能需求分开列
功能侧的核心指标只有几条:AD9680 工作在 1GSPS 采样率,双通道 I/Q 数据经 4 条 Lane 以 JESD204B 子类 1 方式送出,FPGA 端完成字符对齐、解扰、多帧同步,最终拼成可分析的样本流。非功能侧往往被忽略,但恰恰是验收时最容易卡人的:上电配置必须一次成功,链路失锁后能自动重训练,关键节点要能被 ILA 抓到,时钟树在 1G 采样率下不能出现超过 50ps 的偏差。
把这两类需求写成表,后面每个模块都能对应回去,答辩时也不容易被问住。
| 需求类别 | 具体条目 | 验收方式 |
|---|---|---|
| 采样率 | 1GSPS,双通道 | 输入单音做 FFT,看频谱位置 |
| 通道数 | Lane4,M=2,F=2 | 读 JESD IP 状态寄存器 |
| 协议 | JESD204B 子类 1,加扰开启 | 抓 K28.5 与 ILAS 序列 |
| 可调试 | 关键信号引出 ILA | 触发 sync 跳变抓波形 |
| 恢复能力 | 失锁后自动重训练 | 人为断链观察恢复时间 |
2.2 三层架构:配置层、时钟层、协议层
总体架构我习惯分成三层,层与层之间只通过明确的握手信号交互,避免“配置还没完成时钟就跑了”这种低级但致命的顺序问题。
配置层负责 SPI 写 AD9680 寄存器,包括采样率模式、Lane 数量、加扰使能、测试模式选择。它必须在上电延迟之后启动,并且每条写操作带回读校验。时钟层用 MMCM 生成 ADC 采样时钟、JESD 收发器参考时钟和内部处理时钟,三者的相位关系要在 XDC 里显式约束。协议层是 JESD204B 接收核心,包含同步状态机、字符对齐、弹性缓冲和错误计数。
三层之间的启动顺序通常是:时钟层先锁定,配置层再写寄存器,最后协议层等 SYNC 信号拉低后进入对齐。这个顺序如果反过来,链路大概率起不来,而且现象很隐蔽——ILA 上看数据一直是不定态。
2.3 模块职责与接口边界
把三层落到具体模块,边界就清楚了。clk_gen输出adc_clk、gt_refclk、rx_core_clk和全局locked;ad9680_config接收locked作为启动条件,输出cfg_done;jesd204b_rx以cfg_done和sysref为输入,输出rx_valid、rx_data和link_status。数据出口接一个 AXIS FIFO,再往后才是 FFT 或存储逻辑。
这样划分的好处是,调试时可以单独把某一层旁路。比如怀疑时钟问题时,先不跑协议层,只用 ILA 看adc_clk和gt_refclk的相位差;怀疑配置问题时,把 SPI 回读值打印到 ILA 上逐条比对。模块边界清晰,排查路径才不会乱。
3. AD9680 配置实现:SPI 状态机与寄存器写入顺序
配置这块看起来只是“发地址发数据”,实际最容易翻车的是时序和顺序。AD9680 的 SPI 时钟空闲电平、上电等待时间、寄存器写入先后,任何一项不满足,芯片都可能不响应或者配到一半卡住。
3.1 SPI 写时序的状态机写法
我一般不用 IP 核做这颗 ADC 的 SPI,手写状态机反而更可控,因为要精确控制片选拉高后的间隔。下面是一个可综合的写任务框架,时钟用 25MHz,远低于手册上限。
// SPI 单次写:先发 1 位读写标志 + 7 位地址,再发 8 位数据 task spi_write; input [6:0] addr; input [7:0] data; integer i; begin cs_n = 1'b0; #40; // 片选建立时间,按手册留余量 for (i = 15; i >= 0; i = i - 1) begin sclk = 1'b0; if (i >= 8) mosi = (i == 15) ? 1'b0 : addr[i-8]; // 首 bit 为写标志 else mosi = data[i]; #20; sclk = 1'b1; #20; end sclk = 1'b0; #40; cs_n = 1'b1; #100; // 寄存器写入间隔,不能省 end endtask这段代码的关键不在循环本身,而在三个延时:片选建立、SCLK 高低电平各 20ns、片选拉高后的间隔。#100这个间隔是血泪经验,省掉之后连续写多个寄存器时,后一条偶尔不生效,回读值还是旧的。实际综合时这些延时要用计数器实现,不能直接写#,上面用任务形式是为了把时序关系讲清楚。
3.2 关键寄存器与 1G/Lane4 参数映射
AD9680 的寄存器很多,但决定这套方案能不能跑起来的就那么几个。0x14 管采样率模式,0x16 管 Lane 配置,0x15 和 0x1A 涉及 JESD 线速率和加扰。下面这张表是我调试时贴在工位上的版本,参数按 1GSPS、Lane4、M=2、F=2 推算。
| 寄存器 | 写入值(示例) | 作用 | 注意点 |
|---|---|---|---|
| 0x14 | 0x10 | 1G 采样率模式 | 需与时钟分频匹配 |
| 0x16 | 0x04 | 使能 Lane4 | bit5-3 控制 Lane 数 |
| 0x15 | 0xA3 | 线速率与分频 | 对应 5Gbps 线速率 |
| 0x1A | 0x01 | 开启加扰 | 必须与接收端一致 |
| 0x5A | 0x02 | 测试模式选择 | 调试递增数据时用 |
写入顺序通常是先时钟相关、再 Lane 相关、最后 JESD 相关。每写完一条,状态机切到回读状态,把读回值和期望值比较,不一致就置cfg_error并停在原地。这个回读校验看起来多余,但实际项目中至少能省掉一半“配置没生效却以为生效了”的误判。
3.3 上电延迟与回读校验
AD9680 上电后需要一段稳定时间才能接受 SPI 操作,手册里给的是几百微秒量级。我一般会在配置模块里放一个 1ms 的计数器,计满才允许状态机启动,并且这个计数器要等locked有效后才开始计。少了这一步,冷启动时 ADC 随机不响应,热重启又正常,现象非常玄学。
回读校验的实现不复杂:写完成后把地址最高位置 1 再发一次,接收 8 位数据,和写入值逐位比较。连续三次不一致就拉高cfg_error,同时把出错的地址锁存到寄存器里供 ILA 观察。这个错误地址寄存器在后期排查时特别有用,能直接告诉你哪条配置没进去,不用一条条翻波形。
4. 时钟管理实现:MMCM 参数与跨时钟域约束
1G 采样率下,时钟不是“生成出来就行”,相位、抖动、跨时钟域关系都要显式处理。这套方案里至少涉及三个时钟:ADC 采样时钟、JESD 收发器参考时钟、内部处理时钟,它们同源但用途不同,约束写法也不一样。
4.1 MMCM 参数推算与输出配置
以 125MHz 输入为例,要得到 500MHz 的 ADC 采样时钟和 312.5MHz 的 JESD 参考时钟,MMCM 的 VCO 需要落在合适区间。我一般先把 VCO 定在 1GHz 附近,再反推分频系数。
// MMCM 例化关键参数:125MHz 输入,VCO=1GHz MMCME2_ADV #( .CLKIN1_PERIOD (8.0), // 125MHz .CLKFBOUT_MULT_F (8.0), // VCO = 1GHz .DIVCLK_DIVIDE (1), .CLKOUT0_DIVIDE_F (2.0), // 500MHz 给 ADC 采样 .CLKOUT1_DIVIDE (4), // 250MHz 给内部处理 .CLKOUT2_DIVIDE (16), // 62.5MHz 给配置逻辑 .CLKOUT2_PHASE (0.0) ) mmcm_adc ( .CLKIN1 (clk_125m), .CLKFBIN (clk_fb), .CLKFBOUT (clk_fb), .CLKOUT0 (adc_clk), .CLKOUT1 (proc_clk), .CLKOUT2 (cfg_clk), .LOCKED (mmcm_locked), .RST (1'b0) );参数不是拍脑袋定的:CLKFBOUT_MULT_F和DIVCLK_DIVIDE决定 VCO,CLKOUTx_DIVIDE决定各输出频率。VCO 太低会增大抖动,太高又可能超出器件范围,1GHz 附近是这颗芯片比较稳的区间。输出相位默认 0,但 JESD 参考时钟那一路如果和 ADC 时钟同源,后面约束里要单独处理。
4.2 同源时钟的相位约束写法
同源时钟最怕的是工具“自作主张”做时序分析,把本来异步的关系当成同步路径来报错。XDC 里我一般会加时钟组约束,把 ADC 采样时钟和 JESD 参考时钟声明为异步。
# 主时钟定义 create_clock -name clk_125m -period 8.000 [get_ports clk_in] # MMCM 输出时钟 create_generated_clock -name adc_clk -source [get_pins mmcm_adc/CLKIN1] -divide_by 2 [get_pins mmcm_adc/CLKOUT0] create_generated_clock -name gt_refclk -source [get_pins mmcm_adc/CLKIN1] -divide_by 4 [get_pins mmcm_adc/CLKOUT1] # 异步时钟组,避免工具误分析 set_clock_groups -asynchronous \ -group [get_clocks adc_clk] \ -group [get_clocks gt_refclk] # JESD 参考时钟到收发器的手动路径约束 set_max_delay -from [get_pins mmcm_adc/CLKOUT1] \ -to [get_pins jesd204b_rx/refclk] 0.5set_clock_groups这条如果漏掉,时序分析器会在两个同源但不同频的时钟之间疯狂报建立保持违例,而且这些违例大多不是真实问题。set_max_delay则是针对参考时钟到 IP 核内部那条路径,工具默认按周期约束,实际需要更紧的延迟控制。这两条约束我一般放在 XDC 靠前的位置,改时钟参数时一起改,避免遗漏。
4.3 时钟质量的上板观测方法
约束写完不代表时钟就干净。1G 采样率下,MMCM 配置不当导致眼图闭合的情况我遇到过不止一次。上板后第一件事不是跑协议,而是用 ILA 抓adc_clk和gt_refclk的相位关系,看两者边沿是否落在预期窗口内。如果偏差超过 50ps,数据错位几乎是必然的。
观测时可以把adc_clk分频后引到普通 IO,用示波器看抖动;更直接的办法是在 ILA 里同时采两个时钟域下的计数器,看跨域采样是否稳定。时钟问题不解决,后面协议层怎么调都是白费。
5. JESD204B 接收实现:同步状态机与 Lane 对齐
协议层是整个方案的核心,也是最容易“看起来通了其实没通”的部分。JESD204B 子类 1 依赖 SYNC 和 SYSREF 两个信号完成链路建立,接收端要依次完成字符对齐、ILAS 解析、多帧同步,任何一步时序不对,数据就是错的。
5.1 同步状态机的状态划分
我把接收状态机分成五个状态:等待 SYNC 拉低、字符对齐、ILAS 捕获、多帧同步、数据接收。每个状态都有明确的超时计数,超时就回到初始状态并置错误标志。
localparam IDLE = 3'd0, CHAR_ALIGN = 3'd1, ILAS_CAP = 3'd2, FRAME_SYNC = 3'd3, DATA_RX = 3'd4; always @(posedge rx_clk or posedge rst) begin if (rst) begin state <= IDLE; align_cnt <= 0; err_flag <= 0; end else begin case (state) IDLE: begin if (!sync_n) begin state <= CHAR_ALIGN; align_cnt <= 0; end end CHAR_ALIGN: begin // 检测 K28.5 字符,连续命中才认为对齐 if (rx_data == 8'hBC) begin align_cnt <= align_cnt + 1; if (align_cnt >= 4) state <= ILAS_CAP; end else begin align_cnt <= 0; end end ILAS_CAP: begin // 等待 ILAS 序列结束,超时回退 if (ilas_done) state <= FRAME_SYNC; else if (timeout) state <= IDLE; end FRAME_SYNC: begin if (frame_locked) state <= DATA_RX; else if (timeout) state <= IDLE; end DATA_RX: begin if (sync_n) // SYNC 拉高表示链路请求重同步 state <= IDLE; end endcase end end状态划分的关键是CHAR_ALIGN里连续命中 K28.5 才推进,单次命中不算数。实际调试中发现,单次命中就跳转的话,噪声引起的假字符会让状态机反复横跳,链路永远稳不下来。ILAS_CAP和FRAME_SYNC都要带超时,否则一旦卡住就没有恢复路径。
5.2 字符对齐与弹性缓冲的配合
字符对齐解决的是 Lane 内 bit 边界问题,弹性缓冲解决的是多 Lane 之间的偏斜。两者顺序不能反:先对齐字符,再进缓冲,否则缓冲里存的是错位数据,后面怎么调都没用。
Xilinx 的 JESD204B IP 核里,弹性缓冲深度和释放时机都有参数可配。我一般把缓冲深度设到 2048 以上,释放条件用 SYSREF 触发,而不是自由运行。SYSREF 必须与 ADC 端同步,延迟值通过 IDELAY 扫描确定,实测落在 12 到 18 个 tap 之间比较稳。扫描逻辑可以写成上电自校准:从 0 到 31 逐个试,link_status变好就锁定当前值。
5.3 错误计数与自动重训练
链路跑起来之后,还要能发现“跑着跑着错了”的情况。我在接收模块里放了两个计数器:disparity_err_cnt统计 8B/10B 解码错误,frame_err_cnt统计多帧边界错误。任一计数器在单位时间内超过阈值,就拉高retrain_req,触发状态机回到IDLE重新走一遍同步流程。
自动重训练这个机制在温度变化或者电源波动时特别有用。实际项目中遇到过环境温度升高后链路偶发失锁,加了重训练之后系统能自己恢复,不用人工断电重启。阈值不能设太紧,否则正常噪声也会触发;我一般按每秒错误数超过 100 来定,具体值要结合现场误码率调。
6. 避坑与排查:五条最容易翻车的点
高速链路调试的坑很集中,下面五条是我在不同板子上反复遇到的,每条按现象、原因、解决写,照着排查能省不少时间。
6.1 上电后 SPI 配置随机不生效
现象是冷启动时 ADC 不响应,热重启又正常。原因通常是上电延迟不够,或者 SPI 片选拉高后的间隔太短,后一条写覆盖了前一条。解决办法是把上电计数器加到 1ms 以上,并且每条写之后留足间隔,同时开启回读校验,出错就锁存地址。
6.2 SYNC 拉低后链路迟迟不对齐
现象是 ILA 上sync_n已经变低,但rx_valid一直不拉高。原因多半是字符对齐条件太松或太紧:太松会被假字符带偏,太紧则正常数据也过不了。解决方法是把连续命中次数设到 4 次,并且用 ILA 抓 K28.5 的实际位置,确认 bit 边界是否真的对齐。
6.3 频谱上出现固定杂散
现象是输入单音后 FFT 上除了主峰还有规律性杂散。原因可能是 Lane 间偏斜没补偿,或者弹性缓冲释放时机不对。解决方法是检查 SYSREF 延迟扫描值,确认多 Lane 数据在缓冲里对齐后再释放,同时用递增数据测试模式先排除配置问题。
6.4 时序分析报大量跨时钟域违例
现象是综合实现后时序报告里adc_clk和gt_refclk之间全是违例。原因是漏了set_clock_groups,工具把同源不同频的时钟当同步路径分析。解决办法是在 XDC 里显式声明异步组,并对参考时钟到 IP 核的路径加set_max_delay。
6.5 温度升高后偶发失锁
现象是常温跑几小时没事,环境温度上来后误码率上升甚至失锁。原因是收发器均衡参数没开或者采样相位余量不足。解决办法是开启自适应均衡,调整RX_OS_CFG的采样相位,并在系统里加自动重训练机制,检测到连续错误就重新同步。
7. 验收与答辩:怎么证明这条链路真的通了
验收这套方案,不能只看“编译通过、板子不报错”,要有可复现的测试步骤和可量化的指标。我一般按四步走:先验配置、再验时钟、然后验协议、最后验数据质量。每一步都有对应的观测点和通过标准,答辩时按这个顺序讲,逻辑最顺。
第一步是配置验收。上电后通过 ILA 读回关键寄存器值,和写入表逐条比对,全部一致才算配置通过。这一步能排除掉大部分“以为配了其实没配”的问题。第二步是时钟验收,用 ILA 同时抓adc_clk和gt_refclk域下的计数器,观察跨域采样是否稳定,再用示波器确认抖动在可接受范围。第三步是协议验收,人为拉低sync_n,看状态机是否在预期时间内走完对齐、ILAS、多帧同步,link_status是否稳定拉高。第四步是数据验收,ADC 切到递增测试模式,用连续数据检查逻辑统计错误数;再切回正常模式,输入单音做 FFT,看 ENOB 是否达到 11bit 以上。
| 验收项 | 观测手段 | 通过标准 |
|---|---|---|
| 寄存器配置 | ILA 回读值 | 与配置表逐条一致 |
| 时钟相位 | ILA + 示波器 | 偏差小于 50ps |
| 链路同步 | 状态机状态跳转 | 超时内进入 DATA_RX |
| 数据连续性 | 递增模式错误计数 | 错误数为 0 |
| 动态性能 | FFT 分析 | ENOB ≥ 11bit |
答辩时最容易被问的是“你怎么证明数据是对的,而不是碰巧看起来对”。我的习惯是准备两组证据:一组是递增模式下的零错误计数,证明通道和位宽没问题;另一组是单音 FFT 的 ENOB 和杂散指标,证明采样率和模拟前端匹配。两组证据互相独立,比只放一张频谱图有说服力得多。
还有一个细节值得提前准备:把失锁恢复过程录成 ILA 波形。人为断开再恢复链路,展示状态机从IDLE重新走到DATA_RX的全过程,这比口头说“支持自动重训练”直观得多。评审看到波形,基本就不会再追问恢复机制的真实性。
从那以后我每次做高速接口方案,都会把“配置回读、时钟观测、协议状态、数据质量”这四步验收固定成检查单,少一步都不交付。这套 AD9680 的测试程序也是按这个习惯整理出来的,参数和状态机框架可以直接拿去改,希望帮到你。
本文还有配套的精品资源,点击获取