Don't like to read? you can learn while you
watch and listen instead! Every Lesson in this series has a matching YOUTUBE video... with commentary and practical examples Visit the authors Youtube channel, or Click the icons to the right when you see them to watch the Lessons video! |
If you want to learn 68000 get the Cheatsheet! it has all the 68000 commands, and will allow you to easily see all the different commands, what they do, and how they affect the flags. |
We'll be using
the excellent VASM for our assembly in these tutorials... VASM
is an assembler which supports Z80, 6502, 68000, ARM and many
more, and also supports multiple syntax schemes... You can get the source and documentation for VASM from the official website HERE |
Assemblers will
use a symbol to denote a hexadecimal number, in 68000
programming $ is typically used to denote hex, and # is used to
tell the assembler to tell the assembler something is a number
(rather than an address), so $# is used to tell the assembler a
value is a Hex number In this tutorial VASM will be used for all assembly, if you use something else, your syntax may be different! |
Decimal | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ... | 255 |
Binary | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 | 11111111 | |
Hexadecimal | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | FF |
Bit position | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Digit Value (D) | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
Our number (N) | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
D x N | 128 | 64 | 0 | 0 | 8 | 4 | 0 | 0 |
128+64+8+4= 204 So %11001100 = 204 ! |
If you ever get confused, look at Windows
Calculator, Switch to 'Programmer Mode' and it has binary
and Hexadecimal view, so you can change numbers from one form to
another! If you're an Excel fan, Look up the functions DEC2BIN and DEC2HEX... Excel has all the commands to you need to convert one thing to the other! |
Negative number | -1 | -2 | -3 | -5 | -10 | -20 | -50 | -254 | -255 |
Equivalent Byte value | 255 | 254 | 253 | 251 | 246 | 236 | 206 | 2 | 1 |
Equivalent Hex Byte Value | FF | FE | FD | FB | F6 | EC | CE | 2 | 1 |
All these number types can be confusing,
but don't worry! Your Assembler will do the work for you! You can type %11111111 , &FF , 255 or -1 ... but the assembler knows these are all the same thing! Type whatever you prefer in your ode and the assembler will work out what that means and put the right data in the compiled code! |
Register | Bits | Function | |||
D0 | 31-24 | 23-16 | 15-8 | 7-0 | Data Register |
D1 | 31-24 | 23-16 | 15-8 | 7-0 | Data Register |
D2 | 31-24 | 23-16 | 15-8 | 7-0 | Data Register |
D3 | 31-24 | 23-16 | 15-8 | 7-0 | Data Register |
D4 | 31-24 | 23-16 | 15-8 | 7-0 | Data Register |
D5 | 31-24 | 23-16 | 15-8 | 7-0 | Data Register |
D6 | 31-24 | 23-16 | 15-8 | 7-0 | Data Register |
D7 | 31-24 | 23-16 | 15-8 | 7-0 | Data Register |
A0 | 31-24 | 23-16 | 15-8 | 7-0 | Address Register |
A1 | 31-24 | 23-16 | 15-8 | 7-0 | Address Register |
A2 | 31-24 | 23-16 | 15-8 | 7-0 | Address Register |
A3 | 31-24 | 23-16 | 15-8 | 7-0 | Address Register |
A4 | 31-24 | 23-16 | 15-8 | 7-0 | Address Register |
A5 | 31-24 | 23-16 | 15-8 | 7-0 | Address Register |
A6 | 31-24 | 23-16 | 15-8 | 7-0 | Address Register |
A7/SP (USP) | 31-24 | 23-16 | 15-8 | 7-0 | Address Register / User Stack Pointer |
SSP | 31-24 | 23-16 | 15-8 | 7-0 | Supervisor Stack pointer (A7/SP in Supervisor mode) |
PC | 31-24 | 23-16 | 15-8 | 7-0 | Program Counter |
CCR | 15-8 | 7-0 | Condition Code Register (Flags) |
F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
T | - | S | - | - | I | I | I | - | - | - | X | N | Z | V | C |
Flag | Name | Meaning |
T | Trace bit | 1 if tracing |
S | Supervisor | 1 if in supervisor mode |
I | Interrupt | interrupt level (0-7) |
X | eXtend | 1 if value of the C-bit is 1 |
N | Negative | 1 if topmost bit of result is 1 |
Z | Zero | 1 if result equals zero |
V | Overflow | 1 if arithmetic overflow occurred |
C | Carry | 1 if the last operation resulted in a Carry/borrow |
Flag |
Set |
Clear |
X |
ORI #%00010000,CCR | ANDI #%11101111,CCR |
N |
ORI #%00001000,CCR | ANDI #%11110111,CCR |
Z |
ORI #%00000100,CCR | ANDI #%11111011,CCR |
V |
ORI #%00000010,CCR | ANDI #%11111101,CCR |
C |
ORI #%00000001,CCR | ANDI #%11111110,CCR |
Prefix | Example | Z80 equivalent | Meaning |
# | #16384 | 16384 | Decimal Number |
#% | #%00001111 | %00001111 | Binary Number |
#$ | #$4000 | &4000 | Hexadecimal number |
#' | #'a' | 'a' | Ascii 'a' |
12345 | (16384) | decimal memory address | |
$ | $4000 | (&4000) | Hexadecimal memory address |
Letter | Command | Meaning | Why you need it |
Q | ADDQ #1,d0 | Add a small value to a register (Quick) | Faster |
A | ADDA <ea>,A0 | Add a value to an address register | Automatic |
I | ADDI #999,d0 | Add an immediate value | Automatic |
X | ADDX D0,D1 | Add with carry (extend) | Limited use |
Command | Bits | Meaning |
Move.B | 8 | Move Byte |
Move.W | 16 | Move Word |
Move.L | 32 | Move Longword |
Mode | New | Example | Old | Notes |
Immediate Data | #n | #1 | #n | Fixed value - use $ for Hex |
Absolute Address | $nnnnnn | $100000 | $nnnnnn | Fixed memory address |
Direct Data Register | Dn | D0 | Dn | Use Value in register |
Direct Address Register | An | A0 | An | Use Value in register |
Address register Indirect | (An) | (A0) | (An) | Use value in address pointed to by register |
Address register with Postincrement | (An)+ | (A0)+ | (An)+ | Use value in address pointed to by register, Then increase An... this can be used like a POP command! |
Address register with Predecrement | -(An) | -(A0) | -(An) | Decrease An, then read the value pointed to by the register... this can be used like a PUSH command! |
Address register indirect, with 16-bit displacement | (dd,An) | (5,A0) | d(An) | Read a value from the address in A0, shifted by fixed value d |
Address register indirect with indexing and 8-bit displacement | (d,An,Xi) | (5,A0,D0.L) | d(A0,D0.L) | Read a value from the address in A0, shifted by d+Dn (d can be 0 if you only want to shift by Xi)... Xi can be Dn.W , Dn.L, An.W or An.L |
Program counter relative addressing with a 16-bit offset | (dd,PC) | (5,PC) | d(PC) | Read from the current address plus a fixed value |
Program counter relative addressing with an 8-bit offset plus an index register. | (d,PC,Xi) | (5,PC,D0) | d(PC,Xi) | Read from the current address plus a fixed value plus a register (d can be zero if you only want to add Xi) |
Action | 68000 command |
PUSH D0 | move.l a6,-(sp) |
POP DO | move.l (sp)+,d0 |
PUSH EVERYTHING | moveM.L d0-d7/a0-a7,-(sp) |
PULL EVERYTHING | moveM.L (sp)+,d0-d7/a0-a7 |
GCC | MOT | Note |
move.l %d1,-(%sp) | MOVE.L D1,-(sp) | All registers are preceeded by % |
move.l %a1, %sp@- | MOVE.L A1,-(SP) | @ symbol denotes memory address like brackets in MOT |
move.l %a0@(0x34), %d0 | MOVE.L ($34,A0),D0 | displacement specified in brackets |
clr.l %a5@+ | CLR.L (A5)+ | Postincrement specified after address |
Action | 68000 command | RANGE |
INC / DEC | ADDQ.L #-1 , d0 | -8 to +8 |
LOAD | MOVEQ.L # , d0 | -128 to 127 |
CLEAR (set to 0) | CLR.L ($1000) | does not work on address registers |
Basic command | Comparison | 6502 command | Z80 equivalent | 68000 equivalent |
CMP Val2 | CP Val2 | CMP Val2,Val1 | ||
if Val2>=Val1 then goto label | >= | BCS label | JP NC,label | BCC label |
if Val2<Val1 then goto label | < | BCC label | JP C,label | BCS label |
if Val2=Val1 then goto label | = | BEQ label | JP Z,label | BEQ label |
if Val2<>Val1 then goto label | <> | BNE label | JP NZ,label | BNE label |
CMP D0,D1 | Signed | Unsigned |
D1 < D0 | BLT | BCS |
D1 <= D0 | BLE | BLS |
D1 = D0 | BEQ | BEQ |
D1 <> D0 | BNE | BNE |
D1 > D0 | BGT | BHI |
D1 >= D0 | BGE | BCC |
In these tutorials, we'll be
using VASM for our assembly, VASM is free, open source and
supports 6502, Z80 and 68000! We will be testing on various 68000 systems, and you may need to do extra steps (such as adding a header or checksum)... if you download my DevTools, batch files are provided to create the resulting files tested on the emulators used in these tutorials. Note: the Genesis and NeoGeo also have a Z80 processor for sound... you may want to Learn Z80 as well for those systems... You can do some sound from the 68000 on the Genesis... but you MUST use the Z80 for sound on the NeoGeo |
Platform | Symbol Definition Required | Emulator used |
Atari ST | BuildAST equ 1 | Steem |
Commodore Amiga | BuildAMI equ 1 | FSUAE |
Genesis (megadrive) | BuildGEN equ 1 | Fusion |
NeoGeo | BuildNEO equ 1 | MAME |
Sharp X68000 | BuildX68 equ 1 | WinX68kHighSpeed |
The sample
scripts provided with these tutorials will allow us to just
look at the commands for the time being... we'll look at the
contents of the Header+Footer in another series... Of course if you want to do everything yourself that's cool... We're lerning the fundamentals of the 68000 - and they will work on any system with that processor... but you'll need to have some other kind of debugger/monitor or other way to view the results of the commands if you're going it alone!... Good luck! |
Compared to the 8 bits... The 68000 is a monster processor!
it has 8 data registers (for storing maths) and 8 Address
registers (for storing addresses for read and write)... having 16 registers wouldn't be bad at all... but ALL these are 100% 32 bit registers... if you're used to accumulators, and one stack pointer - forget that!... all the Data registers are equally functional... and all 8 Address registers can push and pop data like a stack pointer (Don't worry if you don't know what that means - we'll learn about stack pointers later!) The Data registers are named D0-D7 - All are 32 bit - you can use them in any way you want. The Address registers are named A0-A7 - All are 32 bit - even though the Address bus on the 68000 is only 24 bit (meaning 16mb ram max)... Note: the system uses A7 as the stack pointer...if you use 'SP' the assembler will translate this to A7... so you probably only want to use A0-A6 for your own use Lets learn a command!... MOVE.L... Move is very powerful - it can move a 'fixed' value into a register... move a value from memory into a register... or from a register to memory... or even copy one register to another! Take a look at the example to the right... we're going to load D0, D1 and D2... but notice... we're going to load them in different ways... D0 will be loaded with #$69... D1 will be loaded with #69... and D2 will be loaded with 69... what will the difference be?? |
OR: |
Well here's the result... the values are shown in Hex... so D0=69... because specifying #$69 tells the assembler to use a HEX VALUE but D1=45... this is because without the $ the assembler used a Decimal value (45 hex = 69 decimal) D2=0... why? well when we don't use a # the assembler gets the memory address.... so we read from memory address decimal 000069!... of course we can do $69 or $0069 to read from address hex 000069 too! So #$xx = hex value .... #xx = decimal value.... and xx means read from address! This is the same as the 6502 - but different to the Z80!... Note - if you prefer, you can put addresses in brackets..in VASM. move.l (69),d2 works the same as move.l 69,d2 |
If you
forget the # you're code is going to malfunction - as the
assembler will use an address rather than a fixed value! It's an easy mistake to make, and it'll mean your code won't work... so make sure you ALWAYS put a # at the start of fixed values!... or you WILL regret it! 68000 also lets you put brackets around an address like Move.L ($FFFFFF),d0 |
The 68000
can address 24bits of memory... so memory addresses can be
from $00000000 to $00FFFFFF...
the top two digits of the 32 bit register won't work! Beware though! 68000 isn't like 8 bit - there's an operating system handling the memory, and it may not like us 'Messing' with memory our program shouldn't have - we'll learn about this later! |
Prefix | Example | Z80 equivalent | Meaning |
# | #16384 | 16384 | Decimal Number |
#% | #%00001111 | %00001111 | Binary Number |
#$ | #$4000 | &4000 | Hexadecimal number |
#' | #'a | 'a' | ascii value |
12345 or (12345) |
(16384) | decimal memory address | |
$ | $4000 or ($4000) |
(&4000) | Hexadecimal memory address |
We've been using this JSR
command... but what does it do? Well JSR jumps to a subroutine... in this case JSR monitor will run the 'monitor' debugging subroutine... when the subroutine is done, the processor runs the next command In this case that command is 'JMP *' which tricks the 68000 into an infinite loop! JSR in 68000 is the equivalent of GOSUB in basic or CALL in z80, it's identical to the 6502 JSR command!.... we'll look at how to make our own subroutine in a later lesson! |
JMP is a
jump command ... and * is a special command that means 'the
current line' to the assembler... so 'JMP *' means jump to
this line... This causes the 68000 to jump back to the start of the line... so it ends up running the jump command forever!... it's an easy way to stop the program for testing! |
Size | Letter | Bits | Range (decimal integer) | Range (hex) |
Byte | B | 8 | 0-255 | $00-$FF |
Word | W | 16 | 0-65535 | $0000-$FFFF |
Long | L | 32 | 0-4294967295 | $00000000-$FFFFFFFF |
Note... it is
not possible to load a Word or Long from an ODD address...
we can do Move.W (68),d0 but we CANNOT do Move.W (69),d0... of course we can do Move.B (69),d0... Byte reading can be on an odd or even address... It's worth noting that FUSION seems to ignore the invalid command and make it work anyway. |
The Move command has 3
different options Move.B
to move 8 bits... Move.W
to move 16 bits... or Move.L
to move 32 bits In this example we'll clear all 32 bits of registers D0-D2 Then we'll set all 32 bits of D0 using Move.L.... next we'll copy 16 bits to D1 with Move.W.... and we'll copy 8 bits to D2 using Move.B Finally we'll copy to D3 without specifying B W or L... what will happen? |
|
Here's the result - of course we set all 32 bits of D0 to
$69696969 When we copied 16 bits to D1, the top 16 bits were still 0 (because of that original 32 bit MOVE of #0 to all the registers) When we copied 8 bits to D2, the top 24 bits were still 0 When we didn't specify any length with D3 - the assembler assumed we were working at 16 bits... however I'd reccomend for clarity that you always specify B W or L for your own clarity... NOTE: Some commands ALWAYS work as L or B commands - like LEA or SBCD... so do not assume anything! |
Technically, the move command is different depending on what
we do with it, for example if we're setting an address we
really should use MoveA... this is because the Move command
cannot set an Address register... if we do Move.L $00111111,A0 it WILL work... even though it shouldn't!.... why? because VASM is kind - it knows we should have used MoveA - so it converts it for us... there is no negative to this - we're not 'Technically' correct - but who cares! another is MoveQ.L - which only works with L (32 bit) this will set a register to a value between -127 and 128... why use it? well it's FAST!..... MoveQ will compile to 2 bytes where as Move.L would take 6.... BUT VASM is kind again! it will detect if you could have used the MoveQ.L command - and convert your code to it automatically... There is one similar command we may want to use... CLR.L D0.... this clears (sets to 0) a register, and works with B, W or L... VASM will NOT automatically convert MOVE.L #0,D0 to CLR.L D0... it converts to MoveQ #0,D0 because MoveQ is faster. |
|
We've used CLR.L to clear D1... this compiles to a small 16
bit command. You can see we used MoveA.L to load $69696969 into A0... remember - while it's not 'technically correct'... we could have just used Move.L Next we used MoveQ.L to move $01 to D1... note we could have used Move.L in THIS CASE... and VASM would have automatically converted it to MoveQ... but remember, ther is no MoveQ.W or MoveQ.B |
If you're
confused by all this MoveA MoveQ and all this .B .W
and .L - Just forget it! Just use Move.L for everything!!! - it may not be quite as fast, but it will work, and the assembler will fix most of the times you should have used something else! |
If you do not
want this kind of optimization to occur, you can use the
'-no-opt' switch in VASM This will turn of the Optimisations Vasm would otherwise perform, which you may need to do if you're doing self modifying code!... though that's not something these tutorials will cover. |
The 68000 is a bit different to
the 8 bits... usually there will be an 'Operating system'
doing memory management for us, and this may mean we
don't know where our program ends up running, or what RAM
we've been given for out variables... Fortunately there is a simple solution... the LEA command will Load the Effective Address of a 'label' - the result... we don't need to worry about where the data really is!... Labels always appear at the far left of the code - and mark a 'defined line' of the code... but don't worry too much about 'Labels' yet - we'll cover them in a later lesson In this example 'UserRam' is a 'label' pointing to some RAM we want to use - and store some data into that ram... We'll use a function Monitor_MemDumpDirect to show some bytes of RAM to the screen! Note in this example we're working at the Byte level. |
|
Here's the result of the
programm running... you can see the bytes $11, $22 and $66
were written... these are the two values stored at the
start... and then the result of these two added to the $33
loaded into D2 But Note: Depending on the system the program is compiled for - and even the operating system and running drivers, the address the program uses will change! |
The 68000
is BIG ENDIAN... what's that mean... well... if you store
&1234 in addresses &0000 and &0001 then &12
will be stored in &0000, and &34 is stored in
&0001... Bytes are stored in descending order... BIG
numbers at the front END What? that sounds perfectly logical? Well maybe! but it's the opposite of the Z80 and 6502... not to mention the ARM and PC processors... all of which do it the other way round! |
Don't
go
writing to areas you don't know about and expect it to
work... on the 68000 many areas will have nothing there
(unused areas)... or ROM - or hardware 'registers'... If you write to one of those hardware registers something strange could happen! |
Before we do anything in todays lesson, we need to prepare
some 'test data' in memory and a couple of the registers. We're going to load a bank of 32 bytes into ram, and point the A1 register at the middle of them... we're also going to set D1 to 1, and clear D0 to start. These examples are designed for the GENESIS / Megadrive - because we're using a fixed memory address - they will not work on other systems - however the concepts of the different Addressing modes are universal, and will work on all 68000 systems. |
We've loaded a variety of test values into memory - and we'll be using these with our indirect addressing modes |
Lets's take a look at the cheatsheet You'll see on various commands an <ea> marker.... this means Effective Address In most cases, whenver you see this, you can use any of the Addressing modes we'll look at today. |
We're going to try our all the addressing modes with READ commands... but most of these commands also work with WRITING - and in fact, a wide variety of commands! |
Absolute addressing is where we read from a 'fixed' memory
address... of course the address is specified WITHOUT a #
symbol On a 68000 system, we need to be sure what we're doing, and what will actually be at the address we're reading/writing to... |
|
In this example we've loaded a WORD (+16 bits - 2 bytes)
from memory address $FF0100 - the middle of our test data... Note: the 68000 is BIG ENDIAN... this means D0 ends up with the value $F0F1 (on a little endian system it would be $F1F0) |
Note...
it's not possible to Read or Write a Word or Long to an ODD
address... so we could not do Move.W $00FF0101,d0 This is a limitation of the CPU - it can only work with Words or Longs on even byte boundaries. |
Data register direct is where the source is the value held
in a Data Register D0-D7 |
|
The Specified Data register will be copied to the destination |
Address register direct is where the source is the value
held in a Address Register A0-A7 |
|
The Specified Address register will be copied to the destination |
Because A7 is the stack pointer it works differently to other Address registers - if we write 1 byte to A7 with Postincrement, it will actually move 2... this is because it needs to stay byte aligned to work with Words and Longs |
When
we specify MOVE (2,A1,D1),D0 then D1 will be treated as
a WORD register... So if D1=$00010001 it will actually be
treated as $0001 If we want to use the full Long, we need to specify the whole of D0 we need to specif MOVE (2,A1,D1.L),D0 |
It's
important to bear in mind that our assembler may well be
doing optimizations of our code, and we cannot predict how
many bytes each command will take, therefore it's better to
use a label to specify the offset in real word code than the
(8,pc) we used here in this example. |
The PC
relative modes are something you can do without - they were
never used in Grime 68000 But you need to know about them as they're pretty powerful! They save space, and allow for relocatable code... it's also important to understand there are MORE addressing modes on the later processors... but we're only covering the 68000 in these tutorials |
Command | Move #label,A1 | LEA label,A1 | Move (5,A0),A1 | LEA (5,A0),A1 | Move (Label,PC) | LEA (Label,PC) | ||
Stage 1 | Get address of label | Get address of label | Calculate address A0+5 | Calculate address A0+5 | Calculate address of PC+-offset from label | Calculate address of PC+-offset from label | ||
Stage 2 | Get Value from calculated address | Get Value from calculated address | ||||||
Result A1= | label | label | Value at (A0+5) | Address of A0+5 | value at calculated address | resulting address from PC+-offset |
We're going to try each of the
addressing modes with the MOVE command, and then again with
the LEA command... We have some test data at Label1, and we'll try using it's address with various commands... |
|
The results of the move
commands are all shown in Green,
the results of the LEA commands are shown in Cyan... Note that in all cases except for Move with #label, the result in the register is values from the label - not the address we wanted.... With LEA we have the effective address resulting from the command... |
We can give named 'symbols' a value with the EQU command We can then use that named symbol later to get the same value... we can use symbols to store fixed values or memory addresses... Symbols are not memory, however... once we've defined them they can never change, though we can use a symbol to define a memory address! |
Of course there will be many times we need to do things
depending on the value of a register... We can do this with the CMP command... this compares a register to another register, a register to an Immediate value, or a register to a memory address - effectively all the options we saw in Lesson 2 there are a wide variety of branch commands - they're often known collectively as 'Bcc' - where cc will be a two letter description of the command These will 'Branch' (jump) to a label if the condition is true... in this example we're using BranchCarrySet - this effectively checks if the value is Less than the register (for an 'usigned' register)... The reason 'Carry is Set' is technical... it relates to the 'Carry flag' and the fact the 'CMP' command simulates a subtraction... don't worry about it yet, we'll cover all this later! |
CMP D0,D1 | Signed | Unsigned |
D1 < D0 | BLT | BCS |
D1 <= D0 | BLE | BLS |
D1 = D0 | BEQ | BEQ |
D1 <> D0 | BNE | BNE |
D1 > D0 | BGT | BHI |
D1 >= D0 | BGE | BCC |
We can Branch on a condition... but there's no BSR/JSR equivalent on conditions... If we want to Call a Subroutine on a condition being true, we need to SKIP a BSR command if the condition is not true... or Jump, and jump back of course! |
This time we're going to use the BEQ command - this will
branch if D0 is equal to the compared #0... But Wait! It doesn't work!... can you spot why? We set D0 to Zero at the Byte level with Move.B... but when we did CMP we didn't specify a level... so it worked at the word level! |
|
if we change CMP to CMP.B - it will now work...
alternatively we could change MOVE.B #0,d0 to MOVE.W #0,D0... The important thing is we compare at the same B/W/L level as we set the register to #0 if we want the expected result. This is very important, and you're likely to make the mistake a lot! |
Often we may want to loop a certain number of times, running
a command 6 times for example... We can do this easily with the DBRA command - it will decrease a register, and jump to a label if it's not gone below zero yet. Note... DBRA works at the WORD level.. so we need to set d0 to the count with a Move.W This command is similar to DJNZ on the Z80, but it's important to notice DJNZ on the Z80 stops AT zero... but DBRA stops when it goes BELOW zero |
It's important to understand
that ALL other languages convert to assembly... so anything
Basic or C++ can do can be done in ASM! We can chain multiple branches together to create 'If Then ElseIf' commands or even create 'Case' Statements in assembly, just by chaining multiple branch commands together. |
|
The result is shown on screen |
On
the 6502 branch commands can only jump 128 bytes away, but
the 68000 has no such limitation We can enable the 'ds 1024' command in the axample above, to add a bunch of space - and the branches will still work fine! |
Lesson 4 - Stack, Traps, and Maths! Now we know how to do conditions, jumping and the other basics, it's time to look at some more advanced commands and principles of Assembly.. Lets take a look! |
'Stacks' in assembly are like
an 'In tray' for temporary storage... Imagine we have an In-Tray... we can put items in it, but only ever take the top item off... we can store lots of paper - but have to take it off in the same order we put it on!... this is what a stack does! If we want to temporarily store a register - we can put it's value on the top of the stack... but we have to take them off in the same order... |
When it comes to removing from
the stack, the reverse happens,if we 'pop' off a byte,
the Stack
pointer goes up by two the register will be set to the popped off value |
|
We don't 'have' to pop things
off the stack in the same way we pushed them on... In this case, we've popped off a Long... and got the Word we pushed before, and half another long! The stack pointer doesn't care what we do - but if you don't know what you're doing your code may break |
|
We'll pop off another Long.... Our Stack pointer is back to it's starting value |
We're going to do a test
here... we'll show the stack to the screen... Then we're going to use JSR to jump to subroutine StackTest.... we'll show the stack again... and for reference, we'll also see the address of 'ReturnPos' Then we'll return to the main program and show the stack again... what will happen? |
|
When we call the Subroutine...
the Return
address is pushed
onto the Stack, and the stack pointer decreases by 1 Long (4
bytes) When we do a RTS - the address is Popped off the stack - we can confirm this because the stack pointer has returned to it's original value... For this reason, it's important to ensure that any changes we make to the stack during a subroutine are undone before the subroutine ends! |
There will be times we may want
to move multiple registers on to, or from the stack, we can do
this with MoveM! we can specify a range of registers with a minus - Lets try it out! |
|
In this example, D0, D1 and D2 are pushed onto the stack | |
We can specify ranges of
registers with a minus -, We can specify 'unrelated' registers with a slash / We can even combine the two, and push ALL the registers onto the stack, or pop them off |
If
we pop with MoveM.W then the results will be sign extended -
if we pop $1234 then our register will contain $00001234...
but if we pop $ABCD then the result will be $FFFFABCD. The normal Move.W (SP)+ command does not sign extend in this way. |
We learned before We can use
LEA to load the effective address of a label - which will work
reliably if our code is relocated by the operating system,
however there is another command. There may be times when we want to push the address of a label onto the stack, we can do this with a single command PEA |
|
We can see that the Address pushed on the stack with PEA is the same as the one we got with LEA |
We often push the parameters a function will use onto the stack before calling the function - so PEA may be useful for pushing addresses straight onto the stack in this case! |
Neg Neg converts a positive number to a negative one... Negative numbers in Hex can be calculated by flipping all the bits and adding 1... so $01 negated is $FF... and $02 negated is $FE... this means a signed byte can go from -128 to +127 |
Mulu -
Muls Unlike the 8 bits, the 68000 has multiply! we can give two values - and Multiply them together! MULU is for MULtiplying Unsigned numbers MULS is for MULtiplying Signed numbers |
Divu - Divs But why stop there? We even have a Divide command for integer division! The values returned are interesting, the returned long (4 bytes) are in the following format:
Q=Quotient (Successful divisions) R=Remainder If we Divide 301 by 10, we would have a Quotient of 30, and a Remainder of 1 If we're using signed numbers, we need to use a different commands... MULU is for MULtiplying Unsigned numbers MULS is for MULtiplying Signed numbers |
Be careful
you don't divide by zero! anything divided by zero is
infinity - which is a mathematical impossibility and will
make the WHOLE WORLD EXPLODE!!!1 Well ok, that won't happen, but your computer will crash, so don't do it, K? |
'Traps' in 68000 assembly are responsible for Error
handling, such as overflow within the processor... they are
numbered 0-15 and resemble RST's on the Z80... While we are unlikely to want to 'Cause' errors on our processor, some computers use them for operating system calls... for example on the AtariST TRAP #13 is effectively a bios call, and depending on your hardware, you may need to use them - but what the will do will depend on that system For now you just need to understand that TRAP #n is a system call - and you'll need to research that system to work out what it does. |
Command | Parameter 1 | Parameter 2 | Result |
AND | 1 0 1 0 |
1 1 0 0 |
1 0 0 0 |
OR | 1 0 1 0 |
1 1 0 0 |
1 1 1 0 |
EOR | 1 0 1 0 |
1 1 0 0 |
0 1 1 0 |
NOT | 1 0 |
0 1 |
Command | move.b #%10101010,d0 eor.b #%11110000,d0 |
move.b #%10101010,d0 and.b #%11110000,d0 |
move.b #%10101010,d0 or.b #%11110000,d0 |
move.b #%10101010,d0 not.b d0 |
Result | #%01011010 | #%10100000 | #%11111010 | #%01010101 |
Meaning | Invert the bits where the mask bits are 1 |
return 1 where both bits are1 | Return 1 when either bit is 1 | Invert all the bits |
Sample | EOR %11110000 Invert Bits that are 1 |
AND %11110000 Keep Bits that are 1 |
OR %11110000 Set Bits that are 1 |
Normal command | Immediate command |
And | AndI |
Or | OrI |
Eor | EorI |
Command | Meaning | Bit Before | Bit After |
BTST | TeST a Bit - bit unchanged | 1 0 |
1 0 |
BSET | test a Bit - and SET | 1 0 |
1 1 |
BCLR | test a Bit - and CLeaR | 1 0 |
0 0 |
BCHG | test a Bit - and CHanGe | 1 0 |
0 1 |
We're
using a fixed Immediate value in the examples... but
commands like btst d1,d0 are valid too! Remember, the 68000 has LOTS of different possibilities, so check the Cheatsheet, or the 68000 documentation for all the details |
Command | Left | Right |
ROtate | ROL | ROR |
ROtate with eXtend | ROXL | ROXR |
Logical Shift | LSL | LSR |
Arithmetic Shift | ASL | ASR |
Don't forget -There are two other pairs of shift commands - ROXL/ROXR and ASL/ASR... but we've already covered a lot of ground here - we'll have a look at those next lesson! |
Let's test the EXG and SWAP commands with some sample values. | |
We can see in the first example
the registers were swapped... in the second, we swapped the top and bottom word of the register. |
Rather
than EXG, We could use the stack to exchange registers...
but that would be slower - EXG is fast! Also, we could swap parts of a register with Rotate or shift commands - but again SWAP is more powerful! |
Lesson 6 - More Bits... Extends and Macros We covered bits last time, but the 68000 has so many commands, there's still more to do! The main thing we need to cover is the eXtend flag (X) ! |
F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
T | - | S | - | - | I | I | I | - | - | - | X | N | Z | V | C |
Flag | Name | Meaning |
T | Trace bit | 1 if tracing |
S | Supervisor | 1 if in supervisor mode |
I | Interrupt | interrupt level (0-7) |
X | eXtend | 1 if value of the C-bit is 1 |
N | Negative | 1 if topmost bit of result is 1 |
Z | Zero | 1 if result equals zero |
V | Overflow | 1 if arithmetic overflow occurred |
C | Carry | 1 if the last operation resulted in a Carry/borrow |
We can
only write to the Status Register in Supervisor mode - and
that may not be possible on all systems.... It doesn't matter, as it's probably not something you'll need anyway - so don't worry about it! |
We can use
MOVE SR,Dn to get the CCR on the 68000 - however this
command was REMOVED on later versions of the processor... on
those systems we have to use MOVE CCR,Dn ... which doesn't
exist on the original 68000! It's about the only time when the 68010 was not backwards compatible with the original commands |
You can use ROXL or ROXR with numbers of bits more than one, the eXchange flag will contain the final bit - this can be useful if you've (for example) read in a byte from a hardware device, and need a single status flag from the middle of the byte. |
The
formula for negating a number is 'flip the bits and add
one'... for this reason, we can't just use NEG on both DO
and D1 - as one would be subtracted from both, not just
the lowest one |
There may be times you want to compile two versions of a
program with parts of the program different... One option would be to use branches and conditions - but this adds size to the binary and slows down the program. Another option is conditional compilation - we can define a symbol with EQU - and use IFD, and ENDIF to define a range of code that will ONLY compile if the symbol is defined... and ELSE or IFND This allows us to compile two versions of the code depending on if the symbol is defined or not. |
|
The results of the program will be different depending on if the symbol is defined... |
with Testsym defined with Testsym not defined: |
Remember
- code excluded by the condition DOES NOT EXIST in the
resulting file... These IFD are how the example code is able to compile for so many systems - different modules are compiled in and out depending on the platform we develop to... Symbols can be defined on the VASM command line - so different batch files can compile different versions of your program |
Mnemonic | Description | Example | Valid Lengths | Addressing Modes | Flags |
ABCD Dm,Dn ABCD -(Am),-(An) |
Adds two 8 bit Binary Coded Decimal numbers with eXtend | ABCD D1,D2 | B | X n Z v C | |
ADD <ea>,Dn ADD Dn,<ea> ADDA <ea>,An ADDI #,<ea> |
Adds two numbers together. | ADD D1,D2 | B,W,L | X N Z V C | |
ADDQ #,<ea> | Adds a short immediate value # to <ea>. | ADDQ #1,A1 | B,W,L | X N Z V C | |
AND <ea>,Dn AND Dn,<ea> ANDI #,<ea> |
logically ANDs two numbers together. | AND D1,D2 | B,W,L | X N Z V C | |
ANDI #,CCR | logically ANDs immediate value # with the CCR | ANDI #$F0,CCR | B | X N Z V C | |
ANDI ##,SR | is only available in Supervisor Mode. | ANDI #$0F,SR | W | X N Z V C | |
ASL Dm,Dn ASL #<data>,Dn ASL <ea> |
Shift the bits Left for Arithmetic | ASL.W D1,D2 | B,W,L | X N Z V C | |
ASR Dm,Dn ASR #<data>,Dn ASR <ea> |
Shift the bits Right for Arithmetic | ASR.W D1,D2 | B,W,L | X N Z V C | |
Bcc # | Branch to offset/Label # if the condition cc is true. | BCC TestLabel | B,W | - - - - - | |
BCHG Dn,<ea> BCHG #,<ea> |
Test Bit Dn / # of destination <ea>, and flip bit in <ea> | BCHG #1,D1 | B,L | - - Z - - | |
BCLR Dn,<ea> BCLR #,<ea> |
Test Bit Dn / # of destination <ea>, and zero bit in <ea> | BCLR #1,D1 | B,L | - - Z - - | |
BRA ofst | BRA TestLabel | BRA TestLabel | B,L | - - - - - | |
BSET Dn,<ea> BSET #,<ea> |
Test Bit Dn / # of destination <ea>, and set bit in <ea> | BSET #1,D1 | B,L | - - Z - - | |
BSR # | Branch to Subroutine at relative offset #. | BSR TestLabel | B,W | - - - - - | |
BTST Dn,<ea> BTST #,<ea> |
Test Bit Dn or # of destination <ea> | BTST #1,D1 | B,L | - - Z - - | |
CHK <ea>,Dn CHK #,Dn |
Compare Dn to upper bound # Trap 6 if out of range | CHK #1000,D1 | W, L {on 68020+} |
- N z v c | |
CLR <ea> | Clear <ea> setting it to zero. | CLR.B $1000 | B,W,L | - N Z V C | |
CMP <ea>,Dn CMPA <ea>,An CMPI #,<ea> CMPM (Am)+,(An)+ |
CMP compares <ea> to Dn. | CMPI.B #$FF,(A1) | B, W, L (W,L for CMPA) |
- N Z V C | |
DBcc Dn,# | Decrease Dn, if Dn > -1 and branch if cc not true | DBRA D0,TestLabel | B,W | - - - - - | |
DIVS <ea>,Dn | Divide Signed numbers. Dn is divided by <ea>. Dn=Dn / <ea>. | DIVS #4,D1 | L = L/w | - N Z V C | |
EOR Dn,<ea> EORI #,<ea> |
Logical EOR (Exclusive OR) of bits in Dn or # with <ea>. | EOR #$20,D1 | B,W,L | - N Z V C | |
EORI #,CCR | Logical EOR # with the CCR | EORI #$F0,CCR | B | X N Z V C | |
EORI ##,SR | is only available in Supervisor Mode. | EORI #$F0,SR | X N Z V C | ||
EXG Dn,Dm EXG An,Am |
Exchange the contents of registers Dn and Dm. | EXG D1,D2 | L | - - - - - | |
EXT Dn | Sign extend register Dn, either extending a Byte to Word. | EXT.W D1 | W,L | - N Z V C | |
ILLEGAL | execute "Illegal Instruction Vector" (Trap 4). | ILLEGAL | - - - - - | ||
JMP # | Jump to absolute address #. | JMP TestLabel | L | - - - - - | |
JSR # | Jump to Subroutine at absolute address #. | JSR TestLabel | L | - - - - - | |
LEA <ea>,An | Load the effective address <ea> into An. | LEA (Label,PC),A1 | - - - - - | ||
LINK An,# | Creates a �Tepmporary area� on the stack for work | LINK A1,#-4 | - - - - - | ||
LSL Dm,Dn LSL #,Dn LSL <ea> |
Shift the bits in register Dn Left Logically by Dm or # bits. | LSL #1,D1 | X N Z V C | ||
LSR Dm,Dn LSR #,Dn LSR <ea> |
Shift the bits in register Dn Right Logically by Dm or # bits. | LSR #1,D1 | B, W, L | X N Z V C | |
MOVE
<ea>,<ea2> MOVEA <ea>,An |
Move the contents of source <ea> to the destination <ea2>. | MOVE #15,D1 | B, W, L | - N Z V C | |
MOVE <ea>,CCR | moves a 16 bit value from <ea> to the CCR | MOVE D0,CCR | W | X N Z V C | |
MOVE SR,<ea> MOVE <ea>,SR |
Move to or from the Status Register | MOVE SR,D0 | W | X N Z V C | |
MOVE USP,An MOVE An,USP |
Transfer the User Stack Pointer to or from address register An. | MOVE USP,A0 | L | - - - - - | |
MOVEM
<ea>,<Regs> MOVEM <Regs>,<ea> |
The MOVEM command moves multiple registers | MOVEM.L (A1),D0/D3 | B,W,L | - - - - - | |
MOVEP Dn,(#,An) MOVEP (#,An),Dn |
Move 16 or 32 bits to a set of memory mapped byte data ports. | MOVEP.L D0,(4,A1) | W,L | - - - - - | |
MOVEQ #,Dn | adds short immediate # to the register Dn. | MOVEQ #1,D1 | L | - - - - - | |
MULS <ea>,Dn | Multiply Signed numbers. Dn=Dn*<ea>. | MULS #4,D1 | L=W*W | - N Z V C | |
MULU <ea>,Dn | Multiply Unsigned numbers. Dn=Dn*<ea>. | MULU #4,D1 | L=W*W | - N Z V C | |
NBCD Dn | Negates BCD byte with eXtend. Dn. Dn=(0-Dn)-{X flag} | NBCD D1 | B | X n Z v C | |
NEG <ea> | Negate <ea> | NEG D0 | B, W, L | X N Z V C | |
NEGX <ea> | Negate <ea> with eXtend | NEGX <ea> | B, W, L | X N Z V C | |
NOP | No Operation. | NOP | - - - - - - | ||
NOT <ea> | Invert/Flip all the bits of <ea>. | NOT.L D1 | B, W, L | - N Z V C | |
OR <ea>,Dn OR Dn,<ea> ORI #,<ea> |
logically ORs two numbers together. | OR D1,D2 | B, W, L | - N Z V C | |
ORI #,CCR | logically ORs immediate value # with the CCR | ORI #$0F,CCR | B | X N Z V C | |
ORI ##,SR | logically ORs immediate value ## with the Status Register. | ORI #$0F,SR | W | X N Z V C | |
PEA <ea>,An | Push the effective address <ea> onto the stack. | PEA (Label,PC) | L | - - - - - | |
RESET | Sends an "RSTO" signal | RESET | - - - - - | ||
ROL Dm,Dn ROL #,Dn ROL <ea> |
Rotate bits in Dn to the Left by a number of bits | ROL.B #8,D1 | B, W, L | - N Z V C | |
ROR Dm,Dn ROR #,Dn ROR <ea> |
Rotate bits in Dn to the Right by a number of bits | ROR.B #8,D1 | B, W, L | - N Z V C | |
ROXL Dm,Dn ROXL #,Dn ROXL <ea> |
Rotate bits in Dn to the Left, with the eXtend bit | ROXL.B #8,D1 | B, W, L | X N Z V C | |
ROXR Dm,Dn ROXR #,Dn ROXR <ea> |
Rotate bits in Dn to the Right, with the eXtend bit | ROXR.B #8,D1 | B, W, L | X N Z V C | |
RTE | Return from Exception. | RTE | X N Z V C | ||
RTR | Return and Restore condition codes. | RTR | X N Z V C | ||
RTS | Return from a Subroutine. | RTS | - - - - - | ||
SBCD Dm,Dn SBCD -(Am),-(An) |
Subtracts two 8 bit Binary Coded Decimal with eXtend carry | SBCD D1,D2 | B | X n Z v C | |
Scc <ea> | Set <ea> to 255 or 0 according to condition cc. | SEQ.B TestLabel | B | - - - - - | |
STOP ## | Load the SR Status register with 16 bit immediate ## and halt | ||||
SUB <ea>,Dn SUB Dn,<ea> SUBA <ea>,An SUBI #,<ea> |
Subtracts two numbers. | SUBI.B #1,(A1) | B, W, L | X N Z V C | |
SUBQ #,<ea> | Subtracts a short immediate value # from <ea>. | SUBQ #1,A1 | B, W, L | X N Z V C | |
SUBX Dm,Dn SUBX -(Am),-(An) |
Subtracts with the eXtend bit. | SUBX D1,D2 | B, W, L | X N Z V C | |
SWAP Dn | Swap the high and low words of register Dn. | SWAP D1 | W | - N Z V C | |
TAS <ea> | Test and set <ea>. | TAS D1 | B | - N Z V C | |
TRAP # | causes a jump to exception vector number #. | TRAP #1 | - - - - - | ||
TRAPV | If the oVerflow flag (V) is set, call overflow trap vector | TRAPV | - - - - - | ||
TST <ea> | Set the flags according to <ea>. | TST.B D1 | B, W, L | - N Z V C | |
UNLK An | Reverse the process performed by the LINK command. | UNLK An | - - - - - |