Diferencia entre always @* y always @(posedge

Diseño HDL con este lenguaje. Módulos y testbenchs. Estilos y trucos de codificación, etc. NOTA: dado que hay entornos como ISE que soportan Verilog pero no SystemVerilog, señalad dentro de un post que de lo que se va a tratar es SystemVerilog si es el caso.
Responder
Avatar de Usuario
spark2k06
PLA
Mensajes: 49
Registrado: 17 Ago 2018, 18:43

Diferencia entre always @* y always @(posedge

Mensaje por spark2k06 » 02 Sep 2018, 11:08

Esto está muy bien! en cuanto saque un rato lo pruebo porque me interesa mucho. Qué diferencia hay entre:

always @(posedge clk) begin

y

always @* begin

Gracias!

Edito: intuyo que tal vez el primero sea con el pulso clk en alto y el segundo siempre?
Siempre que cambia un valor de cualquier registro del módulo o cuando? Cuál es el trigger?

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: Diferencia entre always @* y always @(posedge

Mensaje por mcleod_ideafix » 02 Sep 2018, 17:59

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.

Código: Seleccionar todo

reg y;

always @* begin
  y = a & b;
end

Código: Seleccionar todo

reg y;

always @(posedge clk) begin
  y <= a & b;
end
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
always_combinacional.png (9.25 KiB) Visto 8955 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
always_secuencial.png (17.02 KiB) Visto 8955 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:

Código: Seleccionar todo

reg c, d;
always @* begin
  c = a | b;
  d = ~c;
end
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
asignacion_bloqueante.png (17.1 KiB) Visto 8955 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
asignacion_no_bloqueante.png (25.1 KiB) Visto 8955 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:

Código: Seleccionar todo

reg b;
always @* begin
  if (c == 0)
    b = a;
end
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
latch.png (9.04 KiB) Visto 8955 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
registro_carga_condicional_con_cke.png (17.75 KiB) Visto 8955 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
registro_carga_condicional_sin_cke.png (16.81 KiB) Visto 8955 veces
Con la diferencia de que el analizador de tiempos sí podrá analizar este circuito.

Avatar de Usuario
spark2k06
PLA
Mensajes: 49
Registrado: 17 Ago 2018, 18:43

Re: Diferencia entre always @* y always @(posedge

Mensaje por spark2k06 » 02 Sep 2018, 21:02

Gracias por la información. Supongo que no es problema que haya varios always @* begin dentro de un mismo módulo que hagan referencia a un mismo reg, o sí?
mcleod_ideafix escribió:
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.
Desde el propio entorno de desarrollo es posible visualizar la descripción resultante? Estaría bien poder seleccionar una parte de "código" y verla como su descripción equivalente, al igual que los ejemplos que has mostrado... es muy visual y ayuda a entender el circuito resultante.

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: Diferencia entre always @* y always @(posedge

Mensaje por mcleod_ideafix » 02 Sep 2018, 21:11

spark2k06 escribió:
02 Sep 2018, 21:02
Supongo que no es problema que haya varios always @* begin dentro de un mismo módulo que hagan referencia a un mismo reg, o sí?
Si la referencia es en la parte derecha de una asignación, no; no es problema. Si la referencia es en la parte izquierda, SI es un problema. La síntesis te dará el error de "multiple drivers .... etc."

Es decir, que una señal sólo puede ser asignada dentro de un bloque always, no en varios.
spark2k06 escribió:
02 Sep 2018, 21:02
Desde el propio entorno de desarrollo es posible visualizar la descripción resultante? Estaría bien poder seleccionar una parte de "código" y verla como su descripción equivalente, al igual que los ejemplos que has mostrado... es muy visual y ayuda a entender el circuito resultante.
Sí. En ISE, por ejemplo, en la tarea de "Synthesize", si la expandes, verás que hay un "View RTL Schematic" o algo parecido. Ahí puedes ver el circuito correspondiente a lo que has generado. Cada módulo aparecerá como una cajita y tú puedes elegirla y expandirla, accediendo a los submódulos que dicho módulo haya instanciado. En Quartus también hay una opción similar.

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: Diferencia entre always @* y always @(posedge

Mensaje por mcleod_ideafix » 02 Sep 2018, 21:47

De hecho, es muy diferente cómo se trata el tema de las partes izquierdas en varios always o en uno solo. Por ejemplo:

Código: Seleccionar todo

reg a;
always @* begin
  a = b & c;
  a = ~c;
end
Este always, al usar asignaciones bloqueantes, está diciendo que primero se hace el "a = b & c", y luego el "a = ~c"` con lo que en realidad el valor con el que se queda "a" es con el último. O sea, que ese always es equivalente a éste:

Código: Seleccionar todo

reg a;
always @* begin
  a = ~c;
end
En cambio si escribo esto otro:

Código: Seleccionar todo

reg a;
always @(posedge clk) begin
  a <= b & c;
  a <= ~c;
end
El sintetizador me dará un error porque lo que estoy intentando hacer es meter dos valores al mismo tiempo en "a". Es decir, que la señal "a" tiene múltiples fuentes (multiples drivers).

Si divido cualquiera de los dos always, el combinacional o el secuencial, en dos diferentes, cada uno de ellos con una de las asignaciones y el otro con la otra, en los dos casos me dará "error de multiples drivers". En el caso secuencial se entiende, pero en el combinacional... ¿por qué si en un solo always he podido asignar dos veces a "a" y no se ha quejado, no me lo deja hacer en dos always separados?

Código: Seleccionar todo

reg a;
always @* begin
  a = b & c;
end

always @* begin
  a = ~c;
end
La respuesta está en que la secuencialidad a la que obligan las asignaciones bloqueantes sólo se da dentro de un mismo bloque. Cada bloque always, sea del tipo que sea, es independiente de los demás, y por tanto todos se "ejecutan" al mismo tiempo. Con este segundo código, el sintetizador inferiría una puerta AND con salida en A, y por otra parte un inversor con salida en A, y aquí saltaría el error al detectar dos fuentes de señal intentando poner su valor en A.

OJO! Hay una excepción a la regla de "multiple drivers" en una señal, y es cuando todos los drivers de la señal pueden ponerse en alta impedancia:

Código: Seleccionar todo

reg a;
always @* begin
  if (d == 1)
    a = b;
  else
    a = Z;
end

always @* begin
  if (d == 0)
    a = c;
  else
    a = Z;
end
En este caso no tendrás error de multiples drivers porque el sintetizador será capaz de darse cuenta de que las condiciones del IF hacen que una y sólo una de las fuentes que dan su valor a "a" está activa, mientras que las demás están en alta impedancia.

multiple_driver_tri.png
multiple_driver_tri.png (18.02 KiB) Visto 8938 veces
Las FPGAs no suelen tener condición de alta impedancia en sus conexiones internas, sólo en los pines exteriores (algunas Virtex sí que lo tienen), por lo que esta forma de codificar suele ser traducida automáticamente. Así, probablemente el sintetizador no llegue a implementar la alta impedancia, sino que la sustituya por multiplexores, como con este código equivalente:

Código: Seleccionar todo

reg a;
always @* begin
  if (d == 1)
    a = b;
  else
    a = c;
end
multiple_driver_mux.png
multiple_driver_mux.png (9.14 KiB) Visto 8938 veces

Responder

Volver a “Verilog / SystemVerilog”