489 lines
11 KiB
C++
489 lines
11 KiB
C++
#include "Interpreter.h"
|
|
|
|
#include <format>
|
|
#include <iomanip>
|
|
#include <ios>
|
|
#include <iostream>
|
|
#include <bits/ostream.tcc>
|
|
|
|
#include "SDL3/SDL_log.h"
|
|
|
|
Interpreter::Interpreter():
|
|
memory(4096, 0),
|
|
v(16, 0),
|
|
stack(16, 0),
|
|
pc(0x200),
|
|
sp(0),
|
|
i(0),
|
|
dt(0),
|
|
st(0),
|
|
keyboard(0),
|
|
quirks(0) {
|
|
this->random_generator = std::mt19937(std::random_device{}());
|
|
this->load_fonts();
|
|
}
|
|
|
|
void Interpreter::load_fonts() {
|
|
constexpr uint8_t font_set[] = {
|
|
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
|
|
};
|
|
|
|
std::copy(std::begin(font_set), std::end(font_set), memory.begin() + 0x050);
|
|
}
|
|
|
|
|
|
void Interpreter::set_quirks(uint8_t quirks) {
|
|
this->quirks = quirks;
|
|
}
|
|
|
|
void Interpreter::load_rom(const std::vector<uint8_t>& rom) {
|
|
std::copy(std::begin(rom), std::end(rom), memory.begin() + 0x200);
|
|
}
|
|
|
|
|
|
void Interpreter::run() {
|
|
const uint8_t high_instruction = memory[pc];
|
|
const uint8_t low_instruction = memory[pc + 1];
|
|
const uint16_t instruction = (high_instruction << 8) | low_instruction;
|
|
|
|
const uint8_t opcode = (instruction & 0xF000) >> 12;
|
|
const uint8_t x = (instruction & 0x0F00) >> 8;
|
|
const uint8_t y = (instruction & 0x00F0) >> 4;
|
|
const uint8_t n = instruction & 0x000F;
|
|
const uint8_t kk = instruction & 0x00FF;
|
|
const uint16_t nnn = instruction & 0x0FFF;
|
|
|
|
std::cout << std::format(
|
|
"INST: {:04X} | OPCODE: {:X} | X: {:X} | Y: {:X} | N: {:X} | KK: {:X} | NNN: {:X}",
|
|
instruction,
|
|
opcode,
|
|
x,
|
|
y,
|
|
n,
|
|
kk,
|
|
nnn
|
|
) << std::endl;
|
|
pc += 2;
|
|
|
|
switch (opcode) {
|
|
case 0:
|
|
if (kk == 0xE0) {
|
|
clear_screen();
|
|
} else if (kk == 0xEE) {
|
|
return_from_subrutine();
|
|
}
|
|
break;
|
|
case 1:
|
|
jump(nnn);
|
|
break;
|
|
case 2: call_subrutine(nnn);
|
|
break;
|
|
case 3:
|
|
skip_if_vx_eq_val(x, kk);
|
|
break;
|
|
case 4:
|
|
skip_if_vx_neq_val(x, kk);
|
|
break;
|
|
case 5:
|
|
skip_if_vx_eq_vy(x, y);
|
|
break;
|
|
case 6:
|
|
load_vx_val(x, kk);
|
|
break;
|
|
case 7:
|
|
add_vx_val(x, kk);
|
|
break;
|
|
case 8:
|
|
switch (n) {
|
|
case 0:
|
|
load_vx_vy(x, y);
|
|
break;
|
|
case 1:
|
|
vx_or_vy(x, y);
|
|
break;
|
|
case 2:
|
|
vx_and_vy(x, y);
|
|
break;
|
|
case 3:
|
|
vx_xor_vy(x, y);
|
|
break;
|
|
case 4:
|
|
vx_add_vy(x, y);
|
|
break;
|
|
case 5:
|
|
vx_sub_vy(x, y);
|
|
break;
|
|
case 6:
|
|
if (quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_SHIFT)) {
|
|
vx_shift_right_cosmac(x, y);
|
|
} else {
|
|
vx_shift_right(x);
|
|
}
|
|
break;
|
|
case 7:
|
|
vx_subn_vy(x, y);
|
|
break;
|
|
case 0xE:
|
|
if (quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_SHIFT)) {
|
|
vx_shift_left_cosmac(x, y);
|
|
} else {
|
|
vx_shift_left(x);
|
|
}
|
|
|
|
break;
|
|
}
|
|
break;
|
|
case 9:
|
|
skip_if_vx_neq_vy(x, y);
|
|
break;
|
|
case 0xA:
|
|
set_index_val(nnn);
|
|
break;
|
|
case 0xB:
|
|
if (quirks & static_cast<int>(InterpreterQuirks::SUPER_CHIP_JUMP)) {
|
|
jump_with_offset_super_chip(x, nnn);
|
|
} else {
|
|
jump_with_offset(nnn);
|
|
}
|
|
break;
|
|
case 0XC:
|
|
set_random_value(x, kk);
|
|
break;
|
|
case 0xD:
|
|
display_sprite(x, y, n);
|
|
break;
|
|
case 0xE:
|
|
if (kk == 0x9E) {
|
|
skip_if_key_pressed(x);
|
|
} else if (kk == 0xA1) {
|
|
skip_if_key_not_pressed(x);
|
|
}
|
|
case 0xF:
|
|
switch (kk) {
|
|
case 0x07:
|
|
load_vx_dt(x);
|
|
break;
|
|
case 0x0A:
|
|
get_key(x);
|
|
break;
|
|
case 0x15:
|
|
load_dt_vx(x);
|
|
break;
|
|
case 0x18:
|
|
load_st_vx(x);
|
|
break;
|
|
case 0x1E:
|
|
add_i_vx(x);
|
|
break;
|
|
case 0x29:
|
|
font_sprite_location(x);
|
|
break;
|
|
case 0x33:
|
|
calculate_bcd(x);
|
|
break;
|
|
case 0x55:
|
|
store_registers_to_memory(x);
|
|
break;
|
|
case 0x65:
|
|
load_registers_from_memory(x);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (pc >= 0xFFF) {
|
|
SDL_Log("PC Outside of memory, going back 0x200");
|
|
pc = 0x200;
|
|
}
|
|
}
|
|
|
|
void Interpreter::clear_screen() {
|
|
display.reset();
|
|
}
|
|
|
|
void Interpreter::return_from_subrutine() {
|
|
sp -= 1;
|
|
pc = stack[sp];
|
|
}
|
|
|
|
void Interpreter::jump(const uint16_t nnn) {
|
|
pc = nnn;
|
|
}
|
|
|
|
void Interpreter::call_subrutine(const uint16_t nnn) {
|
|
stack[sp] = pc;
|
|
sp += 1;
|
|
pc = nnn;
|
|
}
|
|
|
|
void Interpreter::skip_if_vx_eq_val(const uint8_t x, const uint8_t kk) {
|
|
if (v[x] == kk) {
|
|
pc += 2;
|
|
}
|
|
}
|
|
|
|
void Interpreter::skip_if_vx_neq_val(const uint8_t x, const uint8_t kk) {
|
|
if (v[x] != kk) {
|
|
pc += 2;
|
|
}
|
|
}
|
|
|
|
void Interpreter::skip_if_vx_eq_vy(const uint8_t x, const uint8_t y) {
|
|
if (v[x] == v[y]) {
|
|
pc += 2;
|
|
}
|
|
}
|
|
|
|
void Interpreter::load_vx_val(const uint8_t x, const uint8_t kk) {
|
|
v[x] = kk;
|
|
}
|
|
|
|
void Interpreter::add_vx_val(const uint8_t x, const uint8_t kk) {
|
|
v[x] += kk;
|
|
}
|
|
|
|
void Interpreter::load_vx_vy(const uint8_t x, const uint8_t y) {
|
|
v[x] = v[y];
|
|
}
|
|
|
|
void Interpreter::vx_or_vy(const uint8_t x, const uint8_t y) {
|
|
v[x] = v[x] | v[y];
|
|
}
|
|
|
|
void Interpreter::vx_and_vy(const uint8_t x, const uint8_t y) {
|
|
v[x] = v[x] & v[y];
|
|
}
|
|
|
|
void Interpreter::vx_xor_vy(const uint8_t x, const uint8_t y) {
|
|
v[x] = v[x] ^ v[y];
|
|
}
|
|
|
|
void Interpreter::vx_add_vy(const uint8_t x, const uint8_t y) {
|
|
if (v[x] + v[y] > 0xFF) {
|
|
v[0xF] = 1;
|
|
} else {
|
|
v[0xF] = 0;
|
|
}
|
|
|
|
v[x] = (v[x] + v[y]);
|
|
}
|
|
|
|
void Interpreter::vx_sub_vy(const uint8_t x, const uint8_t y) {
|
|
if (v[x] > v[y]) {
|
|
v[0xF] = 1;
|
|
} else {
|
|
v[0xF] = 0;
|
|
}
|
|
|
|
v[x] = v[x] - v[y];
|
|
}
|
|
|
|
void Interpreter::vx_shift_right(const uint8_t x) {
|
|
if (v[x] & 0x01) {
|
|
v[0xF] = 1;
|
|
} else {
|
|
v[0xF] = 0;
|
|
}
|
|
|
|
v[x] = v[x] >> 1;
|
|
}
|
|
|
|
void Interpreter::vx_shift_right_cosmac(const uint8_t x, const uint8_t y) {
|
|
v[x] = v[y];
|
|
|
|
if (v[x] & 0x01) {
|
|
v[0xF] = 1;
|
|
} else {
|
|
v[0xF] = 0;
|
|
}
|
|
|
|
v[x] = v[x] >> 1;
|
|
}
|
|
|
|
void Interpreter::vx_subn_vy(const uint8_t x, const uint8_t y) {
|
|
if (v[y] > v[x]) {
|
|
v[0xF] = 1;
|
|
} else {
|
|
v[0xF] = 0;
|
|
}
|
|
|
|
v[x] = v[y] - v[x];
|
|
}
|
|
|
|
void Interpreter::vx_shift_left(const uint8_t x) {
|
|
if (v[x] & 0x80) {
|
|
v[0xF] = 1;
|
|
} else {
|
|
v[0xF] = 0;
|
|
}
|
|
|
|
v[x] = v[x] << 1;
|
|
}
|
|
|
|
void Interpreter::vx_shift_left_cosmac(const uint8_t x, const uint8_t y) {
|
|
v[x] = v[y];
|
|
|
|
if (v[x] & 0x80) {
|
|
v[0xF] = 1;
|
|
} else {
|
|
v[0xF] = 0;
|
|
}
|
|
|
|
v[x] = v[x] << 1;
|
|
}
|
|
|
|
void Interpreter::skip_if_vx_neq_vy(const uint8_t x, const uint8_t y) {
|
|
if (v[x] != v[y]) {
|
|
pc += 2;
|
|
}
|
|
}
|
|
|
|
void Interpreter::set_index_val(const uint16_t nnn) {
|
|
i = nnn;
|
|
}
|
|
|
|
void Interpreter::jump_with_offset(const uint16_t nnn) {
|
|
pc = nnn + v[0];
|
|
}
|
|
|
|
void Interpreter::jump_with_offset_super_chip(const uint8_t x, const uint16_t nnn) {
|
|
pc = nnn + v[x];
|
|
}
|
|
|
|
void Interpreter::set_random_value(const uint8_t x, const uint8_t kk) {
|
|
auto random = std::uniform_int_distribution<uint8_t>(0, 0xFF);
|
|
const auto value = random(random_generator);
|
|
|
|
v[x] = value & kk;
|
|
}
|
|
|
|
void Interpreter::display_sprite(uint8_t x, uint8_t y, const uint8_t n) {
|
|
uint8_t start_x = v[x] & 63;
|
|
uint8_t start_y = v[y] & 31;
|
|
v[0xF] = 0;
|
|
|
|
for (auto row = 0; row < n; row++) {
|
|
auto current_y = start_y + row;
|
|
|
|
if (current_y > 31) {
|
|
break;
|
|
}
|
|
|
|
const auto sprite_byte = memory[i + row];
|
|
|
|
for (auto bit = 0; bit < 8; bit++) {
|
|
auto current_x = start_x + bit;
|
|
|
|
if (current_x > 63) {
|
|
break;
|
|
}
|
|
|
|
const auto pixel = (sprite_byte >> (7 - bit)) & 0x01;
|
|
const auto index = (current_y * 64) + current_x;
|
|
|
|
if (pixel) {
|
|
if (display[index]) {
|
|
display.set(index, false);
|
|
v[0xF] = 1;
|
|
} else {
|
|
display.set(index, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Interpreter::skip_if_key_pressed(const uint8_t x) {
|
|
if (keyboard & (1 << x)) {
|
|
pc += 2;
|
|
}
|
|
}
|
|
|
|
void Interpreter::skip_if_key_not_pressed(const uint8_t x) {
|
|
if (!(keyboard & (1 << x))) {
|
|
pc += 2;
|
|
}
|
|
}
|
|
|
|
void Interpreter::load_vx_dt(const uint8_t x) {
|
|
v[x] = dt;
|
|
}
|
|
|
|
void Interpreter::get_key(const uint8_t x) {
|
|
if (keyboard == 0) {
|
|
pc -= 2;
|
|
return;
|
|
}
|
|
|
|
for (auto key = 0; key < 16; key++) {
|
|
if (keyboard & (1 << key)) {
|
|
v[x] = key;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Interpreter::load_dt_vx(const uint8_t x) {
|
|
dt = v[x];
|
|
}
|
|
|
|
void Interpreter::load_st_vx(const uint8_t x) {
|
|
st = v[x];
|
|
}
|
|
|
|
void Interpreter::add_i_vx(const uint8_t x) {
|
|
i = i + v[x];
|
|
}
|
|
|
|
void Interpreter::font_sprite_location(const uint8_t x) {
|
|
i = ((v[x] & 0xF) * 5) + 0x50;
|
|
}
|
|
|
|
void Interpreter::calculate_bcd(const uint8_t x) {
|
|
const auto number = v[x];
|
|
memory[i] = number / 100;
|
|
memory[i + 1] = (number - (memory[i] * 100)) / 10;
|
|
memory[i + 2] = number - (memory[i] * 100) - (memory[i + 1] * 10);
|
|
}
|
|
|
|
void Interpreter::store_registers_to_memory(const uint8_t x) {
|
|
bool use_quirk = quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_STORE_AND_LOAD);
|
|
|
|
for (auto reg = 0; reg <= x; reg++) {
|
|
if (use_quirk) {
|
|
memory[i] = v[reg];
|
|
i++;
|
|
} else {
|
|
memory[i + reg] = v[reg];
|
|
}
|
|
}
|
|
}
|
|
|
|
void Interpreter::load_registers_from_memory(const uint8_t x) {
|
|
bool use_quirk = quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_STORE_AND_LOAD);
|
|
|
|
for (auto reg = 0; reg <= x; reg++) {
|
|
if (use_quirk) {
|
|
v[reg] = memory[i];
|
|
i++;
|
|
} else {
|
|
v[reg] = memory[i + reg];
|
|
}
|
|
}
|
|
}
|