You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
9.3 KiB
342 lines
9.3 KiB
// SPDX-License-Identifier: MIT
|
|
// Copyright Murad Karammaev, Nikita Kuzmin
|
|
|
|
use ux::{u3, u4};
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub enum Register {
|
|
R0,
|
|
R1,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
|
pub enum ShiftMode {
|
|
Logical,
|
|
Circular,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum Instruction {
|
|
Load,
|
|
Store,
|
|
NearJump {
|
|
forward: bool,
|
|
use_carry: bool,
|
|
offset: u4,
|
|
},
|
|
LoadImmediate {
|
|
low: bool,
|
|
reg: Register,
|
|
val: u4,
|
|
},
|
|
FarJump {
|
|
reg: Register,
|
|
use_carry: bool,
|
|
},
|
|
CmpEqual,
|
|
CmpGreater,
|
|
CmpLess,
|
|
Not,
|
|
BitShift {
|
|
right: bool,
|
|
reg: Register,
|
|
mode: ShiftMode,
|
|
len: u3,
|
|
},
|
|
Inc {
|
|
reg: Register,
|
|
},
|
|
Dec {
|
|
reg: Register,
|
|
},
|
|
Add,
|
|
Sub,
|
|
And,
|
|
Or,
|
|
Xor,
|
|
Xnor,
|
|
Complement {
|
|
reg: Register,
|
|
},
|
|
BitShiftVar {
|
|
right: bool,
|
|
mode: ShiftMode,
|
|
},
|
|
Copy {
|
|
reg_from: Register,
|
|
},
|
|
Setc {
|
|
carry: bool,
|
|
},
|
|
Nop,
|
|
Halt,
|
|
Cload {
|
|
reg: Register,
|
|
},
|
|
Zero {
|
|
reg: Register,
|
|
},
|
|
Div,
|
|
Mul,
|
|
Swap,
|
|
PortReady,
|
|
ReadPort,
|
|
WritePort,
|
|
Reserved,
|
|
}
|
|
|
|
pub fn decode(insn: u8) -> Instruction {
|
|
match insn {
|
|
0b00000000..=0b00111111 => Instruction::NearJump {
|
|
forward: insn & 0b00100000 == 0,
|
|
use_carry: insn & 0b00010000 != 0,
|
|
offset: u4::new(insn & 0b00001111),
|
|
},
|
|
0b10001000 => Instruction::Load,
|
|
0b10001001 => Instruction::Store,
|
|
0b01000000..=0b01111111 => Instruction::LoadImmediate {
|
|
low: insn & 0b00100000 == 0,
|
|
reg: if insn & 0b00010000 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
val: u4::new(insn & 0b00001111),
|
|
},
|
|
0b10000000..=0b10000011 => Instruction::FarJump {
|
|
reg: if insn & 0b00000010 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
use_carry: insn & 0b00000001 != 0,
|
|
},
|
|
0b10000100 => Instruction::CmpEqual,
|
|
0b10000101 => Instruction::CmpGreater,
|
|
0b10000110 => Instruction::CmpLess,
|
|
0b10000111 => Instruction::Not,
|
|
0b10100000..=0b10111111 => Instruction::BitShift {
|
|
right: true,
|
|
reg: if insn & 0b00010000 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
mode: if insn & 0b00001000 == 0 {
|
|
ShiftMode::Logical
|
|
} else {
|
|
ShiftMode::Circular
|
|
},
|
|
len: u3::new(insn & 0b00000111),
|
|
},
|
|
0b11000000..=0b11011111 => Instruction::BitShift {
|
|
right: false,
|
|
reg: if insn & 0b00010000 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
mode: if insn & 0b00001000 == 0 {
|
|
ShiftMode::Logical
|
|
} else {
|
|
ShiftMode::Circular
|
|
},
|
|
len: u3::new(insn & 0b00000111),
|
|
},
|
|
0b11100000..=0b11100001 => Instruction::Inc {
|
|
reg: if insn & 0b00000001 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
},
|
|
0b11100010..=0b11100011 => Instruction::Dec {
|
|
reg: if insn & 0b00000001 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
},
|
|
0b11100100 => Instruction::Add,
|
|
0b11100101 => Instruction::Sub,
|
|
0b11100110 => Instruction::And,
|
|
0b11100111 => Instruction::Or,
|
|
0b11101000 => Instruction::Xor,
|
|
0b11101001 => Instruction::Xnor,
|
|
0b11101010..=0b11101011 => Instruction::Complement {
|
|
reg: if insn & 0b00000001 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
},
|
|
0b11101100..=0b11101111 => Instruction::BitShiftVar {
|
|
right: insn & 0b00000010 == 0,
|
|
mode: if insn & 0b00000001 == 0 {
|
|
ShiftMode::Logical
|
|
} else {
|
|
ShiftMode::Circular
|
|
},
|
|
},
|
|
0b11110000..=0b11110001 => Instruction::Copy {
|
|
reg_from: if insn & 0b00000001 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
},
|
|
0b11110010..=0b11110011 => Instruction::Setc {
|
|
carry: insn & 0b00000001 != 0,
|
|
},
|
|
0b11110100 => Instruction::Nop,
|
|
0b11110101 => Instruction::Halt,
|
|
0b11110110..=0b11110111 => Instruction::Cload {
|
|
reg: if insn & 0b00000001 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
},
|
|
0b11111000..=0b11111001 => Instruction::Zero {
|
|
reg: if insn & 0b00000001 == 0 {
|
|
Register::R0
|
|
} else {
|
|
Register::R1
|
|
},
|
|
},
|
|
0b11111010 => Instruction::Div,
|
|
0b11111011 => Instruction::Mul,
|
|
0b11111100 => Instruction::Swap,
|
|
0b11111101 => Instruction::PortReady,
|
|
0b11111110 => Instruction::ReadPort,
|
|
0b11111111 => Instruction::WritePort,
|
|
_ => Instruction::Reserved,
|
|
}
|
|
}
|
|
|
|
pub fn encode(insn: Instruction) -> u8 {
|
|
match insn {
|
|
Instruction::Load => 0b10001000,
|
|
Instruction::Store => 0b10001001,
|
|
Instruction::NearJump {
|
|
forward,
|
|
use_carry,
|
|
offset,
|
|
} => {
|
|
let mut ret = 0u8;
|
|
ret |= match forward {
|
|
true => 0,
|
|
false => 0b00100000,
|
|
};
|
|
ret |= match use_carry {
|
|
true => 0b00010000,
|
|
false => 0,
|
|
};
|
|
ret | u8::from(offset)
|
|
}
|
|
Instruction::LoadImmediate { low, reg, val } => {
|
|
let mut ret = 0b01000000u8;
|
|
ret |= match low {
|
|
true => 0,
|
|
false => 0b00100000,
|
|
};
|
|
ret |= match reg {
|
|
Register::R0 => 0,
|
|
Register::R1 => 0b00010000,
|
|
};
|
|
ret | u8::from(val)
|
|
}
|
|
Instruction::FarJump { reg, use_carry } => match reg {
|
|
Register::R0 => 0b10000000 | if use_carry { 1 } else { 0 },
|
|
Register::R1 => 0b10000010 | if use_carry { 1 } else { 0 },
|
|
},
|
|
Instruction::CmpEqual => 0b10000100,
|
|
Instruction::CmpGreater => 0b10000101,
|
|
Instruction::CmpLess => 0b10000110,
|
|
Instruction::Not => 0b10000111,
|
|
Instruction::BitShift {
|
|
right,
|
|
reg,
|
|
mode,
|
|
len,
|
|
} => {
|
|
let mut ret = if right { 0b10100000 } else { 0b11000000 };
|
|
ret |= match reg {
|
|
Register::R0 => 0,
|
|
Register::R1 => 0b00010000,
|
|
};
|
|
ret |= match mode {
|
|
ShiftMode::Logical => 0,
|
|
ShiftMode::Circular => 0b00001000,
|
|
};
|
|
ret | u8::from(len)
|
|
}
|
|
Instruction::Inc { reg } => match reg {
|
|
Register::R0 => 0b11100000,
|
|
Register::R1 => 0b11100001,
|
|
},
|
|
Instruction::Dec { reg } => match reg {
|
|
Register::R0 => 0b11100010,
|
|
Register::R1 => 0b11100011,
|
|
},
|
|
Instruction::Add => 0b11100100,
|
|
Instruction::Sub => 0b11100101,
|
|
Instruction::And => 0b11100110,
|
|
Instruction::Or => 0b11100111,
|
|
Instruction::Xor => 0b11101000,
|
|
Instruction::Xnor => 0b11101001,
|
|
Instruction::Complement { reg } => match reg {
|
|
Register::R0 => 0b11101010,
|
|
Register::R1 => 0b11101011,
|
|
},
|
|
Instruction::BitShiftVar { right, mode } => {
|
|
let mut ret = 0b11101100;
|
|
ret |= if right { 0 } else { 0b00000010 };
|
|
ret |= match mode {
|
|
ShiftMode::Logical => 0,
|
|
ShiftMode::Circular => 1,
|
|
};
|
|
ret
|
|
}
|
|
Instruction::Copy { reg_from } => match reg_from {
|
|
Register::R0 => 0b11110000,
|
|
Register::R1 => 0b11110001,
|
|
},
|
|
Instruction::Setc { carry } => 0b11110010 | u8::from(carry),
|
|
Instruction::Nop => 0b11110100,
|
|
Instruction::Halt => 0b11110101,
|
|
Instruction::Cload { reg } => match reg {
|
|
Register::R0 => 0b11110110,
|
|
Register::R1 => 0b11110111,
|
|
},
|
|
Instruction::Zero { reg } => match reg {
|
|
Register::R0 => 0b11111000,
|
|
Register::R1 => 0b11111001,
|
|
},
|
|
Instruction::Div => 0b11111010,
|
|
Instruction::Mul => 0b11111011,
|
|
Instruction::Swap => 0b11111100,
|
|
Instruction::PortReady => 0b11111101,
|
|
Instruction::ReadPort => 0b11111110,
|
|
Instruction::WritePort => 0b11111111,
|
|
Instruction::Reserved => 0b00100001,
|
|
}
|
|
}
|
|
|
|
// This crate really needs more tests, but I can't be bothered
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{decode, encode};
|
|
use crate::instruction::Instruction;
|
|
|
|
#[test]
|
|
fn deterministic_instruction_encoding() {
|
|
for i in 0..=255u8 {
|
|
let decoded = decode(i);
|
|
match decoded {
|
|
Instruction::Reserved => (),
|
|
x => assert_eq!(i, encode(x)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|