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
regdichiarato ma mai resettato (la maggior parte del Verilog sintetizzabile usa un reset esplicito per azzerarli). - Un'istruzione
casesenzadefault, raggiunta da un valore di ingresso che nessun caso gestisce. - Un
wireche ha perso il suo unico driver dopo un refactoring. - Una catena
if/elsein cui un ramo non assegna un segnale che l'altro invece assegna (se il caso non è coperto si crea un latch che tienex). - 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 valez.data_outè un tri-state voluto. Quandoenableè basso, l'uscita viene rilasciata esplicitamente az. È 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:
- Trova la
xpiù precoce. Scorri la forma d'onda indietro nel tempo. Il primo segnale che passa axè quello più vicino alla sorgente. - Trova il suo driver. Apri il sorgente. Cosa assegna questo segnale? Un
assign? Un bloccoalways? - 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. - Se il driver ha ingressi puliti ma produce
x, il driver è incompleto. Uncasesenza default, unifsenzaelse, 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.