A família printf do C em Verilog
Três system tasks te deixam imprimir para o stdout do simulador:
$display- imprime uma vez, anexa nova linha.$write- imprime uma vez, sem nova linha.$monitor- imprime automaticamente toda vez que um sinal monitorado muda.
Todos os três pegam uma string de formato e uma lista de argumentos, igualzinho ao printf. Os format specifiers são similares mas específicos do Verilog.
$display: O padrão
$display é o burro de carga:
Você verá algo como:
Hello, world.
byte_val = ab
byte_val = 10101011
byte_val = 171
byte_val = 171 (sem padding)
multi: nibble=1010 count=42
Notas:
%denche até uma largura padrão baseada no tamanho do operando. Para umregde 8 bits, isso são 3 caracteres (espaço para255). Os espaços à esquerda podem ficar feios em saída tabular - use%0dpara suprimi-los.%h,%b,%otêm padding similar por padrão. A maioria do código de testbench usa variantes%0quando o alinhamento não é útil.- A nova linha no final é automática. Não coloque
\nno fim de uma string de formato$display- você terá uma linha em branco.
Format Specifiers
O conjunto que o Verilog suporta:
| Specifier | Significado |
|---|---|
%b | binário |
%d | decimal (signed se sinal é signed) |
%h ou %x | hex |
%o | octal |
%c | caractere ASCII único (8 bits baixos) |
%s | string |
%t | tempo de simulação |
%m | nome hierárquico do escopo atual |
%% | um % literal |
%0X | sem padding à esquerda, para qualquer de %b, %d, etc. |
%b, %d, %h, %o são os quatro que você vai usar 95% do tempo. %t é o próximo mais comum - sempre que você quer uma linha de log com timestamp.
$write: Sem nova linha
$write é idêntico ao $display exceto que não anexa uma nova linha:
Saída:
abc
done
Útil para construir uma única linha a partir do corpo de um loop:
$write("[");
for (integer i = 0; i < 8; i = i + 1) $write("%h ", arr[i]);
$display("]");
$monitor: Auto-imprimir em mudança
$monitor registra uma watch list. O simulador reavalia e imprime sempre que algum sinal mencionado na string de formato muda:
Você verá três linhas, uma para cada mudança nas entradas. Sem necessidade de chamar $display manualmente depois de cada mudança de estímulo - $monitor faz isso.
Duas limitações:
- Só um
$monitorpode estar ativo. Chamá-lo de novo substitui a watch list anterior. Use$monitoroffe$monitoronpara temporariamente suprimir e re-habilitar. - Mudanças dentro do mesmo time step colapsam em uma impressão. Se
aebambos mudam no tempo 5, o monitor dispara uma vez com ambos os novos valores, não duas.
Quando usar cada um
$display: a maior parte da saída de testbench. Chame explicitamente depois de estímulo, depois de transições de estado importantes ou dentro de um blocoalways @(posedge clk)de amostragem.$write: quando você quer construir uma única linha a partir de um loop ou vários pedaços pequenos.$monitor: quando você quer rastrear um pequeno conjunto de sinais continuamente e só ver saída quando mudam. Útil para debug inicial; mais difícil de usar em scripts de regressão porque a saída não é determinística em termos de contagem total de linhas.
Para a maioria dos workflows, $display cobre tudo. Recorra a $monitor só quando saída contínua dirigida por mudança é genuinamente o que você quer.
Trabalhando com tempo
$time retorna o tempo atual de simulação como um inteiro de 64 bits. Pareie com %0t:
$display("em %0t: sinal virou", $time);
A saída fica como em 25: sinal virou (a unidade depende do seu timescale).
Se você precisa de precisão sub-tick (raro), use $realtime no lugar - retorna um real.
%t automaticamente formata tempo com uma largura padrão que o simulador escolhe. %0t tira o padding.
Amostrando na borda de clock
Um idioma limpo para monitorar designs sequenciais: um bloco always @(posedge clk) separado que imprime uma vez por ciclo:
Esse padrão de amostragem garante uma linha de log por clock - perfeito para testes de regressão que fazem pattern-match na saída.
Logging para um arquivo
Abra um arquivo com $fopen, log com $fdisplay (que funciona como $display mas escreve em um handle de arquivo):
integer fd;
initial begin
fd = $fopen("results.txt", "w");
$fdisplay(fd, "test=%s status=%s", test_name, status);
$fclose(fd);
end
$fopen retorna um handle de 32 bits; passe-o como primeiro argumento para $fdisplay, $fwrite, $fstrobe e por aí vai. As funções são além disso idênticas a seus irmãos que imprimem no console.
O que vem a seguir
$display e amigos te dão logs de texto. Para debug visual - vendo sinais como tensões ao longo do tempo - você quer uma forma de onda VCD. O próximo doc, Dumpfile and VCD, cobre $dumpfile e $dumpvars, as duas chamadas que transformam sua simulação em uma forma de onda gráfica pela qual você pode rolar.
Perguntas frequentes
Qual a diferença entre $display e $monitor em Verilog?
$display imprime uma vez, imediatamente, quando é executado - como printf em C. $monitor registra uma watch list; sempre que qualquer sinal na lista muda, a mensagem formatada é impressa automaticamente. Só um $monitor pode estar ativo por vez; chamá-lo de novo substitui a watch list anterior.
Quais format specifiers o $display do Verilog suporta?
Os comuns: %b (binário), %d (decimal), %h (hex), %o (octal), %c (caractere único do byte baixo), %s (string), %t (tempo de simulação), %m (nome de instância hierárquico). Use a forma %0d para descartar zero-padding à esquerda - %d enche até uma largura padrão, %0d não enche.
O que é $write em Verilog?
$write é como $display mas não anexa uma nova linha. Útil quando você quer construir uma linha de saída a partir de múltiplas chamadas. O $display de fim-de-linha (sem argumentos ou com uma nova linha à direita) termina a linha.
Como imprimo o tempo de simulação em Verilog?
Use $time (ou $realtime para resolução sub-tick) com o format specifier %t: $display("em tempo %t: ...", $time);. Use %0t para suprimir o padding padrão. Para uma contagem inteira simples de unidades de tempo, %0d com $time também funciona: $display("t=%0d", $time);.