Menu

Asignación continua en Verilog: la sentencia assign

Cómo funciona assign - la relación siempre cierta que describe, qué puede y qué no puede excitar, y los patrones en los que brilla comparado con el código procedural.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

assign describe una verdad permanente

Una declaración wire crea una señal. Un assign describe qué la excita. La relación es continua: lo que sea que esté en el lado derecho, el lado izquierdo es igual a ello, en cada instante de tiempo simulado:

wire y;
assign y = a & b;

Dos cosas implicadas por ese par de líneas:

  • y existe como un wire en el circuito.
  • y es en todo momento el AND bit a bit de a y b. Cambia cualquier entrada y y la sigue.

No hay reloj ni evento que dispare la actualización. El simulador ve que a o b cambian, marca y como sucio y vuelve a evaluar la expresión. En hardware esto mapea a un par de puertas AND: combinacional, sin estado, instantáneo.

La forma implícita

Puedes combinar la declaración y la asignación en una sola línea:

wire y = a & b;

Eso es lo mismo que las dos líneas de arriba. Útil para wires en línea que a nadie fuera de este ámbito le importan. Muchas guías de estilo prefieren la forma implícita porque pone la declaración justo al lado de la ecuación.

Qué puede excitar assign

El destino de assign debe ser un tipo net - en Verilog clásico, eso es wire (u otro de los primos más raros como tri, wand, wor). No puede ser un reg. Si declaras accidentalmente tu destino como reg:

reg y;
assign y = a & b;   // ERROR: cannot drive reg with assign

El compilador te lo dirá. O cambia el destino a wire, o mueve la lógica a un bloque always @(*) donde reg es el destino legal.

El lado derecho puede ser cualquier cosa que evalúe a un valor: literales, señales, parámetros, expresiones con operadores, llamadas a funciones. Puede mezclar varios anchos de entrada; se aplican las reglas estándar de ensanchamiento.

Cuándo usar assign vs always

Ambos pueden producir lógica combinacional. La elección es mayormente sobre cómo se lee el código:

  • assign es mejor cuando la relación es una sola expresión. Sumadores, muxes simples construidos con ?:, máscaras, bits de paridad, cualquier cosa que puedas escribir en una línea.
  • always @(*) es mejor cuando necesitas sentencias procedurales. Sentencias case multivía, if/else if anidados, cualquier cosa que se beneficie de regs intermedios con nombre. Cubrimos esto en Always Block.

Aquí está el mismo mux 4 a 1 escrito de las dos formas:

Los dos módulos sintetizan a esencialmente el mismo multiplexor. La versión con assign es una línea de código; la versión con always son seis. Para cuatro casos está cerca; para dieciséis casos el bloque case es claramente más fácil de leer.

Patrones comunes

Lógica combinacional plana

assign sum   = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);

Una expresión, un wire. El pan y la mantequilla de assign.

Un mux 2 a 1

assign out = sel ? a : b;

Una única expresión condicional - el sintetizador la convierte en un único mux 2 a 1. La forma más limpia posible de "selecciona entre a y b".

Empaquetar bits

assign status = {error, overflow, ready, busy, 4'b0};

La concatenación en la derecha de un assign es cómo empaquetas flags en un byte de estado. El resultado se calcula y excita continuamente.

Salida tri-state

assign data_pin = output_enable ? data_out : 1'bz;

Cuando output_enable está en alto, excita el pin. Cuando está en bajo, libera a alta impedancia. Este es el patrón canónico en los pines del chip donde varios drivers podrían compartir un wire.

Concurrencia: varios assigns no son secuenciales

Un recordatorio que no dejará de ser relevante: varias sentencias assign en el mismo módulo todas corren en paralelo. No son una secuencia:

assign y = a & b;     // exists at all times
assign z = a | b;     // also exists at all times, independently

El orden en el archivo es irrelevante. Ambas ecuaciones son simultáneamente verdaderas. El sintetizador puede disponer la puerta AND a la izquierda de la OR o a la derecha; no importa, ambas puertas funcionan continuamente.

Si quieres comportamiento que parezca secuencial, estás recurriendo a un bloque always (y probablemente a un reloj). Eso es un capítulo distinto.

Múltiples drivers: el patrón del bus

Un wire puede tener más de un assign que lo apunte, pero casi nunca quieres esto excepto para buses tri-state. Dos drivers peleando por un wire produce comportamiento indefinido:

assign y = a;
assign y = b;   // BAD - two drivers, simulator picks one or x's it out

El patrón legítimo: cada driver libera a z cuando está inactivo, y a lo sumo uno está activo en cualquier momento.

assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;

Eso funciona porque en cualquier momento dado, a lo sumo uno de los dos ternarios produce un valor distinto de z. El valor real del wire es el del driver que no esté liberando.

En lógica interna - cualquier sitio que no sea un pin de chip o un bus on-chip compartido - un driver por wire. Los bugs de múltiples drivers son desagradables de depurar.

Qué no puede hacer assign

Algunas cosas para las que assign es la herramienta equivocada:

  • Almacenamiento. assign describe relaciones combinacionales; no puede introducir un flip-flop. Si necesitas que un valor sea recordado entre ciclos de reloj, eso es un bloque always @(posedge clk).
  • Lógica procedural de varios pasos. No puedes escribir if/else o case dentro de un assign. Lo más cercano que consigues es encadenar ?:, que se vuelve feo pasadas tres ramas.
  • Excitar registros desde dentro de un bloque procedural. Los destinos reg necesitan asignación procedural, no assign.

Conocer los límites es cómo decides cuándo cambiar a always.

Qué viene a continuación

Ya has visto el lado estructural completo de Verilog: declarar módulos, instanciarlos y cablear lógica combinacional con assign. El siguiente capítulo entra en bloques procedurales - las construcciones initial y always donde el tiempo y la ordenación empiezan a importar.

Preguntas frecuentes

¿Qué es una asignación continua en Verilog?

assign target = expression; declara una relación permanente y continua: target siempre es igual a expression. Cuando cualquier señal en la expresión cambia, el simulador vuelve a evaluar el lado derecho y actualiza target. No hay reloj, no hay evento - la relación es verdadera en cada instante de tiempo.

¿Qué puedes tener como destino con un assign en Verilog?

assign puede excitar un wire, pero nunca un reg. El destino tiene que ser un tipo net. Si necesitas asignar a algo dentro de un bloque always, declara eso como reg. El compilador rechazará assign x = ... si x es reg, y rechazará x = ... dentro de un always si x es wire.

¿Cuándo debería usar assign vs un bloque always?

Usa assign para lógica combinacional simple - una expresión de entrada, una señal de salida, sin necesidad de if/else. Usa always @(*) cuando la lógica necesita sentencias procedurales (un case, una cadena if/else if, un loop for). Ambos producen hardware combinacional; la elección es por legibilidad.

¿Puedes tener varios assigns al mismo wire en Verilog?

Solo si estás modelando un bus tri-state donde cada driver libera el wire a z cuando está inactivo. Dos assigns intentando excitar el wire a valores definidos al mismo tiempo producen contención - el simulador puede elegir uno, puede dejar la señal en X, depende de la herramienta. Para lógica combinacional normal, un driver por wire.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR