Nachdem ich letzten Beitrag einmal ausprobiert habe, was ein vollkommen nackter Altair so alles anstellen kann, wenn man ihn mit Deposit und Examine in Maschinencode programmiert, möchte ich dieses Mal das beliebte „Hello, World!“ auch mit diesem Barebone-Rechner ausgeben.
Zuerst einmal das Ergebnis – das beliebte „Hello, World!“ – ausgegeben auf unserem simulierten Altair 800, von einem Intel 8080 Maschinencode-Programm, das insgesamt nur 19 Byte lang ist – vermutlich ein Rekord, was die Kürze des Objektcode des Programmes angeht:
In Fortsetzung des letzten Beitrags habe ich zunächst einmal ausgeknobelt, wie man auf einem Altair ohne Betriebssystem überhaupt etwas ausgeben kann. Voraussetzung dafür ist, dass wir (virtuell) ein Terminal anschließen. Faktisch ist das früher zunächst eine Art Fernschreiber gewesen. Zum Anschließen einer solchen Peripherie wurde der Altair von Anfang an mit einer Serial-IO-Karte (SIO) mit zwei Kanälen ausgeliefert.
Um jetzt das Terminal anzuschließen, müssen wir in die Konfiguration des SIMH-Altairs eingreifen:
; Keyboard und Terminal
SET SIO PORT=10/0/1/0/2/T/3/F
SET SIO PORT=11/0/1/0/2/T/3/T
Damit wird der erste Port mit der Portnummer 10h am ersten Kanal angeschlossen und die Übertragungsparameter eingestellt. Das Ausgabeflag ist die letzte Stelle des Befehls und für Port 10h wird die Ausgabe auf F=False und für Kanal 11h auf T=True gesetzt. SIMH verbindet diese Ports dann mit der laufenden SIMH-Konsole als Ein- und Ausgabemedium.
Das eigentlich Programm ist ziemlich gradlinig gestaltet. Das Literal, das ausgegeben werden soll, ist als Folge von Bytes entsprechend der ASCII-Codes des Strings im Speicher ab Adresse 0300h abgelegt. Wie beim letzten Mal verändern wir den Speicher mit einer Folge von D(eposit)-Befehlen:
D 300 48
D 301 65
D 302 6C
D 303 6C
D 304 6F
D 305 2C
D 306 20
D 307 57
D 308 6F
D 309 72
D 30A 6C
D 30B 64
D 30C 21
D 30D 00
Das Null-Byte am Ende ist absichtlich dort eingefügt, damit das Programm nicht all zu starr wird. So kann damit im Prinzip jeder beliebige String ausgegeben werden, der ab 0300h im Speicher liegt und mit einem Null-Byte endet. Insofern stimmt aber auch meine Aussage über die Größe des Objektcodes nicht ganz, da der Fairness halber die Bytes des Ausgabestrings mit dazu gezählt werden müssen.
Wie funktioniert nun das Programm? Mit ein paar Kommentaren lässt sich das anhand des Quellcodes recht gut nachvollziehen:
RESET ALL
SET CPU 8080
SET CPU MEMORY=1K
;Keyboard und Terminal
SET SIO PORT=10/0/1/0/2/T/3/F
SET SIO PORT=11/0/1/0/2/T/3/T
; Das Programm sendet mit Hilfe des Befehls OUT den Inhalt des Akkumulators an den angegebenen Port
; Der Akkumulator wird Zeichen für Zeichen aus dem Speicher ab der Startadresse 0300h geladen
; Die Addressierung erfolgt indirekt über eine 16-Bit-Adresse, die sich aus dem Inhalt des Registerpaars B/C ergibt
: Solange kein Null-Byte gelesen wurde, wird das gelsene Byte ausgegeben und der Inhalt des Registerpaars B/C um eins erhöht
D 200 FB ; EI = enable Interrupts
D 201 06 ; MVI B
D 202 03 ; Hi-Byte 030h von 0300h in B
D 203 0E ; MVI C
D 204 00 ; Lo-Byte 000h von 0300h in C
D 205 0A ; .LOOP --> LDAX B Load Akku mit Inhalt der durch B,C adressierten Speicherzelle
D 206 FE ; CPI: Vergleiche Akku-Inhalt mit dem Folgenden Wert (00h). Dann wurde das letzte Zeichen gelesen (null-terminierter String)
D 207 00 ; 00h als Vergleichswert
D 208 CA ; JZ = springe zum Ende (.ENDE). Der Jump-Befehl fragt das Zero-Flag (Z) ab und springt, wenn Z gesetzt
D 209 11 ; Adresse Programmende Lo-Byte 011h von 0211h
D 20A 02 ; Adresse Programmende Hi-Byte 002h von 0211h
D 20B D3 ; OUT 11h = Andernfalls das Byte aus dem Akku ausgeben
D 20C 11 ; Ausgabekanal 11h
D 20D 03 ; INX B = Registerpaar B/C hochzählen
D 20E C3 ; JMP springe zurück zum Einlesen des nächsten Bytes (.LOOP)
D 20F 05 ; Lo-Byte 05h des Sprungziels 0205h
D 210 02 ; Hi-Byte 02h des Sprungziels 0205h
D 211 F3 ; Sprungziel .ENDE. DI = disable Interrupts
D 212 76 ; HLT = Programmende
; Text ab 300h
D 300-3FF 0 ; Daten löschen
D 300 48 ; H
D 301 65 ; e
D 302 6C ; l
D 303 6C ; l
D 304 6F ; o
D 305 2C ; ,
D 306 20 ; _
D 307 57 ; W
D 308 6F ; o
D 309 72 ; r
D 30A 6C ; l
D 30B 64 ; d
D 30C 21 ; !
D 30D 00 ; \0
Das alles wurde nicht mit Hilfe eines Assemblers, sondern mit einer Übersetzung des Programmablaufs in Maschinencodes mit absoluter Adressierung von Hand programmiert. So wie damals bei den ersten Altair-Rechnern mit den Schaltern am Frontpanel.