Posts mit dem Label CPLD werden angezeigt. Alle Posts anzeigen
Posts mit dem Label CPLD werden angezeigt. Alle Posts anzeigen

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.

Sonntag, 5. Februar 2012

Extboard und VGA-Versuche

Gestern habe ich meine Z80-Erweiterungsplatine in Auftrag gegeben. Wie schon geschrieben, wird darauf ein AY-3-8912 zur Sounderzeugung mit Verstärker für direkten Lautsprecheranschluss sein und eine IDE-Schnittstelle (wie bereits für das alte Z80-Board implementiert).
Ein dsPIC30F6011 wird für die Anbindung von PS/2-Maus und Tastatur sorgen. Da noch so viel Platz auf der Platine zur Verfügung stand, habe ich mich entschlossen, mich mal an Ethernet zu probieren. Microchip hat den ENC28J60 im Programm, den angeblich weltweit kleinsten Ethernet-Controller. Über SPI wird der an den dsPIC angebunden. Microchip bietet auch gleich einen kompletten TCP/IP-Stack für deren µCs an. So ein Stack ist ziemlich groß, daher habe ich einen der größten dsPICs gewählt, damit neben dem TCP/IP-Stack noch ausreichend Platz für weiteren Code bleibt.
Das Extboard
Sollte alles wie geplant klappen, implementiert mein System dann Maus, Tastatur, IDE, Sound, RS232 und Ethernet. Natürlich ist dann erst einmal viel Software zu schreiben, aber dann fängt der Spaß ja erst richtig an :-)

Nun fehlt nur noch eine "Grafikkarte". Dazu hatte ich vor ein paar Monaten mal eine kleine Testplatine mit einem XC9572 CPLD erstellt, diese lag aber nur unbestückt im Karton herum. Nun habe ich sie bestückt und den CPLD mit Anregungen von http://www.fpga4fun.com/PongGame.html programmiert.

VGA mit CPLD

Die beiden Wannensteckerbuchsen sind zur Anbindung meines alten Z80-Boards. Der unbestückte IC-Sockel kann einen RAM-Baustein (gedacht als Grafikspeicher) aufnehmen. So sieht das Testbild aus, dass der CPLD erzeugt:

VGA-Output
Die Auflösung ist 640x480 bei 8 Farben. Ein paar Verilog-Code-Anpassung sind noch nötig, da das Bild sehr weit nach links verschoben ist. Das lässt sich aber einfach durch das V-Sync-Signal beeinflussen.

Schwieriger wird es, wenn der CPLD die Grafikdaten aus dem RAM holen muss, und den RAM-Zugriff des Z80 koordinieren muss. Ob der CPLD das alles aufnehmen kann?

Mittwoch, 16. November 2011

Neues Board

Yet another Z80-Board



Da ich mit dem Z80-Board unzufrieden war, habe ich nun ein neues designt. Hauptmotivation war die Grafikkarte, die an das System angebunden werden soll. Diese hätte nur über IO-Ports angesprochen werden können. Für Zugriffe auf den Grafikspeicher ist das sehr langsam. Nicht nur, weil IO-Zugriffe einen extra-Wait-State-Takt haben, sondern weil sich die Pixel nicht direkt adressieren lassen. Statt dessen hätte ich Register für die Pixeladresse und dem Pixelfarbwert bereitstellen müssen. Um einen beliebigen Pixel zu ändern, müsste also eine 16-bit Adresse (2 IO-Zugriffe) und ein Byte für die Pixeldaten (1 IO-Zugriff) geschrieben werden.
Im neuen Design befindet sich ein CPLD auf dem Board, der den 64kB-Adressraum in vier Viertel einteilt:

BankStartEnde
00 kB16 kB
116 kB32 kB
232 kB48 kB
348 kB64 kB

In jedes Viertel (Bank) können acht verschiedene RAM-Seiten eingeblendet werden. Zusätzlich lässt sich in jedes Viertel ein externer Bereich einblenden, wie z.B. der Grafikspeicher. Im ersten Viertel ist zuerst das EEProm eingeblendet, auf dem sich der Bootloader befindet.
Das Einblenden von Speicherbereichen wird mit Hilfe eines 16-Bit-Registers im CPLD realisiert, welches über IO-Port-Adresse 0 und 1 geschrieben werden kann.

Für jedes Viertel sind vier Bits im Register vorgesehen:

extbs2bs1bs0
3210

Das ganze mal vier für vier Viertel macht 16 Bits.

Ist das ext-Bit auf 1, wird bei Zugriff auf dieses Viertel der externe Speicher selektiert, egal wie die bs-Bits stehen. In Bank 0 wird bei bs=000 das EEProm selektiert, ansonsten der RAM-Baustein.
Möchte man nun in Bank 1 in eine andere Speicherseite einblenden, z.B. durch bs=001, schreibt man Folgendes:

ld a, 0001 0000 b
out (0), a       

Es wird 00010000 in den Akku geladen und über Port 0 in das CPLD-Register geschrieben.
Das untere Nibble ist null, denn in der Bank 0 soll der EEPRom eingeblendet bleiben. Das obere Nibble ist für Bank 1. Um den EEProm auszublenden und RAM einzublenden, schreibt man:

ld a, 0001 0001 b
out (0), a       

Das obere Nibble bleibt gleich, denn Bank 1 soll ja nicht umgeschaltet werden.
Bank 2 und 3 liegen im oberen Byte des 16-Bit-Registers und werden über Port 1 angesprochen:

out (1),a

Beim "Banking" muss man generell aufpassen, dass man nicht die Seite umschaltet, von der man gerade Code ausführt, denn das führt unweigerlich zum Absturz. Auch die Seite, in der der Stack liegt, sollte nicht umgeschaltet werden, es sei denn, man weiß genau, was man tut :-)

Das neue Z80-System kann auf diese Weise 4 * 8 * 16 kB = 512 kB RAM ansprechen (minus der EEProm-Seite). Mit den ext-Bits lässt sich noch weiterer Speicher von außen einblenden.

Die MMU kann man auch recht einfach mit TTL-Logikbausteinen aufbauen. Mein Ziel, alle Features auf eine Euro-Platine zu packen, hätte dies zunichte gemacht. Es wären nämlich doch einige Bausteine nötig gewesen, um die Logik zu implementieren. Der CPLD erschlägt diese Aufgabe alleine und kann sogar noch umprogrammiert werden, eine flexible Lösung also. Als mein erstes sinnvolles CPLD/Verilog-Projekt war dies schonmal eine gute Einführung für mein Grafikkartenvorhaben.

Hier nochmal wichtige Schnipsel aus dem Schaltplan der MMU:
Speicheranbindung
Die Leitungen MA16-MA18 legen die Werte von bs0-bs2 an den RAM an, abhängig von der Bank, die gerade angesprochen werden soll. Die Bank wiederrum wird durch A14, A15 bestimmt. A14=A15=0 entspricht Bank 0, A14=1, A15=0 entspricht Bank 1, usw.

CPLD XC9536

Die PullUps an den Ausgängen (MA16-MA18, /ROM_CS, /RAM_CS, /EXT_CS) sind nötig, weil der XC9536 es nicht auf 5V-High-Pegel schafft, sondern nur ~3,9V. Statt High-Pegel wird nun ein Ausgang hochohmig geschaltet und über den PullUp auf 5V gezogen.

Weiterhin habe ich den UART 16C550 entfernt. Der dsPIC auf dem Board programmiert jetzt nicht nur den EEPROM, sondern hängt am IO-Port als UART und Co-Prozessor für schnelle Multiplikationen und Divisionen.

Und da Blog-Lesen ohne Bilder nur halb so viel Spaß macht, noch ein paar Impressionen der letzten Debugsession:


 
Erster Einsatz meines neuen Logic Analyzers