Tal is the programming language for the Uxn stack-machine.

The programming language for creating Uxn-compatible applications is called Uxntal or simply Tal, it is a unique flavor of assembly designed for this unique stack-machine. The program that transforms Tal files, into Uxn-compatible roms, is the Uxn Assembler.

Stack machine programming might look at bit odd, as it uses a postfix notation, which means that operators are always found at the end of an operation. For instance, one would write 3 4 + instead of 3 + 4. The expression written (5 + 10) * 3 in conventional notation would be written 10 5 + 3 * in reverse Polish notation.


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

keep kreturn rshort 2opcode BRK

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: ADD2kr.

Stack Memory
0x00BRK/LIT Break 0x10LDZ LoadZeropageaval
0x01INC Incrementaa 0x11STZ StoreZeropageval a
0x02POP Popa 0x12LDR LoadRelativeaval
0x03DUP Duplicateaa a 0x13STR StoreRelativeval a
0x04NIP Nipa bb 0x14LDA LoadAbsolutea*val
0x05SWP Swapa bb a 0x15STA StoreAbsoluteval a*
0x06OVR Overa ba b a 0x16DEI Device Inaval
0x07ROT Rotatea b cb c a 0x17DEO Device Outval a
Logic Arithmetic
0x08EQU Equala bflag 0x18ADD Adda bres
0x09NEQ NotEquala bflag 0x19SUB Subtracta bres
0x0aGTH GreaterThana bflag 0x1aMUL Multiplya bres
0x0bLTH LesserThana bflag 0x1bDIV Dividea bres
0x0cJMP Jumpa 0x1cAND Anda bres
0x0dJCN JumpCondflag a 0x1dORA Ora bres
0x0eJSR JumpStashars 0x1eEOR ExclusiveOra bres
0x0fSTH Stashars 0x1fSFT Shifta bres

The jump operators will jump to a relative address in normal mode, and to an absolute address in short mode. All memory operators expect a single byte, except for the absolute operators expecting an absolute address. Shift in short mode is expecting a single byte.


By default, operators operate on bytes, notice how in the following example only the last two bytes #45 and #67 are added, even if there are two shorts on the stack.

#1234 #4567 ADD  12  34  ac  00  00  00  00  00

The short mode consumes two bytes from the stack. In the case of jump opcodes, the short-mode operation jumps to an absolute address in memory. For the memory accessing opcodes, the short mode operation indicates the size of the data to read and write.

#1234 #4567 ADD2 57  9b  00  00  00  00  00  00

The keep mode does not consume items from the stack, and pushes the result on top. The following example adds the two shorts together, but does not consume them.

#1234 #4567 ADD2k 12  34  45  67  57  9b  00  00
POPkaaSWPka ba b b a
DUPkaa a aOVRka ba b a b a
NIPka ba b bROTka b ca b c b c a

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 from the return-stack.

#1234 #4567 STH ROT STH ADDr STHr 34  45  79  00  00  00  00  00


Uxntal has no reserved words besides the 32 opcodes, each element type begins with its own special character, or rune. Comments are within parentheses, 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 a padding to the address #0018 with |18, followed by the definition of the Console/write label. From that point onward, the absolute or zero-page address of that label can be referenced by name. The zero-page is the name of the first 256 bytes in memory, the 0000-0100 range, it is typically used to store variables and devices.


Next, we pad to |0100, which is where the first page of memory ends, and where all Uxn programs begin. Our program begins by pushing to the stack, the absolute address of the label hello-world, which is defined at the end of the program and points to a series of characters. An absolute address is made of two bytes. We then assign the label &loop to this position of the program so we can return to it later.

Next, the LDAk opcode takes two bytes at the top of the stack to form an absolute address, and loads the value in memory found at that address to the top of the stack, in this case, the ascii value of the letter H. That value is sent to the Console's port named write(#18) which prints that character to the terminal.

Next, we increment the absolute address found on top of the stack, we use #0001 and ADD2 because we know the items on the stack to be an address made of two bytes. We load the incremented value, the JCN opcode will jump to the position of label &loop as long as the item before the address is not zero. We complete the program with POP2 to remove the address on the stack, to keep the stack clean.

( dev/console )

|18 @Console/write

( init )

|0100 ( -> )

		( send ) LDAk .Console/write DEO
		( loop ) INC2 LDAk ,&loop JCN

@hello-word "Hello 20 "World!

Both &loop and @loop are ways to define labels, but using &loop will automatically prefix your new &label with the name of the last @label. Since label names are unique identifiers and you might need multiple instances of something called loop, it's best to use sublabels when possible.


The memory and stacks contain 8-bit values. To differentiate between operations and 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.