WIP
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
mod test_utils;
|
||||
use std::path::Path;
|
||||
use gemma::chip8::computer::Chip8Computer;
|
||||
use gemma::chip8::computer_manager::Chip8ComputerManager;
|
||||
use gemma::chip8::quirk_modes::QuirkMode;
|
||||
use gemma::chip8::quirk_modes::QuirkMode::{Chip8, SChipModern, XOChip};
|
||||
use gemma::constants::TEST_ROM_ROOT;
|
||||
use crate::test_utils::load_compressed_result;
|
||||
|
||||
/// Tests the ComputerManager structure
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn default_test() {
|
||||
let new_manager = Chip8ComputerManager::default();
|
||||
|
||||
assert_eq!(new_manager.core_should_run, false);
|
||||
assert_eq!(new_manager.num_cycles(), 0);
|
||||
assert_eq!(new_manager.quirks_mode(), Chip8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quirks_mode_test() {
|
||||
let mut new_manager = Chip8ComputerManager::default();
|
||||
|
||||
assert_eq!(new_manager.quirks_mode(), Chip8);
|
||||
|
||||
new_manager.reset(QuirkMode::XOChip);
|
||||
|
||||
assert_eq!(new_manager.quirks_mode(), XOChip);
|
||||
|
||||
new_manager.reset(QuirkMode::SChipModern);
|
||||
|
||||
assert_eq!(new_manager.quirks_mode(), SChipModern);
|
||||
|
||||
new_manager.reset(Chip8);
|
||||
|
||||
assert_eq!(new_manager.quirks_mode(), Chip8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_rom_allows_starting() {
|
||||
let mut new_manager = Chip8ComputerManager::default();
|
||||
assert!(!new_manager.core_should_run);
|
||||
let p = TEST_ROM_ROOT.to_string() + "/1-chip8-logo.ch8";
|
||||
let full_path = Path::new(p.as_str());
|
||||
new_manager.load_new_program_from_disk_to_system_memory(full_path);
|
||||
assert!(new_manager.core_should_run)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_run_state() {
|
||||
let mut new_manager = Chip8ComputerManager::default();
|
||||
let p = format!(
|
||||
"{}/../resources/test/roms/1-chip8-logo.ch8",
|
||||
std::env::current_dir().unwrap().display()
|
||||
);
|
||||
new_manager.load_new_program_from_disk_to_system_memory(Path::new(p.as_str()));
|
||||
new_manager.reset(QuirkMode::Chip8);
|
||||
assert!(!new_manager.core_should_run);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_when_ready() {
|
||||
let mut new_manager = Chip8ComputerManager::default();
|
||||
new_manager.load_new_program_from_disk_to_system_memory(Path::new(
|
||||
format!(
|
||||
"{}/../resources/test/roms/1-chip8-logo.ch8",
|
||||
std::env::current_dir().unwrap().display()
|
||||
)
|
||||
.as_str(),
|
||||
));
|
||||
assert!(new_manager.core_should_run);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn start_stop_computer() {
|
||||
let mut computer = Chip8ComputerManager::new();
|
||||
|
||||
assert_eq!(computer.core_should_run, false);
|
||||
computer.start();
|
||||
assert_eq!(computer.core_should_run, true);
|
||||
computer.step();
|
||||
assert_eq!(computer.core_should_run, true);
|
||||
computer.stop();
|
||||
assert_eq!(computer.core_should_run, false);
|
||||
computer.reset(Chip8);
|
||||
assert_eq!(computer.core_should_run, false);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn state_default_matches() {
|
||||
let computer = Chip8Computer::default();
|
||||
let mut manager = Chip8ComputerManager::default();
|
||||
assert_eq!(computer, *manager.state());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_test_manager() {
|
||||
let mut manager = Chip8ComputerManager::default();
|
||||
|
||||
for i in 0..16 {
|
||||
assert_eq!(manager.is_key_pressed(i), false);
|
||||
}
|
||||
|
||||
// press key 5
|
||||
manager.press_key(5);
|
||||
assert_eq!(manager.is_key_pressed(5), true);
|
||||
manager.release_key(5);
|
||||
assert_eq!(manager.is_key_pressed(5), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn status_of_manager() {
|
||||
let mut manager = Chip8ComputerManager::default();
|
||||
println!("MANAGER STATUS [{}]", manager.status_as_string());
|
||||
let expected_state = load_compressed_result("test_manager_status");
|
||||
assert_eq!(expected_state, manager.status_as_string());
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use std::path::Path;
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_video() {
|
||||
let mut x = Chip8Computer::new();
|
||||
@@ -131,98 +132,14 @@ fn level4_test() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registers_equality() {
|
||||
let data_set: Vec<(Chip8Registers, Chip8Registers, bool)> = vec![
|
||||
(Chip8Registers::default(), Chip8Registers::default(), true),
|
||||
(
|
||||
Chip8Registers {
|
||||
registers: [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
],
|
||||
i_register: 0,
|
||||
pc: 0,
|
||||
},
|
||||
Chip8Registers {
|
||||
registers: [
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
],
|
||||
i_register: 0,
|
||||
pc: 0,
|
||||
},
|
||||
false,
|
||||
),
|
||||
];
|
||||
|
||||
for (first, second, matches) in data_set.iter() {
|
||||
assert_eq!(first == second, *matches)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_test() {
|
||||
let new_manager = Chip8ComputerManager::default();
|
||||
|
||||
assert_eq!(new_manager.core_should_run, false);
|
||||
assert_eq!(new_manager.num_cycles(), 0);
|
||||
assert_eq!(new_manager.quirks_mode(), Chip8);
|
||||
fn partial_eq_chip8computer() {
|
||||
let x = Chip8Computer::new();
|
||||
let y = Chip8Computer::new();
|
||||
assert_eq!(x, y)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quirks_mode_test() {
|
||||
let mut new_manager = Chip8ComputerManager::default();
|
||||
|
||||
assert_eq!(new_manager.quirks_mode(), Chip8);
|
||||
|
||||
new_manager.reset(QuirkMode::XOChip);
|
||||
|
||||
assert_eq!(new_manager.quirks_mode(), XOChip);
|
||||
|
||||
new_manager.reset(QuirkMode::SChipModern);
|
||||
|
||||
assert_eq!(new_manager.quirks_mode(), SChipModern);
|
||||
|
||||
new_manager.reset(Chip8);
|
||||
|
||||
assert_eq!(new_manager.quirks_mode(), Chip8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_rom_allows_starting() {
|
||||
let mut new_manager = Chip8ComputerManager::default();
|
||||
assert!(!new_manager.core_should_run);
|
||||
let p = TEST_ROM_ROOT.to_string() + "/1-chip8-logo.ch8";
|
||||
let full_path = Path::new(p.as_str());
|
||||
new_manager.load_new_program_from_disk_to_system_memory(full_path);
|
||||
assert!(new_manager.core_should_run)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_run_state() {
|
||||
let mut new_manager = Chip8ComputerManager::default();
|
||||
let p = format!(
|
||||
"{}/../resources/test/roms/1-chip8-logo.ch8",
|
||||
std::env::current_dir().unwrap().display()
|
||||
);
|
||||
new_manager.load_new_program_from_disk_to_system_memory(Path::new(p.as_str()));
|
||||
new_manager.reset(QuirkMode::Chip8);
|
||||
assert!(!new_manager.core_should_run);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_when_ready() {
|
||||
let mut new_manager = Chip8ComputerManager::default();
|
||||
new_manager.load_new_program_from_disk_to_system_memory(Path::new(
|
||||
format!(
|
||||
"{}/../resources/test/roms/1-chip8-logo.ch8",
|
||||
std::env::current_dir().unwrap().display()
|
||||
)
|
||||
.as_str(),
|
||||
));
|
||||
assert!(new_manager.core_should_run);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_when_not_ready() {}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
use gemma::chip8::delay_timer::DelayTimer;
|
||||
|
||||
#[test]
|
||||
fn delay_timer_default() {
|
||||
let x = DelayTimer::default();
|
||||
assert_eq!(x.current(), 0xff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delay_timer_ticks_reduce_time() {
|
||||
let mut st = DelayTimer::new();
|
||||
st.set_timer(100);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 97);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delay_timer_out_of_ticks_works() {
|
||||
let mut st = DelayTimer::new();
|
||||
st.set_timer(0);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 0);
|
||||
}
|
||||
@@ -0,0 +1,619 @@
|
||||
mod test_utils;
|
||||
use log::debug;
|
||||
use gemma::chip8::computer::Chip8Computer;
|
||||
use gemma::chip8::instructions::Chip8CpuInstructions;
|
||||
use gemma::constants::{CHIP8FONT_2, CHIP8_VIDEO_MEMORY};
|
||||
use crate::test_utils::read_compressed_test_result;
|
||||
|
||||
|
||||
|
||||
/// START OF THE EXECUTION TESTS
|
||||
#[test]
|
||||
fn instruction_tests() {
|
||||
// 0x0nnn Exit to System Call
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::SYS(0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::SYS(0xFA0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0xFA0);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::SYS(0x0AF).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x0AF);
|
||||
|
||||
// 0x1nnn Jump to Address
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::JPA(0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::JPA(0xABC).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0xABC);
|
||||
|
||||
// 0x6xkk Set Vx = kk
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::LDR(1, 0x12).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0x12);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::LDR(2, 0x21).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(2), 0x21);
|
||||
|
||||
// 0x3xkk Skip next instruction if Vx = kk.
|
||||
// The interpreter compares register Vx to kk,
|
||||
// and if they are equal, increments the program counter by 2.
|
||||
|
||||
// test setup: Load value 0x84 into V1
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x84);
|
||||
Chip8CpuInstructions::SEX(1, 0x48).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x84);
|
||||
Chip8CpuInstructions::SEX(1, 0x84).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||
|
||||
// 0x4xkk Skip next instruction if Vx != kk
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x84);
|
||||
x.registers.poke(0x2, 0x84);
|
||||
// skip, compare 0x84 to 0x84
|
||||
Chip8CpuInstructions::SEY(0x1, 0x2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x84);
|
||||
x.registers.poke(0x2, 0x48);
|
||||
Chip8CpuInstructions::SEY(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
|
||||
// 0x8xy0 Set value of Vy in Vx
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x01);
|
||||
x.registers.poke(0x02, 0x02);
|
||||
Chip8CpuInstructions::LDRY(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0x02);
|
||||
|
||||
// 0x8xy1 Set Vx = Vx OR Vy
|
||||
// 0b0101 0000 (0x50)
|
||||
// | 0b0000 1010 (0x0A)
|
||||
// 0b0101 1010 (0x5A)
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0b01010000);
|
||||
x.registers.poke(0x02, 0b00001010);
|
||||
Chip8CpuInstructions::OR(1, 2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0b01011010);
|
||||
|
||||
// 0x8xy2 Set Vx = Vx AND Vy
|
||||
// 0b1111 1100 (0xFC)
|
||||
// & 0b1100 1010 (0xCA)
|
||||
// 0b1100 1000 (0xC8)
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0xFC);
|
||||
x.registers.poke(0x02, 0xCA);
|
||||
Chip8CpuInstructions::AND(1, 2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0xC8);
|
||||
|
||||
// 0x8xy3 Set Vx = Vx XOR Vy
|
||||
// 0b1111 1100 (0xFC)
|
||||
// ^ 0b1100 1010 (0xCA)
|
||||
// 0b0011 0110 (0x36)
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0b11111100);
|
||||
x.registers.poke(0x02, 0b11001010);
|
||||
Chip8CpuInstructions::ORY(1, 2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0b00110110);
|
||||
|
||||
// 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
|
||||
// T1 T2: Judgement Test
|
||||
// 0x01 0xFF
|
||||
// + 0x01 0x01
|
||||
// 0x02 F0 0x00 F1
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x0f, 00);
|
||||
x.registers.poke(0x01, 0x01);
|
||||
x.registers.poke(0x02, 0x01);
|
||||
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0xf), 0x00);
|
||||
assert_eq!(x.registers.peek(0x01), 0x02);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x0f, 0x00);
|
||||
x.registers.poke(0x01, 0xff);
|
||||
x.registers.poke(0x02, 0x01);
|
||||
Chip8CpuInstructions::ADDR(1, 2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0xf), 1);
|
||||
assert_eq!(x.registers.peek(1), 0);
|
||||
|
||||
/*
|
||||
Set Vx = Vx SHR 1.
|
||||
|
||||
If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
|
||||
*/
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x0f, 0x00);
|
||||
x.registers.poke(0x01, 0b00001000);
|
||||
x.registers.poke(0x02, 0b00000000);
|
||||
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Not Set)
|
||||
assert_eq!(x.registers.peek(1), 0b00000100);
|
||||
assert_eq!(x.registers.peek(0xf), 0);
|
||||
|
||||
x = Chip8Computer::new();
|
||||
x.registers.poke(0x0f, 0x00);
|
||||
x.registers.poke(0x01, 0b00001001);
|
||||
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0b00000100);
|
||||
assert_eq!(x.registers.peek(0xf), 1);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::LDIA(0x123).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_i(), 0x123);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jp_v0addr_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
/// jump to I + nnn
|
||||
x.registers.poke(0x0, 0xff);
|
||||
Chip8CpuInstructions::JPI(0x100).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x1FF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cls_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::CLS.execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
for i in 0..CHIP8_VIDEO_MEMORY {
|
||||
assert!(!x.video_memory.peek(i as u16));
|
||||
}
|
||||
// draw some thing to the video memory
|
||||
x.video_memory.poke(0x01, true);
|
||||
x.video_memory.poke(0x03, true);
|
||||
x.video_memory.poke(0x05, true);
|
||||
|
||||
Chip8CpuInstructions::CLS.execute(&mut x);
|
||||
|
||||
for i in 0..CHIP8_VIDEO_MEMORY {
|
||||
assert!(!x.video_memory.peek(i as u16));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_next_instruction_ne_text() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xf0);
|
||||
Chip8CpuInstructions::SNEB(0x1, 0x0f).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xf0);
|
||||
Chip8CpuInstructions::SNEB(0x1, 0xf0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addivx_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke_i(0xabc);
|
||||
x.registers.poke(0x0, 0x10);
|
||||
Chip8CpuInstructions::ADDI(0x0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_i(), 0xacc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ldstvt_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xf0);
|
||||
Chip8CpuInstructions::LDIS(0x01).execute(&mut x);
|
||||
assert_eq!(x.sound_timer.current(), 0xf0);
|
||||
x.sound_timer.tick();
|
||||
x.sound_timer.tick();
|
||||
x.sound_timer.tick();
|
||||
assert_eq!(x.sound_timer.current(), 0xed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rnd_vx_byte_text() {
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::RND(0x1, 0x0f).execute(&mut x);
|
||||
let new_value = x.registers.peek(0x1);
|
||||
assert!(new_value < 0x10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_vx_byte_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
// set a value in the register
|
||||
x.registers.poke(0x01, 0xab);
|
||||
// add 0x10 to register
|
||||
Chip8CpuInstructions::ADD(0x1, 0x10).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0xbb);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_vx_vy_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
// load values in 2 registers
|
||||
x.registers.poke(0x1, 0x10);
|
||||
x.registers.poke(0x2, 0x08);
|
||||
Chip8CpuInstructions::SUB(0x1, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0xf), 1);
|
||||
assert_eq!(x.registers.peek(0x1), 0x8);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sne_vx_vy_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x10);
|
||||
x.registers.poke(0x2, 0x10);
|
||||
Chip8CpuInstructions::SNEY(0x1, 0x2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x10);
|
||||
x.registers.poke(0x2, 0x00);
|
||||
Chip8CpuInstructions::SNEY(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ld_dt_vx_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x10);
|
||||
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
|
||||
assert_eq!(x.delay_timer.current(), 0x10);
|
||||
for _ in 0..0x20 {
|
||||
x.delay_timer.tick();
|
||||
}
|
||||
assert_eq!(x.delay_timer.current(), 0);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn ld_vx_dt_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xf0);
|
||||
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
|
||||
x.delay_timer.tick();
|
||||
x.delay_timer.tick();
|
||||
x.delay_timer.tick();
|
||||
assert_eq!(x.delay_timer.current(), 0xed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subn_vx_vy_test() {
|
||||
// This instruction subtracts the value in
|
||||
// register Vx from the value in register Vy and stores the result in register Vx.
|
||||
// The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx,
|
||||
// the result will wrap around (due to the 8-bit nature of the registers).
|
||||
// The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater
|
||||
// than or equal to Vx), and it is set to 0 if there is a borrow.
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xa0);
|
||||
x.registers.poke(0x2, 0xab);
|
||||
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
|
||||
// expect the result to be 0x0b
|
||||
assert_eq!(x.registers.peek(0x1), 0x0b);
|
||||
// expect the vf register to be set to 1 as there was overflow
|
||||
assert_eq!(x.registers.peek(0xf), 0x1);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0xab);
|
||||
x.registers.poke(0x02, 0xa0);
|
||||
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
|
||||
|
||||
// expect the result to be 11110101, -0xB, -11, 245, 0xF5
|
||||
assert_eq!(x.registers.peek(0x1), 0xf5);
|
||||
assert_eq!(x.registers.peek(0xf), 0x0);
|
||||
|
||||
// 8xyE - SHL Vx {, Vy}
|
||||
// Set Vx = Vx SHL 1.
|
||||
//
|
||||
// If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0b00100000);
|
||||
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x1), 0b01000000);
|
||||
assert_eq!(x.registers.peek(0xf), 0x0);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0b10101010);
|
||||
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x1), 0b01010100);
|
||||
assert_eq!(x.registers.peek(0xf), 0x1);
|
||||
|
||||
// Fx29 - LD F, Vx
|
||||
// Set I = location of sprite for digit Vx.
|
||||
//
|
||||
// The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font.
|
||||
let mut x = Chip8Computer::new();
|
||||
// target_sprite = 2
|
||||
// target_offset = 0x5
|
||||
x.registers.poke(0x1, 0x2);
|
||||
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
|
||||
|
||||
assert_eq!(x.registers.peek_i(), 10);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x06);
|
||||
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_i(), 30);
|
||||
|
||||
// Fx33 - LD B, Vx
|
||||
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
|
||||
//
|
||||
// The interpreter takes the decimal value of Vx, and places the hundreds digit
|
||||
// in memory at location in I, the tens digit at location I+1,
|
||||
// and the ones digit at location I+2.
|
||||
let mut x = Chip8Computer::new();
|
||||
|
||||
// load the value 123 (0x7b)
|
||||
x.registers.poke(0x1, 0x7b);
|
||||
x.registers.poke_i(0x500);
|
||||
Chip8CpuInstructions::BCD(0x1).execute(&mut x);
|
||||
assert_eq!(x.memory.peek(0x500), 0x1);
|
||||
assert_eq!(x.memory.peek(0x501), 0x2);
|
||||
assert_eq!(x.memory.peek(0x502), 0x3);
|
||||
|
||||
// Store registers V0 through Vx in memory starting at location I.
|
||||
//
|
||||
// The interpreter copies the values of registers V0 through Vx into memory,
|
||||
// starting at the address in I.
|
||||
let mut x = Chip8Computer::new();
|
||||
|
||||
// Load Registers.
|
||||
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
|
||||
for (idx, val) in to_load.iter().enumerate() {
|
||||
x.registers.poke(idx as u8, *val);
|
||||
}
|
||||
x.registers.poke_i(0x500);
|
||||
|
||||
Chip8CpuInstructions::LDIX(to_load.len() as u8).execute(&mut x);
|
||||
|
||||
// Verify the values are in memory from 0x500 to 0x507
|
||||
for (idx, value) in to_load.iter().enumerate() {
|
||||
assert_eq!(x.memory.peek(0x500 + idx as u16), *value);
|
||||
}
|
||||
|
||||
// Read registers V0 through Vx from memory starting at location I.
|
||||
//
|
||||
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
|
||||
let base_offset = 0x500;
|
||||
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
|
||||
|
||||
// start by setting values in memory
|
||||
for (idx, memory) in to_load.iter().enumerate() {
|
||||
let target_address = base_offset + idx;
|
||||
let target_value = *memory;
|
||||
x.memory.poke(target_address as u16, target_value);
|
||||
}
|
||||
// where to load from
|
||||
x.registers.poke_i(0x500);
|
||||
// how much to load
|
||||
x.registers.poke(0x6, to_load.len() as u8);
|
||||
|
||||
// then copying them values memory to registers
|
||||
Chip8CpuInstructions::LDRI(0x6).execute(&mut x);
|
||||
|
||||
// now check that we have the right values in our registers
|
||||
for (idx, value) in to_load.iter().enumerate() {
|
||||
assert_eq!(x.registers.peek(idx as u8), *value);
|
||||
}
|
||||
|
||||
// ExA1 - SKNP Vx
|
||||
// Skip next instruction if key with the value of Vx is not pressed.
|
||||
//
|
||||
// Checks the keyboard,
|
||||
// and if the key corresponding to the value of Vx is currently in the up position,
|
||||
// PC is increased by 2.
|
||||
let mut x = Chip8Computer::new();
|
||||
x.keypad.push_key(0x5);
|
||||
x.registers.poke(0x1, 0x5);
|
||||
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
x.keypad.release_key(0x5);
|
||||
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x206);
|
||||
|
||||
// Ex9E - SKP Vx
|
||||
// Skip next instruction if key with the value of Vx is pressed.
|
||||
//
|
||||
// Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
|
||||
let mut x = Chip8Computer::new();
|
||||
x.keypad.push_key(0x5);
|
||||
x.registers.poke(0x1, 0x5);
|
||||
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||
|
||||
x.keypad.release_key(0x5);
|
||||
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x206);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn draw_nibble_vx_vy_n_test_hd() {
|
||||
let mut x = Chip8Computer::new();
|
||||
|
||||
let x_register = 0x01;
|
||||
let x_offset = 0x03;
|
||||
let y_register = 0x02;
|
||||
let y_offset = 0x04;
|
||||
let char_offset = 0x100;
|
||||
x.registers.poke(x_register, x_offset);
|
||||
x.registers.poke(y_register, y_offset);
|
||||
x.video_memory.set_highres();
|
||||
x.registers.poke_i(char_offset);
|
||||
Chip8CpuInstructions::DRW(x_register, y_register, 0).execute(&mut x);
|
||||
|
||||
println!("[[{}]]", x.video_memory.format_as_string());
|
||||
|
||||
assert_eq!(read_compressed_test_result(""), x.video_memory.format_as_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn draw_nibble_vx_vy_n_test_sd() {
|
||||
let mut x = Chip8Computer::new();
|
||||
let x_register = 0x1;
|
||||
let y_register = 0x2;
|
||||
let x_offset = 1;
|
||||
let y_offset = 2;
|
||||
let char_offset = 0x0A;
|
||||
|
||||
// now lets set the X and Y to 1,2
|
||||
x.registers.poke(x_register, x_offset);
|
||||
x.registers.poke(y_register, y_offset);
|
||||
x.registers.poke_i(char_offset);
|
||||
// we are using 5 rows.
|
||||
Chip8CpuInstructions::DRW(x_register, y_register, 5).execute(&mut x);
|
||||
|
||||
// now check that video memory has the values at
|
||||
// 1,2->1,9
|
||||
// 2,2->2,9
|
||||
// 3,2->3,9
|
||||
// 4,2->4,9
|
||||
// 5,2->5,9
|
||||
// let byte_to_check = CHIP8FONT_0[0];
|
||||
for row_in_sprite in 0..5 {
|
||||
let row_data = CHIP8FONT_2[row_in_sprite];
|
||||
for bit_in_byte in 0..8 {
|
||||
let data_offset =
|
||||
(x_offset as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16;
|
||||
let real_bit_in_byte = 7 - bit_in_byte;
|
||||
let shifted_one = 0x01 << real_bit_in_byte;
|
||||
let one_shift_set = (shifted_one & row_data) > 0;
|
||||
debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]");
|
||||
debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}",
|
||||
bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_test() {
|
||||
// 2nnn
|
||||
// Call a subroutine at 2nnn
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::CALL(0x124).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x124);
|
||||
assert_eq!(x.stack.depth(), 1);
|
||||
Chip8CpuInstructions::CALL(0x564).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x564);
|
||||
assert_eq!(x.stack.depth(), 2);
|
||||
|
||||
// SETUP
|
||||
// Return from a subroutine.
|
||||
let mut x = Chip8Computer::new();
|
||||
x.stack.push(&0x132);
|
||||
x.stack.push(&0xabc);
|
||||
// EXECUTE
|
||||
Chip8CpuInstructions::RET.execute(&mut x);
|
||||
// VERIFY
|
||||
assert_eq!(x.registers.peek_pc(), 0xabc);
|
||||
assert_eq!(x.stack.depth(), 1);
|
||||
// EXECUTE
|
||||
Chip8CpuInstructions::RET.execute(&mut x);
|
||||
// VERIFY
|
||||
assert_eq!(x.registers.peek_pc(), 0x132);
|
||||
assert_eq!(x.stack.depth(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ldvxk_test() {
|
||||
// SETUP
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x01);
|
||||
Chip8CpuInstructions::LDRK(0x1).execute(&mut x);
|
||||
assert!(matches!(
|
||||
x.state,
|
||||
gemma::chip8::cpu_states::Chip8CpuStates::WaitingForKey
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn series8xy4_corex_tests() {
|
||||
/// 8xy4
|
||||
/// Set Vx = Vx + Vy
|
||||
/// Set VF=1 if Carry
|
||||
///
|
||||
// 1 + 1
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x01);
|
||||
x.registers.poke(0x02, 0x01);
|
||||
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0x02);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x00);
|
||||
|
||||
// 255+1
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0xff);
|
||||
x.registers.poke(0x02, 0x01);
|
||||
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0x00);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x01);
|
||||
|
||||
// 128+192
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 128);
|
||||
x.registers.poke(0x02, 192);
|
||||
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 64);
|
||||
assert_eq!(x.registers.peek(0x0f), 1);
|
||||
|
||||
// 8xy6 - SHR Vx {, Vy}
|
||||
// Set Vx = Vx SHR 1.
|
||||
//
|
||||
// If the least-significant bit of Vx is 1, then VF is set to 1,
|
||||
// otherwise 0. Then Vx is divided by 2.
|
||||
let mut x = Chip8Computer::new();
|
||||
// 0b10101010 -> 0b01010101
|
||||
x.registers.poke(0x01, 0b10101010);
|
||||
x.registers.poke(0x0f, 0x0);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b01010100);
|
||||
assert_eq!(x.registers.peek(0x0f), 1);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b10101000);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x00);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b01010000);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x01);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b10100000);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x00);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b01000000);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_produces_different_numbers() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x00);
|
||||
let first_number = Chip8CpuInstructions::RND(0x01, 0xff)
|
||||
.execute(&mut x)
|
||||
.registers
|
||||
.peek(0x01);
|
||||
let second_number = Chip8CpuInstructions::RND(0x01, 0xff)
|
||||
.execute(&mut x)
|
||||
.registers
|
||||
.peek(0x01);
|
||||
assert_ne!(first_number, second_number);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
mod test_utils;
|
||||
use gemma::chip8::keypad::Keypad;
|
||||
use crate::test_utils::read_compressed_test_result;
|
||||
|
||||
#[test]
|
||||
fn keypad_keys_check() {
|
||||
let mut k = Keypad::new();
|
||||
|
||||
for i in 0..16 {
|
||||
assert!(!k.key_state(i));
|
||||
}
|
||||
|
||||
// press a key
|
||||
k.push_key(1);
|
||||
k.push_key(2);
|
||||
assert!(k.pressed(1));
|
||||
assert!(k.pressed(2));
|
||||
k.release_key(1);
|
||||
assert!(k.released(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keypad_string_format_test() {
|
||||
let k = Keypad::new();
|
||||
|
||||
let expected_result = read_compressed_test_result("gemma_keypad_string_result");
|
||||
let actual_result = k.format_as_string();
|
||||
|
||||
|
||||
println!("EXPECTING [{}]", expected_result);
|
||||
println!("GOT [{}]", actual_result);
|
||||
assert_eq!(
|
||||
k.format_as_string(),
|
||||
read_compressed_test_result("gemma_keypad_string_result")
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use rand::random;
|
||||
use gemma::chip8::registers::Chip8Registers;
|
||||
|
||||
#[test]
|
||||
fn register_rw_test() {
|
||||
let mut x = Chip8Registers::default();
|
||||
x.poke(0x0, 0xff);
|
||||
x.poke(0x1, 0xab);
|
||||
assert_eq!(x.peek(0x0), 0xff);
|
||||
assert_eq!(x.peek(0x1), 0xab);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pc_test() {
|
||||
let mut x = Chip8Registers::default();
|
||||
x.set_pc(0x300);
|
||||
assert_eq!(x.peek_pc(), 0x300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn invalid_register() {
|
||||
let mut x = Chip8Registers::default();
|
||||
x.poke(0x10, 0xff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_registers() {
|
||||
let mut x = Chip8Registers::default();
|
||||
|
||||
for register in 0..0x10 {
|
||||
x.poke(register, random::<u8>());
|
||||
}
|
||||
x.reset();
|
||||
for register in 0..0x10 {
|
||||
assert_eq!(x.peek(register), 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registers_equality() {
|
||||
let data_set: Vec<(Chip8Registers, Chip8Registers, bool)> = vec![
|
||||
(Chip8Registers::default(), Chip8Registers::default(), true),
|
||||
(
|
||||
Chip8Registers {
|
||||
registers: [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
],
|
||||
i_register: 0,
|
||||
pc: 0,
|
||||
},
|
||||
Chip8Registers {
|
||||
registers: [
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
],
|
||||
i_register: 0,
|
||||
pc: 0,
|
||||
},
|
||||
false,
|
||||
),
|
||||
];
|
||||
|
||||
for (first, second, matches) in data_set.iter() {
|
||||
assert_eq!(first == second, *matches)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use gemma::chip8::sound_timer::SoundTimer;
|
||||
|
||||
/// Tests for The Sound Timer
|
||||
///
|
||||
///
|
||||
|
||||
#[test]
|
||||
fn sound_timer_default() {
|
||||
let x = SoundTimer::default();
|
||||
assert_eq!(x.current(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sound_timer_ticks_reduce_time() {
|
||||
let mut st = SoundTimer::new();
|
||||
st.set_timer(100);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 97);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sound_timer_out_of_ticks_works() {
|
||||
let mut st = SoundTimer::new();
|
||||
st.set_timer(0);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 0);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use rand::random;
|
||||
use gemma::chip8::stack::Chip8Stack;
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn stack_overflow_test() {
|
||||
let mut x = Chip8Stack::new();
|
||||
for i in 0..17 {
|
||||
x.push(&i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn stack_underflow_test() {
|
||||
let mut x = Chip8Stack::new();
|
||||
x.pop();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_lots_of_subs() {
|
||||
let mut x = Chip8Stack::new();
|
||||
let stack_contents = [
|
||||
0x123, 0x321, 0xabc, 0xdef, 0xbad, 0xbef, 0xfed, 0xcab, 0xbed, 0xcad, 0xfeb, 0xcab, 0xfff,
|
||||
0x000, 0x001,
|
||||
];
|
||||
for i in stack_contents {
|
||||
x.push(&i);
|
||||
}
|
||||
|
||||
assert_eq!(x.depth(), 15);
|
||||
|
||||
// up to 50 random loops
|
||||
let num_loops: u8 = random::<u8>() % 50;
|
||||
for i in 0..num_loops {
|
||||
let start_count = x.depth();
|
||||
let num_pop = random::<u8>() % x.depth() as u8;
|
||||
for current_pop in 0..num_pop {
|
||||
x.pop();
|
||||
}
|
||||
|
||||
let post_pop_count = x.depth();
|
||||
assert_eq!(post_pop_count as u8, start_count as u8 - num_pop);
|
||||
for current_push in 0..num_pop {
|
||||
x.push(&stack_contents[(current_push + post_pop_count as u8) as usize]);
|
||||
}
|
||||
assert_eq!(x.depth(), 15);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,20 @@
|
||||
mod test_utils;
|
||||
use crate::test_utils::read_test_result;
|
||||
use gemma::chip8::computer::Chip8Computer;
|
||||
use std::fs;
|
||||
use std::io::prelude::*;
|
||||
use crate::test_utils::load_compressed_result;
|
||||
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn serialization_round_trip() {
|
||||
let original_computer = Chip8Computer::new();
|
||||
let expected_json = read_test_result("smoke_001_round_trip_serialize_deserialize.json");
|
||||
let expected_json = load_compressed_result("smoke_001_round_trip_serialize_deserialize");
|
||||
|
||||
// Serialize the Chip8Computer instance
|
||||
let serialized = serde_json::to_string(&original_computer).expect("Serialization failed");
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use gemma::chip8::system_memory::Chip8SystemMemory;
|
||||
use gemma::constants::TEST_ROM_ROOT;
|
||||
|
||||
#[test]
|
||||
fn system_memory_load_program() {
|
||||
let mut m = Chip8SystemMemory::new();
|
||||
let mut program_to_load = vec![];
|
||||
let file_to_load = format!("{}/2-ibm-logo.ch8", TEST_ROM_ROOT);
|
||||
println!(
|
||||
"Attempt to load {} from {}",
|
||||
file_to_load,
|
||||
std::env::current_dir().unwrap().display()
|
||||
);
|
||||
let mut file_to_load_from = File::open(file_to_load).expect("Unable to load sample rom");
|
||||
file_to_load_from
|
||||
.read_to_end(&mut program_to_load)
|
||||
.expect("Unable to read sample rom");
|
||||
m.load_program(program_to_load.clone().into());
|
||||
let expected_at_200 = program_to_load[0];
|
||||
assert_eq!(m.peek(0x200), expected_at_200);
|
||||
}
|
||||
@@ -6,6 +6,12 @@ use std::io::{Read, Write};
|
||||
use tempfile::tempfile;
|
||||
const TEST_OUTPUT_SAMPLE_DIR: &str = "../resources/test/";
|
||||
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
pub fn compress_string(input: &str) -> Vec<u8> {
|
||||
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
|
||||
encoder
|
||||
@@ -13,7 +19,6 @@ pub fn compress_string(input: &str) -> Vec<u8> {
|
||||
.expect("Failed to write data");
|
||||
encoder.finish().expect("Failed to finish compression")
|
||||
}
|
||||
|
||||
pub fn decompress_to_string(compressed_data: &[u8]) -> Result<String, std::io::Error> {
|
||||
let mut decoder = DeflateDecoder::new(compressed_data);
|
||||
let mut decompressed = String::new();
|
||||
@@ -43,29 +48,11 @@ pub fn read_compressed_test_result(suffix: &str) -> String {
|
||||
pub fn load_compressed_result(suffix: &str) -> String {
|
||||
read_compressed_test_result(suffix)
|
||||
}
|
||||
pub fn read_test_result(suffix: &str) -> String {
|
||||
panic!("THIS SHOULD BE COMPRESSED.");
|
||||
// let full_path = TEST_OUTPUT_SAMPLE_DIR.to_owned() + suffix;
|
||||
// println!("READING TEST RESULTS FROM {}", full_path);
|
||||
// std::fs::read_to_string(full_path).unwrap()
|
||||
}
|
||||
|
||||
pub fn load_rom(to_load: &str) -> Vec<u8> {
|
||||
std::fs::read(format!("../resources/roms/{}.ch8", to_load)).unwrap()
|
||||
}
|
||||
|
||||
fn load_result(to_load: &str) -> String {
|
||||
panic!("THIS SHOULD BE COMPRESSED.");
|
||||
// let full_path = format!(
|
||||
// "{}/../resources/test/state/{}",
|
||||
// std::env::current_dir().unwrap().display(),
|
||||
// to_load
|
||||
// );
|
||||
// println!("CURRENT DIR: {:?}", std::env::current_dir());
|
||||
// println!("Loading state => (([{}]))", full_path);
|
||||
// std::fs::read_to_string(full_path).unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -96,4 +83,6 @@ mod tests {
|
||||
//
|
||||
// ...verify its the same.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+24
-837
@@ -1,4 +1,4 @@
|
||||
use crate::test_utils::{load_compressed_result, read_test_result};
|
||||
use crate::test_utils::load_compressed_result;
|
||||
use gemma::chip8::computer::Chip8Computer;
|
||||
use gemma::chip8::computer_manager::Chip8ComputerManager;
|
||||
use gemma::chip8::cpu_states::Chip8CpuStates::WaitingForInstruction;
|
||||
@@ -48,683 +48,6 @@ fn decoder_test_invalid_instructions() {
|
||||
}
|
||||
}
|
||||
|
||||
/// START OF THE EXECUTION TESTS
|
||||
#[test]
|
||||
fn instruction_tests() {
|
||||
// 0x0nnn Exit to System Call
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::SYS(0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::SYS(0xFA0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0xFA0);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::SYS(0x0AF).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x0AF);
|
||||
|
||||
// 0x1nnn Jump to Address
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::JPA(0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::JPA(0xABC).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0xABC);
|
||||
|
||||
// 0x6xkk Set Vx = kk
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::LDR(1, 0x12).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0x12);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::LDR(2, 0x21).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(2), 0x21);
|
||||
|
||||
// 0x3xkk Skip next instruction if Vx = kk.
|
||||
// The interpreter compares register Vx to kk,
|
||||
// and if they are equal, increments the program counter by 2.
|
||||
|
||||
// test setup: Load value 0x84 into V1
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x84);
|
||||
Chip8CpuInstructions::SEX(1, 0x48).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x84);
|
||||
Chip8CpuInstructions::SEX(1, 0x84).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||
|
||||
// 0x4xkk Skip next instruction if Vx != kk
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x84);
|
||||
x.registers.poke(0x2, 0x84);
|
||||
// skip, compare 0x84 to 0x84
|
||||
Chip8CpuInstructions::SEY(0x1, 0x2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x84);
|
||||
x.registers.poke(0x2, 0x48);
|
||||
Chip8CpuInstructions::SEY(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
|
||||
// 0x8xy0 Set value of Vy in Vx
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x01);
|
||||
x.registers.poke(0x02, 0x02);
|
||||
Chip8CpuInstructions::LDRY(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0x02);
|
||||
|
||||
// 0x8xy1 Set Vx = Vx OR Vy
|
||||
// 0b0101 0000 (0x50)
|
||||
// | 0b0000 1010 (0x0A)
|
||||
// 0b0101 1010 (0x5A)
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0b01010000);
|
||||
x.registers.poke(0x02, 0b00001010);
|
||||
Chip8CpuInstructions::OR(1, 2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0b01011010);
|
||||
|
||||
// 0x8xy2 Set Vx = Vx AND Vy
|
||||
// 0b1111 1100 (0xFC)
|
||||
// & 0b1100 1010 (0xCA)
|
||||
// 0b1100 1000 (0xC8)
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0xFC);
|
||||
x.registers.poke(0x02, 0xCA);
|
||||
Chip8CpuInstructions::AND(1, 2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0xC8);
|
||||
|
||||
// 0x8xy3 Set Vx = Vx XOR Vy
|
||||
// 0b1111 1100 (0xFC)
|
||||
// ^ 0b1100 1010 (0xCA)
|
||||
// 0b0011 0110 (0x36)
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0b11111100);
|
||||
x.registers.poke(0x02, 0b11001010);
|
||||
Chip8CpuInstructions::ORY(1, 2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0b00110110);
|
||||
|
||||
// 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
|
||||
// T1 T2: Judgement Test
|
||||
// 0x01 0xFF
|
||||
// + 0x01 0x01
|
||||
// 0x02 F0 0x00 F1
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x0f, 00);
|
||||
x.registers.poke(0x01, 0x01);
|
||||
x.registers.poke(0x02, 0x01);
|
||||
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0xf), 0x00);
|
||||
assert_eq!(x.registers.peek(0x01), 0x02);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x0f, 0x00);
|
||||
x.registers.poke(0x01, 0xff);
|
||||
x.registers.poke(0x02, 0x01);
|
||||
Chip8CpuInstructions::ADDR(1, 2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0xf), 1);
|
||||
assert_eq!(x.registers.peek(1), 0);
|
||||
|
||||
/*
|
||||
Set Vx = Vx SHR 1.
|
||||
|
||||
If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
|
||||
*/
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x0f, 0x00);
|
||||
x.registers.poke(0x01, 0b00001000);
|
||||
x.registers.poke(0x02, 0b00000000);
|
||||
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Not Set)
|
||||
assert_eq!(x.registers.peek(1), 0b00000100);
|
||||
assert_eq!(x.registers.peek(0xf), 0);
|
||||
|
||||
x = Chip8Computer::new();
|
||||
x.registers.poke(0x0f, 0x00);
|
||||
x.registers.poke(0x01, 0b00001001);
|
||||
Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0b00000100);
|
||||
assert_eq!(x.registers.peek(0xf), 1);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::LDIA(0x123).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_i(), 0x123);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jp_v0addr_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
/// jump to I + nnn
|
||||
x.registers.poke(0x0, 0xff);
|
||||
Chip8CpuInstructions::JPI(0x100).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x1FF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cls_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::CLS.execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
for i in 0..CHIP8_VIDEO_MEMORY {
|
||||
assert!(!x.video_memory.peek(i as u16));
|
||||
}
|
||||
// draw some thing to the video memory
|
||||
x.video_memory.poke(0x01, true);
|
||||
x.video_memory.poke(0x03, true);
|
||||
x.video_memory.poke(0x05, true);
|
||||
|
||||
Chip8CpuInstructions::CLS.execute(&mut x);
|
||||
|
||||
for i in 0..CHIP8_VIDEO_MEMORY {
|
||||
assert!(!x.video_memory.peek(i as u16));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_next_instruction_ne_text() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xf0);
|
||||
Chip8CpuInstructions::SNEB(0x1, 0x0f).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xf0);
|
||||
Chip8CpuInstructions::SNEB(0x1, 0xf0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addivx_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke_i(0xabc);
|
||||
x.registers.poke(0x0, 0x10);
|
||||
Chip8CpuInstructions::ADDI(0x0).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_i(), 0xacc);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ldstvt_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xf0);
|
||||
Chip8CpuInstructions::LDIS(0x01).execute(&mut x);
|
||||
assert_eq!(x.sound_timer.current(), 0xf0);
|
||||
x.sound_timer.tick();
|
||||
x.sound_timer.tick();
|
||||
x.sound_timer.tick();
|
||||
assert_eq!(x.sound_timer.current(), 0xed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rnd_vx_byte_text() {
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::RND(0x1, 0x0f).execute(&mut x);
|
||||
let new_value = x.registers.peek(0x1);
|
||||
assert!(new_value < 0x10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_vx_byte_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
// set a value in the register
|
||||
x.registers.poke(0x01, 0xab);
|
||||
// add 0x10 to register
|
||||
Chip8CpuInstructions::ADD(0x1, 0x10).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(1), 0xbb);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_vx_vy_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
// load values in 2 registers
|
||||
x.registers.poke(0x1, 0x10);
|
||||
x.registers.poke(0x2, 0x08);
|
||||
Chip8CpuInstructions::SUB(0x1, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0xf), 1);
|
||||
assert_eq!(x.registers.peek(0x1), 0x8);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sne_vx_vy_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x10);
|
||||
x.registers.poke(0x2, 0x10);
|
||||
Chip8CpuInstructions::SNEY(0x1, 0x2).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x10);
|
||||
x.registers.poke(0x2, 0x00);
|
||||
Chip8CpuInstructions::SNEY(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ld_dt_vx_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0x10);
|
||||
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
|
||||
assert_eq!(x.delay_timer.current(), 0x10);
|
||||
for _ in 0..0x20 {
|
||||
x.delay_timer.tick();
|
||||
}
|
||||
assert_eq!(x.delay_timer.current(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ld_vx_dt_test() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xf0);
|
||||
Chip8CpuInstructions::LDD(0x1).execute(&mut x);
|
||||
x.delay_timer.tick();
|
||||
x.delay_timer.tick();
|
||||
x.delay_timer.tick();
|
||||
assert_eq!(x.delay_timer.current(), 0xed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subn_vx_vy_test() {
|
||||
// This instruction subtracts the value in
|
||||
// register Vx from the value in register Vy and stores the result in register Vx.
|
||||
// The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx,
|
||||
// the result will wrap around (due to the 8-bit nature of the registers).
|
||||
// The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater
|
||||
// than or equal to Vx), and it is set to 0 if there is a borrow.
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0xa0);
|
||||
x.registers.poke(0x2, 0xab);
|
||||
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
|
||||
// expect the result to be 0x0b
|
||||
assert_eq!(x.registers.peek(0x1), 0x0b);
|
||||
// expect the vf register to be set to 1 as there was overflow
|
||||
assert_eq!(x.registers.peek(0xf), 0x1);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0xab);
|
||||
x.registers.poke(0x02, 0xa0);
|
||||
Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x);
|
||||
|
||||
// expect the result to be 11110101, -0xB, -11, 245, 0xF5
|
||||
assert_eq!(x.registers.peek(0x1), 0xf5);
|
||||
assert_eq!(x.registers.peek(0xf), 0x0);
|
||||
|
||||
// 8xyE - SHL Vx {, Vy}
|
||||
// Set Vx = Vx SHL 1.
|
||||
//
|
||||
// If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0b00100000);
|
||||
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x1), 0b01000000);
|
||||
assert_eq!(x.registers.peek(0xf), 0x0);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x1, 0b10101010);
|
||||
Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x1), 0b01010100);
|
||||
assert_eq!(x.registers.peek(0xf), 0x1);
|
||||
|
||||
// Fx29 - LD F, Vx
|
||||
// Set I = location of sprite for digit Vx.
|
||||
//
|
||||
// The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font.
|
||||
let mut x = Chip8Computer::new();
|
||||
// target_sprite = 2
|
||||
// target_offset = 0x5
|
||||
x.registers.poke(0x1, 0x2);
|
||||
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
|
||||
|
||||
assert_eq!(x.registers.peek_i(), 10);
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x06);
|
||||
Chip8CpuInstructions::LDFX(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_i(), 30);
|
||||
|
||||
// Fx33 - LD B, Vx
|
||||
// Store BCD representation of Vx in memory locations I, I+1, and I+2.
|
||||
//
|
||||
// The interpreter takes the decimal value of Vx, and places the hundreds digit
|
||||
// in memory at location in I, the tens digit at location I+1,
|
||||
// and the ones digit at location I+2.
|
||||
let mut x = Chip8Computer::new();
|
||||
|
||||
// load the value 123 (0x7b)
|
||||
x.registers.poke(0x1, 0x7b);
|
||||
x.registers.poke_i(0x500);
|
||||
Chip8CpuInstructions::BCD(0x1).execute(&mut x);
|
||||
assert_eq!(x.memory.peek(0x500), 0x1);
|
||||
assert_eq!(x.memory.peek(0x501), 0x2);
|
||||
assert_eq!(x.memory.peek(0x502), 0x3);
|
||||
|
||||
// Store registers V0 through Vx in memory starting at location I.
|
||||
//
|
||||
// The interpreter copies the values of registers V0 through Vx into memory,
|
||||
// starting at the address in I.
|
||||
let mut x = Chip8Computer::new();
|
||||
|
||||
// Load Registers.
|
||||
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
|
||||
for (idx, val) in to_load.iter().enumerate() {
|
||||
x.registers.poke(idx as u8, *val);
|
||||
}
|
||||
x.registers.poke_i(0x500);
|
||||
|
||||
Chip8CpuInstructions::LDIX(to_load.len() as u8).execute(&mut x);
|
||||
|
||||
// Verify the values are in memory from 0x500 to 0x507
|
||||
for (idx, value) in to_load.iter().enumerate() {
|
||||
assert_eq!(x.memory.peek(0x500 + idx as u16), *value);
|
||||
}
|
||||
|
||||
// Read registers V0 through Vx from memory starting at location I.
|
||||
//
|
||||
// The interpreter reads values from memory starting at location I into registers V0 through Vx.
|
||||
|
||||
let mut x = Chip8Computer::new();
|
||||
|
||||
let base_offset = 0x500;
|
||||
let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
|
||||
|
||||
// start by setting values in memory
|
||||
for (idx, memory) in to_load.iter().enumerate() {
|
||||
let target_address = base_offset + idx;
|
||||
let target_value = *memory;
|
||||
x.memory.poke(target_address as u16, target_value);
|
||||
}
|
||||
// where to load from
|
||||
x.registers.poke_i(0x500);
|
||||
// how much to load
|
||||
x.registers.poke(0x6, to_load.len() as u8);
|
||||
|
||||
// then copying them values memory to registers
|
||||
Chip8CpuInstructions::LDRI(0x6).execute(&mut x);
|
||||
|
||||
// now check that we have the right values in our registers
|
||||
for (idx, value) in to_load.iter().enumerate() {
|
||||
assert_eq!(x.registers.peek(idx as u8), *value);
|
||||
}
|
||||
|
||||
// ExA1 - SKNP Vx
|
||||
// Skip next instruction if key with the value of Vx is not pressed.
|
||||
//
|
||||
// Checks the keyboard,
|
||||
// and if the key corresponding to the value of Vx is currently in the up position,
|
||||
// PC is increased by 2.
|
||||
let mut x = Chip8Computer::new();
|
||||
x.keypad.push_key(0x5);
|
||||
x.registers.poke(0x1, 0x5);
|
||||
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x202);
|
||||
x.keypad.release_key(0x5);
|
||||
Chip8CpuInstructions::SKNP(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x206);
|
||||
|
||||
// Ex9E - SKP Vx
|
||||
// Skip next instruction if key with the value of Vx is pressed.
|
||||
//
|
||||
// Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
|
||||
let mut x = Chip8Computer::new();
|
||||
x.keypad.push_key(0x5);
|
||||
x.registers.poke(0x1, 0x5);
|
||||
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x204);
|
||||
|
||||
x.keypad.release_key(0x5);
|
||||
Chip8CpuInstructions::SKP(0x1).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x206);
|
||||
}
|
||||
|
||||
fn draw_nibble_vx_vy_n_test_hd() {
|
||||
let mut x = Chip8Computer::new();
|
||||
|
||||
let x_register = 0x01;
|
||||
let x_offset = 0x03;
|
||||
let y_register = 0x02;
|
||||
let y_offset = 0x04;
|
||||
let char_offset = 0x100;
|
||||
x.registers.poke(x_register, x_offset);
|
||||
x.registers.poke(y_register, y_offset);
|
||||
x.video_memory.set_highres();
|
||||
x.registers.poke_i(char_offset);
|
||||
Chip8CpuInstructions::DRW(x_register, y_register, 0).execute(&mut x);
|
||||
|
||||
println!("[[{}]]", x.video_memory.format_as_string());
|
||||
|
||||
assert_eq!(read_test_result(""), x.video_memory.format_as_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn draw_nibble_vx_vy_n_test_sd() {
|
||||
let mut x = Chip8Computer::new();
|
||||
let x_register = 0x1;
|
||||
let y_register = 0x2;
|
||||
let x_offset = 1;
|
||||
let y_offset = 2;
|
||||
let char_offset = 0x0A;
|
||||
|
||||
// now lets set the X and Y to 1,2
|
||||
x.registers.poke(x_register, x_offset);
|
||||
x.registers.poke(y_register, y_offset);
|
||||
x.registers.poke_i(char_offset);
|
||||
// we are using 5 rows.
|
||||
Chip8CpuInstructions::DRW(x_register, y_register, 5).execute(&mut x);
|
||||
|
||||
// now check that video memory has the values at
|
||||
// 1,2->1,9
|
||||
// 2,2->2,9
|
||||
// 3,2->3,9
|
||||
// 4,2->4,9
|
||||
// 5,2->5,9
|
||||
// let byte_to_check = CHIP8FONT_0[0];
|
||||
for row_in_sprite in 0..5 {
|
||||
let row_data = CHIP8FONT_2[row_in_sprite];
|
||||
for bit_in_byte in 0..8 {
|
||||
let data_offset =
|
||||
(x_offset as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16;
|
||||
let real_bit_in_byte = 7 - bit_in_byte;
|
||||
let shifted_one = 0x01 << real_bit_in_byte;
|
||||
let one_shift_set = (shifted_one & row_data) > 0;
|
||||
debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]");
|
||||
debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}",
|
||||
bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_test() {
|
||||
// 2nnn
|
||||
// Call a subroutine at 2nnn
|
||||
let mut x = Chip8Computer::new();
|
||||
Chip8CpuInstructions::CALL(0x124).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x124);
|
||||
assert_eq!(x.stack.depth(), 1);
|
||||
Chip8CpuInstructions::CALL(0x564).execute(&mut x);
|
||||
assert_eq!(x.registers.peek_pc(), 0x564);
|
||||
assert_eq!(x.stack.depth(), 2);
|
||||
|
||||
// SETUP
|
||||
// Return from a subroutine.
|
||||
let mut x = Chip8Computer::new();
|
||||
x.stack.push(&0x132);
|
||||
x.stack.push(&0xabc);
|
||||
// EXECUTE
|
||||
Chip8CpuInstructions::RET.execute(&mut x);
|
||||
// VERIFY
|
||||
assert_eq!(x.registers.peek_pc(), 0xabc);
|
||||
assert_eq!(x.stack.depth(), 1);
|
||||
// EXECUTE
|
||||
Chip8CpuInstructions::RET.execute(&mut x);
|
||||
// VERIFY
|
||||
assert_eq!(x.registers.peek_pc(), 0x132);
|
||||
assert_eq!(x.stack.depth(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ldvxk_test() {
|
||||
// SETUP
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x01);
|
||||
Chip8CpuInstructions::LDRK(0x1).execute(&mut x);
|
||||
assert!(matches!(
|
||||
x.state,
|
||||
gemma::chip8::cpu_states::Chip8CpuStates::WaitingForKey
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn series8xy4_corex_tests() {
|
||||
/// 8xy4
|
||||
/// Set Vx = Vx + Vy
|
||||
/// Set VF=1 if Carry
|
||||
///
|
||||
// 1 + 1
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x01);
|
||||
x.registers.poke(0x02, 0x01);
|
||||
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0x02);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x00);
|
||||
|
||||
// 255+1
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0xff);
|
||||
x.registers.poke(0x02, 0x01);
|
||||
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0x00);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x01);
|
||||
|
||||
// 128+192
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 128);
|
||||
x.registers.poke(0x02, 192);
|
||||
Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 64);
|
||||
assert_eq!(x.registers.peek(0x0f), 1);
|
||||
|
||||
// 8xy6 - SHR Vx {, Vy}
|
||||
// Set Vx = Vx SHR 1.
|
||||
//
|
||||
// If the least-significant bit of Vx is 1, then VF is set to 1,
|
||||
// otherwise 0. Then Vx is divided by 2.
|
||||
let mut x = Chip8Computer::new();
|
||||
// 0b10101010 -> 0b01010101
|
||||
x.registers.poke(0x01, 0b10101010);
|
||||
x.registers.poke(0x0f, 0x0);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b01010100);
|
||||
assert_eq!(x.registers.peek(0x0f), 1);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b10101000);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x00);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b01010000);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x01);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b10100000);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x00);
|
||||
|
||||
Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x);
|
||||
assert_eq!(x.registers.peek(0x01), 0b01000000);
|
||||
assert_eq!(x.registers.peek(0x0f), 0x01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_produces_different_numbers() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.registers.poke(0x01, 0x00);
|
||||
let first_number = Chip8CpuInstructions::RND(0x01, 0xff)
|
||||
.execute(&mut x)
|
||||
.registers
|
||||
.peek(0x01);
|
||||
let second_number = Chip8CpuInstructions::RND(0x01, 0xff)
|
||||
.execute(&mut x)
|
||||
.registers
|
||||
.peek(0x01);
|
||||
assert_ne!(first_number, second_number);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delay_timer_ticks_reduce_time() {
|
||||
let mut st = DelayTimer::new();
|
||||
st.set_timer(100);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 97);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delay_timer_out_of_ticks_works() {
|
||||
let mut st = DelayTimer::new();
|
||||
st.set_timer(0);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keypad_keys_check() {
|
||||
let mut k = Keypad::new();
|
||||
|
||||
for i in 0..16 {
|
||||
assert!(!k.key_state(i));
|
||||
}
|
||||
|
||||
// press a key
|
||||
k.push_key(1);
|
||||
k.push_key(2);
|
||||
assert!(k.pressed(1));
|
||||
assert!(k.pressed(2));
|
||||
k.release_key(1);
|
||||
assert!(k.released(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keypad_string_format_test() {
|
||||
let k = Keypad::new();
|
||||
|
||||
assert_eq!(
|
||||
k.format_as_string(),
|
||||
read_compressed_test_result("gemma_keypad_string_result")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn register_rw_test() {
|
||||
let mut x = Chip8Registers::default();
|
||||
x.poke(0x0, 0xff);
|
||||
x.poke(0x1, 0xab);
|
||||
assert_eq!(x.peek(0x0), 0xff);
|
||||
assert_eq!(x.peek(0x1), 0xab);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pc_test() {
|
||||
let mut x = Chip8Registers::default();
|
||||
x.set_pc(0x300);
|
||||
assert_eq!(x.peek_pc(), 0x300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn invalid_register() {
|
||||
let mut x = Chip8Registers::default();
|
||||
x.poke(0x10, 0xff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_as_string_looks_right() {
|
||||
let mut x = Chip8Registers::default();
|
||||
@@ -737,38 +60,6 @@ fn format_as_string_looks_right() {
|
||||
assert_eq!(result_string, String::from("Vx: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07\n 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f\nI: 0x0cab\tPC: 0x0abc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_registers() {
|
||||
let mut x = Chip8Registers::default();
|
||||
|
||||
for register in 0..0x10 {
|
||||
x.poke(register, random::<u8>());
|
||||
}
|
||||
x.reset();
|
||||
for register in 0..0x10 {
|
||||
assert_eq!(x.peek(register), 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sound_timer_ticks_reduce_time() {
|
||||
let mut st = SoundTimer::new();
|
||||
st.set_timer(100);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 97);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sound_timer_out_of_ticks_works() {
|
||||
let mut st = SoundTimer::new();
|
||||
st.set_timer(0);
|
||||
st.tick();
|
||||
st.tick();
|
||||
st.tick();
|
||||
assert_eq!(st.current(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_push_pop_test() {
|
||||
@@ -781,53 +72,6 @@ fn stack_push_pop_test() {
|
||||
assert_eq!(x.depth(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn stack_overflow_test() {
|
||||
let mut x = Chip8Stack::new();
|
||||
for i in 0..17 {
|
||||
x.push(&i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn stack_underflow_test() {
|
||||
let mut x = Chip8Stack::new();
|
||||
x.pop();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_lots_of_subs() {
|
||||
let mut x = Chip8Stack::new();
|
||||
let stack_contents = [
|
||||
0x123, 0x321, 0xabc, 0xdef, 0xbad, 0xbef, 0xfed, 0xcab, 0xbed, 0xcad, 0xfeb, 0xcab, 0xfff,
|
||||
0x000, 0x001,
|
||||
];
|
||||
for i in stack_contents {
|
||||
x.push(&i);
|
||||
}
|
||||
|
||||
assert_eq!(x.depth(), 15);
|
||||
|
||||
// up to 50 random loops
|
||||
let num_loops: u8 = random::<u8>() % 50;
|
||||
for i in 0..num_loops {
|
||||
let start_count = x.depth();
|
||||
let num_pop = random::<u8>() % x.depth() as u8;
|
||||
for current_pop in 0..num_pop {
|
||||
x.pop();
|
||||
}
|
||||
|
||||
let post_pop_count = x.depth();
|
||||
assert_eq!(post_pop_count as u8, start_count as u8 - num_pop);
|
||||
for current_push in 0..num_pop {
|
||||
x.push(&stack_contents[(current_push + post_pop_count as u8) as usize]);
|
||||
}
|
||||
assert_eq!(x.depth(), 15);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_split_bytes() {
|
||||
// from 0xABCD we should have AB high, CD low
|
||||
@@ -1120,6 +364,7 @@ fn video_write_checkboard() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn video_zero_test() {
|
||||
let mut x = Chip8Video::default();
|
||||
|
||||
@@ -1489,19 +734,18 @@ fn video_hires_loop_check() {
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sound_timer_default() {
|
||||
let x = SoundTimer::default();
|
||||
assert_eq!(x.current(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_memory_new() {
|
||||
let x = Chip8SystemMemory::new();
|
||||
assert!(true);
|
||||
|
||||
// check its empty.
|
||||
for i in 0..CHIP8_MEMORY_SIZE {
|
||||
assert_eq!(x.peek(i as u16), 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn video_lowres_schip_draw_chip8_sprite() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.quirk_mode = QuirkMode::SChipModern;
|
||||
@@ -1512,24 +756,31 @@ fn video_lowres_schip_draw_chip8_sprite() {
|
||||
x.registers.poke(0x01, 0x01);
|
||||
x.registers.poke(0x02, 0x02);
|
||||
Chip8CpuInstructions::DRW(0x01, 0x01, 0x08).execute(&mut x);
|
||||
let expected_state = read_test_result("state/video_lowres_schip_draw_chip8_sprite_result.json");
|
||||
let actual_state = serde_json::to_string(&x).unwrap();
|
||||
let expected_state = read_compressed_test_result("state/video_lowres_schip_draw_chip8_sprite_result");
|
||||
let actual_state = x.dump_state_to_json();
|
||||
assert_eq!(expected_state, actual_state);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_lowres_schip_draw_schip_sprite() {}
|
||||
#[ignore]
|
||||
fn video_lowres_schip_draw_schip_sprite() {
|
||||
let mut x = Chip8Computer::new();
|
||||
x.quirk_mode = SChipModern;
|
||||
x.video_memory.set_lowres();
|
||||
x.registers.poke_i(0x0005);
|
||||
x.registers.poke(0x01, 0x01);
|
||||
x.registers.poke(0x02, 0x02);
|
||||
Chip8CpuInstructions::DRW(0x01, 0x01, 0x08).execute(&mut x);
|
||||
let expected_state = read_compressed_test_result("state/video_lowres_schip_draw_schip_sprite");
|
||||
let actual_state = x.dump_state_to_json();
|
||||
assert_eq!(expected_state, actual_state);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_highres_schip_draw_chip8_sprite() {}
|
||||
#[test]
|
||||
fn video_highres_schip_draw_schip_sprite() {}
|
||||
|
||||
#[test]
|
||||
fn partial_eq_chip8computer() {
|
||||
let x = Chip8Computer::new();
|
||||
let y = Chip8Computer::new();
|
||||
assert_eq!(x, y)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quirk_mode_labels() {
|
||||
@@ -1537,67 +788,3 @@ fn quirk_mode_labels() {
|
||||
assert_eq!(format!("{}", XOChip), LABEL_QUIRK_XOCHIP);
|
||||
assert_eq!(format!("{}", SChipModern), LABEL_QUIRK_SCHIP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_memory_load_program() {
|
||||
let mut m = Chip8SystemMemory::new();
|
||||
let mut program_to_load = vec![];
|
||||
let file_to_load = format!("{}/2-ibm-logo.ch8", TEST_ROM_ROOT);
|
||||
println!(
|
||||
"Attempt to load {} from {}",
|
||||
file_to_load,
|
||||
std::env::current_dir().unwrap().display()
|
||||
);
|
||||
let mut file_to_load_from = File::open(file_to_load).expect("Unable to load sample rom");
|
||||
file_to_load_from
|
||||
.read_to_end(&mut program_to_load)
|
||||
.expect("Unable to read sample rom");
|
||||
m.load_program(program_to_load.clone().into());
|
||||
let expected_at_200 = program_to_load[0];
|
||||
assert_eq!(m.peek(0x200), expected_at_200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_stop_computer() {
|
||||
let mut computer = Chip8ComputerManager::new();
|
||||
|
||||
assert_eq!(computer.core_should_run, false);
|
||||
computer.start();
|
||||
assert_eq!(computer.core_should_run, true);
|
||||
computer.step();
|
||||
assert_eq!(computer.core_should_run, true);
|
||||
computer.stop();
|
||||
assert_eq!(computer.core_should_run, false);
|
||||
computer.reset(Chip8);
|
||||
assert_eq!(computer.core_should_run, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_default_matches() {
|
||||
let computer = Chip8Computer::default();
|
||||
let mut manager = Chip8ComputerManager::default();
|
||||
assert_eq!(computer, *manager.state());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_test_manager() {
|
||||
let mut manager = Chip8ComputerManager::default();
|
||||
|
||||
for i in 0..16 {
|
||||
assert_eq!(manager.is_key_pressed(i), false);
|
||||
}
|
||||
|
||||
// press key 5
|
||||
manager.press_key(5);
|
||||
assert_eq!(manager.is_key_pressed(5), true);
|
||||
manager.release_key(5);
|
||||
assert_eq!(manager.is_key_pressed(5), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_of_manager() {
|
||||
let mut manager = Chip8ComputerManager::default();
|
||||
println!("MANAGER STATUS [{}]", manager.status_as_string());
|
||||
let expected_state = load_compressed_result("test_manager_status");
|
||||
assert_eq!(expected_state, manager.status_as_string());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
use std::collections::BTreeMap;
|
||||
use gemma::chip8::util::InstructionUtil;
|
||||
|
||||
|
||||
#[test]
|
||||
fn smoke() {
|
||||
assert!(true)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_to_bools() {
|
||||
let data_set: BTreeMap<u8, [bool; 8]> = BTreeMap::from(
|
||||
Reference in New Issue
Block a user