#include "input.h" #include "cpu.h" #include "font.h" #include #include #define OPCODE_SIZE 2 uint16_t cpu_fetch(t_chip8 *emu) { // Check if there is enough space in memory to fetch a 2-byte opcode if (emu->pc + 1 >= MEMORY_SIZE) { printf("Error, program counter out of bounds! (pc = 0x%04X)\n", emu->pc); graphics_cleanup(&emu->display); emu->running = 0; return (1); } uint16_t opcode = (emu->memory.memory[emu->pc] << 8) | emu->memory.memory[emu->pc + 1]; return opcode; } t_instruction cpu_decode(uint16_t opcode) { t_instruction instruction; instruction.opcode = opcode; instruction.type = (opcode & 0xF000) >> 12; instruction.x = (opcode & 0x0F00) >> 8; instruction.y = (opcode & 0x00F0) >> 4; instruction.n = opcode & 0x000F; instruction.nn = opcode & 0x00FF; instruction.nnn = opcode & 0x0FFF; return instruction; } void cpu_execute(t_chip8 *emu, t_instruction instruction) { uint8_t x = instruction.x; uint8_t y = instruction.y; uint8_t n = instruction.n; uint8_t nn = instruction.nn; uint16_t nnn = instruction.nnn; switch (instruction.type) { // 0x0NNN: SYS addr (ignored, do nothing) case 0x0: // 0x00E0: CLS (Clear screen) if (instruction.opcode == 0x00E0) memory_clear_screen(&emu->memory); // 0x00EE: RET (Return from subroutine) else if (instruction.opcode == 0x00EE) { if (emu->sp == 0) { printf("Stack underflow on RET!\n"); emu->running = 0; return; } emu->pc = emu->stack[--emu->sp]; } break; // 0x1NNN: JP addr (Jump to address NNN) case 0x1: emu->pc = nnn; return; // Do not increment PC after jump // 0x2NNN: CALL addr (Call subroutine at NNN) case 0x2: if (emu->sp >= 16) { printf("Stack overflow!\n"); emu->running = 0; return; } emu->stack[emu->sp++] = emu->pc; emu->pc = nnn; return; // Do not increment PC after call // 0x3XNN: SE Vx, byte (Skip if Vx == NN) case 0x3: if (emu->v[x] == nn) emu->pc += OPCODE_SIZE; break; // 0x4XNN: SNE Vx, byte (Skip if Vx != NN) case 0x4: if (emu->v[x] != nn) emu->pc += OPCODE_SIZE; break; // 0x5XY0: SE Vx, Vy (Skip if Vx == Vy) case 0x5: if (n == 0 && emu->v[x] == emu->v[y]) emu->pc += OPCODE_SIZE; break; // 0x6XNN: LD Vx, byte (Load NN into Vx) case 0x6: emu->v[x] = nn; break; // 0x7XNN: ADD Vx, byte (Add NN to Vx) case 0x7: emu->v[x] += nn; break; // 0x8XYN: Arithmetic opcodes (switch on last nibble, 'n') case 0x8: switch (n) { // 0x8XY0: LD Vx, Vy (Set Vx = Vy) case 0x0: emu->v[x] = emu->v[y]; break; // 0x8XY1: OR Vx, Vy (Set Vx = Vx OR Vy) case 0x1: emu->v[x] |= emu->v[y]; break; // 0x8XY2: AND Vx, Vy (Set Vx = Vx AND Vy) case 0x2: emu->v[x] &= emu->v[y]; break; // 0x8XY3: XOR Vx, Vy (Set Vx = Vx XOR Vy) case 0x3: emu->v[x] ^= emu->v[y]; break; // 0x8XY4: ADD Vx, Vy (Set Vx = Vx + Vy, set VF = carry) case 0x4: emu->v[0xF] = (emu->v[x] + emu->v[y]) > 0xFF; emu->v[x] += emu->v[y]; break; // 0x8XY5: SUB Vx, Vy (Set Vx = Vx - Vy, set VF = NOT borrow) case 0x5: emu->v[0xF] = emu->v[x] >= emu->v[y]; emu->v[x] -= emu->v[y]; break; // 0x8XY6: SHR Vx, Vy (Set Vx = Vx >> 1, set VF = LSB before shift) case 0x6: emu->v[0xF] = emu->v[x] & 1; emu->v[x] >>= 1; break; // 0x8XY7: SUBN Vx, Vy (Set Vx = Vy - Vx, set VF = NOT borrow) case 0x7: emu->v[0xF] = emu->v[y] >= emu->v[x]; emu->v[x] = emu->v[y] - emu->v[x]; break; // 0x8XYE: SHL Vx, Vy (Set Vx = Vx << 1, set VF = MSB before shift) case 0xE: emu->v[0xF] = (emu->v[x] >> 7) & 1; emu->v[x] <<= 1; break; default: printf("Unknown arithmetic opcode: 0x%04X\n", instruction.opcode); break; } break; // 0x9XY0: SNE Vx, Vy (Skip if Vx != Vy) case 0x9: if (n == 0 && emu->v[x] != emu->v[y]) emu->pc += OPCODE_SIZE; break; // 0x0ANNN: LD I, addr (Set I = NNN) case 0xA: emu->i = nnn; break; // 0xBNNN: JP V0, addr (Jump to NNN + V0) case 0xB: emu->pc = nnn + emu->v[0]; return; // Do not increment PC after jump // 0xCXNN: RND Vx, byte (Set Vx = random byte AND NN) case 0xC: emu->v[x] = (rand() & 0xFF) & nn; break; // 0xDXYN: DRW Vx, Vy, nibble (Draw sprite at (Vx, Vy), N rows) // N=0 means 16 rows (wrapping) case 0xD: graphics_draw_sprite(emu, emu->v[x] % 64, emu->v[y] % 32, n); break; // 0xEX9E: SKP Vx (Skip if key Vx is pressed) // 0xEXA1: SKNP Vx (Skip if key Vx is not pressed) case 0xE: if (nn == 0x9E) { if (emu->keyboard.keyboard[emu->v[x] & 0xF]) emu->pc += OPCODE_SIZE; } else if (nn == 0xA1) { if (!emu->keyboard.keyboard[emu->v[x] & 0xF]) emu->pc += OPCODE_SIZE; } break; // 0xFXNN: Various instructions (switch on NN) case 0xF: switch (nn) { // 0xFX07: LD Vx, DT (Set Vx = delay timer) case 0x07: emu->v[x] = emu->delay_timer; break; // 0xFX0A: LD Vx, K (Wait for key press, store in Vx) case 0x0A: emu->keyboard.waiting_for_key = 1; emu->keyboard.key_register = x; break; // 0xFX15: LD DT, Vx (Set delay timer = Vx) case 0x15: emu->delay_timer = emu->v[x]; break; // 0xFX18: LD ST, Vx (Set sound timer = Vx) case 0x18: emu->sound_timer = emu->v[x]; break; // 0xFX1E: ADD I, Vx (Set I = I + Vx) case 0x1E: emu->i += emu->v[x]; break; // 0xFX29: LD F, Vx (Set I = font sprite address for character in Vx) case 0x29: // emu->i = 0x50 + 5 * (emu->v[x] & 0xF); emu->i = FONTSET_START + 5 * (emu->v[x] & 0xF); break; // 0xFX33: LD B, Vx (Store BCD representation of Vx at I, I+1, I+2) case 0x33: emu->memory.memory[emu->i] = (emu->v[x] / 100) % 10; emu->memory.memory[emu->i + 1] = (emu->v[x] / 10) % 10; emu->memory.memory[emu->i + 2] = emu->v[x] % 10; break; // 0xFX55: LD [I], Vx (Store V0-Vx in memory starting at I) case 0x55: for (int i = 0; i <= x; i++) emu->memory.memory[emu->i + i] = emu->v[i]; break; // 0xFX65: LD Vx, [I] (Load V0-Vx from memory starting at I) case 0x65: for (int i = 0; i <= x; i++) emu->v[i] = emu->memory.memory[emu->i + i]; break; default: printf("Unknown 0xF opcode: 0x%04X\n", instruction.opcode); break; } break; } emu->pc += OPCODE_SIZE; }