Last active
June 16, 2021 16:43
-
-
Save OwenChia/2d44a2827bd050dc18b7695e710c0c0d to your computer and use it in GitHub Desktop.
Chip 8 Emu - C & WASM version - https://owenchia.coding.me/c8emu/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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