implemented the Chip-8 instruction set

This commit is contained in:
Ghostie 2025-02-12 21:57:29 -05:00
parent a1edac8a52
commit 8ae57bba76
11 changed files with 329 additions and 7 deletions

View File

@ -3,3 +3,12 @@
Emulator and assembler for the Chip8. Emulator and assembler for the Chip8.
See notes taken through the development in [[file:Notes.org][Notes.org]] See notes taken through the development in [[file:Notes.org][Notes.org]]
** To-Do
The entire instruction set of the Chip-8 is implemented now, the following items
are pending:
- Fix the delay timer, make things run better? to make it playable, basically
- A Chip-8 assembler (so you can make games)
- A Chip-8 disassembler (so you can read other people's games)
- A Chip-8 debugger (maybe this is too much?)

View File

@ -15,5 +15,6 @@
#define CHIP8_TOTAL_KEYS 16 #define CHIP8_TOTAL_KEYS 16
#define CHIP8_CHARACTER_SET_LOAD_ADDRESS 0x00 #define CHIP8_CHARACTER_SET_LOAD_ADDRESS 0x00
#define CHIP8_SPRITE_DEFAULT_HEIGHT 5
#endif #endif

View File

@ -6,9 +6,11 @@
struct chip8_keyboard struct chip8_keyboard
{ {
_Bool keys[CHIP8_TOTAL_KEYS]; _Bool keys[CHIP8_TOTAL_KEYS];
const char *keyboard_map;
}; };
int chip8_keyboard_map (const char *map, char key); void chip8_keyboard_set_map (struct chip8_keyboard *keyboard, const char *map);
int chip8_keyboard_map (struct chip8_keyboard *keyboard, char key);
void chip8_keyboard_down (struct chip8_keyboard *kbd, int key); void chip8_keyboard_down (struct chip8_keyboard *kbd, int key);
void chip8_keyboard_up (struct chip8_keyboard *kbd, int key); void chip8_keyboard_up (struct chip8_keyboard *kbd, int key);
_Bool chip8_keyboard_is_down (struct chip8_keyboard *kbd, int key); _Bool chip8_keyboard_is_down (struct chip8_keyboard *kbd, int key);

View File

@ -8,6 +8,7 @@ struct chip8_screen
_Bool pixels[CHIP8_DISPLAY_HEIGHT][CHIP8_DISPLAY_WIDTH]; _Bool pixels[CHIP8_DISPLAY_HEIGHT][CHIP8_DISPLAY_WIDTH];
}; };
void chip8_screen_clear (struct chip8_screen *screen);
void chip8_screen_set (struct chip8_screen *screen, int x, int y); void chip8_screen_set (struct chip8_screen *screen, int x, int y);
_Bool chip8_screen_is_set (struct chip8_screen *screen, int x, int y); _Bool chip8_screen_is_set (struct chip8_screen *screen, int x, int y);
_Bool chip8_screen_draw_sprite (struct chip8_screen *screen, int x, int y, _Bool chip8_screen_draw_sprite (struct chip8_screen *screen, int x, int y,

BIN
roms/breakout.ch8 Normal file

Binary file not shown.

Binary file not shown.

BIN
roms/space_invaders.ch8 Normal file

Binary file not shown.

View File

@ -2,6 +2,10 @@
#include <assert.h> #include <assert.h>
#include <memory.h> #include <memory.h>
#include <stdlib.h>
#include <time.h>
#include <SDL2/SDL.h>
const char chip8_default_character_set[] = { const char chip8_default_character_set[] = {
0xf0, 0x90, 0x90, 0x90, 0xf0, // 1 0xf0, 0x90, 0x90, 0x90, 0xf0, // 1
@ -37,7 +41,298 @@ chip8_load (struct chip8 *chip8, const char *buf, size_t size)
chip8->registers.PC = CHIP8_PROGRAM_LOAD_ADDRESS; chip8->registers.PC = CHIP8_PROGRAM_LOAD_ADDRESS;
} }
static void
chip8_exec_extended_eight (struct chip8 *chip8, unsigned short opcode)
{
unsigned char x = (opcode >> 8) & 0x000f;
unsigned char y = (opcode >> 4) & 0x000f;
unsigned char option = opcode & 0x000f;
unsigned short tmp = 0;
switch (option)
{
// 8xy0 - LD Vx, Vy. Vx = Vy
case 0x00:
chip8->registers.V[x] = chip8->registers.V[y];
break;
// 8xy1 - OR Vx, Vy. ORs Vx and Vy, stores the result in Vx
case 0x01:
chip8->registers.V[x] = chip8->registers.V[x] | chip8->registers.V[y];
break;
// 8xy2 - AND Vx, Vy. ANDs Vx and Vy, stores the result in Vx
case 0x02:
chip8->registers.V[x] = chip8->registers.V[x] & chip8->registers.V[y];
break;
// 8xy3 - XOR Vx, Vy. XORs Vx and Vy, stores the result in Vx
case 0x03:
chip8->registers.V[x] = chip8->registers.V[x] ^ chip8->registers.V[y];
break;
// 8xy4 - ADD Vx, Vy. Set Vx = Vx + Vy. Set VF = carry
case 0x04:
tmp = chip8->registers.V[x] + chip8->registers.V[y];
chip8->registers.V[0x0f] = tmp > 0xff;
chip8->registers.V[x] = tmp;
break;
// 8xy5 - SUB Vx, Vy. Set Vx = Vx - Vy, set VF = Not borrow
case 0x05:
chip8->registers.V[0x0f] = chip8->registers.V[x] > chip8->registers.V[y];
chip8->registers.V[x] = chip8->registers.V[x] - chip8->registers.V[y];
break;
// 8xy6 - SHR Vx {, Vy}
case 0x06:
chip8->registers.V[0x0f] = chip8->registers.V[x] & 0x01;
chip8->registers.V[x] = chip8->registers.V[x] / 2;
break;
// 8xy7 - SUBN Vx, Vy. Sets Vx = Vy - Vx
case 0x07:
chip8->registers.V[0x0f] = chip8->registers.V[y] > chip8->registers.V[x];
chip8->registers.V[x] = chip8->registers.V[y] - chip8->registers.V[x];
break;
// 0xyE - SHL Vx {, Vy}
case 0x0E:
chip8->registers.V[0x0f] = chip8->registers.V[x] & 0b10000000;
chip8->registers.V[x] = chip8->registers.V[x] * 2;
break;
}
}
static char
chip8_wait_for_keypress (struct chip8 *chip8)
{
SDL_Event event;
while (SDL_WaitEvent (&event))
{
if (event.type != SDL_KEYDOWN)
continue;
char c = event.key.keysym.sym;
char chip8_key = chip8_keyboard_map (&chip8->keyboard, c);
if (chip8_key != -1)
return chip8_key;
}
return -1;
}
static void
chip8_exec_extended_F (struct chip8 *chip8, unsigned short opcode)
{
unsigned char x = (opcode >> 8) & 0x000f;
switch (opcode & 0x00ff)
{
// fx07 - LD Vx, DT. Set Vx to the delay timer value
case 0x07:
chip8->registers.V[x] = chip8->registers.DT;
break;
// fx0a - LD Vx, K.
case 0x0A:
{
char key = chip8_wait_for_keypress (chip8);
chip8->registers.V[x] = key;
}
break;
// fx15 - LD DT, Vx. Set the delay timer to Vx
case 0x15:
chip8->registers.DT = chip8->registers.V[x];
break;
// fx18 - LD ST, Vx. Set the sound timer to Vx
case 0x18:
chip8->registers.ST = chip8->registers.V[x];
break;
// fx1e - Add I, Vx
case 0x1e:
chip8->registers.I = chip8->registers.I + chip8->registers.V[x];
break;
// fx29 - LD F, Vx
case 0x29:
chip8->registers.I = chip8->registers.V[x] * CHIP8_SPRITE_DEFAULT_HEIGHT;
break;
// fx33 - LD B, Vx
case 0x33:
{
unsigned char hundreds = chip8->registers.V[x] / 100;
unsigned char tens = (chip8->registers.V[x] / 10) % 10;
unsigned char units = chip8->registers.V[x] % 10;
chip8_memory_set (&chip8->memory, chip8->registers.I, hundreds);
chip8_memory_set (&chip8->memory, chip8->registers.I + 1, tens);
chip8_memory_set (&chip8->memory, chip8->registers.I + 2, units);
}
break;
// fx55 - LD [I], Vx
case 0x55:
for (int i = 0; i <= x; i++)
{
chip8_memory_set (&chip8->memory, chip8->registers.I + i,
chip8->registers.V[i]);
}
break;
// fx65 - LD Vx, [I]
case 0x65:
for (int i = 0; i <= x; i++)
{
chip8->registers.V[i]
= chip8_memory_get (&chip8->memory, chip8->registers.I + i);
}
break;
}
}
static void
chip8_exec_extended (struct chip8 *chip8, unsigned short opcode)
{
unsigned short nnn = opcode & 0x0fff;
unsigned char x = (opcode >> 8) & 0x000f;
unsigned char y = (opcode >> 4) & 0x000f;
unsigned char kk = opcode & 0x00ff;
unsigned char n = opcode & 0x000f;
switch (opcode & 0xf000)
{
// jp addr, 1nnn jump to location nnn
case 0x1000:
chip8->registers.PC = nnn;
break;
// call addr, 2nnn call subroutine at location nnn
case 0x2000:
chip8_stack_push (chip8, chip8->registers.PC);
chip8->registers.PC = nnn;
break;
// SE Vx, byte - 3xkk skip next instruction if Vx=kk
case 0x3000:
if (chip8->registers.V[x] == kk)
chip8->registers.PC += 2;
break;
// SNE Vx, byte - 4xkk skip next instruction if Vx!=kk
case 0x4000:
if (chip8->registers.V[x] != kk)
chip8->registers.PC += 2;
break;
// 5xy0 - SE Vx, Vy, skip next instruction if Vx=Vy
case 0x5000:
if (chip8->registers.V[x] == chip8->registers.V[y])
chip8->registers.PC += 2;
break;
// 6xkk LD vx, byte - Vx = kk
case 0x6000:
chip8->registers.V[x] = kk;
break;
// 7xkk - ADD Vc, byte. Set Vx = Vx + kk
case 0x7000:
chip8->registers.V[x] += kk;
break;
// this is a more complex opcode, check the function
case 0x8000:
chip8_exec_extended_eight (chip8, opcode);
break;
// 9xy0 - SNE Vx, Vy. Skips next insturction if Vx != Vy
case 0x9000:
if (chip8->registers.V[x] != chip8->registers.V[y])
chip8->registers.PC += 2;
break;
// Annn - LD I, addr. Sets the I register to nnn
case 0xA000:
chip8->registers.I = nnn;
break;
// bnnn - Jump to location nnn + V0
case 0xB000:
chip8->registers.PC = nnn + chip8->registers.V[0x00];
break;
// Cxkk - RND Vx, byte. Set Vx = random byte & kk
case 0xC000:
srand (clock ());
chip8->registers.V[x] = (rand () % 255) & kk;
break;
// Dxyn - DRW Vx, Vy, nibble.
case 0xD000:
{
const char *sprite
= (const char *)&chip8->memory.memory[chip8->registers.I];
chip8->registers.V[0x0f]
= chip8_screen_draw_sprite (&chip8->screen, chip8->registers.V[x],
chip8->registers.V[y], sprite, n);
}
break;
// keyboard operations
case 0xE000:
{
switch (opcode & 0x00ff)
{
// Ex9e - SKP Vx, skip the next instruction if the key with value
// of Vx is pressed
case 0x9e:
if (chip8_keyboard_is_down (&chip8->keyboard,
chip8->registers.V[x]))
{
chip8->registers.PC += 2;
}
break;
// Exa1 - SKNP Vx, skip the next insturction if the key with value
// of Vx is not pressed
case 0xa1:
if (!chip8_keyboard_is_down (&chip8->keyboard,
chip8->registers.V[x]))
{
chip8->registers.PC += 2;
}
break;
}
}
break;
case 0xF000:
chip8_exec_extended_F (chip8, opcode);
break;
}
}
void void
chip8_exec (struct chip8 *chip8, unsigned short opcode) chip8_exec (struct chip8 *chip8, unsigned short opcode)
{ {
switch (opcode)
{
// clears the screen
case 0x00E0:
chip8_screen_clear (&chip8->screen);
break;
// returns from subroutine
case 0x00EE:
chip8->registers.PC = chip8_stack_pop (chip8);
break;
default:
chip8_exec_extended (chip8, opcode);
break;
}
} }

View File

@ -7,12 +7,18 @@ chip8_keyboard_ensure_in_bounds (int key)
assert (key >= 0 && key < CHIP8_TOTAL_KEYS); assert (key >= 0 && key < CHIP8_TOTAL_KEYS);
} }
void
chip8_keyboard_set_map (struct chip8_keyboard *keyboard, const char *map)
{
keyboard->keyboard_map = map;
}
int int
chip8_keyboard_map (const char *map, char key) chip8_keyboard_map (struct chip8_keyboard *keyboard, char key)
{ {
for (int i = 0; i < CHIP8_TOTAL_KEYS; i++) for (int i = 0; i < CHIP8_TOTAL_KEYS; i++)
{ {
if (map[i] == key) if (keyboard->keyboard_map[i] == key)
{ {
return i; return i;
} }

View File

@ -41,6 +41,7 @@ main (int argc, const char **argv)
struct chip8 chip8; struct chip8 chip8;
chip8_init (&chip8); chip8_init (&chip8);
chip8_load (&chip8, buf, size); chip8_load (&chip8, buf, size);
chip8_keyboard_set_map (&chip8.keyboard, keyboard_map);
SDL_Init (SDL_INIT_EVERYTHING); SDL_Init (SDL_INIT_EVERYTHING);
SDL_Window *window = SDL_CreateWindow ( SDL_Window *window = SDL_CreateWindow (
@ -66,7 +67,7 @@ main (int argc, const char **argv)
case SDL_KEYDOWN: case SDL_KEYDOWN:
{ {
char key = event.key.keysym.sym; char key = event.key.keysym.sym;
int vkey = chip8_keyboard_map (keyboard_map, key); int vkey = chip8_keyboard_map (&chip8.keyboard, key);
if (vkey != -1) if (vkey != -1)
{ {
chip8_keyboard_down (&chip8.keyboard, vkey); chip8_keyboard_down (&chip8.keyboard, vkey);
@ -77,7 +78,7 @@ main (int argc, const char **argv)
case SDL_KEYUP: case SDL_KEYUP:
{ {
char key = event.key.keysym.sym; char key = event.key.keysym.sym;
int vkey = chip8_keyboard_map (keyboard_map, key); int vkey = chip8_keyboard_map (&chip8.keyboard, key);
if (vkey != -1) if (vkey != -1)
{ {
chip8_keyboard_up (&chip8.keyboard, vkey); chip8_keyboard_up (&chip8.keyboard, vkey);
@ -111,7 +112,7 @@ main (int argc, const char **argv)
if (chip8.registers.DT > 0) if (chip8.registers.DT > 0)
{ {
sleep (100); usleep (10 * 1000); // TODO: Fix this lmao
chip8.registers.DT -= 1; chip8.registers.DT -= 1;
} }
@ -123,8 +124,8 @@ main (int argc, const char **argv)
unsigned short opcode unsigned short opcode
= chip8_memory_get_short (&chip8.memory, chip8.registers.PC); = chip8_memory_get_short (&chip8.memory, chip8.registers.PC);
chip8_exec (&chip8, opcode);
chip8.registers.PC += 2; chip8.registers.PC += 2;
chip8_exec (&chip8, opcode);
} }
out: out:

View File

@ -1,6 +1,7 @@
#include "screen.h" #include "screen.h"
#include <assert.h> #include <assert.h>
#include <memory.h>
static void static void
chip8_screen_ensure_in_bounds (int x, int y) chip8_screen_ensure_in_bounds (int x, int y)
@ -9,6 +10,12 @@ chip8_screen_ensure_in_bounds (int x, int y)
assert (y >= 0 && y <= CHIP8_DISPLAY_HEIGHT); assert (y >= 0 && y <= CHIP8_DISPLAY_HEIGHT);
} }
void
chip8_screen_clear (struct chip8_screen *screen)
{
memset (screen->pixels, 0, sizeof (screen->pixels));
}
void void
chip8_screen_set (struct chip8_screen *screen, int x, int y) chip8_screen_set (struct chip8_screen *screen, int x, int y)
{ {