chip-8

chip-8 emulator

feat: more opcode coverage

Arjun Choudhary contact@arjunchoudhary.com

commit: 1ab0c3b parent: 0a93275

1 files changed, 414 insertions(+), 291 deletions(-)
MODmain.c+414-291
MOD · main.c +414 -291
--- a/main.c
+++ b/main.c
@@ -63,7 +63,7 @@ unsigned char chip8_fontset[80] = {
     0xF0, 0x80, 0x80, 0x80, 0xF0, // C
     0xE0, 0x90, 0x90, 0x90, 0xE0, // D
     0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
-    0xF0, 0x80, 0xF0, 0x80, 0x80  // F
+    0xF0, 0x80, 0xF0, 0x80, 0x80 // F
 };
 
 // Keypad
@@ -74,317 +74,440 @@ const int SCREEN_WIDTH = 64 * 10;
 const int SCREEN_HEIGHT = 32 * 10;
 
 /*** initialize CPU ***/
-void chip8_initialize(void) {
-  // Seed for rand from time.h
-  srand((unsigned int)time(NULL));
-  pc = 0x200;        // program counter starts at 0x200 or 512
-  opcode = 0;        // reset current opcode
-  I = 0;             // reset current index register
-  stack_pointer = 0; // reset stack pointer
-  printf("\n Chip 8 Initialized successfully \n");
-  memcpy(memory, chip8_fontset, sizeof(chip8_fontset));
+void chip8_initialize(void)
+{
+    // Seed for rand from time.h
+    srand((unsigned int)time(NULL));
+    pc = 0x200; // program counter starts at 0x200 or 512
+    opcode = 0; // reset current opcode
+    I = 0; // reset current index register
+    stack_pointer = 0; // reset stack pointer
+    printf("\n Chip 8 Initialized successfully \n");
+    memcpy(memory, chip8_fontset, sizeof(chip8_fontset));
 }
 
 /*** load rom ***/
-void chip8_load_program(char *filename) {
-  FILE *program = fopen(filename, "rb");
-
-  // get file size
-  fseek(program, 0, SEEK_END);
-  long buf_len = ftell(program);
-  rewind(program);
-
-  // load data into buffer
-  char *buf = malloc((buf_len + 1) * sizeof(char));
-  fread(buf, buf_len, 1, program);
-  printf("Rom loaded succesfully \n");
-  printf("Rom size: %zu bytes \n", buf_len);
-  fclose(program);
-
-  // load ROM into memory
-  for (int i = 0; i < buf_len; i++) {
-    memory[0x200 + i] = buf[i];
-    // Big endian so check last two values from the right
-    printf("Byte successfully read: %08X \n", buf[i]);
-  }
-  // clear buffer
-  free(buf);
+void chip8_load_program(char* filename)
+{
+    FILE* program = fopen(filename, "rb");
+
+    // get file size
+    fseek(program, 0, SEEK_END);
+    long buf_len = ftell(program);
+    rewind(program);
+
+    // load data into buffer
+    char* buf = malloc((buf_len + 1) * sizeof(char));
+    fread(buf, buf_len, 1, program);
+    printf("Rom loaded succesfully \n");
+    printf("Rom size: %zu bytes \n", buf_len);
+    fclose(program);
+
+    // load ROM into memory
+    for (int i = 0; i < buf_len; i++) {
+        memory[0x200 + i] = buf[i];
+        // Big endian so check last two values from the right
+        printf("Byte successfully read: %08X \n", buf[i]);
+    }
+    // clear buffer
+    free(buf);
 }
 
 /*** initialize display ***/
 
-SDL_Window *window;
-SDL_Renderer *renderer;
-
-int chip8_init_display() {
-  SDL_Init(SDL_INIT_VIDEO);
-  // Init Window struct
-
-  // Init window
-  window =
-      SDL_CreateWindow("CHIP8", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
-                       SCREEN_WIDTH, SCREEN_HEIGHT, 0);
-  // Init renderer struct
-  renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
-
-  if (SDL_Init(SDL_INIT_VIDEO) < 0) {
-    printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
-    return -1;
-  }
-  // Check that the window was successfully created
-
-  if (window == NULL) {
-    // In the case that the window could not be made...
-    printf("Could not create window: %s\n", SDL_GetError());
-    return -1;
-  }
-  return 0;
+SDL_Window* window;
+SDL_Renderer* renderer;
+
+int chip8_init_display()
+{
+    SDL_Init(SDL_INIT_VIDEO);
+    // Init Window struct
+
+    // Init window
+    window = SDL_CreateWindow("CHIP8",
+        SDL_WINDOWPOS_CENTERED,
+        SDL_WINDOWPOS_CENTERED,
+        SCREEN_WIDTH,
+        SCREEN_HEIGHT,
+        0);
+    // Init renderer struct
+    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
+
+    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+        printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
+        return -1;
+    }
+    // Check that the window was successfully created
+
+    if (window == NULL) {
+        // In the case that the window could not be made...
+        printf("Could not create window: %s\n", SDL_GetError());
+        return -1;
+    }
+    return 0;
 }
 
-void stop_display(void) {
-  SDL_DestroyWindow(window);
-  SDL_Quit();
+void stop_display(void)
+{
+    SDL_DestroyWindow(window);
+    SDL_Quit();
 }
 
-void chip8_draw(unsigned char *gfx) {
-  SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
-  // Init renderer
-  SDL_RenderClear(renderer);
-  // SDL Render color (background)
-
-  // Clear the current color and use the one specified above
-  SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
-
-  for (int y = 0; y < 32; y++) {
-    for (int x = 0; x < 64; x++) {
-      if (gfx[x + (y * 64)]) {
-        SDL_Rect rect;
-        rect.x = x * 10;
-        rect.y = y * 10;
-        rect.w = 10;
-        rect.h = 10;
-
-        SDL_RenderFillRect(renderer, &rect);
-      }
+void chip8_draw(unsigned char* gfx)
+{
+    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+    // Init renderer
+    SDL_RenderClear(renderer);
+    // SDL Render color (background)
+
+    // Clear the current color and use the one specified above
+    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
+
+    for (int y = 0; y < 32; y++) {
+        for (int x = 0; x < 64; x++) {
+            if (gfx[x + (y * 64)]) {
+                SDL_Rect rect;
+                rect.x = x * 10;
+                rect.y = y * 10;
+                rect.w = 10;
+                rect.h = 10;
+
+                SDL_RenderFillRect(renderer, &rect);
+            }
+        }
     }
-  }
-  SDL_RenderPresent(renderer);
+    SDL_RenderPresent(renderer);
 }
 
-void chip8_emulate_cycle() {
-  draw_flag = 0;
-  sound_flag = 0;
-  printf(" \n");
-
-  // Fetch opcode present at program counter and program counter + 1
-  // Since each opcode is supposed to be 2 bytes we fetch both pc and pc + 1
-  // Shift the current 8 bytes in memory[pc] left by 8 points
-  // We merge them using binary OR to get the entire opcode
-  opcode = memory[pc] << 8 | memory[pc + 1];
-
-  // Vx register, we are basically "grabbing" the x present in some
-  // instructions like 3XNN
-  unsigned short x = (opcode & 0x0F00) >> 8;
-
-  // Vy register, we are basically "grabbing" the y present in some
-  // instructions like 5XY0
-  unsigned short y = (opcode & 0x00F0) >> 4;
-
-  printf("0x%X \n", opcode);
-  switch (opcode & 0xF000) {
-  case 0x0000:
-    for (int i = 0; i < 64 * 32; i++) {
-      gfx[i] = 0;
-    }
-    printf("00E0: Display Cleared\n");
-    pc += 2;
-    break;
-    
-  case 0xC000:
-    V[x] = (rand() % 256) & (opcode & 0x00FF);
-    pc += 2;
-    printf("Cxkk: The value at V[x] is %d \n", V[x]);
-    break;
-
-  case 0xA000:
-    I = opcode & 0x0FFF;
-    pc += 2;
-    printf("Annn: The value at register I is set to 0x%X \n", opcode & 0x0FFF);
-    break;
-
-  // set of of 7 instructions starting with FX
-  case 0xF000:
-  switch (opcode & 0x00FF) {
-    case 0x0007:
-    printf("Fx07: Place value of delay_timer into V[x]\n");
-    V[x] = delay_timer;
-    pc += 2;
-    break;
-    
-    case 0x000A:
-    printf("Fx0A: Stop all execution till key press, keypress is stored in V[x]\n");
-    for (int i = 0; i < 16; i++) {
-      if (keypad[i]) {
-        V[x] = i;
+void chip8_emulate_cycle()
+{
+    draw_flag = 0;
+    sound_flag = 0;
+    printf(" \n");
+
+    // Fetch opcode present at program counter and program counter + 1
+    // Since each opcode is supposed to be 2 bytes we fetch both pc and pc + 1
+    // Shift the current 8 bytes in memory[pc] left by 8 points
+    // We merge them using binary OR to get the entire opcode
+    opcode = memory[pc] << 8 | memory[pc + 1];
+
+    // Vx register, we are basically "grabbing" the x present in some
+    // instructions like 3XNN
+    unsigned short x = (opcode & 0x0F00) >> 8;
+
+    // Vy register, we are basically "grabbing" the y present in some
+    // instructions like 5XY0
+    unsigned short y = (opcode & 0x00F0) >> 4;
+
+    printf("0x%X \n", opcode);
+    switch (opcode & 0xF000) {
+    case 0x0000:
+        for (int i = 0; i < 64 * 32; i++) {
+            gfx[i] = 0;
+        }
+        printf("00E0: Display Cleared\n");
+        pc += 2;
+        break;
+
+    case 0xC000:
+        V[x] = (rand() % 256) & (opcode & 0x00FF);
         pc += 2;
+        printf("Cxkk: The value at V[x] is %d \n", V[x]);
         break;
-          }
+
+    case 0xA000:
+        I = opcode & 0x0FFF;
+        pc += 2;
+        printf("Annn: The value at register I is set to 0x%X \n",
+            opcode & 0x0FFF);
+        break;
+
+    // 2nnn CALL subroutine at nnn
+    case 0x2000:
+        stack_pointer += 1;
+        stack[stack_pointer] = pc;
+        pc = opcode & 0x0FFF; // parse the NNN
+        printf("2nnn: Calls subroutine at nnn \n");
+        break;
+
+    // 3XNN: Skip next instruction if Vx = kk
+    case 0x3000:
+        if (V[x] == (opcode & 0x00FF)) {
+            pc += 2;
         }
-      break;
-    
-    case 0x0015:
-    printf("Fx15: Sets the delay_timer value to V[x]\n");
-    delay_timer = V[x];
-    pc += 2;
-    break;
-    
-    // FX18: Sets the sound timer to Vx
-    case 0x0018:
-    printf("Fx18: Sets the sound_timer value to V[x]\n");
-    sound_timer = V[x];
-    pc += 2;
-    break;
-
-    // FX1E: Adds Vx to I
-    case 0x001E:
-    printf("Fx1E:The values of I and Vx are added, and the results are stored in I\n");
-    I += V[x];
-    pc += 2;
-    break;
-
-    // FX29: Sets I to the location of the sprite for the character
-    // in Vx
-    case 0x0029:
-    printf("Fx29: Set location of I to mathching 0x sprite in V[x] \n");
-    // Each digit contains 5 bytes 
-    I = V[x] * 5;
-    pc += 2;
-    break;
-    
-    /*
-    * FX33:
-    *
-    * Stores the binary-coded decimal representation
-    * of VX, with the most significant of three digits
-    * at the address in I, the middle digit at I plus
-    * 1, and the least significant digit at I plus 2.
-    * (In other words, take the decimal representation
-    * of VX, place the hundreds digit in memory
-    * at location in I, the tens digit at
-    * location I+1, and the ones digit at
-    * location I+2.)
-    * */
-
-    case 0x0033:
-      printf("Fx33: Stores the binary-coded decimal representation of VX \n");
-      memory[I] = (V[x] % 1000) / 100;
-      memory[I + 1] = (V[x] % 100) / 10;
-      memory[I + 2] = (V[x] % 10);
-
-      pc += 2;
-      break;
-
-    case 0x0055:
-      printf("Fx55: Stores V[0] to V[x] in memory starting at I\n");
-
-      for (int i = 0; i <= x; i++) {
-          V[i] = memory[I + i];
-      }
-
-      pc += 2;
-      break;
-
-    // Fills V0 through Vx (Vx included) with values from memory
-    // starting at addr I.
-    case 0x0065:
-      printf("Read registers V0 through Vx from memory starting at location I \n");
-      for (int i = 0; i <= x; i++) {
-          V[i] = memory[I + i];
-      }
-      pc += 2;
-      break;
+        pc += 2;
+        printf("3XNN: Skip next instruction if V[x] == kk \n");
+        break;
 
-    default:
-      printf("Fx65: Read registers V[0] through V[x] from memory starting at I.\n");
-      break;  
-  }
-
-  break;
-
-
-  // 6XNN: Sets Vx to NN
-  case 0x6000:
-    V[x] = (opcode & 0x00FF);
-    pc += 2;
-    printf("6xkk: The interpreter puts the value kk into register V[x] \n");
-    break;
-
-  // 1NNN: Jumps to address NNN
-  case 0x1000:
-    printf("1nnn: Jump to location nnn \n");
-    pc = opcode & 0x0FFF;
-    break;
-
-  // Display n-byte sprite starting at memory location I at (Vx, Vy)
-  // Set VF = collision.
-  case 0xD000:
-    draw_flag = 1;
-    unsigned short height = opcode & 0x000F;
-    unsigned short pixel;
-    // set collision flag to 0
-    V[0xF] = 0;
-    // loop over each row
-    for (int row = 0; row < height; row++) {
-      // fetch the pixel value from the memory starting at location I
-      pixel = memory[I + row];
-      // loop over 8 bits of one row
-      for (int col = 0; col < 10; col++) {
-        // check if current evaluated pixel is set to 1 (0x80 >>
-        // col scnas throught the byte, one bit at the time)
-        if ((pixel & (0x80 >> col)) != 0) {
-          // if drawing causes any pixel to be erased set the
-          // collision flag to 1
-          if (gfx[(V[x] + col + ((V[y] + row) * 64))] == 1) {
-            V[0xF] = 1;
-          }
-          // set pixel value by using XOR
-          gfx[V[x] + col + ((V[y] + row) * 64)] ^= 1;
+    // 4xkk: Skip next instruction if V[x] != kk
+    case 0x4000:
+        if (V[x] != (opcode & 0x00FF)) {
+            pc += 2;
         }
-      }
+        pc += 2;
+        printf("4xkk: Skip next instruction if V[x] != kk \n");
+        break;
+
+    // 6XNN: Sets Vx to NN
+    case 0x6000:
+        V[x] = (opcode & 0x00FF);
+        pc += 2;
+        printf("6xkk: The interpreter puts the value kk into register V[x] \n");
+        break;
+
+    // 7xkk: ADD Vx, byte
+    case 0x7000:
+        V[x] += opcode & 0x00FF;
+        pc += 2;
+        printf("7xkk: Adds the value kk ot the value of V[x] then stores it in "
+               "V[x] \n");
+        break;
+
+    // 1NNN: Jumps to address NNN
+    case 0x1000:
+        printf("1nnn: Jump to location nnn \n");
+        pc = opcode & 0x0FFF;
+        break;
+
+    // Display n-byte sprite starting at memory location I at (Vx, Vy)
+    // Set VF = collision.
+    case 0xD000:
+        draw_flag = 1;
+        unsigned short height = opcode & 0x000F;
+        unsigned short pixel;
+        // set collision flag to 0
+        V[0xF] = 0;
+        // loop over each row
+        for (int row = 0; row < height; row++) {
+            // fetch the pixel value from the memory starting at location I
+            pixel = memory[I + row];
+            // loop over 8 bits of one row
+            for (int col = 0; col < 10; col++) {
+                // check if current evaluated pixel is set to 1 (0x80 >>
+                // col scnas throught the byte, one bit at the time)
+                if ((pixel & (0x80 >> col)) != 0) {
+                    // if drawing causes any pixel to be erased set the
+                    // collision flag to 1
+                    if (gfx[(V[x] + col + ((V[y] + row) * 64))] == 1) {
+                        V[0xF] = 1;
+                    }
+                    // set pixel value by using XOR
+                    gfx[V[x] + col + ((V[y] + row) * 64)] ^= 1;
+                }
+            }
+        }
+        printf(
+            "Dxyn: Display n-byte sprite starting at memory location I at (Vx, "
+            "Vy), set VF to collision \n");
+        pc += 2;
+        break;
+
+    // Instructions starting with FX
+    case 0xF000:
+        switch (opcode & 0x00FF) {
+        case 0x0007:
+            printf("Fx07: Place value of delay_timer into V[x]\n");
+            V[x] = delay_timer;
+            pc += 2;
+            break;
+
+        case 0x000A:
+            printf(
+                "Fx0A: Stop all execution till key press, keypress is stored in "
+                "V[x]\n");
+            for (int i = 0; i < 16; i++) {
+                if (keypad[i]) {
+                    V[x] = i;
+                    pc += 2;
+                    break;
+                }
+            }
+            break;
+
+        case 0x0015:
+            printf("Fx15: Sets the delay_timer value to V[x]\n");
+            delay_timer = V[x];
+            pc += 2;
+            break;
+
+        // FX18: Sets the sound timer to Vx
+        case 0x0018:
+            printf("Fx18: Sets the sound_timer value to V[x]\n");
+            sound_timer = V[x];
+            pc += 2;
+            break;
+
+        // FX1E: Adds Vx to I
+        case 0x001E:
+            printf("Fx1E:The values of I and Vx are added, and the results are "
+                   "stored in I\n");
+            I += V[x];
+            pc += 2;
+            break;
+
+        // FX29: Sets I to the location of the sprite for the character
+        // in Vx
+        case 0x0029:
+            printf("Fx29: Set location of I to mathching 0x sprite in V[x] \n");
+            // Each digit contains 5 bytes
+            I = V[x] * 5;
+            pc += 2;
+            break;
+
+            /*
+             * FX33:
+             *
+             * Stores the binary-coded decimal representation
+             * of VX, with the most significant of three digits
+             * at the address in I, the middle digit at I plus
+             * 1, and the least significant digit at I plus 2.
+             * (In other words, take the decimal representation
+             * of VX, place the hundreds digit in memory
+             * at location in I, the tens digit at
+             * location I+1, and the ones digit at
+             * location I+2.)
+             * */
+
+        case 0x0033:
+            printf(
+                "Fx33: Stores the binary-coded decimal representation of VX \n");
+            memory[I] = (V[x] % 1000) / 100;
+            memory[I + 1] = (V[x] % 100) / 10;
+            memory[I + 2] = (V[x] % 10);
+
+            pc += 2;
+            break;
+
+        case 0x0055:
+            printf("Fx55: Stores V[0] to V[x] in memory starting at I\n");
+
+            for (int i = 0; i <= x; i++) {
+                V[i] = memory[I + i];
+            }
+
+            pc += 2;
+            break;
+
+        // Fills V0 through Vx (Vx included) with values from memory
+        // starting at addr I.
+        case 0x0065:
+            printf("Read registers V0 through Vx from memory starting at "
+                   "location I \n");
+            for (int i = 0; i <= x; i++) {
+                V[i] = memory[I + i];
+            }
+            pc += 2;
+            break;
+
+        default:
+            printf("Unknown opcode 0x%x.\n", opcode);
+            break;
+        }
+        break;
+
+    // Instructions starting with 8
+    case (0x8000):
+        switch (opcode & 0x0FFF) {
+        // 8xy0: Sets Vx to Vy
+        case (0x0000):
+            V[x] = V[y];
+            pc += 2;
+            break;
+
+        // 8xy1: V[x] = V[x] | V[y]
+        case (0x0001):
+            V[x] = (V[x] | V[y]);
+            pc += 2;
+            break;
+
+        // 8xy2: V[x] = V[x] | V[y]
+        case (0x0002):
+            V[x] = (V[x] & V[y]);
+            pc += 2;
+            break;
+
+        // 8xy3: V[x] = [Vx] ^ V[y]
+        case (0x0003):
+            V[x] = (V[x] ^ V[y]);
+            pc += 2;
+            break;
+
+        // 8xy4: V[x] = V[x] + V[y], set VF = carry.
+        case (0x0004):
+            if (V[x] + V[y] > 255) {
+                printf("is this valid?");
+                V[0xF] = 1;
+            } else {
+                V[0xF] = 0;
+            }
+            V[x] += V[y];
+            pc += 2;
+            break;
+
+        // 8xy5: V[x] = V[x] - V[y], set VF = NOT BORROW
+        case (0x0005):
+            if (V[x] > V[y]) {
+                V[0xF] = 1;
+            } else {
+                V[0xF] = 0;
+            }
+            V[x] -= V[y];
+            pc += 2;
+            break;
+
+        // 8xy6: Sets Vx to Vy minus Vx. Vf is set to 0 when there's a borrow
+        case (0x0006):
+            V[0xF] = V[x] & 0x1;
+            V[x] = (V[x] >> 1);
+            pc += 2;
+            break;
+
+        // Set Vx = Vy - Vx, set VF = NOT borrow
+        case (0x0007):
+            if (V[y] > V[x]) {
+                V[0xF] = 1;
+            } else {
+                V[0xF] = 0;
+            }
+            V[x] = V[y] - V[x];
+            pc += 2;
+            break;
+
+        default:
+            printf("Unknown opcode 0x%X.\n", opcode);
+            break;
+        }
+        break;
+
+    default:
+        printf("Unknown opcode 0x%X.\n", opcode);
     }
-    printf("Dxyn: Display n-byte sprite starting at memory location I at (Vx, Vy), set VF to collision \n");
-    pc += 2;
-    break;
-
-  default:
-    printf("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
-    printf("OpCode after opcode & 0xF000: 0x%X \n", opcode & 0xF000);
-  }
-
-  // Update timers
-  if (delay_timer > 0)
-    --delay_timer;
-  if (sound_timer > 0) {
-    if (sound_timer == 1)
-      printf("BEEP!\n");
-    --sound_timer;
-  }
-}
 
-int main(int argc, char *argv[]) {
-  chip8_initialize();
-  chip8_load_program(argv[1]);
-  chip8_init_display();
-  /*   while (1) {} */
+    // Update timers
+    if (delay_timer > 0)
+        --delay_timer;
+    if (sound_timer > 0) {
+        if (sound_timer == 1)
+            printf("BEEP!\n");
+        --sound_timer;
+    }
+}
 
-  for (int i = 0; i < 50; i++) {
-    chip8_emulate_cycle();
-    if (draw_flag == 1) {
-      chip8_draw(gfx);
+int main(int argc, char* argv[])
+{
+    chip8_initialize();
+    chip8_load_program(argv[1]);
+    chip8_init_display();
+    /*   while (1) {} */
+
+    for (int i = 0; i < 500; i++) {
+        chip8_emulate_cycle();
+        if (draw_flag == 1) {
+            chip8_draw(gfx);
+        }
+        usleep(10000);
     }
-    usleep(150000);
-  }
-  SDL_DestroyWindow(window);
-  SDL_Quit();
-  return 0;
+    SDL_DestroyWindow(window);
+    SDL_Quit();
+    return 0;
 }=
\ No newline at end of file