# HoleyBytes ISA Specification

# Bytecode format
- Image format is not specified, though ELF is recommended
- All numbers are encoded little-endian
- There is 256 registers, they are represented by a byte
- Immediate values are 8, 16, 32 or 64 bit

## Instruction encoding
- Instruction operands are packed (no alignment)
- [opcode, operand 0, operand 1, …]

## Instruction parameter types
- `R`: Register (8 bits)
- Relative program-counter offset immediates:
    - `O`: 32 bit (Si32)
    - `P`: 16 bit (Si16)
- Immediates:
    - `B`: Byte, 8 bit (Xi8)
    - `H`: Half-word, 16 bit (Xi16)
    - `W`: Word, 32 bit (Xi32)
    - `D`: Double-word, 64 bit (Xi64)
- `A`: Absolute address immediate, 64 bit (Ui64)

## Types
- Si*n*: Signed integer of size *n* bits (Si8, Si16, Si32, Si64)
- Ui*n*: Unsigned integer of size *n* bits (Ui8, Ui16, Ui32, Ui64)
- Xi*n*: Sign-agnostic integer of size *n* bits (Xi8, Xi16, Xi32, Xi64)
- Fl*n*: Floating point number of size *n* bits (Fl32, Fl64)

# Behaviour
- There is only one type of register, a general-purpose one.
  Used for both integers and floats.
- Integer operations are wrapping, including signed numbers
    - Bitshifts are truncating
- Two's complement
- Floats as specified by IEEE 754
- Execution model is implementation defined as long all observable
  effects are performed in correct order

## Relative addressing
Relative addresses are computed from address of the first byte
of offset in the code. Not from the beginning of current or following instruction.

## Zero register
- Register 0
- Cannot be clobbered
    - Write is no-op
- Load always yields 0

## Rounding modes
| Rounding mode            | Value |
|:-------------------------|:------|
| To nearest, ties to even | 0b00  |
| Towards 0 (truncate)     | 0b01  |
| Towards +∞ (up)          | 0b10  |
| Towards -∞ (down)        | 0b11  |

- Remaining values in the byte traps with invalid operand exception

# Memory
- Memory implementation is implementation-defined
- Zero address (`0x0`) is considered invalid

# Traps
- Environment call
- Environment breakpoint

Program counter goes to the following instruction

## Exceptions
- Memory access fault
- Invalid operand
- Unknown opcode

Program counter stays on the currently executed instruction

# Instructions
- `#n`: register in parameter *n*
- `$n`: for immediate in parameter *n*
- `#P ← V`: Set register P to value V
- `[x]`: Address x
- `XY`: X bytes from location Y
- `pc`: Program counter
- `<XYZ>`: Placeholder
- `Type(X)`: Cast

## Program execution control
- Type `N`

| Opcode | Mnemonic | Action                                      |
|:-------|:---------|:--------------------------------------------|
| 0x00   | UN       | Throw unreachable code exception            |
| 0x01   | TX       | Terminate execution (eg. on end of program) |
| 0x02   | NOP      | Do nothing                                  |

## Binary register-register ops
- Type `RRR`
- Action: `#0 ← #1 <OP> #2`

## Addition (`+`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x03   | ADD8     | Xi8  |
| 0x04   | ADD16    | Xi16 |
| 0x05   | ADD32    | Xi32 |
| 0x06   | ADD64    | Xi64 |

## Subtraction (`-`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x07   | SUB8     | Xi8  |
| 0x08   | SUB16    | Xi16 |
| 0x09   | SUB32    | Xi32 |
| 0x0A   | SUB64    | Xi64 |

## Multiplication (`*`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x0B   | MUL8     | Xi8  |
| 0x0C   | MUL16    | Xi16 |
| 0x0D   | MUL32    | Xi32 |
| 0x0E   | MUL64    | Xi64 |

## Bitwise ops (type: Xi64)
| Opcode | Mnemonic | Operation           |
|:-------|:---------|:--------------------|
| 0x0F   | AND      | Conjunction (&)     |
| 0x10   | OR       | Disjunction (\|)    |
| 0x11   | XOR      | Non-equivalence (^) |

## Unsigned left bitshift (`<<`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x12   | SLU8     | Ui8  |
| 0x13   | SLU16    | Ui16 |
| 0x14   | SLU32    | Ui32 |
| 0x15   | SLU64    | Ui64 |

## Unsigned right bitshift (`>>`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x16   | SRU8     | Ui8  |
| 0x17   | SRU16    | Ui16 |
| 0x18   | SRU32    | Ui32 |
| 0x19   | SRU64    | Ui64 |

## Signed right bitshift (`>>`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x1A   | SRS8     | Si8  |
| 0x1B   | SRS16    | Si16 |
| 0x1C   | SRS32    | Si32 |
| 0x1D   | SRS64    | Si64 |

## Comparsion
- Compares two numbers, saves result to register
- Operation: `#0 ← #1 <=> #2`

| Ordering | Number |
|:---------|:-------|
| <        | -1     |
| =        | 0      |
| >        | 1      |

| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x1E   | CMPU     | Ui64 |
| 0x1F   | CMPS     | Si64 |

# Merged divide-remainder
- Type `RRRR`
- Operation:
    - `#0 ← #2 / #3`
    - `#1 ← #2 % #3`

- If dividing by zero:
    - `#0 ← Ui64(-1)`
    - `#1 ← #2`

| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x20   | DIRU8    | Ui8  |
| 0x21   | DIRU16   | Ui16 |
| 0x22   | DIRU32   | Ui32 |
| 0x23   | DIRU64   | Ui64 |
| 0x24   | DIRS8    | Si8  |
| 0x25   | DIRS16   | Si16 |
| 0x26   | DIRS32   | Si32 |
| 0x27   | DIRS64   | Si64 |

# Unary register operations (type: Xi64)
- Type: `RR`
- Operation: `#0 ← <OP> #1`

| Opcode | Mnemonic | Operation                |
|:-------|:---------|:-------------------------|
| 0x28   | NEG      | Bitwise complement (`~`) |
| 0x29   | NOT      | Logical negation (`!`)   |

## Sign extensions
- Operation: `#0 ← Si64(#1)`

| Opcode | Mnemonic | Source type |
|:-------|:---------|:------------|
| 0x2A   | SXT8     | Si8         |
| 0x2B   | SXT16    | Si16        |
| 0x2C   | SXT32    | Si32        |

# Binary register-immediate operations
- Type: `RR<IMM>`
- Operation: `#0 ← #1 <OP> $2`

## Addition (`+`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x2D   | ADDI8    | Xi8  |
| 0x2E   | ADDI16   | Xi16 |
| 0x2F   | ADDI32   | Xi32 |
| 0x30   | ADDI64   | Xi64 |

## Multiplication (`*`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x31   | MULI8    | Xi8  |
| 0x32   | MULI16   | Xi16 |
| 0x33   | MULI32   | Xi32 |
| 0x34   | MULI64   | Xi64 |

## Bitwise ops (type: Xi64)
| Opcode | Mnemonic | Operation           |
|:-------|:---------|:--------------------|
| 0x35   | ANDI     | Conjunction (&)     |
| 0x36   | ORI      | Disjunction (\|)    |
| 0x37   | XORI     | Non-equivalence (^) |

# Register-immediate bitshifts
- Type: `RRB`
- Operation: `#0 ← #1 <OP> $2`

## Unsigned left bitshift (`<<`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x38   | SLUI8    | Ui8  |
| 0x39   | SLUI16   | Ui16 |
| 0x3A   | SLUI32   | Ui32 |
| 0x3B   | SLUI64   | Ui64 |

## Unsigned right bitshift (`>>`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x3C   | SRUI8    | Ui8  |
| 0x3D   | SRUI16   | Ui16 |
| 0x3E   | SRUI32   | Ui32 |
| 0x3F   | SRUI64   | Ui64 |

## Signed right bitshift (`>>`)
| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x40   | SRSI8    | Si8  |
| 0x41   | SRSI16   | Si16 |
| 0x42   | SRSI32   | Si32 |
| 0x43   | SRSI64   | Si64 |

## Comparsion
- Compares two numbers, saves result to register
- Operation: `#0 ← #1 <=> $2`
- Comparsion table same for register-register one

| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x44   | CMPUI    | Ui64 |
| 0x45   | CMPSI    | Si64 |

# Register copies
- Type: `RR`

| Opcode | Mnemonic | Operation                        |
|:-------|:---------|:---------------------------------|
| 0x46   | CP       | Copy register value (`#0 ← #1`)  |
| 0x47   | SWA      | Swap register values (`#0 ⇆ #1`) |

# Load immediate
- Load immediate value from code to register
- Type: `R<IMM>`
- Operation: `#0 ← $1`

| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x48   | LI8      | Xi8  |
| 0x49   | LI16     | Xi16 |
| 0x4A   | Li32     | Xi32 |
| 0x4B   | Li64     | Xi64 |

# Load relative address
- Compute value from program counter, register value and offset
- Type: `RRO`
- Operation: `#0 ← pc + #1 + $2`

| Opcode | Mnemonic |
|:-------|:---------|
| 0x4C   | LRA      |

# Memory access operations
- Immediate `$3` specifies size
- If size is greater than register size,
    it overflows to adjecent register
    (eg. copying 16 bytes to register `r1` copies first 8 bytes to it
         and the remaining to `r2`)

## Absolute addressing
- Type: `RRAH`
- Computes address from base register and absolute offset

| Opcode | Mnemonic | Operation          |
|:-------|:---------|:-------------------|
| 0x4D   | LD       | `#0 ← $3[#1 + $2]` |
| 0x4E   | ST       | `$3[#1 + $2] ← #0` |

## Relative addressing
- Type: `RROH`
- Computes address from register and offset from program counter

| Opcode | Mnemonic | Operation               |
|:-------|:---------|:------------------------|
| 0x4F   | LDR      | `#0 ← $3[pc + #1 + $2]` |
| 0x50   | STR      | `$3[pc + #1 + $2] ← #0` |

# Block memory copy
- Type: `RRH`
- Copies block of `$3` bytes from memory location on address on `#0` to `#1`

| Opcode | Mnemonic | Operation         |
|:-------|:---------|:------------------|
| 0x51   | BMC      | `$3[#1] ← $3[x0]` |

# Block register copy
- Type: `RRB`
- Copy block of `$3` registers starting with `#0` to `#1`
- Copying over the 256 registers causes an exception

| Opcode | Mnemonic | Operation     |
|:-------|:---------|:--------------|
| 0x52   | BRC      | `$3#1 ← $3#0` |

# Relative jump
- Type: `O`

| Opcode | Mnemonic | Operation      |
|:-------|:---------|:---------------|
| 0x53   | JMP      | `pc ← pc + $0` |

# Linking jump
- Operation:
    - Save address of following instruction to `#0`
        - `#0 ← pc+<instruction size>`
    - Jump to specified address 

| Opcode | Mnemonic | Instruction type  | Address                  |
|:-------|:---------|:------------------|:-------------------------|
| 0x54   | JAL      | RRO (size = 6 B)  | Relative, `pc + #1 + $2` |
| 0x55   | JALA     | RRA (size = 10 B) | Absolute, `#1 + $2`      |

# Conditional jump
- Perform comparsion, if operation met, jump to relative address
- Type: `RRP`
- Operation: `if #0 <CMP> #1 { pc ← pc + $2 }`

| Opcode | Mnemonic | Condition          | Type |
|:-------|:---------|:-------------------|:-----|
| 0x56   | JEQ      | Equals (`=`)       | Xi64 |
| 0x57   | JNE      | Not-equals (`≠`)   | Xi64 |
| 0x58   | JLTU     | Less-than (`<`)    | Ui64 |
| 0x59   | JGTU     | Greater-than (`>`) | Ui64 |
| 0x5A   | JLTS     | Less-than (`<`)    | Si64 |
| 0x5B   | JGTS     | Greater-than (`>`) | Si64 |

# Environment traps
- Traps to the environment
- Type: `N`

| Opcode | Mnemonic | Trap type        |
|:-------|:---------|:-----------------|
| 0x5C   | ECA      | Environment call |
| 0x5D   | EBP      | Breakpoint       |

# Floating point binary operations
- Type: `RRR`
- Operation: `#0 ← #1 <OP> #2`

| Opcode | Mnemonic | Operation            | Type |
|:-------|:---------|:---------------------|:-----|
| 0x5E   | FADD32   | Addition (`+`)       | Fl32 |
| 0x5F   | FADD64   | Addition (`+`)       | Fl64 |
| 0x60   | FSUB32   | Subtraction (`-`)    | Fl32 |
| 0x61   | FSUB64   | Subtraction (`-`)    | Fl64 |
| 0x62   | FMUL32   | Multiplication (`*`) | Fl32 |
| 0x63   | FMUL64   | Multiplication (`*`) | Fl64 |
| 0x64   | FDIV32   | Division (`/`)       | Fl32 |
| 0x65   | FDIV64   | Division (`/`)       | Fl64 |

# Fused multiply-add
- Type: `RRRR`
- Operation: `#0 ← (#1 * #2) + #3`

| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x66   | FMA32    | Fl32 |
| 0x67   | FMA64    | Fl64 |

# Comparsions
- Type: `RRR`
- Operation: `#0 ← #1 <=> #2`
- Comparsion table same as for `CMPx`/`CMPxI`
- NaN is less-than/greater-than depends on variant

| Opcode | Mnemonic | Type | NaN is |
|:-------|:---------|:-----|:-------|
| 0x6A   | FCMPLT32 | Fl32 | <      |
| 0x6B   | FCMPLT64 | Fl64 | <      |
| 0x6C   | FCMPGT32 | Fl32 | >      |
| 0x6D   | FCMPGT64 | Fl64 | >      |

# Int to float
- Type: `RR`
- Converts from `Si64`
- Operation: `#0 ← Fl<SIZE>(#1)`

| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x6E   | ITF32    | Fl32 |
| 0x6F   | ITF64    | Fl64 |

# Float to int
- Type: `RRB`
- Operation: `#0 ← Si64(#1)`
- Immediate `$2` specifies rounding mode

| Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x70   | FTI32    | Fl32 |
| 0x71   | FTI64    | Fl64 |

# Fl32 to Fl64
- Type: `RR`
- Operation: `#0 ← Fl64(#1)`

| Opcode | Mnemonic |
|:-------|:---------|
| 0x72   | FC32T64  |

# Fl64 to Fl32
- Type: `RRB`
- Operation: `#0 ← Fl32(#1)`
- Immediate `$2` specified rounding mode

| Opcode | Mnemonic |
|:-------|:---------|
| 0x73   | FC64T32  |

# 16-bit relative address instruction variants

| Opcode | Mnemonic | Type | Variant of |
|:-------|:---------|:-----|:-----------|
| 0x74   | LRA16    | RRP  | LRA        |
| 0x75   | LDR16    | RRPH | LDR        |
| 0x76   | STR16    | RRPH | STR        |
| 0x77   | JMP16    | P    | JMP        |

# psABI
## C datatypes and alignment
- One byte is 8 bits

| C Type      | Description              | Byte sizes |
|:------------|:-------------------------|:-----------|
| char        | Character / byte         | 1          |
| short       | Short integer            | 2          |
| int         | Integer                  | 4          |
| long        | Long integer             | 8          |
| long long   | Long long integer        | 8          |
| float       | Single-precision float   | 4          |
| double      | Double-precision float   | 8          |
| long double | Extended-precision float | 8          |

- Bikeshedding note: `long double` is now 8 bytes as
  the base ISA does not support `f128`. an extension
  for that should be made.

## Call convention
- Registers r1 – r31 are caller saved
- Registers r32 – r255 are callee saved

| Register   | Description         | Saver  |
|:-----------|:--------------------|:-------|
| r0         | Hard-wired zero     | N/A    |
| r1 - r2    | Return values       | Caller |
| r2 - r11   | Function parameters | Caller |
| r12 - r30  | General purpose     | Caller |
| r31        | Return address      | Caller |
| r32 - r253 | General purpose     | Callee |
| r254       | Stack pointer       | Callee |
| r255       | Thread pointer      | N/A    |

- If return value is too big to fit r1, r2 is also used.
- Values larger than two double-words are passed by reference