chip-8

main.c

17 kB
  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}