
Hace algún tiempo, durante una discusión en compañía de desarrolladores profesionales de FPGA, surgió una discusión sobre la aprobación de una entrevista. Qué preguntas se hacen allí y qué se podría hacer. Sugerí dos preguntas:
- Dé un ejemplo de un código síncrono sin usar retrasos, lo que dará diferentes resultados al modelar y al trabajar en equipos reales.
- Corrija este código con demoras.
Después de esta pregunta, se produjo una animada discusión, como resultado de lo cual decidí considerar este tema con más detalle.
Ya toqué este tema un poco en el
artículo anterior . Ahora con más detalle. Aquí hay un texto de ejemplo:
library IEEE; use IEEE.STD_LOGIC_1164.all; entity delta_delay is end delta_delay; architecture delta_delay of delta_delay is signal clk1 : std_logic:='0'; signal clk2 : std_logic; alias clk3 : std_logic is clk1;
Por simplicidad, todo el código se coloca en un componente.
Las señales
clk1 y
a son señales de exposición de prueba.
clk1 es una frecuencia de reloj de 100 MHz, la señal
a tiene dos ciclos de reloj a 0 y cuatro ciclos de reloj a 1. La señal
a se genera con un retraso de 1 nc con respecto al borde ascendente de
clk1 . Estas dos señales son suficientes para describir el problema.
Las diferentes opciones de código sintetizado pueden ser descomentadas y modeladas.
Considere la primera opción, este es un código sintetizado sin demora y que utiliza la reasignación de la frecuencia del reloj.
Aquí están los resultados de la simulación para la opción 1:

El diagrama muestra visualmente que las señales de reloj
clk1 y
clk2 coinciden, pero en realidad
clk2 se retrasa en relación con
clk1 por el valor del retraso delta. La señal
c retrasa la señal
b en un ciclo de reloj. Esto es correcto Pero la señal
d debe coincidir con la señal
c , pero esto no sucede. Funciona antes
Recordemos qué retraso delta es. Este es un concepto fundamental, se basa en el trabajo de simuladores de eventos, que utilizamos al modelar circuitos lógicos.
El simulador tiene el concepto de tiempo modelo. Todos los eventos en el sistema se adjuntan a este tiempo modelo. Veamos la formación de la frecuencia del reloj:
clk1 <= not clk1 after 5 ns;
Supongamos que ahora estamos modelando solo
clk1 , no hay otras señales.
En el momento inicial de tiempo,
clk1 es 0, esto se establece cuando se declara la señal. El simulador ve un requisito para invertir la señal. La palabra clave after da instrucciones para asignar un nuevo valor en 5 ns en relación con el tiempo actual del modelo. El simulador ve esto y toma nota de que en el momento 5 ns el valor de
clk1 será 1. Si bien este es un modelo futuro, por cierto, aún puede cambiar. A continuación, el simulador escanea las señales restantes. El simulador verá que para un momento dado en el tiempo del modelo, todo está hecho y él puede calcular el siguiente momento. Surge la pregunta: ¿cuál es el próximo momento? En principio, son posibles diferentes opciones. Por ejemplo, Simulink tiene un modo de tono fijo. En este caso, el tiempo del modelo aumentará en cierta cantidad y los cálculos continuarán.
Los sistemas de simulación de circuitos digitales funcionan de manera diferente. Pasan al siguiente evento, que ya han colocado en el futuro en su eje de tiempo modelo. En este caso, serán 5 ns. El simulador verá que
clk1 ha cambiado y calculará un nuevo valor para él, será 0, que también se colocará con un retraso de 5 ns en el eje de tiempo. Es decir serán 10 ns. Y así, el proceso continuará hasta que finalice el tiempo de simulación especificado.
Ahora agreguemos las señales
a y
b .
La señal
a se asigna en el proceso. Para la señal
b , la construcción condicional cuando se usa; La función rising_edge (
clk1 ) analiza
clk1 y devuelve
verdadero cuando el frente está fijo, es decir el valor anterior es 0 y el valor actual es 1.
En el tiempo de modelo 5 ns,
clk1 cambiará. Será igual a 1 y por el momento de 10 ns se creará un evento para establecerlo en 0. Pero esto es más tarde. Mientras todavía estamos en el momento de 5 ns y continuamos los cálculos. El simulador va a la línea
b<=a when rising_edge(clk1);
Dado que hay una función que depende de
clk1, el simulador calculará el valor de la función, verá que devuelve verdadero y asignará
b<=a;
Aquí comienza la parte más interesante: cuando es necesario cambiar el valor de
b . Parece necesario cambiarlo ahora, en este momento. Pero tenemos procesos paralelos. Quizás todavía necesitemos el valor de
b para calcular otras señales. Y aquí viene el concepto de retraso delta. Este es el valor mínimo por el cual cambia el tiempo del modelo. Este valor ni siquiera tiene la dimensión del tiempo. Esto es solo un delta. Pero puede haber muchos de ellos. Y tanto que el simulador simplemente se detiene por error o se congela.
Entonces, se establecerá un nuevo valor de
b por el momento 5 ns + 1 (1 es el primer retraso delta). El simulador verá que ya no hay nada que calcular por el momento 5 ns e irá al siguiente momento, y esto será 5 ns + 1; En este momento, rising_edge (ckl1) no funciona. Y el valor de b se establecerá en 1. Después de eso, el simulador irá al momento 10 nc.
Ahora agreguemos las señales
c ,
d y veamos por qué son diferentes.
Es mejor considerar el momento del tiempo del modelo 25 ns teniendo en cuenta los retrasos delta
delta | clk1 | clk2 | re (clk1) | re (clk2) | b | c | d |
---|
0 0 | 1 | 0 0 | cierto | falso | 0 0 | 0 0 | 0 0 |
---|
1 | 1 | 1 | falso | cierto | 1 | 0 0 | 0 0 |
---|
2 | 1 | 0 0 | falso | falso | 1 | 0 0 | 1 |
---|
Nota: re - rising_edge
La tabla muestra que en el momento en que se activa la función rising_edge (
clk2 ), el valor de
b ya
es 1. Y, por lo tanto, se asignará a la señal
d .
Basado en el sentido común, este no es el comportamiento que esperábamos del código. Después de todo, simplemente reasignamos la señal
clk1 a
clk2 y esperábamos que las señales
cyd fueran las mismas. Pero siguiendo la lógica del simulador, esto no es así. Esta es una característica
PRINCIPAL . Esta característica, por supuesto, debe ser conocida por los desarrolladores de proyectos FPGA y, por lo tanto, esta es una buena y necesaria pregunta para una entrevista.
¿Qué pasará durante la síntesis? Pero el sintetizador seguirá el sentido común, hará que las señales
clk2 y
clk1 sean una señal y, por lo tanto,
c y
d también serán las mismas. Y con ciertas configuraciones de sintetizador, también se combinarán en una señal.
Este es solo el caso cuando modelar y trabajar en equipos reales dará resultados diferentes. Quiero señalar que la razón de los diferentes resultados es la lógica diferente del simulador y el sintetizador. Esta es una diferencia BÁSICA. Esto no tiene nada que ver con las limitaciones de tiempo. Y si su proyecto en el modelo y en la plancha muestra resultados diferentes, entonces verifique, tal vez un diseño como ese se deslizó allí
clk2 <= clk1
Ahora la segunda pregunta es arreglar este código con demoras.
Esta es la opción 2. Puede ser descomentado y modelado.
Aquí está el resultado.

El resultado es correcto. Que paso Hagamos una tabla nuevamente para un intervalo de 25 - 36 ns
tiempo | delta | clk1 | clk2 | re (clk1) | re (clk2) | b | c | d |
---|
25 | 0 0 | 1 | 0 0 | cierto | falso | 0 0 | 0 0 | 0 0 |
---|
25 | 1 | 1 | 1 | falso | cierto | 0 0 | 0 0 | 0 0 |
---|
26 | 0 0 | 1 | 1 | falso | falso | 1 | 0 0 | 0 0 |
---|
35 | 0 0 | 1 | 0 0 | cierto | falso | 1 | 0 0 | 0 0 |
---|
35 | 1 | 1 | 1 | falso | cierto | 1 | 0 0 | 0 0 |
---|
36 | 0 0 | 1 | 1 | falso | falso | 1 | 1 | 1 |
---|
Se puede ver que el valor de
b no cambia en los momentos de los frentes
clk1 ,
clk2 . Un retraso de 1 ns toma el momento en que la señal cambia más allá de la zona de respuesta de borde. Este código se está acercando a la realidad. En un circuito real, hay tiempo para que se active el activador y para que se propague la señal. Este tiempo debe ser menor que el período de la frecuencia del reloj, de hecho, esto es lo que hace el trazador, y esto es lo que verifica el análisis de tiempo.
La causa del error es la reasignación de la señal del reloj mediante la asignación habitual en la que aparece un retraso delta. Sin embargo, el lenguaje VHDL tiene una construcción de alias. Esto le permite obtener un nombre diferente para la señal. Aquí está el anuncio:
alias clk3 : std_logic is clk1;
En el texto de ejemplo, puede descomentar la opción 3: funcionará correctamente.
Este ejemplo está escrito en VHDL. Tal vez este es el problema solo de este idioma? Pero aquí están las mismas opciones en Verilog.
Texto oculto `timescale 1 ns / 1 ps module delta_delay_2 (); reg clk1 = 1'b0; reg clk2; wire clk3; reg a = 1'b0; reg b; reg c; reg d; initial begin forever clk1 = #5 ~clk1; end initial begin repeat(10) begin #20 a = 1'b1; #60 a = 1'b0; end end
- Opción 1: sin demora. No funciona correctamente
- Opción 2 - con demoras. Funciona correctamente
- Opción 3: reasignación por cable. Funciona correctamente
Verilog tiene el concepto de reg y wire. En este caso, la reasignación de la señal del reloj a través del cable parece más natural. Esto es análogo a una asignación de alias en VHDL. Esto alivia un poco la tensión del problema, pero aún necesita saberlo.
Verilog también tiene el concepto de asignación bloqueante y no bloqueante. La asignación de señal
byc se puede escribir de otra manera:
always @(posedge clk1) begin c = b; b = a; end
Y puedes hacer esto:
always @(posedge clk1) begin b = a; c = b; end
Dependiendo del orden de las líneas, el resultado será diferente.
Volviendo al tema de la entrevista, quiero enfatizar una vez más que estas preguntas son para comprender la esencia del problema. Y a partir de la comprensión del problema, uno puede sacar varias conclusiones, por ejemplo, qué estilo de código usar. Personalmente, siempre uso la asignación de retraso.
Los archivos de muestra están disponibles aquí.