diff --git a/Makefile b/Makefile index dc0d752..5c27225 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,16 @@ build: gcc ./main.c -g -lSDL2 -lGL run: build - ./a.out + ./a.out ${ROM_FILE} + +run-turbo: build + ./a.out ${ROM_FILE} turbo debug: build - gdb ./a.out + gdb ./a.out ${ROM_FILE} + +debug-turbo: build + gdb ./a.out ${ROM_FILE} turbo + format: clang-format ./*.c -i diff --git a/README.md b/README.md index a850cd1..758c5a8 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,56 @@ # yacemu (Yet Another Chip Eight Emulator) -A Chip-8 emulator written in C. Depends on SDL2. +A Chip-8 emulator (interpereter) written in C. Depends on SDL2. + +### Building & Testing + +In order to run yacemu, you must have sdl2 and make installed. + +Once you have the dependencies installed, you can build the emulator with: +```shell +make build +``` + +You can build start the emulator with: +```shell +make run ROM_FILE=[PATH_TO_YOUR_ROM] +``` + +If you would like to run it in turbo mode (faster emulation, useful for testing) then run: +```shell +make run-turbo ROM_FILE=[PATH_TO_YOUR_ROM] +``` + +If you would like to debug with gdb then run: +```shell +make debug ROM_FILE=[PATH_TO_YOUR_ROM] +``` + +If you would like to debug with gdb in turbo mode then run: +```shell +make debug-turbo ROM_FILE=[PATH_TO_YOUR_ROM] +``` + +### Running + +If you have a binary, you can run it with the following: + +```shell +yacemu [PATH_TO_YOUR_ROM] +``` + +If you would like to run it in turbo mode (faster emulation, useful for testing) then run: + +```shell +yacemu [PATH_TO_YOUR_ROM] turbo +``` ### Todo - [x] Graphics - [x] Corax+ Required Instructions -- [ ] Rest of Instructions +- [x] Proper Flag Handling - [ ] Working Input -- [ ] Tetris Running +- [ ] Rest of Instructions +- [ ] Tetris Working Running - [ ] Extended instruction set (MEGACHIP, SUPER CHIP-48) - [ ] Add my own custom instructions - [ ] Write a ROM that uses the above instructions @@ -16,3 +60,4 @@ A Chip-8 emulator written in C. Depends on SDL2. ![Chip 8 Logo Demo](https://github.com/nickorlow/yacemu/blob/main/screenshots/chip8-logo.png?raw=true) ![IBM Logo Demo](https://github.com/nickorlow/yacemu/blob/main/screenshots/ibm-logo.png?raw=true) ![Corax Plus Test Demo](https://github.com/nickorlow/yacemu/blob/main/screenshots/corax+-test.png?raw=true) +![Flag Test Demo](https://github.com/nickorlow/yacemu/blob/main/screenshots/flag-test.png?raw=true) diff --git a/main.c b/main.c index b47284d..cf52554 100644 --- a/main.c +++ b/main.c @@ -1,14 +1,15 @@ +#include +#include +#include +#include #include #include #include -#include -#include -#include #define BASE_ADDR 0x200 #define FONTSET_SIZE 80 -#define SCREEN_WIDTH 64 -#define SCREEN_HEIGHT 32 +#define SCREEN_WIDTH 64 +#define SCREEN_HEIGHT 32 struct emu_state { uint8_t registers[16]; @@ -43,7 +44,6 @@ uint8_t fontset[FONTSET_SIZE] = { 0xF0, 0x80, 0xF0, 0x80, 0x80 // F }; - void load_rom(char *file_name, struct emu_state *emulator_state) { printf("Loading ROM %s\n", file_name); @@ -110,12 +110,12 @@ void instr_CALL(struct emu_state *emulator_state) { // the value in register X is equal to byte KK void instr_SE(struct emu_state *emulator_state) { uint8_t reg = (emulator_state->opcode & 0x0F00U) >> 8; - uint8_t register_val = - emulator_state->registers[reg]; + uint8_t register_val = emulator_state->registers[reg]; uint8_t byte_val = emulator_state->opcode & 0x00FFU; #ifndef NDEBUG - printf("SE R[%#x] %d (reg_val: %d, skip?: %d)\n", reg, byte_val, register_val, register_val == byte_val); + printf("SE R[%#x] %d (reg_val: %d, skip?: %d)\n", reg, byte_val, register_val, + register_val == byte_val); #endif if (byte_val == register_val) { @@ -177,7 +177,8 @@ void instr_ADD(struct emu_state *emulator_state) { uint8_t num = emulator_state->opcode & 0x00FFU; #ifndef NDEBUG - printf("ADD R[%#x] %d (was: %d, now: %d)\n", reg, num, emulator_state->registers[reg], emulator_state->registers[reg] + num); + printf("ADD R[%#x] %d (was: %d, now: %d)\n", reg, num, + emulator_state->registers[reg], emulator_state->registers[reg] + num); #endif emulator_state->registers[reg] += num; @@ -207,50 +208,79 @@ void instr_XOR_reg(struct emu_state *emulator_state) { // ADD instruction adds the value of register X with register Y void instr_ADD_reg(struct emu_state *emulator_state) { + uint8_t prev_value = + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] += emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; + + emulator_state->registers[0xF] = + (prev_value > + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]) + ? 1 + : 0; + emulator_state->program_counter += 2; } // SUB instruction subtracts the value of register X with register Y void instr_SUB_reg(struct emu_state *emulator_state) { + uint8_t prev_value = + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] -= emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; + + // This doesn't exactly make sense, yet the tests pass.... + emulator_state->registers[0xF] = + (prev_value < + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]) + ? 0 + : 1; emulator_state->program_counter += 2; } -// SHR instruction bit shifts Vx one to the right +// SHR instruction bit shifts Vx one to the right void instr_SHR_reg(struct emu_state *emulator_state) { - uint8_t reg_x_val = emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; + uint8_t reg_x_val = + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; + + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] >>= 1; + if ((reg_x_val & 0x01) == 1) { emulator_state->registers[0xF] = 1; } else { emulator_state->registers[0xF] = 0; } - emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] >>= 1; - emulator_state->program_counter += 2; } -// SHL instruction bit shifts Vx one to the left +// SHL instruction bit shifts Vx one to the left void instr_SHL_reg(struct emu_state *emulator_state) { - uint8_t reg_x_val = emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; - if (((reg_x_val & 0xA0) >> 4) == 1) { + uint8_t reg_x_val = + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; + + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] <<= 1; + + if (((reg_x_val & 0xA0) >> 7) == 1) { emulator_state->registers[0xF] = 1; } else { emulator_state->registers[0xF] = 0; } - emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] <<= 1; - emulator_state->program_counter += 2; } -// SUBN sets Vx to Vy - Vx +// SUBN sets Vx to Vy - Vx void instr_SUBN_reg(struct emu_state *emulator_state) { - uint8_t reg_x_val = emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; - uint8_t reg_y_val = emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; + uint8_t reg_x_val = + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; + uint8_t reg_y_val = + emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; + + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] = + reg_y_val - reg_x_val; if (reg_y_val > reg_x_val) { emulator_state->registers[0xF] = 1; @@ -258,8 +288,6 @@ void instr_SUBN_reg(struct emu_state *emulator_state) { emulator_state->registers[0xF] = 0; } - emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] = reg_y_val - reg_x_val; - emulator_state->program_counter += 2; } @@ -271,7 +299,8 @@ void instr_LD_I(struct emu_state *emulator_state) { void instr_LD_reg_I(struct emu_state *emulator_state) { unsigned int reg = (emulator_state->opcode & 0x0F00U) >> 8; for (unsigned int i = 0; i <= reg; i++) { - emulator_state->registers[i] = emulator_state->memory[emulator_state->index + i]; + emulator_state->registers[i] = + emulator_state->memory[emulator_state->index + i]; } emulator_state->program_counter += 2; } @@ -279,18 +308,20 @@ void instr_LD_reg_I(struct emu_state *emulator_state) { void instr_LD_I_reg(struct emu_state *emulator_state) { unsigned int reg = (emulator_state->opcode & 0x0F00U) >> 8; for (unsigned int i = 0; i <= reg; i++) { - emulator_state->memory[emulator_state->index + i] = emulator_state->registers[i]; + emulator_state->memory[emulator_state->index + i] = + emulator_state->registers[i]; } emulator_state->program_counter += 2; } void instr_LD_B_reg(struct emu_state *emulator_state) { - unsigned int val = emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; - emulator_state->memory[emulator_state->index + 2] = val % 10; + unsigned int val = + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; + emulator_state->memory[emulator_state->index + 2] = val % 10; val /= 10; emulator_state->memory[emulator_state->index + 1] = val % 10; val /= 10; - emulator_state->memory[emulator_state->index + 0] = val % 10; + emulator_state->memory[emulator_state->index + 0] = val % 10; emulator_state->program_counter += 2; } @@ -307,8 +338,10 @@ void instr_ADD_I_reg(struct emu_state *emulator_state) { } void instr_DRW(struct emu_state *emulator_state) { - uint8_t x_cord = emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; - uint8_t y_cord = emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; + uint8_t x_cord = + emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; + uint8_t y_cord = + emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; uint8_t size = emulator_state->opcode & 0x000FU; y_cord %= SCREEN_HEIGHT; x_cord %= SCREEN_WIDTH; @@ -316,8 +349,10 @@ void instr_DRW(struct emu_state *emulator_state) { emulator_state->registers[0xF] = 0; for (unsigned int r = 0; r < size; r++) { for (unsigned int c = 0; c < 8; c++) { - uint32_t* screen_pixel = &emulator_state->screen[(r+y_cord) * SCREEN_WIDTH + (x_cord + c)]; - uint8_t sprite_pixel = emulator_state->memory[emulator_state->index + r] & (0x80U >> c); + uint32_t *screen_pixel = + &emulator_state->screen[(r + y_cord) * SCREEN_WIDTH + (x_cord + c)]; + uint8_t sprite_pixel = + emulator_state->memory[emulator_state->index + r] & (0x80U >> c); if (sprite_pixel) { if (*screen_pixel == 0xFFFFFFFF) { emulator_state->registers[0xF] = 1; @@ -398,21 +433,31 @@ int main(int argc, char *argv[]) { memset(state.screen, 0, sizeof(state.screen)); SDL_Init(SDL_INIT_EVERYTHING); - SDL_Window* window = SDL_CreateWindow("Chip-8 Emulator", // creates a window - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - SCREEN_WIDTH * 10, SCREEN_HEIGHT * 10, 0); - SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + SDL_Window *window = + SDL_CreateWindow("Chip-8 Emulator", // creates a window + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + SCREEN_WIDTH * 10, SCREEN_HEIGHT * 10, 0); + SDL_Renderer *renderer = + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); - SDL_Texture* texture = SDL_CreateTexture( - renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT); + SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, + SDL_TEXTUREACCESS_STREAMING, + SCREEN_WIDTH, SCREEN_HEIGHT); - for(int i = 0; i != -1; i++) { + int turbo_mode = argc == 3 && strcmp(argv[2], "turbo") == 0; + + if (turbo_mode) { + printf("WARNING: Turbo mode has been enabled. Interpereter will run " + "extremely fast!\n"); + } + + for (long i = 0; i != -1; i++) { state.opcode = (((uint16_t)state.memory[state.program_counter]) << 8) | (uint16_t)state.memory[state.program_counter + 1]; void (*op_func)(struct emu_state *) = get_op_func(state.opcode); - printf("\n[%d] | PC: %#x / OPCODE: %#x (%s) \n", i, state.program_counter, state.opcode, get_instr_name(op_func)); + printf("\n[%d] | PC: %#x / OPCODE: %#x \n", i, state.program_counter, + state.opcode); if (op_func == NULL) { printf("Illegal Instruction.\n"); @@ -421,14 +466,19 @@ int main(int argc, char *argv[]) { op_func(&state); - SDL_UpdateTexture(texture, NULL, state.screen, sizeof(state.screen[0]) * SCREEN_WIDTH); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + SDL_UpdateTexture(texture, NULL, state.screen, + sizeof(state.screen[0]) * SCREEN_WIDTH); + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); - usleep(16000); + usleep(turbo_mode ? 100 : 16000); + + if (SDL_QuitRequested()) { + printf("Received Quit from SDL. Goodbye!"); + break; + } } - while (1 == 1) {} return 0; } diff --git a/screenshots/flag-test.png b/screenshots/flag-test.png new file mode 100644 index 0000000..12cb10b Binary files /dev/null and b/screenshots/flag-test.png differ