#include #include #include #include #include #include #define BASE_ADDR 0x200 #define FONTSET_SIZE 80 #define SCREEN_WIDTH 64 #define SCREEN_HEIGHT 32 struct emu_state { uint8_t registers[16]; uint8_t memory[4096]; uint16_t index; uint16_t program_counter; uint16_t stack[16]; uint8_t stack_pointer; uint8_t sound_timer; uint8_t delay_timer; uint8_t keypad[16]; uint32_t screen[SCREEN_WIDTH * SCREEN_HEIGHT]; uint16_t opcode; }; uint8_t fontset[FONTSET_SIZE] = { 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 0xF0, 0x90, 0xF0, 0x90, 0x90, // A 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 0xF0, 0x80, 0xF0, 0x80, 0x80 // F }; void load_rom(char *file_name, struct emu_state *emulator_state) { printf("Loading ROM %s\n", file_name); FILE *file = fopen(file_name, "r"); if (file == NULL) { printf("Error reading ROM file."); return; } fseek(file, 0L, SEEK_END); int rom_size = ftell(file); fseek(file, 0L, SEEK_SET); #ifndef NDEBUG printf("ROM size is %d bytes\n", rom_size); #endif for (int i = 0; i < rom_size; i++) { emulator_state->memory[BASE_ADDR + i] = (uint8_t)fgetc(file); } fclose(file); } // SYS is a deprecated instruction, basically a NOP void instr_SYS(struct emu_state *emulator_state) { emulator_state->program_counter += 2; } // CLS instructions clears the screen // we should zero out the video memory void instr_CLS(struct emu_state *emulator_state) { memset(emulator_state->screen, 0, sizeof(emulator_state->screen)); emulator_state->program_counter += 2; } // RET instruction returns from a function // we should decrease stack pointer and set // program_counter to last stack address void instr_RET(struct emu_state *emulator_state) { emulator_state->stack_pointer--; emulator_state->program_counter = emulator_state->stack[emulator_state->stack_pointer]; emulator_state->program_counter += 2; } // JP instruction jumps to an address without // modifying the stack void instr_JP(struct emu_state *emulator_state) { emulator_state->program_counter = emulator_state->opcode & 0x0FFFu; } // CALL instruction is like JP, but the stack is // modified, kind of the reverse of RET void instr_CALL(struct emu_state *emulator_state) { emulator_state->stack[emulator_state->stack_pointer] = emulator_state->program_counter; emulator_state->stack_pointer++; emulator_state->program_counter = emulator_state->opcode & 0x0FFFu; } // SE instruction skips the next instruction if // 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 byte_val = emulator_state->opcode & 0x00FFU; printf("SE R[%#x] %d (reg_val: %d, skip?: %d)\n", reg, byte_val, register_val, register_val == byte_val); if (byte_val == register_val) { emulator_state->program_counter += 2; } emulator_state->program_counter += 2; } // SNE instruction skips the next instruction if // the value in register X is not equal to byte KK void instr_SNE(struct emu_state *emulator_state) { uint8_t register_val = emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; uint8_t byte_val = emulator_state->opcode & 0x00FFU; if (byte_val != register_val) { emulator_state->program_counter += 2; } emulator_state->program_counter += 2; } // SE instruction skips the next instruction if // the value in register X is equal to value in // register Y void instr_SE_reg(struct emu_state *emulator_state) { uint8_t register_x_val = emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; uint8_t register_y_val = emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; if (register_x_val == register_y_val) { emulator_state->program_counter += 2; } emulator_state->program_counter += 2; } // SNE instruction skips the next instruction if // the value in register X is equal to value in // register Y void instr_SNE_reg(struct emu_state *emulator_state) { uint8_t register_x_val = emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; uint8_t register_y_val = emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; if (register_x_val != register_y_val) { emulator_state->program_counter += 2; } emulator_state->program_counter += 2; } // LD instruction loads the value of byte kk into register Y void instr_LD(struct emu_state *emulator_state) { emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] = emulator_state->opcode & 0x00FFU; emulator_state->program_counter += 2; } // ADD instruction adds kk to register X void instr_ADD(struct emu_state *emulator_state) { uint8_t reg = (emulator_state->opcode & 0x0F00U) >> 8; uint8_t num = emulator_state->opcode & 0x00FFU; printf("ADD R[%#x] %d (was: %d, now: %d)\n", reg, num, emulator_state->registers[reg], emulator_state->registers[reg] + num); emulator_state->registers[reg] += num; emulator_state->program_counter += 2; } // LD instruction loads the value of register X into register Y void instr_LD_reg(struct emu_state *emulator_state) { emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] = emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; emulator_state->program_counter += 2; } // OR instruction ors the value of register X with register Y void instr_OR_reg(struct emu_state *emulator_state) { emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] |= emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; emulator_state->program_counter += 2; } // XOR instruction xors the value of register X with register Y void instr_XOR_reg(struct emu_state *emulator_state) { emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] ^= emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; emulator_state->program_counter += 2; } // ADD instruction adds the value of register X with register Y void instr_ADD_reg(struct emu_state *emulator_state) { emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] += emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; 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) { emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] -= emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; emulator_state->program_counter += 2; } // 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]; 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 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) { 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 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]; if (reg_y_val > reg_x_val) { emulator_state->registers[0xF] = 1; } else { emulator_state->registers[0xF] = 0; } emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] = reg_y_val - reg_x_val; emulator_state->program_counter += 2; } void instr_LD_I(struct emu_state *emulator_state) { emulator_state->index = emulator_state->opcode & 0x0FFFU; emulator_state->program_counter += 2; } 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->program_counter += 2; } 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->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; val /= 10; emulator_state->memory[emulator_state->index + 1] = val % 10; val /= 10; emulator_state->memory[emulator_state->index + 0] = val % 10; emulator_state->program_counter += 2; } void instr_AND_reg(struct emu_state *emulator_state) { emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8] &= emulator_state->registers[(emulator_state->opcode & 0x00F0U) >> 4]; emulator_state->program_counter += 2; } void instr_ADD_I_reg(struct emu_state *emulator_state) { emulator_state->index += emulator_state->registers[(emulator_state->opcode & 0x0F00U) >> 8]; emulator_state->program_counter += 2; } 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 size = emulator_state->opcode & 0x000FU; y_cord %= SCREEN_HEIGHT; x_cord %= SCREEN_WIDTH; 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); if (sprite_pixel) { if (*screen_pixel == 0xFFFFFFFF) { emulator_state->registers[0xF] = 1; } *screen_pixel ^= 0xFFFFFFFF; } } } emulator_state->program_counter += 2; } void *get_op_func(uint16_t opcode) { if (opcode == 0x00E0U) { return &instr_CLS; } else if (opcode == 0x00EEU) { return &instr_RET; } else if ((opcode & 0xF000U) == 0x0000U) { return NULL; } else if ((opcode & 0xF000U) == 0x1000U) { return &instr_JP; } else if ((opcode & 0xF000U) == 0x2000U) { return &instr_CALL; } else if ((opcode & 0xF000U) == 0x3000U) { return &instr_SE; } else if ((opcode & 0xF000U) == 0x4000U) { return &instr_SNE; } else if ((opcode & 0xF00FU) == 0x5000U) { return &instr_SE_reg; } else if ((opcode & 0xF00FU) == 0x9000U) { return &instr_SNE_reg; } else if ((opcode & 0xF00FU) == 0x8000U) { return &instr_LD_reg; } else if ((opcode & 0xF00FU) == 0x8001U) { return &instr_OR_reg; } else if ((opcode & 0xF00FU) == 0x8002U) { return &instr_AND_reg; } else if ((opcode & 0xF00FU) == 0x8003U) { return &instr_XOR_reg; } else if ((opcode & 0xF00FU) == 0x8004U) { return &instr_ADD_reg; } else if ((opcode & 0xF00FU) == 0x8005U) { return &instr_SUB_reg; } else if ((opcode & 0xF00FU) == 0x8006U) { return &instr_SHR_reg; } else if ((opcode & 0xF00FU) == 0x8007U) { return &instr_SUBN_reg; } else if ((opcode & 0xF00FU) == 0x800EU) { return &instr_SHL_reg; } else if ((opcode & 0xF0FFU) == 0xf065U) { return &instr_LD_reg_I; } else if ((opcode & 0xF0FFU) == 0xf033U) { return &instr_LD_B_reg; } else if ((opcode & 0xF0FFU) == 0xf055U) { return &instr_LD_I_reg; } else if ((opcode & 0xF0FFU) == 0xf01EU) { return &instr_ADD_I_reg; } else if ((opcode & 0xF000U) == 0xA000U) { return &instr_LD_I; } else if ((opcode & 0xF000U) == 0x6000U) { return &instr_LD; } else if ((opcode & 0xF000U) == 0x7000U) { return &instr_ADD; } else if ((opcode & 0xF000U) == 0xD000U) { return &instr_DRW; } else if ((opcode & 0xF00FU) == 0x8002U) { return &instr_AND_reg; } return NULL; } char* get_instr_name(void* function) { if (function == &instr_CLS) { return "CLS"; } else if (function == &instr_RET) { return "RET"; } else if (function == &instr_JP) { return "JP"; } else if (function == &instr_CALL) { return "CALL"; } else if (function == &instr_SE) { return "SE"; } else if (function == &instr_LD_I) { return "LD_I"; } else if (function == &instr_LD) { return "LD"; } else if (function == &instr_ADD) { return "ADD"; } else if (function == &instr_DRW) { return "DRW"; } else if (function == &instr_AND_reg) { return "AND_reg"; } else { return "UNKNOWN"; // Handle unknown instructions } } int main(int argc, char *argv[]) { struct emu_state state; load_rom(argv[1], &state); state.program_counter = BASE_ADDR; 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_Texture* texture = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT); for(int 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) - ", i, state.program_counter, state.opcode, get_instr_name(op_func)); if (op_func == NULL) { printf("Illegal Instruction.\n"); return 1; } 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); usleep(16000); } while (1 == 1) {} return 0; }