Das Schneider CPC Systembuch

Das Betriebssystem des Schneider CPC

Basic und Maschinencode

Z80-Relocalisitor

Der Schneider CPC unterstuetzt sehr weitreichend den Wunsch, Zusatz-Routinen nach Bedarf zu einem Hauptprogramm dazuladen zu können. Dabei ist es prinzipiell nicht vermeidbar, dass der (noch) freie Speicher dynamisch an die verschiedenen Erweiterungs-Programme verteilt wird.

'Das heißt, ein Programmpaket mit System-Erweiterungen kann nicht damit rechnen, an eine bestimmte, feste Adresse eingeladen zu werden!'

Ideal wäre deshalb ein Maschinencode-Programm, das vollkommen ortsunabhängig geschrieben wurde. Das ist bei der Die ICs im Überblick: Die CPU Z80
Das Innenleben der CPC-Rechner: Die CPU Z80
Die Anschlussbelegungen der wichtigsten ICs im CPC: Die CPU Z80
Z80
leider nur in ganz wenigen Fällen, vor allem bei ganz primitiven, kurzen Routinen möglich, da Anhang: Die Z80die Z80 fast nur über Befehle mit Adressierungsarten der Z80: Absolutabsoluter Adressierung verfügt.

Vorausgesetzt, die Lade-Adresse ist zwar zunächst einmal unbekannt, die Lage des Programmes ändert sich nach der Initialisierung aber nicht mehr (wie das in Datentypen: StringsStrings der Fall sein kann), so genügt es, bei der Initialisierung des Programmpaketes alle Adressierungsarten der Z80: Absolutabsoluten Adressen an die tatsächliche Position des Programms anzupassen.

Adressierungen Adressierungsarten der Z80: Relativrelativ zum Programmzeiger (JR und DJNZ) müssen dagegen nicht angepasst werden. Entfernungen innerhalb eines Programmes ändern sich ja nicht, wenn es an einer anderen Adresse eingeladen wird.

Funktionsweise

Dieser einfache Z80-Relocalisator benötigt für seine Arbeit eine Tabelle, in der man alle Stellen eintragen muss, an denen eine Adressierungsarten der Z80: Absolutabsolute Adresse verwendet wurde.

Er wird bei der Programmentwicklung vor das eigentliche Programm gehängt und mit diesem zusammen abgespeichert. Nachher lädt man das Programm an einer beliebigen Stelle ein und ruft den Relocalisator auf, der dann alle Adressierungsarten der Z80: Absolutabsoluten Adressen anpasst.

Zunächst einmal muss er feststellen, wo im Speicher das Programm überhaupt liegt. Dazu wartet er einfach auf den nächsten Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt (Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: HaltHALT), der als Die CPU Z80: Unterprogramm-AufrufeUnterprogramm-Aufruf die Return-Adresse auf den Maschinenstapel PUSHt. Das ist die mit dem Label 'STELLE' markierte Stelle.

Mit dem Return vom Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt wird dieser Stack-Eintrag aber wieder verbraucht. Deshalb wird der Stapelzeiger wieder um zwei Stellen erniedrigen (DEC SP), um mit 'POP HL' die Rücksprungsadresse ein zweites Mal vom Stack holen zu können.

Sollte aber ausgerechnet zwischen den beiden 'DEC_SP" erneut ein Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt dazwischenfunken, wäre ein Datentypen: Bytes
Datenbreite: Bytes
Byte
der Rücksprungs-Adresse zerstört. Das ist normalerweise aber fast ausgeschlossen, weil ja gerade erst ein Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt bearbeitet wurde. Der nächste Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt dürfte erst wieder nach einer knappen 300stel Sekunde auftreten. Wer ganz sicher gehen will, kann die beiden 'DEC SP' noch mit 'DI' und 'EI' rahmen, wobei das Label STELLE dann auf den Befehl 'DI' zeigen muss.

Danach wird aus der tatsächlichen Lage von 'STELLE' (im HL-Register) und der nominalen Lage (im DE-Register) die Verschiebeweite errechnet. Dieser Wert kann, je nach Verschiebe-Richtung, positiv oder negativ sein und wird für den Rest der Relozierung im Doppelregister BC gespeichert.

Nun kann zu jeder Adressierungsarten der Z80: Absolutabsoluten Nominal-Adresse deren augenblicklicher Wert berechnet werden, indem einfach der Offset aus dem BC-Register addiert wird.

Die erste Adressierungsarten der Z80: Absolutabsolute Adresse, die angepasst werden muss, ist die Adresse der Tabelle selbst, damit der Relocalisator sie überhaupt findet.

In der Programmschleife ab RELOOP werden dann alle Adressierungsarten der Z80: Absolutabsoluten Adressen im eigentlichen Programm angepasst. Bei jedem Schleifen-Durchgang ist zunächst einmal im BC-Register der Offset und im HL-Register der Zeiger in die Tabelle enthalten.

In der Tabelle stehen dabei die 'unverschobenen, Adressierungsarten der Z80: Absolutabsoluten Adressen der Stellen, an denen eine Adressierungsarten der Z80: Absolutabsolute Adresse steht, die verschoben werden muss.' Das muss man sich klar machen, um zu verstehen, was nun in jedem Schleifendurchgang gemacht werden muss.

Aus einem Grund, der nachher noch erklärt wird, werden in die Tabelle dabei nicht die Adresse der Adresse selbst, sondern die Adresse der Stelle davor eingetragen.

Zunächst wird eine Adresse aus der Tabelle in das DE-Register gelesen und der Tabellenzeiger HL um zwei Datentypen: Bytes
Datenbreite: Bytes
Bytes
weitergestellt.

Dann wird erst einmal auf das Tabellen-Ende getestet, wofür der Eintrag &0000 vorgesehen ist.

Danach werden das HL- und DE-Register vertauscht, weil nur im HL-Register gerechnet werden kann.

Zunächst wird zur Nominal-Adresse aus der Tabelle der Offset BC addiert, wodurch man die tatsächliche Adresse der zu verschiebenden Adresse erhält.

Danach kann die Adresse endlich selbst angepasst werden, was über zwei Byte-Additionen im Akku geschieht.

Zum Schluss wird der Tabellen-Zeiger, der die ganze Zeit im DE-Register unverändert blieb, wieder nach HL zurückgetauscht. Auch der Offset im BC-Register wurde nicht angetastet, so dass man jetzt wieder zum Schleifenkopf springen kann.

Programm-Entwicklung

Zunächst wird ein neues Programm ganz normal für eine feste Startadresse geschrieben und dort ausgetestet, bis es fehlerfrei läuft.

Danach muss man den Relocalisator VOR den Programmtext hängen. (Nur dann kann man nach der Initialisierung den Relocater und seine Tabelle 'vergessen' und den Platz wieder für andere Aufgaben benutzen.) Wie das im Einzelnen geschieht, ist von Assembler zu Assembler unterschiedlich. Bei einigen kann man mehrere Source-Dateien 'linken' (logisch verbinden, so dass sie zusammen einen 'Gesamt-Quellcode' bilden). Im Zweifelsfall muss man (evtl. mit Hilfe eines Textverarbeitungsprogrammes) aus der Textdatei mit dem Programm und der mit dem Relocater eine einzige machen.

Zum Schluss muss man auch noch die Tabelle eingeben. Das ist der langweiligste Teil des Ganzen. Trotzdem muss man hier äußerst sorgfältig vorgehen.

Dafür durchsucht man das ganze Programm systematisch nach allen Stellen, an denen eine Adressierungsarten der Z80: Absolutabsolute Adresse vorkommt. Das sind zum Beispiel folgende Befehle:

Maschinencode über HIMEM: CALLCALL adresse
JP   adresse
LD   HL,(adresse)
LD   (adresse),DE

Einige Befehle KÖNNEN eine Adressierungsarten der Z80: Absolutabsolute Adresse enthalten, was im Einzelfall aus dem Programm-Zusammenhang hervorgehen muss:

LD   HL,adresse
DEFW adresse

Jede gefundene, Adressierungsarten der Z80: Absolutabsolute Adresse wird dann darauf untersucht, ob eine Stelle innerhalb des Programms adressiert wird:

• Liegt die Adresse außerhalb des Programmes, und kann sie sich überhaupt nicht ändern, wenn das Programm verschoben wird, dann darf sie natürlich auch nicht reloziert werden. Hierbei handelt es sich in der Regel um Systemvariablen und Grundlagen: UnterprogrammeUnterprogramme des Betriebssystems, die normalerweise über die entsprechenden Vektoren der Firmware-Jumpblocks angesprungen werden sollten.

• Liegt die Adresse aber im Programm, so muss diese Stelle in die Tabelle eingetragen werden.

Markieren von absoluten Adressen

Dazu markiert man die Stelle, an der die Adresse steht (nicht die adressierte Stelle!), mit einem Label, und trägt dieses Label in die Tabelle ein.

Da es sich in den meisten Fällen um einen 3-Byte-Befehl handelt, bei dem der Adresse ein Befehlsbyte vorangeht, ist es sinnvoll, nicht die Stelle direkt zu markieren, sondern die Adresse des Datentypen: Bytes
Datenbreite: Bytes
Bytes
davor in die Tabelle einzutragen. Das bedeutet nämlich, dass man das Label dann vor das Befehlsbyte, und im Assembler-Quelltext also direkt vor den gesamten Befehl setzen kann.

Bei 4-Byte-Befehlen und bei Adressen in Data-Bereichen (mittels DEFW) geht das nicht, hier muss man 'die Stelle davor' mit der Assembler-Pseudo-Operation 'EQU' markieren.

Um den Die Firmware des Schneider CPC: ÜberblickÜberblick zu behalten, sollte man für die Relocator-Label eine einheitliche Nomenklatur verwenden, um sie sofort von den 'normalen' Label unterscheiden zu können. Die in diesem Beispiel verwendete Version mit fortlaufenden Nummern ist dabei empfehlenswert, weil dann auch kein Label in der Tabelle vergessen wird.

Außerdem sollte man sich nicht verlocken lassen, ein 'normales' Label mitzubenutzen, weil es schon an der richtigen Stelle steht. In diesem Fall lieber eine Stelle im Programm mit zwei Label versehen, dem 'normalen' und dem für den Relocalisator. Sonst geht die Übersicht schnell verloren!

Hat man dann alle Stellen markiert und in die Tabelle eingetragen, geht es ans letzte Austesten:

Lauft das Programm nicht, wenn man es an anderen Stellen einlaedt, kann es daran liegen, dass man eine Stelle vergessen, oder auch zuviel markiert hat. Oder man hat einmal daneben 'gelabelt', und z.LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
. LD BC,(xxxx), einen 4-Byte-Befehl, direkt und nicht mit 'EQU' markiert.

Markieren absoluter Adressen für den Relocalisator
2-Datentypen: Bytes
Datenbreite: Bytes
Byte
-'Befehl': X1: EQU $-1 DEFW adresse
3-Byte-Befehle:    X2:   LD   HL,(adresse)
                   X3:   LD   (adresse),HL
                   X4:   LD   BC,adresse
                   X5:   LD   (adresse),Operationen: BD5B / 349A / 349A:  FLO SUBA
                   X6:   Maschinencode über HIMEM: CALLCALL adresse
                   X7:   JP   adresse
4-Byte-Befehle:    X8:   EQU  $+1
                         LD   BC,(adresse)
                   X9:   EQU  $+1
                         LD   (adresse),DE
                   X10:  EQU  $+1
                         LD   IX,adresse
                   X11:  EQU  $+1
                         LD   (adresse),IY

Achtung: Es gibt auch einen 4-Byte-Befehl: LD HL,(adresse). Praktisch alle Assembler generieren hierfür aber den entsprechenden 3-Byte-Befehl.

Mit diesem Relocater können fast alle Z80-Programme relocalisiert werden. Ausgenommen sind nur solche Konstruktionen, bei denen eine Adresse aus zwei Datentypen: Bytes
Datenbreite: Bytes
Bytes
zusammengesetzt wird. Das kommt aber nur ausgesprochen selten vor und meist lässt sich ein solches Programm dann sowieso nur noch in 256-Byte-Schritten verschieben.

; ******************************************
; Relocater und RSX-Binder für RSX-Routinen   (c) G.Woigk       Erklärung zu den Anschlüssen: Vcc und Vss
Erklärung zu den Anschluss-Bezeichnungen: Vcc und Vss
vs
.: 22.4.86 ; ****************************************** ; ; >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Relocalisiere das Programm ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ; ORG 30000 ; ; Bestimme den Adress-Offset des Programmes zwischen der momentanen, Datentypen: Realrealen Lage ; des Programmes und dessen unverschobener Nominal-Lage (Laut ORG-Anweisung): ; RELOC: EI ; Warte auf den nächsten Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: INT - InterruptInterrupt. Alle noch folgenden Anschlüsse fallen unter die Rubrik STEUER- oder auch CONTROLBUS:: HaltHALT ; Dieser Pusht als Die CPU Z80: Unterprogramm-AufrufeUnterprogramm-Aufruf die ; ; Return-Adresse = 'STELLE' auf den Stack. ; STELLE: DEC SP ; Hole Return-Adresse vom Stack: DEC S POP HL ; HL = Datentypen: Realreale Adresse von 'STELLE' ; LD DE,STELLE ; DE = unverschobene Adresse von 'STELLE' AND Operationen: BD5B / 349A / 349A: FLO SUBA SBC HL,DE ; HL = Offset LD LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
,H LD C,L ; BC = Offset ; ; Bestimme Lage der Tabelle mit den zu verschiebenden Adressen: ; LD HL,RTABEL ; HL = Unverschobene Adresse der Tabelle ADD HL,BC ; HL = Datentypen: RealReale Adresse der Tabelle ; ; Nun Schleife über alle Adressen. Das Tabellenende ist mit #0000 markiert: ; BC enthält die ganze Zeit den Adress-Offset. ; HL dient als Zeiger auf die Adressen in der Tabelle. ; RELOOP: LD LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
,(HL) ; Hole eine Adresse aus der Tabelle INC HL LD D,(HL) ; DE = unverschobene Adresse INC HL ; einer unverschobenen Adresse ; LD Operationen: BD5B / 349A / 349A: FLO SUBA,LOW KERNEL JUMPBLOCK: 000E: LOW PCBC INSTRUCTION
LOW KERNEL JUMPBLOCK: 001E: LOW PCHL INSTRUCTION
E
; Erklärung der Anschlussbelegung: TestTest auf Tabellenende: OR D X0: JP Z,BIND ; Ende => Maschinencode über HIMEM: RSXRSX einbinden, weitere Initialisierungen etc. ; EX DE,HL ; DE := Tabellenpointer ; ; HL := unversch. Adresse der unversch. Adresse ADD HL,BC ; HL := Datentypen: Realreale Adresse der unverschobenen Adresse ; ; Nun die Adresse (Lage: ab HL+1 !!!) anpassen: ; INC HL ; Zuerst das niederwertige Adressbyte: LD Operationen: BD5B / 349A / 349A: FLO SUBA,(HL) ADD Operationen: BD5B / 349A / 349A: FLO SUBA,C LD (HL),Operationen: BD5B / 349A / 349A: FLO SUBA ; INC HL ; Dann das höherwertige Adressbyte: LD Operationen: BD5B / 349A / 349A: FLO SUBA,(HL) ADC Operationen: BD5B / 349A / 349A: FLO SUBA,LOW KERNEL JUMPBLOCK: 000B: LOW KL LOW PCHL
LOW KERNEL JUMPBLOCK: 001B: LOW KL FAR PCHL
LOW KERNEL JUMPBLOCK: 003B: LOW EXT INTERRUPT
B
; (Rechnen im Binärsystem: AdditionAddition mit ADC, um evtl. Übertrag mitzunehmen) LD (HL),Operationen: BD5B / 349A / 349A: FLO SUBA ; EX DE,HL ; HL := Tabellenpointer JR RELOOP ; und weiter zur nächsten Adresse ; ; ; Adressen - 1 aller zu verschiebenden Worte !!! ; ---------------------------------------------- RTABEL: DEFW X0,X1,X2,X3,X4,X5 DEFW #0000 ; Endmarke der Tabelle ; ---------------------------------------------- ; ; ; >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Einbinden der RSX-Sammlung ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ; X1: BIND: LD BC,ITABEL ; RSX-Sammlung dem MAIN FIRMWARE JUMPBLOCK: KERNEL
Die Firmware des Schneider CPC: KERNEL
Kernel
vorstellen: X2: LD HL,ISPACE Maschinencode über HIMEM: CALLCALL #BCD1 ; 'KERNEL: BCD1: KL LOG EXTKL LOG EXT' ; RET ; Fertig. Eventuelle, weitere Initialisierungsroutinen ; ; möglichst hier einfügen, da der Platz bis zur ; ; Tabelle 'ISPACE' wieder freigegeben werden kann. ; ; ///////////////////////////////////////////////////////////////////////// ; Bis hierhin kann das Programmpaket mit Relocater, RSX-Binder und weiteren ; Initialisierungen nach der Initialisierung wieder überschrieben werden ! ; \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ ; ISPACE: DEFS 4 ; Raum für den MAIN FIRMWARE JUMPBLOCK: KERNEL
Die Firmware des Schneider CPC: KERNEL
Kernel
(für eine verkettete Trees: ListenListe ; ; aller RSX-Erweiterungen im RAM) ; ; Tabelle der Routinen-Namen. ; Letzter Buchstabe jeweils mit #80 geodert. ; ------------------------------------------ BCD1: KL LOG EXT: 2. Namenstabelle (NAMTAB)NAMTAB: DEFM "HARDCOP" DEFB "Y"+128 DEFM "TEXTCOP" DEFB "Y"+128 ; ------------------------------------------ DEFB 0 ; Endmarke der Tabelle ; ; X3: EQU $-1 ITABEL: DEFW BCD1: KL LOG EXT: 2. Namenstabelle (NAMTAB)NAMTAB ; ; Tabelle mit MAIN FIRMWARE JUMPBLOCK: JUMPER
Die Firmware des Schneider CPC: JUMPER
Jumps
zu den Routinen (je 3 Datentypen: Bytes
Datenbreite: Bytes
Bytes
) ; ---------------------------------------------- X4: JP HARD ; für "HARDCOPY" X5: JP TEXT ; für "TEXTCOPY" ; ---------------------------------------------- ; ; Ab hier folgen die Maschinencode-Routinen. ; ; ....

Valid HTML   Valid CSS