I might make some mistakes here, and since the DCPU-16 specs aren't final yet, some of the information might become outdated. If you notice any errors, or there's something that I've made too complicated (which I unfortunately have a tendency to do), please inform me and I will do my best to correct it.

**UPDATE 1:**I have fixed some mistakes, and written a section on math operators (+ example).

**UPDATE 2:**I have decided to move the tutorial to the 0x10c Wiki instead. This way, those of you who are already knowledgeable in DCPU-16 assembly can help me improve this tutorial by directly editing that article instead. I will probably still be updatign this post from time to time, though.

**UPDATE 3:**New sections on conditionals, labels, jumping and subroutines.

**UPDATE 4:**New section on the stack, as well as updated sections ("introduction" and "subroutines"). Updates may come less frequently due to much school work etc., but I assure you, they will come, just don't expect this to be finished immediately.

Getting Started

First of all, you need an assembler to create machine code, and an emulator to run the program on (until the game is released to the public). You can find a list of such tools here, but I've been using DCPU-16 Studio myself, and this is the tool I am going to describe how to use in this tutorial. If you do not want to download any programs, you can also try this online DCPU-16 emulator.

Before you start this tutorial, I will assume that you have some basic knowledge of

*binary*and

*hexadecimal*numbers. If you do not know what these two are, I will not go into detail in this tutorial, but I would recommend you to google it beforehand, or look at this article in the 0x10c wiki.

Introduction

DCPU-16 is built up of

*16-bit unsigned words*, which is a 16-digit binary number (or 4-digit hexadecimal, or a whole number between 0 and 65,535). It consists of 65,536 such words of RAM, and 11 so-called "registers".

There are 8 regular registers, and 3 special registers. The 8 regular registers (named A, B, C, X, Y, Z, I and J) can be thought of as "temporary memory locations" where you can store words while you do stuff to them, for instance, if you want to compute the sum of two numbers. The program counter (PC) tells the CPU where in the CPU it is, and we can use it to jump back and forth in our program. I will come back to the other two, the stack pointer (SP) and the overflow register (O), later, but for now, you don't have to worry about them.

The rest of the RAM in the DCPU-16 is used to both store

*instructions*that tells the DCPU-16 what to do, to store

*data*, such as texts or images, and to

*communicate with other devices*, such as the keyboard, the display and the ship itself. Generally, all memory is treated alike, so there are no restrictions as to where you can store data and where you can store instructions, with exception of communication with other devices, which happens within special ranges of memory.

Each of the 65,536 words in the RAM are numbered from 0 to 65,535 (or 0xFFFF in hexadecimal notation). The "number" that represents the location of a specific word in memory, is often called an

*address*.

A simple adder

Let's start with a simple program that adds two numbers, say, 72 and 54:

Code: Select All

;; Basic DCPU adder
;; by Fred
SET A, 72 ; Store 72 in A
SET B, 54 ; Store 54 in B
ADD A, B ; Add B to A
:end
SET PC, end

In DCPU-16 Studio, you can generate machine code by selecting "Assemble" or by pressing F9.

When you do this, the program is assembled into machine code and loaded into the CPU memory. Each

*instruction*takes up one word, or sometimes two or even three words, depending on its type. Then it starts the program at address 0.

Let's go through it step by step:

Code: Select All

;; Basic DCPU adder
;; by Fred

This is a

*comment*. Comments in DCPU-16 begin with a semi-colon. Every time DCPU-16 see a semi-colon, it will ignore the rest of the line. This is very useful to make your code more readable to yourself and others.

Code: Select All

SET A, 72

This is the

*SET*instruction, which stores a value somewhere. It takes two arguments: the first is where you want to store it, and the second is what you want to store. In this case, it takes the number 72 and stores it in register A.

In DCPU-Studio, you can select "Single Step" or by pressing F7 to run a single instruction at a time. This way, you can see exactly what your program does. If you run this instruction, you should see that the A field under "Registers" will turn yellow, which tells you that it has changed, and its value will become 72.

Code: Select All

SET B, 54

This is the same as the above: it takes the number 54 and stores it in register B.

Code: Select All

ADD A, B

This is the

*ADD*instruction, which adds one number to another. In other words, it takes the value of A and the value of B, adds them together, and stores the result in A.

In this case, it takes 72 (the value of A) and 54 (the value of B) and adds them together, which yields 126. Then it stores this number in A, so that A is now 126.

Code: Select All

:end
SET PC, end

These two lines cause the program to go into an infinite loop. The reason why we do this is that otherwise, the program would continue to run in RAM after it has finished, can cause unexpected behavior. Loops are explained in the Labels and jumping section below.

More math! Yay!

Let's have a quick look at the other math operators in DCPU-16. Note that

**all**the math operators will store the result in the first argument you pass to them (which is A in the examples).

Basic math operators

Code: Select All

ADD A, B ; adds B to A
SUB A, B ; subtracts B from A
MUL A, B ; multiplies A by B
DIV A, B ; divides A by B
MOD A, B ; divides A by B, and give the remainder

ADD, SUB and MUL work exactly as you would expect them to: they add, subtract or multiply A and B, and store the result in A.

DIV divides A and B and stores the result in A. But, you might say, what about fractions? Since DCPU-16 can only operate on whole numbers, what would happen if we tried to compute 8 / 3? The answer is that it rounds down, and effectively discards the fractional part of the result, so that dividing 8 by 3 would yield 2 as the result (8 / 3 = 2.66..., rounded down).

But this causes another problem: since DIV discards the fractional part of the number, how can we find it? The answer to this is the MOD instruction, which in mathematics often is denoted with as A % B. It divides A by B, but instead of taking the answer and rounding it down, it takes the remainder - that is, what it "left over" after dividing. For instance, if you try to compute 8 % 3, you would get 2 (since 8 / 3 = 2, and 2 * 3 = 6, this means that the remainder that got lost during dividing is 8 - 6 = 2).

Binary math operators

In addition to the five basic math operators, there are also five binary math operators:

Code: Select All

SHL A, B ; shift A left by B bits
SHR A, B ; shift A right by B bits
AND A, B ; "and" of A and B
BOR A, B ; "or" of A and B
XOR A, B ; "exclusive or" of A and B

To understand these operators, we need to look at the numbers in their binary form.

The SHL and SHR instructions takes the value of A and shifts it B bits to the left or right. For instance:

Code: Select All

SET A, 10 ; A = 0000 1010
SHL A, 1 ; A = 0001 0100, 1 bit left
SHL A, 1 ; A = 0010 1000, 1 bit left
SHR A, 1 ; A = 0001 0100, 1 bit right
SHR A, 2 ; A = 0000 0101, 2 bits right

SHL A, B is equvivalent to multiplying A by 2

^{B}, while SHR A, B is equivalent to dividing by 2

^{B}.

The AND ("and") instruction gives us all the bits that are 1 in both A and B:

Code: Select All

SET A, 10 ; A = 1010 (10)
SET B, 3 ; B = 0011 (3)
AND A, B ; A = 0010 (2)

The BOR ("or") instruction gives us all the bits that are 1 in either A or B:

Code: Select All

SET A, 10 ; A = 1010 (10)
SET B, 3 ; B = 0011 (3)
BOR A, B ; A = 1011 (11)

The XOR ("exclusive or") instruction gives us all the bits that are 1 in either A or B, and 0 in the other:

Code: Select All

SET A, 10 ; A = 1010 (10)
SET B, 3 ; B = 0011 (3)
XOR A, B ; A = 1001 (9)

Example: Odd or even?

In this program, we will set A and B to two numbers, and see if the sum of them is odd or even. If it is odd, the C register will be 1, otherwise it will be 0.

Code: Select All

; Set A and B to our numbers
; You may change these and see
; how the outcome will change
SET A, 33
SET B, 12
; Compute A % 2, which is 0 if
; even and 1 if odd
MOD A, 2
; Do the same to B
MOD B, 2
; Copy the value of A to C
SET C, A
; Since the sum of two numbers
; is only odd if one number is
; even and the other is odd,
; we can use XOR here:
; Get the exclusive or of C
; (which is now the same as A)
; and B
XOR C, B
; Finish
:end
SET PC, end

The Stack

The stack acts as a sort of temporary memory storage for your program. It begins at the end of your program's RAM (at address 0xFFFF), and goes backwards from there.

You can think of the DCPU-16 stack as analogous to a stack of plates, where each plate represents a 16-bit word:

- We can "push" plates to the top of the stack.
- We can "pop" plates off the top of the stack.
- We can "peek" at the plate at the top, without removing it.

These are also the three operations we can do to the DCPU-16 stack.

The first operation, PUSH, adds a word to the top of the stack:

Code: Select All

; stack is now: (empty)
SET PUSH, 1
; stack is now: [1]
SET PUSH, 2
; stack is now: [2], 1
SET PUSH, 3
; stack is now: [3], 2, 1

The second operation, PEEK, gives us the word currently on the top of the stack:

Code: Select All

; stack is now: [3], 2, 1
SET I, PEEK
; I is now 3
; stack is now: [3], 2, 1

The third operation, POP, gives us the word currently on the top of the stack, and then removes it:

Code: Select All

; stack is now: [3], 2, 1
SET A, POP
; A is now 3
; stack is now: [2], 1
SET B, POP
; B is now 2
; stack is now: [1]
SET C, POP
; C is now 1
; stack is now: (empty)

Conditionals and program jumping

Conditionals

So far, the programs we have looked at have only one execution path: from start to end. But what if you wanted your program to behave differently based on different kinds of inputs? This is where the conditional instructions come in handy:

Code: Select All

IFE A, B ; if A and B are equal...
IFN A, B ; if A and B are not equal...
IFG A, B ; if A is greater than B...
IFB A, B ; if A AND B (bitwise AND) is nonzero...

These will tell the CPU to perform the next instruction if and only if the condition is true, otherwise it will skip it.

Labels and jumping

In the first program, you saw this piece of code at the end:

Code: Select All

:end
SET PC, end

Here, :end is a

*label*. A label is a name that you can give to a specific instruction. When you use this name in your assembly code, it will be replaced with the address of the next instruction after the label (in this case, "SET PC, end").

The program counter (PC), as aforementioned, tells the CPU where in the program the CPU is, or more specifically, the address of the next instruction. We can alter the value of this to skip to another part of the program.

In the piece of code above, the program will set the program counter to the instruction itself. Now the CPU runs this instruction again, and it continues to do this indefinitely.

We can combine the conditionals and the label jumping to create a loop that actually ends, like this piece of code:

Code: Select All

SET I, 0 ; Initialize the value of I to 0 before we run our loop
:loop ; Label that marks the start of the loop
IFE I, 10 ; If I has reached 10, we want to exit the loop
SET PC, loop_end ; so we jump to the label 'loop_end'
; we can do something in the loop here if we want to
ADD I, 1 ; Make sure to increase the value of I, so that it
; will actually reach 10 sometime
SET PC, loop ; Return to the label 'loop'
:loop_end ; Label that marks where the program will continue after the loop
SET PC, loop_end ; Infinite loop to end the program

We can also use the conditional instructions and jumping to create more complex conditionals:

Code: Select All

SET A, 4 ; Set our input
IFE A, 0 ; If A == 0, we want to jump to label 'a_is_zero'
SET PC, a_is_zero
IFE A, 1 ; If A == 1, we want to jump to label 'a_is_one'
SET PC, a_is_one
IFE A, 2 ; If A == 2, we want to jump to label 'a_is_two'
SET PC, a_is_two
SET PC, end ; If A is anything else, we can put what we want to do
; here
:a_is_zero ; What we want to do if A == 0
ADD B, 1
SET PC, end
:a_is_one ; What we want to do if A == 1
ADD B, 2
SET PC, end
:a_is_two ; What we want to do if A == 2
ADD B, 4
:end
SET PC, end ; Finish the program

Subroutines

A subroutine is a piece of code which will be run, and then return to where it was called from. In other words, it's a jump that will return when it has finished.

A subroutine is defined like this:

Code: Select All

:my_subroutine ; Subroutine name
; what to do
SET PC, POP ; Return to where the subroutine was called

And it is called like this:

Code: Select All

JSR my_subroutine ; Call subroutine 'my_subroutine'

When a subroutine, the current position of the program counter (PC) is pushed to the stack, and is then changed to the address of the first instruction in the subroutine. When the subroutine is complete, it will call

Code: Select All

SET PC, POP

to get the address of the instruction from where it was called, and return execution from there.

For example, a program that calculates A

^{2}- 1:

Code: Select All

SET A, 4 ; Our input
JSR square ; Call the 'square' subroutine
SUB A, 1 ; Subtract 1
:end
SET PC, end
:square ; Subroutine name
MUL A, A ; Multiply A by itself (squaring)
SET PC, POP ; Return

*To be continued...*