Data and address registers

The eight data registers and the eight address registers, what each kind is for, and the three rules an address register follows that a data register does not.

Getting started listed the sixteen registers of the M68K: eight data registers, d0 to d7, and eight address registers, a0 to a7, all of them 32 bits wide. What separates the two banks is which instructions accept them, and what happens when you write fewer than 32 bits into one.

Data registers hold numbers

d0 to d7 are where arithmetic happens. Every instruction that computes something wants a data register somewhere in it: add, sub, muls, divu, and, or, eor, not, neg, the shifts lsl and asr, the bit instructions btst and bset, and swap and ext, which have no other operand at all.

They also hold anything else you want to keep: a loop counter, a character, a flag of your own, the result of a comparison. Nothing marks a data register as holding one kind of thing.

An instruction on a data register carries a size, and the size says how much of the register it touches: .b the lowest byte, .w the lowest word, .l all four bytes. The bytes above the size are left exactly as they were, which is why move.b #$FF, d0 on a register holding $12345678 leaves $123456FF.

Address registers hold addresses

a0 to a7 are what the instructions that reach memory work through. lea (load effective address) writes one, and every indirect addressing mode, (a0), 4(a0), (a0)+, -(a0), names one. Build this and step through it: a0 gets the address of total, and (a0) reads the long sitting there.

a0 comes out at 00001010, which is where the assembler put the 25: four instructions of four bytes each start at $1000, so the data begins sixteen bytes later. d1 is 00000019, which is 25, and d0 is 0000007D, which is 125.

Then come the three rules. An address register is a place to keep an address, and the CPU treats it as one, which means:

There is no byte size. move.b #1, a0 does not assemble, and the assembler says so: "Byte size not allowed for address register". Only .w and .l are accepted, because a quarter of an address is not an address.

A word written into an address register is sign extended into all 32 bits. Writing $FFFE into a1 leaves FFFFFFFE, not 0000FFFE. The register always holds a whole address, so the CPU fills the top half with copies of the sign bit rather than leaving what was there.

Writing an address register never touches the flags. Computing an address is not computing a number, so the X, N, Z, V and C bits keep saying what the last arithmetic instruction left them saying.

registervaluewhy
d0123456FFthe byte write left the three bytes above it
a012345678a long is copied as it is
a1FFFFFFFE$FFFE is -2, and -2 in 32 bits is FFFFFFFE
a200007FFE$7FFE is positive, so the sign bit is a 0

Step to the end with the flags panel open and N is still 1, left there by the move.b #$FF, d0 on the second line: the three address register writes after it changed nothing.

The assembler picks the instruction for you

The real 68000 has separate opcodes for the address register versions, and they are separate instructions in the documentation: movea, adda, suba, cmpa. You almost never write them, because move.l d0, a0 is assembled as movea.l d0, a0, add.w #4, a0 as adda.w #4, a0, and cmp.l a1, a0 as cmpa.l a1, a0. The instruction you get is decided by the destination being an address register, and the three rules above are its rules, not move's.

You can write movea and the rest out by hand, and it changes nothing. What is worth knowing is that an add.w #4, a0 in the middle of your program leaves the flags alone while the add.w #4, d0 next to it sets them.

a7 is the stack pointer

a7 is an address register like the other seven, and the CPU also uses it as the stack pointer: bsr pushes onto it, rts pops off it, and -(sp) and (sp)+ are the addressing modes that write and read the stack. You can write it as a7 or as sp, they are the same register.

d0 and d1 both come out at 01000000, which is $1000000, one byte past the last address of memory: the stack starts at the very top and grows downwards. After the subq.l #4, sp both sp and a7 read 00FFFFFC, and d2 reads it too. The stack gets its own lecture, "The stack, -(sp) and movem"; what matters here is that using a7 for scratch work destroys wherever the return addresses of your subroutines were.

Which register an instruction takes

The documentation of every instruction lists the addressing modes each of its operands accepts, and Dn and An are two different entries in that list. The ones you meet first:

  • Only a data register: swap, ext, muls, mulu, divs, divu (as the destination), and eor as its source.
  • Only an address register: lea and movea as their destination, pea and unlk as their only operand, link as its first.
  • Either one: exg, which exchanges the full 32 bits of any two registers, and movem, which saves and restores a list of both kinds.

swap d0 exchanges the two words of d0, so AABBCCDD becomes CCDDAABB. lea $2000, a0 puts the number $2000 in a0 without reading anything from memory. mulu #7, d1 multiplies the low word of d1 by 7 and writes the 32 bit product over the whole register, so d1 is 0000002A, which is 42. Then exg d1, a1 swaps them, leaving 42 in a1 and 0 in d1.

Try replacing swap d0 with swap a0 and pressing Build. The assembler refuses it, because swap has one operand and that operand is Dn.

Your turn

Two instructions. The test starts d0 at $0000FFFE, and wants the low word of d0 in a0 and the whole of d0 in a1. Because a word written into an address register is sign extended, a0 comes out at $FFFFFFFE while a1 stays $0000FFFE.

Show solution