You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

572 lines
16 KiB

#include "chip.h"
#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
/*
*
* https://tobiasvl.github.io/blog/write-a-chip-8-emulator/
*
* https://github.com/Chromatophore/HP48-Superchip/blob/master/binaries/SCHIP_origin.txt
*
*/
uint8_t inbuilt_font[] = {
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
};
int chip_errored = 0;
uint16_t get_random() { return rand(); }
uint8_t DECODE_ARG0(uint16_t instr) { return ((0x0f00 & instr) >> 8); }
uint8_t DECODE_ARG1(uint16_t instr) { return ((0x00f0 & instr) >> 4); }
uint8_t DECODE_ARG2(uint16_t instr) { return ((0x000f & instr)); }
uint16_t DECODE_ARG16(uint16_t instr) { return (0x0fff & instr); }
uint8_t DECODE_ARG8(uint16_t instr) { return (0x00ff & instr); }
void exit_interpreter() {
info("got exit instruction.");
exit(0);
}
void print_display(struct display *display);
void print_chip_state(emu_state *state, uint16_t instr) {
printf("program counter: %x instr %x\n", state->value->program_counter,
instr);
printf("pointer: %x\n", state->value->pointer_register);
printf("registers: ");
hexdump(state->value->registers, 16);
printf("sound timer: %d, delay_timer: %d\n", state->value->sound_timer,
state->value->delay_timer);
printf("extended mode: %d\n", state->value->extended_display_mode);
}
void clear_display(emu_state *state) {
memset(state->display.buffer, 0, sizeof(state->display_buffer));
}
void scroll_vertical(emu_state *state, int8_t lines) {
int row = DISPLAY_HEIGHT_BYTES * (state->value->extended_display_mode);
int offset = row * (0x7f & lines);
if (lines > 0) {
bcopy(state->display.buffer, state->display.buffer + offset,
sizeof(state->display) - offset);
memset(state->display.buffer, 0, offset);
} else {
bcopy(state->display.buffer + offset, state->display.buffer,
sizeof(state->display) - offset);
memset(state->display.buffer, 0, offset);
}
}
void scroll_horizontal(emu_state *state, int8_t direction) {
int left = direction & 80;
int width = DISPLAY_HEIGHT_BYTES * state->value->extended_display_mode;
for (int i = 0; i < DISPLAY_HEIGHT_BITS; i++) {
uint8_t last_saved = 0;
for (int j = 0; j < width; j++) {
uint8_t cell = state->display.buffer[i * width + j];
uint8_t saved = left ? cell & 0xff00 : cell & 0x00ff;
cell = left ? cell << 4 : cell >> 4;
state->display.buffer[i * width + j] = last_saved | cell;
last_saved = left ? saved << 4 : saved >> 4;
}
}
}
void draw_16_sprite(emu_state *state, uint8_t x, uint8_t y) {
uint16_t sprite_loc = state->value->pointer_register;
int row_bytes = DISPLAY_WIDTH_BYTES;
int screen_y = row_bytes * y;
if (x % 8 != 0) {
warn("Not supported.");
}
uint8_t old[4];
uint8_t new[4];
memcpy(old, &state->display.buffer[screen_y + (x / 8)], 4);
state->display.buffer[screen_y + (x / 8)] ^=
state->memory.all_memory[sprite_loc];
state->display.buffer[screen_y + (x / 8) + 1] ^=
state->memory.all_memory[sprite_loc + 1];
screen_y = row_bytes * (y + 1);
state->display.buffer[screen_y + (x / 8)] ^=
state->memory.all_memory[sprite_loc + 2];
state->display.buffer[screen_y + (x / 8) + 1] ^=
state->memory.all_memory[sprite_loc + 3];
memcpy(new, &state->display.buffer[screen_y + (x / 8)], 4);
for (int i = 0; i < 4; i++) {
if (old[i] ^ new[i]) {
state->value->registers[0xf] = 1;
}
}
// printf("%d\n", sprite_loc);
hexdump(&state->memory.all_memory[sprite_loc], 4);
// hexdump(state->display, sizeof(state->display));
}
void decode_draw(emu_state *state, uint16_t instr) {
// DXYN* Show N-byte sprite from M(I) at coords (VX,VY), VF :=
// collision. If N=0 and extended mode, show 16x16 sprite.
uint8_t X = state->value->registers[DECODE_ARG0(instr)];
uint8_t Y = state->value->registers[DECODE_ARG1(instr)];
uint8_t N = DECODE_ARG2(instr);
if (N == 0 && (state->value->extended_display_mode == 2)) {
draw_16_sprite(state, X, Y);
return;
}
uint16_t sprite_loc = state->value->pointer_register;
int row_bytes = DISPLAY_WIDTH_BYTES;
X = X % state->display.width;
Y = Y % state->display.height;
int xoffset = X % 8;
if (N == 0) {
N = 8;
}
uint8_t changed = 0;
for (int i = 0; i < N; i++) {
uint8_t ssprite = state->memory.all_memory[sprite_loc + i];
uint8_t new_l =
state->display.buffer[row_bytes * Y + (X / 8)] ^ (ssprite >> xoffset);
changed |= state->display.buffer[row_bytes * Y + (X / 8)] & new_l;
uint8_t new_r = state->display.buffer[row_bytes * Y + (X / 8) + 1] ^
(ssprite << (8 - xoffset));
changed |= state->display.buffer[row_bytes * Y + (X / 8) + 1] & new_r;
state->display.buffer[row_bytes * Y + (X / 8)] = new_l;
state->display.buffer[row_bytes * Y + (X / 8) + 1] = new_r;
Y += 1;
}
if (changed) {
state->value->registers[0xf] = 1;
} else {
state->value->registers[0xf] = 0;
}
print_display(&state->display);
}
void decode_special(emu_state *state, uint16_t instr) {
if (DECODE_ARG0(instr) == 0xc) {
// 00CN* Scroll display N lines down
uint8_t lines = DECODE_ARG2(instr);
scroll_vertical(state, lines);
return;
}
switch (instr) {
case 0x00e0:
// 00E0 Clear display
clear_display(state);
break;
case 0x00ee:
// 00EE Return from subroutine
state->value->stack_top--;
state->value->program_counter =
state->value->stack[state->value->stack_top];
break;
case 0x00fb:
// 00FB* Scroll display 4 pixels right
scroll_horizontal(state, 1);
break;
case 0x00fc:
// 00FC* Scroll display 4 pixels left
scroll_horizontal(state, -1);
break;
case 0x00fd:
// 00FD* Exit CHIP interpreter
exit_interpreter();
break;
case 0x00fe:
// 00FE* Disable extended screen mode
state->value->extended_display_mode = 1;
break;
case 0x00ff:
// 00FF* Enable extended screen mode for full-screen graphics
state->value->extended_display_mode = 2;
break;
default:
printf("%x", instr);
error("Illegal instruction.");
break;
}
}
void decode_arith(emu_state *state, uint16_t instr) {
assert((0xf000 & instr) == 0x8000); // invalid decode
uint16_t temp = 0;
uint8_t X = DECODE_ARG0(instr);
uint8_t Y = DECODE_ARG1(instr);
switch (instr & 0xf) {
case 0:
// 8XY0 VX := VY, VF may change
state->value->registers[X] = state->value->registers[Y];
break;
case 1:
// 8XY1 VX := VX or VY, VF may change
state->value->registers[X] =
state->value->registers[X] | state->value->registers[Y];
break;
case 2:
// 8XY2 VX := VX and VY, VF may change
state->value->registers[X] =
state->value->registers[X] & state->value->registers[Y];
break;
case 3:
// 8XY3 VX := VX xor VY, VF may change
state->value->registers[X] =
state->value->registers[X] ^ state->value->registers[Y];
break;
case 4:
// 8XY4 VX := VX + VY, VF := carry
temp = state->value->registers[X] + state->value->registers[Y];
if (temp > 255)
state->value->registers[0xf] = 1;
state->value->registers[X] = temp;
break;
case 5:
// 8XY5 VX := VX - VY, VF := not borrow
temp = state->value->registers[X] - state->value->registers[Y];
state->value->registers[X] = temp;
if (X > Y) {
state->value->registers[0xf] = 1;
} else {
state->value->registers[0xf] = 0;
}
break;
case 7:
// 8XY7 VX := VY - VX, VF := not borrow
temp = state->value->registers[Y] - state->value->registers[X];
state->value->registers[Y] = temp;
if (Y > X) {
state->value->registers[0xf] = 1;
} else {
state->value->registers[0xf] = 0;
}
break;
case 6:
// 8XY6 VX := VX shr 1, VF := carry
// ambiguous should allow set VX to value of VY
state->value->registers[0xf] = (0x80 & state->value->registers[X]) >> 7;
state->value->registers[X] = state->value->registers[X] >> 1;
break;
case 0xE:
// 8XYE VX := VX shl 1, VF := carry
// ambiguous should allow set VX to value of VY
state->value->registers[0xf] = (0x1 & state->value->registers[X]);
state->value->registers[X] = state->value->registers[X] << 1;
break;
default:
error("Illegal instruction.");
break;
}
}
void decode_store(emu_state *state, uint16_t instr) {
assert((0xf000 & instr) == 0xf000); // invalid decode
uint8_t X = DECODE_ARG0(instr);
assert(X < 16);
state->value->registers[1] = 200;
uint16_t temp = 0;
switch (instr & 0x00ff) {
case 0x07:
// FX07 VX := delay_timer
state->value->registers[X] = state->value->delay_timer;
break;
case 0x0A:
// FX0A wait for keypress, store hex value of key in VX
warn("Not implemented");
break;
case 0x15:
// FX15 delay_timer := VX
state->value->delay_timer = state->value->registers[X];
break;
case 0x18:
// FX18 sound_timer := VX
state->value->sound_timer = state->value->registers[X];
break;
case 0x1e:
// FX1E I := I + VX
temp = state->value->pointer_register + state->value->registers[X];
if (temp > 255) {
state->value->registers[0xf] = 1;
} else {
state->value->registers[0xf] = 0;
}
state->value->pointer_register = temp;
break;
case 0x29:
// FX29 Point I to 5-byte font sprite for hex character VX
state->value->pointer_register =
offsetof(struct emu_value, font) + 5 * state->value->registers[X];
break;
case 0x30:
// FX30* Point I to 10-byte font sprite for digit VX (0..9)
warn("Not implemented.");
break;
case 0x33:
// FX33 Store BCD representation of VX in M(I)..M(I+2)
warn("Not implemented.");
break;
case 0x55:
// FX55 Store V0..VX in memory starting at M(I)
warn("Not implemented.");
break;
case 0x65:
// FX65 Read V0..VX from memory starting at M(I)
for (int i = 0; i < X; i++) {
state->value->registers[i] =
state->memory.all_memory[state->value->pointer_register + i];
}
// warn("Not implemented.");
break;
case 0x75:
// FX75* Store V0..VX in RPL user flags (X <= 7)
warn("Not implemented.");
break;
case 0x85:
// FX85* Read V0..VX from RPL user flags (X <= 7)
warn("Not implemented.");
break;
default:
error("Illegal instruction");
break;
}
}
void decode_instr(emu_state *state, uint16_t instr) {
uint16_t temp = 0;
switch ((instr & 0xf000) >> 0xc) {
case 0:
decode_special(state, instr);
break;
case 1:
// 1NNN Jump to NNN
state->value->program_counter = DECODE_ARG16(instr);
break;
case 2:
// 2NNN Call subroutine at NNN
state->value->stack[state->value->stack_top++] =
state->value->program_counter;
// TODO: stack protection
assert(state->value->stack_top <
(sizeof(state->value->stack) / sizeof(uint16_t)));
state->value->program_counter = DECODE_ARG16(instr);
break;
case 3:
// 3XKK Skip next instruction if VX == KK
if (DECODE_ARG8(instr) == state->value->registers[DECODE_ARG0(instr)]) {
state->value->program_counter += 2;
}
break;
case 4:
// 4XKK Skip next instruction if VX <> KK
if (DECODE_ARG8(instr) != state->value->registers[DECODE_ARG0(instr)]) {
state->value->program_counter += 2;
}
break;
case 5:
// 5XY0 Skip next instruction if VX == VY
if (DECODE_ARG0(instr) == DECODE_ARG1(instr)) {
state->value->program_counter += 2;
}
break;
case 9:
// 9XY0 Skip next instruction if VX <> VY
if (DECODE_ARG0(instr) != DECODE_ARG1(instr)) {
state->value->program_counter += 2;
}
break;
case 6:
// 6XKK VX := KK
state->value->registers[DECODE_ARG0(instr)] = DECODE_ARG8(instr);
break;
case 7:
// 7XKK VX := VX + KK
temp = state->value->registers[DECODE_ARG0(instr)] + DECODE_ARG8(instr);
if (temp > 255) {
state->value->registers[0xf] = 1;
} else {
state->value->registers[0xf] = 0;
}
state->value->registers[DECODE_ARG0(instr)] = temp;
break;
case 8:
// 8XY_
decode_arith(state, instr);
break;
case 0xa:
// I := NNN
state->value->pointer_register = DECODE_ARG16(instr);
break;
case 0xb:
// BNNN Jump to NNN+V0
state->value->program_counter =
DECODE_ARG16(instr) + state->value->registers[0];
// state->value->program_counter = DECODE_ARG8(instr)
// + state->value->registers[DECODE_ARG0(instr)];
break;
case 0xc:
// CXKK VX := pseudorandom_number and KK
state->value->registers[DECODE_ARG0(instr)] =
get_random() & DECODE_ARG8(instr);
break;
case 0xd:
// DXYN* Show N-byte sprite from M(I) at coords (VX,VY), VF :=
// collision.
// If N = 0 show 8xN sprite
// If N=0 and extended mode, show 16x16 sprite.
decode_draw(state, instr);
break;
case 0xe:
// EX9E Skip next instruction if key VX pressed
// EXA1 Skip next instruction if key VX not pressed
// skip
warn("Not implemented.");
break;
case 0xf:
// FX__
decode_store(state, instr);
break;
default:
error("Illegal instruction.");
break;
}
}
void initialize(emu_state *state) {
assert(sizeof(state->memory.values) <= sizeof(state->memory.reserved_memory));
state->value = &state->memory.values;
state->value->delay_timer = 0;
state->value->sound_timer = 0;
state->value->extended_display_mode = 1;
state->value->stack_top = 0;
state->value->program_counter = 0x200;
state->display.width = DISPLAY_WIDTH_BITS;
state->display.height = DISPLAY_HEIGHT_BITS;
state->display.buffer = state->display_buffer;
memcpy(state->value->font, inbuilt_font, sizeof(inbuilt_font));
clear_display(state);
}
void load_program(emu_state *state, char *program, size_t program_len) {
memcpy(state->memory.main_memory, program, program_len);
}
void print_display(struct display *display) {
const int width_bytes = display->width / 8;
const int height = display->height;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width_bytes; j++) {
for (int b = 7; b >= 0; b--) {
if (1 & (display->buffer[(width_bytes * i) + j] >> b)) {
putc('8', stdout);
} else {
putc(' ', stdout);
}
}
}
putc('\n', stdout);
}
}
uint16_t fetch(emu_state *state) {
uint16_t res = state->memory.all_memory[state->value->program_counter] << 8;
res |= state->memory.all_memory[state->value->program_counter + 1];
state->value->program_counter += 2;
return res;
}
void loop(emu_state *state) {
state->value->delay_timer--;
state->value->sound_timer--;
uint16_t instr = fetch(state);
#ifdef DEBUG
print_chip_state(state, instr);
putc('\n', stdout);
#endif
decode_instr(state, instr);
}
/* debugging */
void hexdump(const void *d, size_t datalen) {
const uint8_t *data = d;
size_t i, j = 0;
for (i = 0; i < datalen; i += j) {
printf("%4zx: ", i);
for (j = 0; j < 16 && i + j < datalen; j++)
printf("%02x ", data[i + j]);
while (j++ < 16)
printf(" ");
printf("|");
for (j = 0; j < 16 && i + j < datalen; j++)
putchar(isprint(data[i + j]) ? data[i + j] : '.');
printf("|\n");
}
}
void initialize(emu_state *state);