.png)
A register holds one byte: 0 to 255. That has been enough so far because characters fit in a byte and your loops have been short.
It is not enough for much else. A screen coordinate goes past 255. So does a score, a memory address, the length of anything interesting. You need numbers twice the size, and the Z80's answer is to use two registers as one.
By the end of this section you will have drawn a line - which needs exactly that.
Three of the registers you know pair up:
| Pair | Made from | Range |
|---|---|---|
BC |
B and C |
0 to 65535 |
DE |
D and E |
0 to 65535 |
HL |
H and L |
0 to 65535 |
B is the top half of BC and C is the bottom half. They are not separate
things that get combined - they are the same sixteen bits, and you can work
with either half or the whole.
Load a pair in one go:
LD DE,12345
LD HL,1000
LD BC,65535
You have already been doing this without thinking about it. LD HL,buffer in
S9 loaded a 16-bit address into a pair. LD DE,buffer did the same.
There is no instruction that loads one pair from another:
LD HL,DE ; does not assemble
The Z80 simply does not have it, in any combination - LD DE,HL, LD BC,DE
and the rest are all rejected too. Worse, the error blames the wrong thing:
unable to resolve reference: DE, because the assembler read DE as a label
you never defined.
There are three ways round it, and the first is the one to reach for.
Swap them. EX exchanges DE and HL in a single byte:
EX DE,HL ; HL gets what DE had - and DE gets what HL had
That is the cheapest 16-bit move on the machine, and very often exactly what
you want: you are usually finished with whatever was in HL anyway. Note it
is a swap, not a copy, and only the EX DE,HL spelling is accepted -
EX HL,DE is a parse error even though it means the same thing to a human.
Copy both halves, when you genuinely need the original left alone:
LD H,D
LD L,E ; HL now holds what DE holds, and DE still holds it
Or go through the stack:
PUSH DE
POP HL
Also two bytes, but it touches the stack, so keep it for when you were pushing anyway.
If you find LD HL,DE in Einstein code from elsewhere, do not assume whoever
wrote it was confused. Some assemblers accept it as shorthand and quietly emit
the two 8-bit loads, so on the tool it was written for it worked exactly as
its comment says. Ours does not offer the shorthand. Appendix II covers
this and the other differences you will meet in code you did not write.
Two instructions that look almost identical do completely different jobs:
LD DE,1024 ; put the number 1024 into DE
LD DE,(1024) ; put the value stored at address 1024 into DE
Both are valid. The brackets are not optional punctuation - they change
what the instruction means, exactly as they did with LD A,(HL) in S9.
Without brackets you get the number. With brackets you get whatever is living
at that address.
Going the other way:
LD (1024),HL ; store HL at address 1024
LD HL,(1024) ; load HL back from address 1024
A 16-bit value takes two bytes of memory, and the low byte goes first - the
little-endian order from S4. LD (1024),HL writes to 1024 and 1025 together;
you do not have to think about it as long as you use the pair instructions.
One thing you cannot do is store a constant straight into memory:
LD (2000),42 ; rejected: parse error
Values reach memory through a register:
LD A,42
LD (2000),A
Now for something to spend those 16-bit numbers on.
The ROM will draw a line for you: call 200, officially named ZDRWTO. It
needs five things, and they arrive by an odd mixture of routes:
| What | Where it goes |
|---|---|
| Start X | IX |
| Start Y | IY |
| End X | Memory at 64406 |
| End Y | Memory at 64408 |
| Line style | Memory at 64424 to 64427 |
IX and IY are two more 16-bit registers. They are slower to use than
HL and there are fewer instructions for them, which is exactly why the ROM
uses them - it leaves your useful registers alone.
The other three arrive in memory, at fixed addresses in an area the ROM keeps for its own working values. Every one of them is two bytes, which is where the interesting mistake lives. First, the program:
ORG 256
LD A,255
LD (64424),A ; solid line
LD A,0
LD (64425),A ; the other three style bytes must be zero
LD (64426),A
LD (64427),A
LD IX,0 ; start at the origin
LD IY,0
LD HL,200
LD (64406),HL ; end X - both bytes
LD (64408),HL ; end Y - both bytes
RST 8
DEFB 200 ; ZDRWTO - draw it
RET
Run it. A diagonal line crosses the screen, straight over the text that was already there - text and graphics share the display, and neither disturbs the other.
Look at which way it goes. From 0,0 to 200,200, and it runs from the
bottom left to the top right. So 0,0 is the bottom left corner and Y
increases upward, which is the opposite of nearly every modern screen you have
used. Worth fixing in your head now.
Do not leave all four style bytes at zero. Set 64424 to 255 for a solid line, as above.
The coordinates are two bytes each, so write them with a pair:
LD HL,200
LD (64406),HL ; correct - writes both bytes
You could write only the low byte instead:
LD A,200
LD (64406),A ; writes one byte, leaves the other alone
and on a machine that has just booted, that works. The high byte happens to be zero already, so the value comes out right and the line is correct.
It stops working the moment you draw anything past 255. Set a coordinate to 300, then set it to 200 the one-byte way, and read it back:
LD HL,300
LD (64406),HL ; 300
LD A,200
LD (64406),A ; meant to be 200
LD HL,(64406) ; what is actually there?
HL comes back as 456. The low byte became 200, the high byte is still the 1
left over from 300, and 256 + 200 is 456.
Nothing tells you. The line simply goes somewhere else, and you look for the mistake in your arithmetic rather than in a byte you never wrote.
Write both bytes. LD (addr),HL for anything the machine treats as
16-bit, always.
LD HL,200 to LD HL,100 for the end Y only. Which way does the
line tilt, and does that match Y increasing upward?IX to 100 and IY to 0. Where does the line start?LD (64406),HL stores with the one-byte version and run it.
It works. Now add LD HL,300 / LD (64406),HL before them and run it
again. Explain the difference without re-reading the section.LD A,255 for the style byte to LD A,0, so all four style bytes
are zero. Have Restart ready.LD HL,DE somewhere and read the error message. Then work out why the
assembler blames DE.HL and DE with different numbers, EX DE,HL, and check both in the
register line. Then do the same with LD H,D / LD L,E and compare what
happened to DE in each case.10.1 - A different line. Draw from 0,0 to 100,200. Predict the angle
before you run it.
10.2 - A square. Four lines. You will need to move both the start and the end for each side, so this is four blocks of the same shape - which makes it a good candidate for a subroutine that takes its coordinates from memory.
10.3 - Three ways to move a pair. Get DE's value into HL three times
over - with EX, with two 8-bit loads, and through the stack. For each one,
say what DE holds afterwards. Only one of the three leaves it untouched, and
only one costs a single byte.
10.4 - Store and fetch. Put a 16-bit number into memory at an address of
your choosing, read it back into a different pair, and check it survived. Then
look at the two bytes individually with LD A,(addr) and confirm which half
went where.
10.5 - The overflow. Write a program that draws a line correctly, then a second line that is wrong only because of a one-byte coordinate store. Get the two lines visibly different - which means not choosing coordinates that happen to sit on the same diagonal, a mistake worth making once.
| What you see | What it means |
|---|---|
error: unable to resolve reference: DE |
You wrote LD HL,DE or similar. No such instruction exists - use EX DE,HL to swap, or two 8-bit loads to copy. |
error: parse error. Remainder of line=42 |
LD (addr),42. Constants reach memory through a register. |
| The line goes somewhere you did not ask for | A coordinate stored one byte at a time, with a stale high byte. Use LD (addr),HL. |
| The machine hangs when drawing | All four style bytes are zero. Set 64424 to 255. |
| The line is upside down from what you expected | Y increases upward on this machine. |
BC, DE and HL each work as one 16-bit register, 0 to 65535. IX and
IY are two more.LD HL,DE. Use EX DE,HL to swap two pairs in one byte, or two
8-bit loads to copy one into the other.LD DE,1024 and
LD DE,(1024) are both valid and mean different things.IX,IY to the coordinates at 64406 and 64408,
with the style bytes at 64424 onwards. 0,0 is the bottom left.LD (addr),HL.
A one-byte store leaves the other byte behind, and nothing warns you.S11. Text of your own. You can read a line in and draw a line on screen. The remaining gap is text you put there yourself - a message stored in your program and printed on demand. That needs a register pair used as a pointer, which turns out to be the idea behind every list of data you will ever walk.