To do a sign extension, consider the numbers 64 and -64. Let's derive these numbers for various bit sizes and see if anything interesting happens.
Size | 64 | -64 |
---|---|---|
8-bit | $40 | $C0 |
16-bit | $0040 | $FFC0 |
24-bit | $000040 | $FFFFC0 |
32-bit | $00000040 | $FFFFFFC0 |
Zero extension on the Z80 is easy:
;Zero extend DE LD D, 0Sign extension is tougher, you need to decide whether to store $00 or $FF. The instructions to do this haven't been learned yet, and I don't want to introduce them out of their context, so...
INC { reg8 | reg16 | (HL) } | Adds one to the operand. | ||||||||
|
DEC { reg8 | reg16 | (HL) } | Subtracts one from the operand. | ||||||||
|
ADD A, { reg8 | imm8 | (HL) } | Adds to the accumulator. | ||||||||
ADD HL, reg16 | Adds to HL. | ||||||||
|
SUB { reg8 | imm8 | (HL) } | Subtracts from the accumulator. | ||||||||
|
SBC HL, reg16 | Subtracts reg16 and the carry flag from HL. | ||||||||
|
Before | Instruction | After |
---|---|---|
A = 45 | INC A | A = 46 |
DE = 12116 | INC DE | DE = 12117 |
B = 19 | DEC B | B = 18 |
A = 5 L = 21 |
ADD A, L | A = 26 |
A = 95 | SUB 90 | A = 5 |
HL = 5516 DE = 1102 CY = 1 |
SBC HL, DE | HL = 4413 |
One thing that needs to be pointed out about the instructions that allow two 16-bit operands, is that the registers HL and IX are mutually exclusive. What that means is that if the first operand is HL, the second can be any other 16-bit register except IX (and, of course, AF). Similarly for IX. Also, IX can never be an operand for SBC. Anyway, if you're ever confused, just look in the Z80 Instruction Set Reference.
If you want to subtract a constant number x from HL, you should use ADD and load into the other operand the negative of x.
Example:
LD HL, 46243 LD BC, -1000 ADD HL, BC ; HL now equals 45243
However, if the number is already in a register from a previous calculation, you have to use SBC. This becomes quite a sticky situation, because you might not know what the carry flag's value is, thus giving an erroneous result 50% of the time. The solution is to ensure that the carry is reset before doing the subtraction. How to do that?
SCF ; Force carry = 1 CCF ; Flip carry so it is 0 SBC HL, BCThis is actually the most idiotic way to force the carry to zero, since it can be done in just one instruction. Problem is, that instruction doesn't just reset the carry flag, and it belongs to a family of instructions that do similar operations, and the whole thing would be just too much and too messy for one day.
Finally, before I forget, what if you wanted to do the above, but with IX? Since SBC won't accept an index register, you must use ADD, and manually negate the second register.
LD A, B CPL LD B, A LD A, C CPL LD C, A ; We have now found the one's complement of BC so, by definition of ; the two's complement: INC BC ADD IX, BC
LD HL, 10 ADD HL, HL ; 10 * 21 = 20 ADD HL, HL ; 10 * 22 = 40 ADD HL, HL ; 10 * 23 = 80 ; et ceteraIf the number is not a power of two, but can be expressed as the sum or difference of two powers of two, then its still pretty easy, just a little less efficient.
; Calculate HL * 40 as (HL * 32) + (HL * 8) ADD HL, HL ADD HL, HL ADD HL, HL ; HL * 8 LD D, H LD E, L ; Save it for later ADD HL, HL ADD HL, HL ; HL * 32 ADD HL, DE ; HL * 32 + HL * 8
; Calculate HL * 15 as (HL * 16) - (HL * 1) LD D, H LD E, L ; Save HL * 1 for later ADD HL, HL ADD HL, HL ADD HL, HL ADD HL, HL ; HL * 16 SCF CCF SBC HL, DE ; HL * 16 - HL * 1What if it is an awkward number like 13? In this case, it might be better to follow this general-purpose algorithm:
;Calculate HL * 13 LD D, H LD E, L ADD HL, HL ; HL * 2 ADD HL, DE ; HL * 3 ADD HL, HL ; HL * 6 ADD HL, HL ; HL * 12 ADD HL, DE ; HL * 13
LD HL, 127 LD D, H LD E, L ; 256 ÷ 52 = 5, find 127 × 5 ADD HL, HL ; HL = $00FE ADD HL, HL ; HL = $01FC ADD HL, DE ; HL = $027BPlease note that this this method gives only a very rough approximation for the quotient. Later on, I will show you a way to divide a number perfectly, and even get the remainder!
LD A, 203 ADD A, 119If we add 119 to 203, we would get 322, but this does not fit in eight bits, so we have to wrap around. If we look at the binary value of 322, which is %101000010, then eliminating all but the rightmost eight bits will give us the value A will hold. The end result is that A holds 66, but the carry flag is set to hold that ninth bit of the result. This affect applies equally if we consider A to be signed (in this case, the largest and smallest possible values are 127 and -128). There is a similar phenomenon when subtracting.
LD HL, $D361Which puts $D361 into HL. No big suprise there, but since 16-bit registers are just two 8-bit registers taken together, what happens to H and L?
Now take this instruction
LD ($2315), HL
Since H comes before L, You'd figure that register H would be stored in byte $2315 and L would be stored into byte $2316. I mean, it just makes sense, right? |
|
Wrong. Because the Z80 is what's called a "little-endian processor", when you store HL to memory, the number gets "twisted around": The byte in register L is loaded first, then the byte in H (the number is stored "little-end" first). When you store from RAM to HL, the first byte goes into L and the next byte goes into H. |
|
Example, using this array, and considering each element to be an 8-bit number...
Address | $8000 | $8001 | $8002 | $8003 | $8004 | $8005 | $8006 | $8007 | $8008 | $8009 | $800A | $800B |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Element No | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
Value | 232 | 37 | 131 | 103 | 187 | 11 | 86 | 254 | 51 | 204 | 243 | 56 |
array_base .EQU $8000 element_size .EQU 3 LD A, (array_base+(4*element_size)) LD C, AIf the index is in a register, you have a bit more work to do.
LD A, 3 ; Put index in A LD B, A ; Multiply by element size ADD A, A ADD A, B LD D, 0 LD E, A ; Put A in DE LD HL, array_base ; Add index to base ADD HL, DE LD C, (HL)
With row-major ordering, you fill up each row from left to right, then move down to the next row when you have exhausted a row.
Column | |||||
---|---|---|---|---|---|
0 | 1 | 2 | 3 | ||
R o w |
0 | 232 | 37 | 131 | 103 |
1 | 187 | 11 | 86 | 254 | |
2 | 51 | 204 | 243 | 56 |
Column | |||||
---|---|---|---|---|---|
0 | 1 | 2 | 3 | ||
R o w |
0 | 232 | 103 | 86 | 204 |
1 | 37 | 187 | 234 | 243 | |
2 | 131 | 11 | 51 | 56 |
array_base .EQU $8000 row_size .EQU 4 col_size .EQU 3 LD HL, array_base LD A, C ; Multiply by row size ADD A, A ADD A, A ADD A, B ; Add in row index LD D, 0 LD E, A ADD HL, DE LD A, (HL) INC HL LD H, (HL) LD L, A
struct CD { byte title[32]; // Name of the CD byte band[32]; // The guys what made it word release; // Year of release byte tracks; // Number of songs word length; // Total disc length in seconds byte rating; // How am I reflecting upon having thrown } // my hard-earned cash at the RIAA today? (/10)The structure's elements are allocated one after another in memory, just like an array is. To access an element of the structure, you need to know the offset from the beginning of the structure to the first byte of that element. Continuing with the example, we might define some manifest constants to help us:
CD.title .EQU 0 CD.band .EQU 32 CD.release .EQU 64 CD.tracks .EQU 66 CD.length .EQU 67 CD.rating .EQU 69These equates will help enormously in maintaining readability. To access an element, you can put the structure base address into HL, then add the offset. Alternatively, you might use IX and use the equated displacement. Slow, but easy to follow.
Example, given this instance of our CD:
CD.title = "P�u�l�s�e" CD.band = "Pink Floyd" CD.release = 1995 CD.tracks = 23 CD.length = 8863 CD.rating = 10 // Watch the video, it ownz.And say we wanted to set the length element to its proper value:
LD HL, disc01 + length LD (HL), $9F INC HL LD (HL), $22 LD IX, disc01 LD (IX + CD.length), $9F LD (IX + CD.length + 1), $22 disc01: .DB "Pulse" .DB "Pink Floyd" .DW 1995 .DB 23 .DW 6502 .DB 10
struct sprite { byte x; // x-position byte y; // y-position byte dx; // delta-x each frame byte dy; // delta-y each frame byte hp; // hit points byte frame; // animation frame }And suppose we wanted to add the dx byte to the x byte, and the dy byte to the y byte of each element. This could be done
x .EQU 0 y .EQU 1 dx .EQU 2 dy .EQU 3 hp .EQU 4 frame .EQU 5 sizeof .EQU 6 ; Size of each element LD IX, AppBackupScreen ; Get array base LD DE, sizeof ; Use this to update IX LD A, (IX + x) ADD A, (IX + dx) LD (IX + x), A ADD IX, DE LD A, (IX + x) ADD A, (IX + dx) LD (IX + x), A ADD IX, DE LD A, (IX + x) ADD A, (IX + dx) LD (IX + x), A ADD IX, DE LD A, (IX + x) ADD A, (IX + dx) LD (IX + x), A