News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu

My journey into Z80 and CPC

Started by issalig, 18:55, 17 May 21

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

issalig

Hi all, I am learning z80 for CPC and I am documenting it. It is a "work in progress" document and I plan to incude RSX calls, foreground/background ROMs, and some programming for USIFAC.

If you are a noob like me it will help you, if you are a master you can help to improve it (I am sure it has a lot of errors).https://github.com/issalig/cpc/blob/main/doc/cpcz80adventures.md

redbox

#1
Quote from: issalig on 18:55, 17 May 21
I am learning z80 for CPC and I am documenting it. It is a "work in progress" document and I plan to incude RSX calls, foreground/background ROMs, and some programming for USIFAC.

The link above is incorrect, it should be: https://github.com/issalig/misc/blob/master/cpcz80adventures.md

Great to see you're learning Z80.  Prepare for your first optimisation lesson...


        TXT_OUTPUT      equ &bb5a   

        org      &1200          ; our code will start at &1200

main:                         
        ld      hl,message      ; load address of string in HL
        call    printString     ; print it
        ret

printString:
        ld      a,(hl)          ; load char index stored in HL into A
        or      a               ; if 0 then Z flag will be set
        ret     z               ; returns if Z flag is set
        inc     hl              ; hl=hl+1
        call    TXT_OUTPUT      ; call TXT_OUTPUT
        jr      printString

message:
        defb    "Hello World!",0


First thing is you need to put an extra RETurn in your main section so the assembly routine can return to BASIC or whatever other routine.  This is a good habit to get into.

Secondly, I've replaced the termination of the string with 0 instead of 255.  The CP 255 has been replaced with OR A (which is faster).  Can you work out how this works?  :)

issalig

#2
Quote from: redbox on 20:51, 17 May 21
First thing is you need to put an extra RETurn in your main section so the assembly routine can return to BASIC or whatever other routine.  This is a good habit to get into.

Secondly, I've replaced the termination of the string with 0 instead of 255.  The CP 255 has been replaced with OR A (which is faster).  Can you work out how this works?  :)
[EDIT] @redbox, I like the main structure with final ret, but as I already had one ret I do not see why I need an extra one.

So OR r takes 4 cycles vs CP n that takes 7 cycles like OR n   (http://map.grauw.nl/resources/z80instr.php)

Thanks for your advices, I will also include this information to the guide.


redbox

Quote from: issalig on 21:34, 17 May 21I like the main structure with final ret, but as I already had one ret I do not see why I need an extra one.

The Chibiakumas routine relies on using the firmware jump (JP PrintChar) in the NewLine subroutine to return you to BASIC.  This is bad practice and will only cause you headaches later on!

Also, the NewLine routine is redudant because you can include the control codes for CR and LF in your message string.

Here's an example of calling the printString subroutine twice - if you don't include the RET here after calling them, the printString routine would be run again after main had printed the message2 string:


        TXT_OUTPUT      equ &bb5a   

        org      &1200          ; our code will start at &1200

main:                         
        ld      hl,message      ; load address of string in HL (contains CR and LF in string)
        call    printString     ; print it
        ld      hl,message2
        call    printString
        ret                     ; exit main loop (to BASIC or other z80 routine)

printString:
        ld      a,(hl)          ; load char index stored in HL into A
        or      a               ; if 0 then Z flag will be set
        ret     z               ; returns if Z flag is set
        inc     hl              ; hl=hl+1
        call    TXT_OUTPUT      ; call TXT_OUTPUT
        jr      printString

message:
        defb    "Hello World!",13,10,0
message2:
        defb    "Next Line!",0


Quote from: issalig on 21:34, 17 May 21So OR r takes 4 cycles vs CP n that takes 7 cycles like OR n

Yes, it's quicker and also 1 byte smaller.  OR r when r is 0 has the effect of setting the Z flag so we can make use of RET Z.

m_dr_m

Quote from: issalig on 21:34, 17 May 21So OR r takes 4 cycles vs CP n that takes 7 cycles like OR n 
Z80 cycles are relevant up to a certain point. For CPC specifically, oddly enough, I recommend:
https://64nops.wordpress.com/2021/01/13/perfectly-accurate-z80-flags-and-cpc-timing/

Beware premature optimisation anyway!  The `or a` is fine since idiomatic anyway.



issalig

Quote from: redbox on 11:26, 18 May 21
The Chibiakumas routine relies on using the firmware jump (JP PrintChar) in the NewLine subroutine to return you to BASIC.  This is bad practice and will only cause you headaches later on!
...

Here's an example of calling the printString subroutine twice - if you don't include the RET here after calling them, the printString routine would be run again after main had printed the message2 string:

Thanks, now I got it,   :doh: So if we hare here is because someone CALLed and  RET is the best friend of CALL, so they should be always go together :).
If a RET is missing, PC will increment and execute code until a RET is found and behavior will be unexpected. Doesn't it?

redbox

Quote from: m_dr_m on 12:38, 18 May 21
Beware premature optimisation anyway!  The `or a` is fine since idiomatic anyway.

I agree!  It was meant more as tongue in cheek because it's common practice (null terminated string).

Quote from: issalig on 12:59, 18 May 21So if we hare here is because someone CALLed and RET is the best friend of CALL, so they should be always go together :).

I really like that way of saying it, RET is the best friend of CALL.  I will use that saying in future!

Quote from: issalig on 12:59, 18 May 21If a RET is missing, PC will increment and execute code until a RET is found and behavior will be unexpected. Doesn't it?

Exactly.

andycadley

Quote from: issalig on 12:59, 18 May 21
Thanks, now I got it,   :doh: So if we hare here is because someone CALLed and  RET is the best friend of CALL, so they should be always go together :) .
If a RET is missing, PC will increment and execute code until a RET is found and behavior will be unexpected. Doesn't it?


Strictly speaking, the CPU doesn't care if CALLs and RETs pair up. The issue is really only what is theoretically on the top of the stack. If you CALL a routine, the return address gets implicitly PUSHed onto the stack and at some point you need to either POP it off, manipulate the stack pointer or do a RET (which implicitly POPs the result).


It's not uncommon, if the last thing before a RET is a CALL, to optimise the process by replacing the CALL with a JP and relying on the RET at the end of the second routine to handle effectively RETurning from both routines. In cases of tail recursion, this can in fact be critical to avoiding stack overflow.


Abusing the stack is generally a more advanced Z80 technique though, so not something you want to rush into.

Powered by SMFPacks Menu Editor Mod