4-bit virtual CPU
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1601 lines
51 KiB

// 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<u8>,
pub R0: Wrapping<u8>,
pub R1: Wrapping<u8>,
pub C: bool,
pub code: [u8; 0x100],
pub data: [u8; 0x10],
pub port_in: VecDeque<u8>,
pub port_out: VecDeque<u8>,
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::<u8>::new(),
port_out: VecDeque::<u8>::new(),
num_cycles: 0,
}
}
pub fn next_instruction(&self) -> String {
format!("{:?}", decode(self.code[self.IP.0 as usize]))
}
fn load(&mut self) {
self.R0 = Wrapping(self.data[(self.R1.0 & 0xF) as usize]);
self.IP += Wrapping(1);
}
fn store(&mut self) {
self.data[(self.R1.0 & 0xF) as usize] = self.R0.0;
self.IP += Wrapping(1);
}
fn apply_imm(low: bool, mut reg: u8, val: u4) -> Wrapping<u8> {
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, forward: bool, use_carry: bool, offset: u4) {
if use_carry && !self.C {
self.IP += Wrapping(1);
return;
}
if forward {
self.IP += Wrapping(u8::from(offset) + 1);
} else {
self.IP -= Wrapping(u8::from(offset) + 1);
}
}
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 => self.load(),
Instruction::Store => self.store(),
Instruction::NearJump {
forward,
use_carry,
offset,
} => self.near_jump(forward, use_carry, offset),
Instruction::LoadImmediate { low, reg, val } => self.load_imm(low, reg, val),
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::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(),
Instruction::Halt => return true,
Instruction::Nop | Instruction::Reserved => self.nop(),
}
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_near_jump_backward_unconditional() {
let mut cpu = Cpu::new(&[]);
cpu.code[20] = 0b00100010;
cpu.IP = Wrapping(20);
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.IP.0, 17); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_near_jump_backward_conditional_must() {
let mut cpu = Cpu::new(&[]);
cpu.code[40] = 0b00110010;
cpu.IP = Wrapping(40);
cpu.C = true;
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.IP.0, 37); // 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] = 0b00110010;
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(&[0b00000010]);
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.IP.0, 0b11); // instruction pointer had advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_near_jump_forward_conditional_must() {
let mut cpu = Cpu::new(&[0b00010010]);
cpu.C = true;
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.IP.0, 0b11); // 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(&[0b00010010]);
{
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_load() {
let mut cpu = Cpu::new(&[0b10001000]);
cpu.R1 = Wrapping(0x2);
cpu.data[0x2] = 0x77;
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.R0.0, 0x77); // data is loaded
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_load_ignores_high_order_bytes_in_address() {
let mut cpu = Cpu::new(&[0b10001000]);
cpu.R1 = Wrapping(0x72);
cpu.data[0x2] = 0x77;
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.R0.0, 0x77); // data is loaded
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_store() {
let mut cpu = Cpu::new(&[0b10001001]);
cpu.R0 = Wrapping(0x12);
cpu.R1 = Wrapping(0x3);
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.data[0x3], 0x12); // data is stored
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_store_ignores_high_order_bytes_in_address() {
let mut cpu = Cpu::new(&[0b10001001]);
cpu.R0 = Wrapping(0x12);
cpu.R1 = Wrapping(0xD3);
{
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.data[0x3], 0x12); // data is stored
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
}
#[test]
fn insn_store_load() {
let mut cpu = Cpu::new(&[0b10001001, 0b10001000]);
{
cpu.R0 = Wrapping(0x44);
cpu.R1 = Wrapping(0x1);
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.data[0x1], 0x44); // data is stored
assert_eq!(cpu.IP.0, 0x01); // instruction pointer has advanced
assert!(!cpu.C); // carry is not affected
}
{
cpu.R0 = Wrapping(0);
assert!(!cpu.step()); // CPU has not halted
assert_eq!(cpu.R0.0, 0x44); // 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_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_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
}
}
}