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, 0
Sign 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, BC
This 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 cetera
If 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 = $027B
Please 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, 119
If 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?
Two hex digits mean one byte, so $D3 is one byte and $61 is one byte. Since $D3 and H are first from the left, it makes sense that $D3 is stored into H. Similarily, $61 would be stored into 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