Files

657 lines
17 KiB
C
Raw Permalink Normal View History

#include "chip8.h"
#include "munit.h"
#include "test_helpers.h"
#include <stdio.h>
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) {
2026-07-18 17:40:16 -05:00
(void)params;
(void)data;
chip_eight_t chip;
chip8_init(&chip);
// Write Jump instruction
write_jump_instruction(&chip, 0x400);
2026-07-18 17:40:16 -05:00
chip8_step(&chip);
munit_assert_uint16(chip.pc, ==, 0x400);
2026-07-18 17:40:16 -05:00
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);
}