十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FPGA并行ADDA转换与VGA波形显示:AD9708/AD9280时序分析

FPGA并行ADDA转换与VGA波形显示:AD9708/AD9280时序分析 简介这套FPGA读写AD9708AD9280的ADDA实验工程面向数字电路与FPGA学习者尤其适合正在练习AD/DA驱动时序和VGA波形显示的开发者。工程基于Cyclone IV E系列EP4CE6F17C8器件使用Quartus 17.1开发完整覆盖ADC数据采集、DAC波形输出以及VGA显示三个核心环节代码注释清晰、模块划分明确可直接下载后打开工程编译验证。压缩包共163个文件以Verilog源码、Quartus工程配置.qsf/.qpf、初始化数据.mif、编译报告与PDF文档为主另含可烧录的.sof文件包体仅5.33MB便于快速上手。配套文档对AD9708与AD9280的接口逻辑和实验现象做了说明能帮助读者理解数模转换中的数据同步与时钟设计思路。目前已有945人学习适合希望从零搭建ADDA采集与显示链路的FPGA初学者参考。1. 为什么AD9708AD9280的ADDA实验值得用VGA来验证拿到这个名为 an108_adda_vga_test 的 Quartus 工程时压缩包里一半是编译中间文件真正值得看的是 Verilog 逻辑FPGA 同时驱动 AD9280 做模数采集、AD9708 做数模回放再把采到的数据送到 VGA 显示器画波形。很多做 FPGA 入门的人会卡在并行 ADC/DAC 的时序建立上不是看不懂代码而是不知道为什么 AD9280 的 8 位数据会在 VGA 上变成一团噪点。这套逻辑的价值在于它把 ADC 缓存、DAC 波形发生器和 VGA 显示放在同一个时钟框架里适合想独立调通 ADDA 读写、又不想上逻辑分析仪的人。文章从头分析接口时序和代码分段最后给出工程恢复和 SignalTap 调试的完整路径。2. 读懂AD9708和AD9280的并行时序再拆顶层信号打开 top 模块能看到三类信号ADC 输入、DAC 输出和 VGA 输出。信号命名里 adc0_buf 表示一级缓存wave0 表示叠加后的波形层grid 表示网格层。先弄清每个信号到底由谁产生、被谁消费再谈代码不然很容易把 adc_clk 和 dac_clk 当成同一个时钟后面全乱。并行 ADDA 的特点是引脚数多但协议简单难点就两个一是采样数据的同步锁存二是数据在跨时钟域时的缓存写入。2.1 AD9280一个时钟沿抓一次8位数据AD9280 是 8 位并行 ADC最大采样率在 32 MSPS 级别编码时钟由 FPGA 直接输出到 ad9280_clk 引脚。手册上 CLK 上升沿启动一次转换数据经过流水线延迟后出现在 ad9280_data[7:0] 上。因此当前时刻读到的数据并不是当前时刻的模拟输入而是几个采样周期前的历史值但只要后续缓存和 VGA 扫描保持连续这个延迟不会被肉眼察觉。reg [7:0] ad9280_ff1; reg [7:0] ad9280_ff2; always (posedge adc_clk or negedge rst_n) begin if (!rst_n) begin ad9280_ff1 8d0; ad9280_ff2 8d0; end else begin ad9280_ff1 ad9280_data; ad9280_ff2 ad9280_ff1; end end这里打两拍的目的不是消抖而是把引脚上的并行总线数据同步到 adc_clk 域降低亚稳态向后续逻辑传播的概率。ad9280_ff1 是第一拍直接采 ad9280_dataad9280_ff2 是第二拍通常作为实际数据参与缓存写入。如果你发现波形上出现随机毛刺先把这两拍补上再检查后面 RAM 的写使能有没有用过 ad9280_ff1。对 AD9280 这种并行总线来说牺牲一个采样周期完全不影响显示效果。2.2 AD9708数据与时钟的边沿要对齐AD9708 是 8 位并行 DAC最高 125 MSPSFPGA 侧只要送 8 位数据和时钟。DAC 在 ad9708_clk 上升沿锁存数据所以数据端必须满足建立时间。工程里 dac_data 来自一个 512 深度的 ROMROM 地址在每个 dac_clk 下降沿加 1这样 ROM 输出经过内部寄存器后在下一个上升沿仍然稳定相当于给 DAC 留出了半个时钟周期的建立时间裕量。always (negedge dac_clk or negedge rst_n) begin if (!rst_n) rom_addr 9d0; else rom_addr rom_addr 9d1; end rom u_sin_rom ( .address(rom_addr), .clock(dac_clk), .q(dac_data) );这里的关键是 ROM 输出天然比地址晚一个 dac_clk 周期所以地址在下降沿跳变、DAC 上升沿采样时dac_data 已经稳定。如果你换用组合逻辑实现的查找表一定要重新做时序分析否则高速下会出现 DAC 输出频谱变差。这个工程是低速演示dac_clk 通常和 adc_clk 同频但相位需要错开后面 PLL 配置会细说。2.3 VGA 波形显示双口 RAM 的读写仲裁VGA 640x48060 需要 25 MHz 像素时钟工程里 video_clk 就是这个频率。AD9280 采样数据按顺序写入深度为 2048 的 RAMVGA 控制器扫描到有效显示区时按列地址从同一块 RAM 读出数据再映射成波形亮度。写入侧时钟是 adc_clk读出侧时钟是 video_clk两边频率、相位都不同所以不能用单口 RAM必须用双口 RAM。常见的做法是写侧用一个状态机控制 adc0_buf_wr、adc0_buf_addr、adc0_buf_data读侧用行内计数器生成 vga_rd_addr。读写地址在逻辑上没有互斥关系DPRAM 内部会仲裁但可能会有一行数据前半部分是新采样、后半部分是旧采样的现象表现为波形在水平方向轻微错位。后面第四章我会给出一个用双帧切换消除这个问题的思路。2.4 顶层信号怎么对应到硬件引脚信号名方向位宽在工程里的作用ad9280_datainput8AD9280 并行采样输出ad9280_clkoutput1送给 AD9280 的采样时钟ad9708_dataoutput8送给 AD9708 的并行数据ad9708_clkoutput1送给 AD9708 的锁存时钟vga_out_hs / vga_out_vsoutput1 / 1VGA 行场同步vga_out_r / g / boutput5 / 6 / 5VGA 模拟 RGB 引脚top 模块里还有 wave0_hs、grid_hs 这些内部信号它们不是引脚而是 VGA 时序经过图层叠加后的中间总线。调试 ADDA 实验时最值得抓的是 adc0_buf_wr、adc0_buf_addr 和 adc0_buf_data 这三个内部信号它们能直接告诉你 ADC 数据有没有真正进入缓存。引脚约束建议按原理图逐一分配不要靠 Quartus 自动分配否则 PIN 位置对不上板子会毫无反应。3. Verilog 代码实现PLL 分频、ADC 缓存与 DAC 波形发生器顶层代码本身不算长但数据通路值得拆成三段看时钟产生、ADC 写入 VGA 读取、DAC 波形回放。这个实验的全部核心在 PLL 配置和双口 RAM 例化上这两处只要有一处参数不对VGA 画面就会乱。先看时钟树再看缓存最后看叠加输出顺序不能反。3.1 PLL 配置25MHz 像素时钟与 50MHz ADDA 时钟Quartus 17.1 里通过 IP Catalog 生成 ALTPLL输入时钟选板载 50 MHz输出三路c0 给 VGA 25 MHzc1 给 ADC 采样时钟 50 MHzc2 给 DAC 时钟 50 MHz并且 c2 与 c1 反相 180°。反相的目的是让 AD9708 在采样沿能看到更稳定的数据因为数据来自 ROM 寄存器天然落后 dac_clk 一个周期。pll u_pll ( .inclk0(clk), .c0(video_clk), .c1(adc_clk), .c2(dac_clk), .locked(pll_locked) ); wire rst_n_real rst_n pll_locked;加上 pll_locked 作为复位条件是为了防止 PLL 未稳定时逻辑就启动导致 RAM 或 ROM 处于非正常状态。如果你不关心上电时序直接用 rst_n 也能跑但遇到 VGA 画面闪烁、波形位置随机漂移时先检查这里的复位电路。50 MHz 的采样时钟对 AD9280 完全够用实际上这个工程跑 25 MHz 也看不出差别频率低一点反而抗干扰更好。时钟名频率用途相位video_clk25 MHzVGA 行场扫描和图层叠加0°adc_clk50 MHzAD9280 采样和数据写入0°dac_clk50 MHzAD9708 数据锁存180°3.2 ADC 缓存用双口 RAM 把采样数据跨到像素时钟域缓存模块我习惯直接例化 altsyncram 或 altdpram深度 2048地址位宽 11 位。写端口时钟是 adc_clk写使能是 adc0_buf_wr写数据是 ad9280_ff2读端口时钟是 video_clk读地址由 VGA 行扫描模块产生。下面代码按 altdpram 原语写实际工程可以在 IP Catalog 中生成同功能的双端口 RAM。altdpram adc_ram ( .wrclock(adc_clk), .wren(adc0_buf_wr), .wraddress(adc0_buf_addr), .data(adc0_buf_data), .rdclock(video_clk), .rdaddress(vga_rd_addr), .q(vga_rd_data) );写侧逻辑上adc0_buf_wr 应只在有效采样期间拉高。最稳妥的写法是adc0_buf_addr 从 0 到 2047 循环每来一个 adc_clk 上升沿只要数据有效就写一次当地址到达 2047 后回卷同时翻转一帧计数器。这样 VGA 读侧每次扫描一行就能读到连续 2048 个采样点。如果读侧地址范围超过 2048读到的就是重复数据波形会横向拉伸所以 vga_rd_addr 必须限制在 10 位以内剩下高位取行计数器低位借用。这里最容易犯的错误是写使能长期拉高导致同一地址被反复改写波形出现断层。我一般会在 SignalTap 里同时观察 adc0_buf_wr 和 adc0_buf_addr确保写使能是一个时钟周期脉冲而不是电平。3.3 DAC 波形回放ROM 查找表生成正弦波AD9708 的数据可以来自 ADC 环回也可以来自 ROM 查找表。这个工程里用了一个 512 点正弦 ROM地址位宽 9 位数据位宽 8 位。每来一个 dac_clk 上升沿ROM 输出一个正弦码值地址在 dac_clk 下降沿累加保证数据在上升沿稳定。生成 ROM 初始化文件可以用 Python 一行循环。import math with open(sine.mem, w) as fp: for i in range(512): v int(127.5 127.5 * math.sin(2 * math.pi * i / 512)) fp.write(f{v:02x}\n)这段代码把正弦波一个周期量化成 512 个点数值范围 0x00 到 0xFF0x80 对应零电平。ROM 在 Verilog 中用$readmemh加载Quartus 综合时会把它实现为内部存储器块。DAC 输出频率按公式 50 MHz / 512 97.7 kHz 计算这个频率远低于 AD9708 的极限也低于 AD9280 的实际采样能力适合直接观察。如果你想要可调频率只需要用 NCO 累加器替代固定地址加 1把步进值改成键盘输入即可。3.4 图层叠加网格优先还是波形优先VGA 显示粒子效果来自两层信息的叠加网格层和波形层。网格层是固定图案波形层来自 ADC 缓存读出的 vga_rd_data。我常用 select 信号做优先级网格像素设置为最高优先级网格位置输出白色其他位置输出波形颜色这样波形会被网格切割便于读数。assign video_r grid_pixel ? grid_r : wave_r; assign video_g grid_pixel ? grid_g : wave_g; assign video_b grid_pixel ? grid_b : wave_b; assign vga_out_r[4:0] video_r[7:3]; assign vga_out_g[5:0] video_g[7:2]; assign vga_out_b[4:0] video_b[7:3];8 位颜色截断成 5/6/5 位时直接扔掉低 3 位或低 2 位。这是最省 LUT 的转换方式也符合 VGA 电阻网络的实际精度。若你的板子 RGB 顺序是 565green 的高位分配方式可能不同具体看原理图里的电阻网络接在哪几个数据脚上。不要在这里做加权平均VGA 模拟输出本身精度有限低两位的舍入误差肉眼完全看不出。4. Quartus 17.1 工程恢复、引脚约束和 SignalTap 调试这个压缩包里有一堆 .ammdb、.bpm、.cdb 文件它们都是 Quartus 编译过程留下的数据库不是源码。拿到手第一步不是打开工程而是先清理编译中间文件再干净地重建一次。很多人在这一步走了弯路双击 .qpf 打开后发现引脚没有分配或者编译直接报错就是因为中间文件覆盖了新写入的配置。4.1 哪些文件保留哪些可以删除文件类型作用处理建议.qpf / .qsf工程定义和配置保留.v / .sv逻辑源码保留.sdc时序约束保留.ammdb / .bpm / .cdb编译中间数据库删除.sof / .pof下载和固化文件可重新生成.qdbQuartus 数据库可删除解压后先删除编译中间文件让 Quartus 重新做全量分析避免旧数据库干扰新约束。如果你用的是 Git Bash 或者 Linux 终端可以这样清理cd /d/project/an108_adda_vga_test rm -rf db incremental_db find . \( -name *.ammdb -o -name *.bpm -o -name *.cdb \) -delete删除后启动 Quartus选择 File - Open Project找到 .qpf 打开。如果压缩包里没有 .qpf则需要新建一个工程器件选择 Cyclone IV E 系列 EP4CE6F17C8顶层模块设为 top把 zip 内所有 .v 文件添加进去再手工补上 SDC 约束。编译第一次会慢一些因为缓存全部重建但之后的增量编译会恢复正常。4.2 引脚约束QSF 和 SDC 一个都不能少引脚约束写在 .qsf 里用 QSF 语法。EP4CE6F17C8 是 256 引脚封装不同开发板把 AD9280、AD9708 挂在不同的 BANK 上。下面是一段约束模板具体 PIN 编号必须按你手上的原理图改set_location_assignment PIN_85 -to clk set_location_assignment PIN_86 -to rst_n set_location_assignment PIN_101 -to ad9280_data[0] set_location_assignment PIN_102 -to ad9280_data[1] set_instance_assignment -name IO_STANDARD 3.3-V LVTTL -to ad9280_data[*] set_instance_assignment -name IO_STANDARD 3.3-V LVTTL -to ad9708_clk set_instance_assignment -name IO_STANDARD 3.3-V LVTTL -to vga_out_hsIO_STANDARD 用 3.3-V LVTTL 即可因为 Cyclone IV E 的大多数引脚都在 3.3 V BANK。注意 ad9280_data[7:0] 全部要约束到同一个 BANK且电压标准一致否则编译会报 I/O bank mismatch。VGA 同步引脚 HS/VS 是单端输出不用加差分标准。时序约束方面至少要创建两个主时钟板载输入时钟和 PLL 输出时钟。PLL 输出时钟可以在 SDC 中直接 get_nets也可以让 Quartus 综合后自动传播。初学者最常犯的错误是完全不写 create_clock导致信号完整性分析默认按住 1 GHz 评估时序很难收敛。create_clock -period 20.000 -name sys_clk [get_ports {clk}] create_clock -period 20.000 -name adc_clk [get_nets {u_pll|c0}]SDC 文件需要添加到工程的 Timing Settings 里。如果不加工程能编译但 Fitter 会随意布线高速 ADDA 场景下波形可能出现时序违规。低速实验可以跑但我不建议养成这个习惯。4.3 SignalTap II抓内部信号比万用表好用ADDA 实验调波形时我只信 SignalTap II。不需要外部逻辑分析仪也不需要额外接线直接在下载器里跑。方法是在 Quartus 里创建一个 .stp 文件添加 adc_clk 作为采样时钟添加 adc0_buf_wr、adc0_buf_addr、ad9280_ff2 作为观测节点触发条件设为 adc0_buf_wr 的上升沿。设置项推荐值目的Sample depth2048覆盖一整帧波形数据Trigger positionCenter同时看到触发前后数据Trigger conditionsadc0_buf_wr 1捕获写入起始点Storage qualifieradc0_buf_addr 2048限制存储区间编译后打开 Programmer下载 .sof 到 FPGA。运行 SignalTap等一段时间如果采集到波形你会看到 adc0_buf_wr 是一个窄脉冲adc0_buf_addr 在 0 到 2047 之间循环。如果看到 adc0_buf_wr 长期为 1说明状态机有问题采样数据会在同一地址反复覆盖VGA 显示必然断层。SignalTap 会占用器件内嵌存储资源EP4CE6F17C8 只有约 270 Kbit RAM配置 2048 深度采样完全够用但不要同时开多组采样否则会影响原工程的布线。4.4 常见问题板级排查表现象可能原因处理方式VGA 画面全黑PLL 未锁定或 rst_n 无效SignalTap 观察 pll_locked 是否为高波形固定在屏幕一侧读地址没有按行清零检查 vga_rd_addr 是否在行同步期间被复位DAC 输出没有信号dac_clk 相位设置错误PLL 中把 dac_clk 改为反相 180°AD9280 数据全是 0 或 FFADC 输入超出量程检查模拟输入是否偏置到量程中点ModelSim 仿真报 license 错误仿真器环境变量未生效确认 LM_LICENSE_FILE 指向 license.dat第一个值得排查的是 PLL因为整个工程所有时钟都由它派生。第二个是 IO 标准配置BANK 电压不对会导致采样数据读回来全是 FF。ModelSim license 的问题跟 Verilog 本身无关但 Quartus 17.1 经常出现主要是安装时没有设置环境变量启动图形界面时没有自动加载。5. 验证 ADDA 数据通路的土办法从 DAC 自环到 SignalTap 导出 CSV芯片级验证不需要昂贵的仪器利用开发板本身的 AD9708 和 AD9280 就能搭一个自环。把 AD9708 的模拟输出经过电阻分压后接到 AD9280 的模拟输入前提是两者电平范围匹配。如果 AD9708 输出是 0 到 1 V而 AD9280 输入量程是 0 到 2 V就直接串联电阻分压如果板上已有跳线帽直接短接最省事。信号源就是正弦 ROM所以你不接外部信号源也能验证通路的完整性。5.1 环回前必须确认的三个节点先测 AD9708 数据端再测 AD9708 时钟端最后测 AD9280 时钟端。用万用表量 adc_clk 和 dac_clk 的直流电压如果 PLL 输出正常50 MHz 时钟用万用表测会显示约 1.65 V 左右的平均值因为方波占空比 50%。如果平均值接近 0 V 或 3.3 V说明时钟没有翻转问题回到 PLL 配置或引脚约束。环回接线要短。杜邦线超过 10 cm 时AD9280 的输入会看到明显的振铃采样数据低几位会跳动。如果必须用长线串一个 33 欧姆电阻靠近 ADC 引脚能改善反射。5.2 用 SignalTap 导出 CSV 并还原波形SignalTap 采到的数据不只有人肉观测一种用途可以把整帧数据导出成 CSV然后用 Python 重新画图验证波形质量。在 SignalTap 界面右键选择“Export”格式选 CSV导出后在 Python 里读取 ad9280_ff2 列画前 512 个采样点import csv import matplotlib.pyplot as plt samples [] with open(adc_capture.csv) as fp: reader csv.reader(fp) header next(reader) for row in reader: if len(row) 2: samples.append(int(row[1], 16)) plt.plot(samples[:512], linewidth1) plt.xlabel(sample index) plt.ylabel(adc code) plt.grid(True) plt.show()这里 row[1] 是 SignalTap 导出表中 ad9280_ff2 所在列十六进制字符串转成整数。如果环回成功你会看到一条 97.7 kHz 的正弦波幅值约 256 格。出现阶梯感是正常的因为 ADC 是 8 位量化间隔就是 1 LSB。如果波形峰峰值不到 200 个码值说明 AD9708 输出幅度没有充分利用 AD9280 的量程需要调整偏置或放大电路。还有一个技巧计算相邻两个样本的码值差连续出现几十次 0 说明采样时钟没有连续翻转优先检查 adc_clk 引脚上的容性负载。这个数据文件本身就是后续做数字滤波、FFT 分析的基础不要把 SignalTap 只当成看波形的工具。本文还有配套的精品资源点击获取
返回列表