chip8

A simple C CHIP8 VM.
git clone git://git.vgx.fr/chip8
Log | Files | Refs | README

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 }