Forum: Mikrocontroller und Digitale Elektronik Z80 - Bytedefinition mitten in Assemblerdatei


von Bernd M. (berndmm)


Angehängte Dateien:

Lesenswert?

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.)

: Bearbeitet durch Moderator
von Zino (zinn)


Lesenswert?

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:
1
  add a,d
2
  jr nc,sq6
3
  res 5,d
4
  jp sq7
5
sq6:
6
  sub d
7
sq7:
8
  sra d

: Bearbeitet durch User
Beitrag #7926397 wurde vom Autor gelöscht.
von Zino (zinn)


Lesenswert?

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.

: Bearbeitet durch User
von Bernd M. (berndmm)


Lesenswert?

Danke, habs kapiert. Es geht also nur darum, sich das überspringen des 
"sub d"-Befehls zu sparen. Very tricky.

von 900ss (900ss)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

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.

von 900ss (900ss)


Lesenswert?

(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.

: Bearbeitet durch User
von Jochen (hermann_kokoschka)


Lesenswert?

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.

von Thomas W. (datenreisender)


Lesenswert?

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):
1
The first and last iteration are optimized as a special case, we work with the bitwise complement of the root instead of the root and small jumps are replace with overlapping code

So hard-core-Optimieren haette ich selbst in meiner Jugendzeit nicht 
gemacht, weil man den Code ein Jahr spaeter nicht mehr versteht. Aber: 
The Story of Mel (https://en.wikipedia.org/wiki/The_Story_of_Mel) war 
schon 1983 legendaer, siehe 
http://catb.org/jargon/html/story-of-mel.html

von Flunder (flunder)


Lesenswert?

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.

von Route_66 H. (route_66)


Lesenswert?

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.

: Bearbeitet durch User
von Soul E. (soul_eye)


Lesenswert?

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.

von Bradward B. (Firma: Starfleet) (ltjg_boimler)


Lesenswert?

> 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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von Axel S. (a-za-z0-9)


Lesenswert?

(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 :)

von Svensson (svensson)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.