diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7e05b43 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +OBJS = main.c +OBJ_NAME = chip8 + +all : $(OBJS) + gcc -g $(OBJS) -lSDL2 -o $(OBJ_NAME) diff --git a/main.c b/main.c new file mode 100644 index 0000000..f0442b6 --- /dev/null +++ b/main.c @@ -0,0 +1,402 @@ +#include +#include +#include +#include +#include +#include + +#define DEBUG 1 +#define TDISP 0 + +typedef uint8_t byte; +typedef uint16_t word; + +typedef struct { + byte memory[0x1000]; + byte screen[64][32]; + byte registers[0x10]; + word stack[24]; + byte input[0x10]; + + word address_I; + word program_counter; + byte stack_pointer; +} emulator_state_s; + +byte keymap[0x10] = { SDL_SCANCODE_X, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_Q, + SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_D, + SDL_SCANCODE_Z, SDL_SCANCODE_C, SDL_SCANCODE_4, SDL_SCANCODE_R, SDL_SCANCODE_F, SDL_SCANCODE_V }; + +int main(int argc, char **argv) { + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + SDL_Init(SDL_INIT_VIDEO); + + window = SDL_CreateWindow("Chip8 Emulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 64 * 8, 32 * 8, SDL_WINDOW_SHOWN); + renderer = SDL_CreateRenderer(window, 0, SDL_RENDERER_ACCELERATED); + if (renderer == NULL) printf( "Renderer could not be created! SDL Error: %s\n", SDL_GetError() ); + SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); + + srand(time(NULL)); + + emulator_state_s state = { 0 }; // initialize everything to 0 + state.program_counter = 0x200; // chip8 programs start at 0x200 address space + + FILE *f = fopen(argv[1], "rb"); + fread(&state.memory[0x200], 0x1000 - 0x200, 1, f); // see above comment + fclose(f); + + SDL_Event e; + + int delay = 0; + int sound = 0; + int frame = 0; + int press = 0; + int wait = 0; + + while (1) { + if (DEBUG) printf("0x%x ", state.program_counter); + + if (frame == 10) + { + frame = 0; + if (delay > 0) --delay; + if (sound > 0) --sound; + memset(state.input, 0, 0x10); + press = 0; + } + + clock_t start = clock() / (CLOCKS_PER_SEC/1000); + + // opcodes are 16 bits + word opcode = (state.memory[state.program_counter++] << 8) | (state.memory[state.program_counter++]); + + SDL_PollEvent(&e); + if (e.type == SDL_QUIT) break; + + // SDL_PumpEvents(); + const byte *keystate = SDL_GetKeyboardState(NULL); + + for (int i = 0; i < 0x10 && !press; ++i) { + if (keystate[keymap[i]]){ + state.input[i] = 1; + press = 1; + } + + } + + if (wait && !press) continue; + if (wait && press) + { + for (int i = 0; i < 0x10; ++i) { + if (state.input[i] != 0) + { + state.registers[0xF] = i; + break; + } + } + wait = 0; + } + + switch (opcode & 0xF000) + { + case 0x0000: // screen clear or function return + if (opcode == 0x00E0){ + memset(state.screen, 0, 64 * 32); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF); + SDL_RenderClear(renderer); + if (DEBUG) printf("Clearing screen "); + } + + else if (opcode = 0x00EE) { + state.program_counter = state.stack[--state.stack_pointer]; + if (DEBUG) printf("Returning to %x ", state.stack[state.stack_pointer]); + } + + break; + + case 0x1000: // goto statement + state.program_counter = opcode & 0x0FFF; + if (DEBUG) printf("Jumping to %x ", state.program_counter); + + break; + + case 0x2000: // CALL + state.stack[state.stack_pointer++] = state.program_counter; + state.program_counter = opcode & 0x0FFF; + if (DEBUG) printf("Calling subroutine at %x (returning to %x)", state.program_counter, state.stack[state.stack_pointer - 1]); + + break; + + case 0x3000:; // skip if reg 0x0F00 is equal to constant 0x00FF + int reg = (opcode & 0x0F00) >> 8; + if (DEBUG) printf("Comparing (==) register %i (%x) to %x ", reg, state.registers[reg], (byte) opcode); + if (state.registers[reg] == (byte) opcode){ + state.program_counter += 2; + if (DEBUG) printf("and incrementing program_counter "); + } + + break; + + case 0x4000: // skip if not equal + reg = (opcode & 0x0F00) >> 8; + if (DEBUG) printf("Comparing (!=) register %i (%x) to %x ", reg, state.registers[reg], (byte) opcode); + if (state.registers[reg] != (byte) opcode){ + state.program_counter += 2; + if (DEBUG) printf("and incrementing program_counter "); + } + + break; + + case 0x5000:; // skip if reg 0x0F00 is equal to 0x00F0 + int first = (opcode & 0x0F00) >> 8; + int second = (opcode & 0x00F0) >> 4; + if (DEBUG) printf("Comparing (==) register %i (%x) to %i (%x) ", first, state.registers[first], second, state.registers[second]); + if (state.registers[first] == state.registers[second]){ + state.program_counter += 2; + if (DEBUG) printf("and incrementing program_counter "); + } + + break; + + case 0x6000: // set reg to constant + reg = (opcode & 0x0F00) >> 8; + state.registers[reg] = opcode & 0x00FF; + if (DEBUG) printf("Assigning %hx to %i", state.registers[reg], reg); + + break; + + case 0x7000: // add constant to reg + reg = (opcode & 0x0F00) >> 8; + state.registers[reg] += opcode & 0x00FF; + if (DEBUG) printf("Add %hi to reg %i (%hi)", opcode & 0x00FF, reg, state.registers[reg]); + + break; + + case 0x8000:; // math/bitop operators + first = (opcode & 0x0F00) >> 8; // all use XY regs + second = (opcode & 0x00F0) >> 4; + + switch (opcode & 0x000F) + { + case 0: // copy 1 register to another + state.registers[first] = state.registers[second]; + if (DEBUG) printf("Copied register %i to %i (%hx)", first, second, state.registers[first]); + break; + + case 1: // X = X | Y + state.registers[first] |= state.registers[second]; + if (DEBUG) printf("OR register %i and %i (%hx)", first, second, state.registers[first]); + break; + + case 2: // X = X & Y + state.registers[first] &= state.registers[second]; + if (DEBUG) printf("AND register %i and %i (%hx)", first, second, state.registers[first]); + break; + + case 3: // X = X ^ Y + state.registers[first] ^= state.registers[second]; + if (DEBUG) printf("XOR register %i to %i (%hx)", first, second, state.registers[first]); + break; + + case 4:; // X = X + Y, VF = X + 5 > 255 + unsigned int add = state.registers[first] + state.registers[second]; + if (add > 255) state.registers[0xF] = 1; + else state.registers[0xF] = 0; + state.registers[first] = add & 0x00FF; + if (DEBUG) printf("ADD register %i to %i (%hx) ", second, first, state.registers[first]); + if (DEBUG) printf("VF is %hx", state.registers[0xF]); + break; + + case 5: // X = X - Y, VF = X - Y < 0 + state.registers[first] -= state.registers[second]; + if (state.registers[first] > state.registers[second]) state.registers[0xF] = 1; + else state.registers[0xF] = 0; + if (DEBUG) printf("SUB register %i from %i (%hx) ", second, first, state.registers[first]); + if (DEBUG) printf("VF is %hx", state.registers[0xF]); + break; + + case 6: // set least significant bit of X in VF, bitshift right + state.registers[0xF] = state.registers[first] & 0x000F; + state.registers[first] /= 2; + if (DEBUG) printf("RS register %i (%hx) ", first, second, state.registers[first]); + if (DEBUG) printf("VF is %hx", state.registers[0xF]); + break; + + case 7: // X = Y - X, VF = Y - X < 0 + state.registers[first] = state.registers[second] - state.registers[first]; + if (state.registers[second] > state.registers[first]) state.registers[0xF] = 1; + else state.registers[0xF] = 0; + if (DEBUG) printf("SUB %i from %i (%hx) ", first, second, state.registers[first]); + if (DEBUG) printf("VF is %hx", state.registers[0xF]); + break; + + case 0xE:; // set msot significant bit of X in VF, bitshift left + state.registers[0xF] = (state.registers[first] & 0xF000) >> 12; + state.registers[first] *= 2; + if (DEBUG) printf("LS register %i (%hx) ", first, second, state.registers[first]); + if (DEBUG) printf("VF is %hx", state.registers[0xF]); + break; + + default: break; + + } break; + + case 0x9000: // skip if two regs are not equal + first = (opcode & 0x0F00) >> 8; + second = (opcode & 0x00F0) >> 4; + if (DEBUG) printf("Comparing (!=) register %i (%hx) to %i (%hx) ", first, state.registers[first], second, state.registers[second]); + if (state.registers[first] != state.registers[second]) { + state.program_counter += 2; + if (DEBUG) printf("and incrementing program_counter"); + } + break; + + case 0xA000: // assign I address + state.address_I = opcode & 0x0FFF; + if (DEBUG) printf("Assigning %x to I address ", opcode & 0x0FFF); + + break; + + case 0xB000: // jump to 0x0FFF + V0 + state.program_counter = (opcode & 0x0FFF) + state.registers[0x0]; + if (DEBUG) printf("Jumping to %x + %hx (%x)", opcode & 0x0FFF, state.registers[0], (opcode & 0x0FFF) + state.registers[0x0]); + + break; + + case 0xC000: // random generator + reg = (opcode & 0x0F00) >> 8; + state.registers[reg] = (rand() % 255) & ((byte) opcode); + if (DEBUG) printf("Storing random in %i (%x)\n", reg, state.registers[reg]); + + break; + + case 0xD000: // draw sprite + first = (opcode & 0x0F00) >> 8; + second = (opcode & 0x00F0) >> 4; + + int x = state.registers[first]; + int y = state.registers[second]; + int h = (opcode & 0x000F); + + for (int line = 0; line < h; ++line) + { + byte data = state.memory[state.address_I + line]; + for (int xpos = 0; xpos < 8; ++xpos) + { + if (!(data & (1 << xpos))) continue; + if (state.screen[x + (7 - xpos)][y + line]) state.registers[0xF] = 1; + state.screen[x + (7 - xpos)][y + line] ^= 1; + } + } + + if (DEBUG) printf("Drawing"); + if (!TDISP) break; + printf("\n"); + for (int y = 0; y < 32; ++y) { + for (int x = 0; x < 64; ++x) { + printf(state.screen[x][y] ? "O" : " "); + } + printf("\n"); + } + + break; + + case 0xE000: // input handling + reg = (opcode & 0x0F00) >> 8; + switch ((byte) opcode) + { + case 0x9E: // skip if button pressed + if (DEBUG) printf("Testing if %i is pressed", state.registers[reg]); + if (state.input[state.registers[reg]]) state.program_counter += 2; + break; + + case 0xA1: // skip if button *not* pressed + if (DEBUG) printf("Testing if %i is not pressed", state.registers[reg]); + if (!state.input[state.registers[reg]]) state.program_counter += 2; + break; + } + break; + + case 0xF000: // misc (timer, input, sound, memory) + reg = (opcode & 0x0F00) >> 8; + switch ((byte) opcode) + { + case 0x07: + if (DEBUG) printf("Copying delay timer to reg %i (%hx)", reg, state.registers[reg]); + state.registers[reg] = delay; + break; + + case 0x0A: + wait = 1; + break; + + case 0x15: + delay = state.registers[reg]; + break; + + case 0x18: + sound = state.registers[reg]; + break; + + case 0x1E: + state.address_I += state.registers[reg]; + if (DEBUG) printf("Adding reg %i (%hx) to I (%x)", reg, state.registers[reg], state.address_I); + break; + + case 0x29: + break; // fontset not implemented yet + + case 0x33:; + int value = state.registers[reg]; + state.memory[state.address_I] = value / 100; + state.memory[state.address_I + 1] = (value / 10) % 10; + state.memory[state.address_I + 2] = value % 10; + break; + + case 0x55: + for (int i = 0; i < (reg + 1); ++i) + state.memory[state.address_I + i] = state.registers[i]; + break; + + case 0x65: + for (int i = 0; i < (reg + 1); ++i) + state.registers[i] = state.memory[state.address_I + i]; + break; + + default: + break; + } + break; + default: + if (DEBUG) printf("%x", opcode); + break; + } + if (DEBUG) printf("\n"); + + for (int x = 0; x < 64; ++x) { + for (int y = 0; y < 32; ++y) + { + SDL_Rect pixel = { .x = (x * 8), .y = (y * 8), .w = 8, .h = 8}; + if (state.screen[x][y]) + { + SDL_SetRenderDrawColor(renderer, 0xff, 0x69, 0xb4, 0xFF); + SDL_RenderFillRect(renderer, &pixel); + } + else + { + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF); + SDL_RenderFillRect(renderer, &pixel); + } + } + } + + SDL_RenderPresent(renderer); + + clock_t stop = clock() / (CLOCKS_PER_SEC/1000); + SDL_Delay(1); + ++frame; + // SDL_DestroyWindow(window); + // SDL_Quit(); + } +} \ No newline at end of file