
1. 项目缘起为什么要在FPGA里折腾ADC和DAC如果你接触过嵌入式或者信号处理对ADC模数转换器和DAC数模转换器这两个词肯定不会陌生。它们就像是数字世界和模拟世界之间的翻译官ADC把现实世界连续变化的电压、电流信号变成单片机或处理器能理解的0和1DAC则反过来把数字代码变回模拟信号去驱动喇叭、屏幕或者电机。通常我们会用一颗专门的芯片比如TI的ADS系列ADC或者ADI的DAC芯片通过SPI或I2C总线连接到MCU上让MCU去读取数据或者发送指令。那么问题来了既然有现成的芯片为什么还要在FPGA里“设计”ADC和DAC这不是多此一举吗这恰恰是很多初学者甚至一些有经验的工程师容易产生的误解。这里的“设计”核心不是去用硅片造一个物理的转换器而是利用FPGA的可编程逻辑和高速并行处理能力去实现ADC和DAC芯片与数字系统尤其是高速数字系统之间高效、可靠、灵活的接口与控制逻辑。举个简单的例子你手头有一颗超高速的ADC芯片比如采样率1GSPS每秒10亿次采样输出是高速串行数据流比如JESD204B协议。你的主控MCU哪怕是STM32H7系列的SPI或并口根本跟不上这个速度。这时候FPGA的价值就凸显出来了它可以轻松实现JESD204B这种复杂高速串行协议的收发器SerDes完成数据对齐、解帧、时钟域转换然后把整理好的数据通过DMA批量送到DDR内存里或者进行实时的数字下变频DDC处理。FPGA在这里扮演的是一个高速数据搬运工和预处理引擎的角色。反过来对于DAC也一样。你想产生一个复杂的雷达波形或者通信调制信号算法在CPU里算好了是一串数字序列如何以极高的速率和精度稳定地送给DAC芯片FPGA的并行I/O和精准的时序控制能力让它成为连接数字信号源和高速DAC的不二之选。所以这个标题“基于FPGA的ADC和DAC设计”其真正的内涵是如何利用FPGA来构建与高速、高精度数据转换器协同工作的数字接口子系统并解决其中关键的时序、同步、数据处理和系统集成问题。这是数字系统设计特别是无线通信、雷达、仪器仪表等高端应用中的核心技能。接下来我就以一个从业者的视角拆解这里面的门道。2. 理解核心ADC/DAC与FPGA交互的关键技术点在开始动手写代码之前必须把几个核心概念和它们之间的关系吃透。否则你调出来的系统要么性能不达标要么根本跑不起来。2.1 时钟一切同步的基石时钟问题是FPGA与数据转换器联调中最常见、也最头疼的坑。这里主要分两方面1. 采样时钟采样率时钟对于ADC这是驱动其进行模数转换的时钟对于DAC这是驱动其输出新数据点的时钟。这个时钟的质量直接决定了系统的信噪比SNR、无杂散动态范围SFDR等核心指标。它需要非常低的抖动Jitter。通常这个时钟由一颗高性能的时钟芯片如Si5338、AD9528或晶体振荡器XO/VCXO产生同时供给ADC/DAC和FPGA。注意千万不要想当然地用FPGA内部的PLL去直接产生高速ADC的采样时钟。FPGA内部时钟网络的抖动通常比专用时钟芯片大一个数量级会严重劣化ADC的性能。FPGA的PLL更适合用于产生逻辑控制时钟和接口时钟。2. 数据接口时钟这是ADC输出数据或FPGA输出数据到DAC时伴随数据一起的同步时钟。常见的有并行LVDS接口通常有一个数据时钟DCLK和一个帧时钟FCLK。DCLK是双倍数据速率DDR的每个上升沿和下降沿都传输数据。FPGA需要调用专用的IDDR原语来正确采集。高速串行接口如JESD204B/C这是当前高速数据转换器的绝对主流。它不再有单独的时钟线而是通过SerDes在数据流中恢复时钟CDR。这就需要FPGA支持高速收发器GTP/GTX/GTH/GTY等并且理解复杂的链路建立、同步SYNC~和通道对齐过程。时钟域交叉CDC是随之而来的必然问题。ADC数据进入FPGA时在一个时钟域例如来自ADC的DCLK而你的处理逻辑如滤波、存储可能在另一个更低的系统时钟域。必须使用异步FIFO来进行安全的数据传递并且FIFO的深度要仔细计算以应对时钟频率差异和抖动带来的不确定性。2.2 数据接口与协议这是FPGA逻辑设计的主要战场。并行接口相对简单直观但速度受限布线复杂。你需要严格满足ADC/DAC数据手册上的建立时间Setup Time和保持时间Hold Time要求。在FPGA中这通过约束输入延迟set_input_delay和输出延迟set_output_delay来实现。例如一个14位并行输出的ADC你需要在FPGA端定义14个或28个如果是DDR输入引脚及其约束。JESD204B 协议这是必须攻克的堡垒。它把多路高速并行数据流串行化通过少数几对差分线传输大大简化了PCB布局。其核心步骤包括代码组同步CGS接收端FPGA发送同步请求发送端ADC发送连续的K28.5字符接收端调整字对齐。初始通道对齐ILA发送端发送带有多帧和通道对齐字符的序列接收端确保所有通道Lane的数据在同一个多帧边界开始。用户数据传递完成同步后开始传输实际的ADC采样数据。在FPGA中Xilinx和Intel都提供了JESD204B的IP核。但使用IP核绝不意味着可以闭着眼睛通过。你需要根据ADC的配置L数、M数、F数、S数、N‘数正确配置IP核并理解其输出的AXI-Stream数据格式。调试时需要熟练使用ILA集成逻辑分析仪抓取SerDes的原始数据、状态机信号这是定位“链路建立失败”或“数据错位”问题的唯一手段。2.3 电源与模拟考虑虽然FPGA是数字部分但你必须关心模拟部分的电源。高速ADC/DAC通常需要非常干净的模拟电源AVDD和数字电源DVDD。PCB上必须做好电源分割使用磁珠或0Ω电阻进行隔离并布设充足的去耦电容。一个常见的坑是ADC的噪声性能很差排查半天发现是FPGA的I/O电源VCCO噪声通过数据线耦合到了ADC的敏感模拟部分。确保ADC的模拟地和数字地AGND, DGND在芯片下方单点连接良好。3. 实战架构一个典型的双通道高速数据采集系统设计光说不练假把式。我们以一个具体的、中等复杂度的场景来勾勒出完整的FPGA设计框架设计一个双通道、14位、125MSPS采样率的ADC数据采集系统并通过千兆以太网将数据实时上传到PC。系统框图如下概念描述[模拟信号输入] - [抗混叠滤波器] - [ADC芯片 (如AD9643)] - [14位并行LVDS数据DCLK] | V [FPGA (如Xilinx Artix-7)] | [时钟管理] - [外部125MHz低抖动时钟源] | V [数据接收与同步模块 (IDDR, 位拼接)] | V [跨时钟域处理 (异步FIFO, 125MHz - 100MHz)] | V [数据处理 (可选DDC, 滤波 打包)] | V [存储与传输控制 (DDR3控制器 或 以太网MAC/IP)] | V [千兆以太网 PHY] - [PC]3.1 FPGA内部模块详细拆解1. 数据采集与位拼接模块 (adc_interface.v)这是最底层的模块。ADC输出14位数据可能采用双倍数据速率DDR在DCLK的上升沿和下降沿传输。在FPGA端我们需要为每个数据线实例化IDDR原语。// 示例Xilinx 7系列 FPGA中使用IDDR采集一位DDR数据 IDDR #( .DDR_CLK_EDGE(OPPOSITE_EDGE), // 与ADC输出模式匹配 .INIT_Q1(1b0), .INIT_Q2(1b0), .SRTYPE(SYNC) ) IDDR_inst ( .Q1(data_pos[i]), // 上升沿数据 .Q2(data_neg[i]), // 下降沿数据 .C(dclk), // 来自ADC的DCLK .CE(1b1), .D(adc_data_p[i]), // ADC差分数据正端 .R(1b0), .S(1b0) );将14根数据线分别这样处理然后在下一个DCLK周期将data_pos和data_neg拼接成一个完整的14位采样值。这里有一个关键细节需要根据ADC数据手册确认字节序Bit Order和输出格式偏移二进制还是补码。拼接错了数据看起来全是噪声。2. 时钟域转换模块 (cdc_fifo.xci)我们使用FPGA的Clocking Wizard IP产生一个100MHz的系统时钟sys_clk用于逻辑处理和通信。ADC数据在125MHz的DCLK域必须安全地过渡到100MHz的sys_clk域。最可靠的方法是使用FPGA的FIFO Generator IP核生成一个异步FIFO。写时钟wr_clk连接DCLK。读时钟rd_clk连接sys_clk。写数据din拼接后的ADC数据可以加上一个时间戳或通道号。读数据dout在sys_clk域稳定输出的数据。深度设置这是经验值。由于125MHz到100MHz是慢时钟读快时钟写理论上有数据积压风险。深度需要至少为(写速率 - 读速率) * 最大突发长度 / 读时钟周期。通常我会设置一个较深的FIFO如1024并监控其空/满标志在实际测试中观察是否会溢出。3. 数据处理与打包模块 (data_packer.v)从FIFO读出的数据可以直接用于后续处理。在我们的例子中目标是通过以太网传输。千兆以太网MTU通常是1500字节。我们需要将ADC数据打包成以太网帧。定义一个简单的帧头包含帧序号、通道号、采样点数等信息。计算一帧能容纳的采样点数(1500 - 帧头 - CRC) / 2字节每个14位采样占2字节≈ 740个采样点。为了对齐可以取512或1024点。该模块在sys_clk下工作当检测到FIFO中的数据量超过一包所需时开始组包。组包时需要将14位数据扩展到16位高位补零或符号扩展以便按字节操作。4. 以太网传输模块 (udp_tx.v / tri_mode_ethernet_mac)我们可以用Xilinx的三模以太网MAC IP核或者自己实现一个简单的UDP发送器。将数据打包模块输出的字节流加上IP头、UDP头填充前导码和FCS通过GMII或RGMII接口发送给外部PHY芯片。关键点需要正确实现ARP协议让PC能发现FPGA设备。或者更简单在PC端设置静态ARP条目。性能考量千兆以太网理论带宽125MB/s我们的双通道ADC原始数据率为2通道 * 125MSPS * 2字节/采样 500MB/s远超以太网带宽。因此这个架构必须在数据传输前进行降速比如在数据处理模块中加入抽取滤波器或者只传输触发后的片段数据。否则FIFO一定会爆满。3.2 约束文件 (.xdc) 的编写要点约束文件是保证物理实现正确的关键很多时序问题源于约束不当或缺失。# 时钟约束 create_clock -name sys_clk -period 10.000 [get_ports sys_clk_p] # 100MHz系统时钟 # 假设ADC的DCLK是125MHz由时钟芯片驱动FPGA的某个时钟输入引脚 create_clock -name adc_dclk -period 8.000 [get_ports adc_dclk_p] # 125MHz # 输入延迟约束告诉FPGAADC数据在DCLK边沿前后多久是稳定的 # 这需要根据ADC数据手册的t_{SU}和t_{H}值计算并加上PCB走线延迟估算 set_input_delay -clock [get_clocks adc_dclk] -max 2.500 [get_ports {adc_data_p[*]}] set_input_delay -clock [get_clocks adc_dclk] -min 0.500 [get_ports {adc_data_p[*]}] # 对于差分信号需要指定差分对和IO标准 set_property DIFF_TERM TRUE [get_ports adc_dclk_p] set_property IOSTANDARD LVDS_25 [get_ports adc_dclk_p] # ... 其他ADC数据端口同理 # 以太网PHY接口约束以RGMII为例 set_property IOSTANDARD LVCMOS33 [get_ports {rgmii_txd[*]}] set_property SLEW FAST [get_ports {rgmii_txd[*]}] set_property DRIVE 8 [get_ports {rgmii_txd[*]}] create_clock -name rgmii_tx_clk -period 8.000 [get_ports rgmii_txc] # 125MHz # 设置输出延迟满足PHY芯片的时序 set_output_delay -clock [get_clocks rgmii_tx_clk] -max 1.000 [get_ports {rgmii_txd[*] rgmii_tx_ctl}] set_output_delay -clock [get_clocks rgmii_tx_clk] -min -1.000 [get_ports {rgmii_txd[*] rgmii_tx_ctl}]4. 调试与避坑从链路建立到数据验证设计完成编译通过下载到板子这才是“噩梦”的开始。以下是几个典型的坑和排查思路。4.1 现象ADC数据全为零或固定值排查链路电源和时钟检查用示波器测量ADC的模拟电源、数字电源、参考电压是否正常。测量采样时钟CLK/-的幅值、频率、抖动是否达标。没有稳定的时钟一切免谈。配置接口检查很多ADC需要通过SPI进行上电配置如增益、测试模式、输出格式。用逻辑分析仪抓取FPGA发出的SPI波形确认片选、时钟、数据线是否正确配置寄存器值是否按预期写入。一个常见错误是SPI的时钟极性CPOL和相位CPHA设置与ADC要求不匹配。数据接口物理层检查对于LVDS接口用示波器测量一对数据线Dx/-和时钟线DCLK/-的差分信号。看幅值是否足够通常350mV眼图是否张开。如果信号质量差检查PCB布线是否等长终端匹配电阻是否正确。FPGA采集逻辑检查在Vivado/Quartus中添加ILA抓取ADC数据输入引脚adc_data_p上的原始信号。如果这里能看到跳变的数字信号说明物理链路通了问题在FPGA内部的IDDR或后续逻辑。如果这里信号是固定的问题在ADC或前端配置。4.2 现象JESD204B链路无法同步SYNC~信号一直为低排查链路时钟与参考时钟JESD204B要求收发双方有一个共同的参考时钟Device Clock。确认FPGA的GT参考时钟和ADC的参考时钟来自同源且频率正确。用频谱仪测量参考时钟的相位噪声。链路速率与配置核对FPGA IP核中的线速率Lane Rate、编码8B/10B、L/M/F/K参数是否与ADC配置完全一致。一个参数不对链路就无法对齐。SYNC~信号SYNC~是接收端FPGA发给发送端ADC的同步请求信号。在ILA里抓取这个信号。正常情况下FPGA上电后应主动拉低SYNC~请求同步当收到有效的CGS序列后再拉高SYNC~。如果FPGA一直拉低SYNC~说明它没收到有效的K28.5字符检查SerDes的RX是否锁定了信号RXCDRLOCK。眼图与均衡对于长距离或高频信号需要在FPGA的GT设置中调整发送预加重Pre-emphasis和接收均衡Equalization以改善信号质量。可以通过IBERTXilinx或Eye ViewerIntel工具进行扫描和优化。4.3 现象数据存在周期性错误或跳变排查思路跨时钟域CDC问题这是最隐蔽的bug之一。检查所有跨时钟域的路径是否都通过了异步FIFO或同步器处理。在Vivado的时序报告中查看是否有unsynchronized的CDC路径警告。即使代码仿真通过CDC问题在硬件上仍可能间歇性发生。亚稳态Metastability在异步FIFO的读指针和写指针同步过程中如果时钟频率比值不合适或FIFO深度太浅可能导致亚稳态传播造成数据丢失或重复。确保使用工具提供的、经过验证的异步FIFO IP核。电源噪声用示波器的FFT功能测量ADC模拟电源引脚上的噪声频谱。开关电源的开关频率及其谐波可能会耦合到信号中在频谱上表现为固定的杂散峰。解决方法是在电源路径增加LC滤波或使用线性稳压器LDO为模拟部分供电。接地环路如果系统由多个板卡或机箱组成不合理的接地会引入50Hz工频及其谐波干扰。确保单点接地并使用屏蔽线缆传输模拟信号。4.4 数据验证从时域到频域拿到数据后如何判断它是对的时域验证给ADC输入一个已知的直流电压看转换后的数字码是否与理论值吻合Code (V_in - V_ref-) / (V_ref - V_ref-) * (2^N - 1)。输入一个低频正弦波用Python或MATLAB绘制波形看是否光滑、幅值正确。频域验证关键这是评估ADC动态性能的金标准。使用高纯度的正弦波信号源通常频率与采样率互质如10MHz信号用125MHz采样采集一段数据如8192点做FFT分析。计算信噪比SNR信号功率与除直流和谐波外所有噪声功率的比值。低于数据手册值说明时钟抖动大或模拟前端噪声大。查看无杂散动态范围SFDR信号幅值与最大杂散谐波或非谐波幅值的差值。SFDR低通常意味着存在非线性失真检查ADC的输入驱动运放是否工作在线性区。查看频谱底噪均匀抬高的底噪可能是量化噪声或随机噪声如果存在特定的杂散峰则需要根据频率反推干扰源如电源开关频率、时钟耦合、数字开关噪声。5. 性能优化与进阶思考当基本功能调通后下一步就是思考如何优化和扩展。5.1 资源与时序优化流水线设计数据处理链路上的每一个复杂操作如乘法、滤波都应进行流水线化插入寄存器提高系统最大运行频率Fmax。使用DSP Slice和Block RAMFPGA内部的DSP48单元是专为乘加运算优化的比用逻辑LUT实现效率高得多。大规模的数据缓冲使用Block RAM而不是分布式RAMLUTRAM后者容量小且时序差。合理的时序约束除了基本的时钟和I/O约束对于内部逻辑路径可以尝试添加虚拟时钟Virtual Clock和多周期路径Multicycle Path约束。例如一个需要多个时钟周期才能完成的计算可以将其设置为多周期路径避免工具做过度的、不必要的时序优化从而节省资源并可能提高布通率。避免拥塞Congestion如果布局布线后时序不达标查看拥塞报告。高拥塞通常意味着逻辑布局太集中。可以通过区域约束Pblock将相关模块如数据接口、处理、存储约束在芯片的不同区域或者优化代码结构如减少扇出过大的信号来缓解。5.2 系统级集成与处理器协同FPGA擅长高速流处理而ARM或PC擅长复杂控制和上层应用。如何结合AXI4总线互联现代FPGA SoC如Zynq, Cyclone V SoC内部集成了硬核处理器。通过AXI4-Lite总线处理器可以像访问内存一样配置FPGA内的寄存器控制采样开关、设置滤波参数。通过AXI4-Stream或AXI4-Full总线FPGA可以将海量数据直接送入处理器的DDR或从DDR读取数据送给DAC。关键在于设计好AXI接口的从机或主机模块并处理好数据流控。软核处理器对于纯FPGA可以使用MicroBlaze或Nios II软核。虽然性能有限但用于系统管理、命令行交互、简单协议栈如Modbus非常合适。让软核通过AXI-Lite控制FPGA数据通路将复杂的数据流处理交给并行的硬件逻辑。5.3 面向更高速的挑战JESD204B与DDR内存当采样率进入GSPS级别数据带宽达到数十GB/s架构必须升级。JESD204B Subclass 1这是支持确定性延迟的关键。它要求收发双方共享一个高精度的SYSREF信号以对齐所有通道和器件的内部多帧时钟。FPGA端需要正确连接SYSREF到GT参考时钟引脚并在IP核中使能Subclass 1模式。调试时需要验证链路建立后的延迟是否固定。高速数据缓冲如此高的数据率片内Block RAM远远不够。必须使用外部高速内存如DDR3或DDR4。这就需要调用FPGA的MIGMemory Interface GeneratorIP核实现一个高效的内存控制器。设计难点在于DDR内存访问有延迟且是突发传输。你需要设计一个巧妙的仲裁调度器平衡ADC数据写入和处理器读取或后续处理模块读取的带宽避免内存访问冲突导致数据丢失。通常采用“乒乓缓冲”结构将内存划分为多个块轮流写入和读出。从并行LVDS到高速串行JESD204B从简单的FIFO到复杂的DDR内存控制器从单一的采集功能到与处理器协同的异构计算基于FPGA的ADC/DAC系统设计是一个深度和广度都极大的领域。它要求工程师不仅懂数字电路和Verilog还要理解模拟信号链、高速电路设计、信号处理算法和系统架构。每一次调试都是对理论知识和工程经验的综合考验。但当你看到干净的频谱、稳定的波形从自己设计的系统中产生时那种成就感也是无可替代的。这条路没有捷径就是多看数据手册、多写代码、多调板子、多分析问题。希望这些从实际项目中总结的点滴能帮你少走一些弯路。