Bytes, words and longs

The five ways of writing a number in M68K source, the three sizes an instruction can carry and which one you get when you leave it off, and the instructions that read the same bits as signed or as unsigned.

Memory is bytes and a register is 32 bits, and neither of them says what the bits mean. On the M68K two things decide that: the size you put on the instruction, and which of the signed or unsigned instructions you picked.

Five ways of writing the same number

The assembler reads four bases and a character literal, and a # in front means the number itself rather than the address it names.

writtenbase
100decimal
$64hexadecimal
%1100100binary
@144octal
'd'the ASCII code of d

All five of those are 100, and the last one is 100 because ASCII gives the letter d the code $64. A literal of more than one character is packed into as many bytes: #'Hi' is $4869, an H and an i side by side.

An immediate can also be an expression, worked out by the assembler while it assembles, with labels allowed inside it:

d0 to d4 all come out at 00000064. d5 is 00004869 and d6 is 00000202, which is 514. Nothing of the expression survives into the program: the assembler puts 514 in the instruction and the CPU never sees the multiplication.

The three sizes

Every instruction that moves or computes carries one of three sizes, and it says how many bytes of the destination it writes:

  • .b, one byte, 8 bits, the lowest byte of a register.
  • .w, one word, 2 bytes, 16 bits, the lowest word.
  • .l, one long, 4 bytes, all 32 bits.

Leave the size off and you get a word. That holds for move, add, sub, clr, neg, not and ext, and it is the single most common way to write a bug on this machine, because a word is what you want least often.

registervalue
d0AABB1111
d1AABB0000
d2AABB3323
d3AABBCCDE

AABB survived all four of them. Write .l on every one of those lines and the whole register changes instead. A few instructions have no size at all, because there is only one thing they could mean: lea and movea always work on a long address, swap always on the two words of a register, btst and its family always on one bit, and the branches on nothing.

moveq, the size that is not a size

moveq #n, dn takes a number between -128 and 127, sign extends it to 32 bits, and writes all of dn. It exists because that is the common case and it fits in a shorter encoding on a real 68000.

d0 comes out at AABBCCFF and d1 at FFFFFFFF, from the same -1. d2 is 0000007F and d3 is FFFFFF80. moveq #128, d4 does not assemble, because 128 does not fit in a signed byte.

Sign extension

$FF in a byte is 255 read as unsigned and -1 read as signed, and the two mean different things once that byte is copied into a long: 255 is 000000FF and -1 is FFFFFFFF. Sign extension is filling the bytes above with copies of the top bit, which is what keeps a signed number the same number in a bigger box.

ext does it in place on a data register. ext.w extends the low byte into the low word, and ext.l extends the low word into the whole register, so a byte becomes a long in two steps.

d1 goes 000000F0, then 0000FFF0, then FFFFFFF0, which is -16 the whole way. d2 stays 0000007F, because $7F is positive and extending a positive number fills with zeroes.

Two other places do it for you. moveq sign extends its byte, as above, and a word written into an address register is sign extended into all 32 bits, which is why move.w #$FFFE, a0 leaves FFFFFFFE.

Signed or unsigned is your choice, not the register's

Nothing in a register says whether its bits are a signed or an unsigned number. add, sub, move and the logic instructions do not care: the bits come out the same either way, and only the flags differ. Where the answer really is different, the M68K gives you two instructions and you pick:

  • mulu and divu read their operands as unsigned, muls and divs as signed.
  • lsr shifts right and feeds in zeroes, asr shifts right and drags the sign bit along.
  • The branches come in two families, bhi, bls, bcc and bcs for unsigned comparisons and bgt, ble, bge and blt for signed ones. They get their own lecture, "Compare and branch".

d2 comes out at 0002FFFD, which is 196605, and d3 at FFFFFFFD, which is -3. d4 is FFFFFFFB, which is -5, and d5 is 3FFFFFFB, which is 1073741819. Two instructions, the same input bits, two right answers to two different questions.

The registers panel has the same choice. The B, W and L buttons in its header cut each register into bytes, words or one long, and hovering a value shows you its signed and unsigned readings side by side.

Your turn

The test starts d0 at $123456F0. Leave the lowest byte of d0 in d1, read as a signed number and extended to a full long, so d1 comes out at $FFFFFFF0.

Show solution

The second one starts d0 at $0000FFFF and d1 at 3. Multiply them twice: leave the unsigned product in d2 and the signed product in d3. Since $FFFF is 65535 unsigned and -1 signed, d2 comes out at $0002FFFD and d3 at $FFFFFFFD.

Show solution