FPGA实战:基于DDS原理的信号发生器设计与Verilog实现 📅 发布时间:2026/9/1 7:43:50 👁 浏览次数: 简介这是一份基于Quartus II 的DDS信号发生器完整工程面向FPGA初学者和数字系统设计人员解决任意波形生成与频率调节问题也可作为相关课程的实验参考。压缩包共包含476个文件其中有Quartus工程文件qpf/qsf、Verilog/VHDL源码v/vhd、文本设计文件tdf、存储器初始化文件mif以及用于SignalTap逻辑分析和ModelSim仿真的stp与do等配置整体大小约10.14MB。工程基于DDS相位累加原理将正弦波、方波、三角波和锯齿波数据写入ROM并设计频率控制器、相位累加器和波形放大器三个模块通过freq、wave_sel、amp_adj信号分别控制输出频率、波形类型和放大倍数输出波形稳定、精度高频率范围大易于产生高频信号。已有5716人学习下载工程适用于Quartus II 13.0及以上版本模块化设计便于理解相位累加思想和FPGA开发流程既可直接运行验证也可作为波形合成设计的参考模板方便读者二次开发。 前阵子把吃灰很久的EGO1开发板翻出来重新跑了一遍之前调过的DDS信号发生器。DDSDirect Digital Synthesis直接数字频率合成这名字听起来高大上实质就是把“相位”这个抽象概念变成一串数字再用FPGA的高速并行能力把它推成实实在在的波形。这个项目非常适合FPGA入门到进阶过渡它一次性覆盖了Quartus II工程搭建、Verilog时序逻辑、ROM查找表、ModelSim仿真、板级示波器验证这几个核心环节做完一次基本对FPGA开发的完整流程门儿清。如果你手里正好有带USB Blaster的Altera/Intel开发板又想亲手造一个频率可调的正弦波发生器这篇文章可以少走不少弯路。1. 项目概述与核心原理拆解1.1 为什么是 DDS 而不是传统振荡器传统模拟振荡器想改频率要么换电容电阻要么调变容二极管电压频率稳定度和分辨率都很难兼得。而DDS走的是全数字路线频率只取决于一个数学公式里的“频率控制字”跟外部元器件参数几乎没有关系。用个生活化类比DDS就像一台极其精准的“相位采样机”。你在一个圆形跑道上跑步每跑一步都记录当前位于圆周的哪个角度这个角度就是相位。跑得越快单位时间内绕的圈数就越多对应输出频率越高。而FPGA做的就是用一个累加器快速“跑圈”把跑过的角度翻译成正弦波对应的幅值输出去。这个方案在频率分辨率、相位连续性、切换速度上的优势是传统振荡器很难达到的。1.2 DDS 频率合成公式一步一算DDS核心频率公式f_out FTW × f_clk / 2^Nf_out输出信号频率FTW频率控制字就是一个32位的累加步长f_clkFPGA系统时钟频率N相位累加器位数以EGO1板载50MHz晶振为例采用32位相位累加器想产生1kHz正弦波FTW的计算方法FTW f_out × 2^N / f_clk 1000 × 4294967296 / 50000000 ≈ 85899换算成十六进制就是0x14F8B把这个值作为累加步长相位累加器每个时钟周期加一次输出频率就是1kHz。设计DDS前先把目标频率范围定下来。理论上受奈奎斯特定理限制输出最高频率不超过25MHz实际工程应用通常限制在系统时钟的20%以下也就是10MHz以内否则重建滤波很难做波形毛刺会非常明显。2. 开发环境与硬件方案选型2.1 Quartus II 版本选择13.1 vs 15.0做这个项目之前我先卡在了Quartus的版本选择上。相信很多人也遇过这种场景教程写13.1官方推荐15.0下载页又一堆名字到底装哪个我最终用了Quartus II 15.0 Web Edition原因很直接EGO1用的是Cyclone 10 LP系列芯片Quartus II 13.1对Cyclone 10的支持不完整工程能建但后面器件选型、引脚分配都容易踩坑。15.0版本界面跟13.1几乎一样但器件库更新对Cyclone 10 LP的工程支持更友好。如果你是其他系列芯片比如Cyclone IV那13.1和15.0都能跑选13.1也行教程资料更多。但如果是Cyclone 10、Cyclone V建议直接上Quartus Prime 15.0或更高版本。安装时有一个很容易被忽略的点USB Blaster下载器驱动。Windows 10/11系统经常识别不到老版本Quartus自带的驱动需要手动把驱动路径指到Quartus安装目录下的drivers文件夹或者单独装Cypress USB驱动。这个问题我后面在排查章节还会再提一次。2.2 EGO1 开发板与 DAC 输出方案EGO1板子核心是Cyclone 10 LP 10CL025集成50MHz有源晶振、LED、按键、拨码开关以及一组扩展IO口做DDS绰绰有余。板载没有高速DAC芯片我的输出方案是外接一个8位R-2R梯形电阻网络做一个简易DAC。用8个I/O口输出数字波形数据经过R-2R电阻网络转换成模拟电压再进一个一阶RC低通滤波滤掉高频采样杂散R-2R网络的成本极低一颗0805电阻几分钱8位精度足够演示。如果你手里有现成的并口DAC模块比如AD9708直接把8位数据送进去效果更好。需要注意的是FPGA IO输出驱动能力和R-2R电阻网络的阻值要匹配如果电阻选太大比如100kΩ波形带载能力会很差接示波器探头后幅度直接掉一大截。我用了10kΩ和20kΩ的组合实测稳定。3. DDS 核心模块设计与实现3.1 相位累加器把频率控制字变成相位整个DDS最核心的模块就是相位累加器。它本质上就是一个N位寄存器每个时钟周期把上一次的值加上FTW。我选32位累加器理由是频率分辨率能达到50MHz/2^32 ≈ 0.0116Hz这个分辨率远超绝大多数应用需求。设计上如果不需要这么细用24位甚至16位累加器也可以代价是频率分辨率变粗低频段会明显感觉频率只能“跳着走”。相位累加器有两种“步进”策略需要区分FTW固定不变输出单一固定频率FTW实时变化输出频率实时切换只要在累加过程中改变步长下一次累加就按新频率走波形相位保持连续关键代码只有几行reg [31:0] phase_acc; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) phase_acc 32d0; else phase_acc phase_acc ftw; end3.2 正弦查找表相位到波形的映射相位累加器输出的32位相位不可能全部拿去查表否则ROM容量要大到爆炸。常见做法是截取高8位或高10位对应256点或1024点正弦表。我用的256点8位正弦表相位分辨率3.6度输出经过低通滤波后已经足够平滑。设计ROM查找表的流程用Matlab生成256点正弦数据量化为8位无符号整数把数据写入MIF文件在Quartus中调用ROM IP核载入MIF相位累加器的高8位作为ROM地址输出8位正弦值Matlab生成正弦表的代码思路N 256; sine_data round((sin(2*pi*(0:N-1)/N) 1) * 127.5); % 1和乘以127.5是为了把正弦波映射到0~255范围 % 8位DAC输出期望的是无符号波形所以需要直流偏置MIF文件每一行就是地址和对应的数据值也可以用Python脚本生成逻辑一样。如果不想用ROM IP核也可以用Verilog的case语句直接写查找表但256个case分支会占用大量LUT资源256点还好再大就明显不划算了。工程上还是推荐ROM方案。3.3 顶层模块拼接与按键调频顶层模块负责把相位累加器、ROM查找表、按键控制、PLL时钟管理拼在一起。为了在板上直观演示我用两个按键控制频率加减每按一次FTW增加或减少一个步进量。按键处理必须做消抖不然按一次频率会连续跳好几次。用最简单的20ms计数消抖即可不需要引入复杂状态机。整体模块结构module dds_top( input wire clk_50m, input wire rst_n, input wire key_add, input wire key_sub, output wire [7:0] dac_data ); reg [31:0] ftw; reg [31:0] phase_acc; reg [7:0] sine_data; // 按键消抖 频率控制字增减 // 初始FTW 32d85899对应1kHz always (posedge clk_50m or negedge rst_n) begin if (!rst_n) ftw 32d85899; else if (key_add_pulse) ftw ftw 32d1717; else if (key_sub_pulse) ftw ftw - 32d1717; end // 相位累加器 always (posedge clk_50m or negedge rst_n) begin if (!rst_n) phase_acc 32d0; else phase_acc phase_acc ftw; end // ROM查找表实例化 sine_rom u_sine_rom( .address(phase_acc[31:24]), .clock(clk_50m), .q(sine_data) ); assign dac_data sine_data; endmodule这里FTW每次加减1717换算成频率变化量约20Hz方便按键演示。ROM IP核的地址输入需要打一拍所以数据会比相位累加器晚一个时钟周期这个时序在DDS里完全没问题因为每个点都统一延迟一拍波形形状不变。4. 仿真验证与板级调试4.1 ModelSim 时序验证下载到板子之前务必先仿真否则一个引脚分配错误或时序问题查起来非常痛苦。Quartus II 15.0可以集成ModelSim也可以单独用ModelSim Starter Edition。我给顶层模块写了一个testbench主要验证两个点相位累加器输出是否是线性递增的三角波ROM输出是否呈现正弦规律testbench核心部分initial begin clk_50m 1b0; forever #10 clk_50m ~clk_50m; // 50MHz半周期10ns end initial begin rst_n 1b0; #100; rst_n 1b1; // 观察dac_data是否以正弦序列输出 #1000000; $stop; end运行仿真后把dac_data信号设为Analog显示能看到一条完整的正弦曲线。这时候可以检查一个周期包含多少个采样点50MHz / 1kHz 50000即每50个系统时钟周期应该推进一个ROM地址256个地址正好是一个正弦周期。如果在仿真里发现dac_data不是正弦波先看phase_acc的高8位有没有规律变化。一个是线性锯齿波另一个是正弦阶梯波这两个信号对了DDS设计基本就没问题。4.2 引脚约束与示波器实测仿真通过后进入板上调试。引脚约束在Quartus的Assignment Editor里完成clk_50m分配到板载50MHz晶振引脚rst_n分配到按键引脚key_add、key_sub分配到另外两个按键dac_data分配到扩展IO口的8个引脚分配完引脚编译下载.sof文件到FPGA。用示波器探头接R-2R网络的模拟输出端GND一定要共地否则测出来的全是噪声。实测1kHz正弦波输出示波器上能看到标准正弦曲线幅值约0~3.3VR-2R网络输出电压范围由IO电平决定。按键增加频率波形频率明显变密整个过程相位连续没有断裂现象。如果手头没有示波器可以拿一个耳机接口和音频功放模块听频率高低变化虽然不是专业验证手段但至少能确认DDS在正常工作。5. 常见问题与排查技巧实录5.1 输出波形毛刺严重这是做DDS最容易遇到的现象。毛刺来源主要有三类现象核心原因解决办法波形上叠加很密的尖峰R-2R电阻精度不足高位电阻误差大使用1%精度电阻尤其是高几位电阻输出波形像台阶低通滤波截止频率太高把RC低通截止频率压到输出最高频率的3~5倍附近波形跳变处有毛刺ROM地址和数据没有对齐确保ROM读取时地址和数据都寄存打拍避免异步路径我之前用5%精度的电阻搭R-2R网络输出波形在高位跳变点会有明显的毛刺换成1%精度电阻后改善非常明显。5.2 频率切换时波形不连续DDS的天然优势就是相位连续前提是FTW改变时相位累加器不清零。很多人习惯在切换频率时顺手把phase_acc清零结果波形在切换瞬间产生一个相位跳变体现在时域上就是波形断裂。正确做法是直接改变ftw寄存器的值让相位累加器在原有累加基础上继续运行输出波形自然连续。如果确实需要从零相位重新开始必须在设计中明确处理相位复位信号而不是简单清零。5.3 Quartus 编译与下载问题这块我踩过不少坑整理几个高发问题USB Blaster识别不到Windows 10/11下最常见。打开设备管理器如果看到未知设备Triple USB Vendor Class手动更新驱动路径选Quartus安装目录的drivers\usb-blaster或者使用ALTERA_USB_Blaster.inf强制安装。编译报错说器件不支持确认工程里的Device Family和具体型号是否选对。Cyclone 10 LP在Quartus 13.1里可能找不到换15.0就正常。引脚分配后编译通过但下载后没反应检查引脚约束是否正确特别是核心电压VCCIO的bank电压。EGO1扩展IO口某些bank可能是3.3V如果DAC接了5V器件还需要电平转换。下载成功后LED正常但示波器无输出先测IO口是否有3.3V电平信号然后检查R-2R网络和示波器探头接地很多时候是探头没接好不是电路问题。做这个项目最深的体会是DDS听起来是个很理论的信号处理概念但亲手在Quartus II里把相位累加器、ROM查找表、按键调频一路跑到示波器上出波形之后整个FPGA开发的流程就串起来了。后面如果还想扩展可以加调幅控制、相位调制或者把8位R-2R换成SPI接口的高速DAC芯片一整台简易信号源的雏形就出来了。把.sof转成.jic固化到配置芯片里上电免下载直接运行也是值得一试的收尾操作。本文还有配套的精品资源点击获取