implemented the Chip-8 instruction set
This commit is contained in:
parent
a1edac8a52
commit
8ae57bba76
@ -3,3 +3,12 @@
|
||||
Emulator and assembler for the Chip8.
|
||||
|
||||
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?)
|
||||
|
@ -15,5 +15,6 @@
|
||||
#define CHIP8_TOTAL_KEYS 16
|
||||
|
||||
#define CHIP8_CHARACTER_SET_LOAD_ADDRESS 0x00
|
||||
#define CHIP8_SPRITE_DEFAULT_HEIGHT 5
|
||||
|
||||
#endif
|
||||
|
@ -6,9 +6,11 @@
|
||||
struct chip8_keyboard
|
||||
{
|
||||
_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_up (struct chip8_keyboard *kbd, int key);
|
||||
_Bool chip8_keyboard_is_down (struct chip8_keyboard *kbd, int key);
|
||||
|
@ -8,6 +8,7 @@ struct chip8_screen
|
||||
_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);
|
||||
_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,
|
||||
|
BIN
roms/breakout.ch8
Normal file
BIN
roms/breakout.ch8
Normal file
Binary file not shown.
Binary file not shown.
BIN
roms/space_invaders.ch8
Normal file
BIN
roms/space_invaders.ch8
Normal file
Binary file not shown.
295
src/chip8.c
295
src/chip8.c
@ -2,6 +2,10 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <memory.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
const char chip8_default_character_set[] = {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,18 @@ chip8_keyboard_ensure_in_bounds (int key)
|
||||
assert (key >= 0 && key < CHIP8_TOTAL_KEYS);
|
||||
}
|
||||
|
||||
void
|
||||
chip8_keyboard_set_map (struct chip8_keyboard *keyboard, const char *map)
|
||||
{
|
||||
keyboard->keyboard_map = map;
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
if (map[i] == key)
|
||||
if (keyboard->keyboard_map[i] == key)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ main (int argc, const char **argv)
|
||||
struct chip8 chip8;
|
||||
chip8_init (&chip8);
|
||||
chip8_load (&chip8, buf, size);
|
||||
chip8_keyboard_set_map (&chip8.keyboard, keyboard_map);
|
||||
|
||||
SDL_Init (SDL_INIT_EVERYTHING);
|
||||
SDL_Window *window = SDL_CreateWindow (
|
||||
@ -66,7 +67,7 @@ main (int argc, const char **argv)
|
||||
case SDL_KEYDOWN:
|
||||
{
|
||||
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)
|
||||
{
|
||||
chip8_keyboard_down (&chip8.keyboard, vkey);
|
||||
@ -77,7 +78,7 @@ main (int argc, const char **argv)
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
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)
|
||||
{
|
||||
chip8_keyboard_up (&chip8.keyboard, vkey);
|
||||
@ -111,7 +112,7 @@ main (int argc, const char **argv)
|
||||
|
||||
if (chip8.registers.DT > 0)
|
||||
{
|
||||
sleep (100);
|
||||
usleep (10 * 1000); // TODO: Fix this lmao
|
||||
chip8.registers.DT -= 1;
|
||||
}
|
||||
|
||||
@ -123,8 +124,8 @@ main (int argc, const char **argv)
|
||||
|
||||
unsigned short opcode
|
||||
= chip8_memory_get_short (&chip8.memory, chip8.registers.PC);
|
||||
chip8_exec (&chip8, opcode);
|
||||
chip8.registers.PC += 2;
|
||||
chip8_exec (&chip8, opcode);
|
||||
}
|
||||
|
||||
out:
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "screen.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <memory.h>
|
||||
|
||||
static void
|
||||
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);
|
||||
}
|
||||
|
||||
void
|
||||
chip8_screen_clear (struct chip8_screen *screen)
|
||||
{
|
||||
memset (screen->pixels, 0, sizeof (screen->pixels));
|
||||
}
|
||||
|
||||
void
|
||||
chip8_screen_set (struct chip8_screen *screen, int x, int y)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user