Kampis Elektroecke

Linux für den Zynq

Zynq FPGA

In diesem Artikel zeige ich, wie ein Linux Betriebssystem für den Zynq vorbereitet und dieses anschließend gebootet wird.
Dieses Linux Betriebssystem kann anschließend mit eigener oder fertiger Hardware erweitert werden, wodurch es möglich ist den Chip flexibel an verschiedene Einsatzgebiete anzupassen.

Vorbereitungen:

Bevor mit der Erstellung des Betriebssystems losgelegt werden kann, müssen einige Dinge vorbereitet werden.
Für die Erstellung der notwendigen Dateien wird ein Linux Betriebssystem benötigt.
Mit folgender Software treten keine Probleme auf:

  • Ubuntu 14.04
  • Vivado 2014.3.1

Jetzt müssen noch ein paar Zusatzpakete installiert werden:

Das Programm git wird nachher verwendet um die notwendigen Dateien herunter zu laden. Alle anderen Pakete werden zum kompilieren benötigt.
Im nächsten Schritt werden drei Verzeichnisse herunter geladen:

Am Ende dieser Anleitung wurden drei verschiedene Dateien, die für den Bootvorgang wichtig sind, erstellt:

  • BOOT.bin
  • devicetree.dtb
  • uImage

Aber bevor wir mit der Software beginnen, bauen wir uns erst einmal die Hardware genauer an…

Die Hardware:

Für den Anfang verwende ich ganz einfache Hardware, die nur aus dem Zynq Processing System besteht und wie folgt aufgebaut ist:

Als Konfiguration des Processing Systems verwende ich das normale XML-File von Digilent.
Zusätzlich zu der fertigen Konfiguration muss noch ein Timer aktiviert werden, da das System sonst nicht bootet und eine Fehlermeldung ausgibt (Fehler: mmc0: no vmmc regulator found):

Sobald ihr fertig seid könnt ihr die Schaltung synthetisieren, den Bitstream erzeugen und den Bitstream ins SDK exportieren.

Erstellung des First-Stage-Bootloaders:

Als nächstes wird der First-Stage-Bootloader (FSBL) gebaut. Dieser Bootloader wird beim einschalten des Zynq geladen und ausgeführt. Bei der Ausführung lädt der Bootloader den Bitstream für das FPGA und aktiviert anschließend den zweiten Bootlader namens u-boot, welcher dann das Betriebssystem lädt.
Für die Erzeugung des FSBL öffnet ihr das Xilinx SDK und legt unter File / New / Application Project ein neues Projekt namens FSBL an:

Mit einem Klick auf Next wechselt ihr das Fenster und wählt Zynq FSBL aus. Die Auswahl bestätigt ihr mit einem Klick auf Finish.
Der Project Explorer sieht nun wie folgt aus:

Der FSBL ist nun fertig erzeugt und für die Weiterverwendung bereit.

Hinweis!
Sobald Änderungen am Processing System vorgenommen wurden, muss der FSBL aktualisieren werden, da es sonst sein kann, dass die Änderungen nicht übernommen werden!

u-boot kompilieren:

Im nächsten Schritt kompilieren wir u-boot. Bei u-boot handelt es sich um einen universellen Bootloader für Linux-Systeme.
Dieser Bootloader hat die Aufgabe das Betriebssystem zu laden und zu starten.
Als erstes wechselt ihr in das vorhin heruntergeladene Verzeichnis u-boot-xlnx.
In dem Unterverzeichnis /include/configs befindet sich eine Datei namens zynq_zed.h, welche ihr öffnen müsst:

Am Anfang des Headerfiles wird anschließend

eingefügt:

Jetzt kann die Datei gespeichert und geschlossen werden.
Nun wechselt ihr in das Verzeichnis u-boot-xlnx und definiert eine Systemvariable für den Cross-Compiler:

Im nächsten Schritt konfiguriert ihr u-boot:

Es erscheint folgende Ausgabe:

Im Anschluss daran wird der Bootloader gebaut:

Nach dem Kompilieren befindet sich in dem Verzeichnis u-boot-xlnx eine Datei mit dem Namen u-boot.
Diese Datei wird in einen neuen Ordner (z. B. mit dem Namen Linux) kopiert und anschließend in in u-boot.elf umbenannt.

Die Erstellung der Datei BOOT.bin:

In diesem Schritt erstellt ihr das Bootimage für den Zynq. Für diesen Schritt werden folgende drei Dateien benötigt:

  • FSBL.elf
  • System_wrapper.bit
  • u-boot.elf

Im Xilinx SDK öffnet ihr über Xilinx Tools / Create Zynq Boot Image das entsprechende Menü um das Bootimage zu erstellen.
Das Fenster füllt ihr nun wie folgt aus:

Der Zusatz (Bootloader) beim FSBL ist ganz besonders wichtig! Desweiteren müsst ihr die Reihenfolge der Dateien einhalten, sprich immer in dieser Reihenfolge:

  1. FSBL
  2. Bitstream
  3. u-boot

Mit einem Klick auf Create Image erzeugt ihr das Image. Als Resultat solltet ihr zwei Dateien namens

  • BOOT.bin
  • Zybo.bif

erhalten.
Alternativ kann das Bootimage auch über die Konsole erzeugt werden. Dazu wird eine .bif-Datei benötigt (wie die oben erzeugte Datei Zybo.bif).
Die .bif-Datei kann z.B. so aussehen:

Über den Konsolenbefehl

kann die Datei dann erstellt werden. Auf diese Weise erspart ihr euch den Umweg über das SDK und könnt es direkt in der Konsole machen.

Erstellen des Device Tree:

Im nächsten Schritt muss der Device Tree erzeugt werden, der die Hardware des Chips beschreibt.
Dieser Device Tree wird beim booten vom Kernel eingelesen, wodurch der Kernel weiß welche Hardware wie verfügbar ist.
Als erstes müsst ihr den Pfad des Device Tree Generators im Xilinx SDK hinterlegen.
Dazu öffnet ihr das SDK und klickt oben in der Menüleiste auf Xilinx Tools und öffnet das Menü Repositories.
Mit einem Klick auf New wählt ihr anschließend den Pfad zu dem Ordner device-tree-xlnx aus:

Mit einem Klick auf OK bestätigt ihr die Eingabe.
Danach klickt ihr erneut auf File / New, nur diesmal wählt ihr das Menü Board Support Package aus. In diesem Menü wählt ihr dann device_tree und klickt auf Finish:

Direkt nach dem Bestätigen öffnet sich ein neues Menü. Mit Hilfe dieses Menüs könnt ihr den Device Tree konfigurieren.
Für einen vollständigen Device Tree müssen wir noch ein paar bootargs, sprich Befehle die dem Kernel übergeben werden, hinzufügen:

  • console=ttyPS0,115200
    Zum festlegen der seriellen Konsole
  • rootfstype=ext4
    Hiermit wird festgelegt welches Dateisystem die root-Partition besitzt
  • root=/dev/mmcblk0p2
    Hier wird die root-Partition, sprich die Partition mit dem Dateisystem, festgelegt
  • rootwait
    Der Kernel soll mit dem Booten so lange warten bis die root-Partition gemountet werden kann
  • rw
    Die root-Partition soll schreib- und lesbar gemountet werden

Die Argumente werden der Reihe nach in das Feld bootargs eingetragen:

Mit einem Klick auf OK schließt ihr dann das Fenster.
Wenn ihr jetzt die Datei system.dts öffnet, sollten die Bootargumente im Device Tree stehen:

Jetzt muss der Devicetree nur noch gebaut werden. Dies macht ihr mit diesem Befehl:

Beim Bauen entsteht eine Datei namens devicetree.dtb. Dies ist der fertige Device Tree den der Kernel benötigt. Diese Datei solltet ihr euch, zusammen mit der Datei BOOT.bin, in einen separaten Ordner kopieren, damit ihr sie leicht wieder findet.

Hinweis!
Jede Änderung an der Hardware des FPGAs und der Processing Systems zieht eine Änderung des Device Trees mit sich, sprich es muss bei jeder Änderung der Device Tree aktualisiert und neu gebaut werden.

Kompilieren des Kernels:

Als nächstes müsst ihr den Linux-Kernel kompilieren:

Jetzt erscheint folgende Ausgabe:

Das Kompilieren des Kernels dauert etwas länger. Sobald der Vorgang abgeschlossen wurde, findet ihr unter ../linux-xlnx/arch/arm/boot eine Datei namens uImage. Dies ist der Linux-Kernel den ihr zum booten benötigt. Auch diese Datei solltet ihr euch zu den anderen beiden Dateien kopieren.

Erstellen des root-Filesystems:

Im letzten Schritt wird das root-Filesystem erzeugt, welches quasi die Systempartition des Betriebssystems darstellt.
Für das root-Filesystem verwende ich das Filesystem des Yocto-Project mit einem Xilinx meta-layer.
Als erstes erstellt ihr euch einen Ordner für die notwendigen Dateien:

Im nächsten Schritt müsst ihr euch zwei Git-Repositories runterladen, einmal das  Yocto-Basisverzeichnis und dann noch den meta-layer:

Danach müsst ihr die Konfigurationsdateien erzeugen:

Jetzt müssen noch zwei Dateien angepasst werden.
Als erstes müsst ihr die Datei ../Yocto/poky/build/conf/bblayers.conf um den Xilinx meta-layer ergänzen:

Die zweite Datei ist die Datei ../Yocto/poky/build/conf/local.conf.
Dort müsst ihr das Zielsystem, für das ihr das rootfs erstellen wollt, eingeben:

Jetzt könnt ihr das rootfs bauen. Dies dauert ziemlich lange (je nach Rechner zwischen 1,5h – 3h!):

Kurz nach der Eingabe sollte folgende Ausgabe erscheinen:

Am Ende des Bauprozesses findet ihr unter ../Yocto/poky/build/tmp/deploy/images einen Ordner namens zedboard-zynq7.
In diesem Ordner finden sich diverse Dateien, ihr benötigt aber nur die Datei core-image-minimal-zedboard…rootfs.tar.gz, welche das gepackte rootfs enthält.

Die erste Inbetriebnahme:

Damit der Zynq, mitsamt des Betriebssystems in Betrieb genommen werden kann muss noch eine SD-Karte erstellt werden.
Die SD-Karte sollte idealerweise mindestens zwei Partitionen haben:

  • Partition 1: Für das Bootimage, den Kernel und den Devicetree (/boot)
  • Partition 2: Für das Dateisystem (/root)

Die Formatierung der SD-Karte habe ich nach dieser Anleitung von Xilinx durchgeführt (nur den ersten Absatz Task Descritpion).

Sobald ihr die SD-Karte vorbereitet habt, kopiert ihr die Dateien

  • BOOT.bin
  • uImage
  • devicetree.dtb

auf die boot-Partition der SD-Karte.

Das eben erstellte root-Filesystem (rootfs) wird auf die zweite Partition der SD-Karte kopiert:

Für die Konfiguration des Bootloaders erstellt ihr euch zudem noch eine Datei uEnv.txt, welche ihr auf der boot-Partition der SD-Karte speichert.
In dieser Datei wird die Konfiguration des Bootloaders abgespeichert, die wie folgt aussehen kann:

Über diese Konfiguration könnt ihr den Namen des Devicetrees und des Kernelimages ändern.
Dazu müsst ihr nur den Inhalt der entsprechenden Variablen ersetzen.

Danach kann die SD-Karte entmountet werden:

Jetzt legt ihr die Karte in das Zybo ein und stellt mit dem Jumper SD-Boot ein.
Nach dem Einschalten der Spannung könnt ihr mit Hilfe eines Terminals den Bootvorgang beobachten.
Als erstes wird der u-boot mit der übergebenen Konfiguration gestartet:

Und wenig später erscheint dann der Anmeldebildschirm:

Hier könnt ihr euch direkt mit dem User root anmelden. Damit wäre das Linux-System für den Zynq einsatzbereit.

Die aktuelle Version des Linux-Projektes findet ihr GitLab-Repository.

→ Zurück zu FPGA + VHDL


Letzte Bearbeitung: 11.10.2018

10 Kommentare

  1. Hey,

    danke für das wirklich schön kompakte Tutorial. Leider habe ich noch ein Problem mit dem uboot. Der FSBL scheint zu funktionieren und läd auch das bitfile. Denn die DONE LED geht an. Der u-boot produziert auch einen output, leider scheint die Baudrate noch etwas krumm zu sein, denn ich seh nur Zeichenwirrwar. Ein händisches Ausprobieren aller gängigen Baudraten hat auch keinen Erfolg gebracht. Hast du noch eine Idee wo ich was drehen kann das uboot auf dem richtigen Device und mit der richtigen Baudrate sendet ?

    1. Hallo Georg,

      verwendest du ein Zybo?
      Hast du im Hardwaredesigner mal das “Boarddefinition File” (http://www.digilentinc.com/Products/Detail.cfm?NavPath=2,400,1198&Prod=ZYBO) im Processing System eingefügt? Ich könnte mir schon fast denken, dass das das Problem ist, wenn deine Baudrate nicht stimmt. Dann ist irgendwo ein Takt nicht ganz richtig.
      Ich benutze für mein Zybo eigentlich immer dieses File um die Grobeinstellung zu machen und dann ändere ich die benötigten Sachen händisch um. Aber so vergesse ich keinen Takt umzustellen etc.

      Gruß
      Daniel

    2. Ok, erledigt. Copy/Paste fehler, bzw. Gehirn nicht eingeschaltet. Beim normalen Zed-Board muß die config vom u-boot natürlich nicht geändert werden

  2. Hallo,
    vielen Dank für das schöne Tutorial! Es ist ja schon ne Weile her aber ich würde als Neuling trotzdem gerne noch eine Frage stellen:
    Wann genau ist es überhaupt nötig ein neues boot image / FSBL zu erstellen?
    Grüße
    Johannes

    1. Hallo Johannes,

      den FSBL musst du in der Regel nur 1x kompilieren, solange du auf dem selben Target bleibst, bzw. solange sich der Chip nicht ändert.
      Anders sieht es beim Boot-Image aus. Das musst du (theoretisch) jedes Mal ändern, wenn du eine neue Hardware implementieren willst, sprich wenn sich etwas an deinem VHDL ändern.
      Alternativ kannst du einen neuen Bitstream auch übers Linux laden und so das FPGA direkt neu konfigurieren. Allerdings funktioniert dies nur bei Hardware, die NICHTS mit dem Processing System zu tun hat, sprich du kannst nicht zusätzliche Schnittstellen an das Processing System anbauen und das neu laden. Aber du kannst so z. B. nachträglich einen AXI-GPIO einbauen. Allerdings musst du das auch jedes Mal beim Reboot neu machen.
      Aber dazu wollte ich auch noch eine Anleitung schreiben, wenn ich mal wieder ein funktionierendes Linux gebaut bekomme…bei meinem aktuellen steckt noch irgendwo der Wurm drin. Da hat sich anscheinend über die Zeit das ein oder andere geändert…

      Gruß
      Daniel

      1. Hi Daniel,
        danke für deine Antwort – das hat mir schon mal sehr geholfen. Den Weg über devcfg nutzte ich auch momentan.

        Nur noch kurz als Gedankenspiel:
        Wenn ich jetzt z.B. AXI-GPI0 konfigurieren wollen würde, welche Schritte wären dann notwendig, um das System wieder zum Start zu bewegen?

        Konkret die Frage, ob man dann u-boot neu bauen müsste mit aktualisiertem devicetree? Welchen cross-compiler sollte man dazu nutzen? Bei Vivado 2017.x scheint es nur noch arm-linux-gnueabihf und arm-none-eabi als prefix zu geben?

        Grundsätzlich will ich also mein altes Linux mit geänderten PL wieder zum booten zu bekommen aber scheitere da momentan dran.

        Besten Gruß
        Johannes

        1. Hallo Johannes,

          du nimmst dein Design und baust dort den AXI-GPIO dazu. Dann erzeugst du einen Bitstream, den du danach auf die SD-Karte vom Linux kopierst. Diesen Bitstream kannst du dann in das FPGA laden. Wenn das alles geklappt hat, kannst du den AXI-GPIO über Software ansprechen.
          Ich habe leider kein lauffähiges Linux, sonst würde ich es mal ausprobieren, aber so geht es zumindest in der Theorie.

          Gruß
          Daniel

        2. Hallo Johannes,

          ich habe mittlerweile wieder eine lauffähige Variante für Linux auf dem Zynq.
          Ich aktualisiere das Git-Repository die Tage.

          Gruß
          Daniel

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.