Skip to content

Instantly share code, notes, and snippets.

@asiekierka
Last active April 15, 2018 03:06
Show Gist options
  • Save asiekierka/6741832 to your computer and use it in GitHub Desktop.
Save asiekierka/6741832 to your computer and use it in GitHub Desktop.
Modular Computing CPU tutorial

Welcome to Modular Computing! This document's goal is to explain how the Areia-1 CPU works and how to use it.

BASICS:

Let's start with the simplest opcode: move.

move.w dst, src

It lets you move the values inside registers, or to change such a value to a number. ".w" means it is related to WORDS - 16-bit values. This is not important to you for now.

move.w @r1, #1

This will set register 1 to the value 1.

move.w @r2, @r1

And this will set register 2 to the value inside register 1. Registers are marked with @ (@r1, @r2...) and immediate values are marked with #.

But what good is moving values around when you can't add them?

add.w a, b

performs an operation: a = a + b. For that reason, a has to be a register, but b can be anything!

move.w @r1, #2
move.w @r2, #3
add.w @r1, @r2

This code sets register 1 to 2 and register 2 to 3 and then proceeds to add them, setting register 1 to 5.

move.w @r1, #2
move.w @r2, #4
sub.w @r2, @r1

And analogically, this subtracts register 1 from register 2, setting it to (4 - 2) = 2.

MEMORY

There are only two opcodes for accessing memory:

ld.w address, add, register
st.w register, address, add

ld.w loads the value stored at address+add to register, and st.w stores the value in register to address+add. But what is add? Add is a register containing a special offset in memory. You can skip it, though - which is equal to using @r0. @r0 is a special thing - its value is always set to 0 and you cannot change it.

Let's try doing something with it:

move.w @r1, #42
st.w @r1, $00000, @r0

This will store the value 42 at memory location $00000. ($ means that the value is in hexadecimal (0-F)) Due to the fact @r0 is assumed by default, this can also be written as:

move.w @r1, #42
st.w @r1, $00000

Please note that you're storing a WORD - a 16-bit value that takes two locations in memory (a word is two bytes, a location is one byte). What if you want to use a BYTE - an 8-bit value?

move.b @r1, #42
st.b @r1, $00000

Just use .b as a prefix.

JUMPS:

What if you want to make a loop? Here's a simple infinite loop:

loop: jmp loop

"loop:" is a label. jmp loop jumps to that label.

But wait - you might want to have conditional jumps, that is jump on a condition!

move.w @r1, @r0
repeat: add.w @r1, #1
        cmp.w @r1, #20
        jne repeat

This code sets register 1 to register 0 (equal to 0) and keeps adding 1 to it. "cmp" is an opcode that compares a register to a value or another register. "jne" stands for Jump if Not Equal - if the two compared values are not equal, it will jump to the label "repeat".

SUBROUTINES:

A subroutine is a part of code you can jump to and return at any given time. Let's say you have a special part of code to multiply by 3 (by adding a value to itself two times):

mul3:   add @r1, @r1
        add @r1, @r1

But you don't really feel like copypasting it everywhere (to save space or maybe create a faster solution one day). What you can do is turn it into a subroutine!

mul3:   move @r2, @r1
        add @r1, @r2
        add @r1, @r2
        ret
        
code:   move @r1, #3
        jsr mul3

This piece of code will set register 1 to 3 and then jump to "mul3" with the opcode "jsr". JSR differs from JMP in that it stores the address of the next opcode on the stack. "ret" stands for RETurn - it sets the program counter to the last address on the stack. You can, of course, nest subroutines:

mul6:   jsr mul3
        add @r1, @r1
        ret
        
mul3:   move @r2, @r1
        add @r1, @r2
        add @r1, @r2
        ret
        
code:   move @r1, #3
        jsr mul6

mul6 then calls mul3 (to multiply by 3) and adds the result to itself, creating a multiplication by 6.

@iamgreaser
Copy link

Registers are e.g. @3, not @r3.

.b is a suffix, not a prefix, otherwise it would come BEFORE the opcode.

I'm unsure if we should make the ".w" suffix optional or not. Personally I think it should be compulsory, because it encourages people to make damn sure they mean what they mean. Yes, especially for the OP2 block where .b doesn't even exist. OP3 and the special block don't use suffices at all.

I'm more fond of JZ/JNZ rather than JE/JNE, but I'll leave those aliases available in my assembler.

EDIT: Also you have ld and st's args around the wrong way. LD is dest reg first, address second. ST is address first, source reg second.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment