Wie wird der Debugging-Build in VHDL implementiert?

Ich komme aus dem C-Hintergrund und werde in VHDL eingeführt. Ich habe etwas über die Syntax und die Parallelität/Aufeinanderfolge von Aktionen gelesen.

Jetzt frage ich mich, wie reine Entwicklungsfunktionen implementiert werden. Dinge wie assert()und #ifndef NDEBUG LOG_MSG_UART("99 bottles of beer").

Das Szenario, an das ich denke, ist folgendes. Lassen Sie uns ein Entwicklungsboard mit einem FPGA und mehreren Debugging-Headern haben. An diese Debug-Header werden verschiedene Signale zur Messung mit einem Oszilloskop oder Logikanalysator ausgegeben. In der Release-Version wird es keine solchen Header geben. Wie schreibt man den Code, damit man leicht zwischen den Release- und Debug-Builds wechseln kann?

Ein idiomatisches Beispiel würde sehr helfen.

Du wirst einen kompletten Perspektivwechsel vornehmen müssen. Es gibt keinen "Debug"-Build, da der veröffentlichte Code keine Fehler enthalten darf. Sie müssen viel mehr Zeit für die Verifizierung aufwenden, vorzugsweise für die Simulation. Gewöhnen Sie sich an die starke Typprüfung in VHDL und machen Sie es zu Ihrem Freund statt zu Ihrem Feind.

Antworten (2)

In VHDL wird die grundlegende ASSERTAnweisung verwendet, um sicherzustellen, dass Sie bei der Simulation alle diese Bedingungen erfassen. Die Simulation ist ein Schritt des Debugging-Prozesses, der am ehesten softwareähnlich ist, da Sie Zugriff auf alles innerhalb des Designs haben.

Sie können PSL auch (mit Zugriff auf ausreichend hochkarätige Tools) verwenden, um komplexere Assertionen zu schreiben, die sowohl Timings als auch einfache Bedingungen beinhalten können.

Wenn Sie die Simulation zu Ihrer Zufriedenheit durchgeführt haben und die echte Hardware debuggen müssen, können Sie die folgenden Tools verwenden:

  • Eingebetteter Logikanalysator (Xilinx: Chipscope, Altera: SignalTap) - Sie bauen diesen zur Kompilierzeit ein und können dann Signale auslösen und überwachen. Die Signale werden in Echtzeit im internen Speicher erfasst, daher gibt es eine Begrenzung, wie viele und wie lange, aber es bietet eine hervorragende Sichtbarkeit. Es kann jedoch zeitaufwändig sein, da Sie jedes Mal, wenn Sie Ihre Meinung über das Setup ändern, das FPGA neu erstellen müssen (was ein Prozess ist, der in Dutzenden von Minuten oder sogar Stunden gemessen wird, je nach Komplexität des Designs).
  • Ersatzstifte - mit oder ohne LEDs. Heben Sie kritische interne Signale hervor und überwachen Sie sie mit einem Oszilloskop und Logikanalysator. Erstellen Sie erneut jedes Mal, wenn Sie es ändern. Dies kann in Xilinx-Land mit dem FPGA-Editor auch dynamischer erfolgen, sodass Sie relativ schnell Aktualisierungen des Bitstroms vornehmen können, wenn Sie das gewünschte Signal in der optimierten Netzliste finden können!

Wie Brian betonte, benötigen Sie auch ein Oszilloskop (und möglicherweise einen Logikanalysator), um die externen Schnittstellen zu debuggen - das Timing usw. zu überprüfen.


In Bezug auf das Problem, „Debug“- und „Release“-Builds zu haben, wird dies nicht auf „Standard“-Weise gehandhabt (anders als in der Softwarewelt). Das Verhalten wäre in den meisten Fällen nicht sehr unterschiedlich: Da das FPGA sehr deterministisch ist, verlieren Sie nicht an Geschwindigkeit, wenn Sie Debug-Logik darin haben (oder wenn Sie dies tun, erfüllt es immer noch die erforderliche Spezifikation, da Sie sonst nicht debuggen könnten der Chip!). Am Ende haben Sie mehr Logik als Sie brauchen, aber auch hier muss sie in das Gerät passen, das Sie haben (es sei denn, Sie planen, die Größe für die Veröffentlichung zu verkleinern, aber angesichts der Erhöhung der Gerätegröße würden Sie viel Debugging benötigen Logik, dass sich das lohnt!).

Beachten Sie auch, dass (soweit ich gehört habe) niemand mit unterschiedlichen Optimierereinstellungen für Debug vs. Release erstellt, was das Auffinden der gewünschten Signale schwierig machen kann. Sie müssen ihnen also s hinzufügen, um zu verhindern, dass sie so optimiert attributesind wie sie es sonst wären, was gut ist, da der Rest des Designs optimiert wird. Nicht so gut, dass Sie neu erstellen müssen, um die Attribute zu ändern. (Zumindest bleibt genügend Zeit, um Ihre Dokumentation auf dem neuesten Stand zu halten, während Sie auf die Builds warten :)

Wenn Sie dies tun möchten, würde ich ein paar Möglichkeiten vorschlagen:

  • Haben Sie einen booleschen Wert genericauf der Instanz der obersten Ebene - er kann standardmäßig auf Debugging deaktiviert sein, und Sie können ihn in der Befehlszeile/von der GUI Ihres Synthesizers überschreiben.
  • Haben Sie einen booleschen Wert constantin einem Paket, das Sie in Ihrem Hauptcode verwenden - wählen Sie zwischen den beiden Modi aus, indem Sie diese Datei bearbeiten

Bei der ersten Option kann nur die oberste Ebene den Status des Debug-Status sehen (es sei denn, Sie geben ihn in der Hierarchie nach unten weiter, aber das kann schnell unangenehm werden, da Sie die Debug-Signale normalerweise auch dann wieder nach oben weitergeben müssen!).

Mit der zweiten kann jede Entität an die Debug-Konstante gelangen und sie verwenden - auch hier müssen Sie Signale den Baum hinaufleiten.

Sie können den booleschen Wert in an verwenden, if..generate..else generateum die gewünschte (oder nicht gewünschte) Logik je nach "Modus" zu instanziieren.

+1 für die Abdeckung einiger der nach der Simulation übrig gebliebenen Elemente. Für Xilinx-FPGAs ist der eingebettete Analysator "Chipscope". Außerdem: Die Anbindung an externe Chips kann schwierig sein: Das Ändern von E / A-Timings oder der Phase externer Takte kann für eine zuverlässige Kommunikation mit ihnen erforderlich sein.
Konkret lautet meine Frage, wie man bei der Entwicklung einige Signale auf z. B. LEDs ausgibt, dann diese Logik für die Veröffentlichung entfernt und sie dann während der Entwicklung von v2 wieder einfügt.
Antwort aktualisiert...

Wahrscheinlich leichter.

Sie erhalten den Großteil des Designs direkt in der Simulation, bevor Sie jemals zur Hardware wechseln.

Verwenden Sie selbstüberprüfende Testbenches (oder Verifizierungstechniken höherer Ordnung, einschließlich PSL, Constrained Random Testing oder OSVVM ) in der Rolle des Komponententests, um zu überprüfen, ob der Block seine grundlegende Funktionalität erfüllt.

Wenn dies nicht der Fall ist, verfügt der Simulator über einen Wellenform-Viewer, der Ihnen den Wert jedes Signals zu jedem Zeitpunkt anzeigen kann, wobei Unbekannte (z. B. „X“ oder „Z“) in Rot angezeigt werden. Verwenden Sie dies mit Bedacht, um von falschen Ergebnissen auf ihre Ursache zurückzuarbeiten; bearbeiten, kompilieren, neu simulieren usw., bis der Block das tut, was Sie wollen.

Selbstüberprüfende Testbenches können das (synthetisierbare) Design auf niedriger Ebene mit einem algorithmischen Modell auf höherer Ebene vergleichen: Jeder grundlegende Algorithmus, den Sie in C schreiben können, kann auch in VHDL geschrieben werden (aber möglicherweise nicht synthetisierbar!) oder die Testbench kann das Erwartete lesen Ergebnisse aus einer Datei. Testbenches sind somit reine Entwicklungsumgebungen. (Sie haben im Allgemeinen keinen Zugriff auf die Innereien eines Hardwareblocks; nur auf seine Ein- und Ausgänge, also sind sie Blackbox-Tester). Aber eine Testbench kann mehrere Blackboxes verkabeln, um zu testen, wie sie interagieren.

Vergleichen Sie also die tatsächlichen Ergebnisse eines Blocks mit den erwarteten Ergebnissen (möglicherweise müssen Sie die erwarteten Ergebnisse speichern, bis die tatsächlichen Ergebnisse bereit sind) ... dies geschieht normalerweise mit einer assert-Anweisung:

VHDL hilft sehr, dank eines anständigen Typsystems: Verwenden Sie es!
(Oder werden Sie frustriert, indem Sie dagegen ankämpfen: Ihre Wahl!)

Schlechtes VHDL:

addr : integer;
...
assert addr >= 0 report "Address arithmetic overflow" severity FAILURE;

Besseres VHDL:

subtype address is natural range 0 to 2**16 - 1;
addr : address;

Die Bestätigung ist jetzt überflüssig, da der Simulator dies für Sie erledigt: Werte außerhalb des angegebenen Bereichs werden abgefangen. (Synthese kann immer noch Überläufe erzeugen; die Synthesizer-Tools dürfen davon ausgehen, dass Sie das Design bereits richtig gemacht haben! Daher verschwenden sie keine Hardware für Überlaufprüfungen, es sei denn, Sie codieren sie explizit.)

Mit dem Typsystem kann man viele gute Dinge tun: Dies sollte SW-Entwicklern zur zweiten Natur werden, scheint aber nicht mehr gelehrt zu werden. Ein paar Beispiele:

   variable mem : array(addr) of integer;

   for i in addr loop
      mem(i) := 0;
   end loop;

Array-Größe und Schleifengrenzen, die durch den Typ definiert sind: keine Schleifenfehler oder Pufferüberläufe.

assert addr'high + 1 = 65536 report "Wrong address size" severity failure;

Attribute ('high, 'low, 'range (definiert einen ganzzahligen Untertyp) usw. ermöglichen die Laufzeit-Introspektion des Typs (Festlegen von Schleifengrenzen für ein uneingeschränktes Array usw.) oder wie hier, um Annahmen über einen an anderer Stelle deklarierten Typ zu überprüfen ...

type colour is (red, green, blue); 

ein bisschen wie eine C-Enumeration, außer ...

type RGB is array(colour) of real;
constant gain : RGB := (red => 0.31, green => 0.58, blue => 0.11);
signal YUV,RGB_in : RGB;
...
for c in colour loop
   YUV(c) <= RGB_in(c) * gain(c);
end loop;

Nachdem Sie einen solchen Typ definiert haben, können Sie ihn als Array-Index verwenden oder ihn durchlaufen usw. Und so weiter.

Benannte Assoziationen für Argumente, Array-Komponenten usw. reduzieren Fehler und machen den Code klarer.

Pakete ... Typen und ihre Funktionsweise in sicheren wiederverwendbaren Komponenten zusammenfassen. Usw...

Meine Designs neigen dazu, ein "gemeinsames" Paket zu haben, das weit verbreitete Dinge wie "Adresse" oben definiert: Alles verwendet es, und wenn ich die Adressgröße ändere, passen sich alle Schleifen an die neue Größe an, und alles mit fest codierten Annahmen sollte umfallen bei einer Behauptung (wie oben), bis ich es behebe ...

Ich beantworte nicht wirklich die Frage "Wie kann ich manchmal einige interne Zustände/Eingabezustände vom FPGA ausgeben", aber gut, da es mich in die neue Denkweise lenken wird.