Esta pregunta trata sobre la implementación de un filtro IIR en una 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:
(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 sumar en el mismo ciclo. Debe tener un registro de tubería entre estos componentes.
Entonces, si tiene 1 muestra nueva cada reloj, deberá producir 1 salida por reloj. Sin embargo, necesita los 2 relojes de salida anteriores antes de poder producir uno nuevo en este diseño.
La solución obvia es procesar los datos al doble de la velocidad del reloj o deshabilitar el registro de tubería para que pueda multiplicar y sumar en el mismo ciclo.
Desafortunadamente, si dice que está muestreando a la velocidad de reloj máxima del segmento DSP completamente canalizado, 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 cantidad de segmentos DSP)
El objetivo sería ejecutar un filtro de compensación para un ADC de 1 GSPS en una FPGA Xilinx Artix. Sus segmentos DSP pueden ejecutarse a poco más de 500 MHz cuando están completamente canalizados. 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.
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 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 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 causará un retraso de procesamiento constante de 2 ciclos.
Ok, echemos un vistazo a la fórmula y diseñemos una canalización:
y[n] = y[n-1]*b1 + x[n]
Su código de canalización podría tener este aspecto:
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 "salida" en la segunda línea, por lo tanto, usa la salida del último ciclo de reloj!
No trabajé mucho con Verilog, por lo que la sintaxis de este código posiblemente sea 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 hacerte una 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 toma una entrada escalar y calcula solo "veces b1".
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 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 funciona 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] == ??) 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 retraso l=1. Eso significa que los datos de salida son equivalentes pero se retrasan en un "índice". Para ser más claros: los datos de salida se retrasan en 2 ciclos, ya que se necesita un ciclo de reloj (normal) y se agrega 1 ciclo de reloj adicional (retraso l = 1) para la etapa intermedia.
Aquí hay un boceto para representar gráficamente cómo fluyen los datos:
PD: Gracias por echar un vistazo de cerca a mi código. ¡Así que aprendí algo también! ;-) Déjame saber si esta versión es correcta o si ves más problemas.
y[n+l] = y[n-1] * b + x[n]
un valor fijo para el retraso l
que se puede reescribir 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]
.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 para sumar los productos, necesita 2 ciclos (1 ciclo: sumar/sub resultados de los dos primeros productos, 1 ciclo: sumar/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
Sí, puede sincronizar a la frecuencia de muestreo.
Una solución a este problema es manipular la expresión original para que se puedan insertar registros de tubería, 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 sucesión 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 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 los nombres de puertos y registros a continuación. La primera primitiva es multiplicar por b1 y sumar un reloj después; el segundo es multiplicar por b1*b1 y sumar un reloj después. Hay una latencia de tubería de 4 relojes para esta lógica.
-- DSP48E #1
a_puerto1 := b1; -- coeficiente constante, establecer AREG=1
b_puerto1 := x; -- establecer atributo BREG=1
c_puerto1 := x; -- establecer CREG=1
-- interno a DSP48E #1
reg_a1 <= a_port1;
reg_b1 <= b_puerto1;
reg_c1 <= c_puerto1;
registro_m1 <= registro_a1 * registro_b1;
registro_p1 <= registro_m1 + registro_c1; -- salida del 1er 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_puerto2 := b1*b1; -- constante, establecer BREG=1
c_puerto2 := registro_p1; -- establecer CREG=1
-- interno a DSP48E #2
reg_b2 <= b_puerto2;
reg_c2 <= c_puerto2;
reg_m2 <= a_port2 * reg_b2;
registro_p2 <= registro_m2 + registro_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. Interno al 2do 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 multiplica y suma el mismo reloj, pero eso es lo que requiere la ecuación de diferencias. La ecuación de diferencia manipulada nos permite tolerar los registros M y P en el DSP48E y el reloj a toda velocidad.
horta
marcus10110
horta
jbarlow
marcus10110
horta
martin thompson
marcus10110