【FPGA学习】MATLAB与FPGA实现FIR滤波器

本篇博客记录一下在matlab设计和在FPGA平台实现FIR滤波器的方法,平台是Xilinx的ZYNQ

参考:
AMBA AXI-Stream Protocol Specification

使用matlab设计FIR滤波器

fdatool是matlab中专用的滤波器设计工具,在matlab中的命令行窗口直接输入fdatool即可(也可以用filterDesigner):
fdatool
打开后的界面如下:
fdatool界面

  • 设置滤波器的参数,在界面的下半部
    • 响应类型:也就是滤波器类型
    • 设计方法:设计FIR滤波器和IIR滤波器的各种方法
    • 滤波器阶数:定义滤波器的阶数,可指定阶数(对于n阶滤波器,指定阶数应填写n-1)或直接根据选择的滤波器类型使用最小阶数
    • 频率设定:定义频带的各参数
    • 幅值设定:定义幅值衰减的情况
    • 当采用窗函数设计时,还可以选择可选的窗函数
      这个界面实际上是左边的最下面的设计滤波器选项:
      设计滤波器
      由于我们要将FIR滤波器在FPGA的硬件上实现,因此还要在左边的第三个选项选择设置量化参数,并选择定点数:
      设置量化参数
      定点数
      同时在分子字长还可以设定滤波器定点系数的长度

实例

本博客以一个示例来说明整个过程,滤波器要求如下:

  • FIR滤波器输入采样率为100kHz
  • 滤波器通带的单边带带宽为20kHz
  • 带外衰减为90dB
  • 带内波纹为1dB
  • 滤波器系数为定点数,ADC采样信号(即输入信号)的位数为24位,滤波器系数的位数可自己定义

输入信号的频谱如下所示:
信号频谱

根据如上的要求,可在matlab中设计滤波器:
滤波器设计

设计完成后在上面的目标即可生成Xilinx系数文件(.coe文件):
生成.coe文件

调用FIR IP核

实现FIR滤波器可以直接调用提供的IP核,本篇博客中使用的是vivado中的FIR IP核
在工程左边中点击IP Catalog:
IP Catalog
搜索FIR即可找到需要调用的IP核:
FIR Compiler
首先需要配置滤波器的系数:
滤波器系数
接下来需要配置一下输入的采样频率以及时钟频率
频率
还需要修改一些输入数据的位数等参数:
输入数据位数
在Interface中还可以看到IP核的复位信号和使能信号,我这里将复位信号改为了低电平有效:
复位和使能

在vivado中生成IP核后,可以在IP Sources中的Instantiation Template中获取例化IP核的模板,比如在.veo中就是可以获取Verilog的例化模板:
.veo
可以直接根据这个进行例化

在编写Verilog代码之前需要对IP核有所了解,这个一方面要看Xilinx官方的IP手册,FIR IP核对应的是PG149,另一方面可以在IP核配置界面的Implementation Details可以看到一些信息,尤其是确定接口信号的位数:
Implementation Details

接口控制模块编写如下:

// Xilinx FIR IPcore Interface Control
module fir_control(
    input sys_clk,      // 50M clock
    input sys_rst_n,

    input [23:0] s_axis_data_tdata,
    input s_axis_data_tvalid,
    output m_axis_data_tvalid,
    output s_axis_data_tready,
    output [41:0] fir_out,
    output reg clk_100k
    );

parameter count = 250;

wire [47:0] m_axis_data_tdata;
reg [15:0] cnt;
// reg clk_100k;
assign fir_out = m_axis_data_tdata[41:0];

always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
    begin
        cnt <= 16'd0;
        clk_100k <= 1'b0;
    end
    else if(cnt < count - 1)
    begin
        cnt <= cnt + 1;
    end
    else
    begin
        cnt <= 16'd0;
        clk_100k <= ~clk_100k;
    end
end


fir_compiler_0 fir (
  .aresetn(sys_rst_n),                        // input wire aresetn
  .aclk(clk_100k),                              // input wire aclk
  .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_tdata(s_axis_data_tdata),    // input wire [23 : 0] s_axis_data_tdata
  .m_axis_data_tvalid(m_axis_data_tvalid),  // output wire m_axis_data_tvalid
  .m_axis_data_tdata(m_axis_data_tdata)    // output wire [47 : 0] m_axis_data_tdata
);

endmodule

同时还编写了testbench用于测试,testbench一方面要读取输入信号数据,另一方面要将输出信号输出到一个文件里,在matlab进行测试,这些都要用到Verilog语法中的系统任务

  • 使用数据文件对存储器进行初始化:\(readmemb和\)readmemh,分别可以用于读取二进制数和十六进制数
    • $readmemb("<file_name>",<memory_name>,<start_addr>,<finish_addr>);
    • <start_addr>和<finish_addr>是可选的,<start_addr>的默认值是存储器数组的开始位置,<finish_addr>的默认值是数据文件或存储器的结束位置
  • 可以将Verilog的输出重定向到选择的文件
    • 打开文件:$fopen
      • <file_handle>=$fopen("<file_name>");
      • \(fopen返回一个被称为**多通道描述符**的**32位**值,多通道描述符中只有一位被设置成1,标准输出有一个多通道描述符,其最低位被设置成1,标准输出也称通道0,标准输出是一直开放的,以后对\)fopen的每一次调用都会打开一个新的通道,并返回一个32位的描述符,其中最多可设置到第30位,第31位是保留位
    • 写文件:\(fdisplay、\)fmonitor和$fwrite
      • $fdisplay(<file_descripr or>,p1,p2,...,pn);
      • $fmonitor(<file_descripr or>,p1,p2,...,pn);
      • p1,p2,...,pn可以是变量,信号名或带引号的字符串
    • 关闭文件:$fclose
      • $fclose(<file_descriptor>);
      • 文件一旦被关闭就不能再写入,多通道描述符中的相应位被设置成0,下一次$fopen的调用可以重用这一位

testbench编写如下:

module fir_tp;

// fir_control Parameters
parameter PERIOD = 20 ;
parameter count  = 250;

// fir_control Inputs
reg   sys_clk                              = 0 ;
reg   sys_rst_n                            = 0 ;
reg   [23:0]  s_axis_data_tdata            = 0 ;
reg   s_axis_data_tvalid                   = 0 ;

// fir_control Outputs
wire  m_axis_data_tvalid                   ;
wire  s_axis_data_tready                   ;
wire  [41:0]  fir_out                      ;

reg [23:0] data_test [1023:0];
reg [31:0] cnt;

wire clk_100k;
integer file;
reg [41:0] fir_out_buf = 0;

initial
begin
    #10 sys_rst_n = 1;
    s_axis_data_tdata = 24'd0;
    cnt <= 32'd0;
    $readmemh("C:/Users/zhaoyuheng/Desktop/ASIC_experiment/input.txt",data_test);
    s_axis_data_tvalid = 1;
    file = $fopen("C:/Users/zhaoyuheng/Desktop/ASIC_experiment/output.txt","w");
    forever #(PERIOD/2)  sys_clk=~sys_clk;
end

fir_control #(
    .count ( count ))
 u_fir_control (
    .sys_clk                 ( sys_clk                    ),
    .sys_rst_n               ( sys_rst_n                  ),
    .s_axis_data_tdata       ( s_axis_data_tdata   [23:0] ),
    .s_axis_data_tvalid      ( s_axis_data_tvalid         ),

    .m_axis_data_tvalid      ( m_axis_data_tvalid         ),
    .s_axis_data_tready      ( s_axis_data_tready         ),
    .fir_out                 ( fir_out             [41:0] ),
    .clk_100k               (clk_100k)
);


always @(posedge clk_100k) begin
    if(cnt == 1024 - 1)
    begin
        $stop;
    end
    else
    begin
        if(s_axis_data_tready == 1 && m_axis_data_tvalid == 1)
        begin
            cnt <= cnt + 1;
            s_axis_data_tdata <= data_test[cnt];
            fir_out_buf <= fir_out;
            $fwrite(file,"%b\n",fir_out_buf);
        end

    end
end


endmodule

在matlab中验证

matlab中读取txt文件:

cstr=textread('C:\Users\zhaoyuheng\Desktop\input.txt','%s');
n=length(cstr);
d = zeros(n,1);
for i=1:n
   d(i)=hex2dec(cstr{i});     % 16进制数转换为10进制数,如果要将2进制数转换为10进制数要用bin2dec函数
end
plot(abs(fft(d)));

load函数只能读取10进制数

利用Verilog实现FIR滤波器

除了直接使用FIR的IP核外,还可以直接用Verilog语言来实现FIR滤波器,这里就用一个实例说明
我使用了正点原子的新起点FPGA开发板以及高速ADDA模块,实现一个简单的电压采集系统,实现输入16kHz与17kHz信号的分离,输出信号直接使用ADI的AD9834,系统如下:

高速ADDA

正点原子的高速ADDA模块的硬件结构图如下:
高速ADDA硬件结构

  • AD9708芯片输出的是一对差分电流信号,经过处理后输出的模拟电压范围是-5V~5V
  • AD9280芯片的输入模拟电压转换范围是0V2V,因此电压输入端从-5V5V衰减到0V~2V之间

AD9708

AD9708是8位的电流输出型DAC,速率可以达到125MSPS,供电电压范围为2.7V-5.5V,其时序图如下:
AD9708时序图
其中,ts是输入建立时间,tH是保持时间

FIR的Verilog实现

posted @ 2022-04-09 13:45  Destiny_zxx  阅读(1353)  评论(0编辑  收藏  举报