【FPGA学习】FPGA实现LMS自适应滤波算法学习


本篇博客源于b站上的一个视频,视频里up主不仅讲述了FPGA上实现LMS算法,还讲述了在实现一个算法时的整体的思路,很值得学习,想了解这种思路的可以去看一下视频,这里主要将算法与算法实现过程总结为笔记,视频链接如下:
verilog实现LMS自适应滤波算法(FPGA)
另外视频中用到的文献如下:

刘雄飞,高金定,齐海兵. LMS自适应滤波器FPGA实现的新方法[J]. 压电与声光,2007,29(1):87-89. DOI:10.3969/j.issn.1004-2474.2007.01.029.

LMS算法

LMS:Least Mean Square,最小均方
自适应滤波器的模型如下图所示:

输入信号矢量:$ \pmb{x}(n)=[x(n) x(n-1) ... x(n-L)]^T $

输出y为:$ y(n)=\sum_{k=0}^{L}w_k(n)x(n-k) $

权系数可以组成权系数矢量:$ \pmb{w}(n)=[w_0(n) w_1(n) ... w_L(n)]^T $

因此输出可以表示为:$ y(n)= \pmb{w}^T (n)\pmb{x}(n) $

误差信号:$ e(n)=d(n)-\pmb{w}^T (n)\pmb{x}(n) $

定义均方误差:$ J(n)=E[ e^2 (n)]=E[ d^2 (n)]+\pmb{w}^T (n) E[ \pmb{x}(n)\pmb{x}^T(n) ]\pmb{w}(n)-2E[ d(n)\pmb{x}^T(n) ]\pmb{w}(n) $

因此定义输入信号的自相关矩阵为:$ \pmb{R}=E[ \pmb{x}(n)\pmb{x}^T(n) ] $

定义期望信号与输入信号的互相关矩阵为:$ \pmb{P}=E[ d(n)\pmb{x}^T(n) ] $

因此均方误差可表示为:$ J(n)=E[ d^2 (n)]+\pmb{w}^T \pmb{R}\pmb{w}-2\pmb{P}^T \pmb{w} $

梯度:$ \nabla=\frac{\partial J}{\partial w}=2\pmb{R}\pmb{w}-2\pmb{P} $

令梯度等于零,即可求得最小均方误差对应的维纳解:$ \pmb{w}^* =\pmb{R}^{-1} \pmb{P} $

LMS算法直接利用瞬态均方误差对瞬时抽头向量求梯度:

因此得到了LMS算法的迭代式:
$ y(n)= \pmb{w}^T (n)\pmb{x}(n) $

$ e(n)=d(n)-\pmb{w}^T (n)\pmb{x}(n) $

$ \pmb{w}(n+1)=\pmb{w}(n)+2\mu e(n)\pmb{x}(n) $
其中μ为迭代步长

而在FPGA实现时,可以直接使用FIR结构,框图如下所示:

另外还可以在matlab进行一下简单仿真,看一下LMS算法的效果:

close all;clear all;clc
N=2000;
n=8;
UU=randn(n,N);
VV=randn(1,N)*0.1+1;
wo=randn(n,1);
DD=wo'*UU+VV;

%% LMS
mu=0.01;
w_lms=randn(n,1);
for ii=1:N
   Err_LMS(ii)=norm(wo-w_lms);
   di=DD(ii);
   ui=UU(:,ii);
   ei=di-w_lms'*ui;
   w_lms=w_lms+2*mu*ei*ui;
   
end
plot(Err_LMS);

Verilog实现

顶层模块:

module FPGA_LMS(
    input sys_clk,
    input sys_rst_n,
    input [15:0] x_in,
    input [15:0] d_in,
    output [15:0] y_out,
    output [15:0] e_out
    );

    wire [127:0] coeff;

    assign e_out = d_in - y_out;

    filter filter1(
        .sys_clk(sys_clk),
        .sys_rst_n(sys_rst_n),
        .x_in(x_in),
        .coeff(coeff),
        .y_out(y_out)

    );

    coeff coeff1(
        .sys_clk(sys_clk),
        .sys_rst_n(sys_rst_n),
        .x_in(x_in),
        .e_in(e_out),
        .w_out(coeff)
    );


endmodule

输出更新模块:

module filter(
    input sys_clk,
    input sys_rst_n,
    input [15:0] x_in,
    input [127:0] coeff,
    output [15:0] y_out
    );

    // y(k)=w(k)*x(k)
    reg [127:0] x_in_r;
    wire [34:0] m0,m1,m2,m3,m4,m5,m6,m7;

    assign m0 = x_in_r[15:0] * coeff[15:0];
    assign m1 = x_in_r[31:16] * coeff[31:16] + m0;
    assign m2 = x_in_r[47:32] * coeff[47:32] + m1;
    assign m3 = x_in_r[63:48] * coeff[63:48] + m2;
    assign m4 = x_in_r[79:64] * coeff[79:64] + m3;
    assign m5 = x_in_r[95:80] * coeff[95:80] + m4;
    assign m6 = x_in_r[111:96] * coeff[111:96] + m5;
    assign m7 = x_in_r[127:112] * coeff[127:112] + m6;
    assign y_out = m7[15:0];

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(!sys_rst_n)
        begin
            x_in_r <= 128'd0;
        end
        else
        begin
            x_in_r <= {x_in_r[111:0],x_in};
        end
    end


endmodule

系数更新模块:

module coeff(
    input sys_clk,
    input sys_rst_n,
    input [15:0] x_in,
    input [15:0] e_in,
    output reg [127:0] w_out
    );
    // w(k+1)=w(k)+μ*e(k)*x(k)

    reg [127:0] x_in_r;
    wire [47:0] m0,m1,m2,m3,m4,m5,m6,m7;

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(!sys_rst_n)
        begin
            x_in_r <= 128'd0;
        end
        else
        begin
            x_in_r <= {x_in_r[111:0],x_in};
        end
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(!sys_rst_n)
        begin
            w_out <= 128'd0;
        end
        else
        begin
            w_out[15:0] <= m0[15:0];
            w_out[31:16] <= m1[15:0];
            w_out[47:32] <= m2[15:0];
            w_out[63:48] <= m3[15:0];
            w_out[79:64] <= m4[15:0];
            w_out[95:80] <= m5[15:0];
            w_out[111:96] <= m6[15:0];
            w_out[127:112] <= m7[15:0];

        end

    end

assign m0 = x_in_r[15:0] * e_in + {{32{w_out[15]}},w_out[15:0]};
assign m1 = x_in_r[31:16] * e_in + {{32{w_out[31]}},w_out[31:16]};
assign m2 = x_in_r[47:32] * e_in + {{32{w_out[32]}},w_out[47:32]};
assign m3 = x_in_r[63:48] * e_in + {{32{w_out[63]}},w_out[63:48]};
assign m4 = x_in_r[79:64] * e_in + {{32{w_out[79]}},w_out[79:64]};
assign m5 = x_in_r[95:80] * e_in + {{32{w_out[95]}},w_out[95:80]};
assign m6 = x_in_r[111:96] * e_in + {{32{w_out[111]}},w_out[111:96]};
assign m7 = x_in_r[127:112] * e_in + {{32{w_out[127]}},w_out[127:112]};

endmodule

在Verilog代码里的乘法可以由别的方法实现,比如LUT、IP核、原语等
接下来就要验证代码是否能够实现功能,视频里采用的是正弦波去噪的案例,不过由于UP主没有提供测试的波形文件,因此这里我们自己制作一下,使用matlab即可: