mod test_utils; use log::debug; use gemma::chip8::computer::Chip8Computer; use gemma::chip8::instructions::Chip8CpuInstructions; use gemma::constants::{CHIP8FONT_2, CHIP8_VIDEO_MEMORY}; use crate::test_utils::read_compressed_test_result; /// START OF THE EXECUTION TESTS #[test] fn instruction_tests() { // 0x0nnn Exit to System Call let mut x = Chip8Computer::new(); Chip8CpuInstructions::SYS(0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0); let mut x = Chip8Computer::new(); Chip8CpuInstructions::SYS(0xFA0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0xFA0); let mut x = Chip8Computer::new(); Chip8CpuInstructions::SYS(0x0AF).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x0AF); // 0x1nnn Jump to Address let mut x = Chip8Computer::new(); Chip8CpuInstructions::JPA(0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0); let mut x = Chip8Computer::new(); Chip8CpuInstructions::JPA(0xABC).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0xABC); // 0x6xkk Set Vx = kk let mut x = Chip8Computer::new(); Chip8CpuInstructions::LDR(1, 0x12).execute(&mut x); assert_eq!(x.registers.peek(1), 0x12); let mut x = Chip8Computer::new(); Chip8CpuInstructions::LDR(2, 0x21).execute(&mut x); assert_eq!(x.registers.peek(2), 0x21); // 0x3xkk Skip next instruction if Vx = kk. // The interpreter compares register Vx to kk, // and if they are equal, increments the program counter by 2. // test setup: Load value 0x84 into V1 let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x84); Chip8CpuInstructions::SEX(1, 0x48).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x84); Chip8CpuInstructions::SEX(1, 0x84).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); // 0x4xkk Skip next instruction if Vx != kk let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x84); x.registers.poke(0x2, 0x84); // skip, compare 0x84 to 0x84 Chip8CpuInstructions::SEY(0x1, 0x2).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x84); x.registers.poke(0x2, 0x48); Chip8CpuInstructions::SEY(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); // 0x8xy0 Set value of Vy in Vx let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x02); Chip8CpuInstructions::LDRY(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(1), 0x02); // 0x8xy1 Set Vx = Vx OR Vy // 0b0101 0000 (0x50) // | 0b0000 1010 (0x0A) // 0b0101 1010 (0x5A) let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0b01010000); x.registers.poke(0x02, 0b00001010); Chip8CpuInstructions::OR(1, 2).execute(&mut x); assert_eq!(x.registers.peek(1), 0b01011010); // 0x8xy2 Set Vx = Vx AND Vy // 0b1111 1100 (0xFC) // & 0b1100 1010 (0xCA) // 0b1100 1000 (0xC8) let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0xFC); x.registers.poke(0x02, 0xCA); Chip8CpuInstructions::AND(1, 2).execute(&mut x); assert_eq!(x.registers.peek(1), 0xC8); // 0x8xy3 Set Vx = Vx XOR Vy // 0b1111 1100 (0xFC) // ^ 0b1100 1010 (0xCA) // 0b0011 0110 (0x36) let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0b11111100); x.registers.poke(0x02, 0b11001010); Chip8CpuInstructions::ORY(1, 2).execute(&mut x); assert_eq!(x.registers.peek(1), 0b00110110); // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry) // T1 T2: Judgement Test // 0x01 0xFF // + 0x01 0x01 // 0x02 F0 0x00 F1 let mut x = Chip8Computer::new(); x.registers.poke(0x0f, 00); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0xf), 0x00); assert_eq!(x.registers.peek(0x01), 0x02); let mut x = Chip8Computer::new(); x.registers.poke(0x0f, 0x00); x.registers.poke(0x01, 0xff); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::ADDR(1, 2).execute(&mut x); assert_eq!(x.registers.peek(0xf), 1); assert_eq!(x.registers.peek(1), 0); /* Set Vx = Vx SHR 1. If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2. */ let mut x = Chip8Computer::new(); x.registers.poke(0x0f, 0x00); x.registers.poke(0x01, 0b00001000); x.registers.poke(0x02, 0b00000000); Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x); // 0b0000 0010 (0x02) (Not Set) assert_eq!(x.registers.peek(1), 0b00000100); assert_eq!(x.registers.peek(0xf), 0); x = Chip8Computer::new(); x.registers.poke(0x0f, 0x00); x.registers.poke(0x01, 0b00001001); Chip8CpuInstructions::SHR(0x1, 0x2).execute(&mut x); assert_eq!(x.registers.peek(1), 0b00000100); assert_eq!(x.registers.peek(0xf), 1); let mut x = Chip8Computer::new(); Chip8CpuInstructions::LDIA(0x123).execute(&mut x); assert_eq!(x.registers.peek_i(), 0x123); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn jp_v0addr_test() { let mut x = Chip8Computer::new(); /// jump to I + nnn x.registers.poke(0x0, 0xff); Chip8CpuInstructions::JPI(0x100).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x1FF); } #[test] fn cls_test() { let mut x = Chip8Computer::new(); Chip8CpuInstructions::CLS.execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); for i in 0..CHIP8_VIDEO_MEMORY { assert!(!x.video_memory.peek(i as u16)); } // draw some thing to the video memory x.video_memory.poke(0x01, true); x.video_memory.poke(0x03, true); x.video_memory.poke(0x05, true); Chip8CpuInstructions::CLS.execute(&mut x); for i in 0..CHIP8_VIDEO_MEMORY { assert!(!x.video_memory.peek(i as u16)); } } #[test] fn skip_next_instruction_ne_text() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::SNEB(0x1, 0x0f).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::SNEB(0x1, 0xf0).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn addivx_test() { let mut x = Chip8Computer::new(); x.registers.poke_i(0xabc); x.registers.poke(0x0, 0x10); Chip8CpuInstructions::ADDI(0x0).execute(&mut x); assert_eq!(x.registers.peek_i(), 0xacc); } #[test] fn ldstvt_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::LDIS(0x01).execute(&mut x); assert_eq!(x.sound_timer.current(), 0xf0); x.sound_timer.tick(); x.sound_timer.tick(); x.sound_timer.tick(); assert_eq!(x.sound_timer.current(), 0xed); } #[test] fn rnd_vx_byte_text() { let mut x = Chip8Computer::new(); Chip8CpuInstructions::RND(0x1, 0x0f).execute(&mut x); let new_value = x.registers.peek(0x1); assert!(new_value < 0x10); } #[test] fn add_vx_byte_test() { let mut x = Chip8Computer::new(); // set a value in the register x.registers.poke(0x01, 0xab); // add 0x10 to register Chip8CpuInstructions::ADD(0x1, 0x10).execute(&mut x); assert_eq!(x.registers.peek(1), 0xbb); } #[test] fn sub_vx_vy_test() { let mut x = Chip8Computer::new(); // load values in 2 registers x.registers.poke(0x1, 0x10); x.registers.poke(0x2, 0x08); Chip8CpuInstructions::SUB(0x1, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0xf), 1); assert_eq!(x.registers.peek(0x1), 0x8); assert_eq!(x.registers.peek_pc(), 0x202); } #[test] fn sne_vx_vy_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x10); x.registers.poke(0x2, 0x10); Chip8CpuInstructions::SNEY(0x1, 0x2).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x10); x.registers.poke(0x2, 0x00); Chip8CpuInstructions::SNEY(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204) } #[test] fn ld_dt_vx_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0x10); Chip8CpuInstructions::LDD(0x1).execute(&mut x); assert_eq!(x.delay_timer.current(), 0x10); for _ in 0..0x20 { x.delay_timer.tick(); } assert_eq!(x.delay_timer.current(), 0); } #[test] fn ld_vx_dt_test() { let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xf0); Chip8CpuInstructions::LDD(0x1).execute(&mut x); x.delay_timer.tick(); x.delay_timer.tick(); x.delay_timer.tick(); assert_eq!(x.delay_timer.current(), 0xed); } #[test] fn subn_vx_vy_test() { // This instruction subtracts the value in // register Vx from the value in register Vy and stores the result in register Vx. // The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx, // the result will wrap around (due to the 8-bit nature of the registers). // The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater // than or equal to Vx), and it is set to 0 if there is a borrow. let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0xa0); x.registers.poke(0x2, 0xab); Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x); // expect the result to be 0x0b assert_eq!(x.registers.peek(0x1), 0x0b); // expect the vf register to be set to 1 as there was overflow assert_eq!(x.registers.peek(0xf), 0x1); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0xab); x.registers.poke(0x02, 0xa0); Chip8CpuInstructions::SUBC(0x1, 0x2).execute(&mut x); // expect the result to be 11110101, -0xB, -11, 245, 0xF5 assert_eq!(x.registers.peek(0x1), 0xf5); assert_eq!(x.registers.peek(0xf), 0x0); // 8xyE - SHL Vx {, Vy} // Set Vx = Vx SHL 1. // // If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2. let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0b00100000); Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x); assert_eq!(x.registers.peek(0x1), 0b01000000); assert_eq!(x.registers.peek(0xf), 0x0); let mut x = Chip8Computer::new(); x.registers.poke(0x1, 0b10101010); Chip8CpuInstructions::SHL(0x1, 0x1).execute(&mut x); assert_eq!(x.registers.peek(0x1), 0b01010100); assert_eq!(x.registers.peek(0xf), 0x1); // Fx29 - LD F, Vx // Set I = location of sprite for digit Vx. // // The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font. let mut x = Chip8Computer::new(); // target_sprite = 2 // target_offset = 0x5 x.registers.poke(0x1, 0x2); Chip8CpuInstructions::LDFX(0x1).execute(&mut x); assert_eq!(x.registers.peek_i(), 10); let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x06); Chip8CpuInstructions::LDFX(0x1).execute(&mut x); assert_eq!(x.registers.peek_i(), 30); // Fx33 - LD B, Vx // Store BCD representation of Vx in memory locations I, I+1, and I+2. // // The interpreter takes the decimal value of Vx, and places the hundreds digit // in memory at location in I, the tens digit at location I+1, // and the ones digit at location I+2. let mut x = Chip8Computer::new(); // load the value 123 (0x7b) x.registers.poke(0x1, 0x7b); x.registers.poke_i(0x500); Chip8CpuInstructions::BCD(0x1).execute(&mut x); assert_eq!(x.memory.peek(0x500), 0x1); assert_eq!(x.memory.peek(0x501), 0x2); assert_eq!(x.memory.peek(0x502), 0x3); // Store registers V0 through Vx in memory starting at location I. // // The interpreter copies the values of registers V0 through Vx into memory, // starting at the address in I. let mut x = Chip8Computer::new(); // Load Registers. let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef]; for (idx, val) in to_load.iter().enumerate() { x.registers.poke(idx as u8, *val); } x.registers.poke_i(0x500); Chip8CpuInstructions::LDIX(to_load.len() as u8).execute(&mut x); // Verify the values are in memory from 0x500 to 0x507 for (idx, value) in to_load.iter().enumerate() { assert_eq!(x.memory.peek(0x500 + idx as u16), *value); } // Read registers V0 through Vx from memory starting at location I. // // The interpreter reads values from memory starting at location I into registers V0 through Vx. let mut x = Chip8Computer::new(); let base_offset = 0x500; let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef]; // start by setting values in memory for (idx, memory) in to_load.iter().enumerate() { let target_address = base_offset + idx; let target_value = *memory; x.memory.poke(target_address as u16, target_value); } // where to load from x.registers.poke_i(0x500); // how much to load x.registers.poke(0x6, to_load.len() as u8); // then copying them values memory to registers Chip8CpuInstructions::LDRI(0x6).execute(&mut x); // now check that we have the right values in our registers for (idx, value) in to_load.iter().enumerate() { assert_eq!(x.registers.peek(idx as u8), *value); } // ExA1 - SKNP Vx // Skip next instruction if key with the value of Vx is not pressed. // // Checks the keyboard, // and if the key corresponding to the value of Vx is currently in the up position, // PC is increased by 2. let mut x = Chip8Computer::new(); x.keypad.push_key(0x5); x.registers.poke(0x1, 0x5); Chip8CpuInstructions::SKNP(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x202); x.keypad.release_key(0x5); Chip8CpuInstructions::SKNP(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x206); // Ex9E - SKP Vx // Skip next instruction if key with the value of Vx is pressed. // // Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2. let mut x = Chip8Computer::new(); x.keypad.push_key(0x5); x.registers.poke(0x1, 0x5); Chip8CpuInstructions::SKP(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x204); x.keypad.release_key(0x5); Chip8CpuInstructions::SKP(0x1).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x206); } #[test] #[ignore] fn draw_nibble_vx_vy_n_test_hd() { let mut x = Chip8Computer::new(); let x_register = 0x01; let x_offset = 0x03; let y_register = 0x02; let y_offset = 0x04; let char_offset = 0x100; x.registers.poke(x_register, x_offset); x.registers.poke(y_register, y_offset); x.video_memory.set_highres(); x.registers.poke_i(char_offset); Chip8CpuInstructions::DRW(x_register, y_register, 0).execute(&mut x); println!("[[{}]]", x.video_memory.format_as_string()); assert_eq!(read_compressed_test_result(""), x.video_memory.format_as_string()); } #[test] fn draw_nibble_vx_vy_n_test_sd() { let mut x = Chip8Computer::new(); let x_register = 0x1; let y_register = 0x2; let x_offset = 1; let y_offset = 2; let char_offset = 0x0A; // now lets set the X and Y to 1,2 x.registers.poke(x_register, x_offset); x.registers.poke(y_register, y_offset); x.registers.poke_i(char_offset); // we are using 5 rows. Chip8CpuInstructions::DRW(x_register, y_register, 5).execute(&mut x); // now check that video memory has the values at // 1,2->1,9 // 2,2->2,9 // 3,2->3,9 // 4,2->4,9 // 5,2->5,9 // let byte_to_check = CHIP8FONT_0[0]; for row_in_sprite in 0..5 { let row_data = CHIP8FONT_2[row_in_sprite]; for bit_in_byte in 0..8 { let data_offset = (x_offset as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16; let real_bit_in_byte = 7 - bit_in_byte; let shifted_one = 0x01 << real_bit_in_byte; let one_shift_set = (shifted_one & row_data) > 0; debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]"); debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}", bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data); } } } #[test] fn sub_test() { // 2nnn // Call a subroutine at 2nnn let mut x = Chip8Computer::new(); Chip8CpuInstructions::CALL(0x124).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x124); assert_eq!(x.stack.depth(), 1); Chip8CpuInstructions::CALL(0x564).execute(&mut x); assert_eq!(x.registers.peek_pc(), 0x564); assert_eq!(x.stack.depth(), 2); // SETUP // Return from a subroutine. let mut x = Chip8Computer::new(); x.stack.push(&0x132); x.stack.push(&0xabc); // EXECUTE Chip8CpuInstructions::RET.execute(&mut x); // VERIFY assert_eq!(x.registers.peek_pc(), 0xabc); assert_eq!(x.stack.depth(), 1); // EXECUTE Chip8CpuInstructions::RET.execute(&mut x); // VERIFY assert_eq!(x.registers.peek_pc(), 0x132); assert_eq!(x.stack.depth(), 0); } #[test] fn ldvxk_test() { // SETUP let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x01); Chip8CpuInstructions::LDRK(0x1).execute(&mut x); assert!(matches!( x.state, gemma::chip8::cpu_states::Chip8CpuStates::WaitingForKey )); } #[test] fn series8xy4_corex_tests() { /// 8xy4 /// Set Vx = Vx + Vy /// Set VF=1 if Carry /// // 1 + 1 let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x01); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0x02); assert_eq!(x.registers.peek(0x0f), 0x00); // 255+1 let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0xff); x.registers.poke(0x02, 0x01); Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0x00); assert_eq!(x.registers.peek(0x0f), 0x01); // 128+192 let mut x = Chip8Computer::new(); x.registers.poke(0x01, 128); x.registers.poke(0x02, 192); Chip8CpuInstructions::ADDR(0x01, 0x02).execute(&mut x); assert_eq!(x.registers.peek(0x01), 64); assert_eq!(x.registers.peek(0x0f), 1); // 8xy6 - SHR Vx {, Vy} // Set Vx = Vx SHR 1. // // If the least-significant bit of Vx is 1, then VF is set to 1, // otherwise 0. Then Vx is divided by 2. let mut x = Chip8Computer::new(); // 0b10101010 -> 0b01010101 x.registers.poke(0x01, 0b10101010); x.registers.poke(0x0f, 0x0); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b01010100); assert_eq!(x.registers.peek(0x0f), 1); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b10101000); assert_eq!(x.registers.peek(0x0f), 0x00); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b01010000); assert_eq!(x.registers.peek(0x0f), 0x01); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b10100000); assert_eq!(x.registers.peek(0x0f), 0x00); Chip8CpuInstructions::SHL(0x01, 0x00).execute(&mut x); assert_eq!(x.registers.peek(0x01), 0b01000000); assert_eq!(x.registers.peek(0x0f), 0x01); } #[test] fn random_produces_different_numbers() { let mut x = Chip8Computer::new(); x.registers.poke(0x01, 0x00); let first_number = Chip8CpuInstructions::RND(0x01, 0xff) .execute(&mut x) .registers .peek(0x01); let second_number = Chip8CpuInstructions::RND(0x01, 0xff) .execute(&mut x) .registers .peek(0x01); assert_ne!(first_number, second_number); }