Lógica de dois estados vs quatro estados
Em software, um bit é 0 ou 1. Em Verilog, um bit pode ser um de quatro valores:
0- o wire é conduzido para baixo.1- o wire é conduzido para cima.x- o valor do wire é desconhecido. O simulador não sabe.z- o wire está em alta impedância. Nada está acionando ele.
Esse modelo de quatro estados existe porque hardware tem o mesmo problema. Um wire real pode estar amarrado em baixo, amarrado em alto, indefinido (acionado por duas fontes brigando) ou flutuando (sem driver nenhum). O simulador precisa modelar os quatro para ser útil.
Como x aparece
Execute isto e olhe a saída:
a é declarado mas nunca escrito, então fica em x. Somar x + 5 produz x - qualquer aritmética com um desconhecido produz um desconhecido. A saída diz xxxx na coluna de a.
Fontes comuns de x em seus designs:
- Um
regdeclarado mas nunca resetado (a maioria do Verilog sintetizável usa um reset explícito para limpá-los). - Uma declaração
casesemdefault, atingida por um valor de entrada que nenhum caso cobriu. - Um
wireque perdeu seu único driver depois de um refactor. - Uma cadeia
if/elseonde um ramo não atribui um sinal que o outro atribui (latchesxse descoberto). - Leitura além do fim de um vetor ou array.
Propagação de X: Um pouquinho de x arruína tudo
A coisa cruel sobre x é que ele se propaga. Um bit x em um operando vira o resultado inteiro x:
Note que 0 & x é 0 (AND com 0 é sempre 0) e 1 | x é 1 (OR com 1 é sempre 1). O simulador é bit-a-bit pessimista mas ainda respeita identidades. Aritmética e comparação não são tão generosas.
É por isso que um único register não inicializado pode fazer um barramento de saída inteiro virar xxxx. Trace para trás a partir de qualquer x e você encontrará a fonte.
Como z aparece
z é o valor de um wire que ninguém está conduzindo:
Dois padrões nesse trecho:
floatingé só declarado e nunca conduzido. Por padrão éz.data_outé um tri-state deliberado. Quandoenableé baixo, a saída explicitamente libera paraz. É assim que um driver de barramento "solta" para outro driver poder assumir.
Em lógica interna, z quase sempre está errado. Em um pino bidirecional ou um barramento compartilhado, z está exatamente certo.
Comparando com == vs ===
O operador de igualdade regular == retorna x quando algum operando tem um bit x ou z:
=== (e seu parceiro !==) faz uma comparação estrita bit-a-bit incluindo x e z. Use-o sempre que precisar testar para ou contra x/z em um testbench. === não é sintetizável, mas dentro de um bloco initial em um testbench isso não importa.
A função de sistema $isunknown(expr) é a forma mais limpa de perguntar "essa expressão tem algum bit x ou z?" - retorna 1 se sim, 0 se não.
Usando x como um don't-care intencional
Um padrão controverso mas legítimo: 'x em um caso default de uma máquina de estado diz ao sintetizador "esse estado é inalcançável, otimize livremente":
case (state)
IDLE: next_state = go ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = 'x; // inalcançável
endcase
O sintetizador pode usar o x para fundir estados e reduzir contagem de gates. Em simulação, se seu raciocínio estava errado e o default é alcançado, você verá x se propagar de next_state e o bug se torna visível imediatamente.
Use isso só quando você pensou cuidadosamente se o default realmente é inalcançável. Se não pensou, defina o default para um estado seguro.
Receita comum de debug
Você está olhando para uma forma de onda cheia de x. A receita:
- Encontre o
xmais cedo. Caminhe a forma de onda para trás no tempo. O sinal mais cedo a virarxé o mais próximo da fonte. - Encontre seu driver. Abra o fonte. O que atribui esse sinal? Um
assign? Um blocoalways? - Verifique as entradas do driver. Se o lado direito do driver tem algum
x, propagação está fazendo o que devia - o bug é upstream. - Se o driver tem entradas limpas mas produz
x, o driver está incompleto. Umcasesem default, umifsemelse, um register sem reset.
A maioria dos bugs de tempestade de x colapsa em um de: reset ausente, default ausente ou um submódulo que não está conectado.
O que vem a seguir
Você agora tem a história completa de tipos de dados: wire/reg, vetores, parameters, literais numéricos e o modelo lógico de quatro estados. O próximo capítulo começa a usar tudo isso para construir expressões - operadores de cada tipo, incluindo os operadores bit a bit que não fariam sentido em software.
Perguntas frequentes
O que x significa em Verilog?
x é o valor desconhecido. Um sinal que é x pode ser tanto 0 quanto 1 - o simulador não sabe. Ele aparece quando um sinal não foi conduzido, quando dois drivers entram em contenção, quando um register é lido antes do reset, ou em qualquer lugar onde comportamento indefinido se propagaria silenciosamente. Trate x como um sinal de bug; quase nunca significa o que você quer.
O que z significa em Verilog?
z é o valor de alta impedância - o wire não está sendo conduzido de jeito nenhum. É o estado legítimo de saídas tri-state (barramentos de dados, pinos bidirecionais), mas em sinais internos z geralmente é um erro significando 'nada está conectado aqui'. Sintetizadores rejeitam a maioria dos padrões z fora de output enables tri-state explícitos.
Por que minha saída Verilog está xxxx?
Quase sempre porque um sinal não está sendo conduzido por nada, ou porque uma operação propagou x de outro sinal. Caminhe para trás: qual sinal é x, o que o alimenta, o driver está ativo? Culpados comuns são casos default ausentes em declarações case, registers que não são resetados e wires que perderam seu driver depois de um refactor.
Como verifico x ou z em Verilog?
Use o operador ===, que compara bit-a-bit exatamente incluindo x e z. a === 1'bx é verdadeiro quando a é de fato x. O == regular retorna x quando qualquer operando tem um bit x, o que significa que a == 1'bx nunca é a resposta que você quer. Existe também $isunknown(a), que é um booleano arrumadinho.
Posso atribuir x como padrão em Verilog?
Sim, e é uma técnica deliberada em declarações case: default: out = 'x; diz ao sintetizador 'eu prometo que esse caso nunca acontece, otimize livremente'. O custo é que se acontecer em simulação, x se propaga e você vê o bug. Use quando você já provou que o default é inalcançável, não como forma de evitar escrever o caso.