Z80 things that weren't obvious that you wish you'd known earlier
Z80 things that weren't obvious that you wish you'd known earlier
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
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
Re: Z80 things that weren't obvious that you wish you'd known earlier
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 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 !!!
Re: Z80 things that weren't obvious that you wish you'd known earlier
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.
- 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
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.
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.
- 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
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.Stefan wrote: ↑Mon Apr 19, 2021 10:20 pmThe 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.
Mark
Standby alert
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb
Looking forward to summer later in the year.
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb
Looking forward to summer later in the year.
- 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
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.
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
Re: Z80 things that weren't obvious that you wish you'd known earlier
I ran into this a few weeks back. I ended up following the self modifying code route: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 ?
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
Re: Z80 things that weren't obvious that you wish you'd known earlier
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).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 ?
Re: Z80 things that weren't obvious that you wish you'd known earlier
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.
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.
- 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
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.
Re: Z80 things that weren't obvious that you wish you'd known earlier
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
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
Re: Z80 things that weren't obvious that you wish you'd known earlier
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.
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.
Re: Z80 things that weren't obvious that you wish you'd known earlier
100% agree with these!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.
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.
- 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
Something like this?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 ?
Code: Select all
ld h, table/256
and (hl)
align 256
table: db 1,2,4,8,16,32,64,128
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)
- 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
Perfect thanks
Definition of loop : see loop
Re: Z80 things that weren't obvious that you wish you'd known earlier
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?
Re: Z80 things that weren't obvious that you wish you'd known earlier
I'm not so sure if proper manual is required. List of commands with timings and effects would be really enough.in my youth i didn't had a proper manual, only a list of commands
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.
Re: Z80 things that weren't obvious that you wish you'd known earlier
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.
Re: Z80 things that weren't obvious that you wish you'd known earlier
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
Re: Z80 things that weren't obvious that you wish you'd known earlier
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):
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:
... 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.
(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 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.
}
}
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.
Re: Z80 things that weren't obvious that you wish you'd known earlier
Yep, good point..!
My Speccy site: thirdharmoniser.com
Re: Z80 things that weren't obvious that you wish you'd known earlier
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.
And because some instructions modify the value at the same time - e.g. INC (HL) - it allows for faster AND smaller code. Consider:
vs
HL is also quicker than BC/DE for certain tasks:
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:
or even quicker if you don't need the flags:
vs
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):
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)
Code: Select all
LD HL, lives ; 10/3
DEC (HL) ; 4/1
JP Z, game_over ; 10/3
; 24 Ts, 7 bytes
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
Code: Select all
LD HL, (nn) ; 16 Ts
LD BC, (nn) ; 20 Ts
LD DE, (nn) ; 20 Ts
e.g. with flags:
Code: Select all
OR A ; 4/1 (reset carry)
ADC HL, DE ; 15/2
; 19 Ts, 3 bytes
Code: Select all
ADD HL, DE ; 11/1
; 11 Ts, 1 byte
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
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
- 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
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 )
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 )
If something works, don't touch it !!!! at all !!!
- 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
Or simply:presh wrote: ↑Tue May 04, 2021 5:34 pmCode: 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
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.
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.