FPGA实现正弦波加和及滤波(Vivado实现,内含IP核调用)

题目要求

老师给了我一道题,让我用Verilog编写出来:通过100M时钟产生3M、5M和20M正弦波,并将产生的三个不同频率的正弦波加在一起,然后从这个和信号中将20M正弦波提取出来。

我的思路

首先通过DDS分别产生3M、5M、20M正弦波,通过加法器将这三个正弦波加在一起,再通过设计FIR数字滤波器将20M正弦波从和信号中滤出来。整个过程思路还是很清楚的。

DDS原理及代码

关于DDS原理介绍的文章很多,csdn上就很多的文章,可以参考下。
DDS,即直接数字式频率合成法,基本思想就是在存储器中存入正弦波的N个均匀间隔样值,然后以均匀速度将样值传输到数模转换器,将其转换成模拟信号。
这一块我感觉很多文章写的不是很清楚,我直接给大家看看课本上关于DDS原理的介绍:
FPGA实现正弦波加和及滤波(Vivado实现,内含IP核调用)_第1张图片
FPGA实现正弦波加和及滤波(Vivado实现,内含IP核调用)_第2张图片
简单的说就是在存储器ROM中,每隔k个点取一个值,实现k倍频。但由于要满足Nquist定理,所以DDS输出频率小于等于时钟频率的二分之一,即:fo <= fc/2。
DDS输出频率:
在这里插入图片描述

M为频率控制字,N为相位累加器的位宽。

DDS实现过程及代码

首先我们需要生成ROM存储单元。可以直接用Verilog编写,也可以在MATLAB中生成.coe文件,通过Vivado调用ROM的IP核来实现(我更推荐第二种办法)。
MATLAB生成ROM存储单元的.coe文件(.coe文件google便可明白)。波形数据表ROM中存有一个完整周期的正弦波信号。使波形文件ROM的地址为12位,存储数据位宽11位。将一个周期的正弦波,沿横轴等间隔采样212=4096次,每次采集的信号幅度用11位数据表示,最大值为2048,最小值为0。将采集的4096个数据按顺序存入ROM中,然后以相位累加器的输出为地址,进行访问。

MATLAB生成ROM存储单元的.coe文件:

clear;clc;
depth = 4096;   %4096个存储空间
width = 11;      %存储数据位宽
t = 0:depth-1;
ADC = 2^11 - 1;  %直流分量
A = 2^11;        %幅度
s = A * sin(2*pi*t/(depth-1)) + ADC;
plot(t,s);
fid = fopen('sine.coe','wt');
fprintf(fid,'MEMORY_INITIALIZATION_RADIX=10;\n');
fprintf(fid,'MEMORY_INITIALIZATION_VECTOR=\n');
for i = 1:depth-1
    fprintf(fid,'%d,\n',ceil(s(i)));
end
fprintf(fid,'%d;\n',ceil(s(depth)));
fclose(fid);

在Vivado中调用ROM的IP核
点击IP Catalog
FPGA实现正弦波加和及滤波(Vivado实现,内含IP核调用)_第3张图片
在search中搜索rom,选择Block Memory Generator
FPGA实现正弦波加和及滤波(Vivado实现,内含IP核调用)_第4张图片
点击Port A Option进行修改,主要修改Width和Depth的修改,与之前matlab中保持一致。
FPGA实现正弦波加和及滤波(Vivado实现,内含IP核调用)_第5张图片
点击Other Option
FPGA实现正弦波加和及滤波(Vivado实现,内含IP核调用)_第6张图片
之后一路OK便可。IP核设置完成之后,我们需要在模块中调用。
我们先来写3M正弦波的产生文件。
程序中ROM IP核的调用方法:点击IP Source,找到。veo文件,将框中的代码复制进DDS_3M文件中,实现IP核调用。
FPGA实现正弦波加和及滤波(Vivado实现,内含IP核调用)_第7张图片

module DDS_3M(
    input clk,
    input reset,
    output [11:0] data
    );
parameter fre_word = 32'd128849018;     //频率控制字     fre_word = f_out * 2^N / fclk   N为累加器位宽
reg [31:0] addr_sin;

//相位累加器
always @(posedge clk or reset)  
begin
    if(reset == 0)
        addr_sin <= 32'b0;
    else
        addr_sin <= addr_sin + fre_word;
end

wire [11:0]addra = addr_sin[31:20];
//ROM IP核的调用
sin_rom your_instance_name (
  .clka(clk),    // input wire clk	时钟
  .addra(addra),  // input wire [11 : 0] addra	相位累加器输入给rom的地址
  .douta(data)  // output wire [11 : 0] douta	从ROM返回的数据(3M正弦波的采样点)
);
endmodule

5M、20M正弦波的产生方法与3M完全类似,只是需要改变相位控制字即可,此处不再呈现。

下面的代码为了让后续代码编写起来更简洁

module DDS(
    input clk,
    input reset,
    output [11:0] data_3M,
    output [11:0] data_5M,
    output [11:0] data_20M
    );
    
DDS_3M DDS_3M_inst(
    .clk(clk),
    .reset(reset),
    .data(data_3M)
);

DDS_5M DDS_5M_inst(
    .clk(clk),
    .reset(reset),
    .data(data_5M)
);

DDS_20M DDS_20M_inst(
    .clk(clk),
    .reset(reset),
    .data(data_20M)
);
endmodule

至此,DDS产生正弦波成功。

正弦波求和

此处非常简单,我直接上代码:
需要注意的就是求和之后,和的位数可能会超过之前单个正弦波样值的12位,所以需要拓宽正弦波和的位宽。

module wave_summation(
    input clk,
    input reset,
    input [11:0] wave_3M,
    input [11:0] wave_5M,
    input [11:0] wave_20M,
    
    output  reg [13:0] wave_sum
    );
    
DDS wave(
    .clk(clk),
    .reset(reset),
    .data_3M(wave_3M),
    .data_5M(wave_5M),
    .data_20M(wave_20M)
);

always @(posedge clk or reset)
   if(reset == 0)
        wave_sum = 14'b0;
    else
        wave_sum = wave_3M + wave_5M  + wave_20M;
endmodule

滤波器设计

滤波器的设计是我感觉比较难的一个地方。
先说说我自己遇到的问题:就是DDS模块和求和模块中,符号都是unsigned,而在滤波器设计模块,我按照一篇文章将符号改为signed,导致仿真一直出错。

滤波器的设计及调用我推荐大家这篇文章:https://blog.csdn.net/follow_moon/article/details/80376118
只是其中需要注意的是,在保存.coe文件之前需要将hn的系数改为无符号数:
FPGA实现正弦波加和及滤波(Vivado实现,内含IP核调用)_第8张图片
注:其他操作与我推荐的博客中的操作来便可。
fir IP核调用推荐文章:
https://blog.csdn.net/judas1801/article/details/108506433
这篇文章非常使用,照着设置IP核,便可成功。之后的调用方式与ROM的调用方式完全一样。
https://blog.csdn.net/keilzc/article/details/104249702
这篇文章对于fir IP核调用的理解有很大帮助。
下面是fir滤波器的代码:

module bondpass_filter(
    input clk,
    input [13:0] wave_in,
    output [39:0] wave_out,
    output m_tvalid,
    output s_tready
    );


wire [15:0] s_tdata;
assign s_tdata = {2'b0,wave_in};    //s_tdata为16bit位宽,输入信号wave_in为14bit位宽,通过拼接运算填补高位
   
fir_compiler_0 your_instance_name (
  .aclk(clk),                              
  .s_axis_data_tvalid(1'b1),  
  .s_axis_data_tready(s_tready),  //准备好信号,置1表示FIR可以接收数据输入
  .s_axis_data_tdata(s_tdata),    // input wire [15 : 0] s_tdata
  .m_axis_data_tvalid(m_tvalid),  //FIR输出数据有效信号,置1表示输出有效
  .m_axis_data_tdata(wave_out)    // output wire [39 : 0] m_tdata
);
endmodule

测试文件

`timescale 1ns / 1ps
module wave_summation(
    input clk,
    input reset,
    input [11:0] wave_3M,
    input [11:0] wave_5M,
    input [11:0] wave_20M,
    
    output  reg [13:0] wave_sum
    );
    
DDS wave(
    .clk(clk),
    .reset(reset),
    .data_3M(wave_3M),
    .data_5M(wave_5M),
    .data_20M(wave_20M)
);

always @(posedge clk or reset)
   if(reset == 0)
        wave_sum = 14'b0;
    else
        wave_sum = wave_3M + wave_5M  + wave_20M;
endmodule

加粗样式

你可能感兴趣的:(Verilog,fpga)