feat: initial version

This commit is contained in:
Zoëy Noort 2025-05-05 18:50:56 +02:00
parent 0a67fb21d1
commit ee790e99d6
12 changed files with 798 additions and 0 deletions

257
srcs/cpu.c Normal file
View file

@ -0,0 +1,257 @@
#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;
}