Learn Multi platform 68000 Assembly
Programming... By Magic!
|
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! |
|
In the late 80's as the 8 bit machines died out, and the world
evolved into 16 bit... computing monsters were battling out for
supremacy!
Some people favored the Atari ST, others the Amiga... while some
preferred consoles like the Genesis or Neo Geo... while in Japan,
home computer users gazed in awe at the X68K with it's arcade
perfect gameplay...
In fact, there was no battle between the CPU's... as all these
were based on the 68000!... with the exception of the IBM PC's
8086 and the Super nintendo (which was based on a custom 16-bit
6502)... pretty much all the 16 bit machines of the 80's and early
90's were based on the 68000!
With it's 24 bit Address bus, it could access up to 16MB of
memory... it was it's 16 bit Databus that defined it as a 16 bit
processor... in fact all of it's registers are 32 bit!
The successor to the 8 bit 6800... the 68000 kept the powerful
indirect addressing... added loads of new registers... and a huge
command set!...
Adding a Supervisor mode,
Later models (the 68020,68030 and more) would add floating point,
true 32 bit support and more... but it's the first generation
68000 that these tutorials will be looking at!
|
|
|
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 |
Absolute Beginner Series - A warm
up for those who aren't ready to start programming, covers concepts and
terminology
68000 Assembly Series - lets learn the basic 68000 commands by
example!
Hello World Series - Lets look at
simple Hello World examples - each with a single ASM file.
Platform Specific Series - Now we know the basics, lets
learn about the systems we're covering
Simple Samples
Yquest Series (Xquest clone)
Photon Series (Tron clone)
Platforms covered in these tutorials
Amiga 500
Atari ST
Neo Geo
Sega Genesis (Sega Mega Drive)
Sinclair QL
X68000 (Sharp x68k)
PDF resources
Pocket
68000
guide
Motorola
68000
manual
What is the 68000 and what are 16
'bits' You can skip this if you know about binary and Hex
(This is a copy of the same section in the Z80 tutorial)
The 68000 is an 16-Bit processor with a 24 bit Address bus!
What's 8 bit... well, one 'Bit' can be 1 or 0
four bits make a Nibble (0-15)
two nibbles (8 bits) make a byte (0-255)
two bytes (16 bits) make a word (0-65535)
three bytes (24 bits) represent one address on the 68000
(0-16777215)
four bytes (32 bits) is the limit of what one register can store on the
68000 (0-4294967295)
Numbers in Assembly can be represented in different ways.
A 'Nibble' (half a byte) can be represented as Binary (0000-1111) ,
Decimal (0-15) or Hexadecimal (0-F)... unfortunately, you'll need to
learn all three for programming!
Also a letter can be a number... Capital 'A' is stored in the
computer as number 65!
Think of Hexadecimal as being the number system invented by someone wit h
15 fingers, ABCDEF are just numbers above 9!
Decimal is just the same, it only has 1 and 0.
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 |
Another way to think of binary is think what each digit is 'Worth'
... each digit in a number has it's own value... lets take a look at
%11001100 in detail and add up it's total
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 a binary number is small, it may be shown as %11 ... this is the
same as %00000011
Also notice in the chart above, each bit has a number, the bit on
the far right is no 0, and the far left is 7... don't worry about it
now, but you will need it one day!
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! |
|
But wait! I said a Byte could go from 0-255 before, well what
happens if you add 1 to 255? Well it overflows, and goes back to 0!...
The same happens if we add 2 to 254... if we add 2 to 255, we will
end up with 1
this is actually usefully, as if we want to subtract a number, we
can use this to work out what number to add to get the effect we want
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! |
The 68000 Registers
The 68000 has a far more advanced register set than the 8 bit machines we
are used to...it has 8 Data registers for general use (like BC) , 8
Address registers for memory functions and even stack pointers! (Can be
used as HL,DE and even
SP!)... Calling the 68000 16 bit is rather understating the system,
as all it's registers are actually 32 bit!
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) |
Each register can be used as an 8,16 or 32 bit register depending on your
requirements...smaller is faster, but it is not possible to use a 24 bit
register - so for memory pointers you'll have to work at 32 bit.
A7 is generally the stack pointer, and can also be referred to as SP,
however actually any address register can function as a stack pointer!
There are two 'Stack pointers' available, generally A7/SP will point to
the 'User Stack Pointer' (USP) ... however when the processor is in
supervisor mode, the Interrupt stack pointer (ISP) shadow stack pointer
will be used. Supervisor mode is only used by the system firmware and
operating system, and we won't really use it in programming our software.
The Program Counter is 32 bit, but the x68000 uses 24 bits of addressable
memory.
The Condition Code Register is only 16 bit, and only 8 bit can be accessed
outside of Supervisor Mode.
The Condition Control register's flags (CCR) and Status Register
(SR)
While the Status register is 16 bits, we can't use all
of it in normal user mode, - the top 8 bits are protected, but we can
access the first 8 bits (Called the CCR) anytime...
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 |
T-S--III ---XNZVC
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 |
The Carry flag and eXtend flag are almost the same... its should be noted
however that the eXtend flag is used by Add and Rotate commands... and the
Carry flag is used by Branching commands
We have no SCF/CCF type commands, if we want to set a flag, we do it with
ANDI or ORI...
For example, let's set the X flag (bit 4):
ORI #%00010000,CCR
Now, let's clear the X flag:
ANDI #%11101111,CCR
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 |
Addresses, Numbers and Hex... 68000 notification
We'll be using VASM for our assembler, and MOT syntax, but
most other 68000 assemblers use the same formats... however coming from
Z80, they can be a little confusing, so lets make it clear which is which!
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 |
A I Q and X!
Some Registers can support special versions, for example
ADD... there are special versions for different cases!
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 |
if we use Q we can speed up our code, and X will be needed to use the
carry (though with 32 bit registers we probably won't need it!)
Technically we should use A and I when using Address registers, or
immediate values - but actually our assembler will do it for us!
Commands and data sizes
Many (but not all) of the 68000 commands can work on different data sizes,
and working at smaller sizes will save us processing time... With commands
like Move we have up to 3 different options
Command |
Bits |
Meaning |
Move.B |
8 |
Move Byte |
Move.W |
16 |
Move Word |
Move.L |
32 |
Move Longword |
We don't have a 24 bit option! which may seem strange considering on the
68000 our addresses are 24 bits, but that's the way it is... so we use L
for 32 bits with addresses... the later 68020 DID have a 32 bit address
bus!
in fact commands where the destination is an address register always
affect all 32 bits of the destination - and the source register is
extended if it's smaller.
When using B or W with data registers, the other 24 or 16 bits of the will
be left untouched... THEY PROBABLY ARE NOT ZERO - so beware if you use
more bits later!
There are also some special commands like INC and DEC called ADDQ and
SUBQ... these can add or subtract a value upto (and including) 8... and do
so as a 'single command' with no parameter when compiled to bytes
Addressing modes, and their format in the source code
The 68000 has multiple addressing modes, to make things a bit
strange, modern notification has changed compared to the older one, lets
take a look!
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) |
Stack Pointers
technically, the 68000 has no unique 'SP' register... and no
Push Pop commands... but don't panic! A7 works as the stack
pointer... SP and A7 are the SAME REGISTER.... when you specify SP the
assembler will treat it as A7.
In fact we can use ANY address register a bit like a stack pointer...
though they will work differently when we move a Byte (A7 always moves
in word increments even when a byte is moved)... and we can't use
A0-A6 to remember subroutine calls caused by BSR/JSR/RTS
On the 68000 we have many clever functions, We can use the
Postincrement, and Predecrement commands to do our stack commands...
even better we can back up and restore MANY or ALL registers in one
go!... lets take a look!
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 wants to make our life hard with special formatting!
If you're working with code online, it may be in GCC forrmat,
which is really annoying, here's the MOT equivalent.
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 |
|
|
|
|
|
|
Code alignment
Because the 68000 is a 16 bit processor, commands need to be 16
bit aligned... you won't need to worry about this if you're only using
command code, but if you define byte data (like strings or bytes) you
need to ensure the next commands are word aligned, do this by putting
an even
command in before your next code line.
Effective Addresses
Unlike the 8 bit systems, on our computer
systems like the (AtariST and Amiga), we can't be sure where our code
will actually end up running
lets take an example where we have a label (eg mystring):
mystring: dc.b "Hello World"
Rather than loading it's address with MOVE.L
mystring,A0... we should use LEA mystring (load effective
address) or PEA mystring (push effective address... to stack) to get
the address...
On some systems, our MOVE.L
mystring,A0 command may actually work - due to address
relocation (where the OS will alter the addresses in the code!)... but
on others it will not, and you'll be left wondering why your code is
malfunctioning! therefore, you should always use PEA
and LEA to be sure!
The need for SPEED!
Compared to 8 bit processor's, the 68000 is so fast you may not
need to worry about speed, but there are some things worth knowing,
Firstly, you'll notice there are no INC or DEC commands, but we don't
need to keep using the slow MOVE commands...
This job is done by the 'QUICK commands'... why are they Quick?...
because the 'parameter' is included in the command... so the whole
line compiles to a single word... just like INC and DEC on the 8 bits!
We also have a CLEAR command... not only can it wipe registers... it
can wipe memory addresses!
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 |
Compare command and branches
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 |
The 68000 has much more flexibility than the 6502 - though some
of it's commands are the same!...
When we're doing comparisons, the commands we use depend on whether
our data should be treated as signed or unsigned.
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 |
Lesson 1 - Getting started with 68000
68000 is far more powerful than the 8-bit systems... the
advantage is that it can do some things far more easily than
the 8-bits... but the disadvantage is it can be a bit more
tricky, and there are a few mistakes we can make!
Lets take a look at the power of 68000! |
|
|
|
|
Vasm, Build scripts and Emulators
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
|
My sources will use a symbolic definition to define the platform we're
buiilding for, if you use my batch files this will occur
automatically, but if you're using your own scripts, you need to
define this with an EQU statement.
Here's the platform, symbol I use, and emulators we'll be looking at!
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 |
For these tutorials, I have
provided a basic set of include files that will allow us to
look at the technicalities of each platform and just worry
about the workings of 68000 for now...
We will look at ALL of this code later, in the Platform
specific series... but we can't do that until we understand
68000 itself!
The example to the right is split into 3 parts:
The generic
header - this will set
up the system to a text screen
The program
- this is where we do our work
The generic
footer - The functions and resources needed for the
example to work
The example here will load HEX 69 as a 32 bit value into
register D0 - then it will show the monitor
It's important
to notice all the commands are inset by one tab...
otherwise the Assembler will interpret them as labels.
Also, Make
sure you do not put any spaces in between the parameters...
on
VASM 'move.l #$69,d0'
will compile fine....'move.l
#$69 ,d0' will NOT (notice the space after 69)... you
will get an 'instruction not supported on selected
architecture' error |
|
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! |
|
Registers and Numbers
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! |
Here are all the 68000 Assembler ways of representing values, and how
they will be treated.
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 |
What's this JSR thing?... Jump to SubRoutine!
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! |
Bytes, Words and Long!
Despite being sold as 16 bit (because it has a 16 bit data bus -
meaning 16 'wires' coming out the cpu for data) Internally the 68000
is a 32 bit processor - all it's registers are 32 bit - that's not to
say it isn't faster to work at 8 bit... the 68000 can work in Bytes,
Words or Longwords!... lets take a look at what they mean!
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 |
Did you notice that 'L' in the Move.L command we used before? did you
wonder what it meant!... well it meant LONG!... you see, whenver we
use a command, we can specify if we want to work in Bytes - Words or
Longs!... some commands can't do all the kinds (see the cheatsheet)...
but it will be fastest....
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! |
|
VERY IMPORTANT!
When you copy a B or W from one register to another - the remaining
bits will be unchanged! this can cause problems... for example if you
do
Move.B #0,D1
Add.L D0,D1
You will not be able to rely on the value of D1.... why? because the
top 24 bits of D0 could have been ANYTHING....
and you just added them to D1! - either you should have done Add.B -
or Cleared the top bits of D0
NOTE: There is no 24bit command - even though all our addresses are 24
bit - so we need to use Long commands (32 bit) for address registers!
The Many faces of Move... and why we probably don't need them!
(Except CLR!)
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. |
|
Adding and subtracting
The 68000 has an ADD and
SUB
command for adding and subtracting at 8, 16 or 32 bit with
Add.B, Add.W or Add.L
Like with Move, there are some 'Special versions' we should
use ... Like AddI and
SubI with a # number
(immediate number) or AddA
/ SubA for addreses -
but once again VASM will worry about that for us!
One more interesting one is ADDQ
and SUBQ...
these
allow the addition or subtraction of a value up to 8...
If you're familiar with Z80 - these are effectively our
inc
and dec
commands - they're super fast, and compile to 2 bytes (the
minimum for the 68000) - and we can inc or dec by up to 7 in
one go!... these work with Data Registers, and Address
Registers.... that said - the Assembler wil work out when we
could have used them if we don't! |
|
The commands will all work as
we expect, but note, We could have used Add and Sub in all of these cases, and it would
have worked just fine...
Just forget about AddI
and SubI... the
Assembler will do it for us... and don't worry too much about
AddQ and SubQ
if you don't understand them!
|
|
Storing back to memory!
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! |
|
Lesson 2 - Addressing Modes of the 68000
The 68000 has a wide variety of addressing modes - some
of which are quite complex... and of course, all the
registers are 32 bit - so we have a lot of flexability when
it comes to memory addressing
Lets look at all the addressing modes and try them out!
|
|
|
|
|
Prepearation...
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. |
|
Prepearation... the result...
We've loaded a variety of test values into memory - and
we'll be using these with our indirect addressing modes |
|
Usage with ASM commands
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! |
1. Immmediate Addressing
Immediate addressing is where a
'fixed' value is used as a source... this means it will start
with a # Symbol |
|
In this example we've moved
#$12345678 directly into D0 - this is known as immediate
addressing! |
|
2. Absolute Addressing
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.
|
3. Data Register Direct Addressing
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 |
|
4. Address Register Direct Addressing
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 |
|
5. Address Register Indirect Addressing
Address Register indirect looks at the data held at the
address in the address register...
|
|
In this example A1=$FF0100... so the result loaded into D0
will be read from address $FF0100
Note: The 68000 is BIG ENDIAN... so when a Word or Long is
read in the larger value byte in the word or long (in
this case F0) is loaded from the first address... and the
smaller from the second (in this case F1) |
|
6. Address register Indirect Addressing with PostIncrement
Address Register with PostIncrement starts the same as
Address Register Indirect,
The data is read from the address in the Address Register...
however afterwards the address is incremented by the number of
bytes read...
For example if we read in a Word... then 2 will be added to
the address...
When used with
SP/A7 This is effectively the 68000 POP command
|
|
In our example we're reading in a Word... we read in $F0F1 -
and 2 is added to A1.... if we had read in a Long, 4
bytes would have been added to A1
|
|
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 |
|
7. Address register Indirect Addressing with PreDecrement
Address Register With Predecrement starts by decreasing the
specified address register with the amount to be read... then
it ends the same as Address Register Indirect - reading in the
amount to be read from the address in the address register
When used with
a WRITING command and SP/A7 This is effectively the 68000
PUSH command |
|
In this case we're reading in a Word... so 2 is subtracted
from the address register - and then the data is read from the
address in the address register... if we were reading a Byte 1
would be subtracted...
This command is typically used with WRITING commands - and is
effectively the PUSH command for the 68000 |
|
8. Address register indirect, with 16-bit displacement
This is another inderect addressing mode, however with a
'fixed' displacement - the data is read from the Address
register plus the offset - however the address register is
unchanged.
This is similar to (IY+n) on the Z80... it can also be used
for reading values pushed onto the stack |
|
In this example a byte was read from A1+2.... Note A1 was
unchanged...
The displacement does not have to be positive - it can also be
negative. |
|
9.Address register indirect with indexing and 8-bit displacement
This is the most advanced addressing mode available... the
address in the Address register is used PLUS the Data register
PLUS a fixed offset ... the value is read from the resulting
address...
If you want to do a command like (A0,D0) then use this
mode with a ZERO displacement - eg (0,A0,D0) |
|
In this example D1=1 and our displacement is 2... the result
is we're reading in from A1+3
As before A1, and D1 are unchanged by this command |
|
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
|
|
10.Program counter relative addressing with a 16-bit offset
The 'Program Counter' is the current position in the
program... this addressing mode allows for data to be read
from and address relative to the program counter... this is
useful for 'relocatable code'... where you don't know where
your program will actually end up running...
That said, it's possible you will never need this addressing
mode, so don't worry about it if you don't understand it!
Note: This
command can only be used for READING |
|
In this example we're going to load bytes $68 and $69 into
registers D0 and D1...
For D0 we're going to specify the offset as a number... for D1
we'll use the more reliable option of using a label. |
|
|
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.
|
11.Program counter
relative addressing with an 8-bit offset plus an index register.
This 'Program Counter' Relative addressing mode is similar,
however it also allows for a Data register to be added to the
offset and the result read from the final address...
the Address and Data register are unchanged
Note: This
command can only be used for READING |
|
In this example we're going to load a byte from the 'Test
Data' adding the D1 register as an offset (D1=1)
The first time we'll do this using a label... the second using
a numerical value... either way the result is the same.
In the example we'll read a byte from the 'Test Data' - offset
by D1 (1)... the result is we'll load in byte $69 into D0 and
D2 |
|
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 |
|
Lesson 3 - Loops and Conditions
We've looked at quite a few topics, but we've still not
learned enough to really get started... this time, we'll
learn about Loops and Conditions - and a bit more about
Labels and Subroutines
|
|
|
|
|
LEA - Load Effective Address
We mentioned LEA before, but we didn't discuss what it does...
LEA is like a special version of the Move command for Addresses
With Absolute addressing, it's the same as a move command,
However with commands like Program counter relative with offset - like
(Label,PC) - it will return the resulting address - where as a move
command would have returned the value in that address - whereas a move
would have returned the value.
This makes LEA good for making relocatable code which can work even
when it has been repositioned in memory... it also allows us to
save time if we're using the same complex address calculation - for
example (5,A0,D0) - many times... we can store the result in an
address register, and use that address register
It should be noted that VASM will convert MOVE commands like Move
$000011,A0 into a relative LEA command automatically wherever possible
as an optimization.
Below is a table showing different address modes - and how they work
with LEA and Move... we'll try the same commands in an example below
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... |
|
Some overlooked fundamentals - Labels and Symbols and subs
We've been rushing ahead to learn some commands, but we've
overlooked some real basics! Now Lrets fix that!
Labels are a 'Named' line in our compiled code...
Once we've used a label,
we can use it again later to Jump to it with a JMP, or use it as a
subprogram with JSR
|
|
A subprogram called with JSR
is like a GOSUB in basic - it jumps to the named label runs some commands,
then returns back to the line after the JSR... the return
command in 68000 is RTS
We're going to call a simple subroutine called AddTwo, it adds 2 to D0,
then returns
- not much use, but you can see the subroutine worked between
the two calls to Monitor D0
we can also use labels
to mark data, and use that data with a LEA
command!.... effectively the Label name is converted to the
line number by the assembler |
|
Jump and Branch... and why we don't need to worry!
The 68000 has two pairs of Commands, JMP and BRA for Jumping, and JSR
and BSR for calling a subroutine...
While the result is the same, In theory BRA/BSR will save some memory
- it saves the offset to the label - rather than the whole label -
saving a byte or two!... but VASM will optimize our code and do this
for us, so we can just ignore BRA and BSR if we want!
Defining Fixed values with Symbols
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!
|
|
Comparing and Branching on conditions (Bcc)
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!
|
|
Instead of BCS - there are a wide variety of other commands we can
use... when we do CMP D0,D1 we can use the following Bcc command
depending on the branch we want to do.
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! |
Be careful of B W L with Branch!
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! |
|
DBRA - Decrement and BRAnch - for easy looping!
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 |
|
Multiple conditions for a Case statement
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 for temporary storage
'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...
|
|
Push and Pop.... or -(sp) and (sp)+ as
they're known on the 68000
On the 68000 we use A7 - also known as SP as
our stack pointer - we use predecrement and postincrement to alter the
stack pointer
When we want to add a new item to the stack (PUSH) we use a command
like:
Move.W D0,-(SP)
When we want to take an item of the stack (POP) we use:
Move.W (SP)+,D0
We can Push Registers, or Immediate values, really anything
from the Addressing modes lesson last week!
We're going to use a fake stack
at a fixed location - so we can easily see what's going on...
this example will work on the GENESIS... but as always, the
principles will work on all systems.
Remember A7 and SP are THE SAME THING...
We can see the result... the stack pointer is set to $FF0100 -
and the preceding 16 bytes are shown to screen.... the current
Stack pointer is shown as a Magenta
Bar |
|
First lets push a word to the stack ($1234)... because
we pushed two bytes, the
Stack Pointer goes down by two to $FF00FE
You can see the two
bytes have been inserted into the stack memory
|
|
This time we'll push a Long onto the stack... the
Stack pointer will
go down 4 bytes - which is what we'd expect! |
|
This time we'll push two
individual Bytes on the stack... what will happen?
well... we'd probably expect the stack to go down by 2... but
it actually
goes down by 4!
Why? well the stack needs to work with Bytes, Words and
Longs... and W/L can only be used on even memory addresses -
so when we push
a byte to a7- the value is padded
by 1 byte...
Note:
This does not happen with
Move.B #$96,-(A6)... A7/SP is a special case! |
|
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 |
|
The Stack and JSR
We can use the stack pointer to backup and
restore register values ... the processor uses it too, to handle
calling Subroutines!... lets take a look!
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! |
|
MoveM - Moving Multiple items onto the
stack!
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.
|
|
PEA
- LEA for the stack!
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! |
Maths
commands, NEG, DIVS, DIVU... MULS, MULU
Lets take a look at some more advanced maths commands!
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:
Byte
1 |
Byte
2 |
Byte
3 |
Byte
4 |
RR |
RR |
QQ |
QQ |
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?
|
It's a TRAP!
'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.
|
|
Lesson 5 - Bits and swaps!
We've looked at maths and comparison commands, but quite
often we'll need to do things at the BIT level...
fortunately the 68000 has an incredible range of commands to
do everything we could want,
Lets take a look!
|
|
|
|
|
AND, OR and EOR... and NOT!
There will be many times when we need to change some of the bits in a
register, we have a range of commands to do this!
AND will return a bit as 1 where the bits of both parameters are
1
OR will set a bit to 1 where the bit of either parameter is 1
EOR is nothing to do with donkeys... it means Exclusive OR... it will
invert the bits of parameter 2 with parameter 1 - it's called XOR on
the z80!
NOT is similar to EOR - it flips ALL the bits and doesn't take
an extra parameter
Effectively, when a bit is 1 - AND will keep it... OR will set it, and
EOR will invert it
A summary of each command can be seen below:
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 |
In the Z80 tutorials, we saw a visual representation of how these
commands changed the bits - it may help you understand each command.
Sample |
EOR %11110000
Invert Bits that are 1 |
AND %11110000
Keep Bits that are 1 |
OR %11110000
Set Bits that are 1 |
|
|
|
|
Lets try these commands on
the 68000!
We'll use the commands at the byte level for simplicity, and
try each of the commands on a different register.... however
these commands also work at the Word or Long level.
|
|
The bits of each register will be set according to the
logical command. |
|
As with many commands on the 68000 - Technically we should use a
special version of the command when using an immediate value, however
actually the assembler does the work for us!
Normal command |
Immediate command |
And |
AndI |
Or |
OrI |
Eor |
EorI |
Testing and changing single bits with BTST,BSET,BCLR
and BCHG
There will be many times we want to test a bit of a register - we
could do this with AND commands, but there are more powerful commands.
we can specify a bit number, and a register and test that bit... the
Zero flag will be set if the bit was ZERO - or cleared if the bit was
ONE
This would effectively be the same as the 'bit' command on the Z80,
but on the 68000 we can change the bit at the same time!
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 |
Lets try out all these
commands... we'll set the bottom two bits of D0 to 1 and 0 ...
and we'll try each command out on these bits.
We'll show the contents of the ZeroFlag with a function called
ShowZero, and we'll show the results of each command on D0 |
|
The BTST command does not
change the bits... all the other commands had the same effect
on the Zero flag
BSET set both bits to 1
BLCR cleared both bits to 0
BCHG changed both bits to the opposite of what they were |
|
|
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 |
Rotating and shifting bits with ROL,ROR, LSL and LSR
There will be many times when we want to shift bits around... If we
shift all the bits in a byte left, we'll effectively double the number
- if we shift them right, we'll halve it
We may want to use 3 bits from the middle of a byte or word as a
'lookup' - and we'll need to get them in the right position...
You may not immediately see the need for bit shifting - but as you
program, you'll come across many times you need to do it...
The 68000 has a variety of commands - this time we'll look at ROtating
and Logical Shift commands...
Rotating is where we move the bits left or right, and any bit that
goes off the top or bottom of the byte (or word/long) will come back
on the other side of the byte... so if we rotate a byte 8 times, we'll
end up with what we started!
Logically Shifting is where we move the bits left or right, and any
bits that go off the top or bottom are lost - bits that are 'added'
are always 0... we can use this command for halving or doubling an
unsigned number.... if we Logically shift a byte 8 times, we'll end up
with 0!
We can Rotate or Logically shift Left or Right... there are two other
commands... Arithmetic Shift, and ROtate with EXtend - we'll cover
these next lesson!
Command |
Left |
Right |
ROtate |
ROL |
ROR |
ROtate with
eXtend |
ROXL |
ROXR |
Logical Shift |
LSL |
LSR |
Arithmetic Shift |
ASL |
ASR |
Lets try out the ROtate and
Logical Shift commands!
We'll set some bits, and rotate things around, and see the
difference between the results! |
|
We can see the difference
between the rotate and shift commands....
as bits are Rotated they come back, when they are Shifted they
don't!
Pretty obvious by the name of the commands really! |
|
|
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! |
Scc - Set on condition
There will be times we'll want
to do things based on a condition, and unlike on the 8 bit's,
with the 68000 we can set a byte of a register based on a
condition!
If the condition is true - the register will be set to $FF
(255 is considered to be true on the 68k)
If the condition is false - the register will be set to $00 (0
is considered to be false on the 68k)
the 'cc' part of Scc is an abbreviation - we can replace that
cc with any condition code, so like BEQ and BNE we can use SEQ
and SNE to set if EQual or Not Equal
|
|
The command only sets the
register at the BYTE level, and can only set $FF or $00 - but
you can use AND and OR commands to alter the values. |
|
SWAP and EXG - For exchanging registers and words!
There will be times we want to move data around
registers... it may be useful to swap two registers without using any
temporary registers or the stack, and we can do this with EXG...
also we may want to swap the top and bottom two parts of a Long... and
the SWAP command will do this for us - this may be useful for DIVide
commands - remember - they return the Remainer in one part of the
long, and the Quotient in the other
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) !
|
|
|
|
The Extend flag (X) the CCR and the SR
The 68000 cpu is a bit odd in that it has an eXtend flag and a Carry
flag... in most cases they will have the same value - but the commands
use the eXtend bit and are ADDX not ADC...so we'll refer to the
commands as using the Extend flag, not the carry flag.
That said, the purpose of the Extend flag X is basically the same as
the carry on the Z80 or 6502, it contains bits 'pushed' out of a
register by an overflowing command with ADDX, or a rotation
command with ROXL
In the 68000 cpu, the flags are held in and 8 bit register called the
Condition Code Register (CCR)... the CCR is part of the 16 bit Status
Register (SR).... because of the CPU protection, we can't normally
change the SR, but we can read it at any time... we can however set
the bits of the CCR - and we need to do this to set or the eXtend and
Zero flag.
The SR / CCR bits
Here are the content and meaning of the CCR /SR Bits
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 |
T-S--III ---XNZVC
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 |
Setting and Getting the CCR
When it comes to setting the
CCR, we have three options, AND to clear values , OR to set
values, and MOVE to move a combination of bits to the
ccr...
We can use these commands to make any changes to the CCR we
require...
If we want to get the current state of the CCR, we cannot move
the CCR to a register - however we can move the SR to a
register - and the bottom byte of the SR *is* the CCR |
|
The OR command can be used to set bits -we've set them all
to 1... note the top 3 bits didn't do anything!
The AND command can be used to clear bits
The MOVE command can be used to set all the flags to any set
of bits
If we want to get the contents of the CCR, we can use the Move
command to move the SR to a register - the bottom byte will be
the CCR |
|
|
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
|
|
Rotating bits with extend
We've learned how to use ROL
and ROR to move bits around a register, but sometimes we
may want to move them out of, or into a register - and we do
that via the extend bit with ROXL and ROXR
|
|
When we rotate to the right
with ROXR, the extend bit is pushed into Bit 7, and Bit Zero
is pushed out into the extend bit ...
This can be done with Words and Longs too... and we can shift
by more than 1 bit if we want with ROXR #2 or ROXR #n
And of course we can rotate Left with ROXL as well! |
|
If we set the X flag to 1
before the rotate we can fill the new bits with 1's |
|
As all the new bits are now 1,
the byte will eventually be filled with 1s |
|
|
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. |
Arithmetic shifting with ASL and ASR
Arithmetic shift Left and Right
store bits that are pushed out of the byte into the eXtend
flag, but they do not fill extra bits with eXtend
ASL fills extra bits with 0,
ASR fills extra bits with the last top bit - this is so that
ASRing a negative number will STAY negative.
|
|
You can see the results of
these commands here! |
|
By combining ASL and ROXL we
can use 2x 32 bit registers to effectively double 1x
64 bit value! |
|
We can see the result here! |
|
SUBX, ADDX and NEGX!
We can work on 64 bit or
greater numbers by using SUBX, ADDX and NEGX - these use the
eXtend bit to allow carry from each command to flow into the
next register
We'll use D0 and D1 and a 64 bit pair - and repeatedly Add and
Subtract 1 from the pair... we'll show the pair to screen each
time to see how they roll over as the pair go below Zero
The NEGX command will invert multiple registers...
One odd thing... if we do NEGX both times, the Z flag is not
set - so if we want to do a compare after the commands, we
need to set the flag manually before we start. |
|
The results can be seen here...
any 'carry' or 'borrow' from D0 will pass through to D1 |
|
|
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
|
EXT - Sign Extending Byte to Word... or Word to Long
The EXT command 'Sign Extends'
a register - effectively the top bit is expanded to fill the
register - this means a negative byte is converted to a
negative word, or a negative word is converted to a long
|
|
The result with a negative
number is the sign is extended...
With a positive number there is no appararent effect. |
Negative:
Positive:
|
NOP - Doing nothing.... and Macros for saving typing!
the NOP command does nothing -
it's mainly used for 'self modifying code' where code is
changed while it's running... but it can be used as a crude
way to slow things down.
Because the 68000 is so fast... NOP doesn't slow things down
much... so it's not really a good idea in practace!
Using the assembler we can create something called a 'macro' -
we can create a set of commands and give them a name - when we
use that name later, the assembler will replace the name with
the lines of code - note this is not the same as a call...
We'll use a macro here to create an EightNoOps command to slow
things down more
|
|
This example will swap the two registers - if we remove some
of the NOPS it will speed up |
|
Conditional compilation
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 |
|
Appendix
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 |
|
|
- - - - - |
| |
Buy my Assembly programming book on Amazon in Print or Kindle!
Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
Buy my Assembly programming book on Amazon in Print or Kindle!
Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
Buy my Assembly programming book on Amazon in Print or Kindle!
Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
|