From 84aa309a2952fa91f9096df9906ff6785a28336c Mon Sep 17 00:00:00 2001 From: Nicholas Orlowsky Date: Wed, 6 Sep 2023 02:10:31 -0400 Subject: [PATCH] add flag support and update README --- Makefile | 11 ++- README.md | 51 ++++++++++++- main.c | 146 +++++++++++++++++++++++++------------- screenshots/flag-test.png | Bin 0 -> 7142 bytes 4 files changed, 155 insertions(+), 53 deletions(-) create mode 100644 screenshots/flag-test.png 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 0000000000000000000000000000000000000000..12cb10b803aa2c2990e7e480f4b699389627b7e5 GIT binary patch literal 7142 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 icINo}iPO5(&X8yMhf-c-0YBV