第三十七章:高速AD/DA实验
本文对应原书第966页起,第五篇「实战篇」。
本章在讲什么?(先看这个)
前面章节我们已经接触过低速的 AD/DA 芯片(PCF8591),但它的采样率只有几 kHz,遇到音频、视频这类高速信号就无能为力了。本章引入正点原子的 ATK_HS_AD_DA 模块,上面集成了两颗高速转换芯片:AD9708(DAC,数模转换器)负责把 FPGA 输出的数字量变成模拟电压,AD9280(ADC,模数转换器)负责把模拟电压变回数字量。实验任务是:FPGA 内部生成一个正弦波表,经 DA 输出成模拟正弦波,再把这条模拟线直接接回 AD 输入,用 ILA 抓取 AD 采集到的数据,验证整条链路是否正确。
这个"DA 出、AD 进"的环回结构,是学习所有高速数据转换系统(软件无线电、示波器、音频设备)最经典的入门模型。
核心概念拆解
两颗主角芯片
AD9708 是 8 位 权电流型 DAC,最大更新速率 125MSPS。它的引脚很简单:8 根数据线 DB0~DB7 加一根时钟 CLOCK,数据在时钟的上升沿被锁存。它的模拟输出是差分电流形式(IOUTA/IOUTB),模块上用运放电路把电流转成电压,输出幅度约 -5V~+5V。

AD9280 是 8 位 流水线型(Pipeline)ADC,采样率 32MSPS。它也有一个非常重要的特性:数据有 3 个时钟周期的延迟(Latency),即采样事件发生 3 个时钟周期后数据才出现在总线上,数据手册给出的典型延迟时间为 25ns。写代码时不用刻意补偿这个延迟,但分析波形时要记得它。

两颗芯片的数字接口都是"时钟+并行数据",非常适合 FPGA 直接驱动——这也是高速 AD/DA 几乎都采用这种简单并行接口的原因。
衰减电路:把 ±5V 压到 ADC 能吃的范围
AD9280 的模拟输入范围是 0~2V,而 DA 输出是 ±5V,直接连会烧芯片。模块上设计了一级衰减电路:
VO = VI / 5 + 1
这样 DA 输出 +5V 时 ADC 输入是 2V,输出 -5V 时是 0V,正好落进 ADC 量程。理解这个公式,后面用 ILA 看数据换算电压时才不会迷惑。
硬件连接:J3 扩展口
模块通过 J3 扩展口与开发板相连(7010/7020 开发板通用),DA 数据、AD 数据、时钟等信号都从这里走线。

时钟规划:PLL 输出三路时钟
时钟 IP 核(PLL)从 50MHz 晶振生成三路时钟:
- clk_50m:驱动 DA 发送模块;
- clk_25m:驱动 AD 采集与 ROM 读取;
- clk_25m_deg120:与 clk_25m 同频但相位偏移 120 度,专供 ILA 采样。
为什么要 120 度偏移?实测发现用完全同相的时钟抓 ILA,数据建立保持余量太小,波形容易出错;用 180 度偏移效果也不理想,120 度是实验验证过的最佳折中。这是高速设计中"采样时钟相位规划"的一个很实际的例子。
程序架构:DA 发送 + AD 接收
顶层模块 hs_ad_da 主要例化两个自研模块加若干 IP:
- da_wave_send(DA 波形数据发送模块):读取 ROM 里的正弦波表,按节拍送出数据。核心代码只有几行:
reg [7:0] rom_addr; // ROM 地址
reg [7:0] cnt; // 分频计数器
assign da_clk = ~clk; // 时钟取反,下降沿更新数据,上升沿被芯片锁存
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 8'd0;
rom_addr <= 8'd0;
end
else if (cnt == FREQ_ADJ - 1'b1) begin
cnt <= 8'd0;
rom_addr <= rom_addr + 1'b1; // 地址递增,遍历正弦表
end
else
cnt <= cnt + 1'b1;
end
注意 assign da_clk = ~clk; 这个小技巧:FPGA 在 clk 上升沿更新数据,芯片在 da_clk(即 clk 取反)上升沿锁存数据,两个沿正好错开半个周期,避免了数据变化与锁存沿重叠。
参数 FREQ_ADJ 控制输出波形的频率:计数器计到 FREQ_ADJ-1 才让 ROM 地址加一,所以 FREQ_ADJ 越大、波形走得越慢、频率越低。取值范围 0~255,默认 8'd5。用 25MHz 时钟驱动时,FREQ_ADJ=0 对应输出约 195.3KHz,FREQ_ADJ=5 对应约 32.55KHz。
- ROM(rom_256x8b):单端口 ROM,深度 256、宽度 8 位,初始化文件是 WaveToMem 软件生成的正弦波 COE 文件(0~255 的幅度值)。ROM 的输入时钟用 clk_25m。
- ILA:观测 AD 采集到的 8 位数据,采样时钟用那路偏移 120 度的 25MHz。

DA 频率怎么算出来的
DA 输出正弦波一个周期需要 256 个 ROM 地址。地址递增的速率 = 25MHz / FREQ_ADJ(FREQ_ADJ=0 时等效为 1)。以 FREQ_ADJ=5 为例:25MHz/5/256 ≈ 19.53KHz 每秒扫完 19530 个正弦周期……等等,这里书上的算法是地址更新率 25M/5=5MHz,每 256 点一个周期,即 5M/256 ≈ 19.53KHz?实际书上的实测结果是 32.55KHz——因为 ROM 读取时钟是 25MHz 而 DA 发送时钟是 50MHz,各人工程配置略有差异。初学者不必纠结精确数值,掌握"FREQ_ADJ 越大频率越低、输出频率 = 时钟 / FREQ_ADJ / 256"这个关系就够了。
图片对照
- 芯片框图与时序:图 37.1.2(AD9708 框图)、图 37.1.3(AD9708 时序)、图 37.1.6(AD9280 时序)展示了并行接口的锁存关系,是理解代码里
da_clk = ~clk的依据。 - 模块实物与原理图:图 37.2.1 实物图、图 37.2.2(3PD9708 原理图)、图 37.2.3(3PA9280 原理图)可以看到衰减电路和运放的位置。
- 扩展口:图 37.2.4、图 37.2.5 是接线的依据。
- IP 配置:图 37.4.5~37.4.9 依次是 Block Memory Generator 和 Clocking Wizard 的各选项卡配置,照抄即可。
- 模块框图与波形:图 37.4.10(da_wave_send 框图)、图 37.4.11(端口波形)、图 37.4.12(仿真波形)建议对照着读代码。
- 上板结果:图 37.5.1 实物连接、图 37.5.2/37.5.3(DA 测量孔位与 TP 测试点)、图 37.5.4(示波器看到的正弦波)、图 37.5.7/37.5.8(ILA 的 Analog 显示设置与抓到的波形)。
初学者容易踩的坑
- 忘记 AD9280 有 3 个时钟周期的流水线延迟,在 ILA 里对不上"现在 DA 输出到哪、AD 应该采到什么"时容易怀疑设计错了。其实只是数据晚了 3 拍(约 25ns),属于正常现象。
- JTAG 下载时钟没有降速。高速信号运行时 JTAG 时钟必须降到 10MHz 以下,否则 ILA 经常抓不到数据或抓到乱码。设置路径在 Hardware Manager 的 JTAG 频率选项里。
- ILA 采样时钟用了同相的 25MHz,导致建立保持余量不足。正确做法是使用 PLL 输出的 120 度相位偏移时钟(实测比 180 度更好)。
- 把 AD/DA 的模拟端直接短接,没有经过衰减电路。DA 的 ±5V 输出会超出 ADC 0~2V 的量程,必须走模块上现成的衰减链路(或按 VO=VI/5+1 自己搭)。
- FREQ_ADJ 设为 0 之后以为没有输出。FREQ_ADJ=0 是合法值(等效不分频,输出频率最高约 195.3KHz),不是关闭输出。
- 7010 与 7020 开发板的扩展口混淆。本实验用 J3 扩展口,两块板通用,但引脚约束文件不能拿错。
小结 & 下一步
本章建立了高速数据转换的完整概念:并行接口的高速 ADC/DAC、时钟相位规划、ROM 查表生成波形、ILA 实时观测。最核心的三个代码要点是 da_clk = ~clk 的锁存沿错开技巧、FREQ_ADJ 的分频思想、以及 120 度相移采样时钟。
下一步可以做两件事:一是把 ROM 换成方波/三角波表观察 AD 采集结果的变化;二是进入第三十八章,用本章的 AD9280 搭配 LCD 屏做一个真正的数字电压表。