Bytes and 16-bit pairs

What fits in eight bits and what does not, the same byte read as signed and unsigned, how you widen one into a pair when the Z80 has no instruction for it, and the two decimal digits daa packs into a byte.

On a 32 bit machine a register holds any number a beginner is likely to write. On the Z80 a register holds a byte, 256 different patterns, and running out of room is something that happens on the third line of a program rather than in a lecture about edge cases. Let's go through what fits where.

What a byte holds

Eight bits is 256 patterns, and there are two ways to read them:

  • unsigned, 0 to 255,
  • signed, -128 to 127, in two's complement, where the top bit is the sign and negating a number means flipping every bit and adding 1.

Nothing in the register says which. The same FB is 251 and -5 at the same time, and what decides is the instruction that reads it and the flag you branch on afterwards.

a and b both come out at FB. Hover the value in the registers panel and it shows you both readings, 251 and -5, of the one byte. neg is the Z80's negate, and it works on a and on nothing else.

When a byte is not enough

Add 1 to the largest number that fits and it comes back round to the smallest, and which flag says so depends on which reading you meant.

after this lineaSZHP/VNC
add a, 1 (255)00011001
add a, 1 (127)80101100

255 plus 1 is 256, which does not fit, so a is 0 and the carry flag C is 1: that is the unsigned answer to "did it fit". 127 plus 1 is 128, which fits perfectly as an unsigned byte, so C stays 0, but read as signed the answer wrapped from +127 round to -128 and P/V is 1 instead: that is the signed answer to the same question. The same addition sets both every time, and picking the one that matches what your numbers mean is on you.

Try changing the second pair to ld a, 200 and add a, 100. a comes out at 2C, which is 44, and C goes to 1, because 300 needs nine bits.

Sixteen bits, when eight will not do

The way out is a pair, which holds 0 to 65535 unsigned or -32768 to 32767 signed. There is one 16 bit addition, add hl, rr, and it carries out of bit 15 into C the same way.

hl is 0514 after the first addition, which is 1300, and 0000 after the second, with C at 1. add hl, rr writes C and H and leaves S, Z and P/V exactly as they were, which is a trap: after add hl, de there is no zero flag to branch on, the one in the panel belongs to whatever ran before it.

Widening a byte into a pair

You have a byte in a and you want it in hl so you can add it to an address. If the byte is unsigned that is two instructions, ld l, a and ld h, 0, and you are done.

If it is signed it is not, because -5 in one byte is FB and -5 in two bytes is FFFB: the three Fs have to be put there. Filling the high byte with copies of the sign bit is called sign extension, and the M68K has ext for it and RISC-V does it inside every lb. The Z80 has no instruction for it at all, so you write it out.

The two ways of doing it are in here. Build it and press Step through both halves.

hl comes out at FFFB and so does de, which is -5 in sixteen bits, twice. The first half tests the sign bit and picks one of two values for h. Try changing its ld a, 0xFB to ld a, 0x7B and running again: the branch is taken, h stays 0, and hl is 007B, which is 123.

The second half is what Z80 programmers write instead, and you will meet it in other people's code. rla shifts a left through the carry, so bit 7 lands in C. sbc a, a subtracts a from itself and then subtracts the carry, so the answer is 0 minus C, which is 00 when the sign bit was 0 and FF when it was 1. Four instructions and no branch.

Two decimal digits in a byte

There is a third way to read a byte, and the Z80 has an instruction for it that most machines do not. Binary coded decimal puts one decimal digit in each half of the byte, so 0x27 means the number 27 and not 39. It was how a machine with no division kept a score or a clock, since printing a BCD byte is two nibbles and two add a, '0'.

Adding two BCD bytes with a plain add gives the wrong answer, because the CPU carries at 16 and not at 10. daa, decimal adjust accumulator, fixes a up afterwards.

b comes out at 3C and a at 42. 0x27 plus 0x15 really is 0x3C in binary, and 27 plus 15 really is 42, and daa is what turns the first into the second by adding six to a nibble that went past nine. It works out what to add from two flags, H and N, which is what those two flags are in the register for, and the flags lecture comes back to them.

Writing numbers down

Every literal in this course is one of these, and they all mean the same 31:

writtenbase
31decimal
0x1Fhexadecimal, the C spelling
$1Fhexadecimal, the M68K spelling
1Fhhexadecimal, the Zilog spelling
0b00011111binary
0o37octal

'A' is the character code 65, and "A" is the same byte. A leading zero means nothing here, 037 is decimal 37, not octal.

Hexadecimal is what you will read most, because one hex digit is exactly four bits, so 0xFB splits into 1111 and 1011 in your head and 0x27 is the BCD 27 by eye. Binary is for masks, where the bit positions are the point: 0b00010000 says "bit 4" far more clearly than 16 does.

Your turn

The test starts a at 0xFB, which is -5 as a signed byte. Leave the same number in hl as a signed 16 bit value, which is FFFB. Either of the two ways above will do.

Show solution

The second one starts bc at 400 and de at 900, both too big for a byte. Leave their sum in hl, which is 1300, or 0514 in hexadecimal. hl is the only place a 16 bit addition can land.

Show solution