2021-08-24

FPGA DDS 实现及仿真


前言

在信号处理中,经常使用DDS生成不同频率的正弦及余弦信号,与采样数据或调制数据进行复数乘法,完成频谱的搬移。例如在无线电调制中,使用DDS生成的正余弦信号,与调制波形相乘,完成上变频;在无线电解调中,使用DDS生成的正余弦吸纳后,与无线电采样信号相乘,完成信号的零频搬移。DDS在信号处理得到广泛应用。


一、DDS基本原理

DDS (Direct Digital Synthesizer)直接数字式频率合成器,其基本原理是利用相位累加器,每次累加一个频率控制字,通过查找表从ROM查找表中读取波形数据。调节频率控制字的数值,可以改变累加器的累加速度,进而可以调节从ROM查找表中读取波形数据的速度。即频率控制字越大,频率越高。相位控制字可以用来调节初始相位,即ROM地址自加的初始值。

频率分辨率与相位累加器的关系:△f=fclk/2^N;
其中:
△f:频率分辨率;
fclk:时钟频率;
N:为相位累加器的宽度
例如:时钟频率fclk=50M,相位累加器宽度N=26,则 △f=50000000/2^26=0.75Hz.
说明相位累加器的宽度越宽,频率分辨率越细。

输出频率与频率控制字的关系:fout = (fclk * phase_in)/2^N;
其中:
fout:输出的频率;
fclk:时钟频率;
phase_in:频率控制字;
N:为相位累加器的宽度
例如:时钟频率fclk=50M,相位累加器宽度N=26,频率控制字phase_in=50000,则 fout=50000000*50000/2^26=37.253kHz.
说明频率控制字越大,输出频率越大。

二、Matlab程序

首先使用Matlab生成Sine波形数据,存储成FPGA ROM可读取的COE文件,Matlab程序如下。
clc;
clear all;

N=4096;
x=linspace(0,2*pi,N);%一个周期采样4096个点 

sin_data=sin(x);
sin_coe=ceil(sin_data*32767);%生成 16位 有符号整数

figure;
plot(sin_coe);

%生成sin函数coe文件
fid = fopen('sin_coe.coe','wt');
fprintf(fid,'MEMORY_INITIALIZATION_RADIX=10;\n');
fprintf(fid,'MEMORY_INITIALIZATION_VECTOR=\n');

for i = 1:1:N-1
    fprintf(fid,'%d,\n',sin_coe(i));  
end 

fprintf(fid,'%d;\n',sin_coe(N));%COE 文件结束符 ;
    
fclose(fid);

matlab生成的sine波形如下图。
在这里插入图片描述

三、Verilog程序

1.ROM IP核

首先使用FPGA的ROM IP核,读取COE文件。

使用双端口ROM,这样就可以同时输出sin和cos信号。
在这里插入图片描述
PortA 数据宽度使用16位,存储深度选择4096,与COE文件大小一致。PortB与PortA同样设置
在这里插入图片描述
最后Load Matlab生成的COE文件,生成IP核。
在这里插入图片描述

2.DDS生成

使用相位累加器phase_add对输入的相位数据进行累加,然后通过查找表法输出正余弦信号,余弦信号与正弦信号相位相差pi/4,对于4096点,即相差1024点,所以输出的初始相位加1024,则可输出余弦信号。


module DDS_Generator(
    input   clk,
    input   [25:0]  phase_in,    //输出频率   fout = (clk*phase_in)/2^N
    output  [15:0]  sine,
    output  [15:0]  cose
    );
    
    parameter  N            =   26;   //频率分辨率  △f=fclk/2^N   例如:clk=50M  N=26  △f=0.75
    parameter  addr_width    =   12;  //ROM 地址的宽度   
    
    reg  [N-1:0]         phase_add = 0;
    reg  [addr_width-1:0] addra= 0;
    reg  [addr_width-1:0] addrb= 0;

    
    always@(posedge clk)
    begin  
        phase_add <= phase_add + phase_in;
        addra<= phase_add[N-1:N-addr_width];
        addrb<= phase_add[N-1:N-addr_width]+1024;
    end
    
  sine_rom sine_rom(
    .clka(clk),
    .addra(addra),
    .douta(sine),
    
    .clkb(clk),
    .addrb(addrb),
    .doutb(cose)
  );

        
endmodule

3.DDS仿真

编写测试文件,phase_in设置为50000,输入频率设置为50M,则生成的信号应该为37.252kH在,即周期大约为26us,仿真测试文件如下

module Test_DDS(

    );
    reg  clk =0 ;
    reg  [25:0]  phase_in=50000;
    wire [15:0]  sine;
    wire [15:0]  cose;
    
    
    
    DDS_Generator # (
    .N(26),
    .add_width(12)
    )    
    DDS_Generator(
    .clk(clk),
    .phase_in(phase_in),
    .sine(sine),
    .cose(cose)
    );
    
    always  #10  clk=~clk;
    
    
endmodule

仿真结果如下图所示
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
这段代码存在一些语法错误。具体来说: 1. `console.log` 方法的字符串应该使用引号括起来,例如 `"每页 ${val} 条"` 和 `"当前页: ${val}"`。 2. `toggleContent` 方法应该定义在 `methods` 对象,并且缺少一个左大括号 `{`。 3. `toggleContent` 方法的 `console.log` 方法没有使用正确的字符串格式化,应该使用反引号括起来,并在占位符 `${}` 使用变量名,例如 ``console.log(`当前高度: ${content.style.height}`)``。 4. 在 `v-for` 指令,应该使用 `v-bind:key` 显式地绑定 `tableData` 数组每个对象的唯一标识符,例如 `v-for="(item, index) in tableData" v-bind:key="item.number"`。 下面是修正后的代码: ``` <script> let v = new Vue({ el: '#app', data: { value1: '', value2: '', pickerOptions: { shortcuts: [ { text: '今天', onClick(picker) { picker.$emit('pick', new Date()); } }, { text: '昨天', onClick(picker) { const date = new Date(); date.setTime(date.getTime() - 3600 * 1000 * 24); picker.$emit('pick', date); } }, { text: '一周前', onClick(picker) { const date = new Date(); date.setTime(date.getTime() - 3600 * 1000 * 24 * 7); picker.$emit('pick', date); } } ] }, tableData: [ { number: '1', date: '2021-08-01' }, { number: '2', date: '2021-08-01' }, { number: '3', date: '2021-08-01' }, { number: '4', date: '2021-08-01' }, { number: '5', date: '2021-08-01' }, { number: '6', date: '2021-08-01' }, { number: '7', date: '2021-08-01' }, { number: '8', date: '2021-08-01' } ], currentPage1: 5, currentPage2: 5, currentPage3: 5, currentPage4: 4 }, methods: { deleteRow(index, rows) { rows.splice(index, 1); }, handleSizeChange(val) { console.log(`每页 ${val} 条`); }, handleCurrentChange(val) { console.log(`当前页: ${val}`); }, toggleContent() { var content = document.getElementById("content"); var btn = document.getElementById("toggle-btn"); if (content.style.height === "100px") { content.style.height = "auto"; btn.innerHTML = "收起"; } else { content.style.height = "100px"; btn.innerHTML = "展开"; } console.log(`当前高度: ${content.style.height}`); } } }); </script> <table> <thead> <tr> <th>序号</th> <th>日期</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item, index) in tableData" v-bind:key="item.number"> <td>{{ item.number }}</td> <td>{{ item.date }}</td> <td><button @click="deleteRow(index, tableData)">删除</button></td> </tr> </tbody> </table> <div class="pagination"> <el-pagination background layout="sizes, prev, pager, next, jumper" :current-page.sync="currentPage1" :page-sizes="[5, 10, 20]" :page-size="5" @size-change="handleSizeChange" @current-change="handleCurrentChange" :total="tableData.length" ></el-pagination> </div> <div id="content" style="overflow: hidden; height: 100px;"> 这是一段需要展开的内容。 </div> <button id="toggle-btn" @click="toggleContent">展开</button> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mhlicq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值