Wohl jeder, der sich etwas intensiver mit seinem Computer besch„ftigt hat, wird sich schon einmal ge„rgert haben, daž ausgerechnet das Programm, das er gerne besitzen wrde, auf seinem Rechner nicht l„uft. Besonders viel professionelle Software ist z.B. fr andere Betriebssysteme wie MS-DOS oder CP/M zu erhalten. Da diese Systeme jedoch fr andere Prozessoren als den 68000 geschrieben sind, ist es eigentlich nicht m”glich, deren Programme auf dem Atari ST zu verwenden. Ganz ausweglos ist die Situation nun aber auch nicht. Fr die oben genannten Betriebssysteme gibt es inzwischen Emulatoren auf dem Markt, die es erm”glichen, auch Programme, die fr andere Prozessoren oder Betriebssysteme geschrieben worden sind, auf dem ST laufen zu lassen. Von diesen Programmen sind die MAC-Emulatoren besonders hervorzuheben. Da der Macintosh wie der ST ebenfalls mit einen 68000-Prozessor arbeitet, ist es in diesem Fall m”glich, durch ein entsprechendes Programm nicht nur das Macintosh- Betriebssystem auf dem ST zu implementieren, sondern dabei auch noch eine gr”žere Geschwindigkeit im Programmablauf zu erreichen, als dies auf dem "Original" MAC-System der Fall ist. Die Ursache hierfr liegt darin, daž der 68000 beim ST h”her getaktet ist, als im Macintosh. Leider drfte diese Art von Emulatoren ein Sonderfall bleiben, denn die bekanntesten Betriebssysteme laufen nun einmal nur auf Rechnern, die keinen Prozessor der 68000- Familie benutzen. In diesem Fall bleibt als Ausweg nur die Software-Emulation des entsprechenden Prozessors. Der Geschwindigkeitsverlust, der dabei auftritt, l„žt sich leider nicht vermeiden. Allerdings kann man bei entsprechender Programmierung trotzdem recht akzeptable Geschwindigkeiten erreichen, auch wenn man dieses nicht jedem auf dem Markt befindlichen Emulator anmerkt. Als Atari- und C64-Besitzer habe ich mich damit besch„ftigt, das Betriebssystem des C64 so gut wie m”glich auf dem ST zu implementieren. Fr die Spiele-Freaks heižt das aber leider nicht, daž nun der ST in der Lage ist, C64-Spiele zu verarbeiten. Dazu ist der Aufbau der beiden Computer zu verschieden. Dennoch l„žt sich bis auf einige spezielle F„higkeiten des C64 ein brauchbarer Emulator entwickeln. Was man bei der Programmierung eines solchen Emulatorprogramms beachten sollte, wird Gegenstand des Artikels sein. Da dieser Teil Kenntnisse in Assembler- Programmierung voraussetzt, drfte er vor allem fr Assembler- Programmierer interessant sein. Aužerdem m”chte ich kurz ein paar Features des C64-Emulators beschreiben. Konkretere Hinweise finden sich auf der Leserservice-Diskette, auf der sich der Emulator befindet. Bevor man sich um die Implementierung eines konkreten Betriebssystems kmmern kann, geht es erst einmal darum, sich detaillierte Informationen ber den zu emulierenden Prozessor zu besorgen. (Dabei setze ich natrlich voraus, daž man die Programmierung des 68000 gut beherrscht.) Um sich n„her mit dem eigentlichen Betriebssystem besch„ftigen zu k”nnen, muž schliežlich erst einmal die Emulation fr den entsprechenden Prozessor stehen. Fr die bekanntesten Prozessoren, wie im Fall des C64 fr den 6502, ist es kein Problem, an die entsprechende Literatur zu kommen. Wichtig sind vor allem Informationen ber die Behandlung der Flags des Prozessors bei den verschiedenen Befehlstypen sowie eine Beschreibung eventueller Besonderheiten des Prozessors. Zu beachten ist, daž z.B. das Carry-Flag durchaus nicht in allen Prozessoren die gleiche Bedeutung hat. Gerade der 6502 hat hier seine Besonderheit. Im gnstigsten Fall hat man schon einmal mit dem zu emulierenden Prozessor gearbeitet, was sich im weiteren Verlauf der Programmierung als grožer Vorteil erweisen wird. Bei der eigentlichen Programmierung des Emulators spielt die Geschwindigkeit der Befehlsauswertung eine besonders grože Rolle. Da der 68000 mit den Befehlscodes des 6502 berhaupt nichts anfangen kann, muž jeder Opcode interpretiert werden, „hnlich wie es ein BASIC-Interpreter mit einem BASIC-Programm macht. Fr jeden 6502-Code muž eine Routine in 68000-Assembler entwickelt werden, die Aktionen vornimmt, die dem entsprechenden 6502-Befehl entsprechen. Hierin liegt auch der Grund, warum es nicht m”glich ist, mit einem Software-Emulator die gleiche Geschwindigkeit zu erreichen, wie sie das System besitzt, das emuliert wird. Obwohl der 68000 mit 8 MHz getaktet ist und der 6510 im C64 mit nur knapp 1 MHz, bedeutet die Interpretation der Befehlsbytes einen Verlust an Geschwindigkeit. Die eigentliche Bearbeitung der einzelnen Opcodes in den entsprechenden Routinen kann jedoch durchaus schneller vom 68000 ausgefhrt werden, als vom 6502. So ben”tigt der C64-Emulator zum Setzen des Carry-Flags 4 Taktyklen, der 6502 braucht 2 Taktzyklen. Rechnet man diese Angaben auf die Taktfrequenzen der beiden Prozessoren um, so schneidet der 68000 deutlich besser ab. Allerdings muž nun noch die Zeit addiert werden, die der Emulator ben”tigt, bis er die Adresse der Routine zum Setzen des Flags ermittelt hat. Dieser Vorgang nimmt jedoch so viel Zeit in Anspruch, daž der 6502 schliežlich doch der Schnellere ist. Da die Taktfrequenz neuerer Prozessortypen immer weiter steigt, wird es wohl nur eine Frage der Zeit sein, bis es m”glich ist, einen 8 Bit-Prozessor mindestens mit der Geschwindigkeit zu emulieren, mit der dieser Prozessor normalerweise betrieben wird. Nun wiederholt sich der Vorgang der Interpretation des folgenden Befehlsbytes natrlich bei jedem neuen 6502-Opcode. Deshalb ist es gerade an dieser Stelle besonders wichtig, eine m”glichst schnelle Auswertung des n„chsten Befehlsbytes zu erreichen. Spart man hier nur einen Taktzyklus ein, so steigt die Geschwindigkeit der Emulation bereits merklich. Wie kann nun das n„chste Befehlsbyte ausgewertet werden? Im allgemeinen wird hierzu dieses Byte in ein Datenregister geladen und dann die Adresse der zugeh”rigen Emulationsroutine aus diesem Byte berechnet. Dies kann z.B. durch den folgenden Algorithmus geschehen: LOOP: CLR D0 MOVE.B (A0)+,D0 ASL #2,D0 MOVE.L (A1,D0),A0 Algorithmus 1 JSR (A0) BRA LOOP Geht man davon aus, daž A0 auf das n„chste Befehlsbyte im 6502- Adrežraum zeigt und A1 den Anfang einer Tabelle mit Sprungadressen auf die entsprechenden Emulationsroutinen enth„lt, wrde obiges Programmfragment den Anforderungen gerecht. Allerdings l„žt die Geschwindigkeit der Interpretation noch viel zu wnschen brig. Um eine h”here Geschwindigkeit zu erreichen, kann man den ASL-Befehl durch zwei ADD-Befehle ersetzen und die obige Routine fr jeden zu emulierenden Opcode neu programmieren, was dann so aussehen k”nnte: CLR D0 MOVE.B (A0)+,D0 ADD D0,D0 ADD D0,D0 MOVE.L (A1,D0),A0 Algorithmus 2 JMP (A0) In diesem Fall wird die n„chste auszufhrende Routine nicht mehr als Unterprogramm angesprungen, so daž man nicht mit RTS zu einer bergeordneten Interpretationsroutine zurckkehren kann. Eben aus diesem Grund muž sich der obige Programmteil am Ende jeder Emulations-Unterroutine befinden, also fr jeden Opcode einmal im Programm vorhanden sein. Dadurch wird das Emulatorprogramm zwar l„nger, dafr aber deutlich schneller, da der Aufruf und die Rckkehr aus einem Unterprogramm besonders viel Zeit ben”tigt. Und Geschwindigkeit ist fr einen Software-Emulator das h”chste Gebot! Darber hinaus hat man auf dem ST genug Speicherplatz, um bei einem Emulator fr einen Prozessor mit nur 64K Adrežraum nicht sparen zu mssen. Der oben dargestellte Algorithmus 2 findet sich in „hnlicher Form in allen mir bekannten Software-Emulatoren wieder. Lassen sie uns einmal ein wenig rechnen: Die Ausfhrungsgeschwindigkeit fr diesen Algorithmus betr„gt 46 Taktzyklen. Der 6502 ist normalerweise mit 1MHz getaktet, der ST mit 8MHz. Allein die Interpretation eines Befehlsbytes wrde demnach eine Zeit verbrauchen, die 46/8 6502-Zyklen entspricht, also fast 6 6502- Zyklen. Da die krzesten 6502-Befehle 2 Taktzyklen (bezogen auf den 6502) brauchen, wrde nur die Auswertung des n„chsten Befehls durch den Emulator schon extrem viel Zeit verschlingen. Das Prinzip, das in Algorithmus 2 zur Interpretation verwendet wurde, kann jedoch nicht mehr entscheidend verkrzt werden. Ursprnglich arbeitet auch mein C64-Emulator mit einem vergleichbaren Algorithmus. Ist man allerdings bereit, 64K Speicherplatz mehr fr den Emulator zu opfern, was beim ST normalerweise m”glich ist, so kann man die so zeitkritische Befehlsinterpretation durch eine v”llig andere Programmierung deutlich beschleunigen. Hier die Routine, wie sie in meinem Emulator verwendet wird: MOVE.B (A0)+,LBL+2(A1) LBL: JMP $0(A1) Algorithmus 3 Auch hier ist A0 Pointer auf das n„chste Befehlsbyte, A1 ist ein spezieller Pointer in das 64K-Segment, in dem sich der Emulator nun befindet. Nun ein paar Erkl„rungen zu Algorithmus 3, denn er drfte nicht so einsichtig sein, wie die ersten beiden. Im obigen Fall wird das Befehlsbyte n„mlich ohne irgendwelche weiteren Berechnungen direkt zur Adrežbildung im darauffolgenden Sprungbefehl verwendet. Hierzu am besten ein Beispiel. Nehmen wir an, der n„chste 6502-Opcode, auf den das Adrežregister A0 zeigt, ist $EA. Dieses Byte wird nun als Displacement fr den folgenden Sprungbefehl benutzt, wobei sich das Programm selbst ver„ndert. Direkt vor dem Sprung sieht der Sprungbefehl dann also folgendermažen aus: JMP $EA00(A2) Das 6502-Befehlsbyte bildet das Hi-Byte fr das Displacement, das Lo-Byte ist immer Null. Auf diese Art und Weise spart man sich jegliche Adrežberechnung, da die Sprungadresse auf die passende Routine nicht mehr einer Tabelle entnommen wird, wie es bei den ersten beiden Algorithmen der Fall ist. Allerdings muž nun jede Routine zur Behandlung der Opcodes in genau einer Page Abstand hinter dem Beginn der vorherigen Routine anfangen, da das Lo-Byte des Displacements immer Null ist. So kommt es auch, das fr diese Art der Emulation 64K Speicherplatz ben”tigt werden, n„mlich fr jeden Opcode des 6502 256 Bytes. Die Ausfhrungszeit von Algorithmus 3 betr„gt nur noch 30 Taktzyklen. Er ist also um 50% schneller als Algorithmus 2. Dieser Geschwindigkeitszuwachs macht sich deutlich bemerkbar, denn er schl„gt ja bei jedem 6502-Opcode neu zu Buche. Soweit das Wichtigste zur Interpretation der Befehlsbytes des 6502. Ist dieses Problem gel”st, so muž man sich als N„chstes darum kmmern, wo die Register des zu emulierenden Prozessors "aufbewahrt" werden k”nnen. In unserem Fall ist diese Frage recht einfach zu beantworten. Der 6502 hat drei Register (Akkumulator, X- und Y-Register) sowie einen 8-Bit-Stackpointer und den Programmz„hler. Der 68000 besitzt insgesamt 15 Register, wenn man A7 als Stackpointer einmal aužer Acht l„žt. Es ist somit keine Kunst, diese Register in Daten- bzw. Adrežregistern des 68000 unterzubringen. Fr Stackpointer und Programmz„hler wird jeweils ein Adrežregister, fr die anderen 6502-Register werden drei Datenregister verwendet, in denen nur das Lo-Byte genutzt wird. Schliežlich muž das Prozessorstatusregister des 6502 auch noch irgendwo untergebracht werden. Es ist im allgemeinen nicht m”glich, einfach die Flags des 68000 zu verwenden, da sich deren Verwendung von der beim 6502 geringfgig unterscheidet. Darber hinaus hat der 68000 im Gegensatz zum 6502 z.B. kein Dezimalflag. Bei den meisten arithmetischen Operationen besteht in der Behandlung der Flags jedoch kein Unterschied. Es empfiehlt sich, die Flags in einem weiteren Datenregister unterzubringen und nur bei Bedarf in das CCR-Register des 68000 zu bertragen. Schliežlich wird des Prozessorstatusregister nicht von jedem Befehl beeinflužt. Nachdem bei Rechenoperationen die Flags im CCR entsprechend gesetzt worden sind, werden sie dann wieder in das reservierte Datenregister bertragen. Durch die beschriebene Verwendung der Register bleiben noch einige Register dem Programmierer zur Verfgung. Sie k”nnen dann Daten aufnehmen, die w„hrend der Emulation st„ndig zur Verfgung stehen sollen, wie z.B. ein Pointer auf den 64K Adrežraum des 6502 sowie auf die 64K, die fr den Emulator reserviert sind, und in dem sich die Emulationsroutinen fr die einzelnen Opcodes des 6502 befinden. Prinzipiell ist es natrlich auch m”glich, die Registerinhalte des 6502 im Speicher abzulegen. Allerdings dauern Zugriffe auf den Speicher verh„ltnism„žig lange, so daž man keine brauchbare Geschwindigkeit mehr erzielen k”nnte. Will man Prozessoren emulieren, die mehr Register aufweisen, als der 6502 sie besitzt, so muž man die Aufteilung neu berdenken. Dies ist brigens bei den meisten anderen Prozessoren der Fall, z.B. beim 8080, Z80 und 8086. Man sollte jedoch durch geschickte Wahl der Registerbelegung stets dafr sorgen, daž absolute Zugriffe auf den Speicher m”glichst vermieden werden, da sie besonders zeitaufwendig sind. Beim C64-Emulator konnten direkte Zugriffe auf den Speicher vollkommen umgangen werden. Hier wird nur ber Adrežregister auf den Adrežraum des 6502-Prozessors zugegriffen. Will man mit dem 68000 einen 8 Bit-Prozessor emulieren, so sind einige Adressierungsarten und Befehle recht leicht nachzuvollziehen, andere stellen jedoch Probleme dar, besonders dann, wenn es darum geht, eine m”glichst schnelle Ausfhrungszeit zu erzielen. Ich m”chte hier als Beispiel die absolute Adressierung des 6502 anfhren. Eigentlich kein Problem, sollte man meinen. Aber dennoch muž man hier vorsichtig sein. Wie sie sicher wissen, kann der 68000 auf 16 Bit-Worte nur dann zugreifen, wenn sie auf geraden Speicheradressen stehen. Verst”že gegen diese Regel fhren zu einem Adrežfehler, der sich in drei Bomben personifiziert. Bei Programmen, die fr diesen Prozessor geschrieben sind, liegen die Befehlsworte und absoluten Adressen deshalb natrlich immer auf geraden Adressen. Bei 8 Bit Prozessoren sieht die Sache jedoch ganz anders aus. Die oben angefhrte Einschr„nkung fr absolute Adressen besteht hier nicht. Es kann also ohne Weiteres passieren, daž die absolute Adresse, die auf einen Sprungbefehl des 6502 folgt, auf einer ungeraden Adresse liegt. Somit ist es nicht m”glich, die Adressen fr die absolute Adressierung mit einem einzigen Befehl aus dem 6502-Adrežraum in ein Register des 68000 zu holen. Solche Adressen mssen hier grunds„tzlich in zwei einzelne Bytes aufgespalten werden. Darber hinaus existiert noch ein weiteres Žrgernis. Im Gegensatz zum 68000 werden bei 8 Bit Prozessoren absolute Adressen mit den Lo-Byte zuerst im Speicher abgelegt. Bevor man eine solche Adresse verwenden kann, mssen also erst die beiden Adrežbytes in die richtige Reihenfolge gebracht werden. Mit folgender Routine k”nnte dann die absolute Adresse aus dem Speicher in ein Datenregister geholt werden: MOVE.B (A0)+,D0 ASL #8,D0 Algorithmus 4 MOVE.B (A0)+,D0 ROR #8,D0 Die beiden Bytes fr die absolute Adresse werden hier einzeln aus dem Speicher geholt und durch Schieben und Rotieren in die richtige Reihenfolge gebracht. So weit, so gut. Leider brauchen Schiebe- und Rotierbefehle relativ viel Zeit. Algorithmus 4 mag zwar leicht zu durchschauen sein, ben”tigt jedoch 58 Taktzyklen. Dies ist besonders ungnstig, wenn man bedenkt, daž die absolute Adressierung relativ h„ufig vorkommt. Ist man jedoch bereit, ein Adrežregister zu opfern, so kann man durch eine v”llig andersartige Programmierung einen grožen Geschwindigkeitsvorteil erlangen: MOVE.B (A0)+,-(A2) MOVE.B (A0)+,-(A2) Algorithmus 5 MOVE (A2)+,D0 In Algorithmus 5 zeigt A2 auf eine beliebige gerade Adresse im Speicher, an der die beiden Bytes zu einem Wort zusammengesetzt und anschliežend nach D0 bertragen werden. Diese etwas unkonventionelle Art der Programmierung mag zwar umst„ndlich erscheinen, sie kommt dafr jedoch mit nur 32 Zyklen aus, denn Schiebe- und Rotierbefehle entfallen nun v”llig. Leider ist es nicht m”glich, den Stackpointer, also A7, so zu benutzen, wie in diesem Beispiel A2. Der Stackpointer wird n„mlich grunds„tzlich um ein Wort, also zwei Bytes, erh”ht oder erniedrigt, auch wenn Byteoperationen durchgefhrt werden, so daž er sich nicht an Stelle eines anderen Adrežregisters benutzen l„žt. Die obige Problematik stellt nur eines von vielen Problemen dar, die man bei der Emulation eines 8 Bit Prozessors zu bew„ltigen hat, wenn man um jeden Taktzyklus k„mpfen muž. Ist nun endlich die eigentliche Emulation des Prozessors fertiggestellt, wobei natrlich fraglich ist, wieviele Fehler sie noch enth„lt, muž man als n„chstes sein Augenmerk auf die Implementation des Betriebssystems richten. Schliežlich ist es das erste und wichtigste Programm, das man zum Laufen bringen muž. L„uft das Betriebssystem unter dem Emulator einwandfrei, so kann man davon ausgehen, daž sich kaum noch Fehler im Programm befinden, da alle Befehle des emulierten Prozessors irgendwann einmal ausgefhrt werden drften. Nicht jedes Betriebssystem l„žt sich gleich gut auf einen anderen Computer bertragen. Das Betriebssystem des C64 l„žt in dieser Hinsicht einiges zu Wnschen brig. Im gnstigsten Fall gibt es fr jede wichtige Funktion, die vom System erledigt werden soll, also insbesondere um die Behandlung der Ein- und Ausgabe, eine Funktionsnummer oder einen Sprungvektor. Beim C64 gibt es zwar eine solche Liste von Sprungvektoren, nur ist sie leider nicht so vollst„ndig, wie man es gerne h„tte. Besser sieht es da bei CP/M und MS-DOS aus. Hier gibt es weitaus mehr Funktionen als beim C64, so daž eine Emulation erleichtert wird. Dies liegt natrlich daran, daž diese beiden System ohnehin fr den Einsatz auf unterschiedlichen Computern vorgesehen sind, was beim C64 ja nicht unbedingt der Fall ist. Welches System man auch immer emulieren will, alle Aufgaben die ber solche Vektoren oder Funktionsnummern aufgerufen werden, mssen vom Emulator berwacht werden. Hierzu ein konkretes Beispiel: Fr die Ausgabe von Zeichen auf dem C64 existiert ein Sprungvektor BSOUT. Da die Bildschirmdarstellung der Zeichen auf dem ST grunds„tzlich anders realisiert wird als auf dem C64, muž an dieser Stelle eingegriffen werden. Die Ausgabe auf dem Bildschirm darf nicht so erfolgen, wie es beim C64 geschehen wrde, denn dann wrde sich auf dem Bildschirm des ST gar nichts tun. Sie muž in einer eigenen Ausgaberoutine programmiert werden. Das gleiche gilt fr viele andere Funktionen des Betriebssystems auf dem C64. Natrlich muž man darauf achten, daž die Register, die fr den Betrieb des Emulators wichtige Daten enthalten, nicht in den eigenen Routinen ver„ndert werden bzw. nur Daten erhalten, die vom Betriebssystem des C64 erwartet werden. Die LOAD-Routine soll z.B. das Ende des geladenen Programms als Rckgabewert in den Indexregistern liefern. Wie schon angesprochen, muž die Emulation durch geeignete Programmierung so gestaltet werden, daž sie m”glichst schnell erfolgt. Neben entsprechender Programmierung des Emulators gibt es noch weitere M”glichkeiten, die Geschwindigkeit von Programmen auf dem ST zu erh”hen, besonders dann, wenn es sich nicht um Programme handelt, die in GEM-Umgebung laufen. In diesem Fall ist es n„mlich m”glich, Vektoren, wie den evnt_timer-Vektor des GEM auf einen RTS-Befehl umzubiegen, so daž die zugeh”rigen Routinen, die w„hrend eines Interrupts ausgefhrt werden, nicht mehr angesprungen werden. Dies hat unter anderem zur Folge, daž die Uhr des Kontrollfelds nicht mehr l„uft, wenn der Emulator aktiv ist. Da man sie beim C64-Emulator ohnehin nicht ben”tigt, ist dies aber kein Beinbruch. Der Lohn dafr ist eine erh”hte Geschwindigkeit. Weiterhin kann es lohnenswert sein, die Maus abzuschalten, oder besser noch alle Aktionen, die den Tastaturprozessor betreffen, selbst zu bernehmen. Zum Schluž m”chte ich noch auf die Frage eingehen, inwieweit berhaupt eine Kompatibilit„t, insbesondere zum C64, auf einem anderen Computer zu erreichen ist. šberlegt man sich, was den C64 so erfolgreich gemacht hat, so sind dies in erster Linie die unz„hligen Spiele, die fr diesen Computer existieren. Gerade Spiele reizen die speziellen F„higkeiten des C64 (Sprites, Rasterzeilen-Interrupt, Timer) besonders aus. Da die Hardware des ST keine Darstellung von Sprites erlaubt, und diese Grafikobjekte nicht nur aus Zeitgrnden unm”glich durch Software nachgebildet werden k”nnen, ist die Emulation von Spielen also in Frage gestellt. Weiterhin ist es aus Zeitgrnden nicht m”glich, die Interruptroutinen des C64 durch den Emulator ausfhren zu lassen, da dann die Arbeitsgeschwindigkeit merklich nachlassen wrde. Schliežlich sind gerade Interrupts eine besonders zeitkritische Angelegenheit, da sie sehr h„ufig auftreten. Damit sind nun aber alle wesentlichen Einschr„nkungen bei der Emulation aufgefhrt. Ein Grožteil der Programme, die nicht auf die genannten M”glichkeiten zurckgreifen, kann vom C64-Emulator verarbeitet werden. Auch hochaufl”sende Grafik ist in einem gewissen Maže m”glich. Allerdings muž die Bitmap fr die Grafik im Bereich von $E000-$FFFF, also unter dem Betriebssystem liegen, damit der Emulator erkennt, wann der Grafikspeicher angesprochen wird. Da die šberprfung des Bildschirmspeichers und der Bitmap relativ viel Zeit in Anspruch nimmt, hat ein Verzicht auf diese Ausgabekontrolle eine weitere Erh”hung der Geschwindigkeit zur Folge. Neben der Emulation des C64 sind auch eine Drucker- und eine Floppy-Emulation, die der 1541 weitgehend entspricht, im Emulator enthalten. Das Programm l„uft brigens in niedriger und in hoher Aufl”sung, so daž jeder ST-Besitzer sich nun seinen eigenen C64 von Disk laden kann.