commit 699477c6516d42d3ee3679de10e2904a6c3e17d1 Author: Ross Andrews Date: Fri Feb 18 21:02:17 2022 -0600 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..af06aa3 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vulcan-emu.iml b/.idea/vulcan-emu.iml new file mode 100644 index 0000000..c254557 --- /dev/null +++ b/.idea/vulcan-emu.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..780da68 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,85 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "vulcan-emu" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2a8c722 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "vulcan-emu" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.0" \ No newline at end of file diff --git a/src/address.rs b/src/address.rs new file mode 100644 index 0000000..c3407b9 --- /dev/null +++ b/src/address.rs @@ -0,0 +1,66 @@ +// 128k, the amount of memory in a standard Vulcan machine +pub const MEM_SIZE: u32 = 128 * 1024; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct Address(u32); + +impl From
for usize { + fn from(a: Address) -> Self { (a.0 & (MEM_SIZE-1)) as usize } +} + +impl From for Address { + fn from(a: u32) -> Self { Self(a & 0xffffff) } +} + +impl Into for Address { + fn into(self) -> u32 { self.0 } +} + +impl std::ops::Add for Address { + type Output = Address; + fn add(self, rhs: i32) -> Self::Output { + Address::from((self.0 as i32).overflowing_add(rhs).0 as u32) + } +} + +impl std::ops::Sub for Address { + type Output = Address; + fn sub(self, rhs: i32) -> Self::Output { self + -rhs } +} + +impl std::ops::SubAssign for Address { + fn sub_assign(&mut self, rhs: i32) { *self = *self - rhs; } +} + +impl std::ops::AddAssign for Address { + fn add_assign(&mut self, rhs: i32) { *self = *self + rhs; } +} + +#[test] +fn test_address_truncation() { + let a: Address = 0x11223344.into(); + assert_eq!(a, 0x00223344.into()); +} + +#[test] +fn test_addressing_arrays() { + let a: usize = Address::from(0xffffff).into(); + assert_eq!(a, 0x01ffff as usize); +} + +#[test] +fn test_address_overflows() { + let a = Address::from(0xfffffa); + assert_eq!(a + 10, Address(4)); + + let b = Address::from(3); + assert_eq!(b - 10, Address(0xfffff9)); + + let mut c = Address::from(5); + c += 3; + assert_eq!(c, Address(8)); + + let mut d = Address::from(5); + d -= 3; + assert_eq!(d, Address(2)); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0b4d044 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,487 @@ +mod memory; +mod address; +mod opcodes; + +use memory::Memory; +use memory::PeekPoke; +use address::Address; +use opcodes::Opcode; +use crate::opcodes::InvalidOpcode; +use std::convert::TryFrom; + +struct CPU { + memory: Memory, // Main memory, all of it + pc: Address, // program counter, address of the low byte of the instruction + dp: Address, // data pointer, address of the low byte of one cell above the data stack + sp: Address, // stack pointer, address of the low byte of the return stack + iv: Address, // interrupt vector + int_enabled: bool, // interrupt enable bit + halted: bool, // Whether the CPU is halted +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +struct Instruction { + opcode: Opcode, + arg: Option, + length: u8 +} + +impl CPU { + fn new(memory: Memory) -> Self { + Self { + memory, + pc: 1024.into(), + dp: 256.into(), + sp: 1024.into(), + iv: 1024.into(), + int_enabled: false, + halted: true, + } + } + + fn reset(&mut self) { + self.pc = 1024.into(); + self.dp = 256.into(); + self.sp = 1024.into(); + self.iv = 1024.into(); + self.int_enabled = false; + self.halted = true; + } + + fn push_data>(&mut self, word: A) { + self.memory.poke24(self.dp, word.into()); + self.dp += 3; + } + + fn push_call>(&mut self, word: A) { + self.sp -= 3; + self.memory.poke24(self.sp, word.into()); + } + + fn pop_data(&mut self) -> u32 { + self.dp -= 3; + self.memory.peek24(self.dp) + } + + fn pop_call(&mut self) -> u32 { + let val = self.memory.peek24(self.sp); + self.sp += 3; + val + } + + fn peek_call(&self) -> u32 { + self.memory.peek24(self.sp) + } + + fn peek_data(&self) -> u32 { + self.memory.peek24(self.dp - 3) + } + + fn fetch(&self) -> Result { + let instruction = self.memory.peek(self.pc); + match Opcode::try_from(instruction >> 2) { + Ok(opcode) => { + let arg_length = instruction & 3; + if arg_length == 0 { + Ok(Instruction { + opcode: opcode, + arg: None, + length: 1 + }) + } else { + let mut arg = 0u32; + for n in 0..arg_length { + let mut b: u32 = self.memory.peek(self.pc + (n + 1) as i32) as u32; + b = b << (8 * n); + arg += b; + } + Ok(Instruction { + opcode: opcode, + arg: Some(arg), + length: arg_length + 1 + }) + } + }, + Err(e) => Err(e) + } + } + + fn execute(&mut self, instruction: Instruction) -> Address { + if let Some(arg) = instruction.arg { + self.push_data(arg) + } + + if instruction.opcode.is_binary() { + let x = self.pop_data(); + let y = self.pop_data(); + + match instruction.opcode { + Opcode::Add => { self.push_data(x + y) } + Opcode::Sub => { self.push_data(y - x) } + Opcode::Mul => { self.push_data(y * x) } + Opcode::Div => { self.push_data(y / x) } + Opcode::Mod => { self.push_data(y % x) } + Opcode::And => { self.push_data(y & x) } + Opcode::Or => { self.push_data(y | x) } + Opcode::Xor => { self.push_data(y ^ x) } + Opcode::Gt => { self.push_data(bool_as_word(y > x)) } + Opcode::Lt => { self.push_data(bool_as_word(y < x)) } + Opcode::Agt => { self.push_data(bool_as_word(word_as_signed(y) > word_as_signed(x))) } + Opcode::Alt => { self.push_data(bool_as_word(word_as_signed(y) < word_as_signed(x))) } + Opcode::Lshift => { self.push_data(y << x) } + Opcode::Rshift => { self.push_data(y >> x) } + Opcode::Arshift => { + if y & 0x800000 != 0 { + let mut shifted = y; + for _ in 0..x { + shifted = shifted >> 1 | 0x800000; + } + self.push_data(shifted) + } else { + self.push_data(y >> x) + } + } + Opcode::Swap => { + self.push_data(x); + self.push_data(y) + } + Opcode::Store => { self.memory.poke(x, y as u8) } + Opcode::Storew => { self.memory.poke24(x, y) } + Opcode::Setsdp => { + self.dp = x.into(); + self.sp = y.into() + } + Opcode::Brz => { if y == 0 { return self.pc + word_as_signed(x) } } + Opcode::Brnz => { if y != 0 { return self.pc + word_as_signed(x) } } + _ => {} // This can never happen + } + self.pc + instruction.length as i32 + } else { + match instruction.opcode { + Opcode::Nop => { /* No action required */ } + Opcode::Rand => {} // TODO remove this whole instruction + Opcode::Not => { + let x = self.pop_data(); + self.push_data(bool_as_word(x == 0)) + } + Opcode::Pop => { self.pop_data(); } + Opcode::Dup => { self.push_data(self.peek_data()) } + Opcode::Pick => { + let index = self.pop_data(); + let val = self.memory.peek24(self.dp - (index as i32 + 1) * 3); + self.push_data(val) + } + Opcode::Rot => { + let x = self.pop_data(); + let y = self.pop_data(); + let z = self.pop_data(); + self.push_data(y); + self.push_data(x); + self.push_data(z) + } + Opcode::Jmp => { return self.pop_data().into() } + Opcode::Jmpr => { + let x = word_as_signed(self.pop_data()); + return self.pc + x + } + Opcode::Call => { + let x = self.pop_data(); + self.push_call(self.pc + instruction.length as i32); + return x.into() + } + Opcode::Ret => { return self.pop_call().into() } + Opcode::Hlt => { self.halted = true } + Opcode::Load => { + let x = self.pop_data(); + self.push_data(self.memory.peek(x) as u32) + } + Opcode::Loadw => { + let x = self.pop_data(); + self.push_data(self.memory.peek24(x)) + } + Opcode::Inton => { self.int_enabled = true } + Opcode::Intoff => { self.int_enabled = false } + Opcode::Setiv => { self.iv = self.pop_data().into() } + Opcode::Sdp => { + self.push_data(self.sp); + self.push_data(self.dp + 3) // The +3 accounts for the word we're about to push + } + Opcode::Pushr => { + let x = self.pop_data(); + self.push_call(x) + } + Opcode::Popr => { + let r = self.pop_call(); + self.push_data(r) + } + Opcode::Peekr => { + let r = self.peek_call(); + self.push_data(r) + } + Opcode::Debug => { /* TODO This should print the stack or something */ } + _ => {} // This can never happen + } + self.pc + instruction.length as i32 + } + } +} + +impl Opcode { + fn is_binary(self) -> bool { + use Opcode::*; + self != Nop && self != Not && self != Rand && self != Pop && self != Dup && self != Pick && + self != Rot && self != Jmp && self != Jmpr && self != Call && self != Ret && + self != Hlt && self != Load && self != Loadw && self != Inton && self != Intoff && + self != Setiv && self != Sdp && self != Pushr && self != Popr && self != Peekr && + self != Debug + } +} + +fn word_as_signed(word: u32) -> i32 { + if word & 0x800000 != 0 { + -(((word ^ 0xffffff) + 1) as i32) + } else { + word as i32 + } +} + +fn bool_as_word(flag: bool) -> u32 { if flag { 1 } else { 0 } } + +#[cfg(test)] +mod tests { + use super::*; + use Opcode::*; + + impl CPU { + fn get_stack(&self) -> Vec { + let mut v = Vec::new(); + let mut curr = Address::from(256); + while curr < self.dp { + v.push(self.memory.peek24(curr)); + curr += 3 + } + v + } + + fn get_call(&self) -> Vec { + let mut v = Vec::new(); + let mut curr = Address::from(1024); + while curr > self.sp { + curr -= 3; + v.push(self.memory.peek24(curr)); + } + v + } + } + + fn predicate_opcode_test(opcode: Opcode, given: P, pred: Q) where P: FnOnce(&mut CPU), Q: FnOnce(&CPU) + { + let mut cpu = CPU::new(Memory::default()); + given(&mut cpu); + let new_pc = cpu.execute(Instruction{ opcode: opcode, arg: None, length: 1 }); + cpu.pc = new_pc; + pred(&mut cpu) + } + + fn simple_opcode_test(given: Vec, opcode: Opcode, expected: Vec) { + predicate_opcode_test(opcode, |cpu| { + for i in given.into_iter() { cpu.push_data(i) } + }, |cpu| { + assert_eq!(cpu.get_stack(), expected) + }) + } + + fn call_stack_opcode_test(given: Vec, given_r: Vec, opcode: Opcode, expected: Vec, expected_r: Vec, pc: Address) { + predicate_opcode_test(opcode, |cpu| { + for i in given.into_iter() { cpu.push_data(i) } + for i in given_r.into_iter() { cpu.push_call(i) } + }, |cpu| { + assert_eq!(cpu.get_stack(), expected); + assert_eq!(cpu.get_call(), expected_r); + assert_eq!(pc, cpu.pc) + }) + } + + fn control_flow_opcode_test(given: Vec, opcode: Opcode, expected_pc: A) where A: Into
{ + predicate_opcode_test(opcode, |cpu| { + for i in given.into_iter() { cpu.push_data(i) } + }, |cpu| { + assert_eq!(cpu.pc, expected_pc.into()) + }) + } + + fn memory_opcode_test(given: Vec, given_memory: Vec, opcode: Opcode, expected: Vec, expected_memory: Option>) { + predicate_opcode_test(opcode, + |cpu| { + for i in given.into_iter() { cpu.push_data(i) } + for (offset, byte) in given_memory.into_iter().enumerate() { + cpu.memory.poke(Address::from(2048 + offset as u32), byte) + } + }, + |cpu| { + if let Some(expected_memory) = expected_memory { + for (offset, byte) in expected_memory.into_iter().enumerate() { + let actual = cpu.memory.peek(Address::from(2048 + offset as u32)); + assert_eq!(byte, actual, "At address 2048 + {}", offset) + } + assert_eq!(cpu.get_stack(), expected) + } + }) + } + + fn to_word(val: i32) -> u32 { + if val >= 0 { + val as u32 + } else { + ((-val ^ 0xffffff) + 1) as u32 + } + } + + #[test] + fn test_arithmetic() { + simple_opcode_test(vec![5, 3], Add, vec![8]); + simple_opcode_test(vec![5, 3], Sub, vec![2]); + simple_opcode_test(vec![5, 3], Mul, vec![15]); + simple_opcode_test(vec![8, 3], Div, vec![2]); + simple_opcode_test(vec![10, 3], Mod, vec![1]); + } + + #[test] + fn test_stack_manipulation() { + simple_opcode_test(vec![5], Dup, vec![5, 5]); + simple_opcode_test(vec![5, 3], Swap, vec![3, 5]); + simple_opcode_test(vec![10, 20, 30, 2], Pick, vec![10, 20, 30, 10]); + simple_opcode_test(vec![1, 4, 9], Rot, vec![4, 9, 1]); + simple_opcode_test(vec![1, 4, 9], Pop, vec![1, 4]); + } + + #[test] + fn test_basic_ops() { + control_flow_opcode_test(vec![], Nop, 1025); + simple_opcode_test(vec![2], Nop, vec![2]); + predicate_opcode_test(Hlt, |_| { }, |cpu| { assert!(cpu.halted) }) + } + + #[test] + fn test_branching_jumping() { + control_flow_opcode_test(vec![1234], Jmp, 1234); + control_flow_opcode_test(vec![35], Jmpr, 1024 + 35); + control_flow_opcode_test(vec![to_word(-3)], Jmpr, 1024 - 3); + control_flow_opcode_test(vec![0, 35], Brnz, 1024 + 1); + control_flow_opcode_test(vec![17, 35], Brnz, 1024 + 35); + control_flow_opcode_test(vec![5, 35], Brz, 1024 + 1); + control_flow_opcode_test(vec![0, 35], Brz, 1024 + 35); + } + + #[test] + fn test_memory() { + memory_opcode_test(vec![2048], vec![123], Load, vec![123], None); + memory_opcode_test(vec![2048], vec![0x12, 0x34, 0x56], Loadw, vec![0x123456], None); + memory_opcode_test(vec![100, 2048], vec![0x12, 0x34, 0x56], Store, vec![], Some(vec![100, 0x34, 0x56])); + memory_opcode_test(vec![0x112233, 2048], vec![0x12, 0x34, 0x56], Storew, vec![], Some(vec![0x33, 0x22, 0x11])); + } + + #[test] + fn test_logic() { + simple_opcode_test(vec![0b111100, 0b001111], And, vec![0b001100]); + simple_opcode_test(vec![0b100, 0b001], Or, vec![0b101]); + simple_opcode_test(vec![0b101, 0b011], Xor, vec![0b110]); + simple_opcode_test(vec![5], Not, vec![0]); + simple_opcode_test(vec![0], Not, vec![1]); + simple_opcode_test(vec![5, 3], Gt, vec![1]); + simple_opcode_test(vec![5, 7], Gt, vec![0]); + simple_opcode_test(vec![5, 3], Lt, vec![0]); + simple_opcode_test(vec![5, 7], Lt, vec![1]); + simple_opcode_test(vec![5, to_word(-3)], Agt, vec![1]); + simple_opcode_test(vec![5, 10], Agt, vec![0]); + simple_opcode_test(vec![5, to_word(-3)], Alt, vec![0]); + simple_opcode_test(vec![5, 10], Alt, vec![1]); + simple_opcode_test(vec![0b1100, 2], Rshift, vec![3]); + simple_opcode_test(vec![0b1100, 2], Lshift, vec![0b110000]); + simple_opcode_test(vec![0x800010, 2], Arshift, vec![0xe00004]); + } + + #[test] + fn test_cpu_call_stack() { + call_stack_opcode_test(vec![5000], vec![], Call, vec![], vec![1025], 5000.into()); + call_stack_opcode_test(vec![], vec![5000], Ret, vec![], vec![], 5000.into()); + call_stack_opcode_test(vec![], vec![], Sdp, vec![1024, 256 + 6], vec![], 1025.into()); + predicate_opcode_test(Setsdp, + |cpu| { + cpu.push_data(1000u32); + cpu.push_data(2000u32) + }, + |cpu| { + assert_eq!(cpu.sp, 1000.into()); + assert_eq!(cpu.dp, 2000.into()) + }); + call_stack_opcode_test(vec![123], vec![], Pushr, vec![], vec![123], 1025.into()); + call_stack_opcode_test(vec![], vec![123], Popr, vec![123], vec![], 1025.into()); + call_stack_opcode_test(vec![], vec![123], Peekr, vec![123], vec![123], 1025.into()); + } + + #[test] + fn test_cpu_new() { + let cpu = CPU::new(Memory::default()); + assert_eq!(cpu.pc, 1024.into()); + assert_eq!(cpu.halted, true); + } + + #[test] + fn test_cpu_reset() { + let mut cpu = CPU::new(Memory::default()); + cpu.iv = 12345.into(); + cpu.reset(); + assert_eq!(cpu.iv, 1024.into()); + } + + #[test] + fn test_cpu_stacks() { + let mut cpu = CPU::new(Memory::default()); + cpu.push_data(37u32); + cpu.push_data(45u32); + assert_eq!(cpu.memory.peek24(256), 37); + assert_eq!(cpu.memory.peek24(259), 45); + + cpu.push_call(12u32); + cpu.push_call(34u32); + assert_eq!(cpu.memory.peek24(cpu.sp), 34); + assert_eq!(cpu.memory.peek24(cpu.sp + 3), 12); + assert_eq!(cpu.sp, (1024 - 6).into()); + assert_eq!(cpu.dp, (256 + 6).into()); + + assert_eq!(cpu.pop_data(), 45); + assert_eq!(cpu.pop_data(), 37); + assert_eq!(cpu.dp, 256.into()); + + assert_eq!(cpu.pop_call(), 34); + assert_eq!(cpu.pop_call(), 12); + assert_eq!(cpu.sp, 1024.into()); + } + + #[test] + fn test_cpu_fetch() { + let mut cpu = CPU::new(Memory::default()); + cpu.memory.poke(0x400, 0x01); // nop 1 arg + cpu.memory.poke(0x401, 0x02); // 2 + cpu.memory.poke(0x402, 0x07); // add 3 arg + cpu.memory.poke24(0x403, 0x123456); // 3-byte arg + cpu.memory.poke(0x406, 29 << 2); // hlt + cpu.memory.poke(0x407, 0xfc); // gibberish + + assert_eq!(cpu.fetch(), Ok(Instruction { opcode: Opcode::Nop, arg: Some(2), length: 2 })); + + cpu.pc = 0x402.into(); + assert_eq!(cpu.fetch(), Ok(Instruction { opcode: Opcode::Add, arg: Some(0x123456), length: 4 })); + + cpu.pc = 0x406.into(); + assert_eq!(cpu.fetch(), Ok(Instruction { opcode: Opcode::Hlt, arg: None, length: 1 })); + + cpu.pc = 0x407.into(); + assert_eq!(cpu.fetch(), Err(InvalidOpcode(0x3f))); + } +} + +fn main() { + println!("Hello, world!"); +} diff --git a/src/memory.rs b/src/memory.rs new file mode 100644 index 0000000..80da9c4 --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,70 @@ +use rand::Rng; +use crate::address::Address; +use crate::address::MEM_SIZE; + +pub struct Memory([u8; MEM_SIZE as usize]); + +impl Default for Memory { + fn default() -> Self { Self([0u8; MEM_SIZE as usize]) } +} + +impl From for Memory { + fn from(mut rng: R) -> Self { + let mut mem = Memory::default(); + for i in 0..(MEM_SIZE - 1) { + mem.0[i as usize] = rng.gen() + } + mem + } +} + +impl std::ops::Index
for Memory { + type Output = u8; + fn index(&self, index: Address) -> &Self::Output { &self.0[usize::from(index)] } +} + +impl std::ops::IndexMut
for Memory { + fn index_mut(&mut self, index: Address) -> &mut Self::Output { &mut self.0[usize::from(index)] } +} + +pub trait PeekPoke { + fn peek>(&self, addr: A) -> u8; + fn poke>(&mut self, addr: A, val: u8); + + fn peek24>(&self, addr: A) -> u32 { + let addr = addr.into(); + (self.peek(addr) as u32) | ((self.peek(addr + 1) as u32) << 8) | ((self.peek(addr + 2) as u32) << 16) + } + + fn poke24>(&mut self, addr: A, val: u32) { + let addr = addr.into(); + self.poke(addr, val as u8); + self.poke(addr + 1, (val >> 8) as u8); + self.poke(addr + 2, (val >> 16) as u8); + } +} + +impl PeekPoke for Memory { + fn peek>(&self, addr: A) -> u8 { self[addr.into()] } + fn poke>(&mut self, addr: A, val: u8) { self[addr.into()] = val; } +} + +#[test] +fn test_mem_peek_poke() { + let mut mem = Memory::default(); + assert_eq!(mem.peek(35), 0); + mem.poke(35, 45); + assert_eq!(mem.peek(35), 45); + assert_eq!(mem.peek(36), 0); +} + +#[test] +fn test_mem_word_fns() { + let mut mem = Memory::default(); + mem.poke24(10, 0x123456); + assert_eq!(mem.peek(10), 0x56); + assert_eq!(mem.peek(11), 0x34); + assert_eq!(mem.peek(12), 0x12); + assert_eq!(mem.peek24(10), 0x123456); + assert_eq!(mem.peek24(11), 0x001234); +} diff --git a/src/opcodes.rs b/src/opcodes.rs new file mode 100644 index 0000000..decdde9 --- /dev/null +++ b/src/opcodes.rs @@ -0,0 +1,120 @@ +use std::convert::TryFrom; +use std::fmt::{Display, Formatter}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Opcode { + Nop, + Add, + Sub, + Mul, + Div, + Mod, + Rand, + And, + Or, + Xor, + Not, + Gt, + Lt, + Agt, + Alt, + Lshift, + Rshift, + Arshift, + Pop, + Dup, + Swap, + Pick, + Rot, + Jmp, + Jmpr, + Call, + Ret, + Brz, + Brnz, + Hlt, + Load, + Loadw, + Store, + Storew, + Inton, + Intoff, + Setiv, + Sdp, + Setsdp, + Pushr, + Popr, + Peekr, + Debug, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct InvalidOpcode(pub u8); + +impl Display for InvalidOpcode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Invalid opcode {:#02x}", self.0) + } +} + +impl std::error::Error for InvalidOpcode {} + +impl TryFrom for Opcode { + type Error = InvalidOpcode; + + fn try_from(value: u8) -> Result { + use Opcode::*; + Ok(match value { + 0 => Nop, + 1 => Add, + 2 => Sub, + 3 => Mul, + 4 => Div, + 5 => Mod, + 6 => Rand, + 7 => And, + 8 => Or, + 9 => Xor, + 10 => Not, + 11 => Gt, + 12 => Lt, + 13 => Agt, + 14 => Alt, + 15 => Lshift, + 16 => Rshift, + 17 => Arshift, + 18 => Pop, + 19 => Dup, + 20 => Swap, + 21 => Pick, + 22 => Rot, + 23 => Jmp, + 24 => Jmpr, + 25 => Call, + 26 => Ret, + 27 => Brz, + 28 => Brnz, + 29 => Hlt, + 30 => Load, + 31 => Loadw, + 32 => Store, + 33 => Storew, + 34 => Inton, + 35 => Intoff, + 36 => Setiv, + 37 => Sdp, + 38 => Setsdp, + 39 => Pushr, + 40 => Popr, + 41 => Peekr, + 42 => Debug, + other => return Err(InvalidOpcode(other)) + }) + } +} + +#[test] +fn test_decode() { + assert_eq!(Opcode::try_from(18), Ok(Opcode::Pop)); + //assert_eq!(str::fmt("{}", Opcode::try_from(136).unwrap_err()), Err(InvalidOpcode(136))); +}