There are 32 opcodes, each opcode occupies 5 bits of a byte, the remaining 3 are used to select modes of that opcode, modes are explained below.


Operator modes are indicated by appending extra characters at the end of the opcode, for example, the short mode for the ADD opcode is ADD2, modes can also be combined, for example: ADD2r.

Stack Memory
0x00BRK Break 0x10LDZ Load(zero-page)a[b]
0x01LIT Literal++ 0x11STZ Store(zero-page)b a
0x02--- 0x12LDR LoadRelativea[b]
0x03POP Popa 0x13STR StoreRelative[b] a
0x04DUP Duplicateaa a 0x14LDA LoadAbsolutea*[b]
0x05SWP Swapa bb a 0x15STA StoreAbsolute[b] a*
0x06OVR Overa ba b a 0x16DEI Device Ina[b]
0x07ROT Rotatea b cb c a 0x17DEO Device Out[b] a
Logic Arithmetic
0x08EQU Equala bc 0x18ADD Adda bc
0x09NEQ NotEquala bc 0x19SUB Subtracta bc
0x0aGTH GreaterThana bc 0x1aMUL Multiplya bc
0x0bLTH LesserThana bc 0x1bDIV Dividea bc
0x0cJMP Jump[a] 0x1cAND Anda bc
0x0dJCN JumpConda [b] 0x1dORA Ora bc
0x0eJSR JumpStash[a]rs 0x1eEOR ExclusiveOra bc
0x0fSTH Stashars 0x1fSFT Shifta bc

The short mode allows for each operator to do 16-bits operations by pushing and popping the necessary extra items from the stack. In the case of jump opcodes(JMP2, JSR2, and JCN2) the short mode operation jumps to an absolute address in memory. For the getters and setters(LDZ2, STZ2, GET2 and PUT2) the short mode operation indicates the size of the data to read/write.

The return mode makes it possible for any operator to operate on the return-stack directly, for that reason there is no dedicated return opcode. For example, the JumpStash(JSR) operator pushes the program's address onto the return stash before jumping, to return to that address, the JMP2r opcode is used, where instead of using the address on the working-stack, it takes its address directly from the return-stack.


Uxambly has no reserved words besides the 32 opcodes, each element of the program has its own rune. Comments are within parentheses, the curlies are used in the definition of macros, and the square brackets are ignored.

%macro-define #literal hex
|pad(absolute) .literal addr(zero-page)
$pad(relative) ,literal addr(relative)
@label-define ;literal addr(absolute)
&sublabel-define :raw addr
/sublabel spacer 'raw char
"raw word


In the following example, our program begins with ;hello-world pushing the address of the @hello-world label to the top of the stack, followed by ;print which pushes its address on top of that first label. Each address is 2 bytes long, and so our stack contains 4 bytes before the JSR2 opcode.

Next, JSR2 consumes the address(or, two bytes) at the top of the stack and so we move to the subroutine @print, leaving the address of the @hello-world label on the stack. The first thing that happens is DUP2 GET copies it, and reads the value of the byte at that address, then we send it to the console device. We increment the value of the label, and start over until we reach the end of the text by hitting a byte that is equal to 0.

( dev/console )

%RTN { JMP2r }

( devices )

|10 @Console    [ &pad $8 &char $1 ]

( init )

|0100 ( -> )
	;hello-word ,print JSR

@print ( addr* -- )
		( send ) DUP2 GET .Console/char DEO
		( incr ) #0001 ADD2
		( loop ) DUP2 GET #00 NEQ ,&loop JCN


@hello-word "hello 20 "World!


The memory and stacks contains 8-bits values, to differentiate operations from literal numbers, the LIT opcode will push the following byte from memory onto the stack, the 16-bits mode LIT2 opcode will push the following short.

Immediate, or literal, addressing allows to directly specify a byte or short constant. Deferred addressing is a form of immediate addressing where the address of a label is put on the stack.


Control flow

Uxambly allows for basic control flow, here are some of them:

#00 #0d
	( body ) 
	DUP2 LTH ,&loop JCN
#00 #0d
	( body )
	DUP2 EQU ,&end JCN
,&while JMP &end
DUP #01 NEQ ,&b JCN
	( a ) 
&b DUP #02 NEQ ,&c JCN
	( b ) 
&c DUP #03 NEQ ,&default JCN
	( c )