utf-8 Juppi-FORTH für Neugierige ========================== Es war einmal ein Computerfreak, der sich in seiner Anfangszeit mit einer kleinen Plastikkiste namens ZX81 beschäftigte. Nicht etwa, weil er masochistisch veranlagt war (Ihr kennt die Tastatur...), sondern weil es gerade 1984 war und er als Zivi unter chronischem Geldmangel litt. Allerdings interessierte er sich auch für exotische Hard- und Software und so kam es, daß er sich ein Buch über FORTH besorgte. Irgendwie kam eins zum anderen und so setzte sich in seinem Kopf der Gedanke an einen nahen Verwandten des Zeddy fest. Leider war aber soviel anderes los, daß nichts Konkretes daraus wurde... Ihr habt es längst erraten, vom "Jupiter ACE" und mir die Rede. Damals habe ich aus lauter Neugier (und einer gewissen Portion Langeweile) das ZX81-ROM ausgelesen, disassembliert und kommentiert. Gut zehn Jahre später dachte ich, es sei langsam Zeit, noch nach dem "Jupiter ACE" zu suchen, bevor das Wissen über ihn völlig vergessen ist. So setzte ich eine Kleinanzeige auf, die für mich völlig überraschend sogar beantwortet wurde. Die ganze Sache endet jetzt damit, daß ich mich "journalistisch" betätige. Hinter mir liegt eine spannende Zeit der Schaltplan-Aufnahme, der Disassemblierung und der Kommentierung, über die ich berichten will. Der erste Schritt eines solchen Vorhabens besteht darin, sich einen Schaltplan und eine ROM-Datei zu beschaffen. Für die spätere Arbeit wirkt sich auch ein Handbuch stark beschleunigend aus. Dieser Schritt war für mich aufgrund des Kontaktes zum ZX-TEAM recht problemlos. Wenn das Objekt der Untersuchung auch real existierend zur Verfügung steht, bleiben keine Wünsche offen; hier aber gibt es heutzutage kleinere Probleme... Im zweiten Schritt muß der hoffnungsvolle "Re-Ingenieur" einen guten Disassembler zum Laufen bringen. Dies klingt leichter als es getan ist, denn wer hat heute noch einen Z80-Rechner? Zum Glück besteht meine private Rechneranlage aus einem Atari ST, zu dem es seit 1985 auch einen guten CP/M-Emulator gibt. Aus meinen Studenten-Zeiten hatte ich auch noch einen Satz CP/M-Programme, die darauf erstaunlich schnell laufen. Ich empfehle den REZ von Hank Kee, der im Original wohl von Ward Christensen stammt. Aber auch jeder andere Disassembler tut's. Der REZ hat den gravierenden Vorteil, daß er mich interaktiv die Art des Codes festlegen lassen kann. Als nächstes habe ich mir eine CP/M-Diskette erzeugt, die alle nötigen Tools und die binäre ROM-Datei enthielt. Flugs den REZ gestartet, die Datei geladen und die ersten Probleme kamen auf mich zu: wie zum *** sind denn die FORTH-Worte abgelegt - als FORTH-Compilat oder als Maschinencode? Wie sieht der Header eines FORTH-Wortes aus? Warum habe ich mich darauf eingelassen? Und wieso ist es überhaupt schon so spät in der Nacht??? Zum Glück hatte ich aber jenes FORTH-Buch, das mir in der Anfangszeit half. So konnte ich den ersten disassemblierten Quelltext erzeugen. Ein Probelauf durch das berühmt-berüchtigte Microsoft-Gespann M80/L80 (ja ja, solange gibt es diese "Firma" schon!) überzeugte mich, daß sich daraus wieder eine identische Binärdatei erzeugen läßt. Dieses Probeassemblieren führte ich hin und wieder aus, um sicherzustellen, daß ich keine Fehler einbaute, wenn ich REZ's Labels durch meine eigenen ersetzte oder z. B. Code in Daten umwandelte. Um die Hardware besser kennenzulernen, zeichnete ich nach den beiden vorhandenen Schaltplankopien einen neuen OrCAD-Schaltplan. Eigentlich wollte ich auch noch eine Platine daraus machen, aber dafür fehlt mir momentan die Motivation. Also, falls sich einer von Euch dazu berufen fühlt, ich kann ihm entsprechende Dateien zur Verfügung stellen. Bitte richtet Eure Anfragen an das ZX-TEAM, dort sind die bisher erstellten Daten gesammelt. Jetzt wollte ich mir etwas Schönes gönnen und suchte nach dem Zeichensatz. Aus der Betrachtung der binären Daten hatte ich gelernt, daß der Juppi anscheinend alle ASCII-Codes verwendem kann. Also mußten irgendwo die Bitmuster für 96 Zeichen liegen. Mit etwas Erfahrung war das ein leichtes Spiel und ich konnte etliche Labels aus dem Quelltext löschen, weil sie aus den als Maschinencode disassemblierten Bitmuster hervorgegangen waren. Das Stichwort "Label" ist bereits gefallen. Wie erkenne ich eigentlich, was eine Routine macht, um ihr einen sinnvollen Namen zu geben? Das ist wie vieles andere im Leben, ich brauchte eine ganze Zeit und viel Geduld, bis ich entsprechende Erfahrungen hatte. Daher empfehle ich jedem, der programmieren lernen möchte, sich Programme anderer Leute anzusehen! Darunter fällt auch das Disassemblieren, sogenanntes "re-engineering", aber selbstverständlich verurteile ich den Softwarediebstahl. Nach dem Nachvollziehen anderer Programme kommt natürlich das selbständige Programmieren dazu, um einmal in alle Fallen zu tappen. Nur wer Fehler macht, kann aus ihnen lernen und sie in Zukunft vermeiden. Beim Juppi-FORTH lassen sich viele Erkenntnisse aus den Namen der FORTH-Worte, die in den Headern stehen, ziehen. Wenn ich dann noch nachvollziehe, was die CPU macht, komme ich langsam dahinter. Dabei sind mir auch schon in anderen Programmen Fehler der Entwickler aufgefallen... aber weiter im Text. Aus dem Schaltplan konnte ich entnehmen, bei welchen Adressen welche Speicherbereiche liegen. Der Bildspeicher ist über zwei verschiedene Adressen erreichbar, die eine läßt u. U. kleine Blitze auf dem Schirm erscheinen, die andere bremst die CPU etwas aus (um genau diese Blitze zu verhindern - die CGA läßt grüßen!). An dieser Stelle muß ich einmal mit dem Vorurteil aufräumen, daß der Juppi drei Kilobyte Arbeitsspeicher hat. Ja, er hat soviel RAM eingebaut, aber nur ein Kilobyte davon ist Arbeitsspeicher, dafür aber auch das Ganze! Ein weiteres Kilobyte dient als Bildschirmspeicher und Zwischenspeicher und das letzte Kilobyte enthält die umkopierten Bitmuster für die Zeichendarstellung. Moment..., also muß ziemlich zu Anfang des Programms eine solche Routine liegen! Diese hat sich aber ziemlich gut versteckt, weil die Bitmuster recht platzsparend hauptsächlich nur mit sechs Byte im ROM liegen, aber acht Byte benötigen. Dazu kommt, daß der Zeichensatz von hinten her aufgebaut wird. Eine letzte Anmerkung dazu: ein Zeichensatz im RAM läßt sich für alles mögliche gebrauchen... Um möglichst schnell voranzukommen, versuchte ich zuerst die Low-Level-Funktionen zu finden. Im Wechsel mit der Untersuchung von Top-Level-Funktionen mit bekanntem Zweck läuft die ganze Sache meistens recht gut. So kam jetzt die Tastatur-Abfrage an die Reihe und die Tabelle der Tastaturcodes war schnell gefunden. Aus der Erfahrung mit dem ZX81 wußte ich bereits, daß dort die Tastatur über die Adreßleitungen A8 bis A15 gescannt wird - und so war es auch hier! Der "IN r,(C)"-Befehl des Z80 legt auf diese Adreßleitungen beim I/O-Zugriff den Inhalt des Registers B. Nach Umrechnung von Zeile und Spalte in einen Offset und nach Addition von weiteren Offsets für gedrückte Umschalttasten kann auf den Tastencode aus der Tabelle zugegriffen werden. Erfreut stellte ich fest, daß der Juppi die Tastatur im durch das Bildrücklaufsignal ausgelösten Interrupt abfragt. Dabei wird auch die Entprellung und ein Autorepeat (schön, nicht wahr?) durchgeführt. Interessant war, daß die gesamte Reaktion auf eine Taste noch im Interrupt erfolgt; d. h., daß erst ein "ENTER" außerhalb des Interrupts eine Aktion auslöst. Nun ging es weiter, indem ich versuchte, einzelne FORTH-Worte und Unterprogramme zu separieren. Nach einigen frustierenden Stunden gab ich es erstmal auf, die genaue Struktur eines Wortheaders herauszufinden. Stattdessen kommentierte ich die Integerarithmetik und fand dabei auch heraus, daß der Parameterstack in Richtung auf größere Adressen wächst und "zu Fuß" behandelt wird. Der Rücksprungstack für Maschinencode ist ganz normal implementiert (also mit CALL und RET) und der FORTH-Adreßzeiger wird darunter im CPU-Stack abgelegt. Der wichtigste Teil eines FORTH ist die NEXT-Routine. Weil sie nach jedem Wort aufgerufen wird, sollte sie möglichst schnell laufen und daher möglichst einfach sein. Durch intensives Anstarren des Textes fand ich gegen Ende vieler FORTH-Worte den Befehl "JP (IY)". Die Suche nach entsprechenden Ladebefehlen ließ mich an vier Stellen fündig werden, so daß ich auch wußte, an welche Adresse die CPU springt. Also benutzt der ACE das Register IY, um auf die NEXT-Routine zu springen. Warum aber wurde nicht die Adresse direkt im Code abgelegt? Nun, der ACE hat wie der ZX einen SLOW- und einen FAST-Modus. Mit letzterem wird aber nicht der Bildschirm abgeschaltet (von der Bilddarstellung ist die CPU völlig entlastet), sondern die Prüfung von Speicherüberlauf und BREAK-Taste. Der Geschwindigkeitsgewinn hält sich deutlich in Grenzen. Die FORTH-Worte "SLOW" und "FAST" speichern also die gewünschte Adresse im IY-Register - so einfach ist das! Zum Glück bekam ich etwa zu dieser Zeit das deutsche Handbuch, so daß mir einige Zusammenhänge viel klarer wurden. Wie schon oft berichtet, hat Steven Vickers damit ein weiteres Meisterstück abgeliefert, auch wenn der Übersetzer einige Macken "eingebaut" hat. Begeistert war ich, als dort einige Strukturen wie z. B. die Header und einige mir unbekannte Worte ausführlich erläutert wurden. Dagegen fand ich die Implementation der Fließpunktzahlen enttäuschend, dazu später mehr. Mit dem neuen Wissen ging die Arbeit wieder zügig voran. Ich fand heraus, daß das Register IX als Zeiger auf die Basis der Systemvariablen dient (so so, hätte ich mir auch denken können). Einige weitere Befehle mußten ihre Geheimnisse preisgeben, darunter die Konvertierungen für Text nach Zahlen und umgekehrt und einige "Spielfunktionen" wie BEEP und PLOT. Das schwerste Stück war der eigentliche Compiler. FORTH wird normalerweise rein interpretativ ausgeführt, aber es gibt einen sogenannten Compile-Modus. Darin werden gefundene Worte nicht ausgeführt (es sei denn, sie wären "immediate"), sondern ihre Adresse wird abgespeichert. Dieser Modus wird durch verschiedene Worte eingeschaltet, von denen das häufigste ":" ist. Der ACE besitzt zwei Bits ("Flags"), die anzeigen, ob ein neues Wort compiliert wird und ob der Compiler eingeschaltet ist. Mit Hilfe dieser Unterscheidung kann während einer Wortcompilation der Rechner z. B. Berechnungen ausführen, ohne dies erst zur Laufzeit tun zu müssen. Dieser Zusammenhang ist im Handbuch aber viel besser erklärt. Wie jetzt genau die Compilation und der Aufbau von FORTH-Worten in FORTH oder Maschinencode aussieht, ist an dieser Stelle nicht beschreibbar. Es würde schlicht den Rahmen sprengen. Ich möchte nur festhalten, daß die im ROM eingebauten Worte einen leicht anderen Header als die Benutzerworte im RAM haben. Der innere Aufbau ist zum Teil so stark abweichend, daß sich die Entwickler des ACE entschlossen, einfach alle ROM-Worte nicht listbar zu machen. Ansonsten läuft der Compiler ähnlich wie alle anderen FORTH-Compiler. Einen großen Teil des ROMs nehmen die Cassettenfunktionen ein. Der ACE unterscheidet zwischen zwei Sorten "Dateien", FORTH-Wörterbüchern und Binärdaten. Für beide Sorten gibt es Speicher-, Lese- und Vergleichsfunktionen. Intern werden beide Sorten praktisch gleich verwaltet, nur ein Byte im Vorspann unterscheidet beide Sorten. Beim Laden von Wörterbüchern muß der Computer "natürlich" einiges mehr an Arbeit leisten, um die neuen Worte korrekt abzulegen. Wie erwartet, werden zum Lesen und Vergleichen dieselben Routinen verwendet, die aufgrund eines Flags entscheiden, ob sie im RAM speichern oder vergleichen sollen. Der Computer speichert die Bits auf Band, indem er je nach Bitwert eine kurze oder lange Zeit wartet, bis er den digitalen Pegel wechselt. Soweit ich das übersehe, gibt es auch noch eine dritte Zeit, die zum Synchronisieren dient. Mit dieser Methode kann jeder Recorder verwendet werden, egal ob er bei Wiedergabe die Pegel invertiert oder nicht. Ich habe mir nicht die Mühe gemacht, genaue Zeiten auszurechnen. Wer alte Bänder auf anderen Rechnern lesen will, findet hoffentlich genügend Kommentare im Quelltext. Zum Schluß blieben mir nur noch die Fließpunktzahlen zum Kommentieren übrig. Wie versprochen, will ich hier etwas lästern... Die Jungs von "Jupiter Cantab Ltd." haben vier Byte in BCD-Codierung pro Zahl benutzt und erreichen damit eine Auflösung von sechs Stellen bei einem Bereich von ca. +/-1E-19 bis +/-1E+19. Wenn sie eine binäre Codierung (wie z. B. durch das IEEE vorgeschlagen) verwendet hätten, wäre eine Auflösung von knapp sieben Stellen bei einem Bereich von +/-1E-38 bis +/-1E+38 erreicht worden. Ich behaupte 'mal ganz frech, daß die Binärroutinen dazu auch nicht länger geworden wären als die BCD-Routinen. Was soll's, sie haben die Zahlen BCD-codiert, und die Kommentierung war auch nicht schwerer. Der Juppi hat im Gegensatz zum ZX81 (aber im Gleichklang mit anderen FORTH-Rechnern) außer den Grundrechenarten keine weiteren mathematischen Funktionen. Damit ist er nicht so sehr für allgemeine Probleme geeignet. Aber wie der "Erfinder" von FORTH, Charles H. Moore, sich das vor über zwanzig Jahren gedacht hatte, eignet sich die Sprache hervorragend für schnelle Programmierung von Steuerungsaufgaben. Dabei fördert das Prinzip der "Worte" sowohl das modulare Programmieren als auch den Austesten der eben programmierten Module. Das Interessanteste am Jupiter-FORTH ist für mich der Editor gewesen. Bei normalen FORTH-Systemen gibt es einen Massenspeicher in Form von Diskettenlaufwerken oder Festplatten. Darauf können die Rechner wahlfrei in Blöcken zugreifen. Diese Struktur schlägt sich im Begriff der "screen"s nieder, der bei solchen Systemen verwendet wird. Das jeweilige FORTH kennt einen Pufferbereich für einen oder mehrere Blöcke und nummeriert die Blöcke seiner Massenspeicher linear durch. Die Blockgröße beträgt übrigens ein Kilobyte, die sich schön in 16 Zeilen zu 64 Zeichen darstellen lassen. So schreibt der Programmierer auf festen Seiten seine Programme, die er einzeln speichert. Um ein Programm zu compilieren, wird der Compiler mit der ersten Screen-Nummer aufgerufen. Durch ein spezielles Wort "-->" am Ende eines Screens weiß der Compiler, daß der Text auf dem nächsten Screen weitergeht. Der gravierenden Nachteil dieses Prinzips ist, daß der Benutzer und nicht der Rechner für die Verwaltung des Massenspeichers zuständig ist... Bei einem Computer mit einer Cassette als Massenspeicher gibt es aber keine Screens! Deshalb kann der Juppi auch seine Programme nicht in solchen speichern. Stattdessen kann er aus seinen compilierten Worten wieder Texte machen. Wie jeder Juppibenutzer sagen kann, macht er das auch sehr schön, mit Einrückungen und Absätzen... und man kann diese Texte sogar editieren! Im Gegensatz zu einem Standard-FORTH wird das geänderte Wort nach Eingabe von "ENTER" nicht nur wieder kompiliert, sondern kann durch das Spezialwort "REDEFINE" auch das alte Original ersetzen. Die Worte "EDIT" und "LIST" unterscheiden sich übrigens intern nur geringfügig und benutzen praktisch den gleichen Code. So, das ist also mein Bericht... Ich hoffe, die Lektüre hat Euch etwas Spaß gemacht, wie mir die Beschäftigung mit diesem kleinen Wunderwerk. Bodo Wenzel, im September 1996