第三十七章:高速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。

图37.1.2 AD9708内部功能框图

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

图37.1.6 AD9280时序图

两颗芯片的数字接口都是"时钟+并行数据",非常适合 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 数据、时钟等信号都从这里走线。

图37.2.4 开发板扩展口

时钟规划: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。

图37.4.2 高速AD/DA系统框图

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 显示设置与抓到的波形)。

初学者容易踩的坑

  1. 忘记 AD9280 有 3 个时钟周期的流水线延迟,在 ILA 里对不上"现在 DA 输出到哪、AD 应该采到什么"时容易怀疑设计错了。其实只是数据晚了 3 拍(约 25ns),属于正常现象。
  2. JTAG 下载时钟没有降速。高速信号运行时 JTAG 时钟必须降到 10MHz 以下,否则 ILA 经常抓不到数据或抓到乱码。设置路径在 Hardware Manager 的 JTAG 频率选项里。
  3. ILA 采样时钟用了同相的 25MHz,导致建立保持余量不足。正确做法是使用 PLL 输出的 120 度相位偏移时钟(实测比 180 度更好)。
  4. 把 AD/DA 的模拟端直接短接,没有经过衰减电路。DA 的 ±5V 输出会超出 ADC 0~2V 的量程,必须走模块上现成的衰减链路(或按 VO=VI/5+1 自己搭)。
  5. FREQ_ADJ 设为 0 之后以为没有输出。FREQ_ADJ=0 是合法值(等效不分频,输出频率最高约 195.3KHz),不是关闭输出。
  6. 7010 与 7020 开发板的扩展口混淆。本实验用 J3 扩展口,两块板通用,但引脚约束文件不能拿错。

小结 & 下一步

本章建立了高速数据转换的完整概念:并行接口的高速 ADC/DAC、时钟相位规划、ROM 查表生成波形、ILA 实时观测。最核心的三个代码要点是 da_clk = ~clk 的锁存沿错开技巧、FREQ_ADJ 的分频思想、以及 120 度相移采样时钟。

下一步可以做两件事:一是把 ROM 换成方波/三角波表观察 AD 采集结果的变化;二是进入第三十八章,用本章的 AD9280 搭配 LCD 屏做一个真正的数字电压表。