From 0bdf20e5a782fa5e415f40bf60740dda638a1bea Mon Sep 17 00:00:00 2001 From: Murad Date: Wed, 2 Feb 2022 11:36:17 +0200 Subject: [PATCH] implement all instructions with 100% test coverage Also replace RESET with HALT. --- DESIGN.md | 4 +- src/cpu.rs | 1610 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/instruction.rs | 235 ++++++++ src/main.rs | 144 +---- 4 files changed, 1855 insertions(+), 138 deletions(-) create mode 100644 src/cpu.rs create mode 100644 src/instruction.rs diff --git a/DESIGN.md b/DESIGN.md index 33979a3..d2442f9 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -319,7 +319,7 @@ If `R1` value is greater than 8, `R0` is zeroed. +—+—+—+—+—+—+—+—+ ``` -### RESET +### HALT ``` +—+—+—+—+—+—+—+—+ @@ -327,7 +327,7 @@ If `R1` value is greater than 8, `R0` is zeroed. +—+—+—+—+—+—+—+—+ ``` -Resets CPU registers and memory and zeroes `IP`. +Stops CPU execution. ### CLOAD diff --git a/src/cpu.rs b/src/cpu.rs new file mode 100644 index 0000000..4f6e55c --- /dev/null +++ b/src/cpu.rs @@ -0,0 +1,1610 @@ +// SPDX-License-Identifier: MIT +// Copyright Murad Karammaev, Nikita Kuzmin + +use crate::instruction::*; + +use std::collections::VecDeque; +use std::num::Wrapping; +use std::{cmp, mem}; +use ux::{u3, u4}; + +#[allow(non_snake_case)] +pub struct CPU { + IP: Wrapping, + R0: Wrapping, + R1: Wrapping, + C: bool, + code: [u8; 0x100], + data: [u8; 0x010], + port_in: VecDeque, + port_out: VecDeque, +} + +impl CPU { + pub fn new(code: &[u8]) -> CPU { + let num_instructions = cmp::min(0x100, code.len()); + let mut new_code = [0u8; 0x100]; + for i in 0..num_instructions { + new_code[i] = code[i]; + } + CPU { + IP: Wrapping(0u8), + R0: Wrapping(0u8), + R1: Wrapping(0u8), + C: false, + code: new_code, + data: [0u8; 0x010], + port_in: VecDeque::::new(), + port_out: VecDeque::::new(), + } + } + + fn load(&mut self, reg: Register, addr: u4) { + match reg { + Register::R0 => self.R0 = Wrapping(self.data[u8::from(addr) as usize]), + Register::R1 => self.R1 = Wrapping(self.data[u8::from(addr) as usize]), + } + self.IP += Wrapping(1u8); + } + + fn store(&mut self, reg: Register, addr: u4) { + match reg { + Register::R0 => self.data[u8::from(addr) as usize] = self.R0.0, + Register::R1 => self.data[u8::from(addr) as usize] = self.R1.0, + } + self.IP += Wrapping(1u8); + } + + fn apply_imm(low: bool, mut reg: u8, val: u4) -> Wrapping { + if low { + reg &= 0b11110000; + reg |= u8::from(val); + } else { + reg &= 0b00001111; + reg |= u8::from(val) << 4; + } + Wrapping(reg) + } + + fn load_imm(&mut self, low: bool, reg: Register, val: u4) { + match reg { + Register::R0 => self.R0 = CPU::apply_imm(low, self.R0.0, val), + Register::R1 => self.R1 = CPU::apply_imm(low, self.R1.0, val), + } + self.IP += Wrapping(1u8); + } + + fn near_jump(&mut self, use_carry: bool, forward: bool, offset: u8) { + if use_carry && !self.C { + self.IP += Wrapping(1u8); + return; + } + if forward { + self.IP += Wrapping(offset); + } else { + self.IP -= Wrapping(offset); + } + } + + fn far_jump(&mut self, use_carry: bool, reg: Register) { + if use_carry && !self.C { + self.IP += Wrapping(1u8); + return; + } + self.IP = match reg { + Register::R0 => self.R0, + Register::R1 => self.R1, + }; + } + + fn cmp_equal(&mut self) { + self.C = self.R0 == self.R1; + self.IP += Wrapping(1u8); + } + + fn cmp_gt(&mut self) { + self.C = self.R0 > self.R1; + self.IP += Wrapping(1u8); + } + + fn cmp_le(&mut self) { + self.C = self.R0 < self.R1; + self.IP += Wrapping(1u8); + } + + fn not(&mut self) { + self.C = !self.C; + self.IP += Wrapping(1u8); + } + + fn bit_shift_logical(&mut self, right: bool, reg: Register, len: u8) { + match len { + 0..=7 => match reg { + Register::R0 => { + self.R0 = Wrapping(if right { + self.R0.0 >> len + } else { + self.R0.0 << len + }) + } + Register::R1 => { + self.R1 = Wrapping(if right { + self.R1.0 >> len + } else { + self.R1.0 << len + }) + } + }, + _ => match reg { + Register::R0 => self.R0 = Wrapping(0), + Register::R1 => self.R1 = Wrapping(0), + }, + } + } + + fn bit_shift_circular(&mut self, right: bool, reg: Register, len: u32) { + match len { + 0..=7 => match reg { + Register::R0 => { + self.R0 = Wrapping(if right { + self.R0.0.rotate_right(len) + } else { + self.R0.0.rotate_left(len) + }) + } + Register::R1 => { + self.R1 = Wrapping(if right { + self.R1.0.rotate_right(len) + } else { + self.R1.0.rotate_left(len) + }) + } + }, + _ => match reg { + Register::R0 => self.R0 = Wrapping(0), + Register::R1 => self.R1 = Wrapping(0), + }, + } + } + + fn bit_shift(&mut self, right: bool, reg: Register, mode: ShiftMode, len: u3) { + match mode { + ShiftMode::Logical => self.bit_shift_logical(right, reg, u8::from(len)), + ShiftMode::Circular => self.bit_shift_circular(right, reg, u32::from(len)), + } + self.IP += Wrapping(1u8); + } + + fn inc(&mut self, reg: Register) { + match reg { + Register::R0 => { + self.R0 = self.R0 + Wrapping(1u8); + self.C = self.R0 == Wrapping(0u8); + } + Register::R1 => { + self.R1 = self.R1 + Wrapping(1u8); + self.C = self.R1 == Wrapping(0u8); + } + } + self.IP += Wrapping(1u8); + } + + fn dec(&mut self, reg: Register) { + match reg { + Register::R0 => { + self.R0 = self.R0 - Wrapping(1u8); + self.C = self.R0 == Wrapping(0u8); + } + Register::R1 => { + self.R1 = self.R1 - Wrapping(1u8); + self.C = self.R1 == Wrapping(0u8); + } + } + self.IP += Wrapping(1u8); + } + + fn add(&mut self) { + self.C = self.R0.0 as u16 + self.R1.0 as u16 > 0xFF; + self.R0 = self.R0 + self.R1; + self.IP += Wrapping(1u8); + } + + fn sub(&mut self) { + self.C = self.R1 > self.R0; + self.R0 = self.R0 - self.R1; + self.IP += Wrapping(1u8); + } + + fn and(&mut self) { + self.R0 = self.R0 & self.R1; + self.IP += Wrapping(1u8); + } + + fn or(&mut self) { + self.R0 = self.R0 | self.R1; + self.IP += Wrapping(1u8); + } + + fn xor(&mut self) { + self.R0 = self.R0 ^ self.R1; + self.IP += Wrapping(1u8); + } + + fn xnor(&mut self) { + self.R0 = !(self.R0 ^ self.R1); + self.IP += Wrapping(1u8); + } + + fn complement(&mut self, reg: Register) { + match reg { + Register::R0 => self.R0 = !self.R0, + Register::R1 => self.R1 = !self.R1, + } + self.IP += Wrapping(1u8); + } + + fn bit_shift_var(&mut self, right: bool, mode: ShiftMode) { + match mode { + ShiftMode::Logical => self.bit_shift_logical(right, Register::R0, self.R1.0), + ShiftMode::Circular => self.bit_shift_circular(right, Register::R0, self.R1.0 as u32), + } + self.C = self.R0 == Wrapping(0u8); + self.IP += Wrapping(1u8); + } + + fn copy(&mut self, reg_from: Register) { + match reg_from { + Register::R0 => self.R1 = self.R0, + Register::R1 => self.R0 = self.R1, + } + self.IP += Wrapping(1u8); + } + + fn setc(&mut self, carry: bool) { + self.C = carry; + self.IP += Wrapping(1u8); + } + + fn nop(&mut self) { + self.IP += Wrapping(1u8); + } + + fn cload(&mut self, reg: Register) { + match reg { + Register::R0 => self.R0 = Wrapping(self.code[self.R0.0 as usize]), + Register::R1 => self.R1 = Wrapping(self.code[self.R0.0 as usize]), + } + self.IP += Wrapping(1u8); + } + + fn zero(&mut self, reg: Register) { + match reg { + Register::R0 => self.R0 = Wrapping(0u8), + Register::R1 => self.R1 = Wrapping(0u8), + } + self.IP += Wrapping(1u8); + } + + fn div(&mut self) { + if self.R1 == Wrapping(0u8) { + self.R0 = Wrapping(0xFFu8); + self.R1 = Wrapping(0u8); + self.C = true; + } else { + let q = self.R0.0 / self.R1.0; + let r = self.R0.0 % self.R1.0; + self.R0 = Wrapping(q); + self.R1 = Wrapping(r); + self.C = false; + } + self.IP += Wrapping(1u8); + } + + fn mul(&mut self) { + let p = self.R0.0 as u16 * self.R1.0 as u16; + self.R0 = Wrapping((p >> 8 & 0xFF) as u8); + self.R1 = Wrapping((p & 0xFF) as u8); + self.IP += Wrapping(1u8); + } + + fn swap(&mut self) { + mem::swap(&mut self.R0, &mut self.R1); + self.IP += Wrapping(1u8); + } + + fn port_ready(&mut self) { + self.C = self.port_in.len() > 0; + self.IP += Wrapping(1u8); + } + + fn read_port(&mut self) { + match self.port_in.pop_front() { + Some(v) => self.R0 = Wrapping(v), + None => self.R0 = Wrapping(0u8), + } + self.IP += Wrapping(1u8); + } + + fn write_port(&mut self) { + self.port_out.push_back(self.R0.0); + self.IP += Wrapping(1u8); + } + + pub fn step(&mut self) -> bool { + let insn = decode(self.code[self.IP.0 as usize]); + match insn { + Instruction::Load { reg, addr } => self.load(reg, addr), + Instruction::Store { reg, addr } => self.store(reg, addr), + Instruction::LoadImmediate { low, reg, val } => self.load_imm(low, reg, val), + Instruction::NearJumpBackward { use_carry, offset } => { + self.near_jump(use_carry, false, u8::from(offset)) + } + Instruction::NearJumpForward { use_carry, offset } => { + self.near_jump(use_carry, true, u8::from(offset)) + } + Instruction::FarJump { reg, use_carry } => self.far_jump(use_carry, reg), + Instruction::CmpEqual => self.cmp_equal(), + Instruction::CmpGreater => self.cmp_gt(), + Instruction::CmpLess => self.cmp_le(), + Instruction::Not => self.not(), + Instruction::BitShift { + right, + reg, + mode, + len, + } => self.bit_shift(right, reg, mode, len), + Instruction::Inc { reg } => self.inc(reg), + Instruction::Dec { reg } => self.dec(reg), + Instruction::Add => self.add(), + Instruction::Sub => self.sub(), + Instruction::And => self.and(), + Instruction::Or => self.or(), + Instruction::Xor => self.xor(), + Instruction::Xnor => self.xnor(), + Instruction::Complement { reg } => self.complement(reg), + Instruction::BitShiftVar { right, mode } => self.bit_shift_var(right, mode), + Instruction::Copy { reg_from } => self.copy(reg_from), + Instruction::Setc { carry } => self.setc(carry), + Instruction::Nop => self.nop(), + Instruction::Halt => return true, + Instruction::Cload { reg } => self.cload(reg), + Instruction::Zero { reg } => self.zero(reg), + Instruction::Div => self.div(), + Instruction::Mul => self.mul(), + Instruction::Swap => self.swap(), + Instruction::PortReady => self.port_ready(), + Instruction::ReadPort => self.read_port(), + Instruction::WritePort => self.write_port(), + } + false + } +} + +#[cfg(test)] +mod tests { + use crate::CPU; + use std::num::Wrapping; + + #[test] + fn insn_load_r0() { + let mut cpu = CPU::new(&[0b00000001]); + cpu.data[0b1] = 0x45; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.R0.0, 0x45); // data is loaded + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_load_r1() { + let mut cpu = CPU::new(&[0b00010101]); + cpu.data[0b101] = 0x54; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.R1.0, 0x54); // data is loaded + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_load_r0_r1() { + let mut cpu = CPU::new(&[0b00001011, 0b00011100]); + cpu.data[0b1011] = 0x12; + cpu.data[0b1100] = 0x78; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.R0.0, 0x12); // data is loaded + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.R1.0, 0x78); // data is loaded + assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_store_r0() { + let mut cpu = CPU::new(&[0b00100100]); + cpu.R0 = Wrapping(0x42); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.data[0b0100], 0x42); // data is stored + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_store_r1() { + let mut cpu = CPU::new(&[0b00110010]); + cpu.R1 = Wrapping(0x29); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.data[0b0010], 0x29); // data is stored + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_store_r0_r1() { + let mut cpu = CPU::new(&[0b00101111, 0b00110010]); + cpu.R0 = Wrapping(0xA4); + cpu.R1 = Wrapping(0xB3); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.data[0b1111], 0xA4); // data is stored + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.data[0b0010], 0xB3); // data is stored + assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_store_and_load() { + let mut cpu = CPU::new(&[0b00100101, 0b00010101]); + cpu.R0 = Wrapping(0xF3); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.data[0b0101], 0xF3); // data is stored + } + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.R1.0, 0xF3); // same data is loaded + } + } + + #[test] + fn insn_load_imm_r0() { + let mut cpu = CPU::new(&[0b01000101, 0b01100101]); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.R0.0, 0b01010101); // data is loaded + assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_load_imm_r1() { + let mut cpu = CPU::new(&[0b01010110, 0b01111001]); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.R1.0, 0b10010110); // data is loaded + assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_load_imm_low_only() { + let mut cpu = CPU::new(&[0b01001111]); + cpu.R0 = Wrapping(0b10101010); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.R0.0, 0b10101111); // data is loaded correctly + } + } + + #[test] + fn insn_near_jump_backward_unconditional() { + let mut cpu = CPU::new(&[]); + cpu.code[20] = 0b10001010; + cpu.IP = Wrapping(20); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 18); // instruction pointer had advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_near_jump_backward_conditional_must() { + let mut cpu = CPU::new(&[]); + cpu.code[40] = 0b10001110; + cpu.IP = Wrapping(40); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 38); // instruction pointer had advanced + assert_eq!(cpu.C, true); // carry is not affected + } + } + + #[test] + fn insn_near_jump_backward_conditional_must_not() { + let mut cpu = CPU::new(&[]); + cpu.code[40] = 0b10001110; + cpu.IP = Wrapping(40); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 41); // instruction pointer had advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_near_jump_forward_unconditional() { + let mut cpu = CPU::new(&[0b10010101]); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0b101); // instruction pointer had advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_near_jump_forward_conditional_must() { + let mut cpu = CPU::new(&[0b10011011]); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0b011); // instruction pointer had advanced + assert_eq!(cpu.C, true); // carry is not affected + } + } + + #[test] + fn insn_near_jump_forward_conditional_must_not() { + let mut cpu = CPU::new(&[0b10011011]); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 1); // instruction pointer had advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_far_jump_unconditional_r0() { + let mut cpu = CPU::new(&[0b10000000]); + cpu.R0 = Wrapping(0xA0); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0xA0); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_far_jump_unconditional_r1() { + let mut cpu = CPU::new(&[0b10000010]); + cpu.R1 = Wrapping(0xC7); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0xC7); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_far_jump_conditional_must_r0() { + let mut cpu = CPU::new(&[0b10000001]); + cpu.R0 = Wrapping(0x2B); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x2B); // instruction pointer has advanced + assert_eq!(cpu.C, true); // carry is not affected + } + } + + #[test] + fn insn_far_jump_conditional_must_r1() { + let mut cpu = CPU::new(&[0b10000011]); + cpu.R1 = Wrapping(0x0A); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x0A); // instruction pointer has advanced + assert_eq!(cpu.C, true); // carry is not affected + } + } + + #[test] + fn insn_far_jump_conditional_must_not_r0() { + let mut cpu = CPU::new(&[0b10000001]); + cpu.R0 = Wrapping(0x44); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_far_jump_conditional_must_not_r1() { + let mut cpu = CPU::new(&[0b10000011]); + cpu.R1 = Wrapping(0x88); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_equal_true() { + let mut cpu = CPU::new(&[0b10000100]); + cpu.R0 = Wrapping(0xC2); + cpu.R1 = Wrapping(0xC2); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_equal_false() { + let mut cpu = CPU::new(&[0b10000100]); + cpu.R0 = Wrapping(0xC2); + cpu.R1 = Wrapping(0xC1); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_greater_true() { + let mut cpu = CPU::new(&[0b10000101]); + cpu.R0 = Wrapping(0xF4); + cpu.R1 = Wrapping(0xD3); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_greater_false_eq() { + let mut cpu = CPU::new(&[0b10000101]); + cpu.R0 = Wrapping(0xD3); + cpu.R1 = Wrapping(0xD3); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_greater_false() { + let mut cpu = CPU::new(&[0b10000101]); + cpu.R0 = Wrapping(0xD3); + cpu.R1 = Wrapping(0xF4); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_less_true() { + let mut cpu = CPU::new(&[0b10000110]); + cpu.R0 = Wrapping(0xD3); + cpu.R1 = Wrapping(0xF4); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_less_false_eq() { + let mut cpu = CPU::new(&[0b10000110]); + cpu.R0 = Wrapping(0xD3); + cpu.R1 = Wrapping(0xD3); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_less_false() { + let mut cpu = CPU::new(&[0b10000110]); + cpu.R0 = Wrapping(0xF4); + cpu.R1 = Wrapping(0xD3); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_not_from_true() { + let mut cpu = CPU::new(&[0b10000111]); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_not_from_false() { + let mut cpu = CPU::new(&[0b10000111]); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_right_shift_zero_r0() { + let mut cpu = CPU::new(&[0b10100000]); + cpu.R0 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10011101); // data is not shifted + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_right_shift_r0() { + let mut cpu = CPU::new(&[0b10100010]); + cpu.R0 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b00100111); // data is shifted + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_right_shift_zero_r1() { + let mut cpu = CPU::new(&[0b10110000]); + cpu.R1 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b10011101); // data is not shifted + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_right_shift_r1() { + let mut cpu = CPU::new(&[0b10110010]); + cpu.R1 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b00100111); // data is shifted + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_right_rotate_zero_r0() { + let mut cpu = CPU::new(&[0b10101000]); + cpu.R0 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10011101); // data is not rotated + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_right_rotate_r0() { + let mut cpu = CPU::new(&[0b10101010]); + cpu.R0 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01100111); // data is rotated + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_right_rotate_zero_r1() { + let mut cpu = CPU::new(&[0b10111000]); + cpu.R1 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b10011101); // data is not rotated + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_right_rotate_r1() { + let mut cpu = CPU::new(&[0b10111010]); + cpu.R1 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b01100111); // data is rotated + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_left_shift_zero_r0() { + let mut cpu = CPU::new(&[0b11000000]); + cpu.R0 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10011101); // data is not shifted + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_left_shift_r0() { + let mut cpu = CPU::new(&[0b11000010]); + cpu.R0 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110100); // data is shifted + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_left_shift_zero_r1() { + let mut cpu = CPU::new(&[0b11010000]); + cpu.R1 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b10011101); // data is not shifted + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_left_shift_r1() { + let mut cpu = CPU::new(&[0b11010010]); + cpu.R1 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b01110100); // data is shifted + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_left_rotate_zero_r0() { + let mut cpu = CPU::new(&[0b11001000]); + cpu.R0 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10011101); // data is not rotated + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_left_rotate_r0() { + let mut cpu = CPU::new(&[0b11001010]); + cpu.R0 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110110); // data is rotated + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_left_rotate_zero_r1() { + let mut cpu = CPU::new(&[0b11011000]); + cpu.R1 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b10011101); // data is not rotated + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_left_rotate_r1() { + let mut cpu = CPU::new(&[0b11011010]); + cpu.R1 = Wrapping(0b10011101); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0b01110110); // data is rotated + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_inc_carry_false_r0() { + let mut cpu = CPU::new(&[0b11100000]); + cpu.R0 = Wrapping(68); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 69); // data is incremented + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_inc_carry_true_r0() { + let mut cpu = CPU::new(&[0b11100000]); + cpu.R0 = Wrapping(255); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // data is incremented + assert_eq!(cpu.C, true); // carry is unset + } + } + + #[test] + fn insn_inc_carry_false_r1() { + let mut cpu = CPU::new(&[0b11100001]); + cpu.R1 = Wrapping(68); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 69); // data is incremented + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_inc_carry_true_r1() { + let mut cpu = CPU::new(&[0b11100001]); + cpu.R1 = Wrapping(255); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0); // data is incremented + assert_eq!(cpu.C, true); // carry is unset + } + } + + #[test] + fn insn_dec_carry_false_r0() { + let mut cpu = CPU::new(&[0b11100010]); + cpu.R0 = Wrapping(68); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 67); // data is incremented + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_dec_carry_true_r0() { + let mut cpu = CPU::new(&[0b11100010]); + cpu.R0 = Wrapping(1); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // data is incremented + assert_eq!(cpu.C, true); // carry is unset + } + } + + #[test] + fn insn_dec_carry_false_r1() { + let mut cpu = CPU::new(&[0b11100011]); + cpu.R1 = Wrapping(68); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 67); // data is incremented + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_dec_carry_true_r1() { + let mut cpu = CPU::new(&[0b11100011]); + cpu.R1 = Wrapping(1); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0); // data is incremented + assert_eq!(cpu.C, true); // carry is unset + } + } + + #[test] + fn insn_add() { + let mut cpu = CPU::new(&[0b11100100]); + cpu.R0 = Wrapping(0x12); + cpu.R1 = Wrapping(0x34); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x46); // destination data is correct + assert_eq!(cpu.R1.0, 0x34); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_add_overflow() { + let mut cpu = CPU::new(&[0b11100100]); + cpu.R0 = Wrapping(0xA3); + cpu.R1 = Wrapping(0xCB); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x6E); // destination data is correct + assert_eq!(cpu.R1.0, 0xCB); // R1 is not modified + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_sub() { + let mut cpu = CPU::new(&[0b11100101]); + cpu.R0 = Wrapping(0x50); + cpu.R1 = Wrapping(0x10); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x40); // destination data is correct + assert_eq!(cpu.R1.0, 0x10); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_sub_underflow() { + let mut cpu = CPU::new(&[0b11100101]); + cpu.R0 = Wrapping(0x50); + cpu.R1 = Wrapping(0x90); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0xC0); // destination data is correct + assert_eq!(cpu.R1.0, 0x90); // R1 is not modified + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_and() { + let mut cpu = CPU::new(&[0b11100110]); + cpu.R0 = Wrapping(0b10110111); + cpu.R1 = Wrapping(0b10001100); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10000100); // destination data is correct + assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_or() { + let mut cpu = CPU::new(&[0b11100111]); + cpu.R0 = Wrapping(0b10110111); + cpu.R1 = Wrapping(0b10001100); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10111111); // destination data is correct + assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_xor() { + let mut cpu = CPU::new(&[0b11101000]); + cpu.R0 = Wrapping(0b10110111); + cpu.R1 = Wrapping(0b10001100); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b00111011); // destination data is correct + assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_xnor() { + let mut cpu = CPU::new(&[0b11101001]); + cpu.R0 = Wrapping(0b10110111); + cpu.R1 = Wrapping(0b10001100); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b11000100); // destination data is correct + assert_eq!(cpu.R1.0, 0b10001100); // R1 is not modified + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_complement_r0() { + let mut cpu = CPU::new(&[0b11101010]); + cpu.R0 = Wrapping(0b10101010); + cpu.R1 = Wrapping(0x55); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01010101); // destination data is correct + assert_eq!(cpu.R1.0, 0x55); // R1 is not modified + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_complement_r1() { + let mut cpu = CPU::new(&[0b11101011]); + cpu.R0 = Wrapping(0x55); + cpu.R1 = Wrapping(0b10101010); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x55); // R0 is not modified + assert_eq!(cpu.R1.0, 0b01010101); // destination data is correct + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_bit_shift_right_var_zero() { + let mut cpu = CPU::new(&[0b11101100]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(0); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110010); // value is not changed + assert_eq!(cpu.R1.0, 0); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_bit_shift_right_var() { + let mut cpu = CPU::new(&[0b11101100]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(3); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b00001110); // value is shifted correctly + assert_eq!(cpu.R1.0, 3); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_bit_shift_right_var_overflow() { + let mut cpu = CPU::new(&[0b11101100]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(20); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // value is zeroed out + assert_eq!(cpu.R1.0, 20); // R1 is not modified + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_bit_shift_left_var_zero() { + let mut cpu = CPU::new(&[0b11101110]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(0); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110010); // value is not changed + assert_eq!(cpu.R1.0, 0); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_bit_shift_left_var() { + let mut cpu = CPU::new(&[0b11101110]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(3); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10010000); // value is shifted correctly + assert_eq!(cpu.R1.0, 3); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_bit_shift_left_var_overflow() { + let mut cpu = CPU::new(&[0b11101110]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(20); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // value is zeroed out + assert_eq!(cpu.R1.0, 20); // R1 is not modified + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_bit_rotate_right_var_zero() { + let mut cpu = CPU::new(&[0b11101101]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(0); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110010); // value is not changed + assert_eq!(cpu.R1.0, 0); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_bit_rotate_right_var() { + let mut cpu = CPU::new(&[0b11101101]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(3); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01001110); // value is rotated correctly + assert_eq!(cpu.R1.0, 3); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_bit_rotate_right_var_overflow() { + let mut cpu = CPU::new(&[0b11101101]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(20); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // value is zeroed out + assert_eq!(cpu.R1.0, 20); // R1 is not modified + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_bit_rotate_left_var_zero() { + let mut cpu = CPU::new(&[0b11101111]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(0); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b01110010); // value is not changed + assert_eq!(cpu.R1.0, 0); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_bit_rotate_left_var() { + let mut cpu = CPU::new(&[0b11101111]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(3); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0b10010011); // value is rotated correctly + assert_eq!(cpu.R1.0, 3); // R1 is not modified + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_bit_rotate_left_var_overflow() { + let mut cpu = CPU::new(&[0b11101111]); + cpu.R0 = Wrapping(0b01110010); + cpu.R1 = Wrapping(20); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // value is zeroed out + assert_eq!(cpu.R1.0, 20); // R1 is not modified + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_copy_from_r0() { + let mut cpu = CPU::new(&[0b11110000]); + cpu.R0 = Wrapping(34); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 34); // R0 is not changed + assert_eq!(cpu.R1.0, 34); // destination value is correct + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_copy_from_r1() { + let mut cpu = CPU::new(&[0b11110001]); + cpu.R1 = Wrapping(34); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 34); // destination value is correct + assert_eq!(cpu.R1.0, 34); // R1 is not changed + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_setc_true() { + let mut cpu = CPU::new(&[0b11110011]); + cpu.C = false; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_setc_false() { + let mut cpu = CPU::new(&[0b11110010]); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_nop() { + let mut cpu = CPU::new(&[0b11110100]); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + } + } + + #[test] + fn insn_halt() { + let mut cpu = CPU::new(&[0b11110101]); + { + assert_eq!(cpu.step(), true); // CPU has halted + } + } + + #[test] + fn insn_cload_r0() { + let mut cpu = CPU::new(&[0b11110110]); + cpu.code[26] = 23; + cpu.R0 = Wrapping(26); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 23); // value is loaded + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_cload_r1() { + let mut cpu = CPU::new(&[0b11110111]); + cpu.code[26] = 23; + cpu.R0 = Wrapping(26); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 23); // value is loaded + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_zero_r0() { + let mut cpu = CPU::new(&[0b11111000]); + cpu.R0 = Wrapping(1); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // R0 is zeroed out + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_zero_r1() { + let mut cpu = CPU::new(&[0b11111001]); + cpu.R1 = Wrapping(1); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R1.0, 0); // R1 is zeroed out + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_div() { + let mut cpu = CPU::new(&[0b11111010]); + cpu.R0 = Wrapping(100); + cpu.R1 = Wrapping(23); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 4); // quotient is valid + assert_eq!(cpu.R1.0, 8); // remainder is valid + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_div_zero() { + let mut cpu = CPU::new(&[0b11111010]); + cpu.R0 = Wrapping(100); + cpu.R1 = Wrapping(0); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0xFF); // quotient is valid + assert_eq!(cpu.R1.0, 0); // remainder is valid + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_mul() { + let mut cpu = CPU::new(&[0b11111011]); + cpu.R0 = Wrapping(0xC2); + cpu.R1 = Wrapping(0xA7); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x7E); // R0 value is valid + assert_eq!(cpu.R1.0, 0x8E); // R1 value is valid + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_swap() { + let mut cpu = CPU::new(&[0b11111100]); + cpu.R0 = Wrapping(7); + cpu.R1 = Wrapping(42); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 42); // R0 value is valid + assert_eq!(cpu.R1.0, 7); // R1 value is valid + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_port_ready_false() { + let mut cpu = CPU::new(&[0b11111101]); + cpu.C = true; + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, false); // carry is unset + } + } + + #[test] + fn insn_port_ready_true() { + let mut cpu = CPU::new(&[0b11111101]); + cpu.port_in.push_back(0x23); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.C, true); // carry is set + } + } + + #[test] + fn insn_read_port_empty() { + let mut cpu = CPU::new(&[0b11111110]); + cpu.R0 = Wrapping(1); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0); // R0 is zeroed out + assert_eq!(cpu.C, false); // carry is not affected + } + } + + #[test] + fn insn_read_port() { + let mut cpu = CPU::new(&[0b11111110]); + cpu.port_in.push_back(0x23); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 0x23); // value is correct + assert_eq!(cpu.C, false); // carry is not affected + assert_eq!(cpu.port_in.len(), 0); // port is empty + } + } + + #[test] + fn insn_write_port() { + let mut cpu = CPU::new(&[0b11111111]); + cpu.R0 = Wrapping(33); + { + assert_eq!(cpu.step(), false); // CPU has not halted + assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced + assert_eq!(cpu.R0.0, 33); // R0 is not affected + assert_eq!(cpu.C, false); // carry is not affected + assert_eq!(cpu.port_out.back(), Some(&33)); // data is in port + } + } +} diff --git a/src/instruction.rs b/src/instruction.rs new file mode 100644 index 0000000..5c32419 --- /dev/null +++ b/src/instruction.rs @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: MIT +// Copyright Murad Karammaev, Nikita Kuzmin + +use ux::{u2, u3, u4}; + +#[derive(Debug)] +pub enum Register { + R0, + R1, +} + +#[derive(Debug)] +pub enum ShiftMode { + Logical, + Circular, +} + +#[derive(Debug)] +pub enum Instruction { + Load { + reg: Register, + addr: u4, + }, + Store { + reg: Register, + addr: u4, + }, + LoadImmediate { + low: bool, + reg: Register, + val: u4, + }, + NearJumpBackward { + use_carry: bool, + offset: u2, + }, + NearJumpForward { + use_carry: bool, + offset: u3, + }, + 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, +} + +pub fn decode(insn: u8) -> Instruction { + match insn { + 0b00000000..=0b00111111 => { + let reg = if insn & 0b00010000 == 0 { + Register::R0 + } else { + Register::R1 + }; + let addr = u4::new(insn & 0b00001111); + match insn & 0b00100000 { + 0 => Instruction::Load { reg, addr }, + _ => Instruction::Store { reg, addr }, + } + } + 0b01000000..=0b01111111 => Instruction::LoadImmediate { + low: insn & 0b00100000 == 0, + reg: if insn & 0b00010000 == 0 { + Register::R0 + } else { + Register::R1 + }, + val: u4::new(insn & 0b00001111), + }, + 0b10001000..=0b10001111 => Instruction::NearJumpBackward { + use_carry: insn & 0b00000100 != 0, + offset: u2::new(insn & 0b00000011), + }, + 0b10010000..=0b10011111 => Instruction::NearJumpForward { + use_carry: insn & 0b00001000 != 0, + offset: u3::new(insn & 0b00000111), + }, + 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, + } +} diff --git a/src/main.rs b/src/main.rs index ffba700..6e1668a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,144 +1,16 @@ // SPDX-License-Identifier: MIT // Copyright Murad Karammaev, Nikita Kuzmin -use ux::{u2, u3, u4}; +mod cpu; +mod instruction; -#[derive(Debug)] -enum Register { - R0, - R1, -} - -#[derive(Debug)] -enum ShiftMode { - Logical, - Circular, -} - -#[derive(Debug)] -enum Instruction { - Load { reg: Register, addr: u4 }, - Store { reg: Register, addr: u4 }, - LoadImmediate { low: bool, reg: Register, val: u4 }, - NearJumpBackward { use_carry: bool, offset: u2 }, - NearJumpForward { use_carry: bool, offset: u3 }, - 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, - Reset, - Cload { reg: Register }, - Zero { reg: Register }, - Div, - Mul, - Swap, - PortReady, - ReadPort, - WritePort, -} - -fn decode(insn: u8) -> Instruction { - match insn { - 0b00000000..=0b00111111 => { - let reg = if insn & 0b00010000 == 0 { Register::R0 } else { Register::R1 }; - let addr = u4::new(insn & 0b00001111); - match insn & 0b00100000 { - 0 => Instruction::Load {reg, addr}, - _ => Instruction::Store {reg, addr}, - } - }, - 0b01000000..=0b01111111 => Instruction::LoadImmediate { - low: insn & 0b00100000 != 0, - reg: if insn & 0b00010000 == 0 { Register::R0 } else { Register::R1 }, - val: u4::new(insn & 0b00001111), - }, - 0b10001000..=0b10001111 => Instruction::NearJumpBackward { - use_carry: insn & 0b00000100 != 0, - offset: u2::new(insn & 0b00000011), - }, - 0b10010000..=0b10011111 => Instruction::NearJumpForward { - use_carry: insn & 0b00001000 != 0, - offset: u3::new(insn & 0b00000111), - }, - 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::Reset, - 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, - } -} +use cpu::*; fn main() { - for i in 0..=255u8 { - println!("{:#08b} -> {:?}", i, decode(i)); + let mut cpu = CPU::new(&[]); + loop { + if cpu.step() { + break; + } } }