Error al implementar el filtro IIR en FPGA

Quiero implementar varios filtros IIR en un FPGA, usando VHDL. Los filtros son para audio. Comienzo implementando un solo filtro con la siguiente función de transferencia:

H 1 ( z ) = 304 304 z 2 16384 32109 z 1 + 16076 z 2

Esta función de transferencia debe tener la siguiente respuesta de frecuencia:

ingrese la descripción de la imagen aquí

Y debería ser posible implementar con la siguiente ecuación en diferencias:

16384 y [ norte ] = 304 X [ norte ] 304 X [ norte 2 ] + 32109 y [ norte 1 ] 16076 y [ norte 2 ]

He intentado implementar el filtro con las dos siguientes piezas de código:

library ieee;
use ieee.std_logic_1164.all;
use IEEE.numeric_std.all;

entity FILTER is
    port (
    GPIO                    : inout std_logic_vector(35 downto 0); --I/O
    CLK_50                  : inout std_logic; --50 MHz clock
    CLK                     : in  std_logic;   --3.072 MHz clock
    CHANNEL                 : in std_logic;    --Channel select 48 kHz
    data_left_in            : in std_logic_vector(15 downto 0);
    data_right_in           : in std_logic_vector(15 downto 0);
    data_left_out           : out std_logic_vector(15 downto 0);
    data_right_out          : out std_logic_vector(15 downto 0)
    );  
end FILTER;

architecture behave of FILTER is
    signal input_left       : signed (15 downto 0);
    signal input_right      : signed (15 downto 0);
    signal output_left      : signed (15 downto 0);
    signal output_right     : signed (15 downto 0);

    signal i_0_left : signed (15 downto 0);
    signal i_0_right : signed (15 downto 0);

    COMPONENT filter_class
    port (
    GPIO                    : inout std_logic_vector(35 downto 0); --I/O
    CLK_50                  : inout std_logic;   --50 MHz clock
    CLK                     : in  std_logic;     --Channel select 48 kHz
    sample                  : in signed (15 downto 0); --filter input
    sample_filtered         : inout signed (15 downto 0); --filter output

    b00                     : in integer range -32768 to 32767; --filter coefficients
    b01                     : in integer range -32768 to 32767;
    b02                     : in integer range -32768 to 32767;
    a01                     : in integer range -32768 to 32767;
    a02                     : in integer range -32768 to 32767;

    scaling                 : in integer range 0 to 16; --scaling for fixed point

    gain                    : in integer range -32768 to 32767;
    gain_scaling            : in integer range 0 to 15
    );   
    END COMPONENT;

    begin

    Filt_0_r        : filter_class  PORT MAP (GPIO(35 downto 0), CLK_50, NOT CHANNEL, input_right, i_0_right , 16384, 0, 0, 0, 0, 14, 1, 0); --no filtering
    Filt_0_l        : filter_class  PORT MAP (GPIO(35 downto 0), CLK_50, CHANNEL, input_left, i_0_left, 16384, 0, -16384, -32113, 16081, 14, 1, 0); --filter with tf H1(z)

    process (CHANNEL) --send output to DAC
    begin
        if RISING_EDGE(CHANNEL) then
            input_left <= signed(data_left_in);
            data_left_out <= std_logic_vector(i_0_left);
        end if;
        if FALLING_EDGE(CHANNEL) then
            input_right <= signed(data_right_in);
            data_right_out <= std_logic_vector(i_0_right);
        end if;
    end process;
end behave;

y

    library ieee;
use ieee.std_logic_1164.all;
use IEEE.numeric_std.all;

entity filter_class is
    port (
    GPIO                    : inout std_logic_vector(35 downto 0); --I/O
    CLK_50                  : inout std_logic;   --50 MHz clock
    CLK                     : in  std_logic;     --Channel select 48 kHz
    sample                  : in signed (15 downto 0); --filter input
    sample_filtered         : inout signed (15 downto 0); --filter output

    b00                     : in integer range -32768 to 32767; --filter coefficients
    b01                     : in integer range -32768 to 32767;
    b02                     : in integer range -32768 to 32767;
    a01                     : in integer range -32768 to 32767;
    a02                     : in integer range -32768 to 32767;

    scaling                 : in integer range 0 to 16; --scaling for fixed point

    gain                    : in integer range -32768 to 32767;
    gain_scaling            : in integer range 0 to 15
    );     
end filter_class;

architecture behave of filter_class is

    TYPE multipliers IS ARRAY (NATURAL RANGE <>) OF SIGNED (17 DOWNTO 0);
    TYPE result IS ARRAY (NATURAL RANGE <>) OF SIGNED (35 DOWNTO 0);

    signal y00 : signed (15 downto 0);

    signal sum_1 : signed (37 downto 0);

    signal samp : multipliers(0 to 5);
    signal coef : multipliers(0 to 5);
    signal resu : result(0 to 5);

    signal channel_state : std_logic;

begin

    process (CLK_50) --calculate filter 
        variable cnt : integer := 0;
        variable flag : std_logic := '0';
    begin                   
        if RISING_EDGE(CLK_50) then 

            channel_state <= CLK; 

            if channel_state = '0' AND CLK = '1' then --if new sample

                flag := '1';

            elsif flag = '1' then --calculate

                cnt := cnt + 1;

                if cnt = 4 then --save coefficients in array

                    coef(0) <= to_signed(b00,18);
                    coef(1) <= to_signed(b01,18);
                    coef(2) <= to_signed(b02,18);
                    coef(3) <= to_signed(-a01,18);
                    coef(4) <= to_signed(-a02,18);

                    coef(5) <= to_signed(gain,18);

                    samp(5) <= resize(y00, 18);

                end if;

                if cnt = 29 then --reset count if all done
                    cnt := 0;
                    flag := '0';
                elsif cnt > 5 AND cnt < 12 then
                    resu(cnt - 6) <= coef(cnt - 6) * samp(cnt - 6); --multiply coefficients and sample, and gain
                elsif cnt = 12 then
                    sum_1 <= to_signed(0, 38); --reset filter sum
                elsif cnt > 12 then
                    sum_1 <= sum_1 + resu(cnt - 8); --calculate sum
                end if;
            end if;
        end if;
    end process;

    process (CLK)
        variable y00_temp_1 : signed (37 downto 0);
        variable y00_temp_2 : signed (37 downto 0);

        variable sample_filtered_temp_1 : signed (35 downto 0);
    begin
        if RISING_EDGE(CLK) then
            --delay line
            samp(2) <= samp(1); 
            samp(1) <= samp(0);
            samp(0) <= resize(sample, 18);

            samp(4) <= samp(3);
            samp(3) <= resize(y00, 18);
            y00_temp_1 := sum_1; --set output
            y00_temp_2 := shift_right(y00_temp_1, scaling); --divide by 2^14 for scaling
            y00 <= y00_temp_2 (15 downto 0); --filter output

            sample_filtered_temp_1 := shift_right(resu(5), gain_scaling); filter gain scaling
            sample_filtered <= sample_filtered_temp_1 (15 downto 0); --filter output * gain
        end if; 
    end process;
end behave;

Este es mi primer código VHDL y los errores pueden ser muchos.

Con este código el filtro no funciona.

El filtro de canal derecho funciona (solo pasa) y proporciona una salida de 1/1.

El filtro izquierdo no funciona y solo da ruido, independientemente de la señal de entrada. Con una señal de entrada y salida de 1 kHz y una amplitud de 1 V, la señal de salida se puede ver:

ingrese la descripción de la imagen aquí

La forma de onda roja es la salida del filtro derecho, la azul es el filtro izquierdo.

Si usa un factor de escala más bajo, ej. 2^15 o 2^16, la salida se verá diferente. En 2^16, la salida será 0. Debido a esto, sospecho que el problema es algún tipo de truncamiento incorrecto de la señal.

¿Alguien tiene una idea de lo que estoy haciendo mal con el filtro?

Trace unos miles de puntos en Excel y vea qué sucede. Si se ve igual, entonces las ecuaciones podrían considerarse defectuosas.
@Andyaka: acabo de hacer esto, no en Excel, sino en MATLAB, pero supongo que debería ser lo mismo. La forma de la vava está un poco alterada durante los dos primeros períodos, pero después se ve bien. La respuesta de frecuencia también se ve bien.
Sí, eso es bueno, al menos sabes que la fórmula es buena. Has reducido el problema a la mitad.

Respuestas (2)

No estoy siguiendo todo lo que estás haciendo, pero este fragmento de código parece sospechoso:

            if cnt = 29 then --reset count if all done
              -- ...
            elsif cnt > 12 then
                sum_1 <= sum_1 + resu(cnt - 8); --calculate sum
            end if;

Esta rama se ejecuta para cntvalores del 13 al 28, generando índices para la resumatriz de 5 a 20, pero la matriz solo tiene elementos para los índices de 0 a 5.

Me parece que realmente desea que el reinicio ocurra cuando cnt = 19y los índices deberían ser resu(cnt - 13).

Estoy de acuerdo en que parece sospechoso. Creo que podría haber cambiado algo y olvidado esa parte. Sin embargo, lo he cambiado, y el problema sigue ahí.

Yo también tengo problemas para seguir esto, pero una sugerencia sería establecer los coeficientes en algunos valores simples y simular el diseño examinando los resultados matemáticos. Luego avance hasta los coeficientes más complejos. Sin un análisis numérico completo de la simulación, no se puede decir realmente qué está fallando.