Updated spec!

This commit is contained in:
Erin 2023-10-22 18:18:50 +02:00 committed by ondra05
parent 000f09d5ce
commit 1a7ed13cd0
3 changed files with 475 additions and 395 deletions

View file

@ -28,8 +28,8 @@
0x1B, SRS16, RRR, "Signed right bitshift (16b)" ; 0x1B, SRS16, RRR, "Signed right bitshift (16b)" ;
0x1C, SRS32, RRR, "Signed right bitshift (32b)" ; 0x1C, SRS32, RRR, "Signed right bitshift (32b)" ;
0x1D, SRS64, RRR, "Signed right bitshift (64b)" ; 0x1D, SRS64, RRR, "Signed right bitshift (64b)" ;
0x1E, CMP, RRR, "Signed comparsion" ; 0x1E, CMPU, RRR, "Unsigned comparsion" ;
0x1F, CMPU, RRR, "Unsigned comparsion" ; 0x1F, CMPS, RRR, "Signed comparsion" ;
0x20, DIRU8, RRRR, "Merged divide-remainder (unsigned 8b)" ; 0x20, DIRU8, RRRR, "Merged divide-remainder (unsigned 8b)" ;
0x21, DIRU16, RRRR, "Merged divide-remainder (unsigned 16b)" ; 0x21, DIRU16, RRRR, "Merged divide-remainder (unsigned 16b)" ;
0x22, DIRU32, RRRR, "Merged divide-remainder (unsigned 32b)" ; 0x22, DIRU32, RRRR, "Merged divide-remainder (unsigned 32b)" ;
@ -66,8 +66,8 @@
0x41, SRSI16, RRW, "Signed right bitshift with immediate" ; 0x41, SRSI16, RRW, "Signed right bitshift with immediate" ;
0x42, SRSI32, RRW, "Signed right bitshift with immediate" ; 0x42, SRSI32, RRW, "Signed right bitshift with immediate" ;
0x43, SRSI64, RRW, "Signed right bitshift with immediate" ; 0x43, SRSI64, RRW, "Signed right bitshift with immediate" ;
0x44, CMPI, RRD, "Signed compare with immediate" ; 0x44, CMPUI, RRD, "Unsigned compare with immediate" ;
0x45, CMPUI, RRD, "Unsigned compare with immediate" ; 0x45, CMPSI, RRD, "Signed compare with immediate" ;
0x46, CP, RR, "Copy register" ; 0x46, CP, RR, "Copy register" ;
0x47, SWA, RR, "Swap registers" ; 0x47, SWA, RR, "Swap registers" ;
0x48, LI8, RB, "Load immediate (8b)" ; 0x48, LI8, RB, "Load immediate (8b)" ;
@ -86,10 +86,10 @@
0x55, JALA, RRA, "Linking absolute jump" ; 0x55, JALA, RRA, "Linking absolute jump" ;
0x56, JEQ, RRP, "Branch on equal" ; 0x56, JEQ, RRP, "Branch on equal" ;
0x57, JNE, RRP, "Branch on nonequal" ; 0x57, JNE, RRP, "Branch on nonequal" ;
0x58, JLT, RRP, "Branch on lesser-than (signed)" ; 0x58, JLTU, RRP, "Branch on lesser-than (unsigned)" ;
0x59, JGT, RRP, "Branch on greater-than (signed)" ; 0x59, JGTU, RRP, "Branch on greater-than (unsigned)" ;
0x5A, JLTU, RRP, "Branch on lesser-than (unsigned)" ; 0x5A, JLTS, RRP, "Branch on lesser-than (signed)" ;
0x5B, JGTU, RRP, "Branch on greater-than (unsigned)" ; 0x5B, JGTS, RRP, "Branch on greater-than (signed)" ;
0x5C, ECA, N, "Environment call trap" ; 0x5C, ECA, N, "Environment call trap" ;
0x5D, EBP, N, "Environment breakpoint" ; 0x5D, EBP, N, "Environment breakpoint" ;
0x5E, FADD32, RRR, "Floating point addition (32b)" ; 0x5E, FADD32, RRR, "Floating point addition (32b)" ;
@ -100,8 +100,8 @@
0x63, FMUL64, RRR, "Floating point multiply (64b)" ; 0x63, FMUL64, RRR, "Floating point multiply (64b)" ;
0x64, FDIV32, RRR, "Floating point division (32b)" ; 0x64, FDIV32, RRR, "Floating point division (32b)" ;
0x65, FDIV64, RRR, "Floating point division (64b)" ; 0x65, FDIV64, RRR, "Floating point division (64b)" ;
0x66, FMA32, RRR, "Float fused multiply-add (32b)" ; 0x66, FMA32, RRRR, "Float fused multiply-add (32b)" ;
0x67, FMA64, RRR, "Float fused multiply-add (64b)" ; 0x67, FMA64, RRRR, "Float fused multiply-add (64b)" ;
0x68, FINV32, RR, "Float reciprocal (32b)" ; 0x68, FINV32, RR, "Float reciprocal (32b)" ;
0x69, FINV64, RR, "Float reciprocal (64b)" ; 0x69, FINV64, RR, "Float reciprocal (64b)" ;
0x6A, FCMPLT32, RRR, "Flaot compare less than (32b)" ; 0x6A, FCMPLT32, RRR, "Flaot compare less than (32b)" ;

View file

@ -96,32 +96,16 @@ where
SRS16 => self.binary_op(|l: i16, r| i16::wrapping_shl(l, r as u32)), SRS16 => self.binary_op(|l: i16, r| i16::wrapping_shl(l, r as u32)),
SRS32 => self.binary_op(|l: i32, r| i32::wrapping_shl(l, r as u32)), SRS32 => self.binary_op(|l: i32, r| i32::wrapping_shl(l, r as u32)),
SRS64 => self.binary_op(|l: i64, r| i64::wrapping_shl(l, r as u32)), SRS64 => self.binary_op(|l: i64, r| i64::wrapping_shl(l, r as u32)),
CMP => handler!(self, |OpsRRR(tg, a0, a1)| { CMPU => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp(
// Compare a0 <=> a1
// < → 0
// > → 1
// = → 2
self.write_reg(
tg, tg,
self.read_reg(a0) a0,
.cast::<i64>() self.read_reg(a1).cast::<u64>()
.cmp(&self.read_reg(a1).cast::<i64>()) )),
as i64 CMPS => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp(
+ 1,
);
}),
CMPU => handler!(self, |OpsRRR(tg, a0, a1)| {
// Unsigned comparsion
self.write_reg(
tg, tg,
self.read_reg(a0) a0,
.cast::<u64>() self.read_reg(a1).cast::<i64>()
.cmp(&self.read_reg(a1).cast::<u64>()) )),
as i64
+ 1,
);
}),
DIRU8 => self.dir::<u8>(), DIRU8 => self.dir::<u8>(),
DIRU16 => self.dir::<u16>(), DIRU16 => self.dir::<u16>(),
DIRU32 => self.dir::<u32>(), DIRU32 => self.dir::<u32>(),
@ -170,21 +154,9 @@ where
SRSI16 => self.binary_op_ims::<i16>(ops::Shr::shr), SRSI16 => self.binary_op_ims::<i16>(ops::Shr::shr),
SRSI32 => self.binary_op_ims::<i32>(ops::Shr::shr), SRSI32 => self.binary_op_ims::<i32>(ops::Shr::shr),
SRSI64 => self.binary_op_ims::<i64>(ops::Shr::shr), SRSI64 => self.binary_op_ims::<i64>(ops::Shr::shr),
CMPI => handler!(self, |OpsRRD(tg, a0, imm)| { CMPUI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm) }),
self.write_reg( CMPSI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm as i64) }),
tg, CP => handler!(self, |OpsRR(tg, a0)| self.write_reg(tg, self.read_reg(a0))),
self.read_reg(a0)
.cast::<i64>()
.cmp(&Value::from(imm).cast::<i64>())
as i64,
);
}),
CMPUI => handler!(self, |OpsRRD(tg, a0, imm)| {
self.write_reg(tg, self.read_reg(a0).cast::<u64>().cmp(&imm) as i64);
}),
CP => handler!(self, |OpsRR(tg, a0)| {
self.write_reg(tg, self.read_reg(a0));
}),
SWA => handler!(self, |OpsRR(r0, r1)| { SWA => handler!(self, |OpsRR(r0, r1)| {
// Swap registers // Swap registers
match (r0, r1) { match (r0, r1) {
@ -202,28 +174,30 @@ where
LI16 => handler!(self, |OpsRH(tg, imm)| self.write_reg(tg, imm)), LI16 => handler!(self, |OpsRH(tg, imm)| self.write_reg(tg, imm)),
LI32 => handler!(self, |OpsRW(tg, imm)| self.write_reg(tg, imm)), LI32 => handler!(self, |OpsRW(tg, imm)| self.write_reg(tg, imm)),
LI64 => handler!(self, |OpsRD(tg, imm)| self.write_reg(tg, imm)), LI64 => handler!(self, |OpsRD(tg, imm)| self.write_reg(tg, imm)),
LRA => handler!(self, |OpsRRO(tg, reg, off)| { LRA => handler!(self, |OpsRRO(tg, reg, off)| self.write_reg(
self.write_reg(
tg, tg,
self.pcrel(off, 3) self.pcrel(off, 3)
.wrapping_add(self.read_reg(reg).cast::<i64>()) .wrapping_add(self.read_reg(reg).cast::<i64>())
.get(), .get(),
); )),
}),
LD => handler!(self, |OpsRRAH(dst, base, off, count)| {
// Load. If loading more than register size, continue on adjecent registers // Load. If loading more than register size, continue on adjecent registers
self.load(dst, base, off, count)?; LD => handler!(self, |OpsRRAH(dst, base, off, count)| self
}), .load(dst, base, off, count)?),
ST => handler!(self, |OpsRRAH(dst, base, off, count)| {
// Store. Same rules apply as to LD // Store. Same rules apply as to LD
self.store(dst, base, off, count)?; ST => handler!(self, |OpsRRAH(dst, base, off, count)| self
}), .store(dst, base, off, count)?),
LDR => handler!(self, |OpsRROH(dst, base, off, count)| { LDR => handler!(self, |OpsRROH(dst, base, off, count)| self.load(
self.load(dst, base, self.pcrel(off, 3).get(), count)?; dst,
}), base,
STR => handler!(self, |OpsRROH(dst, base, off, count)| { self.pcrel(off, 3).get(),
self.store(dst, base, self.pcrel(off, 3).get(), count)?; count
}), )?),
STR => handler!(self, |OpsRROH(dst, base, off, count)| self.store(
dst,
base,
self.pcrel(off, 3).get(),
count
)?),
BMC => { BMC => {
// Block memory copy // Block memory copy
match if let Some(copier) = &mut self.copier { match if let Some(copier) = &mut self.copier {
@ -295,13 +269,12 @@ where
} }
// Conditional jumps, jump only to immediates // Conditional jumps, jump only to immediates
JEQ => self.cond_jmp::<u64>(Ordering::Equal), JEQ => self.cond_jmp::<u64>(Ordering::Equal),
JNE => handler!(self, |OpsRRP(a0, a1, ja)| { JNE => {
let OpsRRP(a0, a1, ja) = self.decode();
if self.read_reg(a0).cast::<u64>() != self.read_reg(a1).cast::<u64>() { if self.read_reg(a0).cast::<u64>() != self.read_reg(a1).cast::<u64>() {
self.pc = Address::new( self.pc = self.pcrel(ja, 3);
((self.pc.get() as i64).wrapping_add(ja as i64)) as u64, }
)
} }
}),
JLT => self.cond_jmp::<u64>(Ordering::Less), JLT => self.cond_jmp::<u64>(Ordering::Less),
JGT => self.cond_jmp::<u64>(Ordering::Greater), JGT => self.cond_jmp::<u64>(Ordering::Greater),
JLTU => self.cond_jmp::<i64>(Ordering::Less), JLTU => self.cond_jmp::<i64>(Ordering::Less),
@ -329,67 +302,60 @@ where
FDIV64 => self.binary_op::<f64>(ops::Div::div), FDIV64 => self.binary_op::<f64>(ops::Div::div),
FMA32 => self.fma::<f32>(), FMA32 => self.fma::<f32>(),
FMA64 => self.fma::<f64>(), FMA64 => self.fma::<f64>(),
FINV32 => handler!(self, |OpsRR(tg, reg)| { FINV32 => handler!(self, |OpsRR(tg, reg)| self
self.write_reg(tg, 1. / self.read_reg(reg).cast::<f32>()) .write_reg(tg, 1. / self.read_reg(reg).cast::<f32>())),
}), FINV64 => handler!(self, |OpsRR(tg, reg)| self
FINV64 => handler!(self, |OpsRR(tg, reg)| { .write_reg(tg, 1. / self.read_reg(reg).cast::<f64>())),
self.write_reg(tg, 1. / self.read_reg(reg).cast::<f64>())
}),
FCMPLT32 => self.fcmp::<f32>(Ordering::Less), FCMPLT32 => self.fcmp::<f32>(Ordering::Less),
FCMPLT64 => self.fcmp::<f64>(Ordering::Less), FCMPLT64 => self.fcmp::<f64>(Ordering::Less),
FCMPGT32 => self.fcmp::<f32>(Ordering::Greater), FCMPGT32 => self.fcmp::<f32>(Ordering::Greater),
FCMPGT64 => self.fcmp::<f64>(Ordering::Greater), FCMPGT64 => self.fcmp::<f64>(Ordering::Greater),
ITF32 => handler!(self, |OpsRR(tg, reg)| { ITF32 => handler!(self, |OpsRR(tg, reg)| self
self.write_reg(tg, self.read_reg(reg).cast::<i64>() as f32); .write_reg(tg, self.read_reg(reg).cast::<i64>() as f32)),
}), ITF64 => handler!(self, |OpsRR(tg, reg)| self
ITF64 => handler!(self, |OpsRR(tg, reg)| { .write_reg(tg, self.read_reg(reg).cast::<i64>() as f64)),
self.write_reg(tg, self.read_reg(reg).cast::<i64>() as f64); FTI32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg(
}),
FTI32 => handler!(self, |OpsRRB(tg, reg, mode)| {
self.write_reg(
tg, tg,
crate::float::f32toint( crate::float::f32toint(
self.read_reg(reg).cast::<f32>(), self.read_reg(reg).cast::<f32>(),
RoundingMode::try_from(mode) RoundingMode::try_from(mode)
.map_err(|()| VmRunError::InvalidOperand)?, .map_err(|()| VmRunError::InvalidOperand)?,
), ),
); )),
}), FTI64 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg(
FTI64 => handler!(self, |OpsRRB(tg, reg, mode)| {
self.write_reg(
tg, tg,
crate::float::f64toint( crate::float::f64toint(
self.read_reg(reg).cast::<f64>(), self.read_reg(reg).cast::<f64>(),
RoundingMode::try_from(mode) RoundingMode::try_from(mode)
.map_err(|()| VmRunError::InvalidOperand)?, .map_err(|()| VmRunError::InvalidOperand)?,
), ),
); )),
}), FC32T64 => handler!(self, |OpsRR(tg, reg)| self
FC32T64 => handler!(self, |OpsRR(tg, reg)| { .write_reg(tg, self.read_reg(reg).cast::<f32>() as f64)),
self.write_reg(tg, self.read_reg(reg).cast::<f32>() as f64); FC64T32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg(
}),
FC64T32 => handler!(self, |OpsRRB(tg, reg, mode)| {
self.write_reg(
tg, tg,
crate::float::conv64to32( crate::float::conv64to32(
self.read_reg(reg).cast(), self.read_reg(reg).cast(),
RoundingMode::try_from(mode) RoundingMode::try_from(mode)
.map_err(|()| VmRunError::InvalidOperand)?, .map_err(|()| VmRunError::InvalidOperand)?,
), ),
) )),
}), LRA16 => handler!(self, |OpsRRP(tg, reg, imm)| self.write_reg(
LRA16 => handler!(self, |OpsRRP(tg, reg, imm)| {
self.write_reg(
tg, tg,
(self.pc + self.read_reg(reg).cast::<u64>() + imm + 3_u16).get(), (self.pc + self.read_reg(reg).cast::<u64>() + imm + 3_u16).get(),
); )),
}), LDR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.load(
LDR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { dst,
self.load(dst, base, self.pcrel(off, 3).get(), count)?; base,
}), self.pcrel(off, 3).get(),
STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| { count
self.store(dst, base, self.pcrel(off, 3).get(), count)?; )?),
}), STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.store(
dst,
base,
self.pcrel(off, 3).get(),
count
)?),
JMP16 => { JMP16 => {
let OpsP(off) = self.decode(); let OpsP(off) = self.decode();
self.pc = self.pcrel(off, 1); self.pc = self.pcrel(off, 1);
@ -464,6 +430,12 @@ where
Ok(()) Ok(())
} }
/// Three-way comparsion
#[inline(always)]
unsafe fn cmp<T: ValueVariant + Ord>(&mut self, to: u8, reg: u8, val: T) {
self.write_reg(to, self.read_reg(reg).cast::<T>().cmp(&val) as i64);
}
/// Perform binary operating over two registers /// Perform binary operating over two registers
#[inline(always)] #[inline(always)]
unsafe fn binary_op<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) { unsafe fn binary_op<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) {

628
spec.md
View file

@ -1,332 +1,440 @@
# HoleyBytes ISA Specification # HoleyBytes ISA Specification
# Bytecode format # Bytecode format
- Holey Bytes program should start with following magic: `[0xAB, 0x1E, 0x0B]` - Image format is not specified, though ELF is recommended
- All numbers are encoded little-endian - All numbers are encoded little-endian
- There is 256 registers, they are represented by a byte - There is 256 registers, they are represented by a byte
- Immediate values are 64 bit - Immediate values are 8, 16, 32 or 64 bit
- Program is by spec required to be terminated with 12 zero bytes
### Instruction encoding ## Instruction encoding
- Instruction parameters are packed (no alignment) - Instruction operands are packed (no alignment)
- [opcode, …parameters…] - [opcode, operand 0, operand 1, …]
### Instruction parameter types ## Instruction parameter types
- B = Byte - `R`: Register (8 bits)
- D = Doubleword (64 bits) - Relative program-counter offset immediates:
- H = Halfword (16 bits) - `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)
| Name | Size | ## Types
|:----:|:--------| - Si*n*: Signed integer of size *n* bits (Si8, Si16, Si32, Si64)
| BBBB | 32 bits | - Ui*n*: Unsigned integer of size *n* bits (Ui8, Ui16, Ui32, Ui64)
| BBB | 24 bits | - Xi*n*: Sign-agnostic integer of size *n* bits (Xi8, Xi16, Xi32, Xi64)
| BBDH | 96 bits | - Fl*n*: Floating point number of size *n* bits (Fl32, Fl64)
| BBD | 80 bits |
| BBW | 48 bits | # Behaviours
| BB | 16 bits | - Integer operations are always wrapping, including signed numbers
| BD | 72 bits | - Two's complement
| D | 64 bits | - Floats as specified by IEEE 754
| N | 0 bits |
## 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 |
# Instructions # Instructions
- `#n`: register in parameter *n* - `#n`: register in parameter *n*
- `imm #n`: for immediate in parameter *n* - `$n`: for immediate in parameter *n*
- `P ← V`: Set register P to value V - `#P ← V`: Set register P to value V
- `[x]`: Address x - `[x]`: Address x
- `XY`: X bytes from location Y
- `pc`: Program counter
- `<XYZ>`: Placeholder
- `Type(X)`: Cast
## Program execution control ## Program execution control
- N type - Type `N`
| Opcode | Name | Action | | Opcode | Mnemonic | Action |
|:------:|:----:|:-----------------------------:| |:-------|:---------|:--------------------------------------------|
| 0 | UN | Trigger unreachable code trap | | 0x00 | UN | Throw unreachable code exception |
| 1 | TX | Terminate execution | | 0x01 | TX | Terminate execution (eg. on end of program) |
| 2 | NOP | Do nothing | | 0x02 | NOP | Do nothing |
## Integer binary ops. ## Binary register-immediate ops
- BBB type - Type `RR<IMM>`
- `#0 ← #1 <op> #2` - Action: `#0 ← #1 <OP> #2`
| Opcode | Name | Action | ## Addition (`+`)
|:------:|:----:|:-----------------------:| | Opcode | Mnemonic | Type |
| 3 | ADD | Wrapping addition | |:-------|:---------|:-----|
| 4 | SUB | Wrapping subtraction | | 0x03 | ADD8 | Xi8 |
| 5 | MUL | Wrapping multiplication | | 0x04 | ADD16 | Xi16 |
| 6 | AND | Bitand | | 0x05 | ADD32 | Xi32 |
| 7 | OR | Bitor | | 0x06 | ADD64 | Xi64 |
| 8 | XOR | Bitxor |
| 9 | SL | Unsigned left bitshift |
| 10 | SR | Unsigned right bitshift |
| 11 | SRS | Signed right bitshift |
### Comparsion ## Subtraction (`-`)
| Opcode | Name | Action | | Opcode | Mnemonic | Type |
|:------:|:----:|:-------------------:| |:-------|:---------|:-----|
| 12 | CMP | Signed comparsion | | 0x07 | SUB8 | Xi8 |
| 13 | CMPU | Unsigned comparsion | | 0x08 | SUB16 | Xi16 |
| 0x09 | SUB32 | Xi32 |
| 0x0A | SUB64 | Xi64 |
#### Comparsion table ## Multiplication (`*`)
| #1 *op* #2 | Result | | Opcode | Mnemonic | Type |
|:----------:|:------:| |:-------|:---------|:-----|
| < | 0 | | 0x0B | MUL8 | Xi8 |
| = | 1 | | 0x0C | MUL16 | Xi16 |
| > | 2 | | 0x0D | MUL32 | Xi32 |
| 0x0E | MUL64 | Xi64 |
### Division-remainder ## Bitwise ops (type: Xi64)
- Type BBBB | Opcode | Mnemonic | Operation |
- In case of `#3` is zero, the resulting value is all-ones |:-------|:---------|:--------------------|
- `#0 ← #2 ÷ #3` | 0x0F | AND | Conjunction (&) |
- `#1 ← #2 % #3` | 0x10 | OR | Disjunction (\|) |
| 0x11 | XOR | Non-equivalence (^) |
| Opcode | Name | Action | ## Unsigned left bitshift (`<<`)
|:------:|:----:|:-------------------------------:| | Opcode | Mnemonic | Type |
| 14 | DIR | Divide and remainder combinated | |:-------|:---------|:-----|
| 0x12 | SLU8 | Ui8 |
| 0x13 | SLU16 | Ui16 |
| 0x14 | SLU32 | Ui32 |
| 0x15 | SLU64 | Ui64 |
### Negations ## Unsigned right bitshift (`>>`)
- Type BB | Opcode | Mnemonic | Type |
- `#0 ← #1 <op> #2` |:-------|:---------|:-----|
| 0x16 | SRU8 | Ui8 |
| 0x17 | SRU16 | Ui16 |
| 0x18 | SRU32 | Ui32 |
| 0x19 | SRU64 | Ui64 |
| Opcode | Name | Action | ## Signed right bitshift (`>>`)
|:------:|:----:|:----------------:| | Opcode | Mnemonic | Type |
| 15 | NEG | Bit negation | |:-------|:---------|:-----|
| 16 | NOT | Logical negation | | 0x1A | SRS8 | Si8 |
| 0x1B | SRS16 | Si16 |
| 0x1C | SRS32 | Si32 |
| 0x1D | SRS64 | Si64 |
## Integer immediate binary ops. ## Comparsion
- Type BBD - Compares two numbers, saves result to register
- `#0 ← #1 <op> imm #2` - Operation: `#0 ← #1 <=> #2`
| Opcode | Name | Action | | Ordering | Number |
|:------:|:----:|:--------------------:| |:---------|:-------|
| 17 | ADDI | Wrapping addition | | < | -1 |
| 18 | MULI | Wrapping subtraction | | = | 0 |
| 19 | ANDI | Bitand | | > | 1 |
| 20 | ORI | Bitor |
| 21 | XORI | Bitxor |
### Bitshifts | Opcode | Mnemonic | Type |
- Type BBW |:-------|:---------|:-----|
| Opcode | Name | Action | | 0x1E | CMPU | Ui64 |
|:------:|:----:|:-----------------------:| | 0x1F | CMPS | Si64 |
| 22 | SLI | Unsigned left bitshift |
| 23 | SRI | Unsigned right bitshift |
| 24 | SRSI | Signed right bitshift |
### Comparsion # Merged divide-remainder
- Comparsion is the same as when RRR type - Type `RRRR`
- Operation:
- `#0 ← #2 / #3`
- `#1 ← #2 % #3`
| Opcode | Name | Action | - If dividing by zero:
|:------:|:-----:|:-------------------:| - `#0 ← Ui64(-1)`
| 25 | CMPI | Signed comparsion | - `#1 ← #2`
| 26 | CMPUI | Unsigned comparsion |
## Register value set / copy | 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 |
### Copy # Unary register operations (type: Xi64)
- Type BB - Type: `RR`
- `#0 ← #1` - Operation: `#0 ← <OP> #1`
| Opcode | Name | Action | | Opcode | Mnemonic | Operation |
|:------:|:----:|:------:| |:-------|:---------|:-------------------------|
| 27 | CP | Copy | | 0x28 | NEG | Bitwise complement (`~`) |
| 0x29 | NOT | Logical negation (`!`) |
### Swap ## Sign extensions
- Type BB - Operation: `#0 ← Si64(#1)`
- Swap #0 and #1
- Zero register rules:
- Both: no-op
- One: Copy zero to the non-zero register
| Opcode | Name | Action | | Opcode | Mnemonic | Source type |
|:------:|:----:|:------:| |:-------|:---------|:------------|
| 28 | SWA | Swap | | 0x2A | SXT8 | Si8 |
| 0x2B | SXT16 | Si16 |
| 0x2C | SXT32 | Si32 |
### Load immediate # Binary register-immediate operations
- Type BD - Type: `RR<IMM>`
- `#0 ← #1` - Operation: `#0 ← #1 <OP> $2`
| Opcode | Name | Action | ## Addition (`+`)
|:------:|:----:|:--------------:| | Opcode | Mnemonic | Type |
| 29 | LI | Load immediate | |:-------|:---------|:-----|
| 0x2D | ADDI8 | Xi8 |
| 0x2E | ADDI16 | Xi16 |
| 0x2F | ADDI32 | Xi32 |
| 0x30 | ADDI64 | Xi64 |
### Load relative address ## Multiplication (`*`)
- Type BBW | Opcode | Mnemonic | Type |
| Opcode | Name | Action | |:-------|:---------|:-----|
|:------:|:----:|:-----------------------:| | 0x31 | MULI8 | Xi8 |
| 30 | LRA | `#0 ← #1 + imm #2 + PC` | | 0x32 | MULI16 | Xi16 |
| 0x33 | MULI32 | Xi32 |
| 0x34 | MULI64 | Xi64 |
## Memory operations ## Bitwise ops (type: Xi64)
- Type BBDH | Opcode | Mnemonic | Operation |
- If loaded/store value exceeds one register size, continue accessing following registers |:-------|:---------|:--------------------|
| 0x35 | ANDI | Conjunction (&) |
| 0x36 | ORI | Disjunction (\|) |
| 0x37 | XORI | Non-equivalence (^) |
### Load / Store ## Unsigned left bitshift (`<<`)
| Opcode | Name | Action | | Opcode | Mnemonic | Type |
|:------:|:----:|:---------------------------------------:| |:-------|:---------|:-----|
| 31 | LD | `#0 ← [#1 + imm #2], copy imm #3 bytes` | | 0x38 | SLUI8 | Ui8 |
| 32 | ST | `[#1 + imm #2] ← #0, copy imm #3 bytes` | | 0x39 | SLUI16 | Ui16 |
| 0x3A | SLUI32 | Ui32 |
| 0x3B | SLUI64 | Ui64 |
### PC relative Load / Store ## Unsigned right bitshift (`>>`)
- Type BBDW | Opcode | Mnemonic | Type |
| Opcode | Name | Action | |:-------|:---------|:-----|
|:------:|:----:|:--------------------------------------------:| | 0x3C | SRUI8 | Ui8 |
| 33 | LDR | `#0 ← [#1 + imm #2 + PC], copy imm #3 bytes` | | 0x3D | SRUI16 | Ui16 |
| 34 | STR | `[#1 + imm #2 + PC] ← #0, copy imm #3 bytes` | | 0x3E | SRUI32 | Ui32 |
| 0x3F | SRUI64 | Ui64 |
## Block copy ## Signed right bitshift (`>>`)
- Block copy source and target can overlap | Opcode | Mnemonic | Type |
|:-------|:---------|:-----|
| 0x40 | SRSI8 | Si8 |
| 0x41 | SRSI16 | Si16 |
| 0x42 | SRSI32 | Si32 |
| 0x43 | SRSI64 | Si64 |
### Memory copy ## Comparsion
- Type BBD - Compares two numbers, saves result to register
- Operation: `#0 ← #1 <=> $2`
- Comparsion table same for register-register one
| Opcode | Name | Action | | Opcode | Mnemonic | Type |
|:------:|:----:|:--------------------------------:| |:-------|:---------|:-----|
| 35 | BMC | `[#1] ← [#0], copy imm #2 bytes` | | 0x44 | CMPUI | Ui64 |
| 0x45 | CMPSI | Si64 |
### Register copy # Register copies
- Type BBB - Type: `RR`
- Copy a block a register to another location (again, overflowing to following registers)
| Opcode | Name | Action | | Opcode | Mnemonic | Operation |
|:------:|:----:|:--------------------------------:| |:-------|:---------|:---------------------------------|
| 36 | BRC | `#1 ← #0, copy imm #2 registers` | | 0x46 | CP | Copy register value (`#0 ← #1`) |
| 0x47 | SWA | Swap register values (`#0 ⇆ #1`) |
## Control flow # Load immediate
- Load immediate value from code to register
- Type: `R<IMM>`
- Operation: `#0 ← $1`
### Unconditional jump | Opcode | Mnemonic | Type |
- Type D |:-------|:---------|:-----|
| Opcode | Name | Action | | 0x48 | LI8 | Xi8 |
|:------:|:----:|:-------------------------------------------:| | 0x49 | LI16 | Xi16 |
| 37 | JMPR | Jump at address relative to program counter | | 0x4A | Li32 | Xi32 |
| 0x4B | Li64 | Xi64 |
### Unconditional linking jump # Load relative address
- Type BBD - Compute value from program counter, register value and offset
- Type: `RRO`
- Operation: `#0 ← pc + #1 + $2`
| Opcode | Name | Action | | Opcode | Mnemonic |
|:------:|:----:|:-------------------------------------------------------:| |:-------|:---------|
| 38 | JAL | Save PC past JAL to `#0` and jump at `#1 + imm #2` | | 0x4C | LRA |
| 39 | JALR | Save PC past JAL to `#0` and jump at `#1 + imm #2 + PC` |
### Conditional jumps # Memory access operations
- Type BBH - Immediate `$3` specifies size
- Jump at `PC + imm #2` if `#0 <op> #1` - 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`)
| Opcode | Name | Comparsion | ## Absolute addressing
|:------:|:----:|:------------:| - Type: `RRAH`
| 40 | JEQ | = | - Computes address from base register and absolute offset
| 41 | JNE | ≠ |
| 42 | JLT | < (signed) |
| 43 | JGT | > (signed) |
| 44 | JLTU | < (unsigned) |
| 45 | JGTU | > (unsigned) |
### Environment call | Opcode | Mnemonic | Operation |
- Type N |:-------|:---------|:-------------------|
| 0x4D | LD | `#0 ← $3[#1 + $2]` |
| 0x4E | ST | `$3[#1 + $2] ← #0` |
| Opcode | Name | Action | ## Relative addressing
|:------:|:----:|:-------------------------------------:| - Type: `RROH`
| 46 | ECA | Cause an trap to the host environment | - Computes address from register and offset from program counter
| 47 | EBP | Cause breakproint trap to environment |
## Floating point operations | Opcode | Mnemonic | Operation |
- Type BBB |:-------|:---------|:------------------------|
- `#0 ← #1 <op> #2` | 0x4F | LDR | `#0 ← $3[pc + #1 + $2]` |
| 0x50 | STR | `$3[pc + #1 + $2] ← #0` |
| Opcode | Name | Action | # Block memory copy
|:------:|:----:|:--------------:| - Type: `RRH`
| 48 | ADDF | Addition | - Copies block of `$3` bytes from memory location on address on `#0` to `#1`
| 49 | SUBF | Subtraction |
| 50 | MULF | Multiplication |
### Division-remainder | Opcode | Mnemonic | Operation |
- Type BBBB |:-------|:---------|:------------------|
| 0x51 | BMC | `$3[#1] ← $3[x0]` |
| Opcode | Name | Action | # Block register copy
|:------:|:----:|:-------------------------:| - Type: `RRB`
| 51 | DIRF | Same as for integer `DIR` | - Copy block of `$3` registers starting with `#0` to `#1`
- Copying over the 256 registers causes an exception
### Fused Multiply-Add | Opcode | Mnemonic | Operation |
- Type BBBB |:-------|:---------|:--------------|
| 0x52 | BRC | `$3#1 ← $3#0` |
| Opcode | Name | Action | # Relative jump
|:------:|:----:|:---------------------:| - Type: `O`
| 52 | FMAF | `#0 ← (#1 * #2) + #3` |
### Negation | Opcode | Mnemonic | Operation |
- Type BB |:-------|:---------|:---------------|
| Opcode | Name | Action | | 0x53 | JMP | `pc ← pc + $0` |
|:------:|:----:|:----------:|
| 53 | NEGF | `#0 ← -#1` |
### Conversion # Linking jump
- Type BB - Operation:
- Signed - Save address of following instruction to `#0`
- `#0 ← #1 as _` - `#0 ← pc+<instruction size>`
- Jump to specified address
| Opcode | Name | Action | | Opcode | Mnemonic | Instruction type | Address |
|:------:|:----:|:------------:| |:-------|:---------|:------------------|:-------------------------|
| 54 | ITF | Int to Float | | 0x54 | JAL | RRO (size = 6 B) | Relative, `pc + #1 + $2` |
| 55 | FTI | Float to Int | | 0x55 | JALA | RRA (size = 10 B) | Absolute, `#1 + $2` |
## Floating point immediate operations # Conditional jump
- Type BBD - Perform comparsion, if operation met, jump to relative address
- `#0 ← #1 <op> imm #2` - Type: `RRP`
- Operation: `if #0 <CMP> #1 { pc ← pc + $2 }`
| Opcode | Name | Action | | Opcode | Mnemonic | Condition | Type |
|:------:|:-----:|:--------------:| |:-------|:---------|:-------------------|:-----|
| 56 | ADDFI | Addition | | 0x56 | JEQ | Equals (`=`) | Xi64 |
| 57 | MULFI | Multiplication | | 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 |
# Registers # Environment traps
- There is 255 registers + one zero register (with index 0) - Traps to the environment
- Reading from zero register yields zero - Type: `N`
- Writing to zero register is a no-op
# Memory | Opcode | Mnemonic | Trap type |
- Addresses are 64 bit |:-------|:---------|:-----------------|
- Program should be in the same address space as all other data | 0x5C | ECA | Environment call |
- Memory implementation is arbitrary | 0x5D | EBP | Breakpoint |
- Address `0x0` may or may not be valid. Count with compilers
considering it invalid!
- In case of accessing invalid address:
- Program shall trap (LoadAccessEx, StoreAccessEx) with parameter of accessed address
- Value of register when trapped is undefined
## Recommendations # Floating point binary operations
- If paging used: - Type: `RRR`
- Leave first page invalid - Operation: `#0 ← #1 <OP> #2`
- Pages should be at least 4 KiB
# Program execution | Opcode | Mnemonic | Operation | Type |
- The way of program execution is implementation defined |:-------|:---------|:---------------------|:-----|
- The execution is arbitrary, as long all effects are obervable | 0x5E | FADD32 | Addition (`+`) | Fl32 |
in the way as program was executed literally, in order. | 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 |
# Program validation # Fused multiply-add
- Invalid program should cause runtime error: - Type: `RRRR`
- The form of error is arbitrary. Can be a trap or an interpreter-specified error - Operation: `#0 ← (#1 * #2) + #3`
- It shall not be handleable from within the program
- Executing invalid opcode should trap
- Program can be validaded either before execution or when executing
# Traps | Opcode | Mnemonic | Type |
Program should at least implement these traps: |:-------|:---------|:-----|
- Environment call | 0x66 | FMA32 | Fl32 |
- Invalid instruction exception | 0x67 | FMA64 | Fl64 |
- Load address exception
- Store address exception
- Unreachable instruction
and executing environment should be able to get information about them, # Comparsions
like the opcode of invalid instruction or attempted address to load/store. - Type: `RRR`
Details about these are left as an implementation detail. - Operation: `#0 ← #1 <=> #2`
- Comparsion table same as for `CMPx`/`CMPxI`
- NaN is less-than/greater-than depends on variant
# Assembly | Opcode | Mnemonic | Type | NaN is |
HoleyBytes assembly format is not defined, this is just a weak description |:-------|:---------|:-----|:-------|
of `hbasm` syntax. | 0x6A | FCMPLT32 | Fl32 | < |
| 0x6B | FCMPLT64 | Fl64 | < |
| 0x6C | FCMPGT32 | Fl32 | > |
| 0x6D | FCMPGT64 | Fl64 | > |
- Opcode names correspond to specified opcode names, lowercase (`nop`) # Int to float
- Parameters are separated by comma (`addi r0, r0, 1`) - Type: `RR`
- Instructions are separated by either line feed or semicolon - Converts from `Si64`
- Registers are represented by `r` followed by the number (`r10`) - Operation: `#0 ← Fl<SIZE>(#1)`
- Labels are defined by label name followed with colon (`loop:`)
- Labels are references simply by their name (`print`) | Opcode | Mnemonic | Type |
- Immediates are entered plainly. Negative numbers supported. |:-------|:---------|:-----|
| 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 |