首页 > 其他分享 >FPGA的DDS部分学习

FPGA的DDS部分学习

时间:2024-05-10 17:23:53浏览次数:15  
标签:wire FPGA DDS 学习 flag Pword Fword output reg

这边是32个采样点的正弦波信号,通过DAC输出,也就是数模转换出来的。

如果每1ms输出一个信号,也就是DAC以1000HZ输出,那么下面这样一个完整的正弦信号需要32个点。也就是32ms

所以输出一个完整周期正弦波信号的频率为1000/32HZ(f=1/T,这边完整周期信号的时间周期是32ms,1/32ms就是1000/32HZ)

如果用同样的一组DAC数据来输出一个2*(1000/32)HZ的正弦信号,也就频率变高,时间缩小,也就是原来32ms现在16ms就要输出一个完整的正弦信号。

时间缩短一半,频率提高一倍。


在1ms的DAC输出下,如果取点间隔为1

FCLK = 1000hz  (周期1ms),数据输出信号频率

Fo = 1000/32     (N=5),正弦信号输出频率

Fo = FCLK /(2^N)

如果取点间隔为2

Fo = 1000/(32/2)

也就是Fo= 2*1000/32

那么这个2也就可以单独拉出来,频率控制字Fword=2也写成B=2

N是相位累加器中的位数,其实就是精度,通俗的说就是将模拟量分成多少份。分辨率为1/2^N

最终:Fo=FCLK/(2^N/B)=FCLK*B/2^N

dds的时钟频率为FCLK,频率控制字为Fword=1,则输出的频率为Fout=FCLK/2^N,这个频率是“基频”。若Fword=B,则输出频率为Fout=B*FCLK/2^N


1、频率控制字就是上面的B,或者Fword,数值越大,输出信号的频率越高

2、相位控制字,控制输出信号的相位偏移,主要用于相位的信号调制

3、相位累加器=N位加法器+N位相位累加寄存器,每一个时钟脉冲输入时,相位累加器便把频率控制字累加1

  也就是说,相位累加器的作用是帮频率控制字进行相位增量,设位宽N,把之前的输出公式改一下

  Fout=B*FCLK/2^N改成 B=Fout*2^N/FCLK

  也就是通过相位累加寄存器寄存累加的值

4、波形数据表ROM,里面有完成周期的信号,代码中我们设置了正弦,三角,方波三种周期信号。

  假设波形数据表ROM的地址位宽为12位,存储数据位宽8位,也就是ROM有2^12=4096个存储空间,每个空间存储1个字节的数据(一个字节8bit)

  将一个正弦周期信号,沿横轴等间隔采样4096次,每次采样的幅值用1字节数据表示,最大255,最小值0(8bit,2^8)。将4096次采样结果顺序写入ROM的4096个存储单元。

  波形数据表ROM以相位调制器传入的相位码作为ROM读地址,将地址对应存储单元中的电压幅值数字量输出。

  (4096个幅值数据是顺序写入ROM的,也就是在ROM中存了一个完整的周期正弦波信号,在完整的周期T内,每个幅值对应的相位不同)

  所以可以通过相位调制器输出的相位码,作为读ROM的地址,将存储的幅值数字量读出来。

5、最后给DAC数字转模拟量输出正弦波信号。

 


module dds_ad9767(
    clk,
    reset_n,
    mode_sel,   //选择模式,波形选择
    Fword,      //频率控制字
    Pword,      //相位控制字
    data
    );
    
    input   clk;
    input   reset_n;
    input   [1:0]   mode_sel;  //00 01 10 11
    input   [31:0]  Fword;
    input   [11:0]  Pword;
    output  reg [13:0]  data;  //AD9767是14位精度
    
    //频率控制字同步寄存器,同步寄存器就是打拍同步
    reg [31:0]  Fword_r;
    always @(posedge clk)
        Fword_r <= Fword;
        
    //相位控制字同步寄存器
    reg [11:0]  Pword_r;
    always @(posedge clk)
        Pword_r <= Pword;
        
    //相位累加器
    reg [31:0]  Freq_ACC;
    always @(posedge clk or negedge reset_n)
        if(!reset_n)
            Freq_ACC <= 0;
        else
            Freq_ACC <= Fword_r + Freq_ACC;

    //波形数据表地址
    //截取32位累加器的高12位作为ROM的查询地址,这样查询地址也是4096个点。
    //和ROM表的单周期的离散点存储位深度一致
    
    wire    [11:0]  Rom_addr;
    assign  Rom_addr =   Freq_ACC[31:20] + Pword_r;         
    
    
    wire    [13:0]      data_sine,data_square,data_triangular;
    
   rom_sine rom_sine (
  .clka(clk),    // input wire clka
  .addra(Rom_addr),  // input wire [11 : 0] addra
  .douta(data_sine)  // output wire [13 : 0] douta
);

rom_square rom_square (
  .clka(clk),    // input wire clka
  .addra(Rom_addr),  // input wire [11 : 0] addra
  .douta(data_square)  // output wire [13 : 0] douta
);

rom_triangular rom_triangular (
  .clka(clk),    // input wire clka
  .addra(Rom_addr),  // input wire [11 : 0] addra
  .douta(data_triangular)  // output wire [13 : 0] douta
);
 
    always @(*)
        case(mode_sel)
            0: data = data_sine;
            1: data = data_square;
            2: data = data_triangular;
            3: data = 8192;
        endcase    

endmodule

上面这是AD9767芯片的驱动代码,就是根据上面的流程图换算的代码。

下面的顶层模块,通过vio虚拟几个端口出来,因为AD模块一插没啥硬件按键了。

顶层代码
`timescale 1ns / 1ps

`define sim

module dds_top(
    Clk,
    Reset_n,
    DataA,
    ClkA,
    WRTA,
    DataB,
    WRTB,
    ClkB
);
    input Clk;
    input Reset_n;
    output [13:0]DataA;
    output ClkA;
    output WRTA;
    output [13:0]DataB;
    output ClkB;
    output WRTB;
    
    wire CLK125M;
    assign ClkA = CLK125M;
    assign ClkB = CLK125M;
    assign WRTA = ClkA;
    assign WRTB = ClkB;  
    
    reg [31:0]FwordA,FwordB;
    reg [11:0]PwordA,PwordB;
    reg [1:0]Mode_SelA;
    reg [1:0]Mode_SelB;
    reg [2:0]CHA_Fword_Sel;
    reg [2:0]CHB_Fword_Sel;
    
    reg [2:0]CHA_Pword_Sel;
    reg [2:0]CHB_Pword_Sel;
   
   
    MMCM MMCM(
        .clk_out1(CLK125M),
        .resetn(Reset_n), 
        .locked(),
        .clk_in1(Clk)
    );
   
    dds_ad9767 dds_A(
    .clk(CLK125M),     
    .reset_n(Reset_n), 
    .mode_sel(Mode_SelA),
    .Fword(FwordA),   
    .Pword(PwordA),   
    .data(DataA)     
    );
    
    dds_ad9767 dds_B(
    .clk(CLK125M),     
    .reset_n(Reset_n), 
    .mode_sel(Mode_SelB),
    .Fword(FwordB),   
    .Pword(PwordB),   
    .data(DataB)     
    );
    
    wire CHA_Fword_flag;
    wire CHB_Fword_flag;
    wire CHA_Pword_flag;
    wire CHB_Pword_flag;
    wire Mode_SelA_flag;
    wire Mode_SelB_flag;
    
    vio_0 vio (
        .clk(CLK125M),                // input wire clk
        .probe_out0(CHA_Fword_flag),  // output wire [0 : 0] probe_out0
        .probe_out1(CHB_Fword_flag),  // output wire [0 : 0] probe_out1
        .probe_out2(CHA_Pword_flag),  // output wire [0 : 0] probe_out2
        .probe_out3(CHB_Pword_flag),  // output wire [0 : 0] probe_out3
        .probe_out4(Mode_SelA_flag),  // output wire [0 : 0] probe_out4
        .probe_out5(Mode_SelB_flag)  // output wire [0 : 0] probe_out5
    );
    
    //对flag信号打两拍,用于上升沿检测
    reg CHA_Fword_flag_reg0,CHA_Fword_flag_reg1;
    reg CHB_Fword_flag_reg0,CHB_Fword_flag_reg1;
    reg CHA_Pword_flag_reg0,CHA_Pword_flag_reg1;
    reg CHB_Pword_flag_reg0,CHB_Pword_flag_reg1;
    reg Mode_SelA_flag_reg0,Mode_SelA_flag_reg1;
    reg Mode_SelB_flag_reg0,Mode_SelB_flag_reg1;
    
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)begin
        CHA_Fword_flag_reg0 <= 0;
        CHA_Fword_flag_reg1 <= 0;
    end
    else begin
        CHA_Fword_flag_reg0 <= CHA_Fword_flag;
        CHA_Fword_flag_reg1 <= CHA_Fword_flag_reg0;
    end
    
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)begin
        CHB_Fword_flag_reg0 <= 0;
        CHB_Fword_flag_reg1 <= 0;
    end
    else begin
        CHB_Fword_flag_reg0 <= CHB_Fword_flag;
        CHB_Fword_flag_reg1 <= CHB_Fword_flag_reg0;
    end
    
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)begin
        CHA_Pword_flag_reg0 <= 0;
        CHA_Pword_flag_reg1 <= 0;
    end
    else begin
        CHA_Pword_flag_reg0 <= CHA_Pword_flag;
        CHA_Pword_flag_reg1 <= CHA_Pword_flag_reg0;
    end   
    
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)begin
        CHB_Pword_flag_reg0 <= 0;
        CHB_Pword_flag_reg1 <= 0;
    end
    else begin
        CHB_Pword_flag_reg0 <= CHB_Pword_flag;
        CHB_Pword_flag_reg1 <= CHB_Pword_flag_reg0;
    end      

    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)begin
        Mode_SelA_flag_reg0 <= 0;
        Mode_SelA_flag_reg1 <= 0;
    end
    else begin    
        Mode_SelA_flag_reg0 <= Mode_SelA_flag;
        Mode_SelA_flag_reg1 <= Mode_SelA_flag_reg0;
    end       
    
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)begin
        Mode_SelB_flag_reg0 <= 0;
        Mode_SelB_flag_reg1 <= 0;
    end
    else begin
        Mode_SelB_flag_reg0 <= Mode_SelB_flag;
        Mode_SelB_flag_reg1 <= Mode_SelB_flag_reg0;
    end 
 
    wire CHA_Fword_posedge;
    wire CHB_Fword_posedge;
    wire CHA_Pword_posedge;
    wire CHB_Pword_posedge;
    wire Mode_SelA_posedge;
    wire Mode_SelB_posedge;
    //上升沿检测   
    assign CHA_Fword_posedge = (!CHA_Fword_flag_reg1) & CHA_Fword_flag_reg0;
    assign CHB_Fword_posedge = (!CHB_Fword_flag_reg1) & CHB_Fword_flag_reg0;
    assign CHA_Pword_posedge = (!CHA_Pword_flag_reg1) & CHA_Pword_flag_reg0;
    assign CHB_Pword_posedge = (!CHB_Pword_flag_reg1) & CHB_Pword_flag_reg0;
    assign Mode_SelA_posedge = (!Mode_SelA_flag_reg1) & Mode_SelA_flag_reg0;
    assign Mode_SelB_posedge = (!Mode_SelB_flag_reg1) & Mode_SelB_flag_reg0;
    
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)
        `ifdef sim
            CHA_Fword_Sel <= 4;//仿真时启用
        `else
            CHA_Fword_Sel <= 0; //板级验证时启用
        `endif
    else if(CHA_Fword_posedge)
        CHA_Fword_Sel <= CHA_Fword_Sel + 1'd1;
    else
        CHA_Fword_Sel <= CHA_Fword_Sel;
        
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)
        `ifdef sim
            CHB_Fword_Sel <= 4;//仿真时启用
        `else
            CHB_Fword_Sel <= 0;  //板级验证时启用 
         `endif
    else if(CHB_Fword_posedge)
        CHB_Fword_Sel <= CHB_Fword_Sel + 1'd1;
    else
        CHB_Fword_Sel <= CHB_Fword_Sel;

    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)
        `ifdef sim
            CHA_Pword_Sel <= 3;//仿真时使用
        `else
            CHA_Pword_Sel <= 0;//板级验证时使用
        `endif      
    else if(CHA_Pword_posedge)
        CHA_Pword_Sel <= CHA_Pword_Sel + 1'd1;
    else
        CHA_Pword_Sel <= CHA_Pword_Sel;
        
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)
        CHB_Pword_Sel <= 0;//板级验证时使用
    else if(CHB_Pword_posedge)
        CHB_Pword_Sel <= CHB_Pword_Sel + 1'd1;
    else
        CHB_Pword_Sel <= CHB_Pword_Sel;
        
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)
        Mode_SelA <= 0;
    else if(Mode_SelA_posedge)
        Mode_SelA <= Mode_SelA + 1'd1;
    else
        Mode_SelA <= Mode_SelA;
        
    always@(posedge CLK125M or negedge Reset_n)    
    if(!Reset_n)
        `ifdef sim
            Mode_SelB <= 1;
        `else
            Mode_SelB <= 0;
        `endif
    else if(Mode_SelB_posedge)
        Mode_SelB <= Mode_SelB + 1'd1;
    else
        Mode_SelB <= Mode_SelB;

//频率控制字
//如果把周期完整的一个波形等分成2的32次方份,在时钟频率为125M次/秒的条件下,
//如果希望1秒钟输出一个完成的周期,那么每一拍递进多少份?
    always@(*)
        case(CHA_Fword_Sel)   
            0:FwordA = 34;//2**32 / 125000000;     34.35  
            1:FwordA = 344;//2**32 / 12500000;
            2:FwordA = 3436;//2**32 / 1250000;
            3:FwordA = 34360;//2**32 / 125000;
            4:FwordA = 343597;//2**32 / 12500;
            5:FwordA = 3435974;//2**32 / 1250;
            6:FwordA = 34359738;//2**32 / 125;
            7:FwordA = 343597384;//2**32 / 12.5;
        endcase
        
    always@(*)
        case(CHB_Fword_Sel)   
            0:FwordB = 34;//2**32 / 125000000;     34.35
            1:FwordB = 344;//2**32 / 12500000;
            2:FwordB = 3436;//2**32 / 1250000;
            3:FwordB = 34360;//2**32 / 125000;
            4:FwordB = 343597;//2**32 / 12500;
            5:FwordB = 3435974;//2**32 / 1250;
            6:FwordB = 34359738;//2**32 / 125;
            7:FwordB = 343597384;//2**32 / 12.5;
        endcase  
        
    always@(*)
        case(CHA_Pword_Sel)
            0:PwordA = 0;   //0
            1:PwordA = 341; //30
            2:PwordA = 683; //60
            3:PwordA = 1024;    //90
            4:PwordA = 1707;    //150
            5:PwordA = 2048;    //180
            6:PwordA = 3072;    //270
            7:PwordA = 3641;    //320
        endcase 

    always@(*)
        case(CHB_Pword_Sel)
            0:PwordB = 0;   //0
            1:PwordB = 341; //30
            2:PwordB = 683; //60
            3:PwordB = 1024;    //90
            4:PwordB = 1707;    //150
            5:PwordB = 2048;    //180
            6:PwordB = 3072;    //270
            7:PwordB = 3641;    //320
        endcase
endmodule

最后板级验证的时候,可以通过vio端口控制电平触发,上一时刻0,这一时刻1,下面解释了,Q是上一时刻的寄存的,D是这一次的。

最后,看到示波器波形输出正弦波和方波,同时通过vio改变观察波形的周期频率变化。

最后算一下,确实是差不多的,比如频率控制字这边的Fword=34(这个是算出来的,2^32/125000000,在125MHz的时钟频率下,把一个正弦波分成2^32份,频率为f=1/T,一个信号发出就是1/125M,2^32次份信号就是2^32/125M)

用上面的公式Fout=B*FCLK/2^N

343597*125M/2^32=10000hz,10KHz(大概约等于,9,999.98883344233036041259765625)

标签:wire,FPGA,DDS,学习,flag,Pword,Fword,output,reg
From: https://www.cnblogs.com/cjl520/p/18172351

相关文章

  • 【redis学习】Redis-IO多路复用
    为什么要有IO多路复用大家印象中的redis都是单线程的,没有加锁的操作,因此才会是redis这么快的原因其中之一。先暂且不说redis究竟是不是单线程,即便是单线程的,作为服务提供方,面对成百上千的客户端连接请求,读写操作,单线程是怎么做到高效的处理这些请求?单线程处理socket连接,面对客户......
  • docker学习
    rm-fr/etc/yum.repos.d/local.repocurl-o/etc/yum.repos.d/CentOS-Base.repohttp://mirrors.aliyun.com/repo/Centos-7.repowget-O/etc/yum.repos.d/docker-ce.repohttps://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.reposed-i's#download.......
  • C#中的System.Security.SecureString学习
    有一次在公司review代码时,有一个password的字段,原来用的是String类型,有同事提到应该用SecureString比较好于是我花了点时间了解了一下什么是SecureString,以及它与String类型的区别正常的String类型值,在脱离开作用域后,它的值在内存中并不会立即被销毁.这个时候,如果有人恶意扫......
  • 自动驾驶:从模拟学习到逆强化学习的思考
    引言自动驾驶技术正在迅速发展,但要实现真正的自主驾驶,模型必须能够复杂且准确地模拟人类司机的行为。通过我的最近实验,我探索了基本的模拟学习,然后进一步探索逆强化学习等方法,目的是让自动驾驶模型不仅能模仿,更能深入理解驾驶的决策过程。模拟学习的初步尝试在进行VLM端到......
  • CSS-Grid网格布局学习心得
    CSS-grid属性:value(可选值):<'grid-template'>|<'grid-template-rows'>/[auto-flow&&dense?]<'grid-auto-columns'>?|[auto-flow&&dense?]<'grid-auto-rows'>/<'g......
  • Git学习——迁移单一仓库至其他代码托管平台
    目录简介流程总结简介因需迁移单一代码仓库至其他代码托管平台,要迁移的包括仓库内容以及所有历史记录和推送日志。本文中的方法同样适用于在同一代码托管平台中克隆仓库。流程1.创建新仓库:在目的平台的指定位置创建一个新的仓库(目的仓库),用于接收克隆的数据。2.克隆旧仓库......
  • Dos基本命令学习
    Dos基本命令学习打开CMD的方式开始+系统+命令提示符win键+R输入cmd打开控制台(推荐使用)在任意文件夹下面,按住shift键+鼠标右键,在此处打开命令行窗口资源管理器的地址栏加上cmd路径常用的Dos命令盘符切换输入对应盘符号加上英文冒号然后回车。例:E:查看当前目录下的......
  • Markdown基础学习
    Markdown学习标题通过“#+空格+标题内容”进行格式化,几级标题就加几个“#”。字体要变成粗体就是在文字左右加上“**”粗体要变成斜体就是在文字左右加上“*”斜体要变成粗体并且斜体就是在文字左右加上“***”粗体并且斜体中线加上删除线可以在文字左右加上“~~”删除......
  • C# 代码学习
    ......
  • elasticsearch初步使用学习
    通过使用elasticsearch,我们可以加快搜索时间(直接使用SQL的模糊查询搜索耗时会比较久,而且elasticsearch的响应耗时与数据量关系不大)es主要用于存储,计算,搜索数据依次部署elasticsearch,kibanadockerrun-d\--namees\-e"ES_JAVA_OPTS=-Xms512m-Xmx512m"\-e"disco......