8-Bit-Breadboard-Computer auf Basis einer 6502-CPU - Speicheranbindung von RAM und ROM
Unser Breadboard Computer bekommt Zuwachs. Neben dem CPU-Modul mit einem W65C02 Prozessor und dem Clock-Modul, das den Takt angibt, kommt nun auch ein weiteres Modul mit Speicher.Bisher haben wir die Befehle bzw. Datenbytes, die der 6502 ausführen soll, ja nur mit Jumper-Kabeln hard verdrahtet, was uns allerdings immer (und ausschließlich) nur einen einzigen Befehl ausführbar machen lässt. Hier hatten wir NOP (0xEA) und WAI (0xCB) verwendet, um die CPU zu testen; auch ob sie echt ist.
Damit die CPU etwas sinnvolles macht, muss sie ein Programm ausführen, in denen viele unterschiedliche Befehle stehen und nacheinander ausgeführt werden. Jetzt wäre es doch sehr mühsam, jeden einzelnen Befehl per Jumper-Kabel zu stecken und dann den nächsten Takt anzustoßen.
Am einfachsten ist es, wir benutzen einen ROM-Speicher für unser Programm. Den können wir bequem am PC mit einem Eprommer programmieren und dann in unseren Breadboard-Computer stecken, damit dort das Programm ausgeführt wird. Ich werde der Bequemlichkeit wegen an dieser Stelle einen ZIF-Sockel benutzen, denn der EEPROM bzw. Flash-Chip wird sicher öfter mal aus- und eingebaut werden müssen.
Und wenn wir schon dabei sind, bauen wir auch gleich das RAM ein, das ist der Speicher, der auch geschrieben werden kann und in dem wir unsere Varaiblen und mehr ablegen werden.
Über die Speicherarten hatte ich ja bereits in meinem Artikel Speichertypen und Zugriff auf Speicher geschrieben. Und auch, wie wir unseren 6502 daran anbinden wollen. Bitte also diesen Artikel lesen, ich baue darauf auf, und werde nur noch das Wichtigste wiederholen.
Aufbau auf dem Breadboard
Bei dem CPU-Modul wurde es eng auf einem 830er-Breadboard und ich habe gelernt, mir vorher Gedanken zu machen, was auf dem Steckbrett wo hin soll, dann muss man nicht alles wieder und wieder umstöpseln.- das RAM hat mehr Platz oben und unten, weil es keinen ZIF-Sockel benutzt. Hier können wir also jeweils zwei Leitungen einstecken, eine kommende und eine zum ROM gehende. Also kommt das RAM möglichst nahe an die Busse des CPU Moduls. Weil das neue Modul rechts vom CPU-Modul positioniert wird, auf dem Breadboard ganz links.
- Das ROM kommt also rechts vom RAM, von dem es seinen Adressbus bekommt. Denn durch den ZIF-Sockel hat das ROM nur einen Platz ober- und unterhalb für die Leitungen frei. Hier endet also der Bus.
- Allerdings kommt das ROM nicht gleich neben das RAM, denn ich brauche ja noch DIP-Schalter, um die ROM-Bank auszuwählen und die soll gut bedienbar sein und nicht vom Hebel des ZIF-Sockels blockiert werden.
- Und dann brauche ich noch ein Logikbaustein: einen Inverter (7404). Ich verwende zwei Gatter eines NAND-Chips 7400. Der lässt sich auch als Inverter verwenden.
Fertig aufgebaut sieht das Ganze dann so aus:

Unten rechts das neue Modul. Für den Adressbus habe ich wieder orange genommen und für den Datenbus gelb. Das hat die praktische Bewandnis, dass ich weiß, an welcher Farbe ich prüfen muss, wenn mal der Adress- oder Datenbus fehlerhaft funktioniert. Außerdem habe ich die Farbe rot für die oberen Adressbits vom DIP-Schalter verwendet. Und blau für die Schaltleitungen für Adressbit 15 und Read/Write.
Mit dem richtigen Partner (RAM bzw. ROM) kommunizieren
Was auch gleich die im Artikel Speichertypen und Zugriff auf Speicher aufgeworfene Frage beantwortet, wie wir den WE-Pin versorgen. Ganz einfach: die CPU stellt ein R/W Signal zur Verfügung.Immer wenn die CPU etwas lesen will - was abhängig vom aktuellen Befehl / OpCode ist - stellt sie R/W auf High (unsere LED bei Pin 34 des W65C02 geht an) und wenn Sie etwas schreiben will, setzt sie R/W auf Low. Wie praktisch. So müssen wir den R/W des W65C02 nur mit dem WE-Pin des SRAM verbinden und die Sache funktioniert automatisch.
Naja, bis auf eine Kleinigkeit. Wenn wir das SRAM lesen wollen, müssen wir OE (Output Enable) auf Low setzen und zwar nur, wenn wir lesen wollen. Beim Schreiben muss OE High sein - also das genaue Gegenteil von WE. Darum gehen wir mit der Leitung (blau) in die beiden Eingänge des 3. NAND-Gatters (weil das oben liegt und kürzere Wege erlaubt), was dann wir ein NOT fungiert und unser Signal invertiert. Mit dem Ausgang gehts dann an OE. Damit liegt automatisch an OE das Gegenteil von WE an.
Zur besseren Nachvollziehbarkeit hier nochmal das Pinout der beiden Chips:
Die meiste Arbeit macht natürlich wieder das Strippenziehen für Adress- und Datenbus. Auch wenn's langweilig ist: hier am besten sehr sorgfältig sein, denn eine Fehlersuche (per Durchgangsprüfer am Multimeter) und Korrektur würde keinen Spaß machen bei den vielen übereinanderliegenden Kabeln. Fehlt nur noch der Pin CE (Chip Enable) am SRAM, der low sein muss, damit das SRAM sich überhaupt angesprochen fühlt. Ist CE auf High, dann tut das SRAM rein gar nichts. Doch wann soll das SRAM angesprochen werden? (Auch das hatte ich in meinem Artikel Speichertypen und Zugriff auf Speicher schon vorweggenommen. Bitte dort noch einmal nachlesen, wenn folgende Sätze Fragen aufwerfen.) Speicheraufteilung / OrganisationDas SRAM soll immer angesprochen werden, wenn die untere Hälfte Teil des 16-bit-breiten Adressbuses angesprochen wird. Denn die obere haben wir für das ROM reserviert, weil dort per 6502-Definition die Sprungvektoren stehen ( hier nachzulesen). Unser Speicher-Aufteilung sieht ja momentan wie folgt aus:Adr.(hex) Beschreibung
0000 RAM (Variablen, Stack, etc.)
... RAM
7FFF RAM
8000 ROM (eingeblendete Programmbank)
... ROM Programm
FFFA Sprungvektor für BRK/IRQ Low-Byte
FFFB Sprungvektor für BRK/IRQ High-Byte
FFFC Sprungvektor für RES Low-Byte = Programmstartadresse
FFFD Sprungvektor für RES High-Byte = Programmstartadresse
FFFE Sprungvektor für NMI Low-Byte
FFFF Sprungvektor für NMI High-Byte
Oder in anderen Worten: das SRAM soll immer angesprochen werden, wenn das Adressbit A15 Null (also Low) ist. Das ist ganz einfach zu bewerkstelligen. Wir verdrahten einfach die Leitung A15 von der CPU mit CE des RAM. Dann reagiert das RAM nur dann, wenn eine Adresse zwischen 0x0000 und 0x7ff angesprochen wird.Das ROM versorgen wir mit einem invertierten A15. Das bekommen wir wieder, indem wir über zwei Eingänge in den 7400er NAND-Chip gehen und den Ausgang verbinden (blaue Kabel auf Foto). Ergebnis: Das ROM reagiert immer, wenn eine Adresse wischen 0x8000 und 0xfff angesprochen wird. Hierbei ist allerdings immer im Hinterkopf zu behalten. Für das Programm im ROM ist ein 0x8000 der CPU ein 0x0000, weil das ROM ja bei Null beginnt, der abgebildete Speicherbereich für die CPU aber bei 0x8000. Was passiert beim Reset? Von wo wird gelesen?Kleines Beispiel: Wir wollen ein Programm auf die ROM-Bank 0 flashen (beginnt im Falsh bei 0x0000) und das Programm soll auch bei 0 beginnen. Dann müssen wir auf das ROM flashen:Flash-Adr. Inhalt Adr. f. CPU Bedeutung
0000 EA 8000 <-. Programm, erster Befehl
0001 EA 8001 |
0002 EA 8002 |
... ... ... |
7FFC 00 FFFC \_| CPU holt sich die Low und High-Byte der Programmstartadresse
7FFD 80 FFFD / also 8000 und springt dann dorthin zur Ausführung
Beim Reset schaut die CPU bei 0xfffc/d nach der Programmstartadresse. Durch das fehlende 15. Bit wird daraus 0x7ffc im Flashspeicher. Dort findet die CPU 0x8000 als Programmstartadresse vor. Von dort holt sie sich den ersten Befehl. Die 0x8000 der CPU wird aber nun wieder zu einer 0x0000 des ROM, weil das 15. Bit fehlt.Also muss das Programm im Flashspeicher ab 0x0000 stehen. Für die CPU ist das 0x8000. Die Sprungadressen müssen aber für die CPU angepasst werden, weil diese ja auch von der CPU ausgeführt werden. Hier noch eine kleine Grafik, wie Adressbit 15 zu verdrahten ist, damit sich der richtige Partner (RAM bzw. ROM) angesprochen fühlt. Im Foto oben sind diese Leitungen blau (wenn man sie denn sieht). ![]() Programmspeicher sattBleibt noch der DIP-Schalter. Der ist mit den Adressen A18, A17, A16 und A15 des Flash-Speichers verdrahtet, denn der von mir ausgewählte SST39F040 Flash-Chip hat 512 KB Speicher. Wir brauchen aber nur 32 KB. Ich habe sozusagen 16-mal zu viel Speicher. Aber der ist nicht verschwendet, denn ich kann mit den vier DIP-Schaltern die oberen vier Bits (A15-A18), die nicht zur Adressierung gebraucht werden manuell schalten.Setze ich alle vier DIP-Schalter auf Null, dann greift das Flash ganz normal auf die Adresse 0x0'0000 zu und gebraucht nur die ersten 32 KB der vorhandenen 512 KB. Setze ich jetzt aber z. B. den (rechten) DIP-Schalter für A15 auf 1, dann addiert das natürlich 215 = 32'768 (0x8000) auf die Adresse und es wird immer, wenn nach 0 gefragt wird, Adresse 0x0'8000 geholt und für Adresse 1 0x0'8001. Das heißt im Umkehrschluss: ich kann 16 Programme auf das Flash an die entsprechenden Speicheradressen flashen und mit den DIP-Schaltern umschalten. Für jedes Programm stehen dann 32 KB ROM (und 32 KB RAM) zur Verfügung: A18 A17 A16 A15 Adresse im F040
0 0 0 0 0x0'0000 ... 0x0'7FFF Programm 1
0 0 0 1 0x0'8000 ... 0x0'FFFF Programm 2
0 0 1 0 0x1'0000 ... 0x1'7FFF Programm 3
0 0 1 1 0x1'8000 ... 0x1'FFFF Programm 4
0 1 0 0 0x2'0000 ... 0x2'7FFF Programm 5
0 1 0 1 0x2'8000 ... 0x2'FFFF Programm 6
0 1 1 0 0x3'0000 ... 0x3'7FFF Programm 7
0 1 1 1 0x3'8000 ... 0x3'FFFF Programm 8
1 0 0 0 0x4'0000 ... 0x4'7FFF Programm 9
1 0 0 1 0x4'8000 ... 0x4'FFFF Programm 10
1 0 1 0 0x5'0000 ... 0x5'7FFF Programm 11
1 0 1 1 0x5'8000 ... 0x5'FFFF Programm 12
1 1 0 0 0x6'0000 ... 0x6'7FFF Programm 13
1 1 0 1 0x6'8000 ... 0x6'FFFF Programm 14
1 1 1 0 0x7'0000 ... 0x7'7FFF Programm 15
1 1 1 1 0x7'8000 ... 0x7'FFFF Programm 16
Test des neuen AufbausFür einen ersten Test habe ich einfach mal das komplette Flash mit 0xEA gefüllt. Das heißt: egal, an welcher Adresse die CPU nach einem Datenbyte fragt: es wird immer 0xEA (NOP) zurückbekommen.Das sollte im Prinzip die gleichen Ergebnisse zeigen, wie bei unserem Test, wo wir 0xEA fest mit Jumper-Kabeln verdrahtet hatten. Vor dem Rest zeigt die CPU auf dem Adressbus 0xffff an, was auf das ROM (weil über 0x8000) zugreift und dort ein Datenbyte holt, natürlich in diesem Fall mit dem Inhalt 0xEA, was auch korrekt am Datenbus angezeigt wird. Puh, ein Glück, auf den ersten Blick scheinbar richtig verkabelt. ![]() Per manuellem Einzeltakt bin ich den Programmablauf Schritt für Schritt durchgegangen und habe mir die Werte von Adress- und Datenbus notiert: Takt Adresse Datum Bedeutung
1 FFFF EA interne Reset-Routine
2 0000 C0 die Werte sind bei jedem
3 01FD 4A Reset-Durchgang andere
4 01FC 59 ...
5 01FB DD ...
6 FFFC EA holen der Programmstartadresse
7 FFFD EA aus dem vorgegebenen Vektor: EAEA
8 EAEA EA Programmstart: Sprung nach EAEA
9 EAEB EA Ausführung von EA (NOP) und Erhöhung des Program-Counters (PC)
10 EAEB EA Holen des nächsten Befehles: EA
11 EAEC EA Ausführung und Erhöhung des PC
12 EAEC EA Endlose Wiederholung, weil der Flash
13 EAED EA immer wieder EA liefert, weil ja nur
14 EAED EA dieser Wert geflasht wurde
...
Man erkennt, dass der NOP-Befehl ein Befehl ist, der zwei Taktzyklen lang ist. Dieser wird immer wieder wiederholt, wird doch immer wieder EA geliefert.Dass das NOP-Programm so läuft wie es sein soll, ist leider noch keine Garantie dafür, dass wir alles richtig verdrahtet haben. Denn schließlich gibt es keine Unterschiede im Programm und es wird immer 0xEA geliefert. Erst wenn wir weitere und einen Haufen mehr Befehle in unserem Programm benutzen, wird sich eventuell ein Fehler auftun. Also immer im Hinterkopf behalten: wenn in Zukunft ein Programm nicht wie erwartet läuft, kann das durchaus daran liegen, dass wir eine Adressleitung falsch oder nicht angeschlossen haben. Wie immer am Schluss noch ein Erklär- und Demonstrationsvideo zu dem Speichermodul: ![]() |