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 conseguí 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. Nada de alta eficiencia, rápido, ni nada por el estilo. Solo el filtro teórico implementado en VHDL.
He estado buscando, pero solo encuentro fórmulas teóricas. Lo entiendo, lo que no entiendo es cómo procesar las muestras de audio firmadas de 16 bits y 48 KHz 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 solo me da ruido.
Gracias.
Parece que primero necesita descubrir los aspectos de DSP y luego realizar una implementación en FPGA.
Con respecto a los tipos de datos, puede usar números enteros muy bien.
Aquí hay un código de muestra para que pueda comenzar. Tenga en cuenta que faltan muchos problemas del mundo real (por ejemplo, reinicio, gestión de desbordamiento), pero espero que sea 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;
Otro fragmento de código simple (solo las agallas). 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;
Esta es una implementación directa. Requerirá multiplicadores. La síntesis de este circuito, destinado a un Altera Cyclone III, no usó 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 buena), pero debería ser útil como ejemplo.
Además, tengo un par de ejemplos, aquí y aquí , que pueden ser útiles.
Además, su pregunta parece preguntar: "¿cuál es la representación de punto fijo apropiada?" Con frecuencia, cuando se implementan funciones DSP, se utiliza la representación de punto fijo, porque simplifica el análisis de los filtros. Como se mencionó, el punto fijo es solo aritmética entera. La implementación real simplemente funciona con números enteros, pero nuestra representación percibida es fraccionaria.
Por lo general, surgen problemas cuando se convierte de entero de implementación (punto fijo) a/desde punto flotante de diseño.
No sé qué tan bien se admiten los tipos de punto fijo y punto flotante VHDL. Funcionarán bien en simulación, pero no sé si sintetizarán con la mayoría de las herramientas de síntesis. Creé una pregunta separada para esto.
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.
De acuerdo con la fórmula, necesita las muestras de 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 apropiados en el flanco ascendente para obtener la salida adecuada. Dado que está sumando dos valores de 16 bits, 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 inferiores de la respuesta. El código puede verse así, pero no puedo garantizar que funcione completamente 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 que la lógica del algoritmo sea 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 siendo lo más cercanos posible a los números enteros. Su resultado final deberá reducirse por el mismo factor para obtener el resultado correcto.
Espero que esto te pueda ser útil y te ayude a poner la pelota en marcha.
*Esto se ha editado para que el enganche de datos y el enganche de salida estén en procesos separados. También usando tipos firmados en lugar de std_logic_vector. Supongo que su entrada ADC será una señal std_logic_vector.
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 los enlaces muertos y, de hecho, si el enlace a OpenCores muere, la respuesta se volverá inútil. Estoy bastante seguro de que esto no sucederá; mi enlace es genérico, y solo morirá si el dominio completo de OpenCores desapareciera.
Traté de buscar algunos ejemplos que pudiera usar para esta respuesta, pero son demasiado largos para representarlos aquí. Así que seguiré mi consejo de registrarte en el sitio tú mismo (tuve que mudarme a Nueva York porque mi ciudad natal no fue aceptada) y echar un vistazo al código que se presenta allí.
He intentado implementar scripts para la implementación automática de filtros IIR, donde se 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 se reutilice cada multiplicador).
Las fuentes se han publicado en alt.sources como "Implementación conductual pero sintetizable de filtros IIR en VHDL" (también puede encontrarlo en el archivo de Google: https://groups.google.com/group/alt.sources/msg/c8cf038b9b8ceeec ?dmodo=fuente )
Las publicaciones en alt.sources están en formato "compartir", por lo que debe guardar el mensaje como texto y dejar de compartirlo (con la utilidad "descompartir") para obtener las fuentes.
¿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 cuenta con scripts de Python para el diseño y verificación del filtro. No utiliza construcciones de FPGA específicas del proveedor y puede elegir el equilibrio entre el uso de área de alta velocidad y baja.
usuario3624