简介:这是一份基于Xilinx FPGA的DDS与ADDA联合闭环测试工程文件,面向数字信号处理与高速数据采集方向的硬件工程师和进阶学习者,定位于解决DAC波形合成、ADC采样以及信号频谱验证的完整链路问题。工程以50MHz时钟驱动DDS IP输出正弦波,经DAC后自环至ADC输入,由MMCM分频产生25MHz采样时钟,并通过ILA捕获不少于2048个样点;配合VIO动态修改频率字,可生成1MHz与3MHz正弦信号,再用Matlab对ILA数据做频谱分析,从而验证DDS与ADDA链路正确性。资源共613个文件,约46.11MB,包含HDL源码、Vivado工程与IP配置、约束脚本、仿真测试脚本、ILA调试相关文件及Matlab脚本和csv数据文件,目录结构清晰,便于直接打开工程复现实验。压缩包内还提供可运行的bit流文件以及AN108模块插装注意事项、低通滤波通带特性等说明,适合已有一定FPGA基础、希望快速搭建DDS-ADC闭环调试环境并验证信号处理流程的开发者参考。目前已有320人学习下载,可作为课程设计或工程预研的完整示例。
1. DDS ADDA 测试工程:一套把信号生成与采集串起来的参考底板
DDS 和 ADDA 是几乎所有信号链项目的起点,但很多开发者拿到测试工程后连一个干净的正弦波都调不出来。这套 DDS ADDA 测试工程文件把信号生成与采集回放串成了完整链路:DDS 负责产生低频到高频的正弦激励,ADDA 一侧完成模拟信号的采样量化与数字回放,主控工程统一管理两边的时钟、触发和缓存。接手这个工程时,我本来以为只是抄几个寄存器配置,结果发现真正值钱的不是代码,而是验证链路的方法——先校准 DDS 频率字,再核对 ADC 参考电压,最后看 FFT 频谱有没有杂散。这套工程对调信号链路的工程师、写采集回放的 FPGA 开发者,以及做仪器仪表练手的从业者都合适;新手靠它能少走三个月弯路,熟手则能拿它当参考底板做二次开发。
2. DDS 频率字设计:从相位累加器到可复现的调频脚本
2.1 相位累加器与频率分辨率:32 位累加器背后的定量关系
DDS 的核心不是查表,而是相位累加器。每个时钟周期,累加器把一个频率控制字(FTW,Frequency Tuning Word)加到当前相位上,溢出时产生周期性的相位斜坡,再用这个相位去查正弦波表。输出频率的公式是:
f_out = FTW × f_clk / 2^N
其中 N 是累加器位宽,f_clk 是 DDS 工作时钟。常见的工程里 N 取 32,f_clk 取 50 MHz 时,频率分辨率是 50M / 2^32,约 0.0116 Hz。也就是说,16 位频率控制字只能到几百 Hz 步进,而 32 位字在低频段几乎可以做到连续可调。
选 32 位累加器并不是拍脑袋,它直接决定了两个指标:频率分辨率和频率切换速度。分辨率不够,扫频测试时相邻频点之间会出现跳变,画出的幅频曲线是锯齿状的;累加器位宽不够,高频段的 FTW 量化误差会被放大。工程中常见做法是把累加器固定为 32 位,ROM 表用高位截断后的 12~14 位去寻址,这样既保证了频率精度,又不会让波表存储爆炸。
实际调试时,我习惯先把公式写死在一个 Python 脚本里,算好 FTW 再下发寄存器,而不是在 HDL 代码里反复改参数。这个习惯帮我避免了很多次“感觉频率不对但不知道差在哪”的尴尬。
2.2 频率控制字反推:用一段脚本把目标频率变成下发值
拿过这套工程时,第一步不是编译,而是先算目标频率对应的 FTW。下面这段脚本我每次都会跑一遍,确认寄存器值无误再动手改代码:
def calc_ftw(f_out, f_clk, N=32): """ 根据目标输出频率、系统时钟和累加器位宽计算频率控制字 f_out: 期望输出频率, Hz f_clk: DDS 系统时钟, Hz N: 相位累加器位宽, 默认32 返回: ftw, 实际输出频率, 频率误差 """ ftw = int(round(f_out * (2 ** N) / f_clk)) actual = ftw * f_clk / (2 ** N) error = actual - f_out return ftw, actual, error # 例: 50MHz 时钟, 想要 10MHz 正弦波 ftw, actual, err = calc_ftw(10e6, 50e6) print(f"FTW = {ftw:#x} ({ftw})") print(f"实际频率 = {actual:.6f} Hz, 误差 = {err:.6f} Hz")逻辑说明:脚本先按公式把浮点目标频率转成最近的整数 FTW,再反算出实际输出频率和误差。int(round()) 这一步必须有,因为硬件累加器只认整数,直接截断会让低频段误差偏大。
参数说明:N 取 32 时输出 FTW 范围是 0 到 2^32-1,对应频率 0 到 f_clk。注意 f_out 不要超过 f_clk 的一半,否则输出波形会明显失真,这跟采样定理是同一个道理。想调频率就把 f_out 改成目标值重新跑一次,脚本给出的十六进制数直接填进寄存器。
2.3 波表与幅度控制:位宽选择、截断误差与杂散来源
DDS 输出的杂散主要来自两个地方:相位截断和幅度量化。累加器高位寻址时,低位的截断会造成相位误差,反映在频谱上就是与主频对称的杂散峰。ROM 表位宽到 12 位以上时,杂散抑制能做到 60 dB 量级,对一般测试场景足够;要求高时可以用两级查表或者加入抖动(dither)来打散杂散能量。
幅度控制则是查表后乘一个幅度字,通常在 DDS 核的输出级有一级乘法器。这里的坑在于乘法器的位宽:如果输出截断位宽小于 DAC 的输入位宽,波形会有明显的阶梯感,听感上和频谱上都能看出来。常见做法是 DDS 输出位宽比 DAC 位宽宽 2 位,把顶部的削顶余量留给幅度调制。
另外,DDS 输出是离散采样点,DAC 重建后会有一串镜像频率,分布在 f_clk ± f_out、2 × f_clk ± f_out 这些位置。测试工程的板子上必然要有椭圆低通或巴特沃斯低通做重建滤波,截止频率取 f_clk 的 40%~45% 比较稳妥。拿到工程后先确认板上这个滤波器的位置,有时候示波器看到的噪声不是电源问题,是镜像频率没滤干净。
3. ADDA 信号链路设计:采样率、参考电压与前端匹配的取舍
3.1 分辨率与采样率的关系:先定架构再挑器件
ADDA 这个词把 ADC 和 DAC 合在一起,但两边的设计逻辑完全不同。ADC 负责把模拟信号变成数字,DAC 负责把数字变成模拟,它们的共同约束是分辨率、采样率和输入输出带宽。
12 位分辨率下,满量程是 2^12 = 4096 个码,1 LSB 对应的模拟量是满量程除以 4096。比如满量程 2 V,一个码约 0.488 mV,这决定了你能分辨的最小信号变化。采样率则决定了信号的时间分辨率:125 MSPS 意味着相邻采样点间隔 8 ns,能采集的最高信号频率理论上不超过 62.5 MHz,实际工程留 80% 余量,取 50 MHz 以下比较合适。
先定架构再挑器件的逻辑是:先算你需要测量的最高频率,乘 2 得到最小采样率,再留 1.5 倍余量,这个值决定了器件等级。如果你只需要测 1 MHz 以内的传感器信号,用 125 MSPS 的 ADC 是浪费电源预算,20 MSPS 的器件足够;但如果你要做中频信号采集,采样率至少要 100 MSPS 起步,而且前端模拟带宽要能跟上。
3.2 参考电压与模拟前端:端点引脚处理决定信噪比下限
参考电压(Vref)是 ADDA 链路里最容易被忽略的参数。ADC 满量程输入范围由 Vref 决定,典型值是 1.024 V、2.048 V、4.096 V 这几个。Vref 引脚上的任何纹波都会直接调制到采样结果里,等效于给信号叠加了噪声。拿到工程后第一件事是看原理图里 Vref 旁边有没有足够容量的去耦电容,建议至少 10 µF 钽电容并联 100 nF 陶瓷电容,并且电容地端要单独走回芯片的模拟地焊盘,不能和数字地混在一起。
单端输入还是差分输入,决定了前端的复杂度。多数 ADC 支持差分输入,共模抑制比更高,但信号源是单端输出时,需要在前面加一级单端转差分电路。这里的常见做法是用一个高速运放做成全差分驱动,增益通过反馈电阻设定。注意全差分运放的 Vocm 引脚要接 ADC 的共模电压,电平和期望输入共模不一致时,静态工作点会偏移,信号幅度稍微大一点就进入非线性区。
输入阻抗匹配同样要提前算好。高频信号经过长走线时,反射会造成幅度波纹,测试频率到几十 MHz 时尤其明显。常见做法是 ADC 输入端并联一个 50 Ω 电阻到地,信号源用 50 Ω 同轴电缆驱动,把阻抗匹配做干净。
3.3 采样时钟与同步触发:抖动对高频采集的真实影响
采样时钟的质量直接决定 ADC 的信噪比。时钟抖动(jitter)造成孔径不确定性,采样点的时间位置随机偏移,等效于在信号上叠加了与信号幅度和频率成正比的噪声。频率越高、信号幅度越大,抖动的影响越严重。
DDS 和 ADC 共用一个时钟源,是这套工程里最值得学习的设计点。DDS 需要干净时钟保证输出频谱纯净,ADC 需要同步时钟保证采样点与波形对齐。如果两边的时钟各自独立,采样点会相对 DDS 输出缓慢漂移,做回环测试时幅度会周期性起伏,看起来像低频调制,实际上是采样失步。
测试工程的典型做法是:用同一个晶振倍频出 DDS 和 ADC 的工作时钟,再用一个同步脉冲触发启动。同步脉冲的作用是把相位对齐的起点固定下来,后续采样点相对 DDS 输出始终保持固定相位。如果工程里做到了这一点,回环测试波形会非常稳;如果做不到,FFT 结果会出现明显的不确定度。
4. 工程主控实现:并行数据时序、SPI 寄存器与 FIFO 缓存
4.1 顶层文件布局与模块划分
拿到测试工程后先看顶层例化,不要从底层文件开始读。常见的主控结构是 FPGA 加外部 ADDA 芯片:FPGA 里有一个 DDS 核、一个并行数据接口、一个 SPI 配置模块和一个 FIFO 缓存。顶层文件里这几个模块的例化关系,就是整个工程的数据流骨架。
模块划分有个通用原则:数据通道和配置通道分开。DDS 输出的数据走并行总线,采样回来的数据走另一个并行总线,而寄存器配置走 SPI。混在一起的时候,调试时序很难定位。这个工程如果分得干净,跑通的可能性就大;如果配置线和数据线在代码里纠缠不清,建议先自己重构再调试。
另外留意异步时钟域的处理。DDS 数据是系统时钟域的,ADC 采样数据可能因为启停控制变成另一个时钟域,这时不能直接跨时钟域打两拍送进 FIFO,要先把数据对齐到统一的读写时钟上。常见做法是把 FIFO 做成异步 FIFO,读写时钟独立,指针用格雷码同步。
4.2 并行数据接口时序与 IO 约束
并行数据接口的时序问题,是 FPGA 调试里耗时间最多的地方。DAC 和 ADC 的数据手册上都有建立时间(t_setup)和保持时间(t_hold)的要求,FPGA 侧要保证输出数据晚于采样时钟沿到达,并且保持足够时间。
一个容易踩的坑是数据时钟的相位选择。有的 ADC 在时钟上升沿输出数据,有的在下降沿输出,主控里必须根据实际器件把采样边沿配成相反的沿。工程里如果留了这个参数,调试会很快;如果写死了,换器件或改板子后必须回来改这一项。常见做法是在接口模块顶部留两个 parameter,一个管输入采样沿,一个管输出驱动沿。
IO 约束也建议放进工程的约束文件:
set_input_delay -clock adc_clk -max 3.5 [get_ports adc_data[*]] set_input_delay -clock adc_clk -min 0.5 [get_ports adc_data[*]] set_output_delay -clock dac_clk -max 4.0 [get_ports dac_data[*]] set_output_delay -clock dac_clk -min 1.0 [get_ports dac_data[*]]这些值和 PCB 走线长度、器件 IO 输出延迟有关,第一个版本按推荐值填,跑完时序报告后再微调。没有约束文件时,工具会把接口时序按理想情况处理,问题在综合时发现不了,跑到板上才出怪现象。
4.3 SPI 配置通道:慢速控制与上电顺序
ADDA 芯片的寄存器配置一般走 SPI,速率不高但时序要求严谨。配置通道常见问题是时序图中“CS 拉低到第一个 SCLK 上升沿”的延时不够,导致第一个 bit 丢失。下面这段伪代码是我拿到任何 SPI 器件都会先写一遍的模板:
void spi_write_reg(uint8_t cs, uint8_t addr, uint8_t val) { cs_low(cs); delay_us(10); // CS 拉低后等待器件就绪 for (int i = 7; i >= 0; i--) { // 先发地址字节, MSB 在前 sclk_low(); sdi_write((addr >> i) & 1); sclk_high(); // 上升沿锁存数据 } for (int i = 7; i >= 0; i--) { // 再发数据字节 sclk_low(); sdi_write((val >> i) & 1); sclk_high(); } cs_high(cs); delay_us(5); // 片选释放后留出写周期 }逻辑说明:地址和数据都是 MSB 先发,每个 bit 在 SCLK 的低电平期间放到数据线上,上升沿到来时被器件锁存。先写地址再写数据是大多数 SPI 寄存器的规则,但有的芯片要求地址和数据合并成一个 16 位帧,需要根据手册调整。
参数说明:delay_us(10) 和 delay_us(5) 是安全余量,实际可以更短,但上电初期不建议冒险。SCLK 的频率默认给 10 MHz 以内,配寄存器属于低频操作,没必要追求高速。注意有些芯片支持级联,片选信号要跨多条链路保持状态,这种场景要单独做处理。
5. 调试避坑:ADDA 信号链路最常见的五个翻车点
5.1 DAC 没有输出:时钟、复位和使能信号的排查顺序
现象:DDS 配置正常,寄存器回读也正确,但 DAC 输出端量不到交流信号,只有一个直流电平。
原因:最常见的是 DAC 处于省电模式或输出使能没打开。很多 DAC 芯片有独立的输出使能位,默认上电是关闭状态;还有的芯片要求复位释放后至少再等若干微秒才能写配置寄存器,否则第一个配置字被吞掉。
解决:先看 DAC 工作时钟有没有起来,再查复位引脚状态,最后读输出使能寄存器。示波器先测时钟引脚,再用逻辑分析仪抓 SPI 配置字,两步就能缩小到具体原因。从那以后我每次都会在代码里加一个“配置完成后回读校验”的步骤,不再赌它一定写进去了。
5.2 ADC 读回恒定值:参考电压引脚与上电顺序排查
现象:ADC 数据总线输出固定值,信号输入端加多少电压都不变。
原因:参考电压没有建立。有些 ADC 的 Vref 引脚需要外部电阻分压网络提供,如果旁路电容没焊或者取值太小,Vref 电压有半个就可能让 ADC 输出码值恒定。另一个隐蔽原因是电源上电顺序:数字电源比模拟电源先起来,芯片内部 ESD 二极管把参考节点拉偏,导致 Vref 无法到达正常范围。
解决:用万用表量 Vref 引脚电压,对比手册典型值;再查电源时序要求,如果规定 AVDD 先于 DVDD,需要在板子上加电源时序控制。工程里如果已经有现成的电源管理芯片,检查它的使能引脚配置就能解决。
5.3 FFT 频谱多出杂散:电源分割与地弹耦合
现象:输入纯净单频正弦,FFT 结果里除了主频,四周出现一对或几对对称杂散峰。
原因:数字地与模拟地在芯片底部短接成“孤岛”,高频数字信号的回流路径被切断,回流电流只能绕大圈,形成地弹噪声耦合到模拟输入端。FFT 上看到的杂散峰往往不是电源直接纹波,而是地电位在 ADC 采样瞬间发生跳变。
解决:把模拟地和数字地在芯片下方用零欧电阻或磁珠单点连接,模拟部分是星形接地拓扑,数字部分保持低阻抗回路。PCB 改不了的,可以在软件上做平均处理降低杂散底,但治标不治本。这个问题的排查顺序是:先短接模拟输入端看本底,再接信号源看杂散是否随输入变化——杂散间距等于地弹噪声频率时,基本可以锁定。
5.4 高频输出幅度跌落:DAC 建立时间与缓冲运放带宽
现象:DDS 输出低频幅度正常,频率到几十 MHz 后示波器看到信号幅度明显变小。
原因:DAC 的建立时间(settling time)限制了输出变化速度。低速 DAC 在 100 MSPS 更新率下,建立到 0.1% 精度的过程还没走完,下一个采样点就来了,表现为高频正弦幅度下降。另一个原因是 DAC 后面接的缓冲运放增益带宽积不够,100 MHz 信号经过 50 MHz 带宽的运放,幅度直接掉一半。
解决:查 DAC 手册的建立时间和更新率,确认当前设置是否匹配;运放选择增益带宽积至少 5 倍于最高输出频率。把这个参数写进工程文档里,换板子或换芯片时对照检查,不再凭经验猜。
5.5 复位后时序错乱:软复位与时钟稳定延时的配合
现象:硬件复位之后立即写寄存器,回读数据正常,但输出波形完全不对;过几秒再重新配置一次就正常了。
原因:复位释放瞬间,时钟芯片或 PLL 输出频率还没有稳定。此时 SPI 配置虽然成功,但 DDS 的相位累加器是在一个尚未稳定的时钟下开始工作的,现象就是频率不对或波形有毛刺。软件复位同样有这个问题,复位瞬间内部状态清零,但时钟电路需要一个启动期。
解决:任何复位操作后,至少延时 100 µs 再配置第一个寄存器;工程里建议在顶层模块里加入一个复位状态机,按延时顺序释放各模块复位。从那以后我每次做 FPGA 工程都强制走一遍这个流程:先复位时钟域,再释放外设复位,最后写配置寄存器。
6. 进阶用法:把测试工程改造成简易幅频响应测试工装
这套 DDS ADDA 测试工程最大的价值其实是当基础底板做二次开发。我通常会把它改造成一个简易幅频响应测试工装:用 DDS 产生扫描正弦,经过被测电路,再由 ADC 同步采样,对每个频点的采样数据做 FFT,提取基波幅度。这样一台跟随 FPGA 跑的逻辑分析仪就成了信号源加分析仪的组合体。
具体做法是让 DDS 的频率字按对数间隔递增,每改一次频率字后等 FIFO 缓存两个完整周期,再锁存采样数据。对锁存的 1024 点序列做 FFT,找最大幅度对应的 bin 作为基波幅度,把频率和幅度记录成一组数据。直连状态下先测一遍作为基准曲线,接入被测电路后再测一遍,对应频点的比值就是增益或衰减。
用这套方法验证一个简单 RC 低通滤波器时,我测到的截止频率与计算值偏差不到 5%。误差主要来源是频率字四舍五入和 FFT 的泄漏效应,加汉宁窗后泄漏带来的幅度误差可以控制在 1% 以内。需要和设备供应商提供的网络分析仪对比时,最好先做一次三点校准:零频点、截止频点、高频衰减区各测一次,修正系统增益偏移。
从那以后我每次拿到新的 ADDA 评估板,都会先按这套流程跑一遍单频点回环自测,确认 DDS 输出和 ADC 采集的幅度误差在 1% 之内,再去做其他实验。一个工程值不值得留,不在代码量有多大,而在于能不能从里边抽出这套可以反复用的验证方法。希望帮到你。
本文还有配套的精品资源,点击获取