
这个选题我很早就想写了。ADS42LB69这种250MSPS、16bit、双通道的高速ADC输出是标准的LVDS DDRFPGA侧收数据最常见、也最适合入门的就是Xilinx的SelectIO IP核。上个项目里我用它搭了一条完整的采集链路从IP配置、testbench仿真到上板对齐中间踩了不少坑折腾了将近两个礼拜才把数据稳稳当当收回来。这篇文章就把整套流程完整拆开讲一遍给后面要做类似ADC对接的朋友当个参考。先说这篇内容适合谁手里有一块带ADS42LB69或者同类型LVDS DDR接口ADC的板子想在Vivado里把数据收回来但对SelectIO IP核不太熟又不想一上来就手撕原语那你照着我的思路走基本能跑通。已经懂ISERDES/IDELAY原理的也可以看看我在数据拼接和调试阶段的处理方式应该能帮你少走弯路。1. 先把ADS42LB69的接口搞清楚1.1 DDR输出与随路时钟的关系ADS42LB69的核心参数不需要多解释16bit分辨率最高250MSPS采样率双通道1.8V供电输出接口是LVDS。但它输出数据的组织形式有一个关键点它不是每个采样点用16根并行线拍出来而是每个通道的数据被拆到两对LVDS差分线上以DDR方式传输。什么意思呢以单通道为例250M采样率意味着每秒产生2.5亿个16bit的样本。如果直接用SDR方式16根线就是2.5Gbps的线速率普通IO根本扛不住。所以ADC内部把每个样本拆成高8bit和低8bit分别在两对差分线上发送每对线上的数据率变成500Mbps再由DCLK这个随路时钟的上升沿和下降沿各采一次每个沿有效8bit。这样一来DCLK的频率还是250MHz但数据率是500Mbps这就是DDR的含义。这里有个最关键也最容易踩坑的地方ADS42LB69的数据手册时序图里数据的翻转沿和DCLK的边沿是对齐的也就是边沿对齐方式。这意味着每个时钟沿过去的时候数据刚好在跳变如果你拿DCLK的某个沿直接去采样采到的全是数据翻转过程中的中间态复现不出稳定值。而SelectIO IP核里ISERDES的DDR采样逻辑又必须依赖时钟沿来抓数据所以实际工程里必须用IDELAY把数据整体往后延迟一段相位相当于把数据摆到时钟沿之间的稳定区域再交给ISERDES去采。用生活化的方式理解DCLK就像一扇门的开关信号数据是推门出来的人。边沿对齐的意思是门开的那一瞬间人刚好迈过门槛你站在门口拍照根本拍不清人脸。正确做法是让数据晚一点到场等门完全打开、人站在门中间稳定下来这时候按快门才能拍到清晰照片。IDELAY干的就是这件事。1.2 为什么选SelectIO IP核而不是直接写原语很多老工程师习惯直接例化IBUFDS、IDELAYE2、ISERDESE2这些原语觉得可控性强。我不否认原语方案在极端情况下更灵活但对于大多数人用SelectIO IP核是性价比更高的选择。原因有三点。第一IP核把IBUFDS差分缓冲、IDELAYE2可调延迟、ISERDESE2串并转换、还有一套时钟管理逻辑全部封装好了你不必自己去翻原语的端口定义和连接关系出错的概率大幅下降。第二IP核生成的代码里包含了Xilinx官方推荐的连接拓扑比如BUFIO怎么接、BUFR怎么接、复位时序怎么处理这些都是经过验证的比自己拼装原语靠谱。第三IP核在Vivado里能自动帮你做一部分时序约束尤其是IO delay相关的约束这在高速采集场景里能省下不少时间。但是有一点必须明确SelectIO IP核再怎么封装它也只是把物理层的数据采回来、串转并它不会帮你做位对齐、不会帮你判断哪几个bit拼在一起才算一个正确的16bit样本、也不会自动校准IDELAY的最佳延迟值。这些对齐和校准的活必须靠外部逻辑和调试手段来完成这部分我会在第三章和第四章详细展开。2. 在Vivado里把SelectIO IP核调出来2.1 IP配置参数逐项怎么选我用的环境是Vivado 2018.3更高的版本界面大同小异。在IP Catalog里搜索SelectIO会看到SelectIO Interface IP这个条目双击创建Component Name我建议起得明确一点比如adc64_selectio或者adc_ddr_rx别用默认的selectio_0后面在工程里引用时方便很多。配置界面里第一步要选功能方向这里选RXonly因为我们只需要从ADC收数据不需要向ADC发数据。第二个关键选项是Pin Mode选Differential对应LVDS差分输入。Pin Width这里要填16也就是差分数据引脚的数量我在前面说过ADS42LB69每个通道的16bit数据是拆成两对差分线走的所以两个通道总共是4对实际上因为DDR每个沿都有数据16bit拆成两对线后每对线的高低字节在左右半周期各占一个8bit窗口所以数据线总引脚数是16个单端或者说8对差分。这里务必要对着自己的原理图数清楚因为你接的可能只用了单通道也可能双通道全接了。然后是采样类型选DDR。ISERDES Width这一个参数很关键它决定串转并的倍数。可选值一般是2、4、8。我强烈建议第一次调通的时候选8原因我后面讲数据拼接的时候会细说现在先记住DDR模式下选8内部并行时钟就是DCLK的八分之一。DCLK是250MHz的话word clock就是31.25MHz逻辑时序非常宽松方便你先看到正确的数据流。等流程全部跑通、确定没有拼接问题之后再根据吞吐需求改成4也不迟。另外还有一个容易忽略的选项Data Type或者叫DATA_FORMAT一般选BITS就行表示并行输出是位拼接形式。IP核生成之后端口列表里会出现data_in_from_pins_p和data_in_from_pins_n各16bit还有clk_in、clk_div_out、io_reset、clk_reset等端口这些端口的作用和连接方式我们在下一节说。2.2 三种时钟连接方式对比SelectIO IP核的时钟连接是整个配置里最容易出错的地方。IP核心有clk_in这个输入口它接收的是从DCLK差分引脚转出来的单端时钟但它不负责把DCLK的IBUFDS给你例化这个得自己在顶层做。clk_div_out则是IP内部把bit时钟分频之后的word clock输出需要外部接BUFG之后再用。我在实际工程中试过三种接法结果差异很大列个表格给你对比一下。接法clk_in的来源clk_div_out的处理实际效果BUFIO接法IBUFDS - BUFIO - clk_inBUFG后作为逻辑时钟高速下最稳推荐BUFG直接接IBUFDS - BUFG - clk_inBUFG后作为逻辑时钟低速能跑250M DDR时容易花普通全局时钟接IBUFDS - 直接连clk_inBUFG不推荐扇出和skew都不可控BUFIO是专门驱动IO逻辑区域的时钟缓冲器它不走全局时钟网络而是直接连到IO Bank里的ISERDES延迟小、抖动低这正是bit clock需要的特性。所以clk_in一定要用BUFIO来驱动。而word clock因为是给普通逻辑用的必须走BUFG进全局时钟网络否则时序约束过不去。具体到代码顶层里应该这样写IBUFDS #( .DIFF_TERM(TRUE), .IOSTANDARD(LVDS) ) u_dclk_ibufds ( .I (dclk_p), .IB(dclk_n), .O (dclk_s) ); BUFIO u_dclk_bufio ( .I(dclk_s), .O(dclk_bufio) ); BUFG u_wordclk_bufg ( .I(adc_selectio_inst.clk_div_out), .O(word_clk) );需要注意DIFF_TERM必须和板卡实际是否使用了端接电阻匹配。如果板卡原理图上LVDS接收端已经放了100欧姆端接这里设成FALSE否则会重复端接导致信号幅度异常。设反了的现象是数据偶尔出错但时钟频率完全正常非常迷惑人。2.3 复位和IDELAYCTRL的例化不能少SelectIO IP核上电后要复位这个大家一般不会忘。容易忘的是IDELAYCTRL这个模块它是IDELAYE2正常工作的大前提。IDELAYE2内部的可变延迟是根据一个参考时钟来校准的这个参考时钟常数是200MHzIDELAYCTRL就是干这件事的校准模块。我之前第一次用IP核的时候以为IP核内部会自动处理IDELAY结果上板后发现不管怎么改tap值数据完全不受影响后来查了UG471才知道IDELAYCTRL必须自己在顶层例化而且它的参考时钟必须干净、稳定、持续运行。当时我图省事直接把一个普通PLL生成的200MHz时钟接了过去结果复位瞬间IDELAYCTRL有个RDY信号拉高之前tap值全部无效而我的逻辑没有等RDY导致上电初期采到一堆乱七八糟的数据。正确的写法是IDELAYCTRL u_idelay_ctrl ( .REFCLK(clk_200m), .RST(idelay_ctrl_rst), .RDY(idelay_ctrl_rdy) );用法上有个关键要求clk_200m必须是Buffered的时钟走BUFG或者走全局时钟网络不能直接从引脚拉过来就用。RST在上电后拉高几个周期再释放之后等待RDY为高再让采集链路开始工作。这个顺序不能反否则IDELAYE2设置的值在头几百个周期内是不生效的。3. 关键外部逻辑IDELAY、位滑移与数据重排3.1 IDELAY的tap到底该怎么理解IDELAYE2的本质是一根可变的延迟线它把输入信号往后推迟一段时间单位是tap。Xilinx 7系列器件里每个tap大约是78ps总共有0到31共32个档位也就是说最大延迟约2.5ns。这2.5ns看着不大但对于250MHz的DCLK来说一个周期是4ns半个周期才2ns所以IDELAY恰好能覆盖数据相位偏移的一半以上足够把边沿对齐的数据调整到中心对齐。具体调到多少档不能拍脑袋。我调试的时候常用的方法是固定一个稳定的正弦波输入给ADC然后遍历tap值比如从0到31每设一个值就用ILA抓一组数据统计这组数据的标准差或者稳定程度画出曲线找到中间那段“平坦区”取平坦区中间的值作为最终tap。这个方法的原理是当数据足够偏离翻转沿时采样结果应该稳定不变一旦tap调到了数据翻转沿附近采样结果就会在相邻码值之间跳动标准差变大。这个曲线出来之后最佳tap通常一目了然。还有一点要提醒IDELAY连的是串行数据不是并行输出。所以在SelectIO IP核里IDELAY是在ISERDES之前起作用作用对象是每个bit串行流。配置IP核时如果把IDELAY也勾选了那么IP核会自动生成IDELAY控制端口比如idelay_tap_ctrl_in之类的。如果没勾选你又想加延迟就得绕到IP核外面去处理比较麻烦所以我建议一开始就在IP核配置里把IDELAY功能打开幅度扫描也方便。3.2 数据重排从ISERDES并行输出到16bit样本这是整个对接过程里最烧脑的一步我在这里卡了两天。SelectIO IP核的并行输出是按bit slice组织的不是简单地把16根线合在一起。举个例子如果你把ISERDES Width配成了8那么每个数据输入引脚在每个word clock周期会输出8bit16个引脚总共输出128bit这128bit在IP核的端口上怎么排列很多人第一眼根本看不出来。看Xilinx的手册IP核的data_in_to_device端口是按照slice从小到大排的第一个引脚对应的一组8bit放在[7:0]第二个引脚对应的一组放在[15:8]依此类推。也就是说128bit里每8bit一组对应一根LVDS数据线。这时候你需要做的是把每根线的一个8bit字节按照ADC输出格式重新组装成16bit样本。对于ADS42LB69单通道的情况它原本是把一个16bit样本拆成高8bit和低8bit分别放在两根线上传输。那么你从IP核拿到之后需要做的是把这两组8bit拼回去方向取决于ADC的高低位定义。这一步千万别想当然必须用固定电平或者固定码型去验证比如给ADC输入正满幅直流理想输出是0x7FFF你看到0xFF7F还是0x7FFF就能判断高低字节是否反了。这也是我推荐ISERDES Width选8的原因每根线一个word clock周期正好输出8bit两个线拼一起就是完整的16bit样本拼接逻辑特别简单。如果选4每根线一个周期只有4bit凑一个样本需要两个周期的数据交叉合并而且DDR模式下的数据在偶数和奇数周期还不一定按顺序排列新手极容易弄反。3.3 位对齐没有训练序列时怎么办ADS42LB69没有像JESD204B那种带K码的训练序列所以位对齐要靠外部线索来猜。我的做法分三步。第一步先把ADC输入接到一个固定的直流电平上比如接地或者接一个稳定的参考电压这样理论上所有样本应该是同一个值。采集上来的数据如果是一片有规律的重复码型说明位拼接有问题但至少链路是通的。第二步在直流条件下做bitslip扫描。SelectIO IP核内部或者外部逻辑可以控制ISERDES的bitslip每次滑动一个串行bit相当于改变并行输出的位对齐起始位置。我从0开始滑到7每滑一次抓一组数据看哪一次的并行输出正好等于直流电平对应的16bit码型。找到那一次位对齐就算完成了。第三步换成正弦波输入连续采集一段数据画出来看波形是否平滑。如果波形连续、无明显台阶或跳变说明对齐稳定如果波形在某个点突然跳变大概率是位滑移没有完全到位或者IDELAY相位不对回去再微调tap。这套流程虽然土但在没有专用同步头的ADC上非常有效我在项目里反复用了很多次基本没有失手过。4. 仿真验证与上板调试4.1 怎么搭testbench模拟ADS42LB69时序仿真阶段的工作量其实不亚于上板调试。许多新手喜欢直接上板抓数据结果一旦数据不对分不清是硬件问题还是逻辑问题调试效率极低。正确的做法是先写一个精确的ADC行为模型在仿真环境里把数据链路打通确认拼接逻辑和位对齐算法是对的再上板。ADS42LB69的数据输出是边沿对齐所以testbench里要让数据变化沿和DCLK的上升沿、下降沿都对齐。我习惯的写法是这样的reg dclk 0; always #2 dclk ~dclk; // 250MHz时钟 reg [15:0] sample; reg [5:0] addr; wire [3:0] nibble_low; wire [3:0] nibble_high; // 模拟ADS42LB69每个DCLK边沿都输出半个字节 always (posedge dclk) begin data_a sample[7:0]; end always (negedge dclk) begin data_b sample[15:8]; end当然这只是一个概念性片段实际上是模拟DDR传输后某个时刻哪根线上是低8bit还是高8bit需要和你自己板卡的物理连接去对应。关键点是仿真模型里数据翻转沿和DCLK边沿是同一时刻发生的这样才能验证你的IDELAY和采样逻辑是否真的能把不稳定区域避开。这里有个小技巧在testbench里加入一个可变的延迟参数比如#0.2的小延迟让数据翻转稍微晚于时钟边沿一点点模拟真实器件的建立保持时间。如果总在零延迟情况下仿真通过上板后偶发错误多半就是这种边界条件没考虑进去。仿真还需要注意Vivado的IP核仿真模型依赖glbl模块里的初始化逻辑跑仿真前一定要检查工程是否包含了glbl文件否则SelectIO IP核的模型会一直输出高阻。4.2 仿真结果怎么和真实行为对应仿真通过的现象是什么最直接的体现在clk_div_out和data_in_to_device的关系上。在字时钟的上升沿附近data_in_to_device应该是稳定的一组数据而且这个数据在用理想正弦波激励ADC模型时应该能恢复出正弦波的离散序列。我在测试时会在testbench里维护一个期望样本计数器每来一个word clock就检查一次并行输出是否等于当前期望值如果不相等就打印一条信息。这样不用肉眼盯波形跑完一遍仿真看日志就知道有没有问题。还有一种情况比较特殊仿真数据完全对但上了板子就错。这种时候先别怀疑逻辑先检查约束。SelectIO接口的IO约束非常关键尤其要确认data_in_from_pins_p/n这些差分引脚是不是真的被绑到了LVDS标准上。如果IO标准设成了LVCMOSIBUFDS的输出电平行为会异常上板后数据特征和仿真完全对不上。4.3 我踩过的几个坑你提前避开我在实际调这块板子的时候记录了几个典型问题列出来给你参考。现象根本原因解决方式数据全0或全1ISERDES位滑移没做数据错位到一个全零/全一的窗口用直流码型做bitslip扫描数据有时对有时乱IDELAYCTRL的RDY没有等待上电初期配置丢失等RDY为高后再启动采集逻辑采样波形有规律的跳变IDELAY tap值落在数据翻转沿附近遍历tap找平坦区取中值数据高低字节反了两条LVDS数据线拼接顺序反了用0x7FFF/0x8000这类码型测试只有单端信号无差分效果DIFF_TERM设置和板卡不匹配对照原理图检查端接clk_div_out频率不对ISERDES Width和实际DCLK频率换算错误确认DCLK分频系数是Width/2的关系还有一个心得调试时把IP核输出的原始位流用ILA抓下来不要只抓重排后的样本。原始位流能看到哪一根线在哪一个沿上翻转配合ADC数据手册的时序图能快速定位是位对齐问题、时钟问题还是拼接问题。只盯着最终16bit样本信息量太少排查起来非常慢。4.4 板级调试里IDELAY扫描的具体操作最后说一个我比较得意的调试技巧用ILA做IDELAY扫描。先把ADC输入接到一个固定频率、满幅的正弦波然后写一个简单的状态机让它从tap0开始每隔一段时间把tap值加1并且每次修改后维持几百个采样周期让数据稳定下来。ILA同时抓tap值和采集到的样本抓完一轮之后把每个tap值对应的样本标准差导出来用Python或者Excel画个曲线。正常情况下你会看到一条类似山谷的曲线中间一段标准差很低两边标准差高。取低标准差区间最中间的那个tap值就是当前温度和电压条件下的最佳相位点。这个方法我试过很多次比对着眼图肉眼调靠谱得多。注意IDELAY的tap是受温度漂移影响的所以如果板子工作温度变化大建议在逻辑里加一个简单的自适应校准定期测量一段数据的标准差如果变大就微调tap值。虽然做不到闭环自动最优但至少能保证长时间运行不跑飞。整个链路调通之后再回头看这套方案的核心其实就三件事相位对不对、位对不对、字对不对。SelectIO IP核帮我们解决了物理层采样的麻烦剩下的相位调整、位滑移、字节重排是任何一个使用LVDS DDR接口ADC的项目都逃不掉的工作。希望这篇文章能让你少走一段弯路。