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
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
|
|
}
|
|
}
|
|
}
|
|
|