chip-8
chip-8 emulatorfeat: more opcode coverage
| 1 files changed, 414 insertions(+), 291 deletions(-) | |||
|---|---|---|---|
| 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