Esta página es la segunda de tres en una serie sobre dominios de reloj.
Alcance
Como ya se mencionó en la página anterior, si un camino (path) cruza entre dos dominios de reloj (clock domains) cuyos relojes son relacionados (related clocks), la lógica no necesita ningún tratamiento especial. No obstante, hay que asegurarse de que las herramientas de FPGA apliquen las restricciones de temporización (timing constraints) a este camino, para garantizar los requisitos de temporización del elemento síncrono de destino (setup y hold). Por tanto, este caso se gestiona como si no hubiera ningún cruce de dominios de reloj, en el sentido de que ese camino sí se somete a comprobaciones de temporización.
Pero si los relojes son no relacionados (unrelated clocks), hace falta lógica de resincronización: debe haber código en Verilog (o algún otro lenguaje) dedicado a resolver este problema. Esta página repasa los fundamentos de cómo hacerlo.
Si puedes, usa una FIFO
Me ha parecido justo empezar explicando cómo evitar este quebradero de cabeza por completo, en beneficio de quienes tienen la posibilidad de escapar. Y es:
Las FIFOs de doble reloj (dual-clock FIFOs), que generan las herramientas de FPGA, son sin duda la forma más segura de cruzar entre dominios de reloj. Es la solución más habitual, y el hecho de que se use tan ampliamente es ya de por sí un motivo para confiar en ella.
En particular, si eres nuevo en el mundo de las FPGAs, hay una buena razón para preferir una FIFO, aunque tengas que modificar un poco tu propio diseño para ello. Usar una FIFO puede consumir algo más de recursos que una lógica hecha a medida para tu propósito. Ten en cuenta, sin embargo, que normalmente no se necesita una RAM de bloque (block RAM) si la profundidad de la FIFO es reducida. Así que la diferencia no merece necesariamente la pena si se corre el riesgo de meter la pata.
A menudo, la FIFO es la solución natural para cruzar entre dominios de reloj, sobre todo cuando hay un flujo de datos entre dos unidades funcionales. Pero incluso cuando no resulta tan natural, normalmente se puede reorganizar la lógica para que todo encaje. Por ejemplo, si la lógica de un dominio de reloj necesita notificar algo a la lógica de otro dominio de reloj, puede hacerse codificando una palabra con el mensaje y escribiéndola en una FIFO de poca profundidad. Una solución de este tipo no solo es menos propensa a errores, sino que probablemente dé como resultado un diseño más ordenado y legible.
La forma de conectar y usar FIFOs se trata en detalle en esta serie de páginas.
Lógica de resincronización para un solo bit
Si una FIFO no resuelve tu problema, es hora de arremangarse e implementar la lógica de resincronización necesaria para un cruce de dominios de reloj (clock domain crossing) seguro. El resto de esta página se limita a los cruces entre dominios de reloj con señales de un bit de ancho. Esto es la piedra angular de cualquier lógica de resincronización, y hay mucho que decir sobre este caso aparentemente sencillo.
La próxima página de esta serie explica cómo cruzar entre dominios de reloj con señales más anchas, basándose en la técnica del protector de metaestabilidad que se presenta a continuación.
Metaestabilidad
Antes de pasar a las soluciones, conviene entender qué ocurre cuando se incumplen los requisitos de temporización de un flip-flop. En otras palabras, cuando la señal en su entrada de datos no es estable durante el intervalo que empieza tsu antes del flanco de reloj y termina thold después de él. El flanco relevante es, por supuesto, el que llega a la entrada de reloj del flip-flop. Es ese flanco el que hace que el flip-flop muestree la entrada de datos.
Está claro que si el dato cambia durante ese intervalo, la salida del flip-flop después de ese flanco de reloj es impredecible. Pero es incluso peor: el flip-flop puede tardar bastante más de lo normal en presentar un '0' o un '1' válido en su salida. Aunque el flip-flop está diseñado para mantener la salida estable en uno de esos dos valores posibles (es decir, '0' o '1'), una violación de los requisitos de temporización puede hacer que el flip-flop permanezca un breve instante en una condición inestable. O, para usar el término oficial, el flip-flop está metaestable.
No voy a omitir el cliché de la bola en equilibrio en la cima de una colina, que se usa a menudo para representar la situación inestable que puede alcanzar el flip-flop:
La metaestabilidad (metastability) es un problema mucho mayor que la simple incertidumbre sobre el valor en el que finalmente se asienta el flip-flop. Esto es especialmente cierto si la salida del flip-flop está conectada a más de un destino: como tarda más en asentarse en un '0' o en un '1', la señal puede estabilizarse demasiado tarde para los flip-flops del destino. Como resultado, algunos de esos flip-flops de destino pueden recibir la señal oscilante como un '0', y otros como un '1'. Esa discrepancia puede llevar a la lógica a un estado ilegal, y desde esa situación la lógica puede comportarse al estilo de la magia negra. Por tanto, un flip-flop que corra el riesgo de sufrir metaestabilidad no debería conectarse nunca a más de un elemento lógico.
Para abordar la metaestabilidad, habría estado bien saber cuánto tarda el flip-flop en caer en uno de sus estados estables. Por desgracia, no hay una respuesta definitiva. Es exactamente como preguntar cuánto tiempo permanecerá la bola en la cima de la colina: depende de muchos factores, y lo más probable es que unas vibraciones aleatorias acaben haciéndola caer de un lado u otro. Lo mismo ocurre con la metaestabilidad de un flip-flop: saldrá de ese estado gracias al ruido aleatorio de los circuitos electrónicos, o a lo que sea.
Así que, en teoría, un flip-flop puede permanecer en estado de metaestabilidad indefinidamente, pero en la práctica caerá en uno de sus estados estables al poco tiempo. El tiempo que permanece en ese estado es una variable aleatoria. Se han realizado multitud de experimentos y simulaciones para intentar estimar el comportamiento de esa variable aleatoria. Sin embargo, ninguno de esos intentos es realmente relevante, ya que el comportamiento depende de la técnica de fabricación del silicio, de la temperatura, del nivel de ruido por diafonía y de otros factores.
Así que, una vez más, aunque habría sido cómodo disponer de un tiempo máximo especificado que el flip-flop nunca superara en estado de metaestabilidad, ese límite no existe. Ni siquiera es posible obtener una cifra aproximada, porque los procesos de fabricación más nuevos producen flip-flops que tienden a salir del estado de metaestabilidad más rápidamente.
En lugar de eso, esta es la idea a la que hay que acostumbrarse: cuando se cruza entre dominios de reloj con relojes no relacionados, siempre existe la posibilidad de que algún flip-flop permanezca en estado de metaestabilidad más tiempo del que el diseño puede tolerar, y que algo salga mal como consecuencia. Lo único que podemos hacer como diseñadores es reducir ese riesgo. En el mejor de los casos podemos conseguir un MTBF (Mean Time Between Failure, tiempo medio entre fallos) con el que se pueda vivir.
Afortunadamente, existe una técnica bien establecida para conseguirlo, lo que nos lleva al siguiente tema.
El protector de metaestabilidad
Para abreviar, volvamos al primer ejemplo de código de la página anterior. Si @clk1 y @clk2 son relojes no relacionados, la lógica de resincronización habitual para obtener un @bar estable a partir de @foo es
reg foo, bar, bar_metaguard;
always @(posedge clk1)
foo <= !foo;
always @(posedge clk2)
begin
bar_metaguard <= foo;
bar <= bar_metaguard;
end
Como su nombre indica, @bar_metaguard es un protector de metaestabilidad (metastability guard). Los requisitos de temporización del flip-flop que implementa @bar_metaguard se incumplen de vez en cuando al muestrear @foo. Por tanto, @bar_metaguard puede tener breves momentos de metaestabilidad. Como se espera que este flip-flop se recupere rápidamente de ese estado, se estabilizará con la rapidez suficiente para cumplir el requisito de setup de @bar. En consecuencia, @bar puede usarse con fiabilidad dentro del dominio de reloj de @clk2.
Esta explicación puede sonar imprecisa, y de hecho lo es. Contradice lo que escribí antes sobre la metaestabilidad, porque no existe una solución infalible contra la metaestabilidad. Volveré sobre esto más abajo. Pero por ahora, quedémonos con la práctica habitual, que consiste en añadir el protector de metaestabilidad como se ha mostrado y no volver a preocuparse. Y, para ser sincero, nunca he oído que alguien haya tenido problemas con esto.
Quienes quieran ir especialmente seguros añaden más registros. Así, un doble protector de metaestabilidad se haría más o menos así:
reg foo, bar, bar_metaguard_a, bar_metaguard_b;
always @(posedge clk1)
foo <= !foo;
always @(posedge clk2)
begin
bar_metaguard_a <= foo;
bar_metaguard_b <= bar_metaguard_a;
bar <= bar_metaguard_b;
end
@bar_metaguard_a es el primer protector de metaestabilidad. Si tenemos mala suerte, permanece demasiado tiempo en estado metaestable, y se incumplen los requisitos de temporización de @bar_metaguard_b. En consecuencia, @bar_metaguard_b tiene un periodo de metaestabilidad en el siguiente ciclo de reloj. Pero esta vez cabe esperar que el periodo de metaestabilidad sea breve: dicen que un rayo no cae dos veces en el mismo sitio. Pero, por supuesto, @bar_metaguard_b también puede permanecer el tiempo suficiente en estado metaestable como para incumplir la temporización de @bar. ¿Y cuál es la probabilidad de que eso ocurra? Recuerda que el objetivo es conseguir un MTBF razonable.
En resumen: si buscas la solución de manual para cruzar un dominio de reloj con un solo bit, esta es. Con un protector de metaestabilidad, o con dos, el trabajo queda bien hecho. Si buscas una solución que nunca falle, por desgracia es imposible. Pero si quieres reducir al máximo la probabilidad de contratiempos, sigue leyendo.
Análisis de temporización de la metaestabilidad
Volvamos ahora al ejemplo anterior con un único protector de metaestabilidad, y preguntémonos por qué estaba yo tan seguro de que @bar_metaguard se recupera de la metaestabilidad con la rapidez suficiente. La respuesta, como ya se ha dicho, es que no hay motivo alguno para estarlo.
No obstante, hagamos un poco de análisis de temporización. Tanto @bar_metaguard como @bar son síncronos con el mismo reloj. Suponiendo que no se hayan asignado restricciones de temporización especiales a estos registros en concreto, el camino que los une estará limitado por la restricción de temporización de @clk2 (el periodo de reloj). En otras palabras, las herramientas garantizan que, en ausencia de metaestabilidad, la señal de entrada de @bar se muestrea con una temporización válida.
Pero la gracia de @bar_metaguard es que se le permite caer en metaestabilidad de vez en cuando. Por tanto, el análisis de temporización resulta insuficiente: en realidad, el tiempo que el flip-flop pasa en estado metaestable se suma a su tiempo de clock-to-output. Dicho de otro modo, la metaestabilidad hace que la salida del flip-flop se estabilice más tarde de lo habitual. Así que si el camino entre @bar_metaguard y @bar tiene un margen (slack) casi nulo (es decir, su retardo de propagación (propagation delay) permite cumplir la temporización, pero sin margen sobrante), la metaestabilidad puede hacer que el retardo total del camino supere el límite permitido. Ese suceso provocaría una violación de temporización en la entrada de @bar. Por supuesto, esto se refiere al peor caso de temperatura, tensiones y proceso de fabricación; y aun así, el hecho de que las herramientas apliquen la restricción de temporización ordinaria a la salida del protector de metaestabilidad significa que no se garantizan los requisitos de temporización necesarios.
Afortunadamente, esto es fácil de arreglar. O de mejorar, debería decir. El método consiste en añadir una restricción de temporización más estricta a todos los caminos que van desde los protectores de metaestabilidad hasta los registros que deben ser fiables. Dicho de otro modo, la idea es permitir menos retardo de propagación en esos caminos. Al reducir el tiempo permitido para el retardo de propagación, ese tiempo queda disponible para la recuperación de la metaestabilidad.
Por ejemplo, si todos los registros protectores de metaestabilidad llevan el sufijo *_metaguard, se puede escribir una única restricción de temporización que exija que todos los caminos que parten de ellos dispongan de un tiempo extra para estabilizarse. En Vivado, esa restricción sería algo así:
set_max_delay -from [ get_cells -hier -filter {name=~*_metaguard*} ] 0.75
(set_max_delay se explica en la página sobre excepciones de temporización)
Esta restricción de temporización significa que cualquier camino que empiece en un protector de metaestabilidad dispone de 0,75 ns para llegar a su destino (esos caminos se seleccionan por el sufijo de los nombres de los registros). Este es el retardo mínimo que conseguí obtener sin que la restricción fallara en una FPGA concreta en la que lo probé (así que puede ser diferente en otras FPGAs). Para llegar a ese número, el método es elegir un valor más bajo hasta que las herramientas no consigan cumplir la restricción. Cuando falle, aumenta ese número lo necesario para conseguir un margen (slack) muy pequeño (por ejemplo, 0,2 ns). Eso obliga a las herramientas a esforzarse al máximo en esos caminos.
El comando set_max_delay equivale a pedir un periodo de reloj de 0,75 ns (1333 MHz). Así que si, por ejemplo, el periodo real del reloj es 250 MHz (4 ns), cumplir esta restricción proporciona un excedente de al menos 4 – 0,75 = 3,25 ns. Por tanto, si los protectores de metaestabilidad permanecen en estado metaestable hasta 3,25 ns, no se producirá ninguna violación de temporización, gracias a esa restricción set_max_delay.
Esto es, desde luego, mejor que no tener garantía alguna, pero ¿bastan 3,25 ns? ¿Es mucho? ¿Basta para asegurar una transición de dominios de reloj prácticamente infalible?
Como ya se ha dicho, la respuesta depende de varios factores. A juzgar por los experimentos publicados, mi impresión personal es que es extremadamente improbable que un flip-flop permanezca en estado metaestable durante 1 ns o más, en cualquier FPGA que se use de verdad hoy en día. Pero es difícil decir nada con seguridad sobre este asunto.
Pero al añadir esta restricción de temporización, y por tanto al empujar a las herramientas a dar lo mejor de sí, pones tu diseño FPGA en la misma situación que los demás, o incluso mejor. Así que, en el espíritu de la Regla de Oro n.º 4 (no tientes a la suerte), ponte en una posición tal que, si te falla a ti, muchísima otra gente tenga también motivos para quejarse.
Otra conclusión de este análisis de temporización es que si la frecuencia del reloj es alta para la FPGA utilizada, puede ser buena idea emplear un doble protector de metaestabilidad. Esto es especialmente cierto si no se usa la restricción de temporización adicional sugerida antes: como ya se ha dicho, la metaestabilidad consume el margen (slack) del camino. Cuanto menor es el periodo del reloj (es decir, cuanto mayor es la frecuencia), menos margen suele haber y, por tanto, menos tiempo extra para la metaestabilidad.
Por último, preguntémonos por qué prácticamente todo el mundo ignora todo este asunto de la temporización y, sin embargo, nadie se queja de problemas. Voy a ofrecer unas cuantas explicaciones posibles, pero no son más que especulaciones.
La primera explicación es que hay bastantes posibilidades de que las herramientas coloquen el protector de metaestabilidad físicamente muy cerca del otro flip-flop en el tejido lógico de la FPGA. Por tanto, el retardo de propagación entre esos dos flip-flops (por ejemplo, de @bar_metaguard a @bar) es de todos modos muy corto en esa FPGA. Esa colocación tan ajustada no está garantizada, sin embargo, a menos que se añadan restricciones de temporización explícitas, como ya se ha comentado.
Dentro de las FIFOs oficiales que proporcionan los fabricantes de FPGA, a menudo se encuentran pares de flip-flops de este tipo en la misma slice. En cualquier caso, se puede confiar en que las FIFOs oficiales tienen resuelto este asunto.
Otra consideración es que, a medida que las FPGAs se vuelven más rápidas y se alcanzan frecuencias de reloj más altas, resulta que los flip-flops también salen antes de la metaestabilidad. Así que el tiempo de recuperación de la metaestabilidad, más el retardo de propagación, sigue siendo claramente menor que el periodo del reloj.
Así que no sorprende que la mayoría se limite a añadir el protector de metaestabilidad y no se preocupe más. Eso no es excusa, sin embargo, para no tomarse la molestia de añadir una restricción.
Limitaciones en las frecuencias de los relojes
Hasta ahora no he dicho gran cosa sobre las frecuencias de los relojes en ninguno de los dos dominios de reloj. Por ejemplo, ¿qué ocurre si el reloj del dominio de origen (@clk1) es más rápido que el reloj del destino (@clk2)?
Como no se requiere sincronización entre los relojes, en sí no importa si la frecuencia del origen es mayor o menor. Pero si la señal del origen (@foo) cambia demasiado deprisa, puede cambiar de un lado a otro antes de que el flip-flop de destino tenga oportunidad de muestrear ese cambio. Por tanto, si todas las transiciones deben ser visibles en el destino, el periodo del reloj de origen debe ser ligeramente mayor (es decir, tener una frecuencia menor) que el del reloj de destino.
¿Cuánto mayor? No mucho. O, si insistes en una respuesta exacta: depende principalmente de los requisitos de temporización del flip-flop de destino. Aquí tienes un cálculo rápido (sáltatelo a menos que te interese de verdad):
Si la entrada de ese flip-flop cambia exactamente tsu antes del flanco de reloj del flip-flop, está justo en el límite de ser muestreada correctamente, así que habrá que muestrearla en el siguiente ciclo de reloj. De lo contrario, puede que ese cambio se haya perdido. Para muestrear ese cambio con fiabilidad en el siguiente ciclo de reloj, la señal de entrada debe permanecer estable hasta thold después del siguiente flanco. Por tanto, el periodo del reloj de origen debe ser más largo que el del reloj de destino en tsu + thold + 2tj. tj es la incertidumbre en el periodo del reloj (jitter). El jitter se cuenta dos veces: una por el reloj de origen y otra por el reloj de destino.
Así que tsu + thold + 2tj es lo que quería decir con que el periodo del reloj de origen debe ser «ligeramente más largo».
Cómo informar a las herramientas sobre los protectores de metaestabilidad
La documentación de algunas herramientas FPGA recomienda añadir un atributo a los flip-flops que se utilizan como protectores de metaestabilidad. Eso impide que las herramientas manipulen esos flip-flops. También anima a las herramientas a colocar el protector de metaestabilidad en la misma slice que el siguiente flip-flop, de forma que el enrutado sea lo más corto posible.
Por ejemplo, Vivado tiene un atributo llamado ASYNC_REG. Así que, como antes, si todos los registros protectores de metaestabilidad llevan el sufijo *_metaguard, esta línea del archivo XDC establece el atributo como corresponde:
set_property ASYNC_REG true [get_cells -hier -filter {name=~*_metaguard*}]
También es posible definir este atributo dentro del código Verilog.
Sin embargo, es poco probable que este atributo marque alguna diferencia, porque las herramientas normalmente manejan correctamente los protectores de metaestabilidad de todos modos.
Con esto concluye la segunda página de esta serie. La próxima página muestra técnicas para pasar datos entre dominios de reloj.
