使用 Verilog 在 FPGA 上实现低通滤波器

发布于:2025-08-09 ⋅ 阅读:(11) ⋅ 点赞:(0)

在本文中,我们将简要探讨不同类型的滤波器,然后学习如何实现移动平均滤波器并使用 CIC 架构对其进行优化。

滤波在许多设计中都至关重要。它使我们能够提取隐藏在大量噪声之下的有用信号。我们还可以通过滤波特定频率的输出来确定系统的非线性度。

让我们首先讨论一下不同类型的滤波器之间的一些区别。

理论

滤波器类型

根据滤波器的频段类别,滤波器可以分为五类。每种滤波器的功能在其名称中有所体现。例如,低通滤波器是指允许低频输入通过并阻止高频输入的滤波器,等等。

这五种类型是:

  • 低通Low-pass

  • 带通Band-pass

  • 带阻Band-stop

  • 高通High-pass

  • 全通All-pass

滤波器的形状也各有不同。例如,它们的通带上可能有波纹,或者过渡带可能比较平坦等等。

滤波器形状

滤波器通常可按形状分类如下:

  • 贝塞尔Bessel: 与其他相比群延迟最平坦

  • 巴特沃斯滤波器Butterworth: 设计用于在通带中实现最平坦的幅频响应;也称为“最大平坦度”

  • 切比雪夫Chebyshev: 旨在使理想滤波器与实际滤波器之间的误差最小;可分为两类:通带中有纹波的滤波器和阻带中有纹波的滤波器

  • 椭圆形Elliptic:在通带和阻带中都有波纹,但它们在通带和阻带之间具有最快的过渡

选择滤波器的形状取决于所需的规格。例如,我们可能需要输出信号幅度在通带内尽可能精确地跟踪输入信号幅度。在这种情况下,我们应该使用巴特沃斯滤波器,即使它可以提供更多的过渡带。

另一方面,我们可能希望输出信号频率精确跟随输入信号,并具有线性相位响应,因此应该选择贝塞尔滤波器。如果我们需要使用尽可能少的元件,并保持与其他滤波器相同的阶数和过渡速度,椭圆滤波器或切比雪夫滤波器也可以,但通带或阻带中会出现波纹。

模拟和数字滤波器

另一方面,滤波器可以通过两种方式构建:数字和模拟。

在模拟电路中,无源滤波器是由电感器和电容器或电阻器组成的梯形电路。有源模拟滤波器可以是一种利用放大器或谐振器的结构。它们的值可以通过使用已创建的用于设计模拟滤波器的表格或应用程序轻松确定。

数字滤波器可以用两种方法创建:IIR 和 FIR。IIR(无限脉冲响应)滤波器是一种输出取决于输入和先前输出的滤波器。

图 1. IIR 滤波器。图片由Mark Wilde提供(https://en.wikipedia.org/wiki/User:Mwilde)

数字滤波器的另一种实现方式是有限脉冲响应 (FIR)。这类滤波器不使用反馈,其输出仅与当前和先前的输入相关。就稳定性而言,FIR 滤波器始终稳定, 因为它们的输出仅与输入相关。另一方面,它们需要更高的阶数才能满足与 IIR 相同的规格。

图 2. FIR 滤波器 图片由Jonathan Blanchard提供(https://www.jblopen.com/)

移动平均(moving average)

移动平均(moving average)是一种滤波器,它对先前输入的 N 个点进行平均,并根据这些点得出输出。

移动平均滤波器是一个 FIR 滤波器,其 N 个系数为1/N,一些具有不同N的移动平均滤波器的频率响应如图3所示。

图 3.移动平均线的频率响应

移动平均moving average (MA) 滤波器的脉冲响应在 0 到 N 之外的点上为零。

因此,MA 滤波器的频率响应为:

截止频率可估算为:

根据这些公式,截止频率仅与 N 有关。随着 N 的增加,截止频率会降低,但会耗费时间。我们需要等待第 N 个周期才能获得正确的结果,因此 N 越大,所需的时间就越长。随着滤波器的清晰度提高,其输出达到稳定状态所需的时间也会增加。

滤波及其设计实现是 FPGA 设计中一个广泛的课题。设计人员需要学习大量知识才能设计出合适的滤波器,并以最少的资源占用或最快的速度在 FPGA 上实现它。

在本文中,我们将尝试实现一个 N 点MA滤波器。我们假设 N 是一个参数,可以在使用 Xilinx Vivado 等 CAD 工具综合之前进行更改。

如图 2 所示,FIR 滤波器可以通过一个长度为 N(即 FIR 阶数)的延迟链、将系数乘以延迟线的乘法器以及一些将乘法器结果相加的加法器来实现。这种架构需要大量的乘法器和加法器,而这些功能在 FPGA 中受到限制,具体取决于您使用的 FPGA 型号(即使是功能最强大的 FPGA 也存在限制)。

设计 FIR 滤波器需要进行一些研究来减少这些资源,因为在任何 FPGA 设计的阶段,减少资源都是必要的。不过,我们不会讨论这个话题,而是用另一个技巧来设计MA滤波器。在MA滤波器中,所有系数都是1/N。如果我们想要实现像图 2 那样的滤波器,我们应该制作一个抽头延迟线并存储最后 N 个输入,然后将它们乘以1/N,最后将结果相加。不过,我们可以将最后 N 个输入存储在 FIFO 中,然后在每个周期将它们相加,再乘以 1/N。使用这种方法,我们只需要一个 N 乘法器。

代码说明

首先,我们有 N,即输入点的数量,它是一个可调整的参数。我们将这 N 个点相加,得到输出。

我们还假设输入是 28 位格式,并且我们希望输出也采用相同的格式。在处理添加 N 个点时,我们可能会面临位增长的问题。添加两个 28 位点会导致输出为 28 位,并产生一个溢出位。因此,添加 N 个 28 位点时,我们需要 (log2(N) +28) 位输出。

假设所有 N 个点都相同,将它们相加就好比将 N 乘以其中一个点。因此,我们实现了一个“log2”函数,它只需计算输入的对数即可。通过了解 N 的对数,我们可以设置输出长度。需要注意的是,log2 不是一种可综合的方法,并且仅适用于 Xilinx Vivado(即 Xilinx Vivado 计算 log2,然后将结果用于其余的综合)。

“log2”函数如下代码所示:

function integer log2(input integer v); begin log2=0; while(v>>log2) log2=log2+1; end endfunction

现在我们已经设置了输入和输出的长度,我们需要创建一个用于存储 N 个先前和当前输入的 tap 线。以下代码可以实现这个功能:

genvar i;
generate
  for (i = 0; i < N-1 ; i = i + 1) begin: gd
   always @(posedge clock_in) begin
    if(reset==1'b1)
   begin
    data[i+1]<=0;
   end
   else
   begin
    data[i+1] <= data[i];
   end
   end
    end
endgenerate

最后,我们需要一个加法器来对 FIFO 中存储的所有数据进行求和。这个阶段有点棘手。如果我们想在每个时钟周期都有输出,就需要制作一个组合电路,逐步将 FIFO 中的数据相加。下面的代码将实现这一点:

genvar c;
generate
  assign summation_steps[0] = data[0] + data[1];
  for (c = 0; c < N-2 ; c = c + 1) begin: gdz
  assign summation_steps[c+1] = summation_steps[c] + data[c+2];
    end
endgenerate

然而,我们的目标 FPGA 没有这么多资源(假设),在这个 FPGA 上综合这个模块是不可行的。因此,把问题简化了一些。假设我们希望输出每 N 个时钟周期更新一次。有了这个技巧,我们不再需要存储所有接收到的数据。我们可以简单地存储总和,并在每个周期将其添加到当前输入。以下代码可以实现这个技巧:

always@(posedge clock_in)
begin
    if(reset)
    begin
        signal_out_tmp<=0;
        count<=0;
        signal_out<=0;
    end
    else
    begin
        if(down_sample_clk==N_down_sample)
        begin
            if(count             begin
                count<=count+1'b1;
                signal_out_tmp<=signal_out_tmp+signal_in;
            end
            else
            begin
                count<=0;
                signal_out<=signal_out_tmp[27+N2:N2];
                signal_out_tmp<=0;
            end
        end
    end
end

在此代码中,总和保存为 signal_out_tmp,并将在每个周期添加到输入。在 N 个点之后,输出将变为 signal_out_tmp,并且此变量将设置为零,并再次开始存储和。

这种方法使用的资源非常少,但其输出每 N 个周期就会更新一次。

仿真

测试平台包含在项目代码中,文后可以下载。在测试平台中,将输入视为一个步骤,并保存了输出。在测试平台中读写操作非常简单,如下面的代码所示。我们可以在测试平台中使用 fopen 函数打开一个文件,然后使用 fwrite 函数写入文件。

f = $fopen("output.txt","w");  
f2 = $fopen("time.txt","w"); 
$fwrite(f,"%d %d\n",signal_in,signal_out);
$fwrite(f2,"%d\n",cur_time);

fwrite 中的格式化功能与 C 语言中简单的 printf 函数非常相似。我们还将在测试平台中使用  变量。使用 time 变量可以获取当前时间,并将其写入文本文件。在对项目进行仿真后,我们可以在 MATLAB 中使用写入的文件来确保其正确性。用 MATLAB 编写的代码首先读取文件并绘制图表。

A = importdata('D:\low_test\output.txt');
B = importdata('D:\low_test\time.txt');
M2=A(:,2);
M1=A(:,1);
T=B(:,1)*10e-9;
 
M1=M1/(2^24);
M2=M2/(2^24);
plot(M1);
hold on;
plot(M2);
 
s=size(M1);
val=0;
t=0:s(1,1)-1;
t=t*50e-9;
for i=405:s(1,1)
    if(abs(M1(i,1)-M2(i,1))<1*.1)
        val=i;
        break;
    end
end
stepp=stepinfo(M2,t);
pp=stepp.RiseTime;
fc=.35/pp
cycles=val-405
time=((cycles)*50)/1000

为了测试目的,我们首先用阶跃输入仿真我们的工作台,然后将输入改为正弦输入。结果如图 6 和图 7 所示。

图 6.阶跃响应图 7. Sin(x)*sin(x) 响应

如图 6 所示,0.2 ms后,滤波器输出变得与输入幅度一样高。图 6 中每 N 个周期都会有一次响应,因为输出并非平滑变化。相反,它在第 N 个周期后才发生变化。

在图 7 中,因为输入是 6*sin(x)*sin(x),我们知道该输入的直流偏移是 3,因为我们的低通滤波器输出是 3。

CIC滤波器

级联积分器梳状滤波器(The cascaded integrator-comb filter)是一种硬件高效的 FIR 数字滤波器。

CIC 滤波器由相同级数的理想积分滤波器和抽取滤波器组成。CIC 滤波器架构如图 8 所示。

图 8. CIC 滤波器图像。图片来自Wikimedia Commons(https://commons.wikimedia.org/wiki/File:CIC_interpolator.svg)

我们可以使用 CIC 滤波器并重写MA方程来优化MA低通滤波器,如下所示:

该架构由一个梳状部分 (c[n]=x[n]-x[nN]) 和一个积分器 (y[n]=y[n-1]+c[n]) 组成,因此我们可以在这里使用 CIC 架构。在该架构中,我们将加法器精简为仅三个部分,以便每个周期都能获得输出,这正是 CIC 滤波器的神奇之处。

在文后可供下载的代码中,使用 CIC 滤波器拓扑优化了MA。我们可以使用以下 Verilog 代码在硬件中实现上述公式:

wire signed [27+N2:0]  signal_out_tmp_2=signal_out_tmp_3+signal_in-data[N-1];

图 9 显示了具有 sin(x)*sin(x) 输入的新结构的输出。

图 9. CIC 输出

结论

数字和模拟电路都可用于滤波。每种方法各有优势,但数字滤波具有可重新编程的特性,并且实现面积更小。在本文中,我们首先研究了滤波器的构建方法,然后以最简单的方式实现了一个MA滤波器。最后,我们使用 CIC 架构对其进行了优化。

代码

https://github.com/suisuisi/FPGATechnologyGroup/blob/main/files-low-pass.zip


网站公告

今日签到

点亮在社区的每一天
去签到