Coredump

Optimierungen

Auf einem langsamen Prozessor wie dem Z80 ist es unerlässlich, über die Ausführungszeit nachzudenken. Oft ist ein sauberer Ansatz zu langsam, und man muss den Code optimieren, um ihn viel schneller zu machen.

Die ZX Spectrum Bildschirm-Bitmap ist nicht linear. Die 192 Pixelzeilen sind in drei Abschnitte von 64 Pixelzeilen unterteilt. In jedem dieser Abschnitte kommen zuerst alle 8 ersten Pixelzeilen, gefolgt von den zweiten Pixelzeilen und so weiter. Der Vorteil ist, dass man beim Schreiben von Zeichen in die Bitmap nur das H-Register inkrementieren muss, um die nächste Bitmap-Zeile zu erreichen. Der Nachteil ist, dass eine pixelgenaue Adressberechnung die Hölle ist.

So werden die Koordinaten eines Pixels auf die Adresse abgebildet:

HL
1514131211109876543210
010Y7Y6Y2Y1Y0Y5Y4Y3X7X6X5X4X3

X2, X1 und X0 repräsentieren die Bitnummer an der Adresse. Sie können als Zähler für Rechts-Shift-Operationen verwendet werden.

Mein erster Versuch war ein geradliniger Code, der die Bitgruppen verschob, maskierte und an die richtigen Stellen bewegte. Er benötigte 117 Zyklen. Das ist nett, aber wir können es besser machen.

Wir brauchen viele Rotationsoperationen, um die Bits an die richtige Position zu schieben. Die Rotation ist eine ziemlich teure Operation auf einem Z80, da es keine Befehle gibt, die um mehr als ein Bit auf einmal rotieren. Meine Idee war, die X-Koordinate durch 8 zu teilen (indem ich sie dreimal nach rechts rotiere) und gleichzeitig Y3 bis Y5 in das L-Register zu schieben. Mit einem ähnlichen Trick konnte ich Bit 14 während des Rotierens setzen, was mir eine weitere or-Operation mit einer Konstanten ersparte.

Dies ist der finale optimierte Code. Er nimmt die X-Koordinate im C-Register und die Y-Koordinate im B-Register. Die Bildschirmadresse wird im HL-Registerpaar zurückgegeben. BC und DE bleiben unverändert, also gibt es keinen Bedarf für teure push- und pop-Operationen.

pixelAddress:   ld      a, b
                and     %00000111
                ld      h, a    ; h enthält Y2-Y0
                ld      a, b
                rra
                scf             ; Bit 14 setzen
                rra
                rra
                ld      l, a    ; l enthält Y5-Y3
                and     %01011000
                or      h
                ld      h, a    ; h ist jetzt komplett
                ld      a, c    ; X durch 8 teilen
                rr      l       ; und Y5-Y3 hineinrotieren
                rra
                rr      l
                rra
                rr      l
                rra
                ld      l, a    ; l ist jetzt komplett
                ret

Er benötigt nur 108 Zyklen, inklusive ret. Die Optimierung hat mir 9 Zyklen (oder etwa 8%) gespart. Das klingt nicht nach viel, aber wenn der Code in einer Schleife aufgerufen wird, werden diese 9 Zyklen mit der Anzahl der Schleifendurchläufe multipliziert.

Ich behaupte, dies ist die schnellste Lösung, ohne auf eine Lookup-Tabelle zurückzugreifen. Versuch mich zu schlagen! 😁

ZX Spectrum Retro-Spiele-Programmierung

Wenn du ein Kind der 1980er Jahre bist, erinnerst du dich vielleicht an den Sinclair ZX Spectrum. Er war ein erschwinglicher Heimcomputer, der an ein Farbfernsehgerät angeschlossen werden konnte und Kompaktkassetten als Massenspeicher nutzte.

Mein erster Computer war ein Sinclair ZX-81. Ich habe darauf BASIC und auch Z80-Assembler gelernt. Bald wurde der ZX-81 durch einen ZX Spectrum ersetzt. Ich habe viel programmiert, alle möglichen Tools und ein paar Demos geschrieben. Ich wollte immer zusammen mit meinen Freunden ein Spiel schreiben, aber als Teenager fehlte uns die nötige Ausdauer, um so ein Projekt zu Ende zu bringen. Als ich dann meinen Amiga 500 bekam, verlor ich schnell jedes Interesse an meinem guten alten Spectrum.

Aber es ist nie zu spät… Ich habe gerade ein winzig kleines Spielprojekt namens Coredump gestartet, geschrieben in Z80-Assembler für den guten alten ZX Spectrum. Warum? Einfach weil ich es kann. Weil ich es immer wollte. Und weil Retro-Programmierung auch eine Menge Spaß bedeutet!

Dieser erste Artikel handelt von der Toolchain, die ich verwende. Ich werde weitere Artikel hinzufügen, während das Spiel wächst und (hoffentlich) eines Tages fertiggestellt wird.

Damals in den 80er Jahren war die Assembler-Programmierung auf dem ZX Spectrum eine sehr mühsame Aufgabe. Ich musste mich mit Kassettenbändern (und ihrem sehr langsamen Zugriff) herumschlagen, einem Assembler, der bereits etwas von dem knappen RAM verbrauchte, und ich hatte keine Tools, die den Entwicklungsprozess vereinfachten. Wenn ich einen Fehler machte und der Spectrum abstürzte, musste ich den Assembler, den Quellcode und die Ressourcen neu von der Kassette laden. Oft verlor ich auch einen Teil meiner Arbeit, denn im Umgang mit Kassetten ist das Speichern eines Quellcodes viel mehr Arbeit, als nur Strg-S zu drücken, also riskierte ich lieber, die Änderungen nach einem Absturz neu eintippen zu müssen.

Heute ist es viel einfacher, Retro-Software zu schreiben. Ich kann sie auf meinem Linux-Rechner entwickeln, der sehr schnell ist und viel Speicherplatz hat. Ich verwende einen modernen Texteditor und viele mächtige Tools. Zum Testen muss ich nur eine Snapshot-Datei assemblieren und sie auf einem Emulator ausführen, was weniger als eine Sekunde dauert. Wenn der Emulator abstürzt, geht keine Arbeit verloren.

Das sind die Tools, die ich zum Programmieren verwende. Alle davon sind für Linux und MacOS verfügbar, einige auch für Windows.

  • Fuse ist ein exzellenter ZX-Spectrum-Emulator mit einem sehr präzisen Timing.
  • Ein ordentlicher Editor. Ich habe mit Atom angefangen, aber jetzt benutze ich Eclipse, weil es besser zu meinem Workflow passt. Nimm einfach deinen Lieblingseditor.
  • zasm ist ein netter Z80-Cross-Assembler, der auch in der Lage ist, SNA-Dateien zu generieren, die auf dem Emulator laufen.
  • Multipaint ist ein Open-Source-Zeichenprogramm, das mit den Einschränkungen der ZX-Spectrum-Grafik umgehen kann (und glaub mir, es gibt Einschränkungen). Es hat sich für die Erstellung von Sprites und Tiles als nicht so nützlich erwiesen, weil es keine präzise Kontrolle über die Papier- und Tintenfarbe bietet, die in der generierten Bildschirmdatei verwendet wird.
  • Daher verwende ich auch Gimp zum Pixeln von Sprites und Tiles. Vielleicht werde ich später auch Inkscape nutzen.
  • Tiled ist ein exzellenter Map-Editor. Ich benutze ihn, um die Welt meines Spiels zu entwerfen.
  • Einige selbstgeschriebene Hilfsprogramme konvertieren die Grafiken und die Welt in das Binärformat, das im Spiel verwendet wird. Ich verwende Java für diese Tools, einfach weil ich mich mit Java am besten auskenne. Es gibt keinen technischen Grund dafür, nimm einfach die Sprache, mit der du dich am wohlsten fühlst.
  • Zu guter Letzt verwende ich ant, um alle Teile zusammenzufügen und die Snapshot-Datei auszuführen.

Die Hardware des ZX Spectrum ist sehr einfach und leicht zu verstehen (was auch bedeutet, dass du viele Dinge ohne Hardware-Unterstützung machen musst). Der Z80-Prozessor hat einen einfachen Befehlssatz. Retro-Programmierung ist also nicht nur etwas für die Älteren, sondern auch für die junge Generation, die sich für einen ersten Einstieg auf die Hardware-Ebene von Computern interessiert. Es macht auch Spaß, das Maximum aus einer begrenzten und langsamen Hardware herauszuholen.

Es gibt eine Menge Dokumentation im Netz:

  • World of Spectrum hat viel Hardware-Dokumentation in der Rubrik references.
  • Ein kurzer Überblick über alle Z80-Befehle und ihre Timings.
  • Ein kommentiertes ROM-Disassembly bietet einen ersten Blick auf den Z80-Assembler und enthält auch einige nützliche Funktionen (wie Multiplikation; der Z80 selbst bietet keine Multiplikations- oder Divisionsbefehle).

Als ich anfing, nach Ressourcen für den ZX Spectrum zu suchen, war ich überrascht, wie aktiv die Retro-Szene ist. Es gibt viele Blogs, die Tutorials anbieten, die Hardware-Tricks erklären, und es gibt sogar eine Demoszene, die dir Dinge zeigt, von denen du nie gedacht hättest, dass sie auf dieser Maschine möglich sind. Immerhin ist der Speccy jetzt fast 35 Jahre alt und war selbst zu seiner Zeit nicht für eine leistungsstarke Hardware berühmt.