
A programming language for the Uxn virtual machine.

Uxn programs are written in a stack-based flavor of assembly designed especially for this virtual machine. Tal files are textual source files, Rom files are binary program files; applications that transform Tal files into Rom files are called Assemblers.
- Uxn Zine, by Clemens Scott.
- Uxntal Cheatsheet, by Weeble.
- Uxntal Manual(7), by Eiríkr Åsheim.
- Introduction Tutorial, by Compudanzas
Setup
Here is a quick way to get started writing programs with a little self-hosted assembler and an emulator which includes the Console device:
# Build emulator cc uxnmin.c -o uxnmin # Build assembler xxd -r -p drifloon.rom uxnasm.rom # Assemble source echo "#1234 #010e DEO" | uxnmin uxnasm.rom > result.rom # Run program uxnmin result.rom
Uxntal Syntax
In stack programming there are no variables and no precedence rules, the calculations are merely performed in the sequence in which they are presented. The order with which elements come off the stack is known as Last In, First Out. In the stack a b c, the c item was the last to be added, and will be the first to be removed.
#01 DUP ADD #03 MUL program
01 01 02 03 06 stack 01 02
In Uxntal is not decimal, numbers are expressed in hexadecimal. Which means that numbers go like: one,
two, three, four, five, six, seven, eight, nine, ha, be, ce, de, he, fe, ten!
It takes some getting used to, but don't worry.
Without
further ado..
Let's dive into it!
The following example program prints the phrase "Hello World!" by pushing the address to a label on the stack, and iterating through each letter found at that address with a loop that increments the pointer until it reaches end of the phrase, at which point, the stack is emptied and the evaluation halts.
A word starting with @ defines a label, and one starting with ; pushes the address of a label to the stack. ;text pushes two bytes pointing to the absolute address of the @text label to the stack. Next, we define a new label named @while, to mark the start of a loop that will print each character stored at the text label.
The LDAk opcode loads a byte from the address on top of the stack, in this case, the ascii letter H(48). The k-mode indicates that the operator will not consume the address.
The DUP opcode makes a copy of the letter. The ?{ pops that letter from the stack, and if it is not zero, we jump to the corresponding }, which is an anonymous label.
|0100 a0 01 11 ( ;text ) @while |0103 94 ( LDAk ) |0104 06 ( DUP ) |0105 20 00 02 ( ?λ00 ) |0108 22 ( POP2 ) |0109 00 ( BRK )
The #18 word pushes a number to the stack, which maps to the Console/write port(#18), followed by the DEO opcode that pops both bytes and sends the letter to the device port, printing it on the Console, leaving only the address on top of the stack.
@λ00 |010a 80 18 ( #18 ) |010c 17 ( DEO ) |010d 21 ( INC2 ) |010e 40 ff f2 ( !while )
The INC2 opcode increments the address, moving the text pointer to the next letter. The 2-mode is used because the address is made of two bytes.
Finally, we jump back to the start of the loop with !while, and repeat the loop until the letter is zero, when that happens, we POP to remove the letter, and POP2 to remove the address on the stack to keep the stack clean at the end of the evaluation.
@text |0111 48 65 6c ( H e l ) |0114 6c 6f 20 ( l o ) |0116 57 6f 72 ( W o r ) |011a 6c 64 21 ( l d ! )
To summarize, comments are within parentheses, numbers are lowercase hexadecimal shorts or bytes, opcodes are uppercased reserved words, and anonymous labels are within curlies. Runes are special characters at the start of a word that define its meaning, here is the full list:
Padding Runes | Number Rune | ||||||
---|---|---|---|---|---|---|---|
| | absolute | $ | relative | # | literal number | ||
Label Runes | Ascii Runes | ||||||
@ | parent | & | child | " | raw ascii | ||
Addressing Runes | Wrapping Runes | ||||||
, | literal relative | _ | raw relative | ( ) | comment | ||
. | literal zero-page | - | raw zero-page | { } | anonymous | ||
; | literal absolute | = | raw absolute | [ ] | ignored | ||
Immediate Runes | Pre-processor Runes | ||||||
! | jmi | ? | jci | % | macro | ~ | include |
Uxntal Stacks
All programming in Uxntal is done by manipulating the working stack,
and return stack, each stack contains 256 bytes. Here are some stack
primitives assuming the initial state of the stack is a b c
where
c
is the top of the stack:
POP | a b | Discard top item. |
---|---|---|
NIP | a c | Discard second item. |
SWP | a c b | Move second item to top. |
ROT | b c a | Move third item to top. |
DUP | a b c c | Copy top item. |
OVR | a b c b | Copy second item to top. |
A byte is a number between 0-255(256 values), a short is a number between 0-65535(65536 values) made of two bytes, each byte in a short can be manipulated individually:
#0a #0b POP 0a #12 #3456 NIP 12 56 #1234 DUP 12 34 34
The two stacks are circular, to pop an empty stack does not trigger an error, but merely means to set the stack pointer to 255. There are no invalid programs, any sequence of bytes is a potential Uxn program. Values are moved between stacks with the STH opcode.
WST 00 00 00 00 00 00|12 34 <02 RST 00 00 00 00 00 00 00|56 <01
The program above contains 12 and 34 on the working stack, and 56 on the return stack. The stack content can always be printed by sending a non-null byte to the System/debug port.
Uxntal Opcodes
Uxn supports 4 immediate opcodes, and 32 standard
opcodes. The list below show the opcodes and their effect on a given stack
a b c
, where c
is the top of the stack.
- PC: Program Counter.
- [M]: Value at address
M
in Memory. - <D>: Value at address
D
in Device. - a8: a byte, a16: a short.
- |: Return Stack.
LIT a b c [PC] JCI a b , (c8)PC+=[PC] JMI a b c , PC+=[PC] JSI a b c | PC , PC+=[PC] BRK . EQU a b==c LDZ a b [c8] ADD a b+c INC a b c+1 NEQ a b!=c STZ a [c8]=b SUB a b-c POP a b GTH a b>c LDR a b [PC+c8] MUL a b*c NIP a c LTH a b<c STR a [PC+c8]=b DIV a b/c SWP a c b JMP a b , PC+=c LDA a b [c16] AND a b&c ROT b c a JCN a , (b8)PC+=c STA a [c16]=b ORA a b|c DUP a b c c JSR a b | PC , PC+=c DEI a b <c8> EOR a b^c OVR a b c b STH a b | c DEO a <c8>=b SFT a b>>c8l<<c8h
Modes
Each opcode has 3 possible modes, which can combined:
- The short mode 2 operates on shorts, instead of bytes.
- The keep mode k operates without consuming items.
- The return mode r operates on the return stack.
INC2r | |||||||
---|---|---|---|---|---|---|---|
k | r | 2 | opcode | ||||
0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
By default, operators consume bytes from the working stack, 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 ADD12 34 ac
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
The keep mode does not consume items from the stack, and pushes the result on top. Every opcode begins by popping values from the stack before operating on them. This mode keeps a copy of the stack pointer to recover after the popping stage.
#1234 #4567 ADD2k 12 34 45 67 57 9b
The return mode swaps the stacks on which an opcode
operates. Under this mode, a return address will be pushed to the working
stack, and stashing will take from the return stack. For that reason, there is
no return opcode. For example, the JSR
opcode pushes the
return address onto the return stack, and JMP2r
jumps to that
address.
LITr 12 #34 STH ADDr STHr 46

Immediate opcodes
Immediate opcodes are operators that do not take items from the stack, but read values stored immediately after the opcode in the program's memory. Uxntal has 4 immediate opcodes:
- The literal LIT.
- The jump !routine, immediate of JMP.
- The conditional ?routine, immediate of JCN.
- The subroutine routine, immediate of JSR.
The immediate jump opcodes are slightly faster than their standard opcode counterparts, but do not have modes and cannot be used to do pointer arithmetic. The address value of the immediate opcodes are stored in memory as relative shorts, enabling routines making use of these opcodes to be moved around in the program's memory.
@on-reset ( -> ) #0007 fact BRK @fact ( n* -- res* ) ORAk ?{ POP2 #0001 JMP2r } DUP2 #0001 SUB2 fact MUL2 JMP2r
To learn more about each opcode, see the Opcode Reference.
Uxntal Notation
The Uxntal notation follows that of the Forth programming language, where each item on the left of the -- spacer is the state of the stack before, and to the right, the state of the stack after:
@routine ( a b -- a b c ) ADDk JMP2r
By default, single items are a byte long, and shorts are indicated with a * suffix, the order in which they appear is the order of the stack with the top item to the right:
@routine ( a b* -- b* a ) ROT JMP2r
If a routine is a vector, it uses the arrow notation.
@on-event ( -> ) BRK
Brackets
The square brackets do nothing, they are there merely for readability and formatting, they are useful for making explicit certain things like grouping behaviors, joining literals or indicating lookup tables.
@routine ( -- ) [ LIT2 20 -Console/write ] DEO JMP2r %min ( a b -- r ) { GTHk [ JMP SWP ] POP } @sprite [ 00 66 ff ff ff 7e 3c 18 ]
Uxntal Numbers
Uxntal uses only lowercase unsigned hexadecimal numbers of either 2 or 4 characters in length. There are two types of numbers:
- A Literal Hex, like #ab, denotes a number that will be pushed on the stack when evaluated, it is made of a LIT opcode that matches its length, followed by a Raw Hex number.
- A Raw Hex, like aa, is the standard textual encoding of data in a program, generally speaking these are more often loaded than evaluated. It can be anything, an opcode, an ascii byte, an address, part of a sprite.
#12 #34 LIT2 5678 ADD2 68 ac
Uxntal Labels
A label is a non-numeric, non-opcode, and non-runic symbol that correspond to a number between 0 and 65536. A label name is made of two parts, a scope and a sublabel. Sublabels can be added to a scope with the &name rune, or by writing the full name, like @scope/name. Note that a labels like bed, add and cafe are considered numeric.
Functions are simply labels that will be jumped to, and returned from.
@func ( a b -- c ) &loop INC GTHk ?&loop ADD JMP2r
Constants are labels that hold a specific value through the entire execution of the program. They allow to assign a name to a number, making the code more readable.
|1400 @limit
Enums are labels with padded members of equal sizes that can be used as constants in a program, they typically begin by rolling back the program address with |00:
|00 @Suit &clubs $1 &diamonds $1 &hearts $1 &spades
Structs are labels with padded members of different sizes, that maps on a data-structure, they typically begin by rolling back the program address with |00:
|00 @Person &name $2 &age $1 &height $2
Labels can also be used with the padding runes to define a global length. For example, if one needs to specify a length of 0x30 for multiple members of a struct, a value can be specified as follow:
|30 @length |00 @Struct &field $length
Scope
Uxntal objects are defined statically and allow for the enclosed methods to access encapsulated local &members. The example below contains an object with the method set-color, accessible from outside the scope as pen/set-color.
@pen &position &x $2 &y $2 &color $1 &set-color ( color -- ) ,/color STR JMP2r
New methods and members can extend an existing scope from anywhere by creating a label with the scope name followed by a slash and the name of the extension. The &labels declared within the extension have the same access to local labels as the rest of the object.
@pen/get-position ( -- x* y* ) ,/x LDR2 ,/y LDR2 JMP2r
When calling local methods the scope's name can be omitted, starting at the slash, like /method:
@pen/paint ( -- ) /get-position canvas/draw-line-to JMP2r
Addressing
A labels is a way of assigning a name to a number. There are six ways to get
the number corresponding to that label. Literal addressing prefixes the label
with a LIT
for Relative and Zero-Page addressing, and
LIT2
for absolute addressing.
- Literal Relative, like ,label, pushes a relative distance byte to the label.
- Literal Zero-Page, like .label, pushes an absolute address byte to the label.
- Literal Absolute, like ;label, pushes an absolute address short to the label.
- Raw Relative, like _label, writes a relative distance byte to the label.
- Raw Zero-Page, like -label, writes an absolute address byte to the label.
- Raw Absolute, like =label, writes an absolute address short to the label.
Raw addressing is used for building data-structures and more advanced programs. A relatively common usage of raw runes is to create literals directly into the return stack:
[ LITr ab ] STHr INC ac
Anonymous Labels
Anonymous labels are designated with a curly bracket that points to its associated closing bracket, and can be nested. Under the hood, the opening bracket assembles to the address of the closing bracket which allows the destination address to be used like any other label such as a JCI ?{, a JMI, !{ or a plain literal ;{. Here are some example data-structures:
@counted-string _{ "foo 20 "bar } @linked-list ={ ={ "A } ={ "B ={ "C } } }
Unless Blocks
It is important to notice that in the case of a conditional jump, the lambda's content is jumped over when the flag byte is true.
[ LIT2 &last $1 -Mouse/state ] DEI DUP ,&last STR DUP2 #0001 NEQ2 ?{ ( on down ) } DUP2 #0101 NEQ2 ?{ ( on drag ) } DUP2 #0100 NEQ2 ?{ ( on release ) } POP2
The opening curly bracket assembles to a unique label reference, and the closing bracket to a corresponding matching label definition. They do not affect the scope.
Uxntal Macros
A macro is a way of defining inline routines, it allows to create new words that will be replaced by the body of the macro, as opposed to a jump where the program counter will move to a routine and back, therefore it needs to be defined before its usage, as follow:
%modulo ( num denum -- res ) { DIVk MUL SUB } @routine ( -- c* ) #18 #03 modulo JMP2r
In the previous example, the token modulo will get replaced by the body of the macro during assembly:
@routine ( -- c* ) #18 #03 DIVk MUL SUB JMP2r

Uxntal Memory
There are 64kb of addressable memory. Roms are always loaded at 0x0100, which is the address of the Reset Vector and where evaluation begins. During boot, the stacks, device and addressable memories are zeroed. During a soft-reboot, the content of the zero-page is preserved.
Shared | Memory | RAM | Data | 64kb pages |
---|---|---|---|---|
Private | Stacks | Working Stack | Data | 256 bytes |
Pointer | 1 byte | |||
Return Stack | Data | 256 bytes | ||
Pointer | 1 byte | |||
IO | Devices | Data | 256 bytes |
The device page and stacks are located outside of addressable memory.
- An Absolute Padding, like |100 moves the program generation to an address specified by a number or label.
- A Relative Padding, like $18 moves the program generation by a distance specified by a number or label.
|18 @width
|100 @on-reset ( -> )
;buffer/end BRK 02 18
|200 @buffer $width &end
Memory is big-endian, when writing or reading a short from memory, the position is that of the high-byte. The low-byte of a short written at 0xffff wraps to 0x0000.
#12 #0200 STA 0x0200=12 #3456 #0400 STA2 0x0400=34, 0x0401=56 #0400 LDA 34
The zero-page is the memory located below 0x0100, its purpose is to store variables that will be accessed often, or needs to be preserved across a soft-reboot. It is sligthly faster to read and write from the zero-page using the LDZ and STZ opcodes as they use only a single byte instead of a short. This memory space cannot be pre-filled in the rom prior to initialization. The low-byte of a short written at 0xff wraps to 0x00.
#1234 #80 STZ2 0x0080=12, 0x0081=34 #81 LDZ 34
Uxntal Devices
Uxn is non-interruptible, vectors are locations in programs that are evaluated when certain events occur. A vector is evaluated until a BRK opcode is encountered. Uxn can communicate with a maximum of 16 devices, each device has 16 ports, each port handles a specific I/O message. Ports are mapped to the devices memory page, which is located outside of the main addressable memory.

All programs begin by executing the reset vector located at
0x100
. The content of the stacks are preserved between vectors,
but it is discouraged to use the stacks to pass data between vectors.
@on-reset ( -> ) ( set vector ) ;on-mouse .Mouse/vector DEO2 BRK @on-mouse ( -> ) ( read state ) .Mouse/state DEI ?&on-touch BRK &on-touch ( -> ) ( A mouse button is pressed ) BRK
For example, the address stored in the Mouse/vector ports points to a part of the program to be evaluated when the cursor is moved, or a button state has changed.

Uxntal Utilities
Here's a list of small self-hosted development tools:
- Uxnfor is a formatter that standardize the source code, this is the formatting style used across the Uxntal documentation.
- Uxnlin is a peephole optimizer that reveals potential optimizations in opcode sequences.
- Uxnbal is a program validator that warns when routines do not match their definitions.
- Uxndis is a disassembler that prints the opcodes in a rom file.
incoming left noodle drifblim catclock theme bifurcan yufo programming languages gly format ufx format ulz format proquints brainfuck uxn uxntal reference uxntal alphabet uxntal types bicycle beetbug arvelie about oscean computer