4. Uxn Opcodes

Wednesday 29th July 2026

Heavily commented to help when writing roms/tal, but it's just a bunch of consts really, plus a couple of convenience Int extensions:

package yorkshire.systems.kuxn.vm

/**
 *   bit 7 6 5 4 3 2 1 0
 *       k r 2 └───────┘  base opcode (0x00..0x1f)
 *       │ │ └─────────── short  (2)
 *       │ └───────────── return (r)
 *       └─────────────── keep   (k)
 *
 * See: https://wiki.xxiivv.com/site/uxntal_reference.html
 */

//Immediate opcodes, these opcodes have no modes:
const val BRK = 0x00 //Break: Ends the evaluation of the current vector. This opcode has no modes
const val JCI = 0x20 //Jump Conditional Immediate: Pops a byte from the working stack and if it is not zero, moves the PC to a relative address at a distance equal to the next short in memory, otherwise moves PC+2, it is written using the ?label format
const val JMI = 0x40 //Jump Immediate: Moves the PC to a relative address at a distance equal to the next short in memory. It is written using the !label format
const val JSI = 0x60 //Jump Stash Return Immediate: Pushes PC+2 to the return-stack and moves the PC to a relative address at a distance equal to the next short in memory. A plain label name resolves to a JSI operation
const val LIT = 0x80 //Literal: Pushes the next bytes in memory, and moves the PC forward by the same number of bytes (i.e: 1 byte if short mode is off or 2 bytes if it is on). The LIT opcode always has the keep mode active

//Standard opcodes:
const val INC = 0x01 //Increment: Increments the value at the top of the stack, by 1: a > a+1
const val POP = 0x02 //Pop: Removes the value at the top of the stack. POPk is the canonical NOP.
const val NIP = 0x03 //Nip: Removes the second value from the stack. This is practical to truncate a short into a byte: a b > b
const val SWP = 0x04 //Swap: Exchanges the first and second values at the top of the stack: a b > b a
const val ROT = 0x05 //Rotate: Rotates three values at the top of the stack, to the left, wrapping around: a b c > b c a
const val DUP = 0x06 //Duplicate: Duplicates the value at the top of the stack: a > a a
const val OVR = 0x07 //Over: Duplicates the second value at the top of the stack: a b > a b a
const val EQU = 0x08 //Equal: Pushes 01 to the stack if the two values at the top of the stack are equal, 00 otherwise: a b > a==b
const val NEQ = 0x09 //Not Equal: Pushes 01 to the stack if the two values at the top of the stack are not equal, 00 otherwise: a b > a!=b
const val GTH = 0x0a //Greater Than: Pushes 01 to the stack if the second value at the top of the stack is greater than the value at the top of the stack, 00 otherwise: a b > a>b
const val LTH = 0x0b //Lesser Than: Pushes 01 to the stack if the second value at the top of the stack is lesser than the value at the top of the stack, 00 otherwise: a b > a<b
const val JMP = 0x0c //Jump: Moves the PC by a relative distance equal to the signed byte on the top of the stack, or to an absolute address in short mode
const val JCN = 0x0d //Jump Conditional: If the byte preceding the address is not 00, moves the PC by a signed value equal to the byte on the top of the stack, or to an absolute address in short mode
const val JSR = 0x0e //Jump Stash Return: Pushes the PC to the return-stack and moves the PC by a signed value equal to the byte on the top of the stack, or to an absolute address in short mode
const val STH = 0x0f //Stash: Moves the value at the top of the stack to the return stack. Note that with the r-mode, the stacks are exchanged and the value is moved from the return stack to the working stack
const val LDZ = 0x10 //Load Zero-Page: Pushes the value at an address within the first 256 bytes of memory, to the top of the stack
const val STZ = 0x11 //Store Zero-Page: Writes a value to an address within the first 256 bytes of memory
const val LDR = 0x12 //Load Relative: Pushes a value at a relative address in relation to the PC, within a range between -128 and +127 bytes, to the top of the stack
const val STR = 0x13 //Store Relative: Writes a value to a relative address in relation to the PC, within a range between -128 and +127 bytes
const val LDA = 0x14 //Load Absolute: Pushes the value at an absolute address, to the top of the stack
const val STA = 0x15 //Store Absolute: Writes a value to an absolute address
const val DEI = 0x16 //Device Input: Device Input: Pushes a value from the device page, to the top of the stack. The target device might capture the reading to trigger an I/O event
const val DEO = 0x17 //Device Output: Writes a value to the device page. The target device might capture the writing to trigger an I/O event
const val ADD = 0x18 //Add: Pushes the sum of the two values at the top of the stack.: a b > a+b
const val SUB = 0x19 //Subtract: Pushes the difference of the first value minus the second, to the top of the stack: a b > a-b
const val MUL = 0x1a //Multiply: Pushes the product of the first and second values at the top of the stack: a b > a*b
const val DIV = 0x1b //Divide: Pushes the quotient of the first value over the second, to the top of the stack. A division by zero pushes zero on the stack. The rounding direction is toward zero: a b > a/b
const val AND = 0x1c //And: Pushes the result of the bitwise operation AND, to the top of the stack: a b > a&b
const val ORA = 0x1d //Or: Pushes the result of the bitwise operation OR, to the top of the stack: a b > a|b
const val EOR = 0x1e //Exclusive Or: Pushes the result of the bitwise operation XOR, to the top of the stack: a b > a^b
const val SFT = 0x1f //Shifts the bits of the second value of the stack to the left or right, depending on the control value at the top of the stack. The low nibble of the control value indicates how many bits to shift right, and the high nibble, how many bits to shift left. The rightward shift is done first

//Mode flags:
const val FLAG_SHORT = 0x20  //2
const val FLAG_RETURN = 0x40 //r
const val FLAG_KEEP = 0x80   //k

//Int extensions:
fun Int.opcode(): Int = this and 0x1f
fun Int.isShort(): Boolean = this and FLAG_SHORT != 0
fun Int.isReturn(): Boolean = this and FLAG_RETURN != 0
fun Int.isKeep(): Boolean = this and FLAG_KEEP != 0

fun Int.setShort(): Int = checkModable() or FLAG_SHORT
fun Int.setReturn(): Int = checkModable() or FLAG_RETURN
fun Int.setKeep(): Int = checkBaseOpcode() or FLAG_KEEP

//Keep can only be set on standard opcodes. LIT already has the keep bit
private fun Int.checkBaseOpcode(): Int {
    if (opcode() == 0) throw IllegalArgumentException(
        "Can't set keep mode on immediate/LIT opcode: 0x${toString(16).padStart(2, '0')}"
    )
    return this
}

//Short and Return can be set on any standard opcode, plus LIT
private fun Int.checkModable(): Int {
    if (opcode() == 0 && !isKeep()) throw IllegalArgumentException(
        "Can't set short/return mode on immediate opcode: 0x${toString(16).padStart(2, '0')}"
    )
    return this
}