8-Bit-Breadboard-Computer auf Basis einer 6502-CPU - Erste Schritte mit der CPU

Nun ist meine W65C02-CPU (Hersteller: Western Design Center) endlich aus China angekommen. Es hat zwar ein wenig gedauert, aber so einfach ist es gar nicht, günstig an die Dinger zu kommen. Meiner hat unter 4 Euro mit Versand gekostet.

Die Wartezeit habe ich mir ja schon mit der Entwicklung eines Clock-Moduls und eines EEPROM-Lesegerätes vertrieben. Die in diesen Aritkeln besprochenen und gebastelten Breadboard werden aber noch wichtig werden und zum Einsatz kommen. Ich habe nur schon ein wenig vorgegriffen.

Genau genommen habe ich einen W65C02S8P-10 bekommen. Die 8 steht für den Fertigungsprozess, P für das 40-Pin-DIL-Plastikgehäuse und die 10 für eine maximalen Takt von 10 MHz. Mittlerweile habe ich mitbekommen, dass ich mir das 14 MHz-Model auch neu bei Mouser für ca. 7 Euro hätte bestellen können. Egal, die 10 MHz reichen mir vollkommen.

Der W65C02S ist ein moderner Chip, der immer noch hergestellt wird, kompatibel zum MOS 6502 ist, aber ein paar Extra Leitungen und OpCodes hat. Aber das Wichtigste für mich:

Datasheet, Abschnitt 3.8: Phase 2 In (PHI2), Phase 2 Out (PHI2O) and Phase 1 Out (PHI1O)
Phase 2 In (PHI2) is the system clock input to the microprocessor internal clock. During the low power Standby Mode, PHI2 can be held in either high or low state to preserve the contents of internal registers since the microprocessor is a fully static design.
Das heißt, dass ich einen externen sebsterzeugten Takt (eben mit meinem Clock-Modul) benutzen kann und der W65C02 stabil bleibt und sich alle Register und internen Zustände merkt. Das ist beim original MOS 6502 leider nicht so: wird dort der Takt zu langsam, dann wird er instabil. Einzelschritt ist also mit dem MOS 6502 nicht so ohne Weiteres möglich. Mit dem W65C02 aber eben schon.

Und Einzelschritt werde ich für die Analyse und das Debugging meiner Maschinensprache-Programme brauchen. Mit dem W65C02 gegenüber dem MOS 6502 verhält es sich ein wenig wir mit SRAM und DRAM: SRAM ist statisch und vergisst nicht und DRAM will immer wieder aufgefrischt werden. Und geschieht dies nicht schnell genug (weil der Takt so langsam ist), dann wird der MOS halt leider vergesslich.

Pinout des W65C02

Einfach nur so in das Breadboard gesteckt funktioniert unser 6502 natürlich nicht. Er braucht schon die richtigen Verbindungen zur Außenwelt. Das geschieht wie immer über die Pins, der 6502 hat gleich 40 davon. Deren Bedeutung will ich hier kurz stichpunktartig notieren. Als genaue Referenz dient natürlich das Datasheet zum W65C02.

Pin-Nr.BezeichnungIN/OUTBedeutung
1VPBOUTVector Pull
Ist High (bis auf den letzten Takzyklus, da ist es Low), wenn eine Vektor Adresse während eines Interrupts angesprungen wird. Kann dazu benutzt werden, Interrupts zu priorisieren.
2RDYIN/(OUT)Ready
Low hät die CPU an, High startet sie wieder.
Z. B. kann hier langsamer Speicher Warterunden anfordern.

Bidrektional in dem Sinne, dass der OpCode WAI (Wait for Interrupt) den Ausgang auf Low setzen kann. Wird dieser Befehl benutzt, empfiehlt sich ein Setzen über einen Pullup-Widerstand auf High, damit WAI die Leitung auf Low setzen kann.
3PHI1 (OUT)OUTPhase 1 Out
Gibt ein invertierten Taktsignal aus, welches wir in PHI2 IN speisen.
4IRQINInterrupt Request
Wird dieser Pin auf low gesetzt, wird ein Interrupt ausgeführt. Das aktuelle Programm wird angehalten und der Program Counter auf den Stack geschrieben. Die Interrupt-Routine, dessen Adresse bei 0xFFFE/F gespeichert ist, wird ausgeführt. Danach wird das normale Programm weiter ausgeführt. IRQ sollte auf low bleiben, solange der Interrupt ausgeführt wird.
5MLOUTMemory Lock
Wird Low während der letzten drei Taktzyklen von ASL, DEC, INC, LSR, ROL, ROR, TRB und TSB und kann zur Trankaktionssicherheit eines Lese/Ändere/Schreibe-Zyklus verwendet werden.
6NMIINNon-Maskable Interrupt
Weiterer Interrupt-Typ, der auf eine fallende Flanke reagiert und zu dere Adresse springt, die in 0xFFFA/B gespeichert ist.
7SYNCOUTSYNChronize with OpCode fetch
Ist High in den Taktzyklen, in denen ein OpCode abgerufen wird. Kann zusammen mit SYNC verwendet werden, um Einzelschritte zu realisieren (aber das kann unser Clock-Modul auch ohne).
8VCCINVersorgungsspannung: +5V
9-20, 22-25A0-A15OUTAdressbus mit 16 Adressleitungen
Zeigen die aktuelle Adresse binär kodiert an. Z. B. für den Zugriff auf Speicher. Zeigt auch den Programm Counter an.
21GNDINGround / Masse
26-33D7-D0IN/OUTDatenbus mit 8 Datenleitungen
Zeigt das aktuelle Datenbyte binär kodiert an bzw. liest es. Abhängig vom OpCode. Ob Lese/Schreibmodus aktiv ist, wird angezeigt über R/W
34R/WOUTRead/Write
Ist High, wenn ein Datenbyte gelesen wird. Ist Low, wenn ein Datenbyte zum Schreiben anliegt. Ist vom jeweiligen OpCode abhängig. Kann dazu verwendet werden, die Zugriffsart auf das RAM zu managen.
35NC---Not connected
36BEINBus Enable
Wenn High, sind Adress- und Datenbus aktiv und werden von der CPU versorgt.
37PHI2 (IN)INPhase 2 In
Hier wird das Taktsignal von unserem Clock Modul eingespeist. Es kann normal und invertiert an den beiden PHI OUT Ausgängen wieder abgegriffen werden.
38SOINSet Overflow
Sollte nicht mehr benutzt werden und inaktiv, also auf High gehalten werden.
39PHI2 (OUT)OUTPhase 1 Out
Gibt ein das Taktsignal aus, welches wir in PHI2 IN speisen.
40RESINReset
Für die Reset-Routine aus und startet das Programm. Löscht alle internen Register und führt dann eine sieben Taktzyklen lange Rotuine aus, nach dessen Ende die Instruktionen bei der Adresse, die an 0xFFFC/D gespeichert ist, ausgeführt werden.

*Ein Überstrich bedeuted einen invertierter Eingang, d. h. die Funktion ist aktiv, wenn Low anliegt. Im WDC-Datasheet sind diese Pins mit B am Ende gekennzeichnet.

Hier noch einmal übersichtlich die Sprung-Vektoren der CPU: Vector Locations (Low Byte, High Byte) FFFE, F BRK/IRQ Software/Hardware FFFC, D RES Hardware FFFA, B NMI Hardware Nun wissen wir alles, was wir zum Anschluss der CPU wissen müssen. Machen wir uns also daran, den 6502 in ein Breadboard zu drücken und adäquat zu verkabeln.



Zuerst bekommt unsere CPU erst einmal Strom über VCC und GND. Dann legen wir ...
  • Pin 2 (RDY) auf High, schließlich soll die CPU was tun und nicht im Halt verkümmern.
  • Pin 4 (IRQ) und Pin 6 (NMI) auf High, weil wir haben keine Interrupts.
  • Pin 36 (BE) auf High, weil natürlich wollen wir Verkehr auf dem Adress- und Datenbus.
  • Pin 38 (SO) kommt auf High, das soll man eh nicht mehr benutzen.
  • Pin 37 (PHI2 (IN)) bekommt unseren Clock-Pulse, unser Taktsignal von unserem Clock-Modul
  • Pin 40 (RES), unser Reset ziehen wir über einen 10 kΩ-Widerstand auf High und verbinden den Pin außerdem über einen Taster mit GND. Normalerweise ist RES also High, es sei denn, wir drücken den Taster, dann wird er kurz Low und führt einen Reset aus und startet unser Programm.
Apropos Programm. Wir haben ja noch gar keins. Macht aber nichts, denn die Reset-Routine führt der 6502 trotzdem aus, wenn wir auf den Reset-Taster drücken. Blöd nur, dass wir so gar nichts davon mitbekommen, außer vielleicht ein klein wenig mehr Stromaufnahme. Darum kommen als nächstes ein paar Anzeige-Elemente dazu:
  • Die Output-Pins der CPU bekommen gelbe LEDs. An ein Ende habe ich einen 220 Ω-Widerstand angelötet, sonst hätte es nicht mehr aufs Breadboard gepasst. So sehen wir noch einmal, dass der Clock Pulse ankommt, ob der OpCode lesend oder schreibend ist, der Memory Lock aktiv oder ein Interrupt-Vektor angesprungen wird. Das brauchen wir zwar nicht unbedingt jetzt gleich, aber so sind wir schon mal fü die Zukunft gerüstet.

  • Dann wird der Adressbus mit LEDs verbunden (orange). Das heißt 16 Leitungen ziehen und mit den Anoden der (roten) LEDs verbinden. Die Kathoden gehen über 220Ω-Vorwiderstände an Ground. Wenn das Adressbit High ist, leuchtet die entsprechende LED, ist es Low, dann nicht.
    Die LEDs habe ich in Vierergruppen angeordnet, was man auch Halbbyte oder Nibble nennt. Ein Nibble entspricht einer Hexadezimalen Ziffer von 0 bis F. So kann man mit ein bisschen Übung (die werde ich schon mit der Zeit bekommen) die Adresse ablesen. Im Foto oben ist das z. B. gerade F, F, F, E; die aktuelle Adresse ist also 0xFFFE.

  • Auch der Datenbus wird verbunden, diesmal sind es nur acht gelbe Leitungen, die mit den grünen LEDs verbunden sind. Hier gilt das selbe Verkabelungsprinzip wie für den Adressbus. Allerdings ist der Datenbus schreibend und lesend.
Nach dem schon fast meditativem Akt des Jumperkabel schneidens, abisolierens, biegens und steckens ist der spannende Augenblick gekommen. Ist alles richtig verkabelt? Oder wird im schlimmsten Fall der W65C02 in einem Rauchwölckchen aufgeben und ich muss noch einmal wochenlang auf eine CPU warten?

Das Clock-Modul-Breadboard und das CPU-Breadboard werden verheiratet, der Strom und das Clock-Signal vom Clock-Modul weitergereicht, das Labor-Netzteil auf eine max. Stromstärke von 300 mA eingestellt (sicher ist sicher), die Spannung noch einmal überprüft und dann die Stromversorgung eingesteckt...



Ah, es tut sich was! Der Adressbus wird felißig beansprucht und auch auf dem Datenbus tut sich etwas. Außerdem blinkt die SYNC-LED ab und zu, was heißt, dass OpCodes abgerufen wird, also Befehle ausgeführt werden. Die Frage ist nur: welche?

Schauen wir doch mal die einzelnen Zustände der Reihe nach an:

FotoAdresseDatenbyteBedeutung
0x01800x00Routine bei 0x0180
0x01800x22...
0xFFFA0x00NMI-Interrupt Adresse bei 0xFFFA holen
0xBFFA0x00?
0xBFFB0x00?
0xBFF80x00?
0x00000x00Hard-Reset wg. illegalem OpCode?
0x00010x00?
0x01800x00Routine bei 0x0180
0x01800x02...
0x01800x00...
0x01800x36...
0xFFFE0x36IRQ-Interrupt Adresse bei 0xFFFE holen
0xFFFE0x00..
0x3FF80x00?
0x00000x00Hard-Reset wg. illegalem OpCode?


Bei diesem ersten Test waren die Datenbits weder mit Low noch High verbunden und hingen in der Luft (floating). Darum kann in diesen ein zufälliger Wert beim Lesen durch die CPU stehen. Ich erkläre mir das Verhalten der CPU so, dass dadurch des öfteren illegale OpCodes im Datenbyte stehen und ausgeführt werden. Hier bitte einmal auf die gelbe SYNC-LED unten links achten, immer wenn die aufleuchtet, wird ein OpCode geholt und danach ausgeführt. Wenn dann ein OpCode im Datenbyte steht, den es nicht gibt, wird wohl ein Hard-Reset durch die CPU ausgeführt und die Reset-Routine beginnt von Neuem.

Beim nächsten Durchlauf der Reset-Routine ist das Ergebnis dann anders, weil das Datenbyte anders (nämlich zufällig) ist. Höchstwahrscheinlich führt das wieder zu einem illegalen OpCode, zu einem Hard-Reset und zu einem neuen Reset-Durchgang.

Etwas mehr Ordnung in die Sache können wir bringen, in dem wir der CPU im ersten Schritt einfach einen Speicher vorgaukeln, der an allen Stellen den selben OpCode beinhaltet. Hier habe ich den OpCode 0xEA, in binär 0xb1110 1010 ausgewählt. Der steht für den Assembler-Befehl NOP (No Operation) und tut einfach nichts einen Taktzyklus lang.

Unser vorgegaukeltes Programm besteht also nur aus lauter "Tue Nichts"-Befehlen. Da kann doch eigentlich nciht viel kaputt gehen. Also setzen wir das Datenbyte mit ein paar Jumperkabeln hart auf 1110 1010 bzw. HHHL HLHL (von links nach rechts) und starten die CPU durch Reset neu.



Diesmal erhalten wir: Adresse Daten Bedeutung EA00 EA Reset-Routine 0100 EA ... 01FF EA ... FFFC EA Low Byte der Vektor-Adresse FFFC/D holen: EA FFFD EA High Byte der Vektor-Adresse FFFC/D holen: EA Sprung zur Vektor-Adresse EAEA (Programm-Counter auf EAEA setzen) EAEA EA Ausführung des OpCodes NOP (EA), Programm-Counter erhöhen EAEB EA ... EAEC EA ... EAED EA ... EAEE EA ... EAEF EA ... EAF0 EA ... ... ... FFFF Nach sieben Taktzyklen (siehe Taktsignal-LEDs) wird der Adressbus auf den Vektor für den Programmstart 0xFFFC und 0xFFFD gesetzt, um sich die Start-Adresse für das Programm zu holen. Nicht überraschend bekommt die CPU hier zweimal 0xEA und bastelt sich als Startadresse 0xEAEA zusammen. Zu dieser springt sie dann, um das dort hinterlegte Programm auszuführen. Das tut sie, indem sie den Programm-Counter (PC) auf 0xEAEA setzt.

Nun führt die CPU das Programm Befehl für Befehl aus. Es findet bei 0xEAEA den Befehl 0xEA (was ein Zufall!) und führt ihn aus, sprich: dreht eine Runde Däumchen. Danach geht es mit dem nächsten Befehl weiter. Also wieder: PC erhöhen (auf 0xEAEB), den Befehl dort auslesen (ist natürlich wieder 0xEA=NOP) und ausführen.

PC erhöhen, NOP, PC erhöhen, NOP, usw. usf. Was in der LED-Anzeige dazu führt, das es wie ein binärer Zähler aussieht. Es wird ja auch der Programm Counter ab 0xEAEA hochgezählt.

Okay, die CPU funktioniert schon einmal. Wenn auch leider nicht ganz so, wie erhofft. Bei dem Abschnitt
Datasheet, Abschnitt 3.8: Phase 2 In (PHI2), Phase 2 Out (PHI2O) and Phase 1 Out (PHI1O) Phase 2 In (PHI2) is the system clock input to the microprocessor internal clock. During the low power Standby Mode, PHI2 can be held in either high or low state to preserve the contents of internal registers since the microprocessor is a fully static design.
hatte ich mir eigentlich vorgestellt, dass auch der Adressbus erhalten bleibt, denn das ist ja das Result aus dem Programm Counter und das ist ja schließlich ein Register. Aber leider verliert mein W65C02S8P-10 aus China nach einer bis zwei Sekunden das Gedächtnis und der Adressbus flackert zuerst und löscht dann alle Bits. Was dann zu einem Zwangsreset führt, sprich: wenn der Takt unter ein Hertz sinkt, dann stürzt die CPU ab und führt einen Zwangs-Reset aus.

Nun bin ich mir nicht sicher, ob das daran liegt, dass ich einen alten "-10"-Chip habe, aktuell sind ja "-14"-Chips mit max. 14 MHz Takt und es bei den alten Chips noch nicht das "static design" gab. Oder ob es doch nur ein umgelabelter 6502 ist, den ich da aus China bekommen habe. Oder ob WDC in dem Absatz Werbesprech benutzt und das Design nicht so "static" ist, wie ich das auffasse.

Um herauszubekommen, ob ich einen WDC-Chip habe und keinen umgelabelten Rockwell oder sonstigen Hersteller, habe ich einmal den Opcode 0xCB (0b1100 1011) = WAI (Wait for Interrupt), den eigentlich nur der WDC beherrschen sollte, kodiert. Bei einem Original WDC sollte dann auch RDY (Pin 2) auf Low gezogen werden, was die Ready-LED einschalten sollte. Bleibt die LED aus, hab ich wohl einen Fake (oder auch dieses Feature war in der "-10"-Version noch nicht implementiert).



Wie man sieht, wird der OpCode 0xCB nicht aus WAI ausführt, denn dann müsste der Prozessor stoppen und auf ein Interrupt Signal auf Pin 4 (oder 6?) warten. Das tut er aber nicht, sondern arbeitet weiter. Auch geht die LED nicht an.

Adresse Daten Bedeutung CB00 CB Reset-Routine 0100 CB ... 01CF CB ... 01CE CB ... FFFC CB Low Byte der Vektor-Adresse FFFC/D holen: CB FFFD CB High Byte der Vektor-Adresse FFFC/D holen: CB Sprung zur Vektor-Adresse EAEA (Programm-Counter auf EAEA setzen) CBCB CB Ausführung des OpCodes NOP (EA), Programm-Counter erhöhen CBCD CB ... CBCE CB ... ... ... FFFF Der OpCode CB wird also als NOP behandelt. Damit ist immer noch nicht raus, ob ich einen Fake oder einfach nur ein altes Modell, das weniger kann, habe. Denn WDC behandelt (in der neueren Version und hat es wahrscheinlich auch in der alten) alle illegalen OpCodes als NOPs.

Der Rockwell R65C02 würde den OpCode 0xCB als illegale Instruktion ausführen wollen und wahrscheinlich abstürzen. Hier ein Auszug aus dessen Datenblatt:



Alte Datenblätter von einem "-10"-WDC-Modell habe ich nicht finden können. Entweder existieren sie nicht (weil Fake), oder wurden gelöscht, weil das Produkt abgekündigt wurde. Ich glaube, so richtig Gewissheit bekomme ich nur, wenn ich einen neuen "-14" über den offizielle Distributor Mouser bestelle, was ich dann wohl mal machen werden. Denn jetzt bin ich echt neugierig geworden.

Abschließend noch ein Video, in dem ich noch einmal den Anschluss des Prozessors und des Adress- und Datenbuses erkläre:



Mit diesem "-10"-Modell kann ich ja trotzdem erst einmal weiter machen. Das Wesentliche bis auf den Einzelschritt funktioniert ja. Im nächsten Teil will ich das ROM anbinden und ein eigenes dort abgelegtes Programm laufen lassen.

Oder ich bastle eine Arduino-basierte, nicht so flüchtige Ausgabe. Jedesmal Video machen und "durchblättern" für die Analyse nervt doch ein wenig.

Inzwischen ist die Mouser aus den USA bestellte echte WDC W65C02 CPU eingetroffen, die so funktioniert, wie sie soll.