SEGA Genesis: Motorola 68000
This page serves as a extended cheat sheet, condensing selected information from MC68000 assembler manuals and adding some useful examples. This is by no means a comprehensive guide to MC68000 assembler code.
The information here is system agnostic, and should be the same for all MC68000 systems, Sega Megadrive/Genesis, Commodore Amiga, Neo Geo, etc.
Table of contents:
- Useful References
- Overview
- The Move Instruction
- Bit Manipulation
- Branching
- Loops
- Changelog
- References
Useful References
Instruction References
-
M68000 Assembler Reference Manual (Text)
Explains instructions as well as assembler directives.
- Motorola
-
Motorola 68000 Reference Manual (PDF)
Motorola 68000 Reference Manual, the ultimate source of MC68000 information.
- Motorola
-
detailed info from the Motorola reference manual, collected in a handy HTML document.
- Flint/DARKNESS
-
MC68k Beginner's Tutorial (HTML)
Great MC68000 tutorial with a lot of concrete examples
- MarkeyJester
-
Intro to 68k assembly language (HTML)
Introduction to instructions
- Tetracorp
Instruction Timing
These pages basically contain the same information, pick the one you like the most:
Overview
A list of potentially useful one-liners:
--------------------------------------------------------------------------
Example One-liners
Code Description
--------------------------------------------------------------------------
move.l 0,a0 a0 = *(0) (direct addressing)
move.l #0,a0 a0 = 0 (immediate)
move.w (a0,d0),d1 d1 = a0[d0] (indirect, indexed)
clr.w d0 d0 = 0
lea 0,a0 a0 = 0
lea 4(a7),a7 a7 = a7 + 4
lea 0(a2,d0.w),a2 a2 = a2 + d0
add.l d0,a2 a2 = a2 + d0
subq.w #2,a3 a3 = a3 - 2
muls.w #8,d0 d0 *= 8
lsl.w #1,d0 d0 <<= 2 / d0 *= 2 (assumes unsigned int)
lsl.w #8,d0 d0 <<= 8 (max shift bits)
not.w d0 inverts all bits
exg d0,d1 exchange two registers
nop no operation
--------------------------------------------------------------------------
Here's a table of examples that may help clarify certain edge cases related to instruction data size and other issues:
--------------------------------------------------------------------------
Clarifying Examples
------- D0 ------- ------- *0 -------
Code before after before after
--------------------------------------------------------------------------
swap d0 AAAABBBB BBBBAAAA
add.b #$20,d0 FFFFFFFF FFFFFF1F
sub.b #$20,d0 FFFFFF00 FFFFFFE0
move.b #$20,D0 FFFFFFFF FFFFFF20
move.w #$20,D0 FFFFFFFF FFFF0020
move.l #$20,D0 FFFFFFFF 00000020
move.b #$20,(0) FFFFFFFF 20FFFFFF
move.w #$20,(0) FFFFFFFF 0020FFFF
move.l #$20,(0) FFFFFFFF 00000020
and.b #$F,d0 FFFFFFF1 FFFFFF01
and.w #$F,d0 FFFFFFF1 FFFF0001
lsl.b #4,d0 00000012 00000020
lsl.w #4,d0 00001234 00002340
lsl.l #4,d0 12345678 23456780
--------------------------------------------------------------------------
The MC68000 has a lot of useful addressing modes, here's an overview.
--------------------------------------------------------------------------
MC68000 Addressing Modes
Addressing Mode Example Description
--------------------------------------------------------------------------
Immediate Mode: Data specified in code ...................................
Immediate moveq #5, d0 Load immediate 5 into d0
Absolute Mode ............................................................
Absolute Short move.b $1000, d0 Byte from addr $1000
Absolute Long move.l $00100000, d1 Long from addr $00100000
Direct Mode: Data in registers ...........................................
Data Register Direct move.b d0, d1 Byte from d0 to d1
Address Reg. Direct move.l a0, a1 Address from a0 to a1
Indirect Mode: Address register points to data ...........................
Address Reg. Indirect move.b (a0), d0 Byte from address in a0
Post-Increment move.w (a0)+, d1 Word from (a0), then a0+2
Pre-Decrement move.l -(a1), d2 a1-4, then load long
Displacement move.b 4(a2), d3 Byte at a2 + 4
Displacement is a 16-bit
signed int
Indexed move.w 0(a3,d4.w), d5 Word at a3 + d4
PC Relative ..............................................................
PC Relative Displ. move.w 6(pc), d2 Word from PC + 6
PC Relative Indexed move.b 0(pc,d3.w), d4 Byte at PC + d3
--------------------------------------------------------------------------
The Move Instruction
Here are some clarifying examples of how the MOVE instruction works.
move.l #$12345678,d0 ; d0 = $12345678
move.b d0,d1 ; d1 = $78
If memory contains:
addr data
$00000000 $12345678
move.l 0,d0 ; d0.l = $12345678
move.w 0,d1 ; d1.w = $1234
move.b 0,d2 ; d2.b = $12
Address registers:
lea 0,a0 ; a0 = 0
move.l #0,a0 ; a0 = 0
move.l #$FF,(a0) ; Write $000000FF to address 0
Displacement addressing:
lea 0,a0 ; a0 = 0
move.w #$17,2(a0) ; Write $17 to address 2
Note that the displacement always is measured in BYTES, regardless of data size.
--------------------------------------------------------------------------
Move Variants
Example Description
--------------------------------------------------------------------------
moveq #127,d0 move to data register, only [-128;127].
- optimized form of move.b
movea.l #$12345678,a0 only way to MOVE to an address reg.
- Assembler will automatically generate this
from move.l #$12345678,a0
movem.l d0-d7/a0-a6,-(sp) move multiple registers
--------------------------------------------------------------------------
Bit Manipulation
--------------------------------------------------------------------------
Example One-liners
Code Description
--------------------------------------------------------------------------
not.w d0 d0 = ~d0 Invert all bits
and.w #%11111000,d0 d0 &= 0b11111000 Set lower 3 bits to 0
or.w #%00000111,d0 d0 |= 0b00000111 Set lower 3 bits to 1
--------------------------------------------------------------------------
Set first 3 bits of D0 to %101
and.w #%11111000,d0
or.w #%00000101,d0
Branching
If d0 == 7, do stuff
cmp.w d0,#7 ; compute 7-d0 and set CC flags (ignore result)
beq .done ; branch if flag Z is set
... ; do stuff if flag Z is NOT set
.done
If d0 != 0, do stuff
tst.w d0 ; set Z if d0 is 0
bne .done ; branch if flag Z is not set
... ; do stuff if flag Z IS set
.done
The Condition Code Register (CCR, lower byte of status register) has 5 bits that are set by certain instructions:
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | X | N | Z | V | C |
+---+---+---+---+---+---+---+---+
C - Carry
V - Overflow
Z - Zero
N - Negative
X - Extend
The branching instructions depend on the CCR bits:
--------------------------------------------------------------------------
Branching Instructions
Mnemonic Branch on what? Boolean formula
--------------------------------------------------------------------------
BCC carry clear !C
BCS carry set C
BEQ equal Z
BGE greater than or equal (N and V) or (!N and !V)
BGT greater than (N and V and !Z) or (!N and !V and !Z)
BHI higher than !C and !Z
BLE less than or equal Z or (N and !V) or (!N and V)
BLS lower than or same C or Z
BLT less than (N and !V) or (!N and V)
BMI minus (i.e., negative) N
BNE not equal !Z
BPL plus (i.e., positive) !N
BVC overflow clear !V
BVS overflow set V
--------------------------------------------------------------------------
Unconditional Branching
The unconditional branch instructions
JMP, BRA
are functionally equivalent, but after assembly, JMP will use an absolute address, and BRA will use a relative one:
jmp label ; set PC = #label
bra.w label ; set PC += label (relative to current addr)
bra.s label ; "
JMP could be to a word (first 64 KB) or long address, which is 10 or 12 CPU cycles, respectively.
BRA is to a word or byte offset, and always takes 10 CPU cycles.
In cases where your code resides in memory above address $FFFF, using BRA will always be 2 cycles faster, but sometimes will have to be converted to a JMP, if the target address is too far away.
JSR and BSR are like JMP and BRA, but store PC on the stack, to enable returning using RTS.
Loops
We can implement loops using DBcc instructions, instructions that optionally check a condition and decrement a loop counter.
Simple Loops
To perform action N times we use dbra/dbf:
move.w #N-1,d7
loop
... ; perform this N times
dbra d7,loop ; dbra
If you prefer setting N-1, we can jump directly to the dbra:
move.w #N,d7
jmp loop_end
loop_start
... ; perform this N times
loop_end
dbra d7,loop_start
C-style For Loops
If you want a C-style for loop:
move.b #0,d7 ; for(i=0; i<3; ++i):
loop
cmp.b #3,d7
bge loop_end
... ; do something, d7 = i
add.b #1,d7
bra loop
loop_end
Modulus
If you want an unsigned integer that runs from 0..17 and then wraps to 0:
move.w #0,d0
add.w #1,d0 ; d0 = (d0+1) % 18
cmp.w #18,d0
blt .skip ; BLT: lower than or same, unsigned!
sub.w #18,d0
.skip
Note: only works if we don't add more than 1 at a time to d0.
Changelog
- 2025-11-11 Transferred notes to this page
References
-
dbra and dbf are two mnemonics for the same processor opcode 0x51C8.
Implementation of dbra:
[Dn] <- [Dn] - 1 {decrement loop counter} IF [Dn] = -1 THEN [PC] <- [PC] + 2 {fall through to next instruction} ELSE [PC] <- [PC] + d {take branch}Or, in assembly:
dbra Di,Addr = sub.w #1,Di cmp.w #-1,Di bne Addrdbra is the same as dbf:
dbf ; No condition is tested, just the count terminates the loop. ; See description of DBcc. -
These instructions work like dbf/dbra, but with an extra condition check 'cc':
DBcc instructions are implemented like this:
IF(condition false) THEN [Dn] <- [Dn] - 1 {decrement loop counter} IF [Dn] = -1 THEN [PC] <- [PC] + 2 {fall through to next instruction} ELSE [PC] <- [PC] + d {take branch} ELSE [PC] <- [PC] + 2 {fall through to next instruction}