Mini guide to 68000 Assembly Programming


Recently I did some studying on CPU hardware & architecture, and some programming in Assembly language. Specifically the Motorola 68000. We used an IDE & emulator called Easy68K. So this guide/tutorial will be closely linked with that. This mini guide is intended for anyone and any beginner, should be able to get going writing a program. I wont cover everything but I will cover just enough.

computer-language-and-its-typeseasy68keditor

 

 

 

 

 

 

Jump to sectionAssembly  CPU   Hello World  Number system  Registers  Addressing  Syntax  I/O  Move & Load  Math  Program Flow  Branch/Jump  Loops  Subroutines  Macros  Memory  Misc  Practice  Useful Links

What’s Assembly?

Assembly language is the closest thing to the hardware. In other words, while your Java, C#, and JS/Python are all abstracted and higher away from the hardware, Assembly is at the bottom, making working with the hardware easy and more efficient. Because then it gets compiled into machine language, stuff your CPU can understand and execute. Its also very structured. You can’t do something like this: object.get().modify().set();. In C++ for example, you might say “int foo = 5;”. But in assembly, nothing is abstracted, so code looks like this “move  #5, foo”.

What’s the 68K?

The 68K is a CPU made by Motorola. Its one of the most successful microprocessors and is still in use today. Embedded systems, computers, even game consoles have used it. Its a 32-bit CISC CPU. It has its own instruction set, the topic of this tutorial 😉 . 595px-kl_motorola_mc68000_plcc

How do I write ‘Hello World’?

I will assume that you have installed Easy68K, ready to go. If you haven’t, go and install it. Now then, writing “Hello World” in Assembly isn’t exactly a “1-line program”. In Java you might write “System.out.println(“Hello World”);. Here’s Hello World in Assembly. BTW: you don’t need to write assembly code in all caps if you don’t want to.

;PUT ANY ASSIGNMENTS HERE
	
	ORG $1000
START:
	LEA	MESSAGE, A1
	MOVE.B	#14, D0
	TRAP	#15
	SIMHALT

*PUT VARIABLES AND CONSTANTS HERE
MESSAGE	DC.B	'HELLO WORLD',0

	END START

Let’s break all of this down, line by line, piece by piece.

  1. ORG $1000: this tells the assembler that this program will be located and organized in memory at address $1000
  2. START: this is the first instruction of the program, it is also considered a subroutine. Think of it as “int main()”.
  3. LEA  MESSAGE, A1: load the message variable into address register 1 (A1). We’ll cover this later.
  4. MOVE.B  #14, D0: move the byte, number 14, into data register D0.
  5. TRAP   #15: a hardware interrupt, think of it like typing a key. Basically, it tells the assembler: go and display or read input.
  6. SIMHALT: terminate the program.
  7. MESSAGE    DC.B  ‘HELLO WORLD’,0: this is our string variable, called message, initialized by ‘DC.B’, and at the end, has a null terminator (0)
  8. END  START: the last line of the program, the program ends here

Lastly, take a look at the comments. Comments in 68K can be written like so:  ;comment   OR  *comment

Decimal, Binary, and Hex…

Working with numbers in 68K means having to deal with more than just decimals. Binary and Hexadecimal are also used.

  • In 68K, a decimal number is specified like so: #2     <———(just the pound symbol means its a decimal)
  • A hex: #$24      <—–(the dollar symbol tells the assembler that this number is a hex)
  • Binary: #%10101010       <—–(the percent symbol says that this number is a binary)

Registers

68K has 8 data registers (D0-D7) and 8 address registers (A0-A7).

Data register? A data register holds data. In the context of 68K, this data is just numbers. If you put a decimal number like 10 in either a data or address register, it will be interpreted as ‘A’. Yep, hex is king here.

Address register? An address register also holds numbers, BUT the values in here are counted as addresses. In C, you can define a pointer like so: int *val = 10;. Address registers are just that, POINTERS to an address in memory. If you look at your A0 register and inside it says “00003000”, that means A0 is pointing to memory address $3000.

There is a special register, A7. Why is it special? Because its the stack pointer. Yes, you have a stack at your disposal, you can push and pop stuff from it. We’ll get to that (see Moving and loading things).

In Easy68K, if you run your program you will get a screen that displays these registers. Get comfortable with it. Play around.registers

Whats Addressing?

In 68K addressing means how you handle sizes of data. It also means how we work with memory and registers. In C++ for example, an integer is not the same thing as a double. A String is not a character.

Notice how in “Hello world”, there’s the instruction “MOVE.B”. Move is the instruction, and “.B” says that we are going to move a byte of data into a register. In 68K, there are 3 sizes to consider:

  • .B (byte) you know, 8 bits in a byte, example: #$FF
  • .W (word) 16 bits in a word, example: #1234
  • .L (long-word) 32 bits in a long-word, example: #00809070

numsys

Addressing in 68K also means how we decide to deal with memory and registers.

  • Data Register to register: doing instructions that involve only data registers. Example:  MOVE.B    D1, D2
  • Data register to address register: MOVE.L   D5, A2   <—–we’re moving what’s in D5 into A2, so now A2 points something else
  • Immediate data to register:  ADD.W  #1000, D4
  • Immediate data to indirect address register:  MOVE.B   #$AC, (A0)   <—we’re moving a value into whatever data A0 is pointing to
  • Immediate data to absolute address:  MOVE.W   #2000, $9000    <—we’re moving a value into the memory address 9000
  • Increment:   MOVE.B   #$9F, (A5)+    <–move a value into what A5 is pointing to, then increment A5 address
  • Decrement:   MOVE.L   #00102340, -(A1)   <—move a value into the address A1 is pointing to, then decrement the address in A1

More on increment/decrement: When you increment and decrement, you are ultimately adding or subtracting 2 from the address. Example:

  • A0 = 000037BC
  • (A0)+ = 000037BE
  • -(A0) = 000037BA

Syntax

Lets look at the “MOVE.B  #14, D0” instruction again. Notice how after the instruction, we provide a number, or register as a source. Then a destination register, or memory location (INSTRUCTION.SIZE      SOURCE, DEST). Not every instruction has this kind of syntax, but for now, here’s some examples:

  • LEA    $4000, A0  ;load address 4000 into A0
  • ADD   D3, D4  ;add what’s in D3 to D4
  • MOVE.W   #$ABCD, -(A6)  ;move a word value indirectly to A6 then decrement address
  • SUBI.W    #4, D6  ;subtract 4 from D6

Instructions that don’t have similar kinds of syntax:

  • CLR.L   D0  ;clear D0 entirely
  • SWAP  D2  ;swap the top and bottom half of D2 (02347800   —->  78000234)
  • JSR      START  ;jump to subroutine START

Printing and reading I/O

In “Hello world”, all we did was print a simple message to console. In Easy68K its called the simulator window. Remember that this tutorial is closely linked with this assembler, so here’s how I/O works with it. Notice how we worked with D0 and A1, lets examine those closely:

  • Moving a certain value into D0 yields different results.
    • Example, moving #14 will display a null terminated string without a newline afterwards
    • Moving #5 means the simulator will be reading input from the user
  • Moving a value into D1, like the number A, usually means that a number will will be printed.
  • Loading something in A1, usually means we’re going to print a string.
  • NOTE: when all said and done, the final step to displaying to I/O or reading from I/O is writing: TRAP  #15
*displaying decimal a number to console
move	#3, d0     *task #3 in D0 lets us display a decimal number
move.w	#100, d1   *move the word value 100 into D1
trap	#15        *the number displayed on screen is 100

RECOMMEND REFERRING TO THIS FOR ALL KINDS OF I/O OPERATIONS: http://www.easy68k.com/QuickStart/TrapTasks.htm

Moving and Loading things

Moving things around loading data is what Assembly is mostly about. I’ll go straight to the instructions:

  • MOVE: moves values between registers and addresses
  • MOVEA: moves addresses between address registers
  • MOVEM: this is used to move things to the stack pointer A7. We can move a single register or multiple registers:
    • Example: MOVEM   D0/D1, -(A7)      <—-push D0 and D1 onto the stack
    • MOVEM    +(A7), D0-D1    <—-pop from the stack and store into D0 and D1
    • MOVEM    A0-A6, -(A7)
  • LEA: load a value or address into an address register (LEA   $5000, A1)

Doing Math

68K has instructions to add, subtract, divide, multiply, complement, boolean logic, and shift

  • ADD: adds values between registers or immediate data to register/memory
  • ADDI: adds but you can only add immediate values, no registers allowed for source
  • ADDQ: add quick, sometimes used for incrementing address pointer
  • SUB: subtracts values between registers or immediate data to register/memory
  • SUBI: subtract but you can only subtract immediate values, no registers allowed for source
  • SUBQ: subtract quick, sometimes used for decrementing address pointer
  • MULS: multiply signed
  • MULU: multiply unsigned
  • DIVS: divide signed
  • DIVU: divide unsigned
  • ASL/ASR/LSL/LSR/ROL/ROR: shift the bits in a register, example ->  ASL.B   #4, D7   *shift the value in D7 4 bits to the left
  • NOT: complement bits, example: 1010  —>  0101
  • OR: perform logical OR on bits…    1001  OR  0110 = 1111
  • AND: perform logical AND on bits…   1010 1100   AND   0010  1000 = 0010 1000
add	d0, d1		*add byte contents of d0 to d1
addi.w	#$AABB, d2	*add immediate word to d2
mulu	#16, d4		*multiply d3 by 16
not.l	d6		*complement the longword binary bits of d6
lsr.b	#16, (a0)	*shift the bits of the data pointed to by a0 16 bits to the right
divu	#2, d5		*divide d5 by 2
subq	#4, a3		*decrement a3 by 4

Control and Checking

In all high level languages the if-statement is used for program flow and control/checking. In 68K, we can compare.

  • CMP: you can compare immediate data to register, register to register, and more…
  • Example: CMP.B   #4, D5    ;does the byte value in D5 equal 4?
      • After you compare however, YOU MUST DO SOMETHING, and that’s where subroutines come in.
      • Usually after comparing, we branch somewhere.
    CMP.B   #4, D5   *does the byte value in D5 equal 4?
    BEQ     doStuff  *branch if equal, to a subroutine called 'doStuff'
    

Branching and Jumping

Branching means we go to another piece of code, usually a subroutine. Jumping means jumping to a subroutine and then coming back. There’s different kinds of branches and jumps.

  • BRA: just branch somewhere, like ‘BRA   func1’ means go to subroutine ‘func1’
  • BEQ: branch if equal (if D0 = 4 then go here…)
  • BNE: branch if not equal
  • BLT: branch if less than
  • BGT: branch if greater than
  • BCC: branch on carry clear (see Condition codes)
  • BCS: branch on carry set (see Condition codes)
  • JSR: jump to subroutine, note, you will need to have ‘RTS’ at the end of a subroutine should you use JSR
  • JMP: jump. Yes, only jump. You can either jump to a subroutine or jump to a specific address/program displacement. Example: JMP  (A0, D0)

Looping

How would you implement a for loop in 68K? A while loop?

func1:  *for loop example
	cmp.b   #$A, D0   *does D0 equal 10?
	beq	done      *if so, branch to done
	addi	#1, D0    *increment D0
	bra	func1     *else, go back and loop


func2:	*while loop example
	LSR.B	#1, D3    *do a logical shift left 1 bit
	BCS	done      *branch on carry set
	BRA	func2     *loop again

done:
        SIMHALT

Subroutines

You can write subroutines for anything in 68K. Think of them like methods in C/C++. A subroutine must first be defined by a label (the name), then the code afterwards. Example:

doStuff:
	move.b	#5, d0
	move.l	#$00ABC5F0, (a0)+
	lea	message, a1
	cmp.w	#$FF, a5
	beq	goThere
	addq	#4, d0
	bra	doStuff

doMoreStuff:
	clr.l	d2
	move.l	d2, a2
	jsr     routine2
	rts

Macros

You can write a macro that will greatly simplify the way you do things. For example, here’s a how a print macro looks like (all credit given to Prof. Charles Kelly of Easy68K forums)

*-----------------------------------------------------------
* Written by   :  Chuck Kelly
* Description  :  Demo of macros
* Macro definitions should be placed at the top of the source file.
*-----------------------------------------------------------
      OPT   MEX
CODE   EQU   0
TEXT   EQU   1

   SECTION   TEXT
   ORG      $2000
   SECTION   CODE
   ORG      $1000

* print the text string
* use ENDL as second argument to add return and linefeed
PRINT   MACRO
   SECTION   TEXT
MSG\@   DC.B   \1
   IFARG   2
     IFC     '\2','ENDL'
      DC.B   $D,$A
     ENDC
   ENDC
   DC.B   0
   SECTION   CODE
   MOVEM.L   D0/A1,-(SP)
   LEA      MSG\@,A1
   MOVE.B   #14,D0
   TRAP   #15
   MOVEM.L   (SP)+,D0/A1
   ENDM

HALT   MACRO
   MOVE.B   #9,D0
   TRAP   #15
   ENDM

**********************
*   Program Start
**********************
START
   PRINT   <'Macro Demonstration Program'>,ENDL

   HALT         Halt the program
   END   START

Memory in 68K

Easy68K provides a way of viewing what’s going on in the memory we’re working with. When you run your program, under the View menu you can click “Memory” and you will get this screen. Inside the blue outline is our current address. In green is the offset. In red is the data residing at that location.

The current address, the data residing, and the offsets.

The current address, the data residing, and the offsets.

Condition codes

When you do math in 68K, certain condition codes are set. For example, if we compare something and its equal, a special bit called ‘Z’ gets set to 1 that lets us know it is equal. 68K has these things called condition codes:

  • X – extend bit
  • N – negative bit
  • Z – zero bit
  • V – overflow bit
  • C – carry bit

Misc

You can also set variables and constants above your code like so…

*you can define stuff here as well
address1   EQU   $1500
address2   EQU   $2000

**********************
*   Program Start
**********************
START
   
;put program code here

text    dc.b    'bla bla bla',0   
   END   START

Practice!

Anything worth being great at means tons of practice. Assembly language is no exception. To best understand how to program in this language, write programs! Play around in the IDE, look at memory, debug, etc. Here’s some ideas for programs to try and get you going.

  1. Write a 68K program to scan a region of memory and look for a specific value. If found, print the address location.
  2. Write a 68K program that takes number input from a user and prints to screen the hexadecimal representation.
  3. Write a 68K program that uses a macro to print the binary representation of a number.
  4. Write a 68K program that calculates the Fibonacci sequence. Also include a routine that calculates factorial.
  5. Write a 68K program that checks if a string is a palindrome or not.
  6. Write a program that calculates reverse polish notation expression in 68K assembly. Make it so that it can also take user input for the expression.
  7. Create a linked list structure in 68K Assembly.
  8. Make a Towers of Hanoi console based game in 68K Assembly.

Useful Links:

chapter-3-computer-organization-51-638

I hope this tutorial has helped you in any way. Happy programming :). Let me know if I should change or edit anything on here. If you liked this tutorial please rate it and comment on it.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s