1/*** includes ***/
2#include <SDL2/SDL.h>
3#include <SDL2/SDL_config-x86_64.h>
4#include <bits/types/struct_FILE.h>
5#include <errno.h>
6#include <stddef.h>
7#include <stdint.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <sys/stat.h>
11#include <time.h>
12#include <unistd.h>
13#include <sys/types.h>
14
15/*** data ***/
16// opcode store
17// All opcodes are 2 bytes and so are unsigned short
18unsigned short opcode;
19// 4KB of RAM
20unsigned char memory[4096];
21// CPU Register
22// 16 general purpose 8 bit registers
23unsigned char V[16];
24// Index or Address Register
25unsigned short I;
26// Program counter | A pseudo register that points to the next instruction on
27// the stack Instruction pointer being my preferred nomenclature
28unsigned short pc;
29// Display containing 2048 pixels in an array
30unsigned char gfx[64 * 32];
31// Timer registers that countdown to 0 at 60Hz
32unsigned char delay_timer;
33unsigned char sound_timer;
34// Sound flag
35unsigned char sound_flag = 0;
36// Draw flag
37unsigned char draw_flag = 0;
38// Stack and stack pointer
39unsigned short stack[16];
40unsigned short stack_pointer;
41
42// Font set used by CHIP 8
43// Also used to draw sprites using XOR
44// DEC HEX BIN RESULT DEC HEX BIN RESULT
45// 240 0xF0 1111 0000 **** 240 0xF0 1111 0000 ****
46// 144 0x90 1001 0000 * * 16 0x10 0001 0000 *
47// 144 0x90 1001 0000 * * 32 0x20 0010 0000 *
48// 144 0x90 1001 0000 * * 64 0x40 0100 0000 *
49// 240 0xF0 1111 0000 **** 64 0x40 0100 0000 *
50
51unsigned char chip8_fontset[80] = {
52 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
53 0x20, 0x60, 0x20, 0x20, 0x70, // 1
54 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
55 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
56 0x90, 0x90, 0xF0, 0x10, 0x10, // 4
57 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
58 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
59 0xF0, 0x10, 0x20, 0x40, 0x40, // 7
60 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
61 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
62 0xF0, 0x90, 0xF0, 0x90, 0x90, // A
63 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
64 0xF0, 0x80, 0x80, 0x80, 0xF0, // C
65 0xE0, 0x90, 0x90, 0x90, 0xE0, // D
66 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
67 0xF0, 0x80, 0xF0, 0x80, 0x80 // F
68};
69
70// Keypad
71unsigned char keypad[16];
72
73// Screen dimension constants
74const int SCREEN_WIDTH = 64 * 20;
75const int SCREEN_HEIGHT = 32 * 20;
76
77/*** initialize CPU ***/
78void chip8_initialize(void)
79{
80 // Seed for rand from time.h
81 srand((unsigned int)time(NULL));
82 pc = 0x200; // program counter starts at 0x200 or 512
83 opcode = 0; // reset current opcode
84 I = 0; // reset current index register
85 stack_pointer = 0; // reset stack pointer
86 printf("\n Chip 8 Initialized successfully \n");
87 memcpy(memory, chip8_fontset, sizeof(chip8_fontset));
88}
89
90/*** load rom ***/
91void chip8_load_program(char* filename)
92{
93 FILE* program = fopen(filename, "rb");
94
95 // get file size
96 fseek(program, 0, SEEK_END);
97 long buf_len = ftell(program);
98 rewind(program);
99
100 // load data into buffer
101 char* buf = malloc((buf_len + 1) * sizeof(char));
102 fread(buf, buf_len, 1, program);
103 printf("Rom loaded succesfully \n");
104 printf("Rom size: %zu bytes \n", buf_len);
105 fclose(program);
106
107 // load ROM into memory
108 for (int i = 0; i < buf_len; i++) {
109 memory[0x200 + i] = buf[i];
110 // Big endian so check last two values from the right
111 printf("Byte successfully read: %08X \n", buf[i]);
112 }
113 // clear buffer
114 free(buf);
115}
116
117/*** initialize display ***/
118
119SDL_Window* window;
120SDL_Renderer* renderer;
121
122int chip8_init_display()
123{
124 SDL_Init(SDL_INIT_VIDEO);
125 // Init Window struct
126
127 // Init window
128 window = SDL_CreateWindow("CHIP8",
129 SDL_WINDOWPOS_CENTERED,
130 SDL_WINDOWPOS_CENTERED,
131 SCREEN_WIDTH,
132 SCREEN_HEIGHT,
133 0);
134 // Init renderer struct
135 renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
136
137 if (SDL_Init(SDL_INIT_VIDEO) < 0) {
138 printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
139 return -1;
140 }
141 // Check that the window was successfully created
142
143 if (window == NULL) {
144 // In the case that the window could not be made...
145 printf("Could not create window: %s\n", SDL_GetError());
146 return -1;
147 }
148 return 0;
149}
150
151void stop_display(void)
152{
153 SDL_DestroyWindow(window);
154 SDL_Quit();
155}
156
157void chip8_draw(unsigned char* gfx)
158{
159 SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
160 // Init renderer
161 SDL_RenderClear(renderer);
162 // SDL Render color (background)
163
164 // Clear the current color and use the one specified above
165 SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
166
167 for (int y = 0; y < 32; y++) {
168 for (int x = 0; x < 64; x++) {
169 if (gfx[x + (y * 64)]) {
170 SDL_Rect rect;
171 rect.x = x * 20;
172 rect.y = y * 20;
173 // size of each "pixel" keep it consistent with the scaling of the display in SCREEN_WIDTH/HEIGHT
174 rect.w = 20;
175 rect.h = 20;
176
177 SDL_RenderFillRect(renderer, &rect);
178 }
179 }
180 }
181 SDL_RenderPresent(renderer);
182}
183
184void chip8_emulate_cycle()
185{
186 draw_flag = 0;
187 sound_flag = 0;
188 printf(" \n");
189
190 // Fetch opcode present at program counter and program counter + 1
191 // Since each opcode is supposed to be 2 bytes we fetch both pc and pc + 1
192 // Shift the current 8 bytes in memory[pc] left by 8 points
193 // We merge them using binary OR to get the entire opcode
194 opcode = memory[pc] << 8 | memory[pc + 1];
195
196 // Vx register, we are basically "grabbing" the x present in some
197 // instructions like 3XNN
198 unsigned short x = (opcode & 0x0F00) >> 8;
199
200 // Vy register, we are basically "grabbing" the y present in some
201 // instructions like 5XY0
202 unsigned short y = (opcode & 0x00F0) >> 4;
203
204 printf("0x%X \n", opcode);
205 switch (opcode & 0xF000) {
206 case 0x0000:
207 switch (opcode & 0x00FF) {
208 case 0x00E0:
209 for (int i = 0; i < 64 * 32; i++) {
210 gfx[i] = 0;
211 }
212 printf("00E0: Display Cleared\n");
213 pc += 2;
214 break;
215 case 0x00EE:
216 pc = stack[stack_pointer];
217 stack_pointer--;
218 printf("00EE: Retrurn from a subroutine\n");
219 pc += 2;
220 break;
221 default:
222 break;
223 }
224 break;
225
226 // 1NNN: Jumps to address NNN
227 case 0x1000:
228 printf("1nnn: Jump to location nnn \n");
229 pc = opcode & 0x0FFF;
230 break;
231
232 // 2nnn CALL subroutine at nnn
233 case 0x2000:
234 stack_pointer += 1;
235 stack[stack_pointer] = pc;
236 pc = opcode & 0x0FFF; // parse the NNN
237 printf("2nnn: Calls subroutine at nnn \n");
238 break;
239
240 // 3XNN: Skip next instruction if Vx = kk
241 case 0x3000:
242 if (V[x] == (opcode & 0x00FF)) {
243 pc += 2;
244 }
245 pc += 2;
246 printf("3XNN: Skip next instruction if V[x] == kk \n");
247 break;
248
249 // 4xkk: Skip next instruction if V[x] != kk
250 case 0x4000:
251 if (V[x] != (opcode & 0x00FF)) {
252 pc += 2;
253 }
254 pc += 2;
255 printf("4xkk: Skip next instruction if V[x] != kk \n");
256 break;
257
258 // 5xy0 - SE Vx, Vy Skip next instruction if Vx = Vy.
259 case 0x5000:
260 if (V[x] == V[y]) {
261 pc += 2;
262 }
263 pc += 2;
264 printf("5xy0: Skip next instruction if V[x] != kk \n");
265 break;
266
267 // 6XNN: Sets Vx to NN
268 case 0x6000:
269 V[x] = (opcode & 0x00FF);
270 pc += 2;
271 printf("6xkk: The interpreter puts the value kk into register V[x] \n");
272 break;
273
274 // 7xkk: ADD Vx, byte
275 case 0x7000:
276 V[x] += opcode & 0x00FF;
277 pc += 2;
278 printf("7xkk: Adds the value kk ot the value of V[x] then stores it in "
279 "V[x] \n");
280 break;
281
282 case 0x9000:
283 if (V[x] != V[y]) {
284 pc += 2;
285 }
286 pc += 2;
287 break;
288
289 // Instructions starting with 8
290 case 0x8000:
291 switch (opcode & 0x000F) {
292 // 8xy0: Sets Vx to Vy
293 case 0x0000:
294 V[x] = V[y];
295 pc += 2;
296 break;
297
298 // 8xy1: V[x] = V[x] | V[y]
299 case 0x0001:
300 V[x] = (V[x] | V[y]);
301 pc += 2;
302 break;
303
304 // 8xy2: V[x] = V[x] | V[y]
305 case 0x0002:
306 V[x] = (V[x] & V[y]);
307 pc += 2;
308 break;
309
310 // 8xy3: V[x] = [Vx] ^ V[y]
311 case 0x0003:
312 V[x] = (V[x] ^ V[y]);
313 pc += 2;
314 break;
315
316 // 8xy4: V[x] = V[x] + V[y], set VF = carry.
317 case 0x0004:
318 if (V[x] + V[y] > 255) {
319 printf("is this valid?");
320 V[0xF] = 1;
321 } else {
322 V[0xF] = 0;
323 }
324 V[x] += V[y];
325 pc += 2;
326 break;
327
328 // 8xy5: V[x] = V[x] - V[y], set VF = NOT BORROW
329 case 0x0005:
330 if (V[x] > V[y]) {
331 V[0xF] = 1;
332 } else {
333 V[0xF] = 0;
334 }
335 V[x] -= V[y];
336 pc += 2;
337 break;
338
339 // 8xy6: Sets Vx to Vy minus Vx. Vf is set to 0 when there's a borrow
340 case 0x0006:
341 V[0xF] = V[x] & 0x1;
342 V[x] = (V[x] >> 1);
343 pc += 2;
344 break;
345
346 // Set Vx = Vy - Vx, set VF = NOT borrow
347 case 0x0007:
348 if (V[y] > V[x]) {
349 V[0xF] = 1;
350 } else {
351 V[0xF] = 0;
352 }
353 V[x] = V[y] - V[x];
354 pc += 2;
355 break;
356
357 // 8xyE: Stores the most significant bit of V[x] and shifts V[x]
358 case 0x000E:
359 V[0xF] = (V[x] >> 7) & 0x1;
360 V[x] = (V[x] << 1);
361 pc += 2;
362 break;
363
364 default:
365 printf("Unknown opcode 0x%X.\n", opcode);
366 break;
367 }
368 break;
369
370 // Display n-byte sprite starting at memory location I at (Vx, Vy)
371 // Set VF = collision.
372 case 0xD000:
373 draw_flag = 1;
374 unsigned short height = opcode & 0x000F;
375 unsigned short pixel;
376 // set collision flag to 0
377 V[0xF] = 0;
378 // loop over each row
379 for (int row = 0; row < height; row++) {
380 // fetch the pixel value from the memory starting at location I
381 pixel = memory[I + row];
382 // loop over 8 bits of one row
383 for (int col = 0; col < 8; col++) {
384 // check if current evaluated pixel is set to 1 (0x80 >>
385 // col scnas throught the byte, one bit at the time)
386 if ((pixel & (0x80 >> col)) != 0) {
387 // if drawing causes any pixel to be erased set the
388 // collision flag to 1
389 if (gfx[(V[x] + col + ((V[y] + row) * 64))] == 1) {
390 V[0xF] = 1;
391 }
392 // set pixel value by using XOR
393 gfx[V[x] + col + ((V[y] + row) * 64)] ^= 1;
394 }
395 }
396 }
397 printf(
398 "Dxyn: Display n-byte sprite starting at memory location I at (Vx, "
399 "Vy), set VF to collision \n");
400 pc += 2;
401 break;
402
403 case 0xC000:
404 V[x] = (rand() % 256) & (opcode & 0x00FF);
405 pc += 2;
406 printf("Cxkk: The value at V[x] is %d \n", V[x]);
407 break;
408
409 case 0xA000:
410 I = opcode & 0x0FFF;
411 pc += 2;
412 printf("Annn: The value at register I is set to 0x%X \n",
413 opcode & 0x0FFF);
414 break;
415
416 case 0xE000:
417 switch (opcode & 0x00FF) {
418
419 // Ex9e: Skips next instructuion if key stored in V[x]is pressed
420 case 0x009E:
421 if (keypad[V[x]]) {
422 pc += 2;
423 }
424 pc += 2;
425 break;
426
427 // ExA1: Skips next instruction if key stroed in V[x] is not pressed
428 case 0x00A1:
429 if (!keypad[V[x]]) {
430 pc += 2;
431 }
432 pc += 2;
433 break;
434
435 default:
436 printf("Uknown opcode: 0x%X\n", opcode);
437 }
438 break;
439
440 // Instructions starting with FX
441 case 0xF000:
442 switch (opcode & 0x00FF) {
443 case 0x0007:
444 printf("Fx07: Place value of delay_timer into V[x]\n");
445 V[x] = delay_timer;
446 pc += 2;
447 break;
448
449 case 0x000A:
450 printf(
451 "Fx0A: Stop all execution till key press, keypress is stored in "
452 "V[x]\n");
453 for (int i = 0; i < 16; i++) {
454 if (keypad[i]) {
455 V[x] = i;
456 pc += 2;
457 break;
458 }
459 }
460 break;
461
462 case 0x0015:
463 printf("Fx15: Sets the delay_timer value to V[x]\n");
464 delay_timer = V[x];
465 pc += 2;
466 break;
467
468 // FX18: Sets the sound timer to Vx
469 case 0x0018:
470 printf("Fx18: Sets the sound_timer value to V[x]\n");
471 sound_timer = V[x];
472 pc += 2;
473 break;
474
475 // FX1E: Adds Vx to I
476 case 0x001E:
477 printf("Fx1E:The values of I and Vx are added, and the results are "
478 "stored in I\n");
479 I += V[x];
480 pc += 2;
481 break;
482
483 // FX29: Sets I to the location of the sprite for the character
484 // in Vx
485 case 0x0029:
486 printf("Fx29: Set location of I to mathching 0x sprite in V[x] \n");
487 // Each digit contains 5 bytes
488 I = V[x] * 5;
489 pc += 2;
490 break;
491
492 /*
493 * FX33:
494 *
495 * Stores the binary-coded decimal representation
496 * of VX, with the most significant of three digits
497 * at the address in I, the middle digit at I plus
498 * 1, and the least significant digit at I plus 2.
499 * (In other words, take the decimal representation
500 * of VX, place the hundreds digit in memory
501 * at location in I, the tens digit at
502 * location I+1, and the ones digit at
503 * location I+2.)
504 * */
505
506 case 0x0033:
507 printf(
508 "Fx33: Stores the binary-coded decimal representation of VX \n");
509 memory[I] = (V[x] % 1000) / 100;
510 memory[I + 1] = (V[x] % 100) / 10;
511 memory[I + 2] = (V[x] % 10);
512
513 pc += 2;
514 break;
515
516 case 0x0055:
517 printf("Fx55: Stores V[0] to V[x] in memory starting at I\n");
518
519 for (int i = 0; i <= x; i++) {
520 V[i] = memory[I + i];
521 }
522
523 pc += 2;
524 break;
525
526 // Fills V0 through Vx (Vx included) with values from memory
527 // starting at addr I.
528 case 0x0065:
529 printf("Read registers V0 through Vx from memory starting at "
530 "location I \n");
531 for (int i = 0; i <= x; i++) {
532 V[i] = memory[I + i];
533 }
534 pc += 2;
535 break;
536
537 default:
538 printf("Unknown opcode 0x%X.\n", opcode);
539 break;
540 }
541 break;
542 default:
543 printf("Unknown opcode 0x%X.\n", opcode);
544 break;
545 }
546
547 // Update timers
548 if (delay_timer > 0)
549 --delay_timer;
550 if (sound_timer > 0) {
551 if (sound_timer == 1)
552 printf("BEEP!\n");
553 --sound_timer;
554 }
555}
556
557SDL_Event event;
558SDL_Scancode keymappings[16] = {
559 SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4,
560 SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R,
561 SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_D, SDL_SCANCODE_F,
562 SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V
563};
564
565int usleep(unsigned int usec);
566int main(int argc, char* argv[])
567{
568 (void)argc;
569 const Uint8* state = SDL_GetKeyboardState(NULL);
570 chip8_initialize();
571 chip8_load_program(argv[1]);
572 chip8_init_display();
573 while (1) {
574 chip8_emulate_cycle();
575
576 if (draw_flag == 1) {
577 chip8_draw(gfx);
578 // Delay to slow down the clock
579 usleep(45000);
580 }
581 while (SDL_PollEvent(&event) != 0) {
582 switch (event.type) {
583 case SDL_QUIT:
584 SDL_DestroyWindow(window);
585 SDL_Quit();
586 return 0;
587 break;
588 default:
589 if (state[SDL_SCANCODE_ESCAPE]) {
590 return 0;
591 }
592 // update keyboard state
593 for (int keycode = 0; keycode < 16; keycode++) {
594 keypad[keycode] = state[keymappings[keycode]];
595 }
596 }
597 }
598 }
599}