Aufbau und Programmierung FPGA-basierter Designs mit Python und Jupyter-Notebook
Zur Verfügung gestellt von Nordamerikanische Fachredakteure von DigiKey
2019-04-03
Üblicherweise haben Entwickler mit FPGAs (Field Programmable Gate Arrays) gearbeitet, um die Leistung in Hardwaredesigns für rechenintensive Anwendungen wie maschinelles Sehen, Kommunikationssysteme, industrielle Embedded-Systeme und in zunehmendem Maße auch das Internet der Dinge (IoT) zu beschleunigen. Da die herkömmliche FPGA-Programmierung jedoch nicht ganz unkompliziert war, haben sich die Entwickler nur allzu gerne nach alternativen Verarbeitungslösungen umgesehen. Jetzt sind sie fündig geworden.
Die auf Jupyter-Notebooks basierende Entwicklungsumgebung Python Productivity for Zynq (PYNQ) nimmt sich dem Problem der Programmierbarkeit von FPGAs an. Mithilfe eines speziell für PYNQ konzipierten Entwicklungsboards können selbst Entwickler mit geringer FPGA-Erfahrung im Handumdrehen Designs implementieren, die die Vorteile von FPGAs bei der Beschleunigung rechenintensiver Anwendungen in vollem Umfang nutzen.
Dieser Artikel beschreibt den typischen FPGA-Ansatz, bevor er das Entwicklungsboard von Digilent vorstellt und auf ihre Verwendung eingeht. Dieses Board stellt eine leistungsstarke Open-Source-Alternative zur schnelleren Entwicklung von FPGA-basierten Systemen dar.
Warum FPGAs?
Entwickler, die mit komplexen, rechenintensiven Algorithmen arbeiten, greifen häufig auf FPGAs zurück, um ihre Ausführung zu beschleunigen, ohne dabei die knappen Energiebudgets zu sprengen. Tatsächlich haben sich FPGAs zu einer dominanten Plattform zur Beschleunigung von KI-Algorithmen entwickelt, wie sie in Randknotensystemen (Edge Computing) verwendet werden (siehe „Verwenden Sie FPGAs zum Erstellen eingebetteter High-Performance-Vision-Anwendungen mit maschinellem Lernen“).
Speziell für Embedded-Anwendungen konzipierte, modernere FPGA-SoC-Komponenten (SoC: System-on-Chip) integrieren eine programmierbare Logikstruktur (PL-Struktur) in einen Mikrocontroller. Beispielsweise kombiniert das SoC Xilinx Zynq-7000 ein Dual-Core-Arm®-Cortex®-A9-Prozessorsystem mit bis zu 444.000 Logikzellen in der integrierten programmierbaren Logikstruktur (Abbildung 1). Neben den integrierten Prozessoren und einer großen Auswahl an Peripheriebausteinen bietet das Zynq-SoC bis zu 2020 Blöcke zur digitalen Signalverarbeitung (sogenannte DSP-Slices). Mit diesen Ressourcen können die Entwickler für die PL-Struktur spezielle Verarbeitungsketten konfigurieren, die zur Beschleunigung des Durchsatzes in komplexen, rechenintensiven Algorithmen erforderlich sind.
Abbildung 1: Das SoC Zynq-7000 von Xilinx bietet einen Dual-Core-Arm-Cortex-A9-Prozessor, eine PL-Struktur sowie eine große Auswahl an Peripheriebausteinen und Schnittstellen, die in vielen eingebetteten Anwendungen benötigt werden. (Bildquelle: Xilinx)
Neben der Reduzierung der benötigten Bauteile ermöglicht die Integration der Prozessoren und der PL-Struktur Vorgänge über On-Chip-Busse statt über den Off-Chip-Zugriff. Des Weiteren vereinfacht diese Infrastruktur die kritische Aufgabe des Ladens der PL-Struktur beim Einschalten oder beim Zurücksetzen.
In einem typischen System auf Mikrocontrollerbasis mit einem FPGA mussten die Entwickler die Sequenz und die Sicherheit für das Laden von Bitstreams zum Programmieren des FPGA verwalten. Mit dem Zynq-SoC übernimmt ein integrierter Prozessor die Aufgaben eines herkömmlichen Mikrocontrollers, so auch die Verwaltung der PL-Struktur und weiterer On-Chip-Peripheriebausteine. Von daher gleicht der FPGA-Ladeprozess eher dem Boot-Vorgang eines konventionellen Mikrocontrollers als der Initialisierung eines FPGA-Bitstreams.
Dieser Boot-Vorgang erfolgt über eine kurze Abfolge von Schritten, die von einem der Prozessoren des Zynq verwaltet werden (Abbildung 2). Der Boot-Vorgang beim Einschalten oder Zurücksetzen startet, wenn ein Zynq-Prozessor einen kurzen Code aus seinem schreibgeschützten Boot-ROM ausführt, um den tatsächlichen Boot-Code von einem Boot-Gerät abzurufen. Neben dem Code zur Konfiguration der Komponenten des Prozessorsystems beinhaltet der Boot-Code den Bitstream der programmierbaren Logik (PL) sowie die Benutzeranwendung. Nach dem Laden des Boot-Codes verwendet der Prozessor den enthaltenen Bitstream zur Konfiguration der PL. Nachdem alle Konfigurationen abgeschlossen sind, beginnt die Komponente mit der Ausführung der im Boot-Code enthaltenen Anwendung.
Abbildung 2: In einer mit herkömmlichen Mikrocontrollern vergleichbaren Boot-Sequenz führt ein SoC Zynq-7000 von Xilinx Code aus dem Boot-ROM aus. Dieser Code lädt und führt den Bootloader aus, der sich um die nachfolgenden Schritte kümmert, inklusive der Verwendung eines im Boot-Code enthaltenen Bitstreams zur Konfiguration der PL-Struktur. (Bildquelle: Xilinx)
Trotz der Vereinfachung des Ladevorgangs der PL mussten sich die Entwickler früher nach wie vor mit der komplexen FPGA-Entwicklung auseinandersetzen, die zur Erzeugung der erforderlichen Bitstreams nötig war. Für Entwickler, die die Leistung von FPGAs nutzen wollten, stellte die herkömmliche FPGA-Entwicklung auch weiterhin eine enorme Hürde bei der Implementierung dar. Diese Hürde konnte Xilinx mit seiner PYNQ-Umgebung aus dem Weg räumen.
Die PYNQ-Umgebung
In PYNQ sind die PL-Bitstreams in vorgefertigten Bibliotheken enthalten, sogenannten Overlays. Diese erfüllen eine ähnliche Rolle wie Softwarebibliotheken im Entwicklungsprozess und der Ausführungsumgebung. Während dem Boot-Ladeprozess übernehmen mit den erforderlichen Overlays verknüpfte Bitstreams die Konfiguration der PL-Struktur. Dieser Prozess bleibt für die Entwickler jedoch transparent, die die Funktionalität des Overlays über die Anwendungsprogrammierschnittstelle (API) von Python nutzen, die mit jedem Overlay verknüpft ist. Während der Entwicklung können die Entwickler Softwarebibliotheken und Overlays ganz nach Bedarf kombinieren, indem sie über ihre jeweiligen APIs zur Implementierung der Anwendung arbeiten. Während der Ausführung führt das Prozessorsystem wie üblich Code aus der Softwarebibliothek aus, während die PL-Struktur die im Overlay bereitgestellte Funktionalität implementiert. Auf diese Weise ergibt sich die beschleunigte Leistung, die dafür verantwortlich ist, dass FPGA-basierte Designs auch für zunehmend anspruchsvolle Anwendungen interessant bleiben.
Wie der Name schon sagt nutzt PYNQ die Produktivitätssteigerung bei der Entwicklung im Zusammenhang mit der Programmiersprache Python. Python hat sich zu einer der wichtigsten Sprachen entwickelt, was nicht nur daran liegt, dass sie relativ einfach ist, sondern auch an der Tatsache, dass umfassende Ressourcen zur Verfügung stehen. Entwickler können jederzeit Softwarebibliotheken für Supportservices finden oder spezielle Algorithmen in den Repositories von Open-Source-Python-Modulen. Gleichzeitig können die Entwickler wichtige Funktionen in C implementieren, da PYNQ die verbreitete C-Implementierung des Python-Interpreters nutzt. Diese Implementierung bietet Zugriff auf Tausende von C-Bibliotheken, die von anderen Entwicklern bereitgestellt werden. Obwohl erfahrene Entwickler PYNQ mit speziellen Hardware-Overlays und C-Softwarebibliotheken erweitern können, liegen die Stärken von PYNQ in der Bereitstellung einer überaus produktiven Entwicklungsumgebung für jeden Entwickler, der in der Lage ist, ein Python-Programm zu schreiben.
PYNQ, selbst ein Open-Source-Projekt, baut auf dem Open-Source-Projekt Jupyter Notebook auf. Jupyter-Notebooks bieten eine besonders effiziente Umgebung zur interaktiven Untersuchung von Algorithmen und für die Prototyperstellung zu komplexen Anwendungen in Python oder jeder anderen der derzeit über 40 unterstützten Programmiersprachen. Die im Rahmen von Project Jupyter im gemeinschaftlichen Konsens entwickelten Jupyter-Notebooks kombinieren Zeilen von ausführbarem Code mit beschreibendem Text und Grafiken. Auf diese Weise können einzelne Entwickler ihren Fortschritt besser dokumentieren, ohne in eine andere Entwicklungsumgebung wechseln zu müssen. Beispielsweise kann ein Entwickler ein Notebook verwenden, das einige Zeilen Code zur Anzeige der Daten mit der vom Code erzeugten Grafik kombinieren (Abbildung 3).
Abbildung 3: Ein Jupyter-Notebook aus einem Beispiel-Repository von Xilinx kombiniert beschreibenden Text, ausführbaren Code und einen Ausgabe, die mit einer Anwendung verbunden ist. (Bildquelle: Xilinx)
Die Möglichkeit zur Kombination von Code, Ausgabe und beschreibendem Text ist deshalb gegeben, weil es sich bei einem Jupyter-Notebook um ein Live-Dokument handelt, das in einer interaktiven Entwicklungsumgebung verwaltet wird, die von einem Jupyter-Notebook-Server bereitgestellt wird (Abbildung 4). In einer Jupyter-Sitzung stellt der Server die Notebook-Datei mit HTTP in einem herkömmlichen Webbrowser dar. Für den statischen und dynamischen Inhalt im dargestellten Dokument verwendet der Server eine Kombination aus HTTP und WebSocket-Protokollen. Auf dem Back-End kommuniziert der Server über das Open-Source-Nachrichtenprotokoll ZeroMQ (ØMQ) mit einem Kernel zur Ausführung des Codes.
Abbildung 4: In einer Jupyter-Sitzung stellt ein Notebook-Server den Inhalt einer Notebook-Datei in einem Webbrowser dar, während er mit einem Back-End-Kernel interagiert, der den Code ausführt. (Bildquelle: Project Jupyter)
Im Bearbeitungsmodus kann der Benutzer Text und Code modifizieren. Der Server wiederum aktualisiert die entsprechende Notebook-Datei, bei der es sich um eine Textdatei handelt, die aus einer Reihe von Schlüssel-Wert-Paaren im JSON-Format besteht. Diese Zellen werden in der Jupyter-Umgebung als Zellen bezeichnet. Die Webbrowser-Anzeige des Jupyter-Notebooks weiter oben umfasst einige wenige Zellen Code und Text (Listing 1).
Kopieren
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Error plot with Matplotlib\n",
"This example shows plots in notebook (rather than in separate window)."
]
}, {
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAA[truncated]",
"text/plain": [
"<matplotlib.figure.Figure at 0x2f85ef50>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%matplotlib inline\n",
" \n",
"X = np.arange(len(values))\n",
"plt.bar(X + 0.0, values, facecolor='blue', \n",
" edgecolor='white', width=0.5, label=\"Written_to_DAC\")\n",
"plt.bar(X + 0.25, samples, facecolor='red', \n",
" edgecolor='white', width=0.5, label=\"Read_from_ADC\")\n",
"\n",
"plt.title('DAC-ADC Linearity')\n",
"plt.xlabel('Sample_number')\n",
"plt.ylabel('Volts')\n",
"plt.legend(loc='upper left', frameon=False)\n",
"\n",
"plt.show()"
]
},
Abbildung 1: Ein Jupyter-Notebook ist eine Textdatei, die aus einer Reihe von Schlüssel-Wert-Paaren im JSON-Format besteht, die Code-Abschnitte, Text und Ausgaben wie diese enthalten, die der in Abbildung 3 gezeigten Seite entsprechen. Beachten Sie, dass die dem PNG-Bild in dieser Abbildung entsprechende Zeichenfolge zu Darstellungszwecken gekürzt wurde. (Code-Quelle: Xilinx)
Neben ihren Dokumentationsfunktionen beruht die Leistungsfähigkeit der Jupyter-Umgebung auf der Möglichkeit, Code-Zellen interaktiv auszuführen. Entwickler wählen im Browser einfach die gewünschte Zelle aus (blaue Umrandung in Abbildung 3) und klicken im Jupyter-Menü oben im Browserfenster auf die Schaltfläche zum Ausführen des Codes. Der Jupyter-Notebook-Server wiederum übermittelt den entsprechenden Code an einen Kernel zur Ausführung des Codes. Hierbei handelt es sich um den interaktiven Python-Kernel (IPython) in der PYNQ-Umgebung. Im Anschluss an die Ausführung des Codes aktualisiert der Server asynchron sowohl die angezeigte Webseite als auch die Notebook-Datei mit jeder vom Kernel erzeugten Ausgabe.
PYNQ erweitert diesen Ansatz für die FPGA-basierte Entwicklung, indem das Jupyter-Framework inklusive IPython-Kernel und Notebook-Web-Server auf dem Arm-Prozessor des Zynq-SoC eingebettet wird. Das in der Umgebung enthaltene PYNQ-Python-Modul stellt Programmierern die Python-API zur Verfügung, die für den Zugriff auf PYNQ-Dienste in Python-Programmen benötigt wird.
Die FPGA-Entwicklungsumgebung
Das speziell zur Unterstützung von PYNQ entwickelte PYNQ-Z1-Entwicklungskit von Digilent ermöglicht Entwicklern den schnellen Einstieg in die Untersuchung von mit FPGAs beschleunigten Anwendungen, indem einfach das bootfähige Linux-Image geladen wird. Die PYNQ-Z1-Karte kombiniert einen Zynq-SoC XC7Z020 von Xilinx mit 512 Megabyte (MB) RAM, 16 MB FLASH-Speicher und einem microSD-Einschub für zusätzlichen externen Flash-Speicher. Neben Schaltern, Tasten, LEDs und mehreren Ein-/Ausgängen bietet die Karte über die Digilent Pmod-Schnittstelle (Peripheriemodul) und über Arduino-Shields sowie Digilent chipKIT-Shields Anschlüsse für Hardware von Drittanbietern. Die Karte bietet außerdem den Analog/Digital-Wandler (ADC) des Zynq-SoC (den XADC), und zwar entweder als sechs asymmetrische oder als vier symmetrische analoge Eingänge. Digilent hat außerdem das separat erhältliche PYNQ-Z1-Entwicklungskit im Angebot. Dieses Kit umfasst eine Stromversorgung, ein Mikro-USB-Kabel, eine microSD-Karte mit vorinstalliertem PYNQ-Image und ein Ethernet-Kabel zum Aktualisieren oder Hinzufügen von Python-Modulen.
Den Entwicklern stehen sämtliche Funktionen des SoC und der Karte jederzeit über ein Jupyter-Notebook zur Verfügung. Beispielsweise sind für den Zugriff auf die Pmod-Schnittstelle der Karte zum Auslesen des ADC und zum Schreiben von Werten des Digital/Analog-Wandlers (DAC) in einem Loopback-Test lediglich einige wenige Zeilen Code erforderlich (Abbildung 5). Nach dem Importieren der erforderlichen Python-Module wird die PL des SoC über ein „Basis“-Overlay initialisiert (Zelle 2 in Abbildung 5). Wie ein herkömmliches Board Support Package auch ermöglicht dieses Basis-Overlay den Zugriff auf die Peripheriebausteine der Karte.
Abbildung 5: Ein im Beispiel-Repository von Xilinx enthaltenes Jupyter-Notebook demonstriert das einfache Designmuster im Zusammenhang mit dem Zugriff auf Hardwaredienste für Ein-/Ausgabetransaktionen. (Bildquelle: Xilinx)
Entwickler müssen lediglich importierte Module aufrufen, um die Werte zu lesen und zu schreiben (Zelle 3 in der Abbildung). Im gezeigten Beispiel-Notebook übermittelt der Notebook-Server eine Zelle nach der anderen und aktualisiert das Notebook mit den erzeugten Ergebnissen. In diesem Fall lautet der einzige Ausgabewert 0,3418. Jegliche Ausführungsfehler werden jedoch gemäß ihrer entsprechenden Code-Zellen als normale Python-Stack-Tracebacks angezeigt.
Erstellen komplexer Anwendungen
In Kombination mit einer Vielzahl an verfügbaren Python-Modulen verbirgt sich hinter diesem trügerisch einfachen Ansatz zur Entwicklung eingebetteter Anwendungen eine leistungsstarke Plattform zur schnellen Implementierung komplexer, rechenintensiver Anwendungen. Beispielsweise können die Entwickler über den HDMI-Eingang der PYNQ-Z1-Karte und die beliebte OpenCV-Bibliothek für maschinelles Sehen im Handumdrehen eine Webcam zur Gesichtserkennung implementieren. Nach dem Laden des Basis-Overlays und der Webcam-Schnittstelle wird von den Entwicklern ein OpenCV-Kameraobjekt „videoIn“ initialisiert (Abbildung 6). Zum Lesen des Videobilds muss dann lediglich „videoIn-read()“ aufgerufen werden, wodurch in diesem Beispiel „frame_vga“ zurückgegeben wird.
Abbildung 6: Ein Jupyter-Notebook im Beispiel-Repository von Xilinx zeigt, wie Entwickler im Handumdrehen eine Gesichtserkennung mit Webcam erstellen können, indem sie Hardwareressourcen der PYNQ-Z1-Entwicklungskarte mit leistungsstarken Bildverarbeitungsfunktionen aus der OpenCV-Bibliothek kombiniert. (Bildquelle: Xilinx)
In einem Folgeschritt, der im Notebook als separate Zelle verwaltet wird, erstellen die Entwickler mithilfe voreingestellter Kriterien OpenCV-Klassifizierungsobjekte und fügen Bounding Boxes zur Erkennung von Merkmalen hinzu (grün für Augen und blau für Gesichter in diesem Beispiel). In einem anderen Zellenpaar wird die Anwendung abgeschlossen, nachdem die Ausgabe über den HDMI-Ausgang der Karte angezeigt wird (Abbildung 7).
Abbildung 7: Die abschließenden Zellen im Notebook zur Gesichtserkennung mit der Xilinx-Webcam demonstrieren die Verwendung von OpenCV-Klassifizierern, deren Ergebnisse verwendet werden, um Bounding Boxes zu den Originalbildern hinzuzufügen, und die über den HDMI-Ausgang der PYNQ-Z1-Entwicklungskarte angezeigt werden. (Bildquelle: Xilinx)
Die Möglichkeit, komplexe Software interaktiv zu erstellen, zu testen und gemeinsam darüber zu diskutieren, hat dafür gesorgt, dass Jupyter-Notebooks sehr beliebt sind bei Wissenschaftlern und Ingenieuren, die an der Optimierung von Algorithmen für KI-Anwendungen arbeiten. Mit dem Voranschreiten der Arbeit zeigt das Notebook nicht nur Code und seine Ausgabe an, sondern auch die Analyse des Entwicklers zu den Ergebnissen. Auf diese Weise erhält man eine Art Bericht über die durchgeführten Berechnungen, der an Teammitglieder und Kollegen weitergegeben werden kann.
Die Entwickler müssen sich jedoch darüber im Klaren sein, dass Notebooks für eher produktionsorientierte Bemühungen weniger gut als Repositories geeignet sind. Beispielsweise werden durch große Zeichenfolgen in Hexadezimal-Kodierung für Image-Daten (siehe gekürzter Bereich in Listing 1) nicht nur die Dokumente größer, sondern sie können auch Differenzverfahren komplizieren, die von typischen Steuersystemen der Quellversion verwendet werden. Die Verflechtung von Code und normalem Text kann die Migration von Code, der in frühen analytischen Phasen erstellt wurde, auf die Entwicklungsprozesse der Produktionsstufe weiter komplizieren. Für die Untersuchung von Code und eine schnelle Prototyperstellung stellen Jupyter-Notebooks jedoch eine leistungsfähige Entwicklungsumgebung dar.
Fazit
FPGAs sorgen für den nötigen Leistungsschub, um den steigenden Anforderungen von Embedded-Systemen für das IoT, das maschinelle Sehen, die industrielle Automatisierung, die Automobilbranche und viele weitere Anwendungsbereiche gerecht zu werden. Während herkömmliche FPGA-Entwicklungmethodologien vielen Entwicklern auch weiterhin Schwierigkeiten bereiten, stellt die neue PYNQ-Entwicklungsumgebung auf der Basis von Python und Jupyter-Notebooks eine effiziente Alternative dar. Mithilfe eines speziell für PYNQ konzipierten Entwicklungsboards können selbst Entwickler mit geringer FPGA-Erfahrung im Handumdrehen Designs implementieren, die die Vorteile von FPGAs bei der Beschleunigung rechenintensiver Anwendungen in vollem Umfang nutzen.

Haftungsausschluss: Die Meinungen, Überzeugungen und Standpunkte der verschiedenen Autoren und/oder Forumsteilnehmer dieser Website spiegeln nicht notwendigerweise die Meinungen, Überzeugungen und Standpunkte der DigiKey oder offiziellen Politik der DigiKey wider.