¿Es posible crear un filtro IIR en un FPGA con frecuencia de muestra?

9

Esta pregunta se trata de implementar un filtro IIR en un FPGA con cortes DSP, con criterios muy específicos.

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

y[n]=y[n1]b1+x[n]

(ver imagen)

Tome el segmento DSP48A1 de Xilinx como ejemplo: la mayoría de los segmentos IP DSP 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 sincrónicamente en el reloj de muestra.

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

Entonces, si tiene 1 nueva muestra cada reloj, necesitará 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 una velocidad de reloj doble o deshabilitar el registro de canalización para poder multiplicar y agregar en el mismo ciclo.

Desafortunadamente, si dice que está muestreando a la frecuencia de reloj máxima del segmento DSP totalmente canalizado, ninguna de esas soluciones es posible. ¿Hay alguna otra forma 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 cortes DSP)

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

Ejemplo de filtro IIR de retroalimentación única

Marcus10110
fuente
1
Solo para aclarar, no hay ninguna razón por la que no tendrías una salida por ciclo de reloj con el método de canalización, ¿verdad? Estás tratando de minimizar la latencia a un ciclo de reloj en lugar de dos, ¿verdad? Dependiendo de su situación, si está usando un número entero para b1, entonces podría convertir la multiplicación en una suma gigante que incluye x [n].
horta
derecha: dado que hay una entrada por reloj, debe haber una salida por reloj. la latencia tampoco es un problema. el segmento DSP solo tiene un sumador de 2 entradas, y los grifos suelen ser números bastante grandes, por lo que no puede agregar b1 veces en 1 ciclo de reloj. El límite principal es que la salida necesita retroalimentarse en 1 reloj, pero se necesitan 2 relojes para producir.
Marcus10110
1
Creo que todavía no entiendes cómo funciona una tubería. Una tubería aumenta potencialmente la latencia, pero le permite obtener 1 salida para cada entrada en cada ciclo de reloj. Es solo que el resultado es ahora 2 relojes después en lugar del ideal 1 reloj después. La entrada sería la secuencia de esta manera: x [0], x [1], x [2], x [3], x [4] mientras que la salida sería en el mismo intervalo de tiempo y [-2], y [-1], y [0], y [1], y [2]. No estás perdiendo ninguna muestra. Además, está en un FPGA, por lo que si desea realizar más trabajo del que están diseñadas las tuberías DSP, utilice el fpga para paralelizar la carga de trabajo.
horta
Ese DSP es capaz de hacer una acumulación múltiple fusionada en un ciclo. Sin embargo, no me queda claro si la salida de un segmento DSP se puede conectar a su propia entrada con retroalimentación en un solo ciclo.
jbarlow
horta: tiene razón acerca de la canalización en general, pero el problema es que la pestaña b1 en este caso tiene retroalimentación, lo que significa que una etapa en la tubería depende de la salida del valor anterior. Si siempre se necesitan 2 relojes para producir la siguiente salida de la salida anterior, no hay forma de producir 1 salida por reloj, independientemente de la latencia que haya agregado. jbarlow: tienes razón, el segmento DSP tiene una opción fusionada de 1 ciclo. Sin embargo, no puede correr lo suficientemente rápido en este caso. agregando el registro M (ver hoja de datos) puede alcanzar 500 MHz. Sin embargo, no puede multiplicar y agregar el mismo clk.
Marcus10110

Respuestas:

3

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

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

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

En un ciclo, haces la multiplicación y en un ciclo necesitas hacer la suma de cada muestra de entrada. ¡Eso significa que su FPGA debe poder multiplicar en un ciclo cuando se registra a la frecuencia de muestreo dada! Entonces solo necesitará 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 de procesamiento constante de 2 ciclos.

Ok, 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 utiliza la salida del último ciclo de reloj!

No trabajé mucho con Verilog, por lo que la sintaxis de este código es posiblemente 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

PD: Tal vez algún programador experimentado de Verilog podría editar este código y eliminar este comentario y el comentario sobre el código después. ¡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 tome una entrada escalar y calcule "veces b1" solamente.

Respuesta a: "Desafortunadamente, esto en realidad es 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í, eso era realmente correcto para la siguiente versión antigua (¡INCORRECTA!):

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

Espero haber corregido este error ahora retrasando 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 valores de salida anteriores (por ejemplo, y [-1] == ??) están disponibles. 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 comenzando con 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 en un "índice". Para ser más claro: los datos de salida retrasados ​​son 2 ciclos, ya que se necesita un ciclo de reloj (normal) y se agrega 1 ciclo de reloj adicional (retraso 1 = 1) para la etapa intermedia.

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

bosquejo del flujo de datos

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

SDwarfs
fuente
¡Buen trabajo! Lamentablemente, y [n] = y [n-2] * b + x [n-1] no es funcionalmente equivalente a y [n] = y [n-1] * b + x [n] con latencia. La forma de una función de transferencia IIR en realidad se ve así: y [n] = x [n] * b0 + x [n-1] * b1 - y [n-1] * a1 - y [n-2] * a2 y así. Su formulario establece b0 y a1 en 0, y en su lugar usa b1 y a2. Sin embargo, esa transformación en realidad produce un filtro muy diferente. Sin embargo, si hubiera una manera de calcular un filtro con el primer denominador (a1) establecido en cero, ambas soluciones funcionarían perfectamente.
Marcus10110
Bueno, debe comprender el problema del "retraso introducido" correctamente. Como ejemplo, un filtro de "procesamiento de flujo de datos" debería reenviar su entrada ya que y [n] = x [n] funcionaría correctamente si produce y [n] = x [n-1] como salida. ¡La salida se retrasa 1 ciclo (por ejemplo, el índice de salida se compensa con un valor fijo en relación con todos los índices de entrada)! En nuestro ejemplo, esto significa que su función tiene y[n+l] = y[n-1] * b + x[n]un valor fijo para el retraso lque puede reescribirse y[n] = y[n-1-l] * b + x[n-l]y para l = 1 esto es y[n] = y[n-2] * b + x[n-1].
SDwarfs
Para su filtro IIR más complejo, necesitaría hacer lo mismo: y[n+l] = x[n] * b0 + x[n-1] * b1 - y[n-1] * a1 - y[n-2] * a2=> y[n] = x[n-l]*b0 + x[n-1-l] * b1 - y[n-1-l] * a1 - y[n-2-l]*a2. Suponiendo que puede hacer las tres multiplicaciones en paralelo (1. etapa / 1 ciclo) y necesita hacer lo mismo para agregar los productos, necesita 2 ciclos (1 ciclo: agregar / sub los dos primeros resultados del producto, 1 ciclo: agregar / sub el resultado de esos dos add / subs), necesitará 2 ciclos adicionales. Entonces l = (3-1) = 2 dándote y[n]=x[n-2]*b0+x[n-1-2]*b1-y[n-1-2]*a1-y[n-2-2]*a2=>y[n]=x[n-2]*b0+x[n-3]*b1-y[n-3]*a1-y[n-4]*a2
SDwarfs
Por supuesto, para que esto funcione, su FPGA debe poder hacer en paralelo: 4 multiplicaciones y 3 sumas / restas. Lo que significa que necesita recursos para 4 multiplicadores y 3 sumadores.
SDwarfs
0

Sí, puedes ver la frecuencia de la muestra.

Una solución a este problema es manipular la expresión original para que se puedan insertar registros de canalización, mientras se mantiene la secuencia de salida deseada.

Dado: y [n] = y [n-1] * b1 + x [n];

esto se puede manipular en: y [n] = y [n-2] * b1 * b1 + x [n-1] * b1 + x [n].

Para verificar que esta es la misma secuencia, considere lo que sucede con las primeras muestras x [0], x [1], x [2], etc., donde antes de x [0] todas las muestras x, y eran cero.

Para la expresión original, la secuencia es:

y = x[0],

x[1] +x[0]*b1,

x[2] +x[1]*b1 +x[0]*b1*b1,

x[3] +x[2]*b1 +x[1]*b1*b1 +x[0]*b1*b1*b1, ...

Está claro que es necesario que b1 <1, de lo contrario, esto crecerá sin límite.

Ahora considere la expresión manipulada:

y = x[0],

x[0]*b1 +x[1],

x[0]*b1*b1 +x[1]*b1 +x[2],

x[0]*b1*b1*b1 +x[1]*b1*b1 +x[2]*b1 +x[3], ...

Esta es la misma secuencia.

Una solución de hardware en las primitivas de la biblioteca Xilinx necesitaría dos DSP48E en cascada. Consulte la figura 1-1 en UG193 v3.6 para conocer el puerto y los nombres de registro a continuación. La primera primitiva es multiplicar por b1 y agregar un reloj más tarde; el segundo se multiplica por b1 * b1 y se agrega un reloj más tarde. Hay una latencia de canalización de 4 relojes para esta lógica.

- DSP48E # 1

a_port1: = b1; - coeficiente constante, establecer AREG = 1

b_port1: = x; - establecer atributo BREG = 1

c_port1: = x; - establecer CREG = 1

- interno a DSP48E # 1

reg_a1 <= a_port1;

reg_b1 <= b_port1;

reg_c1 ​​<= c_port1;

reg_m1 <= reg_a1 * reg_b1;

reg_p1 <= reg_m1 + reg_c1; - salida del primer DSP48E

- fin de DSP48E # 1

- DSP48E # 2

a_port2: = reg_p2; - establecer atributo AREG = 0

                -- this means the output of register reg_p2

                -- directly feeds back to the multiplier

b_port2: = b1 * b1; - constante, establecer BREG = 1

c_port2: = reg_p1; - establecer CREG = 1

- interno a DSP48E # 2

reg_b2 <= b_port2;

reg_c2 <= c_port2;

reg_m2 <= a_port2 * reg_b2;

reg_p2 <= reg_m2 + reg_c2;

- fin de DSP48E # 2

La secuencia en reg_p1:

x [0],

x [1] + x [0] * b1,

x [2] + x [1] * b1,

x [3] + x [2] * b1,

etc.

La secuencia en reg_p2 es el resultado deseado. Interna al segundo DSP48E, el registro reg_m2 tiene una secuencia:

x [0] * b1 * b1,

x [1] * b1 * b1 + x [0] * b1 * b1 * b1,

x [2] * b1 * b1 + x [1] * b1 * b1 * b1 + x [0] * b1 * b1 * b1 * b1

Hay una bonita elegancia en este resultado. Claramente, el DSP48E no se multiplica y agrega en el mismo reloj, sin embargo, eso es lo que requiere la ecuación de diferencia. La ecuación de diferencia manipulada nos permite tolerar los registros M y P en el DSP48E y el reloj a toda velocidad.

Dave Brown
fuente