Die C-printf-Familie von Verilog
Drei System-Tasks lassen dich auf die stdout des Simulators schreiben:
$display- einmal drucken, Newline anhängen.$write- einmal drucken, ohne Newline.$monitor- automatisch jedes Mal drucken, wenn sich ein beobachtetes Signal ändert.
Alle drei nehmen einen Format-String und eine Argumentliste, wie printf. Die Format-Spezifizierer sind ähnlich, aber Verilog-spezifisch.
$display: Der Default
$display ist das Arbeitspferd:
Du wirst etwa Folgendes sehen:
Hello, world.
byte_val = ab
byte_val = 10101011
byte_val = 171
byte_val = 171 (no padding)
multi: nibble=1010 count=42
Anmerkungen:
%dpolstert auf eine Default-Breite basierend auf der Operandengröße. Für ein 8-Bit-regsind das 3 Zeichen (Platz für255). Die führenden Leerzeichen können in tabellarischer Ausgabe hässlich aussehen - nutze%0d, um sie zu unterdrücken.%h,%b,%ohaben ähnliches Default-Padding. Der meiste Testbench-Code nutzt%0-Varianten, wenn Ausrichtung nicht hilfreich ist.- Der Newline am Ende ist automatisch. Setze kein
\nans Ende eines$display-Format-Strings - du bekommst eine Leerzeile.
Format-Spezifizierer
Was Verilog unterstützt:
| Spezifizierer | Bedeutung |
|---|---|
%b | binär |
%d | dezimal (signed, wenn Signal signed) |
%h oder %x | hex |
%o | oktal |
%c | einzelnes ASCII-Zeichen (untere 8 Bit) |
%s | String |
%t | Simulationszeit |
%m | hierarchischer Name des aktuellen Scopes |
%% | ein literales % |
%0X | kein führendes Padding, für %b, %d usw. |
%b, %d, %h, %o sind die vier, die du in 95% der Fälle nutzt. %t ist das nächsthäufigste - immer, wenn du eine Log-Zeile mit Zeitstempel willst.
$write: Kein Newline
$write ist identisch zu $display, hängt aber keinen Newline an:
Ausgabe:
abc
done
Nützlich, um eine einzelne Zeile aus einem Schleifen-Body aufzubauen:
$write("[");
for (integer i = 0; i < 8; i = i + 1) $write("%h ", arr[i]);
$display("]");
$monitor: Auto-Druck bei Änderung
$monitor registriert eine Beobachtungsliste. Der Simulator wertet neu aus und druckt, wenn sich ein im Format-String erwähntes Signal ändert:
Du wirst drei Zeilen sehen, eine pro Eingangsänderung. Kein manuelles $display nach jeder Stimulus-Änderung nötig - $monitor macht es.
Zwei Einschränkungen:
- Nur ein
$monitorkann aktiv sein. Ein weiterer Aufruf ersetzt die vorherige Beobachtungsliste. Nutze$monitoroffund$monitoron, um temporär zu unterdrücken und wieder zu aktivieren. - Änderungen im selben Zeitschritt fallen auf einen Druck zusammen. Ändern sich
aundbbeide zur Zeit 5, feuert der Monitor einmal mit beiden neuen Werten, nicht zweimal.
Wann was nehmen
$display: die meiste Testbench-Ausgabe. Explizit nach Stimulus, nach wichtigen Zustandsübergängen oder innerhalb eines abtastendenalways @(posedge clk)-Blocks aufrufen.$write: wenn du eine einzelne Zeile aus einer Schleife oder mehreren kleinen Stücken aufbauen willst.$monitor: wenn du einen kleinen Satz Signale kontinuierlich verfolgen und nur Ausgabe sehen willst, wenn sie sich ändern. Nützlich fürs initiale Debuggen; in Regressions-Skripten schwieriger, weil die Ausgabe in der Gesamtanzahl Zeilen nicht deterministisch ist.
Für die meisten Workflows deckt $display alles ab. Greife zu $monitor nur, wenn kontinuierliche, änderungsgetriebene Ausgabe wirklich das ist, was du willst.
Mit Zeit arbeiten
$time liefert die aktuelle Simulationszeit als 64-Bit-Integer. Kombiniere mit %0t:
$display("at %0t: signal flipped", $time);
Die Ausgabe sieht aus wie at 25: signal flipped (die Einheit hängt von deinem timescale ab).
Brauchst du Sub-Tick-Präzision (selten), nimm stattdessen $realtime - es liefert einen real.
%t formatiert Zeit automatisch mit einer Default-Breite, die der Simulator wählt. %0t entfernt das Padding.
Abtasten an der Taktflanke
Ein sauberes Idiom zum Beobachten sequentieller Designs: ein separater always @(posedge clk)-Block, der einmal pro Zyklus druckt:
Dieses Sample-Muster garantiert eine Log-Zeile pro Takt - perfekt für Regressions-Tests, die per Pattern-Matching auf der Ausgabe arbeiten.
In Datei loggen
Öffne eine Datei mit $fopen, logge mit $fdisplay (das wie $display funktioniert, aber in einen File-Handle schreibt):
integer fd;
initial begin
fd = $fopen("results.txt", "w");
$fdisplay(fd, "test=%s status=%s", test_name, status);
$fclose(fd);
end
$fopen liefert einen 32-Bit-Handle; übergib ihn als erstes Argument an $fdisplay, $fwrite, $fstrobe usw. Die Funktionen sind ansonsten identisch zu ihren Konsolen-Druck-Geschwistern.
Wie es weitergeht
$display und seine Verwandten geben dir Text-Logs. Für visuelles Debugging - Signale als Spannungen über Zeit beobachten - willst du eine VCD-Waveform. Das nächste Doc, Dumpfile und VCD, behandelt $dumpfile und $dumpvars, die zwei Aufrufe, die deine Simulation in eine grafische Waveform verwandeln, durch die du scrollen kannst.
Häufig gestellte Fragen
Was ist der Unterschied zwischen $display und $monitor in Verilog?
$display druckt einmal, sofort, wenn es ausgeführt wird - wie printf in C. $monitor registriert eine Beobachtungsliste; wann immer sich ein Signal in dieser Liste ändert, wird die formatierte Nachricht automatisch ausgegeben. Es kann immer nur ein $monitor aktiv sein; ein erneuter Aufruf ersetzt die vorherige Liste.
Welche Format-Spezifizierer unterstützt Verilog $display?
Die gängigen: %b (binär), %d (dezimal), %h (hex), %o (oktal), %c (einzelnes Zeichen aus dem unteren Byte), %s (String), %t (Simulationszeit), %m (hierarchischer Instanzname). Nutze die %0d-Form, um Null-Padding zu unterdrücken - %d polstert auf eine Default-Breite, %0d produziert kein Padding.
Was ist $write in Verilog?
$write ist wie $display, fügt aber keinen Zeilenumbruch an. Nützlich, wenn du eine Ausgabezeile aus mehreren Aufrufen aufbaust. Ein abschließendes $display (ohne Argumente oder mit nachgestelltem Newline) beendet die Zeile.
Wie drucke ich die Simulationszeit in Verilog?
Mit $time (oder $realtime für Sub-Tick-Auflösung) und dem %t-Format-Spezifizierer: $display("at time %t: ...", $time);. Mit %0t unterdrückst du das Default-Padding. Für eine einfache Zahl von Zeiteinheiten funktioniert %0d mit $time ebenfalls: $display("t=%0d", $time);.