Menu

$display y $monitor en Verilog: imprimir desde un testbench

Cómo funcionan $display, $write y $monitor - los especificadores de formato que usarás, la diferencia entre ellos y cuándo cada uno es la herramienta correcta.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

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:

  • %d rellena hasta un ancho por defecto basado en el tamaño del operando. Para un reg de 8 bits, eso son 3 caracteres (sitio para 255). Los espacios al inicio pueden verse feos en salida tabular - usa %0d para suprimirlos.
  • %h, %b, %o tienen padding similar por defecto. La mayoría del código de testbench usa variantes %0 cuando el alineamiento no es útil.
  • El newline al final es automático. No pongas \n al final de una cadena de formato de $display - obtendrás una línea en blanco.

Especificadores de formato

El conjunto que soporta Verilog:

EspecificadorSignificado
%bbinario
%ddecimal (con signo si la señal tiene signo)
%h o %xhex
%ooctal
%cun único carácter ASCII (8 bits bajos)
%sstring
%ttiempo de simulación
%mnombre jerárquico del ámbito actual
%%un % literal
%0Xsin 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 $monitor activo. Llamarlo de nuevo reemplaza la watch list anterior. Usa $monitoroff y $monitoron para suprimir y reactivar temporalmente.
  • Los cambios dentro del mismo paso de tiempo se colapsan a una impresión. Si a y b cambian 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 bloque always @(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);.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR