基于FPGA的DDS信号发生器设计:Verilog实现与仿真验证

发布时间:2026/10/20 14:09:14

基于FPGA的DDS信号发生器设计:Verilog实现与仿真验证 简介基于FPGA的DDS信号发生器设计工程包面向FPGA入门学习者与数字信号处理方向的学生解决直接数字频率合成DDS中频率、幅度与波形可调的实现问题。工程以Verilog为核心配套Quartus工程文件覆盖相位累加器、波形查找表、幅度控制等关键模块也体现了奈奎斯特采样定理对波形重建的影响适合课程设计、毕业设计与自主实验参考。包内共254个文件核心包括.v源码、.tdf设计文件、.mif波形数据文件、.qpf/.qsf工程文件辅以.png原理框图、.txt与.readme说明、.m仿真脚本及大量工程记录、备份文件整体约22.08MB目录层次清晰便于按模块检索与复现。已有10018人学习下载。压缩包内不仅提供可直接综合的RTL代码还保留完整辅助脚本与工具记录可帮助读者理解DDS工作原理、参数可调的设计思路和工程组织方式快速搭建并验证自己的信号发生器。1. 基于 FPGA 的 DDS 信号发生器频率、幅度、波形三参数可调FPGA、Verilog、DDS 这三个词放到一起通常意味着你正在找一个能随时改变频率、幅度、波形的信号源。这个工程的思路很直接用 FPGA 内部逻辑搭一个相位累加器再用查找表把相位映射成正弦波数据最后通过输出级做波形选择和幅度缩放。它不依赖外部专用 DDS 芯片时钟一跑起来就能出波形适合需要把信号源嵌进自动化测试系统、或者做变频/移频实验的开发者。新手可以照代码抄一遍跑通仿真老手能直接用这个框架去改扫频逻辑或接入外部 DAC。2. DDS 核心原理与关键参数设计相位累加器、查找表和频率控制字2.1 DDS 为什么能精确控制频率相位累加器的工作方式DDS 的基础是相位累加器它本质上是一个每来一个时钟就累加一次频率控制字的寄存器。假设累加器位宽是 N 位每周期加上的数值是 freq那么累加器溢出一次所需要的时间就是 2^N / (freq * fclk)。输出波形的频率就是溢出率本身所以频率控制公式可以写成fin freq * fclk / 2^N反推频率控制字就是freq fin * 2^N / fclk。这个公式是整套代码的出发点后续所有频率参数都要靠它换算。选位宽 N 时我一般会把系统时钟频率、目标频率范围和最低步进一起放进公式里算而不是先拍脑袋定一个数。例如 50MHz 系统时钟下想做到 1Hz 级调节累加器位宽至少要 26 位因为 50e6 / 2^26 约等于 0.745Hz。工程里做成 32 位频率分辨率能到 0.011Hz 左右覆盖从 0.1Hz 到几十 MHz 的常见范围都没问题。累加器取了高位做查找表地址这意味着低位只参与进位累积并不直接进表。2.2 顶层模块代码可用的 Verilog 实现下面给出一个可直接仿真的顶层设计参数全部放在 module 头部方便适配不同开发板。module dds_top #( parameter N 32, // 相位累加器位宽 parameter M 10, // 查找表地址位宽深度 2^M parameter LUT_WIDTH 8 // 波形数据位宽 )( input wire clk, // 系统时钟 input wire rst_n, // 低电平复位 input wire [N-1:0] freq, // 频率控制字 input wire [1:0] wave_sel, // 波形选择0正弦 1方波 2三角波 input wire [LUT_WIDTH-1:0] amp, // 幅度控制字 output reg [LUT_WIDTH-1:0] dout // 波形输出 ); reg [N-1:0] phase_acc; // 相位累加器 always (posedge clk or negedge rst_n) begin if (!rst_n) phase_acc {N{1b0}}; else phase_acc phase_acc freq; end // 取累加器高位作为查找表地址 wire [M-1:0] addr phase_acc[N-1:N-M]; // 波形存储 reg [LUT_WIDTH-1:0] sin_lut [0:(1M)-1]; reg [LUT_WIDTH-1:0] tri_lut [0:(1M)-1]; initial begin $readmemh(sin_lut.hex, sin_lut); // 正弦表 $readmemh(tri_lut.hex, tri_lut); // 三角波表 end reg [LUT_WIDTH-1:0] sin_val; reg [LUT_WIDTH-1:0] tri_val; reg [LUT_WIDTH-1:0] squ_val; always (posedge clk) begin sin_val sin_lut[addr]; tri_val tri_lut[addr]; squ_val phase_acc[N-1] ? {LUT_WIDTH{1b1}} : {LUT_WIDTH{1b0}}; end // 波形选择 reg [LUT_WIDTH-1:0] wave_val; always (*) begin case (wave_sel) 2b00: wave_val sin_val; 2b01: wave_val squ_val; 2b10: wave_val tri_val; default: wave_val sin_val; endcase end // 幅度控制乘法加移位注意有效位 reg [LUT_WIDTHLUT_WIDTH-1:0] amp_result; always (posedge clk) begin amp_result wave_val * amp; dout amp_result[LUT_WIDTHLUT_WIDTH-1 : LUT_WIDTH]; // 右移LUT_WIDTH位 end endmodule这里有几个参数值得单独解释。M决定查找表深度10 位地址对应 1024 个采样点对普通正弦波来说相噪和杂散已经能压到可接受范围如果后续要接高精度 DAC 可以改成 12 位。LUT_WIDTH决定波形数据位宽8 位对应 256 级量化幅度配合 8 位 DAC 恰好一一对应。幅度控制放在查表输出之后做乘法这会导致额外一个时钟周期的延迟所以我在乘法器前后都用了寄存器打拍避免波形切换时出现中间态。2.3 查找表生成方法用 Python 生成初始化文件上面代码用$readmemh加载查找表所以需要准备sin_lut.hex和tri_lut.hex。这两个文件用下面的 Python 脚本几秒钟就能生成注意数值必须是无符号整数格式。import math depth 1024 # 查找表深度对应 Verilog 参数 M10 width 8 # 数据位宽对应 LUT_WIDTH8 max_val 2**width - 1 with open(sin_lut.hex, w) as f: for i in range(depth): sin_v (math.sin(2 * math.pi * i / depth) 1) / 2 f.write(f{int(round(sin_v * max_val)):02x}\n) with open(tri_lut.hex, w) as f: for i in range(depth): # 三角波前半段上升后半段下降 tri_v i / depth * 2 if i depth // 2 else (depth - i) / depth * 2 f.write(f{int(round(tri_v * max_val)):02x}\n)脚本把正弦波归一化到 0~255这样 8 位查找表可以直接用无符号数存储和参与乘法不用处理负数偏移。三角波表生成逻辑是前半周期从 0 升到最大值后半周期从最大值回落到 0。频率控制字的换算这里再强调一次假设系统时钟是 50MHz要输出 10kHz 正弦波freq 10000 * 2^32 / 50000000算出来约等于 858993。写在 Verilog 里用十六进制就是32h0D1B717仿真和上板都用这个值验证。3. 仿真验证Testbench 写法与波形分析方法3.1 搭建最小仿真环境拿到代码先别急着上板用 Modelsim 或者 Vivado 自带的仿真器跑一遍确认相位累加器和乘法器行为正确。Testbench 的关键是给足够的仿真时间因为 DDS 输出一个完整周期需要2^N / (freq * fclk)秒频率越低仿真时间越长。timescale 1ns / 1ps module tb_dds; reg clk 0; reg rst_n 0; reg [31:0] freq 32h0D1B717; // 10kHz 50MHz reg [1:0] wave_sel 2b00; reg [7:0] amp 8hFF; wire [7:0] dout; // 生成 50MHz 时钟 always #10 clk ~clk; // 实例化被测模块 dds_top #( .N(32), .M(10), .LUT_WIDTH(8) ) dut ( .clk(clk), .rst_n(rst_n), .freq(freq), .wave_sel(wave_sel), .amp(amp), .dout(dout) ); initial begin #100 rst_n 1; #2000000 $finish; // 仿真 2ms能覆盖约 20 个 10kHz 周期 end // 打印频率控制字对应的理论输出频率 initial $monitor(time%0t dout%0d, $time, dout); endmodule这个 Testbench 里#10翻转一次时钟产生 50MHz 信号#100后释放复位。仿真总时长 2ms能观察至少 20 个完整的 10kHz 波形周期。如果只跑 200us 会看到波形像没溢出完容易被误判为代码错误。$monitor可开可不开开了之后终端会持续刷数据配合波形图看会更直观。3.2 从仿真波形里读周期和幅度打开仿真波形后先看dout。10kHz 信号在 50MHz 采样下的周期应该是 5000 个时钟周期也就是 100us。确认波形每两个峰值之间间隔确实是 100us说明频率控制字换算正确。接着看幅度amp 8hFF时波形峰值应该接近 255如果把amp改成8h80峰值会变成约 128。这个现象验证的是乘法器的右移截断逻辑如果峰值变成 127 或者 127.5 附近出现台阶说明右移位宽选错了一位。仿真时常见的问题是看不到sin_val变化。如果你把内部信号添加进波形窗口后发现一直保持未知值十有八九是$readmemh的路径不对。Vivado 仿真默认工作目录是工程所在文件夹文件必须放在那个路径下或者用绝对路径。ModelSim 则是在你启动仿真的目录下找。这个坑我刚学时踩过很多次后来养成了先把 hex 文件放到仿真目录再跑的习惯。3.3 仿真通过后仍需关注的边界条件仿真能跑通只是第一步有几个边界条件仿真里不容易暴露但上板一定会遇到。第一是复位释放的时机如果复位释放瞬间刚好在时钟上升沿附近累加器初始相位可能是错的不过由于相位累加器是周期性的这个错误会在下一个溢出周期自动消失不致命但会干扰第一次触发。第二是波形切换瞬间的输出毛刺wave_sel变化时wave_val可能在一个周期内从正弦值跳到方波值如果后续接的是 DAC模拟输出会有一个尖峰需要在模拟端加一级低通滤波。这里给一个参数检查清单方便你在仿真环境里逐项核对。检查项预期结果判断方法输出频率与 freq 换算值一致波形两个峰值间隔 1/fin输出幅度ampFF 时接近满幅峰值等于 LUT_WIDTH 最大值波形切换wave_sel 变化后 1~2 周期输出新波形观察 disable/反应时间频率字边界freq1 时输出最低频率确认累加器不溢出报错复位信号rst_n0 时输出清零波形图无 X 态4. DDS 设计常见问题排查五条真实踩坑记录4.1 改了频率控制字但输出频率没变现象在板上通过按键或者串口修改 freq示波器观察输出频率始终不变甚至输出完全不动。原因最常见的是累加器位宽选小了频率字没有帮累加器溢出。比如 N16 时freq 必须大于 1 才能在一个合理时间内溢出如果你上位机发的 freq 数值只有几溢出周期长到看起来像没输出。另一种可能是你改了 testbench 里的 freq但顶层代码里还有另一个 freq 端口被例化时固定死写寄存器的地方没有连接到真正的顶层引脚。解决先看仿真里是不是能复现仿真里也不变就直接查例化连接。仿真正常但板上不变优先查 SPI/UART/拨码开关到顶层端口的映射把freq从只读常量改成实时可写寄存器并用逻辑分析仪抓一下写入时序。4.2 高频输出毛刺多、波形发虚现象频率设到几 MHz 后示波器上正弦波边缘出现明显毛刺波形边缘密度不一致用 DDS 字面意思理解就是“波形在抖”。原因查找表输出没有打拍组合逻辑直接接 DAC 或者后级电路导致在一个时钟周期内地址变化时查找表输出短暂出现错误值。尤其当 M 变大、查找表实现为分布式 RAM 时组合读路径延迟更大。解决把sin_val和tri_val的赋值从阻塞改成非阻塞并且保证查找表输出之后至少经过一级寄存器再送 DAC。我在 2.2 节代码里已经把sin_val sin_lut[addr]写成了时序逻辑这就是为消除毛刺加的打拍。如果毛刺仍存在检查 DDS 输出引脚到 DAC 的走线是不是太远。4.3 板上调幅度没有任何效果现象amp 从 0xFF 改到 0x01示波器上输出波形幅度纹丝不动。原因FPGA 的 GPIO 输出电平本身是固定的CMOS 输出高电平接近 VCC、低电平接近 GND。你在 FPGA 内部改乘法器只改变了数字信号的值但引脚上到不了模拟连续变化的电压没有 DAC 的情况下幅度调节无从谈起。这个是我带某高校同学做课程设计时发现的经典误区他们直接在排针上用杜邦线接示波器以为内部乘法后引脚电压会变。解决要么在外部用 R-2R 电阻网络或者 DAC 芯片把数字波形转换成模拟电压要么用内部乘法器配合外部可控增益放大器。如果只是做逻辑验证可以把幅度控制当成数字增益来看接一个 LED 阵列观察亮度变化也能验证乘法器效果。带宽要求不高时一个便宜的 R-2R 网络即可注意电阻精度会影响输出毛刺。4.4 仿真正常但板级没有输出现象ModelSim 里波形完全正常下载到板子后示波器什么都测不到检查引脚绑定也确认过。原因大概率是复位时序问题。板上电瞬间时钟还没稳定如果复位信号恰好是同步复位且释放时固定相位不对累加器可能一直抱死在某一个状态。另外有些板子的复位按键是上电默认高电平而你代码里用的是低电平复位rst_n一直为 0 导致整个模块被关掉。解决用逻辑分析仪或者示波器先测rst_n引脚电平再在代码里把复位改成默认值可用的电平。常见做法是加一个上电复位延时用一个计数器在复位释放后继续拉低几百个时钟周期让 PLL 锁定完再启动 DDS。我一般会写一个简单的复位模块而不是直接把外部复位信号接进 DDS。4.5 输出波形有台阶和量化噪声现象正弦波上升沿不是平滑的曲线而是阶梯状低频时尤其明显换更高位宽 DAC 后依然存在。原因查找表数据位宽和 DAC 位宽不匹配。例如 DAC 是 12 位你的 LUT_WIDTH 只有 8 位量化台阶直接被放大。也可能是查找表点数太少M8 时只有 256 点正弦波每个台阶对应 14° 相位变化视觉上很粗糙。解决把 LUT_WIDTH 扩到 12 或 16M 扩到 10 或 12。查找表深度增大不会消耗太多 BRAM1024 点 12 位宽的 ROM 大约 1.5KBFPGA 完全承受得起。理想情况下M 位宽对应的相位分辨率要小于 DAC 的 LSB 对应角度量化噪声才能被 DAC 自身噪声掩盖。5. 板上调试技巧与扩展思路从固定频率到简单扫频实现5.1 用示波器快速验证 DDS 输出的方法上板后先用低频信号验证比如 1kHz 正弦波。示波器时基调到 500us/div触发方式选上升沿。观察波形除了幅度和频率还要看波形顶部有没有划平顶部划平说明输出接近电源轨需要降低 amp 或者检查外部衰减电阻。建议第一步先跑方波方波对相位噪声不敏感容易确认电路通路再切到正弦波检查失真。如果没有 DAC直接测 FPGA 引脚输出的数字波形也是可行的只是看到的是 0 和 1 跳变需要用示波器的余晖模式观察占空比变化这个方法能验证频率字是否正确但看不出正弦幅度。要看到模拟正弦波至少要在输出引脚上并联一个 RC 低通滤波器截止频率设为 DDS 输出频率的 3~5 倍否则波形会严重衰减。5.2 扫频信号源的实现思路很多测试场景需要扫频信号比如测滤波器幅频特性。DDS 天然适合做扫频因为只需周期性改变 freq 寄存器。简单做法是用一个计数器做地址扫描每 N 个时钟把 freq 加上一个步长循环往复。要注意扫频速度和步进的配合步长太大频点不够密滤波器曲线会折线化扫频太快相位累加器来不及建立稳定输出测出来的幅度值偏小。我常用的参数是每 100us 改变一次 freq步长对应 100Hz这样从 1kHz 扫到 10kHz 需要 9ms示波器用峰值检测模式就能看到完整包络。这个扫频逻辑可以直接挂在 DDS 顶层外面不影响原有模块。如果想做更精细的扫频可以改成线性扫频计数器但注意相位累加器里没有相位连续控制扫频切换瞬间波形会有跳变这是 DDS 架构固有的特性。5.3 波形种类扩展的接口预留波形选择用 2bit 信号已经扩展了正弦、方波、三角波三种如果想加锯齿波或者任意波只需要在查找表区域增加一块 ROM 并在 case 里加一个分支。任意波形存储可以用上位机通过串口写入 FPGA 的 BRAM这个设计做出来后再加上外部 DAC整个信号发生器可以当一个小型任意波形源用。整个工程里最值得反复检查的还是 freq 换算和位宽匹配这两个点一旦出错仿真波形再漂亮上板也白搭。这套工程的完整 Verilog 代码、Python 查找表生成脚本和 Vivado 仿真工程都整理在下载包里拿回去直接打开仿真就能看到 10kHz 正弦波形改成自己的板子时钟后就能上板。从那以后我每次做 DDS 工程都会先算一遍频率字换算再检查一次查找表深度和 DAC 位宽是否匹配省掉不少调板时间希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/20 14:09:14

Spring Boot实战:苍穹外卖JavaWeb项目源码拆解与避坑指南

简介:苍穹外卖是一套基于JavaWeb技术开发的在线订餐系统完整项目,面向正在学习JavaWeb、需要真实项目经验以提升开发能力的初学者与进阶开发者。项目源码与笔记由作者在一个月内完整开发整理,系统覆盖Servlet、JSP、MVC设计模式、数据库表设计…

2026/10/20 14:09:14

从链式调用到图编排:LangGraph实现多智能体调度实战

如果要用一句话概括最近这轮架构折腾的核心,那就是:我把一条跑得挺稳的线性链Agent拆了,换成了基于LangGraph的动态图编排,重新复刻了某区域总部那套"AI agent指挥官"的调度内核。这篇既是架构实战记录,也是…

2026/10/20 14:09:14

MEDUSA:单模型内生树状注意力加速推理

1. 项目概述:当“草稿模型”不再是个累赘,而是一台可编程的推理加速引擎你有没有遇到过这样的场景:训练好一个主模型,想让它跑得更快,就硬塞进去一个轻量级的“草稿模型”——它负责快速生成一串候选token,…

2026/10/20 17:30:59

代码审查最佳实践:从形式主义到真正的质量闸门

代码审查这件事,我在三个团队里做过三种完全不同的姿势。第一个团队基本不审,合并全靠自觉,线上事故一年几次;第二个团队审了但流于形式,评论里全是“LGTM”和“变量名建议”;第三个团队才真正把代码审查做…

2026/10/20 17:30:59

C++直接映射大模型权重:绕过反序列化的加载优化

新上线的大模型权重 30 多 GB,第一次冷启动加载就卡了将近半分钟。坐在旁边的同学说磁盘速度看着没问题,怎么会这么慢。真正的瓶颈往往不在磁盘,而在加载链路里层层解析、分配和拷贝。这个场景促使我用 C 对模型序列化格式做了一次重构&#…

2026/10/20 17:30:59

五年前端面试笔记:十万字高频考点与原理级拆解

年前那阵子,我把自己关在书房里,干了一件看起来有点傻的事:把过去五年写前端攒下来的所有知识,全部推倒重来,整理成了一份面试自用笔记。刚开始只是想列个大概,结果越写越收不住,最后愣是写到了…

2026/10/20 17:30:59

企业智能体中心选型与落地指南:架构、成本与安全实践

1. 企业智能体中心到底在解决什么问题1.1 从一个真实困境说起去年下半年,我参与了一个中型制造企业的数字化项目。他们的IT负责人跟我吐槽:公司内部已经有三套AI工具在跑了——客服部门用了一套对话机器人,研发部门自己接了一个代码助手&…

2026/10/20 17:30:59

MathArena:用未污染数学竞赛题还原大模型真实推理能力

做LLM评测做得久了,心里总有一根刺:排行榜上的数字,究竟有多少是模型真会算,有多少只是因为它"背过"题目?尤其数学评测,你拿公开的数学竞赛题去测大模型,本质上就是往复读机面前摆历年真题。2025年,AI学术顶会上的MathArena项目把这个问题摆到了台面上——它专门构建…

2026/10/20 17:25:57

Apache SeaTunnel:多源数据同步与CDC集成框架实战解析

先说结论:如果你的团队正在为多源数据同步维护一堆脚本和自研工具,Apache SeaTunnel 值得花一周时间做技术验证。这个开源项目从早期的简单离线同步,一路演进到如今带有完整 Checkpoint、分布式快照、CDC(变更数据捕获&#xff09…

2026/10/19 18:06:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/20 1:05:37

单片机C运行时从启动到多任务:中断安全与共享资源冲突

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

GCC编译四步详解:预处理、编译、汇编、链接实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

FIW线是什么?FEP注塑线与普通氟塑料线的区别及应用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑