9 votos

¿Es posible crear un filtro IIR en una FPGA que se sincronice con la frecuencia de muestreo?

Esta pregunta se refiere a la implementación de un filtro IIR en una FPGA con rodajas DSP, con criterios muy específicos.

Digamos que estás haciendo un filtro sin grifos de avance y sólo 1 grifo de retroceso, con esta ecuación:

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

(ver imagen)

Tomemos como ejemplo el slice DSP48A1 de Xilinx - la mayoría de los slices DSP de IP dura son similares.

Digamos que tienes datos analógicos entrando a 1 muestra por reloj. Me gustaría diseñar un filtro IIR que se ejecuta de forma sincrónica en el reloj de la muestra.

El problema es que para hacer funcionar la porción de DSP a la máxima velocidad, no se puede multiplicar Y sumar en el mismo ciclo. Tienes que tener un registro de canalización entre estos componentes.

Por lo tanto, si tienes 1 nueva muestra cada reloj, necesitarás producir 1 salida por reloj. Sin embargo, necesitas la salida anterior 2 relojes antes de poder producir una nueva en este diseño.

La solución obvia es procesar los datos al doble de la velocidad del reloj, o desactivar el registro del pipeline para poder multiplicar y sumar en el mismo ciclo.

Desgraciadamente, si dices que estás muestreando a la máxima velocidad de reloj de la rebanada de DSP totalmente canalizada, ninguna de esas soluciones es posible. ¿Hay alguna otra manera de construir esto?

(Puntos extra si puedes 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 una FPGA Xilinx Artix. Sus cortes de DSP pueden funcionar a poco más de 500 MHz cuando están totalmente canalizados. 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.

single feedback IIR filter example

1 votos

Sólo para aclarar, no hay ninguna razón por la que no tendrías una salida por ciclo de reloj con el método de la tubería, ¿verdad? Usted está tratando de minimizar la latencia a un ciclo de reloj en lugar de dos, ¿verdad? Dependiendo de tu situación, si estás usando un entero para b1, entonces podrías convertir la multiplicación en una suma gigante incluyendo x[n].

0 votos

Correcto - ya que hay una entrada por reloj, tiene que haber una salida por reloj. la latencia tampoco es un problema. la rebanada de DSP sólo tiene un sumador de 2 entradas, y las derivaciones suelen ser números bastante grandes, por lo que no se podría sumar b1 veces en 1 ciclo de reloj. el principal límite es que la salida tiene que retroalimentarse en 1 reloj, pero tarda 2 relojes en producirse.

1 votos

Creo que sigues sin entender cómo funciona una tubería. Un pipeline aumenta potencialmente la latencia, pero te permite obtener 1 salida por cada entrada en cada ciclo de reloj. Es sólo que el resultado es ahora 2 relojes después en lugar de la ideal 1 reloj después. La entrada sería la secuencia así: x[0],x[1],x[2],x[3],x[4] mientras que la salida sería en ese mismo intervalo de tiempo y[-2],y[-1],y[0],y[1],y[2]. No estás perdiendo ninguna muestra. Además, estás en una FPGA, así que si quieres hacer más trabajo del que los pipelines DSP están diseñados, utiliza la fpga para paralelizar la carga de trabajo.

3voto

Thunderforge Puntos 111

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

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

una vez por ciclo de CPU, se puede utilizar el pipelining.

En un ciclo se hace la multiplicación y en un ciclo hay que hacer la suma para cada muestra de entrada. Esto significa que tu FPGA debe ser capaz de hacer la multiplicación en un ciclo cuando se sincroniza a la velocidad de muestreo dada. Entonces sólo tendrás 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 de procesamiento constante 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]

El código de su canalización podría ser el siguiente:

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" de la segunda línea utiliza por tanto la salida del último ciclo de reloj.

No he trabajado mucho con Verilog, así que la sintaxis de este código es muy posiblemente errónea (por ejemplo, falta el ancho de bits de las señales de entrada/salida; la sintaxis de ejecución para la multiplicación). Sin embargo, usted debe obtener 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 de Verilog con experiencia podría editar este código y eliminar este comentario y el que está encima del código después. Gracias.

PPS: En caso de que tu factor "b1" sea una constante fija, podrías optimizar el diseño implementando un multiplicador especial que sólo tome una entrada escalar y calcule "veces b1" solamente.

Respuesta a: "Desgraciadamente, esto equivale en realidad a y[n] = y[n-2] * b1 + x[n]. Esto se debe a la etapa extra de la tubería" como comentario a la versión antigua de la respuesta

Sí, en realidad era 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 esta vez funciona correctamente, veamos qué ocurre en los primeros ciclos. Observa que los primeros 2 ciclos producen más o menos basura (definida), ya que no hay valores de salida anteriores (por ejemplo y[-1] == ??). El registro y se inicializa con 0, lo que equivale a asumir que 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 el cilindro n=2 obtenemos la siguiente salida:

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

lo que equivale 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 ha mencionado anteriormente, introducimos un retraso adicional de l=1 ciclos. Eso significa que su salida y[n] se retrasa en el lag l=1. Esto significa que los datos de salida son equivalentes pero están retrasados en un "índice". Para ser más claros: Los datos de salida se retrasan 2 ciclos, ya que se necesita un ciclo de reloj (normal) y se añade 1 ciclo de reloj adicional (lag l=1) para la etapa intermedia.

A continuación se muestra un esquema para representar gráficamente cómo fluyen los datos:

sketch of data flow

P.D.: Gracias por mirar de cerca mi código. Yo también he aprendido algo ;-) Hazme saber si esta versión es correcta o si ves algún otro problema.

0 votos

Buen trabajo. Desafortunadamente, y[n] = y[n-2] * b + x[n-1] no es en realidad funcionalmente equivalente a y[n] = y[n-1] * b + x[n] con latencia. La forma de una función de transferencia IIR en realidad es la siguiente: y[n] = x[n] * b0 + x[n-1] * b1 - y[n-1] * a1 - y[n-2] * a2 y así sucesivamente. Su forma establece b0 y a1 en 0, y en su lugar utiliza b1 y a2. Sin embargo, esa transformación produce en realidad un filtro muy diferente. Si hubiera una forma de calcular un filtro con el primer denominador (a1) puesto a cero, ambas soluciones funcionarían perfectamente.

0 votos

Hay que entender bien el tema del "retraso introducido". Como ejemplo, un filtro de "procesamiento de flujo de datos" que sólo debe reenviar su entrada como y[n] = x[n] funcionaría correctamente si produce y[n] = x[n-1] como salida. La salida sólo se retrasa 1 ciclo (por ejemplo, el índice de salida se desplaza un valor fijo respecto a todos los índices de entrada). En nuestro ejemplo esto significa que su función es y[n+l] = y[n-1] * b + x[n] con un valor fijo para el retardo l que puede reescribirse como y[n] = y[n-1-l] * b + x[n-l] y para l=1 es y[n] = y[n-2] * b + x[n-1] .

0 votos

Para tu filtro IIR más complejo tendrías que 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 puedas hacer las tres multiplicaciones en paralelo (1. etapa / 1 ciclo) y necesites hacer la para sumar los productos necesitas 2 ciclos (1 ciclo: sumar/subir los dos primeros resultados del producto, 1 ciclo: sumar/subir el resultado de esas dos sumas/subidas), necesitarás 2 ciclos adicionales. Así que l=(3-1)=2 te da 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

0voto

tmnt13 Puntos 6

Sí, se puede sincronizar con la frecuencia de muestreo.

Una solución a este problema es manipular la expresión original expresión para que se puedan insertar registros en la tubería manteniendo la secuencia de salida deseada.

Dada: 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 comprobar que se trata de la misma secuencia, considere lo que ocurre 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 este crecerá sin límite.

Ahora considera 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 bibliotecas primitivas de Xilinx necesitaría dos DSP48E en cascada. Consulte la figura 1-1 de la UG193 v3.6 para los nombres de los puertos y de los registros. La primera primitiva es multiplicando por b1 y añadiendo un reloj después; la segunda multiplica por b1*b1 y añade un reloj más tarde. Hay una latencia de 4 relojes para esta lógica.

-- DSP48E #1

a_puerto1 := b1; -- coeficiente constante, fijar AREG=1

b_port1 := x; -- establecer el atributo BREG=1

c_puerto1 := x; -- set CREG=1

-- interno al 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 1er DSP48E

-- fin del DSP48E #1

-- DSP48E #2

a_puerto2 := reg_p2; -- establecer el atributo AREG=0

                -- this means the output of register reg_p2

                -- directly feeds back to the multiplier

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

c_port2 := reg_p1; -- set CREG=1

-- interno al DSP48E #2

reg_b2 <= b_port2;

reg_c2 <= c_port2;

reg_m2 <= a_port2 * reg_b2;

reg_p2 <= reg_m2 + reg_c2;

-- fin del 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. Internamente en el 2º 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

Este resultado es muy elegante. Claramente el DSP48E no multiplica y suma en el mismo reloj, sin embargo, eso es lo que la ecuación de la diferencia está requiriendo. La ecuación de diferencia manipulada nos permite tolerar los registros Registros M y P en el DSP48E y reloj a toda velocidad.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X