Syntax
Wave2 Assembly's syntax is very similar to the NASM flavoured assembly. The operands are ordered Intel style (instruction destination, source) and use the semicolon ;
for comments.
Section Directives
The first thing you'll see in most Wave2 assembly files is the .memory
directive, which defines the start of a region containing a sequence of values to be stored in the constant registers.
There are two directives at present, .memory
and .code
, each simply defining the beginning of their eponymous regions. These two directives also correspond to the two VM commands !write
1 and !code
respectively.
.memory
1234 5678
.code
mov r0, c0
; etc...
Structure
The core syntax typically takes the following structure:
instruction | size | operands | ||
---|---|---|---|---|
add | .w | r0, | r0, | c1 |
mov | r0, | r1 | ||
swizzle | r4.zyyw | |||
mul | r1, | r2 |
Not all instructions require anything more than the instruction mnemonic itself, such as nop
and halt
, however the ones that do typically follow the pattern above, as ordered in the below operands section.
Mnemonics
The first part of any typical instruction is the mnemonic, i.e. the identifier of the instruction being used. Some of these map directly to the respective bytecode operations, others choose the operation from context such as the move
instruction selecting move
for register-to-register operations, store
for register-to-memory operations, and load
for memory-to-register operations.
Here is a non-exhaustive list of some instructions and the actual opcode they resolve to:
mov r0, r1 ; `mov r0, r1` - move r1 into r0
mov r3, [r2] ; `load r3, [r2]` - load the memory value at [r2] into r3
mov [r5], r1 ; `store [r5], r1` - store the value of r1 into memory at [r5]
sub.w r0, r0, r1 ; `sub16 r0, r1` - 16-bit Subtract. r0 = r0 - r1
sub.b r4, c2, r4 ; `rsub8 r4, c2` - 8-bit Reciprocal subtract. r4 = c2 - r4
Size Specifiers
The math and the bit shift operations both require size specifiers, since there are a set of math and bit shift instructions for operating on both bytes and words, we need to specify which data type to operate on. For more information on how the size of each operation affects the output, see the appropriate pages for details.
Operands
Most instructions take operands which specify which registers or data on which the operation takes place.
The operand order is, as described earlier, in the Intel operand order, similar to NASM. This means that pointers are bracketed such as [r0.x]
and, pertinent to this section, the order of the operands are typically destination, source
which means if you want to move a value c2
into into register r0
, you use the order:
mov r0, c2
This is because r0
is the destination, and c2
is the source. An easy way to remember this may be thinking of the order as being similar to writing out a math equation:
Some instructions take three operands, such as the math instructions. The three operands in this case are in the order dest, lhs, rhs
where lhs
and rhs
are the left and right hand sides of the math equation, and the destination is the register the arithmetic is performed on, and as such the register the value will be stored in.
Literals
Literal values can be either decimal or hex. Decimal values do not require any special prefix or enclosing symbols and such can be written 12
or 99
or similar.
Hex values must be prefixed with $
so as to not be ambiguous or conflict with the constant register identifiers such as c0
and $c0
etc.
Any instruction that takes a literal value (such as the bit shift instructions) will accept either kind of literal.
ror.w r0, $a
rol.b r1, 2
asl.w r5, $4
Inline Values
Sometimes it might be necessary to include raw values arbitrarily within the code memory. This can be achieved with the inline value syntax, which is simply a !
followed by the hex value to be placed at that address. The hex value must not exceed 0xffff
.
; An alternative way to store a literal value from code memory.
; This is equivalent to using the macro `set r0, $f000`
mov r0.x, [ri.x+]
!f000
-
While the
!write
command starts at address0x0000
it can, given enough values, overflow into the!code
command's region which starts at0x0040
meaning that if you also write starting values to all the registers, your write command can overflow into the code region, potentially resulting in a separate!code
command to be unnecessary. ↩