VHDL 中 FIR/IIR 滤波器的代码示例?

电器工程 FPGA 视频文件 数字信号处理器 伊尔 冷杉
2022-01-20 10:09:34

我正在尝试在我的 Spartan-3 板上开始使用 DSP。我用旧主板上的芯片制作了一个 AC97 板,到目前为止,我让它做 ADC,将样本乘以 <1(减小音量),然后是 DAC。

现在我想做一些基本的 DSP 东西,比如低通滤波器、高通等。但我真的对数字表示感到困惑(整数?定点?Q0.15?溢出还是饱和?)。

我只想要一些实际简单过滤器的示例代码来帮助我入门。没有高效、快速之类的东西。只是用 VHDL 实现的理论滤波器。

我一直在寻找,但我只是找到了理论公式——我明白了,我不明白如何处理我从 ADC 获得的带符号的 16 位、48KHz 音频样本。我一直在使用这些库:http ://www.vhdl.org/fphdl/ 。如果我将样本乘以 0.5、0.25 等,我可以听到差异。但是更大的过滤器只会给我带来噪音。

谢谢。

4个回答

听起来您需要先弄清楚 DSP 方面,然后在 FPGA 中实现。

  • 在 C、Matlab、Excel 或其他任何地方整理 DSP
  • 尝试思考如何将从中学到的知识转移到 FPGA 领域
  • 发现您对无法正常工作的实现做出了一些假设(例如使用浮点)
  • 返回并更新您的离线 DSP 资料以考虑到这一点。
  • 迭代n次:)

关于数据类型,您可以很好地使用整数。

这里有一些示例代码可以帮助您。请注意,它缺少很多现实世界的问题(例如重置、溢出管理)——但希望它具有启发性:

library ieee;
use ieee.std_logic_1164.all;
entity simple_fir is
    generic (taps : integer_vector); 
    port (
        clk      : in  std_logic;
        sample   : in  integer;
        filtered : out integer := 0);
end entity simple_fir;
----------------------------------------------------------------------------------------------------------------------------------
architecture a1 of simple_fir is
begin  -- architecture a1
    process (clk) is
        variable delay_line : integer_vector(0 to taps'length-1) := (others => 0);
        variable sum : integer;
    begin  -- process
        if rising_edge(clk) then  -- rising clock edge
            delay_line := sample & delay_line(0 to taps'length-2);
            sum := 0;
            for i in 0 to taps'length-1 loop
                sum := sum + delay_line(i)*taps(taps'high-i);
            end loop;
            filtered <= sum;
        end if;
    end process;
end architecture a1;
----------------------------------------------------------------------------------------------------------------------------------
-- testbench
----------------------------------------------------------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
entity tb_simple_fir is
end entity tb_simple_fir;
architecture test of tb_simple_fir is
    -- component generics
    constant lp_taps : integer_vector := ( 1, 1, 1, 1, 1);
    constant hp_taps : integer_vector := (-1, 0, 1);

    constant samples : integer_vector := (0,0,0,0,1,1,1,1,1);

    signal sample   : integer;
    signal filtered : integer;
    signal Clk : std_logic := '1';
    signal finished : std_logic;
begin  -- architecture test
    DUT: entity work.simple_fir
        generic map (taps => lp_taps)  -- try other taps in here
        port map (
            clk      => clk,
            sample   => sample,
            filtered => filtered);

    -- waveform generation
    WaveGen_Proc: process
    begin
        finished <= '0';
        for i in samples'range loop
            sample <= samples(i);
            wait until rising_edge(clk);
        end loop;
        -- allow pipeline to empty - input will stay constant
        for i in 0 to 5 loop
            wait until rising_edge(clk);
        end loop;
        finished <= '1';
        report (time'image(now) & " Finished");
        wait;
    end process WaveGen_Proc;

    -- clock generation
    Clk <= not Clk after 10 ns when finished /= '1' else '0';
end architecture test;

另一个简单的代码片段(只是胆量)。注意我没有直接编写 VHDL,我使用 MyHDL 生成 VHDL。

-- VHDL code snip
architecture MyHDL of sflt is

type t_array_taps is array(0 to 6-1) of signed (15 downto 0);
signal taps: t_array_taps;

begin

SFLT_RTL_FILTER: process (clk) is
    variable sum: integer;
begin
    if rising_edge(clk) then
        sum := to_integer(x * 5580);
        sum := to_integer(sum + (taps(0) * 5750));
        sum := to_integer(sum + (taps(1) * 6936));
        sum := to_integer(sum + (taps(2) * 6936));
        sum := to_integer(sum + (taps(3) * 5750));
        sum := to_integer(sum + (taps(4) * 5580));
        taps(0) <= x;
        for ii in 1 to 5-1 loop
            taps(ii) <= taps((ii - 1));
        end loop;
        y <= to_signed(sum, 16);
    end if;
end process SFLT_RTL_FILTER;

end architecture MyHDL;

合成电路

这是一个直接的实现。这将需要乘数。该电路的综合针对 Altera Cyclone III,没有使用任何显式乘法器,但需要 350 个逻辑元件。

这是一个小型 FIR 滤波器,将具有以下响应(不是很好),但作为示例应该很有用。

过滤器响应

另外我有几个例子,herehere,可能有用。

此外,您的问题似乎在问:“什么是合适的定点表示?” 在实现 DSP 功能时,经常使用定点表示,因为它简化了对滤波器的分析。如前所述,定点只是整数算法。实际的实现只是简单地使用整数,但我们收到的表示是小数。
从实现整数(定点)到/从设计浮点转换时通常会出现问题。

我不知道 VHDL 定点和浮点类型的支持情况如何。它们在模拟中可以正常工作,但我不知道它们是否会与大多数合成工具进行合成。我为此创建了一个单独的 问题

您可以尝试的最简单的低通 FIR 滤波器是 y(n) = x(n) + x(n-1)。您可以在 VHDL 中很容易地实现这一点。下面是您要实现的硬件的非常简单的框图。

简单低通滤波器的框图

根据公式,您需要当前和以前的 ADC 样本才能获得适当的输出。您应该做的是在时钟的下降沿锁存输入的 ADC 样本,并在上升沿执行适当的计算以获得适当的输出。由于您将两个 16 位值相加,因此您最终可能会得到一个 17 位的答案。您应该将输入存储到 17 位寄存器中并使用 17 位加法器。但是,您的输出将是答案的低 16 位。代码可能看起来像这样,但我不能保证它会完全工作,因为我没有测试它,更不用说合成它了。

IEEE.numeric_std.all;
...
    signal x_prev, x_curr, y_n: signed(16 downto 0);
    signal filter_out: std_logic_vector(15 downto 0);
...
process (clk) is
begin
    if falling_edge(clk) then
        --Latch Data
        x_prev <= x_curr;
        x_curr <= signed('0' & ADC_output); --since ADC is 16 bits
    end if;
end process;

process (clk) is
begin
    if rising_edge(clk) then
        --Calculate y(n)
        y_n <= x_curr + x_prev;
    end if;
end process;

filter_out <= std_logic_vector(y_n(15 downto 0));  --only use the lower 16 bits of answer

如您所见,您可以使用这个一般思想来添加更复杂的公式,例如带有系数的公式。更复杂的公式,如 IIR 滤波器,可能需要使用变量来使算法逻辑正确。最后,绕过将实数作为系数的滤波器的一种简单方法是找到一个比例因子,以便所有数字最终都尽可能接近整数。您的最终结果必须按相同的因子按比例缩小才能获得正确的结果。

我希望这对您有用,并帮助您开始工作。

*这已被编辑,以便数据锁存和输出锁存在不同的过程中。还使用有符号类型而不是 std_logic_vector。我假设您的 ADC 输入将是 std_logic_vector 信号。

OpenCores有许多 DSP 示例,IIR 和 FIR,包括 BiQuad。您必须注册才能下载文件。

编辑
我理解 Kortuk 对死链接的评论,事实上,如果到 OpenCores 的链接失效,答案将变得毫无用处。我非常有信心这不会发生;我的链接是通用链接,只有在完整的 OpenCores 域消失时才会消失。
我试图寻找一些我可以用于这个答案的例子,但它们都太长了,无法在这里展示。所以我会坚持我的建议,自己注册该网站(我不得不搬到纽约,因为我的家乡不被接受)并查看那里提供的代码。