CREATOR API (CAPI)

CAPI allows instruction definitions to interact with custom CREATOR functions.

[!TIP] CAPI is a global variable, so you can access it from your browser's developer console if you're using the web application.

Memory

Interaction with CREATOR's memory.

CAPI.MEM.write

CAPI.MEM.write(
  address: bigint,
  bytes: number,
  value: bigint,
  reg_name?: string,
  hint?: string,
  noSegFault: boolean = true,
): void { }

Writes a specific value to the specified address and number of bytes. You can provide extra information about which register used to hold the value (reg_name), or any hint about what that value might be (hint). To enable checking if the address is in a writable segment, set noSegFault to false.

E.g.:

CAPI.MEM.write(registers.t0, 1, registers.t0, "t0");

CAPI.MEM.read

CAPI.MEM.read(
  address: bigint,
  bytes: number,
  reg_name: string,
  noSegFault: boolean = true,
): bigint { }

Reads a specific number of bytes in the specified address and returns them. You can provide extra information about which register will hold the value (reg_name). To enable checking if the address is in a readable segment, set noSegFault to false.

E.g.:

registers.t0 = CAPI.MEM.read(registers.t1, 1, "t0");

CAPI.MEM.addHint

CAPI.MEM.addHint(address: bigint, hint: string, sizeInBits?: number): boolean { }

Adds a hint (description of what the address holds) for the specified memory address. If a hint already exists at the specified address, it replaces it. You can optionally specify the size of the stored type in bits (sizeInBits). Returns true if the hint was successfully added, else false.

E.g.:

CAPI.MEM.addHint(registers.f0, "float64", 64);

System calls

CREATOR's system calls.

CAPI.SYSCALL.exit

CAPI.SYSCALL.exit(): void { }

Terminates the execution of the program.

E.g.:

CAPI.SYSCALL.exit();

CAPI.SYSCALL.print

CAPI.SYSCALL.print(
  value: number | bigint,
  type: "int32" | "float" | "double" | "char" | "string",
): void { }

Prints the specified value of the specified type to the console.

Supported types are:

  • "int32": signed 32-bit integer
  • "float" / "double": JS's Number
  • "char": UTF-16 character value
  • "string": address of a null-terminated array of 1-byte chars

E.g.:

CAPI.SYSCALL.print(registers.a0, "char");

CAPI.SYSCALL.read

CAPI.SYSCALL.read(
  dest_reg_info: string,
  type: "int32" | "float" | "double" | "char" | "string",
  aux_info?: string,
): void { }

Reads the specified value type from the console and stores it in the specified register by name (dest_reg_info).

Supported types are:

  • "int32": signed 32-bit integer
  • "float" / "double": JS's Number
  • "char": UTF-16 character value
  • "string": aux_info is the name of the register that holds the address of a null-terminated array of 1-byte chars

E.g.:

CAPI.SYSCALL.read("a0", "char");
CAPI.SYSCALL.read("a0", "string", "a1");

CAPI.SYSCALL.get_clk_cycles

CAPI.SYSCALL.get_clk_cycles(): number { }

Returns the number of clock cycles that have passed since the program started.

E.g.:

CAPI.SYSCALL.get_clk_cycles();

Validation

CAPI.SYSCALL.raise

CAPI.SYSCALL.raise(msg: string): never { }

Raises an error with a specific msg.

E.g.:

CAPI.SYSCALL.raise("Help!");

CAPI.SYSCALL.isOverflow

CAPI.SYSCALL.isOverflow(op1: bigint, op2: bigint, res_u: bigint): boolean { }

Checks if the result res_u of operating two operands op1 and op2 caused an overflow.

E.g.:

CAPI.SYSCALL.isOverflow(registers.t0, registers.t1, registers.t0 + registers.t1);

Stack

These functions are used for the stack tracker and sentinel modules, and are a way to tell CREATOR when a new function frame begins and ends. They should be included in the instructions that jump to, o return from, a routine, as is the case of RISC-V's jal and jr instructions.

CAPI.STACK.beginFrame

CAPI.STACK.beginFrame(addr?: bigint): void { }

Marks the beginning of a function frame at address. If not specified, it takes the current (real) value of the program counter.

E.g.:

registers.pc = ...
CAPI.SYSCALL.beginFrame();

CAPI.STACK.endFrame

CAPI.STACK.endFrame(): void { }

Ends the current stack frame.

E.g.:

registers.pc = ...
CAPI.SYSCALL.endFrame();

Floating point

CAPI.FP.split_double

CAPI.FP.split_double(reg: bigint, index: 0 | 1): string { }

Given a double precision IEEE 754 value reg, gets the 32-bits most significant (index=1) bits or the least significant bits (index=0). It returns it as a string of bits.

E.g.:

const foo = CAPI.FP.split_double(registers.t0, 1);

CAPI.FP.uint2float32

CAPI.FP.uint2float32(value: number): number { }

Transforms the unsigned integer value to a single precision IEEE754 JS Number.

E.g.:

CAPI.FP.uint2float32(5);

CAPI.FP.float322uint

CAPI.FP.float322uint(value: number): bigint { }

Transforms the single precision floating point (JS Number) value into an unsigned integer.

E.g.:

CAPI.FP.uint2float32(5.0);

CAPI.FP.int2uint

CAPI.FP.int2uint(
  value: number | bigint,
  bits: number = 64,
): bigint { }

Transforms a signed integer (JS Number) value into an unsigned integer of bits number of bits.

E.g.:

CAPI.FP.int2uint(-5);

CAPI.FP.uint2int

CAPI.FP.uint2int(value: number | bigint): bigint { }

Transforms an unsigned integer value into a signed integer (JS Number) of bits number of bits.

E.g.:

CAPI.FP.uint2int(5);

CAPI.FP.uint2float64

CAPI.FP.uint2float64(value0: number | bigint, value1?: number): number { }

Transforms an unsigned integer value0 into a signed integer to a 64-bit float (JS Number). Supports two calling conventions:

  1. Single value0 argument: converts a 64-bit integer directly
  2. Two 32-bit arguments: converts low (value0) and high (value1) 32-bit parts.

E.g.:

CAPI.FP.uint2float64(5);

CAPI.FP.float642uint

CAPI.FP.float642uint(value: number): [number, number] { }

Transforms the double precision floating point (JS Number) value into an unsigned integer.

E.g.:

CAPI.FP.float642uint(5.0);

CAPI.FP.check_ieee

CAPI.FP.check_ieee(sign: string, exponent: string, mantissa: string): number { }

Check the type of number is in IEEE 754 format. Returns a 10-bit mask where the position of the set bit indicates the type of the IEEE 754 number:

  • 0 -> -inf
  • 1 -> -normalized number
  • 2 -> -non-normalized number
  • 3 -> -0
  • 4 -> +0
  • 5 -> +non-normalized number
  • 6 -> +normalized number
  • 7 -> +inf
  • 8 -> signaling NaN
  • 9 -> quiet NaN

E.g.:

CAPI.FP.check_iee(parseInt(a[0]), parseInt(a.slice(1,9), 2), parseInt(a.slice(10), 2));

CAPI.FP.float2bin

CAPI.FP.float2bin(f: number): string { }

Transforms the single precision floating point JS Number f into a binary string.

E.g.:

CAPI.FP.float2bin(5.0);

Registers

CAPI.REG.read

CAPI.REG.read(name: string): bigint { }

Returns the value stored in register name.

E.g.:

const foo = CAPI.REG.read("t0");

CAPI.REG.write

CAPI.REG.write(value: bigint, name: string): void { }

Stores value in register name.

E.g.:

CAPI.REG.write(0x69n, "t0");

Architecture

CAPI.ARCH exposes the interface of the currently loaded architecture plugin.

The supported plugins are riscv, mips and z80.

RISC-V

The RISC-V architecture plugin provides utilities for handling floating point operations and immediate value generation.

generateLoadImmediate

CAPI.ARCH.generateLoadImmediate(val: bigint | number, destReg: string): string { }

Generates a sequence of RISC-V instructions to load an immediate value into a register. Returns a semicolon-separated string of instructions that can load values of any size into the destination register.

E.g.:

const instructions = CAPI.ARCH.generateLoadImmediate(0x12345678n, "t0");
// Returns: "lui t0, 0x12345;addiw t0, t0, 1656"

toJSNumberD

CAPI.ARCH.toJSNumberD(bigIntValue: bigint): [number, string] { }

Converts a 64-bit register value to a JavaScript number when the D (double-precision floating point) extension is enabled. Returns a tuple containing the numeric value and a type string indicating the representation.

The function handles:

  • Canonical 64-bit NaN (0x7ff8000000000000n) → [NaN, "NaN64"]
  • NaN-boxed 32-bit floats (upper 32 bits all 1's) → [value, "NaNBfloat32_64"]
  • Valid 64-bit doubles → [value, "float64"]
  • Zero → [0, "float32"]

E.g.:

let value, type;
[value, type] = CAPI.ARCH.toJSNumberD(registers.ft0);

toJSNumberS

CAPI.ARCH.toJSNumberS(bigIntValue: bigint): [number, string] { }

Converts a 32-bit register value to a JavaScript number when only the S (single-precision floating point) extension is enabled. Returns a tuple containing the numeric value and a type string.

The function handles:

  • Canonical 32-bit NaN (0x7fc00000n) → [NaN, "NaN32"]
  • Valid 32-bit floats → [value, "float32"]
  • Zero → [0, "float32"]

E.g.:

let value, type;
[value, type] = CAPI.ARCH.toJSNumberS(registers.ft0);

toBigInt

CAPI.ARCH.toBigInt(number: number, type: string): bigint { }

Converts a JavaScript number to a bigint representation suitable for storing in a RISC-V register. The type parameter specifies the format.

Supported types:

  • "float32": Single-precision float
  • "float64": Double-precision float
  • "NaNBfloat32_64": NaN-boxed single-precision (upper 32 bits all 1's)
  • "NaN64": Canonical 64-bit NaN
  • "NaN32": Canonical 32-bit NaN

E.g.:

registers.ft0 = CAPI.ARCH.toBigInt(3.14, "float32");

NaNBox

CAPI.ARCH.NaNBox(bigIntValue: bigint): bigint { }

NaN-boxes a 32-bit floating point value for use in 64-bit floating point registers. Sets the upper 32 bits to all 1's as per the RISC-V specification (section 21.2).

E.g.:

const nanBoxed = CAPI.ARCH.NaNBox(registers.ft0);

MIPS

The MIPS architecture plugin provides utilities for handling double-precision floating point operations across register pairs.

validateEvenRegister

CAPI.ARCH.validateEvenRegister(regName: string): void { }

Validates that a register number is even. Required for double-precision operations in MIPS, which use register pairs. Throws an error if the register is not even.

E.g.:

CAPI.ARCH.validateEvenRegister("f0");  // OK
CAPI.ARCH.validateEvenRegister("f1");  // Throws error

readDouble

CAPI.ARCH.readDouble(regName: string): number { }

Reads a double-precision floating point value from a MIPS register pair. The regName must be even (e.g., "f0", "f2"). The function reads from the specified register and the next sequential register to reconstruct a 64-bit double.

E.g.:

const value = CAPI.ARCH.readDouble("f0");  // Reads f0 and f1

writeDouble

CAPI.ARCH.writeDouble(value: number, regName: string): void { }

Writes a double-precision floating point value to a MIPS register pair. The regName must be even. The function splits the value across the specified register and the next sequential register.

E.g.:

CAPI.ARCH.writeDouble(3.14159, "f0");  // Writes to f0 and f1

readDoublePair

CAPI.ARCH.readDoublePair(reg1Name: string, reg2Name: string): number[] { }

Reads two double-precision values from register pairs and returns them as an array. Both register names must be even.

E.g.:

const [val1, val2] = CAPI.ARCH.readDoublePair("f0", "f2");

writeDoublePair

CAPI.ARCH.writeDoublePair(value1: number, value2: number, reg1Name: string, reg2Name: string): void { }

Writes two double-precision values to register pairs. Both register names must be even.

E.g.:

CAPI.ARCH.writeDoublePair(1.5, 2.5, "f0", "f2");

binaryDoubleOperation

CAPI.ARCH.binaryDoubleOperation(
  destReg: string,
  src1Reg: string,
  src2Reg: string,
  operation: (val1: number, val2: number) => number
): void { }

Performs a binary operation on two double-precision values. Reads from source register pairs, applies the operation function, and writes the result to the destination register pair. All register names must be even.

E.g.:

CAPI.ARCH.binaryDoubleOperation("f0", "f2", "f4", (a, b) => a + b);

unaryDoubleOperation

CAPI.ARCH.unaryDoubleOperation(
  destReg: string,
  srcReg: string,
  operation: (val: number) => number
): void { }

Performs a unary operation on a double-precision value. Reads from the source register pair, applies the operation function, and writes the result to the destination register pair. Both register names must be even.

E.g.:

CAPI.ARCH.unaryDoubleOperation("f0", "f2", (x) => Math.sqrt(x));

Z80

The Z80 architecture plugin provides utilities for flag calculations, keyboard handling, and I/O operations.

Flag Constants

The following flag bit masks are available:

CAPI.ARCH.S_FLAG   // 0x80 - Sign flag
CAPI.ARCH.Z_FLAG   // 0x40 - Zero flag
CAPI.ARCH.H_FLAG   // 0x10 - Half-carry flag
CAPI.ARCH.PV_FLAG  // 0x04 - Parity/Overflow flag
CAPI.ARCH.N_FLAG   // 0x02 - Add/Subtract flag
CAPI.ARCH.C_FLAG   // 0x01 - Carry flag

State Variables

The following variables track the Z80 system state:

CAPI.ARCH.borderColor     // Border color (0-7) for ZX Spectrum
CAPI.ARCH.interruptMode   // Current interrupt mode (0, 1, or 2)
CAPI.ARCH.interruptPin    // Interrupt pin state (0 = low, 1 = high)
CAPI.ARCH.timerCounter    // Timer counter value (bigint)
CAPI.ARCH.keyState        // Object mapping event.code strings to pressed state (boolean)
CAPI.ARCH.keyMap          // Keyboard matrix mapping port high bytes to key codes

E.g.:

// Set interrupt mode 2
CAPI.ARCH.interruptMode = 2;

// Check if a key is pressed
if (CAPI.ARCH.keyState["KeyA"]) {
  // Key A is pressed
}

// Set border color to red
CAPI.ARCH.borderColor = 2n;

calculateFlags_INC

CAPI.ARCH.calculateFlags_INC(oldValue: bigint, initialF: bigint): bigint { }

Calculates flags for an 8-bit INC (increment) operation. Returns the new F register value with appropriate flags set. The C flag is preserved from initialF.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_INC(registers.A, registers.F);

calculateFlags_DEC

CAPI.ARCH.calculateFlags_DEC(oldValue: bigint, initialF: bigint): bigint { }

Calculates flags for an 8-bit DEC (decrement) operation. Returns the new F register value. The C flag is preserved, and N flag is always set.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_DEC(registers.A, registers.F);

calculateFlags_ADD

CAPI.ARCH.calculateFlags_ADD(val1: bigint, val2: bigint): bigint { }

Calculates flags for 8-bit addition (ADD). Returns the new F register value with all flags computed based on the operation.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_ADD(registers.A, registers.B);

calculateFlags_ADC

CAPI.ARCH.calculateFlags_ADC(val1: bigint, val2: bigint, initialF: bigint): bigint { }

Calculates flags for 8-bit addition with carry (ADC). The carry bit is extracted from initialF.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_ADC(registers.A, registers.B, registers.F);

calculateFlags_SUB

CAPI.ARCH.calculateFlags_SUB(val1: bigint, val2: bigint): bigint { }

Calculates flags for 8-bit subtraction (SUB). The N flag is always set for subtraction operations.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_SUB(registers.A, registers.B);

calculateFlags_SBC

CAPI.ARCH.calculateFlags_SBC(val1: bigint, val2: bigint, initialF: bigint): bigint { }

Calculates flags for 8-bit subtraction with carry (SBC). The carry bit is extracted from initialF.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_SBC(registers.A, registers.B, registers.F);

calculateFlags_LOGICAL

CAPI.ARCH.calculateFlags_LOGICAL(result: bigint, setH: number): bigint { }

Calculates flags for logical operations (AND, OR, XOR). Set setH to 1 for AND operations (sets H flag), or 0 for OR/XOR operations (resets H flag). N and C flags are always reset.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_LOGICAL(registers.A & registers.B, 1);

calculateFlags_CP

CAPI.ARCH.calculateFlags_CP(val1: bigint, val2: bigint): bigint { }

Calculates flags for the compare (CP) operation. Performs a subtraction for flag calculation purposes without storing the result.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_CP(registers.A, registers.B);

calculateFlags_ADD16

CAPI.ARCH.calculateFlags_ADD16(val1: bigint, val2: bigint, initialF: bigint): bigint { }

Calculates flags for 16-bit addition (ADD HL, rr). The S, Z, and P/V flags are preserved from initialF. N flag is reset.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_ADD16(registers.HL, registers.BC, registers.F);

calculateFlags_SBC16

CAPI.ARCH.calculateFlags_SBC16(val1: bigint, val2: bigint, initialF: bigint): bigint { }

Calculates flags for 16-bit subtraction with carry (SBC HL, rr). The carry bit is extracted from initialF.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_SBC16(registers.HL, registers.BC, registers.F);

calculateFlags_BIT

CAPI.ARCH.calculateFlags_BIT(value: bigint, bit: number, initialF: bigint): bigint { }

Calculates flags for the BIT instruction, which tests a specific bit (0-7) in a value. The C flag is preserved from initialF, and H flag is always set. N flag is always reset.

E.g.:

registers.F = CAPI.ARCH.calculateFlags_BIT(registers.A, 7, registers.F);

calculateFlags_ROTATE

CAPI.ARCH.calculateFlags_ROTATE(result: bigint, carry: bigint): bigint { }

Calculates flags for rotate and shift instructions (RLC, RRC, SLA, etc.). The carry parameter should be 0n or 1n representing the bit that was shifted out. H and N flags are always reset.

E.g.:

const carry = registers.A >> 7n;
const result = ((registers.A << 1n) | carry) & 0xffn;
registers.F = CAPI.ARCH.calculateFlags_ROTATE(result, carry);

calculateFlags_SRL

CAPI.ARCH.calculateFlags_SRL(result: bigint, carry: bigint): bigint { }

Calculates flags for the SRL (Shift Right Logical) instruction. The carry parameter is the original bit 0 that was shifted out. S, H, and N flags are always reset.

E.g.:

const carry = registers.A & 1n;
const result = registers.A >> 1n;
registers.F = CAPI.ARCH.calculateFlags_SRL(result, carry);

pressKey

CAPI.ARCH.pressKey(code: string): void { }

Registers a keyboard key as being pressed. The code parameter should be a JavaScript event.code string (e.g., "KeyA", "Digit1").

E.g.:

CAPI.ARCH.pressKey("KeyA");

releaseKey

CAPI.ARCH.releaseKey(code: string): void { }

Registers a keyboard key as being released.

E.g.:

CAPI.ARCH.releaseKey("KeyA");

readULAKeyboard

CAPI.ARCH.readULAKeyboard(port: bigint): bigint { }

Emulates a read from the ZX Spectrum ULA keyboard port. The high byte of the port address determines which half-row of keys to poll. Returns an 8-bit value where bits 0-4 represent key states (0 = pressed, 1 = not pressed), and bits 5-7 are typically high.

E.g.:

const keyState = CAPI.ARCH.readULAKeyboard(0xf7fen);  // Read row with keys 1-5

read

CAPI.ARCH.read(port: bigint): bigint { }

Reads a byte from an I/O port. Handles:

  • Port 0x01: Keyboard buffer (non-blocking)
  • ULA ports (keyboard matrix): ZX Spectrum keyboard
  • Unhandled ports: Returns 0xFF

E.g.:

const value = CAPI.ARCH.read(0x01n);  // Read from keyboard buffer

write

CAPI.ARCH.write(port: bigint, value: bigint): void { }

Writes a byte to an I/O port. Handles:

  • Port 0x02: Screen output
  • Port 0xFE: ZX Spectrum ULA (sets border color from bits 0-2)
  • Port 0x17: CREATOR-specific port for ecall (exits on value 10n)

E.g.:

CAPI.ARCH.write(0x02n, 65n);  // Write 'A' to screen
CAPI.ARCH.write(0xfen, 0x02n);  // Set border color to red

Interrupts

Functions to manage interrupts and privilege modes.

For more information, see Interrupt Support.

CAPI.INTERRUPTS.setUserMode

CAPI.INTERRUPTS.setUserMode(): void { }

Sets the privilege level to User.

E.g.:

CAPI.INTERRUPTS.setUserMode();

CAPI.INTERRUPTS.setKernelMode

CAPI.INTERRUPTS.setKernelMode(): void { }

Sets the privilege level to Kernel.

E.g.:

CAPI.INTERRUPTS.setKernelMode();

CAPI.INTERRUPTS.create

CAPI.INTERRUPTS.create(type: InterruptType): void { }

Creates an interrupt of the specified type.

E.g.:

CAPI.INTERRUPTS.create(InterruptType.Software);

CAPI.INTERRUPTS.enable

CAPI.INTERRUPTS.enable(type: InterruptType): void { }

Enables an interrupt type.

E.g.:

CAPI.INTERRUPTS.enable(InterruptType.Software);

CAPI.INTERRUPTS.globalEnable

CAPI.INTERRUPTS.globalEnable(): void { }

Globally enables interrupts.

E.g.:

CAPI.INTERRUPTS.globalEnable();

CAPI.INTERRUPTS.disable

CAPI.INTERRUPTS.disable(type: InterruptType): void { }

Disables an interrupt type.

E.g.:

CAPI.INTERRUPTS.disable(InterruptType.Software);

CAPI.INTERRUPTS.globalDisable

CAPI.INTERRUPTS.globalDisable(): void { }

Globally disables interrupts.

E.g.:

CAPI.INTERRUPTS.globalDisable();

CAPI.INTERRUPTS.isEnabled

CAPI.INTERRUPTS.isEnabled(type: InterruptType): boolean { }

Checks if an interrupt type is enabled.

E.g.:

CAPI.INTERRUPTS.isEnabled(InterruptType.Software);

CAPI.INTERRUPTS.isGlobalEnabled

CAPI.INTERRUPTS.isGlobalEnabled(): boolean { }

Checks if interrupts are globally enabled.

E.g.:

CAPI.INTERRUPTS.isGlobalEnabled();

CAPI.INTERRUPTS.clear

CAPI.INTERRUPTS.clear(type: InterruptType): void { }

Clears interrupts of the specified type.

E.g.:

CAPI.INTERRUPTS.clear(InterruptType.Software);

CAPI.INTERRUPTS.globalClear

CAPI.INTERRUPTS.globalClear(): void { }

Clears all interrupts.

E.g.:

CAPI.INTERRUPTS.globalClear();

CAPI.INTERRUPTS.setCustomHandler

CAPI.INTERRUPTS.setCustomHandler(): void { }

Sets the interrupt handler to the custom handler.

E.g.:

CAPI.INTERRUPTS.setCustomHandler();

CAPI.INTERRUPTS.setCREATORHandler

CAPI.INTERRUPTS.setCREATORHandler(): void { }

Sets the interrupt handler to the default CREATOR handler.

E.g.:

CAPI.INTERRUPTS.setCREATORHandler();

CAPI.INTERRUPTS.setHighlight

CAPI.INTERRUPTS.setHighlight(): void { }

Highlights the current instruction as "interrupted" in the UI.

E.g.:

CAPI.INTERRUPTS.setHighlight();

CAPI.INTERRUPTS.clearHighlight

CAPI.INTERRUPTS.setHighlight(): void { }

Removes the "interrupted" highlight in the UI. Typically used in instructions that return from the interrupt handler, such as RISC-V's mret.

E.g.:

CAPI.INTERRUPTS.clearHighlight();

results matching ""

    No results matching ""