Skip to content

Instructions

Instruction listings

The BELLE-ISA has 16 instructions. Below is a description, definition, use case, and example of every instruction.

Please refer to the encoding document for more information about the specifics on how certain arguments are encoded, particularly the meaning of “determinant bits” and the different encodings that each instruction can use. This document omits such information for brevity’s sake, as it is already quite long.

Note: The BELLE-ISA uses 16 bit long memory addresses.

Rounding: For instructions that will round a number, the number will simply have its decimal points cut off. It will not be rounded to the nearest integer, it will simply have the decimal places removed. (e.g. 4.5 becomes 4)

HLT - Halt

HLT will halt BELLE when it is encountered. The program will stop and gracefully exit, and no other instructions will be executed.

In binary, it is simply 16 consecutive zeros, thus making it possible to call a halt instruction by simply loading a zero into a memory address and performing a jump to the address.

HLT takes no arguments, and is not compatible with any argument types.

NameArgumentsDescriptionUse caseExample
HLTNoneHalt BELLEHalting once the overflow flag is set.hlt ; Stop the program

Flags affected: running state.

Binary encoding

Instruction: hlt

OpcodeRest of instruction
[0000][000000000000]
Pseudocode representation
running := false

ADD - Addition

ADD will perform arithmetic addition, taking the second operand (right-hand side), and adding it to the first operand (left-hand side), storing the result in the first operand. ADD will always take 2 arguments, and can also perform subtraction if the value in RHS is negative.

ADD will perform floating point arithmetic as the 6th and 7th registers are floating-point, however the values will be cast to integers if the destination is an integer register.

ADD can only perform floating-point-to-floating-point addition if the two floating-point registers are passed as arguments. Literal values cannot be floating point.

Compatible LHS argument types: Register

Compatible RHS argument types: Register, literal, register pointer, memory address pointer, can be signed and unsigned

Flags affected: None

NameArgumentsDescriptionUse caseExample
ADD2ADD two valuesAdding values in a Fibonacci sequenceadd r0, 3 ; add 3 to r0

Binary encoding

Instruction: add r4, &r3

OpcodeLHSDTBsRHS
[0001][100][0010][00011]

Pseudocode representation

lhs := lhs + rhs

Bcc - Conditional Branch

- BZ - Branch if zero flag is set

- BNZ - Branch if zero flag is not set

- BO - Branch if overflow flag is set

- BNO - Branch if overflow flag is not set

- BL - Branch if less

- BG - Branch if greater

Bcc instructions perform conditional branches, which set the CPU’s program counter to the destination specified.

BO will only branch if the overflow flag on the CPU is set. This flag becomes set if an arithmetic operation (e.g. add) overflows the destination register.

BNO will only branch if the overflow flag is not set.

BZ will only branch if the zero flag is set. This flag becomes set if a compare instruction (cmp) has two operands that are of the same value.

BNZ will only branch if the zero flag is not set.

BL will only branch if the left-hand side is smaller than the right-hand side of a CMP instruction, and BG performs the inverse.

BR will only branch if the remainder flag is set.

When a Bcc instruction is performed, the memory address that it is performed at is “pushed” onto the call stack (if the PUSHRET flag is enabled). This memory address is known as a return address and is retrieved if the branch is to be returned from. Thus, if a value is to be retrieved off the stack after a Bcc instruction, the return address must first be removed, or the stack and base pointers must be adjusted.

Using a register as the destination can be beneficial, as r4 and r5 are unsigned 16 bit integers, being able to address all memory addresses. It is also possible to pass r6 or r7 - the floating point registers - to this instruction.

If the condition is not met, the branch will simply be skipped, and the next instruction will be executed accordingly.

Note: Any branch instruction can overflow the stack if it is full

Compatible DEST types: Register pointer, memory address

Flags affected: Affected by overflow flag, sign flag, remainder flag, and zero flag. Cannot change any flags.

All encoding is similar to the following

Opcode listings:

BO : 00100

BNO: 00101

BL : 01010

BG : 01011

BR : 10001

BZ : 10010

BNZ: 10011

Binary encoding

Instruction: bo &r3

OpcodeDTBDEST
[00100][1][0000000011]

Binary encoding

Instruction: bz [1]

OpcodeDTBDEST
[10010][0][0000000001]

Pseudocode representation

if flag // dependent on branch condition
if sp > bp // adjust stack pointer
sp := sp + 1
else
sp := sp - 1
if sp < 0 || sp > 65535
throw overflow_err
memory[sp] := pc // push onto stack
if dtb = 1
pc := reg_val(dest)
else
pc := memory[dest]

POP - Pop off the stack

POP reads the stack pointer, and then “pops” the value contained at the stack pointer into a destination. POP will only ever take one argument. The value at the top of the call stack - which is referenced with the stack pointer - will be cleared, and the stack pointer will be adjusted depending on the direction the call stack is “going”.

If a POP instruction is called whilst the stack pointer does not point to a value, the CPU will report a stack underflow - a type of segmentation fault - where the call stack does not have enough data to satisfy a POP instruction.

As the memory is composed of signed 16 bit integers, the values “popped” off it will always be 16 bit signed integers. Thus, it is typically unnecessary to pop into a floating point register or an unsigned 16-bit register.

Compatible DEST argument types: Register

Flags affected: None

NameArgumentsDescriptionUse caseExample
POP1Take a value off the stackRetrieving a memory address from a JMPpop %r4 ; pop a value off the stack into register 4

Binary encoding

Instruction: pop r2

OpcodeDEST
[0011][000000000010]

Pseudocode representation

dest := memory[sp]
memory[sp] := NULL
if sp > bp // adjust stack pointer
sp := sp - 1
else
sp := sp + 1

DIV - Division

DIV divides LHS by RHS, storing the result in LHS.

DIV will perform arithmetic division between the two arguments passed to it, and the result will be cast to an integer if the destination is not a register capable of storing floating point values (any register that is not register 6 or 7).

If DIV results in a value with a remainder, the remainder flag will be set.

Compatible LHS argument types: Register

Compatible RHS argument types: Register, literal, register pointer, memory address pointer, can be signed and unsigned

Flags affected: Remainder flag

NameArgumentsDescriptionUse caseExample
DIV2DIVide two valuesFloating-point mathdiv r4, 3 ; divide value in r4 by 3

Binary encoding

Instruction: div r1, &2

OpcodeLHSDTBsRHS
[0100][001][0100][00010]

Pseudocode representation

lhs := lhs / rhs
if remainder
rflag := true

RET - Return

RET will read the value that the memory address that the stack pointer points to holds. The program counter will then be set to this address, and the memory address will be cleared. This instruction is similar to the POP instruction, however, it doesn’t load the value into a register.

RET takes no arguments.

RET can be used after a jump instruction is called to return to where the jump instruction was called from if the return address is still on the top of the call stack. If not, it is possible to create a return address on the call stack by saving the return address after a jump, then pushing it back on before RET is called.

Example:

jmp @somewhere
somewhere:
pop r4 ; save the return address
push r3 ; do other things with the stack
...
push r4 ; push the return address back on
ret ; the first line should be returned to

Compatible argument types: None

Flags affected: None

NameArgumentsDescriptionUse caseExample
RETNoneReturnReturning from a jumpret

Binary encoding

Instruction: ret

OpcodeRest of instruction
[0101][000000000000]

Pseudocode representation

pc := memory[sp]
memory[sp] := NULL
if sp > bp // adjust stack pointer
sp := sp - 1
else
sp := sp + 1

LD - Load direct

LD will read the memory address provided by the RHS, and retrieve the value at the memory address specified. Then, the value will be loaded into the register provided by the LHS of the instruction. If no value is found, a segmentation fault will occur, and the emulator will halt.

Compatible LHS argument types: Register

Compatible RHS argument types: Memory address

Flags affected: None

NameArgumentsDescriptionUse caseExample
LD2Load from address to registerLoading to registersld r0, [40] ; load value at address 40 into r0

Binary encoding

Instruction: ld r5, [42]

OpcodeLHSRHS
[0110][101][000101010]

Pseudocode representation

lhs := memory[rhs]

ST - Store direct and indirect

ST takes two arguments, and retrieves the value in the RHS register and stores it into the LHS. LHS can be either a register pointer, or a direct memory address. This allows the ST instruction to addresses all addresses if used indirectly via a register pointer. ST will overwrite any data at the destination address, and it can also overwrite program memory if the destination address is also part of program memory. Using ST in conjunction with LD or with the call stack can allow for full usage of the memory of the BELLE-ISA.

If a given register pointer is negative, the value will simply be stored at address 0.

Compatible LHS argument types: Register pointer, memory address

Compatible RHS argument types: Register

Flags affected: None

NameArgumentsDescriptionUse caseExample
ST2Store into an address<---st [4], r1 ; store r1 into addr 4

Binary encoding

Instruction: st &r3, r4

OpcodeDTBLHSRHS
[0111][1][011][00000100]

Pseudocode representation

if lhs = reg_ptr
memory[reg_val(lhs)] := reg_val(rhs)
else
memory[lhs] := reg_val(rhs)

JMP - Unconditional jump

JMP performs an unconditional jump, which sets the CPU’s program counter to the destination specified.

When JMP is performed, the memory address that it is performed at is “pushed” onto the call stack. This memory address is known as a return address and is retrieved if the jump is to be returned from. Thus, if a value is to be retrieved off the stack after JMP, the return address must first be removed, or the stack and base pointers must be adjusted.

Note: The value it pushes onto the stack may overflow a memory address, as memory address values are signed but the addresses themselves are not. Thus, if the program is very large or starts at a very high memory address, it may not be feasible to retrieve a jumped address as it may have overflowed. This is unlikely unless the assembly program is above 27,000 lines of source code.

As subroutines and labels in assembly resolve to memory addresses, JMP will always jump to a memory address if the jump location is a subroutine, granted the destination is small enough to fit within 11 bits.

JMP can also take a register pointer argument, meaning that the value in a register is interpreted as a memory address for the destination of a jump. This must be set manually, as the assembler will not do this.

Using a register as the destination can be beneficial, as r4 and r5 are unsigned 16 bit integers, being able to address all memory addresses. It is also possible to pass r6 or r7 - the floating point registers - to this instruction.

Note: Any jump instruction can overflow the stack if it is full

Compatible DEST types: Register pointer, memory address

Flags affected: None

NameArgumentsDescriptionUse caseExample
JMP1JumpJumpingjmp [31] ; jump to address 31

Binary encoding

Instruction: jmp [50]

OpcodeDTBDEST
[10000][0][0000110010]

Pseudocode representation

if sp > bp // adjust stack pointer
sp := sp + 1
else
sp := sp - 1
if sp < 0 || sp > 65535
throw overflow_err
memory[sp] := pc // push onto stack
if dtb = 1
pc := reg_val(dest)
else
pc := memory[dest]

CMP - Compare two operands

CMP Takes two arguments and compares their values, setting flags accordingly. The zero and sign flags are affected with this instruction, with the sign flag being set if the compared values result in a negative result, and the zero flag being set if they are equal. CMP requires two arguments.

The CMP instruction can take registers, literals, register pointers, and memory address pointers on the RHS, and it can only take a register on the LHS.

CMP will set the sign flag if the LHS is less than the RHS (if the result is negative), and it will be unset if the LHS is greater than the RHS.

CMP can be used along with Bcc to perform a conditional branch out of a loop after recursion occurs enough times in a loop.

Compatible LHS argument types: Register

Compatible RHS argument types: Register, literal, register pointer, memory address pointer, can be signed and unsigned

Flags affected: Zero flag, sign flag

NameArgumentsDescriptionUse caseExample
CMP2Compare RHS and LHSExiting loopscmp r3, 4 ; compare value in r3 to literal 4

Binary encoding

Instruction: cmp r4, 1

OpcodeLHSDTBsRHS
[1010][0100][1000][0001]

Pseudocode representation

if lhs = rhs
zflag := true
if lhs < rhs
sflag := true

NAND - Not And

NAND will perform a Not And operation on two locations.

Similar to the other arithmetic instructions, NAND does not take floating point literal values.

Compatible LHS argument types: Register

Compatible RHS argument types: Register, literal, register pointer, memory address pointer, can be signed and unsigned

Flags affected: None

NameArgumentsDescriptionUse caseExample
NAND2Any boolean operationPerforming an ORnand r4, 3 ; nand r4 by 3

Binary encoding

Instruction: nand r2, 42

OpcodeLHSDTBsRHS
[1011][010][1][00101010]

Pseudocode representation

lhs := !(lhs & rhs)

PUSH - Push a value onto the stack

PUSH will take either a register or literal value, increment or decrement the stack pointer depending on the direction of the call stack, then assign the memory address in the stack pointer to the value passed to the instruction.

PUSH can be used in conjunction with POP to easily manipulate values on the stack, and understanding both instructions can allow for efficient utilization of the call stack, reducing the need to directly use memory addresses.

PUSH will take one argument.

If the stack pointer will become negative if it is decremented or go above the unsigned 16 bit integer limit if it is incremented (depending on the direction of the call stack), the PUSH instruction will result in a stack overflow, where the stack will have ran out of space to continue to add values onto it.

When a jump instruction is called, PUSH is also called internally to push a return address onto the call stack.

Compatible SRC argument types: Register, literal

Flags affected: None

NameArgumentsDescriptionUse caseExample
PUSH1Push onto the stackPushing a return addresspush 4 ; push literal 4 onto stack

Binary encoding

Instruction: push 55

OpcodePaddingDTBsRHS
[1100][000][1][00110111]

Pseudocode representation

if sp > bp // adjust stack pointer
sp := sp + 1
else
sp := sp - 1
if sp < 0 || sp > 65535
throw overflow_err
memory[sp] := src // push onto stack

INT - Pseudo-instruction/“interrupt”

INT is a “pseudo-instruction”. In other architectures, INT is the “interrupt” instruction, which generates a system interrupt to stop the program momentarily to perform an action, such as reading from the standard input, writing to the standard output, or waiting for an event to finish.

In the BELLE-ISA, however, INT also has further functionality beyond a simple interrupt. INT is the pseudo-instruction instruction, being able to perform tasks such as setting, unsetting, and inverting flags. INT exists to keep the size of the ISA small, as implementing separate instructions to perform these tasks would greatly increase the number of instructions in the ISA.

INT was chosen as the name as some functionalities are modeled after system interrupts, however, it is not primarily an interrupt instruction, and can be utilized to alter CPU flags or generate an “interrupt”.

INT takes one literal argument, and depending on the literal argument passed, it will perform a different action.

Compatible SRC argument types: Literal

Below is a table for interrupt codes and their functions.

CodeAction
0-7Print the value at the register specified by code to stdout as their numeric values
8Print values from memory indexed by r0 to r1 as characters
9Read a single byte from stdin and store it in r0
10Pause the CPU for 1 second
11Set the zero flag
12Unset the zero flag
13Invert the zero flag
20Set the maximum clock cycles to the value in r0
21Set the overflow flag
22Unset the overflow flag
23Invert the overflow flag
30Clear the screen
31Set the remainder flag
32Unset the remainder flag
33Invert the remainder flag
40Read an integer from stdin into r0
41Set the sign flag
42Unset the sign flag
43Invert the sign flag
51Make the CPU halt on overflow
52Make the CPU not halt on overflow
53Invert the CPU’s halt on overflow property
60Set the stack pointer to the value in r4
61Set the base pointer to the value in r4
70Set the “Push return address” flag for branches
71Unset the “Push return address” flag for branches

Flags affected: All

NameArgumentsDescriptionUse caseExample
INT1Pseudo-instructionPrinting to stdoutint 52 ; don't halt on overflow

Binary encoding

Instruction: int #42

OpcodeSRC
[1101][000000101010]

MOV - Move

MOV will move a value from a source to a destination, keeping the source intact. MOV takes two arguments, and the RHS can be a literal, register pointer, memory address pointer, register, and can be both signed and unsigned. Similar to other instructions, MOV uses floating point reassignments to reassign values of registers, and if the destination is an integer register, casts it as an integer.

MOV can accept floating point values to move around if the source is a floating point register. Otherwise, it will only accept an integer.

The destination, or the LHS of MOV must always be a register. To change memory address values, please refer to ST.

The source, or RHS of MOV cannot be a memory address. Refer to LD for loading from memory addresses.

Unlike LD, however, MOV allows register indirects - otherwise known as register pointers - to be passed as the RHS of the instruction. This allows for MOV to load from very large memory addresses that wouldn’t fit into a simple LD instruction. Thus, MOV is also the indirect load instruction (in other architectures).

Compatible LHS argument types: Register

Compatible RHS argument types: Register, literal, register pointer, memory address pointer, can be signed and unsigned

Flags affected: None

NameArgumentsDescriptionUse caseExample
MOV2Copy a valueAny program evermov r0, 22 ; mov 22 into r0

Binary encoding

Instruction: mov r4, &r3

OpcodeLHSDTBsRHS
[1110][100][0010][00011]

Pseudocode representation

lhs := rhs // we'll ignore segfaults

LEA - Load effective address

LEA is the Load Effective Address instruction that loads a memory address into a register instead of a value.

The encoding is exactly the same as LD, however rather than loading the value at an address, it simply places the address into a register.

Compatible LHS argument types: Register

Compatible RHS argument types: Memory address

Flags affected: None

NameArgumentsDescriptionUse caseExample
LEA2Load Effective AddressLoading an address for an indirect jumplea r0, [33]

Binary encoding

Instruction: lea r3, [42]

OpcodeLHSRest of instruction
[1111][011][000101010]

Pseudocode representation

lhs := rhs // but this only handles addresses