257 lines
8.5 KiB
C
257 lines
8.5 KiB
C
#include "input.h"
|
|
#include "cpu.h"
|
|
#include "font.h"
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
|
|
#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;
|
|
}
|