总体设计:
1.使用Matlab的filter designer 设计一个FIR滤波器并输出定点数抽头系数;
2.在Vivado设计中使用FIR compiler IP核,并将生成的coe文件导入;
3.连接FIR滤波器的输入数据信号;
4.FIR滤波器输出数据由FPGA的片内DAC输出,测试波形。
Matlab里获取抽头系数的操作网上有很多资源,在这里简单提一点:
如果您的filter designer界面左下角图标数量不够(如上图),且Targets中没有输出Xilinx COE文件选项,您需要安装DSP System Toolbox以补充该app功能。
这里利用filter designer 设计了一个9阶的等波纹低通滤波器。
Verilog代码部分主要为DAC输出和FIR滤波器两大部分。先说DAC部分。
DAC不妨自己先单独测试输出。本EGO1板子中内置的DAC型号为DAC0832,下载相关数据手册编写测试代码即可。
这里依然采用MATLAB生成波形数据信号coe文件。注意你的生成的数据范围,应该配合DAC的数据数据位数。DAC0832是8位的,所以输出的数据范围为0~255。
下面生成的数据中,含有基频的2倍分量,200倍分量,220倍分量,共500个数据点。
x = linspace(0,2*pi,500); %在0和2pi间取500个点
y1 = sin(2*x) +0.4*sin(200*x)+0.2*sin(220*x-0.6);%设计含有高频信号与低频信号的输入信号
y1 = y1-min(y1); //抬高y1为非负值
y1 = round(y1/3.3*256); %在将波形抬高到坐标轴上后,再将上面算得的数值放大量化成8位
fid = fopen('filter_coe.txt','wt'); % 生成TXT文件
fprintf( fid, 'memory_initialization_radix=10;\n', y1);%生成索引
fprintf( fid, 'memory_initialization_vector =', y1);
fprintf(fid,'%.0f,',y1);//最后多了一个逗号记得更改
fclose(fid)
plot(y1)
参见DDS原理,生成控制DAC工作的时钟。这里选定基频f_out为100Hz,也就是1秒钟将生成上面的波形100个,所以采样率为:
进而下面生成的dac_clk时钟频率应该由下式计算:
另一方面,对于下一段代码对应的分频系数period(Np):
(注意到period由0计数到Np,这里Np应该是1999,对于你自己编写的代码,应该由自己的逻辑决定该值)
module new_clk(clk,rst_n,dac_clk);
input clk,rst_n;
output dac_clk;
parameter period=2000-1; //100M/(500*f) f=100Hz
reg [17:0]count=0;
always@(posedge clk) begin
if(!rst_n)
count<=0;
else if(count<period)
count<=count+1;
else
count<=0;
end
assign dac_clk=(count==period);
endmodule
在Vivado中使用Block Memory Generater IP核,导入coe即可将数据存入ROM中。
该IP还要一个地址值addr以读取ROM的数据:
always@(posedge dac_clk or negedge rst_n)
begin
if(!rst_n)
addr<=0;
else if(addr==9'd500)
addr<=0;
else
addr<=addr+1;
end
实例化ip,输出的数据直接接给DAC的八位input数据。
blk_mem_gen_0 origin_wave_output (
.clka(dac_clk), // input wire clka
.ena(1), // input wire ena
.addra(addr), // input wire [8 : 0] addra
.douta(origin_DAC_data) // output wire [7 : 0] douta
);
下面是DAC数据手册给出的波形时序
对于其他的控制信号,实际上不用太多复杂的操作。参照数据手册,您可以选择最为简单的输出模式:Flow-Through Operation
参照手册将wire型CS WR1 WR2等信号assign为高电平或低电平即可,DAC会实时反映输入的八位数据的变化。
这里采用稍微复杂一点的模式:Single-Buffered Operation
该模式下需要控制CS 和WR1信号,其他的仍然assign对应电平就行。
简单来说,该模式下当CS和WR1置低(低有效)时,对应的输入数据锁存入缓冲区。
所以信号频率和dac_clk一致即可,也就是每当addr变化时,数据更新为下一个新数据,拉低CS和WR1以存入信号。
module set_wr1(clk,rst_n,dac_clk,wr1);
input clk,rst_n,dac_clk;
output wr1;
parameter wr1_period=200; //wr1的低电平有效时长为200个clk
reg [7:0]count_wr=0;
always@(posedge clk) begin
if(!rst_n)
count_wr<=0;
else if(dac_clk)
count_wr<=count_wr+1;
else if(count_wr>0&&count_wr<wr1_period)
count_wr<=count_wr+1;
else
count_wr<=0;
end
assign wr1=(count_wr==0); //低有效
endmodule
必须注意一下信号低电平的持续时间,有最小值的要求。
再说FIR部分,实例化FIR滤波器的IP核后连接各个引脚。
fir_compiler_0 myFIR(
.aclk(clk), // input wire aclk
.s_axis_data_tvalid(!start_FIR_flag), // input wire s_axis_data_tvalid
.s_axis_data_tready(FIR_data_ready), // output wire s_axis_data_tready
.s_axis_data_tdata(origin_DAC_data), // input wire [7 : 0] s_axis_data_tdata
.m_axis_data_tvalid(FIR_data_valid_flag), // output wire m_axis_data_tvalid
.m_axis_data_tdata(FIR_data) // output wire [31 : 0] m_axis_data_tdata
);
我们需要一个input信号data_tvalid一次来控制FIR滤波转换的开始。时序逻辑上,ROM输出的信号更新时应该将该其置为有效一次,即实际进行卷积一次。
您也可以让该input信号一直为高电平有效,效果您可以自己调试仿真。
当然,ROM读取的数据更新到滤波后信号完成有一定的时序差,所以想要靠DAC输出滤波后的数据就要靠FIR_data_valid_flag生成另一组CS和WR1信号以输出FIR滤波后数据给DAC。生成方法完全类似生成用于原始数据的CS和WR1信号的流程,见下:
module set_wr1_FIR(clk,rst_n,FIR_data_valid_flag,wr1_FIR);
input clk,rst_n,FIR_data_valid_flag;
output wr1_FIR;
parameter wr1_FIR_period=250;
reg [7:0]count_wr=0;
always@(posedge clk) begin
if(!rst_n)
count_wr<=0;
else if(FIR_data_valid_flag)
count_wr<=count_wr+1;
else if(count_wr>0&&count_wr<wr1_FIR_period)
count_wr<=count_wr+1;
else
count_wr<=0;
end
assign wr1_FIR=(count_wr==0); //低有效
endmodule
另外,FIR_data信号可能并不是所有时候都是正确的滤波数据,应该在其有效时获取该值。
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
FIR_data_out<=0;
else if(FIR_data_valid_flag)
FIR_data_out<=FIR_data;
else
FIR_data_out<=FIR_data;
end
当然,给DAC的输出的话数据还得是7位的。仿真的时候获取到了FIR_data_out的大致的范围,将其除以适当的值缩小至0~255的范围即可(下面用移位代替除法)。
虽然不是很精确,输出信号幅度也改变了、、
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
FIR_DAC_data<=0;
else
FIR_DAC_data<=FIR_data_out>>16; //FIR_DATA仿真最大值约除以50000就能缩减至256以内
end
弄个按键就可以切换输出,选择未滤波信号(ROM原始数据)或滤波后信号了。
仿真波形示例:
效果:
2倍基频的信号(200Hz)被保留,200倍和220倍基频信号(20kHz和22kHz的信号)基本被滤除。
verilog main模块整体代码:
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/04/05 17:12:28
// Design Name:
// Module Name: main
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module main(clk,rst_n,mode,data_out,ile,cs,wr1,wr2,xfer);
input clk,rst_n;
input mode;
output [7:0]data_out;
output ile,cs,wr1,wr2,xfer;
reg [8:0]addr=9'd0;
wire dac_clk;
wire FIR_data_ready,FIR_data_valid_flag;
wire [31:0]FIR_data;
reg [31:0] FIR_data_out;
wire start_FIR_flag;
wire wr1_origin,wr1_FIR; //输出原始信号和滤波信号对应的用于DAC的wr1信号
wire [7:0]origin_DAC_data;
reg [7:0]origin_DAC_data_1; //原始信号的延迟信号
reg [7:0]FIR_DAC_data;
new_clk U1(clk,rst_n,dac_clk);
set_wr1 U2(clk,rst_n,dac_clk,wr1_origin);
set_wr1_FIR U3(clk,rst_n,FIR_data_valid_flag,wr1_FIR);
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
origin_DAC_data_1<=0;
else
origin_DAC_data_1<=origin_DAC_data;
end
assign start_FIR_flag= (origin_DAC_data_1==origin_DAC_data);
always@(posedge dac_clk or negedge rst_n)
begin
if(!rst_n)
addr<=0;
else if(addr==9'd500)
addr<=0;
else
addr<=addr+1;
end
blk_mem_gen_0 origin_wave_output (
.clka(dac_clk), // input wire clka
.ena(1), // input wire ena
.addra(addr), // input wire [8 : 0] addra
.douta(origin_DAC_data) // output wire [7 : 0] douta
);
fir_compiler_0 myFIR(
.aclk(clk), // input wire aclk
.s_axis_data_tvalid(!start_FIR_flag), // input wire s_axis_data_tvalid
.s_axis_data_tready(FIR_data_ready), // output wire s_axis_data_tready
.s_axis_data_tdata(origin_DAC_data), // input wire [7 : 0] s_axis_data_tdata
.m_axis_data_tvalid(FIR_data_valid_flag), // output wire m_axis_data_tvalid
.m_axis_data_tdata(FIR_data) // output wire [31 : 0] m_axis_data_tdata
);
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
FIR_data_out<=0;
else if(FIR_data_valid_flag)
FIR_data_out<=FIR_data;
else
FIR_data_out<=FIR_data;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
FIR_DAC_data<=0;
else
FIR_DAC_data<=FIR_data_out>>16; //FIR_DATA仿真最大值约除以50000就能缩减至256以内
end
assign wr1 = mode? wr1_origin:wr1_FIR;
assign cs=wr1;
assign data_out= mode? origin_DAC_data:FIR_DAC_data;
assign ile=1;
assign wr2=0;
assign xfer=0;
endmodule