|
|
ZPU: Softcore Implementierung auf Spartan-3 FPGA
[Bearbeiten] BeschreibungDieses Projekt beschreibt die Implementierung einer Opensource Softcore CPU auf einem FPGA-Board Spartan-3 von Xilinx und Anbindung von Peripheriemodulen über deren Busschnittstelle. Forumsdiskussion: http://www.mikrocontroller.net/topic/212445 Artikel auf Englisch: http://embdev.net/articles/ZPU:_Softcore_implementation_on_a_Spartan-3_FPGA [Bearbeiten] FeaturesProjekt:
CPU:
Bus:
Peripherie:
[Bearbeiten] EinleitungIm Rahmen des Projekts stand ein Experimentierboard Spartan-3 von Xilinx zur Verfügung. Das Spartan-3 ist ein Einsteigerboard (s. Abbildung 1) mit einem XC3S200 FPGA (1) das sich aufgrund der geringen Anzahl an BlockRAMs (12 Stk., entspricht 24 KB) nicht unbedingt für den Einsatz eines anspruchsvollen Softcore-Prozessors mit Peripherie eignet.Die wichtigsten Komponenten für das Projekt sind: eine PS/2- (2), eine VGA- (3), eine RS-232-Schnittstelle (4), 8 Schalter (5), 8 LEDs (6), 4 Buttons (7) und 4 Siebensegment-Anzeigen (8), des Weiteren der SRAM auf der Rückseite der Platine. Programmieren lässt sich das Board bzw. der FPGA mittels eines JTAG-Adapters. Eine passende Software, die Xilinx ISE Entwicklungsumgebung lässt sich kostenlos von Xilinx herunterladen. Für das Projekt wurde die Version 10.1 verwendet. Für das Projekt viel die Auswahl auf eine frei verfügbare Softcore-CPU, die ZPU von Øyvind Harboe. Sie ist eine kleine Stack-basierte 32 Bit CPU mit einem 8 Bit breiten Opcode. Sie hat einen einfachen und übersichtlichen Aufbau und ist deshalb optimal für die hier angestrebten Erweiterungen geeignet. Diese Opensource Softcore-CPU ist als VHDL-Projekt angelegt. Sehr nützlich ist die Möglichkeit, Programme, die in C oder C++ verfasst sind über eine spezielle GCC-Toolchain (ZPUGCC) in einen Maschinencode zu wandeln, den die ZPU direkt interpretieren kann. So kann man die ZPU mittels C-Programmen steuern. Verwendet wurde letztendlich deren „Small“-Variante.
Das Tutorial umfasst folgende Steps:
Neben diesem Artikel existiert zu dem Projekt eine ausführliche Dokumentation, die zusammen mit dem Projekt heruntergeladen werden kann. [Bearbeiten] Allgemeines[Bearbeiten] Aufbau und Funktionsweise der ZPUDie ZPU funktioniert im Prinzip wie eine Zustandsmaschine, die während der Abarbeitung im Wesentlichen die Phasen Fetch (Befehl holen), Decode (Befehl decodieren) und Execute (Befehl ausführen) durchläuft:
Nach dem Fetch-State existiert zusätzlich noch ein Fetch-Next-State. Dies bedeutet, dass mindestens 4 Takte benötigt werden, um einen Befehl abzuarbeiten. Bei der Taktfrequenz der ZPU von 50 MHz ergibt dies eine maximale Frequenz von 12,5 MHz. Die ZPU besitzt direkt untergeordnet eine BlockRAM-Einheit, die sie als 32 Bit breiten Programmspeicher verwendet. Hierbei handelt es sich um einen Dualport-BlockRAM: Über beide Ports kann gleichzeitig auf die gleiche Adresse schreibend oder lesend zugegriffen werden. Jedoch nicht von beiden Ports gleichzeitig auf die gleiche Adresse schreibend. Außer dem Programm ist hier auch der Stack abgelegt, also auch der Datenspeicher. Der Speicher hat im Moment eine Größe von 16 KB. Im VHDL-Code entspricht die Struktur der ZPU einer Entity, die den ZPU Core integriert und nach außen hin eine Wishbone-Anbindung zur Verfügung stellt. Die Entity des ZPU Cores wiederum implementiert den Dualport-RAM. [Bearbeiten] Wishbone-BusDer Wishbone-Bus ist ein Opensource Hardware Computerbus, über den die Einheiten einer integrierten Schaltung miteinander kommunizieren können. Wishbone ist ein logischer Bus, er definiert also nicht elektrische Informationen. Stattdessen definiert die Spezifikation Signale, Taktzyklen und High- und Low-Pegel. Dies macht es so einfach, ihn in VHDL zu verwenden. Alle Signale sind dabei synchron zum Taktsignal. Für das Projekt wurde die 32 Bit Variante des Wishbone-Busses verwendet (32 Bit Adress- und 32 Bit Datenbreite). Als Topologie wird der Shared Bus verwendet, d.h. alle Teilnehmer sitzen am selben Adress- und Datenbus und es existiert nur ein Master, nämlich die ZPU. Sollten mehrere Slaves angeschlossen sein, wird anhand der auf dem Adressbus liegenden Adresse der entsprechende Slave aktiviert. Alle Peripherie-Geräte sind als Slaves angesetzt. Ein Beispiel für eine Verbindung vom Master zu einem Slave ist in Abbildung 2 zu sehen. Hierbei enthält der Block SysCon den Taktgenerator des FPGAs und eine Verbindung zum Reset-Button. Eine Erklärung der Signale folgt in Tabelle 1. Beispiele für ein Wishbone-Output-Interface und Wishbone-Input-Interface sind in Abbildung 3 und 4 zu sehen. Im Projekt wurden Schnittstellen für die zu Anfang des Artikels beschriebenen Komponenten realisiert. Auf www.opencores.org gibt es viele frei verfügbare Peripherie-Geräte mit Wishbone-Schnittstelle zum downloaden. Die Schnittstelle muss trotzdem meist etwas angepasst werden. Sinnvoll ist es, einen Baustein zu verwenden, der bereits funktioniert, also in einem eigenen Projekt getestet wurde. So kann man diese Fehlerquelle beim Anbinden schon einmal ausschließen. [Bearbeiten] DownloadDownload Projekt und Dokumentation: Datei:ZPU Softcore Implementierung auf Spartan-3 FPGA.zip [Bearbeiten] Inbetriebnahme von Board und Entwicklungsumgebung1. Board anschließen:
2. Entwicklungsumgebung installieren:
3. Erstes Beispielprojekt für das Board in VHDL erstellen:
[Bearbeiten] Überblick über das VHDL-ProjektZunächst eine Übersicht über die Struktur des Projektes: ![]() Wie in obiger Abbildung zu sehen ist, befindet sich an der Spitze des Baumes, also als Top Modul, die Datei softcore_top.vhd. Sie verbindet alle benötigten externen Signale des Boards über die io_pins.ucf mit den Modulen. Sie beinhaltet den Wishbone-Bus und die Anbindung der Bus-teilnehmer. Hierzu gehört auch die Festlegung der Adressbereiche der Teilnehmer. Über die softcore_top.vhd werden alle benötigten Wishbone-Interfaces eingebunden, begin-nend mit der als Wishbone-Master konfigurierten ZPU, sowie einem Interrupt-Controller, einem UART, einer PS/2-(Keyboard-)Schnittstelle, einem Baustein, der die LEDs, Schalter, Taster und 7-Segment-Anzeigen des Boards steuert, einem SRAM-Controller und einer VGA-Schnittstelle. Die Datei wb_core.vhd enthält das Wishbone-Interface der ZPU. Sie setzt den Adress-, den Datenbus und die Steuersignale der ZPU in die Wishbone-Kommunikation um. Die ZPU selbst ist in der Datei zpu_core.vhd enthalten. Der Baustein ZPU enthält die Zustandsmaschi-ne dieser, den Speicherzugriff mit Interpretation des Opcodes, sowie den Zugriff auf externe Peripherie mittels des Wishbone-Interfaces (s. Kapitel 3). Sie bindet den als Programmspei-cher und Stack genutzten BRAM mit ein (Datei zpu_bram.vhd und softcore.bmm). Alle Dateien im Projekt mit der Endung _pgk.vhd beinhalten die Festlegung von Konstanten und Definitionen von Komponenten. Speziell die Datei zpu_pkg.vhd enthält die wertemäßige Definition der Opcodes der ZPU. Die Dateien mit der Endung _config.vhd beinhalten die Festlegung von Konstanten, also glo-bal zu verwendende Werte. In der softcore_config.vhd kann z.B. die CPU-Taktfrequenz und die Baudrate des UART festgelegt werden. In der zpu_config.vhd die Adressbreiten, Datenbreiten, Stackgröße und Programmspeichergröße. Die Datei interrupt.vhd enthält den Interrupt-Controller und sein Wishbone-Interface. Zusätz-lich zu den Wishbone-Signalen hat er Eingänge für die Interrupts anderer Peripherie-Geräte und einen Ausgang, um den Interrupt der ZPU zu setzen. Wenn über letzteren der ZPU ein Interrupt mitgeteilt wird und diese ihn annimmt (bzw. Interrupt aktiviert hat), sendet der Controller ihr die Interruptnummer. Per Software kann der ZPU mitgeteilt werden, was bei Auftreten eines Interrupt mit diesem gemacht werden soll. Dies bedeutet auch, dass der Soft-ware-Programmierer wissen muss, welche Interruptnummer welchem Peripherie-Gerät zuge-ordnet ist. Die Datei uart.vhd enthält das Wishbone-Interface des UART. Ihm untergeordnet sind die Bausteine zum Senden (Datei tx_unit.vhd) und Empfangen (Datei rx_unit.vhd). Beide haben jeweils noch einen Timer-Baustein (brgen.vhd), einen so genannten Taktteiler. Diese werden genutzt, um einerseits zyklisch den UART nach empfangenen Daten abzufragen und anderer-seits synchron dazu über den UART zu senden. Die Timerzeiten berechnen sich hierbei über die Baudrate (in der Datei softcore_config.vhd festgelegt). Beim Eintreffen von Daten wird ein Interrupt ausgelöst, der an den Interrupt-Controller gemeldet wird. Die Bausteine zum Senden und Empfangen sind einfache 8N1 Module von www.opencores.org. Die Datei ps2.vhd enthält eine ganz einfache PS/2-Schnittstelle mit Wishbone-Interface. Hierzu wird das von der Tastatur gesendete Taktsignal abgefragt und dann das 10 Bit breite Telegramm eingelesen. Auch hier wird mittels Interrupt signalisiert, dass Daten zur Verfü-gung stehen. Die Datei intIO.vhd enthält das Wishbone-Interface, um die Schalter, Buttons, LEDs und 7-Segment-Anzeigen auf dem Board zu steuern. Hierbei werden die Schalter und Buttons bei Ansprechen ihrer Adresse direkt eingelesen bzw. die LEDs direkt über die unteren 8 Bit des Datenworts geschaltet. Da die 7-Segment-Anzeigen nicht getrennt unterschiedliche Werte erhalten können, gibt es eine Zustandsmaschine, die zeitlich nacheinander zwischen den 4 Anzeigen hin- und herschaltet. Hierfür wird aus dem 32 Bit breiten Datenwort je ein Byte pro Anzeige verwendet, wobei das höchstwertigste Byte der linken Anzeige usw. entspricht. Die Datei wb_sram.vhd enthält das Wishbone-Interface des SRAM-Controllers. Dieser selbst ist in der Datei MemoryController.vhd enthalten. Da der SRAM nur eine Adressbreite von 18 Bit hat, wird das höchstwertigste Adressbit verwendet, um zwischen Lesen und Schreiben zu entscheiden. Die Datei wb_vga.vhd enthält das Wishbone-Interface für den VGA-Controller. Hier werden im Prinzip zwei Projekte vereinigt. Zum einen eine reine Ansteuerung der Farbkanäle des VGA-Ports, hierzu ein Implementierungsbeispiel in Kapitel 5.2. Und zum anderen die Mög-lichkeit Text auf dem Bildschirm darzustellen (80x30 Zeichen). Hierbei stellt Bit 3 die Ent-scheidung zwischen diesen Modi dar, im Falle der Farbansteuerung werden Bit 0 bis 2 für die Farbwerte RGB genutzt. Andernfalls wird im Moment einfach ein statischer Text erzeugt, der am Bildschirm ausgegeben wird (Datei font_rom_pixel_generation.vhd). In der Datei font_rom.vhd ist ein ROM enthalten, dass mit den einzelnen darzustellenden Zeichen gefüllt ist. Für beide Modi wird die Auflösung 640x480 verwendet. Die Datei vga_sync.vhd beinhal-tet die Ansteuerung des VGA-Ports, sie generiert letztendlich das Signal, das den Bildschirm abtastet.
[Bearbeiten] Peripherie[Bearbeiten] Integrierte PeripherieWährend der Studienarbeit wurden im Folgenden genannte Peripherie-Geräte an den Wishbo-ne-Bus angeschlossen. Die Adressen für die Interfaces sind hart kodiert und lassen sich durch Anpassung im softcore_top.vhd anpassen. 1. RS-232-Schnittstelle:
2. Internal I/Os:
![]()
3. PS/2-Schnittstelle:
4. SRAM-Speicher:
5. VGA-Schnittstelle:
Des Weiteren ist ein Interrupt-Controller als Wishbone-Slave integriert. An diesen werden die Interrupts der Wishbone-Slaves weitergereicht und dieser teilt der ZPU dann mit, von welcher Adresse der Interrupt kam. Jeder Interrupt muss eine eigene IRQ-Leitung besitzen, hierfür ist bei Hinzufügen eines weiteres Wishbone-Slaves mit Interrupt die Konstante irqLines in der Datei softcore_top.vhd anzupassen. Standardmäßig sind alle Interrupts deaktiviert. Sie können über die IR-Control-Adresse (0x8000000C) in der Software als vorhanden gekennzeichnet und dann über die IR-Enable-Adresse (0x80000008) aktiviert werden. Wenn ein Interrupt ausgelöst wird, wird die Funktion _zpu_interrupt () aufgerufen. Welcher Interrupt gesetzt ist, lässt sich über die Interrupt-Adresse 0x80000000 abfragen. [Bearbeiten] Weitere Peripherie erstellenAuf www.opencores.org gibt es viele frei verfügbare Peripherie-Geräte mit Wishbone-Schnittstelle zum downloaden. Die Schnittstelle muss trotzdem meist etwas angepasst werden. In diesem Kapitel wird anhand eines Beispiels vorgestellt, wie ein Peripherie-Gerät von Grund auf erstellt und mittels Wishbone-Interface angeschlossen wird. Mit diesem Wissen ist es möglich sein, auch bereits vorgefertigte Bausteine, ob mit oder ohne Wishbone-Schnittstelle, an die ZPU anzuschließen. Dieses Beispiel implementiert einen VGA-Controller. Sinnvoll ist es, einen Baustein zu verwenden, der bereits funktioniert, also in einem eigenen Projekt getestet wurde. So kann man diese Fehlerquelle beim Anbinden schon einmal aus-schließen. [Bearbeiten] HardwareDie Wishbone-Signale sind in Record Types zusammengefasst, die in der Datei softco-re_pkg.vhd definiert sind. Für die Eingangssignale zum Master gibt es die Struktur wb_master_in, sie setzt sich wie folgt zusammen:
Somit reicht es einem Wishbone-Interface als Eingänge das Takt-, das Resetsignal und ein Signal vom Typ wb_master_out zu verpassen (slave_in), sowie einen Ausgang vom Typ wb_master_in (slave_out). Je nach Bedarf kann man noch ein Interruptsinal als Ausgang und in der .ucf-Datei gemappte Ein- oder Ausgangssignale definieren. Das VGA-Interface bekommt die notwendigen Signale für die Wishbone-Kommunikation und ein Interruptsignal, das jedoch im Moment nicht genutzt wird und insofern auf 0 gelegt ist. Des Weiteren die Signale die auf den VGA-Port gemappt werden: Ein Signal hsync für die horizontale Synchronisation, ein Signal vsync für die vertikale Synchronisation und ein drei-kanaliges Signal rgb für die drei Farbkanäle. Damit lassen sich dann 8 Farben darstellen. Zuerst wird ein neues VHDL-Modul angelegt mit dem Namen vga_sync.vhd. Der Quellcode findet sich hier: Datei:Vga sync.vhd. Nun ein weiteres VHDL-Modul mit dem Namen wb_vga.vhd. Die hier zu erstellende Entity trägt den Namen wb_vga und wird der Wishbone-Slave. Er erhält alle Ein- und Ausgänge. Damit die Strukturen wb_master_in und wb_master_out genutzt werden können, wird nach dem Einbinden der Standardbibliotheken noch die Bibliothek work eingebunden zusammen mit use work.softcore_pkg.all. Bis jetzt sieht der Baustein wb_vga wie folgt aus:
Zunächst wird jetzt dieser leere Wishbone-Slave angeschlossen. Dies bedeutet, die 3 Signale für den VGA-Port bis zur obersten Ebene durchzuschleifen und den Slave an den Bus anzu-schließen. Dazu wird in der .ucf-Datei folgendes hinzugefügt:
Damit sind die Signale vom VGA-Port im FPGA angeschlossen. Die oberste Ebene ist die Entity softcore_top in der softcore_top.vhd. Hier werden diese Signale als weitere Ports hin-zugefügt:
Jetzt muss noch die Datei softcore_pkg.vhd angepasst werden. Hier werden die Komponenten definiert. Deswegen muss hier ebenfalls der neue Baustein wb_vga hinzugefügt werden, da-mit er verwendet werden kann:
Jetzt kann der Baustein wb_vga in der Entity softcore_top eingebunden werden. Hierfür wird wie bei den anderen Wishbone-Slaves auch ein Portmapping durchgeführt. Vorher müssen jedoch die notwendigen Signale definiert werden. In der architecture der softcore_top (in softcore_top.vhd) wird vor begin folgendes eingefügt:
Diese Signale dienen zur Ankopplung des Wishbone-Busses an wb_vga und zur Ankopplung des Interrupt-Signals des wb_vga an den Interrupt-Controller. Um den Interrupt anschließen zu können, muss der Interrupt-Controller eine weitere Interrupt-Leitung erhalten. Hierfür die Konstante irqLines um 1 erhöhen. Nun kann die neue Interrupt-Leitung an den Interrupt des wb_vga angeschlossen werden (Beispiel für neuen Interrupt mit der Nummer 4):
Das Portmapping kann im Prinzip von einem anderen Slave kopiert werden und an wb_vga angepasst werden:
Die Signale hsync, vsync und rgb sind bereits bis auf die oberste Ebene durchgeschleift und dann an den VGA-Port angeschlossen, der Interrupt, das Takt- und das Resetsignal sind eben-falls angeschlossen. Jetzt fehlt nur noch der Anschluss an den Wishbone-Bus über die Signale vga_in und vga_out. Diese sind zwar definiert und auf der einen Seite an wb_vga angeschlos-sen, jedoch noch nicht an den Wishbone-Bus. Hier muss erst einmal eine freie Adresse ausgewählt werden, über die der VGA-Controller letztendlich von der ZPU angesteuert werden soll, beispielsweise 0x800E0000. Die Daten vom VGA-Controller zum Master gehen über das Signal vga_out an das Signal master_in. Wenn nun von der ausgewählten Adresse gelesen wird, muss vga_out auf master_in gemappt werden. Hierfür kann man einfach den Multiplexer am Ende der softcore_top.vhd wie folgt erweitern:
Die Adresse „1000“ & ‚0’ & „0000000111000000“ entspricht 0x800E0000. Ebenso muss jetzt noch vga_in angeschlossen werden:
Nun ist die wb_vga komplett angeschlossen. Beispielsweise kann man sie so füllen, dass man die drei RGB-Werte per ZPU steuern kann. Hierfür wird die Entity vga_sync eingebunden. Diese steuert das analoge VGA-Signal. Wie ein VGA-Bildschirm genau angesteuert wird, wird hier nicht erläutert. Des Weiteren werden unbenötigte Signale auf 0 gesetzt, wie das Interruptsignal und slave_out.data, also der Datenbus zum Master. ACK wird ganz einfach auf CYC gesetzt. Dann wird noch ein Prozess benötigt, der die vom Master ge-schickten Daten auswertet und entsprechend die RGB-Farben setzt. Die architecture von wb_vga sieht dann wie folgt aus:
[Bearbeiten] SoftwareEin C-Programm für die ZPU kann jetzt die Adresse des VGA-Controllers als volatile int Zeiger definieren. Wenn man die Zustände der Taster am Board an den VGA-Controller schickt, kann man dann darüber die Bildschirmfarbe steuern. Mehr zur Programmierung s. unten.
[Bearbeiten] Programmierung der ZPUDie ZPU lässt sich in C bzw. C++ programmieren. Zum Erstellen der Quellcodes kann z.B. Notepad++ verwendet werden. Zum Kompilieren wird die spezielle ZPUGCC Toolchain verwendet. Diese ist jedoch für Linux gedacht. Zur Studienarbeit stand nur ein Windows-Rechner zur Verfügung. Deswegen wurde auf diesem Cygwin installiert und unter Cygwin die ZPUGCC in Betrieb genommen. Wie Cygwin installiert, welche Komponenten für die Programmierung erforderlich sind und wie die ZPUGCC auf diesem in Betrieb genommen werden kann, findet sich in Unterpunkt Kompilieren. [Bearbeiten] Ansprechen von HardwareDie absolute Hardware-Adresse kann über Zeiger des Typs volatile int gesetzt werden. Als Beispiel hier die Definition der Adressen für die Schalter und die LEDs auf dem Board. Als Wert wird direkt die Adresse angegeben, auf die der Hardwaremultiplexer in der Datei softco-re_top.vhd überprüft.
Über Dereferenzierung des Zeigers intIOSW lässt sich der Zustand der Schalter auslesen. E-benso lässt sich über das Dereferenzieren des Zeigers intIOLED der Zustand der LEDs setzen.
[Bearbeiten] InterruptsFür die Nutzung der Interrupts müssen diese zuerst aktiviert werden. Ab dem Zeitpunkt der Aktivierung reagiert die ZPU auf diese.
Um die Interrupts Abfragen zu können, muss die Funktion _zpu_interrupt () verwendet wer-den. Ein Beispiel ist in folgendem Code zu sehen:
Mit 32 Bit Datenbreite sind so maximal 32 Interrupts möglich, da jedes Bit einen Interrupt präsentiert. Mit den if-Anweisungen in dem Code-Beispiel werden die einzelnen Bits isoliert und dann abgefragt. [Bearbeiten] KompilierenKompiliert wird der Quellcode mit ZPUGCC. Hierbei wird eine .elf-Datei erstellt, die direkt in den BlockRAM geladen werden kann. 1. Cygwin herunterladen und installieren:
2. ZPUGCC in Betrieb nehmen:
Unter Cygwin folgenden Befehl zum Kompilieren in die Konsole eingeben:
Hierbei reicht es, die Haupt-C-Datei anzugeben, weitere Header, auch eigene etc. bindet ZPUGCC automatisch mit ein und kompiliert sie. Man kann auch eine Textdatei mit der Endung .sh erstellen, in der oben genannter Befehl ab-gespeichert ist und diese dann unter Cygwin aufrufen. Dies entspricht den Batch-Dateien un-ter Windows, nur dass die .sh-Dateien nicht mit Doppelklick ausführbar sind. Ausführbar sind diese mit folgendem Kommandozeilenbefehl:
[Bearbeiten] Programmspeicher laden – Data2MEM und .bmm-FilesNach dem Erstellen der .elf-Datei durch die Kompilierung muss der Inhalt in den Programm-speicher (BlockRAM) geladen werden. Dieser befindet sich in der Datei zpu_bram.vhd. Ab-hängig von der Größe des Prozessorsystems und des verwendeten FPGAs kann die Synthese des Systems sehr langwierig sein. Damit nicht für jede Änderung am Programmspeicher der Cores das ganze System neu synthetisiert werden muss, wird von Xilinx das Kommandozei-len-Tool Data2MEM in der Entwicklungsumgebung des ISE zur Verfügung gestellt. Dieses ist standardmäßig bei der Installation von Xilinx ISE dabei. Ebenfalls spart man viel Arbeit, da man nicht jedes Byte einzeln hineinkopieren muss. Die Daten, die zur Konfigurierung eines FPGAs benötigt werden und damit auch die Pro-gramme der Softcores, befinden sich in der so genannten .bit-Datei. In dieser .bit-Datei kann Data2MEM ein altes Programm durch ein Neues ersetzen. Um diese Transformation durchzu-führen, benötigt Data2MEM Informationen darüber, wo Daten ersetzt werden sollen und wie diese aufgeteilt werden. Diese Informationen werden im BlockRAM Memory Map File, der so genannten .bmm-Datei, angegeben. Die .bmm-Datei ist eine lesbare Textdatei und hat für die ZPU folgenden Aufbau:
Dem Core stehen 16 KB (4096 x 32 Bit Datenbreite) Programmspeicher zur Verfügung, die in 8 BlockRAMs aufgeteilt werden. Der Adressbereich wird unter ADDRESS_SPACE in Bytes angegeben. Die Aufteilung der Programmdaten in die 8 BlockRAMs ist unter BUS_BLOCK aufgelistet. Dabei werden nicht jeweils 512 Zeilen von 32 Bit Werten in einem BlockRAM gespeichert, sondern 4096 Zeilen mit jeweils 4 Bit des 32 Bit Wertes. Es sind 8 Instanzen vom Typ RAMB16_S4_S4 definiert. Die 16 deutet auf die Gesamtgröße von 16 KB hin. S4 auf die Datenbreite von 4 Bit. Das doppelt verwendete S4 bezeichnet das es ein Dualport-RAM ist. Hieraus folgt, dass 4 K x 4 Bit Breite = 16 KB Gesamtgröße sind. Da 8 Instanzen à 4 Bit breit definiert sind, können diese durch entsprechende Ansteuerung eine 32 Bit Breite ergeben. Es wird also ein Speicher der Größe 4 K und 32 Bit breit erstellt. Die genauen Instanznamen bzw. Pfade, wie core/zpu_core/memory/RAMB16_S4_S4_inst7, sind mit Hilfe des Floorplaners im ISE zu ermitteln. Es beschreibt die ineinander verschach-telten Bausteine: Die Entity core bindet die Entity zpu_core ein, die Entity zpu_core bindet die Entity memory ein usw. Und letztendlich sind in der Entity memory die 8 Instanzen beschrieben. Nur mit diesen Angaben lässt sich die .bmm-Datei jedoch noch nicht für die Transformation mittels Data2MEM verwenden, da bisher die Angabe des exakten BlockRAMs fehlt. Hier empfiehlt es sich die Arbeit direkt durch das ISE, genauer gesagt durch Bitgen, erledigen zu lassen. Dazu wird die .bmm-Datei in das ISE-Projekt eingebunden und während des Generate Programming File-Prozesses wird die .bmm-Datei automatisch um die fehlenden Angaben ergänzt. Nach diesem Schritt findet sich im Projekt-Ordner eine neue .bmm-Datei, die im Da-teinamen um „_bd“ erweitert ist und nun die Ortsangaben für alle Instanzen in folgender Form enthält:
Neben der .bmm-Datei muss Data2MEM noch die ursprüngliche .bit-Datei und das Pro-gramm als .elf-File übergeben werden. Die .bit-Datei liegt normalerweise auf oberster Ebene des Projektverzeichnisses ab und trägt auch den Namen des Projektes. Mit folgendem Befehl wird die Transformation der .bit-Datei ausgeführt:
Speziell für das vorhandene Projekt lauten die Dateinamen:
Ausgeführt werden kann das konsolenbasierte Data2MEM über die Windows-Konsole. Alter-nativ kann man eine Batch-Datei mit dem entsprechenden Befehl erstellen. Hierfür den Befehl in eine Textdatei kopieren und die Dateiendung in .bat ändern. Bei Verwendung einer Batch-Datei kann man auch zum Pfad der Batch-Datei relative Pfadangaben verwenden. Das Ergebnis dieser Transformation ist die neue .bit-Datei softcore_top_fw.bit mit geänder-tem Programmspeicher. Dieses wird dann statt der softcore_top.bit auf den FPGA geladen. Hierfür kann wie gewohnt die Software iMPACT genutzt werden, um über den JTAG-Adapter die neue .bit-Datei auf den FPGA zu laden. Kommentare können, wie aus der Sprache C bekannt, mit // oder /* und */ gesetzt werden. Allerdings ist zu beachten, dass Kommentare nur vor dem eigentlichen Code als Kommentare gewertet werden. Ein Kommentar zwischen den Zeilen des Codes erzeugt einen Fehler. Passend zu der .bmm-Datei muss natürlich auch der zu füllende Speicher in VHDL beschrie-ben sein. In der Datei zpu_bram.vhd werden die entsprechenden Instanzen definiert. Jede In-stanz erhält einen Namen und einen Typ, z.B. RAMB16_S4_S4. Dem folgt ein generic map, bei dem, egal um welchen Typ es sich handelt, die Konstanten von INIT_00 bis INIT_3F komplett mit 0 gefüllt werden (128 Bit bzw. 32 Zeichen in Hex). Anschließend erfolgt das Portmapping. Hier als Beispiel das Portmapping für einen Dualport-RAM des Typs RAMB16_S4_S4. Die auskommentierten Zeilen sind Ports, die nur in der S9er, S18er und S36er Konfiguration vorhanden sind (s. Tabelle Einteilungsmöglichkeiten des BlockRAMs).
![]() [Bearbeiten] Referenzen, Links, Quellen...
|