chip-8

chip-8 emulator

feat: rom loading and init

Arjun Choudhary contact@arjunchoudhary.com

commit: 0a93275 parent: a2e4208

1 files changed, 272 insertions(+), 53 deletions(-)
MODmain.c+272-53
MOD · main.c +272 -53
--- a/main.c
+++ b/main.c
@@ -1,8 +1,12 @@
 /*** includes ***/
 #include <SDL2/SDL.h>
+#include <SDL2/SDL_config-x86_64.h>
+#include <bits/types/struct_FILE.h>
 #include <errno.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <sys/stat.h>
 #include <time.h>
 #include <unistd.h>
@@ -18,20 +22,24 @@ unsigned char memory[4096];
 unsigned char V[16];
 // Index or Address Register
 unsigned short I;
-// Program counter | A psudo register that points to the next instruction on the
-// stack Instruction pointer being my preffered nomenclature
+// Program counter | A pseudo register that points to the next instruction on
+// the stack Instruction pointer being my preferred nomenclature
 unsigned short pc;
 // Display containing 2048 pixels in an array
 unsigned char gfx[64 * 32];
 // Timer registers that countdown to 0 at 60Hz
 unsigned char delay_timer;
 unsigned char sound_timer;
-//
+// Sound flag
+unsigned char sound_flag = 0;
+// Draw flag
+unsigned char draw_flag = 0;
+// Stack and stack pointer
 unsigned short stack[16];
 unsigned short stack_pointer;
 
-// Fontset used by CHIP 8
-// Also used to draw sprits using XOR
+// Font set used by CHIP 8
+// Also used to draw sprites using XOR
 // DEC   HEX    BIN         RESULT    DEC   HEX    BIN         RESULT
 // 240   0xF0   1111 0000    ****     240   0xF0   1111 0000    ****
 // 144   0x90   1001 0000    *  *      16   0x10   0001 0000       *
@@ -62,20 +70,18 @@ unsigned char chip8_fontset[80] = {
 unsigned char keypad[16];
 
 // Screen dimension constants
-const int SCREEN_WIDTH = 640;
-const int SCREEN_HEIGHT = 320;
+const int SCREEN_WIDTH = 64 * 10;
+const int SCREEN_HEIGHT = 32 * 10;
 
-/*** initialize cpu ***/
+/*** 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("Chip 8 Initialized successfully \n");
-  // Load fontset into memory 
-  //for (int i = 0; i < 80; i++) {
-  //      memory[i] = fontset[i];
-  //  }
+  printf("\n Chip 8 Initialized successfully \n");
   memcpy(memory, chip8_fontset, sizeof(chip8_fontset));
 }
 
@@ -95,7 +101,7 @@ void chip8_load_program(char *filename) {
   printf("Rom size: %zu bytes \n", buf_len);
   fclose(program);
 
-  // load rom into memory
+  // 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
@@ -106,66 +112,279 @@ void chip8_load_program(char *filename) {
 }
 
 /*** initialize display ***/
-int chip8_init_display (){
+
+SDL_Window *window;
+SDL_Renderer *renderer;
+
+int chip8_init_display() {
   SDL_Init(SDL_INIT_VIDEO);
   // Init Window struct
-  SDL_Window *window;
+
   // Init window
-   window =
-      SDL_CreateWindow(
-        "CHIP8", 
-        SDL_WINDOWPOS_CENTERED, 
-        SDL_WINDOWPOS_CENTERED,
-        SCREEN_WIDTH,
-        SCREEN_HEIGHT, 
-        0);
+  window =
+      SDL_CreateWindow("CHIP8", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+                       SCREEN_WIDTH, SCREEN_HEIGHT, 0);
   // Init renderer struct
-  SDL_Renderer* renderer;
-  // Init renderer
-    renderer =  
-      SDL_CreateRenderer(
-        window, 
-        -1, 
-        SDL_RENDERER_ACCELERATED);
-  //SDL Render color (background)
-  SDL_SetRenderDrawColor( renderer, 0, 0, 0, 0 );
-  // Clear the current color and use the one specified above 
-  SDL_RenderClear( renderer );
-  // Shape 
-  SDL_Rect rectangle;
-    rectangle.x = 0;
-    rectangle.y = 0;
-    rectangle.w = 90;
-    rectangle.h = 100;
-
-  SDL_SetRenderDrawColor( renderer, 0, 0, 255, 255 );
-  SDL_RenderFillRect( renderer, &rectangle );
-  SDL_RenderPresent(renderer);
-
+  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;
   }
-  SDL_Delay(3000);
-  // Pause execution for 3000 milliseconds, for example
-  // Close and destroy the window
+  return 0;
+}
+
+void stop_display(void) {
   SDL_DestroyWindow(window);
-  // Clean up
   SDL_Quit();
-  return 0;
+}
+
+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);
+}
+
+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;
+        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("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;
+        }
+      }
+    }
+    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) {} */
+
+  for (int i = 0; i < 50; i++) {
+    chip8_emulate_cycle();
+    if (draw_flag == 1) {
+      chip8_draw(gfx);
+    }
+    usleep(150000);
+  }
+  SDL_DestroyWindow(window);
+  SDL_Quit();
   return 0;
-}
+}<
\ No newline at end of file