chip-8

chip 8 emulator
Contents

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#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}