Wasp assembly language reference Last modified 28/09/2023
This is a guide to the assembly language used in the Wasp and Sting programs. This is not a teaching
tool, but a reference document. For further explanation, please see the teaching material.
The language is specified in the document “instruction.set”, which is used by both Wasp and Sting.
This contains the microcode for each instruction, which may be useful if you need more detail. It is
also possible to modify the instructions if you want to develop your own language.
Instructions
Each assembly instruction starts with a mnemonic such as “MOV” or “ADD”. This will be followed by
0,1 or 2 operands. For example,
MOV AX,BX
The first operand is generally the destination of the result of the instruction. For example
MOV AX,BX
copies information from BX to AX.
Operands can be:
A register name: AX, BX or SP
A numerical value: an example is 0x1234
In some cases, operands will be surrounded by brackets to indicate addressing. This is discussed
below.
Registers
The two registers directly accessible by Wasp assembly are AX and BX. These are 16-bit general-
purpose registers.
It is also possible to access SP directly using MOV operations. SP is the Stack Pointer and provides
the address of the memory space that will be used by the next PUSH operation. (In other words, the
address above the top of the stack.) The stack starts at 0x1FFF and grows downwards. By accessing
SP, it is possible to use a different location for the stack. However, it is recommended that you
access the stack only through PUSH and POP.
Numerical values
Numerical values can be expressed in hexadecimal, binary, decimal or octal. All numerical values
should be preceded with one of the following:
0x Hexadecimal Example 0x10 = 16 in decimals
0d Decimal Example 0d10 = 10 in decimals
0b Binary Example 0b10 = 2 in decimals
0q Octal Example 0q10 = 8 in decimals
Numerical values can be up to two bytes in length.
Negative values can be handled using two-byte two’s complement.
Values less than 256 will be handled slightly differently by the Wasp processor, as discussed in
“instruction format”, but this is mostly transparent to the programmer.
Most mathematical operations will not support numerical values larger than 255. Instead, you
should use register operands for larger numbers.
Instruction format
Most machine code instructions are a single two-byte word. The high byte contains the opcode,
which specifies the mnemonic and any register operands. The low byte contains the numerical
operand if it less than 256. Numerical operands larger than 255 will be stored in the next word in
memory.
Example 1:
0x2033
contains two bytes. The first byte is 0x20, which indicates the command will be
ADD AX, byte
The second byte is 0x33, which indicates that the byte value will be 0x33. The result is:
ADD AX,0x33
Example 2:
0x0F00
contains two bytes. The first byte is 0x0F, which indicates the command will be
MOV BX, word
The second byte is not used in this instruction. The value of the word will be in the next location in
memory.
Memory addressing
Wasp assembly uses [ ] brackets to indicate that the instruction should use the value in a memory
address indexed by the operand.
Example 1:
MOV AX,0x1A Copies the value 0x1A into AX.
MOV AX,[0x1A] Copies the contents of memory address 0x1A into AX.
Example 2:
MOV [0x2F],AX Copies the value of the AX register into memory location 0x2F
MOV 0x2F,AX Makes no sense, as 0x2F is a literal that cannot be changed.
Labels
Labels are used to reference specific lines of assembly. These are used to create variables and to
control program flow. A label can be almost any word, followed by a colon.
Labels are case-insensitive. It is recommended that you only use letters a-z and digits 0-9 in your
labels. Certain words are reserved – the assembler will warn you if you use a reserved word for a
label.
A label should appear at the start of a line of assembly code, or on the line above the assembly code.
A label can be used in place of any numerical two-byte operand.
Labels are used in three situations:
To create variables.
MOV AX,[size] // Copies from the memory address labelled “size”
INC AX // Adds 1 to AX
MOV [size],AX // Copies to the memory address labelled “size”
HALT
size: DC.W 0x12 // Stores the value 0x12 in a memory location, and labels that location with
// “size”
To control program flow.
MOV AX,0x05
top: // Labels the following line of code with the label “top”
ADD AX,0x02
ADD BX,AX
JMP top // Jumps the program execution to the line labelled “top”.
// (This example creates an infinite loop.)
To create functions
MOV AX,0x03
MOV BX,0x92
CALL swap // Runs the function that starts with the label “swap”
HALT
swap: // Labels the following line of code with “swap”
PUSH AX
PUSH BX
POP AX
POP BX
RET // Returns from the function
DC.W
This command allows the program to place a value into a memory location. It is normally used in
conjunction with a label to create a variable. For example:
size: DC.W 0x12
The assembler will use the next memory location to store this number. To avoid these values being
mistaken for machine code instructions, they are normally put at the end of the program, after the
HALT instruction.
EQU
EQU allows the programmer to specify that an identifier in the assembly code that should be
replaced with a numerical value. This is not the same as a label, as no memory address is allocated,
and the value of the identifier cannot be changed. This is similar to a constant in higher-level
programming languages.
NEWLINE: equ 0x0A
(Programmers often use capital letters for constants. This is not required for WASP assembly, which
is case insensitive. Using capitals is purely an aid to the programmer.)
Fetch-execute
The Wasp processor uses a fetch-execute cycle. The fetch will retrieve the instruction indexed by the
program counter from memory, and increment the program counter. This means that the program
counter will always point to the memory address after the current instruction.
ORG
The ORG instruction is a command to the assembler to use a specific address for the next
instruction, and to continue from there for all following instructions. For example,
ORG 0x0100
Tells the assembler to start using memory address 0x0100 for the machine code. This is used to
place program code in specific locations in memory.
If you do not use an ORG instruction, the assembler will use location 0.
You can use multiple ORG instructions in a program to specify the location of different sections of
code.
It is possible to use ORG instructions to make machine code instructions over-write other
instructions. This is a programmer error and may cause undesired results.
NOP
This command tells the Wasp processor to take no action, and proceed to the next instruction. This
can be used to create space in a program for later expansion, or to replace code that has been
removed.
Any opcode that does not have a known instruction will be treated as NOP.
MOV
This copies the value of the second operand into the first operand. In spite of the name, this is a
copy, not a move. The second operand will be unchanged.
MOV operations support one-byte and two-byte numerical operands.
ADD
This adds the value of the second operand to the first operand. The second operand will be
unchanged.
This sets the value of the processor status registers (VNZC), depending on the result of the
calculation.
Mathematical calculations do not support two-byte numerical operands. To perform calculations on
numbers greater than 255, move the numbers into the AX,BX registers first.
SUB
This subtracts the value of the second operand from the first operand. The second operand will be
unchanged.
This sets the value of the processor status registers (VNZC), depending on the result of the
calculation.
Mathematical calculations do not support two-byte numerical operands. To perform calculations on
numbers greater than 255, move the numbers into the AX,BX registers first.
OR
This performs a bitwise “OR” operation, with the result placed in the first operand.
This sets the value of the processor status registers (VNZC), depending on the result of the
calculation.
Mathematical calculations do not support two-byte numerical operands. To perform calculations on
numbers greater than 255, move the numbers into the AX,BX registers first.
AND
This performs a bitwise “AND” operation, with the result placed in the first operand.
This sets the value of the processor status registers (VNZC), depending on the result of the
calculation.
Mathematical calculations do not support two-byte numerical operands. To perform calculations on
numbers greater than 255, move the numbers into the AX,BX registers first.
MUL
This multiplies the value of the first operand by the second operand, with the result in the first
operand.
Both operands, and the result, are treated as two-byte two’s complement numbers.
This will set the PSR carry flag if the result is greater than 32767 or smaller than -32768. The PSR
overflow flag is meaningless.
Mathematical calculations do not support two-byte numerical operands. To perform calculations on
numbers greater than 255, move the numbers into the AX,BX registers first.
DIV
This divides the value of the first operand by the second operand, with the result in the first
operand.
Both operands, and the result, are treated as two-byte two’s complement numbers.
If the value of the second operand is zero, the result will be 0xFFFF and the PSR carry flag will be set.
Mathematical calculations do not support two-byte numerical operands. To perform calculations on
numbers greater than 255, move the numbers into the AX,BX registers first.
MOD
This performs modulo division (remainder division) on the value of the first operand with the value
of the second operand, with the result in the first operand.
Both operands, and the result, are treated as two-byte two’s complement numbers.
Mathematical calculations do not support two-byte numerical operands. To perform calculations on
numbers greater than 255, move the numbers into the AX,BX registers first.
SHL
This moves shifts the bits of the value of the operand left one space. This normally has the effect of
multiplying by two, unless overflow occurs.
SHR
This moves shifts the bits of the value of the operand right one space. This normally has the effect of
dividing by two.
As a special case, the carry flag is set if the final bit of the operand is 1 before the shift. (I.e. the
operand is an odd number.)
INC
This adds one to the value of the operand right.
This sets the value of the processor status registers (VNZC), depending on the result of the
calculation.
DEC
This adds one to the value of the operand right.
This sets the value of the processor status registers (VNZC), depending on the result of the
calculation.
NEG
This performs a two’s complement negation on the operand. This effectively make a positive
number negative, and vice versa.
This sets the value of the processor status registers (VNZC), depending on the result of the
calculation.
NOT
This performs a bitwise negation on the operand. This turns every 0 bit into 1 and every 1 bit into 0.
This sets the value of the processor status registers (VNZC), depending on the result of the
calculation.
CMP
This subtracts the value of the second operand from the value of the first operand, but does not
store the result anywhere. It will set the value of the processor status registers (VNZC), depending
on the result of the calculation. This is normally used in conjunction with a conditional branch
operation. Effectively, this compares the two operands and allows the next instruction to see if the
numbers were equal, not equal or which one was largest.
Mathematical calculations do not support two-byte numerical operands. To perform calculations on
numbers greater than 255, move the numbers into the AX,BX registers first.
JMP
This jumps the program to the specified address. This is an “unconditional jump”.
JEQ
“Jump if equal”. This is a conditional jump to the specified address. The jump will only occur if the
PSR.Z flag is set. It is normally used after a CMP instruction and will jump if the two operands are
equal.
JNE
“Jump if not equal”. This is a conditional jump to the specified address. The jump will only occur if
the PSR.Z flag is not set. It is normally used after a CMP instruction and will jump if the two operands
are not equal.
JL
“Jump if less then”. This is a conditional jump to the specified address. The jump will only occur if the
PSR.N flag is set. It is normally used after a CMP instruction and will jump if the first operand is
smaller than the second operand.
JGE
“Jump if greater than or equal”. This is a conditional jump to the specified address. The jump will
only occur if the PSR.N flag is not set. It is normally used after a CMP instruction and will jump if the
first operand is greater than the second operand or equal to it.
JC
Jump on carry. This is a conditional jump to the specified address. The jump will only occur if the
PSR.C flag is set. It is normally used after an addition or subtraction to check if the calculation
resulted in a 16-bit overflow.
JNC
Jump on not carry. This is a conditional jump to the specified address. The jump will only occur if the
PSR.C flag is not. It is normally used after an addition or subtraction to check if the calculation did
not result in a 16-bit overflow.
JV
“Jump on two’s complement overflow. This is a conditional jump to the specified address. The jump
will only occur if the PSR.V flag is set. It is normally used after a mathematical operation to check if
the calculation resulted in a two’s complement overflow. (For example, if the numbers were too
big.)
JNV
“Jump on no two’s complement overflow. This is a conditional jump to the specified address. The
jump will only occur if the PSR.V flag is not set. It is normally used after a mathematical operation to
check if the calculation resulted in a two’s complement overflow. (For example, if the numbers were
not too big.)
PUSH
This pushes the value of the operand onto the stack. The stack is managed by the stack pointer, and
gets smaller as more items are added to the stack. Note that the stack is used by the programmer, as
well as used for subroutine calls. A skilled programmer can manage this without causing problems.
The stack pointer points to the next available free space for use by the stack.
POP
This pops the value off the top of the stack and stores it in the location specified by the operand. The
stack is managed by the stack pointer and increases as items are popped from the stack.
CALL
This jumps the program to a subroutine specified by the operand. The current program counter is
pushed onto the stack, so the subroutine can return to this part of the code once complete.
Normally, CALL is used with a label to specify the name of the subroutine.
RET
This returns the program to the address on the top of the stack, and also pops the stack. This is
normally used to return at the end of a subroutine.
HALT
This halts the program. A program can contain many HALT instructions.