AD7768与FPGA接口驱动设计:Verilog实现SPI配置与DCLK数据捕获
2026/9/23 13:56:08 网站建设 项目流程

简介:这份资源面向FPGA开发工程师与嵌入式学习者,提供基于XILINX平台控制AD7768高精度ADC的完整Verilog驱动程序,可直接调用并已通过仿真验证,适合用于数据采集系统设计、ADC接口调试及课程项目参考。压缩包共72个文件,约28.81MB,以Verilog源码与头文件为主,包含18个.v与19个.vh文件,另有7个.xdc约束文件用于引脚与时序定义,以及6个VHDL文件、3个IP核配置文件(xci/dcp)和若干仿真与说明文本,覆盖从RTL设计到综合实现的完整流程。内容预览显示顶层模块AD7768.v已集成时钟、电源使能、增益控制、模式配置及同步复位等接口,并配套ila_0、fifo_generator_0、clk_wiz_0等IP,便于快速搭建调试环境。目前已有46人学习下载,读者可借此掌握AD7768的寄存器配置时序、SPI/接口控制逻辑与仿真验证方法,也可作为XILINX FPGA驱动开发的参考模板。

1. AD7768 与 FPGA 的接口为什么值得单独写一套驱动

如果你手上有 AD7768 这块 8 通道 24 位同步采样 ADC,又打算用 XILINX FPGA 做数据采集主控,那么你迟早会面对同一个问题:SPI 配置寄存器好写,但真正把 8 通道数据以 256 kSPS 甚至更高吞吐率稳定收进来,才是这套方案的核心难点。AD7768 不是普通 SPI ADC,它的数据接口是每通道独立的 Σ-Δ 调制位流加 DCLK 同步,配置口才走 SPI。这意味着 Verilog 驱动要同时处理两条完全不同的时序链路:一条是低速 SPI 写寄存器,一条是高速同步数据捕获加抽取滤波。

标题里说的「已仿真,可以直接调用」,落到工程上就是一套可综合的 RTL 加 testbench,能让你在 Vivado 里直接例化、跑通行为仿真、再上板验证。它解决的不是「AD7768 是什么」这种科普问题,而是「我拿到板子后,怎么在两周内把 8 通道数据稳定送进 FIFO」这种落地问题。适合谁?适合已经会写 Verilog 状态机、用过 Vivado 做综合实现,但对 AD7768 这种多通道同步 ADC 接口时序还没踩过坑的 FPGA 工程师。下面我按自己实际调通的顺序,把配置、捕获、仿真、上板排查一条线讲清楚。

2. AD7768 的接口时序拆解与 Verilog 顶层规划

2.1 先分清 SPI 配置口和 DCLK 数据口

AD7768 的引脚里有两组容易混淆的接口。第一组是 SPI 口:CS、SCLK、SDI、SDO,用来读写内部寄存器,配置采样率、滤波器类型、通道使能、功耗模式。第二组是数据口:DCLK、DOUTx(每通道一根或复用)、DRDY。数据口的工作方式是 ADC 在 DCLK 上升沿输出调制位流,FPGA 在 DCLK 边沿采样,然后自己内部做抽取滤波得到 24 位结果。

很多人第一次翻车就是把 DOUT 当成 SPI 的 MISO 去读,结果数据全是乱的。血泪经验是:SPI 只在初始化阶段用一次,数据采集阶段完全靠 DCLK 域的逻辑。所以 Verilog 顶层我一般拆成三个模块:ad7768_spi_cfg负责寄存器配置,ad7768_data_capture负责 DCLK 域位流捕获和抽取,ad7768_ctrl_top做跨时钟域握手和 FIFO 写入。

2.2 时钟域划分与复位策略

AD7768 的 DCLK 由 FPGA 提供还是 ADC 提供,取决于你选的模式。常见做法是 FPGA 用 MMCM 生成 DCLK 输出给 ADC,同时用同一时钟的相移版本去采样 DOUT,这样能保证建立保持时间。我一般会把 DCLK 设成 MCLK 的分频,MCLK 给 16.384 MHz,DCLK 给 4.096 MHz,对应 256 kSPS 的抽取率。

复位策略上,SPI 配置完成前不能让数据捕获模块工作,否则会收到无效位流。所以顶层用一个cfg_done信号做门控,配置完成后拉高,数据模块才开始计数和写 FIFO。跨时钟域的地方,cfg_done从 SPI 时钟域同步到 DCLK 域,用两级触发器打拍,这是最基本的 CDC 处理,别省。

2.3 顶层端口与参数定义

下面是我常用的顶层端口定义,参数化通道数和抽取比,方便复用到不同采样率场景。

module ad7768_ctrl_top #( parameter CH_NUM = 8, // AD7768 通道数 parameter DECIM_RATE = 64, // 抽取比,对应 256kSPS @ 16.384MHz MCLK parameter FIFO_DEPTH = 512 // 每通道 FIFO 深度 )( input wire rst_n, input wire clk_spi, // SPI 配置时钟,一般 10MHz 以下 input wire clk_dclk, // DCLK 采样时钟 // SPI 接口 output wire spi_cs_n, output wire spi_sclk, output wire spi_sdi, input wire spi_sdo, // AD7768 数据接口 input wire dclk_in, // 来自 ADC 的 DCLK(若 ADC 主模式) input wire [CH_NUM-1:0] dout, // 每通道位流输入 input wire drdy_n, // 用户侧读接口 input wire rd_en, output wire [CH_NUM*24-1:0] rd_data, output wire rd_valid, output wire fifo_full );

这段代码的关键点在于:clk_dclkdclk_in的关系要明确。如果 FPGA 是 DCLK 主模式,dclk_in可以不用,直接用clk_dclk输出给 ADC;如果是从模式,dclk_in接 ADC 输出的 DCLK,再用它去采样 DOUT。参数DECIM_RATE决定抽取滤波器的阶数,后面会展开。

2.4 抽取滤波器的选型理由

AD7768 输出的是 1 位 Σ-Δ 位流,要得到 24 位 PCM 结果,必须做抽取滤波。常见做法是三级级联:第一级 CIC 滤波器降采样,第二级补偿 FIR,第三级半带滤波器。但在 FPGA 里如果资源紧张,我一般先用一级 CIC 加一级 FIR 就能满足 256 kSPS 下的 24 位精度要求。

CIC 滤波器的阶数 N 和抽取比 R 决定阻带衰减。AD7768 的位流是 1 位,CIC 积分器位宽要够,否则会溢出。我一般取积分器位宽 = 24 + log2(R*N),比如 R=64、N=4 时,位宽取 24+8=32 位。这个参数设错,仿真时数据会周期性跳变,上板就是噪声。

提示:CIC 抽取比和 AD7768 内部滤波器是两回事。AD7768 内部有 sinc 滤波器,但输出位流模式下它不生效,抽取完全由 FPGA 做。别把 ADC 寄存器里的滤波器配置和 FPGA 抽取搞混。

3. 用 Verilog 写 SPI 配置状态机与寄存器写入

3.1 AD7768 寄存器写入时序要点

AD7768 的 SPI 模式支持 4 线制,SCLK 空闲高或低取决于 CPOL 配置。我一般用 CPOL=0、CPHA=0,即 SCLK 空闲低、上升沿采样。每次写寄存器是 16 位:1 位读写标志、7 位地址、8 位数据。CS 拉低后第一个 SCLK 上升沿采样第一位,CS 拉高结束传输。

配置顺序很重要:先写通道使能寄存器,再写采样率寄存器,最后写功耗模式。如果顺序反了,ADC 可能进入低功耗模式后不响应后续配置。我踩过的坑是先把所有寄存器写一遍,结果发现某些寄存器写入无效,后来查手册才知道部分寄存器需要先解锁。

3.2 SPI 状态机的 Verilog 实现

下面是一个可综合的 SPI 写状态机,参数化寄存器地址和数据,方便扩展成配置序列。

module ad7768_spi_cfg #( parameter CLK_DIV = 16 // clk_spi 分频得到 SCLK )( input wire rst_n, input wire clk_spi, input wire cfg_start, input wire [6:0] reg_addr, input wire [7:0] reg_data, output reg spi_cs_n, output reg spi_sclk, output reg spi_sdi, input wire spi_sdo, output reg cfg_done ); localparam IDLE = 2'd0; localparam WRITE = 2'd1; localparam DONE = 2'd2; reg [1:0] state; reg [4:0] bit_cnt; reg [15:0] shift_reg; reg [7:0] div_cnt; always @(posedge clk_spi or negedge rst_n) begin if (!rst_n) begin state <= IDLE; spi_cs_n <= 1'b1; spi_sclk <= 1'b0; spi_sdi <= 1'b0; cfg_done <= 1'b0; bit_cnt <= 5'd0; div_cnt <= 8'd0; end else begin case (state) IDLE: begin cfg_done <= 1'b0; if (cfg_start) begin // 组装 16 位:bit15=0 写,bit14:8 地址,bit7:0 数据 shift_reg <= {1'b0, reg_addr, reg_data}; spi_cs_n <= 1'b0; bit_cnt <= 5'd16; state <= WRITE; end end WRITE: begin if (div_cnt == CLK_DIV-1) begin div_cnt <= 8'd0; spi_sclk <= ~spi_sclk; if (spi_sclk == 1'b0) begin // 上升沿前更新 SDI spi_sdi <= shift_reg[15]; shift_reg <= {shift_reg[14:0], 1'b0}; bit_cnt <= bit_cnt - 1'b1; if (bit_cnt == 5'd1) begin spi_cs_n <= 1'b1; state <= DONE; end end end else begin div_cnt <= div_cnt + 1'b1; end end DONE: begin cfg_done <= 1'b1; state <= IDLE; end endcase end end endmodule

逻辑说明:状态机在 IDLE 等待cfg_start,收到后组装 16 位帧,拉低 CS,进入 WRITE。WRITE 里用div_cnt分频产生 SCLK,在 SCLK 低电平期间更新 SDI,保证上升沿时数据稳定。bit_cnt从 16 递减到 1 后拉高 CS,进入 DONE 输出cfg_done脉冲。

参数说明:CLK_DIV决定 SCLK 频率,clk_spi50 MHz 时取 16 得到约 3.1 MHz,满足 AD7768 的 SCLK 上限。reg_addrreg_data由上层配置序列提供,我一般用一个 ROM 或 case 语句依次发出多条配置。

3.3 配置序列的组织方式

实际工程里不会只写一个寄存器,我一般用一个计数器加 case 语句,按顺序发出配置。比如先写 0x01 通道使能,再写 0x02 采样率,再写 0x03 功耗模式。每条配置之间插入几十个时钟的间隔,等cfg_done拉高后再发下一条。这样比一次性写一个长状态机更清晰,也方便后期改寄存器值。

注意:AD7768 的 SDO 在写操作时也可以读回寄存器值做校验。如果上板后发现配置不生效,先把 SDO 接上逻辑分析仪,看回读值是否和写入一致。不一致多半是 SCLK 相位或 CS 建立时间问题。

4. DCLK 域数据捕获与 CIC 抽取滤波的 Verilog 实现

4.1 位流捕获的边沿选择与打拍

AD7768 在 DCLK 上升沿输出数据,FPGA 应该在 DCLK 下降沿采样,或者用相移 90 度的时钟采样。我一般用 MMCM 生成 DCLK 的 90 度相移版本clk_dclk_90,用它去采 DOUT。这样建立保持时间最宽裕。如果不想用 MMCM,也可以在 DCLK 下降沿直接采样,但要注意 PCB 走线延迟。

采样后先打两拍同步到clk_dclk域,再做后续处理。别小看这两拍,省掉它仿真可能过,上板就是亚稳态。

4.2 CIC 抽取滤波器的 Verilog 结构

CIC 滤波器由积分器和梳状器组成,中间做降采样。对于 1 位输入,积分器就是累加器。下面是一个 N=4、R=64 的 CIC 抽取实现。

module cic_decim #( parameter R = 64, // 抽取比 parameter N = 4, // 阶数 parameter IN_W = 1, // 输入位宽 parameter OUT_W = 32 // 输出位宽 )( input wire rst_n, input wire clk, input wire din, input wire din_valid, output reg [OUT_W-1:0] dout, output reg dout_valid ); // 积分器级 reg [OUT_W-1:0] integrator [0:N-1]; // 梳状器级 reg [OUT_W-1:0] comb [0:N-1]; reg [OUT_W-1:0] comb_delay [0:N-1]; reg [15:0] dec_cnt; integer i; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin for (i=0; i<N; i=i+1) begin integrator[i] <= {OUT_W{1'b0}}; comb[i] <= {OUT_W{1'b0}}; comb_delay[i] <= {OUT_W{1'b0}}; end dec_cnt <= 16'd0; dout <= {OUT_W{1'b0}}; dout_valid <= 1'b0; end else begin dout_valid <= 1'b0; if (din_valid) begin // 积分器级联 integrator[0] <= integrator[0] + {{(OUT_W-IN_W){1'b0}}, din}; for (i=1; i<N; i=i+1) integrator[i] <= integrator[i] + integrator[i-1]; // 降采样计数 if (dec_cnt == R-1) begin dec_cnt <= 16'd0; // 梳状器级联 comb[0] <= integrator[N-1] - comb_delay[0]; comb_delay[0] <= integrator[N-1]; for (i=1; i<N; i=i+1) begin comb[i] <= comb[i-1] - comb_delay[i]; comb_delay[i] <= comb[i-1]; end dout <= comb[N-1]; dout_valid <= 1'b1; end else begin dec_cnt <= dec_cnt + 1'b1; end end end end endmodule

逻辑说明:积分器在每个din_valid时累加,N 级级联。dec_cnt计数到 R-1 时触发一次梳状器运算,梳状器做差分,N 级级联后输出。dout_valid在输出时拉高一个时钟。

参数说明:RN决定滤波器的频率响应,OUT_W要足够宽防止溢出。我一般取OUT_W = 24 + ceil(log2(R*N)),R=64、N=4 时约 32 位。输出再截取高 24 位作为 PCM 数据。

4.3 多通道并行与 FIFO 写入

8 个通道各用一个cic_decim实例,输出拼成 192 位写进 FIFO。FIFO 用 XILINX 的xpm_fifo_asyncfifo_generatorIP,写时钟是clk_dclk,读时钟是用户侧时钟。写使能由 8 个通道的dout_valid相与得到,保证 8 通道数据对齐。

这里有个坑:不同通道的 CIC 延迟可能因为复位不同步而差一拍。我一般让所有通道共用同一个复位和din_valid,这样延迟一致。如果发现通道间有固定相位差,先查复位同步。

提示:CIC 输出截位时别直接取高位,先做舍入。直接截位会引入直流偏置,做 FFT 时底噪会抬高。我一般加 0.5 LSB 再截。

5. 仿真验证与上板调试的避坑记录

5.1 用 testbench 模拟 AD7768 位流

仿真时不需要真实 ADC,我一般写一个行为级模型,用正弦查表生成 1 位 Σ-Δ 位流。下面是一个简化 testbench 片段。

// 生成 1kHz 正弦的 1 位位流,DCLK 4.096MHz reg [15:0] phase_acc; wire [15:0] phase_inc = 16'd16; // 对应约 1kHz always @(posedge clk_dclk or negedge rst_n) begin if (!rst_n) phase_acc <= 16'd0; else phase_acc <= phase_acc + phase_inc; end // 简单一阶 sigma-delta 调制 reg sd_out; reg [15:0] sd_integ; always @(posedge clk_dclk or negedge rst_n) begin if (!rst_n) begin sd_out <= 1'b0; sd_integ <= 16'd0; end else begin sd_integ <= sd_integ + {phase_acc[15], 15'b0} - {sd_out, 15'b0}; sd_out <= sd_integ[15]; end end

把这个sd_out接到ad7768_ctrl_topdout,跑足够长的时间,看 FIFO 读出的数据做 FFT,主频点应该在 1kHz,SFDR 和 SNR 符合预期。仿真通过不代表上板通过,但仿真不过一定上板不过。

5.2 避坑记录:五条上板常见问题

现象一:SPI 配置回读全 0。原因:CS 拉低到第一个 SCLK 上升沿之间建立时间不够。解决:在 CS 拉低后插入至少 4 个clk_spi周期的延时再发 SCLK。

现象二:数据捕获全是 0 或全 1。原因:DCLK 相位不对,采样点落在数据跳变沿。解决:用 MMCM 相移 90 度,或改用 DCLK 下降沿采样,用示波器看 DOUT 和采样时钟的关系。

现象三:8 通道数据只有前 4 通道有效。原因:通道使能寄存器只写了低 4 位。解决:检查配置序列里通道使能寄存器的值,AD7768 的 8 通道使能是 8 位,别只写 0x0F。

现象四:CIC 输出噪声大,FFT 底噪高。原因:积分器位宽不够,溢出后回绕。解决:把OUT_W加大 4 位,或者降低抽取比。仿真时用 $display 打印积分器最大值确认。

现象五:FIFO 偶尔溢出。原因:读侧时钟太慢,或者写使能没有和dout_valid严格对齐。解决:加大 FIFO 深度,或者用rd_en反压写侧。我一般会在 FIFO 快满时拉低din_valid,丢几个采样点比溢出后数据错位好排查。

5.3 仿真与上板结果不一致时看什么

先看时钟。仿真里时钟是理想的,上板有抖动和偏斜。用 Vivado 的 ILA 抓 DCLK、DOUT、DRDY 三根线,看 DRDY 下降沿和第一个 DCLK 的间隔是否符合手册。再看复位。仿真里复位可能只给一个脉冲,上板复位释放时机和时钟不同步,会导致状态机跑飞。我一般用rst_n同步释放,每个时钟域单独同步。

最后看约束。DCLK 和 DOUT 之间的建立保持时间要在 XDC 里写set_input_delay,不写约束 Vivado 可能布出很差的时序。这一步很多人省,省了就等着上板玄学。

6. 把驱动封装成可复用 IP 的进阶技巧

6.1 参数化抽取比与采样率切换

实际项目里经常需要在不同采样率之间切换,比如 256 kSPS 做振动监测,8 kSPS 做温度采集。如果每次改抽取比都重新综合,太浪费时间。我一般把DECIM_RATE做成运行时可配置的寄存器,CIC 的dec_cnt比较值从寄存器取,而不是参数。这样一套 bit 流可以覆盖多个采样率。

具体做法是在ad7768_ctrl_top里加一个 AXI-Lite 从接口,用xilinxaxi_lite_ipif或自己写一个简单寄存器组。上位机通过 AXI 写decim_rate寄存器,CIC 模块实时读取。注意切换抽取比时 CIC 内部状态要复位,否则会输出一段错误数据。我一般在切换后等 2 个抽取周期再使能dout_valid

6.2 用 ILA 和 VIO 做在线调试

Vivado 的 ILA 是上板调试的后悔药。我一般会在ad7768_ctrl_top里例化一个 ILA,抓doutdout_validfifo_fullcfg_done这几个信号。触发条件设成fifo_full上升沿,这样能抓到溢出前的数据。VIO 用来在线改decim_rate和复位,不用重新综合。

ILA 的采样深度别设太大,8 通道 32 位数据,深度 1024 就够看一个完整抽取周期。设太大综合时间会爆炸,而且波形加载慢。

6.3 资源占用与时序收敛的取舍

8 通道 CIC 加 FIFO,在 Artix-7 上大概占 3000 到 5000 LUT,具体看抽取比和位宽。如果资源紧张,可以把 CIC 的阶数从 4 降到 3,阻带衰减会差一些,但 24 位精度下影响不大。时序收敛的关键是 DCLK 域的路径,我一般给 DCLK 域单独加set_max_delay约束,别让它和 SPI 域混在一起优化。

下面是我常用的资源对比,基于 Artix-7 XC7A100T,DCLK 4.096 MHz,8 通道。

配置LUTFFDSP备注
CIC N=4, R=64420038000默认配置
CIC N=3, R=64310029000阻带衰减降 10dB
CIC N=4, R=128460041000128kSPS
加 FIR 补偿680055008通带更平

这张表是我实际综合后看的,不同 Vivado 版本会有浮动。如果 LUT 超了,先降 N,再考虑时分复用 CIC。

6.4 一个具体技巧:用 DRDY 做帧同步

AD7768 的 DRDY 下降沿表示一组新数据准备好。我一般用 DRDY 做帧同步,而不是靠 CIC 的dout_valid计数。具体做法是:DRDY 下降沿触发一个标志,CIC 输出时检查这个标志,只有标志有效时才写 FIFO。这样即使 CIC 延迟有波动,也不会写错帧。

DRDY 是慢速信号,和 DCLK 不同域,需要同步。我一般用 DCLK 采样 DRDY,打两拍后做下降沿检测。这个技巧在采样率切换时特别有用,能避免切换瞬间的错帧。

注意:DRDY 的极性可以通过 AD7768 寄存器配置,别默认它是低有效。配置前先读手册确认,或者用示波器看。

这套驱动我从第一次调通到现在,前后改过五六版,最大的教训是别信仿真通过就上板。仿真里时钟理想、复位干净,上板全是玄学。我现在的习惯是:仿真跑通后,先上 ILA 抓一轮,确认 SPI 配置回读正确、DCLK 采样点居中、DRDY 帧同步稳定,再跑长时间采集。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询