ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于FPGA的DDS信号发生器设计与方波生成实现

基于FPGA的DDS信号发生器设计与方波生成实现 简介基于FPGA的DDS信号发生器设计工程面向数字电路与信号处理学习者可生成方波、正弦波、三角波、锯齿波四种基本波形通过按键调整频率控制字FCW实现频率精细调节。工程采用相位累加器产生数字相位方波通过相位范围比较输出高低电平正弦波借助查找表实现三角波与锯齿波利用累加翻转逻辑完整呈现DDS核心架构。压缩包共449个文件以Quartus工程数据库文件hdb、cdb、rdb等为主含Verilog源码、mif波形存储表、sof/pof配置文件、pin引脚分配、仿真报告及readme说明整体15.57MB。已有498人学习下载。同时附带完整工程备份与分层目录可快速定位各模块并直接打开修改适合课堂实验、课程设计及竞赛准备。通过该工程可深入理解FPGA并行计算与直接数字合成技术的结合方式为通信测试、音频处理等实际应用提供设计参考。1. 用FPGA把方波做到MHz级DDS的工程化选择方波谁都会生成一个定时器翻转IO就能跑出几百kHz。但当你需要区分1.000000 MHz和1.000001 MHz、需要16路相位严格同步、需要频率切换时波形不产生相位跳变计数器和PLL方案就都不成立了。直接数字合成DDS通过相位累加器把频率分辨率做到系统时钟的2的N次方分之一FPGA负责把累加、查表和波形映射放进并行流水线。这套基于FPGA的DDS信号发生器可同时输出正弦波、方波、三角波和锯齿波方波只是其中实现成本最低的一路一个比较器逻辑即可。它适合FPGA入门阶段想弄清楚DDS机制的人也适合测试测量、电机驱动等场景需要自研廉价信号源的工程师。2. DDS相位累加器原理与FPGA位宽选型2.1 相位累加器决定频率分辨率DDS的本质是把相位随时间做线性增长再将当前相位映射成波形幅度。相位累加器是一个N位寄存器每个时钟脉冲增加一个频率控制字FCW累加到2^N后自然溢出回绕完成一次完整相位扫描。输出频率由f_out FCW × f_clk / 2^N决定。以50 MHz系统时钟、32位累加器为例频率分辨率是50e6 / 4294967296 ≈ 0.0116 Hz也就是说FCW可以无级分辨到毫赫兹级别这是计数器分频方案难以达到的。在FPGA里相位累加器退化成一条加法链综合后就是一组级联的加法器和寄存器Xilinx的DDS IP核内部实现完全相同。自己写累加器最大的好处是每一级都可以通过仿真窗口观察行为模型与硬件一一对应。对信号发生器仿真或FPGA入门学习来说这种方式能同时把RTL设计和DDS数学联系起来。DDS相位输出连续增长因此频率切换过程不会产生毛刺或相位跳变这也是DDS适合做扫频信号源的根本原因。相比之下AD9834/AD9850等DDS芯片遵循同样原理只是把累加器做进专用硅片FPGA方案的优势在于波形种类和位宽都能自定义改一段ROM就能出任意非标准波形。2.2 相位截断与查表深度取舍用完整32位相位去索引波形ROM需要2^32个存储单元即使改用BRAM也需要上百块因此工程上只取相位累加器的高位作为地址。截断会带来相位噪声正弦波表现为杂散分量但方波这类二值波形几乎不受截断影响ROM深度只影响多种波形复用一个地址总线时的拾取范围。累加器位宽查表地址位宽频率分辨率50MHz典型场景24102.98 Hz教学演示32120.0116 Hz通用信号发生器32140.0116 Hz低杂散正弦输出48161.78e-7 Hz精密仪器从表格可见32位累加器配合12位地址是最均衡组合。计算FCW时可直接用Python脚本批量生成省去手工换算f_clk 50e6 # 系统时钟50MHz N 32 # 累加器位宽 for f_out in (100e3, 1e6, 5e6): fcw int(round(f_out * 2**N / f_clk)) print(ff_out{f_out/1e6:.3f} MHz, FCW{fcw})脚本输出结果就是后面要写进RTL的常数或按键步进的基准值浮点累积误差不会渗入FCW。这里需要注意f_out不能超过奈奎斯特上限f_clk/2DDS理论上限虽然可以到f_clk/2但实际工程建议不超过f_clk/3否则镜像分量会明显干扰方波边沿质量。2.3 四种波形的相位映射差异波形实现方式额外资源相位连续性正弦波ROM查表1块BRAM连续方波取累加器最高位无连续三角波高位异或加取反2个LUT连续但有尖角锯齿波直接截取高位无回绕处瞬跳方波的映射最简单相位累加器最高位为0时输出低电平为1时输出高电平。这个判断在Verilog里就是一行连续赋值综合后只有一个LUT和一个输出寄存器assign square_raw phase_acc[PHASE_WIDTH-1]; // 50%占空比方波三角波要利用方向位。取高12位phase_hi后方向位为0直接输出为1取反输出得到从0升到满幅再降回0的对称波形。锯齿波不需要方向判断直接把phase_hi当作幅度溢出回绕即完成重置。对比四种映射可以发现方波和锯齿波是零成本波形三角波消耗2个LUT只有正弦波必须依赖存储器这也是很多低成本信号发生器优先保证方波和锯齿波精度的原因。对FPGA入门学习者来说建议先用方波打通整个链路因为方波不依赖ROM仿真时任何位宽变化都能通过dac_data的0/1跳变直接观察调通了再添加正弦ROM和三角波逻辑。3. Verilog实现四波形DDS信号发生器3.1 顶层模块与端口规划顶层模块把DDS拆成相位累加器、方波生成、波形选址三个独立部分。端口设计上保留12位DAC数据总线波形选择用2位拨码频率控制字由按键模块给出。参数化设计让同一套代码可以适配不同时钟和DAC位宽改动仅限顶层参数。端口名方向位宽功能说明clkinput150 MHz 系统时钟rst_ninput1低电平复位wave_selinput200正弦 01方波 10三角 11锯齿btn_freqinput4频率调整按键输入dac_dataoutput12当前波形采样值输出module dds_top #( parameter PHASE_WIDTH 32, parameter ADDR_WIDTH 12, parameter DAC_WIDTH 12 )( input wire clk, input wire rst_n, input wire [1:0] wave_sel, input wire [3:0] btn_freq, output wire [DAC_WIDTH-1:0] dac_data ); wire [PHASE_WIDTH-1:0] freq_ctrl; wire [PHASE_WIDTH-1:0] phase_acc; wire [ADDR_WIDTH-1:0] phase_hi phase_acc[PHASE_WIDTH-1 -: ADDR_WIDTH]; wire [DAC_WIDTH-1:0] squ_data, sin_data, tri_data, saw_data; dds_phase_accum #(.PHASE_WIDTH(PHASE_WIDTH)) u_phase ( .clk(clk), .rst_n(rst_n), .freq_ctrl(freq_ctrl), .phase_acc(phase_acc) ); dds_square_wave #(.DAC_WIDTH(DAC_WIDTH)) u_square ( .clk(clk), .phase_acc(phase_acc), .dac_data(squ_data) ); assign dac_data (wave_sel 2b01) ? squ_data : (wave_sel 2b10) ? tri_data : (wave_sel 2b11) ? saw_data : sin_data; endmodule逻辑说明顶层把所有子模块通过相位总线串起来dac_data用多路选择器在四种波形间切换。phase_acc是全局唯一的相位基准四个波形模块共享同一个相位累加器输出因此切换波形时相位不会重置周期间连续。wave_sel切换瞬间dac_data就切换到目标波形不需要额外的握手信号。参数说明PHASE_WIDTH和DAC_WIDTH是核心位宽参数修改后内部所有总线自动对齐。ADDR_WIDTH只影响ROM地址截取对方波无影响。被例化的两个子模块都使用同名参数Vivado综合时会检查参数传递是否一致不匹配会直接报错提示。3.2 相位累加器与频率控制字加载module dds_phase_accum #( parameter PHASE_WIDTH 32 )( input wire clk, input wire rst_n, input wire [PHASE_WIDTH-1:0] freq_ctrl, output reg [PHASE_WIDTH-1:0] phase_acc ); always (posedge clk or negedge rst_n) begin if (!rst_n) phase_acc {PHASE_WIDTH{1b0}}; else phase_acc phase_acc freq_ctrl; end endmodule逻辑说明这是整套信号发生器的频率基准。每个时钟上升沿累加一次FCWFCW固定时相位斜率恒定输出频率稳定FCW变化时相位从当前值继续走不产生相位跳变。复位后相位归零确保首拍从相位0开始输出。累加器自然溢出不需要显式清零这也是DDS能够覆盖超宽频率范围的原因。参数说明freq_ctrl位宽必须与PHASE_WIDTH一致否则综合时发生位宽截断高频段频率会突然跳变。复位优先级高于累加上电后建议保持复位至少一个时钟周期再释放防止相位累加器进入未知态。若需要多通道同步输出只需复制多个累加器并共用一个复位和时钟相位天然同步。3.3 方波、三角波与锯齿波的并行生成module dds_square_wave #( parameter DAC_WIDTH 12 )( input wire clk, input wire [31:0] phase_acc, output reg [DAC_WIDTH-1:0] dac_data ); always (posedge clk) begin if (phase_acc[31]) dac_data {DAC_WIDTH{1b1}}; // 高电平满幅 else dac_data {DAC_WIDTH{1b0}}; // 低电平零 end endmodule逻辑说明方波利用相位累加器最高位做幅度映射输出只有满幅和零两种状态。每个时钟周期最多翻转一次边沿严格对齐相位0和π位置不会出现计数器方案那种比较器建立时间带来的抖动。dac_data在时钟沿后更新和DAC接口的建立时间天然匹配。参数说明DAC_WIDTH为12时满幅输出4095接10位DAC则改为10位全1多出的低位直接截断。如果只想要1位CMOS逻辑方波可以不经过DAC模块直接把最高位引到FPGA引脚驱动能力取决于该引脚的IO标准设置。若要调整占空比把判断位换成相位累加器高位的某个阈值比较即可。三角波和锯齿波从相同的高位相位直接构造wire tri_dir phase_hi[ADDR_WIDTH-1]; wire [ADDR_WIDTH-1:0] tri_amp tri_dir ? (~phase_hi) : phase_hi; assign tri_data {tri_amp, {(DAC_WIDTH-ADDR_WIDTH){1b0}}}; assign saw_data {phase_hi, {(DAC_WIDTH-ADDR_WIDTH){1b0}}};逻辑说明phase_hi取自相位累加器高12位。tri_dir为1时位取反波形开始下降构成峰值为满幅的三角波位取反不产生进位链综合后延迟比减法小。锯齿波直接截取高位回绕瞬间从满幅跌到0对应相位累加器的自然溢出点。这两路数据在顶层多路选择器中与方波和正弦波汇合。参数说明如果三角波需要偏置到非对称范围可以加一个直流偏置寄存器在拼接前与tri_amp做加法。这里用拼接操作把12位相位扩展到12位DAC总线若DAC_WIDTH大于ADDR_WIDTH剩余低位补零量化噪声由地址位宽决定。正弦波模块只需例化ROM用phase_hi索引预先用Python生成的coe文件coe中每个地址存放一个12位正弦样本。ROM在Vivado中会推断为BRAM数据文件用Xilinx ROM IP核加载。四路波形共用同一个phase_hi总线切换不会造成相位错位。4. 频率控制字计算与按键调频接口实战4.1 FCW取值与频率换算DDS输出频率由f_out FCW × f_clk / 2^N决定反向推导FCW f_out × 2^N / f_clk。以50MHz时钟和32位累加器为例目标1MHz时FCW 1e6 × 4294967296 ÷ 50e6 85899345.92取整85899346实际输出频率偏差小于0.000005 Hz远小于晶振ppm级别误差。目标频率FCW实际频率误差10 kHz8589930.0000004 Hz100 kHz85899340.000004 Hz1 MHz858993450.000005 Hz5 MHz4294967290.000023 Hz12.5 MHz10737418240.00006 Hz如果使用24位累加器1MHz对应FCW 335544.32只能取整335544实际频率相差约2.98 Hz这就是24位方案只适合教学的原因。设计时把上表直接固化成ROM查找表或按键步进数组能避免FPGA运行时做定点除法节省大量逻辑资源。实际产品中频率和FCW的对应关系通常由上位机算好后下发FPGA只负责加载和锁定。按键切换频率时freq_ctrl寄存器根据步进更新reg [PHASE_WIDTH-1:0] freq_ctrl; always (posedge clk or negedge rst_n) begin if (!rst_n) freq_ctrl 32d85899345; // 默认1 MHz else if (key_freq_up) freq_ctrl freq_ctrl 32d85899; // 步进约1 kHz else if (key_freq_dn) freq_ctrl freq_ctrl - 32d85899; end逻辑说明FCW更新与相位累加器解耦按键触发只修改freq_ctrl寄存器。相位累加器下一拍就按新FCW累加频率切换立即生效且不打断当前相位这是DDS相对于锁相环方案的核心优势。递增步进85899对应1kHz的FCW增量实际偏差约0.0004 Hz。参数说明步进值要防止累加越界freq_ctrl最高不超过f_clk/2对应FCW否则输出频率超过奈奎斯特限制方波会严重失真。上限保护可以用饱和比较实现到达上限后保持不再累加。步进值在不同频段可切换低频段用细步进高频段用粗步进。4.2 按键消抖与脉冲生成机械按键直接接在逻辑上会产生5-20ms抖动一次按压可能被判定多次。常规做法是用20ms窗口采样配合两级同步触发器消除亚稳态reg [1:0] btn_sync; always (posedge clk or negedge rst_n) begin if (!rst_n) begin btn_sync 2b11; cnt_20ms 0; btn_prev 1b1; key_freq_up 1b0; end else begin btn_sync {btn_sync[0], btn_freq[0]}; if (cnt_20ms 20d999_999) begin cnt_20ms 0; key_freq_up (btn_sync[1] 1b0) (btn_prev 1b1); btn_prev btn_sync[1]; end else begin cnt_20ms cnt_20ms 1b1; end end end逻辑说明btn_sync是两级触发器同步链把异步按键输入转变到系统时钟域。每20ms对同步后的信号采样一次检测到电平从高变低时输出一个周期宽度的key_freq_up脉冲。这个脉冲直接送给freq_ctrl更新逻辑一次按压只产生一次触发。参数说明50MHz下计数到999999正好是20ms想加快响应可以缩短窗口到5ms但消抖效果会变差。这里以btn_freq[0]单键示例多按键时每个按键独立复制一套同步采样逻辑或用状态机轮询减少寄存器开销。按键低有效和高有效板的差异可以在同步链前加一个反相器统一。4.3 频率范围与步进精度的工程约束32位累加器频率分辨率极高但按键步进必须和应用匹配。100Hz步进对应FCW增量约86个单位适合低频精细调节MHz级扫频则需要大步进快速遍历。可以设计两个按键分别控制粗调细调粗调步进为1MHz对应FCW细调步进为1kHz对应FCW两个按键同时按下时归零复位。如果完全不想自己写累加器Xilinx的DDS Compiler IP核可以配置相位累加器位宽、输出通道数和相位偏移但IP核生成的正弦查表是固定结构方波输出仍需自己从phase_out里取最高位。自己写RTL与IP核混用是常见做法用IP核处理正弦用自定义逻辑处理方波和锯齿波既保留IP核的时序优化又拿到二值波形的零成本优势。DDS输出方波在接近f_clk/2时每周期只有两三个采样点边沿位置只能落在相位累积序列上方波的上升沿间隔会出现量化抖动。仪器级方波源通常把DDS频率上限限制在f_clk/8以下比如50MHz时钟只输出到6.25MHz这样每个周期至少16个采样点抖动肉眼不可见。5. 仿真验证与板级调试技巧5.1 用Vivado行为仿真验证FCW与相位翻转testbench把顶层模块实例化设置固定FCW运行足够周期数module tb_dds_top; reg clk 1b0; reg rst_n 1b0; reg [1:0] wave_sel 2b01; wire [11:0] dac_data; always #10 clk ~clk; // 50 MHz initial begin repeat (5) (posedge clk); rst_n 1b1; repeat (10000) (posedge clk); $finish; end dds_top #( .PHASE_WIDTH(32), .ADDR_WIDTH(12), .DAC_WIDTH(12) ) u_dds_top ( .clk(clk), .rst_n(rst_n), .wave_sel(wave_sel), .btn_freq(4b0000), .dac_data(dac_data) ); endmodule逻辑说明复位释放后跑10000个时钟周期足够看到多个完整方波周期。仿真结束后在波形窗口添加phase_acc和dac_data检查phase_acc[31]的翻转周期是否为FCW整倍。10000周期只是起点若要验证1MHz方波在50MHz时钟下有50个周期建议仿真5倍数量观察频率漂移。参数说明波形的切换验证可以在仿真的中段改变wave_sel观察dac_data是否从当前相位点连续过渡到目标波形不应出现从0重新开始的现象。仿真的时间步长默认1ns对50MHz时钟足够精确。5.2 方波上升沿与DAC建立时间的匹配FPGA并行数据到DAC通常要求DAC_CLK比数据晚半个周期采样以避开数据翻转的非稳态区。在Vivado约束里对DAC数据线设置set_output_delay把数据驱动延迟约束到DAC建立时间窗口内。若DAC数据是输入到FPGA则用set_input_delay约束外部数据相对DAC_CLK的到达时间。常见做法是在顶层用ODDR原语输出DAC_CLK确保时钟和数据边沿对齐。方波整形的工程建议在DAC后加一级高速比较器把模拟沿转换为陡峭CMOS沿阈值设在DAC满幅一半处。方波经过比较器后边沿抖动由比较器传播延迟抖动决定不再受相位量化噪声影响。这个做法在输出给电机预驱或通信同步信号时非常关键。5.3 频率计实测与晶振精度判断上板后用频率计设定1MHz输出测量10秒周期数配合信号发生器仿真结果对照。如果测得频率稳定在999999.8到1000000.2之间说明DDS链路正常误差主要来自板载晶振偏差。此时用高精度外部时钟源替换板载晶振偏差会直接回落借此可以区分是时钟问题还是逻辑问题。若偏差超过0.1%优先检查时钟树约束和晶振负载电容。最后一个可落地的技巧在Vivado里用set_false_path约束按键同步链路径因为按键信号没有固定周期要求加上约束能减少时序报告中的噪音。在PCB改版或升级DAC芯片时这个约束能帮你快速定位真正的时序失效点而不是被按键路径的假违规带偏方向。本文还有配套的精品资源点击获取
返回列表