move tests to unit_tests to clean up individual files

adds start of font characters
This commit is contained in:
Trevor Merritt 2024-10-27 12:08:22 -04:00
parent e29ac45c84
commit 1694157e27
31 changed files with 1797 additions and 1524 deletions

View File

@ -28,34 +28,3 @@ impl DelayTimer {
} }
} }
} }
#[cfg(test)]
mod test {
use crate::chip8::sound_timer::SoundTimer;
use super::*;
#[test]
fn smoke() {
assert!(true)
}
#[test]
fn 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 out_of_ticks_works() {
let mut st = DelayTimer::new();
st.set_timer(0);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 0);
}
}

View File

@ -735,7 +735,23 @@ impl Chip8CpuInstructions {
let source_memory_offset = input.registers.peek_i(); let source_memory_offset = input.registers.peek_i();
let x_offset = input.registers.peek(*x) as u16; let x_offset = input.registers.peek(*x) as u16;
let y_offset = input.registers.peek(*y) as u16; let y_offset = input.registers.peek(*y) as u16;
if input.video_memory.is_highres() {
// if n == 0 we have a 16 row sprite (font maybe)
let actual_num_loops = if *n == 0u8 {
16
} else {
*n
};
for byte_index in 0..actual_num_loops {
let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset);
let next_byte = input.memory.peek(byte_index as u16 + 1u16 + source_memory_offset);
let x_offset = (x_offset + byte_index as u16) * 64;
for bit_index in 0..8 {
input.video_memory.poke(x_offset + (y_offset + bit_index as u16), (current_byte & (0x80 >> bit_index)) != 0);
input.video_memory.poke(x_offset + (y_offset + bit_index as u16) + 8, (current_byte & (0x80 >> bit_index)) != 0);
}
}
} else {
for byte_index in 0..*n { for byte_index in 0..*n {
let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset); let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset);
let x_offset: u16 = (x_offset + byte_index as u16) * 64; let x_offset: u16 = (x_offset + byte_index as u16) * 64;
@ -743,6 +759,7 @@ impl Chip8CpuInstructions {
input.video_memory.poke(x_offset + (y_offset + bit_index as u16), (current_byte & (0x80 >> bit_index)) != 0); input.video_memory.poke(x_offset + (y_offset + bit_index as u16), (current_byte & (0x80 >> bit_index)) != 0);
} }
} }
}
let target = if input.video_memory.has_frame_changed { let target = if input.video_memory.has_frame_changed {
1u8 1u8
@ -921,766 +938,3 @@ impl Chip8CpuInstructions {
Chip8CpuInstructions::XXXXERRORINSTRUCTION Chip8CpuInstructions::XXXXERRORINSTRUCTION
} }
} }
#[cfg(test)]
mod test {
use crate::chip8::system_memory::{*};
use crate::constants::CHIP8_VIDEO_MEMORY;
use super::*;
#[test]
fn smoke() {
assert!(true)
}
#[test]
fn encode_decode_test() {
assert_eq!(Chip8CpuInstructions::CLS.encode(), 0x00E0);
assert_eq!(Chip8CpuInstructions::RET.encode(), 0x00EE);
assert_eq!(Chip8CpuInstructions::SYS(0x123).encode(), 0x0123);
assert_eq!(Chip8CpuInstructions::JPA(0x234).encode(), 0x1234);
assert_eq!(Chip8CpuInstructions::CALL(0x345).encode(), 0x2345);
assert_eq!(Chip8CpuInstructions::SEX(0x4, 0x56).encode(), 0x3456);
assert_eq!(Chip8CpuInstructions::SNEB(0xa, 0xbc).encode(), 0x4abc);
assert_eq!(Chip8CpuInstructions::SEY(0xa, 0xb).encode(), 0x5ab0);
assert_eq!(Chip8CpuInstructions::LDR(0xa, 0xff).encode(), 0x6aff);
assert_eq!(Chip8CpuInstructions::ADD(0xa, 0xbc).encode(), 0x7abc);
assert_eq!(Chip8CpuInstructions::LDR_Y(0xa, 0xb).encode(), 0x8ab0);
assert_eq!(Chip8CpuInstructions::OR(0xb, 0xa).encode(), 0x8ba1);
assert_eq!(Chip8CpuInstructions::AND(0xc, 0xd).encode(), 0x8cd2);
assert_eq!(Chip8CpuInstructions::ORY(0xd, 0xe).encode(), 0x8de3);
assert_eq!(Chip8CpuInstructions::ADDR(0xe, 0xf).encode(), 0x8ef4);
assert_eq!(Chip8CpuInstructions::SUB(0xf, 0x0).encode(), 0x8f05);
assert_eq!(Chip8CpuInstructions::SHR(0x0, 0x1).encode(), 0x8016);
assert_eq!(Chip8CpuInstructions::SUBC(0x1, 0x2).encode(), 0x8127);
assert_eq!(Chip8CpuInstructions::SHL(0x3, 0x4).encode(), 0x834e);
assert_eq!(Chip8CpuInstructions::SNEY(0xa, 0xb).encode(), 0x9ab0);
assert_eq!(Chip8CpuInstructions::LDIA(0x123).encode(), 0xa123);
assert_eq!(Chip8CpuInstructions::JPI(0x234).encode(), 0xb234);
assert_eq!(Chip8CpuInstructions::RND(0xa, 0xca).encode(), 0xcaca);
assert_eq!(Chip8CpuInstructions::DRW(0xa, 0xb, 0x4).encode(), 0xdab4);
assert_eq!(Chip8CpuInstructions::SKP(0x1).encode(), 0xe19e);
assert_eq!(Chip8CpuInstructions::SKNP(0x2).encode(), 0xe2a1);
assert_eq!(Chip8CpuInstructions::LDRD(0x1).encode(), 0xf107);
assert_eq!(Chip8CpuInstructions::LDRK(0x4).encode(), 0xf40a);
assert_eq!(Chip8CpuInstructions::LDD(0x6).encode(), 0xf615);
assert_eq!(Chip8CpuInstructions::LDI_S(0xb).encode(), 0xfb18);
assert_eq!(Chip8CpuInstructions::ADDI(0xd).encode(), 0xfd1e);
assert_eq!(Chip8CpuInstructions::LDFX(0xc).encode(), 0xfc29);
assert_eq!(Chip8CpuInstructions::BCD(0xd).encode(), 0xfd33);
assert_eq!(Chip8CpuInstructions::LDIX(0xe).encode(), 0xfe55);
assert_eq!(Chip8CpuInstructions::LDRI(0x3).encode(), 0xf365);
assert_eq!(Chip8CpuInstructions::SDN(0x1).encode(), 0x00C1);
assert_eq!(Chip8CpuInstructions::SLF.encode(), 0x00FC);
assert_eq!(Chip8CpuInstructions::SRT.encode(), 0x00FB);
assert_eq!(Chip8CpuInstructions::EXIT.encode(), 0x00FD);
assert_eq!(Chip8CpuInstructions::ENA.encode(), 0x00FF);
assert_eq!(Chip8CpuInstructions::DIS.encode(), 0x00FE);
assert_eq!(Chip8CpuInstructions::LDF2(0).encode(), 0xF030);
assert_eq!(Chip8CpuInstructions::STR(1).encode(), 0xF175);
assert_eq!(Chip8CpuInstructions::LIDR(1).encode(), 0xF185);
assert!(matches!(Chip8CpuInstructions::decode(0xF175), Chip8CpuInstructions::STR(1)));
assert!(matches!(Chip8CpuInstructions::decode(0xF185), Chip8CpuInstructions::LIDR(1)));
assert!(matches!(Chip8CpuInstructions::decode(0x00C1u16), Chip8CpuInstructions::SDN(0x01)));
assert!(matches!(Chip8CpuInstructions::decode(0x00FCu16), Chip8CpuInstructions::SLF));
assert!(matches!(Chip8CpuInstructions::decode(0x00FBu16), Chip8CpuInstructions::SRT));
assert!(matches!(Chip8CpuInstructions::decode(0x00FDu16), Chip8CpuInstructions::EXIT));
assert!(matches!(Chip8CpuInstructions::decode(0x00FEu16), Chip8CpuInstructions::DIS));
assert!(matches!(Chip8CpuInstructions::decode(0x00FFu16), Chip8CpuInstructions::ENA));
assert!(matches!(Chip8CpuInstructions::decode(0xF030u16), Chip8CpuInstructions::LDF2(0)));
assert!(matches!(Chip8CpuInstructions::decode(0x00E0u16), Chip8CpuInstructions::CLS));
assert!(matches!(Chip8CpuInstructions::decode(0x00EEu16), Chip8CpuInstructions::RET));
assert!(matches!(Chip8CpuInstructions::decode(0x0123), Chip8CpuInstructions::SYS(0x123)));
assert!(matches!(Chip8CpuInstructions::decode(0x0FFF), Chip8CpuInstructions::SYS(0xfff)));
assert!(matches!(Chip8CpuInstructions::decode(0x1002), Chip8CpuInstructions::JPA(0x2)));
assert!(matches!(Chip8CpuInstructions::decode(0x1FF0), Chip8CpuInstructions::JPA(0xFF0)));
assert!(matches!(Chip8CpuInstructions::decode(0x2002), Chip8CpuInstructions::CALL(0x2)));
assert!(matches!(Chip8CpuInstructions::decode(0x3123), Chip8CpuInstructions::SEX(0x1, 0x23)));
assert!(matches!(Chip8CpuInstructions::decode(0x4abc), Chip8CpuInstructions::SNEB(0xa, 0xbc)));
assert!(matches!(Chip8CpuInstructions::decode(0x5ab0), Chip8CpuInstructions::SEY(0xa, 0xb)));
assert!(matches!(Chip8CpuInstructions::decode(0x6aff), Chip8CpuInstructions::LDR(0xa, 0xff)));
assert!(matches!(Chip8CpuInstructions::decode(0x7abc), Chip8CpuInstructions::ADD(0xa, 0xbc)));
assert!(matches!(Chip8CpuInstructions::decode(0x8ab0), Chip8CpuInstructions::LDR_Y(0xa, 0xb)));
assert!(matches!(Chip8CpuInstructions::decode(0x8ba1), Chip8CpuInstructions::OR(0xb, 0xa)));
assert!(matches!(Chip8CpuInstructions::decode(0x8cd2), Chip8CpuInstructions::AND(0xc, 0xd)));
assert!(matches!(Chip8CpuInstructions::decode(0x8de3), Chip8CpuInstructions::ORY(0xd, 0xe)));
assert!(matches!(Chip8CpuInstructions::decode(0x8ef4), Chip8CpuInstructions::ADDR(0xe, 0xf)));
assert!(matches!(Chip8CpuInstructions::decode(0x8f05), Chip8CpuInstructions::SUB(0xf, 0x0)));
assert!(matches!(Chip8CpuInstructions::decode(0x8016), Chip8CpuInstructions::SHR(0x0, 0x1)));
assert!(matches!(Chip8CpuInstructions::decode(0x8127), Chip8CpuInstructions::SUBC(0x1, 0x2)));
assert!(matches!(Chip8CpuInstructions::decode(0x834e), Chip8CpuInstructions::SHL(0x3, 0x4)));
assert!(matches!(Chip8CpuInstructions::decode(0x9ab0), Chip8CpuInstructions::SNEY(0xa, 0xb)));
assert!(matches!(Chip8CpuInstructions::decode(0xa123), Chip8CpuInstructions::LDIA(0x123)));
assert!(matches!(Chip8CpuInstructions::decode(0xb234), Chip8CpuInstructions::JPI(0x234)));
assert!(matches!(Chip8CpuInstructions::decode(0xcaca), Chip8CpuInstructions::RND(0xa, 0xca)));
assert!(matches!(Chip8CpuInstructions::decode(0xdab4), Chip8CpuInstructions::DRW(0xa, 0xb, 0x4)));
assert!(matches!(Chip8CpuInstructions::decode(0xe19e), Chip8CpuInstructions::SKP(0x1)));
assert!(matches!(Chip8CpuInstructions::decode(0xe2a1), Chip8CpuInstructions::SKNP(0x2)));
assert!(matches!(Chip8CpuInstructions::decode(0xf107), Chip8CpuInstructions::LDRD(0x1)));
assert!(matches!(Chip8CpuInstructions::decode(0xf40a), Chip8CpuInstructions::LDRK(0x4)));
assert!(matches!(Chip8CpuInstructions::decode(0xf615), Chip8CpuInstructions::LDD(0x6)));
assert!(matches!(Chip8CpuInstructions::decode(0xfb18), Chip8CpuInstructions::LDI_S(0xb)));
assert!(matches!(Chip8CpuInstructions::decode(0xfd1e), Chip8CpuInstructions::ADDI(0xd)));
assert!(matches!(Chip8CpuInstructions::decode(0xfc29), Chip8CpuInstructions::LDFX(0xc)));
assert!(matches!(Chip8CpuInstructions::decode(0xfd33), Chip8CpuInstructions::BCD(0xd)));
assert!(matches!(Chip8CpuInstructions::decode(0xfe55), Chip8CpuInstructions::LDIX(0xe)));
assert!(matches!(Chip8CpuInstructions::decode(0xf365), Chip8CpuInstructions::LDRI(0x3)));
}
#[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).encode(), 0xffff);
assert!(matches!(Chip8CpuInstructions::decode(i), Chip8CpuInstructions::XXXXERRORINSTRUCTION));
}
}
/// START OF THE EXECUTION TESTS
#[test]
fn sys_test() {
// 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);
}
#[test]
fn jpaddr_test() {
// 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);
}
// ** test moved up so it can be used later
#[test]
fn ld_vx_byte_test() {
// 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);
}
#[test]
fn sevxbyte_match_test() {
// 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);
}
#[test]
fn se_vx_vy_test() {
// 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);
}
#[test]
fn ld_vx_vy_test() {
// 0x8xy0 Set value of Vy in Vx
let mut x = Chip8Computer::new();
x.registers.poke(0x01, 0x01);
x.registers.poke(0x02, 0x02);
Chip8CpuInstructions::LDR_Y(0x01, 0x02).execute(&mut x);
assert_eq!(x.registers.peek(1), 0x02);
}
#[test]
fn or_vx_vy_test() {
// 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);
}
#[test]
fn and_vx_vy_test() {
// 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);
}
#[test]
fn xor_vx_vy_test() {
// 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);
}
#[test]
fn add_vx_vy_test() {
// 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);
}
#[test]
fn shr_vx_vy_test() {
/*
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);
}
#[test]
fn ldi_addr_test() {
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_eq!(x.video_memory.peek(i as u16), false);
}
// 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_eq!(x.video_memory.peek(i as u16), false);
}
}
#[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::LDI_S(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);
}
#[test]
fn shl_vx_vy_test_1() {
// 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);
}
#[test]
fn ld_f_vx_test() {
// 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);
}
#[test]
fn ld_b_vx_test() {
// 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);
}
#[test]
fn ld_i_vx_test() {
// 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);
}
}
#[test]
fn ld_vx_i_test() {
// 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);
}
}
#[test]
fn Sknkpvx_test() {
// 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);
}
#[test]
fn skpvx_test() {
// 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]
fn draw_nibble_vx_vy_n_test() {
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);
}
#[test]
fn ret_test() {
// 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, 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);
}
#[test]
fn series8xy6_corex_tests() {
// 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);
}
}

View File

@ -60,28 +60,3 @@ impl Keypad {
!self.key_state(key) !self.key_state(key)
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn smoke() { assert!(true) }
#[test]
fn 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));
}
}

View File

@ -2,9 +2,9 @@
/// Privately using zero base array so -1 to shift from pub to priv. /// Privately using zero base array so -1 to shift from pub to priv.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Chip8Registers { pub struct Chip8Registers {
registers: [u8; 16], pub registers: [u8; 16],
i_register: u16, pub i_register: u16,
pc: u16, pub pc: u16,
} }
impl Chip8Registers { impl Chip8Registers {
@ -82,60 +82,3 @@ impl Chip8Registers {
) )
} }
} }
#[cfg(test)]
mod test {
use rand::random;
use crate::chip8::registers::Chip8Registers;
#[test]
fn smoke() { assert!(true) }
#[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.registers[register] = random::<u8>();
}
x.reset();
for register in 0..0x10 {
assert_eq!(x.registers[register], 0x00);
}
}
}

View File

@ -37,33 +37,3 @@ impl SoundTimer {
self.counter = 0x00; self.counter = 0x00;
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn smoke() {
assert!(true)
}
#[test]
fn 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 out_of_ticks_works() {
let mut st = SoundTimer::new();
st.set_timer(0);
st.tick();
st.tick();
st.tick();
assert_eq!(st.current(), 0);
}
}

View File

@ -39,71 +39,3 @@ impl Chip8Stack {
self.items = vec![] self.items = vec![]
} }
} }
#[cfg(test)]
mod test {
use rand::random;
use super::*;
#[test]
fn smoke() { assert!(true) }
#[test]
fn 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 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);
}
}
}

View File

@ -87,45 +87,3 @@ impl Chip8SystemMemory {
} }
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn smoke() {
assert!(true)
}
#[test]
fn model_smoke() {
let m = Chip8SystemMemory::default();
for i in 0..5 {
assert_eq!(m.peek(i), CHIP8FONT_0[i as usize]);
}
assert_eq!(m.peek((CHIP8_MEMORY_SIZE - 1) as u16), 0);
}
#[test]
fn known_data_loaded_correctly() {
let to_load = [ 0x01, 0x02, 0x03, 0x04, 0x05 , 0x06 ];
let mut x = Chip8SystemMemory::default();
for (index, value) in [1..10].iter().enumerate() {
assert_ne!(x.peek(0), 0x01);
x.poke(0, 0x01);
assert_eq!(x.peek(0), 0x01);
}
}
#[test]
fn verify_load_program() {
// first line of 1-chip-logo.ch8
let program_to_load = [0x00e0, 0x6101, 0x6008, 0xa250, 0xd01f, 0x6010, 0xa25f, 0xd01f];
let mut x = Chip8SystemMemory::new();
}
}

View File

@ -62,59 +62,3 @@ impl InstructionUtil {
((to_read_from & 0x0f00) >> 8) as u8 ((to_read_from & 0x0f00) >> 8) as u8
} }
} }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn smoke() {
assert!(true)
}
#[test]
fn 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 join_bytes() {
// from 0xAB low and 0xCD high we get 0xABCD
let merged = InstructionUtil::join_bytes(0xcd, 0xab);
assert_eq!(merged, 0xcdab);
}
#[test]
fn 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 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 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);
}
}

View File

@ -1,4 +1,5 @@
use log::{debug}; use log::{debug};
use crate::chip8::util::InstructionUtil;
use crate::chip8::video::Chip8VideoModes::{HighRes, LowRes}; use crate::chip8::video::Chip8VideoModes::{HighRes, LowRes};
use crate::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_MEMORY, CHIP8_VIDEO_WIDTH, SCHIP_VIDE_MEMORY, SCHIP_VIDEO_HEIGHT, SCHIP_VIDEO_WIDTH}; use crate::constants::{CHIP8_VIDEO_HEIGHT, CHIP8_VIDEO_MEMORY, CHIP8_VIDEO_WIDTH, SCHIP_VIDE_MEMORY, SCHIP_VIDEO_HEIGHT, SCHIP_VIDEO_WIDTH};
@ -204,393 +205,3 @@ impl Default for Chip8Video {
Chip8Video { memory: mem, has_frame_changed: false, current_res: Chip8VideoModes::LowRes } Chip8Video { memory: mem, has_frame_changed: false, current_res: Chip8VideoModes::LowRes }
} }
} }
#[cfg(test)]
mod test {
use std::io::Read;
use crate::constants::*;
use super::*;
const TEST_OUTPUT_SAMPLE_DIR: &str = "../resources/test/";
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)
}
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 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 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 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 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 poke_multirow_2_byte_sprite() {
// take 2 rows of 16bits and write them to memory
}
#[test]
fn cls_stddef() {
let width = 64;
let height = 32;
let mut 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 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 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 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 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 write_checkboard() {
let mut v = build_checkerboard();
assert_eq!(v.clone().format_as_string(), read_test_result("test_video_write_checkerboard.asc"));
}
#[test]
fn 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 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 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 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_eq!(false, x.peek(0x00));
assert_eq!(true, x.clone().has_frame_changed);
}
#[test]
fn collision_test2() {
let mut x = Chip8Video::default();
x.poke_byte(0x00, 0b11110000);
assert_eq!(true, x.has_frame_changed);
x.tick();
assert_eq!(false, x.has_frame_changed);
// clear the 'has changed' flag
// now set a no-collision value
x.poke_byte(0x00, 0b00001111);
assert_eq!(true, x.has_frame_changed);
}
#[test]
fn 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 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 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 high_res_has_right_resolution() {
let mut x = build_checkboard_hd();
println!("[{}]", x.format_as_string());
assert_eq!(read_test_result("test_video_highdef.asc"), x.format_as_string());
}
#[test]
fn 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 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 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 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 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 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());
}
}

View File

@ -5,6 +5,9 @@ pub const CHIP8_VIDEO_HEIGHT: i32 = 32i32;
pub const CHIP8_VIDEO_MEMORY: usize = (CHIP8_VIDEO_HEIGHT * CHIP8_VIDEO_WIDTH) as usize; pub const CHIP8_VIDEO_MEMORY: usize = (CHIP8_VIDEO_HEIGHT * CHIP8_VIDEO_WIDTH) as usize;
pub const CHIP8_ROM_SIZE: usize = 512; pub const CHIP8_ROM_SIZE: usize = 512;
pub const RESOURCES_ROOT: &str = "../resources";
pub const TESTS_ROOT: &str = "../resources/tests/";
pub const CHIP8_KEYBOARD: [[u8; 4]; 4] = [ pub const CHIP8_KEYBOARD: [[u8; 4]; 4] = [
[0x01, 0x02, 0x03, 0x0C], [0x01, 0x02, 0x03, 0x0C],
[0x04, 0x05, 0x06, 0x0D], [0x04, 0x05, 0x06, 0x0D],
@ -53,7 +56,7 @@ pub const INST_SKP: &str = "SKP";
pub const INST_SNEB: &str = "SNEB"; pub const INST_SNEB: &str = "SNEB";
pub const INST_SNEY: &str = "SNEY"; pub const INST_SNEY: &str = "SNEY";
pub const INST_SNKP: &str = "SNKP"; pub const INST_SNKP: &str = "SNKP";
pub const INST_STR : &str = "STR"; pub const INST_STR: &str = "STR";
pub const INST_SUB: &str = "SUB"; pub const INST_SUB: &str = "SUB";
pub const INST_SUBC: &str = "SUBC"; pub const INST_SUBC: &str = "SUBC";
pub const INST_SYS: &str = "SYS"; pub const INST_SYS: &str = "SYS";
@ -61,8 +64,6 @@ pub const INST_DIS: &str = "DIS";
pub const INST_ENA: &str = "ENA"; pub const INST_ENA: &str = "ENA";
pub const INST_ORY: &str = "ORY"; pub const INST_ORY: &str = "ORY";
pub const CHIP8_PROGRAM_LOAD_OFFSET: i32 = 0x200; pub const CHIP8_PROGRAM_LOAD_OFFSET: i32 = 0x200;
pub const CHIP8FONT_0: [u8; 5] = [0xF0, 0x90, 0x90, 0x90, 0xF0]; pub const CHIP8FONT_0: [u8; 5] = [0xF0, 0x90, 0x90, 0x90, 0xF0];
pub const CHIP8FONT_1: [u8; 5] = [0x20, 0x60, 0x20, 0x20, 0x70]; pub const CHIP8FONT_1: [u8; 5] = [0x20, 0x60, 0x20, 0x20, 0x70];
@ -81,47 +82,90 @@ pub const CHIP8FONT_D: [u8; 5] = [0xE0, 0x90, 0x90, 0x90, 0xE0];
pub const CHIP8FONT_E: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0xF0]; pub const CHIP8FONT_E: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0xF0];
pub const CHIP8FONT_F: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0x80]; pub const CHIP8FONT_F: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0x80];
pub const SCHIPFONT_0: [u8; 0x20] = [
pub const SCHIPFONT_0: [u8; 0x10] = [0xF0, 0xFC, 0xFE, 0xFF, 0xF3, 0xE1, 0xE0, 0xE0, 0x00, 0x00,
0xE0, 0xE0, 0xE0, 0xF1, 0xFB, 0xFF, 0xFE, 0x7C]; 0x01, 0x80,
pub const SCHIPFONT_1: [u8; 0x10] = [0x18, 0x3C, 0x7E, 0xFF, 0xF7, 0xE3, 0xC1, 0xC0, 0x03, 0xc0,
0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0 0x06, 0x60,
0x0c, 0x30,
0x0c, 0x30,
0x18, 0x18,
0x18, 0x18,
0x18, 0x18,
0x18, 0x18,
0x0c, 0x30,
0x0c, 0x30,
0x06, 0x60,
0x03, 0xc0, // 0b0000001111000000
0x01, 0x80, // 0b0000000110000000
0x00, 0x00 // 0b0000000000000000
]; ];
pub const SCHIPFONT_2: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0, pub const SCHIPFONT_1: [u8; 0x20] = [
0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF 0x00, 0x00,
0x03, 0xc0,
0x02, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x00, 0xc0,
0x03, 0xf0,
0x00, 0x00
]; ];
pub const SCHIPFONT_3: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0, pub const SCHIPFONT_2: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0,
0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF]; 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
pub const SCHIPFONT_4: [u8; 0x10] = [0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xF7, 0xF3, 0xF1, 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];
0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0]; pub const SCHIPFONT_3: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC7, 0xC3, 0xC0, 0xE0,
pub const SCHIPFONT_5: [u8; 0x10] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x3F, 0x7F, 0x7F, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0xFF, 0xFF,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0x01, 0x01, 0xC1, 0xE3, 0xFF, 0xFE, 0xFC, 0x78]; 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];
pub const SCHIPFONT_6: [u8; 0x10] = [0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF, pub const SCHIPFONT_4: [u8; 0x20] = [0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xF7, 0xF3, 0xF1,
0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];
pub const SCHIPFONT_5: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x3F, 0x7F, 0x7F,
0x01, 0x01, 0xC1, 0xE3, 0xFF, 0xFE, 0xFC, 0x78,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];
pub const SCHIPFONT_6: [u8; 0x20] = [0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78 0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
]; ];
pub const SCHIPFONT_7: [u8; 0x10] = [0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0xF0, 0x78, 0x3C, pub const SCHIPFONT_7: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0xF0, 0x78, 0x3C,
0x1E, 0x0F, 0x07, 0x03, 0x01, 0x01, 0x01, 0x01 0x1E, 0x0F, 0x07, 0x03, 0x01, 0x01, 0x01, 0x01,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
]; ];
pub const SCHIPFONT_8: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, pub const SCHIPFONT_8: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF,
0x7E, 0xFE, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C 0x7E, 0xFE, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
]; ];
pub const SCHIPFONT_9: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, pub const SCHIPFONT_9: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF,
0xFF, 0x7F, 0x03, 0x03, 0xC7, 0xFF, 0xFE, 0x7C 0xFF, 0x7F, 0x03, 0x03, 0xC7, 0xFF, 0xFE, 0x7C,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
]; ];
pub const SCHIPFONT_A: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, pub const SCHIPFONT_A: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF,
0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
]; ];
pub const SCHIPFONT_B: [u8; 0x10] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xFE, 0xFF, pub const SCHIPFONT_B: [u8; 0x20] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xFE, 0xFF,
0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
]; ];
pub const SCHIPFONT_C: [u8; 0x10] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0x01, 0x01, pub const SCHIPFONT_C: [u8; 0x20] = [0x7C, 0xFE, 0xFF, 0xFF, 0xC3, 0xC3, 0x01, 0x01,
0x01, 0x01, 0xC3, 0xC3, 0xFF, 0xFE, 0xFC, 0x78 0x01, 0x01, 0xC3, 0xC3, 0xFF, 0xFE, 0xFC, 0x78,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
]; ];
pub const SCHIPFONT_D: [u8; 0x10] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, pub const SCHIPFONT_D: [u8; 0x20] = [0xFE, 0xFF, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xFE, 0x7C, 0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
]; ];
pub const SCHIPFONT_E: [u8; 0x10] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF, pub const SCHIPFONT_E: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF,
0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, 0xFF, 0xFF 0xFF, 0xFF, 0x03, 0x03, 0xFF, 0xFF, 0xFF, 0xFF,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78
]; ];
pub const SCHIPFONT_F: [u8; 0x10] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF, pub const SCHIPFONT_F: [u8; 0x20] = [0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0xFF,
0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03]; 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,0x78, 0xFE, 0xFF, 0xFF, 0x83, 0x01, 0x01, 0xFF,
0xFF, 0xFF, 0xC3, 0xE3, 0xFF, 0xFE, 0xFC, 0x78];

View File

@ -48,11 +48,9 @@ fn level2_test() {
let mut x = Chip8Computer::new(); let mut x = Chip8Computer::new();
// Load the IBM rom and run it. // Load the IBM rom and run it.
// it takes 39 cycles to get to the end so lets run it 40. // it takes 39 cycles to get to the end so lets run it 40.
let test_rom_to_run = load_rom("2-ibm-logo.ch8"); let test_rom_to_run = load_rom("2-ibm-logo.ch8");
x.load_bytes_to_memory(0x200, (&test_rom_to_run).into()); x.load_bytes_to_memory(0x200, (&test_rom_to_run).into());
for _ in 0..40 {
for i in 0..40 {
x.step_system(); x.step_system();
} }
// ...then verify that the current video memory of the chip-8 // ...then verify that the current video memory of the chip-8
@ -79,12 +77,12 @@ fn level3_test() {
fn rps_test() { fn rps_test() {
let mut x = Chip8Computer::new(); let mut x = Chip8Computer::new();
x.load_bytes_to_memory(0x200, &load_rom("RPS.ch8").into()); x.load_bytes_to_memory(0x200, &load_rom("RPS.ch8").into());
for i in 0..0xF0 { for _ in 0..0xF0 {
x.step_system(); x.step_system();
} }
assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_rps_stage1.asc")); assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_rps_stage1.asc"));
x.keypad.push_key(0x01); x.keypad.push_key(0x01);
for i in 0..0x200 { for _ in 0..0x200 {
x.step_system(); x.step_system();
} }
assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_rps_stage2.asc")); assert_eq!(x.dump_video_to_string(), load_result("gemma_integration_rps_stage2.asc"));
@ -95,7 +93,7 @@ fn level4_test() {
// flags // flags
let mut x = Chip8Computer::new(); let mut x = Chip8Computer::new();
x.load_bytes_to_memory(0x200, &load_rom("4-flags.ch8").into()); x.load_bytes_to_memory(0x200, &load_rom("4-flags.ch8").into());
for i in 0..0x400 { for _ in 0..0x400 {
x.step_system(); x.step_system();
} }

1320
gemma/tests/unit_tests.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ fn main() {
support::simple_init(file!(), move |_, ui| { support::simple_init(file!(), move |_, ui| {
let current_time = Instant::now(); let current_time = Instant::now();
let mut num_cycles = 0;
// Key Checks // Key Checks
let down_keys = ui.io().keys_down; let down_keys = ui.io().keys_down;
@ -58,9 +59,10 @@ fn main() {
} }
} }
while Instant::now().duration_since(current_time).as_millis() < 16 && num_cycles < 1000 {
system.tick(); system.tick();
num_cycles += 1;
}
// GUI Parts // GUI Parts
if ui_state.show_video { if ui_state.show_video {
GemmaImguiSupport::video_display(&system.state(), &ui_state, ui); GemmaImguiSupport::video_display(&system.state(), &ui_state, ui);

View File

@ -16,7 +16,7 @@ use crate::ImGuiUiState;
use crate::support::gui_file_list::GuiFileList; use crate::support::gui_file_list::GuiFileList;
use super::ui_state; use super::ui_state;
const ROM_ROOT: &str = "/home/tmerritt/Projects/trevors_chip8_toy/resources/octoroms"; const ROM_ROOT: &str = "/home/tmerritt/Projects/trevors_chip8_toy/resources/roms";
pub struct GemmaImguiSupport {} pub struct GemmaImguiSupport {}
@ -43,23 +43,37 @@ impl GemmaImguiSupport {
pub fn video_display(system_to_control: &Chip8Computer, gui_state: &ImGuiUiState, ui: &Ui) { pub fn video_display(system_to_control: &Chip8Computer, gui_state: &ImGuiUiState, ui: &Ui) {
// draw area size // draw area size
let (width, height) = system_to_control.video_memory.get_resolution();
let draw_area_size = ui.io().display_size; let draw_area_size = ui.io().display_size;
// println!("DRAW_AREA_SIZE = {}x{}", draw_area_size[0], draw_area_size[1]); // println!("DRAW_AREA_SIZE = {}x{}", draw_area_size[0], draw_area_size[1]);
let cell_width = ((draw_area_size[0] as i32 / 64) * 6) / 10; let cell_width = ((draw_area_size[0] as i32 / width) * 6) / 10;
let cell_height = ((draw_area_size[1] as i32 / 32) * 6) / 10; let cell_height = ((draw_area_size[1] as i32 / height) * 6) / 10;
ui.window(format!("Display {cell_width}x{cell_height}")) ui.window(format!("Display {cell_width}x{cell_height}"))
.size([300.0, 300.0], Condition::Once) .size([300.0, 300.0], Condition::Once)
.build(|| { .build(|| {
let (width, height) = system_to_control.video_memory.get_resolution();
let origin = ui.cursor_screen_pos(); let origin = ui.cursor_screen_pos();
let fg = ui.get_window_draw_list(); let fg = ui.get_window_draw_list();
if (system_to_control.video_memory.is_highres()) { if (system_to_control.video_memory.is_highres()) {
ui.text("High Def Video here"); ui.text("High Def Video here");
for current_row in 0..=height {
let y_offset = origin[1] as i32 + (current_row * cell_height);
for current_column in 0..=width {
let x_offset = origin[0] as i32 + (current_column * cell_width);
let current_origin = [x_offset as f32, y_offset as f32];
let current_limit = [(x_offset + cell_width) as f32, (y_offset + cell_height) as f32];
let memory_offset = (current_row * width + current_column) as u16;
let to_render = system_to_control.video_memory.peek(memory_offset);
let color: ImColor32 = if to_render {
gui_state.on_colour
} else {
gui_state.off_colour
};
fg.add_rect_filled_multicolor(current_origin, current_limit, color, color, color, color);
}
}
} else { } else {
ui.text("StdDef video here."); ui.text("StdDef video here.");
for current_row in 0..=height { for current_row in 0..=height {
let y_offset = origin[1] as i32 + (current_row * cell_height); let y_offset = origin[1] as i32 + (current_row * cell_height);
for current_column in 0..=width { for current_column in 0..=width {
@ -88,7 +102,7 @@ impl GemmaImguiSupport {
ui.text(format!("Step {:04x}", system_to_control.num_cycles()).as_str()); ui.text(format!("Step {:04x}", system_to_control.num_cycles()).as_str());
/* ROM Lister */ /* ROM Lister */
let new_filename = GuiFileList::display_path(PathBuf::from("/home/tmerritt/Projects/trevors_chip8_toy/resources/octoroms/"), &gui_state.filename_to_load, ui); let new_filename = GuiFileList::display_path(PathBuf::from("/home/tmerritt/Projects/trevors_chip8_toy/resources/roms/"), &gui_state.filename_to_load, ui);
if !new_filename.is_empty() { if !new_filename.is_empty() {
if new_filename != gui_state.filename_to_load { if new_filename != gui_state.filename_to_load {
debug!("NEW FILENAME SELECTED -> {new_filename}"); debug!("NEW FILENAME SELECTED -> {new_filename}");
@ -98,7 +112,7 @@ impl GemmaImguiSupport {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
debug!("PREPARING TO LOAD {}", gui_state.filename_to_load); debug!("PREPARING TO LOAD {}", gui_state.filename_to_load);
// let mut input_file = File::open(Path::new("./1-chip8-logo.ch8")).expect("put 1-chip8-logo.ch8 in this directory"); // let mut input_file = File::open(Path::new("./1-chip8-logo.ch8")).expect("put 1-chip8-logo.ch8 in this directory");
let mut input_file = File::open(Path::new(&("/home/tmerritt/Projects/trevors_chip8_toy/resources/octoroms/".to_string() + &gui_state.filename_to_load))).expect("put 1-chip8-logo.ch8 in this directory"); let mut input_file = File::open(Path::new(&("/home/tmerritt/Projects/trevors_chip8_toy/resources/roms/".to_string() + &gui_state.filename_to_load))).expect("put 1-chip8-logo.ch8 in this directory");
input_file.read_to_end(&mut buffer).expect("unable to read file"); input_file.read_to_end(&mut buffer).expect("unable to read file");
system_to_control.load_bytes_to_system_memory((&*buffer).into()); system_to_control.load_bytes_to_system_memory((&*buffer).into());
} }

View File

@ -0,0 +1,83 @@
use std::fs::File;
use std::io;
use std::io::{BufReader, Read};
use clap::{Arg, Command, ArgAction, ValueEnum};
#[derive(Debug)]
struct CliArgs {
input: String,
}
fn main() {
println!("Taxation is Theft!");
// Set up the command line arguments
let matches = Command::new("my_program")
.about("Processes an input file and outputs it with a specified bit width")
.arg(
Arg::new("input")
.help("The input file to process")
.required(true)
.index(1),
)
.get_matches();
// Parse the command-line arguments
let args = CliArgs {
input: matches.get_one::<String>("input").unwrap().to_string(),
};
// Use the parsed arguments
println!("Input file: {}", args.input);
// behave like a shift register and load each character from the file 1 by 1.
let results = read_file_to_bools(&args.input);
for result in results.unwrap().bytes() {
print!("0x{:02x}, ", result.unwrap());
}
}
fn read_file_to_bools(file_path: &str) -> io::Result<Vec<u8>> {
// Open the file
let file = File::open(file_path)?;
let mut reader = BufReader::new(file);
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes)?;
let mut output = Vec::new();
let mut current_byte = 0u8;
let mut bit_index = 0;
for &byte in &bytes {
// Convert ASCII character '1' or '0' to boolean, skip any other characters
let bit = match byte {
b'1' => true,
b'0' => false,
_ => continue, // Skip non-'1' or '0' characters
};
// Set the appropriate bit in the current byte
if bit {
current_byte |= 1 << (7 - bit_index); // Set the bit at the correct position
}
bit_index += 1;
// Once we have filled 8 bits, push the byte and reset
if bit_index == 8 {
output.push(current_byte);
current_byte = 0;
bit_index = 0;
}
}
// If there are remaining bits, push the last byte (it will be partially filled)
if bit_index > 0 {
output.push(current_byte);
}
Ok(output)
}

View File

@ -0,0 +1,16 @@
0000000000000000
0000000110000000
0000001111000000
0000011001100000
0000110000110000
0000110000110000
0001100000011000
0001100000011000
0001100000011000
0001100000011000
0000110000110000
0000110000110000
0000011001100000
0000001111000000
0000000110000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000001111000000
0000001011000000
0000000011000000
0000000011000000
0000000011000000
0000000011000000
0000000011000000
0000000011000000
0000000011000000
0000000011000000
0000000011000000
0000000011000000
0000000011000000
0000001111110000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000011111000000
0000100000100000
0001000000010000
0000000000100000
0000000001000000
0000000010000000
0000000100000000
0000001000000000
0000010000000000
0000100000000000
0000100000000000
0000100000000000
0000100000000000
0000111111110000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000011111000000
0000100000100000
0001000000010000
0001000000010000
0000000000010000
0000000000010000
0000011111100000
0000000000010000
0000000000010000
0000000000010000
0000100000010000
0000100000100000
0000011111000000
0000000000000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0001000000100000
0001000000100000
0001000000100000
0001000000100000
0001000000100000
0001000000100000
0001111111100000
0000000000100000
0000000000100000
0000000000100000
0000000000100000
0000000000100000
0000000000100000
0000000000100000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0011111111111100
0010000000000000
0010000000000000
0010000000000000
0010000000000000
0010000000000000
0010111111000000
0011000000100000
0000000000010000
0000000000010000
0000000000010000
0001000000100000
0001111111000000
0000000000000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000000000000000
0000011111111100
0000100000000000
0001000000000000
0010000000000000
0010000000000000
0010000000000000
0010111111000000
0011000000100000
0010000000010000
0010000000010000
0010000000010000
0011000000100000
0001111111000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000000110000000
0000001001000000
0000010000100000
0000001001000000
0000000110000000
0000011110000000
0001100001100000
0011000000110000
0011000000110000
0011000000110000
0001100001100000
0000110011000000
0000011110000000
0000000000000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0111111111111000
0100000000001000
0100000000001000
0100000000000000
0100000000000000
0111111111000000
0100000000000000
0100000000000000
0100000000000000
0100000000000000
0100000000000000
0100000000000000
0100000000001000
0111111111111000
0000000000000000

View File

@ -0,0 +1,16 @@
0000000000000000
0111111111111000
0100000000001000
0100000000001000
0100000000000000
0100000000000000
0111111111000000
0100000000000000
0100000000000000
0100000000000000
0100000000000000
0100000000000000
0100000000000000
0100000000000000
0100000000000000
0000000000000000