01signal.com

Uso del muestreo de señal 01 con entradas síncronas de fuente

Introducción

Esta página explica el muestreo de señal 01 (01-signal sampling) como método para realizar la interfaz con una entrada síncrona de fuente. Otras estrategias para conectar con fuentes de datos de este tipo se describen en una página sobre las entradas síncronas de fuente en general. Esa página también explica qué es una entrada síncrona de fuente.

La idea que subyace al muestreo de señal 01 es tratar el reloj externo como una señal de datos: en consecuencia, el reloj se muestrea mediante un registro. Este registro utiliza un reloj interno estable e independiente del reloj externo.

Las señales de datos se muestrean con registros adicionales. Para ello se utiliza el mismo reloj interno. Este reloj interno también lo usa toda la lógica que implementa el muestreo de señal 01.

Esta lógica detecta cambios en el registro que muestrea el reloj externo. Cuando este registro cambia de '0' a '1', significa que ha habido un flanco de subida en el reloj externo. La lógica responde a este evento escribiendo los valores de los demás registros en una FIFO. Estos registros contienen los valores de las señales de datos que estaban presentes cuando se produjo un flanco de subida del reloj externo.

Esta lógica consigue el mismo resultado que si el reloj de la FIFO fuera el reloj externo y las entradas de datos de la FIFO estuvieran conectadas directamente a las señales de datos. La diferencia está en qué reloj utiliza la lógica: el reloj externo o el reloj interno. La ventaja del muestreo de señal 01 es que toda la lógica depende únicamente del reloj interno. Este reloj es estable y fiable. Incluso si el reloj externo se comporta mal, la lógica sigue comportándose de manera sensata.

La siguiente imagen ilustra el muestreo de señal 01:

Example of 01-signal sampling

En esta imagen, @stable_clk es el reloj interno de la FPGA. @data_clk y @data son señales que llegan a la FPGA. @data_clk_samp y @data_samp son registros dentro de la FPGA. La señal externa @data_clk está representada por @data_clk_samp. Lo mismo ocurre con @data_samp en relación con @data.

La imagen muestra que cuando aparece un patrón «0 1» en @data_clk_samp, el valor de @data_samp se escribe en una FIFO. Esta es la razón por la que este método se denomina «muestreo de señal 01».

La FIFO se presenta aquí como un ejemplo de qué hacer con los datos que llegan. A menudo es una solución adecuada, porque la frecuencia del reloj interno es considerablemente más alta que la velocidad de datos. Por tanto, suele ser conveniente seguir procesando los datos con lógica basada en una frecuencia de reloj más baja. Una FIFO es un método cómodo para entregar los datos a una lógica que trabaja en otro dominio de reloj (clock domain).

No obstante, es posible implementar el resto de la lógica con el mismo reloj interno utilizado para el muestreo de señal 01. Usar una FIFO es solo una posibilidad.

Un ejemplo en Verilog

Este código Verilog ilustra la idea. El reloj externo es @data_clk.

module top (
   input stable_clk,

   input data_clk,
   input [7:0] data
);

   reg [7:0] data_guard, data_samp;
   reg       data_clk_guard, data_clk_samp, data_clk_samp_d;
   wire      fifo_wr_en;

   always @(posedge stable_clk)
     begin
        data_guard <= data;
        data_clk_guard <= data_clk;

        data_samp <= data_guard;
        data_clk_samp <= data_clk_guard;

        data_clk_samp_d <= data_clk_samp;
     end

   assign fifo_wr_en = data_clk_samp && !data_clk_samp_d;

   data_fifo fifo_i
     (
      .wr_clk(stable_clk),
      .din(data_samp),
      .wr_en(fifo_wr_en),

       [ ... other ports connected here ... ]
      );
endmodule

La parte importante es la señal wr_en de la FIFO: @fifo_wr_en. Esta señal es igual a «data_clk_samp && !data_clk_samp_d». Por tanto, esta señal está a nivel alto como resultado de detectar un patrón «0 1» en @data_clk_samp. Eso hace que el valor de @data_samp se escriba en la FIFO.

Nótese que la lógica solo utiliza @stable_clk como reloj. @data_clk se trata como una entrada de E/S normal.

Los requisitos de temporización de @data_guard y @data_clk_guard no están garantizados: los puertos de entrada son asíncronos con respecto a @stable_clk. @data_guard y @data_clk_guard son por tanto registros de protección contra la metaestabilidad (metastability guards). La lógica no depende directamente de los valores de estos dos registros. En cambio, @data_samp y @data_clk_samp sí los usa directamente la lógica, porque estos registros son el segundo escalón del registro de protección contra la metaestabilidad.

Pero, ¿funciona esto realmente? La respuesta está en el análisis de temporización.

Análisis de temporización

El análisis de temporización del muestreo de señal 01 difiere del enfoque habitual: Normalmente, existe una diferencia de tiempo constante entre el flanco del reloj y el momento en que se muestrean las señales de datos, porque el reloj utilizado para el muestreo es síncrono con las señales de datos. Pero con el muestreo de señal 01, el muestreo de @data se hace con @stable_clk. Por tanto, el momento del muestreo no tiene nada que ver con la temporización propia de las señales de datos. En su lugar, la lógica solo selecciona los valores de @data_clk_samp que están próximos a un flanco de subida del reloj.

Así pues, hay una diferencia de tiempo aleatoria entre el momento del flanco de subida del reloj de datos y el momento en que se produce realmente el muestreo. ¿Cómo es posible que este método sea fiable?

Hay una página aparte sobre los fundamentos de la temporización en un diseño lógico. Esa página explica el significado de tsu y thold. En resumen, la entrada de un biestable debe ser estable antes y después del flanco de subida del reloj (suponiendo que el biestable se active en un flanco de subida). tsu define cuánto tiempo debe ser estable la entrada antes del flanco de subida. thold define cuánto tiempo debe ser estable la entrada después del flanco de subida. Si se viola una de estas condiciones, la respuesta del biestable al flanco de subida es impredecible.

Se puede ver de otra manera: la entrada debe ser estable durante un intervalo de tiempo concreto alrededor del flanco de subida. Llamemos a este intervalo Δt = tsu + thold. Partiendo de Δt y de otros parámetros, vamos a obtener ahora los requisitos de temporización que garantizan un funcionamiento fiable.

Todo el análisis de temporización se basa en la situación en que @data_clk_guard está a nivel alto y @data_clk_samp a nivel bajo. Cuando esto ocurre, el patrón «0 1» se detecta un ciclo de reloj más tarde. En otras palabras, en el siguiente ciclo @data_clk_samp_d será '0' y @data_clk_samp será '1'. Como resultado, @fifo_wr_en estará a nivel alto, así que @data_samp debe contener el valor que la fuente de datos pretendía enviar.

El análisis se centrará por tanto en esta cuestión: cuando @data_clk_guard está a nivel alto y @data_clk_samp a nivel bajo, ¿qué requisitos debe cumplir @data para que la información llegue correctamente?

El análisis se hará en dos partes. En ambas supondré que @data_clk_guard está a nivel alto y @data_clk_samp a nivel bajo. En la primera parte, la pregunta será: ¿cuál es el momento más tardío en que @data_clk puede pasar de bajo a alto sin romper esta suposición? A continuación hallaré el requisito de temporización sobre @data que garantiza que @data_samp contenga el valor correcto.

En la segunda parte del análisis, plantearé la pregunta opuesta: ¿cuál es el momento más temprano en que @data_clk puede pasar de bajo a alto sin romper la suposición sobre @data_clk_guard y @data_clk_samp? Después haré un análisis similar sobre los requisitos de temporización.

Pero antes, definamos algunos símbolos:

Primera parte del análisis

Este diagrama de tiempos muestra dos ciclos de reloj de @stable_clk. En el análisis siguiente, @data_clk_guard copia su nuevo valor desde @data_clk en el flanco de subida situado a la derecha. Del mismo modo, @data_guard copia su nuevo valor desde @data en el mismo flanco de reloj.

Por el mismo principio, @data_clk_samp y @data_samp están relacionados con el flanco de subida de @stable_clk situado a la izquierda.

Timing diagram for late @data_clk scenario

En este escenario, @data_clk pasa a nivel alto exactamente al final de la región amarilla. Se violan los requisitos de temporización del biestable, por lo que el resultado es impredecible. Pero es posible que @data_clk_guard esté a nivel alto y @data_clk_samp a nivel bajo.

Sin embargo, si @data_clk cambia de valor un poco más tarde, @data_clk_guard estará seguramente a nivel bajo, porque el comportamiento del biestable es predecible: en este escenario, la entrada está a nivel bajo durante toda la región amarilla. Por eso podemos afirmar lo siguiente: si @data_clk_guard está a nivel alto y @data_clk_samp a nivel bajo, @data_clk pasó de bajo a alto antes de lo que se muestra en el diagrama de tiempos anterior. En consecuencia, el diagrama muestra el escenario en que @data_clk cambió en el momento más tardío posible.

Para garantizar que @data_guard contenga un valor fiable, @data debe ser estable antes de la zona amarilla. Esto nos da el primer requisito de temporización: @data debe ser estable durante un periodo de tiempo Δt antes del flanco de subida de @data_clk.

Nótese que si @data_clk pasa de bajo a alto antes, este requisito de temporización sigue garantizando el requisito de tsu de @data_guard. Por tanto, el tsu de los biestables queda garantizado para todos los escenarios en que @data_clk_guard esté a nivel alto y @data_clk_samp a nivel bajo.

El diagrama de tiempos anterior no muestra nada relacionado con la desviación temporal (skew). Para tenerla en cuenta, el requisito de temporización es: @data debe ser estable durante un periodo de tiempo de Δt + tskew antes del flanco de subida de @data_clk.

En este escenario no es necesario tener en cuenta la fluctuación de fase (jitter), porque en la discusión solo interviene un flanco de reloj.

Segunda parte del análisis

Este es el diagrama de tiempos para este escenario:

Timing diagram for early @data_clk scenario

En este escenario, @data_clk pasa a nivel alto exactamente al comienzo de la región amarilla del flanco de reloj anterior de @stable_clk.

No hay duda de que en esta situación @data_clk_guard estará a nivel alto. Sin embargo, el valor de @data_clk_samp no es predecible, debido a la violación de temporización en el ciclo de reloj anterior. Como antes, es posible que @data_clk_samp esté a nivel bajo. Pero si @data_clk cambia su valor antes que esto, @data_clk_samp estará con seguridad a nivel alto.

Así pues, si @data_clk_guard está a nivel alto y @data_clk_samp a nivel bajo, @data_clk pasó de bajo a alto más tarde de lo que se muestra en el diagrama anterior. En consecuencia, el diagrama muestra el escenario en que @data_clk cambió en el momento más temprano posible.

Para garantizar que @data_guard contenga el valor correcto, @data debe ser estable después de la región amarilla de la derecha.

Según el diagrama anterior, la diferencia de tiempo entre el flanco de subida de @data_clk y el final de la segunda región amarilla es Δt + tclk. Pero también hay que tener en cuenta la desviación temporal (skew) y la fluctuación de fase (jitter). Por tanto, @data debe ser estable durante un periodo de tiempo de Δt + tclk + tskew + tj después de @data_clk.

Nótese que si @data_clk pasa de bajo a alto antes, este requisito de temporización sigue cubriendo el requisito de mantenimiento (hold) de @data_guard. Por tanto, el thold de los biestables queda garantizado para todos los escenarios en que @data_clk_guard esté a nivel alto y @data_clk_samp a nivel bajo.

Los requisitos de temporización

Como conclusión del análisis de temporización anterior, estos son los dos requisitos de temporización para garantizar un funcionamiento fiable del muestreo de señal 01:

Estos requisitos pueden parecer complicados, pero a menudo es fácil asegurar que se cumplen. Por ejemplo, si @data cambia junto con el flanco de bajada de @data_clk, normalmente es fácil cumplirlos. Si @stable_clk es tres veces más rápido que @data_clk, suele ser suficiente.

En la mayoría de los casos no hace falta conocer los valores exactos de Δt, tskew o tj. A menudo basta con calcular lo grandes que pueden ser Δt y Δt + tskew + tj a partir de los dos requisitos anteriores. Si la frecuencia de @stable_clk es suficientemente alta, el valor de estas dos expresiones suele ser mayor de lo que es realísticamente posible en una FPGA.

Conviene usar registros IOB para minimizar las diferencias de temporización entre los puertos de entrada de la FPGA (tskew). Además, deben escribirse restricciones de temporización (timing constraints) en relación con estos puertos de entrada para asegurar que se usan registros IOB.

Variantes del muestreo de señal 01

Hasta ahora he supuesto que @data cambia junto con el flanco de bajada de @data_clk. Si @data cambia junto con el flanco de subida de @data_clk, la lógica debe ajustarse para activarse en cambio cuando @data_clk_guard esté a nivel bajo y @data_clk_samp a nivel alto. En otras palabras, la lógica debe detectar el flanco de bajada de @data_clk buscando un patrón «1 0».

También es posible elegir un criterio distinto para saber cuándo obtener el valor de @data. Por ejemplo, puede ser mejor retrasar @fifo_wr_en unos pocos ciclos de reloj. A veces es mejor que @fifo_wr_en se active antes de lo sugerido anteriormente. Esto depende de las relaciones de temporización entre @data_clk y @data. Consulta la hoja de datos (datasheet) de la fuente de las señales y analiza la temporización como se ha mostrado antes.

Si la frecuencia de @data_clk es relativamente alta, es posible usar registros DDR para muestrear @data_clk y @data. La lógica que implementa esta solución es algo más complicada, pero se aplican los mismos principios.

¿Es @data_guard realmente un registro de protección contra la metaestabilidad?

La respuesta corta es sí. @data es asíncrono con respecto a @stable_clk, por lo que los requisitos de temporización de @data_guard no están garantizados.

Pero centrémonos en los ciclos de reloj en los que realmente se utiliza el contenido de @data_guard: nótese que los dos requisitos de temporización anteriores garantizan que los biestables que implementan @data_guard funcionen de forma fiable. Tanto el tsu como el thold de estos biestables quedan garantizados.

Por tanto, @data_guard no se usa realmente como registro de protección contra la metaestabilidad. En la forma en que se utiliza este registro, funciona solo como registro de retardo. Pero no está de más que un registro adicional proteja contra violaciones de temporización que puedan ocurrir si la señal física (@data) se comporta mal. Esto es relevante en particular cuando la señal se conecta a la FPGA a través de un conector.

Un ejemplo real

Hay un ejemplo de lógica que se conecta con un sensor de cámara OV7670 en una página distinta. Esa lógica obtiene los datos de píxeles del sensor de cámara mediante el muestreo de señal 01. Las entradas del sensor de cámara son las siguientes:

   input       pclk_in;
   input [7:0] D_in;
   input       hsync_in, vsync_in;

La lógica que realiza el muestreo de señal 01 es la siguiente:

   (* IOB = "TRUE" *) reg [7:0] D_guard;
   (* IOB = "TRUE" *) reg       pclk_guard, hsync_guard, vsync_guard;

   reg [7:0]  D;
   reg 	      pclk, hsync, vsync;

   wire       sample_valid;
   reg 	      previous_pclk;

   always @(posedge stable_clk)
     begin
	// Metastability guards on asynchronous inputs
	D_guard <= D_in;
	pclk_guard <= pclk_in;
	hsync_guard <= hsync_in;
	vsync_guard <= vsync_in;

	D <= D_guard;
	pclk <= pclk_guard;
	hsync <= hsync_guard;
	vsync <= vsync_guard;

        previous_pclk <= pclk;
     end

   assign sample_valid = pclk && !previous_pclk;

Para mayor claridad, hay pequeñas diferencias entre el código Verilog que se presenta aquí y el código Verilog de la página sobre OV7670. Estas diferencias no tienen ningún impacto en el funcionamiento de la lógica.

Comparemos este código Verilog con el que presenté al principio de esta página. Los nombres de las señales son distintos, pero significan lo mismo: en lugar de @data, ahora tenemos @D_in, @hsync_in y @vsync_in. En lugar de @data_clk, ahora tenemos @pclk_in. Y en lugar de @fifo_wr_en, ahora tenemos @sample_valid. El cambio de nombres puede resultar confuso, pero no hay ninguna diferencia respecto al código Verilog anterior.

Nótese las partes «(* IOB = "TRUE" *)» antes de las declaraciones de los registros. Con Vivado, esta es una forma posible de solicitar que los registros se inserten en los IOB.

En este ejemplo no se presenta la FIFO. Esto se debe a que no queremos escribir todos los datos en la FIFO: cuando @sample_valid está a nivel alto, significa que @D, @hsync y @vsync contienen valores correctos del sensor de cámara. Pero eso no significa que queramos escribir @D en la FIFO: depende de @hsync y @vsync. Por eso, en el ejemplo de OV7670 hay lógica adicional que se asegura de que solo se escriban píxeles en la FIFO.

Pero vayamos a la parte interesante: el análisis de temporización.

La frecuencia de @stable_clk en este ejemplo es de 100 MHz. La frecuencia de @pclk_in es de 25 MHz. Según la hoja de datos (datasheet) del OV7670, se garantiza que las señales @D_in, @hsync_in y @vsync_in son estables durante los 5 ns posteriores a que @pclk_in pase de alto a bajo (flanco de bajada).

El ciclo de reloj de @pclk_in es de 40 ns. Por tanto, la distancia desde el flanco de bajada hasta el flanco de subida es de 20 ns. Ahora comparemos con los requisitos de temporización.

El primer requisito de temporización era que @D_in, @hsync_in y @vsync_in fueran estables durante un periodo de tiempo Δt antes del flanco de subida de @pclk_in. En realidad, estas señales son estables a partir de 5 ns después del flanco de bajada. Por tanto, son estables al menos 15 ns antes del siguiente flanco de subida. Recuérdese que Δt = tsu + thold. Así que el requisito de temporización es en realidad que tsu + thold sea menor que 15 ns. Esto se cumple en cualquier FPGA.

El segundo requisito es que estas señales sean estables durante al menos un periodo de tiempo de Δt + tclk + tskew + tj después del flanco de subida de @pclk_in. Pero estas señales solo cambian como resultado de un flanco de bajada. Así que el requisito es que Δt + tclk + tskew + tj sea menor que 20 ns. tclk es igual a 10 ns porque la frecuencia de @stable_clk es 100 MHz. El requisito real es por tanto que Δt + tskew + tj sea menor que 10 ns. Una vez más, esto se cumple de forma evidente en cualquier FPGA.

Este ejemplo demuestra cómo se pueden garantizar fácilmente los requisitos de temporización sin necesidad de conocer los parámetros exactos de temporización de la FPGA.

Conclusión

El muestreo de señal 01 es una solución excelente cuando la velocidad de datos es baja en relación con la frecuencia de reloj que la FPGA puede soportar: el reloj de datos no tiene por qué ser estable. Tampoco es necesario conocer de antemano la frecuencia exacta del reloj de datos. Basta con garantizar los dos requisitos de temporización.

Este método tiene ventajas adicionales: si este reloj queda inactivo durante un breve momento, la única consecuencia es que no se recogen datos durante ese periodo concreto. El daño causado por cualquier mal funcionamiento de este reloj se limita a la interrupción del flujo de datos. Esto provocará un mal funcionamiento visible del sistema, pero ese mal funcionamiento parece un problema del reloj, y no como si a la FPGA la persiguieran fantasmas.

Así pues, aunque el muestreo de las señales de datos tiene una aleatoriedad inherente, el muestreo de señal 01 es una solución fiable y robusta para una entrada síncrona de fuente. La única desventaja real es la limitación en la velocidad de datos.

Esta página se ha traducido del inglés mediante traducción automática. En caso de duda, consulta el texto original.
Copyright © 2021-2026. All rights reserved. (dcc38493)