FPGA驱动AD9764 DAC实战:从Verilog接口时序到Quartus工程验证

📅 发布时间:2026/9/16 1:35:32
FPGA驱动AD9764 DAC实战:从Verilog接口时序到Quartus工程验证
简介针对AD9764这款十四位高速数模转换器该资源是一套完整的FPGA逻辑驱动与数据采集参考工程主要面向FPGA开发工程师、数字系统学习者以及从事信号采集处理的工程人员帮助解决DAC芯片与FPGA之间的接口时序设计、并行数据写入、时钟配合等实际问题。压缩包整体约6.93MB内部包含AD9764技术手册以及基于Quartus Prime 18.0创建的Verilog工程文件源码与配置可直接打开查看省去自行搭建工程环境的麻烦。该资源已有1048人学习可作为设计参考被反复翻阅。使用这套工程可以减少底层时序调试时间让开发者将精力集中在算法与系统功能上。通过学习其中驱动逻辑读者能够理解DAC的数据锁存、输出建立时间、复位与使能控制等关键细节进而迁移到波形发生器、任意信号源、高速数据采集系统等应用场景也可作为课程设计、毕业设计或项目预研的起点。1. 直接用并行数据线怼上 AD9764 就能出波形想简单了把 FPGA 的 14 根 IO 接到 AD9764 的 DB0~DB13再给个时钟看着像是最简单的 DAC 驱动。真正上板以后你会发现示波器显示的波形毛刺满天飞正弦波变成了一级一级的台阶甚至数据手册标称的 125 MSPS 在自家板子上连 1 MHz 都跑不稳。问题基本出在两个地方一是数据时序没满足手册的建立保持时间二是时钟信号和电源噪声把 DAC 的积分非线性直接放大成了可见失真。AD9764 是 ADI 公司经典的 14 位、125 MSPS 并行电流输出型 DAC常见于波形发生、数字中频和闭环控制前端。FPGA Verilog 驱动它的工作量不大但需要把接口时序、时钟管理、约束文件和验证环境串起来才能让这颗 DAC 跑出接近手册的性能。我这篇博文就按「数据手册读哪些参数 → Verilog 驱动怎么写 → Quartus Prime 18.0 工程怎么搭 → 板上怎么验证」这条完整落地的路径来讲适合正在用 Cyclone IV / MAX 10 之类 Altera 器件做信号源或者调制器前端的人。2. AD9764 数据手册里必看的接口参数与 FPGA 连接方式2.1 引脚功能与并行数据通道AD9764 的并行接口一眼看上去只有三组信号14 位数据总线 DB0~DB13、一个时钟输入 CLOCK、一个模式选择 MODE。数据手册的时序图会告诉你DAC 在 CLOCK 上升沿锁存数据所以在 FPGA 内部必须把输出数据对齐到时钟沿而不是用组合逻辑直接输出。引脚方向功能说明FPGA 连接建议DB13~DB0输入14 位并行二进制数据对应 FPGA 普通 IOCLOCK输入数据锁存时钟上升沿有效用 PLL 输出或全局时钟引脚MODE输入1双缓冲模式0直通模式普通 IO按速率需求接固定电平REFIO输入/输出内部 1.2V 参考电压接 0.1uF 去耦电容到地FSADJ输入满量程电流调整引脚通过 RSET 电阻接地IOUT / IOUTN输出互补电流输出接运放或电阻负载这里有一个新手必踩的坑MODE 引脚。很多例程直接把 MODE 接地让 DAC 工作在直通模式数据一进总线就立刻转换。直通模式下只要 FPGA 的 IO 翻转稍微不同步毛刺就会直接反映到输出电流上。我一般会把 MODE 拉高用双缓冲模式让数据先进入第一级锁存器再靠 LOAD 信号统一送入 DAC 寄存器时序可控性明显更好。FSADJ 引脚决定了满量程输出电流公式是 IFS 32 × VREFIO / RSET。VREFIO 典型值 1.2VRSET 用 2kΩ 电阻时满量程电流约 19.2mA需要在 IOUT 引脚接运放转换成电压。如果 RSET 选得太大电流太小会牺牲信噪比选太小又可能超出运放线性范围2kΩ 作为起步值是稳妥的。2.2 FPGA IO 标准与驱动能力设置AD9764 的数据输入兼容 3.3V 和 5V 逻辑Altera FPGA 常见的是 3.3V LVCMOS 或 2.5V LVCMOS。Quartus 里需要用 Assignment Editor 显式指定每个引脚的电平标准默认的 3.3V 不一定能驱动较长的走线。set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to dac_data[0] set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to dac_clk set_instance_assignment -name CURRENT_STRENGTH_NEW 8MA -to dac_data[0] set_instance_assignment -name SLEW_RATE 2 -to dac_data[0]注意SLEW_RATE选项。默认的慢摆率在走线过长时会让数据位的跳变时间不一致建立时间和保持时间都会恶化但摆率太快会带来更大的地弹噪声。对于 AD9764 这种 125 MSPS 级别的 DAC8 mA 驱动电流加中低摆率通常是比较平衡的组合。IO 内部也不要配置成开漏模式DAC 的并行数据总线是推挽输入开漏输出反而会导致上升沿缓慢。2.3 时钟抖动对 SNR 的影响与 PCB 布局AD9764 的 CLOCK 引脚可以接收单端 CMOS 时钟也可以接收差分 PECL 时钟。对 14 位分辨率而言时钟抖动控制在几十皮秒量级以内才不会把 SNR 压下去。FPGA 内部 PLL 输出的时钟抖动在大多数情况下够用前提是你别从普通 IO 引一根长线再绕回来而是复用 Altera 的专用时钟输出引脚或者将时钟信号从靠近 DAC 的引脚引出。PCB 布局方面有三个要点直接决定 DAC 的底线性能。第一每个电源引脚都要就近放置 100nF 和 1uF 去耦电容AVDD 和 DVDD 分开走线模拟地和数字地在 DAC 下方单点相连。第二数据总线和时钟线尽量等长特别是时钟线要走阻抗受控的微带线避免在 DAC 输入端产生反射。第三IOUT 和 IOUTN 是电流输出走线尽量短负载电阻和运放靠近 DAC 放置反馈电阻不要离运放太远。3. Verilog 驱动逻辑实现直通、双缓冲与 FIFO 对接3.1 最基础的寄存器直通驱动如果只是想让 DAC 先动起来一个简单的寄存器锁存就能满足需求。把 FPGA 内部待发送的数据先用 D 触发器打一拍再连接到 DAC 数据总线避免组合逻辑冒险产生的毛刺被 DAC 锁存。module dac14_direct #( parameter DATA_WIDTH 14 )( input wire clk, input wire rst_n, input wire [DATA_WIDTH-1:0] data_in, output reg [DATA_WIDTH-1:0] dac_data, output reg dac_clk ); always (posedge clk or negedge rst_n) begin if (!rst_n) begin dac_data {DATA_WIDTH{1b0}}; dac_clk 1b0; end else begin dac_data data_in; dac_clk ~dac_clk; end end endmodule这段代码里dac_clk是对输入时钟直接取反生成的。这样的优点是 DAC 在输入时钟的下降沿看到数据变化在上升沿锁存某种程度上弥补了输出延时不固定的问题。但这只是一个简化方案实际工程里我建议直接让 PLL 把同一时钟分别送到 FPGA 寄存器和 DAC 时钟引脚尽量减少时钟偏斜。clk用 FPGA 内部 PLL 输出dac_clk用相同频率的 IO 输出数据在时钟上升沿前后保持稳定即可。3.2 双缓冲模式下的写时序状态机MODE 接高电平后DAC 内部变成一个双寄存器结构数据先写入输入寄存器LOAD 信号上升沿再把数据传送到 DAC 寄存器并开始转换。这种结构在数据需要并行更新时特别有用例如同时驱动多片 DAC 做正交调制。module dac14_double_buf #( parameter DATA_WIDTH 14 )( input wire clk, input wire rst_n, input wire wr_en, input wire [DATA_WIDTH-1:0] data_in, output reg [DATA_WIDTH-1:0] dac_data, output reg load ); localparam IDLE 2b00; localparam STORE 2b01; localparam PULSE 2b10; reg [1:0] state; reg [DATA_WIDTH-1:0] buf_data; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; buf_data {DATA_WIDTH{1b0}}; dac_data {DATA_WIDTH{1b0}}; load 1b0; end else begin case (state) IDLE: begin if (wr_en) begin buf_data data_in; state STORE; end end STORE: begin load 1b1; state PULSE; end PULSE: begin dac_data buf_data; load 1b0; state IDLE; end default: state IDLE; endcase end end endmodule这个状态机的逻辑是外部把 14 位数据写入buf_data第二个周期把load拉高一个时钟宽度第三个周期在load下降沿把buf_data送入dac_data并输出给 DAC。注意load的宽度保持恰好一个时钟周期不要用组合逻辑产生否则在高速时钟下容易产生毛刺。如果系统需要流水线吞吐可以把 IDLE 状态去掉让写入操作每个周期都可以接受新数据代价是 DAC 的更新频率会有两个周期延迟。3.3 用异步 FIFO 把前端数据接到 DAC 时钟域很多实际工程里DAC 的数据不是直接从寄存器来而是来自 ADC 采集、滤波运算或者以太网/串口下发的波形点。这些数据的产生时钟和 DAC 的更新时钟往往不在同一个域直接跨时钟域打拍子会出现数据错位。常见做法是在中间插入一个异步 FIFO把写侧时钟域的数据缓存下来读侧用 DAC 时钟读出。module dac14_fifo_bridge #( parameter DATA_WIDTH 14, parameter FIFO_DEPTH 1024 )( input wire wr_clk, input wire [DATA_WIDTH-1:0] wr_data, input wire wr_en, output wire fifo_full, input wire dac_clk, input wire rst_n, input wire rd_en, output wire [DATA_WIDTH-1:0] dac_data, output wire fifo_empty ); async_fifo #( .DATA_WIDTH(DATA_WIDTH), .FIFO_DEPTH(FIFO_DEPTH) ) u_fifo ( .wr_clk (wr_clk), .wr_en (wr_en), .wr_data (wr_data), .wr_full (fifo_full), .rd_clk (dac_clk), .rd_en (rd_en), .rd_data (dac_data), .rd_empty (fifo_empty) ); endmodule使用异步 FIFO 时有个细节FIFO 的空满标志本身存在跨时钟域同步延迟读侧判断fifo_empty之后再去读数据要晚一拍才有效。所以 DAC 侧的读控制一般是组合逻辑判断比如assign rd_en ~fifo_empty;然后再在dac_clk上升沿打拍输出。如果省略这条打拍DAC 在最后一个数据读取时可能会读到无效数据输出波形尾部会多出一个台阶或者毛刺。3.4 代码组织中值得参数化的点参数推荐值作用DATA_WIDTH14匹配 AD9764 分辨率改其他 DAC 时调整FIFO_DEPTH1024 或 4096缓存深度取决于写突发长度CLK_DIV1、2、4需要对 DAC 降频时用的分频系数UPDATE_MODE直通/双缓冲用一个参数控制 MODE 引脚逻辑把这些参数提到模块顶部换用其他 14 位 DAC比如 AD9744、AD9760时只需要改端口名称和时序参数不用重新整理整个逻辑。如果后续要升级到 16 位 DACDATA_WIDTH改成 16FPGA 引脚多分配两位代码主体不需要重写。4. Quartus Prime 18.0 完整工程建工程、引脚约束与 ModelSim 仿真4.1 创建工程并组织源文件Quartus Prime 18.0 装载 Altera 全系列器件的工程File → New Project Wizard 选择目标器件型号比如 Cyclone IV EP4CE10F17C8。工程里至少包含三个文件顶层 Verilog 文件、DAC 驱动模块、时序约束 SDC 文件。如果用了 PLL 或异步 FIFO还需要添加对应的 IP 文件.qip。Altera 的 FPGA 用什么软件开发答案就是 Quartus Prime。18.0 版本对 Cyclone V 之前的器件支持最稳定不要为了用新版本而去装 22.x18.0 对 AD9764 这类简单逻辑完全足够。ModelSim 建议用随 Quartus 一起安装的 Intel FPGA Starter Edition 10.5b免费的 ModelSim-Intel FPGA Edition 可以完成仿真验证。4.2 引脚约束与 QSF 配置引脚分配有两种方式图形界面的 Assignment Editor或者直接编辑 .qsf 文件。后者更适合版本管理这里给出一个片段set_location_assignment PIN_C12 -to dac_clk set_location_assignment PIN_D12 -to dac_data[0] set_location_assignment PIN_E12 -to dac_data[1] set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to dac_clk set_instance_assignment -name IO_STANDARD 3.3-V LVCMOS -to dac_data[*] set_instance_assignment -name SLEW_RATE 2 -to dac_clk set_instance_assignment -name OUTPUT_ENABLE_GROUP GROUP_A -to dac_data[*]OUTPUT_ENABLE_GROUP保证所有 DAC 数据引脚在配置完成后同时使能避免上电瞬间某些引脚处于未定义状态把 DAC 输入端拉出异常电平。这里提醒一句DAC 数据总线的引脚不要分散到 FPGA 的不同 BANK尽量集中在一个 BANK 内。Altera 的每个 GPIO BANK 电压不同混用 BANK 容易出现电平标准不匹配极端情况下导致 DAC 数据位翻转时间差异上百皮秒。时序约束里面有几行不可或缺的内容。时钟定义必须存在否则 TimeQuest 会报未约束时钟输出延时是为了让综合器知道 DAC 的数据建立时间要求。create_clock -name dac_clk -period 8.000 [get_ports dac_clk] set_output_delay -clock [get_clocks dac_clk] -min -add_delay 1.000 [get_ports {dac_data[*]}] set_output_delay -clock [get_clocks dac_clk] -max -add_delay 2.000 [get_ports {dac_data[*]}]这里假设 DAC 时钟周期 8 ns即 125 MHzDAC 数据手册要求的建立时间约 2 ns、保持时间约 1 ns。set_output_delay的-min和-max分别对应保持时间和建立时间要求具体数值要从 AD9764 数据手册的 AC 特性表查不同参考设计给出的余量习惯不一样。4.3 ModelSim 仿真验证驱动逻辑仿真这一步可以在没有硬件的情况下先验证状态机的正确性。写 Testbench 时给模块输入一个正弦波查表数据序列观察 dac_data 输出是否符合预期。module tb_dac14; reg clk_100m; reg rst_n; reg [13:0] sine_data; wire [13:0] dac_out; wire load; dac14_double_buf u_dac ( .clk (clk_100m), .rst_n (rst_n), .wr_en (1b1), .data_in (sine_data), .dac_data (dac_out), .load (load) ); always #5 clk_100m ~clk_100m; reg [13:0] rom [0:1023]; integer i; initial begin $readmemh(sine_table.hex, rom); rst_n 0; clk_100m 0; #100 rst_n 1; for (i 0; i 2048; i i 1) begin (posedge clk_100m); sine_data rom[i 1023]; end #1000 $finish; end endmodule$readmemh读取十六进制波形表文件Testbench 再用每个时钟沿向模块写入新数据。仿真时重点看两件事load 信号是否只有一个时钟周期宽度dac_data 是否比数据输入延迟正好两个周期。仿真遇到17.1 error: failure to obtain a verilog simulation license这类错误时先查 ModelSim 的许可证路径LM_LICENSE_FILE环境变量是否指向有效的 license 文件。Quartus 免费版的 ModelSim Starter Edition 不支持所有器件库如果器件是 Cyclone 10 或较新的 MAX 10需要下载对应版本的仿真库并手动编译到 ModelSim 的目录下。常常是许可证问题不是代码问题。4.4 SignalTap II 板上抓到真实数据仿真过了并不代表板子没问题。用 SignalTap II 加一个触发条件抓取 DAC 数据总线和状态机的关键信号。这里要注意 SignalTap 占用的逻辑资源和存储器会在综合后改变布局PLL 的相位偏移可能和没有插 IP 时不同所以抓完信号后如果 DAC 输出有所改善最好重新编译一次再跑。SignalTap 触发建议设在 FIFO 读侧看rd_en和fifo_empty的时序关系如果发现rd_en在 FIFO 快空的时候拉高但数据已经停止更新就要检查读侧逻辑是否多打了一拍。这个场景在异步 FIFO 里特别容易出现听上去像是 FPGA 裂了实际上只是空标志和读使能之间的握手差了周期。5. 用 DDS 相位累加器生成扫频正弦波并量化输出质量驱动的最终验证不能只看波形像不像正弦还要看频率误差和频谱纯度。把 DAC 驱动模块接到一个直接用 DDS 相位累加器实现的信号源就能在示波器上观察扫频结果用频谱仪看杂散水平。module dds_dac_sweep #( parameter PHASE_WIDTH 24, parameter DATA_WIDTH 14 )( input wire clk, input wire rst_n, input wire [PHASE_WIDTH-1:0] freq_word, output wire [DATA_WIDTH-1:0] dac_data ); reg [PHASE_WIDTH-1:0] phase_acc; always (posedge clk or negedge rst_n) begin if (!rst_n) phase_acc 0; else phase_acc phase_acc freq_word; end sine_lut u_sine ( .phase (phase_acc[PHASE_WIDTH-1:PHASE_WIDTH-10]), .data (dac_data) ); endmodule输出频率的计算公式是f_out f_clk × freq_word / 2^PHASE_WIDTH。以 100 MHz 时钟、24 位相位累加器为例freq_word 1677721时约输出 100 kHz扫频时用一个计数器分段改变freq_word即可。相位截断取高 10 位查表引入的量化噪声大约在 -60 dB 以下对 14 位 DAC 来说刚刚好如果要求更高把查表地址提高到 12 位或者改用分段线性插值。扫频开始时先用低频 1 kHz 验证双缓冲模式下的波形是否平滑再把频率往上抬。AD9764 在理想条件下 SFDR 能到 70 dBc 以上但板上跑 20 MHz 以上时如果频谱出现明显的谐波簇优先检查电源去耦而非 DAC 本身。用频谱仪测 10 MHz 正弦输出比较基波与二次谐波差值低于 55 dB 的时候基本可以断定时钟抖动或数字馈通是主要来源回头调整 PCB 布局的接地比在代码里加滤波更有效。这套驱动方案同样能直接移植到任意 14 位并行 DAC 上AD9744 只需改参考电压配置AD9764 的替代型号在时序上基本兼容换器件后重新仿真一遍读写时序就能跑通。调通一个信号源以后把 DAC 驱动的输出接到 LPF 和功放级就能作为整块数据采集板的前端激励通道来使用。本文还有配套的精品资源点击获取