commit 2f99d56f62bad2f03e7f443614d609a78d8fd3e9
Author: Christos Margiolis <christos@margiolis.net>
Date: Mon, 18 May 2020 20:21:24 +0300
initial commit
Diffstat:
8 files changed, 533 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,40 @@
+TARGET = chip8
+INSTALL_PATH = /usr/local/bin
+
+SRC_DIR = src
+OBJ_DIR = obj
+BIN_DIR = bin
+
+SRC = $(wildcard $(SRC_DIR)/*.c)
+OBJ = $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
+
+MOVE = mv
+MKDIR_P = mkdir -p
+
+CC = gcc
+CPPFLAGS += -Iinclude
+CFLAGS += -Wall
+LDFLAGS += -Llib
+LDLIBS += -lSDL2
+
+.PHONY: all clean
+
+all: $(TARGET)
+
+$(TARGET): $(OBJ)
+ $(MKDIR_P) $(BIN_DIR)
+ $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
+ $(MOVE) $(TARGET) $(BIN_DIR)
+
+$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
+ $(MKDIR_P) $(OBJ_DIR)
+ $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
+
+run:
+ ./$(BIN_DIR)/$(TARGET)
+
+install: $(TARGET)
+ cp $(BIN_DIR)/$(TARGET) $(INSTALL_PATH)
+
+clean:
+ $(RM) $(OBJ) $(BIN_DIR)/$(TARGET)
diff --git a/README.md b/README.md
@@ -0,0 +1,21 @@
+# CHIP-8 Emulator
+
+Your typical copy of CHIP-8. This one uses SDL2 as a rendering API.
+
+## Dependencies
+
+`cmake`
+`SDL2`
+
+## Usage
+
+```
+$ cd path/to/chip8
+$ make
+$ cd bin
+$ ./chip8 [../path/to/ROM]
+```
+
+## To Do
+
+* Fix flickering
diff --git a/bin/chip8 b/bin/chip8
Binary files differ.
diff --git a/obj/chip8.o b/obj/chip8.o
Binary files differ.
diff --git a/obj/main.o b/obj/main.o
Binary files differ.
diff --git a/src/chip8.c b/src/chip8.c
@@ -0,0 +1,346 @@
+#include "chip8.h"
+
+#define V chip8->V
+#define pc chip8->pc
+#define opcode chip8->opcode
+#define I chip8->I
+#define sp chip8->sp
+#define memory chip8->memory
+#define gfx chip8->gfx
+#define stack chip8->stack
+#define keys chip8->keys
+#define delaytimer chip8->delaytimer
+#define soundtimer chip8->soundtimer
+#define drawflag chip8->drawflag
+
+void
+chip8_init(Chip8 *chip8)
+{
+ uint8_t 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
+ };
+
+ pc = 0x200;
+ opcode = 0;
+ I = 0;
+ sp = 0;
+ delaytimer = 0;
+ soundtimer = 0;
+
+ memset(V, 0, 16 * sizeof(uint8_t));
+ memset(keys, 0, 16 * sizeof(uint8_t));
+ memset(stack, 0, 16 * sizeof(uint16_t));
+ memset(gfx, 0, 2048 * sizeof(uint8_t));
+ memset(memory, 0, 4096 * sizeof(uint8_t));
+ int i;
+ for (i = 0; i < 80; i++)
+ memory[i] = fontset[i];
+}
+
+int
+load(Chip8 *chip8, const char *fpath)
+{
+ FILE *rom = fopen(fpath, "rb");
+ if (rom == NULL)
+ {
+ fprintf(stderr, "Error loading ROM (%s). Exiting. . .\n", fpath);
+ return -1;
+ }
+ fseek(rom, 0, SEEK_END);
+ long romsize = ftell(rom);
+ rewind(rom);
+
+ char *buf = (char *)malloc(romsize * sizeof(char));
+ if (buf == NULL)
+ {
+ fprintf(stderr, "Cannot allocate memory. Exiting. . .\n");
+ return -1;
+ }
+
+ size_t res = fread(buf, sizeof(char), (size_t)romsize, rom);
+ if (res != romsize)
+ {
+ fprintf(stderr, "Error reading ROM. Exiting. . .\n");
+ return -1;
+ }
+
+ int i;
+ if ((4096 - 512) > romsize)
+ for (i = 0; i < romsize; i++)
+ memory[i + 512] = (uint8_t)buf[i];
+ else
+ {
+ fprintf(stderr, "ROM can't fit into memory. Exiting. . .\n");
+ return -1;
+ }
+
+ fclose(rom);
+ free(buf);
+ return 1;
+}
+
+void
+emulate(Chip8 *chip8)
+{
+ fetch(chip8);
+ if (decode(chip8) > 0)
+ {
+ execute(chip8);
+ update_timers(chip8);
+ }
+
+ printf("opcode: %x\tmemory: %x\tI: %x\tsp: %x\tpc: %d\n",
+ opcode, memory[pc] << 8 | memory[pc + 1], I, sp, pc);
+}
+
+void
+fetch(Chip8 *chip8)
+{
+ opcode = memory[pc] << 8 | memory[pc + 1];
+}
+
+int
+decode(Chip8 *chip8)
+{
+ int i;
+ switch (opcode & 0xF000)
+ {
+ case 0x0000: // 00E_
+ switch (opcode & 0x00FF)
+ {
+ case 0xE0: // 00E0 - Clear screen
+ memset(gfx, 0, 2048 * sizeof(uint8_t));
+ drawflag = 1;
+ break;
+ case 0xEE: // 00EE - Return from subroutine
+ pc = stack[--sp];
+ break;
+ default:
+ fprintf(stderr, "Unknown opcode: %x\n", opcode);
+ return -1;
+ }
+ break;
+ case 0x1000: // 1NNN - Jump to address NNN
+ pc = (opcode & 0x0FFF) - 2;
+ break;
+ case 0x2000: // 2NNN - Call subroutine at NNN
+ stack[sp++] = pc;
+ pc = (opcode & 0x0FFF) - 2;
+ case 0x3000: // 3NNN - Skip next instruction if VX == NN
+ if (V[(opcode & 0x0F00) >> 8] == (opcode & 0x00FF)) pc += 2;
+ break;
+ case 0x4000: // 4NNN - Skip next instruction if VX != NN
+ if (V[(opcode & 0x0F00) >> 8] != (opcode & 0x00FF)) pc += 2;
+ break;
+ case 0x5000: // 5XY0 - Skip next instruction if VX == VY
+ if (V[(opcode & 0x0F00) >> 8] == V[(opcode & 0x00F0) >> 4]) pc += 2;
+ break;
+ case 0x6000: // 6XNN - Set VX to NN
+ V[(opcode & 0x0F00) >> 8] = opcode & 0x00FF;
+ break;
+ case 0x7000: // 7XNN - Add NN to VX
+ V[(opcode & 0x0F00) >> 8] += opcode & 0x00FF;
+ break;
+ case 0x8000: // 8XY_
+ switch (opcode & 0x000F)
+ {
+ case 0x0000: // 8XY0 - Set VX to VY
+ V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4];
+ break;
+ case 0x0001: // 8XY1 - Set VX to (VX OR VY)
+ V[(opcode & 0x0F00) >> 8] |= V[(opcode & 0x00F0) >> 4];
+ break;
+ case 0x0002: // 8XY2 - Set VX to (VX AND VY)
+ V[(opcode & 0x0F00) >> 8] &= V[(opcode & 0x00F0) >> 4];
+ break;
+ case 0x0003: // 8XY3 - Set VX to (VX XOR VY)
+ V[(opcode & 0x0F00) >> 8] ^= V[(opcode & 0x00F0) >> 4];
+ break;
+ case 0x0004: // 8XY4 - Add VY to VX, VF = 1 if there is a carry
+ V[(opcode & 0x0F00) >> 8] += V[(opcode & 0x00F0) >> 4];
+ V[0xF] = (V[(opcode & 0x00F0) >> 4] > (0xFF - V[(opcode & 0x0F00) >> 8])) ? 1 : 0;
+ break;
+ case 0x0005: // 8XY5 - Sub VY from VX, VF = 0 if there is a borrow
+ V[0xF] = (V[(opcode & 0x00F0) >> 4] > V[(opcode & 0x0F00) >> 8]) ? 0 : 1;
+ V[(opcode & 0x0F00) >> 8] -= V[(opcode & 0x00F0) >> 4];
+ break;
+ case 0x0006: // 8XY6 - Shift VX right by 1. VF = LSB of VX before shift
+ V[0xF] = V[(opcode & 0x0F00) >> 8] & 0x1;
+ V[(opcode & 0x0F00) >> 8] >>= 1;
+ break;
+ case 0x0007: // 8XY7 - Set VX to VY-VX. VF = 0 if there is a borrow
+ V[0xF] = (V[(opcode & 0x0F00) >> 8] > V[(opcode & 0x00F0) >> 4]) ? 0 : 1;
+ V[(opcode & 0x0F00) >> 8] = V[(opcode & 0x00F0) >> 4] - V[(opcode & 0x0F00) >> 8];
+ break;
+ case 0x000E: // 8XYE - Shift VX left by 1. VF = MSB of VX before shift
+ V[0xF] = V[(opcode & 0x0F00) >> 8] >> 7;
+ V[(opcode & 0x0F00) >> 8] <<= 1;
+ break;
+ default:
+ fprintf(stderr, "Unknown opcode: %x\n", opcode);
+ return -1;
+ }
+ break;
+ case 0x9000: // 9XY0 - Skip next instruction if VX != VY
+ if (V[(opcode & 0x0F00) >> 8] != V[(opcode & 0x00F0) >> 4]) pc += 2;
+ break;
+ case 0xA000: // ANNN - Set I to the address NNN
+ I = opcode & 0x0FFF;
+ break;
+ case 0xB000: // BNNN - Jump to NNN + V0
+ pc = ((opcode & 0x0FFF) + V[0]) - 2;
+ break;
+ case 0xC000: // CNNN - Set VX to random number masked by NN
+ V[(opcode & 0x0F00) >> 8] = (rand() % (0xFF + 1)) & (opcode & 0x00FF);
+ break;
+ case 0xD000: // Draw an 8 pixel sprite at (VX, VY)
+ {
+ uint8_t VX = V[(opcode & 0x0F00) >> 8];
+ uint8_t VY = V[(opcode & 0x00F0) >> 4];
+ uint16_t h = opcode & 0x000F;
+ uint16_t pixel;
+
+ V[0xF] = 0;
+ int yl, xl;
+ for (yl = 0; yl < h; yl++)
+ {
+ printf("Decoding: %x\n", opcode);
+ pixel = memory[I + yl];
+ for (xl = 0; xl < 8; xl++)
+ {
+ printf("Decoding: %x\n", opcode);
+ printf("VX: %x\n", VX);
+ printf("VY: %x\n", VY);
+ printf("Pixel: %d\n", pixel);
+ printf("yline: %d\txline: %d\n", yl, xl);
+
+ if ((pixel & (0x80 >> xl)) != 0)
+ {
+ if (gfx[VX + xl + ((VY + yl) * 64)] == 1)
+ V[0xF] = 1;
+ gfx[VX + xl + ((VY + yl) * 64)] ^= 1;
+ printf("gfx: %d\n", gfx[VX + xl + ((VY + yl) * 64)]);
+ }
+ }
+ }
+
+ drawflag = 1;
+ }
+ break;
+ case 0xE000: // EX__
+ switch (opcode & 0x00FF)
+ {
+ case 0x009E: // EX9E - Skip next instruction if key in VX is pressed
+ if (keys[V[(opcode & 0x0F00) >> 8]] != 0) pc += 2;
+ break;
+ case 0x00A1: // EXA1 - Skip next instruction if key in VX isn't pressed
+ if (keys[V[(opcode & 0x0F00) >> 8]] == 0) pc += 2;
+ break;
+ default:
+ fprintf(stderr, "Unknown opcode: %x\n", opcode);
+ return -1;
+ }
+ break;
+ case 0xF000: // FX__
+ switch (opcode & 0x00FF)
+ {
+ case 0x0007: // FX07 - Set VX to delaytimer
+ V[(opcode & 0x0F00) >> 8] = delaytimer;
+ break;
+ case 0x000A: // FX0A - Wait for key press and then store it in VX
+ {
+ int keypressed = 0;
+ for (i = 0; i < 16; i++)
+ {
+ if (keys[i] != 0)
+ {
+ V[(opcode & 0x0F00) >> 8] = i;
+ keypressed = 1;
+ }
+ }
+
+ if (!keypressed) return -1;
+ }
+ break;
+ case 0x0015: // FX15 - Set the delaytimer to VX
+ delaytimer = V[(opcode & 0x0F00) >> 8];
+ break;
+ case 0x0018: // FX18 - Set the soundtimer to VX
+ soundtimer = V[(opcode & 0x0F00) >> 8];
+ break;
+ case 0x001E: // FX1E - Add VX to I
+ V[0xF] = ((I + V[(opcode & 0x0F00) >> 8]) > 0xFFF) ? 1 : 0;
+ I += V[(opcode & 0x0F00) >> 8];
+ break;
+ case 0x0029: // FX29 - Set I to the location of the sprite for char VX
+ I = V[(opcode & 0x0F00) >> 8] * 0x5;
+ break;
+ case 0x0033: // FX33 - Store bin coded decimal of VX at I, I+1 and I+2
+ memory[I] = V[(opcode & 0x0F00) >> 8] / 100;
+ memory[I+1] = (V[(opcode & 0x0F00) >> 8] / 10) % 10;
+ memory[I+2] = V[(opcode & 0x0F00) >> 8] % 10;
+ break;
+ case 0x0055: // FX55 - Store V0 to VX in memory starting at I
+ for (i = 0; i <= ((opcode & 0x0F00) >> 8); i++)
+ memory[I + i] = V[i];
+ I += ((opcode & 0x0F00) >> 8) + 1;
+ break;
+ case 0x0065: // FX65 - Fill V0 to VX with vals from memory starting at I
+ for (i = 0; i <= ((opcode & 0x0F00) >> 8); i++)
+ V[i] = memory[I + i];
+ I += ((opcode & 0x0F00) >> 8) + 1;
+ break;
+ default:
+ fprintf(stderr, "Unknown opcode: %x\n", opcode);
+ return -1;
+ }
+ break;
+ default:
+ fprintf(stderr, "Unimplemented opcode\n");
+ return -1;
+ }
+
+ return 1;
+}
+
+void
+execute(Chip8 *chip8)
+{
+ pc += 2;
+}
+
+void
+update_timers(Chip8 *chip8)
+{
+ if (delaytimer > 0) --delaytimer;
+ if (soundtimer > 0) --soundtimer;
+}
+
+#undef V
+#undef pc
+#undef opcode
+#undef I
+#undef sp
+#undef memory
+#undef gfx
+#undef stack
+#undef keys
+#undef delaytimer
+#undef soundtimer
+#undef drawflag
diff --git a/src/chip8.h b/src/chip8.h
@@ -0,0 +1,37 @@
+#ifndef CHIP8_H
+#define CHIP8_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+typedef struct {
+ uint8_t memory[4096];
+ uint16_t stack[16];
+ uint16_t sp;
+
+ uint8_t V[16];
+ uint16_t opcode;
+ uint16_t I;
+ uint16_t pc;
+
+ uint8_t delaytimer;
+ uint8_t soundtimer;
+ uint8_t gfx[64 * 32];
+ uint8_t keys[16];
+ int drawflag;
+} Chip8;
+
+extern Chip8 chip8;
+
+void chip8_init(Chip8 *chip8);
+int load(Chip8 *chip8, const char *fpath);
+void emulate(Chip8 *chip8);
+void fetch(Chip8 *chip8);
+int decode(Chip8 *chip8);
+void execute(Chip8 *chip8);
+void update_timers(Chip8 *chip8);
+
+#endif /* CHIP8_H */
diff --git a/src/main.c b/src/main.c
@@ -0,0 +1,89 @@
+#include "chip8.h"
+#include <SDL2/SDL.h>
+#include <unistd.h>
+
+static uint8_t keymap[16] = {
+ SDLK_1, SDLK_2,
+ SDLK_3, SDLK_4,
+ SDLK_q, SDLK_w,
+ SDLK_e, SDLK_r,
+ SDLK_a, SDLK_s,
+ SDLK_d, SDLK_f,
+ SDLK_z, SDLK_x,
+ SDLK_c, SDLK_v,
+};
+
+int
+main(int argc, char **argv)
+{
+ SDL_Window *win = NULL;
+ int w = 1024, h = 512;
+ uint32_t pixels[2048];
+ srand(time(NULL));
+
+ if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
+ {
+ fprintf(stderr, "Cannot initialize SDL. Exiting. . .\n");
+ return -1;
+ }
+
+ win = SDL_CreateWindow("CHIP-8 Emulator", SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED, w, h,
+ SDL_WINDOW_SHOWN);
+ if (win == NULL)
+ {
+ fprintf(stderr, "Cannot create SDL window. Exiting. . .\n%s\n", SDL_GetError());
+ return -1;
+ }
+
+ SDL_Renderer *renderer = SDL_CreateRenderer(win, -1, 0);
+ SDL_RenderSetLogicalSize(renderer, w, h);
+ SDL_Texture *texture = SDL_CreateTexture(
+ renderer, SDL_PIXELFORMAT_ARGB8888,
+ SDL_TEXTUREACCESS_STREAMING, 64, 32);
+
+ Chip8 chip8;
+ chip8_init(&chip8);
+ if (load(&chip8, argv[1]) == -1) return -1;
+
+ for (;;)
+ {
+ SDL_Event e;
+ emulate(&chip8);
+
+ int i;
+ while (SDL_PollEvent(&e))
+ {
+ if (e.type == SDL_QUIT) return -1;
+ if (e.type == SDL_KEYDOWN)
+ {
+ if (e.key.keysym.sym == SDLK_ESCAPE) return -1;
+ for (i = 0; i < 16; i++)
+ if (e.key.keysym.sym == keymap[i])
+ chip8.keys[i] = 1;
+ }
+
+ if (e.type == SDL_KEYUP)
+ for (i = 0; i < 16; i++)
+ if (e.key.keysym.sym == keymap[i])
+ chip8.keys[i] = 0;
+ }
+
+ if (chip8.drawflag)
+ {
+ chip8.drawflag = 0;
+ for (i = 0; i < 2048; i++)
+ {
+ uint8_t pixel = chip8.gfx[i];
+ pixels[i] = (0x00FFFFFF * pixel) | 0xFF000000;
+ }
+ SDL_UpdateTexture(texture, NULL, pixels, 64 * sizeof(Uint32));
+ SDL_RenderClear(renderer);
+ SDL_RenderCopy(renderer, texture, NULL, NULL);
+ SDL_RenderPresent(renderer);
+ }
+ usleep(1500);
+ }
+
+ return 0;
+}