Skip to content

Instantly share code, notes, and snippets.

@OwenChia
Last active June 16, 2021 16:43
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OwenChia/2d44a2827bd050dc18b7695e710c0c0d to your computer and use it in GitHub Desktop.
Save OwenChia/2d44a2827bd050dc18b7695e710c0c0d to your computer and use it in GitHub Desktop.
Chip 8 Emu - C & WASM version - https://owenchia.coding.me/c8emu/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <emscripten.h>
#define SCREEN_W 64
#define SCREEN_H 32
#define ROMSINK 0x200
#define N(opcode) (opcode & 0x000F)
#define NN(opcode) (opcode & 0x00FF)
#define NNN(opcode) (opcode & 0x0FFF)
#define VX(opcode) ((opcode & 0x0F00) >> 8)
#define VY(opcode) ((opcode & 0x00F0) >> 4)
typedef void (*instruction) (uint16_t);
typedef struct CPU_t {
uint8_t memory[0x1000];
uint8_t registers[0x10];
uint16_t pc;
uint16_t sp;
uint16_t i;
instruction ops[0x10];
} CPU;
/*
* keyboard layout
* +---+---+---+---+ +---+---+---+---+
* | 1 | 2 | 3 | C | | 1 | 2 | 3 | 4 |
* +---+---+---+---+ +---+---+---+---+
* | 4 | 5 | 6 | D | | Q | W | E | R |
* +---+---+---+---+ ==> +---+---+---+---+
* | 7 | 8 | 9 | E | | A | S | D | F |
* +---+---+---+---+ +---+---+---+---+
* | A | 0 | B | F | | Z | X | C | V |
* +---+---+---+---+ +---+---+---+---+
* map to [ 1, 2, 3, C,
* 4, 5, 6, D,
* 7, 8, 9, E,
* A, 0, B, F ]
*/
CPU* cpu;
uint8_t timer_delay;
uint8_t timer_sound;
uint8_t keyboard[0x10] = {0};
uint16_t opcode;
uint8_t vbuf[32][64];
void op_0x0___(uint16_t operand)
{ uint16_t addr;
switch (NN(operand)) {
case 0xE0: // CLS
for (int i = 0; i < 32; i ++)
for (int j = 0; j < 64; j ++)
vbuf[i][j] = 0;
break;
case 0xEE: // RET
addr = cpu->memory[cpu->sp++] << 8;
addr |= cpu->memory[cpu->sp++];
cpu->pc = addr;
break;
}
}
// JMP NNN
void op_0x1NNN(uint16_t operand)
{
cpu->pc = NNN(operand);
}
// CALL NNN
void op_0x2NNN(uint16_t operand)
{
cpu->sp -= 2;
cpu->memory[cpu->sp] = (cpu->pc & 0xFF00) >> 8;
cpu->memory[cpu->sp + 1] = (cpu->pc & 0x00FF);
cpu->pc = NNN(operand);
}
// SEQ Vx NN
void op_0x3XNN(uint16_t operand)
{
if (cpu->registers[VX(operand)] == NN(operand)) {
cpu->pc += 2;
}
}
// SNE Vx NN
void op_0x4XNN(uint16_t operand)
{
if (cpu->registers[VX(operand)] != NN(operand)) {
cpu->pc += 2;
}
}
// SEQ Vx Vy
void op_0x5XY0(uint16_t operand)
{
if (cpu->registers[VX(operand)] == cpu->registers[VY(operand)]) {
cpu->pc += 2;
}
}
// LD Vx NN
void op_0x6XNN(uint16_t operand)
{
cpu->registers[VX(operand)] = NN(operand);
}
// ADD Vx NN
void op_0x7XNN(uint16_t operand)
{
cpu->registers[VX(operand)] += NN(operand);
}
void op_0x8XY_(uint16_t operand)
{
uint8_t* vx = &cpu->registers[VX(operand)];
uint8_t* vy = &cpu->registers[VY(operand)];
uint16_t temp;
switch (N(operand)) {
case 0x0: // LD Vx Vy
*vx = *vy;
break;
case 0x1: // AND Vx Vy
*vx |= *vy;
break;
case 0x2: // OR Vx Vy
*vx &= *vy;
break;
case 0x3: // XOR Xv Xy
*vx ^= *vy;
break;
case 0x4: // ADD Vx Vy
temp = *vx + *vy;
cpu->registers[0xF] = temp > 0xFF;
*vx = temp & 0xFF;
break;
case 0x5: // SUB Vx Vy
cpu->registers[0xF] = *vx > *vy;
*vx -= *vy;
break;
case 0x6: // SHR Vx Vy
cpu->registers[0xF] = *vx & 0x01;
*vx >>= 1;
break;
case 0x7: // SUBN Vx Vy
cpu->registers[0xF] = *vy > *vx;
*vx = *vy - *vx;
break;
case 0xE: // SHL Vx Vy
cpu->registers[0xF] = (*vx & 0x80) >> 7; // 0x80 = 0b10000000
*vx <<= 1;
break;
}
}
// SNE Vx Vy
void op_0x9XY0(uint16_t operand)
{
if (cpu->registers[VX(operand)] != cpu->registers[VY(operand)]) {
cpu->pc += 2;
}
}
// LD I NNN
void op_0xANNN(uint16_t operand)
{
cpu->i = NNN(operand);
}
// JMP V0 NNN
void op_0xBNNN(uint16_t operand)
{
cpu->pc = cpu->registers[0] + NNN(operand);
}
// RND Vx kk
void op_0xCXNN(uint16_t operand)
{
srand(time(NULL));
cpu->registers[VX(operand)] = (rand() % 256) & NN(operand);
}
// DRAW Vx Vy N
void op_0xDXYN(uint16_t operand)
{
uint8_t src, dst;
uint16_t x_, y_;
cpu->registers[0xF] = 0;
for (int y = 0; y < N(operand); y ++) {
y_ = (cpu->registers[VY(operand)] + y) % 32;
for (int x = 0; x < 8; x ++) {
x_ = (cpu->registers[VX(operand)] + x) % 64;
src = (cpu->memory[cpu->i + y] >> ( 7 - x )) & 1;
dst = vbuf[y_][x_];
if (src & dst) cpu->registers[0xF] = 1;
vbuf[y_][x_] ^= src;
}
}
}
void op_0xEX__(uint16_t operand)
{
switch (NN(operand)) {
case 0x9E: // SKP Vx
if (keyboard[cpu->registers[VX(operand)]]) {
cpu->pc += 2;
}
break;
case 0xA1: // SKNP Vx
if (!keyboard[cpu->registers[VX(operand)]]) {
cpu->pc += 2;
}
break;
}
}
void op_0xFX__(uint16_t operand)
{
uint8_t* vx = &cpu->registers[VX(operand)];
switch(NN(operand)) {
case 0x07: // LD Vx DT
*vx = timer_delay;
break;
case 0x0A: // LD K Vx
cpu->pc -= 2;
for (int i = 0; i < 0x10; i ++) {
if (keyboard[i] == 1) {
*vx = i;
cpu->pc += 2;
break;
}
}
break;
case 0x15: // LD DT Vx
timer_delay = *vx;
break;
case 0x18: // LD ST Vx
timer_sound = *vx;
break;
case 0x1E: // ADD I Vx
cpu->i += *vx;
break;
case 0x29: // LD F Vx
cpu->i = *vx * 5;
break;
case 0x33: // LD B Vx
cpu->memory[cpu->i] = *vx / 100;
cpu->memory[cpu->i + 1] = (*vx / 10) % 10;
cpu->memory[cpu->i + 2] = *vx % 10;
break;
case 0x55: // LD [I] Vx
for (int i = 0; i <= VX(operand); i ++) {
cpu->memory[cpu->i + i] = cpu->registers[i];
}
break;
case 0x65: // LD Vx [I]
for (int i = 0; i <= VX(operand); i ++) {
cpu->registers[i] = cpu->memory[cpu->i + i];
}
break;
}
}
CPU* cpu = &(CPU) {
.memory = {
// font data
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
},
.registers = {0},
.pc = 0x200,
.sp = 0xEFF,
.i = 0,
.ops = {
op_0x0___, op_0x1NNN, op_0x2NNN, op_0x3XNN,
op_0x4XNN, op_0x5XY0, op_0x6XNN, op_0x7XNN,
op_0x8XY_, op_0x9XY0, op_0xANNN, op_0xBNNN,
op_0xCXNN, op_0xDXYN, op_0xEX__, op_0xFX__,
},
};
void update_timer() {
if (timer_delay > 0) timer_delay --;
if (timer_sound > 0) {
timer_sound --;
if (timer_sound == 0) printf("beep!\n");
}
}
EMSCRIPTEN_KEEPALIVE
void cpu_step() {
opcode = cpu->memory[cpu->pc ++] << 8;
opcode |= cpu->memory[cpu->pc ++];
cpu->ops[(opcode & 0xF000) >> 12](opcode);
update_timer();
}
EMSCRIPTEN_KEEPALIVE
uint8_t* get_display() {
return &vbuf[0][0];
}
EMSCRIPTEN_KEEPALIVE
uint8_t* get_memory() {
return cpu->memory;
}
EMSCRIPTEN_KEEPALIVE
uint8_t* get_romsink() {
return &cpu->memory[ROMSINK];
}
EMSCRIPTEN_KEEPALIVE
uint8_t* get_registers() {
return cpu->registers;
}
EMSCRIPTEN_KEEPALIVE
void key_down(uint8_t idx) {
keyboard[idx] = 1;
}
EMSCRIPTEN_KEEPALIVE
void key_up(uint8_t idx) {
keyboard[idx] = 0;
}
/* vim: set fdm=syntax : */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment