trevors_chip8_toy/gemma/tests/unit_tests.rs
Trevor Merritt 67ca71ccb7 my first schip rom works in my schip emulator.
BUGFIX: Corrects runaway after drawing in my first schip rom
scroll down, left, right all test with test rom
assembler now assembles to the expected output it seems
fixes incorrect loading of schip font to memory
replaces schip font from new chatgpt feedback
2024-11-06 10:40:52 -05:00

1524 lines
43 KiB
Rust

use gemma::chip8::computer::Chip8Computer;
use gemma::chip8::cpu_states::Chip8CpuStates::WaitingForInstruction;
use gemma::chip8::delay_timer::DelayTimer;
use gemma::chip8::instructions::Chip8CpuInstructions;
use gemma::chip8::instructions::Chip8CpuInstructions::JPX;
use gemma::chip8::keypad::Keypad;
use gemma::chip8::quirk_modes::QuirkMode::{self, Chip8, SChipModern, XOChip};
use gemma::chip8::registers::Chip8Registers;
use gemma::chip8::sound_timer::SoundTimer;
use gemma::chip8::stack::Chip8Stack;
use gemma::chip8::system_memory::Chip8SystemMemory;
use gemma::chip8::util::InstructionUtil;
use gemma::chip8::video::{Chip8Video, Chip8VideoModes};
use gemma::constants::*;
use log::debug;
use rand::random;
use serde::Serialize;
const TEST_OUTPUT_SAMPLE_DIR: &str = "../resources/test/";
fn read_test_result(suffix: &str) -> String {
std::fs::read_to_string(TEST_OUTPUT_SAMPLE_DIR.to_owned() + suffix).unwrap()
}
#[test]
fn smoke() {
assert!(true)
}
#[test]
fn decoder_test_invalid_instructions() {
let invalid_to_encode = [
0x5ab1, 0x5abf, 0x8ab8, 0x8abd, 0x8abf, 0x9ab1, 0x9abf, 0xea9d, 0xea9f, 0xeaa0, 0xeaa2,
0xf006, 0xf008,
];
for i in invalid_to_encode {
assert_eq!(Chip8CpuInstructions::decode(i, &Chip8).encode(), 0xffff);
assert!(matches!(
Chip8CpuInstructions::decode(i, &Chip8),
Chip8CpuInstructions::XXXXERRORINSTRUCTION
));
}
}
/// 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 i 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_test_result("gemma_keypad_string_result.asc")
);
}
#[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();
for i in 0..0x10 {
x.registers[i] = i as u8;
}
x.pc = 0xabc;
x.i_register = 0xcab;
let result_string = x.format_as_string();
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() {
let mut x = Chip8Stack::new();
// lets see if we can push and pop a bunch
x.push(&0xabcu16);
x.push(&0xcdeu16);
x.pop();
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
let (low, high) = InstructionUtil::split_bytes(0xabcd);
assert_eq!(low, 0xAB);
assert_eq!(high, 0xCD);
}
#[test]
fn video_join_bytes() {
// from 0xAB low and 0xCD high we get 0xABCD
let merged = InstructionUtil::join_bytes(0xcd, 0xab);
assert_eq!(merged, 0xcdab);
}
#[test]
fn instruction_read_from_instruction() {
// from 0xABCD
let source = 0xABCD;
assert_eq!(InstructionUtil::read_addr_from_instruction(source), 0xBCD);
assert_eq!(InstructionUtil::read_nibble_from_instruction(source), 0xD);
assert_eq!(InstructionUtil::read_x_from_instruction(source), 0xB);
assert_eq!(InstructionUtil::read_y_from_instruction(source), 0xC);
assert_eq!(InstructionUtil::read_byte_from_instruction(source), 0xCD);
}
#[test]
fn instruction_ubln() {
// from 0xABCD we should see B
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0xABCD), 0xB);
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0x0123), 0x1);
assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0x0000), 0x0);
}
#[test]
fn instruction_byte_to_bool_changes() {
assert_eq!(
InstructionUtil::byte_to_bools(0b00000000),
[false, false, false, false, false, false, false, false]
);
assert_eq!(
InstructionUtil::byte_to_bools(0b11111111),
[true, true, true, true, true, true, true, true]
);
assert_eq!(
InstructionUtil::byte_to_bools(0b11001100),
[false, false, true, true, false, false, true, true]
);
assert_eq!(
InstructionUtil::byte_to_bools(0b11110000),
[false, false, false, false, true, true, true, true]
);
assert_eq!(
InstructionUtil::bools_to_byte([false, false, false, false, false, false, false, false]),
0b00000000
);
assert_eq!(
InstructionUtil::bools_to_byte([true, true, true, true, true, true, true, true]),
0b11111111
);
assert_eq!(
InstructionUtil::bools_to_byte([false, false, true, true, false, false, true, true]),
0b11001100
);
assert_eq!(
InstructionUtil::bools_to_byte([false, false, false, false, true, true, true, true]),
0b11110000
);
}
fn real_build_checkboard(in_hd: bool) -> Chip8Video {
let mut r = Chip8Video::default();
let (width, height) = if in_hd {
r.set_highres();
(SCHIP_VIDEO_WIDTH, SCHIP_VIDEO_HEIGHT)
} else {
(CHIP8_VIDEO_WIDTH, CHIP8_VIDEO_HEIGHT)
};
println!("BUILDING BOARD WITH SIZE OF {width}x{height}");
for row in 0..height {
let data_offset = row * width;
for col in 0..width {
// XOR row and column indices to alternate in a checkerboard pattern
let to_poke = (row % 2) ^ (col % 2) == 1;
let local_offset: u16 = (data_offset + col) as u16;
r.poke(local_offset, to_poke);
}
}
r
}
fn build_checkboard_hd() -> Chip8Video {
real_build_checkboard(true)
}
fn build_checkerboard() -> Chip8Video {
real_build_checkboard(false)
}
#[test]
fn video_default_test() {
let mut x = Chip8Video::default();
for i in 0..CHIP8_VIDEO_MEMORY {
assert!(!x.clone().peek(i as u16));
// then flip the value and test again.
x.poke(i as u16, true);
assert!(x.clone().peek(i as u16));
}
}
#[test]
fn video_set_initial_memory_sd() {
let mut x = Chip8Video::default();
// let mut initial_memory = [false; CHIP8_VIDEO_MEMORY];
let mut ws = String::new();
// set our checkerboard
for cbr in 0..32 {
for cbc in 0..64 {
let dof = cbr * 64 + cbc;
if (dof as i32 % 2) == 0 {
x.poke(dof, true);
ws += "*";
} else {
ws += " ";
}
}
ws += "\n";
}
assert_eq!(x.format_as_string(), ws);
}
#[test]
fn video_poke_byte_test() {
let to_poke = 0b11001111;
let mut x = Chip8Video::default();
x.poke_byte(0x05, to_poke);
let mut expected = String::new();
expected = " ** **** \n".to_string();
for i in 0..31 {
expected += &*(" ".repeat(64) + "\n");
}
assert_eq!(x.format_as_string(), expected);
}
#[test]
fn video_poke_2byte_test() {
let to_poke: [u8; 2] = [0b11001111, 0b00111100];
let mut x = Chip8Video::default();
x.poke_2byte(0x00, to_poke);
let mut expected = String::new();
expected = "** **** **** ".to_string() + &*" ".repeat(64 - 16).to_string() + "\n";
for i in 0..31 {
expected += &*((*" ".repeat(64)).to_string() + "\n");
}
assert_eq!(expected, x.format_as_string());
}
#[test]
fn video_poke_multirow_2_byte_sprite() {
// take 2 rows of 16bits and write them to memory
}
#[test]
fn video_cls_stddef() {
let width = 64;
let height = 32;
let initial_memory = vec![];
let mut ws = String::new();
let mut set_x = Chip8Video::new(initial_memory.into());
for cbr in 0..32 {
ws += &*" ".repeat(width);
ws += "\n";
}
set_x.cls();
assert_eq!(set_x.format_as_string(), ws);
}
#[test]
fn video_poke_byte_test_2() {
let to_poke = 0b10101010;
let mut v = Chip8Video::default();
v.poke_byte(0x00, to_poke);
assert!(v.clone().peek(0x00));
assert!(!v.clone().peek(0x01));
assert!(v.clone().peek(0x02));
assert!(!v.clone().peek(0x03));
assert!(v.clone().peek(0x04));
assert!(!v.clone().peek(0x05));
assert!(v.clone().peek(0x06));
assert!(!v.clone().peek(0x07));
for i in 0x8..CHIP8_VIDEO_MEMORY {
assert!(!v.clone().peek(i as u16));
}
}
#[test]
fn video_poke_multi_line_test() {
let mut v = Chip8Video::default();
let to_poke = [0b00000000, 0b11111111, 0b10101010, 0b01010101];
for (byte_in_set, byte_to_poke) in to_poke.iter().enumerate() {
let base_offset = byte_in_set * 64;
v.poke_byte(base_offset as u16, *byte_to_poke);
}
// row 2 column 1
{
assert!(v.clone().peek(0x40));
assert!(v.clone().peek(0x41));
assert!(v.clone().peek(0x42));
assert!(v.clone().peek(0x43));
assert!(v.clone().peek(0x44));
assert!(v.clone().peek(0x45));
assert!(v.clone().peek(0x46));
assert!(v.clone().peek(0x47));
// row 3 column 1
assert!(!v.clone().peek(0xC0));
assert!(v.clone().peek(0xC1));
assert!(!v.clone().peek(0xC2));
assert!(v.clone().peek(0xC3));
assert!(!v.clone().peek(0xC4));
assert!(v.clone().peek(0xC5));
assert!(!v.clone().peek(0xC6));
assert!(v.clone().peek(0xC7));
}
}
#[test]
fn video_moved_poke_test() {
let mut v = Chip8Video::default();
let to_poke = [0b00000000, 0b11111111, 0b10101010, 0b01010101];
let x_offset = 20;
let y_offset = 5;
for (byte_in_set, byte_to_poke) in to_poke.iter().enumerate() {
let base_offset = (x_offset + byte_in_set) * 64 + y_offset;
v.poke_byte(base_offset as u16, *byte_to_poke);
}
let test_offset = (x_offset * 64 + y_offset) as u16;
assert!(!v.clone().peek(test_offset));
assert!(!v.clone().peek(test_offset + 1));
assert!(!v.clone().peek(test_offset + 2));
assert!(!v.clone().peek(test_offset + 3));
assert!(!v.clone().peek(test_offset + 4));
assert!(!v.clone().peek(test_offset + 5));
assert!(!v.clone().peek(test_offset + 6));
assert!(!v.clone().peek(test_offset + 7));
let test_offset = test_offset + 0x40;
assert!(v.clone().peek(test_offset));
assert!(v.clone().peek(test_offset + 1));
assert!(v.clone().peek(test_offset + 2));
assert!(v.clone().peek(test_offset + 3));
assert!(v.clone().peek(test_offset + 4));
assert!(v.clone().peek(test_offset + 5));
assert!(v.clone().peek(test_offset + 6));
assert!(v.clone().peek(test_offset + 7));
}
#[test]
fn video_verify_change_registered() {
let mut v = Chip8Video::default();
v.poke(0x01, true);
v.poke(0x01, true);
assert!(v.has_frame_changed);
v.start_frame();
assert!(!v.has_frame_changed);
}
#[test]
fn video_write_checkboard() {
let v = build_checkerboard();
assert_eq!(
v.clone().format_as_string(),
read_test_result("test_video_write_checkerboard.asc")
);
}
#[test]
fn video_zero_test() {
let mut x = Chip8Video::default();
for (byte_index, data_offset) in (0..=0x100).step_by(0x40).enumerate() {
x.poke_byte(data_offset as u16, CHIP8FONT_0[byte_index]);
}
assert_eq!(
read_test_result("test_video_zero.asc"),
x.format_as_string()
);
}
#[test]
fn video_multi_sprite_test() {
let mut x = Chip8Video::default();
// draw a row of digits 01234567
let to_draw = [
CHIP8FONT_0,
CHIP8FONT_1,
CHIP8FONT_2,
CHIP8FONT_3,
CHIP8FONT_4,
CHIP8FONT_5,
CHIP8FONT_6,
CHIP8FONT_7,
];
for (index, sprite) in to_draw.iter().enumerate() {
let data_base_offset = index * 0x8;
for (index, offset) in (0..=0x100).step_by(0x40).enumerate() {
x.poke_byte((data_base_offset + offset) as u16, sprite[index]);
}
}
assert_eq!(
read_test_result("test_multi_sprite.asc"),
x.format_as_string()
);
}
#[test]
fn video_reset_test() {
let mut x = build_checkerboard();
x.reset();
assert_eq!(
x.format_as_string(),
read_test_result("test_reset_clears_video.asc")
);
}
#[test]
fn video_collision_test() {
// Setup: Set 0xFF to 0x00 with a new frame ready
// Action: Run Poke to the same area
// Test: Verify the 'changed' flag is tripped
let mut x = Chip8Video::default();
x.poke_byte(0x00, 0xff);
x.tick();
// set the cell thats already set...
x.poke(0x00, true);
// it becomes unset and theres a frame changed
assert!(!x.peek(0x00));
assert!(x.clone().has_frame_changed);
}
#[test]
fn video_collision_test2() {
let mut x = Chip8Video::default();
x.poke_byte(0x00, 0b11110000);
assert!(x.has_frame_changed);
x.tick();
assert!(!x.has_frame_changed);
// clear the 'has changed' flag
// now set a no-collision value
x.poke_byte(0x00, 0b00001111);
assert!(x.has_frame_changed);
}
#[test]
fn video_collision_test3() {
// draw a couple sprites that do not overlap.
// goal being drawing without triggering the collision
// detection.
let mut x = Chip8Video::default();
x.poke_byte(0x00, 0b11110000);
}
#[test]
fn video_peek_out_of_bounds_doesnt_panic() {
let x = Chip8Video::default();
let y = x.clone().peek(2049);
let y = x.clone().peek(0);
// if we got here we didn't panic
assert!(true);
}
#[test]
fn video_scroll_down_1_row_test() {
let mut x = build_checkerboard();
x.scroll_down(1);
assert_eq!(
read_test_result("test_video_scroll_down_1.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_down_10_row_test() {
let mut x = build_checkerboard();
x.scroll_down(10);
assert_eq!(
read_test_result("test_video_scroll_down_10.asc"),
x.format_as_string()
);
}
#[test]
fn video_high_res_has_right_resolution() {
let x = build_checkboard_hd();
println!("[{}]", x.format_as_string());
assert_eq!(
read_test_result("test_video_highdef.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_down_1_row_test_schip() {
let mut x = build_checkboard_hd();
x.scroll_down(1);
println!("[{}]", x.format_as_string());
println!("[{}]", read_test_result("test_scroll_down_1_hd.asc"));
assert_eq!(
read_test_result("test_scroll_down_1_hd.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_down_10_row_test_schip() {
let mut x = build_checkboard_hd();
x.scroll_down(10);
assert_eq!(
read_test_result("test_scroll_down_10_hd.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_left_4_row_test_std_def() {
let mut x = build_checkerboard();
x.scroll_left();
assert_eq!(
read_test_result("test_scroll_left_4.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_left_4_row_test_high_def() {
let mut x = build_checkboard_hd();
x.scroll_left();
assert_eq!(
read_test_result("test_scroll_left_4_hd.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_right_4_row_test_std_def() {
let mut x = build_checkerboard();
x.scroll_right();
assert_eq!(
read_test_result("test_scroll_right_4.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_right_4_row_test_high_def() {
let mut x = build_checkboard_hd();
x.scroll_right();
assert_eq!(
read_test_result("test_scroll_right_4_hd.asc"),
x.format_as_string()
);
}
#[test]
fn instructions_operands_tests() {
assert_eq!(Chip8CpuInstructions::SYS(0x000).operands(), "0x0000");
assert_eq!(Chip8CpuInstructions::JPI(0x123).operands(), "0x0123");
assert_eq!(Chip8CpuInstructions::JPA(0x234).operands(), "0x0234");
assert_eq!(Chip8CpuInstructions::LDIA(0x345).operands(), "0x0345");
assert_eq!(Chip8CpuInstructions::CALL(0x456).operands(), "0x0456");
}
#[test]
fn instruction_ena_dis_tests() {
let mut x = Chip8Computer::new();
for quirk in [SChipModern, XOChip] {
x.quirk_mode = quirk;
assert!(!x.video_memory.is_highres());
Chip8CpuInstructions::HIGH.execute(&mut x);
assert!(x.video_memory.is_highres());
Chip8CpuInstructions::HIGH.execute(&mut x);
assert!(x.video_memory.is_highres());
Chip8CpuInstructions::LOW.execute(&mut x);
assert!(!x.video_memory.is_highres());
}
}
#[test]
fn instruction_test_scrolling_lowres() {
for quirk in [SChipModern, XOChip] {
let mut x = Chip8Computer::new();
x.video_memory = build_checkerboard();
x.quirk_mode = quirk.clone();
Chip8CpuInstructions::SCR.execute(&mut x);
assert_eq!(
read_test_result("test_scroll_right_4.asc"),
x.dump_video_to_string()
);
x = Chip8Computer::new();
x.video_memory = build_checkerboard();
x.quirk_mode = quirk.clone();
Chip8CpuInstructions::SCL.execute(&mut x);
assert_eq!(
read_test_result("test_scroll_left_4.asc"),
x.dump_video_to_string()
);
x = Chip8Computer::new();
x.video_memory = build_checkerboard();
x.quirk_mode = quirk.clone();
Chip8CpuInstructions::SCD(0x01).execute(&mut x);
assert_eq!(
read_test_result("test_video_scroll_down_1.asc"),
x.dump_video_to_string()
);
x = Chip8Computer::new();
x.video_memory = build_checkerboard();
x.quirk_mode = quirk.clone();
Chip8CpuInstructions::SCD(0xA).execute(&mut x);
assert_eq!(
read_test_result("test_video_scroll_down_10.asc"),
x.dump_video_to_string()
);
}
}
#[test]
fn computer_dump_keypad_to_string() {
let mut x = Chip8Computer::new();
x.keypad.push_key(0x1);
x.keypad.push_key(0x2);
assert_eq!(
read_test_result("test_keypad_to_string.asc"),
x.dump_keypad_to_string()
);
}
#[test]
fn computer_dump_registers_to_string() {
let mut x = Chip8Computer::new();
let values_to_set = [
0x0b, 0xad, 0xbe, 0xef, 0xca, 0xb0, 0x7a, 0xc0, 0xca, 0x70, 0xba, 0xdb, 0xed, 0x00, 0x00,
0x00,
];
let expected_value = "Vx: 0x0b 0xad 0xbe 0xef 0xca 0xb0 0x7a 0xc0\n 0xca 0x70 0xba 0xdb 0xed 0x00 0x00 0x00\nI: 0x0000\tPC: 0x0200";
for i in 0..16 {
x.registers.poke(i, values_to_set[i as usize]);
}
// now verify.
assert_eq!(expected_value, x.dump_registers_to_string());
}
#[test]
fn video_scroll_up_tests_sd() {
let mut x = build_checkerboard();
let distance = 1u8;
x.scroll_up(&distance);
assert_eq!(
read_test_result("test_video_scroll_up_test_sd.asc"),
x.format_as_string()
);
}
#[test]
fn video_scroll_up_tests_hd() {
let mut x = build_checkboard_hd();
let distance = 1u8;
x.scroll_up(&distance);
assert_eq!(
read_test_result("test_video_scroll_up_test_hd.asc"),
x.format_as_string()
);
}
#[test]
fn video_resolution_changing() {
let mut x = Chip8Video::default();
x.set_highres();
assert_eq!(x.get_resolution(), (SCHIP_VIDEO_WIDTH, SCHIP_VIDEO_HEIGHT));
assert!(matches!(
x.get_screen_resolution(),
Chip8VideoModes::HighRes
));
x.set_lowres();
assert_eq!(x.get_resolution(), (CHIP8_VIDEO_WIDTH, CHIP8_VIDEO_HEIGHT));
assert!(matches!(x.get_screen_resolution(), Chip8VideoModes::LowRes));
}
#[test]
fn delay_timer_default() {
let t = DelayTimer::default();
assert_eq!(t.current(), 0xff);
}
#[test]
fn instruction_jpx() {
// JPX -> 0xBXnn
// Jump to Xnn+Vx
let mut x = Chip8Computer::new();
// set X1 to 4...
x.registers.poke(0x01, 0x04);
// ...use (x1)+0x134
let to_execute = JPX(0x01, 0x34);
// expect to set PC to 0x834
to_execute.execute(&mut x);
assert_eq!(x.registers.peek_i(), 0x834);
}
#[test]
fn instruction_ldrd() {
let mut x = Chip8Computer::new();
x.state = WaitingForInstruction;
x.delay_timer.set_timer(0x01);
Chip8CpuInstructions::LDRD(0x01).execute(&mut x);
assert_eq!(x.registers.peek(0x01), 0x01);
x.delay_timer.set_timer(0xff);
Chip8CpuInstructions::LDRD(0x0).execute(&mut x);
assert_eq!(x.registers.peek(0x00), 0xff);
x.step_system();
x.step_system();
x.step_system();
x.step_system();
Chip8CpuInstructions::LDRD(0x0).execute(&mut x);
assert_eq!(x.registers.peek(0x0), 0xfb);
}
#[test]
fn video_hires_loop_check() {
let max_address = SCHIP_VIDE_MEMORY;
let x = build_checkboard_hd();
x.peek((max_address + 1) as u16);
// if we got here we didn't explode!
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);
}
#[test]
fn video_lowres_schip_draw_chip8_sprite() {
let mut x = Chip8Computer::new();
x.quirk_mode = QuirkMode::SChipModern;
x.video_memory.set_lowres();
// point at the 1 from chip8
x.registers.poke_i(0x0005);
// point to 1,2 for the drawing
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();
assert_eq!(expected_state, actual_state);
}
#[test]
fn video_lowres_schip_draw_schip_sprite() {}
#[test]
fn video_highres_schip_draw_chip8_sprite() {}
#[test]
fn video_highres_schip_draw_schip_sprite() {}