/*** 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>
#include <sys/types.h>
/*** data ***/
// opcode store
// All opcodes are 2 bytes and so are unsigned short
unsigned short opcode;
// 4KB of RAM
unsigned char memory[4096];
// CPU Register
// 16 general purpose 8 bit registers
unsigned char V[16];
// Index or Address Register
unsigned short I;
// 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;
// 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 *
// 144 0x90 1001 0000 * * 32 0x20 0010 0000 *
// 144 0x90 1001 0000 * * 64 0x40 0100 0000 *
// 240 0xF0 1111 0000 **** 64 0x40 0100 0000 *
unsigned char chip8_fontset[80] = {
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
0x20, 0x60, 0x20, 0x20, 0x70, // 1
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
0xF0, 0x80, 0xF0, 0x80, 0x80 // F
};
// Keypad
unsigned char keypad[16];
// Screen dimension constants
const int SCREEN_WIDTH = 64 * 20;
const int SCREEN_HEIGHT = 32 * 20;
/*** 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));
}
/*** 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);
}
/*** 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;
}
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 * 20;
rect.y = y * 20;
// size of each "pixel" keep it consistent with the scaling of the display in SCREEN_WIDTH/HEIGHT
rect.w = 20;
rect.h = 20;
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:
switch (opcode & 0x00FF) {
case 0x00E0:
for (int i = 0; i < 64 * 32; i++) {
gfx[i] = 0;
}
printf("00E0: Display Cleared\n");
pc += 2;
break;
case 0x00EE:
pc = stack[stack_pointer];
stack_pointer--;
printf("00EE: Retrurn from a subroutine\n");
pc += 2;
break;
default:
break;
}
break;
// 1NNN: Jumps to address NNN
case 0x1000:
printf("1nnn: Jump to location nnn \n");
pc = 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;
}
pc += 2;
printf("3XNN: Skip next instruction if V[x] == kk \n");
break;
// 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;
// 5xy0 - SE Vx, Vy Skip next instruction if Vx = Vy.
case 0x5000:
if (V[x] == V[y]) {
pc += 2;
}
pc += 2;
printf("5xy0: 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;
case 0x9000:
if (V[x] != V[y]) {
pc += 2;
}
pc += 2;
break;
// Instructions starting with 8
case 0x8000:
switch (opcode & 0x000F) {
// 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;
// 8xyE: Stores the most significant bit of V[x] and shifts V[x]
case 0x000E:
V[0xF] = (V[x] >> 7) & 0x1;
V[x] = (V[x] << 1);
pc += 2;
break;
default:
printf("Unknown opcode 0x%X.\n", opcode);
break;
}
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 < 8; 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;
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;
case 0xE000:
switch (opcode & 0x00FF) {
// Ex9e: Skips next instructuion if key stored in V[x]is pressed
case 0x009E:
if (keypad[V[x]]) {
pc += 2;
}
pc += 2;
break;
// ExA1: Skips next instruction if key stroed in V[x] is not pressed
case 0x00A1:
if (!keypad[V[x]]) {
pc += 2;
}
pc += 2;
break;
default:
printf("Uknown opcode: 0x%X\n", opcode);
}
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;
default:
printf("Unknown opcode 0x%X.\n", opcode);
break;
}
// Update timers
if (delay_timer > 0)
--delay_timer;
if (sound_timer > 0) {
if (sound_timer == 1)
printf("BEEP!\n");
--sound_timer;
}
}
SDL_Event event;
SDL_Scancode keymappings[16] = {
SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_4,
SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_R,
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_D, SDL_SCANCODE_F,
SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_C, SDL_SCANCODE_V
};
int usleep(unsigned int usec);
int main(int argc, char* argv[])
{
(void)argc;
const Uint8* state = SDL_GetKeyboardState(NULL);
chip8_initialize();
chip8_load_program(argv[1]);
chip8_init_display();
while (1) {
chip8_emulate_cycle();
if (draw_flag == 1) {
chip8_draw(gfx);
// Delay to slow down the clock
usleep(45000);
}
while (SDL_PollEvent(&event) != 0) {
switch (event.type) {
case SDL_QUIT:
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
break;
default:
if (state[SDL_SCANCODE_ESCAPE]) {
return 0;
}
// update keyboard state
for (int keycode = 0; keycode < 16; keycode++) {
keypad[keycode] = state[keymappings[keycode]];
}
}
}
}
}