// SPDX-License-Identifier: MIT // Copyright Murad Karammaev, Nikita Kuzmin use crate::instruction::{decode, Instruction, Register, ShiftMode}; use std::{cmp, collections::VecDeque, mem, num::Wrapping}; use ux::{u3, u4}; #[allow(non_snake_case)] pub struct Cpu { pub IP: Wrapping, pub R0: Wrapping, pub R1: Wrapping, pub C: bool, pub code: [u8; 0x100], pub data: [u8; 0x10], pub port_in: VecDeque, pub port_out: VecDeque, pub num_cycles: u64, } impl Cpu { pub fn new(code: &[u8]) -> Cpu { let num_instructions = cmp::min(0x100, code.len()); let mut new_code = [0u8; 0x100]; new_code[..num_instructions].clone_from_slice(&code[..num_instructions]); Cpu { IP: Wrapping(0), R0: Wrapping(0), R1: Wrapping(0), C: false, code: new_code, data: [0u8; 0x10], port_in: VecDeque::::new(), port_out: VecDeque::::new(), num_cycles: 0, } } 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(1); } 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(1); } 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(1); } fn near_jump(&mut self, use_carry: bool, forward: bool, offset: u8) { if use_carry && !self.C { self.IP += Wrapping(1); 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(1); 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(1); } fn cmp_gt(&mut self) { self.C = self.R0 > self.R1; self.IP += Wrapping(1); } fn cmp_le(&mut self) { self.C = self.R0 < self.R1; self.IP += Wrapping(1); } fn not(&mut self) { self.C = !self.C; self.IP += Wrapping(1); } 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(1); } fn inc(&mut self, reg: Register) { match reg { Register::R0 => { self.R0 += Wrapping(1); self.C = self.R0 == Wrapping(0); } Register::R1 => { self.R1 += Wrapping(1); self.C = self.R1 == Wrapping(0); } } self.IP += Wrapping(1); } fn dec(&mut self, reg: Register) { match reg { Register::R0 => { self.R0 -= Wrapping(1); self.C = self.R0 == Wrapping(0); } Register::R1 => { self.R1 -= Wrapping(1); self.C = self.R1 == Wrapping(0); } } self.IP += Wrapping(1); } fn add(&mut self) { self.C = self.R0.0 as u16 + self.R1.0 as u16 > 0xFF; self.R0 += self.R1; self.IP += Wrapping(1); } fn sub(&mut self) { self.C = self.R1 > self.R0; self.R0 -= self.R1; self.IP += Wrapping(1); } fn and(&mut self) { self.R0 &= self.R1; self.IP += Wrapping(1); } fn or(&mut self) { self.R0 |= self.R1; self.IP += Wrapping(1); } fn xor(&mut self) { self.R0 ^= self.R1; self.IP += Wrapping(1); } fn xnor(&mut self) { self.R0 = !(self.R0 ^ self.R1); self.IP += Wrapping(1); } fn complement(&mut self, reg: Register) { match reg { Register::R0 => self.R0 = !self.R0, Register::R1 => self.R1 = !self.R1, } self.IP += Wrapping(1); } 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(0); self.IP += Wrapping(1); } 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(1); } fn setc(&mut self, carry: bool) { self.C = carry; self.IP += Wrapping(1); } fn nop(&mut self) { self.IP += Wrapping(1); } 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(1); } fn zero(&mut self, reg: Register) { match reg { Register::R0 => self.R0 = Wrapping(0), Register::R1 => self.R1 = Wrapping(0), } self.IP += Wrapping(1); } fn div(&mut self) { if self.R1 == Wrapping(0) { self.R0 = Wrapping(0xFF); self.R1 = Wrapping(0); 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(1); } 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(1); } fn swap(&mut self) { mem::swap(&mut self.R0, &mut self.R1); self.IP += Wrapping(1); } fn port_ready(&mut self) { self.C = !self.port_in.is_empty(); self.IP += Wrapping(1); } fn read_port(&mut self) { match self.port_in.pop_front() { Some(v) => self.R0 = Wrapping(v), None => self.R0 = Wrapping(0), } self.IP += Wrapping(1); } fn write_port(&mut self) { self.port_out.push_back(self.R0.0); self.IP += Wrapping(1); } pub fn step(&mut self) -> bool { self.num_cycles += 1; 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 } pub fn visualize(&self) { println!("+--+----+----------+"); println!("|IP|{:#04X}|{:#010b}|", self.IP, self.IP); println!("|R0|{:#04X}|{:#010b}|", self.R0, self.R0); println!("|R1|{:#04X}|{:#010b}|", self.R1, self.R1); println!("+--+----+----------+"); println!("|FLAGS:{} |", if self.C { "C" } else { " " }); println!("+------------------+"); println!("|CYCLES:{:11}|", self.num_cycles); println!("|PORTIN:{:11}|", self.port_in.len()); println!("|PORTOUT:{:10}|", self.port_out.len()); println!("+------------------+"); println!("| MEM +0|+1|+2|+3 |"); for i in 0..4 { println!( "| 0x{:X} {:02X}|{:02X}|{:02X}|{:02X} |", i * 4, self.data[i * 4], self.data[i * 4 + 1], self.data[i * 4 + 2], self.data[i * 4 + 3] ); } println!("+------------------+"); } } #[cfg(test)] mod tests { use super::Cpu; use std::num::Wrapping; #[test] fn insn_load_r0() { let mut cpu = Cpu::new(&[0b00000001]); cpu.data[0b1] = 0x45; { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.R0.0, 0x45); // data is loaded assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_load_r1() { let mut cpu = Cpu::new(&[0b00010101]); cpu.data[0b101] = 0x54; { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.R1.0, 0x54); // data is loaded assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // 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!(!cpu.step()); // CPU has not halted assert_eq!(cpu.R0.0, 0x12); // data is loaded assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.R1.0, 0x78); // data is loaded assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_store_r0() { let mut cpu = Cpu::new(&[0b00100100]); cpu.R0 = Wrapping(0x42); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.data[0b0100], 0x42); // data is stored assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_store_r1() { let mut cpu = Cpu::new(&[0b00110010]); cpu.R1 = Wrapping(0x29); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.data[0b0010], 0x29); // data is stored assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // 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!(!cpu.step()); // CPU has not halted assert_eq!(cpu.data[0b1111], 0xA4); // data is stored assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.data[0b0010], 0xB3); // data is stored assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_store_and_load() { let mut cpu = Cpu::new(&[0b00100101, 0b00010101]); cpu.R0 = Wrapping(0xF3); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.data[0b0101], 0xF3); // data is stored } { assert!(!cpu.step()); // 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!(!cpu.step()); // CPU has not halted assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.R0.0, 0b01010101); // data is loaded assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_load_imm_r1() { let mut cpu = Cpu::new(&[0b01010110, 0b01111001]); { assert!(!cpu.step()); // CPU has not halted assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.R1.0, 0b10010110); // data is loaded assert_eq!(cpu.IP.0, 0x02); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_load_imm_low_only() { let mut cpu = Cpu::new(&[0b01001111]); cpu.R0 = Wrapping(0b10101010); { assert!(!cpu.step()); // 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!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 18); // instruction pointer had advanced assert!(!cpu.C); // 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!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 38); // instruction pointer had advanced assert!(cpu.C); // 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!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 41); // instruction pointer had advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_near_jump_forward_unconditional() { let mut cpu = Cpu::new(&[0b10010101]); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0b101); // instruction pointer had advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_near_jump_forward_conditional_must() { let mut cpu = Cpu::new(&[0b10011011]); cpu.C = true; { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0b011); // instruction pointer had advanced assert!(cpu.C); // carry is not affected } } #[test] fn insn_near_jump_forward_conditional_must_not() { let mut cpu = Cpu::new(&[0b10011011]); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 1); // instruction pointer had advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_far_jump_unconditional_r0() { let mut cpu = Cpu::new(&[0b10000000]); cpu.R0 = Wrapping(0xA0); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0xA0); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_far_jump_unconditional_r1() { let mut cpu = Cpu::new(&[0b10000010]); cpu.R1 = Wrapping(0xC7); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0xC7); // instruction pointer has advanced assert!(!cpu.C); // 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!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x2B); // instruction pointer has advanced assert!(cpu.C); // 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!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x0A); // instruction pointer has advanced assert!(cpu.C); // carry is not affected } } #[test] fn insn_far_jump_conditional_must_not_r0() { let mut cpu = Cpu::new(&[0b10000001]); cpu.R0 = Wrapping(0x44); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_far_jump_conditional_must_not_r1() { let mut cpu = Cpu::new(&[0b10000011]); cpu.R1 = Wrapping(0x88); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is not affected } } #[test] fn insn_equal_true() { let mut cpu = Cpu::new(&[0b10000100]); cpu.R0 = Wrapping(0xC2); cpu.R1 = Wrapping(0xC2); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(cpu.C); // carry is set } } #[test] fn insn_equal_false() { let mut cpu = Cpu::new(&[0b10000100]); cpu.R0 = Wrapping(0xC2); cpu.R1 = Wrapping(0xC1); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is unset } } #[test] fn insn_greater_true() { let mut cpu = Cpu::new(&[0b10000101]); cpu.R0 = Wrapping(0xF4); cpu.R1 = Wrapping(0xD3); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(cpu.C); // carry is set } } #[test] fn insn_greater_false_eq() { let mut cpu = Cpu::new(&[0b10000101]); cpu.R0 = Wrapping(0xD3); cpu.R1 = Wrapping(0xD3); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is unset } } #[test] fn insn_greater_false() { let mut cpu = Cpu::new(&[0b10000101]); cpu.R0 = Wrapping(0xD3); cpu.R1 = Wrapping(0xF4); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is unset } } #[test] fn insn_less_true() { let mut cpu = Cpu::new(&[0b10000110]); cpu.R0 = Wrapping(0xD3); cpu.R1 = Wrapping(0xF4); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(cpu.C); // carry is set } } #[test] fn insn_less_false_eq() { let mut cpu = Cpu::new(&[0b10000110]); cpu.R0 = Wrapping(0xD3); cpu.R1 = Wrapping(0xD3); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is unset } } #[test] fn insn_less_false() { let mut cpu = Cpu::new(&[0b10000110]); cpu.R0 = Wrapping(0xF4); cpu.R1 = Wrapping(0xD3); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is unset } } #[test] fn insn_not_from_true() { let mut cpu = Cpu::new(&[0b10000111]); cpu.C = true; { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is unset } } #[test] fn insn_not_from_false() { let mut cpu = Cpu::new(&[0b10000111]); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(cpu.C); // carry is set } } #[test] fn insn_right_shift_zero_r0() { let mut cpu = Cpu::new(&[0b10100000]); cpu.R0 = Wrapping(0b10011101); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_right_shift_r0() { let mut cpu = Cpu::new(&[0b10100010]); cpu.R0 = Wrapping(0b10011101); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 0b00100111); // data is shifted assert!(!cpu.C); // carry is not affected } } #[test] fn insn_right_shift_zero_r1() { let mut cpu = Cpu::new(&[0b10110000]); cpu.R1 = Wrapping(0b10011101); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_right_shift_r1() { let mut cpu = Cpu::new(&[0b10110010]); cpu.R1 = Wrapping(0b10011101); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R1.0, 0b00100111); // data is shifted assert!(!cpu.C); // carry is not affected } } #[test] fn insn_right_rotate_zero_r0() { let mut cpu = Cpu::new(&[0b10101000]); cpu.R0 = Wrapping(0b10011101); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_right_rotate_r0() { let mut cpu = Cpu::new(&[0b10101010]); cpu.R0 = Wrapping(0b10011101); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 0b01100111); // data is rotated assert!(!cpu.C); // carry is not affected } } #[test] fn insn_right_rotate_zero_r1() { let mut cpu = Cpu::new(&[0b10111000]); cpu.R1 = Wrapping(0b10011101); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_right_rotate_r1() { let mut cpu = Cpu::new(&[0b10111010]); cpu.R1 = Wrapping(0b10011101); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R1.0, 0b01100111); // data is rotated assert!(!cpu.C); // carry is not affected } } #[test] fn insn_left_shift_zero_r0() { let mut cpu = Cpu::new(&[0b11000000]); cpu.R0 = Wrapping(0b10011101); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_left_shift_r0() { let mut cpu = Cpu::new(&[0b11000010]); cpu.R0 = Wrapping(0b10011101); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 0b01110100); // data is shifted assert!(!cpu.C); // carry is not affected } } #[test] fn insn_left_shift_zero_r1() { let mut cpu = Cpu::new(&[0b11010000]); cpu.R1 = Wrapping(0b10011101); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_left_shift_r1() { let mut cpu = Cpu::new(&[0b11010010]); cpu.R1 = Wrapping(0b10011101); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R1.0, 0b01110100); // data is shifted assert!(!cpu.C); // carry is not affected } } #[test] fn insn_left_rotate_zero_r0() { let mut cpu = Cpu::new(&[0b11001000]); cpu.R0 = Wrapping(0b10011101); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_left_rotate_r0() { let mut cpu = Cpu::new(&[0b11001010]); cpu.R0 = Wrapping(0b10011101); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 0b01110110); // data is rotated assert!(!cpu.C); // carry is not affected } } #[test] fn insn_left_rotate_zero_r1() { let mut cpu = Cpu::new(&[0b11011000]); cpu.R1 = Wrapping(0b10011101); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_left_rotate_r1() { let mut cpu = Cpu::new(&[0b11011010]); cpu.R1 = Wrapping(0b10011101); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R1.0, 0b01110110); // data is rotated assert!(!cpu.C); // carry is not affected } } #[test] fn insn_inc_carry_false_r0() { let mut cpu = Cpu::new(&[0b11100000]); cpu.R0 = Wrapping(68); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 69); // data is incremented assert!(!cpu.C); // carry is unset } } #[test] fn insn_inc_carry_true_r0() { let mut cpu = Cpu::new(&[0b11100000]); cpu.R0 = Wrapping(255); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 0); // data is incremented assert!(cpu.C); // carry is unset } } #[test] fn insn_inc_carry_false_r1() { let mut cpu = Cpu::new(&[0b11100001]); cpu.R1 = Wrapping(68); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R1.0, 69); // data is incremented assert!(!cpu.C); // carry is unset } } #[test] fn insn_inc_carry_true_r1() { let mut cpu = Cpu::new(&[0b11100001]); cpu.R1 = Wrapping(255); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R1.0, 0); // data is incremented assert!(cpu.C); // carry is unset } } #[test] fn insn_dec_carry_false_r0() { let mut cpu = Cpu::new(&[0b11100010]); cpu.R0 = Wrapping(68); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 67); // data is incremented assert!(!cpu.C); // carry is unset } } #[test] fn insn_dec_carry_true_r0() { let mut cpu = Cpu::new(&[0b11100010]); cpu.R0 = Wrapping(1); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 0); // data is incremented assert!(cpu.C); // carry is unset } } #[test] fn insn_dec_carry_false_r1() { let mut cpu = Cpu::new(&[0b11100011]); cpu.R1 = Wrapping(68); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R1.0, 67); // data is incremented assert!(!cpu.C); // carry is unset } } #[test] fn insn_dec_carry_true_r1() { let mut cpu = Cpu::new(&[0b11100011]); cpu.R1 = Wrapping(1); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R1.0, 0); // data is incremented assert!(cpu.C); // carry is unset } } #[test] fn insn_add() { let mut cpu = Cpu::new(&[0b11100100]); cpu.R0 = Wrapping(0x12); cpu.R1 = Wrapping(0x34); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is unset } } #[test] fn insn_add_overflow() { let mut cpu = Cpu::new(&[0b11100100]); cpu.R0 = Wrapping(0xA3); cpu.R1 = Wrapping(0xCB); { assert!(!cpu.step()); // 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!(cpu.C); // carry is set } } #[test] fn insn_sub() { let mut cpu = Cpu::new(&[0b11100101]); cpu.R0 = Wrapping(0x50); cpu.R1 = Wrapping(0x10); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is unset } } #[test] fn insn_sub_underflow() { let mut cpu = Cpu::new(&[0b11100101]); cpu.R0 = Wrapping(0x50); cpu.R1 = Wrapping(0x90); { assert!(!cpu.step()); // 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!(cpu.C); // carry is set } } #[test] fn insn_and() { let mut cpu = Cpu::new(&[0b11100110]); cpu.R0 = Wrapping(0b10110111); cpu.R1 = Wrapping(0b10001100); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_or() { let mut cpu = Cpu::new(&[0b11100111]); cpu.R0 = Wrapping(0b10110111); cpu.R1 = Wrapping(0b10001100); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_xor() { let mut cpu = Cpu::new(&[0b11101000]); cpu.R0 = Wrapping(0b10110111); cpu.R1 = Wrapping(0b10001100); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_xnor() { let mut cpu = Cpu::new(&[0b11101001]); cpu.R0 = Wrapping(0b10110111); cpu.R1 = Wrapping(0b10001100); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_complement_r0() { let mut cpu = Cpu::new(&[0b11101010]); cpu.R0 = Wrapping(0b10101010); cpu.R1 = Wrapping(0x55); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_complement_r1() { let mut cpu = Cpu::new(&[0b11101011]); cpu.R0 = Wrapping(0x55); cpu.R1 = Wrapping(0b10101010); { assert!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(cpu.C); // carry is set } } #[test] fn insn_copy_from_r0() { let mut cpu = Cpu::new(&[0b11110000]); cpu.R0 = Wrapping(34); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_copy_from_r1() { let mut cpu = Cpu::new(&[0b11110001]); cpu.R1 = Wrapping(34); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_setc_true() { let mut cpu = Cpu::new(&[0b11110011]); cpu.C = false; { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(cpu.C); // carry is set } } #[test] fn insn_setc_false() { let mut cpu = Cpu::new(&[0b11110010]); cpu.C = true; { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is unset } } #[test] fn insn_nop() { let mut cpu = Cpu::new(&[0b11110100]); { assert!(!cpu.step()); // 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!(cpu.step()); // CPU has halted } } #[test] fn insn_cload_r0() { let mut cpu = Cpu::new(&[0b11110110]); cpu.code[26] = 23; cpu.R0 = Wrapping(26); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 23); // value is loaded assert!(!cpu.C); // carry is not affected } } #[test] fn insn_cload_r1() { let mut cpu = Cpu::new(&[0b11110111]); cpu.code[26] = 23; cpu.R0 = Wrapping(26); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R1.0, 23); // value is loaded assert!(!cpu.C); // carry is not affected } } #[test] fn insn_zero_r0() { let mut cpu = Cpu::new(&[0b11111000]); cpu.R0 = Wrapping(1); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_zero_r1() { let mut cpu = Cpu::new(&[0b11111001]); cpu.R1 = Wrapping(1); { assert!(!cpu.step()); // 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!(!cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // carry is unset } } #[test] fn insn_div_zero() { let mut cpu = Cpu::new(&[0b11111010]); cpu.R0 = Wrapping(100); cpu.R1 = Wrapping(0); { assert!(!cpu.step()); // 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!(cpu.C); // carry is set } } #[test] fn insn_mul() { let mut cpu = Cpu::new(&[0b11111011]); cpu.R0 = Wrapping(0xC2); cpu.R1 = Wrapping(0xA7); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_swap() { let mut cpu = Cpu::new(&[0b11111100]); cpu.R0 = Wrapping(7); cpu.R1 = Wrapping(42); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_port_ready_false() { let mut cpu = Cpu::new(&[0b11111101]); cpu.C = true; { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(!cpu.C); // carry is unset } } #[test] fn insn_port_ready_true() { let mut cpu = Cpu::new(&[0b11111101]); cpu.port_in.push_back(0x23); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert!(cpu.C); // carry is set } } #[test] fn insn_read_port_empty() { let mut cpu = Cpu::new(&[0b11111110]); cpu.R0 = Wrapping(1); { assert!(!cpu.step()); // 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!(!cpu.C); // carry is not affected } } #[test] fn insn_read_port() { let mut cpu = Cpu::new(&[0b11111110]); cpu.port_in.push_back(0x23); { assert!(!cpu.step()); // CPU has not halted assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced assert_eq!(cpu.R0.0, 0x23); // value is correct assert!(!cpu.C); // 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!(!cpu.step()); // 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!(!cpu.C); // carry is not affected assert_eq!(cpu.port_out.back(), Some(&33)); // data is in port } } }