【FPGA开发】DDS信号发生器设计

一、常见IP模块介绍

IP(IntellectualProperty)原指知识产权、著作权等,在IC设计领域通常被理解为实现某种功能的设计。IP模块则是完成某种比较复杂算法或功能(如FIR滤波器、FFT、SDRAM控制器、PCIe接口、CPU核等)并且参数可修改的电路模块,又称为IP核(IPCore)。随着CPLD/FPGA器件的集成度越来越高,设计越来越复杂,使用IP核是EDA设计的发展趋势。

根据实现方式的不同,IP核可以分为软核(softcore)、固核(firmcore)和硬核(hardcore),三种IP核的特点如表所示。

IP软核是FPGA设计中常用的预构建功能模块,可以显著提高开发效率。下面介绍RAM、ROM和FIFO这三种常见IP核的使用方法。

1.1 RAM IP核的使用

ROM简介

ROM 是只读存储器(Read-Only Memory)的简称,是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除,且资料不会因为电源关闭而消失。而事 实上在 FPGA 中通过 IP 核生成的 ROM 或 RAM(RAM 将在下一节为大家讲解)调用的是FPGA 内部的 RAM 资源,掉电内容都会丢失(这也很容易解释,FPGA 芯片内部本来就没有掉电非易失存储器单元)。用 IP 核生成的 ROM 模块只是提前添加了数据文件(.coe 格式)(.mf/.nex格式) ,在 FPGA 运行时通过数据文件给 ROM 模块初始化,才使得 ROM 模块像个"真正"的掉电非易失存储器;也正是这个原因,ROM 模块的内容必须提前在数据文件中写死,无法在电路中修改。

参数设置要点

(1)存储器类型:

· 单端口RAM

· 简单双端口RAM(一个读端口一个写端口)

**·**真双端口RAM(两个独立读写端口)

(2)数据宽度:设置存储单元的位宽(如8位、16位、32位等)

(3)存储深度:设置存储单元的数量(如1024、2048等)

(4)操作模式:写优先模式、读优先模式、不变模式

(5)初始化文件:可选.hex或.mif文件初始化RAM内容

调用过程(以Xilinx Vivado为例)

(1)在IP Catalog中搜索Block Memory Generator

(2)选择RAM类型和接口类型(Native或AXI)

(3)设置数据宽度和存储深度

(4)配置操作模式和时钟选项

(5)生成IP核并添加到设计中

示例代码(Verilog实例化):

复制代码
ram_8x1024 your_ram_instance (
  .clka(clk),       // 时钟输入
  .ena(ram_en),     // 使能信号
  .wea(ram_we),     // 写使能
  .addra(ram_addr), // 地址总线
  .dina(ram_din),   // 数据输入
  .douta(ram_dout)  // 数据输出
);

1.2 ROM IP核的使用

参数设置要点

(1)数据宽度:与RAM类似,设置输出数据的位宽

(2)存储深度:决定ROM的大小

(3)初始化文件:必须提供.mif或.hex文件初始化ROM内容

(4)输出寄存器:可选择是否在输出端添加寄存器

调用过程

(1)在IP Catalog中搜索"Block Memory Generator"

(2)选择ROM类型

(3)设置数据宽度和存储深度

(4)指定初始化文件路径

(5)配置其他选项(如输出寄存器)

(6)生成IP核

示例代码:

复制代码
rom_16x512 your_rom_instance (
  .clka(clk),       // 时钟输入
  .addra(rom_addr), // 地址输入
  .douta(rom_data)  // 数据输出
);

1.3 FIFO IP核的使用

参数设置要点

(1)FIFO类型:

同步FIFO(单时钟)

异步FIFO(读写不同时钟)

(1)数据宽度:设置FIFO存储数据的位宽

(2)FIFO深度:设置FIFO能存储的数据量

(3)满/空标志:设置满和空标志的生成方式

(4)握手信号:可选添加读写确认信号

(5)存储类型:选择使用Block RAM或Distributed RAM

调用过程

(1)在IP Catalog中搜索"FIFO Generator"

(2)选择同步/异步FIFO

(3)设置数据宽度和深度

(4)配置标志信号和握手信号

(5)选择存储资源类型

(6)生成IP核

示例代码:

复制代码
fifo_32x256 your_fifo_instance (
  .clk(clk),        // 时钟输入(同步FIFO)
  .rst(reset),      // 复位信号
  .din(fifo_in),    // 数据输入
  .wr_en(wr_en),    // 写使能
  .rd_en(rd_en),    // 读使能
  .dout(fifo_out),  // 数据输出
  .full(full),      // 满标志
  .empty(empty)     // 空标志
);

1.4 通用注意事项

  1. 时钟域处理:特别是异步FIFO,要确保跨时钟域信号正确处理

  2. 资源选择:根据设计需求选择Block RAM或Distributed RAM

  3. 时序约束:使用IP核后要添加适当的时序约束

  4. 仿真模型:大多数IP工具会生成仿真模型,便于验证设计

  5. 功耗考虑:大容量存储器可能显著影响功耗,需合理选择

掌握这些IP核的使用可以大大提高FPGA设计的效率,特别是在需要存储和缓冲数据的应用中。

二、实操演练

2.1 DDS信号发生器设计

使用Quartus Prime Lite创建工程,顶层文件名为DDS_top,芯片选择EP4CE115F29C7(详细步骤看其余FPGA文章)。

2.1.1相位累加器的设计

新建Verilog HDL File文件,文件名为addr_cnt.v(如果与VS Code连用代码写好后另存为就好)

复制代码
//=====相位累加器和数据锁存器=====
module addr_cnt(CPi,K,ROMaddr,Address);
        input CPi;                     //系统基准时钟(100MHz)
        input [12:0] K;                //13位频率控制字
        output reg [9:0] ROMaddr;      //10位ROM地址
        output reg [16:0] Address;     //17位相位累加器地址信号
    always @(posedge CPi)
    begin
        Address = Address + K;
        ROMaddr = Address[16:7];
    end
endmodule

在项目中添加addr_cnt.v文件,选择Files,右键点击Files,点击添加

找到刚才保存的文件添加

选择Set as Top-Level Entity将其设为顶层文件,点击编译

右键点击addr_cnt.v文件,选择CreateSymbol Files for Current File命令,生成该模块的符号

在Quartus中打开生成的addr_cnt.bsf文件,生成的该模块的符号如图

2.1.2波形存储器ROM的设计
(1)方波模块

步骤跟上面的一样,文件名为squwave.v,其代码如下

复制代码
//=====方波产生模块:squwave.v ======
module squwave(CPi,RSTn,Address,Qsquare);
    input CPi;                          //系统基准时钟(100MHz)
    input RSTn;                         //同步清零
    input [16:0] Address;               //17位地址输入信号
    output reg [11:0] Qsquare;          //输出方波信号,12位宽,送至DAC
    always @(posedge CPi)        
    if(!RSTn) Qsquare=12'h000;          //同步清零
    else begin                   
        if(Address<=17'h0FFFF)  
            Qsquare=12'hFFF;            //输出高电平
        else Qsquare=12'h000;           //输出低电平
    end
endmodule

打开生成的squwave.bsf文件后该模块的符号如图

(2)正弦波形存储器模块

Quartus Prime软件接受两种格式的初始化文件MemoryInitialization File(.mif)和Hexadecimal(Intel-Format)File(.hex)。使用时,将初始化文件放在当前工程项目子目录中,在配置LPM_ROM时会对其进行初始化。而建立.mif格式文件有两种方法,一种是直接编辑法,另一种是用C语言等软件生成初始化文件(初始化储存单元较多时更加实用)

打开c语言编译器,建立sinewave.c文件,代码如下:

复制代码
#include <stdio.h>
#include <math.h>
#define PI 3.141592
#define DEPTH 1024     //数据深度,即存储单元的个数
#define WIDTH 12       //存储单元的宽度
int main(void)
{int n,temp;
 float v;
 FILE *fp;
/*建立文件名为Sine1024.mif的新文件,允许写入数据,对文件名没有特殊要求,但扩展名必须为.mif*/
    fp=fopen("Sine1024.mif","w+");
    if(NULL==fp)
            printf("Can not creat file!\r\n");
        else
        {
            printf("File created successfully!\n");
                /*生成文件头,注意不要忘了";" */
            fprintf(fp,"DEPTH =%d;\n",DEPTH);
            fprintf(fp,"WIDTH =%d;\n",WIDTH);
            fprintf(fp,"ADDRESS_RADIX=HEX;\n");
            fprintf(fp,"DATA_RADIX=HEX;\n");
            fprintf(fp,"CONTENT\n");
            fprintf(fp,"BEGIN\n");
                /*以十六进制输出地址和数据*/
            for(n=0;n<DEPTH;n++)
            {/*周期为1024个点的正弦波*/
             v=sin(2*PI*n/DEPTH);
             /*将-1~1之间的正弦波的值扩展到0~4095之间*/
             temp=(int)((v+1)*4095/2); //v+1将数值平移到0~2之间
             /*以十六进制输出地址和数据*/
             fprintf(fp,"%x\t:\t%x;\n",n,temp);
        }
        fprintf(fp,"END;\n");
        fclose(fp);    //关闭文件
    }
}

运行此文件后会生成sinewave.exe文件,双击运行就会生成Sine1024.mif文件

接着,验证生成的数据是否正确。用记事本打开生成的mif文件,同时用Quartus Prime软件打开mif文件,若能成功导入数据且数据一致,则说明生成文件正确,将其添加到工程文件中。

在Quartus Prime主界面选择Tool→IP Catalog

编辑

在查找框内输入ROM, IP核目录(IP Catalog)栏中会列出相关的IP核,选择ROM:1-PORT并双击

弹出如图所示的保存IP设置界面,输入文件名SineROM.v,并选中Verilog,单击OK按钮

设置ROM的数据位宽为12,存储容量(字数)为1024,单击Next按钮

点击Next,配置如下

点击Browse...选择生成的Sine1024.mif文件,这是指明初始化ROM所使用的数据文件名

然后Next直到最后一页,弹出如图所示的选择输出文件的对话框(最重要的是.v文件,其余文件按需要勾选,.bsf文件也可以选上),选择好后点击Finish

SineROM.v等相关文件就生成好了

该模块的符号如下图所示:

2.2 锁相环倍频电路设计

定制一个名称为PLL100M_CP的时钟模块,该模块的输入inclk0为50MHz时钟信号,输出c0为100MHz的脉冲信号,占空比为50%,带有相位锁定指示输出端locked。

在右侧查找框内输入ALTPLL(嵌入式锁相环)

双击打开,输入文件名PLL100M_CP.v,并选中Verilog,单击OK按钮

设置输入时钟(inclk0)频率为50MHz

其余的按如下图片设置

最后选择需要生成的文件

2.3 顶层电路设计

代码如下:

复制代码
//========DDS的顶层模块:DDS_top.v ======
module DDS_top(CLOCK_50, RSTn, WaveSel, K,
WaveValue, LEDG, CLOCK_100);
    input CLOCK_50;                           //50MHz时钟
    input RSTn;                               //控制方波清零,低电平有效
    input [1:0] WaveSel;                      //波形选择:SW[17:16]=10时为方波;SW
                                                     //[17:16]=01时为正弦波
    input [12:0] K;                           //频率控制字SW12..SW0
    output reg [11:0] WaveValue;              //输出波形数据
    wire [9:0] ROMaddr;                       //波形存储器地址
    wire [16:0] Address;                      //17位相位累加器地址
    wire [11:0] Qsine, Qsquare;               //正弦、方波数据输出
    output [0:0]LEDG;                         //锁相环相位锁定指示灯,亮表示锁定
    output CLOCK_100;                         //锁相环输出时钟,频率为100MHz
    wire CPi =CLOCK_100;
    PLL100M_CP PLL100M_CP_inst (              //实例引用锁相环子模块
    .inclk0 ( CLOCK_50 ),                     //50MHz时钟输入
    .c0 ( CLOCK_100 ),                        //100MHz时钟输出
    .locked ( LEDG[0] )                       //相位锁定指示
);
    addr_cnt U0_instance(CPi,K,ROMaddr,Address);
                                              //实例引用地址累加器
    SineROM ROM_inst (                        //实例引用正弦LPM_ROM子模块
    .address (ROMaddr),
    .clock ( CPi ),
    .q ( Qsine )
);
    squwave U1(CPi,RSTn, Address,Qsquare);    //实例引用方波子模块
    always @(posedge CPi)
begin
    case(WaveSel)                             //选择输出波形
        2'b01:WaveValue=Qsine;                //输出正弦波
        2'b10:WaveValue=Qsquare;              //输出方波
        default:WaveValue=Qsine;
    endcase
end
endmodule

写好后添加到工程中,将此文件设为顶层模块并进行编译。

三、设计实现

使用DE2-115开发板来验证上述设计。用板上的50MHz晶振作为时钟输入,用KEY3控制方波清零,用SW12~SW0设置频率控制字,SW17、SW16用来选择输出波形的种类,用LEDG0作为PLL的相位锁定指示。

有DE2_115_pin_assignments.csv文件可以直接导入不用手动配置引脚,没有的话参考DE2-115文档配置。为了方便导入文件DE2_115_pin_assignments.csv进行引脚分配,将使用该文件中的端口名称代替上述DDS_top.v(代码如下)中的信号名称。为此再编写一个顶层文件DE2_DDS_top.v代码如下:

复制代码
//=====在开发板上运行的DDS的顶层模块:DE2_115_DDS_top.v ======
module DE2_115_DDS_top(CLOCK_50, KEY, SW, GPIO_0, LEDG);
    input CLOCK_50;                          //50MHz时钟
    input[3:3] KEY;                          //按键KEY3,控制方波清零
    input[17:0] SW;                          //拨动开关
    output [12:0] GPIO_0;                    //扩展接口,送出波形数据给DAC
    output [0:0]LEDG;                        //绿色LED指示相位是否锁定
    wire CLOCK_100;                          //100MHz时钟
    assign GPIO_0[12]=CLOCK_100;             //送给DAC的时钟
    wire RSTn = KEY[3];                      //控制方波清零,低电平有效
    wire [1:0] WaveSel = SW[17:16];          //选择输出波形
    wire [12:0] K = SW[12:0];                //设置频率控制字,最小值必须为1
    wire [11:0] WaveValue;
    assign GPIO_0[11:0] = WaveValue;         //输出波形数据
    DDS_top DE2(CLOCK_50, RSTn, WaveSel, K, WaveValue, LEDG, CLOCK_100);
endmodule
相关推荐
yingheceping12 分钟前
复旦微FPGA开发板厂商TOP5甄选榜:2026选型参考
fpga开发·fpga
NamoAmitabha1283 小时前
PL 端的 GPIO IP(AXI GPIO)---PS控制----deepseek
fpga开发·zynq--ps
hahaha60166 小时前
3D降噪算法YUV降噪FPGA实现
fpga开发
Hello-FPGA1 天前
基于RFSOC FPGA ZCU216 的CoaXPress 采集demo
fpga开发
9527华安1 天前
FPGA基于ADV7182解码PAL视频,Video Processing Subsystem去隔行,提供6套工程源码和技术支持
fpga开发·音视频·pal·adv7182
hahaha60161 天前
数字图像时域滤波或者3D降噪算法FPGA实现
算法·计算机视觉·fpga开发
GateWorld1 天前
Xilinx 7系列FPGA时钟网络详解
fpga开发·时钟网络·xilinx 7系列
szxinmai主板定制专家2 天前
X86 工控机 vs RK3588 异构主控:AMR 控制器 BOM、散热、长期运维对比
运维·stm32·单片机·嵌入式硬件·fpga开发·am62x
szxinmai主板定制专家2 天前
AMR 项目落地:机器人控制器硬件定制化需求复盘
arm开发·单片机·fpga开发·系统架构·机器人·am62x
FPGA小迷弟3 天前
FPGA 与卫星:从星载硬件到在轨智能的完整技术解析
fpga开发·架构·fpga·1024程序员节·无线电