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.
Jump to section: Assembly CPU Hello World Number system Registers Addressing Syntax I/O Move & Load Math Program Flow Branch/Jump Loops Subroutines Macros Memory Variables, Constants, Misc Practice Useful Links
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 😉 .
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.
- ORG $1000: this tells the assembler that this program will be located and organized in memory at address $1000
- START: this is the first instruction of the program, it is also considered a subroutine. Think of it as “int main()”.
- LEA MESSAGE, A1: load the message variable into address register 1 (A1). We’ll cover this later.
- MOVE.B #14, D0: move the byte, number 14, into data register D0.
- TRAP #15: a hardware interrupt, think of it like typing a key. Basically, it tells the assembler: go and display or read input.
- SIMHALT: terminate the program.
- 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)
- 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)
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.
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
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) <—decrement the address in A1, then, move a value into the address A1 is pointing to
More on increment/decrement: When you increment and decrement, you are ultimately adding or subtracting by the size you are moving/using from the address. Example:
- A0 = 000037BC <— original address
- MOVE.B #$AC, (A0)+ = 000037BD <— Byte increments by 1
- MOVE.W #$EEAC, (A0)+ = 000037BE <— Word increments by 2
- MOVE.L #$22AC0044, (A0)+ = 000037C0 <— Long increments by 4
- MOVE.B #30, -(A0) = 000037BB <– Byte decrement by 1 first, then move the value
Finally, note that if the symbol is on the left side of the register -> -(A2) it is called pre-decrement/increment, meaning the the pointer will advance or decrease before data is moved. Vice versa if the symbol is on the right side of the register -> (A2)+, it means that we first move data, then advance/decrease the pointer.
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 decimal number 10 (in hex it is ‘A’), 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)
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)
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
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
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.
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
Variables, Constants, 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 variableX EQU #40 ********************** * Program Start ********************** START ;put program code here text dc.b 'bla bla bla',0 END START
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.
- Write a 68K program to scan a region of memory and look for a specific value. If found, print the address location.
- Write a 68K program to take user input, such as your name, and display it to the console. Hint: look into an ASCII table for reference.
- Learn about subroutines, jumping, branching, and program displacement by writing small subroutines.
- Write a 68K program that takes number input from a user and prints to screen the hexadecimal representation.
- Write a 68K program that uses a macro to print the binary representation of a number.
- Calculate the Fibonacci sequence using the 68K assembly. Bonus: include a routine that calculates factorial.
- Write a 68K program that checks if a string is a palindrome or not.
- 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.
- Create a linked list structure in 68K Assembly.
- Make a Towers of Hanoi console based game in 68K Assembly.
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.