Z80 things that weren't obvious that you wish you'd known earlier

The place for codemasters or beginners to talk about programming any language for the Spectrum.
presh
Manic Miner
Posts: 237
Joined: Tue Feb 25, 2020 8:52 pm
Location: York, UK

Z80 things that weren't obvious that you wish you'd known earlier

Post by presh »

I'll start with these:

1. If you're using IM 2 so that you can use IY, you can't use some ROM routines (e.g. RST 16) without setting IY to 5C3A first

2. ADD HL, HL doesn't alter the Z flag, whereas ADC HL, HL does

3. The Z80 manual is actually a really useful programming resource and you really should have RTFM*

Please share your wisdom and collectively save us all from these black holes of faff :?

* I still haven't properly
User avatar
Bubu
Manic Miner
Posts: 542
Joined: Fri Apr 02, 2021 8:24 pm
Location: Spain

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Bubu »

Nice! Here there are mine:

- If you want to substract 16 bits registers, you have tu use carry flag with SBC HL, rr. So, to set CF to zero I used "SCF" + "CCF". Many years later I learnt that the same thing culd be done by "AND A"
- For IM 2 interrupt I used to set a 255 bytes array for the I address. But after much experience never needed it
- Using LD (HL), NN doesn't work, althoug some compilers eat it transforming it into LD (HL), N and doesn't print any error or bug. This made me crazy for months... Instead of this, use LD (NN), HL
If something works, don't touch it !!!! at all !!!
User avatar
Stefan
Manic Miner
Posts: 809
Joined: Mon Nov 13, 2017 9:51 pm
Location: Belgium
Contact:

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Stefan »

Bubu wrote: Mon Apr 19, 2021 9:24 pm - For IM 2 interrupt I used to set a 255 bytes array for the I address. But after much experience never needed it
The idea with IM2 is that I provides the high byte of a vector table. The low byte is provided by the data bus. This allows any device to request a "named" interrupt. To be useful, Zilog (or so) recommends that all devices that request an interrupt put an even byte on the data bus, allowing 128 interrupt vectors.

But... instead of using interrupts sensibly, various devices just polluted the data bus with junk, making sensible interrupts impossible. Since you cannot know what junk is being put on the data bus, I needs to point to a table of 257 identical bytes to guarantee the same address always being called regardless of what other hardware is hooked up.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Ast A. Moore »

presh wrote: Mon Apr 19, 2021 8:58 pm 1. If you're using IM 2 so that you can use IY, you can't use some ROM routines (e.g. RST 16) without setting IY to 5C3A first
That’s a Spectrum’s ROM feature, not a Z80 one. Most ROM routines rely on system variables, which are addressed through the IY register plus an offset. Naturally, if you change the value stored in IY, the offsets will be off as well.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
1024MAK
Bugaboo
Posts: 3123
Joined: Wed Nov 15, 2017 2:52 pm
Location: Sunny Somerset in the U.K. in Europe

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by 1024MAK »

Stefan wrote: Mon Apr 19, 2021 10:20 pm
Bubu wrote: Mon Apr 19, 2021 9:24 pm - For IM 2 interrupt I used to set a 255 bytes array for the I address. But after much experience never needed it
The idea with IM2 is that I provides the high byte of a vector table. The low byte is provided by the data bus. This allows any device to request a "named" interrupt. To be useful, Zilog (or so) recommends that all devices that request an interrupt put an even byte on the data bus, allowing 128 interrupt vectors.

But... instead of using interrupts sensibly, various devices just polluted the data bus with junk, making sensible interrupts impossible. Since you cannot know what junk is being put on the data bus, I needs to point to a table of 257 identical bytes to guarantee the same address always being called regardless of what other hardware is hooked up.
The problem being that rather too many hardware expansions/interfaces did not follow the guidance from Zilog (either due to the designer not knowing, ignoring or to keep the cost down) and will put data on the data bus during an interrupt acknowledgment cycle.

Mark
:!: Standby alert :!:
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb :dance
Looking forward to summer later in the year.
User avatar
Turtle_Quality
Manic Miner
Posts: 506
Joined: Fri Dec 07, 2018 10:19 pm

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Turtle_Quality »

On using IM2, I tried and gave up on that in the 80s, seemed to just run the interrupt once then hang. Was getting deju vu trying it last month till I realised that interrupts get disabled automatically when the interrupt is performed, so you have to enable them before exit. What had me fooled is most examples I've seen of the technique show DI as the first command in the interrupt routine, with EI just before the RET. I won't be needing that I thought...

And yes I'm only using IM2 so far to reclaim the IY register and save a few cycles.

Back in the day I thought all Z80 instructions were lightning fast and had no idea how to make code faster, apart from less instructions. PUSH and POPs all over the place, throwing IX+d around like confetti. I was trying not to use IX+d at all this time round, but now I see it's usually quicker to set IX to my defined labels area, and use one INC(IX+d) than to free up HL, LD HL,NN and INC (HL)

The lack of BIT A,r has always been a bit annoying, and I now see that my self modifying code solution to build the SET, BIT or RES command to order, well it's slower than doing up to 7 RLCA followed by an AND or OR instruction , as well as being harder to follow on any Assembler / Debugger. What is the fastest way to do these ?

But back in the day I wrote bits and pieces but I don't think I wrote any assembler over 300 bytes, the core of game I'm writing now nearly functional at just over 2k of code so far but there's lots to add so I'm sure I've still got a lot to learn. I've had 4 different methods of moving sprites on screen working so far and contemplating a 5th.
Definition of loop : see loop
User avatar
Stefan
Manic Miner
Posts: 809
Joined: Mon Nov 13, 2017 9:51 pm
Location: Belgium
Contact:

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Stefan »

Turtle_Quality wrote: Mon Apr 19, 2021 11:35 pm The lack of BIT A,r has always been a bit annoying, and I now see that my self modifying code solution to build the SET, BIT or RES command to order, well it's slower than doing up to 7 RLCA followed by an AND or OR instruction , as well as being harder to follow on any Assembler / Debugger. What is the fastest way to do these ?
I ran into this a few weeks back. I ended up following the self modifying code route:

Code: Select all

@flag.operation:

    ; a = flag [&00-&ff]

    ; c = opcode mask
    ;       %0100 0110 = bit
    ;       %1000 0110 = res
    ;       %1100 0110 = set

    ld h,flags.high

    ld l,a
    srl l
    srl l
    srl l

    and %00000111
    rlca
    rlca
    rlca
    or c
    ld (@smc.bit+1),a

@smc.bit:
    set 0,(hl)  ; or res n,(hl) or bit n,(hl)

    ret
Initially I didn't even bother and just spent an additional 224 extra bytes to store a flag per byte.
AndyC
Dynamite Dan
Posts: 1408
Joined: Mon Nov 13, 2017 5:12 am

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by AndyC »

Turtle_Quality wrote: Mon Apr 19, 2021 11:35 pm
The lack of BIT A,r has always been a bit annoying, and I now see that my self modifying code solution to build the SET, BIT or RES command to order, well it's slower than doing up to 7 RLCA followed by an AND or OR instruction , as well as being harder to follow on any Assembler / Debugger. What is the fastest way to do these ?
Usually I find it can be refactored to just do the relevant AND or OR instruction (depending on whether I need to SET or RESET the bit).
User avatar
Joefish
Rick Dangerous
Posts: 2059
Joined: Tue Nov 14, 2017 10:26 am

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Joefish »

That 8-bit INC and DEC affect the Z flag but not the C flag.
Also that the BIT instruction puts the bit in the Z flag but not the C flag, so you can't follow up by rotating it into another byte or use the SBC A trick for sign-extending.
User avatar
rastersoft
Microbot
Posts: 151
Joined: Mon Feb 22, 2021 3:55 pm

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by rastersoft »

RETI only sends the signals to notify that an ISR has ended, but DOESN'T enable the interrupts. If you aren't using daisy chained interrupts, you must use EI+RET.
User avatar
Morkin
Bugaboo
Posts: 3276
Joined: Mon Nov 13, 2017 8:50 am
Location: Bristol, UK

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Morkin »

Laughably simple things that might have convinced me to continue trying to learn assembly in the 80s and not giving up after 10 minutes:

LD isn't a free-for-all, e.g. you can't do LD BC,DE

You can use INC H to move down one pixel on screen

How quick it is compared to BASIC
My Speccy site: thirdharmoniser.com
Ralf
Rick Dangerous
Posts: 2288
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Ralf »

Some more general, not technical stuff:

1. You need really to write a lot of comments in your code. Don't listen to guys who claim that
your code should be "self-documenting". They haven't written in their lives a single line in assembler.
And they are usually trying to justify that they are just lazy ;)

2. Formatting of your code is also important

3. You can optimize your code by speed, by size but also by readability. Sometimes it's better to choose
a bit slower and longer but easier solution without all these cool tricks which you won't understand yourself
after a month.

4. Don't be overambitious. Start projects that you will be able to finish.
presh
Manic Miner
Posts: 237
Joined: Tue Feb 25, 2020 8:52 pm
Location: York, UK

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by presh »

Ralf wrote: Tue Apr 20, 2021 11:27 am Some more general, not technical stuff:

1. You need really to write a lot of comments in your code. Don't listen to guys who claim that
your code should be "self-documenting". They haven't written in their lives a single line in assembler.
And they are usually trying to justify that they are just lazy ;)

...

3. You can optimize your code by speed, by size but also by readability. Sometimes it's better to choose
a bit slower and longer but easier solution without all these cool tricks which you won't understand yourself
after a month.
100% agree with these!

I always start by writing the most readable code possible, then optimising once I know it's working (and keeping a backup of the original, just in case!)

Also I tend to plan my code out with pseudocode comments rather than diving straight in. Then it's "just" a case of translating the English into Z80. When something goes wrong, it's much easier to spot logic errors by being able to read what should happen at each point, instead of trying to figure it out from the (potentially erroneous) code.

Having a list of input, output and trashed registers at the top of CALLable subroutines is a huge time saver too. Much easier than digging through the code every time & trying to figure out which parameters it's expecting, what needs PUSH/POPing either side of the CALL, etc.
User avatar
lister_of_smeg
Microbot
Posts: 145
Joined: Thu Nov 16, 2017 1:44 pm

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by lister_of_smeg »

Turtle_Quality wrote: Mon Apr 19, 2021 11:35 pm The lack of BIT A,r has always been a bit annoying, and I now see that my self modifying code solution to build the SET, BIT or RES command to order, well it's slower than doing up to 7 RLCA followed by an AND or OR instruction , as well as being harder to follow on any Assembler / Debugger. What is the fastest way to do these ?
Something like this?

Code: Select all

	ld	h, table/256
	and	(hl) 
		
	align	256
table: 	db	1,2,4,8,16,32,64,128
Bit position in L
and (hl) for BIT
or (hl) for SET
or (hl); xor (hl) for RES

Or am I misunderstanding what you mean by BIT/SET/RES A,r? (I'm assuming r is a register containing 0-7)
User avatar
Turtle_Quality
Manic Miner
Posts: 506
Joined: Fri Dec 07, 2018 10:19 pm

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Turtle_Quality »

Perfect thanks
Definition of loop : see loop
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by ketmar »

in my youth i didn't had a proper manual, only a list of commands, so i was sure that most commands have equal timings, with memory r/w being very slightly slower than others. and i felt so smart seeing blocks of LDI in games and thought: "heh, they don't even know about LDIR. what a morons!" because CPU doesn't need to advance PC and read LDIR each time, and it must be faster than alot of LDI, right?
Ralf
Rick Dangerous
Posts: 2288
Joined: Mon Nov 13, 2017 11:59 am
Location: Poland

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Ralf »

in my youth i didn't had a proper manual, only a list of commands
I'm not so sure if proper manual is required. List of commands with timings and effects would be really enough.

Personally I would advise to avoid the well-known Rodnay Zaks book:
https://archive.org/details/Programming ... 1/mode/2up

In the past I have seen some people treating it like a kind of "Bible". Well, it isn't:

- the style is too dry and not suitable for a beginner
- the content is too close to hardware for a programmer's needs
-it lacks any references to Zx Spectrum

So if you want to learn asm, don't start with Zaks.
User avatar
ketmar
Manic Miner
Posts: 713
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by ketmar »

Ralf wrote: Tue Apr 20, 2021 4:46 pm
in my youth i didn't had a proper manual, only a list of commands
I'm not so sure if proper manual is required. List of commands with timings and effects would be really enough.
sure, but there weren't any timings. only the table with opcodes and mnemonics. i knew enough about computers to make sense of it, but was thinking that all operations were done in one or two ticks, regardless of complexity, and that opcode fetching is free.
User avatar
Bedazzle
Manic Miner
Posts: 305
Joined: Sun Mar 24, 2019 9:03 am

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Bedazzle »

Morkin wrote: Tue Apr 20, 2021 9:58 am You can use INC H to move down one pixel on screen
It isn't for the last byte of 8x8 cell.

P.S.
For the topic starter:
- register R for the random number generation must be used with care; given numbers are far from random - it is constantly increasing, and bit 7 is always the same
Tommo
Dizzy
Posts: 92
Joined: Sat Mar 20, 2021 3:23 pm

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Tommo »

For me it's mainly non-Z80-specific stuff that is nevertheless useful for low-budget computing in general, none of it especially original:

(a+b)^2 - (a-b)^2 = (a^2 + b^2 + 2ab) - (a^2 + b^2 - 2ab) = 4ab, so if you have 256 bytes to spare then a lookup table of x^2/4 lets you multiply 8-bit numbers very quickly.

Linear feedback shift registers (i.e. LFSRs) provide cheap random-number generators. E.g. (extemporaneous, please forgive errors):

Code: Select all

    CCF
    RRA
    JR NC, done
    
    XOR $AF
.done
If you seed A with 0 that'll have exactly one state. But if you seed A with anything other than 0, that'll proceed through the other 255 possible numbers in a non-obvious order. If you don't like $AF, use any of the other constants from here to ensure you hit all 255 states. You can seed with (R << 1) | 1 or something like that before each game.

If you have a large number of independent objects on screen then keep them sorted by x to decrease collision detection costs. In your sorted list do the equivalent of:

Code: Select all

    for(i = 0; i < num_objects; i++) {
        for(c = i+1; c < num_objects; c++) {
            if(obj[c].left > obj[i].right) break;
            
            // Collision test obj[c] and obj[j] here.
        }
    }
... and use insertion sort to maintain your sorted list, because the sorting on frame n is probably also the proper sorting on frame n+1, or if not then it'll be very close, and insertion sort will detect that in linear time and is likely to fix discrepancies relatively quickly.

If your memory footprint allows it, you can use the Minkowski sum of two objects to do pixel-perfect collision detection with a single lookup. It's a separate lookup table for each pair of objects though, and if the objects are (a, b) and (c, d) in size then the table needs to contain (a+c)*(b+d) bits — if you can't be bothered with the mathsy stuff, just figure that it's a lookup table of whether a collision has happened given the relative position of the sprites, after you've done the basic bounding box stuff.

On the plus side, it's an easy thing to compute at runtime so if you have a flick-screen JSW-style game in mind then you need reserve only a few slots for those tables and can calculate just the ones you need during each screen transition.
User avatar
Morkin
Bugaboo
Posts: 3276
Joined: Mon Nov 13, 2017 8:50 am
Location: Bristol, UK

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Morkin »

Bedazzle wrote: Wed Apr 21, 2021 1:53 pm
Morkin wrote: Tue Apr 20, 2021 9:58 am You can use INC H to move down one pixel on screen
It isn't for the last byte of 8x8 cell.
Yep, good point..! :oops:
My Speccy site: thirdharmoniser.com
presh
Manic Miner
Posts: 237
Joined: Tue Feb 25, 2020 8:52 pm
Location: York, UK

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by presh »

HL is much more useful than BC/DE.

I don't know why it took me so long to realise this, but anything you can do with a register can be done with (HL)
and conversely, none of the following can be done with (BC) or (DE)

e.g.

Code: Select all

LD D, (HL)
LD (HL), number
OR (HL)
INC (HL)
BIT b, (HL)
SET b, (HL)
And because some instructions modify the value at the same time - e.g. INC (HL) - it allows for faster AND smaller code. Consider:

Code: Select all

LD HL, lives    ; 10/3
DEC (HL)        ;  4/1
JP Z, game_over ; 10/3
; 24 Ts, 7 bytes
vs

Code: Select all

LD A, (lives)   ; 13/3
DEC A           ;  4/1
LD (lives), A   ; 13/3
JP Z, game_over ; 10/3
; 40Ts, 10 bytes
HL is also quicker than BC/DE for certain tasks:

Code: Select all

LD HL, (nn) ; 16 Ts
LD BC, (nn) ; 20 Ts
LD DE, (nn) ; 20 Ts
More obviously is the ability to do 16-bit addition / subtraction (though as already mentioned, you have to use ADC instead of ADD if you need the Z, S or P/V flags!) - again, faster AND smaller.

e.g. with flags:

Code: Select all

OR A        ;  4/1 (reset carry)
ADC HL, DE  ; 15/2
; 19 Ts, 3 bytes
or even quicker if you don't need the flags:

Code: Select all

ADD HL, DE  ; 11/1
; 11 Ts, 1 byte
vs

Code: Select all

LD A, L     ; 4/1
ADD A, E    ; 4/1
LD L, A     ; 4/1
LD A, H     ; 4/1
ADC A, D    ; 4/1
LD H, A     ; 4/1
; 24Ts, 6 bytes
In fact its ownly downside (and something definitely worthy of the thread title, from personal experience!) seem to be interoperability with IXH and IXL (or IYH / IYL):

Code: Select all

; LD BC, IX
LD B, IXH   ; ok
LD C, IXL   ; ok

; LD HL, IX
LD H, IXH   ; nope!
LD L, IHL   ; nope!

; instead, have to do:
LD D, IXH
LD E, IHL
EX DE, HL

; or, if DE is unavailable:
LD A, IXH
LD H, A
LD A, IXL
LD L, A
User avatar
Turtle_Quality
Manic Miner
Posts: 506
Joined: Fri Dec 07, 2018 10:19 pm

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Turtle_Quality »

presh wrote: Tue May 04, 2021 5:34 pm HL is much more useful than BC/DE.

True that, though I think all those can be done with IX / IY also, slower but with the displacement byte when it's indirect ; such as OR (IX+d)
Definition of loop : see loop
User avatar
Bubu
Manic Miner
Posts: 542
Joined: Fri Apr 02, 2021 8:24 pm
Location: Spain

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Bubu »

Z80 things that weren't obvious that you wish you'd known earlier:

When I started programming with Z80, I thought that using IM2 would be a symbol of professional developing... but I never found a real use or utility to implement interrupts: it makes code run so slow, avoids to have sounds meanwhile playing... Never understood the reason for using IM2. My Anteater game doesn't use it at all ( well, only for detecting the coin at the beginning :D )
If something works, don't touch it !!!! at all !!!
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2641
Joined: Mon Nov 13, 2017 3:16 pm

Re: Z80 things that weren't obvious that you wish you'd known earlier

Post by Ast A. Moore »

presh wrote: Tue May 04, 2021 5:34 pm

Code: Select all

; LD BC, IX
LD B, IXH   ; ok
LD C, IXL   ; ok

; LD HL, IX
LD H, IXH   ; nope!
LD L, IHL   ; nope!

; instead, have to do:
LD D, IXH
LD E, IHL
EX DE, HL

; or, if DE is unavailable:
LD A, IXH
LD H, A
LD A, IXL
LD L, A
Or simply:

Code: Select all

push ix		;2 bytes
pop hl 		;(or any other register) 1 byte
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
Post Reply