Esta es la tercera y última página de una serie sobre resets en FPGAs. Se recomienda leer las dos primeras antes de esta.
Visión general
Siempre es buena idea dedicar tiempo a implementar una lógica adecuada para generar los resets. Aunque no haya un problema aparente con el diseño tal como está, saltarse esta etapa en el diseño probablemente acabe pasando factura: más adelante podrías pasarte días intentando resolver algún problema de inestabilidad sin darte cuenta de que se debe a que la lógica no se inicializó correctamente. En los intentos por resolver problemas así a lo largo del tiempo, el diseño acumula parches feos hechos sin entender la raíz del problema. Más sobre inestabilidades extrañas en esta página.
Pensar y diseñar las señales de reset desde el principio del proyecto también es importante para asegurar que el proyecto crezca adecuadamente a medida que se añaden funcionalidades. Cuando un proyecto de FPGA se empieza de cero, a menudo se implementa primero la funcionalidad principal, y las características se van añadiendo con el tiempo. Como los módulos distintos suelen requerir relojes y resets separados, es muy fácil caer en la trampa de improvisar algo rápido para cada parte por separado. Esto a menudo hace que el proyecto sea cada vez más caótico conforme avanza.
Es más fácil evitar un proyecto desordenado teniendo un módulo central, escrito correctamente desde el primer día, que contenga los resets y los relojes. Y como se explica más abajo, el controlador de reset y los recursos de reloj (PLLs y buffers de reloj según sea necesario) se influyen mutuamente, así que ponerlos en el mismo módulo merece la pena. Normalmente le doy a este módulo el nombre clkrst.v.
Sin embargo, a menudo no es posible concentrar todo esto en un solo módulo, porque algunos núcleos IP (IP cores), subsistemas o bloques de diseño pueden generar su propio grupo de resets y relojes. Cuando esto ocurre, hay que pensar cuidadosamente qué depende de qué, y cómo debería reaccionar el sistema global ante peticiones de reset que llegan de distintas fuentes. Por ejemplo, el reset de un bloque PCIe llega casi siempre del propio bus, y el bloque genera una señal de reset para que la use la lógica conectada a ese bloque. En situaciones así hay que considerar, por ejemplo, cómo debería responder el sistema en general si llega un reset desde el bus PCIe (incluida la posibilidad de no responder en absoluto).
Como cada proyecto tiene su propia historia, no hay una solución única para todos los casos. Esta página describe conceptos y sugiere ideas y fragmentos de código que pueden servir como bloques para tu propio controlador de reset. Sin embargo, estos fragmentos no están pensados para copiarlos y pegarlos directamente en un proyecto, sino que deben tratarse como demostraciones.
Por simplificar, supongamos que ningún otro bloque del sistema genera ni relojes ni resets; no obstante, extender el controlador al caso general es bastante sencillo.
La máquina de estados del reset
En la mayoría de los diseños hay dos escenarios principales que requieren resetear:
- Inmediatamente después del encendido de la FPGA y de la configuración del bitstream, para garantizar un arranque limpio.
- Cuando se pide un reset explícitamente, ya sea pulsando un botón o mediante una orden de un procesador u ordenador.
Además, puede ser necesario resetear cuando expiran los temporizadores watchdog o cuando se detecta un fallo importante del sistema por algún otro medio.
La respuesta esperada a todos estos escenarios es un reinicio fundamental, de ese tipo que garantiza que, sin importar lo que haya fallado, queda corregido. A menudo esto se hace mejor con algún tipo de máquina de estados, que garantice una secuencia de reset coherente y repetible, sin importar el motivo por el que se inició.
Dicho esto, hay escenarios donde un reset local, posiblemente recurrente, tiene sentido. Por ejemplo, la lógica que procesa fotogramas de vídeo puede resetearse antes del comienzo de cada fotograma. Eso suele exigir un mecanismo ligero, que se reduce a asignar valores iniciales a varios registros cuando se activa un reset síncrono local. Es un mecanismo de reset como cualquier otro, y a menudo es una forma limpia y sencilla de garantizar un funcionamiento robusto.
Pero como no hay mucho más que añadir sobre esta posibilidad, el resto de esta página se centra en el reset fundamental, que cubre toda la FPGA.
Relojes inestables y la necesidad de resetear
Es bastante común (y normalmente recomendable) usar los PLLs propios de la FPGA para generar relojes. Pero estos PLLs normalmente empiezan a funcionar al mismo tiempo que toda la demás lógica de la FPGA se activa. Como resultado, a la lógica que depende de esos relojes le llega un reloj inestable, cuya frecuencia puede ser significativamente más alta de lo normal.
Cuando esto ocurre, la temporización de los caminos lógicos relevantes no está garantizada. Por tanto, cualquier lógica que dependa de relojes generados por PLLs debe tratarse como si no cumpliera las restricciones de temporización (timing constraints) hasta que el PLL esté enclavado.
La solución adecuada y habitual es mantener toda esa lógica en reset hasta que el PLL correspondiente esté enclavado. Alternativamente, esa lógica puede configurarse para que ignore el reloj hasta que el PLL esté enclavado, por ejemplo asegurando que la entrada de habilitación de reloj (Clock Enable, CE) de los flip-flops esté inactiva.
Si un reloj externo se conecta directamente desde un pin de la FPGA a sus elementos lógicos, la cuestión es si el generador de reloj (normalmente un PLL) se ha enclavado antes de que la FPGA se haya cargado con su bitstream. No siempre es fácil estar seguro de esto.
En consecuencia, en la mayoría de los diseños muchos elementos síncronos necesitan ser reseteados. O, más precisamente, respecto a si hace falta un reset debido a un reloj inestable, se pueden dividir esos elementos en tres grupos:
- Los que pueden contener datos basura y eso no es un problema.
- Los que se sabe que ignoran el reloj mientras este sea inestable (por ejemplo mediante una habilitación de reloj).
- Los que necesitan ser reseteados.
Vale la pena mencionar que algunas FPGAs permiten configurar el proceso de configuración (es decir, el proceso de cargar el bitstream e inicializar la FPGA), de modo que el despertar de la FPGA se retrase hasta que todos los PLL estén enclavados. Esta podría ser una forma de resolver este problema. La desventaja de esta solución es que si hay un problema con un reloj externo, la FPGA no arrancará en absoluto. Una situación así puede ser muy desconcertante.
Una máquina de estados sencilla
Como el arranque o reinicio fundamental son eventos bastante poco frecuentes, no importa que se tarde unos microsegundos más de lo necesario. En muchos casos incluso hasta unos 100 ms está bien, y se puede aprovechar. Por tanto, un simple contador es una forma sencilla de implementar una secuencia de eventos.
En su forma más simple, se reduce a algo así:
reg [4:0] reset_count;
reg rst_src_pll, rst_src_shreg, rst_src_debounce;
reg clear_counter;
reg master_reset;
initial reset_count = 0;
initial master_reset = 1;
initial clear_counter = 1;
always @(posedge wakeup_clk)
begin
clear_counter <= rst_src_pll || rst_src_shreg || rst_src_debounce;
master_reset <= (reset_count != 31);
if (clear_counter)
reset_count <= 0;
else if (reset_count != 31)
reset_count <= reset_count + 1;
end
@rst_src_pll, @rst_src_shreg y @rst_src_debounce representan distintas razones para resetear el sistema. A estos registros se les asignan valores desde otra lógica. Comentaré algunos ejemplos de esa lógica, pero por ahora el punto importante es que estos registros son síncronos con @wakeup_clk (así que no hace falta ningún cruce de dominios de reloj (clock domain crossing)).
@clear_counter es la OR lógica de estas razones de reset. Este registro cambia @reset_count a cero. De lo contrario, este contador va de 0 a 31 (en este ejemplo) y se detiene después.
Finalmente, @master_reset está activo mientras @reset_count no haya terminado de contar. Esta es la forma más simple de una máquina de estados de reset, y contar hasta 31 también es bastante modesto.
Así que si cualquiera de las señales @rst_src_N está activa, aunque sea durante un solo ciclo de reloj, el reset síncrono (synchronous reset) permanece activo durante 31 ciclos de reloj.
Hay dos ventajas en un pulso de reset largo. Primera: si la @rst_src_N correspondiente se activa y se desactiva de forma aleatoria (por ejemplo, detectores de enclavamiento del PLL inestables, pulsadores, software que pide resets varias veces), estas activaciones múltiples no se propagan al reset síncrono visible. Aunque esas activaciones múltiples suelen ser inofensivas, pueden provocar una actividad innecesaria en los pines de salida. Esa actividad puede tener un efecto negativo, por ejemplo confundir a una persona que está probando la electrónica y piensa que algo va mal.
En ese sentido, el ejemplo de 31 ciclos de reloj es bastante minimalista. Es incluso mejor contar hasta un valor que corresponda a 10-100 ms, si ese retardo es aceptable. De ese modo, cualquier fluctuación más corta que eso queda oculta por el controlador de reset.
La segunda razón para un pulso de reset largo es que la distribución del reset síncrono original en copias locales (como se explicó anteriormente) implica un retardo de un ciclo de reloj. Si la distribución del reset por toda la lógica requiere copiar la señal varias veces, el retardo total no solo es mayor, sino que posiblemente también se vuelva irregular. Un pulso de reset largo garantiza que en algún momento toda la lógica esté expuesta a un reset síncrono activo. Sigue siendo mala idea tener retardos desiguales en el camino del reset, porque la desactivación del reset también se vuelve desigual, pero a veces eso no supone un problema.
Resets para otros dominios de reloj
@master_reset es un reset síncrono normal, pero está sincronizado con un reloj que puede ser distinto de los relojes que usa la lógica de aplicación. Para generar resets síncronos para otros relojes, debería hacerse algo así para cada reloj:
reg reset_clk_pre1, reset_clk_pre2;
reg reset_clk;
always @(posedge clk)
begin
reset_clk <= reset_clk_pre2;
reset_clk_pre2 <= reset_clk_pre1;
reset_clk_pre1 <= master_reset;
end
Esto no es más que un cruce de dominios de reloj normal de tres etapas, que produce @reset_clk. Esta señal es el reset síncrono que va asociado a @clk. Con dos etapas basta en realidad, pero como es una señal importante, le he dado un registro extra para ir más seguro.
El reloj del controlador de reset
En principio hay tres tipos de relojes que se pueden usar como @wakeup_clk, es decir, para la máquina de estados de reset:
- Un reloj generado externamente a la FPGA, del que se sabe que es estable cuando la FPGA despierta.
- Un reloj generado por un PLL de la FPGA, y que por tanto no se espera que esté listo cuando la FPGA despierta.
- En algunas FPGAs es posible usar el reloj de configuración, que genera un oscilador de anillo dentro de la propia FPGA.
La primera opción es la más fácil de manejar. Como casi siempre hay un reloj de referencia que alimenta los PLLs de la FPGA, ese reloj de referencia a menudo se puede usar directamente como reloj de despertar. Sin embargo, es importante verificar que el reloj es realmente estable cuando la FPGA despierta, es decir, que el tiempo que tarda la FPGA en cargar el bitstream es mayor que el tiempo que tarda el oscilador externo en producir un reloj válido. Normalmente es así con un margen amplio al mirar las hojas de datos. Pero si la secuencia de encendido de la placa no está bien planificada, puede muy bien ocurrir que se le dé a la FPGA luz verde para leer su bitstream mucho antes de que la tensión de alimentación del oscilador haya alcanzado su nivel correcto.
La segunda opción es usar un reloj generado por un PLL de la propia FPGA. La ventaja obvia es que ese reloj posiblemente también se use para la lógica de aplicación, así que es más eficiente tanto con los recursos de reloj como con el consumo. Esta elección exige mantener la máquina de estados de reset en su estado inicial hasta que el reloj sea estable, lo cual puede hacerse con algo así:
reg rst_src_pll;
reg rst_src_pll_pre;
initial rst_src_pll = 1;
initial rst_src_pll_pre = 1;
always @(posedge wakeup_clk)
begin
rst_src_pll <= rst_src_pll_pre;
rst_src_pll_pre <= !pll_locked;
end
@pll_locked es la salida del detector de enclavamiento del PLL que genera @wakeup_clk (activa en alto). Esta señal es asíncrona, así que primero se sincroniza con @wakeup_clk, y después se usa como una de las razones para activar @clear_counter, como se mostró antes (es decir, junto con @rst_src_pll).
Otra ventaja de esta opción es que si el reloj de referencia es temporalmente inestable o está ausente (en particular, justo después de encender la placa), es muy probable que el detector de enclavamiento también sea inestable. Por tanto, si @reset_count cuenta hasta un número grande (desde luego mucho mayor que 31) antes de desactivar @master_reset, hay una posibilidad de que la FPGA permanezca firmemente en reset hasta que el reloj de referencia esté en condiciones. Sin embargo, no se puede depender de ello.
En cualquier caso, el hecho de que @wakeup_clk sea la salida de un PLL significa necesariamente que alimenta lógica antes de que ese reloj se haya estabilizado. Por tanto, no está claro si la máquina de estados funciona correctamente durante ese periodo. Se puede argumentar que eso no importa, porque en algún momento el reloj será lo bastante bueno, y entonces se generarán los resets adecuados. Si un reset lo deja todo en el pasado, ¿a quién le importa lo que pasara antes?
Un enfoque más riguroso es que los resets deben mantenerse firmemente activos hasta que el reloj de despertar esté enclavado, para que la FPGA no se comporte de forma rara justo después de cargar su bitstream. Eso exige prestar atención a usar solo lógica muy sencilla con este reloj. En otras palabras, la lógica debe ser del tipo que no falle de forma significativa si el reloj tiene una frecuencia más alta de lo esperado.
Para analizar qué pasa si @wakeup_clk tiene temporalmente una frecuencia demasiado alta, observa que @reset_count es el único registro de la máquina de estados de reset que es un vector. Eso significa que todos los demás flip-flops pueden, en el peor caso, muestrear su "siguiente valor" calculado un ciclo de reloj más tarde, debido a una violación de temporización. En particular, como @pll_locked está en bajo mientras el PLL no esté enclavado, @rst_src_pll pronto se vuelve estable en alto y, por tanto, @clear_counter también es estable en alto. Cuando la entrada D (siguiente valor) de un flip-flop no cambia, no importa lo rápido que vaya el reloj.
El único problema posible está por tanto en @reset_count, que podría contar incorrectamente hasta que su siguiente valor calculado se mantenga firmemente en cero gracias a @clear_counter. Por ejemplo, si su valor actual es 3 (011 en binario), su siguiente valor calculado es 4 (100 en binario). Pero si los dos bits menos significativos no se muestrean por problemas de temporización y el tercer bit sí se muestrea, el valor del contador puede saltar a 7 (111 en binario) en su lugar.
Para evitar que esto ocurra, los valores iniciales de la cadena de registros desde @pll_locked hasta @clear_counter están todos asignados de modo que @reset_count se mantenga firmemente en cero. De ahí que si @pll_locked permanece estable en bajo (como debe ser) hasta que @wakeup_clk sea estable, @reset_count no se moverá de cero, y @master_reset permanecerá firmemente activo.
En cuanto a la última opción, usar el oscilador de anillo de la FPGA para el controlador de reset: yo no lo he probado, así que no estoy seguro de lo buena que es la idea. Pero si le sirve a alguien que se ha quedado sin otra opción, aquí va más o menos cómo hacerlo con FPGAs de Xilinx: busca una primitiva FPGA (primitive) llamada STARTUPE2 (o algo parecido) en la guía de configuración de tu FPGA. Debería tener una salida llamada CFGMCLK, que es un reloj de un oscilador de anillo impreciso dentro de la propia FPGA. Su frecuencia ronda los 50-65 MHz. Se puede garantizar que este reloj es estable cuando la FPGA despierta, y yo pondría la restricción de temporización a una frecuencia considerablemente más alta (digamos, 100 MHz).
Pero esto es algo que yo haría solo si de verdad no hubiera otra opción. Por ejemplo, si el reloj de referencia externo no es estable cuando la FPGA despierta, de modo que haya que implementar un retardo adicional en lógica.
Resetear los PLLs
Por lo general es buena idea resetear los PLLs como parte de la secuencia de reset. Eso garantiza que se reseteen cuando se sabe que su reloj de referencia es válido. También, si el reset lo inicia el usuario (p. ej. pulsando un botón de reset), puede ser como respuesta a un problema que provenga de un PLL que no se ha enclavado bien. No debería ocurrir, pero por si acaso.
@clear_counter no se puede usar para resetear el PLL, porque se activa en cuanto un PLL deja de estar enclavado. Si se usara, el PLL permanecería en estado de reset, nunca llegaría a enclavarse, y el reset nunca se liberaría. Por la misma razón, los resets de los PLLs no se pueden derivar de @reset_count: se mantiene en cero excepto cuando todos los PLLs están enclavados.
La solución es crear un registro de reset separado para el PLL, similar a @clear_counter. Así que, usando la notación de antes, donde los PLLs que no están enclavados se reflejan en @rst_src_pll, se reduce a algo así:
reg clear_counter;
reg reset_plls;
initial clear_counter = 1;
initial reset_plls = 1;
assign reset_sources = rst_src_shreg || rst_src_debounce;
always @(posedge wakeup_clk)
begin
clear_counter <= rst_src_pll || reset_sources;
reset_plls <= reset_sources;
[ ... ]
En este fragmento, @rst_src_pll se ha excluido de @reset_sources, y se usa solo para @clear_counter. Como resultado, los PLLs se resetean junto con toda la FPGA, excepto como consecuencia de que ellos mismos no estén enclavados.
Vale la pena aclarar que @wakeup_clk no puede generarlo el PLL que se está reseteando, así que este reloj debe generarse basándose en las otras posibilidades.
Observa que si @reset_sources baja y sube aleatoriamente, @reset_plls también lo hará según el código mostrado. Eso normalmente es inofensivo, porque no le pasa nada malo a los PLLs cuando su reset se activa y desactiva sin parar. No obstante, también hay una forma de evitarlo, como se explica a continuación.
Secuencias de arranque más complejas
El uso de un simple contador (@reset_count) como variable de estado facilita implementar secuencias de arranque más complicadas. Por ejemplo, generar resets asíncronos (asynchronous resets) correctos, que estén activos con los relojes apagados, es bastante sencillo: se hace con expresiones lógicas simples que definen las franjas temporales en las que los relojes están apagados y en las que los resets están activos.
Por tanto, este método del contador simple es un buen punto de partida incluso para diseños que al empezar el proyecto parecen tener necesidades sencillas por parte de la máquina de estados de reset. Si más adelante resulta que se necesita una secuencia de arranque compleja, es fácil ampliar la lógica existente para conseguirlo.
En cualquier caso, planificar la secuencia de arranque se reduce a definir los periodos de tiempo en los que cada fase de la secuencia está en vigor. Cada uno de esos periodos se traduce en un rango de valores que debe tener @reset_count.
Por ejemplo, si el diseño tiene dos o más relojes no relacionados (unrelated clocks), es posible crear señales de reset para cada dominio de reloj como se mostró antes (ver "Resets para otros dominios de reloj"). Pero si se adopta ese método, cada dominio de reloj sale del reset en un orden efectivamente aleatorio. Normalmente esto no es un problema, pero si lo es, se puede hacer que cada dominio de reloj desactive su reset en un momento definido, basándose en el avance de @reset_count.
También es posible implementar más de un contador. Esto resulta útil cuando la secuencia de reset exige esperar a que se cumplan ciertas condiciones antes de continuar. Por ejemplo, si la secuencia de reset incluye resetear PLLs, esperar a que se enclaven, y luego continuar con la secuencia, tiene sentido tener un contador que se mantenga en cero hasta que todos los PLL estén enclavados. El segundo contador se mantiene en cero hasta que el primero termine de contar.
Observa que si un PLL pierde el enclavamiento, los propios PLLs no se resetean, pero sí todo lo que depende de ellos. Puede que ese no sea el comportamiento deseado, ya que un PLL que pierde el enclavamiento es una falta grave en la mayoría de los diseños. Para pedir un reset completo en caso de pérdida de enclavamiento, añade @pll_restart, definido a continuación, a las señales que se combinan con una OR para obtener @reset_sources:
assign pll_restart = rst_src_pll && !master_reset;
Esto simplemente dice: si el PLL se desenclavó después de que el reset maestro estuviera inactivo, resetea todo otra vez, incluidos los PLLs. Para que esto funcione, la cuenta del reset debe ser lo bastante larga para aguantar las posibles fluctuaciones de los detectores de enclavamiento. En otras palabras, la cuenta del reset debe ser más larga que el tiempo durante el cual el detector de enclavamiento del PLL puede estar en alto aunque el PLL todavía no esté enclavado. Eso no debe confundirse con el tiempo que tarda el PLL en adquirir el enclavamiento. Algunos detectores de enclavamiento no fluctúan en absoluto, y esas fluctuaciones son casi con toda seguridad bastante más cortas que el tiempo de enclavamiento del PLL (según la hoja de datos).
El registro de desplazamiento de despertar
Puede ser un poco excesivo, pero suelo añadir en mis diseños un registro de desplazamiento de despertar de este tipo:
reg [15:0] wakeup_shift;
reg rst_src_shreg;
initial rst_src_shreg = 1;
initial wakeup_shift = 0;
always @(posedge wakeup_clk)
begin
rst_src_shreg <= !wakeup_shift[15];
wakeup_shift <= { wakeup_shift, 1'b1 };
end
Como la mayoría de las FPGAs implementan @wakeup_shift como una primitiva de registro de desplazamiento, que consume el equivalente a una LUT, resulta barato en recursos, y ofrece otro mecanismo para asegurar que se produzca un reset durante el encendido. Esto es necesario en un diseño sin PLLs, porque nada más activará @clear_counter. Pero incluso si hay PLLs, existe la posibilidad de que ya estén enclavados cuando la FPGA despierta, ya que esa es una opción posible para la configuración del bitstream en algunas FPGAs.
Así que, en cualquier caso, es una incorporación recomendable. Más vale prevenir que curar.
Botón de reset externo
Los botones de reset son bastante habituales. Lo que hacen exactamente difiere de un diseño a otro. Una posibilidad es que el botón que el usuario considera "reset" esté conectado al pin de la FPGA que pone en marcha el proceso de cargar el bitstream en la FPGA. También puede estar conectado al pin de reset de un procesador, en FPGAs que tienen un procesador integrado.
Y este botón de reset podría estar conectado a un pin de E/S general de la FPGA, con el propósito de resetear la lógica de la FPGA. En ese caso, es una razón más para activar @clear_counter.
Si @reset_count cuenta hasta un número que corresponde a 10 ms o más, no hace falta un antirrebote de la señal del pin de entrada, porque las fluctuaciones serán absorbidas por el propio contador. En ese caso, esto es suficiente:
reg rst_src_debounce;
reg rst_src_debounce_pre;
initial rst_src_debounce = 1;
initial rst_src_debounce_pre = 1;
always @(posedge wakeup_clk)
begin
rst_src_debounce <= rst_src_debounce_pre;
rst_src_debounce_pre <= reset_button_pin;
end
Sin embargo, si el contador termina pronto (como en el ejemplo anterior, llegando solo a 31), el pulsador necesita antirrebote. Hay varias formas de hacerlo, por ejemplo:
reg [17:0] debounce_count = 0;
reg reset_button_d, reset_button_d2, reset_button_d3;
wire debounce_reached = (debounce_count == 250000);
initial debounce_count = 0;
initial rst_src_debounce = 1;
always @(posedge wakeup_clk)
begin
reset_button_d3 <= reset_button_d2;
reset_button_d2 <= reset_button_d;
reset_button_d <= reset_button_pin;
if (reset_button_d2 != reset_button_d3)
debounce_count <= 0;
else if (!debounce_reached)
debounce_count <= debounce_count + 1;
if (debounce_reached)
rst_src_debounce <= reset_button_d3;
end
Este es un antirrebote relativamente estricto, que no funciona bien con señales de entrada ruidosas. Eso es una ventaja o una desventaja, según lo que necesites.
Este ejemplo de código está escrito para un reloj de 25 MHz. El valor de @reset_button_pin se copia en @rst_src_debounce si @reset_button_pin ha tenido el mismo valor durante 10 ms. Observa que @debounce_count cambia a cero en el mismo ciclo de reloj en el que @reset_button_d3 cambia de valor. Por tanto, cuando @reset_button_d3 cambia de valor, nunca se copia inmediatamente en @rst_src_debounce, sino solo después de mantener el mismo valor durante un buen rato.
Resumen
Hay muchas cosas a tener en cuenta al diseñar la inicialización de una FPGA. Esta página ha presentado algunos conceptos e ideas, pero es importante tener presente que la verdadera tarea consiste en reconocer correctamente los eventos que deben iniciar una acción. También es importante definir la respuesta correcta a cada uno de esos eventos para llevar la lógica de forma fiable a un estado de funcionamiento.