Samstag, 17. Dezember 2011

Z80 in C programmieren

Ich bin wieder fleißig gewesen und habe am Z80-Computer gearbeitet. Ich plane die erste Erweiterungskarte für das System. Darauf wird sich eine IDE-Schnittstelle befinden, die ich ja bereits für die andere Z80-Platine erstellt hatte. Dann ein Controller zum Anschluss von Maus und Tastatur. Ich habe hier ein paar Trockenübungen auf einem Steckbrett gemacht: Tastatur auslesen und Numlock blinken lassen funktioniert, das sollte also kein Problem mehr sein. Eine PS/2-Maus hat das gleiche elektrische Interface, aber die Softwareseite habe ich mir noch nicht näher angeschaut und weiß daher nicht, wie schwierig das zu implementieren ist.
Weiterhin wird sich auf dem Erweiterungsboard der AY-3-8912 befinden. Das ist ein 8-Bit Sound-Chip mit drei "Stimmen". Er kam in einigen 8-Bit Computern und Arcadespielen zum Einsatz. Ich habe noch keinen in der Hand gehabt, aber bereits den Schaltplan für die elektrische Anbindung an den Z80 erstellt. In den nächsten Tagen sollte er eintreffen, er ist bereits bestellt.

Nun zum eigentlichen Thema dieses Posts:
Ich habe mich heute mit dem C-Compiler SDCC beschäftigt, der Code für "Small Devices" erzeugen kann, wie Z80, 8051, 68HC08 und 8-Bit PIC-Controller von Microchip. Eigentlich habe ich mit größeren Schwierigkeiten gerechnet, bis ich etwas Gescheites zum Laufen zu bringen, aber es hat relativ schnell geklappt.
Die erste Frage war, wie man in C auf die IO-Ports des Z80 zugreifen kann. Im Assembler macht man das mit IN/OUT-Befehlen:
IN A, (32)   (Lesen von Port 32)
OUT (32), A  (Schreiben auf Port 32)
Diese Befehle werden Spracherweiterungen mit dem schönen Namen Storage Class Language Extension zugeordnet, wie ich nach einigem Suchen herausfand.
Mit
__sfr __at (32) uart_rxtx;
definiert man eine Variable, die man lesen und schreiben kann. Der Code, der daraus erzeugt wird, ist dann das entsprechende IN/OUT. sfr steht übrigens für Special Function Register.
Ich schreibe mir also folgenden Code in schönstem ANSI-C:

__sfr __at (32) uart_rxtx;                
__sfr __at (33) uart_st;                  
                                          
void sendstr(const char *s) {             
while( *s ) {                            
while( uart_st&2 );                     
uart_rxtx = *s;                         
++s;                                    
}                                        
}                                         
                                          
int main(void) {                          
sendstr("Z80 Computer\r\n");             
sendstr("Programmiert in C!\r\n");       
while(1);                                
}                                         


Kompiliert wird das mit
sdcc -mz80 test.c
Heraus kommt test.ihx im Intel-Hex-Format, mein Programmer schluckt aber nur einfache Binärdaten. Das Hex-File kann man mit
makebin.exe -p test.ihx test.bin
in eine einfache Binärdatei wandeln. Und es funktioniert :-)
So ganz habe ich noch nicht durchdrungen, was da alles vor sich geht. Es wird noch gewisser Startup-Code hinzugefügt, unter Anderem in Form der Datei crt0.s, wo Interruptvektoren und der Stackpointer initialisiert werden. Jetzt als Standalone-Programm läuft das mit dem SDCC-Startup-Code. Wie das eines Tages mal aussieht, wenn ich ein kleines OS geschrieben habe, von dem aus ich solch ein Binary ausführe, kann ich nicht so wirklich einschätzen. Ich werde es sehen :-)

Dienstag, 29. November 2011

Grafisches Display am Z80

Zum Testen und als erste halbwegs sinnvolle Anwendung der neuen Platine habe ich ein grafisches LCD-Display mit 128x64 Auflösung angebunden:
Z80 mit Display
Das ist ein DEM 128064A SYH-LY mit einem 8-Bit-Interface. Das Interface besteht im Wesentlichen aus dem 8-Bit-Datenbus, einer Register Select Leitung (RS), zwei high-aktive ChipSelects (CS1,CS2), high-aktives Enable (E) und low-aktives Reset (RSTB).
Das Display ist aus zwei Segmenten mit 64x64 Pixeln aufgebaut, betrieben durch je einem Treiber-IC S6B0108. Deswegen gibt es auch die beiden Chip Selects CS1, CS2. Mit CS1 selektiert man den linken Teil des Displays, mit CS2 den rechten. Register Select bestimmt, ob das Datum auf dem Datenbus ein Befehl für einen Treiber-IC ist (RS=low), oder in den Speicher des Displays geschrieben werden soll (RS=high). Bei einer fallenden Flanke von E wird das Datum vom Datenbus vom selekierten IC gelatcht und dort entsprechend verwurstet.

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