ARTICLE DETAIL

资讯详情

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

FPGA多功能波形发生器设计:DDS原理与Verilog实现详解

FPGA多功能波形发生器设计:DDS原理与Verilog实现详解 简介这是一份基于FPGA的多功能波形发生器课程设计报告面向电子信息类本科生、FPGA初学者以及需要完成数字系统课程设计的同学。文档围绕波形发生器设计完整展开内容涵盖课设目的与要求、方案选择、设计原理、VHDL程序分析、仿真验证与RTL视图解读可帮助读者理解FPGA数字信号处理的基本流程并快速模仿搭建自己的设计。资源为单个doc文档压缩包大小946KB共1个文件以课程报告正文形式呈现正文包含摘要、章节目录与文献参考结构清晰适合用于学习汇报、设计参考或提交作业。已有167人浏览学习说明该主题有一定需求。借助这份报告读者可以掌握从需求分析、模块设计到编译仿真、警告处理与结果分析的完整思路同时获得常用波形发生模块的代码分析与关键函数语句解释为后续电路设计打下基础。1. 基于FPGA的多功能波形发生器课程设计最值得做深的是DDS和信号链很多电子信息、自动化专业的课程设计题目里都有“基于FPGA的多功能波形发生器”这个选项网上也能找到大量报告模板。但真把工程文件下载到开发板上经常出现波形拼不齐、频率调不准、数码管显示和输出对不上等问题。不同来源的代码风格差异很大有的用查表法做正弦波有的用计数器做方波把它们拼到一块时钟域、位宽和符号格式先打架了。这里按我习惯的做法把“多功能”拆成波形种类多、频率可调、输出可切换三个维度用DDS直接数字频率合成统一生成正弦波、方波、三角波和锯齿波所有波形共用同一个相位累加器模块边界清晰后面加AM、FM或者扫频也能在同样框架上扩展。这篇博文适合正在写课程设计报告或准备电赛基础题的人看完能直接对着代码和参数把板子跑起来。2. 波形发生器核心选型DDS方案与查表法、计数器方案的取舍2.1 为什么课程设计里选DDS而不是直接拼计数器方波、三角波、锯齿波都可以用简单的计数器生成比如方波就是计数器翻转输出三角波就是加减计数。但“多功能”一旦要求正弦波计数器方案就够呛了。正弦波需要按相位查幅度表而方波、三角波只是相位到幅度的映射关系不同。DDS的核心思想是把所有波形都看成“相位—幅度”映射相位累加器每周期累加一个频率控制字再把相位高位作为地址去查波形表。这样的话换波形只需要换查表的ROM或组合逻辑频率调整只需要改频率控制字结构非常统一。查表法虽然也能生成正弦波但频率切换要靠切换查表步进本质就是DDS的简化版不如直接把累加器做完整。计数器方案在频率切换时很难做到相位连续波形衔接处会有毛刺课程设计答辩时被问到“两个频点之间切换会不会有跳变”就容易卡住。2.2 DDS输出频率公式与相位累加器位宽选择DDS输出频率的计算公式是f_out FWORD × f_clk / 2^N其中FWORD是频率控制字f_clk是FPGA系统时钟N是相位累加器位宽。位宽N直接决定频率分辨率分辨率 f_clk / 2^N。如果开发板时钟是50MHz用16位累加器分辨率是50000000 / 65536 ≈ 762.9Hz也就是说想输出1000Hz实际只能到762.9Hz的倍数高出近24%。这个精度做音乐或信号源肯定不够看。所以常用做法是把N取到24位或更高分辨率能到2.98Hz配合按键步进调整就有实用价值。需要注意的是波形ROM一般只存2^M个采样点M比N小比如24位累加器取最高10位做地址查256点或1024点波形表。舍弃低位相位不影响频率精度只影响波形相位量化噪声。2.3 多功能波形发生器顶层模块拆解与各模块职责一个能在课程设计答辩环节站得住的系统顶层模块通常分成五个部分按键输入模块、DDS核心、波形存储/映射模块、DA输出驱动、数码管显示模块。按键部分负责读入波形选择、频率加、频率减三个动作需要消抖和同步DDS核心只维护一个相位累加器频率控制字由按键模块输出波形映射模块把相位地址翻译成对应波形的幅度值DA驱动则把幅度值按DA芯片时序送到数据总线数码管显示当前频率值和波形编号。下面用一张参数表把各模块的设计要点列出来。模块位宽/参数说明系统时钟50MHz以板载晶振为准后续频率控制字都基于它计算相位累加器24位频率分辨率约2.98Hz取高12位做波形地址波形ROM/映射12位地址8位数据256点/周期输出无符号幅度0~255DA芯片8位并行低有效写信号常见开发板用DAC0832或类似并行接口按键3个按下低有效分别控制波形切换、频率加、频率减这套拆分的好处是每个模块都好单独调试。DDS累加频率不对看相位累加器仿真波形就能定位DA输出毛刺先从波形映射模块下手。下面进入具体实现。3. 用Verilog把多功能波形发生器核心模块逐个写通3.1 相位累加器与波形表映射的Verilog实现下面这段代码是DDS的核心包含相位累加器、波形选择开关和波形映射三个部分。它没有依赖外部ROM IP核直接用case生成波形表适合课程设计这种对资源不敏感的场景。module dds_wavegen ( input wire clk, // 50MHz系统时钟 input wire rst_n, // 异步复位低有效 input wire [1:0] wave_sel, // 波形选择00正弦 01方波 10三角 11锯齿 input wire [23:0] fword, // 频率控制字 output reg [7:0] wave_data // 8位幅度输出无符号0~255 ); reg [23:0] phase_acc; // 相位累加器每个时钟沿累加一次频率控制字 always (posedge clk or negedge rst_n) begin if (!rst_n) phase_acc 24d0; else phase_acc phase_acc fword; end wire [11:0] phase_addr phase_acc[23:12]; // 取高12位做查表地址 // 按波形选择输出对应幅度 always (*) begin case (wave_sel) 2b00: wave_data sin_table(phase_addr); 2b01: wave_data phase_addr[11] ? 8hFF : 8h00; // 方波 2b10: wave_data {phase_addr[11:4], 4h0}; // 三角波 2b11: wave_data phase_addr[11:4]; // 锯齿波 default: wave_data 8h00; endcase end // 正弦查表这里用函数方式生成也可换成ROM function [7:0] sin_table; input [11:0] addr; begin // 用Matlab或Python生成256点正弦表后填入case sin_table 8h80; // 占位实际替换为查表 end endfunction endmodule这段代码里相位累加器的溢出频率就是输出频率fword在外部计算好再传入。相位取高12位实际只用了2048个地址正弦表如果只做256点需要对addr再截位到8位再做查表。三角波和锯齿波直接用了高8位幅度分辨率刚好是1/256。方波只判断最高位可以避免查表带来的延迟。实际工程中遇到DA输出波形台阶感明显问题多数出在波形表点数太少同一张表换到1024点就能明显平滑。需要注意波形映射用组合逻辑实现相位累加器是时序逻辑两者分开写综合后时序路径清晰。下面把波形表如何生成和数据校准讲清楚。3.2 用Python生成多功能波形发生器的正弦表与系数校准正弦表是课程设计里最容易出错也最容易被追问细节的部分。很多模板直接把sin值乘以255存成整数忽略了无符号偏移。8位DA芯片通常接受0到255的单极性输入而sin函数范围是-1到1直接取整会造成负半周截断。正确的映射关系是value (sin(2π×n/N) 1) × 127.5四舍五入到整数。这样波形中点对应127或128满幅对应255零对应0。import math N 256 # 采样点数 with open(sine_table.mif, w) as f: f.write(DEPTH 256;\nWIDTH 8;\nADDRESS_RADIX HEX;\nDATA_RADIX HEX;\n) f.write(CONTENT BEGIN\n) for i in range(N): val int(round((math.sin(2 * math.pi * i / N) 1) * 127.5)) if val 255: val 255 f.write(f{i:02X} : {val:02X};\n) f.write(END;\n)这里生成的MIF文件可以直接导入Quartus的ROM IP核也可以把表格粘贴到刚才Verilog代码的case语句里。DEPTH和WIDTH要和ROM IP核配置一致。生成后建议验证三个特殊点地址0对应0x80地址64π/2对应0xFF地址128π对应0x00。如果这几个值对不上多半是公式里漏了偏移量。做课程设计报告时可以在附录里贴上一小段Python代码答辩老师会觉得数据来源不是拍脑袋写的。3.3 按键处理与频率加减步进的合理设置按键输入是FPGA课程设计里必定出现的功能。这里采用最稳妥的三级同步加计数消抖方案。三个按键统一用同一个参数表控制。按键功能按键编号动作说明波形切换key0按下一次wave_sel加1循环0→1→2→3频率加key1按下一次fword增加一个步进值频率减key2按下一次fword减小一个步进值最小为1module key_scan ( input wire clk, input wire rst_n, input wire key_in, // 单按键输入 output reg key_pulse // 按键按下时的单周期脉冲 ); reg [2:0] key_sync; // 三级同步寄存器 reg [19:0] cnt; // 消抖计数器 reg key_stable; // 第一段异步信号同步化消除亚稳态 always (posedge clk or negedge rst_n) begin if (!rst_n) key_sync 3b111; else begin key_sync[0] key_in; key_sync[1] key_sync[0]; key_sync[2] key_sync[1]; end end // 第二段稳定电平判断持续抖动结束才认为按下 always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 20d0; key_stable 1b1; end else if (key_sync[2] ! key_stable) begin cnt cnt 1b1; if (cnt 20d999_999) begin // 20ms 50MHz key_stable key_sync[2]; cnt 20d0; end end else cnt 20d0; end // 第三段产生单周期脉冲 always (posedge clk or negedge rst_n) begin if (!rst_n) key_pulse 1b0; else key_pulse (key_stable 1b0); // 低有效按键 end endmodule这段代码把按键消抖拆成三段同步、消抖、脉冲生成。消抖时间常数是cnt的最大计数值50MHz时钟下计数到999999刚好20ms适应绝大多数机械按键的抖动范围。按键按下为低电平key_pulse在检测到稳定低电平期间每个时钟周期都输出高实际使用时还要再配合边沿检测。如果脉冲长度太长可以改成在key_stable从1变0的那一拍产生高电平。频率步进值放在顶层模块里FWORD_STEP 50_000_000 / 1_000对应1kHz步进也就是24位累加器下每次按键改变约1kHz输出频率这样显示和实际输出都对得上。3.4 数码管动态显示频率值与波形编号的实现数码管动态显示是FPGA热词里出现频率很高的功能点。波形发生器需要实时显示当前频率和当前波形编号如果用静态显示会占用大量IO动态扫描用4位数码管轮流点亮实现。刷新周期不能太慢低于60Hz人眼就能感到闪烁。常见做法是把刷新周期做到1ms左右即50000个时钟周期切换一次位选。module seg_display ( input wire clk, input wire rst_n, input wire [15:0] freq_bcd, // 频率值的BCD码每位4bit input wire [1:0] wave_sel, output reg [3:0] seg_sel, // 位选低有效 output reg [7:0] seg_data // 段选共阴极 ); reg [15:0] cnt; reg [1:0] scan_pos; wire [3:0] current_data; reg [7:0] seg_code; // 1ms扫描计数器 always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 16d0; scan_pos 2d0; end else if (cnt 16d49999) begin cnt 16d0; scan_pos scan_pos 1b1; end else cnt cnt 1b1; end // 根据位选获取当前要显示的数据 always (*) begin case (scan_pos) 2d0: current_data freq_bcd[3:0]; 2d1: current_data freq_bcd[7:4]; 2d2: current_data freq_bcd[11:8]; default: current_data {2b00, wave_sel}; // 最高位显示波形编号 endcase end // 段码转换共阴极数码管 always (*) begin case (current_data) 4h0: seg_code 8h3F; 4h1: seg_code 8h06; 4h2: seg_code 8h5B; 4h3: seg_code 8h4F; 4h4: seg_code 8h66; 4h5: seg_code 8h6D; 4h6: seg_code 8h7D; 4h7: seg_code 8h07; 4h8: seg_code 8h7F; 4h9: seg_code 8h6F; default: seg_code 8h00; endcase end // 输出位选和段选 always (*) begin seg_sel 4b1111; seg_sel[scan_pos] 1b0; seg_data seg_code; end endmodule这里scan_pos是扫描位置0到2显示频率的低三位第3位显示波形编号。freq_bcd在顶层模块里用二进制转BCD模块生成否则频率值超过9就需要额外处理。动态扫描的关键是段选数据必须在位选切换前稳定否则会出现拖影。如果看到数字有残影可以在位选切换后插入两个空周期再给段选数据。这个模块位选低有效共阴极数码管换成共阳极需要把seg_code按位取反。4. 在ModelSim里跑通多功能波形发生器的仿真验证4.1 用Testbench验证DDS输出频率和波形选择功能写Testbench的时候大多数人直接给fword赋一个固定值然后看仿真波形。这里需要验证两个东西输出波形周期是否符合公式按键切换波形后相位是否连续。下面的测试代码给fword定了两个不同值验证频率变化。timescale 1ns / 1ps module tb_dds_wavegen; reg clk; reg rst_n; reg [1:0] wave_sel; reg [23:0] fword; wire [7:0] wave_data; dds_wavegen uut ( .clk (clk), .rst_n (rst_n), .wave_sel (wave_sel), .fword (fword), .wave_data(wave_data) ); initial clk 0; always #10 clk ~clk; // 50MHz时钟 initial begin rst_n 0; wave_sel 0; fword 24d0; #100 rst_n 1; #100 fword 24d838861; // 50MHz * 1000 / 2^24 ≈ 1000Hz附近 #200_000; wave_sel 2b01; // 切方波 #200_000; fword 24d1_677_721; // 频率翻倍 #200_000; $stop; end endmodule这里fword的计算方式是f_out × 2^24 / f_clk。838861对应约999.7Hz如果观察波形周期是1ms说明累加器和查表逻辑正确。仿真的关键检查点是改变wave_sel时wave_data的第一个跳变点是否从当前相位开始而不是从相位0重新开始。因为统一用一个phase_acc天然满足相位连续。如果发现切换违和检查是否有别的模块单独管理相位。4.2 验证多功能输出时常见的三个仿真误区第一个误区是看方波只看wave_data不看它在DA输出端经过转换后的实际波形。方波在相位累加器端是理想0和255跳变但经过DA转换后会有上升沿和下降沿时间仿真里要看da_out引脚的波形。第二个误区是没有验证最小频率步进只验证了fword较大时输出正常。当fword等于1时相位累加器要过2^24个周期才走完一个完整波形仿真时间要开到秒级才看得到完整周期。第三个误区是复位信号没有跨时钟域处理直接在testbench里给rst_n低电平100ns就释放实际FPGA复位信号释放时可能正好在时钟上升沿附近产生亚稳态。仿真通过后上板验证时还有一个容易忽略的问题波形存储器的初始值。如果使用ROM IP核需要确认.mif文件路径正确否则仿真输出全是0。4.3 在ModelSim中快速定位波形异常的技巧如果仿真波形明显不是正弦波优先查波形映射模块的地址截位。24位相位累加器取高12位再查256点表时需要再截到8位。直接使用phase_acc[23:16]作为地址效果相当于取高8位是常见的地址用法。出现台阶状正弦波时检查正弦表生成时N的值N越小台阶越明显。波形显示频率翻倍但幅度变小多半是DA输出端电阻网络阻抗不匹配。结合ModelSim的波形对比功能把理想正弦波以模拟信号形式加载到wave窗口与生成的wave_data对比可以直观看到相位偏差。5. 把波形发生器调得更实用相位连续切换与DAC毛刺控制课程设计的验收标准和产品级信号源不一样但多花半小时处理以下两个细节报告和答辩都会明显加分。第一个细节是波形切换时的相位保持。上面代码已经把相位累加器独立出来切换wave_sel不会复位相位这就是相位连续的优势。可以做个简单实验在仿真中让正弦波运行到某个非过零相位直接切到锯齿波观察输出是否从当前相位值起跳。如果起跳点等于phase_acc当前高位对应的幅度说明相位连续性成立。第二个细节是DA输出毛刺的抑制。FPGA的引脚信号变化会有毛刺尤其波形表输出多位同时变化时DA数据总线的不一致会出现短时的输出尖峰。常见做法是加一级输出寄存器让数据在时钟驱动下统一更新或者用并行的DAC芯片带数据锁存功能。Verilog里可以加always (posedge clk) da_data wave_data;这样数据变化与时钟边沿严格同步毛刺时间被压到时钟周期内。如果板子上DA芯片对时序要求严格比如写信号需要先低后高要把写信号和数据信号的相对时序对齐。另一个实用技巧是把频率控制字做成表驱动比如按键切换“1kHz、5kHz、10kHz、50kHz”几个预置频点比连续加减更容易使用也更容易在报告里展示。课程设计做到这一步整套系统中的FPGA多功能波形发生器功能完整、逻辑清晰仿真和板上验证路径畅通。剩下的事情就是把顶层测试代码整理干净pin脚约束文件做好让每个输入输出信号都在报告中有明确定义。本文还有配套的精品资源点击获取
返回列表