chip8.c (9933B)
1 /* See LICENSE file for copyright and license details. */ 2 #include <sys/types.h> 3 4 #include <err.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <time.h> 8 #include <unistd.h> 9 10 #include <SDL2/SDL.h> 11 12 #define EXECUTE(pc) do { pc += 2; } while (0) 13 14 struct chip8 { 15 uint16_t stack[16]; 16 uint16_t i; 17 uint16_t opcode; 18 uint16_t pc; 19 uint16_t sp; 20 uint8_t mem[4096]; 21 uint8_t gfx[64 * 32]; 22 uint8_t v[16]; 23 uint8_t keys[16]; 24 uint8_t delaytimer; 25 uint8_t soundtimer; 26 uint8_t drawflag; 27 }; 28 29 static void chip8_init(struct chip8 *); 30 static void rom_load(struct chip8 *, const char *); 31 static void emulate(struct chip8 *); 32 static int decode(struct chip8 *); 33 static int handle_events(struct chip8 *); 34 static void render(SDL_Renderer *, SDL_Texture *, struct chip8 *); 35 36 static void 37 chip8_init(struct chip8 *c8) 38 { 39 int i; 40 uint8_t fontset[80] = { 41 0xf0, 0x90, 0x90, 0x90, 0xf0, // 0 42 0x20, 0x60, 0x20, 0x20, 0x70, // 1 43 0xf0, 0x10, 0xf0, 0x80, 0xf0, // 2 44 0xf0, 0x10, 0xf0, 0x10, 0xf0, // 3 45 0x90, 0x90, 0xf0, 0x10, 0x10, // 4 46 0xf0, 0x80, 0xf0, 0x10, 0xf0, // 5 47 0xf0, 0x80, 0xf0, 0x90, 0xf0, // 6 48 0xf0, 0x10, 0x20, 0x40, 0x40, // 7 49 0xf0, 0x90, 0xf0, 0x90, 0xf0, // 8 50 0xf0, 0x90, 0xf0, 0x10, 0xf0, // 9 51 0xf0, 0x90, 0xf0, 0x90, 0x90, // a 52 0xe0, 0x90, 0xe0, 0x90, 0xe0, // b 53 0xf0, 0x80, 0x80, 0x80, 0xf0, // c 54 0xe0, 0x90, 0x90, 0x90, 0xe0, // d 55 0xf0, 0x80, 0xf0, 0x80, 0xf0, // e 56 0xf0, 0x80, 0xf0, 0x80, 0x80 // f 57 }; 58 59 c8->pc = 0x200; 60 c8->opcode = 0; 61 c8->i = 0; 62 c8->sp = 0; 63 c8->delaytimer = 0; 64 c8->soundtimer = 0; 65 memset(c8->v, 0, sizeof(c8->v)); 66 memset(c8->keys, 0, sizeof(c8->keys)); 67 memset(c8->stack, 0, sizeof(c8->stack)); 68 memset(c8->gfx, 0, sizeof(c8->gfx)); 69 memset(c8->mem, 0, sizeof(c8->mem)); 70 for (i = 0; i < 80; i++) 71 c8->mem[i] = fontset[i]; 72 } 73 74 static void 75 rom_load(struct chip8 *c8, const char *fpath) 76 { 77 FILE *rom; 78 char *buf; 79 size_t res, romsiz; 80 int i; 81 82 if ((rom = fopen(fpath, "rb")) == NULL) 83 err(1, "fopen: %s", fpath); 84 fseek(rom, 0, SEEK_END); 85 if ((romsiz = ftell(rom)) >= 4092 - 512) 86 errx(1, "ROM cannot fit into memory"); 87 rewind(rom); 88 if ((buf = malloc(romsiz + 1)) == NULL) 89 err(1, "malloc"); 90 if ((res = fread(buf, sizeof(char), romsiz, rom)) != romsiz) 91 err(1, "fread"); 92 buf[romsiz] = '\0'; 93 for (i = 0; i < romsiz; i++) 94 c8->mem[i + 512] = buf[i]; 95 fclose(rom); 96 free(buf); 97 } 98 99 static void 100 emulate(struct chip8 *c8) 101 { 102 uint16_t *pc = &c8->pc; 103 104 c8->opcode = c8->mem[*pc] << 8 | c8->mem[*pc + 1]; 105 if (decode(c8)) { 106 EXECUTE(*pc); 107 if (c8->delaytimer > 0) 108 c8->delaytimer--; 109 if (c8->soundtimer > 0) 110 c8->soundtimer--; 111 } 112 printf("Opcode: %x\tMemory: %x\tI: %x\tSP: %x\tPC: %d\n", 113 c8->opcode, c8->mem[*pc] << 8 | c8->mem[*pc + 1], c8->i, c8->sp, *pc); 114 } 115 116 static int 117 decode(struct chip8 *c8) 118 { 119 int yl, xl, pos, i, keypress = 0; 120 uint16_t vx, vy, nn, nnn; 121 uint16_t h, pixel; 122 uint8_t cvx, cvy; 123 124 vx = (c8->opcode & 0x0f00) >> 8; 125 vy = (c8->opcode & 0x00f0) >> 4; 126 nn = c8->opcode & 0x00ff; 127 nnn = c8->opcode & 0x0fff; 128 129 switch (c8->opcode & 0xf000) { 130 case 0x0000: // 00E_ 131 switch (nn) { 132 case 0xe0: // 00E0 - Clear screen 133 memset(c8->gfx, 0, sizeof(c8->gfx)); 134 c8->drawflag = 1; 135 break; 136 case 0xee: // 00EE - Return from subroutine 137 c8->pc = c8->stack[--c8->sp]; 138 break; 139 default: 140 warnx("unknown opcode: %x\n", c8->opcode); 141 return (0); 142 } 143 break; 144 case 0x1000: // 1NNN - Jump to address NNN 145 c8->pc = nnn - 2; 146 break; 147 case 0x2000: // 2NNN - Call subroutine at NNN 148 c8->stack[c8->sp++] = c8->pc; 149 c8->pc = nnn - 2; 150 case 0x3000: // 3NNN - Skip next instruction if VX == NN 151 if (c8->v[vx] == nn) 152 EXECUTE(c8->pc); 153 break; 154 case 0x4000: // 4NNN - Skip next instruction if VX != NN 155 if (c8->v[vx] != nn) 156 EXECUTE(c8->pc); 157 break; 158 case 0x5000: // 5XY0 - Skip next instruction if VX == VY 159 if (c8->v[vx] == c8->v[vy]) 160 EXECUTE(c8->pc); 161 break; 162 case 0x6000: // 6XNN - Set VX to NN 163 c8->v[vx] = nn; 164 break; 165 case 0x7000: // 7XNN - Add NN to VX 166 c8->v[vx] += nn; 167 break; 168 case 0x8000: // 8XY_ 169 switch (c8->opcode & 0x000f) { 170 case 0x0000: // 8XY0 - Set VX to VY 171 c8->v[vx] = c8->v[vy]; 172 break; 173 case 0x0001: // 8XY1 - Set VX to (VX OR VY) 174 c8->v[vx] |= c8->v[vy]; 175 break; 176 case 0x0002: // 8XY2 - Set VX to (VX AND VY) 177 c8->v[vx] &= c8->v[vy]; 178 break; 179 case 0x0003: // 8XY3 - Set VX to (VX XOR VY) 180 c8->v[vx] ^= c8->v[vy]; 181 break; 182 case 0x0004: // 8XY4 - Add VY to VX, VF = 1 if there is a carry 183 c8->v[vx] += c8->v[vy]; 184 c8->v[0xf] = (c8->v[vy] > (0xff - c8->v[vx])) ? 1 : 0; 185 break; 186 case 0x0005: // 8XY5 - Sub VY from VX, VF = 0 if there is a borrow 187 c8->v[0xf] = (c8->v[vy] > c8->v[vx]) ? 0 : 1; 188 c8->v[vx] -= c8->v[vy]; 189 break; 190 case 0x0006: // 8XY6 - Shift VX right by 1. VF = LSB of VX before shift 191 c8->v[0xf] = c8->v[vx] & 0x1; 192 c8->v[vx] >>= 1; 193 break; 194 case 0x0007: // 8XY7 - Set VX to VY-VX. VF = 0 if there is a borrow 195 c8->v[0xf] = (c8->v[vx] > c8->v[vy]) ? 0 : 1; 196 c8->v[vx] = c8->v[vy] - c8->v[vx]; 197 break; 198 case 0x000e: // 8XYE - Shift VX left by 1. VF = MSB of VX before shift 199 c8->v[0xf] = c8->v[vx] >> 7; 200 c8->v[vx] <<= 1; 201 break; 202 default: 203 warnx("unknown opcode: %x\n", c8->opcode); 204 return (0); 205 } 206 break; 207 case 0x9000: // 9XY0 - Skip next instruction if VX != VY 208 if (c8->v[vx] != c8->v[vy]) 209 EXECUTE(c8->pc); 210 break; 211 case 0xa000: // ANNN - Set I to the address NNN 212 c8->i = nnn; 213 break; 214 case 0xb000: // BNNN - Jump to NNN + V0 215 c8->pc = (nnn + c8->v[0]) - 2; 216 break; 217 case 0xc000: // CNNN - Set VX to random number masked by NN 218 c8->v[vx] = (rand() % (0xff + 1)) & nn; 219 break; 220 case 0xd000: // Draw an 8 pixel sprite at (VX, VY) 221 cvx = c8->v[vx]; 222 cvy = c8->v[vy]; 223 c8->v[0xf] = 0; 224 h = c8->opcode & 0x000f; 225 for (yl = 0; yl < h; yl++) { 226 for (xl = 0; xl < 8; xl++) { 227 pixel = c8->mem[c8->i + yl]; 228 pos = cvx + xl + ((cvy + yl) * 64); 229 if ((pixel & (0x80 >> xl)) == 0) 230 continue; 231 if (c8->gfx[pos] == 1) 232 c8->v[0xf] = 1; 233 c8->gfx[pos] ^= 1; 234 } 235 } 236 c8->drawflag = 1; 237 break; 238 case 0xe000: // EX__ 239 switch (nn) { 240 case 0x009e: // EX9E - Skip next instruction if key in VX is pressed 241 if (c8->keys[c8->v[vx]]) 242 EXECUTE(c8->pc); 243 break; 244 case 0x00a1: // EXA1 - Skip next instruction if key in VX isn't pressed 245 if (!c8->keys[c8->v[vx]]) 246 EXECUTE(c8->pc); 247 break; 248 default: 249 warnx("unknown opcode: %x\n", c8->opcode); 250 return (0); 251 } 252 break; 253 case 0xf000: // FX__ 254 switch (nn) { 255 case 0x0007: // FX07 - Set VX to delaytimer 256 c8->v[vx] = c8->delaytimer; 257 break; 258 case 0x000a: // FX0A - Wait for key press and then store it in VX 259 for (i = 0; i < 16; i++) { 260 if (c8->keys[i]) { 261 c8->v[vx] = i; 262 keypress = 1; 263 } 264 } 265 if (!keypress) 266 return (0); 267 break; 268 case 0x0015: // FX15 - Set the delaytimer to VX 269 c8->delaytimer = c8->v[vx]; 270 break; 271 case 0x0018: // FX18 - Set the soundtimer to VX 272 c8->soundtimer = c8->v[vx]; 273 break; 274 case 0x001e: // FX1E - Add VX to I 275 c8->v[0xf] = ((c8->i + c8->v[vx]) > 0xfff) ? 1 : 0; 276 c8->i += c8->v[vx]; 277 break; 278 case 0x0029: // FX29 - Set I to the location of the sprite for char VX 279 c8->i = c8->v[vx] * 0x5; 280 break; 281 case 0x0033: // FX33 - Store bin coded decimal of VX at I, I+1 and I+2 282 c8->mem[c8->i] = c8->v[vx] / 100; 283 c8->mem[c8->i+1] = (c8->v[vx] / 10) % 10; 284 c8->mem[c8->i+2] = c8->v[vx] % 10; 285 break; 286 case 0x0055: // FX55 - Store V0 to VX in memory starting at I 287 for (i = 0; i <= (vx); i++) 288 c8->mem[c8->i + i] = c8->v[i]; 289 c8->i += (vx) + 1; 290 break; 291 case 0x0065: // FX65 - Fill V0 to VX with vals from memory starting at I 292 for (i = 0; i <= (vx); i++) 293 c8->v[i] = c8->mem[c8->i + i]; 294 c8->i += vx + 1; 295 break; 296 default: 297 warnx("unknown opcode: %x\n", c8->opcode); 298 return (0); 299 } 300 break; 301 default: 302 warnx("unimplemented opcode\n"); 303 return (0); 304 } 305 return (1); 306 } 307 308 static int 309 handle_events(struct chip8 *c8) 310 { 311 SDL_Event e; 312 int i; 313 static const uint8_t keymap[16] = { 314 SDLK_1, SDLK_2, SDLK_3, SDLK_4, 315 SDLK_q, SDLK_w, SDLK_e, SDLK_r, 316 SDLK_a, SDLK_s, SDLK_d, SDLK_f, 317 SDLK_z, SDLK_x, SDLK_c, SDLK_v 318 }; 319 320 while (SDL_PollEvent(&e)) { 321 if (e.type == SDL_QUIT || e.key.keysym.sym == SDLK_ESCAPE) 322 return (0); 323 if (e.type != SDL_KEYUP && e.type != SDL_KEYDOWN) 324 continue; 325 for (i = 0; i < 16; i++) { 326 if (e.key.keysym.sym != keymap[i]) 327 continue; 328 if (e.type == SDL_KEYUP) 329 c8->keys[i] = 0; 330 else if (e.type == SDL_KEYDOWN) 331 c8->keys[i] = 1; 332 } 333 } 334 return (1); 335 } 336 337 static void 338 render(SDL_Renderer *ren, SDL_Texture *tex, struct chip8 *c8) 339 { 340 uint32_t pixels[2048]; 341 int i; 342 343 c8->drawflag = 0; 344 for (i = 0; i < 2048; i++) 345 pixels[i] = (0x00ffffff * c8->gfx[i]) | 0xff000000; 346 SDL_UpdateTexture(tex, NULL, pixels, 64 * sizeof(Uint32)); 347 SDL_RenderClear(ren); 348 SDL_RenderCopy(ren, tex, NULL, NULL); 349 SDL_RenderPresent(ren); 350 } 351 352 int 353 main(int argc, char *argv[]) 354 { 355 SDL_Window *win; 356 SDL_Renderer *ren; 357 SDL_Texture *tex; 358 struct chip8 *c8; 359 int w = 1024, h = 512; 360 361 if (argc != 2) { 362 fprintf(stderr, "usage: %s rom\n", *argv); 363 return (1); 364 } 365 if (SDL_Init(SDL_INIT_EVERYTHING) < 0) 366 errx(1, "SDL_Init: %s", SDL_GetError()); 367 if ((c8 = malloc(sizeof(struct chip8))) == NULL) 368 err(1, "malloc"); 369 370 chip8_init(c8); 371 rom_load(c8, *++argv); 372 srand(time(NULL)); 373 374 win = SDL_CreateWindow("CHIP-8", SDL_WINDOWPOS_UNDEFINED, 375 SDL_WINDOWPOS_UNDEFINED, w, h, 376 SDL_WINDOW_SHOWN); 377 ren = SDL_CreateRenderer(win, -1, 0); 378 SDL_RenderSetLogicalSize(ren, w, h); 379 tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, 380 SDL_TEXTUREACCESS_STREAMING, 64, 32); 381 382 if (!win || !ren || !tex) 383 errx(1, "SDL error: %s", SDL_GetError()); 384 for (;;) { 385 if (!handle_events(c8)) 386 break; 387 emulate(c8); 388 if (c8->drawflag) 389 render(ren, tex, c8); 390 /* FIXME: VERY slow on some machines */ 391 usleep(1500); 392 } 393 394 free(c8); 395 SDL_DestroyTexture(tex); 396 SDL_DestroyRenderer(ren); 397 SDL_DestroyWindow(win); 398 SDL_Quit(); 399 400 return (0); 401 }