#include "chip8.h" #include "munit.h" #include "test_helpers.h" #include static MunitResult test_init(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START); munit_assert_uint8(chip.dt, ==, 0); munit_assert_uint8(chip.st, ==, 0); munit_assert_false(chip.waiting_for_key); return MUNIT_OK; } static MunitResult test_cls(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); // Fill entire display buffer with active pixels for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) { chip.display[i] = 1; } // Write Clear Screen instruction write_clear_screen_instruction(&chip); chip8_step(&chip); // Verify all pixels are reset to 0 for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) { munit_assert_uint32(chip.display[i], ==, 0); } munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_ret(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); // Set up stack state as if a subroutine call was active chip.sc = 1; chip.stack[0] = 0x300; // Write Return instruction write_return_instruction(&chip); chip8_step(&chip); // Verify stack pointer decrements and PC is restored munit_assert_uint8(chip.sc, ==, 0); munit_assert_uint16(chip.pc, ==, 0x300); return MUNIT_OK; } static MunitResult test_group_jump(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); // Write Jump instruction write_jump_instruction(&chip, 0x400); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, 0x400); return MUNIT_OK; } static MunitResult test_group_call(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); // Simulate existing stack depth of 2 chip.sc = 2; // Write Call instruction write_call_instruction(&chip, 0x400); chip8_step(&chip); // PC jumps to subroutine munit_assert_uint16(chip.pc, ==, 0x400); // Stack pointer is incremented munit_assert_uint8(chip.sc, ==, 3); // Pushed return address points to instruction following call munit_assert_uint16(chip.stack[2], ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_se_vx_byte(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; // Skip next instruction chip8_init(&chip); chip.V[0] = 0x42; write_skip_if_register_equal_value_instruction(&chip, 0, 0x42); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 4); // Do not skip chip8_init(&chip); chip.V[0] = 0x10; write_skip_if_register_equal_value_instruction(&chip, 0, 0x42); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_sne_vx_byte(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; // Skip next instruction chip8_init(&chip); chip.V[0] = 0x10; write_skip_if_register_not_equal_value_instruction(&chip, 0, 0x42); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 4); // Do not skip chip8_init(&chip); chip.V[0] = 0x42; write_skip_if_register_not_equal_value_instruction(&chip, 0, 0x42); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_se_vx_vy(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; // Skip chip8_init(&chip); chip.V[0] = 0x20; chip.V[1] = 0x20; write_skip_if_registers_equal_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 4); // No skip chip8_init(&chip); chip.V[0] = 0x20; chip.V[1] = 0x30; write_skip_if_registers_equal_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_sne_vx_vy(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; // Skip chip8_init(&chip); chip.V[0] = 0x20; chip.V[1] = 0x30; write_skip_if_registers_not_equal_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 4); // No skip chip8_init(&chip); chip.V[0] = 0x20; chip.V[1] = 0x20; write_skip_if_registers_not_equal_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_ld_vx_byte(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); write_set_register_value_instruction(&chip, 2, 0xAB); chip8_step(&chip); munit_assert_uint8(chip.V[2], ==, 0xAB); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_add_vx_byte(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); chip.V[3] = 10; write_add_value_to_register_instruction(&chip, 3, 20); chip8_step(&chip); munit_assert_uint8(chip.V[3], ==, 30); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_arithmetic(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; // Copy Vy into Vx chip8_init(&chip); chip.V[0] = 5; chip.V[1] = 10; write_copy_register_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 10); // OR Vx, Vy chip8_init(&chip); chip.V[0] = 0x0F; chip.V[1] = 0xF0; write_or_registers_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 0xFF); // AND Vx, Vy chip8_init(&chip); chip.V[0] = 0x0F; chip.V[1] = 0x3F; write_and_registers_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 0x0F); // XOR Vx, Vy chip8_init(&chip); chip.V[0] = 0xAA; chip.V[1] = 0xFF; write_xor_registers_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 0x55); // ADD Vx, Vy // Carry overflow (200 + 100 = 300 -> 300 % 256 = 44, VF = 1) chip8_init(&chip); chip.V[0] = 200; chip.V[1] = 100; write_add_registers_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 44); munit_assert_uint8(chip.V[15], ==, 1); // No carry overflow (50 + 100 = 150, VF = 0) chip8_init(&chip); chip.V[0] = 50; chip.V[1] = 100; write_add_registers_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 150); munit_assert_uint8(chip.V[15], ==, 0); // SUB Vx, Vy // No borrow (100 >= 50 -> VF = 1) chip8_init(&chip); chip.V[0] = 100; chip.V[1] = 50; write_subtract_registers_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 50); munit_assert_uint8(chip.V[15], ==, 1); // Borrow occurs (50 < 100 -> VF = 0) chip8_init(&chip); chip.V[0] = 50; chip.V[1] = 100; write_subtract_registers_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 206); munit_assert_uint8(chip.V[15], ==, 0); // Shift right by 1, VF stores shifted LSB // LSB is 1 (5 >> 1 = 2, VF = 1) chip8_init(&chip); chip.V[0] = 0b00000101; write_shift_right_register_instruction(&chip, 0, 0); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 2); munit_assert_uint8(chip.V[15], ==, 1); // LSB is 0 (4 >> 1 = 2, VF = 0) chip8_init(&chip); chip.V[0] = 0b00000100; write_shift_right_register_instruction(&chip, 0, 0); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 2); munit_assert_uint8(chip.V[15], ==, 0); // SUBN Vx, Vy // No borrow (Vy 100 >= Vx 50 -> VF = 1) chip8_init(&chip); chip.V[0] = 50; chip.V[1] = 100; write_subtract_registers_reverse_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 50); munit_assert_uint8(chip.V[15], ==, 1); // Borrow occurs (Vy 50 < Vx 100 -> VF = 0) chip8_init(&chip); chip.V[0] = 100; chip.V[1] = 50; write_subtract_registers_reverse_instruction(&chip, 0, 1); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 206); munit_assert_uint8(chip.V[15], ==, 0); // Shift left by 1, VF stores shifted MSB // MSB is 1 (0b10000001 << 1 = 2, VF = 1) chip8_init(&chip); chip.V[0] = 0b10000001; write_shift_left_register_instruction(&chip, 0, 0); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 2); munit_assert_uint8(chip.V[15], ==, 1); // MSB is 0 (0b00000001 << 1 = 2, VF = 0) chip8_init(&chip); chip.V[0] = 0b00000001; write_shift_left_register_instruction(&chip, 0, 0); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 2); munit_assert_uint8(chip.V[15], ==, 0); return MUNIT_OK; } static MunitResult test_ld_i_addr(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); write_set_index_register_instruction(&chip, 0x123); chip8_step(&chip); munit_assert_uint16(chip.I, ==, 0x123); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_jp_v0_addr(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); // Set V0 offset to 5, target base address to 0x300 -> Expect PC = 0x305 chip.V[0] = 0x05; write_jump_with_offset_instruction(&chip, 0x300); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, 0x305); return MUNIT_OK; } static MunitResult test_rnd_vx_byte(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; chip8_init(&chip); // Mask of 0x00 guarantees result is 0 regardless of random output write_random_byte_instruction(&chip, 0, 0x00); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 0x00); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_drw_vx_vy_n(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; // Draw sprite without collision (VF = 0) chip8_init(&chip); chip.V[0] = 63; chip.V[1] = 31; chip.I = 0x300; chip.memory[0x300] = 0b11000000; chip.memory[0x301] = 0b11000000; write_draw_sprite_instruction(&chip, 0, 1, 2); chip8_step(&chip); munit_assert_uint8(chip.V[15], ==, 0); // Draw sprite over an active pixel (VF = 1) chip8_init(&chip); chip.V[0] = 0; chip.V[1] = 0; chip.I = 0x300; chip.memory[0x300] = 0xFF; chip.display[0] = 1; write_draw_sprite_instruction(&chip, 0, 1, 1); chip8_step(&chip); munit_assert_uint8(chip.V[15], ==, 1); return MUNIT_OK; } static MunitResult test_key_skip(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; // Key is pressed chip8_init(&chip); chip.V[0] = 5; chip.keys[5] = 1; write_skip_if_key_pressed_instruction(&chip, 0); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 4); // Key is NOT pressed chip8_init(&chip); chip.V[0] = 5; chip.keys[5] = 0; write_skip_if_key_pressed_instruction(&chip, 0); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); // Key is NOT pressed chip8_init(&chip); chip.V[0] = 5; chip.keys[5] = 0; write_skip_if_key_not_pressed_instruction(&chip, 0); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 4); // Key is pressed chip8_init(&chip); chip.V[0] = 5; chip.keys[5] = 1; write_skip_if_key_not_pressed_instruction(&chip, 0); chip8_step(&chip); munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); return MUNIT_OK; } static MunitResult test_misc_instructions(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; // Read Delay Timer into Vx chip8_init(&chip); chip.dt = 42; write_get_delay_timer_instruction(&chip, 1); chip8_step(&chip); munit_assert_uint8(chip.V[1], ==, 42); // Wait for key press chip8_init(&chip); write_wait_for_key_instruction(&chip, 2); chip8_step(&chip); munit_assert_true(chip.waiting_for_key); munit_assert_uint8(chip.key_register, ==, 2); chip8_step(&chip); // Stepping while waiting should keep PC unchanged munit_assert_uint16(chip.pc, ==, PROGRAM_START + 2); // Set Delay Timer = Vx chip8_init(&chip); chip.V[3] = 99; write_set_delay_timer_instruction(&chip, 3); chip8_step(&chip); munit_assert_uint8(chip.dt, ==, 99); // Set Sound Timer = Vx chip8_init(&chip); chip.V[4] = 88; write_set_sound_timer_instruction(&chip, 4); chip8_step(&chip); munit_assert_uint8(chip.st, ==, 88); // Set I = I + Vx chip8_init(&chip); chip.I = 0x200; chip.V[5] = 0x50; write_add_register_to_index_instruction(&chip, 5); chip8_step(&chip); munit_assert_uint16(chip.I, ==, 0x250); // Set I = Font Character location for digit Vx chip8_init(&chip); chip.V[0] = 0x0A; write_set_index_to_font_character_instruction(&chip, 0); chip8_step(&chip); munit_assert_uint16(chip.I, ==, FONT_START + (0x0A * 5)); // Store BCD representation of Vx in memory starting at I // 234 -> memory[I] = 2, memory[I+1] = 3, memory[I+2] = 4 chip8_init(&chip); chip.I = 0x300; chip.V[0] = 234; write_store_bcd_representation_instruction(&chip, 0); chip8_step(&chip); munit_assert_uint8(chip.memory[0x300], ==, 2); munit_assert_uint8(chip.memory[0x301], ==, 3); munit_assert_uint8(chip.memory[0x302], ==, 4); // Dump registers V0 through Vx into memory starting at address I chip8_init(&chip); chip.I = 0x400; chip.V[0] = 10; chip.V[1] = 20; chip.V[2] = 30; write_dump_registers_to_memory_instruction(&chip, 2); chip8_step(&chip); munit_assert_uint8(chip.memory[0x400], ==, 10); munit_assert_uint8(chip.memory[0x401], ==, 20); munit_assert_uint8(chip.memory[0x402], ==, 30); // Load registers V0 through Vx from memory starting at address I chip8_init(&chip); chip.I = 0x400; chip.memory[0x400] = 11; chip.memory[0x401] = 22; chip.memory[0x402] = 33; write_load_registers_from_memory_instruction(&chip, 2); chip8_step(&chip); munit_assert_uint8(chip.V[0], ==, 11); munit_assert_uint8(chip.V[1], ==, 22); munit_assert_uint8(chip.V[2], ==, 33); return MUNIT_OK; } static MunitResult test_timers_and_rom(const MunitParameter params[], void *data) { (void)params; (void)data; chip_eight_t chip; // Verify timer tick countdown logic chip8_init(&chip); chip.dt = 2; chip.st = 1; chip8_tick_timers(&chip); munit_assert_uint8(chip.dt, ==, 1); munit_assert_uint8(chip.st, ==, 0); chip8_tick_timers(&chip); munit_assert_uint8(chip.dt, ==, 0); munit_assert_uint8(chip.st, ==, 0); // Verify chip8_load_rom error when file does not exist chip8_init(&chip); munit_assert_int(chip8_load_rom(&chip, "non_existent_rom.ch8"), ==, FILE_MISSING); // Verify chip8_load_rom success with valid ROM file munit_assert_int(chip8_load_rom(&chip, "test_files/ibm_logo.ch8"), ==, COMPLETED_SUCCESSFULLY); // Verify chip8_load_rom error when ROM exceeds maximum allowable memory FILE *f = fopen("test_files/too_large.ch8", "wb"); if (f != NULL) { uint8_t dummy[3600] = {0}; fwrite(dummy, 1, sizeof(dummy), f); fclose(f); munit_assert_int(chip8_load_rom(&chip, "test_files/too_large.ch8"), ==, PROGRAM_TOO_LARGE); remove("test_files/too_large.ch8"); } // Unrecognized opcode step branch write_opcode_to_memory(&chip, 0x0000); chip8_step(&chip); return MUNIT_OK; } static MunitTest tests[] = { {"/init", test_init, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/cls", test_cls, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_ret", test_ret, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_group_jump", test_group_jump, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_group_call", test_group_call, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_se_vx_byte", test_se_vx_byte, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_sne_vx_byte", test_sne_vx_byte, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_se_vx_vy", test_se_vx_vy, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_sne_vx_vy", test_sne_vx_vy, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_ld_vx_byte", test_ld_vx_byte, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_add_vx_byte", test_add_vx_byte, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_arithmetic", test_arithmetic, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_ld_i_addr", test_ld_i_addr, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_jp_v0_addr", test_jp_v0_addr, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_rnd_vx_byte", test_rnd_vx_byte, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_drw_vx_vy_n", test_drw_vx_vy_n, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_key_skip", test_key_skip, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_misc_instructions", test_misc_instructions, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {"/test_timers_and_rom", test_timers_and_rom, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}, {NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL}}; static const MunitSuite suite = {"/chip8", tests, NULL, 1, MUNIT_SUITE_OPTION_NONE}; int main(int argc, char *argv[]) { return munit_suite_main(&suite, NULL, argc, argv); }