目录
一、自我介绍
第一篇文章,先做一个简短的自我介绍。
笔者目前就读于西电本科三年级,攻读电子信息工程方向。之前一直苦于学过的、做过的东西总会随着时间大浪淘沙,因此想在CSDN这个平台上记录下这些东西,方便日后温故知新,同时也希望自己记录的东西能帮到同行者,与诸君共勉!
二、学习任务及内容
学习FPGA一个月有余,最近在着手老师布置的最后一个学习任务:
利用FPGA的DDS核产生两个不同频率的点频信号叠加后进行频谱分析(使用FFT核),然后设计FIR滤波器滤除其中的一个信号分量,将滤波后的信号再次进行频谱分析,以确定滤波器的结果是否符合设计要求。
任务较为简单,主要考核对DDS、FIR、FFT等IP核的学习使用,为日后的信号处理工程打基础。根据要求,我打算采用思路如下:
1.利用DDS分别生成1MHz和10MHz的正弦波进行相加生成叠加信号。
2.利用MATLAB的fdatool工具设计低通滤波器,将生成的滤波器系数.coe文件载入vivado平台的FIR核中,从而生成FIR滤波器。
3.将叠加信号载入FIR滤波器滤除掉高频10MHz信号。
4.利用不同输入位宽的FFT核对叠加信号以及滤除后信号做频谱分析,从而验证滤波效果。
三、工程实现
3.1 创建工程文件以及fir顶层文件
工程文件的创建不再赘述。工程文件创建好后,首先创建fir顶层文件,考虑到模块功能,需要例化两个DDS核分别生成1MHz以及10MHz的正弦波并进行叠加;之后送入FIR核例化的滤波器中进行滤波;之后将叠加信号与滤波信号输送入FFT核中进行频谱分析。因此给出代码如下(代码详解见注释):
//顶层模块:FIR滤波器设计
//功能:滤除掉xin(1MHz&10MHz)中的高频分量,并做频谱分析
//2021.8.14——8.18
module fir(
input wire sclk ,//系统时钟
input wire rst_n ,//复位按钮(低有效)
output wire m_axis_data_tvalid,//fir输出有效标志
output wire signed [34:0] fir_dout ,//滤波后输出数据
output wire signed [47:0] fft_out_xin ,//对复合输入信号做频谱分析
output wire signed [63:0] fft_out_dout //对滤波后输出数据做频谱分析
);
wire [15:0] sin_1MHz ; //1MHz正弦波
wire [15:0] sin_10MHz ; //10MHz正弦波
wire signed [16:0] xin; //复合输入正弦波
reg [34:0] fir_out_reg;//保存滤波输出数据的寄存器
//保存滤波输出数据至寄存器
always @ (posedge sclk) begin
fir_out_reg <= fir_dout;
end
//fir滤波器引脚声明
wire s_axis_data_tready;//FIR准备接受数据
wire [39 : 0] m_axis_data_tdata ;//原FIR输出数据
//根据FIR信息栏中显示输出数据只有35位,因此对原输出数据进行截取
assign fir_dout = m_axis_data_tdata[34:0];
//DDS例化1——生成1MHz正弦波
dds_1MHz sin1MHz(
.sclk(sclk),
.sin_1MHz(sin_1MHz)
);
//DDS例化2--生成10MHz正弦波
dds_10MHz sin10MHz(
.sclk(sclk),
.sin_10MHz(sin_10MHz)
);
//两信号想加得到复合信号
assign xin = {sin_1MHz[15],sin_1MHz} + {sin_10MHz[15],sin_10MHz};//注意这里添加了符号位,因为两边均要求是有符号数的相加
//FIR例化
fir_compiler_0 inst_fir(
.aclk(sclk), // input wire aclk
.s_axis_data_tvalid(1'b1), // input wire s_axis_data_tvalid
.s_axis_data_tready(s_axis_data_tready), // output wire s_axis_data_tready
.s_axis_data_tdata({{7{xin[16]}},xin}), // input wire [23 : 0] s_axis_data_tdata(!注意这里要用讲17位的xin符号扩展至要求的24位)
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(m_axis_data_tdata) // output wire [39 : 0] m_axis_data_tdata
);
//FFT例化1——对复合信号进行频谱分析
fft_xin inst_fft_xin(
.sclk(sclk) ,
.rst_n(rst_n),
.xin(xin) ,
.fft_out(fft_out_xin)
);
//FFT例化2——对FIR输出信号进行频谱分析
fft_dout inst_fft_dout(
.sclk(sclk) ,
.rst_n(rst_n),
.fir_out(fir_out_reg) ,
.fft_out(fft_out_dout)
);
endmodule
其中需要注意的几点罗列如下:
1.我个人喜欢自顶而下的编程方式,因此先创建好顶层文件,再在之后的步骤中例化IP核(上面展示的代码也是后来在IP核的一步步例化中完善的,部分要点也会在文章之后的部分说明)。也可以先创建需要的例化文件,再编写顶层文件。
2.部分变量(引脚)数据位以及模块的具体管脚需要根据具体生成的IP核的管脚数据显示而定,自顶而下时可先将数据位省略,而只列管脚名。
3.fir_dout、fir_dout、fft_out_dout以及xin均为signed(有符号数)变量。
4.xin为有符号数且数据位比右侧数据多一位,因此等号右侧两变量需扩展一位数据位。具体Verilog有符号数的运算推荐此链接:https://www.cnblogs.com/LJWJL/p/3481807.html
5.对滤波后信号fir_dout做FFT变换时,刚开始我将之直接连到了FFT核的输入管脚,但vivado一直报错。因此我在这两者之间加了一级寄存器fir_out_reg保存滤波输出数据就可以了。此处问题还请看官指点。
顶层文件有了基本的雏形后,剩下的就是编写例化文件以及IP核了。
3.2 生成正弦波模块的文件编写与DDS核的例化
其实此处不需要单独编写生成正弦波模块的文件,只需要直接在顶层文件例化DDS核即可。但考虑到工程需要两个DDS核,每个DDS核引脚相同,若全部例化在顶层文件需要对其引脚做大量且不同的声明,会使顶层文件变得冗长;此外我们所需的生成正弦波模块只需两个引脚即可(即输入时钟及输出正弦信号),而无需多余引脚。综合以上两点,我选择先编写两个生成正弦波模块的文件,再在此文件中例化DDS核。之后FFT核的情况与之一致。
(1)编写生成1MHz正弦波模块文件如下:
//生成1MHz正弦波
module dds_1MHz(
input wire sclk,
output wire [15:0] sin_1MHz
);
//DDS核引脚声明
wire m_axis_data_tvalid ;
wire m_axis_phase_tvalid;
wire [15:0] m_axis_phase_tdata ;
//DDS核例化
dds_compiler_0 inst_dds_1MHz (
.aclk(sclk), // input wire aclk
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(sin_1MHz), // output wire [15 : 0] m_axis_data_tdata
.m_axis_phase_tvalid(m_axis_phase_tvalid), // output wire m_axis_phase_tvalid
.m_axis_phase_tdata(m_axis_phase_tdata) // output wire [15 : 0] m_axis_phase_tdata
);
endmodule
本工程中DDS核采用100MHz系统时钟,输出正弦信号数据位宽为16,因此Spurious Free Dynamic Range填入16*6=96;相位累加器位宽为16位,因此根据DDS频率步进精度计算公式:
可得Frequency Resolution项为100M/2^16=1525.87890625(Hz)。因此Configuration配置如下:
在 Implementation设置输出波形为正弦波:
在Output Frequencies中设置输出频率为1MHz:
其它选项选择缺省值。此时可以在Summary中看到DDS核的具体信息以确定是否设计正确:
生成IP核后可在IP界面的.veo文件中找到例化模板,同时可确定响应管脚名及数据位宽:
(2)编写生成10MHz正弦波模块文件如下:
module dds_10MHz(
input wire sclk,
output wire [15:0] sin_10MHz
);
//DDS核引脚声明
wire m_axis_data_tvalid ;
wire m_axis_phase_tvalid;
wire [15:0] m_axis_phase_tdata ;
//DDS核例化
dds_compiler_1 inst_dds_1MHz (
.aclk(sclk), // input wire aclk
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tdata(sin_10MHz), // output wire [15 : 0] m_axis_data_tdata
.m_axis_phase_tvalid(m_axis_phase_tvalid), // output wire m_axis_phase_tvalid
.m_axis_phase_tdata(m_axis_phase_tdata) // output wire [15 : 0] m_axis_phase_tdata
);
endmodule
其DDS核的例化与1MHz的DDS核类似,仅将输出频率改为10MHz即可,这里不在赘述具体过程。
3.3 FIR核例化
Vivado平台的FIR核不支持生成滤波器系数,因此需要借助MATLAB的fdatool工具箱设计FIR滤波器,将其产生的滤波器系数.coe文件载入FIR核中,从而完成设计。
(1)利用MATLAB设计FIR滤波器
打开MATLAB软件,在命令行键入fdatool打开滤波器设计工具箱。考虑到滤波要求,这里选择低通滤波器,利用等波纹法设计法(Equiripple)设计滤波器,选择滤波器阶数为16,采样频率为100MHz(与DDS输出频率保持一致),通带截止频率为2MHz,阻带截止频率为10MHz,其它采用缺省值(当然进一步优化滤波器也可以深入学习下此工具箱的应用),点击Design Filter可观察设计滤波器的幅频响应:
将滤波器系数导出为.coe文件需要将Filter arithmetic改为Fixed-point,另外选择数据位宽为16位。
导出.coe文件并保存:
(2)设计FIR核
打开FIR的IP核,在Filter Options界面导入刚刚生成的.coe文件:
可以看到滤波器系数有17个,正好比阶数多1。
在Channel Specification界面设置输入数据的采样频率与系统时钟(均为100MHz):
考虑到FIR是对叠加信号进行滤波,而叠加信号xin是两个16位DDS核的输出的加和,因此此处选择输入数据位宽为17位,数据类型为有符号数。注意系数也要选择有符号数,位宽为16。
由于FIR本质是两个数列的卷积和运算,因此注意到输出数据扩展至35位。
其它选项选择缺省值,可在Summary界面看到FIR相关系数如下:
注意到上图中箭头标出,输入数据位宽实际为24位,因此在顶层文件的FIR核例化中需要对17位的xin扩展7位符号位:
.s_axis_data_tdata({{7{xin[16]}},xin}), // input wire [23 : 0] s_axis_data_tdata(!注意这里要用讲17位的xin符号扩展至要求的24位)
同时注意到输出数据位宽实际为40位,因此为了不引起理解上的偏差,此处再声明一个变量fir_dout 用来盛装输出数据m_axis_data_tdata的低35位:
assign fir_dout = m_axis_data_tdata[34:0];
此外为了方便接受数据,此处将准备接受数据标志位s_axis_data_tvalid一直置为1:
.s_axis_data_tvalid(1'b1), // input wire s_axis_data_tvalid
配置完FIR核后,即可同上DDS核一样通过.veo文件得到例化模板在顶层文件例化,此处不再赘述。
3.4 FFT核例化
如上所述,为了优化文件结构,这里对FFT核进行单独的模块文件编写 。
(1)编写对叠加信号xin进行频响分析的文件
首先进行FFT的IP核例化,在Configuration界面选择做256点FFT,系统时钟为100MHz,算法选择基2,BurstI/O算法:
在Implementation界面中选择输入数据位宽为17,输出位序为自然位序,若想要观察算法过程中是否会出现数据溢出的现象还可勾选OVFLO引脚。
其它采取缺省项,生成IP核,找到其对应的.veo文件,可以看到例化模型以及各个管脚的功能:
之后建立模块文件,并对该IP核进行例化,先给出代码如下:
//对复合信号做频谱分析
module fft_xin(
input wire sclk ,//系统时钟
input wire rst_n ,//复位按钮
input wire signed [16:0] xin ,//复合信号
output reg signed [47:0] fft_out //幅频响应
);
//FFT核的引脚声明
wire s_axis_config_tready;
reg [47:0] s_axis_data_tdata;
reg s_axis_data_tvalid ;
wire s_axis_data_tready ;
reg s_axis_data_tlast ;
wire [47:0] m_axis_data_tdata;
wire [7:0] m_axis_data_tuser;
wire m_axis_data_tvalid;
wire m_axis_data_tlast;
wire [7:0] m_axis_status_tdata;
wire m_axis_status_tvalid;
wire m_axis_status_tready;
wire event_frame_started ;
wire event_tlast_unexpected;
wire event_tlast_missing;
wire event_fft_overflow;
wire event_status_channel_halt;
wire event_data_in_channel_halt;
wire event_data_out_channel_halt;
//声明count变量用以计数输入数据个数
reg [8:0] count;
//FFT输出
reg signed [23:0] fft_i_out;//输出虚部
reg signed [23:0] fft_q_out;//输出实部
//FFT过程
always @ (posedge sclk or negedge rst_n) begin
if(!rst_n) begin
s_axis_data_tvalid <= 1'b0;
s_axis_data_tdata <= 48'd0;
s_axis_data_tlast <= 1'b0;
count <= 9'd0;
end
//当s_axis_data_tready准备就绪时开始输入数据,并接受256个数据
else if (s_axis_data_tready) begin
if(count == 10'd255) begin
s_axis_data_tvalid <= 1'b1;
s_axis_data_tlast <= 1'b1;
s_axis_data_tdata <= {24'd0,{{7{xin[16]}},xin}};//注意要符号扩展(下同)
count <= 9'd0;
end
else begin
s_axis_data_tvalid <= 1'b1;
s_axis_data_tlast <= 1'b0;
s_axis_data_tdata <= {24'd0,{{7{xin[16]}},xin}};
count <= count + 1'b1;
end
end
else begin
s_axis_data_tvalid <= 1'b0;
s_axis_data_tlast <= 1'b0;
s_axis_data_tdata <= s_axis_data_tdata;
end
end
//FFT输出赋值
always @ (posedge sclk) begin
if(m_axis_data_tvalid) begin
fft_i_out <= m_axis_data_tdata[23:0] ;
fft_q_out <= m_axis_data_tdata[47:24];
end
end
//计算幅频响应
always @ (posedge sclk) begin
fft_out <= $signed(fft_i_out)* $signed(fft_i_out)+ $signed(fft_q_out)* $signed(fft_q_out);//注意是有符号数的乘法
end
//FFT核例化
xfft_0 your_instance_name (
.aclk(sclk), // input wire aclk
.aresetn(rst_n), // input wire aresetn
//由于fft变换数据位数会不断增加,可能会出现溢出的情况,因此这里config通道在其scale部分设置各级放缩倍数;因为是256个数据(2^8)因此这里scale占2*8=16位
.s_axis_config_tdata(24'b0000000_0101010101010101_1), // input wire [23 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(s_axis_config_tready), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_data_tdata), // input wire [47 : 0] s_axis_data_tdata !!!!!!!!!
.s_axis_data_tvalid(s_axis_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(s_axis_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(s_axis_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_data_tdata), // output wire [47 : 0] m_axis_data_tdata!!!!!!!!!!!
.m_axis_data_tuser(m_axis_data_tuser), // output wire [7 : 0] m_axis_data_tuser
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(1'b1), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_data_tlast), // output wire m_axis_data_tlast
.m_axis_status_tdata(m_axis_status_tdata), // output wire [7 : 0] m_axis_status_tdata
.m_axis_status_tvalid(m_axis_status_tvalid), // output wire m_axis_status_tvalid
.m_axis_status_tready(m_axis_status_tready), // input wire m_axis_status_tready
.event_frame_started(event_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_tlast_missing), // output wire event_tlast_missing
.event_fft_overflow(event_fft_overflow), // output wire event_fft_overflow
.event_status_channel_halt(event_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(event_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(event_data_out_channel_halt) // output wire event_data_out_channel_halt
);
endmodule
代码详细说明可见注释,相应管脚说明可参此链接:https://www.cnblogs.com/lgy-gdeu/p/11590626.html
其中需要注意的几点罗列如下:
1.在.veo例化模型中,注意到FFT输入位宽为48位,即虚部和实部各24位,因此需要对输入的17位数据xin进行7位的符号扩展并赋值给低24位,高24位接地(赋零)即可。
2.输出的48位数据m_axis_data_tdata中高24和第24位分别为结果的虚部fft_i_out和实部fft_q_out,此两部分均为有符号数,因此计算幅频响应时需利用系统函数$signed进行修饰。该函数具体说明可参此链接:https://blog.csdn.net/wordwarwordwar/article/details/108039574
3.由于FFT算法中存在较多加法与乘法运算,因此会存在数据位增大而导致的数据溢出的现象,因此FFT核提供了数据缩放设置(SCALE_SCH),该系数可在s_axis_config_tdata通道中进行设置,对应数据位可在IP界面的Implementation Details中找到:
其具体的数据配置说明可参此链接:https://www.cnblogs.com/xiaoxuesheng993/p/8064156.html,此处笔者就不班门弄斧了。
本工程中我选择进行的是256(2^8)点FFT,因此对应8级压缩,每级对应2bits数据进行1,2,4,8倍的选择压缩,因此SCALE_SCH对应[16:1]位,选择对每级进行2倍的压缩,并通过配置s_axis_config_tdata[0]为1设置为正向FFT变换(0为逆向),得到s_axis_config_tdata配置数据为24'b0000000_0101010101010101_1。
(2)编写对滤波后信号fir_out进行频响分析的文件
该部分与对xin进行频响分析的文件大同小异,仿照上述IP核设置过程与代码可编程如下:
//对FIR输出信号做频谱分析
module fft_dout(
input wire sclk ,//系统时钟
input wire rst_n ,//复位按钮
input wire signed [34:0] fir_out,//FIR输出信号
output reg signed [63:0] fft_out //幅频响应
);
//FFT核的引脚声明
wire s_axis_config_tready;
reg [79:0] s_axis_data_tdata;
reg s_axis_data_tvalid ;
wire s_axis_data_tready ;
reg s_axis_data_tlast ;
wire [79:0] m_axis_data_tdata;
wire [7:0] m_axis_data_tuser;
wire m_axis_data_tvalid;
wire m_axis_data_tlast;
wire [7:0] m_axis_status_tdata;
wire m_axis_status_tvalid;
wire m_axis_status_tready;
wire event_frame_started ;
wire event_tlast_unexpected;
wire event_tlast_missing;
wire event_fft_overflow;
wire event_status_channel_halt;
wire event_data_in_channel_halt;
wire event_data_out_channel_halt;
//声明count变量用以计数输入数据个数
reg [8:0] count;
//FFT输出
reg signed [39:0] fft_i_out;
reg signed [39:0] fft_q_out;
//FFT过程
always @ (posedge sclk or negedge rst_n) begin
if(!rst_n) begin
s_axis_data_tvalid <= 1'b0;
s_axis_data_tdata <= 80'd0;
s_axis_data_tlast <= 1'b0;
count <= 9'd0;
end
//当s_axis_data_tready准备就绪时开始输入数据,并接受256个数据
else if (s_axis_data_tready) begin
if(count == 10'd255) begin
s_axis_data_tvalid <= 1'b1;
s_axis_data_tlast <= 1'b1;
s_axis_data_tdata <= {40'd0,{{5{fir_out[34]}},fir_out}};//注意要符号扩展(下同)
count <= 9'd0;
end
else begin
s_axis_data_tvalid <= 1'b1;
s_axis_data_tlast <= 1'b0;
s_axis_data_tdata <= {40'd0,{{5{fir_out[34]}},fir_out}};
count <= count + 1'b1;
end
end
else begin
s_axis_data_tvalid <= 1'b0;
s_axis_data_tlast <= 1'b0;
s_axis_data_tdata <= s_axis_data_tdata;
end
end
//FFT输出赋值
always @ (posedge sclk) begin
if(m_axis_data_tvalid) begin
fft_i_out <= m_axis_data_tdata[39:0] ;
fft_q_out <= m_axis_data_tdata[79:40];
end
end
//计算幅频响应
always @ (posedge sclk) begin
fft_out <= $signed(fft_i_out)* $signed(fft_i_out)+ $signed(fft_q_out)* $signed(fft_q_out);
end
//FFT核例化
xfft_1 your_instance_name (
.aclk(sclk), // input wire aclk
.aresetn(rst_n), // input wire aresetn
//注意这里config通道需要在scale相应位配置放缩因子,不然一方面会出现数据溢出现象;另外一方面会出现因为输出结果太大而fft_out位数大于64位而无法呈现analog波形的情况
.s_axis_config_tdata(24'b0000000_0101101010101010_1), // input wire [23 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(s_axis_config_tready), // output wire s_axis_config_tready
.s_axis_data_tdata(s_axis_data_tdata), // input wire [79 : 0] s_axis_data_tdata
.s_axis_data_tvalid(s_axis_data_tvalid), // input wire s_axis_data_tvalid
.s_axis_data_tready(s_axis_data_tready), // output wire s_axis_data_tready
.s_axis_data_tlast(s_axis_data_tlast), // input wire s_axis_data_tlast
.m_axis_data_tdata(m_axis_data_tdata), // output wire [79 : 0] m_axis_data_tdata
.m_axis_data_tvalid(m_axis_data_tvalid), // output wire m_axis_data_tvalid
.m_axis_data_tready(1'b1), // input wire m_axis_data_tready
.m_axis_data_tlast(m_axis_data_tlast), // output wire m_axis_data_tlast
.event_frame_started(event_frame_started), // output wire event_frame_started
.event_tlast_unexpected(event_tlast_unexpected), // output wire event_tlast_unexpected
.event_tlast_missing(event_tlast_missing), // output wire event_tlast_missing
.event_status_channel_halt(event_status_channel_halt), // output wire event_status_channel_halt
.event_data_in_channel_halt(event_data_in_channel_halt), // output wire event_data_in_channel_halt
.event_data_out_channel_halt(event_data_out_channel_halt) // output wire event_data_out_channel_halt
);
endmodule
IP核设置只需将输入数据位宽改为fir_out的位宽35即可,但发现此处选项最高可选为34,但选择34后,系统显示的输入位宽为80(虚实部各40位):
因此还需要对输入数据fir_out进行5位的符号扩展:
s_axis_data_tdata <= {40'd0,{{5{fir_out[34]}},fir_out}};//注意要符号扩展
此外还需注意,因为Vivado平台不支持对64位以上的数据进行Analog显示,因此一方面需要将输出数据fft_out改为64位,另一方面需要为SCALE_SCH配置更高的压缩值,此处选择s_axis_config_tdata通道值为24'b00000_010101101010101010_1。
编写好两FFT文件后,在顶层文件fir中进行例化即可。
3.5 Testbench文件编写与方针实现
因为我已经在顶层文件中将很多模块进行了连接,因此Testbench文件的编写就比较简单了,仅仅给出时钟信号、复位信号以及顶层文件的例化即可:
//Testbench仿真文件
module tb_fir;
reg sclk ;//系统时钟
reg rst_n ;//复位按钮(低有效)
wire m_axis_data_tvalid;//fir输出有效标志
wire signed [34:0] fir_dout ;//滤波后输出数据
wire signed [47:0] fft_out_xin ;//对复合输入信号做频谱分析
wire signed [63:0] fft_out_dout;//对滤波后输出数据做频谱分析
//初始化
initial begin
rst_n =0 ;
sclk = 0;
#100
rst_n = 1;
end
//生成100MHz时钟
always #5 sclk <= ~sclk;
//顶层模块例化
fir inst_fir(
.sclk(sclk),
.rst_n(rst_n),
.m_axis_data_tvalid(m_axis_data_tvalid),
.fir_dout(fir_dout),
.fft_out_xin(fft_out_xin),
.fft_out_dout(fft_out_dout)
);
endmodule
点击Run Simulation进行行为级仿真即可得到结果图如下:
可见叠加信号、滤波后信号以及两信号的幅频响应均达到预期,滤波效果也不错,较好的完成了此项工程任务。
四、结语
断断续续学习FPGA一月有余,第一次自己着手做这么大的仿真任务,着实花费了笔者诸多心力,但也使我收获颇多。另外鉴于笔者乍学识浅,文中部分代码以及陈述说明均有再提升或是不当之处,还望诸君海涵斧正。
最后感谢F学长和实验室几个学长的悉心指导,以及由于篇幅文中所未提及但对笔者学习有帮助的各个书籍、网站与作者!再次感谢!