8-Bit-Breadboard-Computer auf Basis einer 6502-CPU - Von Maschinensprache zu Assembler
Bisherige Artikel dieser Serie - hier könnt ihr nochmal alle Grundlagen nachlesen, falls ihr jetzt erst einsteigt:- Digitale Logik und Logikgatter einfach erklärt
- Verwendung des 555-Timer als Taktgeber / Clock
- Das Clock-Modul: Taktgeber für unseren Breadboard-Computer
- Speichertypen und Zugriff auf Speicher
- Erste Schritte mit der CPU
- Eine echte WDC W65C02-CPU
- Das Speicher-Modul: Anbindung von RAM und ROM
- Erstes Programm in Maschinensprache: RAM-Test
- Das Sniffer-Modul: Ein Arduino/STM32 zeigt an, was auf dem Bus los ist.
- Erstes Ausgabegerät: Adressierung und Ausgabe auf 8 LEDs
- OpCode und Adressierungsart notieren und den richtigen Bytecode heraussuchen
- Zugehörige Parameter dahinter schreiben als Bytes
- Bei PC-relativen Sprüngen (z. B. BNE) die Bytes zählen, die es vor bzw. zurück gehen soll und ggf. auch noch das Zweierkomplement dafür ausrechnen und als Paramter notieren
- Für absolute Sprünge (JMP) die Adresse wissen, an die gesprungen werden muss. Dazu zu jedem Befehl die Adresse dazu schreiben und bitte nicht verzählen, auch wenn es hexadezimal ist
- Die Bytes für Opcodes und Parameter extrahieren und aufs EEPROM flashen
- Daumen drücken, dass man sich nirgends verzählt hat
Adr. Assembler Bytecode (hex) Bedeutung (hex)
8000 LDA# 01 A9 01 lade 01 (rechte LED) in den Akku
8002 STA 7000 8D 00 70 speichere den Akku-Inhalt an RAM-Adresse 7000,
die rechte LED sollte nun leuchten
8005 LDA# 07 A9 07 wir müssen 7 mal nach links shiften, bis die LED ganz links
angekommen ist
8007 ROL 7000 <-. 2E 00 70 alle Bits in 7000 werden eine Stelle nach links verschoben,
| LED wandert nach links
800A DECA | 3A erniedrigt den Akku um eins
800B BNEr --' D0 FA nach 7 Durchläufen ist der Akku auf Null und BNE (Branch If Not
Equal Zero) springt nicht mehr zurück. BNE ist ein 2-Byte-Befehl,
der 1-Byte Parameter gibt den Offset zum aktuellen PC an.
Sprünge zurück haben einen negativen Offset, der als Zweier-
komplement angegeben werden muss. 0xFF ist -1, 0xFA ist -6
800D LDA# 07 A9 07 wir müssen die LED-Position 7 mal nach rechts verschieben, bis
die LED wieder ganz rechts angekommen ist
800F ROR 7000 <-. 6E 00 70 alle Bits in 7000 werden eine Stelle nach rechts verschoben,
| LED wandert nach rechts
8012 DECA | 3A erniedrigt den Akku um eins
8013 BNEr --' D0 FA nach 7 Durchläufen ist der Akku auf Null und BNE springt nicht
mehr zurück
8015 JMP 8005 4C 05 80 und wieder zum Scrollanfang zurück, dann geht es wieder nach
links usw. usf.
Bzw. genauer gesagt sind das die Notizen die zu dem Maschinenspracheprogramm geführt haben, denn das Programm ist eigentlich nur
A9 01 8D 00 70 A9 07 2E 00 70 3A D0 FA A9 07 6E 00 70 3A D0 FA 4C 05 80
Aber das geht alles auch ein bisschen einfacher.Der nächste Schritt in der Programmiersprachen-Evolution: Assembler
Und zwar mit Assembler. Dann sieht unser Programm nämlich so aus:; K.I.T.T. Lauflicht auf die 8-LED-Ausgabe oben rechts auf dem Breadboard
org $8000 ; Programmstartadresse (von der CPU aus gesehen)
; --- Konstanten: IO-Adressen ---
LED: equ $7000 ; 8-LED-Ausgabe, hört aber auf alles von $7000 bis $7fff
start:
lda #$01 ; rechts anfangen
sta LED
scroll:
lda #$07 ; 7x nach links
left:
rol LED
dec A
bne left
lda #$07 ; 7x nach rechts
right:
ror LED
dec A
bne right
jmp scroll; und wieder nach links
Die Vorteile liegen auf der Hand:
- kein Heraussuchen von Bytecodes für OpCodes mehr, einfach den Befehl hinschreiben
- statt sich zu merken, dass 0x7000 für die LED-Ausgabe steht, das einmal am Anfang als LED definieren und dann nur noch LED statt "00 70" schreiben, wenn die LED gemeint ist
- Sprungmarken machen das Leben so viel einfacher. Diese einfach als Ziel bei BNE, JMP und Co. angeben, ob der Sprung relativ oder absolut ist, weiß der Assembler. Der zählt die Adresse mit und setzt die richtige Adresse ein bzw. errechnet den Zweierkomplement bei relativen Sprüngen. Kein Byte-Abzählen mehr.
- und weitere Vorteile, die wir noch in Zukunft entdecken werden
Okay, einen Nachteil hat es: wenn wir debuggen und uns den Maschinensprachecode im Sniffer anschauen, z. B. bei diesem Maschinensprache-Programmausschnitt
8000 A9 r LDA#
8001 01 r
8002 8D r STA
8003 00 r
8004 70 r
7000 00 W
7000 01 W
8005 A9 r LDA#
8006 07 r
8007 2E r ROL
8008 00 r
8009 70 r
7000 01 r
7000 00 W
7000 02 W
800A 3A r DECA
800B D0 r
800B D0 r BNEr
800C FA r
dann würde uns die Vorarbeit schon helfen, denn dann wüssten wir z. B. gleich, wohin der BNE bei 800B mit FA verzweigt.Aber: warum diese Arbeit (Übersetzung in Maschinensprache) immer machen, wenn es doch ausreicht, sie sich zu machen, wenn es unbedingt nötig ist. Vielleicht unser Programm ja auch gleich tadellos und wir müssen gar nicht debuggen, oder nicht so tief, oder nicht so viel.
Und den nervigen Teil der Arbeit, nämlich das Heraussuchen der Befehle für die Byte-OpCodes nimmt uns ja glücklicherweise der Deassemblierungsteil unseres Sniffers ab.
Assembler gibt es mehrere auf dem Markt, die meisten frei und kostenlos. Mein erster Blick ging natürlich Richtung WDC, von dem ja auch unsere CPU W65C02 stammt.
Der WDC-Assembler und Linker
Der WDC 6502er hat ja ein paar zusätzliche OpCodes, die im Original MOS 6502 noch nicht vorhanden sind. Und wenn es vom Hersteller des Chips einen Assembler gibt, dann kann man davon ausgehen, dass dieser all diese schönen, neuen Befehle auch unterstützt.Und für wahr: WDC bietet einen Assembler an, ja sogar ein ganzes Paket an Tools, kostenlos und aus dem Internet herunterladbar. Man muss sich zwar registrieren bzw. seine e-mail-Adresse angeben, aber ansonsten geht der Download ganz problemlos.
Im Tookilt findet man außer Entwicklungsumgebungen für die WDC-Boards auch einen Simulator und für uns interessant: einen Assembler und einen Linker. Dazu gibt es auch eine fast 100-seitige Dokumentation als PDF. NAtürlich auf englisch, aber diesmal Angabe der e-mail downloadbar.
Nachdem man das Toolkit in ein Verzeichnis seiner Wahl (bei mir d:\6502\wdc, besser aber c:\wdc, weil die PATH-Variable in Windows bei der Installation hart darauf gesetzt wird und dann nicht per Hand angepasst werden muss) entpackt hat, sollte man den Ordner ...\Tools\bin ansteuern. Dort finden sich die für uns interessanten Exes:
Verzeichnis von d:\6502\wdc\Tools\bin
24.09.2016 01:33 1.816.855 easysxb.exe
29.06.2008 23:37 1.905.365 srec_cat.exe
30.06.2017 18:29 4.037.632 tide.exe
07.02.2006 02:25 116.224 WDC02AS.exe
07.02.2006 01:25 104.960 WDC02CC.exe
07.02.2006 02:23 75.776 WDC02OP.exe
07.02.2006 02:24 116.224 WDC816AS.exe
19.01.2006 23:38 178.688 WDC816CC.exe
06.02.2006 22:38 98.304 WDC816OP.exe
15.09.2010 21:05 243.712 wdcdb.exe
22.12.2005 23:46 65.536 WDCLIB.exe
25.04.2006 00:40 99.328 WDCLN.exe
19.01.2006 21:36 66.048 WDCOBJ.exe
19.01.2006 21:36 62.464 WDCSYM.exe
WDC02AS.exe ist der Assembler für die W65C02 CPU. Dieser assembliert unseren Assembler-Sourcecode (.asm) und macht daraus .obj-Dateien. WDCLN.exe macht aus dem .obj-File dann ein .bin-File, welches unser Maschinensprachecode enthält.Versucht es mal: Gebt in einem Texteditor eurer Wahl das Assembler-Programm von oben ein und speichert es als 0-LED-Lauflicht.asm. Baut viellicht noch absichtlich einen Syntax-Fehler ein. Öffnet dann eine Eingabeaufforderung und gebt ein
WDC02AS.exe 0-LED-Lauflicht.asm
Jetzt sollte der Assembler melden, an welcher Stelle im Code er den Syntax-Fehler gefunden hat. Korrigiert ihn und assembliert neu. Erscheint dann nur noch
WDC 65C02 Assembler Version 3.49.1 Feb 6 2006 17:25:36
Copyright (C) 1992-2006 by The Western Design Center, Inc.
habt ihr es geschafft und der Assembler hat ein .obj-File erstellt, das ungefähr so aussieht:
Ich habe den für uns interessanten Teil, nämlich das Maschinenspracheprogramm, einmal blau markiert. Der Rest interessant uns aber herzlich wenig. Um nicht immer zu schauen, an welcher Stelle genau unser Maschinencode steht, benutzen wir den Linker:
WDCLN 0-LED-Lauflicht.obj -HB
WDC 65C816 Linker Version 3.49.1 Apr 24 2006 15:40:38
Copyright (C) 1992-2006 The Western Design Center, Inc.
Section: ORG: ROM ORG: SIZE:
CODE 008000 008000 18H ( 24)
Total 18H ( 24)
Der macht uns ein Binär-File. Allerdings packt er uns auch 32768 (0x8000) unnötige Null-Bytes an den Anfang, weil wir ja angegeben haben, dass ORG bei $8000 liegt:
Aber das macht ja nichts. Wir können ja ans Ende springen. Und dann wie gehabt die Bytes kopieren, in unserem Eprommer unser EEPROM lesen und im Hexeditor den Maschinencode an die richtige Stelle kopieren bzw. unser altes Programm überschreiben. War das alte PRogramm länger, können wir es ja wieder mit 0xEA auffüllen. Der Startvektor von 0x8000 bleibt ja gleich, so dass wir hier nicht nochmal Hand anlegen müssen.
Fertig. So viel menschenfreundlicher und einfacher mit einem Assembler.
Mal eben eine Entwicklungsumgebung programmiert
Ich habe mir die Sache sogar noch einfacher gemacht und ein kleines Programm geschrieben, dass mich beim assemblieren unterstützt, sozusagen eine kleine Entwicklungsumgebung:
Hier kann ich eine Programmbank zwischen 0 und 15 wählen. Ich habe dank des 512KB großen SST39SF040 16 mal 32 KB zur Verfügung, die ich ja per DIP-Schalter auf meinem Breadboard auswählen kann. Nachdem ich die eine Bank ausgewählt habe, kann ich den dort hinterlegten Assembler-Sourcecode editieren.
Es ist fertig, kann ich ihn speichern, und danach mit build assemblieren. Eventuelle Fehler werden dann rechts angezeigt. Sind alle Fehler besetigt, wird mit link der Linker aufgerufen, dessen Ausgabe darunter erscheint. Einmal evtl. Fehlermeldungen und zum anderen der Binärcode, unser Maschinenspracheprogramm. Das wird auch gleich als bank0.bin auf der Platte gespeichert. Beim upload werden die 16 Programmbänke dann zusammenkopiert in ein 512 KB großes Flash-File und die Eprommer-Software ferngesteuert, so dass das File autoamtisch geladen wird. Dann muss ich nur noch überprüfen, ob alles okay ist und kann das EEPROM flashen.
Das wird mir in Zukunft doch einiges an Zeit sparen. Wer mag schon ständig zwischen Editor, Command-Line und Eprommer-Software hin und herspringen? Das lenkt doch nur von der eigentlichen Entwicklung ab.
Das das Programm eine Eigenentwicklung ist, kann ich es natürlich beliebig erweitern. Wer weiß, vielleicht ist mir irgendwann langweilig und ich baue ein Syntax-Highlighting ein? Aber vorerst soll es mir so reichen.
Video
Ich habe mich an den PC gesetzt und nocheinmal alles in einem Video vorgeführt, was hier besprochen wurde. Viel Spaß beim Schauen: