use crate::memory::PeekPoke; use crate::memory::{Memory, PeekPokeExt}; use crate::opcodes::InvalidOpcode; use crate::opcodes::Opcode; use crate::word::Word; use std::convert::TryFrom; #[allow(clippy::upper_case_acronyms)] pub struct CPU { memory: Memory, // Main memory, all of it pc: Word, // program counter, address of the low byte of the instruction dp: Word, // data pointer, address of the low byte of one cell above the data stack sp: Word, // stack pointer, address of the low byte of the return stack iv: [Word; 16], // interrupt vectors int_enabled: bool, // interrupt enable bit halted: bool, // Whether the CPU is halted sp_top: Word, // The last value given for the sp, or 0x400 dp_btm: Word, // The last value given for the dp, or 0x100 } #[derive(Copy, Clone, Eq, PartialEq, Debug)] struct Instruction { opcode: Opcode, arg: Option, length: u8, } #[derive(Copy, Clone, Eq, PartialEq, Debug)] enum ExecutionError { DivZero, DataUnderflow, StackUnderflow, Overflow, InvalidOpcode, } impl PeekPoke for CPU { fn peek(&self, addr: Word) -> u8 { self.memory.peek(addr) } fn poke(&mut self, addr: Word, val: u8) { self.memory.poke(addr, val) } } impl Default for CPU { fn default() -> Self { Self::new(Memory::default()) } } impl CPU { pub fn new(memory: Memory) -> Self { Self { memory, pc: 1024.into(), dp: 256.into(), sp: 1024.into(), iv: [1024.into(); 16], int_enabled: false, halted: true, sp_top: 0x400.into(), dp_btm: 0x100.into(), } } pub fn new_random() -> Self { let rng = rand::thread_rng(); Self::new(Memory::from(rng)) } pub fn reset(&mut self) { self.pc = 1024.into(); self.dp = 256.into(); self.sp = 1024.into(); self.iv = [1024.into(); 16]; self.int_enabled = false; self.halted = true; self.sp_top = 0x400.into(); self.dp_btm = 0x100.into(); } pub fn push_data>(&mut self, word: A) { self.memory.poke24(self.dp, word); self.dp += 3; } pub fn push_call>(&mut self, word: A) { self.sp -= 3; self.memory.poke24(self.sp, word); } pub fn pop_data(&mut self) -> Word { self.dp -= 3; self.memory.peek24(self.dp) } pub fn pop_call(&mut self) -> Word { let val = self.memory.peek24(self.sp); self.sp += 3; val } fn peek_call(&self) -> Word { self.memory.peek24(self.sp) } fn peek_data(&self) -> Word { 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, 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 <<= 8 * n; arg += b; } Ok(Instruction { opcode, arg: Some(Word::from(arg)), length: arg_length + 1, }) } } Err(e) => Err(e), } } #[allow(clippy::cmp_owned)] // This is needed because clippy can't figure out, on alt / agt, that even though yes you can // directly compare two Words, that means something different than comparing the i32s those // Words represent. That difference is the entire point of the agt / alt instructions. fn execute(&mut self, instruction: Instruction) -> Word { if let Some(arg) = instruction.arg { self.push_data(arg) } if instruction.opcode.arity() == 2 { 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(i32::from(y) / i32::from(x)), Opcode::Mod => self.push_data(i32::from(y) % i32::from(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(y > x), Opcode::Lt => self.push_data(y < x), Opcode::Agt => self.push_data(i32::from(y) > i32::from(x)), Opcode::Alt => self.push_data(i32::from(y) < i32::from(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..u32::from(x).clamp(0, 24) { 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.poke8(x, y.to_bytes()[0]), Opcode::Storew => self.memory.poke24(x, y), Opcode::Setsdp => { self.dp = x; self.sp = y; self.dp_btm = self.dp; self.sp_top = self.sp } Opcode::Brz => { if y == 0 { return self.pc + i32::from(x); } } Opcode::Brnz => { if y != 0 { return self.pc + i32::from(x); } } Opcode::Setiv => { self.iv[usize::from(x % 16)] = y; } _ => unreachable!(), } self.pc + instruction.length as i32 } else { match instruction.opcode { Opcode::Nop => { /* No action required */ } Opcode::Copy => { let dest = self.pop_data(); let src = self.pop_data(); let len = self.pop_data(); self.copy_region(len, dest, src); } Opcode::Not => { let x = self.pop_data(); self.push_data(x == 0) } Opcode::Pop => { self.pop_data(); } Opcode::Dup => self.push_data(self.peek_data()), Opcode::Pick => { let index = self.pop_data(); let addr = self.dp - (i32::from(index) + 1) * 3; if addr >= self.dp_btm { self.push_data(self.memory.peek24(addr)); } else { self.push_data(0); } } 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(), Opcode::Jmpr => { let x = i32::from(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; } Opcode::Ret => return self.pop_call(), Opcode::Hlt => self.halted = true, Opcode::Load => { let x = self.pop_data(); self.push_data(self.memory.peek8(x)) } Opcode::Loadw => { let x = self.pop_data(); self.push_data(self.memory.peek24(x)) } Opcode::Setint => { let x = self.pop_data(); self.int_enabled = x != 0; } 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 => { for v in self.get_stack().iter() { println!("{:#08x}", u32::from(*v)); } } _ => {} // This can never happen } self.pc + instruction.length as i32 } } /// Checks if an instruction would cause an error if executed, and returns it if so. /// There are four possible error conditions, evaluated in order: /// /// - If an instruction requires more stack space after execution than exists /// - If an instruction would pop more from the data stack than is on it /// - If an instruction would pop more from the return stack than is on it /// - If an instruction would divide (or modulus) by zero /// /// So, running `div` on a stack containing a single 0 is a `DataUnderflow`, not a `DivZero`. /// /// Also, this function doesn't consider the requirements for actually handling the error, which /// could change a `DivZero` into an `Overflow`: handling any error consumes one more stack cell, /// for the new return address when calling the interrupt handler. So, although this may /// return `DivZero` the actual error thrown may be `Overflow`. fn error(&self, instruction: Instruction) -> Option { use ExecutionError::*; use Opcode::{Div, Mod}; let Instruction { opcode, arg, .. } = instruction; let data_height: usize = usize::from(self.dp - self.dp_btm) / 3; let stack_height: usize = usize::from(self.sp_top - self.sp) / 3; let room: i32 = i32::from(self.sp - self.dp) / 3; let net = opcode.net_stack() + arg.map_or(0, |_| 1); // If we're out of room, any instruction with an arg is out. Also, if the total amount // we'll add to the stack (including arg) overflows, then we overflow if arg.is_some() && room < 1 || net > room { return Some(Overflow); } // Figure out if there might be an underflow let provided_args = data_height + if arg.is_some() { 1 } else { 0 }; // Arity taller than data stack? if opcode.arity() > provided_args { return Some(DataUnderflow); } // Underflowing the rstack? if opcode.r_arity() > stack_height { return Some(StackUnderflow); } // The top of the stack during the instruction let top = arg.unwrap_or_else(|| self.peek_data()); // Dividing by zero? if (opcode == Div || opcode == Mod) && top == 0u32 { return Some(DivZero); } None } fn handle_error(&mut self, error: ExecutionError) -> Word { // If the error is not an overflow then we still might turn it into one: we require one new // stack frame to handle every non-overflow error (for the new return stack cell) so if we // don't have that we'll just pretend this is an overflow. if self.dp >= self.sp && error != ExecutionError::Overflow { return self.handle_error(ExecutionError::Overflow); } // The procedure for handling an overflow is a bit different, because we need to have some // stack available to do it. First, we reset the stack to its original size, and then we // put the old sp and dp on the data stack: if error == ExecutionError::Overflow { let (old_sp, old_dp) = (self.sp, self.dp); (self.sp, self.dp) = (self.sp_top, self.dp_btm); self.push_data(old_sp); self.push_data(old_dp); } // Now we have room to handle whatever this is, so, handle it: let int = match error { ExecutionError::DivZero => 0, ExecutionError::DataUnderflow => 1, ExecutionError::StackUnderflow => 2, ExecutionError::Overflow => 3, ExecutionError::InvalidOpcode => 4, }; self.int_enabled = false; self.push_call(self.pc); self.iv[int] } pub fn tick(&mut self) { if self.halted { return; } match self.fetch() { Ok(instr) => { //if cfg!(debug_assertions) { //self.debug_instr(instr); //} self.pc = if let Some(err) = self.error(instr) { self.handle_error(err) } else { self.execute(instr) } } Err(_) => self.pc = self.handle_error(ExecutionError::InvalidOpcode), } } fn debug_instr(&self, instr: Instruction) { let stack_str: Vec = self .get_stack() .iter() .map(|w| format!("{:#08x} ", u32::from(*w))) .collect(); let call_str: Vec = self .get_call() .iter() .map(|w| format!("{:#08x} ", u32::from(*w))) .collect(); let arg_str = instr .arg .map_or(String::from(""), |w| format!("{:#08x}", u32::from(w))); println!( "{:#8x}: {} <{}> ( {}) [ {}]", u32::from(self.pc), instr.opcode, arg_str, stack_str.join(" "), call_str.join(" ") ); } fn copy_region(&mut self, len: Word, dest: Word, src: Word) { // Degenerate cases: if dest == src || len == 0 { return } // Now, check if we're moving to a greater or lesser address: if dest > src { // If the dest is greater, we need to move from the end of the range backwards, // in case there's overlap: for offset in (0..len.into()).rev() { self.memory.poke(dest + offset, self.memory.peek(src + offset)) } } else { // Dest is before src, so we'll move from low index to high, in case of overlap: for offset in 0..len.into() { self.memory.poke(dest + offset, self.memory.peek(src + offset)) } } } pub fn interrupt(&mut self, irq: usize, arg: Option) { if irq >= self.iv.len() { panic!("Invalid interrupt: {}", irq) } if self.int_enabled { if self.halted { self.start() } self.int_enabled = false; let room = self.sp - self.dp; self.pc = if room == 0 || room == 1 && arg.is_some() { self.handle_error(ExecutionError::Overflow) } else { self.push_call(self.pc); if let Some(val) = arg { self.push_data(val) } self.iv[irq] } } } pub fn start(&mut self) { self.halted = false } pub fn running(&self) -> bool { !self.halted } pub fn run_to_halt(&mut self) { self.start(); while !self.halted { self.tick() } } pub fn get_stack(&self) -> Vec { let mut v = Vec::new(); let mut curr = self.dp_btm; while curr < self.dp { v.push(self.memory.peek24(curr)); curr += 3 } v } pub fn get_call(&self) -> Vec { let mut v = Vec::new(); let mut curr = self.sp_top; while curr > self.sp { curr -= 3; v.push(self.memory.peek24(curr)); } v } pub fn sdp(&self) -> (Word, Word) { (self.sp, self.dp) } pub fn pc(&self) -> Word { self.pc } pub fn sp(&self) -> Word { self.sp } pub fn dp(&self) -> Word { self.dp } pub fn dp_btm(&self) -> Word { self.dp_btm } pub fn sp_top(&self) -> Word { self.sp_top } pub fn set_pc(&mut self, new_pc: Word) { self.pc = new_pc; } pub fn halted(&self) -> bool { self.halted } pub fn int_enabled(&self) -> bool { self.int_enabled } } #[cfg(test)] mod tests { use super::*; use Opcode::*; 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, 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: Word, ) { 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(Word::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(Word::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]); simple_opcode_test(vec![12, to_word(-3)], Div, vec![to_word(-4)]); } #[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![10, 0], Pick, vec![10, 10]); simple_opcode_test(vec![10, 1], Pick, vec![10, 0]); 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![to_word(-3), 5], Agt, vec![0]); simple_opcode_test(vec![5, 10], Agt, vec![0]); simple_opcode_test(vec![5, to_word(-3)], Alt, vec![0]); simple_opcode_test(vec![to_word(-3), 5], Alt, vec![1]); 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_copy() { // No overlap, forwards memory_opcode_test( vec![3, 2048, 2051], vec![1, 2, 3, 4, 5, 6], Copy, vec![], Some(vec![1, 2, 3, 1, 2, 3]), ); // No overlap, backwards memory_opcode_test( vec![3, 2051, 2048], vec![1, 2, 3, 4, 5, 6], Copy, vec![], Some(vec![4, 5, 6, 4, 5, 6]), ); // Overlap, move forward memory_opcode_test( vec![3, 2048, 2050], vec![1, 2, 3, 4, 5, 6], Copy, vec![], Some(vec![1, 2, 1, 2, 3, 6]), ); // Overlap, move backwards memory_opcode_test( vec![3, 2051, 2049], vec![1, 2, 3, 4, 5, 6], Copy, vec![], Some(vec![1, 4, 5, 6, 5, 6]), ); // Degenerate cases memory_opcode_test( vec![0, 2051, 2049], vec![1, 2, 3, 4, 5, 6], Copy, vec![], Some(vec![1, 2, 3, 4, 5, 6]), ); memory_opcode_test( vec![5, 2048, 2048], vec![1, 2, 3, 4, 5, 6], Copy, vec![], Some(vec![1, 2, 3, 4, 5, 6]), ); } #[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); assert_eq!(cpu.dp, 2000) }, ); 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); assert!(cpu.halted); } #[test] fn test_cpu_reset() { let mut cpu = CPU::new(Memory::default()); cpu.iv[2] = 12345.into(); cpu.reset(); assert_eq!(cpu.iv[2], 1024); } #[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); assert_eq!(cpu.dp, 256 + 6); assert_eq!(cpu.pop_data(), 45); assert_eq!(cpu.pop_data(), 37); assert_eq!(cpu.dp, 256); assert_eq!(cpu.pop_call(), 34); assert_eq!(cpu.pop_call(), 12); assert_eq!(cpu.sp, 1024); } #[test] fn test_cpu_fetch() { let mut cpu = CPU::new(Memory::default()); cpu.memory.poke8(0x400, 0x01); // nop 1 arg cpu.memory.poke8(0x401, 0x02); // 2 cpu.memory.poke8(0x402, 0x07); // add 3 arg cpu.memory.poke24(0x403, 0x123456); // 3-byte arg cpu.memory.poke8(0x406, 29 << 2); // hlt cpu.memory.poke8(0x407, 0xfc); // gibberish assert_eq!( cpu.fetch(), Ok(Instruction { opcode: Opcode::Nop, arg: Some(Word::from(2)), length: 2 }) ); cpu.pc = 0x402.into(); assert_eq!( cpu.fetch(), Ok(Instruction { opcode: Opcode::Add, arg: Some(Word::from(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))); } }