Menu

Valori X e Z in Verilog: segnali sconosciuti e ad alta impedenza

I segnali di Verilog hanno quattro valori possibili, non due. Ecco cosa significano davvero x (sconosciuto) e z (alta impedenza) in simulazione e come fare il debug.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Logica a due stati e a quattro stati

Nel software un bit vale 0 o 1. In Verilog un bit può assumere uno di quattro valori:

  • 0: il wire è pilotato basso.
  • 1: il wire è pilotato alto.
  • x: il valore del wire è sconosciuto. Il simulatore non può saperlo.
  • z: il wire è in alta impedenza. Nessuno lo sta pilotando.

Questo modello a quattro stati esiste perché l'hardware ha lo stesso problema. Un wire reale può essere collegato a massa, collegato all'alimentazione, indefinito (pilotato da due sorgenti in conflitto) o flottante (senza alcun driver). Per essere utile, il simulatore deve modellare tutti e quattro i casi.

Come compare x

Esegui questo codice e guarda l'output:

a è dichiarato ma mai scritto, quindi resta a x. Sommare x + 5 produce x: qualsiasi operazione aritmetica con un valore sconosciuto produce un valore sconosciuto. Nell'output, al posto di aaaa compare una fila di x.

Le fonti più comuni di x nei tuoi progetti:

  • Un reg dichiarato ma mai resettato (la maggior parte del Verilog sintetizzabile usa un reset esplicito per azzerarli).
  • Un'istruzione case senza default, raggiunta da un valore di ingresso che nessun caso gestisce.
  • Un wire che ha perso il suo unico driver dopo un refactoring.
  • Una catena if/else in cui un ramo non assegna un segnale che l'altro invece assegna (se il caso non è coperto si crea un latch che tiene x).
  • Leggere oltre la fine di un vettore o di un array.

Propagazione di X: basta un bit x per rovinare tutto

La cosa crudele di x è che si propaga. Un solo bit x in un operando rende x l'intero risultato:

Nota che 0 & x vale 0 (AND con 0 dà sempre 0) e 1 | x vale 1 (OR con 1 dà sempre 1). Il simulatore è pessimista bit per bit ma rispetta comunque queste identità. L'aritmetica e i confronti non sono altrettanto generosi.

Ecco perché un solo registro non inizializzato può portare a xxxx un intero bus di uscita. Risali all'indietro da qualsiasi x e troverai la sorgente.

Come compare z

z è il valore di un wire che nessuno sta pilotando:

In quel frammento ci sono due pattern:

  • floating è solo dichiarato e mai pilotato. Per impostazione predefinita vale z.
  • data_out è un tri-state voluto. Quando enable è basso, l'uscita viene rilasciata esplicitamente a z. È così che il driver di un bus "molla la presa" per lasciare il controllo a un altro driver.

Nella logica interna, z è quasi sempre sbagliato. Su un pin bidirezionale o su un bus condiviso, z è esattamente ciò che serve.

Confrontare con == e ===

Il normale operatore di uguaglianza == restituisce x quando uno dei due operandi ha un bit x o z:

=== (e il suo compagno !==) esegue un confronto rigoroso bit per bit, compresi x e z. Usalo ogni volta che in un testbench devi verificare la presenza o l'assenza di x/z. === non è sintetizzabile, ma dentro un blocco initial di un testbench non importa.

La funzione di sistema $isunknown(expr) è il modo più pulito per chiedere "questa espressione ha qualche bit x o z?": restituisce 1 se sì, 0 se no.

Usare x come don't-care intenzionale

Un pattern discusso ma legittimo: 'x nel caso default di una macchina a stati dice al sintetizzatore "questo stato è irraggiungibile, ottimizza pure":

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

Il sintetizzatore può sfruttare la x per fondere stati e ridurre il numero di porte. In simulazione, se il tuo ragionamento era sbagliato e il default viene raggiunto, vedrai x propagarsi da next_state e il bug diventa subito visibile.

Usalo solo dopo aver verificato con attenzione che il default sia davvero irraggiungibile. Se non l'hai fatto, imposta invece il default su uno stato sicuro.

Ricetta di debug tipica

Hai davanti una forma d'onda piena di x. La ricetta:

  1. Trova la x più precoce. Scorri la forma d'onda indietro nel tempo. Il primo segnale che passa a x è quello più vicino alla sorgente.
  2. Trova il suo driver. Apri il sorgente. Cosa assegna questo segnale? Un assign? Un blocco always?
  3. Controlla gli ingressi del driver. Se il lato destro del driver contiene qualche x, la propagazione sta facendo il suo lavoro: il bug è più a monte.
  4. Se il driver ha ingressi puliti ma produce x, il driver è incompleto. Un case senza default, un if senza else, un registro senza reset.

Quasi tutti i bug con una tempesta di x si riducono a uno di questi: reset mancante, default mancante o un sottomodulo non collegato.

Cosa viene dopo

Ora hai il quadro completo dei tipi di dato: wire/reg, vettori, parametri, letterali numerici e il modello logico a quattro stati. Il prossimo capitolo inizia a usare tutto questo per costruire espressioni: operatori di ogni tipo, compresi quelli a livello di bit che nel software non avrebbero senso.

Domande frequenti

Cosa significa x in Verilog?

x è il valore sconosciuto. Un segnale che vale x potrebbe essere 0 oppure 1: il simulatore non può saperlo. Compare quando un segnale non è pilotato, quando due driver sono in conflitto, quando un registro viene letto prima del reset o in qualsiasi punto in cui un comportamento indefinito si propagherebbe altrimenti in silenzio. Considera x un segnale di bug: quasi mai significa ciò che vuoi.

Cosa significa z in Verilog?

z è il valore ad alta impedenza: il wire non è pilotato affatto. È lo stato legittimo delle uscite tri-state (bus dati, pin bidirezionali), ma sui segnali interni z di solito è un errore che significa 'qui non è collegato niente'. I sintetizzatori rifiutano la maggior parte dei pattern con z al di fuori di enable tri-state espliciti.

Perché il mio output Verilog è xxxx?

Quasi sempre perché un segnale non è pilotato da niente, oppure perché un'operazione ha propagato x da un altro segnale. Risali all'indietro: quale segnale è x, cosa lo alimenta, il driver è attivo? I colpevoli più comuni sono i casi default mancanti nelle istruzioni case, i registri senza reset e i wire che hanno perso il loro driver dopo un refactoring.

Come si controlla se un segnale è x o z in Verilog?

Usa l'operatore ===, che confronta bit per bit in modo esatto, compresi x e z. a === 1'bx è vero quando a vale davvero x. Il normale == restituisce x quando uno dei due operandi ha un bit x, quindi a == 1'bx non ti dà mai la risposta che cerchi. C'è anche $isunknown(a), che restituisce un booleano pulito.

Si può assegnare x come valore di default in Verilog?

Sì, ed è una tecnica voluta nelle istruzioni case: default: out = 'x; dice al sintetizzatore 'ti prometto che questo caso non si verifica mai, ottimizza pure'. Il prezzo è che, se in simulazione si verifica, x si propaga e vedi il bug. Usalo quando hai già dimostrato che il default è irraggiungibile, non come scorciatoia per non scrivere il caso.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA