chip8

CHIP-8 emulator
git clone git://git.margiolis.net/chip8.git
Log | Files | Refs | README | LICENSE

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 }