Matlab&Vivado FIR滤波器设计 配合DAC测试输出

总体设计:

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个,所以采样率为:

f_s=500\times 100 =50000 Hz

进而下面生成的dac_clk时钟频率应该由下式计算:

f_{out}=\frac{f_{dac\_clk}}{N}

另一方面,对于下一段代码对应的分频系数period(Np):

f_{dac\_clk}=\frac{f_{FPGA}}{N_{p}+1}

(注意到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

  • 4
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值