Ejemplo de código para filtros FIR / IIR en VHDL?

11

Estoy tratando de comenzar con DSP en mi placa Spartan-3. Hice una placa AC97 con un chip de una placa base antigua, y hasta ahora pude hacer ADC, multiplicar las muestras por un número <1 (disminuir el volumen) y luego DAC.

Ahora me gustaría hacer algunas cosas básicas de DSP, como un filtro de paso bajo, paso alto, etc. Pero estoy realmente confundido acerca de la representación numérica (enteros? Punto fijo? Q0.15? ¿Desbordamiento o saturación?).

Solo quiero un código de ejemplo de un filtro simple real para comenzar. No es de alta eficiencia, rápido, ni nada de eso. Solo el filtro teórico implementado en VHDL.

He estado buscando, pero acabo de encontrar fórmulas teóricas: entiendo que lo que no entiendo es cómo procesar las muestras de audio de 16 bits y 48 kHz firmadas que obtengo del ADC. He estado usando estas bibliotecas: http://www.vhdl.org/fphdl/ . Si multiplico mis muestras por 0.5, 0.25, etc., puedo escuchar la diferencia. Pero un filtro más grande me da solo ruido.

Gracias.

hjf
fuente
2
Si bien estoy dispuesto a usar lo que tenga a mano para aprender cosas, me gustaría señalar que hacer filtros de audio en un FPGA no es una forma muy eficiente o rentable de hacerlo. Entonces, si haces un proyecto real, te recomendaría usar un DSP de bajo costo. Excepciones: cuando está haciendo un número impío de canales de audio al mismo tiempo, o está haciendo FIR con un número absurdo de toques.

Respuestas:

8

Parece que primero necesita descubrir los aspectos de DSP y luego realizar una implementación en FPGA.

  • Clasifique el DSP en C, Matlab, Excel o en cualquier otro lugar
  • Intenta y piensa cómo vas a transferir lo que has aprendido de eso a FPGA-land
  • Descubra que ha asumido que la implementación no funciona bien (como el uso de punto flotante, por ejemplo)
  • Regrese y actualice sus cosas DSP fuera de línea para tener esto en cuenta.
  • Iterar n veces :)

En cuanto a los tipos de datos, puede usar enteros muy bien.

Aquí hay un código de muestra para que pueda comenzar. Tenga en cuenta que le faltan muchos problemas del mundo real (por ejemplo, reinicio, gestión de desbordamiento), pero con suerte es instructivo:

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;
Martin Thompson
fuente
Gracias por tu respuesta. Eso es más o menos lo que hice, pero tengo algunos problemas con la representación de números. Mi ADC me da valores en el -32k a + 32k (firmado de 16 bits). También tengo el problema de la constante del filtro: ¿cómo lo represento? ¿Y el resultado de multiplicar entre la muestra y la constante? Eso es lo que más me confunde.
hjf
@hjf: todo son enteros. Mientras todo se mantenga dentro de los 32 bits, estás bien. SI necesita más ancho que ese, puede usar vectores SIN FIRMAR o FIRMADOS tan anchos como desee. O utilice los tipos de punto fijo de VHDL2008 (ver aquí: vhdl.org/fphdl )
Martin Thompson
5

El filtro FIR de paso bajo más simple que puede probar es y (n) = x (n) + x (n-1). Puede implementar esto con bastante facilidad en VHDL. A continuación se muestra un diagrama de bloques muy simple del hardware que desea implementar.

Diagrama de bloques para un filtro de paso bajo simple

De acuerdo con la fórmula, necesita las muestras ADC actuales y anteriores para obtener la salida adecuada. Lo que debe hacer es bloquear las muestras de ADC entrantes en el flanco descendente del reloj y realizar los cálculos adecuados en el flanco ascendente para obtener la salida adecuada. Como está agregando dos valores de 16 bits juntos, es posible que termine con una respuesta de 17 bits. Debe almacenar la entrada en registros de 17 bits y usar un sumador de 17 bits. Su salida, sin embargo, serán los 16 bits más bajos de la respuesta. El código puede verse así, pero no puedo garantizar que funcione por completo, ya que no lo he probado, y mucho menos sintetizado.

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

Como puede ver, puede usar esta idea general para agregar fórmulas más complicadas, como las que tienen coeficientes. Las fórmulas más complicadas, como los filtros IIR, pueden requerir el uso de variables para obtener la lógica del algoritmo correcta. Finalmente, una manera fácil de sortear los filtros que tienen números reales como coeficientes es encontrar un factor de escala para que todos los números terminen tan cerca de los números enteros como sea posible. Su resultado final deberá reducirse por el mismo factor para obtener el resultado correcto.

Espero que esto pueda serle útil y ayudarlo a poner en marcha la pelota.

* Esto se ha editado para que el enganche de datos y el enganche de salida se encuentren en procesos separados. También se utilizan tipos con signo en lugar de std_logic_vector. Supongo que su entrada de ADC será una señal std_logic_vector.

dhsieh2
fuente
2
Los procesos que desencadenan los dos bordes (como usted ha descrito) son muy poco probable que sintetizan
Martin Thompson
@ Martin Asumo que sabes mucho más sobre FPGA que yo, pero he bloqueado los datos entrantes en el flanco descendente y la salida bloqueada en el flanco ascendente para una tarea de clase, así que pensé que esto habría funcionado. ¿Puedes explicar por qué tales procesos no funcionan?
dhsieh2
3
Funcionará bien en un simulador. Sin embargo, los sintetizadores se ahogarán (según mi experiencia) ya que las chanclas en el dispositivo solo pueden cronometrar en un borde.
Martin Thompson
@ dhsieh2 Gracias, este es el tipo de respuesta que estaba buscando. Otra pregunta, ¿cómo lo haría si estuviera usando números firmados (mi ADC me da valores en -32k a + 32k).
hjf
44
@Martin I reloj cosas de ambos bordes del reloj todo el tiempo en Xilinx FPGA, no hay problema. Simplemente no puede registrar el mismo FF en ambos bordes. Cuando observa la salida del analizador de temporización, en realidad deja muy claro que está haciendo bordes opuestos y ajusta el presupuesto de temporización en consecuencia.
5

Otro fragmento de código simple (solo las tripas). Tenga en cuenta que no escribí el VHDL directamente, usé MyHDL para generar el 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;

circuito sintetizado

Esta es una implementación directa. Requerirá multiplicadores. La síntesis de este circuito, dirigido a un Altera Cyclone III, no utilizó ningún multiplicador explícito, pero requirió 350 elementos lógicos.

Este es un filtro FIR pequeño y tendrá la siguiente respuesta (no tan bueno) pero debería ser útil como ejemplo.

respuesta de filtro

Además, tengo un par de ejemplos, aquí y aquí , que pueden ser útiles.

Además, su pregunta parece preguntar: "¿qué es la representación apropiada de punto fijo?" Con frecuencia al implementar funciones DSP, se utiliza la representación de punto fijo, ya que simplifica el análisis de los filtros. Como se mencionó, el punto fijo es un entero artimético. La implementación real es simplemente trabajar con números enteros, pero nuestra representación recibida es fraccional.
Por lo general, surgen problemas al convertir de entero de implementación (punto fijo) a / de punto flotante de diseño.

No sé qué tan bien se admiten los tipos de punto fijo y de punto flotante VHDL. Funcionarán bien en la simulación, pero no sé si sintetizarán con la mayoría de las herramientas de síntesis. Creé una pregunta separada para esto.

Christopher Felton
fuente
3

OpenCores tiene varios ejemplos de DSP, IIR y FIR, incluido BiQuad. Tendrás que registrarte para descargar los archivos.

editar
Entiendo el comentario de Kortuk sobre enlaces muertos, y de hecho, si el enlace a OpenCores muere, la respuesta será inútil. Estoy bastante seguro de que esto no sucederá; mi enlace es genérico, y solo morirá si desaparece el dominio completo de OpenCores.
Traté de buscar algunos ejemplos que pudiera usar para esta respuesta, pero son demasiado largos para ser representados aquí. Así que seguiré mi consejo de registrarme en el sitio usted mismo (tuve que mudarme a Nueva York, porque mi ciudad natal no fue aceptada) y echar un vistazo al código presentado allí.

stevenvh
fuente
Como con todas las cosas, los enlaces se rompen. Hemos discutido antes que un enlace por sí solo no responde. ¿Puede traer algo de lo que hay y hacer una respuesta carnosa que tenga ese enlace como referencia para obtener más información?
Kortuk
@Kortuk: quería hacer esto ayer. Me inscribí ayer con opencores para obtener algunos detalles, pero necesitan unos días para pensar si me tendrán
stevenvh
Me alegro de escucharlo, honestamente me preguntaba si algo se había interpuesto en tu camino. Esperamos escuchar más al respecto.
Kortuk
1

He intentado implementar scripts para la implementación automática de filtros IIR, donde puede definir si el diseño debe ser lo más rápido posible (para que cada multiplicación se realice con un multiplicador dedicado) o lo más pequeño posible (para que cada multiplicador se reutilice).

Las fuentes se han publicado en alt.sources como "Implementación conductual pero sintetizable de filtros IIR en VHDL" (también puede encontrarla en el archivo de Google: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ? dmode = fuente )

Las publicaciones en alt.sources están en formato "shar", por lo que debe guardar el mensaje como texto y descomprimirlo (con la utilidad "unshar") para obtener las fuentes.

WZab
fuente
0

¿Qué tal esto? https://github.com/MauererM/VIIRF

Implementa un filtro IIR basado en biquad (SOS, secciones de segundo orden) que se encarga de la implementación de punto fijo. También presenta scripts Python para el diseño y verificación del filtro. No utiliza construcciones FPGA específicas del proveedor y puede elegir el equilibrio entre el uso de alta velocidad y bajo área.

Tuercas
fuente