FPGA驱动AD9764并行DAC:从时序约束到信号链设计实战

发布时间:2026/9/16 1:32:27
FPGA驱动AD9764并行DAC:从时序约束到信号链设计实战
简介针对AD9764 14位DAC的数据采集FPGA实现此压缩包提供了一套基于Quartus Prime 18.0的Verilog逻辑驱动完整工程文件。内容以AD9764技术手册、Verilog驱动源码以及完整工程结构为主体读者可直接在Quartus中打开工程查看DAC并行数据写入、时钟配合与复位等关键逻辑理解14位数字信号如何经DAC转换为模拟输出适合从事高速数据采集的硬件工程师、FPGA入门进阶者及电子类专业课题设计者参考。压缩包大小约6.93MB文件组织紧凑以手册文档和工程源文件为主要类型查阅起来较为方便。目前已有1048人学习下载。该资源将芯片手册与实际可综合的驱动工程放在一起较散落的技术笔记更有参考价值既可以从顶层了解FPGA与AD9764的连接方式也可以直接复用或修改驱动模块快速搭建自己的DAC采集验证平台有效缩短开发调试周期。1. 为什么AD9764这种14位并行DAC在FPGA信号链里依然值得手写驱动做任意波形发生器或者高速信号重建时第一反应往往是选带SPI或LVDS接口的串行DAC觉得连线少、布线容易。但一旦开始追求波形更新率或者要把DAC放进一个由FPGA主导的闭环采集系统里串行协议的开销就会成为瓶颈。AD9764是14位、最高125 MSPS的电流输出型并行DAC数据准备好、时钟给到模拟端就出结果没有寄存器配置、没有帧同步这在FPGA侧意味着驱动逻辑可以做得非常薄也更容易做时序收敛。这篇分享基于一个可直接打开的Quartus Prime 18.0完整工程把AD9764从手册参数、PCB布局、Verilog驱动、ModelSim仿真到DDS回环验证串起来讲。适合正在做信号源、波形重建或者想搭DACADC回环验证平台的工程师。2. AD9764接口时序与PCB布局从手册参数到规避时钟抖动与电源噪声2.1 引脚定义与并行数据接口的工作方式AD9764的数据接口是14根并行输入线DB0~DB13外加一个时钟输入CLK。它内部没有SPI寄存器也没有配置字DAC在每个时钟上升沿把数据总线上的值锁存并转换为差分电流输出IOUTA和IOUTB。这意味着FPGA侧的逻辑模型很简单保证数据在时钟上升沿前稳定模拟输出就是一个与数据码成正比的电流。这种架构的优点是延迟确定、无协议解析缺点是对时序和电源敏感所以工程文件里把技术手册放在一起是很有用的对参数时直接翻PDF。参数典型值/范围说明分辨率14 bit数据总线DB0~DB13DB13为最高位最大更新率125 MSPS时钟频率上限实际使用留20%裕量输出形式差分电流IOUTA/IOUTB满量程由外部电阻RSET和参考电压共同决定供电电压3.3V / 5VAVDD与DVDD分开供电引脚布局不同建立时间约35 ns典型数据到输出稳定的时间决定FPGA侧约束目标接口协议无纯并行无寄存器配置时钟上升沿直接锁存数据手工设计时最容易忽略的是建立时间这条。它不只是芯片本身的指标还包含数据总线从FPGA引脚到DAC输入引脚之间的PCB走线延迟。数据总线走线长度不一致高位和低位到达的时间就不一样反映到输出端就是毛刺和杂散。所以布线时除了等长还要在FPGA内部做输出寄存器把数据切换的时刻统一对齐到同一个时钟沿。注意建立时间和保持时间同时要看只约束建立时间、不约束保持时间编译报告里容易出现时序违规。2.2 规避时钟抖动与电源噪声的PCB布局要点DAC的时钟质量直接决定输出信号的谐波纯度。时钟抖动会让采样点前后偏移等效成输出噪声尤其在正弦波重建时表现为相位噪声和SFDR恶化。这里有一个常见的反直觉结论在14位DAC系统中PCB布局对SFDR的影响往往比芯片选型更大。我一般会遵守三个布局要点对应adc/dac电路设计中高频出现的问题。第一时钟与数据总线物理隔离。DAC的CLK走线要远离DB0~DB13总线最好包地两侧打过孔地墙。数据总线会持续翻转容性耦合到时钟线上就变成了抖动源如果时钟走线跨过分割平面回流路径断裂问题更严重。第二模拟电源和数字电源分开处理。AVDD和DVDD在DAC引脚处用磁珠或PI型滤波器连接去耦电容尽量靠近电源引脚电容地过孔不要和数字地共享。第三差分输出IOUTA/IOUTB紧贴参考地层走线长度保持一致避免差分不平衡引入偶次谐波。这三点做好后面很多调试问题都不会出现。2.3 输出端的I/V变换与滤波选型AD9764输出是电流型需要外部电路变换成电压。常见做法是差分转单端IOUTA和IOUTB各接一个电阻到地通过差分放大器或变压器合成单端信号。用运放做I/V变换时运放的-3dB带宽至少要取DAC时钟频率的10倍否则高频分量被衰减波形边沿变缓。比如125 MSPS的DAC运放带宽低于1 GHz的话输出正弦波到几十MHz就开始明显掉幅。如果用变压器输出次级中心抽头接地可以配合一个简单的LC低通滤波器。滤波器截止频率按信号最高频率的1.2倍预留不要刚好卡在信号频率上否则带内平坦度会很差。整个模拟输出部分建议与FPGA的数字区域保持至少5mm的间距敏感信号不要跨越DAC下方的地层缝隙。3. Verilog驱动AD9764双缓冲、时钟沿对齐与上电时序的实现细节3.1 驱动模型与两级寄存器的时序考量AD9764只有一个时钟和数据总线驱动逻辑的核心是时序对齐。FPGA内部数据源通常是DDS相位累加器、FIFO或ROM查表这些模块的输出往往带组合逻辑路径。如果不加处理直接把组合逻辑结果送到DAC引脚数据在时钟上升沿附近可能还在跳变建立时间和保持时间都很难满足。常见做法是加两级寄存器第一级在时钟上升沿锁存数据源输出切断组合逻辑路径第二级在时钟下降沿更新DAC数据总线让DAC在下一个时钟上升沿采样时数据已经稳定了接近半个时钟周期。3.2 Verilog驱动代码与关键参数说明module ad9764_driver #( parameter DATA_WIDTH 14 )( input wire clk, // 系统时钟如 100MHz input wire rst_n, // 低有效复位 input wire en, // 输出使能 input wire [DATA_WIDTH-1:0] data_in, // 待转换数据 output reg [DATA_WIDTH-1:0] dac_data, // 到 DAC 的数据总线 output wire dac_clk // 到 DAC 的时钟 ); // 第一级上升沿打拍隔离上游组合逻辑路径 reg [DATA_WIDTH-1:0] data_buf; always (posedge clk) begin if (!rst_n) data_buf {DATA_WIDTH{1b0}}; else data_buf data_in; end // 第二级下降沿更新总线保证上升沿采样时数据稳定 always (negedge clk or negedge rst_n) begin if (!rst_n) dac_data {DATA_WIDTH{1b0}}; else if (en) dac_data data_buf; else dac_data {DATA_WIDTH{1b0}}; end // DAC 时钟直接用系统时钟不额外做相移 assign dac_clk clk; endmodule代码逻辑分三块说明。第一级寄存器data_buf的作用是消除数据源到输出引脚之间可能存在的组合逻辑延迟时序上以时钟上升沿为基准。第二级寄存器在时钟下降沿把data_buf的内容送到dac_data此时数据总线在时钟低电平期间切换到DAC采样上升沿还有大约半个周期的稳定时间。en信号作为总开关关闭时输出全0复位时同样输出全0避免上电瞬间DAC输出随机码。dac_clk直接使用系统时钟没有使用PLL相移原因是在100MHz这种常规频率下下降沿打拍的半个周期裕量已经足够PLL相移反而引入新的时钟偏移变量。如果实际项目中DAC时钟需要分频比如100MHz系统时钟、DAC工作在50MHz不要在驱动模块里做计数器分频而是用PLL或者Quartus里的AltPLL生成50MHz时钟分频后的时钟与数据总线之间仍然要保持时序约束。DAC时钟不要用always块内的时钟翻转来描述那会生成组合逻辑时钟在FPGA里容易产生毛刺。3.3 上电时序与PLL锁定后的数据使能并行电流型DAC没有复杂的寄存器配置流程但上电时序仍然值得处理一下。FPGA配置完成后IO引脚在配置期间处于高阻态DAC输入电压不确定可能输出异常电流。常见做法是在Verilog里用PLL锁定信号作为使能条件reg pll_locked_r; always (posedge clk or negedge rst_n) begin if (!rst_n) pll_locked_r 1b0; else if (pll_locked) pll_locked_r 1b1; end wire en_sync pll_locked_r en;pll_locked_r把PLL的locked信号同步到系统时钟域避免异步信号直接驱动使能逻辑。en_sync作为最终的dac_data输出使能保证FPGA时钟稳定之前数据总线一直为0。实际工程里如果数据源是FIFO还要把FIFO的空标志加入使能链避免读出无效数据数据源是DDS相位累加器时累加器复位后需要先清零几个周期再启动因为累加器刚释放复位时输出的第一个相位点通常不是波形起始点。4. Quartus Prime 18.0工程搭建引脚约束、ModelSim仿真与上板排错4.1 新建工程、器件选型与引脚分配Quartus Prime 18.0下新建工程时器件选型和引脚分配的优先级比代码本身更高。打开工程后先确认器件型号Altera系老器件选Cyclone IV E新一点的设计可以用Cyclone 10 LP两者的DAC接口电平标准都支持3.3V LVTTL/LVCMOS。AD9764的数据引脚输入是TTL/CMOS电平FPGA的IO bank如果供电3.3V直接连线即可如果bank供电1.8V必须加电平转换不能直接接。引脚分配建议在QSF文件里写而不是完全依赖图形界面方便版本管理。分配前先对照板卡原理图确认DAC引脚与FPGA的物理Pin一一对应分配时还要注意数据总线尽量落在同一个IO bank减少跨bank导致的片内走线长度差异。4.2 时序约束Tcl脚本的写法set_location_assignment PIN_AA1 -to dac_clk set_location_assignment PIN_AB1 -to dac_data[0] set_location_assignment PIN_AC1 -to dac_data[1] set_location_assignment PIN_AD1 -to dac_data[2] # 其余引脚按原理图填写 set_instance_assignment -name IO_STANDARD 3.3-V LVTTL -to dac_data[*] set_instance_assignment -name IO_STANDARD 3.3-V LVTTL -to dac_clk set_instance_assignment -name CURRENT_STRENGTH_NEW MAXIMUM -to dac_data[*] # 数据输出路径延迟约束目标4ns set_max_delay -from [get_registers data_buf[*]] -to [get_ports dac_data[*]] 4.0这段脚本做三件事。set_location_assignment把逻辑信号映射到具体引脚dac_data[*]通配符可以同时约束14根数据线不用逐条写。IO_STANDARD指定3.3V LVTTL电平标准与AD9764输入电平兼容。set_max_delay约束从data_buf寄存器到DAC引脚的输出延迟不超过4ns对应AD9764手册中建立时间典型值再留一定裕量。注意get_registers里写的是data_buf而不是dac_data因为数据路径的起点是驱动模块内部第一级寄存器第二级寄存器dac_data到引脚的路径很短约束第二级意义不大。注意IO_STANDARD如果写成LVTTL但在bank上却以1.8V供电编译会报电压不匹配错误这种问题在多人协作的项目里很常见。4.3 ModelSim仿真Testbench与波形检查点工程跑仿真推荐用ModelSim-Intel FPGA Starter EditionQuartus Prime 18.0自带版本可以联动。Testbench的关键点是产生时钟、递增数据激励以及在波形窗口检查两个关键时间点dac_data的跳变沿和dac_clk上升沿之间的距离。timescale 1ns/1ps module tb_ad9764_driver; reg clk 0; reg rst_n 0; reg en 0; reg [13:0] data_in 0; wire [13:0] dac_data; wire dac_clk; ad9764_driver dut ( .clk(clk), .rst_n(rst_n), .en(en), .data_in(data_in), .dac_data(dac_data), .dac_clk(dac_clk) ); initial #10 rst_n 1; always #5 clk ~clk; // 100MHz always (posedge clk) begin if (rst_n) begin en 1; data_in data_in 1b1; end end initial begin #1000 $finish; end endmodule激励逻辑里data_in每个时钟周期加1产生一个从0到16383反复循环的数字斜坡信号。仿真时重点看dac_data相对于dac_clk的时序正常情况下dac_data在clk下降沿之后跳变跳变点距离下一个clk上升沿大约5ns这段就是AD9764的建立时间窗口。如果波形显示dac_data在clk上升沿附近跳变说明代码中的打拍方式与预期不符需要检查是用了同一个边沿触发还是复位释放存在问题。4.4 上板调试三个高频坑与排查方法上板后最常遇到的现象是输出波形不对或者完全没有输出。Debug时用SignalTap II抓内部信号比用示波器直接量DAC引脚高效得多特别是在数据总线位宽较大时。三个坑按出现频率排序第一个坑是ModelSim的License报错报错信息类似17.1 error: failure to obtain a verilog simulation license。这种情况通常是环境变量里ModelSim的License路径错误或者系统里同时安装了多个版本的ModelSim导致冲突。解决方法是检查LM_LICENSE_FILE环境变量确认指向Quartus安装目录下ModelSim对应的license文件并把旧版本卸载干净。第二个坑是SignalTap抓到的数据看起来正确DAC输出却有毛刺。原因多半是时序约束没生效数据总线在DAC采样沿附近还在变化。此时打开编译报告里的Timing Analyzer看set_max_delay约束是否成功应用未约束的话输出路径延迟可能超过10ns在100MHz下必然出问题。第三个坑是PLL尚未锁定时DAC被灌入了随机数据表现为上电瞬间输出一个大尖峰。这个问题在代码里加pll_locked_r使能即可解决但如果复位逻辑和使能逻辑共用了同一个异步复位信号要注意检查复位释放的同步处理。我习惯在驱动模块里再打一拍复位释放信号然后把en_sync和复位释放一起参与组合逻辑。5. 进阶用法DDS波形生成与ADC/DAC回环直方图验证时钟噪声5.1 用相位累加器生成正弦波数据流有了稳定的DAC驱动模块下一步就是往数据总线上喂有意义的波形。自写DDS比用NCO IP核更直观相位累加器加一个正弦ROM表就能工作reg [31:0] phase_acc; wire [13:0] sin_addr; wire [13:0] sin_data; always (posedge clk) begin if (!rst_n) phase_acc 32d0; else phase_acc phase_acc freq_word; end assign sin_addr phase_acc[31:18]; // 取高14位做查表地址 // 正弦ROM表使用Quartus的ROM IP或手工初始化文件输出14位无符号正弦值freq_word是频率控制字32位累加器在100MHz时钟下频率分辨率约为0.023Hz输出频率由freq_word决定。取phase_acc的高14位作为ROM地址等价于把正弦波相位按每周期16384个点切分。ROM表的数据位宽和DAC位宽一致都为14位。频谱上这种截断方式会产生相位截断杂散如果要做高SFDR输出可以把累加器扩宽到40位以上ROM地址取高16位。5.2 DACADC回环的直方图噪声验证技巧把AD9764的输出端经过衰减网络接到片内ADC输入端FPGA同一颗芯片同时采集ADC数据就构成一个完整的信号回环。这个结构用来评估整个信号链的噪声水平非常直观。最简单有效的验证方法是输出直流电平DAC写入固定码ADC连续采集1024个点统计码值分布直方图。理想情况下码值集中在一个码内分布宽度1~2个LSB属于正常如果直方图展宽到4个LSB以上优先怀疑DAC电源噪声或者时钟抖动而不是ADC的问题。在Verilog里做直方图统计可以在PC上处理也可以直接在片内用一个数组统计。工程上我一般把ADC原始数据通过UART或者JTAG读回PC用Python脚本画直方图比在FPGA里写统计逻辑快得多。如果读回的数据出现周期性起伏检查DAC输出端的滤波是否到位回环里建议在DAC和ADC之间加一个无源低通滤波器。5.3 用SFDR对比定位时钟路径问题这个方法特别适合没有频谱仪的场合仅靠回环数据就能判断DAC时钟路径是否合格。让DAC输出一个满幅正弦波ADC采集一段数据后做FFT计算基波幅值与最大杂散幅值的比值就是SFDR。分别在两种情况下测量一是DAC时钟来自FPGA的PLL输出二是把FPGA的某个普通IO引脚直接作为DAC时钟源。普通IO输出的时钟边沿抖动大SFDR通常会比PLL低3dB以上。如果两者测出来几乎一样说明时钟路径基本没有瓶颈问题多半出在数据总线长度差或者电源去耦上。这个方法本质上就是adc/dac信号链调试里最常用的差分对比法不需要昂贵的仪器也能定位到具体模块。我一般还会顺手测一组不同freq_word下的SFDR数据观察杂散频率是否随DDS输出频率线性变化。相位截断杂散的频率是DDS频率字的函数是固定值而时钟耦合杂散则定格在某个固定频率上。这两种情况在FFT图上很容易区分下次再有人问SFDR差是因为DAC还是时钟拿这个FFT图就讲清楚了。配合一个ADC回环和一段FFT脚本整条DAC信号链的健康状态基本都能评估出来。本文还有配套的精品资源点击获取