¿Es posible crear un filtro IIR en un FPGA que esté sincronizado en la frecuencia de muestreo?

8

Esta pregunta es sobre la implementación de un filtro IIR en un FPGA con segmentos DSP, con criterios muy específicos.

Digamos que estás haciendo un filtro sin toques hacia adelante y solo 1 toque hacia atrás, con esta ecuación:

$$ y [n] = y [n-1] \ cdot b1 + x [n] $$

(ver imagen)

Tome como ejemplo el segmento DSP48A1 de Xilinx; la mayoría de los segmentos DSP IP duros son similares.

Digamos que tiene datos analógicos entrantes a 1 muestra por reloj. Me gustaría diseñar un filtro IIR que se ejecute de forma síncrona en el reloj de muestra.

El problema es que para ejecutar el segmento DSP a la tasa máxima, no puede multiplicar Y agregar en el mismo ciclo. Debe tener un registro de canalización entre estos componentes.

Por lo tanto, si tiene 1 muestra nueva en cada reloj, deberá producir 1 salida por reloj. Sin embargo, necesita la salida anterior 2 relojes antes de poder producir uno nuevo en este diseño.

La solución obvia es procesar los datos a doble velocidad o deshabilitar el registro de canalización para que pueda multiplicar y agregar en el mismo ciclo.

Lamentablemente, si dice que está muestreando a la velocidad máxima de reloj del segmento DSP totalmente segmentado, ninguna de esas soluciones es posible. ¿Hay alguna otra manera de construir esto?

(Puntos de bonificación si puede diseñar un filtro IIR que funcione a la mitad de la frecuencia de muestreo, utilizando cualquier número de segmentos DSP)

El objetivo sería ejecutar un filtro de compensación para un ADC de 1 GSPS en un FPGA de Xilinx Artix. Sus rodajas DSP pueden correr un poco más de 500 MHz cuando se canalizan por completo. Si hay una solución para 1 muestra por reloj, me gustaría intentar escalar la solución para 2 muestras por reloj. Todo esto es muy fácil con un filtro FIR.

    
pregunta Marcus10110

1 respuesta

3

Todavía no he trabajado con filtros IIR, pero si solo necesitas calcular la ecuación dada

y[n] = y[n-1]*b1 + x[n]

una vez por ciclo de CPU, puede utilizar la canalización.

En un ciclo haces la multiplicación y en un ciclo necesitas hacer la suma para cada muestra de entrada. ¡Eso significa que su FPGA debe poder hacer la multiplicación en un ciclo cuando se cronometra a la frecuencia de muestreo dada! Entonces solo tendrá que hacer la multiplicación de la muestra actual Y la suma del resultado de la multiplicación de la última muestra en paralelo. Esto provocará un retraso constante en el procesamiento de 2 ciclos.

Bien, echemos un vistazo a la fórmula y diseñemos una tubería:

y[n] = y[n-1]*b1 + x[n]

Su código de canalización podría verse así:

output <= last_output_times_b1 + last_input
last_output_times_b1 <= output * b1;
last_input <= input

Tenga en cuenta que los tres comandos deben ejecutarse en paralelo y que la "salida" en la segunda línea, por lo tanto, utiliza la salida del último ciclo de reloj.

No trabajé mucho con Verilog, por lo que la sintaxis de este código es muy incorrecta (por ejemplo, falta el ancho de bits de las señales de entrada / salida; sintaxis de ejecución para la multiplicación). Sin embargo deberías tener la idea:

module IIRFilter( clk, reset, x, b, y );
  input clk, reset, x, b;
  output y;

  reg y, t, t2;
  wire clk, reset, x, b;

  always @ (posedge clk or posedge reset)
  if (reset) begin
    y <= 0;
    t <= 0;
    t2 <= 0;
  end else begin
    y <= t + t2;
    t <= mult(y, b);
    t2 <= x
  end

endmodule

PS: Tal vez algún programador Verilog experimentado pueda editar este código y eliminar este comentario y el comentario sobre el código posterior. Gracias!

PPS: en caso de que su factor "b1" sea una constante fija, es posible que pueda optimizar el diseño implementando un multiplicador especial que solo toma una entrada escalar y calcula solo "veces b1".

Respuesta a: "Desafortunadamente, esto es realmente equivalente a y [n] = y [n-2] * b1 + x [n]. Esto se debe a la etapa de canalización adicional". como comentario a la versión anterior de la respuesta

Sí, en realidad era lo correcto para la siguiente versión antigua (¡¡¡INCORRECTO !!!):

  always @ (posedge clk or posedge reset)
  if (reset) begin
    t <= 0;
  end else begin
    y <= t + x;
    t <= mult(y, b);
  end

Ojalá haya corregido este error ahora al retrasar los valores de entrada, también en un segundo registro:

  always @ (posedge clk or posedge reset)
  if (reset) begin
    y <= 0;
    t <= 0;
    t2 <= 0;
  end else begin
    y <= t + t2;
    t <= mult(y, b);
    t2 <= x
  end

Para asegurarnos de que funcione correctamente esta vez, veamos qué sucede en los primeros ciclos. Tenga en cuenta que los primeros 2 ciclos producen más o menos basura (definida), ya que no hay disponibles valores de salida anteriores (por ejemplo, y [-1] == ??). El registro y se inicializa con 0, lo que equivale a suponer y [-1] == 0.

Primer ciclo (n = 0):

BEFORE: INPUT (x=x[0], b); REGISTERS (t=0, t2=0, y=0)

y <= t + t2;      == 0
t <= mult(y, b);  == y[-1] * b  = 0
t2 <= x           == x[0]

AFTERWARDS: REGISTERS (t=0, t2=x[0], y=0), OUTPUT: y[0]=0

Segundo ciclo (n = 1):

BEFORE: INPUT (x=x[1], b); REGISTERS (t=0, t2=x[0], y=y[0])

y <= t + t2;      ==     0  +  x[0]
t <= mult(y, b);  ==  y[0]  *  b
t2 <= x           ==  x[1]

AFTERWARDS: REGISTERS (t=y[0]*b, t2=x[1], y=x[0]), OUTPUT: y[1]=x[0]

Tercer ciclo (n = 2):

BEFORE: INPUT (x=x[2], b); REGISTERS (t=y[0]*b, t2=x[1], y=y[1])

y <= t + t2;      ==  y[0]*b +  x[1]
t <= mult(y, b);  ==  y[1]   *  b
t2 <= x           ==  x[2]

AFTERWARDS: REGISTERS (t=y[1]*b, t2=x[2], y=y[0]*b+x[1]), OUTPUT: y[2]=y[0]*b+x[1]

Cuarto ciclo (n = 3):

BEFORE: INPUT (x=x[3], b); REGISTERS (t=y[1]*b, t2=x[2], y=y[2])

y <= t + t2;      ==  y[1]*b +  x[2]
t <= mult(y, b);  ==  y[2]   *  b
t2 <= x           ==  x[3]

AFTERWARDS: REGISTERS (t=y[2]*b, t2=x[3], y=y[1]*b+x[2]), OUTPUT: y[3]=y[1]*b+x[2]

Podemos ver que a partir de cylce n = 2 obtenemos el siguiente resultado:

y[2]=y[0]*b+x[1]
y[3]=y[1]*b+x[2]

que es equivalente a

y[n]=y[n-2]*b + x[n-1]
y[n]=y[n-1-l]*b1 + x[n-l],  where l = 1
y[n+l]=y[n-1]*b1 + x[n],  where l = 1

Como se mencionó anteriormente, introducimos un retraso adicional de l = 1 ciclos. Eso significa que su salida y [n] se retrasa por el retraso l = 1. Eso significa que los datos de salida son equivalentes, pero se retrasan con un "índice". Para ser más claros: los datos de salida retrasados son 2 ciclos, ya que se necesita un ciclo de reloj (normal) y se agrega 1 ciclo adicional (retraso l = 1) para la etapa intermedia.

Aquí hay un bosquejo para representar gráficamente cómo fluyen los datos:

PD: Gracias por mirar de cerca mi código. ¡Así que también aprendí algo! ;-) Avísame si esta versión es correcta o si ves más problemas.

    
respondido por el SDwarfs

Lea otras preguntas en las etiquetas