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

JO and JZ - Conditional jump

JO - Jump if the overflow flag is set, JZ - Jump if the zero flag is set

JO and JZ perform conditional jumps, which set the CPU’s program counter to the destination specified.

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

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

When JO or JZ are 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 JO or JZ, 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.

The overflow flag can be set unconditionally with int 21, unset with int 22, and inverted with int 23.

The zero flag can be set unconditionally with int 11, unset with int 12, and inverted with int 13.

As subroutines and labels in assembly resolve to memory addresses, JO and JZ 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.

JO and JZ 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.

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

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

Compatible DEST types: Register pointer, memory address

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

NameArgumentsDescriptionUse caseExample
JO1Jump if overflow flag is trueLeaving a loop when a register overflowsjo [3] ; jump to address 3 if overflow flag is set
NameArgumentsDescriptionUse caseExample
JZ1Jump if zero flag is trueJumping somewhere after a cmpjz [42] ; jump to address 42 if zero flag is set

Binary encoding

Instruction: jo &r3

OpcodeDTBDEST
[0010][1][00000000011]

Binary encoding

Instruction: jz [1]

OpcodeDTBDEST
[1001][0][00000000001]

Pseudocode representation

if flag // dependent on jump 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]
else
return

POP - Pop off the stack

POP reads the stack pointer, and then “pops” the value contained at the stack pointer into a destination. The destination can only be a register, and 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

if memory[sp] = NULL
throw underflow_err
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

if memory[sp] = NULL
throw underflow_err
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

if memory[rhs] = NULL
throw segfault_err
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
JMP1JumpRecursionjmp [31] ; jump to address 31

Binary encoding

Instruction: jmp [50]

OpcodeDTBDEST
[1000][0][00000110010]

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 JZ to perform a conditional jump 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

MUL - Multiplication

MUL will perform arithmetic signed floating point multiplication (as there are floating point registers), and will cast the value to an integer if the destination is an integer register.

MUL can also be used for division if the RHS is a decimal value less than one. In particular, if it is a floating point register containing a decimal value less than one.

MUL, in conjunction with ADD and MOV can be used to change register values to very large numbers, thus allowing large memor y addresses to be addressed with indirect loading and storing via ST and MOV.

Similar to the other arithmetic instructions, MUL 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
MUL2Multiply two valuesAchieving large numbers in registersmul r4, 3 ; multiply r4 by 3

Binary encoding

Instruction: mul 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.

| Code | Action |

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

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

NOP - No operation

NOP is an instruction that does absolutely nothing - or as close to nothing as possible - and continues the program.

In BELLE, the emulated NOP instruction calls a real NOP instruction via inline assembly.

Flags affected: None

NameArgumentsDescriptionUse caseExample
NOP0NO-OPTiming alignmentsnop ; do nothing

Binary encoding

Instruction: nop

OpcodeRest of instruction
[1111][000000000000]

Pseudocode representation

// 😛