# 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 - ``: 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 #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 ← #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` - Operation: `#0 ← #1 $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 $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` - 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+` - 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 #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 #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(#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