News
Documents
Source Code
Downloads
Message Board


Sega Megadrive Assembly Programming Basics

Copyright, Lewis Bassett, December 2005


Chapter 4 - Assembly Language

Section 2 - Arithmetic Instructions

Arithmetic instructions are used to move things forwards. Without these, our programs would be unable to move onto new things.

First I'll explain the way the M68000 uses numbers. As you know, data registers are 32-bits in size. Most instructions can access this as 8-bits, 16-bits or the whole 32-bits. When an instruction accesses a register as 16-bits, it reads (or writes) 16-bits from the end of the register, right to left. The very left bit is called the most significant bit and the utmost right bit is called the least significant bit. The least significant bit (the utmost left) is always accessed, regardless of whether the instructions uses 8-bits, 16-bits or 32-bits.

On a computer, a number can either be signed or unsigned. What does this mean? It means that a number can be a plain and simple 25, or it can be -25 or +25. To make this work, the most significant bit is used to represent the sign instead of part of the number.

Let's take 8-bits. Normally, with 8 bits we can represent any numbers from 0 to 255; this is called 'unsigned'. However, if we want the number be signed, we would have have to used the most significant bit to represent a sign. This nows leaves us only able to represent the numbers 0 to 128. However, if we set the most significant bit, we can also represent 0 to -128.

In binary '01111111' represents 128, regardless. If the number is unsigned, 11111111 represents 255. However, if the number is signed, '11111111' actually represents -128.

Add

The 'Add' instruction is used to add the source operand to the destination. Either one or both of the opcodes must be a data register. Address registers cannot be used with this instruction, but memory addresses can.

	Add.w	d0,	d1;
The above instruction adds the contents of d0 to the contents of d1. Notice that the add instruction also requires the programmer to specify whether the instruction is working with a byte, word or longword. Add instructions cannot work with immediate data.


Adda - Add Address

To add addresses, we use the 'adda' (ADD Address) instruction. Adda instructions require that the destination is an address register. The following instructions demonstrate it's use.

	move.l	#$ff0000,		a1;
	move.b	#$10,		d0;
	move.b	#$04,		d1;	
	move.l	d0,		(a1);
	adda.b	d1,		a1;
	move.l	d0,		(a1);
First, I pointed register a1 to address $ff0000. I then put the number $10 into d0, which is later written to memory. I also put $4 into d1, which is used to add to the address and increment it (by four bytes). I then write to the address in a1, then increment it, then write to the new address. This code is very pointless and messy, as the same code can be written this way:
	move.l	#$ff0000,		a1;
	move.b	#$10,		d0;
	move.l	d0,		(a1)+;
	move.l	d0,		(a1);
The code directly above is faster and neater, and allows the processor to work out how many bytes to increment the address by, rather than us. However, adda is a very useful instruction, as it allows us to increment an address using a variable number, creating more flexable code.


Addi - Add Immediate

One last variation of the add instruction we will use, is the 'addi' (ADD Immediate) instruction. Addi works exactly the same as add, only it allows the programmer to add immediate data.

	addi.b	#$02,	d0;
The above instruction adds $2 to the contents of d0. Addi can also add an immediate number to an address.
	addi.b	#$02,	$ff0002;
There are also other variants of the add instruction. I will not explain them here, but more information can be found in the M68000 Programmer's Reference.


Sub - Subtract

The 'sub' (SUBtract) instruction is used the subtract a value from registers or memory. It works exactly the same as the add insruction, except it subtracts a value, rather than adding it.

	sub.l	d0, 	d1;
The above instruction subtracts the value of d0 from the contents of d1, as a longword. Sub instructions also cannot use immediate data.


Suba - Subtract Address

'Sub' also has the variants 'suba' (SUBtract Address)which works like its Add counterpart.

	move.b	#$02,	d0;

	move.l	#$01,	(a1);
	suba.b	d0,	a1;
	move.l	#$01,	(a1);
These instructions work exactly the same as:
	move.l	#$01,	-(a1);
	move.l	#$01,	(a1);
Using suba to subtract from working addresses is very messy, but can often come in quite useful. This will be demonstrated later when we produce working Sega code.


Subi - Subtract Immediate

Programmers can also subtract immediate values in exactly the same was as Addi works:

	subi.b	#$09,	$ff0008;
This subtracts the value $9 from the contents of address $ff0008.


Neg - Negate

The NEG instruction simply changes a positive number into a negative, ie, it subtracts it from zero. NEG cannot work with immediate data as it is a single operand instruction.

	move.b	#53,	d0;
	neg.b	d0;
After being moved into register d0, the number 53 is then changed to -53.


Multply

Multiplication is done using two different instructions: MULU and MULS. Both instructions work with 16-bits, however the result is stored as 32-bits. The multiply instructions require that the destination is a data register, however the source can be a memory location or a data register. The result is always stored in the destination register.

MULU (MULtiply Unsigned) is used whenever we wish to multiply an unsigned number. Consider the following demonstration:

	move.w	#16,	d0;
	move.w	#02,	d1;
	mulu	d1,	d0;
Here we place 16 into register d0, and 02 into register d1. D0 is then multiplied by the contents of d1. The contents of d0 is now 32.

MULS (Multiply Signed) is used if we want to multiply a signed number. It works in exactly the same was as MULU, except we can also work with negatives too. MULS also works with 16-bits. Consider this demonstration, which uses negative numbers.

	move.w	#16,	d0;
	move.w	#1,	d1;
	neg.w	d1;
	muls	d1,	d0;
First, I moved 16 into register d0, and 1 into register d1. D1 is then negated, changing 1 to -1. D0 (16) is then multplied by d1 (-1). The contents of d0 is now -16.


Divide

Divistion is also done using two different instructions for signed and unsigned: DIVU and DIVS. Just like with the multply instructions, divide instructions require that the destination be data register, and that the source is either a memory address of a data register. The result is stored as 32-bits, but the instruction only works with 16-bits. The result is stored in the destination data register.

DIVU (Divide Unsigned) is used to divided an unsigned number.

	move.w	#16,	d0;
	move.w	#8	d1;	
	divu	d1,	d0;
After 16 and 8 are moved into registers d0 and d1, d0 (16) is divided by d1 (8). D0 now holds the number 2, as 32-bits.

DIVS (Divide Signed) is used if we want to divide signed numbers, either positive or negative, just like the MULS instruction.

	
	move.w	#16,	d0;
	move.w	#8,	d1;
	neg.w	d1;
	divs	d1,	d0;
As before, 16 and 8 are copied into d0 and d1. D1 is negated so it now holds -8. D0 (16) is now divided by d1 (-8). D0 now holds the value -2.

In section 3, we'll take a look at logical instructions.



Chapter 4 - Section 1 Contents Chapter 4 - Section 3


Designed & maintained by Lewis AS Bassett
SEGA, Megadrive, Genesis, Sonic the Hedgehog, etc are all owned by Sega Enterprises Ltd