Tal is the programming language for the Uxn virtual machine.

Uxn programs are written in a unique flavor of assembly designed explicitly for this virtual machine. TAL files are human-readable program files, ROM files are uxn-compatible binary application files; the application that transforms a program into an application is called an 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. To learn more, see the Uxntal Reference.

Stackinout Memoryinout
0x00BRK/LIT Breaknum 0x10LDZ LoadZeropageaddr^val
0x01INC Incrementaa+1 0x11STZ StoreZeropageval addr^
0x02POP Popa 0x12LDR LoadRelativeaddr^val
0x03DUP Duplicateaa a 0x13STR StoreRelativeval addr^
0x04NIP Nipa bb 0x14LDA LoadAbsoluteaddr*val
0x05SWP Swapa bb a 0x15STA StoreAbsoluteval addr*
0x06OVR Overa ba b a 0x16DEI Device Inaddr^val
0x07ROT Rotatea b cb c a 0x17DEO Device Outval addr^
Logicinout Arithmeticinout
0x08EQU Equala bbool^ 0x18ADD Adda ba+b
0x09NEQ NotEquala bbool^ 0x19SUB Subtracta ba-b
0x0aGTH GreaterThana bbool^ 0x1aMUL Multiplya ba*b
0x0bLTH LesserThana bbool^ 0x1bDIV Dividea ba/b
0x0cJMP Jumpaddr 0x1cAND Anda bres
0x0dJCN JumpCondbool^ addr 0x1dORA Ora bres
0x0eJSR JumpStashaddr 0x1eEOR ExclusiveOra bres
0x0fSTH Stasha 0x1fSFT Shifta b^res

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. You can see the complete list of stack permutations.

#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(absolute)
'raw char
~include"raw word

Hello World

In this example program, we begins by creating the EMIT macro, which contains the byte value of the Console/write port, followed by the DEO device output opcode. From that point onward, using EMIT will send a byte from the stack, to the Console/write port. Each device has 16 ports, each port handle a specific I/O.

%HALT { #010f DEO }
%EMIT { #18 DEO }

( init )

|0100 @program

		( send ) LDAk EMIT
		( loop ) INC2 LDAk ,&loop JCN


@hello-word "Hello 20 "World!

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, and points to a series of characters. An absolute address is made of two bytes.

We then assign the child 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 Console/write(port #18) which prints that character to the terminal.

To increment the absolute address found on top of the stack, we use INC2 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.

Both &loop and @loop are ways to define labels, but using &loop will automatically prefix a new &label with the name of the last @label, in this example program/loop. 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 bytes, values with a length of 8 bits. 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. A literal is a byte or short prefixed with a LIT opcode, raw values are not prefixed with the LIT opcode.



The assembler is about 400 lines of ANSI C, it is used to create rom files, from tal source files.