Sega Programming Network Information Projects Community Downloads
[Guestbook]
[Programming Articles]
[Games]
[Message Board]
[Source Code]
[News]
[Technical Manuals]
[Misc]
[Suggested Links]
[Devtools]

ASM Programming for the Sega Megadrive - Chapter 2 .

Assembly Language

Writing software for the Sega Megadrive and other computer systems using machine code is very difficult. For this reason, assembly language, also known as ASM, was invented. Assembly language uses a very similar sytax to machine code. Just as each different processor uses a different machine code, each processor uses a different assembly language. Assembly languages use an assembler to convert each instuction into machine code.

A typical assembly instruction may look like this:

	move.l	#$0001ffee, $ff0000
The first part, "move.l", is an opcode. An opcode describes the type of operation to be performed. The second part of the instruction, "#$0001ffee", is refered to as an operand. Operands are usually immediate numbers or memory addresses, used by the instruction. The third part of the instruction is also an operand. The above instruction informs the processor to move the hexadecimal number 0001ffee into memory address ff0000.


Move Instructions

Move instructions inform the processor to move data to and from different memory locations or internal registers. The M68000 uses many different types of move instructions, all of which are explained further down.

The most basic move instruction takes the contents of one register or memory location, and copies the data into a different address of a memory location. To move the contents of register d0 into address ff0000 (in hexadecimal), the following instruction could be used:

	move.l	d0, $ff0000
A '$' has been placed before the memory address ff0000. This is because ff0000 is a hexadecimal number. If one wanted to write address ff0000 in binary, one would use the following instruction:
	move.l	d0, %111111110000000000000000
To use a decimal number for the memory location, the address is left as it is:
	move.l	d0, 16711680
Although all three instructions are written differently, they all still perform the same task. It may also have been noticed that a ".l" has been placed after the "move" opcode. A very useful feature of the M68000 is that it allows the programmer to access each memory address of register as either a byte, in which case the opcode is suffixed with ".b"; as a word, in which case, the opcode is suffixed with ".w"; as a long word, in which case the opcode is suffixed with ".l". Using this information, one is able to understand that all of the following instructions perform the same fuction.
	move.b	$ff,       d0
	move.w  $00ff,     d0
	move.l  $000000ff, d0
It is worth knowing that an instruction which accesses a register or memory location as a byte is slightly faster than other instructions which access as a word or longword.

The same opcode as mentioned above can also be used with immediate numbers, by placing a '#' before the immediate operand to be moved:

	move.b  #$01, d0
Instructions which use immediate numbers are extremely slow; a good engineer would avoid using immediate numbers whenever possible.
For example:
	move.b  #$10, d0
	move.b  d0,   d1
	move.b  d0,   d2
Rather than:
	move.b  #$01, d0
	move.b  #$01, d1
	move.b  #$01, d2

Movem is an instruction which can be used to move data from multiple locations, into a number of different destinations. It is sometimes quite useful; that is why it is described in this document.

	movem.l  d0/d4, $ee0000
The above instruction moves the data contained in registers d0 and d4, into memory location $ee0000. Note that with this instruction, data can only be moved into a memory address. The following instruction will not execute:
	movem.l  d0/d4, d1
Movem instructions may also be used with multiple destinations:
	movem.l  d0/d4, $ee0000/$ff0000
The above instruction will move the data contained in d0 into memory address $ee0000 and the data contained in d4 into memory address $ff0000.

Using the movem instruction, registers can also be listed:

	movem.l  d0-d6/a1-a6, $ee0000
The above instruction moves the data contained in registers d0, d1, d2, d3, d4, d5 and d6, and the data contained in registers a1, a2, a3, a4, a5 and a6, into memory address $ee0000.


Arithmetic Instructions

Arithmetic instructions are used for a wide variety of operations. Some of the more useful instructions are described in this document. Many more arithmetic instructions not described in this document are described in the Motorola 68000 Programmers Reference

An add instruction simply adds the first operand to the second operand. The result of the operation is stored at the location of the second operand.

	add   d0, d1
The above instruction adds the contents of d0 to the contents of d1. The result of the operation is stored in d1.

Addi is a different instruction which is used with an immediate number.

	addi  #$01, d0
The above instruction adds immediate number $01, with d0. The result is stored in d0.

Subtract instructions are similar to add instructions in that they are used to subtract one operand with another.

	sub   d0, d1
The above instruction subtracts the contents of d0 from d1 and stores the data in d1.

Subbi is another subtraction instruction, only, it is used with immediate numbers.

	subi  #$ff, d0
The instruction above subtracts the hex number $ff from the data contained in d0. The result is stored in d0.>

In all instances, using an instruction which contains immediate data is always very slow. It is always good practise to avoid using immediate numbers, whenever possible.


The Stack

Although registers are the most fast and practical method of storing data temporarily, there are only a limited ammount. Far too often, there aren't enough registers to store everything a program may need. This is where the stack pointer becomes useful. A stack pointer is a piece of hardware which stores multiple ammounts of data in a last in first out format, that is, the last piece of data stored on the stack will be the first that is taken off. Multiple ammounts of data may be stored on the stack. The stack pointer (address register A7) always points to the last element stored onto the stack. When a piece of data is placed onto the stack, it is pushed. Once that data is taken from the stack, it is popped. If multiple ammounts of data is stored on the stack, once one piece of data has been popped from the stack, the stack pointer now points to the next item of the stack, that is, the item that was stored immediately before the last element popped from the stack.

Move instructions are used to push and pop data to and from the stack. When data is pushed onto the stack, one would use -(a7) as an operand, since a7 is the stack pointer.

	move.w  d0, -(a7)
To pop a peice of data from the stack, (a7)+ is used as an operand.
	move.w  (a7)+, d0


Indirect Addressing

Move instructions as explained above using memory addresses are usually exremely fast, yet, there is still a more efficient way. Using address registers, one can use indirect addressing which is both faster and easier for the programmer.

Take this example:

	move.l  #$29809877, $ff00ff
	move.l  #$99999199, $ff00ff
	move.l  #$87772222, $ff00ff
	move.l  #$76661111, $ff00ff
	move.l  #$99999222, $ff00ff
As mentioned in Chapter 1, sending data to main memory is quite slow. It is much faster to use registers instead. Thus, the above code could be optimised:
	move.l  #$ff00ff, a1
	move.l  #$99999199, (a1)
	move.l  #$87772222, (a1)
	move.l  #$76661111, (a1)
	move.l  #$99999222, (a1)
Note how the address in RAM, $ff00ff, has been changed into an immediate number. If it were not, the processor would treat it as a reference to RAM and store the data contained at RAM address $ff00ff into address register a1. Also, be sure to notice that whenever data is sent to register a1, which points to RAM address $ff00ff, 'a1' has been enclosed with parentheses. This allows the processor to treat the instruction as an idirect reference to RAM address $ff00ff. This form of code is much faster than the first, however, booth perform the same fuction.


Data Storage When using data such as graphics, sound, colours, variables, etc, it is often much more convenient to store the data as part of your ROM, rather than using instructions which use immediate numbers.

For example:

	move.l  #$00000000, $c00000
	move.l  #$00111100, $c00000
	move.l  #$01100110, $c00000
	move.l  #$01100110, $c00000
  	move.l  #$01111110, $c00000
	move.l  #$01100110, $c00000
	move.l  #$01100110, $c00000
	move.l  #$01100110, $c00000
This method of sending data to RAM address $c00000 is extremely slow. A more conventonal method would be to store the data as part of the ROM:
Data_1:
	dc.l  #$00000000
	dc.l  #$00111100
	dc.l  #$01100110
	dc.l  #$01100110
  	dc.l  #$01111110
	dc.l  #$01100110
	dc.l  #$01100110
	dc.l  #$01100110
This data can then be used by many different instructions and is much faster to execute.
	lea        Data_1(pc), a1
	move.l  #$c00000, a2
	move.b  #$08, d0 
Transfer: 
	move.l (a1)+, (a2)+
	dbra     d0, Transfer
The first line of the above code, points address register a1 towards the data we stored earlier. The second line points address register a2 towards RAM address $c00000, which is where we want to send our data to. The third line moves $8 into data register d0, which is used as a counter for the next part.

The next part of the code, labeled as 'Transfer' copies a longword from our stored data, and moves it into RAM address $c00000. After completing that instruction, it reads the next longword, and copies that. This process is exeuted 8 times.

Although the code for the second method is more complexed, it performs the same function as the first method, but at a much faster speed. There are many advantages of using the second method, other than for speed. For example, data is easier to edit, can be used by multiple instructions, etc.



Back to Megadrive Programming Articles