DDS原理及简易实现

DDS原理及简易实现

文章目录

摘要 :讲解 DDS 直接数字频率合成器基础原理,相位累加器、相位调制、ROM 查表的工作机制;提供两种 Verilog 实现:标准框图极简 DDS、状态机实现可调频调相调幅 DDS,分析两种方案优缺点,给出仿真上板注意事项。

一、DDS基本概念

DDS 是直接数字式频率合成器(Direct Digital Synthesizer)的英文缩写,是一项关键的数字化技术。与传统的频率合成器相比,DDS 具有低成本、低功耗、高分辨率和快速转换时间 等优点,广泛应用于电信与电子仪器领域,是实现设备全数字化的关键技术。作为设计人员,我们习惯称它为信号发生器 ,一般用它产生正弦、锯齿、方波等不同波形或不同频率的信号波形,在电子设计和测试中得到广泛应用。

基本结构:主要由相位累加器、相位调制器、波形数据表 ROM、D/A 转换器等四大结构组成,其中较多设计还会在数模转换器之后增加一个低通滤波器。

基本原理:根据结构图来看,相位累加器其实就是以频率字为单位进行累加,然后在相位调制器中将累加后的值与相位字相加作为 ROM 的读取地址,从而获得相应的数据,例如在 ROM 中已经存储了 256 字节的波形数据,那么最开始的时候频率字路径到 ROM 经过两个寄存器,而相位字只有一个寄存器,所以第一次到来的地址是相位字+0,读取第一个数据,第二次来的是相位字+频率字,第三次来的是相位字+2*频率字...以此类推,就可以根据需求取到相应频率和初始相位的信号,此时尚未对数据进行幅值处理。ROM 输出的是数字信号,再经 DAC 转换为模拟信号,即可用示波器观测。

二、DDS核心工作原理

DDS 本质是相位‑地址映射查表机制

  1. 相位累加器 :每个时钟周期,将内部寄存器与频率控制字做加法,持续累加,输出相位值;频率字大小直接决定相位累加步长。
  2. 相位调制器 :把相位累加器输出的值,再叠加相位控制字,得到最终 ROM 读取地址,从而实现输出波形的初始相位偏移。
  3. ROM 波形查找表:ROM 预先存储一个周期波形的采样数据;输入地址,输出对应时刻的波形数字采样值。> 举例:ROM 内部预存 256 点正弦波形采样数据
  • 第 1 个时钟:地址 = 相位字 + 0,读出第一点波形数据
  • 第 2 个时钟:地址 = 相位字 + 频率字,读出第二点
  • 第 3 个时钟:地址 = 相位字 + 2×频率字
  • 以此循环往复,持续查表输出波形数字量。
    频率字越大,相位每次步进越大,查表地址跳转越快,输出信号频率越高;相位字用来控制波形起始偏移位置。
  1. DAC数模转换:ROM输出的是数字波形数据,经过DAC转换为模拟电平信号,外接示波器就可以观测波形。
  2. 后置低通滤波器:滤除DAC带来的量化噪声、高频镜像分量,输出干净连续模拟波形。

关键公式

f o u t = f c l k ⋅ K 2 N f_{out}=\frac{f_{clk}\cdot K}{2^N} fout=2Nfclk⋅K

  • f o u t f_{out} fout:DDS输出信号频率
  • f c l k f_{clk} fclk:DDS模块工作时钟
  • K K K:频率控制字
  • N N N:相位累加器位宽

三、两种 Verilog 实现思路

两种实现思路:

思路 1:严格参照 DDS 标准框图实现,极简版本,只完成调频、调相;适合理解经典 DDS 架构。

思路 2:面向功能需求实现,使用状态机完成调频、调相、调幅、波形选择,可以完成更多控制功能。

方式一:参照标准 DDS 结构框图实现

核心:相位累加器持续叠加频率字,叠加相位字得到 ROM 读地址,直接查表输出波形数据。

verilog 复制代码
module DDS (
    input   wire            clk         ,
    input   wire            rst_n       ,
    input   wire    [7:0]   phal        ,//相位字,设置初始相位偏移
    input   wire    [7:0]   freq        ,//频率控制字,相位步进
    output  wire    [7:0]   data_wave    //输出波形数字信号
);
    reg  [7:0]    phase_add    ;//相位累加器
    wire [7:0]    address_dds  ;//ROM 的读取地址

    always @(posedge clk or negedge rst_n) begin
        if(rst_n == 0) begin
            phase_add <= 0;
        end
        else begin 
            phase_add <= phase_add + freq;    //相位累加
        end
    end

    assign address_dds = phase_add + phal;    //叠加相位字,得到 ROM 访问地址

rom rom_u (
    .address    (address_dds),
    .clock      (clk        ),
    .q          (data_wave  )
);

endmodule

说明:rom为ROM IP核,预先存储单周期波形采样点(正弦波/锯齿波/方波);本版本只实现调频、调相,幅值固定。

方式二:基于功能需求(状态机方式实现 DDS)

设计思路:对 ROM 读出的波形数据完成调相、调频、调幅处理,输出调制之后的波形数据给后级模块。

  • 调相:修改 ROM 读取的起始地址,实现波形相位偏移;
  • 调频:
    • 调大频率:相同时间内采样点数变少,间隔采样,波形周期缩小;
    • 调小频率:插入等待计数,相当于补点,拉长波形周期;
  • 调幅:对 ROM 输出原始波形数据做除法/乘法运算,缩放输出幅值。
verilog 复制代码
//本质其实就是对已经存好的数据进行相位,频率,幅度的调制,然后再把已调信号输出到其他模块里
//调相:设置ROM最开始读出的地址,即初始相位
//调频:调大频率,同等采样时钟下间隔采样;调小频率,增加计数等待进行补点,拉长周期
//调幅:直接对ROM读取出来的数据做缩放运算
    always @(posedge clk or negedge rst_n) begin
        if (rst_n == 0) begin
            wave_reg <= 0;
            phal_reg <= 0;
            freq_reg <= 0;
            ampl_reg <= 0;
            data_reg <= 0;
            cnt_freq <= 0;
            address  <= 0;
        end
        else begin
            case (state_c)
                IDLE: begin//等待done_cmd指令有效信号
                    wave_reg <= 0;
                    phal_reg <= 0;
                    freq_reg <= 0;
                    ampl_reg <= 0;
                    data_reg <= 0;
                    cnt_freq <= 0;
                    address  <= 0;
                end
                REG :begin//寄存外部配置指令
                    wave_reg <= wave;
                    phal_reg <= phal;
                    freq_reg <= freq;
                    ampl_reg <= ampl;                    
                end
                WAVE:begin//进行波形选择,只做了判断,可扩展多波形
                end
                PHAL:begin//调相,设置ROM起始读地址
                    address  <= phal_reg;
                end
                FREQ:begin//调频,调大(周期缩小)、调小(周期扩宽)
                    //频率分频计数
                    if (cnt_freq == freq_reg[3:0] - 1) begin
                        cnt_freq <= 0;
                    end
                    else begin
                        cnt_freq <= cnt_freq + 1;
                    end
                    //更新ROM地址
                    if ((freq_reg[4] == 1 && cnt_freq == freq_reg[3:0] - 1 ) || freq_reg[4] == 0) begin
                        address <= address + 1;
                    end
                end
                AMPL:begin//调幅,读取ROM数据之后做幅值缩放
                    data_reg <= q/ampl_reg;
                end
                DATA:begin//产生数据有效指示信号
                end
                WAIT:begin//状态机过渡等待状态
                end
                default:begin
                    wave_reg <= 0;
                    phal_reg <= 0;
                    freq_reg <= 0;
                    ampl_reg <= 0;
                    data_reg <= 0;
                    cnt_freq <= 0;
                    address  <= 0;
                end 
            endcase
        end
    end

四、工程对比总结

实现方式 优点 缺点 适用场景
框图式 DDS 代码极简,严格贴合 DDS 理论模型 缺少调幅,功能简单 学习 DDS 原理,快速生成固定幅值波形
状态机功能式 DDS 支持调相、调频、调幅、多波形切换 状态机复杂,逻辑较多 可配置信号源,项目工程使用
相关推荐
励志不掉头发的内向程序员1 小时前
【LibreCAD 2D架构】鼠标点下的坐标为什么会被“吸”走?LibreCAD 对象捕捉系统解析
开发语言·c++·qt·学习·系统架构
明德扬1 小时前
AD9653采集模块怎样连接FPGA底板?从ADC采样到数据处理
学习·fpga开发·fpga
Generalzy1 小时前
像 gofmt 一样格式化 Python:Black、Ruff、YAPF、autopep8 谁才是 2026 年的首选?
开发语言·python
朝朝辞暮i1 小时前
C++第一课
开发语言·c++·算法
李少兄1 小时前
JavaScript 对象完全指南
开发语言·javascript·ecmascript
bug嘛我经常写1 小时前
MIKE21结合ArcGIS制作随时间变化的降水文件(.dfs2)-笔记
经验分享·笔记·arcgis
hanlin031 小时前
刷题笔记:力扣第169题-多数元素
笔记·算法·leetcode
张小姐的猫1 小时前
【AI大模型接入SDK】 —— Ollama本地接入Deepseek
java·linux·开发语言·网络·c++·人工智能
Nuanyt1 小时前
JUC常见核心知识梳理01 线程 并发 JMM volatile 管程 锁 synchronized
java·开发语言·网络·jvm