Samstag, 8. Februar 2014

VGA Karte mit CPLD

Ich wollte schon seit geraumer Zeit eine kleine VGA-Karte bauen. Nun habe ich einen ersten funktionierenden Prototypen. Herzstück ist ein XC95144XL - CPLD von Xilinx. Dieser kommt im TQFP-Gehäuse mit mindestens 100 Pins und fine-pitch, eine neue Herausforderung für meine Lötfähigkeiten :-)

Glücklicherweise hat das Löten problemlos geklappt- mit viel Flußmittel und Entlötlitze.
VGA-Board

Der entlötete IC war ein AVR ATmega128, mit dem ich den CPLD ansteuern wollte. Geplant war eine Daten-/Adressbus-Schnittstelle wie beim Motorola M68008-Prozessor. Das bedeutet, der Grafik-RAM wird mit einem 17-Bit breiten Adressbus, einem 8-Bit breiten Datenbus, einem /CS und R/W-Signal angesteuert.

Aus mir unbekannten Gründen ließ sich der AVR nicht programmieren, obwohl mein AVR-Dragon den Chip über die ICSP-Schnittstelle (der Wannenstecker auf der Platine) erkannt hat.

Mein Plan, den CPLD über die 68k-Schnittstelle anzusprechen, war also gescheitert. Aus dem CPLD-Verilog-Code habe ich die Schnittstelle rausgeschmissen und stattdessen eine simple SPI-Schnittstelle implementiert, worüber sich nun Daten in den Grafik-RAM schieben lassen. Den PIC32 auf dem roten Digilent-Board verwende ich, um Daten per Software-SPI in den CPLD zu schieben (die drei dünnen Kupferlackdrähte, die an den Pads des AVR aufgelötet sind).

Das SPI-Protokoll besteht aus 17+8 = 25 Bits. Die oberen 17 Bits sind Adressbits, die unteren 8 sind die Datenbits für die Pixelfarbe. Bei low-activen Chipselect shiftet der PIC32 die Adress- und Pixel-Bits in ein internes CPLD-Datenregister. Bei der dann folgenden steigenden /CS-Flanke legt der CPLD im Schreibzyklus die Daten- und Adress-Bits auf den Grafik-RAM-Bus und schreibt so die Daten in den RAM.

Pro Pixel müssen also 25 Bits in den CPLD geschoben werden. Bei einer Auflösung von 320x200 sind 64000 Pixel für ein vollständiges Bild zu schreiben, es müssen also 64000*25 Bits geschoben werden. Das begrenzt daher die Geschwindigkeit, mit der der RAM beschrieben werden kann, recht stark. Hinzu kommt, dass der CPLD mit 25 MHz getaktet ist, und daher nur SPI-Taktraten kleiner 12 MHz schaffen kann.

Prinzipielle Beschreibung

Die Grafikkarte besteht aus diesen ICs:

  • CPLD XC95144XL
  • 128 kByte SRAM AS7C31025C-10 (10 ns schnell)
  • Oszillator 25 MHz
Für ein VGA-Signal der Auflösung 640x400 benötigt man einen Pixel-Takt von 25,175 MHz (VGA-Timings siehe hier), eine Frequenz von 25 MHz tut es aber auch. Für jeden Pixel (bei 8-Bit-Farben) muss ein Byte aus dem SRAM ausgelesen werden, wofür bei der Frequenz also 40 ns zur Verfügung stehen. Das ist schon recht sportlich, die SRAMs, die man bei Reichelt findet, schaffen 55 ns oder 70 ns. Bei TME.EU habe ich aber schnellere SRAMs gefunden.

Um die Speicher- und Taktanforderungen zu senken, habe ich die Auflösung auf 320x200 halbiert. Im Prinzip verdoppel ich die Pixel horizontal und vertikal. Dadurch habe ich die doppelte Zeit, um den RAM auszulesen und nur ein Viertel des Speicherbedarfs.

Nur den RAM auszulesen, reicht aber ja nicht aus. Der Speicher muss auch beschrieben werden können. Dazu verdoppel ich den Takt, mit dem der RAM angesprochen wird. In "geraden Takt" wird der RAM ausgelesen, um die Daten aus dem Bildschirm anzuzeigen. Im ungeraden Takt können die Daten, die vom PIC32 reingeshiftet wurden, in den RAM geschrieben werden.

Testbild

Schaltung

Schaltung VGA-Karte

VGA ist analog. Daher müssen aus einer 8-Bit-Farbe drei analoge Farbkanäle (RGB) erzeugt werden. Die 8 Bits eines Farb-Bytes werden als RRRGGGBB-Farbe interpretiert. Für den roten und grünen Kanal werden drei Bits verwendet, für den blauen zwei.

Pixeldaten und RAM-Adressierung

Die begrenzte Makrozellen- und Registerzahl des CPLDs zwingen zu einer etwas verschwenderischen RAM-Belegung. Eine Bildschirmzeile besteht aus 320 Pixeln, was 320 Byte im RAM entspricht. Die naheliegenste Adressierung eines Bytes in Abhängigkeit der x-y-Koordinate wäre ja
addr = y*320 + x
Eine Multiplikation mit 320 lässt sich in dem CPLD nicht realisieren, bzw. ich wüsste nicht wie :-)
Eine Multiplikation mit 512 hingegen ist schnell gemacht, denn das entspricht einem Bit-Shift nach links um 9 Stellen. Und das ist mit nem CPLD überhaupt kein Problem. Eine Bildschirmzeile belegt daher 512 Pixel, wovon nur die ersten 320 Pixel belegt sind. Die hinteren 192 Pixel können zwar geschrieben werden, werden aber nicht dargestellt.

Palettenkompatible Bilddaten erzeugen

Das digitale RRRGGGBB-Farbsignal mit den dazugehörigen Widerständen gibt die darstellbaren Farben auf dem Bildschirm vor. Die R-Bits sind die niederwertigen Bits, die B-Bits sind die höchstwertigen. Ein Byte mit Wert 0 entspricht Schwarz, 255 ist weiß. Mit C# lässt sich ein Bild mit einer beliebigen Farbpalette und Farbtiefe in ein kompatibles Format mit ähnlichen Farben erzeugen. Folgender C#-Code erzeugt Byte-Daten mit dem gewünschten Pixelformat:

Bitmap bild = new Bitmap("sample.bmp");                         
Byte[] picdat = new Byte[bild.Width * bild.Height];             
for (int y = 0; y < bild.Height; y++)                           
{                                                               
for (int x = 0; x < bild.Width; x++)                    
{                                                       
Color col = bild.GetPixel(x, y);                
int r = col.R / 32;                             
int g = col.G / 32;                             
int b = col.B / 64;                             
byte rgb = (byte)( r | (g << 3) | (b << 6) );   
picdat[x + y * bild.Width] = rgb;               
}                                                       
}                                                               


Verilog-Code

Die CPLD-Sourcen findet man hier.

Ausblick

Mein langfristiges Ziel ist es noch weiterhin, einen kompletten kleinen Homebrew-Computer zu bauen. Das größte Problem war immer, ein sinnvolles Display an meine Systeme anzubinden. LCD-Displays in Smartphone-Größe sind mir zu klein und etwas farbig sollte es auch sein. Diese Grafikkarte erfüllt nun meine wichtigsten Anforderungen.
Auf meinem Wunschzettel stehen Sprites, um schnelle Animationen zu ermöglichen und Hardware-Cursor für einen Mauszeiger und Tastatur-Cursor. Das lässt sich aber wohl nicht mit so einem kleinen CPLD realisieren. Der Sprung zu einem FPGA ist da nur logisch, aber auch gleich nochmal ne Runde anspruchsvoller.