hi-res mode
This commit is contained in:
1
Makefile
1
Makefile
@@ -20,5 +20,6 @@ $(BIN_DIR):
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BIN_DIR)
|
rm -rf $(BIN_DIR)
|
||||||
|
rm -rf tests/extern
|
||||||
|
|
||||||
.PHONY: all clean run
|
.PHONY: all clean run
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ This is a simple emulator for the Game Boy. It is written in C++ and uses SDL fo
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] Implement sound
|
- [X] Implement sound
|
||||||
- [ ] Implement keyboard input
|
- [X] Implement keyboard input
|
||||||
|
- [ ] Fix games like Tetris and pong
|
||||||
- [ ] Implement better e2e testing for visuals and other things
|
- [ ] Implement better e2e testing for visuals and other things
|
||||||
- [ ] Implement a disassembler
|
- [ ] Implement a disassembler
|
||||||
- [ ] Get better debugging
|
- [ ] Get better debugging
|
||||||
|
|||||||
566
src/main.cpp
566
src/main.cpp
@@ -1,9 +1,13 @@
|
|||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_audio.h>
|
#include <SDL2/SDL_audio.h>
|
||||||
|
#include <SDL2/SDL_keycode.h>
|
||||||
#include <SDL2/SDL_render.h>
|
#include <SDL2/SDL_render.h>
|
||||||
|
#include <SDL2/SDL_video.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <mutex>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -20,21 +24,27 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
const int SCREEN_WIDTH = 64;
|
|
||||||
const int SCREEN_HEIGHT = 32;
|
|
||||||
const size_t RAM_SIZE = 0x1000;
|
const size_t RAM_SIZE = 0x1000;
|
||||||
const int TARGET_CYCLES_PER_SECOND = 500;
|
const int TARGET_CYCLES_PER_SECOND = 500;
|
||||||
const int TARGET_MS_PER_CYCLE = 1000 / TARGET_CYCLES_PER_SECOND;
|
const int TARGET_MS_PER_CYCLE = 1000 / TARGET_CYCLES_PER_SECOND;
|
||||||
const int TARGET_FRAMERATE = 60;
|
const int TARGET_FRAMERATE = 60;
|
||||||
const int TARGET_MS_PER_FRAME = 1000 / TARGET_FRAMERATE;
|
const int TARGET_MS_PER_FRAME = 1000 / TARGET_FRAMERATE;
|
||||||
const int TARGET_MS_PER_TICK = 1000 / 60;
|
const int TARGET_MS_PER_TICK = 1000 / 60;
|
||||||
|
static int SCREEN_WIDTH = 64;
|
||||||
|
static int SCREEN_HEIGHT = 32;
|
||||||
static int SCALE = 10;
|
static int SCALE = 10;
|
||||||
static int BG_COLOR = 0x081820;
|
static int BG_COLOR = 0x081820;
|
||||||
static int FG_COLOR = 0x88c070;
|
static int FG_COLOR = 0x88c070;
|
||||||
|
|
||||||
const int SAMPLE_RATE = 44100; // 44.1kHz sample rate
|
// Spcae Invaders by David Winter uses misaligned addresses, so we might not
|
||||||
const int FREQUENCY = 440; // A4 tone (you can change this)
|
// always want to align pc
|
||||||
const int AMPLITUDE = 28000; // Volume level
|
#define ALIGN_PC true
|
||||||
|
// the SYS instruction technically shouldnt be used, so it can be compiled out.
|
||||||
|
#define SYS_INSTRUCTION false
|
||||||
|
|
||||||
|
const int SAMPLE_RATE = 44100;
|
||||||
|
const int FREQUENCY = 440; // A4
|
||||||
|
const int AMPLITUDE = 28000;
|
||||||
const int SAMPLES_PER_CYCLE = SAMPLE_RATE / FREQUENCY;
|
const int SAMPLES_PER_CYCLE = SAMPLE_RATE / FREQUENCY;
|
||||||
|
|
||||||
void audioCallback(void *userdata, Uint8 *stream, int len) {
|
void audioCallback(void *userdata, Uint8 *stream, int len) {
|
||||||
@@ -82,19 +92,54 @@ static uint8_t FONT[0x10][0x05] = {
|
|||||||
{0xF0, 0x80, 0xF0, 0x80, 0x80}, // F
|
{0xF0, 0x80, 0xF0, 0x80, 0x80}, // F
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void clear_framebuffer(bool **fb, int width, int height) {
|
||||||
|
// since the pixel array is just one big array, we can just memset the
|
||||||
|
// first element of the array and that will clear the entire pixel array
|
||||||
|
memset(fb[0], 0, width * height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool **allocate_framebuffer(int width, int height) {
|
||||||
|
// allocate the frambuffer, which is a pointer to an array of booleans, the
|
||||||
|
// pointer to the arrays should be height elements long. The boolean arrays
|
||||||
|
// should be wdith elements long.
|
||||||
|
bool **fb = (bool **)malloc(height * sizeof(bool *));
|
||||||
|
if (fb == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool *pixel_array = (bool *)malloc(width * height * sizeof(bool));
|
||||||
|
if (pixel_array == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
fb[y] = pixel_array + (y * width);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_framebuffer(fb, width, height);
|
||||||
|
|
||||||
|
return fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_framebuffer(bool **fb) {
|
||||||
|
// since the pixel array is just one big array, we can just free the
|
||||||
|
// first element of the array and that will free the entire pixel array
|
||||||
|
free(fb[0]);
|
||||||
|
free(fb);
|
||||||
|
}
|
||||||
|
|
||||||
class Chip8 {
|
class Chip8 {
|
||||||
public:
|
public:
|
||||||
Chip8(char *rom_path) {
|
Chip8(char *rom_path) {
|
||||||
int rom_fd = open(rom_path, O_RDONLY);
|
int rom_fd = open(rom_path, O_RDONLY);
|
||||||
if (rom_fd < 0) {
|
if (rom_fd < 0) {
|
||||||
printf("Failed to open file: %s\n", rom_path);
|
fprintf(stderr, "Failed to open file: %s\n", rom_path);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pc = 0x200;
|
|
||||||
ram = (uint8_t *)malloc(RAM_SIZE);
|
ram = (uint8_t *)malloc(RAM_SIZE);
|
||||||
if (ram == NULL) {
|
if (ram == NULL) {
|
||||||
printf("Failed to allocate ram!");
|
fprintf(stderr, "Failed to allocate ram!");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,12 +152,12 @@ class Chip8 {
|
|||||||
|
|
||||||
int err = read(rom_fd, ram + 0x200, file_size);
|
int err = read(rom_fd, ram + 0x200, file_size);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
printf("Failed to read file: %s\n", rom_path);
|
fprintf(stderr, "Failed to read file: %s\n", rom_path);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != file_size) {
|
if (err != file_size) {
|
||||||
printf("Failed to read file: %s\n", rom_path);
|
fprintf(stderr, "Failed to read file: %s\n", rom_path);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +165,15 @@ class Chip8 {
|
|||||||
|
|
||||||
stack = (uint16_t *)malloc(sizeof(uint16_t) * 16);
|
stack = (uint16_t *)malloc(sizeof(uint16_t) * 16);
|
||||||
if (stack == NULL) {
|
if (stack == NULL) {
|
||||||
printf("Failed to allocate stack!");
|
fprintf(stderr, "Failed to allocate stack!");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fb = allocate_framebuffer(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
|
fb_length = SCREEN_HEIGHT * SCREEN_WIDTH;
|
||||||
|
|
||||||
|
if (fb == NULL) {
|
||||||
|
fprintf(stderr, "Failed to allocate framebuffer!\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,6 +181,7 @@ class Chip8 {
|
|||||||
~Chip8() {
|
~Chip8() {
|
||||||
free(ram);
|
free(ram);
|
||||||
free(stack);
|
free(stack);
|
||||||
|
free_framebuffer(fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
int run();
|
int run();
|
||||||
@@ -135,86 +189,79 @@ class Chip8 {
|
|||||||
void dump_ram();
|
void dump_ram();
|
||||||
void view_stack();
|
void view_stack();
|
||||||
|
|
||||||
// allow the font to be read
|
// only allow addresses in the program space to be executed, addresses
|
||||||
bool is_protected(size_t addr) {
|
// not protected, but in the reserved space, for example, the font,
|
||||||
if (addr <= sizeof(FONT))
|
// should not be executable
|
||||||
return false;
|
|
||||||
return addr < 0x1FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only allow addresses in the program space to be executed, addresses not
|
|
||||||
// protected, but in the reserved space, for example, the font, should not
|
|
||||||
// be executable
|
|
||||||
bool is_executable(size_t addr) { return addr > 0x1FF; }
|
bool is_executable(size_t addr) { return addr > 0x1FF; }
|
||||||
|
|
||||||
int read_mem(size_t addr) {
|
int read_mem(size_t addr) { return this->ram[addr]; }
|
||||||
if (is_protected(addr)) {
|
|
||||||
printf("Attempted to read from protected address: 0x%04x\n",
|
|
||||||
(unsigned int)addr);
|
|
||||||
dump_ram();
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
return this->ram[addr];
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_mem(size_t addr, uint8_t val) {
|
void write_mem(size_t addr, uint8_t val) { this->ram[addr] = val; }
|
||||||
if (is_protected(addr)) {
|
|
||||||
printf("Attempted to write to protected address: 0x%04x\n",
|
|
||||||
(unsigned int)addr);
|
|
||||||
dump_ram();
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
this->ram[addr] = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_sound_timer(uint8_t val) {
|
void set_sound_timer(uint8_t val) { this->sound_timer = val; }
|
||||||
// enable buzzer
|
|
||||||
printf("The sound timer is set to %d\n", val);
|
|
||||||
this->sound_timer = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_pixel(int x, int y, uint8_t val) {
|
void set_pixel(int x, int y, uint8_t val) {
|
||||||
|
assert(fb != NULL);
|
||||||
assert(x >= 0 && x < SCREEN_WIDTH);
|
assert(x >= 0 && x < SCREEN_WIDTH);
|
||||||
assert(y >= 0 && y < SCREEN_HEIGHT);
|
assert(y >= 0 && y < SCREEN_HEIGHT);
|
||||||
|
assert(this->fb[y] != NULL);
|
||||||
this->fb[y][x] = val;
|
this->fb[y][x] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t get_pixel(int x, int y) {
|
uint8_t get_pixel(int x, int y) {
|
||||||
|
assert(fb != NULL);
|
||||||
assert(x >= 0 && x < SCREEN_WIDTH);
|
assert(x >= 0 && x < SCREEN_WIDTH);
|
||||||
assert(y >= 0 && y < SCREEN_HEIGHT);
|
assert(y >= 0 && y < SCREEN_HEIGHT);
|
||||||
|
assert(this->fb[y] != NULL);
|
||||||
return this->fb[y][x];
|
return this->fb[y][x];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fb[SCREEN_HEIGHT][SCREEN_WIDTH] = {};
|
size_t fb_length = 0;
|
||||||
std::atomic_uint8_t delay;
|
bool **fb = nullptr;
|
||||||
std::atomic_uint8_t sound_timer;
|
std::atomic_uint8_t delay = 0;
|
||||||
std::mutex key_mutex;
|
std::atomic_uint8_t sound_timer = 0;
|
||||||
std::condition_variable key_cv;
|
std::mutex key_mutex = {};
|
||||||
std::atomic<bool> key_pressed = false;
|
std::condition_variable key_cv = {};
|
||||||
std::atomic<uint8_t> last_key = 0;
|
bool key_pressed_map[0x10] = {};
|
||||||
|
std::atomic<uint8_t> last_key = 0xFF;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t *ram;
|
uint8_t *ram = nullptr;
|
||||||
uint16_t pc;
|
uint16_t pc = 0x200;
|
||||||
uint16_t *stack;
|
uint16_t *stack = nullptr;
|
||||||
uint8_t sp;
|
uint8_t sp = 0;
|
||||||
uint8_t v[16];
|
uint8_t v[16] = {0};
|
||||||
uint16_t i;
|
uint16_t i = 0;
|
||||||
// bool compat;
|
// bool compat;
|
||||||
};
|
};
|
||||||
|
|
||||||
void draw(SDL_Renderer *renderer, SDL_Texture *texture, Chip8 *chip8) {
|
size_t pixels_length = 0;
|
||||||
printf("Drawing...\n");
|
uint32_t *pixels_array = nullptr;
|
||||||
|
|
||||||
|
void draw(SDL_Renderer *renderer, SDL_Texture *texture, Chip8 *chip8) {
|
||||||
|
if (pixels_array == NULL ||
|
||||||
|
pixels_length != (size_t)(SCREEN_HEIGHT * SCREEN_WIDTH)) {
|
||||||
|
if (pixels_array != NULL) {
|
||||||
|
free(pixels_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels_array = (uint32_t *)malloc(sizeof(uint32_t) *
|
||||||
|
(SCREEN_HEIGHT * SCREEN_WIDTH));
|
||||||
|
if (pixels_array == NULL) {
|
||||||
|
fprintf(stderr, "Failed to allocated pixels buffer!");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
pixels_length = (size_t)(SCREEN_HEIGHT * SCREEN_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t pixels[SCREEN_WIDTH * SCREEN_HEIGHT];
|
|
||||||
for (int i = 0; i < SCREEN_HEIGHT; i++) {
|
for (int i = 0; i < SCREEN_HEIGHT; i++) {
|
||||||
for (int j = 0; j < SCREEN_WIDTH; j++) {
|
for (int j = 0; j < SCREEN_WIDTH; j++) {
|
||||||
pixels[i * SCREEN_WIDTH + j] =
|
pixels_array[i * SCREEN_WIDTH + j] =
|
||||||
chip8->fb[i][j] ? FG_COLOR : BG_COLOR;
|
chip8->fb[i][j] ? FG_COLOR : BG_COLOR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UpdateTexture(texture, nullptr, pixels,
|
SDL_UpdateTexture(texture, nullptr, pixels_array,
|
||||||
SCREEN_WIDTH * sizeof(uint32_t));
|
SCREEN_WIDTH * sizeof(uint32_t));
|
||||||
SDL_RenderClear(renderer);
|
SDL_RenderClear(renderer);
|
||||||
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
||||||
@@ -243,18 +290,58 @@ void timer_thread(Chip8 *chip8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void render_thread(Chip8 *chip8) {
|
void render_thread(Chip8 *chip8) {
|
||||||
SDL_Window *window = SDL_CreateWindow(
|
SDL_Window *sdl_window = SDL_CreateWindow(
|
||||||
"CHIP-8 Emulator", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
"CHIP-8 Emulator", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE, SDL_WINDOW_SHOWN);
|
SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE, SDL_WINDOW_SHOWN);
|
||||||
SDL_Renderer *renderer =
|
SDL_Renderer *sdl_renderer =
|
||||||
SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
SDL_CreateRenderer(sdl_window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888,
|
SDL_Texture *render_texture = SDL_CreateTexture(
|
||||||
SDL_TEXTUREACCESS_STREAMING,
|
sdl_renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING,
|
||||||
SCREEN_WIDTH, SCREEN_HEIGHT);
|
SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
|
|
||||||
|
int width, height;
|
||||||
|
SDL_GetWindowSize(sdl_window, &width, &height);
|
||||||
|
|
||||||
|
width /= SCALE;
|
||||||
|
height /= SCALE;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
printf("Rendering...\n");
|
// check if we need to resize the window
|
||||||
draw(renderer, texture, chip8);
|
if (width != SCREEN_WIDTH || height != SCREEN_HEIGHT) {
|
||||||
|
// resize the framebuffer
|
||||||
|
bool **new_fb = allocate_framebuffer(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
|
|
||||||
|
if (new_fb == NULL) {
|
||||||
|
fprintf(stderr, "Failed to allocate framebuffer!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to copy the old framebuffer to the new one, but, it
|
||||||
|
// is not guranteed that the old framebuffer is smaller than the
|
||||||
|
// new framebuffer, so we need to make sure we only read at most
|
||||||
|
// the smaller framebuffer.
|
||||||
|
size_t smaller_fb_size = width * height;
|
||||||
|
if (width > SCREEN_WIDTH || height > SCREEN_HEIGHT) {
|
||||||
|
smaller_fb_size = SCREEN_WIDTH * SCREEN_HEIGHT;
|
||||||
|
}
|
||||||
|
memcpy(new_fb[0], chip8->fb[0], smaller_fb_size);
|
||||||
|
|
||||||
|
free_framebuffer(chip8->fb);
|
||||||
|
chip8->fb_length = SCREEN_HEIGHT * SCREEN_WIDTH;
|
||||||
|
chip8->fb = new_fb;
|
||||||
|
|
||||||
|
// resize the SDL window
|
||||||
|
SDL_SetWindowSize(sdl_window, SCREEN_WIDTH * SCALE,
|
||||||
|
SCREEN_HEIGHT * SCALE);
|
||||||
|
SDL_DestroyTexture(render_texture);
|
||||||
|
render_texture = SDL_CreateTexture(
|
||||||
|
sdl_renderer, SDL_PIXELFORMAT_RGBA8888,
|
||||||
|
SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
|
width = SCREEN_WIDTH;
|
||||||
|
height = SCREEN_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(sdl_renderer, render_texture, chip8);
|
||||||
std::this_thread::sleep_for(
|
std::this_thread::sleep_for(
|
||||||
std::chrono::milliseconds(TARGET_MS_PER_FRAME)); // 60Hz
|
std::chrono::milliseconds(TARGET_MS_PER_FRAME)); // 60Hz
|
||||||
}
|
}
|
||||||
@@ -267,23 +354,65 @@ void input_thread(Chip8 *chip8, std::atomic_bool &running) {
|
|||||||
if (event.type == SDL_QUIT)
|
if (event.type == SDL_QUIT)
|
||||||
running = false;
|
running = false;
|
||||||
if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) {
|
if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) {
|
||||||
printf("Key pressed! %c\n", event.key.keysym.sym);
|
|
||||||
std::unique_lock<std::mutex> lock(chip8->key_mutex);
|
std::unique_lock<std::mutex> lock(chip8->key_mutex);
|
||||||
chip8->key_pressed = (event.type == SDL_KEYDOWN);
|
|
||||||
// check if the key is a valid CHIP-8, either a number or a, b,
|
|
||||||
// c, d, e, or f
|
|
||||||
if ((event.key.keysym.sym < 0x30 ||
|
|
||||||
event.key.keysym.sym > 0x39) && // 0-9
|
|
||||||
(event.key.keysym.sym < 0x61 ||
|
|
||||||
event.key.keysym.sym > 0x66))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
uint8_t key = event.key.keysym.sym - 0x30;
|
// map the key to a CHIP-8 key in a numpad way, but not on
|
||||||
if (key > 0x09) {
|
// the numpad since I dont have a numpad
|
||||||
key = key - 0x27;
|
uint8_t key;
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_1:
|
||||||
|
key = 0x01;
|
||||||
|
break;
|
||||||
|
case SDLK_2:
|
||||||
|
key = 0x02;
|
||||||
|
break;
|
||||||
|
case SDLK_3:
|
||||||
|
key = 0x03;
|
||||||
|
break;
|
||||||
|
case SDLK_q:
|
||||||
|
key = 0x04;
|
||||||
|
break;
|
||||||
|
case SDLK_w:
|
||||||
|
key = 0x05;
|
||||||
|
break;
|
||||||
|
case SDLK_e:
|
||||||
|
key = 0x06;
|
||||||
|
break;
|
||||||
|
case SDLK_a:
|
||||||
|
key = 0x07;
|
||||||
|
break;
|
||||||
|
case SDLK_s:
|
||||||
|
key = 0x08;
|
||||||
|
break;
|
||||||
|
case SDLK_d:
|
||||||
|
key = 0x09;
|
||||||
|
break;
|
||||||
|
case SDLK_x:
|
||||||
|
key = 0x00;
|
||||||
|
break;
|
||||||
|
case SDLK_z:
|
||||||
|
key = 0x0A;
|
||||||
|
break;
|
||||||
|
case SDLK_c:
|
||||||
|
key = 0x0B;
|
||||||
|
break;
|
||||||
|
case SDLK_4:
|
||||||
|
key = 0x0C;
|
||||||
|
break;
|
||||||
|
case SDLK_r:
|
||||||
|
key = 0x0D;
|
||||||
|
break;
|
||||||
|
case SDLK_f:
|
||||||
|
key = 0x0E;
|
||||||
|
break;
|
||||||
|
case SDLK_v:
|
||||||
|
key = 0x0F;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
printf("Key: %x\n", key);
|
chip8->key_pressed_map[key] = event.type == SDL_KEYDOWN;
|
||||||
chip8->last_key = key; // Map this to CHIP-8 keys
|
chip8->last_key = key;
|
||||||
chip8->key_cv.notify_one();
|
chip8->key_cv.notify_one();
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -298,7 +427,7 @@ int Chip8::run() {
|
|||||||
constexpr auto cycle_time = milliseconds(TARGET_MS_PER_CYCLE);
|
constexpr auto cycle_time = milliseconds(TARGET_MS_PER_CYCLE);
|
||||||
|
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||||
printf("Failed to initialize SDL: %s\n", SDL_GetError());
|
fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,12 +445,13 @@ int Chip8::run() {
|
|||||||
|
|
||||||
uint16_t op = (read_mem(pc) << 8) | read_mem(pc + 1);
|
uint16_t op = (read_mem(pc) << 8) | read_mem(pc + 1);
|
||||||
if (!is_executable(pc)) {
|
if (!is_executable(pc)) {
|
||||||
printf("Attempted to execute protected memory at 0x%04x\n", pc);
|
fprintf(stderr, "Attempted to execute protected memory at 0x%04x\n",
|
||||||
|
pc);
|
||||||
view_ram();
|
view_ram();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
pc += 2;
|
|
||||||
printf("PC: 0x%04x OP: 0x%04x\n", pc, op);
|
printf("PC: 0x%04x OP: 0x%04x\n", pc, op);
|
||||||
|
pc += 2;
|
||||||
Bytecode bytecode = parse(op);
|
Bytecode bytecode = parse(op);
|
||||||
|
|
||||||
printf("OPCODE: 0x%04x INSTRUCTION_TYPE: %d\n", op,
|
printf("OPCODE: 0x%04x INSTRUCTION_TYPE: %d\n", op,
|
||||||
@@ -329,8 +459,8 @@ int Chip8::run() {
|
|||||||
|
|
||||||
switch (bytecode.instruction_type) {
|
switch (bytecode.instruction_type) {
|
||||||
case EXIT: {
|
case EXIT: {
|
||||||
// From Peter Miller's chip8run. Exit emulator with a return value
|
// From Peter Miller's chip8run. Exit emulator with a return
|
||||||
// of N.
|
// value of N.
|
||||||
ret = bytecode.operand.byte;
|
ret = bytecode.operand.byte;
|
||||||
running = false;
|
running = false;
|
||||||
break;
|
break;
|
||||||
@@ -340,22 +470,31 @@ int Chip8::run() {
|
|||||||
// This instruction is only used on the old computers on which
|
// This instruction is only used on the old computers on which
|
||||||
// Chip-8 was originally implemented. It is ignored by modern
|
// Chip-8 was originally implemented. It is ignored by modern
|
||||||
// interpreters.
|
// interpreters.
|
||||||
|
#if SYS_INSTRUCTION
|
||||||
uint16_t addr = bytecode.operand.word & 0x0FFF;
|
uint16_t addr = bytecode.operand.word & 0x0FFF;
|
||||||
assert(addr < RAM_SIZE && addr % 0x2 == 0);
|
assert(addr < RAM_SIZE);
|
||||||
|
#if ALIGN_PC
|
||||||
|
assert(addr % 0x2 == 0);
|
||||||
|
#endif // ALIGN_PC
|
||||||
|
|
||||||
pc = bytecode.operand.word & 0x0FFF;
|
pc = bytecode.operand.word & 0x0FFF;
|
||||||
|
#else
|
||||||
|
(void)bytecode;
|
||||||
|
fprintf(stderr, "SYS instruction not enabled\n");
|
||||||
|
exit(1);
|
||||||
|
#endif // SYS_INSTRUCTION
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CLS: {
|
case CLS: {
|
||||||
// Clear the screen.
|
// Clear the screen.
|
||||||
// fprintf(stderr, "CLS not implemented\n");
|
clear_framebuffer(this->fb, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
memset(this->fb, 0, SCREEN_WIDTH * SCREEN_HEIGHT);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RET: {
|
case RET: {
|
||||||
// Return from a subroutine.
|
// Return from a subroutine.
|
||||||
// The interpreter sets the program counter to the address at the
|
// The interpreter sets the program counter to the address at
|
||||||
// top of the stack, then subtracts 1 from the stack pointer.
|
// the top of the stack, then subtracts 1 from the stack
|
||||||
|
// pointer.
|
||||||
|
|
||||||
// --sp subtracts 1 from the stack pointer and returns the value
|
// --sp subtracts 1 from the stack pointer and returns the value
|
||||||
// after the subraction
|
// after the subraction
|
||||||
@@ -366,8 +505,21 @@ int Chip8::run() {
|
|||||||
// Jump to location nnn.
|
// Jump to location nnn.
|
||||||
// The interpreter sets the program counter to nnn.
|
// The interpreter sets the program counter to nnn.
|
||||||
|
|
||||||
|
if ((pc - 2) == 0x200 && bytecode.operand.word == 0x260) {
|
||||||
|
printf("Entering Hi-Res mode\n");
|
||||||
|
// This is the Hi-Res enable opcode (0x1260), we need to
|
||||||
|
// change the resolution to 64x64 and jump to 0x2C0 instead
|
||||||
|
// of 0x260.
|
||||||
|
bytecode.operand.word = 0x2C0;
|
||||||
|
// resize_framebuffer(this, 64, 64);
|
||||||
|
SCREEN_HEIGHT = 64;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t addr = bytecode.operand.word & 0x0FFF;
|
uint16_t addr = bytecode.operand.word & 0x0FFF;
|
||||||
assert(addr < RAM_SIZE && addr % 0x2 == 0);
|
assert(addr < RAM_SIZE);
|
||||||
|
#if ALIGN_PC
|
||||||
|
assert(addr % 0x2 == 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
pc = bytecode.operand.word & 0x0FFF;
|
pc = bytecode.operand.word & 0x0FFF;
|
||||||
break;
|
break;
|
||||||
@@ -375,10 +527,11 @@ int Chip8::run() {
|
|||||||
case CALL: {
|
case CALL: {
|
||||||
// Call subroutine at nnn.
|
// Call subroutine at nnn.
|
||||||
// The interpreter increments the stack pointer, then puts the
|
// The interpreter increments the stack pointer, then puts the
|
||||||
// current PC on the top of the stack. The PC is then set to nnn.
|
// current PC on the top of the stack. The PC is then set to
|
||||||
|
// nnn.
|
||||||
|
|
||||||
// sp++ increments the stack pointer and returns the value before
|
// sp++ increments the stack pointer and returns the value
|
||||||
// the addition
|
// before the addition
|
||||||
stack[sp++] = pc;
|
stack[sp++] = pc;
|
||||||
pc = bytecode.operand.word & 0x0FFF;
|
pc = bytecode.operand.word & 0x0FFF;
|
||||||
break;
|
break;
|
||||||
@@ -396,8 +549,8 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
case SKIP_INSTRUCTION_NE_BYTE: {
|
case SKIP_INSTRUCTION_NE_BYTE: {
|
||||||
// Skip next instruction if Vx != kk.
|
// Skip next instruction if Vx != kk.
|
||||||
// The interpreter compares register Vx to kk, and if they are not
|
// The interpreter compares register Vx to kk, and if they are
|
||||||
// equal, increments the program counter by 2.
|
// not equal, increments the program counter by 2.
|
||||||
if (this->v[bytecode.operand.byte_reg.reg] !=
|
if (this->v[bytecode.operand.byte_reg.reg] !=
|
||||||
bytecode.operand.byte_reg.byte) {
|
bytecode.operand.byte_reg.byte) {
|
||||||
pc += 2;
|
pc += 2;
|
||||||
@@ -407,8 +560,8 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
case SKIP_INSTRUCTION_REG: {
|
case SKIP_INSTRUCTION_REG: {
|
||||||
// Skip next instruction if Vx = Vy.
|
// Skip next instruction if Vx = Vy.
|
||||||
// The interpreter compares register Vx to register Vy, and if they
|
// The interpreter compares register Vx to register Vy, and if
|
||||||
// are equal, increments the program counter by 2.
|
// they are equal, increments the program counter by 2.
|
||||||
if (this->v[bytecode.operand.reg_reg.x] ==
|
if (this->v[bytecode.operand.reg_reg.x] ==
|
||||||
this->v[bytecode.operand.reg_reg.y]) {
|
this->v[bytecode.operand.reg_reg.y]) {
|
||||||
pc += 2;
|
pc += 2;
|
||||||
@@ -417,8 +570,8 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
case SKIP_INSTRUCTION_NE_REG: {
|
case SKIP_INSTRUCTION_NE_REG: {
|
||||||
// Skip next instruction if Vx != Vy.
|
// Skip next instruction if Vx != Vy.
|
||||||
// The values of Vx and Vy are compared, and if they are not equal,
|
// The values of Vx and Vy are compared, and if they are not
|
||||||
// the program counter is increased by 2.
|
// equal, the program counter is increased by 2.
|
||||||
if (this->v[bytecode.operand.reg_reg.x] !=
|
if (this->v[bytecode.operand.reg_reg.x] !=
|
||||||
this->v[bytecode.operand.reg_reg.y]) {
|
this->v[bytecode.operand.reg_reg.y]) {
|
||||||
pc += 2;
|
pc += 2;
|
||||||
@@ -434,8 +587,8 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
case ADD_BYTE: {
|
case ADD_BYTE: {
|
||||||
// Set Vx = Vx + kk.
|
// Set Vx = Vx + kk.
|
||||||
// Adds the value kk to the value of register Vx, then stores the
|
// Adds the value kk to the value of register Vx, then stores
|
||||||
// result in Vx.
|
// the result in Vx.
|
||||||
this->v[bytecode.operand.byte_reg.reg] +=
|
this->v[bytecode.operand.byte_reg.reg] +=
|
||||||
bytecode.operand.byte_reg.byte;
|
bytecode.operand.byte_reg.byte;
|
||||||
break;
|
break;
|
||||||
@@ -450,8 +603,9 @@ int Chip8::run() {
|
|||||||
case ADD_REG: {
|
case ADD_REG: {
|
||||||
// Set Vx = Vx + Vy, set VF = carry.
|
// Set Vx = Vx + Vy, set VF = carry.
|
||||||
// The values of Vx and Vy are added together. If the result is
|
// The values of Vx and Vy are added together. If the result is
|
||||||
// greater than 8 bits (i.e., > 255,) VF is set to 1, otherwise 0.
|
// greater than 8 bits (i.e., > 255,) VF is set to 1, otherwise
|
||||||
// Only the lowest 8 bits of the result are kept, and stored in Vx.
|
// 0. Only the lowest 8 bits of the result are kept, and stored
|
||||||
|
// in Vx.
|
||||||
int result = this->v[bytecode.operand.reg_reg.x] +
|
int result = this->v[bytecode.operand.reg_reg.x] +
|
||||||
this->v[bytecode.operand.reg_reg.y];
|
this->v[bytecode.operand.reg_reg.y];
|
||||||
this->v[bytecode.operand.reg_reg.x] = result & 0xFF;
|
this->v[bytecode.operand.reg_reg.x] = result & 0xFF;
|
||||||
@@ -469,11 +623,6 @@ int Chip8::run() {
|
|||||||
// Set Vx = Vx - Vy, set VF = NOT borrow.
|
// Set Vx = Vx - Vy, set VF = NOT borrow.
|
||||||
// If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is
|
// If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is
|
||||||
// subtracted from Vx, and the results stored in Vx.
|
// subtracted from Vx, and the results stored in Vx.
|
||||||
printf("Subtracting %d - %d (v%x - v%x)\n",
|
|
||||||
this->v[bytecode.operand.reg_reg.x],
|
|
||||||
this->v[bytecode.operand.reg_reg.y],
|
|
||||||
bytecode.operand.reg_reg.x, bytecode.operand.reg_reg.y);
|
|
||||||
|
|
||||||
bool borrow = this->v[bytecode.operand.reg_reg.x] >=
|
bool borrow = this->v[bytecode.operand.reg_reg.x] >=
|
||||||
this->v[bytecode.operand.reg_reg.y];
|
this->v[bytecode.operand.reg_reg.y];
|
||||||
|
|
||||||
@@ -488,23 +637,19 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (borrow) {
|
if (borrow) {
|
||||||
printf("Overflowed!\n");
|
|
||||||
this->v[0xF] = 1;
|
this->v[0xF] = 1;
|
||||||
} else {
|
} else {
|
||||||
this->v[0xF] = 0;
|
this->v[0xF] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Resulting in %d (v%x) with %s\n",
|
|
||||||
this->v[bytecode.operand.reg_reg.x],
|
|
||||||
bytecode.operand.reg_reg.x, borrow ? "borrow" : "no borrow");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OR_REG: {
|
case OR_REG: {
|
||||||
// Set Vx = Vx OR Vy.
|
// Set Vx = Vx OR Vy.
|
||||||
// Performs a bitwise OR on the values of Vx and Vy, then stores the
|
// Performs a bitwise OR on the values of Vx and Vy, then stores
|
||||||
// result in Vx. A bitwise OR compares the corrseponding bits from
|
// the result in Vx. A bitwise OR compares the corrseponding
|
||||||
// two values, and if either bit is 1, then the same bit in the
|
// bits from two values, and if either bit is 1, then the same
|
||||||
// result is also 1. Otherwise, it is 0.
|
// bit in the result is also 1. Otherwise, it is 0.
|
||||||
this->v[bytecode.operand.reg_reg.x] =
|
this->v[bytecode.operand.reg_reg.x] =
|
||||||
this->v[bytecode.operand.reg_reg.x] |
|
this->v[bytecode.operand.reg_reg.x] |
|
||||||
this->v[bytecode.operand.reg_reg.y];
|
this->v[bytecode.operand.reg_reg.y];
|
||||||
@@ -512,10 +657,11 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
case AND_REG: {
|
case AND_REG: {
|
||||||
// Set Vx = Vx AND Vy.
|
// Set Vx = Vx AND Vy.
|
||||||
// Performs a bitwise AND on the values of Vx and Vy, then stores
|
// Performs a bitwise AND on the values of Vx and Vy, then
|
||||||
// the result in Vx. A bitwise AND compares the corrseponding bits
|
// stores the result in Vx. A bitwise AND compares the
|
||||||
// from two values, and if both bits are 1, then the same bit in the
|
// corrseponding bits from two values, and if both bits are 1,
|
||||||
// result is also 1. Otherwise, it is 0.
|
// then the same bit in the result is also 1. Otherwise, it is
|
||||||
|
// 0.
|
||||||
this->v[bytecode.operand.reg_reg.x] =
|
this->v[bytecode.operand.reg_reg.x] =
|
||||||
this->v[bytecode.operand.reg_reg.x] &
|
this->v[bytecode.operand.reg_reg.x] &
|
||||||
this->v[bytecode.operand.reg_reg.y];
|
this->v[bytecode.operand.reg_reg.y];
|
||||||
@@ -523,11 +669,11 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
case XOR_REG: {
|
case XOR_REG: {
|
||||||
// Set Vx = Vx XOR Vy.
|
// Set Vx = Vx XOR Vy.
|
||||||
// Performs a bitwise exclusive OR on the values of Vx and Vy, then
|
// Performs a bitwise exclusive OR on the values of Vx and Vy,
|
||||||
// stores the result in Vx. An exclusive OR compares the
|
// then stores the result in Vx. An exclusive OR compares the
|
||||||
// corrseponding bits from two values, and if the bits are not both
|
// corrseponding bits from two values, and if the bits are not
|
||||||
// the same, then the corresponding bit in the result is set to 1.
|
// both the same, then the corresponding bit in the result is
|
||||||
// Otherwise, it is 0.
|
// set to 1. Otherwise, it is 0.
|
||||||
this->v[bytecode.operand.reg_reg.x] =
|
this->v[bytecode.operand.reg_reg.x] =
|
||||||
this->v[bytecode.operand.reg_reg.x] ^
|
this->v[bytecode.operand.reg_reg.x] ^
|
||||||
this->v[bytecode.operand.reg_reg.y];
|
this->v[bytecode.operand.reg_reg.y];
|
||||||
@@ -550,11 +696,6 @@ int Chip8::run() {
|
|||||||
// Set Vx = Vy - Vx, set VF = NOT borrow.
|
// Set Vx = Vy - Vx, set VF = NOT borrow.
|
||||||
// If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is
|
// If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is
|
||||||
// subtracted from Vy, and the results stored in Vx.
|
// subtracted from Vy, and the results stored in Vx.
|
||||||
printf("Subtracting %d - %d (v%x - v%x)\n",
|
|
||||||
this->v[bytecode.operand.reg_reg.y],
|
|
||||||
this->v[bytecode.operand.reg_reg.x],
|
|
||||||
bytecode.operand.reg_reg.y, bytecode.operand.reg_reg.x);
|
|
||||||
|
|
||||||
bool borrow = this->v[bytecode.operand.reg_reg.y] >=
|
bool borrow = this->v[bytecode.operand.reg_reg.y] >=
|
||||||
this->v[bytecode.operand.reg_reg.x];
|
this->v[bytecode.operand.reg_reg.x];
|
||||||
|
|
||||||
@@ -569,15 +710,11 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (borrow) {
|
if (borrow) {
|
||||||
printf("Overflowed!\n");
|
|
||||||
this->v[0xF] = 1;
|
this->v[0xF] = 1;
|
||||||
} else {
|
} else {
|
||||||
this->v[0xF] = 0;
|
this->v[0xF] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Resulting in %d (v%x) with %s\n",
|
|
||||||
this->v[bytecode.operand.reg_reg.x],
|
|
||||||
bytecode.operand.reg_reg.x, borrow ? "borrow" : "no borrow");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SHL_REG: {
|
case SHL_REG: {
|
||||||
@@ -607,25 +744,26 @@ int Chip8::run() {
|
|||||||
}
|
}
|
||||||
case RND: {
|
case RND: {
|
||||||
// Set Vx = random byte AND kk.
|
// Set Vx = random byte AND kk.
|
||||||
// The interpreter generates a random number from 0 to 255, which is
|
// The interpreter generates a random number from 0 to 255,
|
||||||
// then ANDed with the value kk. The results are stored in Vx. See
|
// which is then ANDed with the value kk. The results are stored
|
||||||
// instruction 8xy2 for more information on AND.
|
// in Vx. See instruction 8xy2 for more information on AND.
|
||||||
this->v[bytecode.operand.byte_reg.reg] =
|
this->v[bytecode.operand.byte_reg.reg] =
|
||||||
static_cast<uint8_t>(rand()) & bytecode.operand.byte_reg.byte;
|
static_cast<uint8_t>(rand()) & bytecode.operand.byte_reg.byte;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DRW: {
|
case DRW: {
|
||||||
// Display n-byte sprite starting at memory location I
|
// Display n-byte sprite starting at memory location
|
||||||
// at (Vx, Vy), set VF = collision.
|
// I at (Vx, Vy), set VF = collision.
|
||||||
// The interpreter reads n bytes from memory, starting at the
|
// The interpreter reads n bytes from memory, starting at the
|
||||||
// address stored in I. These bytes are then displayed as sprites on
|
// address stored in I. These bytes are then displayed as
|
||||||
// screen at coordinates (Vx, Vy). Sprites are XORed onto the
|
// sprites on screen at coordinates (Vx, Vy). Sprites are XORed
|
||||||
// existing screen. If this causes any pixels to be erased, VF is
|
// onto the existing screen. If this causes any pixels to be
|
||||||
// set to 1, otherwise it is set to 0. If the sprite is positioned
|
// erased, VF is set to 1, otherwise it is set to 0. If the
|
||||||
// so part of it is outside the coordinates of the display, it wraps
|
// sprite is positioned so part of it is outside the coordinates
|
||||||
// around to the opposite side of the screen. See instruction 8xy3
|
// of the display, it wraps around to the opposite side of the
|
||||||
// for more information on XOR, and section 2.4, Display, for more
|
// screen. See instruction 8xy3 for more information on XOR, and
|
||||||
// information on the Chip-8 screen and sprites.
|
// section 2.4, Display, for more information on the Chip-8
|
||||||
|
// screen and sprites.
|
||||||
this->v[0x0F] = 0;
|
this->v[0x0F] = 0;
|
||||||
|
|
||||||
for (int i = 0; i < bytecode.operand.reg_reg_nibble.nibble; i++) {
|
for (int i = 0; i < bytecode.operand.reg_reg_nibble.nibble; i++) {
|
||||||
@@ -641,45 +779,39 @@ int Chip8::run() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get_pixel(x, y)) {
|
if (this->get_pixel(x, y)) {
|
||||||
set_pixel(x, y, 0);
|
this->set_pixel(x, y, 0);
|
||||||
this->v[0x0F] = 1;
|
this->v[0x0F] = 1;
|
||||||
} else {
|
} else {
|
||||||
set_pixel(x, y, 1);
|
this->set_pixel(x, y, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SKIP_PRESSED_REG: {
|
case SKIP_PRESSED_REG: {
|
||||||
// Skip next instruction if key with the value of Vx is
|
// Skip next instruction if key with the value of Vx
|
||||||
// pressed.
|
// is pressed.
|
||||||
// Checks the keyboard, and if the key corresponding to the value of
|
// Checks the keyboard, and if the key corresponding to the
|
||||||
// Vx is currently in the down position, PC is increased by 2.
|
// value of Vx is currently in the down position, PC is
|
||||||
// fprintf(stderr, "SKIP_PRESSED_REG not implemented\n");
|
// increased by 2.
|
||||||
printf("Last key: %x\n", this->last_key.load());
|
|
||||||
printf("Key: %x\n", bytecode.operand.byte);
|
|
||||||
uint8_t key = this->v[bytecode.operand.byte];
|
uint8_t key = this->v[bytecode.operand.byte];
|
||||||
|
|
||||||
if (this->last_key.load() == key && this->key_pressed.load()) {
|
if (this->key_pressed_map[key]) {
|
||||||
pc += 2;
|
pc += 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SKIP_NOT_PRESSED_REG: {
|
case SKIP_NOT_PRESSED_REG: {
|
||||||
// Skip next instruction if key with the value of Vx is
|
// Skip next instruction if key with the value of Vx
|
||||||
// not pressed.
|
// is not pressed.
|
||||||
// Checks the keyboard, and if the key corresponding to the value of
|
// Checks the keyboard, and if the key corresponding to the
|
||||||
// Vx is currently in the up position, PC is increased by 2.
|
// value of Vx is currently in the up position, PC is increased
|
||||||
// fprintf(stderr, "SKIP_NOT_PRESSED_REG not implemented\n");
|
// by 2. fprintf(stderr, "SKIP_NOT_PRESSED_REG not
|
||||||
printf("Last key: %x\n", this->last_key.load());
|
// implemented\n");
|
||||||
printf("Key: %x\n", bytecode.operand.byte);
|
|
||||||
uint8_t key = this->v[bytecode.operand.byte];
|
uint8_t key = this->v[bytecode.operand.byte];
|
||||||
|
|
||||||
if (this->last_key.load() != key) {
|
if (!this->key_pressed_map[key]) {
|
||||||
pc += 2;
|
|
||||||
}
|
|
||||||
if (this->last_key.load() == key && !this->key_pressed.load()) {
|
|
||||||
pc += 2;
|
pc += 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -687,24 +819,22 @@ int Chip8::run() {
|
|||||||
case LD_REG_DT: {
|
case LD_REG_DT: {
|
||||||
// Set Vx = delay timer value.
|
// Set Vx = delay timer value.
|
||||||
// The value of DT is placed into Vx.
|
// The value of DT is placed into Vx.
|
||||||
this->v[bytecode.operand.reg_reg.x] = this->delay;
|
this->v[bytecode.operand.byte] = this->delay;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LD_REG_K: {
|
case LD_REG_K: {
|
||||||
// Wait for a key press, store the value of the key in
|
// Wait for a key press, store the value of the key
|
||||||
// Vx.
|
// in Vx.
|
||||||
// All execution stops until a key is pressed, then the value of
|
// All execution stops until a key is pressed, then the value of
|
||||||
// that key is stored in Vx.
|
// that key is stored in Vx.
|
||||||
std::unique_lock<std::mutex> lock(this->key_mutex);
|
std::unique_lock<std::mutex> lock(this->key_mutex);
|
||||||
|
|
||||||
this->key_cv.wait(lock, [&]() {
|
this->key_cv.wait(lock, [&]() {
|
||||||
this->v[bytecode.operand.byte] = this->last_key.load();
|
this->v[bytecode.operand.byte] = this->last_key.load();
|
||||||
return this->key_pressed.load();
|
return this->key_pressed_map[this->last_key.load()];
|
||||||
});
|
});
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
while (this->key_pressed.load()) {
|
while (this->key_pressed_map[this->last_key.load()]) {
|
||||||
printf("Waiting for key to be released %x %x\n",
|
|
||||||
this->last_key.load(), this->key_pressed.load());
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -718,71 +848,65 @@ int Chip8::run() {
|
|||||||
case LD_ST_REG: {
|
case LD_ST_REG: {
|
||||||
// Set sound timer = Vx.
|
// Set sound timer = Vx.
|
||||||
// ST is set equal to the value of Vx.
|
// ST is set equal to the value of Vx.
|
||||||
printf("Sound timer set to %d\n", this->v[bytecode.operand.byte]);
|
|
||||||
set_sound_timer(this->v[bytecode.operand.byte]);
|
set_sound_timer(this->v[bytecode.operand.byte]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ADD_I_REG: {
|
case ADD_I_REG: {
|
||||||
// Set I = I + Vx.
|
// Set I = I + Vx.
|
||||||
// The values of I and Vx are added, and the results are stored in
|
// The values of I and Vx are added, and the results are stored
|
||||||
// I.
|
// in I.
|
||||||
this->i += this->v[bytecode.operand.byte];
|
this->i += this->v[bytecode.operand.byte];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LD_F_REG: {
|
case LD_F_REG: {
|
||||||
// Set I = location of sprite for digit Vx.
|
// Set I = location of sprite for digit Vx.
|
||||||
// The value of I is set to the location for the hexadecimal sprite
|
// The value of I is set to the location for the hexadecimal
|
||||||
// corresponding to the value of Vx. See section 2.4, Display, for
|
// sprite corresponding to the value of Vx. See section 2.4,
|
||||||
// more information on the Chip-8 hexadecimal font.
|
// Display, for more information on the Chip-8 hexadecimal font.
|
||||||
|
|
||||||
//? This is the ONLY spot in 0x0000-0x01FF of the RAM where the
|
//? This is the ONLY spot in 0x0000-0x01FF of the RAM where the
|
||||||
//? emulator is allowed to access. Since that area of RAM is
|
//? emulator is allowed to access. Since that area of RAM is
|
||||||
//? where the font is stored.
|
//? where the font is stored.
|
||||||
this->i = (uint16_t)(bytecode.operand.byte * 5);
|
this->i = (uint16_t)(this->v[bytecode.operand.byte] * 5);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LD_B_REG: {
|
case LD_B_REG: {
|
||||||
// Store BCD representation of Vx in memory locations I,
|
// Store BCD representation of Vx in memory
|
||||||
// I+1, and I+2.
|
// locations I, I+1, and I+2.
|
||||||
// The interpreter takes the decimal value of Vx, and places the
|
// The interpreter takes the decimal value of Vx, and places the
|
||||||
// hundreds digit in memory at location in I, the tens digit at
|
// hundreds digit in memory at location in I, the tens digit at
|
||||||
// location I+1, and the ones digit at location I+2.
|
// location I+1, and the ones digit at location I+2.
|
||||||
// TODO: is this correct?
|
write_mem(this->i,
|
||||||
write_mem(
|
(uint8_t)((this->v[bytecode.operand.byte] / 100) & 0x0F));
|
||||||
this->i,
|
write_mem(this->i + 1,
|
||||||
(uint8_t)((this->v[bytecode.operand.reg_reg.x] / 100) & 0x0F));
|
(uint8_t)((this->v[bytecode.operand.byte] % 100) / 10) &
|
||||||
write_mem(
|
|
||||||
this->i + 1,
|
|
||||||
(uint8_t)((this->v[bytecode.operand.reg_reg.x] % 100) / 10) &
|
|
||||||
0x0F);
|
0x0F);
|
||||||
write_mem(this->i + 2,
|
write_mem(this->i + 2,
|
||||||
(uint8_t)(this->v[bytecode.operand.reg_reg.x] % 10) &
|
(uint8_t)(this->v[bytecode.operand.byte] % 10) & 0x0F);
|
||||||
0x0F);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LD_PTR_I_REG: {
|
case LD_PTR_I_REG: {
|
||||||
// Store registers V0 through Vx in memory starting at
|
// Store registers V0 through Vx in memory starting
|
||||||
// location I.
|
// at location I.
|
||||||
// The interpreter copies the values of registers V0 through Vx into
|
// The interpreter copies the values of registers V0 through Vx
|
||||||
// memory, starting at the address in I.
|
// into memory, starting at the address in I.
|
||||||
for (int i = 0; i <= bytecode.operand.byte; i++) {
|
for (int i = 0; i <= bytecode.operand.byte; i++) {
|
||||||
write_mem(this->i + i, this->v[i]);
|
write_mem(this->i + i, this->v[i]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LD_REG_PTR_I: {
|
case LD_REG_PTR_I: {
|
||||||
// Read registers V0 through Vx from memory starting at
|
// Read registers V0 through Vx from memory starting
|
||||||
// location I.
|
// at location I.
|
||||||
// The interpreter reads values from memory starting at location I
|
// The interpreter reads values from memory starting at location
|
||||||
// into registers V0 through Vx.
|
// I into registers V0 through Vx.
|
||||||
for (int i = 0; i <= bytecode.operand.byte; i++) {
|
for (int i = 0; i <= bytecode.operand.byte; i++) {
|
||||||
this->v[i] = read_mem(this->i + i);
|
this->v[i] = read_mem(this->i + i);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case UNKNOWN_INSTRUCTION: {
|
case UNKNOWN_INSTRUCTION: {
|
||||||
fprintf(stderr, "Unknown instruction type: %d\n",
|
fprintf(stderr, "Unknown instruction: %04x\n", op);
|
||||||
bytecode.instruction_type);
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,11 +922,6 @@ int Chip8::run() {
|
|||||||
render.detach();
|
render.detach();
|
||||||
input.detach();
|
input.detach();
|
||||||
|
|
||||||
// SDL_DestroyTexture(texture);
|
|
||||||
// SDL_DestroyRenderer(renderer);
|
|
||||||
// SDL_DestroyWindow(window);
|
|
||||||
// SDL_Quit();
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -849,22 +968,13 @@ void Chip8::view_stack() {
|
|||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroy(int _) {
|
void signal_handler(int signum) {
|
||||||
(void)_;
|
(void)signum;
|
||||||
|
exit(1);
|
||||||
if (!SDL_WasInit(SDL_INIT_VIDEO)) {
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Event event;
|
|
||||||
event.type = SDL_QUIT;
|
|
||||||
SDL_PushEvent(&event);
|
|
||||||
|
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
signal(SIGINT, destroy);
|
signal(SIGINT, signal_handler);
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
printf("Usage: %s <file> [options]\n", argv[0]);
|
printf("Usage: %s <file> [options]\n", argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -942,8 +1052,10 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srand(time(0));
|
||||||
|
|
||||||
Chip8 chip8 = Chip8(file_name);
|
Chip8 chip8 = Chip8(file_name);
|
||||||
chip8.view_ram();
|
chip8.dump_ram();
|
||||||
int ret = chip8.run();
|
int ret = chip8.run();
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
12
test.sh
12
test.sh
@@ -13,6 +13,18 @@ extern_roms=(
|
|||||||
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/4-flags.ch8"
|
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/4-flags.ch8"
|
||||||
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/6-keypad.ch8"
|
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/6-keypad.ch8"
|
||||||
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/7-beep.ch8"
|
"https://github.com/Timendus/chip8-test-suite/raw/main/bin/7-beep.ch8"
|
||||||
|
"https://github.com/kripod/chip8-roms/raw/refs/heads/master/games/Pong%20(1%20player).ch8"
|
||||||
|
"https://github.com/kripod/chip8-roms/raw/refs/heads/master/games/Tetris%20%5BFran%20Dachille,%201991%5D.ch8"
|
||||||
|
"https://github.com/kripod/chip8-roms/raw/refs/heads/master/games/Lunar%20Lander%20(Udo%20Pernisz,%201979).ch8"
|
||||||
|
"https://github.com/kripod/chip8-roms/raw/refs/heads/master/games/Breakout%20%5BCarmelo%20Cortez,%201979%5D.ch8"
|
||||||
|
# Space Invaders uses misaligned addresses, so for a typical testing suite,
|
||||||
|
# where we force aligned instructions, we dont want to test this since
|
||||||
|
# it would fail for obvious reasons
|
||||||
|
# "https://github.com/kripod/chip8-roms/raw/refs/heads/master/games/Space%20Invaders%20%5BDavid%20Winter%5D%20(alt).ch8"
|
||||||
|
"https://github.com/kripod/chip8-roms/raw/refs/heads/master/hires/Hires%20Maze%20%5BDavid%20Winter,%20199x%5D.ch8"
|
||||||
|
"https://github.com/kripod/chip8-roms/raw/refs/heads/master/hires/Hires%20Stars%20%5BSergey%20Naydenov,%202010%5D.ch8"
|
||||||
|
"https://github.com/kripod/chip8-roms/raw/refs/heads/master/programs/Random%20Number%20Test%20%5BMatthew%20Mikolay,%202010%5D.ch8"
|
||||||
|
"https://github.com/kripod/chip8-roms/raw/refs/heads/master/hires/Hires%20Sierpinski%20%5BSergey%20Naydenov,%202010%5D.ch8"
|
||||||
)
|
)
|
||||||
|
|
||||||
for rom in "${extern_roms[@]}"; do
|
for rom in "${extern_roms[@]}"; do
|
||||||
|
|||||||
Reference in New Issue
Block a user