Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 18 Min.

FPGAs programmieren mit C/C++

Komponenten als Hardware lassen sich nicht nur mit VHDL erzeugen, sondern auch mit weitverbreiteten Programmiersprachen wie C oder C++ – mit Vitis HLS als Werkzeug.
© dotnetpro
Die Erzeugung von Hardware mithilfe der Sprache VHDL wurde bereits in vier vorangegangenen dotnetpro-Artikeln [1] [2] [3] [4] vorgestellt. Die Sache hat aber einen Haken: Man muss die Sprache VHDL lernen und verstehen. Wäre es nicht schön, wenn man weit­verbreitete Programmiersprachen wie C oder C++ einsetzen könnte, um Komponenten als Hardware zu erzeugen? Kein Problem: Wir benutzen dazu Vitis HLS als Werkzeug.

Einführung

Vitis HLS – HLS bedeutet hier High Level Synthesis – kann C- oder C++-Code in Hardware umsetzen und letztendlich ein sogenanntes IP (Intellectual Property, geistiges Eigentum) erstellen, welches dann mit zusätzlicher Hardware in einem Vivado-Block-Design [4] weiterverwendet werden kann. Man ist dann also in der Lage, mathematische Algorithmen (zum Beispiel eine Fast-Fourier-Transformation) sehr vorteilhaft in C++ zu implementieren und die dazugehörige Eingabe- und Ausgabe-Hardware (zum Beispiel Sensor-Zugriff, Ausgabe auf ein LC-Display) in VHDL zu generieren.In den bisherigen FPGA-Artikeln [1] – [4] wurde in erster Linie die Sprache VHDL benutzt. In [4] wurden dann mehrere IPs (also eigenständige Komponenten) erstellt, die schließlich mit Vivado in einem sogenannten Block-Design zu einer vollständigen Hardware zusammengebaut wurden. Diese Vor­gehensweise kommt mit Vitis HLS auch weiterhin zum Einsatz, allerdings lassen sich einige der IPs eben in C++ erstellen. Der Vorteil liegt klar auf der Hand: Viele Algorithmen sind bereits in C oder C++ verfügbar und sind auf die Vitis-HLS-Plattform (mehr oder weniger) problemlos migrierbar. Außerdem können mathematische Zusammenhänge in C/C++ einfacher programmiert werden, insbesondere dann, wenn es um Arrays und Schleifen geht. Auf der anderen Seite ist VHDL besser anwendbar, wenn es um die zeit- und bitgenaue Steuerung einer Hardware (zum Beispiel 7-Segment-Anzeige, VGA-Monitor, Sensoren …) geht. Dieser Artikel soll zeigen, wie einfache Komponenten mit Vitis HLS erstellt und dann mit Vivado weiterbenutzt werden. Die Zusammenhänge zeigt Bild 1. Der linke Ablauf (IP-Cores/VHDL) wurde in [1] – [4] behandelt. In diesem Artikel (und auch in den nächsten) geht es um den rechten Zweig von Bild 1.
VHDL und HLSim Zusammenspiel(Bild 1) © Autor

Software-Installation

Wenn Sie bereits die Vivado-Software installiert haben, können Sie mit Vitis HLS sofort loslegen, denn dieses Softwarepaket wird gleich mitinstalliert. Wenn Sie die Vivado-Software noch nicht installiert haben, finden Sie den ­Installationsprozess in [1] beschrieben. Achten Sie darauf, dass Sie die aktuelle Version der Software verwenden. Eine ältere Version lässt sich ganz einfach deinstallieren und durch eine neuere ­Variante ersetzen. Der komplette Installationsvorgang wird in [5] vollständig dargestellt.Bevor es losgeht, ist es sinnvoll, sich auf eine gemeinsame Verzeichnisstruktur für die Projekte zu einigen. Das hilft beim Wiederfinden aller verwendeten Dateien. Zunächst gilt es, ein Basis-Verzeichnis Vivado-Projects anzulegen. Dort können dann Unterverzeichnisse für die einzelnen Demos erstellt werden (etwa: Demo01, Demo02 …). In diesen Verzeichnissen legen wir zwei weitere Unterverzeichnisse an: HLS für die Dateien und den C++-Code des HLS-IP und VIVADO für den VHDL-Code auf der Hardwareseite. Außerdem wird im obersten Verzeichnis das Unterverzeichnis HLS_IP erstellt, um dann die HLS-IPs der einzelnen Projekte in entsprechenden Unterverzeichnissen abzulegen. Dieses Verzeichnis ist in Vivado in die Liste der zu durchsuchenden IP-Verzeichnisse aufzunehmen [6]. Die Hie­rarchie ist dann also folgende:
<span class="hljs-string">\Vivado-Projects</span> 
    <span class="hljs-string">\Demo01</span> 
        <span class="hljs-string">\HLS</span> 
        <span class="hljs-string">\VIVADO</span> 
    <span class="hljs-string">\Demo02</span> 
        <span class="hljs-string">\HLS</span> 
        <span class="hljs-string">\VIVADO</span> 
    ... 
    <span class="hljs-string">\HLS_IP</span> 
        <span class="hljs-string">\Demo01</span> 
        <span class="hljs-string">\Demo02</span> 
        ... 

Ein Beispiel zum Einstieg

Das erste Beispiel ist sehr einfach – es sollen nur zwei 8-Bit-Zahlen addiert und das Ergebnis ausgegeben werden –, damit hierbei der Ablauf der gesamten Prozedur mit Vitis HLS und Vivado nachvollziehbar wird.Ich habe mir angewöhnt, die drei Startdateien, die es für das Vitis-HLS-Projekt braucht, mit meinem bevorzugten Editor zu erzeugen. Dazu zählen eine Header-Datei, eine C++-Datei, welche die HLS-Funktion enthält, und eine weitere C++-Datei mit dem Code der Testbench. Die Namen wähle ich nach folgendem Schema aus: XXX_HLS.h, XXX_HLS.cpp und XXX_HLS_Test.cpp, wobei XXX der aktuelle Projekt­name ist. Listing 1 zeigt die benötigten Dateien für eine HLS-Komponente, die zwei 8-Bit-Zahlen addiert und das Ergebnis wiederum als 8-Bit-Zahl zurückgibt.
Listing 1: Ein einfaches HLS-IP (alle drei Dateien)
// ***** &lt;span class="hljs-type"&gt;Demo01_HLS&lt;/span&gt;.h ****************** &lt;br/&gt;#ifndef _DEMO01_HLS_H_ &lt;br/&gt;#define _DEMO01_HLS_H_ &lt;br/&gt;&lt;br/&gt;typedef unsigned char din_t;  &lt;br/&gt;typedef unsigned char dout_t;  &lt;br/&gt;&lt;br/&gt;// &lt;span class="hljs-type"&gt;Top&lt;/span&gt;-&lt;span class="hljs-type"&gt;Level&lt;/span&gt;-&lt;span class="hljs-type"&gt;Funktions&lt;/span&gt;-&lt;span class="hljs-type"&gt;Deklaration&lt;/span&gt; &lt;br/&gt;dout_t demo01(din_t a, din_t b);  &lt;br/&gt;&lt;br/&gt;#endif &lt;br/&gt;&lt;br/&gt;// ***** &lt;span class="hljs-type"&gt;Demo01_HLS&lt;/span&gt;.cpp **************** &lt;br/&gt;#include "&lt;span class="hljs-type"&gt;Demo01_HLS&lt;/span&gt;.h" &lt;br/&gt;&lt;br/&gt;// *** &lt;span class="hljs-type"&gt;Top&lt;/span&gt; &lt;span class="hljs-type"&gt;Level&lt;/span&gt; &lt;span class="hljs-type"&gt;Funktion&lt;/span&gt; &lt;br/&gt;dout_t demo01(din_t a, din_t b)  &lt;br/&gt;{ &lt;br/&gt;  dout_t y = a + b;  &lt;br/&gt;  return y; &lt;br/&gt;} &lt;br/&gt;&lt;br/&gt;// ***** &lt;span class="hljs-type"&gt;Demo01_HLS_Test&lt;/span&gt;.cpp *********** &lt;br/&gt;#include &amp;lt;stdio.h&amp;gt; &lt;br/&gt;#include "&lt;span class="hljs-type"&gt;Demo01_HLS&lt;/span&gt;.h" &lt;br/&gt;&lt;br/&gt;int main(void) &lt;br/&gt;{ &lt;br/&gt;  // &lt;span class="hljs-type"&gt;Deklarationen&lt;/span&gt; &lt;br/&gt;  din_t x1 = 23;  &lt;br/&gt;  din_t x2 = 41; &lt;br/&gt;  dout_t res1; &lt;br/&gt;  int ret_value; &lt;br/&gt;&lt;br/&gt;  // &lt;span class="hljs-type"&gt;Berechnungen&lt;/span&gt; &lt;br/&gt;  res1 = demo01(x1, x2);  &lt;br/&gt;  printf("&lt;span class="hljs-type"&gt;Result&lt;/span&gt;: %d\n", res1); &lt;br/&gt;  printf("&lt;span class="hljs-type"&gt;Result&lt;/span&gt;: 0x%0x\n", res1); &lt;br/&gt;&lt;br/&gt;  if(res1 != 64)  &lt;br/&gt;  { &lt;br/&gt;    fprintf(stdout, "\nTest &lt;span class="hljs-type"&gt;FAILED&lt;/span&gt;!\n"); &lt;br/&gt;    ret_value = 1;  // &lt;span class="hljs-type"&gt;ERROR&lt;/span&gt; &lt;br/&gt;  } &lt;br/&gt;  else &lt;br/&gt;  { &lt;br/&gt;    fprintf(stdout, "\nTest &lt;span class="hljs-type"&gt;PASSED&lt;/span&gt;!\n"); &lt;br/&gt;    ret_value = 0;  // &lt;span class="hljs-type"&gt;OK&lt;/span&gt; &lt;br/&gt;  } &lt;br/&gt;&lt;br/&gt;  return ret_value; &lt;br/&gt;}  
Diese drei Dateien werden nun zu einem Vitis-HLS-Projekt zusammengestellt und mit den entsprechenden Compiler-Optionen versehen. Zunächst wird Vitis HLS gestartet und aus dem Hauptmenü der Befehl File | New Project … ausgewählt (alternativ funktioniert auch ein Klick auf den Befehl Create Project … im Hauptfenster). Im Dialogfenster trägt man Demo01_HLS als Project name ein und wählt im Eingabefeld Location das Verzeichnis aus, in dem die drei erstellten Dateien aus Listing 1 abgelegt wurden.Nun klickt man sich durch die Fenster des Wizards, welche das Projekt im Einzelnen definieren. Alle Dialoge, die für dieses Beispiel aufgerufen werden, sind im Unterverzeichnis WeitereDateien in den Downloads zu diesem Artikel in einem Textdokument abgelegt.Im nächsten Fenster (Schaltfläche: Next) wird mit dem Befehl Add Files … zunächst die Datei Demo01_HLS.cpp ausgewählt und in das Projekt eingefügt. Außerdem ist die sogenannte Top-Level-Funktion auszuwählen. Das ist die Funktion, die von Vivado aufgerufen werden kann. In diesem Fall ist es die Funktion demo01 mit den beiden Parametern a und b(Listing 1). Nach einem erneuten Klick auf die Schaltfläche Next wird mit dem Befehl Add Files … die C++-Datei der Testbench in das Projekt eingefügt (hier: Demo01_HLS_Test.cpp). Die Header-Datei Demo01_HLS.h, die in den beiden C++-Dateien benutzt wird, fügt Vitis HLS automatisch zum aktuellen Projekt hinzu. Nach dem Anklicken der Next-Schaltfläche erfolgt noch die Abfrage verschiedener Projekt-Parameter (Bild 2), die bei der Erzeugung der Hardware-Komponente von Bedeutung sind. Den Solution Name lassen wir zunächst einmal unverändert, aber wir kommen später noch darauf zurück. Auch die Clock Period bleibt unverändert (hier: 10 ns = 100 MHz). Das ist die Taktfrequenz, mit der die Komponente versorgt wird. Vitis HLS versucht bei der Erstellung der Komponente, die einzelnen Operationen optimal in die einzelnen Takte zu verteilen (auch parallel), um die Gesamtoperation möglichst schnell abzuschließen. Das Eingabefeld für die Unsicherheit (Uncertainty) der Taktfrequenz kann zunächst leer gelassen werden. In diesem Fall verwendet Vitis HLS automatisch den Standardwert (12,5 Prozent der Taktfrequenz).Sehr wichtig ist an dieser Stelle jedoch die korrekte Definition des verwendeten FPGA-Chips (Part Selection). Die Auswahl findet in einem separaten Dialog statt, der über die Schaltfläche mit den drei Punkten („…“) aufgerufen wird. Wenn Sie das Projekt mit dem Basys-3-Board von Digilent aus den vorangegangenen Artikeln durchführen möchten, dann muss Part als xc7a35tcpg236-1 angegeben werden (Bild 2). Auch die Auswahl Vivado IP Flow Target bleibt unverändert. In diesem Fall wird eine in Vivado nutzbare IP-Komponente erzeugt und für die Weiterverwendung als ZIP-Datei mit allen dazugehörigen Dateien bereitgestellt. Die gesamte Ak­tion wird nun durch die Schaltfläche Finish abgeschlossen.
Weitere Vitis-HLS-Projektparameter– wichtig ist insbesondere die Part Selection(Bild 2) © Autor
Nach der Projekterzeugung wird die Arbeitsumgebung (IDE) von Vitis HLS sichtbar (Bild 3). Die fünf wichtigen Bereiche wurden mit den Buchstaben A bis E gekennzeichnet. Das Fenster A enthält in dieser Ansicht den Explorer, der alle zum Projekt gehörenden Dateien auflistet. Die Dateien können durch einen Doppelklick aufgerufen und im Fenster B dargestellt werden (hier: Demo01_HLS.cpp). Der Flow Navigator (Fenster C) steuert den Ablauf der Übersetzung, Erzeugung und des Testens der HLS-Komponente. Die einzelnen Posi­tionen in diesem Ablauf finden Sie weiter unten erläutert. Schließlich werden im Teil D in mehreren Unterfenstern diverse Ausgaben angezeigt, die während der Abarbeitung des Workflows anfallen. Im Fenster E wird der Aufbau der gesamten Anwendung ausgegeben. Die Anordnung der Fenster in der IDE kann der Anwender ändern.
Die Vitis-HLS-Arbeitsumgebungmit dem Projekt „Demo01_HLS“(Bild 3) © Autor
Da bereits alle benötigten Dateien fertig eingegeben sind, sind keine Code-Änderungen notwendig und das Projekt kann direkt übersetzt und getestet werden. Der ganze Vorgang wird mit dem Flow Navigator gesteuert.Vor der Erstellung des HLS-IP werfen wir aber zunächst noch einmal einen Blick auf die drei Dateien des Projekts. Die Header-Datei Demo01_HLS.h findet man im Explorer im ­Projektzweig Includes/XXX, wobei XXX für das Verzeichnis des Projekts steht. In der Header-Datei werden der Einfachheit halber die Datentypen din_t und dout_t vom Typ char ­definiert. Diese Definitionen machen den weiteren Code etwas übersichtlicher und besser wiederverwendbar. Schließlich wird an dieser Stelle auch die Top-Level-Funktion demo01 deklariert.Die C++-Datei für den Algorithmus (Demo01_HLS.cpp) ist eher unspektakulär. Mit der Funktion demo01 kann man also zwei 8-Bit-Ganzzahlen addieren. Ja, Sie haben recht: Das kann man auch mit VHDL sehr einfach machen, aber es geht bei diesem ersten Beispiel nur um die grundsätzliche Vorgehensweise. Das Rechen­ergebnis wird ganz normal mit return zurückgegeben.Die spannende Frage ist nun: Wie kommen die Daten in das HLS-IP hinein und wie wird das Ergebnis dann weiterverwendet? Auch das soll im Folgenden erläutert werden.Die Testbench Demo01_HLS_Test.cpp enthält den Code für diverse Tests, die bei der IP-Erzeugung durchgeführt werden. Hier entscheidet natürlich der Entwickler, was alles getestet wird. In Zeile 14 wird die HLS-Komponente über die Top-Level-Funktion aufgerufen. Dabei werden zwei initialisierte Parameter (x1 und x2 vom Typ din_t) übergeben und die Rückgabe vom Typ dout_t in der entsprechenden Variablen res1 abgelegt. Im ab Zeile 18 folgenden if-Block wird das erhaltene Ergebnis überprüft. Dabei ist wichtig, dass bei einem korrekten Ergebnis die Rückgabe-Variable der Testfunktion ret_value auf den Wert 0 und im Fehlerfall auf den Wert 1 zu setzen ist. Zusätzlich kann man beliebige Meldungen im Konsolen-Fenster (unten) ausgeben. Die Variable ret_value wird am Ende der Testbench an den Aufrufer zurückgegeben.Nun kann es losgehen. Erster Schritt ist ein Klick auf die Schaltfläche Run C Simulation im Flow Navigator. Hier wird der C++-Code in der Funktion demo01 ebenso wie die Testbench von einem C++-Compiler übersetzt und dann ausgeführt. Das bedeutet, dass hier die grundsätzliche Korrektheit des Codes festgestellt wird. Eventuell auftretende Fehler lassen sich in der Vitis-HLS-IDE direkt beheben. Solange dieser Schritt nicht fehlerfrei beendet wird, kann man nicht fort­fahren. Im Dialog C Simulation werden die Einstellungen unverändert übernommen. Nach einigen Sekunden sollte im Hauptfenster das Ergebnis der C-Simulation erscheinen (Bild 4). Die Ausgaben in den Zeilen 6, 7 und 9 werden durch die print-Befehle der Testbench erzeugt.
Die C-Simulationwurde erfolgreich abgeschlossen(Bild 4) © Autor
Weiter geht es nun mit dem Befehl Run C Synthesis. Dieser Schritt ist eigentlich der wichtigste, denn nun wird die Hardware des HLS-IP aus dem C++-Code erzeugt. Im folgenden Dialog sind keine Änderungen einzugeben. Nach einer kurzen Wartezeit wird die Synthese (hoffentlich) erfolgreich beendet und im Hauptfenster werden einige interessante Informationen ausgegeben (Bild 5).
Die Ausgabennach der erfolgreichenC-Synthese(Bild 5) © Autor
Bild 5 zeigt nur einen Ausschnitt der ausgegebenen Daten. Zunächst erhält man wichtige Timing-Informationen. Da in der Demo nur zwei Zahlen addiert werden, passt die ganze Operation in einen Taktzyklus. Von den 10 ns werden nur (ungefähr) 2,115 ns benötigt. Danach folgen weitere Performance- und Ressourcen-Angaben. Die Ausgabe der Latency (Wartezeit, Latenz) erfolgt in Nanosekunden. Hier sind das 0 ns, weil das Ergebnis in einem Taktzyklus zur Verfügung steht. Weiter rechts wird es interessanter: Dort wird aufgelistet, wie viel RAM (Speicher), wie viele digitale Signal-Prozessoren (DSP), FlipFlops (FF) und Logical Unit Tables (LUT) für das Projekt notwendig sind. In diesem Fall werden allerdings nur 15 LUTs benutzt. Im FPGA wird die benötigte Logik nicht explizit in Und-, Oder- und Nicht-Gattern abgebildet. Stattdessen wird die Logik in LUTs in einer Tabellenform abgelegt. Das spart Ressourcen und ist sehr performant.Weiter unten werden außerdem noch Informationen zum Daten-Interface der HLS-Komponente ausgegeben. Hier sind die Variablen a und b als Input und der Rückgabewert (ap_return) mit einer Breite von jeweils 8 Bit aufgelistet.Im nächsten Schritt wird nun in der C/RTL-Cosimulation die im vorherigen Schritt erzeugte Hardware getestet. Das bedeutet, dass das erzeugte HLS-IP über die C++-Testbench aufgerufen wird. Natürlich müssen auch in diesem Fall die erwarteten Ergebnisse (siehe C-Simulation) erzeugt werden. Nach der Auswahl des Befehls Run Cosimulation im Flow Navigator lassen sich die vorgegebenen Eingaben im Dialog nahezu unverändert übernehmen. Es soll der Vivado-Simulator XSIM für die Tests zum Einsatz kommen. Der benötigte HLS-Code kann in Verilog oder VHDL erstellt werden. Hier wählen wir VHDL aus und starten die Simulation nun mit der OK-Schaltfläche. Als Ergebnis erhält man im Hauptfenster wiede­rum Performance-Daten, und im Konsolen-Fenster (unten) sollten erneut die korrekten Testergebnisse ausgegeben werden.Im letzten Schritt wird ein in Vivado nutzbares IP (Intellectual Property, geistiges Eigentum) erstellt. Dieses IP kann dann in ein Vivado-Block-Design eingefügt und mit weiterer Hardware (zum Beispiel Eingabeschalter oder Ausgabekomponenten) versehen werden. Um die vielen HLS-IPs etwas zu organisieren, habe ich im Ordner Vivado-Projects ein Unterverzeichnis HLS_IP angelegt. In diesem Ordner wird für jedes neue IP ein eigener Ordner angelegt. Vitis HLS erzeugt nämlich eine ZIP-Datei, die wiederum viele Dateien enthält, die dann einfach an dieser Stelle entpackt werden können. Der Befehl Export RTL erzeugt nun die finale ZIP-Datei mit dem Vivado-IP. Im Dialog (Bild 6) wird das Zielverzeichnis ausgewählt. Zudem ist es sinnvoll, den Namen des Erstellers und eine Versionsnummer anzugeben. Mit der Meldung Finished Export/RTL Implementation im Console-Fenster ist der Vorgang abgeschlossen. Nun wird die ZIP-Datei im Verzeichnis …/HLS_IP/Demo01 noch mit dem Windows-Explorer entpackt, um das IP für Vivado verfügbar zu machen. Die Vitis-HLS-Software kann beendet werden.
Exportierendes Vivado-IP(Bild 6) © Autor
Jetzt wenden wir uns dem zweiten Teil des Projekts zu. Das gerade erzeugte Additions-IP soll in einem Vivado-Block-Design mit Schaltern für die Eingabe der beiden Zahlen und mit LEDs für eine Ergebnisausgabe verbunden werden. Weitere Hardware ist in diesem einfachen Beispiel nicht erforderlich.Nach dem Start der Vivado-Software soll zunächst der Pfad zu den HLS-IPs in den Einstellungen der Software eingetragen werden [5]. Über den Befehl Tools | Settings kann man im Zweig IP Defaults im Eingabefeld IP Catalog mit der Plus-Schaltfläche das Verzeichnis Vivado-Projects/HLS_IP hinzufügen. An dieser Einstellung muss nichts mehr geändert werden, wenn die Ablage aller weiteren HLS-IPs als neue Unterordner in diesem Verzeichnis erfolgt.Mit dem Befehl Create Project erzeugt man nun im Unterverzeichnis Demo01/VIVADO ein neues Projekt mit dem ­Namen Demo01. Als FPGA-Board wählen wir das bekannte Basys-3-Board von Digilent aus. Nach dem Anklicken der ­Finish-Schaltfläche erhalten wir ein neues, leeres Vivado-Projekt. Im Project Manager wird nun im Zweig IP Integrator der Befehl Create Block Design aufgerufen. Es wird ein leeres Fenster generiert, in dem im Weiteren die benötigten IPs ­platziert und dann miteinander verbunden werden können. Als Design name kann man im Dialog den Namen TestHLS eingeben.Nach einem Klick auf das Pluszeichen mitten im Fenster ­erscheint ein kleiner Dialog, in dem man ein IP auswählen kann. Im Eingabefeld Search sollte man nun den Suchtext Demo eingeben, um die Liste der verfügbaren IPs etwas zu verkürzen (Bild 7). Unser HLS-IP hatte ja den Namen Demo01, der sich durch einen Doppelklick in das geöffnete Block-Design einfügen lässt.
Auswahldes HLS-IP„Demo01“(Bild 7) © Autor
Das Ergebnis nach dem Einfügen sollte wie in Bild 8 aussehen. Was man dort sieht, ist eigentlich sehr interessant und klärt schon einige Fragen hinsichtlich der weiteren Benutzung der Komponente. Es gibt hier die Schnittstelle ap_ctrl und drei Ports mit den Namen a, b und ap_return. Eigentlich ist klar, dass es sich hier um die beiden Parameter der Additionsfunktion und den dazugehörigen Rückgabewert (mit return) handelt. Die drei Ports haben die Breite [7:0] – also 8 Bit –, da in der Funktion den Datentyp char benutzt wurde. Die Datenübergabe erfolgt in diesem Fall – wie gewohnt – mit Call-by-value. Man kann die Daten in der Komponente ändern, was aber keinen Einfluss auf außen liegende Teile hat.
Das eingefügte IPim Block-Design(Bild 8) © Autor
Jetzt fehlt nur noch die Schnittstelle ap_ctrl. Man kann die einzelnen Ein- und Ausgänge betrachten, wenn man mit der Maus auf das Plus links neben ap_ctrl klickt (Bild 9). Eigentlich benötigen wir diese Anschlüsse im ersten Beispiel gar nicht. Trotzdem müssen wir zumindest den Eingang ap_start mit einem Taster des FPGA-Boards versehen, da sonst das Vivado-Tool bei der Hardware-Generierung eine Fehlermeldung ausgibt. Mit dieser Schnittstelle werden wir uns jedoch später noch mehrfach beschäftigen.
Das Interface „ap_ctrl“mit allen Ein- und Ausgängen(Bild 9) © Autor
Die Verdrahtung ist eigentlich ganz einfach. Die zweimal acht Anschlüsse der Ports a und b werden mit den 16 Schaltern des FPGA-Boards verbunden. Dazu klickt man mit der rechten Maustaste auf den Text a[7:0] im HLS-IP und ruft den Befehl Make External im Menü auf. Den Anschluss a_0[7:0] klickt man mit der linken Maustaste einmal an und benennt ihn dann links im Fenster External Port Properties in zahl1 um. Das Gleiche wird mit dem Anschluss b[7:0] durchgeführt. Als Name soll hier zahl2 eingesetzt werden. Der Ausgang ap_return der HLS-Komponente wird mit acht LEDs des Boards verbunden. Wiederum klickt man den Ausgang mit der rechten Maustaste an, ruft den Befehl Make External auf, klickt dann auf den generierten Ausgang ap_return_0[7:0] und gibt den neuen Namen ergebnis links im Fenster External Port Properties ein. Als Letztes soll noch der Anschluss
ap_start mit einem Taster auf dem Board verbunden werden. Auch hier kommt der Befehl Make External zum Einsatz, um dem Eingang den neuen Namen start zuzuweisen. Das Endergebnis der Aktionen ist in Bild 10 dargestellt.
Das HLS-IPmit allen externen Anschlüssen(Bild 10) © Autor
Die HLS-Komponente bekommt kein Clock-Signal zugewiesen, arbeitet also ohne einen Takt. Hier wird das Rechenergebnis ermittelt, sobald an den Eingangs-Ports geänderte Daten anliegen.Nun muss noch die XDC-Datei (Constraints) erstellt werden, um die definierten externen Anschlüsse mit der Hardware (also den LEDs, Schaltern und Buttons) zu verbinden. Diese Datei soll hier nicht aufgelistet werden. Sie können die Datei Demo01.xdc in den Downloads zum Artikel unter [7] herunterladen. Diese Datei ist jedoch noch in das Projekt selbst einzufügen. Dazu klickt man im Fenster Sources auf die Plus-Schaltfläche und wählt im Dialog den Punkt Add or create constraints aus. Nach Next folgt ein Klick auf die Schaltfläche Add Files und im Dateiauswahl-Dialog kann man die heruntergeladene XDC-Datei auswählen. Den Vorgang schließt man mit der Open-Schaltfläche ab und die Constraints-Datei wird in das Projekt an der korrekten Stelle ein­gefügt.Im Grunde genommen sind nun alle Teile des Projekts vorhanden, allerdings fehlt noch die Top-Level-Datei für das Vivado-Block-Design. In dieser VHDL-Datei werden alle im Block-Design benutzten Komponenten instanziert und mit
den erforderlichen Verbindungen unter­ei­nander versehen. Außerdem werden die Anschlüsse nach außen bereitgestellt (siehe oben: Make External). Hierzu klickt man mit der rechten Maustaste im Fenster Sources auf das erstellte Block-Design und ruft den Befehl Create HDL Wrapper … auf. Im Dialog wird die Op­tion Let Vivado manage wrapper and auto-update aktiviert. Nach Anklicken der OK-Schaltfläche ist Vivado für einige Sekunden mit der Neuordnung des Projekts beschäftigt. Danach wird unten im Flow Navigator der Befehl Generate Bitstream (im Zweig PROGRAM AND DEBUG) aufgerufen und mit den Schaltflächen Yes und OK der Übersetzungsvorgang gestartet. Auch diese Aktion dauert, je nach Rechner, einige Zeit. Der Vorgang lässt sich über den Cancel-Link (ganz links oben) abbrechen (Bild 11). Zusätzliche Ausgaben werden im Tab Messages dargestellt. Hier erscheinen dann auch die eher unerwünschten Fehlermeldungen.
Abbrechender Bitstream-Generierung mit „Cancel“(Bild 11) © Autor
In der Zwischenzeit kann auch das Basys-3-Board an einen USB-Port des Computers angeschlossen werden. Im finalen Dialog nach der Bitstream-Erzeugung erfolgt der Aufruf der Option Open Hardware Manager. Danach folgt der Befehl Open Target | Auto Connect, um die Verbindung mit dem Board herzustellen. Schließlich wird mit Program Device (dann im Kontextmenü: xc7a35t_0 wählen) und nach dem Bestätigen mit der Schaltfläche Program der Bitstream in den FPGA-Chip transferiert und ausgeführt.In Bild 12 werden die Zahlen 11 (links SW8 bis SW15) und 7 (rechts SW0 bis SW7) addiert. Das Ergebnis 18 (Dual: 00010010) ist in der LED-Zeile abzulesen. Ja, das ist nun unser neuer HLS-Taschenrechner!
Die Diodenzeigen das Ergebnis der Addition 11 + 7 = 18(Bild 12) © Autor
Mit diesem sehr einfachen Beispiel haben Sie nun den kompletten Ablauf der Erzeugung und Benutzung einer HLS-Komponente mit Vitis HLS und Vivado kennengelernt. Normalerweise wird im Block-Design von Vivado wesentlich mehr Hardware definiert. Dies ist zum Beispiel erforderlich, wenn das Rechenergebnis mit der 7-Segment-Anzeige des Boards ausgegeben werden soll.

Ein weiteres Beispiel mit Zeigern und ap_int<...>

Im zweiten Beispiel, das wir jetzt aber wesentlich kürzer abhandeln können, sollen C/C++-Zeiger implementiert werden, damit aus einer HLS-Funktion mehrere Ergebniswerte zurückgegeben werden können. Hier ist noch ein wichtiger Punkt zu beachten: Über einen Zeiger kann man einen Parameter in die HLS-Komponente hineinbringen, man kann aber auch einen Wert zurückgeben. Die Zeiger lassen sich im C++-Code des HLS-IP mit der bekannten Syntax benutzen. Allerdings ist Zeiger­arithmetik mit Port-Variablen (also mit den Übergabe-Parametern in der Top-Level-Funktion) nicht erlaubt. Im ersten Simulationsschritt funktionieren diese Zeiger zwar, bei der Synthese im zweiten Schritt gibt es aber eine Fehlermeldung. Zeiger, welche auf im HLS-IP erzeugte Objekte (zum Beispiel Arrays) angewendet werden, funktionieren jedoch ganz normal – auch mit Zeiger-Arithmetik.Außerdem sollen in diesem Beispiel Integer-Formate mit einer genau definierten Bitbreite (arbitrary precision = ap) im C++-Code verwendet werden, um Ressourcen des FPGA-Chips zu sparen.Natürlich kann man in den HLS-IPs die Standard-C/C++-Datentypen für ganze Zahlen verwenden (char, short, int und long, sowie die unsigned-Typen). Allerdings können die Datentypen eine viel zu große Bitbreite für die gewünschte Anwendung haben. Da die Ressourcen in einem FPGA begrenzt sind, ist es in der Regel sinnvoll, die Bitbreite der Variablen exakt in der Größe zu definieren, die erforderlich ist. C++ bietet dazu das Template ap_int<n> und ap_uint<n> an (#in­clude <ap_int.h> nicht vergessen!). Hier ist n die gewünschte Bitbreite der ganzen Zahl. Listing 2 zeigt einige Beispiele.
Listing 2: Verschiedene Integer-Deklarationen
#include &amp;lt;ap_int.h&amp;gt; &lt;br/&gt;ap_int&amp;lt;&lt;span class="hljs-number"&gt;4&lt;/span&gt;&amp;gt; i4 = &lt;span class="hljs-number"&gt;3&lt;/span&gt;;      // &lt;span class="hljs-number"&gt;4&lt;/span&gt;&lt;span class="hljs-built_in"&gt;bit&lt;/span&gt;-&lt;span class="hljs-built_in"&gt;Integer&lt;/span&gt; von -&lt;span class="hljs-number"&gt;8&lt;/span&gt; bis +&lt;span class="hljs-number"&gt;7&lt;/span&gt; &lt;br/&gt;ap_int&amp;lt;&lt;span class="hljs-number"&gt;7&lt;/span&gt;&amp;gt; i7 = &lt;span class="hljs-number"&gt;256&lt;/span&gt;;    // &lt;span class="hljs-number"&gt;7&lt;/span&gt;&lt;span class="hljs-built_in"&gt;bit&lt;/span&gt;-&lt;span class="hljs-built_in"&gt;Integer&lt;/span&gt; von -&lt;span class="hljs-number"&gt;64&lt;/span&gt; bis &lt;br/&gt;                      // +&lt;span class="hljs-number"&gt;63&lt;/span&gt; = PROBLEM! &lt;br/&gt;ap_uint&amp;lt;&lt;span class="hljs-number"&gt;8&lt;/span&gt;&amp;gt; ui8 = &lt;span class="hljs-number"&gt;255&lt;/span&gt;;  // &lt;span class="hljs-number"&gt;8&lt;/span&gt;&lt;span class="hljs-built_in"&gt;bit&lt;/span&gt;-&lt;span class="hljs-built_in"&gt;Integer&lt;/span&gt; (&lt;span class="hljs-built_in"&gt;unsigned&lt;/span&gt;) &lt;br/&gt;                      // von &lt;span class="hljs-number"&gt;0&lt;/span&gt; bis +&lt;span class="hljs-number"&gt;255&lt;/span&gt; &lt;br/&gt;ap_uint&amp;lt;&lt;span class="hljs-number"&gt;3&lt;/span&gt;&amp;gt; ui3 = &lt;span class="hljs-number"&gt;1&lt;/span&gt;;    // &lt;span class="hljs-number"&gt;3&lt;/span&gt;&lt;span class="hljs-built_in"&gt;bit&lt;/span&gt;-&lt;span class="hljs-built_in"&gt;Integer&lt;/span&gt; (&lt;span class="hljs-built_in"&gt;unsigned&lt;/span&gt;) &lt;br/&gt;                      // von &lt;span class="hljs-number"&gt;0&lt;/span&gt; bis +&lt;span class="hljs-number"&gt;7&lt;/span&gt; = PROBLEM!  
In den Zeilen 2 und 4 von Listing 2 gibt es keine Fehlermeldung. Hier ist die Einstellung des Overflow-Modes von Bedeutung, der sich als weiterer Template-Parameter angeben lässt [8].Mit C-Code kann man die Templates natürlich nicht benutzen. In diesem Fall gibt es diverse #define-Statements in einer speziellen Header-Datei, die alle möglichen Bitbreiten fertig definiert (#include <ap_cint.h>).Weiterhin gibt es auch spezielle Festkommaformate, die mit den Templates ap_fixed<m,n> und ap_ufixed<m,n> für ­signed- und unsigned-Variablen erzeugt werden können [8]. Hierbei gibt m die Gesamtzahl der Bits für die Festkommazahl an, und n ist die Anzahl der Bits vor dem Komma (inklusive Vorzeichen). Einige Beispiele sind in Listing 3 zu sehen.
Listing 3: Verschiedene Festkomma-Deklarationen
&lt;span class="hljs-meta"&gt;#&lt;span class="hljs-meta-keyword"&gt;include&lt;/span&gt; &lt;span class="hljs-meta-string"&gt;&amp;lt;ap_fixed.h&amp;gt;&lt;/span&gt; &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-meta"&gt;ap_fixed&lt;span class="hljs-meta-string"&gt;&amp;lt;16,8&amp;gt;&lt;/span&gt; a1 = 3.5; // 16bit Breite, 8bit vor &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-meta"&gt;                        // dem Komma, mit Vorzeichen &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-meta"&gt;ap_ufixed&lt;span class="hljs-meta-string"&gt;&amp;lt;8,4&amp;gt;&lt;/span&gt; a2 = 2.6; // 8bit Breite, 4bit vor dem &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-meta"&gt;                          // Komma, ohne Vorzeichen &lt;/span&gt; 
Die Wertebereiche und Umrechnung der diversen Formate lässt man sich am besten von einem Tool ausgeben (zum Beispiel [9]; Achtung: Dort sind m und n anders definiert!).Nun kann es losgehen. Zunächst werden wieder die drei Basis-Dateien mit dem Editor erstellt. Das Projekt bekommt den Namen Demo02, also heißen die Dateien Demo02_HLS.cpp, Demo02_HLS.h und Demo02_HLS_Test.cpp(Listing 4).
Listing 4: Das zweite HLS-IP mit Zeigern und speziellen Datentypen
// ***** Demo02_HLS.h ****************** &lt;br/&gt;#ifndef _DEMO02_HLS_H_ &lt;br/&gt;#&lt;span class="hljs-built_in"&gt;define&lt;/span&gt; _DEMO02_HLS_H_ &lt;br/&gt;&lt;br/&gt;#include &amp;lt;ap_fixed.h&amp;gt; &lt;br/&gt;#include &amp;lt;ap_int.h&amp;gt; &lt;br/&gt;&lt;br/&gt;typedef ap_fixed&amp;lt;&lt;span class="hljs-number"&gt;16&lt;/span&gt;, &lt;span class="hljs-number"&gt;8&lt;/span&gt;&amp;gt; din1_t; &lt;br/&gt;typedef ap_int&amp;lt;&lt;span class="hljs-number"&gt;6&lt;/span&gt;&amp;gt; din2_t; &lt;br/&gt;typedef ap_fixed&amp;lt;&lt;span class="hljs-number"&gt;16&lt;/span&gt;, &lt;span class="hljs-number"&gt;8&lt;/span&gt;&amp;gt; dout_t; &lt;br/&gt;&lt;br/&gt;// Top Level Function Declaration &lt;br/&gt;void demo02(din1_t a, din2_t iFak, dout_t* y1, &lt;br/&gt;  dout_t* y2); &lt;br/&gt;&lt;br/&gt;#endif &lt;br/&gt;&lt;br/&gt;// ***** Demo02_HLS.cpp **************** &lt;br/&gt;#include &lt;span class="hljs-string"&gt;"Demo02_HLS.h"&lt;/span&gt; &lt;br/&gt;&lt;br/&gt;// *** Top Level Function &lt;br/&gt;void demo02(din1_t a, din2_t iFak, dout_t* y1, &lt;br/&gt;    dout_t* y2) &lt;br/&gt;{ &lt;br/&gt;  din1_t dHelp = a + (din1_t)&lt;span class="hljs-number"&gt;1.&lt;/span&gt;1f; &lt;br/&gt;  &lt;br/&gt;  *y1 = (dHelp + *y1) / (iFak + &lt;span class="hljs-number"&gt;1&lt;/span&gt;); &lt;br/&gt;  *y2 = dHelp / (iFak + &lt;span class="hljs-number"&gt;3&lt;/span&gt;); &lt;br/&gt;} &lt;br/&gt;&lt;br/&gt;// ***** Demo02_HLS_Test.h ************* &lt;br/&gt;#include &amp;lt;stdio.h&amp;gt; &lt;br/&gt;#include &amp;lt;math.h&amp;gt; &lt;br/&gt;&lt;br/&gt;#include &lt;span class="hljs-string"&gt;"Demo02_HLS.h"&lt;/span&gt; &lt;br/&gt;&lt;br/&gt;bool compFixed(dout_t testVal, &lt;span class="hljs-built_in"&gt;float&lt;/span&gt; value, &lt;br/&gt;    &lt;span class="hljs-built_in"&gt;float&lt;/span&gt; &lt;span class="hljs-built_in"&gt;delta&lt;/span&gt;) &lt;br/&gt;{ &lt;br/&gt;  // Returns &lt;span class="hljs-literal"&gt;true&lt;/span&gt;, &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; Test &lt;span class="hljs-built_in"&gt;is&lt;/span&gt; passed, &lt;span class="hljs-literal"&gt;false&lt;/span&gt; &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; Test fails &lt;br/&gt;&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;if&lt;/span&gt;(fabs((&lt;span class="hljs-built_in"&gt;float&lt;/span&gt;)testVal - value) &amp;lt;= &lt;span class="hljs-built_in"&gt;delta&lt;/span&gt;) &lt;br/&gt;  { &lt;br/&gt;    &lt;span class="hljs-built_in"&gt;return&lt;/span&gt; &lt;span class="hljs-literal"&gt;true&lt;/span&gt;; &lt;br/&gt;  } &lt;br/&gt;&lt;br/&gt;  &lt;span class="hljs-built_in"&gt;return&lt;/span&gt; &lt;span class="hljs-literal"&gt;false&lt;/span&gt;; &lt;br/&gt;} &lt;br/&gt;&lt;br/&gt;int main(void) &lt;br/&gt;{ &lt;br/&gt;  // Deklarationen &lt;br/&gt;  din1_t x1 = (din1_t)&lt;span class="hljs-number"&gt;2.&lt;/span&gt;5f; &lt;br/&gt;  din2_t &lt;span class="hljs-built_in"&gt;i1&lt;/span&gt; = (din2_t)&lt;span class="hljs-number"&gt;4&lt;/span&gt;; &lt;br/&gt;  dout_t y1 = (din1_t)&lt;span class="hljs-number"&gt;1.&lt;/span&gt;5f; &lt;br/&gt;  dout_t y2 = (din1_t)&lt;span class="hljs-number"&gt;0.&lt;/span&gt;&lt;span class="hljs-number"&gt;0f&lt;/span&gt;; &lt;br/&gt;&lt;br/&gt;  dout_t res1; &lt;br/&gt;  int ret_value; &lt;br/&gt;&lt;br/&gt;  // Berechnungen &lt;br/&gt;  demo02(x1, &lt;span class="hljs-built_in"&gt;i1&lt;/span&gt;, &amp;amp;y1, &amp;amp;y2); &lt;br/&gt;  &lt;span class="hljs-built_in"&gt;printf&lt;/span&gt;(&lt;span class="hljs-string"&gt;"Result: y1=%f  y2=%f\n"&lt;/span&gt;, (&lt;span class="hljs-built_in"&gt;float&lt;/span&gt;)y1, &lt;br/&gt;    (&lt;span class="hljs-built_in"&gt;float&lt;/span&gt;)y2); &lt;br/&gt;&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;if&lt;/span&gt;(!compFixed(y1, &lt;span class="hljs-number"&gt;1.&lt;/span&gt;&lt;span class="hljs-number"&gt;019531f&lt;/span&gt;, &lt;span class="hljs-number"&gt;0.&lt;/span&gt;&lt;span class="hljs-number"&gt;0001f&lt;/span&gt;) || &lt;br/&gt;      !compFixed(y2, &lt;span class="hljs-number"&gt;0.&lt;/span&gt;511719f, &lt;span class="hljs-number"&gt;0.&lt;/span&gt;&lt;span class="hljs-number"&gt;0001f&lt;/span&gt;)) &lt;br/&gt;  { &lt;br/&gt;    fprintf(stdout, &lt;span class="hljs-string"&gt;"\nTest FAILED!\n"&lt;/span&gt;); &lt;br/&gt;    ret_value = &lt;span class="hljs-number"&gt;1&lt;/span&gt;;  // ERROR &lt;br/&gt;  } &lt;br/&gt;  &lt;span class="hljs-keyword"&gt;else&lt;/span&gt; &lt;br/&gt;  { &lt;br/&gt;    fprintf(stdout, &lt;span class="hljs-string"&gt;"\nTest PASSED!\n"&lt;/span&gt;); &lt;br/&gt;    ret_value = &lt;span class="hljs-number"&gt;0&lt;/span&gt;;  // OK &lt;br/&gt;  } &lt;br/&gt;&lt;br/&gt;  &lt;span class="hljs-built_in"&gt;return&lt;/span&gt; ret_value; &lt;br/&gt;}  
Dann wird, wie bereits oben beschrieben, das Vitis-HLS-Projekt mit den drei Dateien erzeugt. Die ausgewählte Top-Level-Funktion wird folgendermaßen deklariert:
void demo02(din1_t a, din2_t iFak, dout_t* y1, 
  dout_t* y2) 
Es gibt also keinen Rückgabewert, was bedeutet, dass die Komponenten-Schnittstelle keinen Port ap_return (wie im ersten Beispiel) enthält. Die beiden Parameter a und iFak werden über ganz normale Ports abgebildet und sind 16 Bit beziehungsweise 6 Bit breit. Jetzt kommt der interessante Teil: Das IP arbeitet mit zwei Zeigern, y1 wird gelesen und geschrieben, y2 wird nur gelesen. Aus diesem Grund werden für y1 zwei Ports (in und out) und für y2 nur ein Port (out) erzeugt. Diese sind im Vivado-Block-Design entsprechend zu verdrahten.Wir wollen das IP nun synthetisieren. Dazu müssen wieder die vier Schritte im Flow Navigator ausgeführt werden. Zum Schluss erhalten wir eine ZIP-Datei, die dann im Unterverzeichnis Demo02 entpackt und mit Vivado benutzt werden kann.Anschließend erzeugen wir im Unterverzeichnis Demo02/VIVADO ein neues Projekt Demo02 mit Vivado mit den gleichen Grundeinstellungen wie im ersten Beispiel. Dann wird ein Block-Design erstellt und das neue HLS-IP über die Plus-Taste (Name: Demo02) eingefügt. Die Schnittstellen der Komponente sind in Bild 13 dargestellt.
Die Schnittstellemit den Zeigern y1 und y2(Bild 13) © Autor
Neben den beiden 1 Bit breiten Standard-Ports ap_clk (Takt) und ap_res (Reset) sieht man die Ports für die Parameter a (16 Bit) und iFak (6 Bit). Interessant wird es nun bei den Zeigern. Noch mal zur Erinnerung: y1 wird geschrieben und gelesen. Darum werden zwei Ports in 16-Bit-Breite implementiert. Der erste Port y1_i wird für das Einlesen des Parameters in das HLS-IP benutzt. Port y1_o dient der Ausgabe des geänderten Parameters. Allerdings gehört zum Ausgabe-Port noch das Status-Bit y1_o_ap_vld. Dieses Bit hat den Wert true, wenn das Signal am Port y1_o gelesen werden darf, das heißt, wenn es dann tatsächlich auch zur Verfügung steht. Der Compiler erkennt, dass der Parameter y2 nur mit einem neuen Wert beschrieben wird. Er wird nicht als Eingabe benutzt. Darum werden nur der Port y2 und das dazugehörige Status-Bit y2_ap_vld implementiert.Damit stellt das HLS-IP alle Signale bereit, die für eine Benutzung in Vivado notwendig sind. So könnten a und iFak zum Beispiel aus einem ROM-Baustein in die Komponente eingelesen werden. Die Parameter y1 und y2 können in RAM-Bausteinen verwaltet oder mit einem Ausgabegerät verarbeitet werden. Hierbei muss man gegebenenfalls die ­Signale y1_o_ap_vld und y2_ap_vld korrekt auswerten. Das Vivado-Block-Design wollen wir an dieser Stelle jedoch nicht vorstellen.

Zusammenfassung

In diesem Artikel haben Sie erfahren, wie Sie einfache HLS-IPs in C/C++ erstellen. In zwei Beispielen wurde dargestellt, wie Eingabe- und Ausgabeparameter mit dem Code der Komponente verbunden werden. Das ist bisher alles sehr übersichtlich und anschaulich. Es fehlt jedoch noch ein sehr wichtiges Thema: Arrays – und die Schleifen, in denen man die Array-Elemente verarbeitet. Hier gibt es sehr viele Optimierungsmöglichkeiten für die Performance und den Ressourcenverbrauch. Im Bereich der High-Level-Synthese sind Arrays und Schleifen das beherrschende Thema. Aus diesem Grund kommen die verschiedenen Möglichkeiten in diesem Bereich im nächsten Artikel ausführlich zur Sprache.
Projektdateien herunterladen

Fussnoten

  1. Bernd Marquardt, Die flexible Hardware, dotnetpro 7/2021, Seite 128 ff., http://www.dotnetpro.de/A2107FPGA
  2. Bernd Marquardt, Der Simulant, dotnetpro 8/2021, Seite 130 ff., http://www.dotnetpro.de/A2108FPGA
  3. Bernd Marquardt, Hardware durch Software, dotnetpro 9/2021, Seite 127 ff., http://www.dotnetpro.de/A2109FPGA
  4. Bernd Marquardt, Stein auf Stein, dotnetpro 1/2022, Seite 120 ff., http://www.dotnetpro.de/A2201FPGA
  5. Siehe Datei im Download-Verzeichnis …\WeitereDateien\Demo01_AlleDialoge_Vivado.pdf, ganz am Anfang,
  6. Siehe Datei im Download-Verzeichnis …\WeitereDateien\Installation.pdf,
  7. Siehe Datei im Download-Verzeichnis …\WeitereDateien,
  8. Overview of Arbitrary Precision Fixed-Point Data Types, http://www.dotnetpro.de/SL2309FPGA1
  9. Q-format Converter & Calculator, http://www.dotnetpro.de/SL2309FPGA2

Neueste Beiträge

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

Mehr Code, weniger Scrollen
Wie der BenQ RD280U und die RD-Serie die Produktivität von Entwicklern steigern (Sponsored Post)
3 Minuten
24. Jun 2025
UIs für Linux - Bedienoberflächen entwickeln mithilfe von C#, .NET und Avalonia
Es gibt viele UI-Frameworks für .NET, doch nur sehr wenige davon unterstützen Linux. Avalonia schafft als etabliertes Open-Source-Projekt Abhilfe.
16 Minuten
16. Jun 2025
Mythos Motivation - Teamentwicklung
Entwickler bringen Arbeitsfreude und Engagement meist schon von Haus aus mit. Diesen inneren Antrieb zu erhalten sollte für Führungskräfte im Fokus stehen.
13 Minuten
19. Jan 2017
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige