Hallo,
ich bin auf
https://wikiti.brandonw.net/index.php?title=Z80_Routines:Math:Square_root
auf ein Assemblerprogramm zur Wurzelberechnung gestoßen. Dabei ist mir
Bytedefinitionen mitten im Programm aufgefallen, auf die ich mir keinen
Reim machen kann, hier ein kurzer Ausschnitt, das ganze Programm im
Anhang:
1
add a,d ; 4
2
jr nc,sq6 ; 12 / 7
3
res 5,d ; 8
4
db 254 ; 7
5
sq6:
6
sub d ; 4
7
sra d ; 8
Mann kann ja einen Befehl durchaus auch als Byte definieren, aber hier
macht das irgendwie keinen Sinn: 254 ist FEh und CMP n. "n" ist dann
"sub d", also 92h. Kann sich da jemand einen Reim drauf machen, ich
würde das gerne verstehen.
(Edit: code tags eingebaut - Mod.)
Das war früher so üblich, um die Codegröße zu reduzieren. Damit wird
"sub d" ignoriert (cp setzt nur die Flags) und man spart sich den Sprung
auf "sra d".
In lang sähe das so aus:
Nachtrag: Um zwei Bytes zu ignorieren wurde z.B. 01h (ld bc,n)
verwendet.
Es gab auch mal einen Assembler, bei dem konnte man das direkt
formulieren, das sah ungefähr so aus:
1
cp a, {sq6:sub d}
Ach ja, man spart auch (WIMRE, seitdem sind einige Jahrzehnte vergangen)
einen Taktzyklus gegenüber der Variante mit jp.
Zino schrieb:> Das war früher so üblich, um die Codegröße zu reduzieren
Also ob das so üblich war, weiß ich nicht. Es ist richtig "dreckig"
programmiert meiner Ansicht nach. Ich hab nie zu solch seltsamen
Methoden gegriffen (greifen müssen).
Gut lesbarer Code war mir damals mit Z80 Assembler auch schon wichtig.
Der oben gezeigte ist das definitiv nicht.
900ss schrieb:> Also ob das so üblich war, weiß ich nicht.
Kenne ich von 6502, da war das auch nicht so selten.
> Gut lesbarer Code war
... das mit einem Makro-Assembler. Notfalls schrieb man eben
db skip1byte
db skip2bytes
Eine andere Variante, bestens geeignet um Disassembler zu ärgern, waren
Parameter direkt hinter einem Unterprogrammaufruf. Sowas wie
call stringcompare
db 'word' - letztes Byte mit Bit 7 gesetzt
Ein 8 KB Zwerg-Compiler bestand fast nur aus sowas in der Art. Das
Unterprogramm hat sich den PC aus dem Stack geholt, inkrementell den
String abgegrast, und hinterher wieder zurück geschrieben. Bei 8080/Z80
konnte man die wichtigsten solchen Aufrufe um 2 Bytes kürzen, indem man
RST Opcodes an Stelle von CALL verwendete.
900ss schrieb:> Also ob das so üblich war, weiß ich nicht. Es ist richtig "dreckig"> programmiert meiner Ansicht nach. Ich hab nie zu solch seltsamen> Methoden gegriffen (greifen müssen).
du Glücklicher, ich wollte PC1500 relokatibel programmieren um es in
EEPROM in verschiedene Adressbereiche einbauen zu können.
Es gab aber kein jsr jump sub routine wo man mit ret/rts zurück kam!
Der aktuelle PC programm counter konnte auch nicht gelesen werden aber
man konnte ihn in Register schieben diese dann auslesen und mußte nur
noch vom Stack lesen und die Korrekturbytes am Stack manipulieren, waren
wohl 6 bytes und so konnte man mit jump branch vorwärts oder rückwärts
um x Byte springen um mit RTS oder RET genau da weiter zu machen von wo
man herkam.
(prx) A. K. schrieb:> das mit einem Makro-Assembler. Notfalls schrieb man eben> db skip1byte> db skip2bytes
Ja so wäre es schon deutlich besser wenn es denn sein muss zu solchen
Maßnahmen zu greifen.
Ein weiterer Grund für .DB-Anweisungen mitten im Code waren
etliche INOFFIZIELLE Befehle der Z80, die sich damit umsetzen ließen.
Natürlich war das brandgefährlich, da völlig unsicher war ob
jedes Derivat die auch unterstützte.
Bernd M. schrieb:> Danke, habs kapiert. Es geht also nur darum, sich das überspringen des> "sub d"-Befehls zu sparen. Very tricky.
Und wenn Du die Webseite, die Du verlinkt hattest, bis zum Ende gelesen
haettest, haettest Du die Webseite des Autors (John Metcalf,
http://www.retroprogramming.com) gefunden.
In seinem Blog-Eintrag
(http://www.retroprogramming.com/2017/07/a-fast-z80-integer-square-root.html)
findest Du die Herleitung des Algorithmus in C (Square-Root with
Bit-shifting, sehr schoenes Stueck Software) und die Erklaerung fuer die
Konstanten (fuer das Code-Overlapping):
Noch ein Grund war, zu verhindern, dass jede Nase sofort kapiert, was
der Code macht. Also zwischen Ende eines Unterprogramms und Anfang des
nächsten ein "db ErstesByteDreiByteBefehl" brachte damals noch den
Disassembler ins Schlittern.
Hallo!
Ich habe für einige kleine Z80-Systeme ganz ohne RAM Software
geschrieben und dennoch Unterprogramme trotz fehlendem Stack verwendet.
Für diese Funktion wurde ein Register (IX oder IY) missbraucht.
Also z.B.:
....
LD IX,lab100 ;Returnadresse
JMP up100 ;oder JR up100
lab100: LD a,xy... ;hier gehts nach der Subroutine up100 weiter
....
up100:
LD DE,para3...
....
JP (IX) ;statt RET
Es funktionierte problemlos in einem 8-fach EPROM-Programmer für 2716 in
der Serienproduktion zur Vervielfältigung aus einem Muster-2716.
Route_66 H. schrieb:> Ich habe für einige kleine Z80-Systeme ganz ohne RAM Software> geschrieben und dennoch Unterprogramme trotz fehlendem Stack verwendet.> Für diese Funktion wurde ein Register (IX oder IY) missbraucht.
RCA hat das auf die Spitze getrieben mit dem CDP1802 ("COSMAC"). Der
hatte 16 völlig gleichberechtigte Register, und jedes konnte
Programmzähler sein. Zum Springen hat man einfach das Register
umgeschaltet.
> So hard-core-Optimieren haette ich selbst in meiner Jugendzeit nicht> gemacht, weil man den Code ein Jahr spaeter nicht mehr versteht.
Ist halt die Frage, ob man das noch zum Algo optimieren zählen sollte,
oder eher unter die Rubrik "CPU-Architektur ausnutzen/"mißbrauchen".
Denn Architekturunabhängig ist so ein Code sicher nicht.
Nur bei so schmalen Datenbussen wie in der 8 bit Ära konnte man Code
überlappen, bei breiten Daten-Bussen geht das dann nicht mehr.
Mit Harvard Architektur hat man sogar getrennte Busse, kann also weiter
8 bit Daten schubsen mit bspw. 13 Bit Befehlscodebus (AVR?) .
IMHO lohn es sich eher die "mathematischen Tricks" (Numerik) zu
lernen/verstehen, um dann schnell eine ausreichend genaue Berechnung
runterzureissen; beispielsweise der Taschrechner von sinclair von 1973,
alle 4 Grundrechenarten aus gerade 320 ROM-Worten:
https://static.righto.com/calculator/sinclair_scientific_simulator.html
und ich frage mich heute noch wiese ein Mephisto Schachcomputer ohne
gross RAM ein PC-Schachprogramm auf 4 Mbyte schlagen konnte.
Bradward B. schrieb:> bei breiten Daten-Bussen geht das dann nicht mehr
Geht genauso, sofern die Befehle ein Byte–Stream sind, keine RISCs mit
fester Befehlslänge. Nur die Implementierung stolpert u.U. über die
eigenen Füsse und muss zeitraubend neu aufsetzen.
Disassembler ärgern kann man noch viel besser mit selbstmodifizierendem
Code, das habe ich noch auf dem x86 gerne gemacht. Für Assembler auf dem
Z80 oder 6502/10 bin ich zu jung.
Ben B. schrieb:> Disassembler ärgern kann man noch viel besser mit selbstmodifizierendem> Code,
Damit konnte man allerdings auch die Implementierer von Prozessoren ganz
böse ärgern.
(prx) A. K. schrieb:> Ben B. schrieb:>> Disassembler ärgern kann man noch viel besser mit selbstmodifizierendem>> Code,>> Damit konnte man allerdings auch die Implementierer von Prozessoren ganz> böse ärgern.
Naja, der Z80 oder 6502 hatten ja keinen Cache. Da konnte man sogar das
Byte nach dem gerade verarbeiteten Opcode überschreiben.
Ich kann mich lebhaft an einen Fall auf den 6510 (aka C64) erinnern, wo
der Code nur verschleiert war (EOR mit einem festen Byte). Ich habe da
lange gegrübelt wie der Prozessor die Schleife verläßt um den Code
anzuspringen. Dann fiel es mir wie Schuppen von den Augen: die
EOR-Schleife schloß sich mit einem relativen Sprung und verwendete auch
sonst selbstmodifizierenden Code um mehr als 256 Byte entschlüsseln zu
können. Sie lief allerdings rückwärts durch den RAM. So hat sie dann
irgendwann mal die Sprungdistanz des finalen BRAnches überschrieben. Und
das war der Moment wo die Schleife endete :)
Ich kenne es nur vom Hörensagen, aber die Geschichte ist zu schön, um
nicht erzählt zu werden.
Ein Kollege berichtete, daß sie früher auf der PDP (wahrscheinlich
PDP-11) einen kleinen Trick gemacht hätten, um den doch begrenzten
Speicher optimal zu nutzen.
In den Programmbefehlen gab es "ungenutze Bits", also solche, die für
den Programmablauf nicht benötigt wurden. Hier konnte man Daten
speichern - natürlich verteilt über mehrere Programmbefehle.
Also nicht nur selbstmodifizierender Code, sondern auch noch die
Vermischung von Daten und Programm...
Was aber genial an der PDP war, ist die Tatsache, daß sie vollständig
dokumentiert war. Ebenso die RS6000 von IBM. So habe ich damals ca. 30
Leitz-Ordner (es war natürlich eine andere Marke, die damals schon einen
Plastikeinband hatten, der fürchterlich gestunken hat) "geerbt", in den
massenweise Schaltpläne waren. Auch war jeder einzelne Befehl einzeln
haarklein dokumentiert mit allen Auswirkungen.
Und nein, die gibt es nicht mehr. Nachdem sie noch ein paar Jahre im
Serverraum herumstanden und ein Regal gefüllt haben, habe ich sie dann
entsorgt.