Add code
This commit is contained in:
parent
b0dd532971
commit
52c86bee81
|
@ -0,0 +1,16 @@
|
||||||
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
project(chip8)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
chip8
|
||||||
|
Source/Chip8.cpp
|
||||||
|
Source/Main.cpp
|
||||||
|
Source/Platform.cpp)
|
||||||
|
|
||||||
|
target_compile_options(chip8 PRIVATE -Wall)
|
||||||
|
|
||||||
|
target_link_libraries(chip8 PRIVATE SDL2::SDL2)
|
|
@ -0,0 +1,583 @@
|
||||||
|
#include "Chip8.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
|
||||||
|
const unsigned int FONTSET_SIZE = 80;
|
||||||
|
const unsigned int FONTSET_START_ADDRESS = 0x50;
|
||||||
|
const unsigned int START_ADDRESS = 0x200;
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t fontset[FONTSET_SIZE] =
|
||||||
|
{
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Chip8::Chip8()
|
||||||
|
: randGen(std::chrono::system_clock::now().time_since_epoch().count())
|
||||||
|
{
|
||||||
|
// Initialize PC
|
||||||
|
pc = START_ADDRESS;
|
||||||
|
|
||||||
|
// Load fonts into memory
|
||||||
|
for (unsigned int i = 0; i < FONTSET_SIZE; ++i)
|
||||||
|
{
|
||||||
|
memory[FONTSET_START_ADDRESS + i] = fontset[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize RNG
|
||||||
|
randByte = std::uniform_int_distribution<uint8_t>(0, 255U);
|
||||||
|
|
||||||
|
// Set up function pointer table
|
||||||
|
table[0x0] = &Chip8::Table0;
|
||||||
|
table[0x1] = &Chip8::OP_1nnn;
|
||||||
|
table[0x2] = &Chip8::OP_2nnn;
|
||||||
|
table[0x3] = &Chip8::OP_3xkk;
|
||||||
|
table[0x4] = &Chip8::OP_4xkk;
|
||||||
|
table[0x5] = &Chip8::OP_5xy0;
|
||||||
|
table[0x6] = &Chip8::OP_6xkk;
|
||||||
|
table[0x7] = &Chip8::OP_7xkk;
|
||||||
|
table[0x8] = &Chip8::Table8;
|
||||||
|
table[0x9] = &Chip8::OP_9xy0;
|
||||||
|
table[0xA] = &Chip8::OP_Annn;
|
||||||
|
table[0xB] = &Chip8::OP_Bnnn;
|
||||||
|
table[0xC] = &Chip8::OP_Cxkk;
|
||||||
|
table[0xD] = &Chip8::OP_Dxyn;
|
||||||
|
table[0xE] = &Chip8::TableE;
|
||||||
|
table[0xF] = &Chip8::TableF;
|
||||||
|
|
||||||
|
table0[0x0] = &Chip8::OP_00E0;
|
||||||
|
table0[0xE] = &Chip8::OP_00EE;
|
||||||
|
|
||||||
|
table8[0x0] = &Chip8::OP_8xy0;
|
||||||
|
table8[0x1] = &Chip8::OP_8xy1;
|
||||||
|
table8[0x2] = &Chip8::OP_8xy2;
|
||||||
|
table8[0x3] = &Chip8::OP_8xy3;
|
||||||
|
table8[0x4] = &Chip8::OP_8xy4;
|
||||||
|
table8[0x5] = &Chip8::OP_8xy5;
|
||||||
|
table8[0x6] = &Chip8::OP_8xy6;
|
||||||
|
table8[0x7] = &Chip8::OP_8xy7;
|
||||||
|
table8[0xE] = &Chip8::OP_8xyE;
|
||||||
|
|
||||||
|
tableE[0x1] = &Chip8::OP_ExA1;
|
||||||
|
tableE[0xE] = &Chip8::OP_Ex9E;
|
||||||
|
|
||||||
|
tableF[0x07] = &Chip8::OP_Fx07;
|
||||||
|
tableF[0x0A] = &Chip8::OP_Fx0A;
|
||||||
|
tableF[0x15] = &Chip8::OP_Fx15;
|
||||||
|
tableF[0x18] = &Chip8::OP_Fx18;
|
||||||
|
tableF[0x1E] = &Chip8::OP_Fx1E;
|
||||||
|
tableF[0x29] = &Chip8::OP_Fx29;
|
||||||
|
tableF[0x33] = &Chip8::OP_Fx33;
|
||||||
|
tableF[0x55] = &Chip8::OP_Fx55;
|
||||||
|
tableF[0x65] = &Chip8::OP_Fx65;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::LoadROM(char const* filename)
|
||||||
|
{
|
||||||
|
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||||
|
|
||||||
|
if (file.is_open())
|
||||||
|
{
|
||||||
|
std::streampos size = file.tellg();
|
||||||
|
char* buffer = new char[size];
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
file.read(buffer, size);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
for (long i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
memory[START_ADDRESS + i] = buffer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::Cycle()
|
||||||
|
{
|
||||||
|
// Fetch
|
||||||
|
opcode = (memory[pc] << 8u) | memory[pc + 1];
|
||||||
|
|
||||||
|
// Increment the PC before we execute anything
|
||||||
|
pc += 2;
|
||||||
|
|
||||||
|
// Decode and Execute
|
||||||
|
((*this).*(table[(opcode & 0xF000u) >> 12u]))();
|
||||||
|
|
||||||
|
// Decrement the delay timer if it's been set
|
||||||
|
if (delayTimer > 0)
|
||||||
|
{
|
||||||
|
--delayTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement the sound timer if it's been set
|
||||||
|
if (soundTimer > 0)
|
||||||
|
{
|
||||||
|
--soundTimer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::Table0()
|
||||||
|
{
|
||||||
|
((*this).*(table0[opcode & 0x000Fu]))();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::Table8()
|
||||||
|
{
|
||||||
|
((*this).*(table8[opcode & 0x000Fu]))();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::TableE()
|
||||||
|
{
|
||||||
|
((*this).*(tableE[opcode & 0x000Fu]))();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::TableF()
|
||||||
|
{
|
||||||
|
((*this).*(tableF[opcode & 0x00FFu]))();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_NULL()
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Chip8::OP_00E0()
|
||||||
|
{
|
||||||
|
memset(video, 0, VIDEO_HEIGHT * VIDEO_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_00EE()
|
||||||
|
{
|
||||||
|
--sp;
|
||||||
|
pc = stack[sp];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_1nnn()
|
||||||
|
{
|
||||||
|
uint16_t address = opcode & 0x0FFFu;
|
||||||
|
|
||||||
|
pc = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_2nnn()
|
||||||
|
{
|
||||||
|
uint16_t address = opcode & 0x0FFFu;
|
||||||
|
|
||||||
|
stack[sp] = pc;
|
||||||
|
++sp;
|
||||||
|
pc = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_3xkk()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t byte = opcode & 0x00FFu;
|
||||||
|
|
||||||
|
if (registers[Vx] == byte)
|
||||||
|
{
|
||||||
|
pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_4xkk()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t byte = opcode & 0x00FFu;
|
||||||
|
|
||||||
|
if (registers[Vx] != byte)
|
||||||
|
{
|
||||||
|
pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_5xy0()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
|
||||||
|
if (registers[Vx] == registers[Vy])
|
||||||
|
{
|
||||||
|
pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_6xkk()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t byte = opcode & 0x00FFu;
|
||||||
|
|
||||||
|
registers[Vx] = byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_7xkk()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t byte = opcode & 0x00FFu;
|
||||||
|
|
||||||
|
registers[Vx] += byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_8xy0()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
|
||||||
|
registers[Vx] = registers[Vy];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_8xy1()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
|
||||||
|
registers[Vx] |= registers[Vy];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_8xy2()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
|
||||||
|
registers[Vx] &= registers[Vy];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_8xy3()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
|
||||||
|
registers[Vx] ^= registers[Vy];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_8xy4()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
|
||||||
|
uint16_t sum = registers[Vx] + registers[Vy];
|
||||||
|
|
||||||
|
if (sum > 255U)
|
||||||
|
{
|
||||||
|
registers[0xF] = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
registers[0xF] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
registers[Vx] = sum & 0xFFu;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_8xy5()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
|
||||||
|
if (registers[Vx] > registers[Vy])
|
||||||
|
{
|
||||||
|
registers[0xF] = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
registers[0xF] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
registers[Vx] -= registers[Vy];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_8xy6()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
// Save LSB in VF
|
||||||
|
registers[0xF] = (registers[Vx] & 0x1u);
|
||||||
|
|
||||||
|
registers[Vx] >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_8xy7()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
|
||||||
|
if (registers[Vy] > registers[Vx])
|
||||||
|
{
|
||||||
|
registers[0xF] = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
registers[0xF] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
registers[Vx] = registers[Vy] - registers[Vx];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_8xyE()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
// Save MSB in VF
|
||||||
|
registers[0xF] = (registers[Vx] & 0x80u) >> 7u;
|
||||||
|
|
||||||
|
registers[Vx] <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_9xy0()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
|
||||||
|
if (registers[Vx] != registers[Vy])
|
||||||
|
{
|
||||||
|
pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Annn()
|
||||||
|
{
|
||||||
|
uint16_t address = opcode & 0x0FFFu;
|
||||||
|
|
||||||
|
index = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Bnnn()
|
||||||
|
{
|
||||||
|
uint16_t address = opcode & 0x0FFFu;
|
||||||
|
|
||||||
|
pc = registers[0] + address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Cxkk()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t byte = opcode & 0x00FFu;
|
||||||
|
|
||||||
|
registers[Vx] = randByte(randGen) & byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Dxyn()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t Vy = (opcode & 0x00F0u) >> 4u;
|
||||||
|
uint8_t height = opcode & 0x000Fu;
|
||||||
|
|
||||||
|
// Wrap if going beyond screen boundaries
|
||||||
|
uint8_t xPos = registers[Vx] % VIDEO_WIDTH;
|
||||||
|
uint8_t yPos = registers[Vy] % VIDEO_HEIGHT;
|
||||||
|
|
||||||
|
registers[0xF] = 0;
|
||||||
|
|
||||||
|
for (unsigned int row = 0; row < height; ++row)
|
||||||
|
{
|
||||||
|
uint8_t spriteByte = memory[index + row];
|
||||||
|
|
||||||
|
for (unsigned int col = 0; col < 8; ++col)
|
||||||
|
{
|
||||||
|
uint8_t spritePixel = spriteByte & (0x80u >> col);
|
||||||
|
uint32_t* screenPixel = &video[(yPos + row) * VIDEO_WIDTH + (xPos + col)];
|
||||||
|
|
||||||
|
// Sprite pixel is on
|
||||||
|
if (spritePixel)
|
||||||
|
{
|
||||||
|
// Screen pixel also on - collision
|
||||||
|
if (*screenPixel == 0xFFFFFFFF)
|
||||||
|
{
|
||||||
|
registers[0xF] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Effectively XOR with the sprite pixel
|
||||||
|
*screenPixel ^= 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Ex9E()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
uint8_t key = registers[Vx];
|
||||||
|
|
||||||
|
if (keypad[key])
|
||||||
|
{
|
||||||
|
pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_ExA1()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
uint8_t key = registers[Vx];
|
||||||
|
|
||||||
|
if (!keypad[key])
|
||||||
|
{
|
||||||
|
pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Fx07()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
registers[Vx] = delayTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Fx0A()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
if (keypad[0])
|
||||||
|
{
|
||||||
|
registers[Vx] = 0;
|
||||||
|
}
|
||||||
|
else if (keypad[1])
|
||||||
|
{
|
||||||
|
registers[Vx] = 1;
|
||||||
|
}
|
||||||
|
else if (keypad[2])
|
||||||
|
{
|
||||||
|
registers[Vx] = 2;
|
||||||
|
}
|
||||||
|
else if (keypad[3])
|
||||||
|
{
|
||||||
|
registers[Vx] = 3;
|
||||||
|
}
|
||||||
|
else if (keypad[4])
|
||||||
|
{
|
||||||
|
registers[Vx] = 4;
|
||||||
|
}
|
||||||
|
else if (keypad[5])
|
||||||
|
{
|
||||||
|
registers[Vx] = 5;
|
||||||
|
}
|
||||||
|
else if (keypad[6])
|
||||||
|
{
|
||||||
|
registers[Vx] = 6;
|
||||||
|
}
|
||||||
|
else if (keypad[7])
|
||||||
|
{
|
||||||
|
registers[Vx] = 7;
|
||||||
|
}
|
||||||
|
else if (keypad[8])
|
||||||
|
{
|
||||||
|
registers[Vx] = 8;
|
||||||
|
}
|
||||||
|
else if (keypad[9])
|
||||||
|
{
|
||||||
|
registers[Vx] = 9;
|
||||||
|
}
|
||||||
|
else if (keypad[10])
|
||||||
|
{
|
||||||
|
registers[Vx] = 10;
|
||||||
|
}
|
||||||
|
else if (keypad[11])
|
||||||
|
{
|
||||||
|
registers[Vx] = 11;
|
||||||
|
}
|
||||||
|
else if (keypad[12])
|
||||||
|
{
|
||||||
|
registers[Vx] = 12;
|
||||||
|
}
|
||||||
|
else if (keypad[13])
|
||||||
|
{
|
||||||
|
registers[Vx] = 13;
|
||||||
|
}
|
||||||
|
else if (keypad[14])
|
||||||
|
{
|
||||||
|
registers[Vx] = 14;
|
||||||
|
}
|
||||||
|
else if (keypad[15])
|
||||||
|
{
|
||||||
|
registers[Vx] = 15;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pc -= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Fx15()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
delayTimer = registers[Vx];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Fx18()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
soundTimer = registers[Vx];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Fx1E()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
index += registers[Vx];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Fx29()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t digit = registers[Vx];
|
||||||
|
|
||||||
|
index = FONTSET_START_ADDRESS + (5 * digit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Fx33()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
uint8_t value = registers[Vx];
|
||||||
|
|
||||||
|
// Ones-place
|
||||||
|
memory[index + 2] = value % 10;
|
||||||
|
value /= 10;
|
||||||
|
|
||||||
|
// Tens-place
|
||||||
|
memory[index + 1] = value % 10;
|
||||||
|
value /= 10;
|
||||||
|
|
||||||
|
// Hundreds-place
|
||||||
|
memory[index] = value % 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Fx55()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i <= Vx; ++i)
|
||||||
|
{
|
||||||
|
memory[index + i] = registers[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chip8::OP_Fx65()
|
||||||
|
{
|
||||||
|
uint8_t Vx = (opcode & 0x0F00u) >> 8u;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i <= Vx; ++i)
|
||||||
|
{
|
||||||
|
registers[i] = memory[index + i];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
|
||||||
|
const unsigned int KEY_COUNT = 16;
|
||||||
|
const unsigned int MEMORY_SIZE = 4096;
|
||||||
|
const unsigned int REGISTER_COUNT = 16;
|
||||||
|
const unsigned int STACK_LEVELS = 16;
|
||||||
|
const unsigned int VIDEO_HEIGHT = 32;
|
||||||
|
const unsigned int VIDEO_WIDTH = 64;
|
||||||
|
|
||||||
|
|
||||||
|
class Chip8
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Chip8();
|
||||||
|
void LoadROM(char const* filename);
|
||||||
|
void Cycle();
|
||||||
|
|
||||||
|
uint8_t keypad[KEY_COUNT]{};
|
||||||
|
uint32_t video[VIDEO_WIDTH * VIDEO_HEIGHT]{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Table0();
|
||||||
|
void Table8();
|
||||||
|
void TableE();
|
||||||
|
void TableF();
|
||||||
|
|
||||||
|
// Do nothing
|
||||||
|
void OP_NULL();
|
||||||
|
|
||||||
|
// CLS
|
||||||
|
void OP_00E0();
|
||||||
|
|
||||||
|
// RET
|
||||||
|
void OP_00EE();
|
||||||
|
|
||||||
|
// JP address
|
||||||
|
void OP_1nnn();
|
||||||
|
|
||||||
|
// CALL address
|
||||||
|
void OP_2nnn();
|
||||||
|
|
||||||
|
// SE Vx, byte
|
||||||
|
void OP_3xkk();
|
||||||
|
|
||||||
|
// SNE Vx, byte
|
||||||
|
void OP_4xkk();
|
||||||
|
|
||||||
|
// SE Vx, Vy
|
||||||
|
void OP_5xy0();
|
||||||
|
|
||||||
|
// LD Vx, byte
|
||||||
|
void OP_6xkk();
|
||||||
|
|
||||||
|
// ADD Vx, byte
|
||||||
|
void OP_7xkk();
|
||||||
|
|
||||||
|
// LD Vx, Vy
|
||||||
|
void OP_8xy0();
|
||||||
|
|
||||||
|
// OR Vx, Vy
|
||||||
|
void OP_8xy1();
|
||||||
|
|
||||||
|
// AND Vx, Vy
|
||||||
|
void OP_8xy2();
|
||||||
|
|
||||||
|
// XOR Vx, Vy
|
||||||
|
void OP_8xy3();
|
||||||
|
|
||||||
|
// ADD Vx, Vy
|
||||||
|
void OP_8xy4();
|
||||||
|
|
||||||
|
// SUB Vx, Vy
|
||||||
|
void OP_8xy5();
|
||||||
|
|
||||||
|
// SHR Vx
|
||||||
|
void OP_8xy6();
|
||||||
|
|
||||||
|
// SUBN Vx, Vy
|
||||||
|
void OP_8xy7();
|
||||||
|
|
||||||
|
// SHL Vx
|
||||||
|
void OP_8xyE();
|
||||||
|
|
||||||
|
// SNE Vx, Vy
|
||||||
|
void OP_9xy0();
|
||||||
|
|
||||||
|
// LD I, address
|
||||||
|
void OP_Annn();
|
||||||
|
|
||||||
|
// JP V0, address
|
||||||
|
void OP_Bnnn();
|
||||||
|
|
||||||
|
// RND Vx, byte
|
||||||
|
void OP_Cxkk();
|
||||||
|
|
||||||
|
// DRW Vx, Vy, height
|
||||||
|
void OP_Dxyn();
|
||||||
|
|
||||||
|
// SKP Vx
|
||||||
|
void OP_Ex9E();
|
||||||
|
|
||||||
|
// SKNP Vx
|
||||||
|
void OP_ExA1();
|
||||||
|
|
||||||
|
// LD Vx, DT
|
||||||
|
void OP_Fx07();
|
||||||
|
|
||||||
|
// LD Vx, K
|
||||||
|
void OP_Fx0A();
|
||||||
|
|
||||||
|
// LD DT, Vx
|
||||||
|
void OP_Fx15();
|
||||||
|
|
||||||
|
// LD ST, Vx
|
||||||
|
void OP_Fx18();
|
||||||
|
|
||||||
|
// ADD I, Vx
|
||||||
|
void OP_Fx1E();
|
||||||
|
|
||||||
|
// LD F, Vx
|
||||||
|
void OP_Fx29();
|
||||||
|
|
||||||
|
// LD B, Vx
|
||||||
|
void OP_Fx33();
|
||||||
|
|
||||||
|
// LD [I], Vx
|
||||||
|
void OP_Fx55();
|
||||||
|
|
||||||
|
// LD Vx, [I]
|
||||||
|
void OP_Fx65();
|
||||||
|
|
||||||
|
uint8_t memory[MEMORY_SIZE]{};
|
||||||
|
uint8_t registers[REGISTER_COUNT]{};
|
||||||
|
uint16_t index{};
|
||||||
|
uint16_t pc{};
|
||||||
|
uint8_t delayTimer{};
|
||||||
|
uint8_t soundTimer{};
|
||||||
|
uint16_t stack[STACK_LEVELS]{};
|
||||||
|
uint8_t sp{};
|
||||||
|
uint16_t opcode{};
|
||||||
|
|
||||||
|
std::default_random_engine randGen;
|
||||||
|
std::uniform_int_distribution<uint8_t> randByte;
|
||||||
|
|
||||||
|
typedef void (Chip8::*Chip8Func)();
|
||||||
|
Chip8Func table[0xF + 1]{&Chip8::OP_NULL};
|
||||||
|
Chip8Func table0[0xE + 1]{&Chip8::OP_NULL};
|
||||||
|
Chip8Func table8[0xE + 1]{&Chip8::OP_NULL};
|
||||||
|
Chip8Func tableE[0xE + 1]{&Chip8::OP_NULL};
|
||||||
|
Chip8Func tableF[0x65 + 1]{&Chip8::OP_NULL};
|
||||||
|
};
|
|
@ -0,0 +1,47 @@
|
||||||
|
#include "Chip8.hpp"
|
||||||
|
#include "Platform.hpp"
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc != 4)
|
||||||
|
{
|
||||||
|
std::cerr << "Usage: " << argv[0] << " <Scale> <Delay> <ROM>\n";
|
||||||
|
std::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int videoScale = std::stoi(argv[1]);
|
||||||
|
int cycleDelay = std::stoi(argv[2]);
|
||||||
|
char const* romFilename = argv[3];
|
||||||
|
|
||||||
|
Platform platform("CHIP-8 Emulator", VIDEO_WIDTH * videoScale, VIDEO_HEIGHT * videoScale, VIDEO_WIDTH, VIDEO_HEIGHT);
|
||||||
|
|
||||||
|
Chip8 chip8;
|
||||||
|
chip8.LoadROM(romFilename);
|
||||||
|
|
||||||
|
int videoPitch = sizeof(chip8.video[0]) * VIDEO_WIDTH;
|
||||||
|
|
||||||
|
auto lastCycleTime = std::chrono::high_resolution_clock::now();
|
||||||
|
bool quit = false;
|
||||||
|
|
||||||
|
while (!quit)
|
||||||
|
{
|
||||||
|
quit = platform.ProcessInput(chip8.keypad);
|
||||||
|
|
||||||
|
auto currentTime = std::chrono::high_resolution_clock::now();
|
||||||
|
float dt = std::chrono::duration<float, std::chrono::milliseconds::period>(currentTime - lastCycleTime).count();
|
||||||
|
|
||||||
|
if (dt > cycleDelay)
|
||||||
|
{
|
||||||
|
lastCycleTime = currentTime;
|
||||||
|
|
||||||
|
chip8.Cycle();
|
||||||
|
|
||||||
|
platform.Update(chip8.video, videoPitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,229 @@
|
||||||
|
#include "Platform.hpp"
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
|
||||||
|
Platform::Platform(char const* title, int windowWidth, int windowHeight, int textureWidth, int textureHeight)
|
||||||
|
{
|
||||||
|
SDL_Init(SDL_INIT_VIDEO);
|
||||||
|
|
||||||
|
window = SDL_CreateWindow(title, 0, 0, windowWidth, windowHeight, SDL_WINDOW_SHOWN);
|
||||||
|
|
||||||
|
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
|
|
||||||
|
texture = SDL_CreateTexture(
|
||||||
|
renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, textureWidth, textureHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform::~Platform()
|
||||||
|
{
|
||||||
|
SDL_DestroyTexture(texture);
|
||||||
|
SDL_DestroyRenderer(renderer);
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Platform::Update(void const* buffer, int pitch)
|
||||||
|
{
|
||||||
|
SDL_UpdateTexture(texture, nullptr, buffer, pitch);
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Platform::ProcessInput(uint8_t* keys)
|
||||||
|
{
|
||||||
|
bool quit = false;
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
|
||||||
|
while (SDL_PollEvent(&event))
|
||||||
|
{
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
|
case SDL_QUIT:
|
||||||
|
{
|
||||||
|
quit = true;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
{
|
||||||
|
switch (event.key.keysym.sym)
|
||||||
|
{
|
||||||
|
case SDLK_ESCAPE:
|
||||||
|
{
|
||||||
|
quit = true;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_x:
|
||||||
|
{
|
||||||
|
keys[0] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_1:
|
||||||
|
{
|
||||||
|
keys[1] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_2:
|
||||||
|
{
|
||||||
|
keys[2] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_3:
|
||||||
|
{
|
||||||
|
keys[3] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_q:
|
||||||
|
{
|
||||||
|
keys[4] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_w:
|
||||||
|
{
|
||||||
|
keys[5] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_e:
|
||||||
|
{
|
||||||
|
keys[6] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_a:
|
||||||
|
{
|
||||||
|
keys[7] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_s:
|
||||||
|
{
|
||||||
|
keys[8] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_d:
|
||||||
|
{
|
||||||
|
keys[9] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_z:
|
||||||
|
{
|
||||||
|
keys[0xA] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_c:
|
||||||
|
{
|
||||||
|
keys[0xB] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_4:
|
||||||
|
{
|
||||||
|
keys[0xC] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_r:
|
||||||
|
{
|
||||||
|
keys[0xD] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_f:
|
||||||
|
{
|
||||||
|
keys[0xE] = 1;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_v:
|
||||||
|
{
|
||||||
|
keys[0xF] = 1;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDL_KEYUP:
|
||||||
|
{
|
||||||
|
switch (event.key.keysym.sym)
|
||||||
|
{
|
||||||
|
case SDLK_x:
|
||||||
|
{
|
||||||
|
keys[0] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_1:
|
||||||
|
{
|
||||||
|
keys[1] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_2:
|
||||||
|
{
|
||||||
|
keys[2] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_3:
|
||||||
|
{
|
||||||
|
keys[3] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_q:
|
||||||
|
{
|
||||||
|
keys[4] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_w:
|
||||||
|
{
|
||||||
|
keys[5] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_e:
|
||||||
|
{
|
||||||
|
keys[6] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_a:
|
||||||
|
{
|
||||||
|
keys[7] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_s:
|
||||||
|
{
|
||||||
|
keys[8] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_d:
|
||||||
|
{
|
||||||
|
keys[9] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_z:
|
||||||
|
{
|
||||||
|
keys[0xA] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_c:
|
||||||
|
{
|
||||||
|
keys[0xB] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_4:
|
||||||
|
{
|
||||||
|
keys[0xC] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_r:
|
||||||
|
{
|
||||||
|
keys[0xD] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_f:
|
||||||
|
{
|
||||||
|
keys[0xE] = 0;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case SDLK_v:
|
||||||
|
{
|
||||||
|
keys[0xF] = 0;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return quit;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
|
class SDL_Window;
|
||||||
|
class SDL_Renderer;
|
||||||
|
class SDL_Texture;
|
||||||
|
|
||||||
|
|
||||||
|
class Platform
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Platform(char const* title, int windowWidth, int windowHeight, int textureWidth, int textureHeight);
|
||||||
|
~Platform();
|
||||||
|
void Update(void const* buffer, int pitch);
|
||||||
|
bool ProcessInput(uint8_t* keys);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_Window* window{};
|
||||||
|
SDL_Renderer* renderer{};
|
||||||
|
SDL_Texture* texture{};
|
||||||
|
};
|
Loading…
Reference in New Issue