La familia printf de C en Verilog
Tres tareas de sistema te permiten imprimir al stdout del simulador:
$display- imprime una vez, añade un newline.$write- imprime una vez, sin newline.$monitor- imprime automáticamente cada vez que cambia una señal vigilada.
Los tres toman una cadena de formato y una lista de argumentos, igual que printf. Los especificadores de formato son similares pero específicos de Verilog.
$display: por defecto
$display es el caballo de batalla:
Verás algo como:
Hello, world.
byte_val = ab
byte_val = 10101011
byte_val = 171
byte_val = 171 (no padding)
multi: nibble=1010 count=42
Notas:
%drellena hasta un ancho por defecto basado en el tamaño del operando. Para unregde 8 bits, eso son 3 caracteres (sitio para255). Los espacios al inicio pueden verse feos en salida tabular - usa%0dpara suprimirlos.%h,%b,%otienen padding similar por defecto. La mayoría del código de testbench usa variantes%0cuando el alineamiento no es útil.- El newline al final es automático. No pongas
\nal final de una cadena de formato de$display- obtendrás una línea en blanco.
Especificadores de formato
El conjunto que soporta Verilog:
| Especificador | Significado |
|---|---|
%b | binario |
%d | decimal (con signo si la señal tiene signo) |
%h o %x | hex |
%o | octal |
%c | un único carácter ASCII (8 bits bajos) |
%s | string |
%t | tiempo de simulación |
%m | nombre jerárquico del ámbito actual |
%% | un % literal |
%0X | sin padding al inicio, para cualquiera de %b, %d, etc. |
%b, %d, %h, %o son los cuatro que usarás el 95% del tiempo. %t es el siguiente más común - cada vez que quieras una línea de log con timestamp.
$write: sin newline
$write es idéntico a $display excepto que no añade un newline:
Salida:
abc
done
Útil para construir una sola línea desde el cuerpo de un loop:
$write("[");
for (integer i = 0; i < 8; i = i + 1) $write("%h ", arr[i]);
$display("]");
$monitor: auto-imprimir al cambiar
$monitor registra una watch list. El simulador vuelve a evaluar e imprime cuando cualquier señal mencionada en la cadena de formato cambia:
Verás tres líneas, una por cada cambio en las entradas. Sin necesidad de llamar manualmente a $display después de cada cambio de estímulo - $monitor lo hace.
Dos limitaciones:
- Solo puede haber un
$monitoractivo. Llamarlo de nuevo reemplaza la watch list anterior. Usa$monitoroffy$monitoronpara suprimir y reactivar temporalmente. - Los cambios dentro del mismo paso de tiempo se colapsan a una impresión. Si
aybcambian ambos en el tiempo 5, el monitor dispara una vez con los dos valores nuevos, no dos veces.
Cuándo usar cada uno
$display: la mayoría de la salida de testbench. Llámalo explícitamente tras estímulos, tras transiciones de estado importantes o dentro de un bloquealways @(posedge clk)de muestreo.$write: cuando quieras construir una sola línea desde un loop o varios trozos pequeños.$monitor: cuando quieras seguir un pequeño conjunto de señales continuamente y solo ver salida cuando cambien. Útil para depuración inicial; más difícil de usar en scripts de regresión porque la salida no es determinista en términos del total de líneas.
Para la mayoría de los flujos, $display cubre todo. Recurre a $monitor solo cuando la salida continua dirigida por cambios sea realmente lo que quieres.
Trabajando con tiempo
$time devuelve el tiempo de simulación actual como un entero de 64 bits. Combínalo con %0t:
$display("at %0t: signal flipped", $time);
La salida es algo así como at 25: signal flipped (la unidad depende de tu timescale).
Si necesitas precisión sub-tick (raro), usa $realtime - devuelve un real.
%t formatea el tiempo automáticamente con un ancho por defecto que elige el simulador. %0t quita el padding.
Muestreo en el flanco del reloj
Un modismo limpio para monitorizar diseños secuenciales: un bloque always @(posedge clk) separado que imprime una vez por ciclo:
Este patrón de muestreo garantiza una línea de log por reloj - perfecto para tests de regresión que hacen pattern-match sobre la salida.
Loguear a un archivo
Abre un archivo con $fopen, loguea con $fdisplay (que funciona como $display pero escribe a un handle de archivo):
integer fd;
initial begin
fd = $fopen("results.txt", "w");
$fdisplay(fd, "test=%s status=%s", test_name, status);
$fclose(fd);
end
$fopen devuelve un handle de 32 bits; pásalo como primer argumento a $fdisplay, $fwrite, $fstrobe, etc. Las funciones son por lo demás idénticas a sus hermanas que imprimen por consola.
Qué viene a continuación
$display y compañía te dan logs de texto. Para depuración visual - ver señales como voltajes a lo largo del tiempo - quieres una forma de onda VCD. El siguiente doc, Dumpfile and VCD, cubre $dumpfile y $dumpvars, las dos llamadas que convierten tu simulación en una forma de onda gráfica que puedes recorrer.
Preguntas frecuentes
¿Cuál es la diferencia entre $display y $monitor en Verilog?
$display imprime una vez, inmediatamente, cuando se ejecuta - como printf en C. $monitor registra una watch list; cuando cualquier señal de la lista cambia, el mensaje formateado se imprime automáticamente. Solo un $monitor puede estar activo a la vez; llamarlo de nuevo reemplaza la watch list anterior.
¿Qué especificadores de formato soporta $display de Verilog?
Los comunes: %b (binario), %d (decimal), %h (hex), %o (octal), %c (un único carácter del byte bajo), %s (string), %t (tiempo de simulación), %m (nombre jerárquico de instancia). Usa la forma %0d para quitar el padding inicial con ceros - %d rellena hasta un ancho por defecto, %0d no produce padding.
¿Qué es $write en Verilog?
$write es como $display pero no añade un newline. Útil cuando quieres construir una línea de salida a partir de varias llamadas. El $display de fin de línea (sin argumentos o con un newline al final) termina la línea.
¿Cómo imprimo el tiempo de simulación en Verilog?
Usa $time (o $realtime para resolución sub-tick) con el especificador de formato %t: $display("at time %t: ...", $time);. Usa %0t para suprimir el padding por defecto. Para un simple recuento entero de unidades de tiempo, %0d con $time también funciona: $display("t=%0d", $time);.