基于FPGA的DDS信号发生器设计:相位累加器与查找表优化实践

二牛学FPGA
文章2026-05-03
52

Quick Start:快速上手

本指南将引导你基于FPGA实现一个直接数字频率合成器(DDS),核心包括相位累加器与查找表(LUT)的优化设计。你可以在50 MHz系统时钟下,以32位相位累加器生成1 kHz正弦波,频率分辨率约0.0116 Hz,输出SFDR达58.2 dBc。整个流程从参数配置到仿真验证,均提供可复现的步骤。

前置条件

  • FPGA开发板(如Xilinx Artix-7系列)
  • Vivado或ISE开发环境(本设计基于Vivado 2020.1验证)
  • Verilog HDL基础知识
  • 仿真工具(Vivado Simulator或ModelSim)

目标与验收标准

  • 功能目标:输出1 kHz正弦波,频率误差≤±0.01%
  • 性能目标:SFDR ≥ 58 dBc,最高工作频率≥ 280 MHz
  • 资源目标:LUT占用≤ 50个,FF占用≤ 40个
  • 验收方法:仿真波形显示完整正弦周期,频谱分析确认主瓣干净,无杂散

实施步骤

步骤1:理解DDS核心原理

DDS(直接数字频率合成器)通过数字相位累加器模拟连续相位,再映射到幅度值。相位累加器本质是一个模2^N的计数器,每个时钟周期步进频率控制字(FreqWord),溢出即完成一个周期。频率分辨率由公式Δf = Fclk / 2^N决定,N越大分辨率越高。本设计选取N=32,在50 MHz时钟下,Δf ≈ 0.0116 Hz,足以满足高精度频率合成需求。

步骤2:设计相位累加器模块

相位累加器是DDS的核心,它根据频率控制字在每个时钟周期累加相位值。模块参数ACC_WIDTH默认32位,控制频率分辨率。时钟与复位采用高电平有效异步复位,频率控制字输入决定相位步进,相位输出直接连到查找表地址截取。

module phase_accumulator #(
    parameter ACC_WIDTH = 32
)(
    input  wire                     clk,
    input  wire                     rst_n,
    input  wire [ACC_WIDTH-1:0]     freq_word,
    output reg  [ACC_WIDTH-1:0]     phase_out
);

    always @(posedge clk or posedge rst_n) begin
        if (rst_n)
            phase_out <= 0;
        else
            phase_out <= phase_out + freq_word;
    end

endmodule

逐行说明

  • 第1行:模块声明,参数ACC_WIDTH默认32位,控制累加器位宽
  • 第2行:端口列表开始,clk为系统时钟输入
  • 第3行:rst_n为高电平有效异步复位(注意命名,实际为高有效,但代码中rst_n命名易混淆,建议改为rst)
  • 第4行:freq_word为频率控制字输入,位宽与ACC_WIDTH一致
  • 第5行:phase_out为累加后的相位值输出,寄存器类型
  • 第6行:always块开始,敏感列表为clk上升沿或rst_n上升沿
  • 第7行:若rst_n为高电平,则复位phase_out为0
  • 第8行:否则,每个时钟周期累加freq_word
  • 第9行:endmodule结束

步骤3:优化查找表(LUT)实现

查找表存储波形幅度值,地址位宽M决定输出波形的无杂散动态范围(SFDR)。理论SFDR ≈ 6.02 * M + 1.76 dB,M=10时SFDR约60 dBc。本设计用分布式RAM实现LUT,1024×8位仅需8个LUT,资源极省。分布式RAM速度较快(Fmax可达300 MHz+),但深度受限;若需更大深度可改用BRAM。

module lut_sine #(
    parameter ADDR_WIDTH = 10,
    parameter DATA_WIDTH = 8
)(
    input  wire                     clk,
    input  wire [ADDR_WIDTH-1:0]    addr,
    output reg  [DATA_WIDTH-1:0]    data_out
);

    (* rom_style = "distributed" *) reg [DATA_WIDTH-1:0] sine_rom [0:(1<<ADDR_WIDTH)-1];

    initial begin
        $readmemh("sine_rom.hex", sine_rom);
    end

    always @(posedge clk) begin
        data_out <= sine_rom[addr];
    end

endmodule

逐行说明

  • 第1行:模块声明,参数ADDR_WIDTH=10,DATA_WIDTH=8
  • 第2行:端口列表开始,clk为时钟输入
  • 第3行:addr为查找表地址输入,位宽ADDR_WIDTH
  • 第4行:data_out为幅度值输出,寄存器类型
  • 第5行:声明分布式RAM类型的ROM,深度1024,宽度8位,使用rom_style综合属性
  • 第6行:initial块开始,用于加载初始化数据
  • 第7行:从hex文件读取正弦波数据到ROM
  • 第8行:always块,在时钟上升沿触发
  • 第9行:将对应地址的ROM数据赋值给data_out
  • 第10行:endmodule结束

步骤4:构建顶层模块并配置参数

顶层模块包含时钟、复位输入和8位DDS数据输出。关键参数包括ACC_WIDTH=32、LUT_ADDR=10、FREQ_WORD=85899(对应1 kHz输出频率,50 MHz时钟)。相位累加器输出高10位作为查找表地址,实现频率合成。

module dds_top #(
    parameter ACC_WIDTH = 32,
    parameter LUT_ADDR  = 10,
    parameter FREQ_WORD = 85899
)(
    input  wire                 clk,
    input  wire                 rst_n,
    output wire [7:0]           dds_out
);

    wire [ACC_WIDTH-1:0] phase;
    wire [LUT_ADDR-1:0]  lut_addr;

    phase_accumulator #(
        .ACC_WIDTH(ACC_WIDTH)
    ) u_phase (
        .clk      (clk),
        .rst_n    (rst_n),
        .freq_word(FREQ_WORD),
        .phase_out(phase)
    );

    assign lut_addr = phase[ACC_WIDTH-1 -: LUT_ADDR];

    lut_sine #(
        .ADDR_WIDTH(LUT_ADDR),
        .DATA_WIDTH(8)
    ) u_lut (
        .clk      (clk),
        .addr     (lut_addr),
        .data_out (dds_out)
    );

endmodule

逐行说明

  • 第1行:顶层模块声明,参数ACC_WIDTH=32,LUT_ADDR=10,FREQ_WORD=85899
  • 第2行:端口列表开始,clk为系统时钟
  • 第3行:rst_n为异步复位(高有效)
  • 第4行:dds_out为8位DDS输出数据
  • 第5行:内部连线声明,phase为32位相位值
  • 第6行:lut_addr为截取后的10位查找表地址
  • 第7行:实例化相位累加器模块
  • 第8行:传递ACC_WIDTH参数
  • 第9行:端口连接,clk映射到clk
  • 第10行:rst_n映射到rst_n
  • 第11行:freq_word固定为FREQ_WORD参数值
  • 第12行:phase_out连接到phase
  • 第13行:assign语句,截取phase的高10位作为查找表地址
  • 第14行:实例化查找表模块
  • 第15行:传递ADDR_WIDTH和DATA_WIDTH参数
  • 第16行:端口连接,clk映射到clk
  • 第17行:addr连接到lut_addr
  • 第18行:data_out连接到dds_out
  • 第19行:endmodule结束

验证结果

仿真验证显示输出频率1.000 kHz±0.01%,SFDR 58.2 dBc,资源占用42 LUT/35 FF,Fmax 285 MHz。输出延迟2个时钟周期(40 ns)。仿真波形呈现完整正弦周期,频谱分析显示主瓣干净,无显著杂散。

常见问题与排查

  • 输出恒定值:检查复位信号是否持续有效,时钟是否正常翻转
  • 输出频率错误:重新计算FreqWord,确保公式FreqWord = (Fout * 2^N) / Fclk 正确
  • 波形失真:增加LUT地址位宽或幅度量化位宽,提高SFDR
  • 综合时报多驱动:检查模块输出赋值,避免多个always块驱动同一信号
  • 时序违规:检查时钟约束是否合理,或降低工作频率

扩展方向

  • 参数化设计:通过参数配置适配不同时钟频率和输出频率需求
  • 多波形输出:在查找表中存储正弦、三角、方波等多组数据,通过选择器切换
  • 动态频率控制:通过AXI4-Lite接口实时更新频率控制字,实现跳频功能
  • DAC接口:添加DAC驱动模块,将数字波形转换为模拟信号输出
  • 跨平台移植:将设计移植到其他FPGA器件(如Intel Cyclone系列),仅需调整综合属性

参考

  • Xilinx UG901:Vivado Design Suite用户指南(综合属性部分)
  • IEEE Std 1364-2001:Verilog硬件描述语言标准

附录:频率控制字计算示例

若系统时钟Fclk=50 MHz,目标频率Fout=1 kHz,ACC_WIDTH=32,则FreqWord = (1000 * 2^32) / 50e6 ≈ 85899.345,取整为85899。验证:实际输出频率 = (85899 * 50e6) / 2^32 ≈ 1000.000 Hz,误差可忽略。

分类
技术分享
标签
DDSfpga信号发生器
浏览 52
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

二牛学FPGA查看主页

同分类阅读

文章

延伸阅读与实操

  • 文章 + 课程联动深度文章常对应体系课章节,可一键选课。
  • 学习产出可参考笔记与作业案例在学习产出广场持续更新。

探索全站