always @* se usa para crear un bloque combinacional, hecho con puertas lógicas, sin memoria, sin flipflops. Por ejemplo, sumadores, comparadores, multiplexores, decos, cosas así. Las variables que son destinos de asignaciones en este tipo de bloques son señales (hilos), aunque en Verilog se definan como "reg" (esta parte de Verilog es confusa cuando estás empezando, ya que "reg" a veces denota una señal, y a veces denota una memoria.
always @(posedge se usa para crear un bloque secuencial, en el que puede haber puertas lógicas y parte combinacional, pero también hay memoria (flipflops). De hecho, los destinos de las asignaciones serán flipflops definidos con "reg" (que en este caso sí que se referirá a memoria).
Recordad que siempre hay que imaginar que lo que estamos viendo en código no es un programa, sino la descripción de un circuito. Supongamos las dos siguientes secuencias de código.
En ambos casos, las señales a, b y clk suponemos que están definidas fuera del bloque, no nos importa cómo ni donde. Lo que nos interesa es el bloque en sí, y lo que es "y" en cada caso.
En el primer código, "y" es un cable, un hilo, una señal... llámalo como quieras. Entonces, ¿por qué no se ha definido como wire? Pues porque una variable "wire" no puede aparecer como destino de una asignación dentro de un bloque always. Sólo las "reg". Las variables "wire" sólo pueden aparecer en lo que se llama "asignaciones continuas" (assign)
Seguimos con el primer código: en el bloque del always lo que aparece es y = a & b. Esto lo que infiere es sencillamente una puerta AND con A y B como entradas, y salida conectada a la señal Y. Ya está. Un circuito combinacional muy sencillito.
- always_combinacional.png (9.25 KiB) Visto 9425 veces
Por otra parte, en el segundo código, "y" es un biestable, un flipflop, una memoria en definitiva, disparado por flanco de subida o flanco positivo (posedge) de reloj. El segundo código produce este circuito:
- always_secuencial.png (17.02 KiB) Visto 9425 veces
En un always combinacional se suele usar asignación no bloqueante (con el signo = ). En síntesis no hay en realidad diferencia entre una y otra pero algunas herramientas de síntesis no te van a dejar mezclar de un tipo u otro en el mismo bloque always. En simulación sí que hay diferencia en cómo se comportan. La asignación bloqueante sirve para poder modelar lógica combinacional compleja usando varias asignaciones cortas en lugar de una muy larga. Un ejemplo (bastante idiota) sería:
Hasta que no termina una asignación no empieza la otra. No ocurren a la vez. Pero no lo hacen no sólo porque aparezca un signo = sino porque además la segunda asignación tiene dependencia real de datos respecto de la primera. Es decir, que la segunda asignación usa en su parte derecha una expresión para la cual no hay un valor actualizado hasta que no termine la primera asignación.
Se ve mejor si muestro el circuito que produce esto:
- asignacion_bloqueante.png (17.1 KiB) Visto 9425 veces
En un always secuencial...
Código: Seleccionar todo
reg c, d;
always @(posedge clk) begin
c <= a | b;
d <= ~c;
end
- asignacion_no_bloqueante.png (25.1 KiB) Visto 9425 veces
Parece que tenemos algo parecido a lo anterior, pero con biestables en medio. En realidad, la presencia de los biestables hace que este otro circuito se comporte de forma radicamente diferente del primero.
Supongamos que A y B valen ambas 1. En el primer circuito (always combinacional), se realiza el OR de A y B, obteniendo 1.
A CONTINUACION, ese valor que sería C, es invertido para llegar a la señal D, que acaba valiendo 0.
Supongamos ahora el segundo circuito, y supongamos que ambos biestables están borrados (a 0). Mientras no ocurra un flanco positivo en la señal CLK, la salida D no cambia, aunque lo hagan A y B.
Entonces, ocurre un flanco positivo de reloj y en ese momento tanto A como B valen 1. Lo que ocurre es que el primer flipflop se carga con el valor 1 (el resultado de A OR B).
AL MISMO TIEMPO que está ocurriendo esto, el valor que había hasta ese momento en el biestable C (que era un 0 porque estaba borrado) es invertido y pasado al biestable D, que por tanto guarda un 1. La salida de ese biestable va a la señal D, que valdrá por tanto 1.
Nótese que como ambas asignaciones ocurren al mismo tiempo, es perfectamente posible escribir el bloque always anterior de esta otra forma:
Código: Seleccionar todo
reg c, d;
always @(posedge clk) begin
d <= ~c;
c <= a | b;
end
El circuito, y por tanto el comportamiento resultante, no varía.
INFERENCIA DE LATCHES:
Como un always @* genera, o debe generar, un circuito combinacional, las variables "reg" que aparecen a la izquierda en las asignaciones de un bloque de estos deben tener siempre una asignación. Si hay sentencias if-else if-else if, cada variable debe tener un valor (estar asignada) en todas las ramas del if.
Es un (¿error?) común cuando se escriben bloques always combinacionales en los que hay sentencias condicionales, "olvidarse" de dar un valor a una variable en una rama. Por ejemplo:
Estamos diciendo que genere un circuito combinacional en el que la señal "b" toma el valor de "a" si c vale 0, pero no estamos diciendo qué valor toma si c no vale 0. El sintetizador inferirá que "en ese caso, b se queda con el valor que tenga". Pero el problema es que esa inferencia hace que "b" sea una memoria, y eso es algo que no se permite en un circuito combinacional.... ¿o sí?
El circuito que se generaría sería éste:
- latch.png (9.04 KiB) Visto 9425 veces
O sea, un circuito combinacional con un lazo de realimentación: eso es precisamente la definición de un circuito secuencial. En simulación, este tipo de cosas es admisible, pero en síntesis no tiene por qué serlo. Hay sintetizadores que al detectar esto infieren que la señal B es en realidad un latch, es decir, un registro cuya carga se controla no por un flanco positivo o negativo de un reloj, sino por el nivel (alto o bajo) que tenga ese reloj. El latch "accidental" que se ha creado es activo a nivel bajo.
Lo malo de los latches es que hay FPGAs que no los implementan como primitivas, y entonces este tipo de construcciones se realizan con las LUTs. Y lo malo de implementarlos con LUTs es que los análisis temporales fallan y no dan una estimación correcta de si el diseño funcionará a la frecuencia deseada o no. En otras FPGAs sí que existen los latches como primitivas, pero se exige que la señal de reloj (señal de carga más bien) sea una señal global, en un buffer global, como los relojes.
Aunque los latches no son el diablo, no es habitual que los necesitemos en un diseño, por lo que las herramientas de síntesis nos van a dar un warning si ven always combinacionales en donde hay señales que no tienen una asignación en todas las ramas de todos los ifs, "por si acaso" no era eso lo que queríamos describir.
En un always secuencial (posedge) este código produce un resultado bastante diferente:
Código: Seleccionar todo
reg b;
always @(posedge clk) begin
if (c == 0)
b <= a;
end
Si la FPGA sobre la que se sintetiza soporta biestables con habilitación de reloj, el circuito es como éste:
- registro_carga_condicional_con_cke.png (17.75 KiB) Visto 9425 veces
En este tipo de biestables existe una señal CKE (ClocK Enable) que permite habilitar o no el reloj. Internamente hay una puerta AND entre la señal de reloj entrante y la señal CKE. Si C es 0, su inversa es 1, y se habilita el biestable para la carga (cuando haya un flanco positivo de reloj). Si C vale 1, su inversa es 0, y el biestable no carga nada aunque ocurra un flanco positivo de reloj.
Si la arquitectura de la FPGA no soporta biestables con habilitación de reloj, entonces lo que se infiere es algo parecido al latch del always combinacional:
- registro_carga_condicional_sin_cke.png (16.81 KiB) Visto 9425 veces
Con la diferencia de que el analizador de tiempos sí podrá analizar este circuito.