Una asignación en Verilog es la relación que determina cómo obtiene su valor una señal, ya sea de forma continua o dentro de un bloque procedimental.
Si en el artículo anterior vimos los tipos de datos (wire y reg), hoy vamos a ver cómo se les da valor. En programación clásica (C, Python), solo existe una forma de asignar: variable = valor.
En Verilog, al estar describiendo hardware, la cosa se complica. Tenemos dos filosofías distintas:
- Conectar cables permanentemente (
assign). - Definir comportamientos ante eventos (
always).
Y por si fuera poco, dentro de los bloques de comportamiento, tenemos dos tipos de igualdad: la Bloqueante (=) y la No Bloqueante (<=).
Entender esto es la barrera que separa a quien “copia código” de quien “diseña hardware”. Vamos a derribar esa barrera.
Tabla práctica de asignaciones
| Objetivo | Herramienta | Tipo de Dato | Operador |
|---|---|---|---|
| Conexión simple | assign | wire | = |
| Lógica Combinacional Compleja | always @(*) | reg | = |
| Lógica Secuencial (Memoria) | always @(posedge clk) | reg | <= |
Asignación continua (assign)
La sentencia assign se utiliza para describir lógica combinacional pura.
Imagina que tienes un soldador y estás conectando cables físicos. Una vez que sueldas un cable entre una puerta AND y una salida, esa conexión es permanente. Siempre que cambien las entradas, la salida cambiará instantáneamente (bueno, tras un pequeñísimo retardo físico).
- Solo funciona con tipos
wire. - Se escribe fuera de los bloques
always. - Es ideal para ecuaciones booleanas sencillas.
module puertas_logicas (
input wire a,
input wire b,
output wire y_and,
output wire y_mux
);
// Asignación continua: Esto OCURRE SIEMPRE, en paralelo
assign y_and = a & b;
// Podemos usar el operador ternario para hacer multiplexores
assign y_mux = (a == 1'b1) ? b : 1'b0;
endmodulePiensa en assign como en una fórmula de Excel. Si cambias la celda A1, el resultado en C1 cambia al momento. No hay “pasos”, es una relación permanente.
Bloques procedimentales (always)
Los bloques always nos permiten describir el circuito de una forma más parecida a un algoritmo (“Si pasa esto, haz aquello”). Pero cuidado, sigue siendo hardware.
Un bloque always se ejecuta cuando ocurre un evento en su lista de sensibilidad.
Tenemos dos sabores principales de always, y conviene no mezclarlos:
El bloque always combinacional (always @*)
Se usa para describir lógica combinacional compleja (como un gran multiplexor o una máquina de estados) usando sentencias cómodas como if-else o case.
- Lista de sensibilidad: Usamos
*(oalways_comben SystemVerilog) para decirle al sintetizador: “Ejecuta esto si cambia CUALQUIERA de las señales que leo dentro”. - Genera puertas lógicas, NO genera memoria.
- Debemos usar Asignación Bloqueante (
=).
// Ejemplo: Un decodificador simple
reg salida; // Debe ser 'reg' porque se asigna dentro de un always
always @(*) begin
if (entrada == 1'b1) begin
salida = 1'b0;
end else begin
salida = 1'b1;
end
endEl bloque always secuencial (always @(posedge clk))
Este es el creador de Flip-Flops. Se usa para circuitos que tienen memoria y avanzan al ritmo del reloj.
- Lista de sensibilidad: Solo reacciona al flanco del reloj.
- Debemos usar Asignación No Bloqueante (
<=).
Asignación bloqueante (=) y no bloqueante (<=)
Esta diferencia provoca muchos de los errores difíciles de detectar al empezar con Verilog.
En un bloque always, podemos asignar valores de dos formas. La diferencia radica en cuándo se actualiza el valor.
Asignación bloqueante (=)
Funciona como en C++. La línea se ejecuta, el valor se actualiza inmediatamente, y la siguiente línea ya ve el valor nuevo.
- Uso: Solo en lógica combinacional (
always @*).
Asignación no bloqueante (<=)
Significa “Calcula el valor ahora, pero no lo guardes todavía”. Todos los cálculos del bloque se hacen en paralelo, y al final del ciclo de reloj, todas las variables se actualizan a la vez.
- Uso: Solo en lógica secuencial (
always @(posedge clk)).
Ejemplo práctico: intercambio de valores
Vamos a intentar intercambiar el valor de dos registros, A y B.
Intento con Bloqueante (=) - ERROR
always @(posedge clk) begin
a = b; // 'a' toma el valor de 'b' inmediatamente
b = a; // 'b' toma el valor de 'a'... ¡pero 'a' ya es igual a 'b'!
end
// Resultado: Ambos acaban valiendo lo que valía 'b'. Hemos perdido el dato de 'a'.Forma Correcta con No Bloqueante (<=) - ÉXITO
always @(posedge clk) begin
a <= b; // "Planifico" que 'a' será 'b'
b <= a; // "Planifico" que 'b' será lo que vale 'a' AHORA MISMO (el valor viejo)
end
// Al final del ciclo, ambos se actualizan cruzados. ¡Perfecto!= es como pasar una nota en clase. Yo te la doy, tú la lees y se la pasas al siguiente. Es secuencial
<= es como si el profesor pide copiar en la libreta lo que pone en la pizarra. Todos miran el valor anterior y escriben a la vez.
Generación involuntaria de latches
Hay un peligro al usar always para lógica combinacional. Si usamos if pero se nos olvida el else, o usamos un case que no cubre todas las opciones, estamos diciendo: “Si no se cumple la condición, mantén el valor anterior”.
“Mantener el valor” implica memoria. Pero si no estamos usando un reloj, la herramienta intentará crear un elemento de memoria asíncrono llamado Latch.
Un latch no es incorrecto por definición, pero casi siempre aparece aquí sin que lo hayamos querido y complica el análisis de tiempos.
// CÓDIGO PELIGROSO: Genera Latch
always @(*) begin
if (activar == 1'b1)
salida = 1'b1;
// Falta el 'else'! ¿Qué pasa si activar es 0?
// La FPGA creará un Latch para recordar el estado.
endSolución: Siempre pon un valor por defecto al principio del bloque o asegúrate de cubrir todos los else.
// CÓDIGO SEGURO
always @(*) begin
salida = 1'b0; // Valor por defecto
if (activar == 1'b1)
salida = 1'b1;
end