From 8ae57bba765161e52e181620c0faf4fd2b7c1e2a Mon Sep 17 00:00:00 2001 From: Ghostie Date: Wed, 12 Feb 2025 21:57:29 -0500 Subject: [PATCH] implemented the Chip-8 instruction set --- Readme.org | 9 ++ include/config.h | 1 + include/keyboard.h | 4 +- include/screen.h | 1 + roms/breakout.ch8 | Bin 0 -> 232 bytes roms/horsey_jump.ch8 | Bin 326 -> 0 bytes roms/space_invaders.ch8 | Bin 0 -> 1301 bytes src/chip8.c | 295 ++++++++++++++++++++++++++++++++++++++++ src/keyboard.c | 10 +- src/main.c | 9 +- src/screen.c | 7 + 11 files changed, 329 insertions(+), 7 deletions(-) create mode 100644 roms/breakout.ch8 delete mode 100644 roms/horsey_jump.ch8 create mode 100644 roms/space_invaders.ch8 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 0000000000000000000000000000000000000000..70b50dbc2399a49032ea11711160eec4754bd994 GIT binary patch literal 232 zcmZ2d)Yz{*n%e#d+|MJ*?B+~`Kh9egQ{TYR3I5DI!I@zcE zcV-Ye=PWO@1f;9Ur8(r1p*^FJle3)Aj4Y;K5@n2wP6>5fGK83Rl+lzi6)2m<{7V9) zzWBe88Hg6rSakM+;Gz%Tj6bC&OQcAD(7fzg%=!^begcw0Cq6X%|G(&7vS^aPWz|KG VlVvZf3axtAb*`uDUB{V@cL2dMVebF{ literal 0 HcmV?d00001 diff --git a/roms/horsey_jump.ch8 b/roms/horsey_jump.ch8 deleted file mode 100644 index 4dc09e7d43653714ab4ea613f251613afe078ff3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 326 zcmYeYPv%PDO65;yNM%i9VtDXf^J=P-eLh3cpT%mI3>LLHGA>$H$mqzF$dJ#}FK*8u zWRbw|U|}M&A+wOfqGKuShAcuJK$^vnRmgAAGeb6^5Fp7e6tkEOD90g`0;IXFIyiD9 zG8!=pRVCTmGYVCtGYD0cGyZ1>npTz1^Iuemj{yNV7-Sf%7z*Sc=s)m(z*8W>p}>NW z1hZHeBQ4-66vEDTHtIWS8FXcIEFVW?q{0{~uyMA!fT diff --git a/roms/space_invaders.ch8 b/roms/space_invaders.ch8 new file mode 100644 index 0000000000000000000000000000000000000000..3ada8dfe93a7d95925a9f52a1bf2cf385101bacd GIT binary patch literal 1301 zcmZWnQAk@?82)d1y_piamh`f#L!@1A(Y5IsX0;I_gJ9ar3bKc*%*)hU?J_db zHpya)flW574=cK`vDvNnWm{kNuoto79NUwy4is_bVa)mx!w{k+p1c1hX*=A&s%{wQRRi1nI9zSDv1 zJPLJiw$)+c()-6IejM1JczHl<1M;gJPYIVr@_U<${9*fTlx&tciEOvEkR3Vx2Doc) z3O)2`Rq?%YRUtWY*g(dLwdaA19X80Jse2cRyD{b%E?soEWt@3AL7PtV1uN2{^5bAUzCT&h3Dh;D&&*7BvD{v<+c;@^XuA) zH6HSE=u=q^X*C5})%ghW!|+LNgx~p!T>I8}=qU^;mivcc7~!vTqn!KhH+2OMYKU>} z7WaCs`!3{t3Co(DMbp%@ov?hq#Kw3RavLshi~)QI9XJ3ujyr|`=#Z-D5Jd}sK?w&y zqN{5F0f1Zu=vWYk&Uj5c4zRSe;x}5nN8?3TeX+>%05LyXLL80nN-d8cpE(0?F!?j$ z;AngSBt=?S04Va?lauMaqw#0;@3!nhP8<7Q{AvA*uxJCI#8**rxvYcnpQh&Kp1_lj z=jJ}2-T%9}d9S^_9bT+&KY#9(U$%OC-(FkWf~^-$lE#%uGJz_j>(EFpM&Xr;xN?2m}F*reTjZFfRof zTXCYrXT%-lj7$_oj4%ggWO!B(SbBMx@jOd1^z9a~JBLFMgmS+Om+1SMA3w7KqYs?a zRDua0R5#2%U2W1K5TL{qrcD9~g_vn27^Zq<`?MN015qPv5^98Xb=nNK{^xX9MFEQ0(9n^cvUT~Vjc{JvVlv!A|lx9~5`cIUbP literal 0 HcmV?d00001 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) {