Menu

Valores X y Z en Verilog: señales desconocidas y de alta impedancia explicadas

Las señales de Verilog tienen cuatro valores posibles, no dos. Aquí está lo que x (desconocido) y z (alta impedancia) significan realmente en simulación, y cómo depurarlos.

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

Lógica de dos estados vs cuatro estados

En software, un bit es 0 o 1. En Verilog, un bit puede ser uno de cuatro valores:

  • 0 - el wire está excitado a bajo.
  • 1 - el wire está excitado a alto.
  • x - el valor del wire es desconocido. El simulador no puede decirlo.
  • z - el wire está en alta impedancia. Nada lo está excitando.

Ese modelo de cuatro estados existe porque el hardware tiene el mismo problema. Un wire real puede estar conectado a bajo, conectado a alto, indefinido (excitado por dos fuentes peleando entre sí) o flotando (sin driver). El simulador tiene que modelar los cuatro para ser útil.

Cómo aparece x

Ejecuta esto y mira la salida:

a está declarado pero nunca escrito, así que se queda en x. Sumar x + 5 produce x - cualquier aritmética con un desconocido produce un desconocido. La salida lee aaaa cantidades de x's.

Fuentes comunes de x en tus diseños:

  • Un reg declarado pero nunca reseteado (la mayoría del Verilog sintetizable usa un reset explícito para limpiarlos).
  • Una sentencia case sin default, alcanzada por un valor de entrada que ninguno de los casos emparejó.
  • Un wire que perdió su único driver tras una refactorización.
  • Una cadena if/else donde una rama no asigna una señal que la otra sí (latches x si no se cubre).
  • Leer pasado el final de un vector o array.

Propagación de X: un pequeño bit de x arruina todo

Lo cruel de x es que se propaga. Un bit x en un operando convierte todo el resultado en x:

Fíjate en que 0 & x es 0 (AND con 0 siempre es 0) y 1 | x es 1 (OR con 1 siempre es 1). El simulador es bit a bit pesimista pero sigue respetando las identidades. La aritmética y la comparación no son tan generosas.

Por esto un único registro no inicializado puede hacer que un bus de salida entero vaya xxxx. Sigue hacia atrás desde cualquier x y encontrarás la fuente.

Cómo aparece z

z es el valor de un wire que nadie está excitando:

Dos patrones en ese fragmento:

  • floating solo se declara y nunca se excita. Por defecto va a z.
  • data_out es un tri-state deliberado. Cuando enable está en bajo, la salida libera explícitamente a z. Así es como un driver de bus "suelta" para que otro driver pueda tomar el control.

En lógica interna, z casi siempre es incorrecto. En un pin bidireccional o un bus compartido, z es exactamente correcto.

Comparar con == vs ===

El operador de igualdad regular == devuelve x cuando cualquier operando tiene un bit x o z:

=== (y su compañero !==) hace una comparación estricta bit a bit incluyendo x y z. Úsalos siempre que necesites testear a favor o en contra de x/z en un testbench. === no es sintetizable, pero dentro de un bloque initial en un testbench eso no importa.

La función de sistema $isunknown(expr) es la forma más limpia de preguntar "¿esta expresión tiene algún bit x o z?" - devuelve 1 si sí, 0 si no.

Usar x como un don't-care intencional

Un patrón controvertido pero legítimo: 'x en un caso default de una máquina de estados le dice al sintetizador "este estado es inalcanzable, optimiza libremente":

case (state)
    IDLE:    next_state = go ? RUNNING : IDLE;
    RUNNING: next_state = done ? IDLE : RUNNING;
    default: next_state = 'x;   // unreachable
endcase

El sintetizador puede usar el x para fusionar estados y reducir el conteo de puertas. En simulación, si tu razonamiento estaba equivocado y el default se alcanza, verás x propagar desde next_state y el bug se hace visible inmediatamente.

Usa esto solo cuando hayas pensado a fondo si el default realmente es inalcanzable. Si no lo has hecho, pon el default a un estado seguro en su lugar.

Receta común de depuración

Estás mirando una forma de onda llena de x. La receta:

  1. Encuentra el x más temprano. Recorre la forma de onda hacia atrás en el tiempo. La señal más temprana en ir a x es la más cercana a la fuente.
  2. Encuentra su driver. Abre el código fuente. ¿Qué asigna esta señal? ¿Un assign? ¿Un bloque always?
  3. Comprueba las entradas al driver. Si el RHS del driver tiene algún x, la propagación está haciendo lo que se supone - el bug está aguas arriba.
  4. Si el driver tiene entradas limpias pero produce x, el driver está incompleto. Un case al que le falta un default, un if al que le falta un else, un registro al que le falta un reset.

La mayoría de los bugs tipo tormenta de x se reducen a uno de estos: falta reset, falta default o un submódulo que no está conectado.

Qué viene a continuación

Ya tienes la historia completa de tipos de datos: wire/reg, vectores, parámetros, literales numéricos y el modelo lógico de cuatro estados. El siguiente capítulo empieza a usar todo eso para construir expresiones - operadores de todos los sabores, incluidos los operadores a nivel de bit que no tendrían sentido en software.

Preguntas frecuentes

¿Qué significa x en Verilog?

x es el valor desconocido. Una señal que es x podría ser 0 o 1 - el simulador no puede decirlo. Aparece cuando una señal no se ha excitado, cuando dos drivers entran en contención, cuando se lee un registro antes del reset, o en cualquier sitio donde un comportamiento indefinido se propagaría silenciosamente. Trata x como una señal de bug; casi nunca significa lo que quieres.

¿Qué significa z en Verilog?

z es el valor de alta impedancia - el wire no está siendo excitado en absoluto. Es el estado legítimo de salidas tri-state (buses de datos, pines bidireccionales), pero en señales internas z normalmente es un error que significa 'no hay nada conectado aquí'. Los sintetizadores rechazan la mayoría de los patrones con z fuera de enables tri-state explícitos.

¿Por qué mi salida de Verilog es xxxx?

Casi siempre porque una señal no está siendo excitada por nada, o porque una operación propagó x desde otra señal. Camina hacia atrás: qué señal es x, qué la alimenta, ¿está activo el driver? Los culpables comunes son: faltan casos default en sentencias case, registros que no se resetean y wires que perdieron su driver tras una refactorización.

¿Cómo se comprueba x o z en Verilog?

Usa el operador ===, que compara bit por bit exacto incluyendo x y z. a === 1'bx es verdadero cuando a es realmente x. El == normal devuelve x cuando cualquier operando tiene un bit x, lo que significa que a == 1'bx nunca es la respuesta que quieres. También está $isunknown(a), que es un booleano limpio.

¿Se puede asignar x como valor por defecto en Verilog?

Sí, y es una técnica deliberada en sentencias case: default: out = 'x; le dice al sintetizador 'prometo que este case nunca sucede, optimiza libremente'. El coste es que si sucede en simulación, x se propaga y ves el bug. Úsalo cuando ya hayas probado que el default es inalcanzable, no como una forma de evitar escribir el case.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR