diff --git a/Readme.org b/Readme.org index 924e1d9..9c0580f 100644 --- a/Readme.org +++ b/Readme.org @@ -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?) diff --git a/include/config.h b/include/config.h index 2e47abe..984f9b1 100644 --- a/include/config.h +++ b/include/config.h @@ -15,5 +15,6 @@ #define CHIP8_TOTAL_KEYS 16 #define CHIP8_CHARACTER_SET_LOAD_ADDRESS 0x00 +#define CHIP8_SPRITE_DEFAULT_HEIGHT 5 #endif diff --git a/include/keyboard.h b/include/keyboard.h index 785053b..7a851d3 100644 --- a/include/keyboard.h +++ b/include/keyboard.h @@ -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); diff --git a/include/screen.h b/include/screen.h index 59e285e..3b8b204 100644 --- a/include/screen.h +++ b/include/screen.h @@ -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, diff --git a/roms/breakout.ch8 b/roms/breakout.ch8 new file mode 100644 index 0000000..70b50db Binary files /dev/null and b/roms/breakout.ch8 differ diff --git a/roms/horsey_jump.ch8 b/roms/horsey_jump.ch8 deleted file mode 100644 index 4dc09e7..0000000 Binary files a/roms/horsey_jump.ch8 and /dev/null differ diff --git a/roms/space_invaders.ch8 b/roms/space_invaders.ch8 new file mode 100644 index 0000000..3ada8df Binary files /dev/null and b/roms/space_invaders.ch8 differ diff --git a/src/chip8.c b/src/chip8.c index 622d092..9bf92e1 100644 --- a/src/chip8.c +++ b/src/chip8.c @@ -2,6 +2,10 @@ #include #include +#include +#include + +#include 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; + } } diff --git a/src/keyboard.c b/src/keyboard.c index b0f026d..caee4a1 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -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; } diff --git a/src/main.c b/src/main.c index fd9efe2..742369b 100644 --- a/src/main.c +++ b/src/main.c @@ -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: diff --git a/src/screen.c b/src/screen.c index c1f3c65..97e8dac 100644 --- a/src/screen.c +++ b/src/screen.c @@ -1,6 +1,7 @@ #include "screen.h" #include +#include 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) {