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