mirror of
https://github.com/fachat/xa65.git
synced 2024-11-19 20:31:03 +00:00
395 lines
14 KiB
Plaintext
395 lines
14 KiB
Plaintext
----------------------------------------------------------------------------
|
||
|
||
XA 2.1.0
|
||
|
||
65(c)02 Cross-Assembler
|
||
|
||
von Andre Fachat
|
||
|
||
----------------------------------------------------------------------------
|
||
|
||
* Block-Struktur (Versteckte Label)
|
||
|
||
* Sehr schnell durch hashing
|
||
|
||
* C-ähnlicher Preprozessor
|
||
|
||
----------------------------------------------------------------------------
|
||
|
||
1. Was ist das überhaupt und Geschichte
|
||
|
||
2. Aufruf und Features
|
||
|
||
3. 6502 Assembler
|
||
|
||
4. Pseudo-Opcodes, Block-Struktur und Gültigkeitsbereich von Labels
|
||
|
||
5. Preprozessor
|
||
|
||
----------------------------------------------------------------------------
|
||
|
||
|
||
1. Was ist das überhaupt und Geschichte
|
||
----------------------------------------
|
||
|
||
Mit dem Cross-Assembler können auf einem Rechner Programme für einen
|
||
anderen Rechner(typ) erstellt werden. Die Programmdateien müssen dann
|
||
nur noch auf das Zielsystem übertragen werden. In diesem Fall handelt
|
||
es sich um einen 6502-Cross-Assembler. Der 6502 ist ein 8-Bit-Prozessor,
|
||
der ursprünglich von MOS Technologies entwickelt wurde. Er tut seine
|
||
Dienste u.a. im Apple II, Commodore PET, VC20 und C64 (in abgewandelter
|
||
Form) und vielen anderen.
|
||
Inzwischen gibt es ihn in vielen Varianten mit verschiedenen Zusätzen
|
||
und in verschiedenen Gehäusen. Gemeinsam ist allen der Befehlssatz,
|
||
der für den Assembler die Grundlage bildet. Die CMOS-Versionen bieten
|
||
allerdings Erweiterungen des Befehlssatzes.
|
||
Die Idee zu einem Cross-Assembler entstand, als ich mir einen 6502-Computer
|
||
selbst baute und der (ebenfalls selbstgeschriebene) Assembler auf dem C64
|
||
zu langsam wurde. Nachdem auch noch ein Atari ST bei mir rumstand, war
|
||
die Idee schnell in die Tat umgesetzt.
|
||
|
||
|
||
2. Aufruf und Features
|
||
-----------------------
|
||
|
||
Der Assembler besteht nur aus dem Programm "xa".
|
||
Der Assembler verarbeitet eine oder mehrere Quelldateien zu einer
|
||
Objektdatei, die direkt verwendet werden kann. Das Linken entfällt, da der
|
||
Aufwand zu groß und die Geschwindigkeit hoch genug ist, um die
|
||
'Libraries' im Quelltext einzubinden. Ca. 350kByte Quelltext werden in
|
||
1 Minute und 50 Sekunden zu einer 32kByte Objektdatei für ein EPROM
|
||
assembliert (naja, der 8MHz Atari war nicht schnell. Aber dafür ist der
|
||
Wert ziemlich gut. Mein 486DX4/100 braucht vielleicht 2 Sekunden)!
|
||
Als Ausgabedateien werden eine Objektdatei, eine Fehlerdatei und eine
|
||
Labeldatei geschrieben.
|
||
|
||
Der Aufruf lautet:
|
||
XA Quell1 [Quell2 ...] [-oObject] [-eFehler] [-lLabel] [-C] [-v] [-x]
|
||
|
||
Die Angabe der Objekt-, Fehler- und Labeldatei ist optional, ohne Angabe
|
||
wird die Endung der ersten Quelldatei auf 'obj', 'err' und 'lab' verändert,
|
||
wenn "-x" angegeben ist. Ansonsten wird "a.o65" als Ausgabedatei verwendet
|
||
und keine Fehler- und Labeldatei geschrieben.
|
||
Die Option -C erzeugt Fehlermeldungen bei CMOS-Befehlen.
|
||
Im Environment werden die Variablen XAOUTPUT und XAINPUT unterstützt.
|
||
Falls die Quell- und Include-Dateien nicht gefunden werden, werden die in
|
||
XAINPUT aufgeführten Pfade der Reihe nach durchgetestet. Falls XAOUTPUT
|
||
existiert, wird dieser Pfad als Pfad für .err, .lab und .obj-Dateien
|
||
benutzt. Die Komponenten des Pfades sind mit ',' getrennt.
|
||
|
||
Die Labeldatei enthält hinterher eine Liste aller Labels mit Block-Nummer
|
||
und Wert in dezimal in der Form: 'Label, 1,-1234' in lesbarem ASCII.
|
||
Die Fehlerdatei enthält die Version des Assemblers, Datum und Uhrzeit des
|
||
Assemblerlaufs, die Liste der Fehler, die Ausdrücke, die mit #echo
|
||
und #print im Preprozessor erzeugt werden und eine Statistik über die
|
||
benutzten Resourcen (Speicherplatz etc.).
|
||
Die Objektdatei wird nur durch die Quelldatei bestimmt, es wird kein Code
|
||
vorgesetzt oder angehängt.
|
||
Die Quelldatei muß im ASCII-Format vorliegen.
|
||
|
||
|
||
3. 6502 Assembler
|
||
------------------
|
||
|
||
Da dies kein 6502-Assemblerkurs werden soll, nur eine ganz kurze
|
||
Beschreibung. Unterstützt wird der Code für alle Standard-6502 sowie
|
||
der Code für die Rockwell 65C02-CPU. Der Prozessor hat drei Register,
|
||
über die die meisten Operationen laufen. Transferoperationen benötigen
|
||
deshalb immer einen Load- und einen Store-Befehl.
|
||
Zusätzlich gibt es das Statusregister und den Stackpointer sowie,
|
||
natürlich, den Programmzähler. Der Stack liegt immer im Bereich $100 und
|
||
$1ff (Hexadezimal mit vorangestelltem '$'), weshalb der Stackpointer ein
|
||
8-Bit-Register ist. Eine besondere Behandlung durch kürzere Befehle
|
||
erfährt die Zeropage ($0-$ff), bei deren Adresse das Hi-Byte Null ist.
|
||
Das Statusregister besitzt folgende Flags:
|
||
|
||
N = Negativ
|
||
O = Overflow
|
||
B = Break
|
||
D = Dezimal
|
||
I = Interrupt
|
||
Z = Zeroflag
|
||
C = Carry
|
||
|
||
Befehle:
|
||
|
||
LDA lade Akkumulator
|
||
LDX lade X-Register
|
||
LDY lade Y-Register
|
||
|
||
STA speichere Akkumulator
|
||
STX speichere X-Register
|
||
STY speichere Y-Register
|
||
STZ speichere NULL (*)
|
||
|
||
TAX Kopiere Akku nach X
|
||
TAY Kopiere Akku nach Y
|
||
TXA Kopiere X nach Akku
|
||
TYA Kopiere Y nach Akku
|
||
TSX Kopiere Stackpointer nach X
|
||
TXS Kopiere X nach Stackpointer
|
||
|
||
ADC Addiere zu Akku mit Übertrag (Carry) (D)
|
||
SBC Subtrahiere von Akku mit Carry (D)
|
||
AND Logisches Und mit Akku
|
||
ORA Logisches Oder mit Akku
|
||
EOR Exklusiv-Oder mit Akku
|
||
BIT Bit-Test: Z=A&M, N=M7, O=M6
|
||
ROL Rotiere Links Akku oder Speicher A=A*2+C, C=A7
|
||
ROR Rotiere Rechts A=A/2+C*127, C=A0
|
||
ASL Arithmetisches Linksschieben A=A*2
|
||
LSR Logisches Rechtsschieben A=A/2
|
||
INX Erhöhe X-Register um eins
|
||
INY Y
|
||
INC Erhöhe Akku oder Speicher um eins
|
||
DEX Erniedrige X-Register um eins
|
||
DEY Y
|
||
DEC Erniedrige Akku oder Speicher um eins
|
||
|
||
CMP Vergleiche mit Akku (Substraktion ohne Akku zu ver„ndern)
|
||
CPX Vergleiche mit X-Register
|
||
CPY Vergleiche mit Y-Register
|
||
|
||
BNE Verzweige falls nicht Null
|
||
BEQ Null
|
||
BMI Negativ
|
||
BPL Positiv
|
||
BVC Overflow Clear
|
||
BVS Overflow Set
|
||
BCS Carry Set
|
||
BCC Carry Clear
|
||
BRA Verzweige immer (*)
|
||
|
||
JMP Springe an Adresse
|
||
JSR Springe in Unterroutine, R<>cksprungadresse auf dem Stack
|
||
RTS Return from Subroutine
|
||
|
||
CLC Carry-Flag löschen
|
||
SEC setzen
|
||
CLD Dezimal-Flag löschen
|
||
SED setzen
|
||
CLI Interrupt freigeben
|
||
SEI sperren
|
||
CLV Overflow-Flag löschen
|
||
|
||
PHA Akku auf Stack legen
|
||
PHX XR (*)
|
||
PHY YR (*)
|
||
PHP Status
|
||
PLA Akku vom Stack holen
|
||
PLX XR (*)
|
||
PLY YR (*)
|
||
PLP Status
|
||
|
||
BRK Löst Interrupt mit gesetztem Break-Flag aus
|
||
RTI Return from Interrupt
|
||
|
||
NOP No Operation
|
||
|
||
TRB Test und Reset Speicher mit Akku (*)
|
||
BBR Branch on Bit Reset (*)
|
||
BBS Branch on Bit Set (*)
|
||
RMB Reset Memory Bit (*)
|
||
SMB Set Memory Bit (*)
|
||
|
||
Die mit (*) markierten Befehle sind CMOS-Befehle. Außerdem haben einige
|
||
der anderen Befehle zusätzliche Addressierungsarten. Die mit (D) markierten
|
||
Befehle arbeiten im Dezimal-Mode (Dezimal-Flag gesetzt) anders, nämlich
|
||
im BCD-Mode (eine Ziffer von 0-9 in 4 Bit).
|
||
|
||
Addressierungsarten:
|
||
|
||
-Immediate LDA #$12
|
||
-Absolute STA $1234
|
||
-Zeropage EOR $10
|
||
-Bit,ZP,REL BBR #7,$10,label
|
||
-Akku ASL
|
||
-Implied TAX
|
||
-(Indirect,x) LDA ($10,X)
|
||
-(Indirect),y STA ($3e),Y
|
||
-Zeropage,x CMP $12,X
|
||
-Absolut,x LDY $4356,x
|
||
-(Absolut,x) jmp (jumptabelle,x)
|
||
-Absolut,y ORA $2345,y
|
||
-Relative BNE irgendwohin
|
||
-(Indirect) jmp (<28>berVektor)
|
||
-Zeropage,y ldx $12,y
|
||
-Bit,Zeropage RMB #1,zeropage
|
||
|
||
Bei Adressierungsarten, die in der Zeropage und Absolut existieren, wird,
|
||
soweit möglich die Zeropage-Adressierung angewendet. Ein vorangestelltes
|
||
'!' erzwingt absolute Adressierung, auch bei einem Wert kleiner 256.
|
||
Als Wert oder Adresse können arithmetische Ausdrücke mit Hierarchie und
|
||
Klammerung verwendet werden. Der Assembler versteht folgende Operanden:
|
||
|
||
123 -Dezimal
|
||
$234 -Hexadezimal
|
||
&123 -Oktal
|
||
%010110 -Binär
|
||
* -Programmzähler
|
||
"A" -ASCII-Code
|
||
labelx -Label
|
||
-(lab1+1) -Ausdruck
|
||
|
||
Folgende Operatoren können benutzt werden:
|
||
|
||
+ -Addition 9
|
||
- -Subtraktion 9
|
||
* -Multiplikation 10
|
||
/ -Integer-Division 10
|
||
<< -Shift nach links 8
|
||
>> -Shift nach rechts 8
|
||
>=,=> -größer oder gleich 7
|
||
<=,=< -kleiner oder gleich 7
|
||
< -kleiner 7
|
||
> -größer 7
|
||
= -gleich 6
|
||
<>,>< -ungleich 6
|
||
&& -Logisches UND 2
|
||
|| -Logisches ODER 1
|
||
& -Bitweises UND 5
|
||
| -Bitweises ODER 3
|
||
^ -Bitweises Exklusiv-Oder 4
|
||
|
||
Die Operatoren mit der höheren Priorität werden zuerst bearbeitet.
|
||
Ein gültiger Ausdruck ist dann z.B.
|
||
|
||
LDA base+number*2,x
|
||
|
||
Bei Addressierungsarten, die nicht mit einer Klammer beginnen, darf
|
||
auch im ersten Ausdruck keine Klammer am Anfang stehen:
|
||
|
||
LDX (1+2)*2,y ; Falsch !
|
||
LDX 2*(1+2),y ; Richtig !
|
||
|
||
Vor einem Ausdruck kann ein unärer Operator stehen:
|
||
|
||
< bildet Lo-Byte des Wertes
|
||
> bildet Hi-Byte des Wertes
|
||
|
||
LDA #<adresse
|
||
|
||
Die Einzelnen Befehle werden durch ':' oder eine neue Zeile getrennt.
|
||
Hinter jedem Befehl kann, durch ';' abgetrennt ein Kommentar stehen.
|
||
Der Kommentar gilt bis zum nächsten Doppelpunkt oder bis zur nächsten
|
||
Zeile.
|
||
|
||
|
||
4. Pseudo-Opcodes, Block-Struktur und Gültigkeitsbereich von Labels
|
||
-------------------------------------------------------------------
|
||
|
||
Folgende Pseudo-Opcodes stehen noch zur Verfügung:
|
||
|
||
.byt wert1,wert2,wert3, ...
|
||
.word wert1,wert2, ...
|
||
.asc "text1","text2", ...
|
||
.dsb länge [,füllbyte]
|
||
*=
|
||
.(
|
||
.)
|
||
|
||
Hierbei sind '.byt' und '.asc' identisch und legen Daten Byteweise im
|
||
Speicher ab. '.word' legt Daten Wortweise (=2 Byte) im Speicher ab.
|
||
'.dsb' füllt einen Speicherbereich der Länge 'länge' mit dem Wert
|
||
'füllbyte' ab. Falls füllbyte nicht abgegeben ist, wird mit Null gefüllt.
|
||
Die folgenden Opcodes haben keine direkte Einwirkung auf die Objektdatei.
|
||
'*=' definiert den Programmzähler.
|
||
'.(' eröffnet einen neuen 'Block'. Alle Labels innerhalb eines solchen
|
||
Blocks sind lokal. Allerdings darf vorher kein Label gleichen Namens in
|
||
einem höheren Block definiert worden sein. '.)' schließt den Block wieder.
|
||
Die Maximale Blockschachtelungstiefe beträgt 16 Blocks.
|
||
|
||
Ein Label wird definiert dadurch, daß es kein Opcode ist:
|
||
|
||
label1 LDA #0 ; Position(=Programmzähler) des Opcodes
|
||
label2 =1234 ; direkte definition
|
||
label3 label4 label5 ; implizit Programmzähler, auch mehrere
|
||
; Labels werden definiert
|
||
label6 label7 = 3 ; label6 wird mit dem Wert des Programm-
|
||
; zählers gesetzt, label7 mit 3
|
||
label8: sta label2 ; Da Opcodes mit ':' getrennt werden,
|
||
; wird auch die übliche Schreibweise mit
|
||
; 'label:' erkannt.
|
||
|
||
Dabei werden Groß- und Kleinbuchstaben unterschieden.
|
||
Labels, die mit vorangestelltem '+' definiert werden, sind global (Block=0),
|
||
d.h. überall gültig. Mit vorangestellten '&' kann ein Label jeweils eine
|
||
Hierarchiestufe höher definiert werden als ohne '&'.
|
||
Mit vorangestelltem '-' kann ein Label umdefiniert werden:
|
||
|
||
-sysmem +=4 ; da gibts ==, +=, -=, *=, /=, &=, |=
|
||
-syszp =123
|
||
|
||
|
||
5. Preprozessor
|
||
---------------
|
||
|
||
Der Preprozessor ist stark an den Preprozessor der Sprache C angelehnt.
|
||
So sind die für C typischen /* */ -Kommentare möglich.
|
||
Ein Preprozessor-Befehl wird mit einem '#' am Beginn der Zeile eingeleitet.
|
||
|
||
#include "Dateiname" fügt die Datei 'Dateiname' an dieser Stelle in den
|
||
Quelltext ein. Beim Laden wird zuerst der Name
|
||
direkt gesucht, danach mit den Pfaden aus
|
||
XAINPUT.
|
||
|
||
#echo Kommentar Gibt Kommentar in der Fehlerdatei aus.
|
||
|
||
#print ausdruck Gibt Ausdruck direkt, nach Preprzessorbehandlung
|
||
und nach dem Ausrechnen aus.
|
||
|
||
#printdef DEFINIERT Gibt die Definition in der Fehlerdatei aus.
|
||
|
||
#define DEF Text Definiert DEF als Text, deshalb wird hinterher
|
||
immer DEF durch Text ersetzt
|
||
|
||
#ifdef DEF Der Quelltext bis zum folgenden #endif oder #else
|
||
wird nur assembliert, falls DEF vorher mit #define
|
||
definiert wurde.
|
||
|
||
#else else halt...
|
||
|
||
#endif Beendet #if..-Konstrukt. Nach jedem #if.. muž!
|
||
ein #endif stehen.
|
||
|
||
#ifndef DEF .... falls DEF nicht definiert wurde.
|
||
|
||
#if ausdruck .... falls ausdruck ungleich null ist.
|
||
|
||
#iflused label .... falls Label schon benutzt wurde.
|
||
|
||
#ifldef label .... falls Label schon definiert wurde.
|
||
|
||
Dabei beziehen sich #iflused und #ifldef auf Labels, nicht auf Preprozessor-
|
||
Definitionen! Damit l„žt sich z.B. eine Bibliotheksstruktur aufbauen:
|
||
|
||
#iflused label
|
||
#ifldef label
|
||
#echo label schon definiert, nicht aus Library
|
||
#else
|
||
label lda #0
|
||
....
|
||
#endif
|
||
#endif
|
||
|
||
Die #if-Schachtelungstiefe beträgt 15.
|
||
|
||
Mit #define können auch wie in C 'Funktionen' mit Parametern definiert
|
||
werden.
|
||
|
||
#define mult(a,b) ((a)*(b))
|
||
|
||
|
||
Literaturangaben
|
||
----------------
|
||
|
||
-"Das Maschinensprachebuch zum Commodore 64"
|
||
Lothar Englisch
|
||
Data Becker GmbH
|
||
-"Controller Products Data Book"
|
||
Rockwell International, Semiconductor Products Division
|
||
-"Programmieren in C"
|
||
Kernighan, Ritchie
|
||
Hanser Verlag
|
||
|