uxn
uxn is a virtual machine based on Forth and assembly, built to be a slim wrapper to run on system.
The Uxn ecosystem is a personal computing playground, created to host small tools and games, programmable in its own unique assembly language.[1]
Varvara[9] is a virtual computer that uxn is the core of.
Basics
CPU
The uxn CPU is capable of performing 32 different instructions with three different mode flags. These instructions and flags can be encoded in a single 8-bit word and operate on the stack.
uxn reads one byte at a time from the main memory, with a program counter of a 16-bit word indicating the address to be read next.
Memory
There is 64kb of main memory, 256b of I/O memory, 256b of a working stack, and 256b for a return stack.
The main memory can be accessed using 16-bit shorts, except for the first 256b (the "zero page") which can be accessed using an 8-bit word. The main memory stores the program being executed as well as data starting at the 257th byte (hexadecimal address 0x0100
). Accessing the stacks is handled by uxn and can't be accessed out of order.
uxntal[11]
uxntal is the assembly language utilized by uxn machines.
Getting Started[4]
uxn can be run locally through pre-compiled binaries or by building from source via their code repository[7]. This will give three executables:
- uxnemu, the emulator
- uxnasm: the assembler
- uxncli: a console emulator
It can also be used online using the Javascript port[7] from metasyn.
Syntax
Instructions[11]
An instruction is one byte and in some instances takes in one or more arguments. For instance, to push one byte as a hexadecimal literal onto the stack, we can use the LIT
keyword, which is an alias for opcode 0x80
, followed by the byte we want to push. If we use the LIT2
keyword, or opcode 0xa0
, we would follow this instruction with a two bytes (a "short"). All instructions that operate on shorts have the sixth bit flipped from 0 to 1. In our LIT
example, LIT
== 0x80
== 0b10000000
and LIT2
== 0xa0
== 0b10100000
.
Jumps
JCN
is a conditional jump. It takes a signed relative address and a value off the stack; if the value is not 0x00
, it jumps forward or backward the amount specified in the signed relative address; else, it will continue to the next instruction. It acts like a strict if/else conditional, if you are more used to that world, but is otherwise similar to the 6502 assembly BNE
command.
Addresses
Each of these are an instruction that will push an address on the stack. The table describe what will be pushed on the stack.
Symbol | Size | Description |
---|---|---|
.label |
byte | Literal zero page address |
;label |
short | Literal main memory address |
,label |
byte | Literal relative address in main memory |
:label |
short | Raw address in main memory |
Note that certain address types require specific instructions; for instance, absolute calls use LDA
, the zero page needs LDZ
, and relative calls use LDR
.
Macros
A macro is defined by a %
, the macro's name, and the instructions to execute when called enclosed in curly braces.
( macros to square the top number of the stack )
%SQUARE { DUP MUL } ( num -- product )
%SQUARE2 { DUP2 MUL2 } ( num -- product )
#0a SQUARE ( 0x64 )
#00a6 SQUARE2 ( 0x6ba4 )
Keep in mind that through clever usage of the stack, we may be able to make macros out of more than we think we can.
Labels/Variables
When a label is assigned, it attaches to the current address. There are parent labels (@
) and child labels (&
), which are assigned immediately after the parent label. Child labels are set using relative addresses ($
). For instance, |1000 @Data [ &health $1 gold $2 ]
would contain two pieces of data: &health
which is a single byte found at 0x1000
, and &gold
, which is a short found at 0x1001
.
( assigns "System" to 0x00 (00 on the zero-page) )
( and child labels at the following relative addresses )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
( assigns the label "main-program" to 0x0100 )
|0100 @main-program
( jumps to label "subroutine" )
,subroutine JMP
BRK
( assigns "subroutine" to this address )
@subroutine
#01 #00 EQU ( some kind of conditional check )
,success JCN
,end JMP
( assigns child label "success" to this address )
&success
( things happen here )
( assigns child label "end" to this address )
&end
BRK
Labels can also be used or thought of as variables, setting a place in memory to store a given piece of data. You can also declare them without setting an initial value, using relative pads.
( A variable initialization )
@pixel-x 0008
@pixel-y 0008
( A variable declaration with a length of a short )
@pixel-x $2
@pixel-y $2
( A variable declaration with child labels )
@pixel [ &x $2 &y $2 ]
( A zero page declaration )
|0000
@pixel [ &x $2 &y $2 ]
Devices
Values found at a given device address (like those found in the above "Labels" example with Console
) can be manipulated like any other value. To put the current values on the stack, you can use DEO
/DEO2
, and to put new values into the device, you can use DEI
/DEI2
(I/O corresponding to input/output).
The "vector" of a given device is the address where uxn will jump when an event on that device occurs. Think "if event on [device], go to [device]'s vector". For example, #1000 .Controller/vector DEO2
says when an event occurs on Controller
, go to 0x1000
.
References
- https://wiki.xxiivv.com/site/uxn.html
- https://www.youtube.com/watch?v=LrNuq_JgaOA
- https://github.com/hundredrabbits/awesome-uxn
- Compudanza's uxn tutorial
- https://llllllll.co/t/uxn-virtual-computer/46103
- https://100r.co/site/uxn.html
- https://git.sr.ht/~rabbits/uxn
- Javascript port of uxn
- https://wiki.xxiivv.com/site/varvara.html
- Syntax highlighting for Vim
- uxntal syntax and opcode reference
- Common UXN Macros
Incoming Links
Last modified: 202401040446