Sunday, 18 October 2015

ASM development on Amiga - part 2: the 680x0 vs the 6502

I've been reading up on the 680x0 CPU family.  One of the things I needed to get my head around are the differences between this CPU and the 6502, the CPU I know. Data handling, address manipulation and basic arithmetic are drastically enhanced in the 680x0.

If you know your 6502 CPU architecture then this short list of differences might be interesting.

Memory management and Byte order

Do not think that because the CPU is 32 bits it also accesses and uses memory like this. The 680x0 still looks at its memory as BYTES. This is important to know, as some operations require to you align addresses or data to even bytes in memory. So this is not really a difference! :) What a way to start this list.

The 6502 stores numeric values as little-endian. This means that the low byte of a value is first in memory, and then the high byte. The 680x0 uses big-endian: values are stored with the high byte first.

Example 16 bit value #$12a0:

6502:  stored as #$a0, #$12
680x0: stored as #$12, #$a0

Number size

Most commands in the 680x0 require an indication of how big the value we're working with is. This is done by using .b.w, and .l, meaning byte (8 bits), word (16 bits) and longword (32 bits) respectively. Operations are performed only on those bits. So using MOVE.B #$11, D0 will set the lowest 8 bits in D0 only. The rest is unmodified.

Data registers

The well known A, X and Y registers are absent on the 680x0. Instead, there are 8 data registers: D0 to D7. The load commands (LDA, LDX, LDY) for the old registers are gone, of course, and replaced by the, very important and multi-use, Move instruction.


6502:   LDA #$10
680x0:  MOVE.B #$10, D0

And as each data register is 32 bit, it's so much easier to work with larger numbers, like:

MOVE.B #$12, D0
MOVE.W #$1234, D0
MOVE.L #$12345678, D0

Although the data registers are 32 bits, the data bus itself is 16 bits. So 32 bits values will transfer slower than 16 or 8 bit values, because it will use two data transfers. Something to remember: if you can get away with using 16 bits or less, do it.

Address registers

These were not present on the 6502, we needed to use memory locations to store pointers.  This meant that this data was OUTSIDE the CPU. On the 680x0, the address registers are INSIDE the CPU and therefore they are much more efficient.

You can use A0 to A6. You should stay away from A7 as it is used as the stack pointer by the CPU. But having 7 address registers and 8 data registers is incredibly useful to someone who previously had 3 data registers and a few zero page locations to work with.

One of the ways to 'walk' through addresses on the 6502 was to use the zero page. You put pointers to the data into an address in the zero page (preferably, because zero page addressing was faster than higher page addressing), and then used zero page indirect indexed addressing:

Example: Read bytes from address $1000 onwards until read byte is a zero:

  LDA #$00
  LDA #$10
  LDY #$00
  LDA ($FA),Y
  BEQ end
  JMP loop

This is done in 680x0 by setting the address registers, and using +:

  MOVE.W #$1000, A0
  MOVE.B (A0)+, D0
  BEQ end
  BRA loop

The address in A0 is incremented after reading it, because we use (A0)+. The fun thing is, you can use +(AO) and then the value in A0 is increased before it is read. Really powerful.  This also eliminates the need for self modifying code, where we modified addresses that reference locations in the machine. The modifying is still done, of course (in A0), but it is CPU controlled and we cannot destroy the program this way because it is not happening in memory.

There are variants to using the address registers:

MOVE.W 8(A1), D1

will first add 8 to the address pointer in A1 and put the word from that location into D1. The big difference between this and the first example is that it does not modify the address pointer in A0, and it is more like the 6502 example seen above. Another example:

MOVE.W 8(A1,D1.L), D2

This will add 8 to the address pointer in A1, and then the 32 bit number in D1 to that pointer as well, and it will then put the word from that location into D2. 

This adds so much choice to the way we can construct loops, its almost overwhelming. I will probably stick to the more simple versions when I begin creating some programs. :)


We need branching so we can create logic and loops. As expected, the branching possibilities of the 680x0 are numerous. The same instructions as on the 6502 are available (BEQ, BNE, BPL, BMI, BCC, BCS, etc) but there are more added.

The Test, Decrement And Branch variety, or DBcc is one I really like because it is two commands in one. First, the test is performed and if this test fails a branch is performed to the label specified. If the test passes, program flow continues. Also, a data register (acting as a counter) is decremented and while this value is not -1, the program is branching back to the label as specified.

Here is an example of a WHILE count =>0 loop, which exits if the read value is 0:

  MOVE.W #9, D0   ; we do this ten times
  TST.W (A1)+     ; test value of address in A1
  DBNE D0, loop   ; branch if not zero, or counter is positive

This is the same as:

  MOVE.W #9, D0   ; we do this ten times
  TST.W (A1)+     ; test value of address in A1
  BEQ end         ; if address value is 0, go to end
  SUB.W  #1, D0   ; subtract 1 from counter
  BPL loop        ; If higher than 0, branch to loop

I know which one I like more!


The 680x0 sure is a fun CPU family. It fixes so many things that were cumbersome in the 6502 and the other 8 bit chips. There is more of a choice on how to tackle programming challenges.
I'm still reading the book about the 680x0 so maybe I will do another post like this to write down some interesting differences and gotcha's.  I'm making this up as we go along :)

See ya next time!

Review 1541 Ultimate II+

Recently I bought the new version of the 1541 Ultimate II cartridge. It is the best investment you can do when you enjoy your Commodore 64 a...