chip8.c (8185B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <time.h> 5 #include <unistd.h> 6 7 #include "media.h" 8 9 #define FREQ 840 10 #define STACK_SIZE 24 11 12 unsigned char RAM[0x1000]; 13 14 unsigned char V[16]; 15 unsigned short I; 16 17 unsigned char DT; 18 unsigned char ST; 19 20 unsigned short PC=0x200; 21 unsigned char SP; 22 23 unsigned short STACK[STACK_SIZE]; 24 25 unsigned short KEYBOARD; 26 27 unsigned char SCREEN[256]; 28 29 #define CHAR_SPRITES_OFFSET 0x100 30 char char_sprites[80] = { 31 /* Source: Cowgod's Chip-8 Technical Reference */ 32 /* 0 */ 33 0xF0, 34 0x90, 35 0x90, 36 0x90, 37 0xF0, 38 /* 1 */ 39 0x20, 40 0x60, 41 0x20, 42 0x20, 43 0x70, 44 /* 2 */ 45 0xF0, 46 0x10, 47 0xF0, 48 0x80, 49 0xF0, 50 /* 3 */ 51 0xF0, 52 0x10, 53 0xF0, 54 0x10, 55 0xF0, 56 /* 4 */ 57 0x90, 58 0x90, 59 0xF0, 60 0x10, 61 0x10, 62 /* 5 */ 63 0xF0, 64 0x80, 65 0xF0, 66 0x10, 67 0xF0, 68 /* 6 */ 69 0xF0, 70 0x80, 71 0xF0, 72 0x90, 73 0xF0, 74 /* 7 */ 75 0xF0, 76 0x10, 77 0x20, 78 0x40, 79 0x40, 80 /* 8 */ 81 0xF0, 82 0x90, 83 0xF0, 84 0x90, 85 0xF0, 86 /* 9 */ 87 0xF0, 88 0x90, 89 0xF0, 90 0x10, 91 0xF0, 92 /* A */ 93 0xF0, 94 0x90, 95 0xF0, 96 0x90, 97 0x90, 98 /* B */ 99 0xE0, 100 0x90, 101 0xE0, 102 0x90, 103 0xE0, 104 /* C */ 105 0xF0, 106 0x80, 107 0x80, 108 0x80, 109 0xF0, 110 /* D */ 111 0xE0, 112 0x90, 113 0x90, 114 0x90, 115 0xE0, 116 /* E */ 117 0xF0, 118 0x80, 119 0xF0, 120 0x80, 121 0xF0, 122 /* F */ 123 0xF0, 124 0x80, 125 0xF0, 126 0x80, 127 0x80, 128 }; 129 130 /* instructions are a 2 char array */ 131 132 #define I_SHORT(instr) (short)((instr)[0]<<8u | (instr)[1]) 133 #define I_CLASS(instr) ((instr)[0]>>4u) 134 #define I_NNN(instr) ((((instr)[0]&15u)<<8u) | (instr)[1]) 135 #define I_ADDR(instr) I_NNN(instr) 136 #define I_NN(instr) ((instr)[1]) 137 #define I_N(instr) ((instr)[1]&15u) 138 #define I_X(instr) ((instr)[0]&15u) 139 #define I_Y(instr) ((instr)[1]>>4u) 140 141 #define WARN(...) (fprintf(stderr, __VA_ARGS__)) 142 143 static void display(){ 144 for(int y=0 ; y<32 ; y++){ 145 for(int x=0 ; x<64 ; x++){ 146 draw(x, y, SCREEN[y*8+x/8]&1<<(7-x%8) ? 1:0); 147 } 148 } 149 } 150 151 static void step(){ 152 if(PC < 0x200 || PC >= 0x1000){ 153 WARN("PC out of usable adress space: %X\n", (unsigned) PC); 154 return; /*TODO: abort? */ 155 } 156 157 unsigned char instr[2] = {RAM[PC], RAM[PC+1]}; 158 /* TODO: debug: print state */ 159 PC+=2; 160 switch(I_CLASS(instr)){ 161 case 0: 162 switch(I_SHORT(instr)){ 163 case 0x00E0: /* clear screen */ 164 for(int i=0 ; i<256 ; i++) 165 SCREEN[i] = 0; 166 clear_screen(); 167 frame(); 168 return; 169 case 0x00EE: /* return from a subroutine */ 170 if(!SP) 171 WARN("Stack underflow\n"); 172 else 173 PC=STACK[--SP]; 174 return; 175 default: /* legacy machine routine call */ 176 WARN("Legacy machine routine call: %X\n", 177 (unsigned) I_SHORT(instr)); 178 return; 179 } 180 return; 181 case 1: /* jump */ 182 PC=I_ADDR(instr); 183 return; 184 case 2: /* call a subroutine */ 185 if(SP==STACK_SIZE) 186 WARN("Stack overflow\n"); 187 else 188 STACK[SP++] = PC; 189 PC=I_ADDR(instr); 190 return; 191 case 3: 192 if(V[I_X(instr)] == I_NN(instr)) 193 PC+=2; 194 return; 195 case 4: 196 if(V[I_X(instr)] != I_NN(instr)) 197 PC+=2; 198 return; 199 case 5: 200 if(V[I_X(instr)] == V[I_Y(instr)]) 201 PC+=2; 202 return; 203 case 6: 204 V[I_X(instr)] = I_NN(instr); 205 return; 206 case 7: 207 V[I_X(instr)] += I_NN(instr); 208 return; 209 case 8: 210 switch(I_N(instr)){ 211 case 0: 212 V[I_X(instr)] = V[I_Y(instr)]; 213 return; 214 case 1: 215 V[I_X(instr)] |= V[I_Y(instr)]; 216 return; 217 case 2: 218 V[I_X(instr)] &= V[I_Y(instr)]; 219 return; 220 case 3: 221 V[I_X(instr)] ^= V[I_Y(instr)]; 222 return; 223 case 4: 224 V[0xf] = V[I_X(instr)] + V[I_Y(instr)] > 0xff ? 1 : 0; 225 V[I_X(instr)] += V[I_Y(instr)]; 226 return; 227 case 5: 228 V[0xf] = V[I_X(instr)] > V[I_Y(instr)] ? 1 : 0; 229 V[I_X(instr)] -= V[I_Y(instr)]; 230 return; 231 case 6: 232 V[0xf] = V[I_X(instr)]&1u; 233 V[I_X(instr)] >>= 1u; 234 return; 235 case 7: 236 V[0xf] = V[I_X(instr)] < V[I_Y(instr)] ? 1 : 0; 237 V[I_X(instr)] = V[I_Y(instr)] - V[I_X(instr)] ; 238 return; 239 case 0xE: 240 V[0xf] = V[I_X(instr)]>>7u; 241 V[I_X(instr)] <<= 1u; 242 return; 243 244 default: /* unknown instruction */ 245 WARN("Unknown instruction: %X\n", 246 (unsigned) I_SHORT(instr)); 247 return; 248 } 249 return; 250 case 9: 251 if(V[I_X(instr)] != V[I_Y(instr)]) 252 PC+=2; 253 return; 254 case 0xA: 255 I=I_ADDR(instr); 256 return; 257 case 0xB: 258 PC = I_ADDR(instr) + V[0]; 259 return; 260 case 0xC: 261 V[I_X(instr)] = rand() & I_NN(instr); 262 return; 263 case 0xD: /* display */ 264 if(I+I_N(instr) >= 0x1000){ 265 WARN("Sprite data out of memory bounds: %X..%X\n", 266 (unsigned)I, (unsigned)(I+I_N(instr))); 267 return; 268 } 269 270 V[0xf] = 0; 271 unsigned char x = V[I_X(instr)]; 272 unsigned char y = V[I_Y(instr)]; 273 unsigned char shift = x%8; 274 275 for(unsigned i=0;i<I_N(instr);i++){ 276 unsigned char wrapped_y = (y+i)%32; 277 unsigned char *screen_tile = &SCREEN[wrapped_y*8+(x/8)%8]; 278 unsigned char sprite_tile = RAM[I+i]>>shift; 279 V[0xf] |= *screen_tile & sprite_tile ? 1:0; 280 *screen_tile ^= sprite_tile; 281 if(shift){ 282 screen_tile = &SCREEN[wrapped_y*8+(x/8+1)%8]; 283 sprite_tile = RAM[I+i]<<(8u-shift); 284 V[0xf] |= *screen_tile & sprite_tile ? 1:0; 285 *screen_tile ^= sprite_tile; 286 } 287 } 288 289 /*TODO: maybe update only written regions */ 290 display(); 291 return; 292 293 case 0xE: 294 switch(I_NN(instr)){ 295 case 0x9E: 296 if(V[I_X(instr)] > 16) 297 WARN("Unknown key: %u\n", 298 (unsigned)V[I_X(instr)]); 299 if(KEYBOARD & 1<<(V[I_X(instr)])) 300 PC+=2; 301 return; 302 case 0xA1: 303 if(V[I_X(instr)] > 16) 304 WARN("Unknown key: %u\n", 305 (unsigned)V[I_X(instr)]); 306 if(!(KEYBOARD & 1<<(V[I_X(instr)]))) 307 PC+=2; 308 return; 309 default: /* unknown instruction */ 310 WARN("Unknown instruction: %X\n", 311 (unsigned) I_SHORT(instr)); 312 return; 313 314 } 315 return; 316 case 0xF: 317 switch(I_NN(instr)){ 318 case 0x07: 319 V[I_X(instr)]=DT; 320 return; 321 case 0x0A: 322 if(!KEYBOARD) 323 PC-=2; 324 else { 325 V[I_X(instr)]=0; 326 for(unsigned short k=KEYBOARD;!(k&1);k>>=1) 327 V[I_X(instr)]++; 328 } 329 return; 330 case 0x15: 331 DT=V[I_X(instr)]; 332 return; 333 case 0x18: 334 ST=V[I_X(instr)]; 335 return; 336 case 0x1E: 337 I+=V[I_X(instr)]; 338 /* Undocumented feature, reported on Wikipedia */ 339 V[0xf] = I >= 0x1000 ? 1 : 0; 340 I &= 0xfff; 341 return; 342 case 0x29: 343 if(V[I_X(instr)]>0xF) 344 WARN("Too large input for a digit: %X\n", 345 (unsigned)V[I_X(instr)]); 346 else 347 I = CHAR_SPRITES_OFFSET + 5*V[I_X(instr)]; 348 return; 349 case 0x33: 350 if(I<0x200 || I+3 >= 0x1000){ 351 WARN("BCD store out of memory bounds: %X..%X\n", 352 (unsigned)I, (unsigned)(I+3)); 353 return; 354 } 355 356 RAM[I+2] = V[I_X(instr)]%10; 357 RAM[I+1] = (V[I_X(instr)]/10)%10; 358 RAM[I] = (V[I_X(instr)]/100)%10; 359 return; 360 case 0x55: 361 if(I<0x200 || I+I_X(instr) >= 0x1000){ 362 WARN("Register store out of memory bounds: %X..%X\n", 363 (unsigned)I, 364 (unsigned)(I+I_X(instr))); 365 return; 366 } 367 368 for(unsigned i=0 ; i<=I_X(instr) ; i++) 369 RAM[I+i] = V[i]; 370 return; 371 case 0x65: 372 if(I+I_X(instr) >= 0x1000){ 373 WARN("Register load out of memory bounds: %X..%X\n", 374 (unsigned)I, 375 (unsigned)(I+I_X(instr))); 376 return; 377 } 378 379 for(unsigned i=0 ; i<=I_X(instr) ; i++) 380 V[i] = RAM[I+i]; 381 return; 382 default: /* unknown instruction */ 383 WARN("Unknown instruction: %X\n", 384 (unsigned) I_SHORT(instr)); 385 return; 386 } 387 return; 388 default: /* unreachable */ 389 /*TODO: assert */ 390 return; 391 } 392 393 } 394 395 int main(int argc, char **argv){ 396 /* Set up char sprites */ 397 memcpy(RAM+CHAR_SPRITES_OFFSET, char_sprites, sizeof(char_sprites)); 398 399 /* Load ROM */ 400 if(argc<2) 401 return 1; 402 FILE *rom=fopen(argv[1], "r"); 403 if(!rom) 404 return 2; 405 fread(RAM+0x200,1,0x1000-0x200,rom); 406 if(ferror(rom)) 407 return 3; 408 fclose(rom); 409 410 /* Init media stuff: graphics, input, sound */ 411 m_init(argc, argv); 412 413 /* Let's go */ 414 for(int i=0 ; 1 ; i++){ 415 KEYBOARD = get_input(KEYBOARD); 416 if(KEYBOARD == (unsigned short)-1) 417 break; 418 419 step(); 420 421 if(!(i%(FREQ/60))){ 422 frame(); 423 if(DT) 424 DT--; 425 if(ST) 426 ST--; 427 } 428 429 set_buzzer_state(ST ? 1 : 0); 430 431 printf("i=%5d, DT=%3d, K=[", i, (int)DT); 432 for(int k=0 ; k<16;k++) 433 printf("%c", KEYBOARD & 1<<k ? "0123456789ABCDEF"[k]:'.'); 434 printf("] \r"); 435 } 436 437 /* Quit media */ 438 m_quit(); 439 440 return 0; 441 }