GPIF enträtselt

Das GPIF (General Purpose Interface = Vielzweck-Schnittstelle) ist das Interface nach draußen vom EZUSB FX2 ("Easy USB Effects 2" = Einfaches USB, Effekte 2). Das Handbuch belässt manches im unklaren, und deshalb hier einige erklärende Beispiele.

Also eher "Unverständliche USB-Effekte, in hoher Geschwindigkeit".

Verständnisprobleme

Besitz von FIFOs

Falsch, nicht Besitz ganzer FIFOs, sondern von einzelnen FIFO-Puffern. Ein Puffer der Quantum-FIFO kann nur im Besitz genau einer Instanz sein: Im folgenden Beispiel gehe ich von OUT-FIFOs aus, d.h. Datentransport vom PC zur Peripherie: Daraus ergeben sich folgende Konsequenzen: Mit dem Rücksetzen einer FIFO werden die Puffer nicht so richtig für leer erklärt, sondern es passiert folgendes:

Voll und Leer bei Quantum-FIFOs

Es gibt eine wirklich einfache Festlegung: Logisch - aber was heißt hier „gefüllt“?

Ein Puffer gilt als „gefüllt“, wenn:

Der Abschluss erfolgt wie folgt: Daraus folgt, dass USB-Puffer immer nur mit Paket-Länge gefüllt werden. Die 512-Byte-Puffer werden also bei USB 1.1 mit einer maximalen Bulk-Länge von 64 Bytes nur schlecht ausgenutzt. Zu lange USB-Pakete werden nicht automatisch auf mehrere Puffer verteilt!
Was die SIE, das serielle USB-Interface, in diesem Falle macht, ist – raten Sie mal! – undokumentiert! Vermutlich wird NAK zurückgeschickt [svw. „Lieber Host, ich habe keinen Platz, versuche es im nächsten Mikroframe noch einmal.“], oder auch keine Antwort [svw. „Lieber Host, ich kann dich nicht hören, versuche es im nächsten Mikroframe noch einmal, aber höchstens dreimal, dann melde Deinem Anwenderprogramm, dass es schief ging.“]. Oder aber, die SIE platziert die Daten irgendwie über Puffer und 8051-XRAM hinweg, und schießt nebenbei die Firmware ab…
Also muss der Puffer stets größer/gleich der maximalen USB-Länge gewählt werden.

Puffer werden stets abgeschlossen und gelten dann als gefüllt.

Wichtig (Faustregel): Leere Puffer sind normalerweise niemals in 8051-ZVE-Besitz, sondern rutschen automatisch zur entsprechenden „Tankstelle“:

Aber es gibt Eingriffsmöglichkeiten.

Entladestation: Die ZVE kann in das (Betanken und) Leeren der Puffer eingreifen:

„Verarbeiten“ und „Verschlucken“ sind praktisch gleiche Vorgänge; ob die Daten angefasst werden oder nicht ist für den Puffer ohne Belang. Nebenbei, der Zugriff auf die Puffer durch die drei Instanzen ist grundweg verschieden:

Zuordnungen
InstanzZugriff
ZVEwahlfrei (adressierbar)
USBen bloc (USB ist zwar seriell, aber ohne Belang! U.a. wegen CRC)
GPIFsequenziell (Byte für Byte oder Wort für Wort)

Die Sichtweise von GPIF auf Ausgabe-Puffer:

Null-Byte-Pakete: Sie sind lästig und sollten vermieden werden. Ein Windows-Host weigert sich, Null-Byte-Bulk-Pakete zu senden; bei Isochron geht es womöglich doch.

Ungerade-Bytezahl-Pakete: Was ein 16-bit-GPIF in dieser Situation macht ist noch völlig ungeklärt und nirgends dokumentiert! Vermutlich wird in diesem Fall ein ungültiges High-Byte ausgegeben. Also: vermeiden!!
Bei IN-Paketen kann dies nicht passieren; außer wenn man den automatischen Puffer-Abschluss auf einen ungeraden Füllstand gelegt hat. (Was das GPIF dann macht, ist undokumentiert!!)

Interrupt-Spagetti

Was Cypress vorführt, ist alles andere als lehrreich. Bedenken Sie folgenden Satz, der für die Mikrocontrollerprogrammierung fundamental ist:

Wenn ein Problem ohne Interrupts lösbar ist, dann ist das bereits ein Grund dagegen.
Sie können Interruptanforderungen (IRQs) sehr einfach in einer Abfrageschleife abtesten und darauf reagieren: Ihr Programm wird nicht nur schneller, es entfallen alle Race Conditions, und es ist einfach debugbar.

In der Hauptschleife der 8051-Firmware werten Sie einfach den Inhalt von USBIRQ (für Endpoint EP0) sowie die OUTxCS / INxCS für die übrigen Endpoints aus und verzweigen entsprechend. Nicht anders machen es die Cypress-Beispiele, nur umständlicher.

Sieht so aus als hätte irgendein Chef bei Cypress befohlen, dass ihre Beispiele unbedingt den ach-so-tollen vektorisierten USB-Interrupt verwenden müssen.

Mit oder ohne Bytezähler?

Hm...

Mehrere Ein/Ausgabeströme und das AUTOOUT/AUTOIN-Feature

…sind eigentlich zwei verschiedene Schuhe!

AutoOut sorgt dafür, dass vom USB gefüllte Puffer automatisch zum GPIF-Besitz übergehen. (Sie werden zu Slave FIFOs.) FIFO-Statusleitungen (im Slave-FIFO-Betrieb also externe Leitungen) verändern entsprechend dem Füllstand ihren Zustand.

Das GPIF bleibt jedoch weiterhin „ausgeschaltet“! Ich habe noch keine Funktion hinbekommen, s.u.!

AutoIn sorgt dafür, dass vom GPIF (oder Slave FIFO) gefüllte und terminierte(!) Puffer zum USB-Besitz übergehen. Dabei darf der Füllstand eines Puffers nicht die maximale USB-Paketgröße übersteigen; also für Bulk-Pipes im Full-Speed-Modus maximal 64 Bytes!

Was die SIE in diesem Falle tut, ist – es wird langsam langweilig – undokumentiert! Wahrscheinlich ist, sie sendet auf Gedeih und Verderb alles, und erzeugt USB-Babble, lässt auf PC-Seite Speicher überlaufen (je nach Host-Controller), lässt Windows abstürzen, wer weiß? Die SIE weiß ja nichts von Maximallängen!
Wenn mehrere FIFO-Datenströme über das GPIF laufen sollen (bspw. ein OUT- und ein IN-Strom), muss der Programmierer das GPIF bei entsprechendem Bedarf „anstoßen“ (am einfachsten durch Schreiben auf GpifTrig @BB) - es gibt hierfür keinen Automatismus!! Und die GPIF-Wellenform muss so gestaltet werden, dass sie auch umgehend endet: Wann das GPIF angestoßen werden muss, lässt sich am leichtesten in einer engen Abfrageschleife abtesten: Achtung: Bei Eingabe-Datenströmen muss ein Mechanismus eingebaut werden, dass halb gefüllte (nicht terminierte) Puffer auf der Slave-FIFO-Seite firmwaremäßig terminiert werden, damit diese vom Rechner gelesen werden können! Gut geeignet hierfür ist die NAK-Interruptanforderung (NakIrq @E65B, InPktEnd @E648), die auch ohne Interrupt zyklisch abgefragt werden kann.

Anwendung

Volle Ausgabegeschwindigkeit (Xilinx-FPGA konfigurieren)

Das GPIF wird zum Großteil zur Kommunikation mit CPLD oder FPGA gebraucht. Clever ist es dabei, die Busverdrahtung so zu gestalten, dass die Konfiguration großer RAM-basierter FPGA ebenfalls über dieselbe Verbindung läuft. Damit spart man sich den Konfigurations-PROM und erleichtert sich das Configware-Update, weil dazu einfach nur eine Treiberdatei ausgetauscht werden muss.

Das folgende Bild zeigt außerdem die Möglichkeit, auch die 8051-Firmware per USB laden zu lassen. Packt man alles in eine Treiberdatei (.SYS), ist ein komplettes Software-Update sehr einfach. Ein derartiges Gerät startet in mehreren Phasen:

Die Verzögerung durch den Bootprozess liegt bei unter 1 s ohne Re-Numerierung, bei 3 s mit Re-Numerierung (je nachdem wie schnell Windows Treiber lädt).
[Übersichtsschaltplan]
Die günstigste Möglichkeit, einen FPGA mit USB anzuschließen

Ein RAM-Pufferspeicher wird fast immer benötigt, wenn bei hoher USB-Datenrate Unterbrechungen im 100-ms-Bereich überbrückt werden sollen; der FPGA-interne RAM ist dafür viel zu klein oder zu teuer. Für den Mikrocontroller genügt fast immer der kleine 56-polige Gehäusetyp.

Zur Konfiguration eines Virtex-FPGA mit der Methode Slave SelectMAP benötigt man folgende Verbindungen, die man auch im nachhinein (also wenn FPGA im regulären Betrieb) als schnelle Übertragungsstrecke nutzen kann:

Cypress CY7C68013AXilinx Virtex II
IFCLKCCLK
Dedizierter Konfigurationsanschluss
PB0..7D7..0
Bits stürzen! Wirklich! Bei Virtex ist D0 das MSB! Wird oft überlesen.
CTL0CS_B
CTL1RDWR_B
RDY0BUSY
Wird nur bei komprimierten oder verschlüsselten Konfigurationsdaten gebraucht.
Beim Virtex4 ausschließlich zum Rücklesen
PA2
oder irgendein Ausgabepin
PROG_B
Dedizierter Konfigurationsanschluss
PA3
oder irgendein Ein/Ausgabepin
DONE
Dedizierter Konfigurationsanschluss
PA4
oder irgendein Eingabepin
INIT_B
Nicht unbedingt nötig, siehe Text
Mit DONE als Ein/Ausgabe(!) kann man einen definierten Start des FPGA erreichen und benötigt so keine Design-Resetleitung o.ä. Ansonsten könnten Konfigurationsdaten als Eingabedaten für den bereits konfigurierten FPGA fehlinterpretiert werden.
Hintergrund: Der Konfigurationsdatenstrom *.RBT, *.BIT enthält am Ende einige überflüssige Bits bzw. Bytes, um den Startup auszulösen. Diese könnten, wenn der FPGA das DONE-Pin freigibt, »in den falschen Hals« gelangen.

INIT_B wird nur benötigt, um:

Als eine mögliche Handshakeleitung (im konfigurierten Fall) ist INIT_B auch ein Kandidat für eine RDYx-Leitung.
GPIF-Designer:
Für „volles Rohr“ benötigt man im GPIF-Designer tatsächlich drei Zustände:
// GPIF Waveform 1: FIFOWr                                                                 
//
// Interval     0         1         2        ...    Idle (7)
//          _________ _________ _________ _________ _________
//
// AddrMode Same Val  Same Val  Same Val
// DataMode Activate  Activate  NO Data
// NextData SameData  NextData  NextData
// Int Trig No Int    No Int    No Int
// IF/Wait  IF        IF        IF
//   Term A EF+1      EF+1      (egal)
//   LFunc  AND       AND       AND                                              
//   Term B EF+1      EF+1      (egal)
// Branch1  Then 2    Then 2    ThenIdle
// Branch0  Else 1    Else 1    ElseIdle
// Re-Exec  No        Yes       No
// Sngl/CRC Default   Default   Default
// CS_B         0         0         1
// RDWR_B       0         0         1
Die Einstellung „NextData“ führt zu Beginn des Zustandes zum Lese-Takt und damit zum Datenwechsel. Mit geringer GPIF-Taktverzögerung, sofern der FX2 die Taktquelle ist.
C-Programm:
Wichtig ist, dass man das Empty-Flag 1 Byte früher braucht!
 EPxGpifFlgSel=1	// Anhalten bei Empty-Flag, nicht bei TerminalCount
 EPxFifoCfg=0x20	// Handarbeit, 8 bit, OEP1 (Empty-Flag 1 Byte früher)
Man braucht kein SyncDelay bei 48 MHz, weil nur 3 Takte Wartezeit erforderlich sind und der C-Compiler genügend (unsinnige) Befehle einstreut.
Wichtig (Die verflixte Null): Das GPIF darf nicht aktiviert werden, wenn Null Bytes in den Ausgabepuffern stehen! Die Zustandsmaschine würde ein ungültiges Byte zum FPGA schaffen.

Bei nur einem Byte läuft die Zustandsmaschine direkt von Zustand 0 (= Fall-Through-Byte zum FPGA schicken) zum Zustand 2 (= FIFO leer lesen, aber FPGA laden beenden [CS_B=1]). Notfalls kann man sich darauf verlassen, dass der (Windows-)Host kein Null-Byte-Paket schickt.

Ob Bytes im Puffer vorhanden sind, kann nicht mittels EPxCS, EPxFifoFlags oder EPxFifoFlgs abgefragt werden, sobald das EF+1-Feature aktiviert ist. Vermutlich ist EPxFifoBc zu benutzen.

Eine in dieser Hinsicht „eigensichere“ Zustandsmaschine zu konstruieren ist nicht möglich, wenn man das EF+1-Feature benutzt.

Im Experiment war es egal, ob IFCLK (Interface-Takt) negiert oder nicht-negiert betrieben wurde (Virtex-II, 48 MHz).

Wie man sich vorstellen kann, sind auch größere FPGAs mit einem Fingerschnipp konfiguriert. Man kann von einer Datenrate von 30 MByte/s (halbe High-Speed-USB-Bandbreite) ausgehen. Serielle Schneckentempo-Konfiguration war gestern.

Weitergehende Überlegungen

Eine Auslegung der Übertragungsstrecke auf 16 bit (für den regulären FPGA-Betrieb) für mehr Durchsatz erscheint unnötig und als Pinverschwendung, weil die Puffer des FX2 recht klein sind und USB den Flaschenhals darstellt.

Der Verzicht auf den FX2 und Implementation des USB auf dem FPGA selbst bedeutet FPGA-Ressourcenverbrauch und kommt in der Praxis teurer als der zusätzliche Chip. Vom Entwicklungsaufwand ganz zu schweigen. Und den Konfigurations-Flash braucht man in diesem Falle ja auch noch.

Wäre das Xilinx-USB-JTAG-Protokoll (iMPACT) dokumentiert, könnte der FX2 gleich noch die Rolle des „USB Cable“ zum Debuggen übernehmen! Wäre schön, aber das hat noch niemand geknackt…

Genug geträumt! Zurück zur Konfiguration!

Gedrosselte Ausgabegeschwindigkeit

Die vorhergehende Wellenform wertet BUSY nicht aus. Damit funktioniert sie nicht für komprimierte oder verschlüsselte Bitströme. (Betrifft eigentlich nur Virtex-II. Virtex4 nicht! Der geht immer! Spartan hat eh' keine Verschlüsselung.) Am Zustandsgrafen habe ich wirklich Stunden zugebracht, und so sieht er aus:
// GPIF Waveform 1: FIFOWr                                                                 
//
// Interval     0         1         2         3         4     Idle (7)
//          _________ _________ _________ _________ _________ _________
//
// AddrMode Same Val  Same Val  Same Val  Same Val  Same Val
// DataMode Activate  Activate  Activate  Activate  NO Data
// NextData SameData  SameData  NextData  SameData  NextData
// Int Trig No Int    No Int    No Int    No Int    No Int
// IF/Wait  IF        IF        IF        IF        IF
//   Term A EF+1      BUSY      EF+1      BUSY      (egal)
//   LFunc  AND       AND       AND       AND       AND                                              
//   Term B EF+1      BUSY      EF+1      BUSY      (egal)
// Branch1  Then 3    Then 0    Then 3    Then 0    ThenIdle
// Branch0  Else 1    Else 2    Else 1    Else 4    ElseIdle
// Re-Exec  No        No        No        No        No
// Sngl/CRC Default   Default   Default   Default   Default
// CS_B         0         1         0         1         1
// RDWR_B       0         0         0         0         0
Die Zustandsmaschine als Automatengraf:
   _____             _____            _____            _____
  |0    |---Empty-->|3    |   ____   |4    |          |i    |
  |  A  |           |     |---Busy-->|>    |--------->|     |
  |_____|<--Busy----|_____|          |_____|          |_____|
   ^   |               ^
   |  _|___            |	Zeichenerklärung:
 Busy Empty          Empty	0  Zustands-Nummer (i=Idle)
   |   |               |	A  Aktivität (von CS_B)
   |___v    ____     __|__	>  NextData (akt. FIFO-Daten verwerfen) 
  |1    |---Busy--->|2    |
  |     |   _____   |> A  |	Empty = letztes Byte in FIFO
  |_____|<--Empty---|_____|	Busy  = FPGA lehnte Byte ab

C-Programm:
Auch hier wird das Empty-Flag 1 Byte früher gebraucht. Noch wichtiger ist es, die GPIF-Auswertung auf synchron zu stellen, damit nur ein Flipflop wirksam wird, nicht zwei.
Es ist ja auch synchron! Es liegt nur eine Takt-Domäne vor.

Auch mit Drosselung sind größere FPGAs mit einem Fingerschnipp konfiguriert. Die theoretische Datenrate von 15 MByte/s (halbe IFCLK-Taktfrequenz) wird wegen diverser Windows-Aussetzer nicht erreicht. Trotzdem: affenschnell.

Volle Lesegeschwindigkeit

Zu schreiben

Dokumentationsfehler und Bugs