z80 Assembler Anfaengerfragen

  • z80 Assembler Anfaengerfragen

    Moins,

    ich lerne grade Z80-Assembler am Beispiel eines ROM-Monitors fuer meinen Z80-Selbstbau.
    Dabei stosse ich hin&wieder auf Fragen wie man bestimmt Dinge am besten in Z80-Assembler implementiert.
    Aktuell: Ich habe eine Kopierfunktion die bekommt drei Adressen uebergeben (Start, Ende und Ziel) und soll jetzt von Start bis Ende laufen.

    Wie kann ich am besten das Endekriterium abfragen? Ich habe keine Moeglichkeit gefunden zwei 16-Bit Register mit einander zu vergleichen.
    Meine derzeitige Loesung siehtwie unten aus, geht das eleganter? :hae:

    Quellcode

    1. ; ==========
    2. ; USAGE: c <SADDR> <EADDR> <DADDR> = copy bytes from SADDR-EADDR to DADDR
    3. ; saddr = BC
    4. ; eaddr = DE
    5. ; daddr = HL
    6. monitor_copy:
    7. ld a,(hl) ; load second char
    8. CP SPACE ; check if SPACE
    9. jp nz,monitor_usage ; nope -> no parameters?
    10. inc hl ; parameter start
    11. call console_gethex16 ; read addr to
    12. jp c,monitor_usage ; conversion error->usage
    13. push bc ; put saddr to stack
    14. ld a,(hl) ; load next char
    15. CP SPACE ; check if SPACE
    16. jr nz,mc.error1 ; nope -> no parameters?
    17. inc hl ; parameter start
    18. call console_gethex16 ; read addr to
    19. jr c,mc.error1 ; conversion error->usage
    20. push bc ; put eaddr to stack
    21. ld a,(hl) ; load next char
    22. CP SPACE ; check if SPACE
    23. jr nz,mc.error2 ; nope -> no parameters?
    24. inc hl ; parameter start
    25. call console_gethex16 ; read addr to
    26. jr c,mc.error2 ; conversion error->usage
    27. ld h,b ; put daddr to HL
    28. ld l,c
    29. pop de ; get other parameters
    30. pop bc
    31. mc.loop:
    32. ld a,b ; compare upper address bytes
    33. cp d
    34. jr nz,mc.cont ; not equal -> continue
    35. ld a,c ; compare lower address bytes
    36. cp e
    37. jr nz,mc.cont ; not equal -> continue
    38. jp monitor ; both equal -> end reached
    39. mc.cont:
    40. ld a,(bc) ; load byte
    41. ld (hl),a ; store at dest
    42. inc bc ; increment pointers
    43. inc hl
    44. jr mc.loop ; next
    45. mc.error2:
    46. pop bc ; pop second parameter
    47. mc.error1:
    48. pop bc ; pop first parameter
    49. jp monitor_usage
    Alles anzeigen
    Acht C64 haben auch 64-Bit...
  • LDIR und ähnliche Befehle verwenden. Die benötigen in HL, DE und BC ihre Daten und laufen automatisch bis zum Ende durch.
    Bei kleinen Schleifen kann man auch DJNZ verwenden.
    Schau mal auf der F64 Wolke nach Z80 Literatur. Da sollte genug in Deutsch vorhanden sein.

    cpcwiki.eu/index.php/Z80
    Mitwäre das nicht passiert! :prof:
    :syshack: Meine 3D-Drucker Teile auf thingiverse.com/oobdoo/designs :strom:
    Sammlung | 91 Bücher bzw. 25.261 Buchseiten mit Z80/8080 Power
  • ah danke, LDDR und LDIR waren mir bisher entgangen :)

    Literatur habe ich Offline und Online eine Menge, nur DEN passenden Befehl in einer 700-Seiten Referenz finden ist nicht ganz so einfach.
    Ausserdem warnt einen so ein Buch auch nicht vor Dummheiten *g*
    Acht C64 haben auch 64-Bit...
  • SuperIlu schrieb:

    Ich *hust* komme (wenn ueberhaupt) vom m68k. Seit dem habe ich zwar noch bruchstueckhaft SPARC und ARM Assembler gemacht, aber im Grossen und Ganzen nur C und aufwaerts :)
    Da kenn ich ein bisschen den Weg vom Z80 -> 68k und hatte mich damals sehr schwer getan.
    Mitwäre das nicht passiert! :prof:
    :syshack: Meine 3D-Drucker Teile auf thingiverse.com/oobdoo/designs :strom:
    Sammlung | 91 Bücher bzw. 25.261 Buchseiten mit Z80/8080 Power
  • Ich bin jetzt auch nicht gerade ein Hardcore-Z80-Coder und wage nur eher einfache Ratschläge diesbezüglich zu erteilen, aber vielleicht einige Anmerkungen zum Ganzen:
    • Das Parsing der Argumente: da wiederholt sich die Überprüfung auf BLANK immer wieder. Das würde ich auch in ein Unterprogramm auslagern. Da kann man dann auch in einer Schleife auch mehrere Leerzeichen akzeptieren, was die Syntax etwas entspannt. Einen Fehler kann man ja mit dem Carry retournieren und individuell behandeln.
    • Beim Kopieren sollte man generell auch eine etwaige Überlappung berücksichtigen und entsprechend "von oben" oder "von unten" kommend übertragen, sonst artet eine solche Aktion in eine mehr oder weniger arge Datenvernichtung aus.
    Beim Durchlesen der Frage fiel mir doch noch etwas zu der Endebedingung ein, weil ich das immer wieder gesehen habe bzw. auch für andere CPUs oft gilt: Für solche Schleifen am Besten einen Zähler für die Anzahl der Durchläufe (zu übertragenden Bytes) ermitteln. Denn kann man dann bequem runterzählen und relativ "billig" auf 0 überprüfen (wenn man nicht gerade LDIR und Verwandte verwendet), z.B.

    Quellcode

    1. loop:
    2. ...
    3. dec bc
    4. ld a,b
    5. or c
    6. jp nz,loop
    Es gibt auch noch die Speed-Variante. In BC ist die Anzahl, die für das Herunterzählen so korrigiert wird, dass der Unterlauf auf das High-Byte beim Übergang von 1 auf 0 (statt sonst von 0 auf 255) passiert.

    Quellcode

    1. ld a,b
    2. jp z,nocorr ; wenn Low-Byte <> 0
    3. inc c ; High-Byte korrigieren
    4. nocorr:
    5. ...
    6. loop:
    7. ...
    8. djnz loop ; Low-Byte herunterzählen
    9. dec c ; High-Byte herunterzählen
    10. jp nz,loop
  • Danke fuer die Anmerkungen.

    JeeK schrieb:

    Das Parsing der Argumente: da wiederholt sich die Überprüfung auf BLANK immer wieder. Das würde ich auch in ein Unterprogramm auslagern. Da kann man dann auch in einer Schleife auch mehrere Leerzeichen akzeptieren, was die Syntax etwas entspannt. Einen Fehler kann man ja mit dem Carry retournieren und individuell behandeln.
    Was wuerde das Unterprogramm im Ablauf verbessern? AFAIK verbraucht der SPACE-check 5-6 Byte, ein einzelnes CALL benoetigt schon 3 Byte, der bedingte Sprung wg. CF dann noch mal 2-3 Byte. Ausser der von Dir angesprochenen Funktionalitaet das ich auch mal mehrere SPACE weg'parsen' kann gewinne ich also nichts.
    Da ich auf absehbare Zeit der einzige Anwender fuer den Monitor bin kann ich auf die entspannte Syntax erst mal verzichten. Zumal ich das vermutlich eh sehr schnell mit Python scripten werde :)

    JeeK schrieb:

    Beim Kopieren sollte man generell auch eine etwaige Überlappung berücksichtigen und entsprechend "von oben" oder "von unten" kommend übertragen, sonst artet eine solche Aktion in eine mehr oder weniger arge Datenvernichtung aus.
    Du hast voellig recht, ich schau mal das ich eine Fallunterscheidung fuer SADDR>DADDR und SADDR<DADDR einbaue. Ich sehe mich naemlich schon Fehler jagen wenn ich das das erste Mal falsch benutze. Da hab ich ein Haendchen fuer :)


    JeeK schrieb:

    Beim Durchlesen der Frage fiel mir doch noch etwas zu der Endebedingung ein, weil ich das immer wieder gesehen habe bzw. auch für andere CPUs oft gilt: Für solche Schleifen am Besten einen Zähler für die Anzahl der Durchläufe (zu übertragenden Bytes) ermitteln. Denn kann man dann bequem runterzählen und relativ "billig" auf 0 überprüfen (wenn man nicht gerade LDIR und Verwandte verwendet)
    @oobdoo erwaehnte ja schon LDIR und LDDR. Die Kommandozeilensyntax mit SADDR und EADDR habe ich mir bei einem anderen Monitor abgeguckt und finde die recht nett so, deswegen habe ich mich fuer Endadressen statt Groessenangaben entschieden. Sowohl fuer die LDIR/LDDR, als auch fuer Deine Variante muesste ich jetzt die Endadresse erst "umstaendlich" in eine Groesse umrechnen.
    Dein Codebeispiel mit dec, ld und or habe ich an anderer Stelle (wo ich mit Groessen arbeite) genau so stehen :)
    Acht C64 haben auch 64-Bit...
  • Bitte, ich möchte niemanden überzeugen oder überreden etwas zu verwenden, das ist nur Anregungen. Es is tvöllig freigestellt es zu verwenden oder auch nicht. Außerdem sind diese öffentlich, können und sollen auch von anderen gelesen werden, die vielleicht ähnliche Fragestellungen haben und auf der Suche sind.

    SuperIlu schrieb:

    Was wuerde das Unterprogramm im Ablauf verbessern? AFAIK verbraucht der SPACE-check 5-6 Byte, ein einzelnes CALL benoetigt schon 3 Byte, der bedingte Sprung wg. CF dann noch mal 2-3 Byte. Ausser der von Dir angesprochenen Funktionalitaet das ich auch mal mehrere SPACE weg'parsen' kann gewinne ich also nichts.
    Da ich auf absehbare Zeit der einzige Anwender fuer den Monitor bin kann ich auf die entspannte Syntax erst mal verzichten. Zumal ich das vermutlich eh sehr schnell mit Python scripten werde
    Normalerweise geht es bei Monitoren eher sehr beengt von Platzanforderungen her zu (jetzt ungeachtet deiner konkreten Randbedingungen). Da darf ein Hinweis zur Faktorisierung schon gestattet sein. Wenn man vielleicht von mehreren Kommandos ausgehen würde, die alle dieses Parsing betreiben, dann multipliziert sich das entsprechend, selbst wenn nur ein Byte Ersparnis wäre. BTW, das INC HL könnte man auch reinpacken. Wie gesagt, es war eigentlich eine allgemeine Überlegung und keine auskalkulierte Lösung. Ein CALL mit JR braucht fix 5 Bytes im Vergleich zu den 6 (mit dem INC HL), oder? Die Faktorisierung, egal ob mit Unterprogrammen oder auch mit Makros (wenn es die Geschwindigkeit erfordern sollte) hat auch hinsichtlich Fehlerträchtigkeit beim Programmieren selbst seine Vorteile.


    SuperIlu schrieb:

    @oobdoo erwaehnte ja schon LDIR und LDDR. Die Kommandozeilensyntax mit SADDR und EADDR habe ich mir bei einem anderen Monitor abgeguckt und finde die recht nett so, deswegen habe ich mich fuer Endadressen statt Groessenangaben entschieden. Sowohl fuer die LDIR/LDDR, als auch fuer Deine Variante muesste ich jetzt die Endadresse erst "umstaendlich" in eine Groesse umrechnen.
    Dein Codebeispiel mit dec, ld und or habe ich an anderer Stelle (wo ich mit Groessen arbeite) genau so stehen
    Klar, @oobdoo hat es ja schon erwähnt. Es war nur eine notwendige Referenz auf seine Erwähnung am Rande. Es lag nicht in meiner Absicht die "Lorbeeren" @oobdoos irgendwie wegzuschnappen. :huh: Die Urheberrechte wird er wohl auch nicht auf die Worte einfordern können. :rolleyes:
    Das ist ja üblich, dass Monitore eine Start- und Endadresse für einen Bereich erwarten. Das schließt ja nicht aus, dass man die Länge berechnen kann. Umständlich? Der Z80 hat doch 16-Bit-Arithmetik, wo das ziemlich unaufwändig geht, oder. Aber selbst beim 6502, wo man das byte-weise machen muss, lohnt sich das. Wenn man wegen der Überlappung herumrechnet, dann braucht man die Länge ohnehin oder ergibt sich nebenbei.
    Ich hab ja nicht behauptet die Codebeispiele seien ein Novum, genau das Gegenteil (zumindest die 1. Variante, die 2. Variante hab in freier Wildbahn ehrlich gesagt noch nicht gesehen, wobei mir aber nur sporadisch Z80-Code unterkommt und ich das in Wirklichkeit schlecht beurteilen kann), denn die 1. ist faktisch Standard. Es war ja Eingangs so gefragt, wie man denn das macht, wegen dem Endadressenvergleich - ich wäre fast geneigt zu sagen: gar nicht, wenn es nicht notwendig ist oder mit einem Zähler einfacher oder schneller geht. Aber das können Z80-Gurus vielleicht besser beurteilen. Tut mir leid, das ich mich erdreistet habe, etwas Konkretes anzugeben statt vielleicht noch ein Buch zur Lektüre zu empfehlen... :D
  • JeeK schrieb:

    Tut mir leid, das ich mich erdreistet habe, etwas Konkretes anzugeben statt vielleicht noch ein Buch zur Lektüre zu empfehlen...
    Du hast meine Antwort scheinbar falsch verstanden. Ich wollte nur meine Motivation und die Gruende fuer meine Art der Implementation darlegen und nicht kritisieren das Du Vorschlaege machst oder Alternativen anbietest. Ich bin Dankbar fuer Vorschlaege, ich moechte aber trotzdem darlegen wie und warum ich zu meinen Loesungen gekommen bin. Wenn meine Art der 'Diskussionsfuehrung' aneckt, dann tut mir das leid.


    JeeK schrieb:

    Das ist ja üblich, dass Monitore eine Start- und Endadresse für einen Bereich erwarten. Das schließt ja nicht aus, dass man die Länge berechnen kann. Umständlich? Der Z80 hat doch 16-Bit-Arithmetik, wo das ziemlich unaufwändig geht, oder.
    Genau solche Infos hatte ich mir aus der Diskussion erhofft. Das SBC mit 16-Bit Registern war mir voellig entgangen weil ich in den Referenzen immer nach 'sub' gesucht habe. Mit der Info sieht Dein Vorschlag gleich wieder anders aus.


    JeeK schrieb:

    [...] SPACE-Parsing [...]
    An Macros hab ich auch schon gedacht, 'Optimierungen' vertage ich aber auf 'spaeter' wenn ich die Funktionalitaet mal im Kasten habe. An die Version mit dem INC HL hab ich noch nicht gedacht, da muss ich noch mal genauer nachsehen. Auc hier gilt wieder: Ich hab nur meine Gruende dargelegt warum ich das mit der Unterfunktion nicht gemacht habe. Ich bin beim ersten Nachrechnen drauf gekommen das der Platzgewinn minimal bis nicht vorhanden waere und auch die Anzahl Codezeilen bei der SPACE Erkennung nicht sinkt. Ich wollte nur rausfinden ob Du evtl. noch mehr Potential siehst als ich.
    Acht C64 haben auch 64-Bit...
  • SuperIlu schrieb:

    Du hast meine Antwort scheinbar falsch verstanden. Ich wollte nur meine Motivation und die Gruende fuer meine Art der Implementation darlegen und nicht kritisieren das Du Vorschlaege machst oder Alternativen anbietest. Ich bin Dankbar fuer Vorschlaege, ich moechte aber trotzdem darlegen wie und warum ich zu meinen Loesungen gekommen bin. Wenn meine Art der 'Diskussionsfuehrung' aneckt, dann tut mir das leid.
    Offenbar. Kein Problem, ist schon gut so. ;)

    SuperIlu schrieb:

    Genau solche Infos hatte ich mir aus der Diskussion erhofft. Das SBC mit 16-Bit Registern war mir voellig entgangen weil ich in den Referenzen immer nach 'sub' gesucht habe. Mit der Info sieht Dein Vorschlag gleich wieder anders aus.
    Ja, irgendwie witzig, diese an diversen Stellen fehlende Symmetrie im Befehlssatz (was recht symptomatisch bei 8-Bittern zu sein scheint).
    ADD HL und ADC HL, dazu aber nur ein SBC HL ... (wenn ich richtig lese).

    SuperIlu schrieb:

    An Macros hab ich auch schon gedacht, 'Optimierungen' vertage ich aber auf 'spaeter' wenn ich die Funktionalitaet mal im Kasten habe. An die Version mit dem INC HL hab ich noch nicht gedacht, da muss ich noch mal genauer nachsehen. Auc hier gilt wieder: Ich hab nur meine Gruende dargelegt warum ich das mit der Unterfunktion nicht gemacht habe. Ich bin beim ersten Nachrechnen drauf gekommen das der Platzgewinn minimal bis nicht vorhanden waere und auch die Anzahl Codezeilen bei der SPACE Erkennung nicht sinkt. Ich wollte nur rausfinden ob Du evtl. noch mehr Potential siehst als ich.
    Naja, Makros schöpfen ihren Sinn eigentlich besonders daraus, wenn man sie beim Entwicklen gleich einsetzt ... wenn man es schon mal hat, dann ist es fast nur noch Kosmetik.
    Ja wie gesagt, es gibt ja auch andere Gründe, die ich angeführt habe, die eine Kapselung in Unterprogrammen rechtfertigt - auch ohne Platzgewinn. Schon alleine zur Fehlervermeidung, der Übersichtlichkeit wegen beispielsweise. Wie gesagt, jeder nach seinem Gusto. :D
  • ReTach,

    nach einigen Ausfluegen in die DOS-Welt und einem kurzen Urlaub hab ich mal wieder Zeit fuer den Z80 gehabt.
    Ich habe Eure Anregungen aufgegriffen und umgesetzt und will jetzt mal das Ergebnis praesentieren.

    Der Code ist zwar nicht unbedingt kleiner geworden, aber an einigen Stellen doch lesbarer/praegnanter.

    Quellcode

    1. ; ==========
    2. ; USAGE: c <SADDR> <EADDR> <DADDR> = copy bytes from SADDR-EADDR to DADDR
    3. ; SADDR = HL
    4. ; SIZE = BC
    5. ; daddr = DE
    6. monitor_copy:
    7. call monitor_chkspace ; check for SPACE
    8. jp c,monitor_usage
    9. call console_gethex16 ; read addr to
    10. jp c,monitor_usage ; conversion error->usage
    11. push bc ; put saddr to stack
    12. call monitor_chkspace ; check for SPACE
    13. jr c,mc.error1 ; nope -> no parameters?
    14. call console_gethex16 ; read addr to
    15. jr c,mc.error1 ; conversion error->usage
    16. push bc ; put eaddr to stack
    17. call monitor_chkspace ; check for SPACE
    18. jr c,mc.error2 ; nope -> no parameters?
    19. call console_gethex16 ; read addr to
    20. jr c,mc.error2 ; conversion error->usage
    21. ld d,b ; put daddr to DE
    22. ld e,c
    23. pop hl ; get EADDR
    24. pop bc ; get SADDR
    25. push bc
    26. or a ; clear CF
    27. sbc hl,bc ; EADDR-SADDR
    28. ld b,h ; put size to BC
    29. ld c,l
    30. pop hl ; put SADDR to HL
    31. ldir ; copy data
    32. jp monitor ; and exit
    33. mc.error2:
    34. pop bc ; pop second parameter
    35. mc.error1:
    36. pop bc ; pop first parameter
    37. jp monitor_usage
    38. ; ==========
    39. ; check if current char in (HL) is a SPACE.
    40. ; YES
    41. ; CF=0
    42. ; HL := HL+1
    43. ; NO
    44. ; CF=1
    45. ; return the chart in A
    46. monitor_chkspace:
    47. ld a,(hl) ; load next char
    48. CP SPACE ; check if SPACE
    49. jr nz,mcp.no ; nope
    50. inc hl
    51. scf ; CF=0
    52. ccf
    53. ret
    54. mcp.no:
    55. scf
    56. ret
    57. ; ==========
    58. ; print memory contents
    59. monitor_print_mem:
    60. call monitor_chkspace ; check for SPACE
    61. jr c,monitor_usage
    62. call console_gethex16 ; read addr to
    63. jr c,monitor_usage ; conversion error->usage
    64. ld d,b ; put addr to DE
    65. ld e,c
    66. call monitor_chkspace ; check for SPACE
    67. jr nc, mpm.read_size ; yep-> try to read size
    68. ld b,8 ; nope->use 8 bytes
    69. jr mpm.print_address
    70. mpm.read_size:
    71. call console_getdez ; read decimal
    72. jr c,monitor_usage ; conversion error->usage
    73. ld b,l ; use lower 8bits of value
    74. mpm.print_address:
    75. ld a,'$' ; print hex marker
    76. call console_putc
    77. ld a,d ; print upper address byte
    78. call console_print_hex
    79. ld a,e ; print lower address byte
    80. call console_print_hex
    81. ld a,':' ; print colon
    82. call console_putc
    83. ld c,9 ; store counter in C
    84. mpm.print_loop:
    85. dec c ; decrement line-counter
    86. jr nz,mpm.next_byte ; not 0 -> print next byte
    87. call console_newline ; print newline
    88. jr mpm.print_address ; and address again
    89. mpm.next_byte:
    90. ld a,' ' ; print SPACE
    91. call console_putc
    92. ld a,(de) ; get byte
    93. inc de ; point to next
    94. call console_print_hex ; print HEX
    95. djnz mpm.print_loop ; decrement counter
    96. call console_newline
    97. jp monitor
    Alles anzeigen
    Danke nochmal
    Ilu
    Acht C64 haben auch 64-Bit...
  • JeeK schrieb:


    Quellcode

    1. ld a,b
    2. jp z,nocorr ; wenn Low-Byte <> 0
    3. inc c ; High-Byte korrigieren
    4. nocorr:
    5. ...
    6. loop:
    7. ...
    8. djnz loop ; Low-Byte herunterzählen
    9. dec c ; High-Byte herunterzählen
    10. jp nz,loop
    Da fehlt ein "or a" nach dem "ld a,b" in Zeile 1 ;) LoaD-Befehle setzen auf dem Z80 keine Flags, mit Ausnahme von "ld a,i" und "ld a,r".