*ALWAYS UNDER CONSTRUCTION*

Uxntal (EN)

created:
updated:


Notice

This article was translated by AI.
See the original article here (in Korean).

A programming language for the Uxn virtual machine.

Uxntal is the language of Uxn.

Uxntal is an Assembly language that compiles into ROM to run on the Uxn CPU.

Note

Like any other programming language, Uxntal has an interconnected structure, making it difficult to learn sequentially. Here, we have organized the concepts from most independent to least.

Read Only Memory (ROM)

ROM is assembled from Uxntal. Despite being called ROM (Read Only Memory), once it is loaded into Uxn, it can be modified using STA and similar opcodes.

Most of Uxntal’s data, including opcodes , is converted directly into bytes.

Uxn loads ROM starting at ROM 0x0100 by default, and writing begins at 0x0100 . However, you can start writing at a specific address if desired.

Warning

Zero-page
Addresses below 0x0100 are called zero-page, and they are used by the Uxn/Varvara system. If you try to write there, you will get an error, and the actual ROM also starts writing from 0x0100 . Therefore, you typically only attach labels to those addresses.

|00 #00
( ASSEMBLY ERROR
Writing zero-page: #00 in @on-reset, test.tal:1.
)

Random Access Memory (RAM)

Uxn provides RAM consisting of two 256-byte stacks.
RAM consists of the WST (Working STack), which is the default workspace, and the RST (Return STack), which can be used as additional storage for operations.

Memory:

  • WST (Working Stack)
  • RST (Return Stack)

Data Structure

Data is categorized into byte (integers in the range 0~255 (28-1)) and short (integers in the range 0~65535 (216-1)), but the only difference is the amount of space they occupy on the stack. Byte data can be inserted onto the stack as two-digit hex (0x00 ), and short data as four-digit hex (0x0000 ), but there is no distinction within the stack itself.

#00 #ab #1234
( RESULT
WST: 00 ab 12 34
)

Caution

This may be confusing, but #00 by itself is the code that inserts the 00 byte onto the top of WST.
Literal(LIT)

Data:

  • Byte

  • Short = Byte × 2

Comments

Strings wrapped in ( and ) are treated as comments. (Spaces are included.)  

( This is comment. )

( 
    This is comment too )

( ( You can also nest ) )

Runes

Uxntal has several special characters that provide various features.

Labels

Labels serve to assign a name to a specific ROM address .

Labels are divided into scopes and sublabels. A scope is created with a string starting with @. A sublabel is created with a string starting with &, or in the form @scope/name. As you can see, sublabels are nested within a scope.

In other words, the following two code snippets produce the same result:

@banana &peel
@banana @banana/peel

Note

Warning

Uxntal treats hex as regular data, so a label cannot consist solely of characters a-f.

#00 bed INC @bed INC
( ASSEMBLY ERROR
Hexadecimal invalid: bed in @on-reset, hello.tal:2.
)

@on-reset

Labels can be used for the following purposes.

Function is literally a function — it is executed at a certain position and then returns to that position.

@func ( -- )
  ( ... )
  JMP2r

Constant — since uxntal has no variable syntax, data is placed in ROM and its address is referenced and used as a variable.

@text
    "Hello 20 "World 0a

Vector is a concept similar to an event listener.
It is almost exclusively used in Varvara, so we will cover it in more detail when we discuss Varvara later.

Note

In fact, at 0x0100 — the starting point of the Uxntal ROM — a vector called @on-reset is assigned by default.

Padding

You can start writing at a specific ROM memory address using |. However, since Uxn will execute from ROM ROM 0x0100 , you must write your code at 0x0100 if you want it to run. Also, you cannot write to memory that has already been passed, so you must write in ascending order. (Attaching labels alone is possible.)

|100 #00 INC routine INC
|200 @routine
( RESULT
WST: 01
RST: 01 06
)
|100 #00 INC r-one INC
|200 @r-one r-two
|150 @r-two INC
( ASSEMBLY ERROR
Writing rewind: INC in @r-two, test.tal:3.
)

You can move relative to the current ROM address using $.

By combining it with labels, you can create a struct at a specific position in ROM .

|d0 @player &x $2 &y $2 &health $1
(
The player struct has x and y as shorts, and health as a byte.
You can reference them in code as player/x, etc.
)

Note

As a result of padding, the ROM file looks like this:

Details
( test.tal )
|100 "A
|150 "B
$4f  "C
$ ./uxnasm test.tal test.rom
$ xxd test.rom
00000000: 4100 0000 0000 0000 0000 0000 0000 0000  A...............
00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000050: 4200 0000 0000 0000 0000 0000 0000 0000  B...............
00000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000000a0: 43                                       C

Number, ASCII Runes

Runes for inserting data onto the stack

  • You can insert a hex value onto the stack using #. Literal (LIT)

  • You can insert each character of an ASCII string as a byte into ROM using ".

    Warning

    This is not inserting onto the stack during program execution. When the assembler encounters ", it converts the following string into ASCII hex and inserts it directly into ROM .

    ( test.rom )
    "banana
    
    $ ./uxnasm test.tal test.rom
    $ xxd hello.rom
    00000000: 6261 6e61 6e61                           banana
    

Addressing

Uxntal has 6 runes for handling ROM addresses . Each rune serves to retrieve a label’s address .

Literal- fetches the address at runtime and inserts it onto the stack .

  • Literal Relative(,) inserts the distance from the current PC to the label’s address onto the stack.
  • Literal Zero Page(.) inserts the address (byte) of a zero-page label onto the stack.
  • Literal Absolute(;) inserts the address (short) of a label onto the stack.

Note

( test.rom )
;label @label
Result & Analyze
$ ./uxnasm test.tal test.rom
$ xxd test.rom
00000000: a001 03                                  ...
          ^^~~~~~
    (1)---+     +--(2)
  • (1) 0x0a: The opcode for LIT2 , which inserts the following short onto the stack
  • (2) 0x0103: The ROM address of @label

Raw- fetches the address at assembly time and inserts it into ROM .

  • Raw Relative(_) inserts the distance from the current PC to the label’s address into ROM.
  • Raw Zero Page(-) inserts the address (byte) of a zero-page label into ROM.
  • Raw Absolute(=) inserts the address (short) of a label into ROM.

Note

( test.rom )
=label @label
Result & Analyze
$ ./uxnasm test.tal test.rom
$ xxd test.rom
00000000: 0102                                     ..
          ^^^^
    (1)---+ 
  • (1) 0x0102: The ROM address of @label

As you can see from the result, Raw Labeling inserts the label’s ROM address at assembly time, so the label’s address (0x0102) is one byte before Literal Labeling (0x0103 ).

Wrappings

  • ( , ) parentheses are comments.

  • [, ] square brackets are themselves ignored, unlike ( , ) where the content inside is ignored. Square brackets are used as notation for grouping opcodes for readability.

  • {, } curly braces are used as anonymous, i.e., nameless routines.

Macro

You can create macros with %name { }.

%modulo ( num denum -- res ) {
	DIVk MUL SUB }

@routine ( -- c* )
	#18 #03 modulo JMP2r

Opcodes

Uxntal has no operator precedence; operations are simply executed on the stack in the order they are programmed.
The PC (Program Counter) is the ROM address of the next opcode, and it increases by 21 after each opcode execution.2

#0f #06 #04 ADD MUL
( RESULT
WST: 96
)
  1. 0x06 + 0x04 = 0x0a (WST: 0f 0a)
  2. 0x0f × 0x0a = 0x96 (WST: 96)

Notation

Uxntal follows the notation of the Forth programming language.

Each item before -- represents the stack state before execution, and each item after represents the stack state after execution.

#12 #34 ADD
( RESULT
WST: 46
)

ADD ( a b -- c )

For operations targeting shorts , the item is annotated with * after it.

#1234 #abcd ADD2
( RESULT
WST: be 01
)

ADD2 ( a* b* -- c* )

For operations that affect the RST , the RST state is annotated after ..

#12 STH
( RESULT
WST: 
RST: 12
)

STH ( a -- . a)
( BEFORE RST: 12 )
STHr
( RESULT
WST: 12
)

STHr ( . a -- a )

Note

Stash

Stash(STH) consumes the top byte of the stack(WST) and inserts it into the RST.
STHr consumes the top byte of the RST and inserts it into the WST.

Mode

Uxntal has 3 modes.

ModeDescription
Short mode 2Performs operation on shorts
Keep mode kPreserves the operand values
Return mode rPerforms operation on the RST

Short mode consumes shorts (two bytes) instead of bytes for operations.
For jump-related opcodes3, it uses absolute positions.

#0123 #abcd ADD2
( RESULT
WST: ac f0
)

ADD2 ( a* b* -- c* )

Keep mode preserves the values used in the operation instead of consuming them.

#01 #23 ADDk
( RESULT
WST: 01 23 24
)

ADD2 ( a b -- a b c )

Return mode performs operations on the RST instead of the WST.

( BEFORE RST: 01 23 )
ADDr
( RESULT
RST: 24
)

ADDr ( . a b -- . c )

Note

Stash

Stash(STH) consumes the top byte of the stack and inserts it into the RST.
STHr consumes the top byte of the RST and inserts it into the WST.

These modes can be combined. Each opcode has a total of 8 variants.4

ADD    ( a b -- c )
ADD2   ( a* b* -- c* )
ADDk   ( a b -- a b c )
ADDr   ( . a b -- . c )
ADD2k  ( a* b* -- a* b* c* )
ADDkr  ( . a b -- . a b c )
ADD2r  ( . a* b* -- . c* )
ADD2kr ( . a* b* -- . a* b* c* )

Therefore, each of Uxntal’s opcodes occupies 2 bytes.

OPCODE
2kropcode id
00000000

Immediate Opcodes

Immediate Opcodes do not consume data from the top of the stack; instead, they immediately fetch and use the value written in ROM after the opcode.
Since they only work in a predetermined way, immediate opcodes do not have modes.

NameOpcodeSyntax
LiteralLIT#
Jump ImmediateJMI!routine
Jump Conditional ImmediateJCI?routine
Jump Stash Return ImmediateJSIroutine

Literal(LIT) uses # to insert the next data onto the stack.

#12 #abcd
( RESULT
WST: 12 ab cd
)

Note

Literal(LIT)

Literal(LIT) can actually be used beyond just #; it is an opcode that inserts the next data from ROM onto the stack as-is.

( test.tal )
LIT "A
( RESULT
WST: 41
)
$ ./uxnasm test.tal test.rom
$ xxd test.rom
00000000: 8041                                     .A
          ^^~~
    (1)---+  +--(2)
  • (1) 0x80: The opcode for LIT, which inserts the next byte (0x41) from ROM onto the stack.
  • (2) 0x41: The ASCII value of A

LIT2 of course consumes two bytes from ROM .

Jump Immediate(JMI) jumps to a routine by writing its name after !.

#00 !routine INC
@routine INC INC
( RESULT
WST: 02
)

Without the jump opcode, the result would have been 03, but since it jumps to the @routine routine (address), the result is 02.

Jump Conditional Immediate(JCI) conditionally jumps by writing the routine’s name after ?.
When executed, it takes the top byte from the stack and jumps to the routine based on its value. In other words, a conditional jump consumes a byte.

When ? is followed by a routine, it consumes the top byte of the stack , and jumps to the routine if it is not 0x00. If it is 0x00, execution continues as normal. (The official documentation also describes this as incrementing the PC by 2.)

#00 ?routine INC @routine INC INC
( RESULT
WST: 03
)

Top byte is 00, so no jump

#01 ?routine INC @routine INC INC
( RESULT
WST: 02
)

Top byte is 01, so jump occurs

If a {, } block follows ?, the opposite applies — the inside is executed if the top byte of the stack is 00.

#10 #00 ?{ INC }
( RESULT
WST: 11
)
#10 #01 ?{ INC }
( RESULT
WST: 10
)

This is one of the confusing parts of Uxntal, but it helps to think of } as the routine definition and { as the routine label.

#10 #00 ?{ INC }

->

#10 #00 ?end INC @end

Jump Stash Return Immediate(JSI) simply executes a routine at the current position.
The difference from JMI is that it pushes the current PC+2 value onto the RST and then jumps to the routine’s address.

1#00
2routine 
3INC         
4@routine    
5( RESULT    
6WST: 00     
7RST: 01 05  
8     ^^^^^ PC of Line 3
9)

Note

Jump Stash Return

There is another opcode called Jump Stash Return(JSR), which dynamically jumps to the address at the top of the stack while simultaneously pushing the current PC+2 value onto the RST.
Therefore, the following code produces exactly the same result as the JSI example above.

1#00
2;routine JSR
3INC         
4@routine    
5( RESULT    
6WST: 00     
7RST: 01 05  
8     ^^^^^ PC of Line 3
9)

The reason JSR exists despite JSI is that, unlike JSI which jumps to a fixed routine, JSR can jump to a dynamic routine/address.

Literal Absolute(;)

Since it saves the ROM address (PC) of the current position, it is used like a function — execute a routine and return to the original position. And since it can be made to return, it is also called a subroutine when used at any point internally.

#07 #04 modulo BRK
( If we continue as-is, we would execute modulo's opcodes, so we break before that. )

( Forth notation is mainly used to annotate a routine's side effects within code. )
@modulo ( a mod -- res ) 
  DIVk MUL SUB JMP2r
( RESULT
WST: 03
)

Note

Break

Break(BRK) terminates the operation of the current vector. This opcode has no modes.

Further More…

This article has only covered the basic syntax and ecosystem of Uxntal. Please refer to the various opcodes[↗] on your own.

References


  1. The reason it increases by 2 is that each opcode occupies 2 bytes↩︎

  2. As an exception, immediate opcodes advance by the length of the data stored immediately after the opcode. ↩︎

  3. JMP, JCN, JSR ↩︎

  4. Immediate opcodes do not have modes. ↩︎

Webmentions[0]

    Q. Why isn't my webmention not appearing?

    A. I deploy this site not so often and this site is static site. So don't expect your webmention to appear instantly. Thanks for the webmention and sorry for the inconvenience.
    last deployed: see footer