Spielende Synthese

c't-Lab: FPGA-Anwendung in Beispielen

Praxis & Tipps | Praxis

Das FPGA auf dem gleichnamigen c't-Lab-Modul ist ein faules Stück Hardware, solange man es nicht mit einer Konfiguration füttert. Dann aber läuft es zur Höchstform auf und macht im Nanosekunden-Takt für den Laboreinsatz sinnvolle Dinge.

Eigentlich hatten wir beschlossen, dass die inzwischen auf insgesamt 75 Heftseiten akkumulierte c't-Lab-Reihe mit der Vorstellung des FPGA-Moduls abgeschlossen sein sollte – doch das rege Interesse an der der FPGA-Anwendung im Allgemeinen und der nur beiläufig erwähnten Implementierung des Videospiel-Klassikers „Asteroids“ samt Bildschirmschoner-Analoguhr im Besonderen (siehe nächstes Bild) zwang den Autor zum Umdenken. Sogar etwas Hardware gibt es als Bonus obenauf, im Prinzip eine Experimentierplatine zum Aufstecken auf das FPGA-Modul, auf der die zusätzliche Elektronik bequem aufgebaut werden kann – gegebenenfalls etappenweise mit wachsendem Schwierigkeitsgrad. Zu den im folgenden besprochenen Beispielen gehört neben dem Mini-Asteroids ein Frequenzzähler mit programmierbarem Pulsgenerator, ein Video-Interface für QVGA-Grafik und ein (auf komplexe arbiträre Wellenformen erweiterbarer) Signalgenerator, der von 0 bis über 10 MHz reicht.

Unsere Asteroids-Implementierung bietet auch eine „analogoide“ Bildschirmuhr für die Spielpausen. Zur Darstellung genügt ein XY-fähiges Oszilloskop, das an die vorgestellte Platine angeschlossen wird.

Die größte Hürde beim Wunsch, eigene Entwicklungen ins FPGA zu gießen, ist weniger das Löten von SMD-Bauteilen, sondern eher das Anfreunden mit der Xilinx’schen Entwicklungsumgebung, die der Hersteller kostenlos zum Herunterladen (siehe untenstehenden Soft-Link) anbietet. Da fangen die Probleme schon an: Die harmlosen paarundvierzig Megabyte des Downloads beinhalten nur den Installer (!), für das eigentliche „ISE WebPack“ in der aktuellen Version 10.1 rauschen dann beängstigende zweieinhalb Gigabyte durch die Leitung. Ein Rechner an einem Standard-DSL-Anschluss ist damit über zwei Stunden außer Gefecht gesetzt, denn die Daten wollen ja auch in einem Dickicht aus Verzeichnissen installiert sein. Es ist ratsam, für die ISE-Installation ein eigenes Volume zur Verfügung zu stellen, sonst ist ein chronisch knappes C:-Laufwerk schnell verstopft.

Das ISE WebPack ist eigentlich eine gewaltige Sammlung von mächtigen Befehlszeilen-Tools, einigen Editoren und zahllosen Töölchen, denen ein imposantes GUI übergestülpt wurde. Letzteres koordiniert den Aufruf und Ablauf der Hilfsprogramme, zusätzlich hat es ein einfaches Schaltplan-Zeichenprogramm eingebaut. Mit dem kann man zur Not sogar einzelne Gatter verdrahten, in der Regel wird man ihn aber zum übersichtlichen Verbinden von Source-Modulen untereinander verwenden. Die können wiederum in Form eines Schaltplans oder in den Hardware-Beschreibungssprachen VHDL oder Verilog verfasst sein. Welche der beiden (von ISE gleichermaßen und auch gemischt unterstützten) Sprachen Sie wählen, ist mehr oder weniger Geschmackssache; das etwas eleganter anmutende Verilog erfreut sich in den USA größerer Beliebtheit, während man in Europa eher VHDL den Vorzug gibt.

Was in einer Programmiersprache die Kompilierung ist, wird bei frei konfigurierbaren Logikbausteinen allgemein als „Synthese“ bezeichnet – ein (bei in unserem Fall 400 000 zur Verfügung stehenden Gattern) äußerst komplexer und zeitaufwendiger Vorgang, mit der ISE selbst schnellere Rechner minutenlang vollbeschäftigt. Auch wenn die Entwicklungsumgebung die Befehlszeilen-Programme sequentiell abarbeitet, ist ein Mehrkern-System kein Luxus: Ein nur mäßig schneller Rechner würde während der Synthese (die dann regelmäßig 100 Prozent CPU-Auslastung fordert) fast unbedienbar. Wir empfehlen für ein frustrationsbegrenztes Arbeiten wenigstens 2 GHz Takt und 1 GByte RAM sowie der Übersichtlichkeit halber einen möglichst großen Bildschirm (20 Zoll oder besser, wenn nicht sogar zwei davon), darunter ist die Arbeit mit dem WebPack eine Qual. Während der Synthese rödelt ISE wie besessen auf den beteiligten Festplatten herum, die deshalb nicht zur Gemütlichkeit neigen sollten. Dies gilt auch für das Laufwerk, auf dem sich die Sourcen zu einem Projekt befinden.

ISE sollte nach Aufruf ein mehrfach geteiltes IDE-Fenster zeigen, das Hauptfenster wird letzte Änderungen und Bugfixes melden und für weitere Xilinx-Produkte werben. Links oben findet sich der „Sources“-Bereich, der eine Art Dateibaum mit den zum Projekt gehörenden Sourcen und deren hierarchische Abhängigkeit zeigt. Ist ein Schaltplan geöffnet, werden hier auch die verfügbaren Bauteil-Symbole in Gruppen aufgelistet. Mitte links befindet sich das „Processes“-Fenster, hier zeigt ISE den Implementierungsablauf und bereits „abgehakte“ Syntheseschritte, je nach Kontext auch weitere Optionen, etwa den Auswahlmodus im Schaltplan-Editor. Ganz unten sieht man das Konsolen-Fenster, hier zeigen die abgearbeiteten Befehlzeilen-Tools (mit denen Sie anfangs kaum in Berührung kommen werden) ihre Meldungen, Warnungen und Fehlerberichte an.

… ist das Anlegen eines neuen Projekts, will man nicht ein vorhandenes weiterbearbeiten. Ein Wizard führt durch die grundlegenden Schritte: Bauteil und Gehäuse festlegen (in unserem Fall der „XC3S400-4 TQ144“), Top-Level-Designtyp wählen (Schematic, also Schaltplan, ist für den Anfang gut geeignet), zugehörige Sourcen zusammenstellen. Dieser Vorgang wird Ihnen bald in Fleisch und Blut übergehen, weil ISE sporadisch projektbezogene Abhängigkeiten (etwa die Auswahl des übergeordneten Top-Moduls, also des „Hauptprogramms“) vergisst oder schlicht ignoriert. Wenn bei progressivem Zicken und unerklärlichen Fehlermeldungen auch der Menüpunkt „Cleanup Project Files“ nicht mehr hilft, bleibt leider nur der Weg, die Projektdatei wegzuwerfen und das Projekt gänzlich neu anzulegen, natürlich unter Verwendung der vorhandenen Sourcen.

Weil das Entwicklungssystem seinen Projekt-Arbeitsordner (den man mit dem Wizard gewählt hat) mit mehr oder weniger temporären Dateien regelrecht zumüllt, empfehlen wir dringend, die Sourcen getrennt, also zum Beispiel in einem Unterverzeichnis des Projektordners, aufzubewahren – das Neuanlegen eines Projekts ist dann im Falle eines Falles nicht allzu mühsam, zumal man im Dateidialog ja auch mehrere Dateien gleichzeitig auswählen und laden kann. Ein wichtiger Hinweis gleich vorweg: Hat man zu eigenen Modulen Schaltplan-Symbole angelegt (Dateiendung .SYM), müssen sich diese direkt im Projektordner befinden. Dies ist vor allem bei der Weitergabe oder beim Download von ISE-Projekten relevant: Ohne SYMs wird der Schaltplan, der sie beinhaltet, korrumpiert.

Zu den Projekt-Sourcen gehört eine sogenannte User-Constraints-Datei (Endung .UCF), die Timing-Pflichtwerte oder sogar die Platzierung der Komponenten auf dem Gatter-Array vorgeben kann, vornehmlich aber zum Zuweisen der I/O-Netze auf bestimmte Anschlusspins dient. Ohne UCF geht’s zwar auch, die Synthese weist den Signalen (genauer: den I/O-Markern im Top-Modul) dann aber nach Gutdünken Anschlüsse zu – nicht immer im Sinne des Erfinders, aber immerhin mit optimierten Verbindungswegen innerhalb des FPGA. Steht das Platinenlayout allerdings noch nicht fest, wird man sich dieses Vorgehen zu Nutze machen und die Pin-Beschaltung nach den Vorstellungen der Synthese-Optimierung auslegen.

Alternativ kann man den Netzen auch schon im Schaltplan eine Pin-Property (LOC=Pxx) zuordnen – das gerät aber schnell unübersichtlich. Auch wenn es einen grafischen Editor für die UCF-Vorgaben gibt, ist für den Anfang eine simples Textfenster („Edit Constraints as Text“ im „Processes“-Fenster doppelklicken, wenn ein UCF in den Sourcen ausgewählt ist) leichter zu durchschauen. Die UCF-Constraints geben übrigens nicht die Datenrichtung (In, Out, Tristate) an, die ergibt sich aus der Richtung der I/O-Marker im Top-Modul (bzw. der Port-Deklaration im VHDL-File, sollte ein solches das Top-Modul bilden).

Den Umgang mit ISE lernt man am besten anhand eines Beispiels, zumal unsere kleine Einführung das Studium der (mit weit über 1000 Seiten fast schon zu ausführlichen) Xilinx-Dokumentation letztlich nicht ersparen kann und schon gar keinen VHDL-Lehrgang ersetzt – damit füllen andere Autoren ganze Bücher. Öffnen Sie das Beispiel-Projekt „Frequenzzähler“ (nach Download und Entpacken im Ordner \ct-frequz\main\main.ise): Der Schaltplan CT_Lab_FPGA steht als Top-Modul oben links, die darin verwendeten Module sind darunter hierarchisch aufgereiht. Einige der gelisteten Module, zum Teil als VHDL-Implementierung, finden Sie im Schaltplan wieder. „SPI_Ports“, das Interface zum Atmel-Controller auf der FPGA-Platine, beinhaltet wiederum Unter-Schaltpläne, die durch „Aufklappen“ sichtbar werden.

Wenn alle Module an der richtigen Stelle sitzen (die Hierarchie ergibt sich aus der „Verdrahtung“ oder den VHDL-Deklarationen automatisch) und keines (außer den Konstanten) mit einem orangenem Fragezeichen (= Datei nicht geladen, unbekannt – ggf. nachladen mit Rechtsklick auf das Top-Modul und „Open“) gekennzeichnet ist, können Sie die Synthese starten: Einfach auf den grünen „Käfer“ in der Menüleiste klicken. Dann eine Zigarette rauchen oder einen Kaffee holen, und wenn wenige Minuten später die Synthese (hoffentlich) fehlerfrei durchgelaufen ist, erhalten Sie mit einem Doppelklick auf „Generate Programming File“ im Processes-Fenster ein fertiges BIT-File, das Sie dann direkt ins FPGA laden können. Die Properties der Konfigurationsdatei (über Rechtsklick im Processes-Fenster) dürfen bis auf eine Ausnahme vorerst auf dem Default-Zustand belassen werden. Wenn Sie das FPGA mit dem separaten JTAG-Programmieradapter beschicken wollen, muss unter „Startup Options“ und „FPGA Start-up Clock“ allerdings „JTAG Clock“ ausgewählt sein, für BIN-/BIT-Files, die zur Konfiguration auf die SD-Karte kopiert werden, ist dagegen „CCLK“ die richtige Einstellung.

Bei Missachtung dieser Bitstream-Einstellung startet das FPGA nicht, auch wenn es die Konfigurationsdaten bereitwillig angenommen hat. Der in c't 16/2008 nebenbei vorgestellte JTAG-Programmieradapter sollte nach Doppelklicken von „Configure Target Device“ automatisch erkannt werden; man muss dann nur noch das (einzige) FPGA XC3S400 in der JTAG-“Kette“ als Target auswählen.

Wer die Schaltplan-Module neuzeitlicher Platinenlayout-Programme kennt, wird die Arbeit mit dem ISE-Schaltplaner geradewegs als Zumutung empfinden – „gewöhnungsbedürftig“ wäre eine eher milde Umschreibung, auch wenn der Editor den Eigenheiten der FPGAs Tribut zollen muss. Trotzdem ist gerade dem Anfänger die grafische Repräsentation der Modul- und Symbolverschaltung hilfreich, und auch VHDL-Profis greifen beim Top-Modul der schnellen Übersicht halber gern auf den Schaltplan zurück.

Nur mit Schaltplan-Symbolen zu arbeiten, führt allerdings schon bei mäßig komplexen Projekten zu einem unübersichtlichen Drahtverhau; außerdem neigt man dann dazu, asynchrone Designs zu entwerfen, die von Seiten der Geschwindigkeit her nicht der Weisheit letzter Schluss sind. Der den FPGAs immanente Vorteil der synchronen Arbeitsweise, die möglichst wenig Gatterlaufzeiten anhäuft, ist dann dahin. VHDL und Verilog dagegen machen dem Programmierer „nebenläufige“, also nicht innerhalb von getakteten Prozessen ablaufende logische Zuweisungen bewusst und Performance-Fallen klar erkennbar.

Die DACRAM-Platine, hier unser nicht ganz finaler Prototyp, wird auf das FPGA-Modul gesteckt und erweitert es um vielfältig nutzbare Optionen.

Die Bezeichnung der Netze folgt üblichen oder zumindest eingängigen Konventionen: Signale mit dem gleichen Namen sind miteinander elektrisch verbunden (auch wenn eine direkte Verbindung im Schaltplan fehlt), Busse gehorchen der Nomenklatur <Busname>(<msb>:<lsb>), also etwa „DAC(11:0)“ oder „RAM_Adresse(17:5)“. Etwas tricky wird es, wenn Busse angezapft oder aus Teilen zusammengeführt werden müssen. Während es im ersteren Fall reicht, Teil-Busse bitmäßig einzuschränken – „RAM_Adresse(17:5)“ ist zum Beispiel eine Teilmenge von „RAM_Adresse(23:0)“ – kann man Busse nur zusammenführen (kombinieren), indem man (in diesem Fall elektrisch funktionslose, aber den Namen „isolierende“) Buffer zwischen die einzelnen Buszweige setzt.

Die verlockenden „Bus Taps“, die der Editor anbietet, sind leider wenig hilfreich, weil sie die Busnamen halt doch nicht voneinander trennen – und außerdem lassen sie sich nur mit Übung platzieren: Teilbus anklicken, anzuzapfende Bits in den Optionen wählen, anzuzapfenden Bus anklicken, und schwupps! – sitzt der Tap dort, wo er nicht hin soll. Busse innerhalb des FPGA sind übrigens niemals Tristate-fähig, nur die nach außen geführten Signale. Tristate- und Open-Collector-Logik muss innerhalb des FPGA durch Auftrennen der Signalrichtung in getrennte Busse/Netze realisiert werden.

Um Busnamen im Schaltbild anzuzeigen, bemüht man deren „Object Properties“ (rechter Mausklick auf das Netz) und fügt mit „Add“ ein Visible-Attribut hinzu. Der angezeigte Netzname lässt sich dann durch Draufklicken und Eintippen einfach ändern, was sonst über die Properties nur umständlich möglich ist. Namen von I/O-Markern werden automatisch angezeigt, im Top-Modul-Schaltbild müssen diese aber zwingend mit einem I/O-Pin verbunden sein (sofern eine UCF-Datei vorliegt).

Das Frequenzzähler-Schaltbild enthält einige FPGA-spezifische „Bauteile“, auf die wir hier nur kurz eingehen können. Zum einen die Global Clock Buffer, die man gern für verzögerungsarme („Low Skew“) Taktsignale verwendet, hier für den 50-MHz-Eingang vom Quarzoszillator (Typ IBUFG) und für das intern vervielfachte 200-MHz-Signal (Typ BUFG). Zum anderen den Digital Clock Manager DCM, den man, mit entsprechenden Properties beaufschlagt, als Takt-Teiler, Takt-Vervielfacher (beides auch mit ungeraden Verhältnissen) und Takt-Verteiler verwenden kann. Unser Spartan-3-FPGA beinhaltet insgesamt vier DCMs. Ihr Eingangssignal darf zwar über 200 MHz betragen, aber nur in kleinen Grenzen variieren, weshalb sich ihr (an sich nahe liegender) Einsatz als Eingangsteiler für einen Frequenzzähler verbietet.

Blöcke wie der „Frequency_Generator“ sind keine originären Schaltplan-Symbole aus der Xilinx-Bauteilebibliothek, sondern selbst erzeugt: Modul in der Sources-Liste auswählen, dann unter „Design Utilities“ im Processes-Fenster „Create Schematic Symbol“ aufrufen. Einige Sekunden später erscheint das erzeugte „Bauteil“ in der Symbol-Bibliothek im Processes-Fenster, von hier aus kann man es bei Bedarf in den Schaltplan ziehen. Das Symbol lässt sich nachträglich bearbeiten, wenn es einmal im Schaltbild platziert wurde (Rechtsklick auf das Symbol, dann „Symbol/Edit Symbol“); da die automatische Symbol-Generierung recht großzügig Platz verschwendet, sollten Sie dies für dicht bevölkerte Schaltbilder immer tun.

Nicht nur VHDL- oder Verilog-Code, auch eine Schaltbild-Repräsentation kann in ein Bauteil-Symbol eingedampft werden, womit es gleichsam als Sub-Schaltbild fungiert. Die sonst die Verbindung mit den Außenwelt-Pins herstellenden I/O-Marker eines Schaltbilds werden in einem daraus erzeugten Symbol zu Signal- und Bus-Anschlüssen, analog zu den Port-Deklarationen in einer VHDL-Datei.

Auf die SPI-Schnittstelle wollen wir wegen ihrer breiten Verwendung (und weil sie auf dem c't-Lab-Modul die einzige Möglichkeit für den Atmel-Controller darstellt, mit dem FPGA zu kommunizieren) etwas näher eingehen: Als reiner Slave hält sich der Aufwand in Grenzen (siehe SPI-Schaltbild), ein paar Schieberegister, ein Decoder und etwas Glue-Logik reichen dafür aus. Notwendig ist das Taktsignal DCLK nur, um die Ausgangsregister mit einem FPGA-internen Takt zu synchronisieren; für nichtsynchrone Simpel-Designs kann man auch die (hier nicht nach außen geführten) ungelatchten Ausgänge DS statt DQ verwenden. Alte Bekannte sind die SPI-Signale F_SCLK, F_MOSI, F_MISO, F_DS und F_RS, die Sie im Schaltbild des FPGA-Moduls wiederfinden.

ISE Webpack ist trotz einiger (für uns nicht relevanter) Einschränkungen gegenüber der kostenpflichtigen Entwickler-Vollversion immer noch ein überaus dicker Brummer. Hier ist der Frequenzzähler geöffnet.

Ausgangsseitig (d. h. in Richtung c't-Lab-Controller) stehen vier 32-Bit-Register zur Verfügung, eingangsseitig (d.h. in Richtung FPGA-Interna) acht mit der gleichen Breite. Jedes Eingangsregister besitzt ein eigenes Strobe-Signal, das mit beginnender SPI-Übertragung auf der jeweiligen Registeradresse auf log. „1“ geht und nach Abschluss um zwei DCLKs verzögert auf „0“. Die doppelte Verzögerung stellt sicher, dass die gerade eingetroffenen Daten mit fallender Flanke sicher an den Ausgängen DQ anliegen und sich auch bei zufälliger Konvergenz von SPI- und FPGA-Takt nicht mitten in der Flanke ändern. Die vier Ausgangsregister sind dagegen transparent, das heißt, der Controller liest die Werte so, wie sie mit beginnender SPI-Kommunikation an den Bussen anliegen.

Unser FPGA-SPI (Serial Peripheral Interface), der Block links unten im Screenshot zum ISE Webpack, ist mit „diskreten“ Bauteilen wie Schieberegistern, Gattern und Decodern aufgebaut.

Gegenüber der allerersten SPI-Implementation (in alten Beispielen noch auf www.ct-lab.zu finden) wurde das SPI noch um eine (hier per Konstante im Top Module festgelegte) Basisadresse erweitert, mit der sich die Schnittstelle bequem auf alle FPGA-Modul-SubChannels (0 bis 63) legen lässt (in 16er-Schritten, die Konstante legt das obere Nibble des Adressbytes fest – nützlich, wenn das FPGA-SPI auf 16 Register erweitert werden soll). Mit der Konstante „1“ liegt der Frequenzzähler-Adressbereich also auf den SubChannels 16 bis 31, von denen hier nur 16 bis 23 benutzt werden.

Der Rest des Frequenzzählers ist schnell erklärt: Das unbekannte Eingangssignal gelangt für exakt die Hälfte einer festen Torzeit-Periode (1 s) an einen von zwei 32-Bit-Zählern. Ein Multiplexer wählt den gerade nicht arbeitenden Zähler aus und leitet den Zählerstand an das SPI-Register 16 zum Auslesen durch den c't-Lab-Controller. Durch diesen Trick – Dank an unseren Leser Klaus Philipp für Idee und Ausführung – gibt es kein „totes“ Zeitfenster, einer der beiden Zähler ist immer aktiv und das Eingangssignal wird kontinuierlich gemessen. Bei unserem Musteraufbau durfte die maximale Eingangsfrequenz an Pin FN100 (Anschluss a12 der VG-Steckleiste auf dem FPGA-Modul) mehr als 150 MHz betragen – Anmerkungen dazu gleich.

Das Schaltbild beinhaltet zwei weitere, auch für den Test hilfreiche Funktionsblöcke: Einen Rechteck-Frequenzgenerator nach dem DDS-Prinzip, der bis 100 MHz arbeitet, und einen Pulsgenerator mit bis hinunter auf 10 ns einstellbarer Puls- und Pausenlänge. Der Frequenzgenerator nimmt den Wert in SPI-Register 20 als zu addierenden Phasenwinkel (mit 32 Bit Auflösung), je größer der Wert, desto höher die Frequenz. Für sehr niedrige Frequenzen kann man über Register/SubCh 21 einen 16-Bit-Vorteiler hinzuschalten: Bei Registerinhalt „0“ wird nicht vorgeteilt, „1“ teilt durch 2 …, „15“ teilt durch 32768.

Praktische Anwendung der Frequenzzähler-Konfiguration: Das LabVIEW-Progrämmchen steuert den Puls- und Frequenzgenerator im FPGA und liest kontinuierlich den Frequenzzählerstand aus.

Der Ausgang des Frequenzgenerators liegt auf Pin FP99 (b12 der VG-Leiste), sodass zum Test einfach eine Steckbrücke von FP99/b12 auf FN100/a12 gelegt werden kann: Der Frequenzzähler misst dann die Frequenz des DDS-Generators. Beim Pulsgenerator (Ausgang auf FP97/b9) gibt man mit Register 22 die Pausenlänge in 5-ns-Schritten vor, mit Register 23 die Impulslänge in gleicher Auflösung. Zum Einstellen und Abfragen der Werte können Sie das recht simple LabVIEW-Programm „FPGA-FrequZ.vi“ verwenden.

Schon bei kleineren Vorhaben oder Unterprojekten empfiehlt es sich durchaus, vor der Synthese zumindest eine „Behavioral Simulation“ durchzuführen, also ein hardwareloser Trockenlauf der grundsätzlichen Funktionalität von Schaltbild und VHDL-Code ohne Berücksichtigung der Timing-Vorgaben. Nötigenfalls fördert die „Post-Route Simulation“ nach der Synthese dann eine auf Pikosekunden genaue Analyse des tatsächlich zu erwartenden Timings zu Tage. Beide Simulationen werden im Processes-Fenster angelegt („New Source/Test Bench Waveform“, vorher zu testende Sourcen in die jeweils gewählte Simulation aufnehmen). Klaus Philip hat dies für das Frequenzzähler-Projekt bereits erledigt, die fertigen Simulationen und Stimuli können aus dem „isim“-Ordner geladen werden.

Für das nächste „Experiment“ müssen wir etwas weiter ausholen: Mit Version 1.1 der FPGA-Modul-Firmware wurde eine Script-Möglichkeit (c't-LabScript) eingeführt. Eine Text-Datei (Endung .INI) auf der SD-Karte kann im File-Menü oder per Befehl ausgewählt und als Batch-Script ausgeführt werden. Die Befehlszeilen werden so interpretiert, als ob sie vom OptoBus bzw. PC kommen würden, aber nur dann selbst behandelt, wenn die mitgegebene Moduladresse mit der des FPGA-Moduls übereinstimmt. Zusätzlich wurden Befehle für bedingte Sprünge und Schleifen sowie einige mathematische Befehle für einen Universal-Registersatz hinzugefügt (siehe Syntax-Tabelle, unter „Dokumentation“ im Repo-Browser auf www.ct-lab.de). Somit ist es möglich, auch aufwendigere Parameter-Konstrukte oder ganze Datenblöcke in die SPI-Register des FPGA zu schreiben. Ebenso ist es möglich, über das Script Befehle oder Abfragen an andere c't-Lab-Module abzusetzen, dafür muss das FPGA-Modul aber das erste in der OptoBus-Kette sein.

Mit dem FPGA als Video-Interface lassen sich Messwerte anschaulicher darstellen.

Eine ganz neue Perspektive ergibt sich, wenn man das FPGA als grafikfähiges Video-Interface konfiguriert – eine (bislang unbenutzte) VGA-Buchse ist auf dem Modul ja schon vorhanden. Der Autor hat die Modul-Firmware so erweitert, dass c't-LabScript nun auch Befehle zum farbigen Zeichnen von Text, Linien und Balken sowie zum Füllen von Rechtecken akzeptiert (etwa zur Visualisierung von Messwerten), wenn dem Atmel-Controller ein entsprechend konfiguriertes FPGA zur Seite steht, das die Video-Ausgabe übernimmt. Die Auflösung beträgt 320 x 240 Bildpunkte bei 64 Farben (QVGA, mit Zeilenverdopplung auf VGA-Standard-Timing) – mehr würde den kleinen Atmel-Controller, der sich ja auch noch um die Befehlsabwicklung kümmern muss, arg überfordern.

Die Firmware überprüft nach jedem Laden einer Konfigurationsdatei, ob sie ein Video-Interface enthält – indem sie versucht, in den Grafikspeicher bestimmte Werte abzulegen und wieder fehlerfrei auszulesen. Die FPGA-Modul-SubChannels auf den Adressen 64 bis 79 sind für das Video-Interface reserviert und dürfen nicht für andere Zwecke missbraucht werden, das könnte die Firmware verwirren. Unser FPGA-Video-Interface nutzt von diesem Adressbereich nur die ersten drei Register: Das erste ist ein Adressregister für das Framebuffer-RAM mit Autoinkrement (zählt bei jedem Byte-Zugriff auf das Buffer-RAM eins weiter), das zweite dient nur zum Auslesen der Framebuffer-Daten, und das dritte ist ein Befehlsregister für den hier „pixel_engine“ genannten VHDL-Funktionsblock.

Die etwas hochtrabende Bezeichnung täuscht darüber hinweg, das die „Engine“ nur einfachste Bit-Manipulationen beherrscht (Pixel anhand einer Maske setzen, invertieren, löschen) – wir wollten ja auch keinen 6845 oder gar 3D-Grafikchip implementieren. Immerhin wurde der Block gut auf den (eigentlich für kleine LCDs gedachten) generischen Grafiktreiber abgestimmt, den der bei der Firmware-Entwicklung verwendete Cross-Compiler (AVRCo Pascal von E-Lab) bereitstellt.

FPGA-Schaltung eines QVGA-Videoadapters: Für den Framebuffer wird ein externes 32-KByte-RAM benötigt. Wegen der eingeschränkten Lesbarkeit empfehlen wir, zur Ansicht das QVGA-Projekt mit ISE zu öffnen.

Wenn Sie die FPGA-Sourcen (wie immer vollständig auf www.ct-lab.de) genauer untersuchen, werden Sie feststellen, dass das SPI gegenüber dem des Frequenzzählers auf das Nötigste zusammengestrichen wurde. Der Video-Sync-Generator „fbas_sync“ ist dagegen beispielhaft und kann (über Konstanten im zugehörigen VDHL-Code, siehe Kommentare dort) in weiten Grenzen parametriert werden. Für das langsamere FBAS-Timing benötigt er allerdings einen durch acht und nicht durch vier geteilten 50-MHz-Takt; ein Zähler (Mitte oben im QVGA-Schaltbild) ist dafür schon vorgesehen.

Das zunächst „schwarzweiße“ Video-Signal serialisiert nach alter Väter Sitte ein 8-Bit-Schieberegister, das seine Daten direkt aus dem (externen, dazu später mehr) Video-RAM bekommt. Die farbige Darstellung erreichen wir, indem die Video-Ausgänge mit einem Farb-Byte aus der oberen Adresshälfte verUNDet werden. Großer Vorteil: Der Atmel-Controller muss sich nur um einbittige Schwarzweiß-Pixel kümmern, als Nachteil haben immer acht nebeneinanderliegende Pixel die gleiche Farbe – was für die angedachte Messwert-Visualisierung wohl kein allzu großes Manko darstellt.

Während die Frequenzzähler-Anwendung im Prinzip ohne externe Komponenten auskommt (solange nur Logik-Pegel verarbeitet werden), bedarf schon das Video-Interface etwas Hardware-Nachhilfe in Form eines externen RAM-Bausteins. Unsere weiteren Beispiele auch – weshalb wir hier eine kleine „Experimentierplatine“ vorstellen wollen, die alle für die hier präsentierten Anwendungen benötigten Bauteile enthält. Selbstverständlich genügt es, nur die für den jeweiligen Zweck angedachten Optionen (siehe einzelne Stücklisten im Kasten unten) aufzulöten. Die einfachste Erweiterung ist sicher der QVGA-Framebuffer in Form eines 62256-SRAMs mit 70 ns Zugriffszeit: Das gibt es auch im bedrahteten Gehäuse, was dem Anfänger die Bestückung erleichtert. Statt des 32-KByte-SRAMs kann auch eines mit 512 KByte (z. B. ein 62LV4006) verwendet werden, falls man für eigene Anwendungen mehr externen Speicher benötigt. Ein Jumper bestimmt, welcher von beiden Typen eingesetzt ist.

Statt der bedrahteten Gehäuseversion kann man auch die SMD-Ausführung des SRAMs wählen, die 32-polige Fassung für den Baustein darf dann nicht bestückt werden; zusätzliche (zur DIL-Fassung parallelgeschaltete) Lötpads sind hierfür vorhanden. Aus Platzgründen sind diese sehr knapp bemessen. Eventuell ist es je nach Gehäusetoleranz sinnvoll, die SMD-Stummelbeinchen etwas weiter anzuwinkeln, sodass das SMD-RAM schmaler aufliegt. Die kleinere RAM-Version besitzt nur 28 statt 32 Beinchen, vier Pins nahe JP1/JP2 bleiben hierbei frei.

Nach zehn c't-Lab-Folgen sollten auch die vielen SMD-Bauteile der DACRAM-Zusatzplatine keine große Herausforderung mehr darstellen. Die Platine kann je nach Bedarf mit den Stücklisten-Optionen ausgestattet werden.

Etwas kniffliger ist dann schon die Bestückung des Vorverstärkers für den Frequenzzähler, damit der auch die Frequenz niedrigpegeliger Signale messen kann. Der sehr schnelle Komparator U12 erledigt diese Aufgabe; sein Schwellenwert ist über das RC-Netzwerk C25/R27 selbsteinstellend. Die zu messende Frequenz führt man dem Steckverbinder PL5 zu, für Wechselspannungskopplung ist ein 1µ-Kondensator vorzuschalten. Die Eingangsempfindlichkeit beträgt im gesamten Frequenzbereich (1 Hz bis 150 MHz) unter 200 mV. SMD-Bauteile sind hier leider unumgänglich – schon wegen der kleineren parasitären Kapazitäten und Induktivitäten, aber auch, weil es den äußerst flotten ADCMP600 nur im SOT-23-Krümelgehäuse gibt. Die noch nicht erwähnten Jumper JP3 und JP4 konfigurieren den Komparator-Eingang: Bei gestecktem JP3 wird der Eingang mit 51 Ohm terminiert, während JP4 den Eingangskondensator für eine Gleichspannungskopplung überbrückt.

Immerhin haben wir es hier mit Hochfrequenzen im UKW-Bereich zu tun: Hört sich vielleicht angesichts der Gigahertz-Taktraten moderner PCs harmlos an, ist es aber keineswegs. HF ist per se unlogisch, breitet sich auch dort aus, wo gar keine Drähte sind, und wo ein Draht ist, kehrt sie oft am Ende um. Eine hervorragende und praxisnahe Abhandlung über Messungen an schnellen Schaltungen findet sich in der Application Note AN47 von Linear Technologies [1], die auch explizit auf das Tastkopf-Handling eingeht und nebenbei brauchbare und ausgetestete Applikationen (natürlich vornehmlich mit LT-Bausteinen) zeigt.

Etwas gemäßigter geht es bei der D/A-Wandler-Erweiterung zu: U5, ein AD5447 von Analog Devices, ist ein zweikanaliger Hochgeschwindigkeits-DAC mit 12 Bit Auflösung, der 21 Megasamples pro Sekunde verarbeitet und damit beliebige Wellenformen bis rund 10 MHz direkt erzeugen kann. Seine Bezugsspannung von 2,5 V erhält er aus einer mit U10 rund ums Massepotential symmetrierten REF-02-Referenzspannungsquelle. Zwei schnelle OpAmps besorgen schließlich die Umsetzung der DAC-Ausgangsströme in Spannungshübe. Unser Mini-Asteroids-Spiel verwendet ebendiesen Wandler für die XY-Koordinaten, auch wenn er damit schwer unterfordert wird. An die Grenzen geht eher die DDS-Applikation, die wir gleich besprechen – und möglicherweise Ihre Geduld beim Auflöten des winzigen DAC-Bausteins.

Unsere DACRAM genannte Erweiterungsplatine ist nicht nur für die vorgestellten Beispiele eine geeignete Plattform. Die farbig unterlegten Bauteile werden nur für den schnellen TxDAC (Vorsicht, teuer!) benötigt.

Spezialisten vorbehalten bleibt die recht teure Option mit dem TxDAC AD9752, einem 12-Bit-Wandler mit der sagenhaften Geschwindigkeit von 125 Megasamples/s. Der kann im Prinzip modulierten Kurzwellenrundfunk oder die Signale auf einer DSL-Leitung direkt erzeugen (daher auch der Familienname TxDAC). Hier ist ebenso der horrend bepreiste, pinkompatible AD9754 einsetzbar, die zwei zusätzlichen Datenleitungen sind bereits verdrahtet. Der zugehörige OpAmp-Ausgangsverstärker LT1818 gehört ebenfalls zu den schnellsten seiner Zunft. Wie es sich gehört, liefert er das bis 60 MHz reichende Signal an eine SMB-Koaxialbuchse und nicht an einen profanen Platinen-Steckverbinder ab.

Die beiden Festspannungsregler sind obsolet, wenn keiner der DAC-Bausteine eingesetzt werden soll; Frequenzzähler-Vorverstärker und Video-RAM kommen mit den vorhandenen 3,3 V aus. Die aus Widerständen aufgebauten Simpel-DACs mit 3 und 4 Bit Auflösung an PL4 schließlich dienen dem Mini-Asteroids als „Soundkarte“ (Pin 3) und als Ausgangsstufe für das Z-Signal (Strahl-Helligkeit, Pin 1).

Die verschiedenen Optionen der DACRAM-Platine dürfen jederzeit gemeinsam auf der Platine verbleiben, auch wenn sie für die gerade geladene FPGA-Konfigaration nicht benötigt werden. Vergessen Sie bei eigenen Anwendungen aber nicht, die jeweils unbenutzten Bauteile mittels der zugehörigen Portleitungen abzuschalten: Also Output- und Write-Enable (RAM_OE, RAM_WR) des SRAMs im FPGA auf „Vcc“ verdrahten (zu sehen beispielsweise im folgenden Messsender-Schaltbild), ebenso die DAC-Steuerleitungen R/W (DAC_WR), SEL_AB (DAC_MPX) und CLK (DAC_CLK) für den Fall, dass die DACs nicht verwendet werden.

FPGA-Innenschaltung für den amplituden-modulierbaren Messsender. Ein wenig DSP-Technik kommt mit den schnellen Multiplizierern (in den Sinusgenerator-Blöcken enthalten) ins Spiel.

Der Frequenzzähler-Komparator lässt sich leider nicht abschalten, Netz FN100 bleibt also außen vor. Die gleichfalls nicht abschaltbaren Widerstände an FN87 bis FN96 sollten in der Regel nicht stören. Die vorgesehenen SMA-Printbuchsen lassen sich übrigens ohne weiteres gegen SMB-, SMC- oder MMCX-Ausführungen substituieren, solange diese den gleichen Layout-„Fußabdruck“ besitzen.

Sowohl der Dual-DAC als auch der optionale TxDAC auf der umseitig vorgestellten Erweiterungsplatine kommen beim nächsten Beispiel zum Einsatz, einem amplitudenmodulierbaren Messsender bis 10 MHz (60 MHz beim Einsatz des AD9252). Das Prinzip kennen Sie vom c't-Lab-DDS-Modul, hier wurde es allerdings in VHDL für das FPGA realisiert (Block „sinus_generator“): Ein 32 Bit breiter Phasenakkumulator addiert mit bis zu 120 MHz Takt einen per SPI übermittelten, ebenfalls 32 Bit breiten Phasenwinkel. Der Akkumulatorstand adressiert ein Wellenform-RAM, in dem eine Sinuswelle abgespeichert ist; der gewonnene Lookup-Wert gelangt schließlich an den D/A-Wandler. Je größer der eingestellte Phasenwinkel, desto höher ist auch die ausgegebene Frequenz.

Die Sinusgenerator-Blöcke enthalten jeweils noch einen 16-Bit-Pegelsteller, der mit einem der (etwa 5 ns schnellen) FPGA-Multiplizierer aufgebaut ist. Eine Amplitudenmodulation ist deshalb durch einfaches „In-Reihe-Schalten“ von zwei Sinusgeneratoren möglich: Der letzte liefert das Trägersignal, der vorgeschaltete die Modulation, die sich dank des digitalen Pegelstellers von 0 bis 100 Prozent quasi stufenlos einstellen lässt. Der zwischengeschaltete Addierer sorgt dann noch für den stationären Anteil des „Sendesignals“; ist dieser 0, erhält man eine Modulation mit unterdrücktem Träger. Unser für diese Applikation verfasstes Beispiel-Script erzeugt ein 1-MHz-Signal, das mit 1 kHz zu 70 Prozent moduliert ist. Mit kleinen Erweiterungen wäre diese FPGA-Anwendung übrigens ebenso in der Lage, frequenzmodulierte Signale oder beliebige (ladbare) Wellenformen zu erzeugen, etwa zur Anwendung als Arbiträrgenerator oder als elektronisches Musikinstrument.

Wie versprochen, nun unser Mini-Asteroids-Spiel im FPGA – eine vom Umfang her recht anspruchsvolle Anwendung, die den XC3S400 schon zur Hälfte füllt. Die Implementierung basiert auf einem Entwurf von Mike Johnson [2], der das Spiel in mühevoller Kleinarbeit auf einem Xilinx-Development-Kit zum Laufen gebracht hat – hier allerdings mit einem Video- und keinem Vektor-Ausgang.

Blockschaltbild des Mini-Asteroids-Automaten. Zur Darstellung benötigt man ein XY-fähiges Oszilloskop, das an die DAC-Ausgänge an PL3 der Experimentierplatine angeschlossen wird.

Wie beim Original wollten wir jedoch eine „richtige“ Vektordarstellung mit ihrem unvergleichlichen Analogrechner-Charme. Hierfür eignet sich fast jedes alte Analog-Oszilloskop, schärfer und heller sind aber spezielle Vektordisplays (z. B. die Display Unit 602 von Tektronix, auf dem Gebrauchtmarkt oft für unter 50 Euro angeboten). Letztere haben noch den Vorteil, über einen linearen Z-Helligkeitseingang zu verfügen, Oszilloskope gestatten dagegen oft nur eine simple Austastung des Strahls bei XY-Darstellung. Wir mussten bei Mikes Entwurf eigentlich nur Überflüssiges wegstreichen und die hardwaremäßige D/A-Wandler-Steuerung anpassen.

Mit dem simplen LabView-Programm FPGA-AMDDS.vi kann man den FPGA-Messsender fernsteuern. Das Oszillogramm zeigt simuliert die Auswirkung der Pegel- und Modulationseinstellung.

Kern der Schaltung ist nach wie vor ein 1,5 MHz schneller 6502-Prozessor, hier gleichwohl in Form des quelloffenen T65-Cores [3]. Ausnahmslos jedes TTL-IC der imposanten Asteroids-Platine findet sich in den VHDL-Sourcen in Form einiger Programmzeilen wieder; eine besondere Herausforderung war laut Mike Johnson das Re-Engineering des „Pokey“ genannten Custom-Soundchips von Atari, der in der Deluxe-Version des Spiels die vorher verwendeten diskreten NE555-Tongeneratoren ersetzte. Bei der im FPGA realisierten digitalen Filterung der Klänge kommen dabei echte DSP-Techniken (Finite Impulse Response) zum Einsatz, Interessierte können sich gern in die veröffentlichten Sourcen vertiefen. Die ROMs für CPU-Instruktionen und Vektorgenerator sind mit den Block-RAMs des Spartan-3 aufgebaut, die mit dem ROM-Inhalt „vorgeladen“ werden.

Als nettes Gimmick haben wir für die Spielpausen noch eine Bildschirm-Uhr eingebaut – die auf einem eigenen „Mikroprozessor“ im FPGA läuft, einer aufs Nötigste zusammengestrichenen 8051-Emulation (T51-Core) mit 25 MHz Takt. Hier erzeugt die CPU die XY-Koordinaten und die Helligkeitssteuerung (Z-Ausgang) selbst, ein Vektorgenerator wie im Asteroids-Spiel erübrigt sich aufgrund der einfachen Grafik und der vergleichsweise „stationären“ Anzeige von Zifferblatt und Zeigern. Die Idee und der 8051-Sourcecode für die „Scope Clock“ stammen von Sascha Ittner, der sie dereinst mit einem 89C4051-Controller aufgebaut hatte [4]. Unsere kleinen Anpassungen sorgen nun dafür, dass sich die Software auch auf der doppelt so schnellen 8051-Minimal-Emulation wohlfühlt und die Uhr sogar richtig „tickt“ – nämlich über den Sound-Ausgang auf PL4 unserer Experimentier-Platine.

Wenn Sie sich an eigenen Firmware-Anpassungen für den emulierten Controller versuchen wollen: Es ist ausgesprochen unpraktisch, nach jedem (sekundenschnellen) Assemblieren einer winzigen Firmware-Änderung die komplette Neusynthese durchlaufen zu lassen. Zum Glück besteht mit dem Data2mem-Tool von Xilinx die Möglichkeit, den neuen ROM-Inhalt direkt in ein bereits bestehendes BIT-File zu schreiben – dorthin, wo die Inhalte der korrespondierenden Block-RAMs definiert werden. Damit Data2mem deren Aufenthaltsort erfährt, ist den Sourcen ein sogenanntes BMM-Loc-Annotation-File (hier: ScopeClock.bmm, eine Textdatei mit einigen Instanz-Angaben zum Block-RAM) hinzuzufügen. Die Synthese (genauer gesagt: der sogenannte Floorplanner) erzeugt dann wiederum eine BMM-Datei (Suffix „_bd“) mit den notwendigen Angaben. Die vertrackten Befehlszeilen-Parameter verabreicht man Data2mem am besten über ein Batch-File – siehe hierzu die BAT-Dateien im „roms“-Verzeichnis des c't-AsteroidsClock-Projekts, hier stehen auch die verwendeten BMM-Files.

Die BMM-Prozedur hatte Xilinx nebenbei bemerkt eigentlich für die PicoBlaze- und MicroBlaze-Cores geschaffen. Diese „virtuellen“ 8- und 32-Bit-CPUs sind der FPGA-Architektur viel besser angepasst als die kompromissbehafteten Emulationen herkömmlicher Controller und CPUs: So belegt ein PicoBlaze-Softcore nur 96 FPGA-Slices entsprechend 2,5 Prozent der beim XC3S400 verfügbaren Ressourcen, erreicht dank RISC-Architektur aber über 40 MIPS. Mit der neuen MEM-Upload-Funktion des FPGA-Moduls lassen sich PicoBlaze-Firmwares übrigens deutlich leichter direkt von SD-Karte ins FPGA überspielen.

Spiel und Uhr werden über 12 Bits in den SPI-Registern 0 und 1 „bedient“, was allerdings nur für Testzwecke gedacht ist: Die Bits sind so verteilt, dass jedes davon auf dem PM8-Bedienpanel ein eigenes Digit belegt. Besser, Sie legen die Button-Eingänge stattdessen an acht freie FPGA-Portleitungen, an die dann 10 „richtige“ Taster und zwei Schalter (Uhr/Spiel-Umschaltung und Z-Invertierung) angeschlossen werden. Das Umverdrahten im Schaltbild ist dann auch gleich eine schöne Übung für den ISE-Einstieg. (cm)

[1] Jim Williams, High Speed Amplifier Techniques, Linear Technologies Application Note AN47

[2] FPGA Arcade

[3] T51-Core, OpenCores

[4] Scope Clock von Sascha Ittner

DDS-Konfiguration laden

//DDS-Konfiguration laden: 
0:CFG=DDS3.BIN
//SPI-Reg. 0, Carrier-Frequ. 051EB852h = 1 MHz:
0:0=85899346!
//SPI-Reg. 1, Pegel 2CCDh für Modulation, 4000h für Carrier:
0:1=751648768!
//Mod.frequenz 00014F8Bh = 1 kHz:
0:2=85899!

Dieses kleine c't-LabScript-Beispiel (als INI-Datei auf der SD-Karte gespeichert) stellt die DDS-Applikation auf 1 MHz mit 70 Prozent Amplitudenmodulation ein, die Modulationsfrequenz beträgt 1 kHz.

Stückliste
Video-RAM-Erweiterung
U3/U4 SRAM 62256-70,
62LV4006 (siehe Text),
DIL oder SOL32
C13 100n ker. RM5
JP1,JP2 Jumper 2pol.
Dual-DAC-Erweiterung
U5 AD5447YRUZ, TSSOP
U6, U7 AD8055, SO8
U8 78L05, TO92
U9 79L05, TO92
U10 OP-27, SO8
U11 REF-02, SO8
C1..23,26 100n, SMD1206
C14,16 2p2, SM0805
C8 22µ 16V Tantal RM2,5
L1 10 µH axial
R10,21 1k, SMD0805
R11,14 20k, SMD0805
R12,13,15,16 100R, SMD0805
R17,24 270R, SMD0805
R18,22 4k7, SMD0805
R19,23 470R, SMD0805
R20 2k2, SMD0805
PL3, PL4 Platinen-Steckverbinder 3pol.
TxDAC-Erweiterung
U1 LT1818, SO8
U2 AD9752AR, SO28
C4 2p2, SM0805
R1 100R, SMD0805
R2,4 470R, SMD0805
R3 100R, SMD0805
R5,6 220R, SMD0805
R7,8 51R, SMD0805
R9 3k3, SMD0805
PL1 SMA-Buchse RM 5x5, 90°
Frequenzzähler-Vorverstärker
U12 ADCMP600, SOT-23
R25 100R, SMD0805
R26 51R, SMD0805
R27 10k, SMD0805
R28 2k2, SMD0805
R29,30 100k, SMD0805
R32 47k, SMD0805
C24 100n, SMD1206
C25, C27 1µ, SMD1206
PL5 SMA-Buchse RM 5x5, 90°
JP3,JP4 Jumper 2pol.
Sonstiges, immer benötigt
PL2 VG-Messerleisten, 64pol. a/b 90°
Platine FPGA-DACRAM (eMedia, Segor)
Anzeige