The Motorola 68000 CPU has about 50–60 basic instructions, each with variants that bring the total to around 150. If you’re coding for the 68080 CPU, you can multiply those numbers significantly. Luckily, you can get pretty far with just a few of the basic ones, and an understanding of variants. In this article, I’ll share just 12 commands that are all you need to build complete projects.

Even though the 68000 is a 16-bit CPU, moving at most 16 bits of memory in one go, it has a 32-bit internal architecture and appears as a 32-bit processor from a programmer’s perspective. From the 68020 onward, the CPUs in this series are true 32-bit. Instructions that read or write data have variants indicating the size of the data being processed. This is done by adding a suffix to the command.
| instruction.b | Byte – 8 bits |
| instruction.w | Word – 16 bits |
| instruction.l | Long – 32 bits |
Be careful to use the correct size when writing your code. Getting this wrong is one of the most common mistakes and can lead to tricky bugs. If you write a byte-sized value into a register, you have no idea what’s in the upper 24 bits. Reading that register as a long afterward might just give you a random number.
Here’s the list of the 12 most common instructions along with a couple of usage examples for each.
move lea add sub cmp tst btst beq (and other branches) dbf jsr bsr rts
MOVE – The move command is used to move data to and from memory and registers.
| Instruction | Example | Description |
| move.b | move.b #255,d0 | Write the number 255 into the lower 8 bits of d0 |
| move.w | move.w #$4ff,d0 | Write the hexadecimal number 4ff into the lower 16-bits of d0 |
| move.l | move.l a0,d0 | move the address stored in a0 into d0 |
| move.l (a0),d0 | Move the content that a0 points to into d0. | |
| move.l (a0)+,d0 | Same as above, but modify a0 to point to the next address. | |
| move.l (a0),(a1) | move the data that a0 point to, into the address that a1 points to. | |
| moveq | moveq #200,d0 | Move the number 200 into d0 with a quick command, but ensure the number is 8-bit. The operation still performs a 32-bit write to the register. |
| movea.l | movea.l d0,a0 | move the content of d0 into address register a0 |
| movea.l | a0,a1 | move the content of a0 into a1 |
| movem | movem.l d0-d2/a6,-(sp) | Move multiple. This example stores d0,d1,d2, and a6 on the stack. |
The key takeaways are:
- # is used to indicate a hardcoded number to be written; otherwise, it represents an address.
- The $ symbol is used to indicate that the number is in hexadecimal format.
- An address register in parentheses refers to the data stored at the address contained in the register.
- MOVEA is used when the destination is an address register, but most assemblers will automatically convert MOVE to MOVEA during assembly.
LEA – Load Effective Address
The LEA command creates a pointer to an address and stores it into an address register.
Example
lea mydata,a0 mydata: dc.l 1000,2000,3000,0
Here we have a data section with some numbers. The LEA command will load the a0 register with the address where these numbers are stored. A movea.l mydata,a0, on the other hand, would place the actual number 1000 into a0. This is an important distinction.
Add – Mathematical addition.
| Example | Description |
| add.l #50,d0 | Add the content of d0 with 50 |
| add.w #50,d0 | Same as above, but only writes the lower 16-bits. |
| addq.l #5,d0 | Add the content of d0 with 5. Quick command that only works with numbers between 1 and 8. |
| add.l d0,d1 | Add the content of d0 to d1. result is stored in d1 |
| adda.l #4,a0 | Adding 4 to the address stored in a0. a0’s pointer have now been increased with 4 bytes. |
Sub – Mathematical substraction.
| Example | Description |
| sub.l #50,d0 | Substracts the content of d0 with 50 |
| sub.w #50,d0 | Same as above, but only writes the lower 16-bits. |
| subq.l #5,d0 | Substracts the content of d0 with 5. Quick command that only works with numbers between 1 and 8. |
| sub.l d0,d1 | Substracts the content of d0 to d1. result is stored in d1 |
| suba.l #4,a0 | Substracts 4 to the address stored in a0. a0’s pointer have now been decreased with 4 bytes. |
Tip: If you want to clear an address register, you can indeed use:
movea.l #0,a0
However, the same result can be achieved using sub, which would be quicker for the CPU to execute.
suba.l a0,a0
CMP – Compare two values and sets status flag
| Example | Description |
| cmp.b #100,d0 | Check if the lower 8 bits of d0 contain the value 100. |
| cmp.l #100,d0 | Check if d0 (all 32 bits) contain the value 100. |
| cmp.l d0,d1 | Check if the content of d0 is the same as in d1. |
| cmpa.l a0,a1 | Check if content of a0 is the same as in a1 |
| cmpm.l (a0),(a1) | Check if the data at the location a0 points to is the same as the data at the location a1 points to. |
| cmpi.l #100,(a0) | Check if the data at the location a0 points to is 100 |
CMP instructions are paired with branch commands like beq, bne, and others to handle conditions correctly.
Start: cmp.b #'A',d0 ; Does d0 contain the letter A? beq .Afound ; Branch if equal to .Afound cmp.b #'B',d0 ; Does d0 contain the letter B? beq .BFound ; Branch if equal to .BFound .... .... .done rts ; Return from subroutine .Afound moveq #0,d0 bra .done ;Branch always .Bfound moveq #1,d0 bra .done ; Branch always
TST – Test for 0 and set status flag
| Example | Description |
| tst.l d0 | Does d0 contain 0? |
| tst.b (a0) | Does the byte a0 points to contain 0? |
TST is faster than using CMP to test for zero. Note that TST can not test an address register.
BTST – Bit Test – Test single bit for value 0
| Example | Description |
| btst #7,d0 | Does bit number 7 in d0 contain 0? |
| btst #20,d0 | Does bit number 20 in d0 contain 0? |
| btst #6,$bfe001 | Does bit number 6 at address $bfe001 contain 0 ? |
Important: When testing a bit in a data register, you can use a number between 0 and 31. When testing directly in memory, it’s an 8-bit operation, so you’re checking the byte at the given address. In that case, you should use a number between 0 and 7.
Branches – A branch jumps to a specific location when a condition is met.
| Example | Description |
| beq | Branch if equal to |
| bne | Branch if not equal to |
| bgt | Branch if greater than |
| bge | Branch if greater or equal to |
| blt | Branch if less than |
| ble | Branch if less or equal to |
| bra | Branch always |
cmp.l d0,d1 ; Compare d0 to d1 beq .isEqual ; Jump if d0 and d1 have the same values bgt .isGreater ; Jump if d0 have a greater value than d1 does blt .isLess ; Jump if d0 have a lower value than d1 does bne .isNotEqual ; Jump if d0 and d1 are not equal.
DBF – Decrement and Branch if False.
This is your go-to command for creating loops that run a specific number of times. DBF decreases the data register by one and branches to the label if the register is not -1.
moveq #9,d0 ; Loop 10 times (0-9) .loop: ; ... your code ... dbf d0,.loop ; d0 = d0 -1, branch if d0 >= 0
JSR – Jump to Subroutine
This command lets you jump to a subroutine and return to the same point once it’s finished. In AmigaOS, it’s widely used for calling OS functions.
BSR – Branch to Subroutine
BSR works just like JSR, but it’s a more efficient command that can only jump up to 32KB from the current location. It’s commonly used to jump to your own subroutines within your code.
RTS – Return from Subroutine
RTS returns from a subroutine called by either JSR or BSR.
Start: moveq #100,d0 move.l #5000,d1 bsr AddNumbers .... ; d0 now contains the value 5100 rts ; Exit this program ; AddNumbers ; INPUT: d0,d1 = numbers to add ; OUTPUT: d0 = result AddNumbers: add.l d0,d1 ; Adds d0 to d1 move.l d1,d0 ; Move result to d0 rts ; Return to line after the calling BSR
