Home · part 8 of 9
From bytes to registers
Why a byte has to be widened before a 32-bit processor can compare it, what the two loads write into the bits that were never in memory, and how the widened compare gets its flags. Drawn cell by cell, for any pair of bytes.
1. Why a byte has to widen
A C program declares uint8_t a or int8_t a, and either way the variable occupies one byte of memory. Memory is addressed in bytes, so that is where a byte lives. But the processor cannot compute on memory. It computes on registers, and on ARM every register is 32 bits wide. CMP, like every other ALU instruction, compares whole registers, and there is no 8-bit compare to use instead.
So before the compare can run, the byte has to be loaded into a register, and a 32-bit register holding an 8-bit value has 24 bits that were never in memory. Something has to be written into them. There are two sensible answers, and the instruction set has one load for each: LDRB writes zeros, LDRSB writes copies of the byte's top bit. The declaration decides which one the compiler emits. A true 8-bit processor, with 8-bit registers, would need neither, and its flags would match the Bit Lab's exactly.
2. One byte, both loads
Here is a byte put through both loads, with the registers they leave behind, and then a byte on which the two loads agree. The dashed cells are the ones the load had to invent.
Why anything has to be written there
A register cell is a flip-flop, and a flip-flop is always holding a 0 or a 1. There is no third state meaning "empty" or "not in use". So when an 8-bit value goes into a 32-bit register, the other 24 cells will contain something whether anyone chooses it or not, and the only question is whether what they contain changes the number. The two loads are the two fills that do not.
Why the fill does not change the number
For an unsigned number the fill is zeros, and leading zeros are worth nothing: 0005 is 5. Bits 8 to 31 have place values 256 up to 231, and a 0 in each contributes 0, so the register reads exactly what the byte read.
For a signed number the fill is copies of the top bit. To see why, recall how a register holds a negative number at all. A register with 8 bits has only 256 patterns, and counting runs round them in a loop: one step down from 0x00 is 0xFF, the last pattern, as if the 256 patterns were laid round a wheel. A negative number is simply a count back from zero round that wheel, so −2 is the pattern two steps below 0x00, which is 0x100 − 2 = 0xFE. (From adding to subtracting draws the wheel and shows the adder doing this.) A 32-bit register is the same thing with a much bigger wheel, 0x100000000 patterns round. On it, −2 is two steps back from 0x00000000, which lands on 0x100000000 − 2 = 0xFFFFFFFE.
Now compare the two patterns. The steps were the same; only the size of the wheel changed. So the two patterns differ by exactly the difference in wheel sizes, 0x100000000 − 0x100 = 0xFFFFFF00, and that number is 24 ones followed by 8 zeros. The 32-bit pattern of any negative byte is therefore the 8-bit pattern with 24 ones stacked on top, and nothing else. A positive byte is not a count back from anything, so it gets no wheel-size term and its 24 extra bits are zeros. The byte's top bit tells you which case you are in, which is why copying it is the whole rule.
The place values say the same thing in one line. In the byte, bit 7 is worth −128. In the 32-bit word, bit 7 is worth +128 and bits 8 to 31, all set, are worth −256 between them: +128 − 256 = −128, unchanged. Fill those 24 bits with zeros instead and bit 7 quietly becomes +128, which is exactly why 0x80 through LDRB reads as 128 and through LDRSB as −128.
| value | as a byte | as a 32-bit word | the 24 bits the load added |
|---|---|---|---|
| 5 | 0x05 | 0x00000005 | zeros |
| 127 | 0x7F | 0x0000007F | zeros |
| −1 | 0xFF | 0xFFFFFFFF | ones, 0xFFFFFF00 |
| −2 | 0xFE | 0xFFFFFFFE | ones, 0xFFFFFF00 |
| −128 | 0x80 | 0xFFFFFF80 | ones, 0xFFFFFF00 |
The wheel says the same thing another way. On the 256-position wheel, −128 is 128 steps back from 0, which lands on 0x80. On a wheel with 232 positions, 128 steps back from 0 lands on 0xFFFFFF80. Sign extension moves a number from the small wheel to the same place on the big one.
So both loads produce "the same number, now with room to spare". They disagree only about which number the byte was, 128 or −128, and that is the whole disagreement.
3. Your pair, live
Pick two bytes and a declaration. The panel loads both bytes the way the declaration requires, then runs the 32-bit compare the Bit Lab's SUB would do, with 32 columns instead of 8, and reads the flags off the result.
If switching the declaration changes nothing, both top bits are 0, and the panel says so: copying a 0 into the spare cells is the same as writing 0. Load a pair with a top bit set to see the cells, and then the flags, change.
Where this leads
- The Signed or unsigned: who decides? section puts these loads in a program: an
ifin C beside the ARM a compiler emits, with the lines that run highlighted and a trace. - The flag drills ask you to widen a byte by hand, then draw these rows for your answer.
- The From adding to subtracting page is the compare's other half: why A + NOT B + 1 subtracts, and why the carry out reports the borrow.
- The two's complement tutorial (PDF) covers sign extension alongside the rest of the arithmetic.