From 7069e0ae49192182aea3cbcd08fc93246b65dde6 Mon Sep 17 00:00:00 2001 From: Pablo Rodriguez Date: Tue, 26 Nov 2024 06:52:49 -0500 Subject: [PATCH] started work on the CHIP-8 interpreter --- Buzzer.cpp => BuzzerSDL.cpp | 16 ++-- Buzzer.hpp => BuzzerSDL.hpp | 12 +-- CMakeLists.txt | 2 +- Interpreter.cpp | 160 ++++++++++++++++++++++++++++++++++++ Interpreter.hpp | 51 ++++++++++++ Peripherals.hpp | 41 +++++++++ main.cpp | 4 +- 7 files changed, 270 insertions(+), 16 deletions(-) rename Buzzer.cpp => BuzzerSDL.cpp (84%) rename Buzzer.hpp => BuzzerSDL.hpp (66%) create mode 100644 Interpreter.cpp create mode 100644 Interpreter.hpp create mode 100644 Peripherals.hpp diff --git a/Buzzer.cpp b/BuzzerSDL.cpp similarity index 84% rename from Buzzer.cpp rename to BuzzerSDL.cpp index 5f72a2f..db2fbab 100644 --- a/Buzzer.cpp +++ b/BuzzerSDL.cpp @@ -1,15 +1,15 @@ -#include "Buzzer.hpp" +#include "BuzzerSDL.hpp" #include #include #include #include -void SDLCALL Buzzer::audioCallback(void *userdata, Uint8 *stream, int len) { - static_cast(userdata)->copySamples(stream, len); +void SDLCALL BuzzerSDL::audioCallback(void *userdata, Uint8 *stream, int len) { + static_cast(userdata)->copySamples(stream, len); } -Buzzer::Buzzer(unsigned buzzerFrequency) { +BuzzerSDL::BuzzerSDL(unsigned buzzerFrequency) { SDL_AudioSpec desired; SDL_AudioSpec obtained; @@ -42,19 +42,19 @@ Buzzer::Buzzer(unsigned buzzerFrequency) { miCurrentSample = 0; } -Buzzer::~Buzzer() { +BuzzerSDL::~BuzzerSDL() { SDL_CloseAudioDevice(mAudioDevice); } -void Buzzer::on() { +void BuzzerSDL::on() { SDL_PauseAudioDevice(mAudioDevice, SDL_FALSE); } -void Buzzer::off() { +void BuzzerSDL::off() { SDL_PauseAudioDevice(mAudioDevice, SDL_TRUE); } -void Buzzer::copySamples(Uint8 *stream, int len) { +void BuzzerSDL::copySamples(Uint8 *stream, int len) { // Copy len samples from the current position of the buffer, // looping back to the start of the buffer when needed. while(len > 0) { diff --git a/Buzzer.hpp b/BuzzerSDL.hpp similarity index 66% rename from Buzzer.hpp rename to BuzzerSDL.hpp index a789893..0182e6b 100644 --- a/Buzzer.hpp +++ b/BuzzerSDL.hpp @@ -3,15 +3,17 @@ #include #include -class Buzzer { +#include "Peripherals.hpp" + +class BuzzerSDL : public chocochip8::Buzzer { public: using sample_t = Sint16; - Buzzer(unsigned frequency); - ~Buzzer(); + BuzzerSDL(unsigned frequency); + ~BuzzerSDL(); - void on(); - void off(); + void on() override; + void off() override; private: static void SDLCALL audioCallback(void *userdata, Uint8 *stream, int len); diff --git a/CMakeLists.txt b/CMakeLists.txt index e1078f6..1187052 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,5 +5,5 @@ SET(CMAKE_BUILD_TYPE Debug) add_subdirectory(submodules/sdl2) SET(CMAKE_EXPORT_COMPILE_COMMANDS ON) -add_executable(chocochip8 main.cpp Buzzer.cpp) +add_executable(chocochip8 main.cpp Interpreter.cpp BuzzerSDL.cpp) target_link_libraries(chocochip8 SDL2::SDL2-static) diff --git a/Interpreter.cpp b/Interpreter.cpp new file mode 100644 index 0000000..e987f91 --- /dev/null +++ b/Interpreter.cpp @@ -0,0 +1,160 @@ +#include "Interpreter.hpp" +#include +#include + +namespace chocochip8 { + +Interpreter::Interpreter(unsigned ticksPerSecond, Display &display, Buzzer &buzzer, Keypad &keypad): + mvMemory(scMemorySize), + mCallStack{}, + mrDisplay{display}, + mrBuzzer{buzzer}, + mrKeypad{keypad}, + mcTicksPerSecond{ticksPerSecond}, + mvSpecialReg{}, + mvReg{} {} + +void Interpreter::tick() { + constexpr Opcode opcodeMap[16] = { + Opcode::SET, Opcode::OR, Opcode::AND, Opcode::XOR, + Opcode::ADD, Opcode::SUB, Opcode::RSH, Opcode::SUB2, + Opcode::UNIMPL, Opcode::UNIMPL, Opcode::UNIMPL, Opcode::UNIMPL, + Opcode::UNIMPL, Opcode::UNIMPL, Opcode::LSH, Opcode::UNIMPL, + }; + + unsigned pc = mvSpecialReg[SR_PC]; + unsigned inst = (mvMemory[pc] << 8) | mvMemory[pc + 1]; + unsigned reg1 = (inst & 0x0F00) >> 8; + unsigned reg2 = (inst & 0x00F0) >> 4; + unsigned imm1 = (inst & 0x000F); + unsigned imm2 = (inst & 0x00FF); + unsigned imm3 = (inst & 0x0FFF); + + mvSpecialReg[SR_PC] += 2; + switch(inst & 0xF000) { + case 0x0000: // 0NNN - call machine language routine + if(inst == 0x00E0) { + // clear display + for(auto &scanline : *mrDisplay.mpFramebuffer) { + scanline.reset(); + } + } else if(inst == 0x00EE) { + // return from subroutine + mvSpecialReg[SR_PC] = mCallStack.top(); + mCallStack.pop(); + } else { + throw std::invalid_argument("not implemented"); + } + break; + case 0x1000: // 1NNN - unconditional jump + mvSpecialReg[SR_PC] = imm3; + break; + case 0x2000: // 2NNN - call subroutine + mCallStack.push(pc); + mvSpecialReg[SR_PC] = imm3; + break; + case 0x3000: // 3XNN - skip if equal immediate + executeArithmetic(Opcode::JEQ, reg1, imm2); + break; + case 0x4000: // 4XNN - skip if nonequal immediate + executeArithmetic(Opcode::JNEQ, reg1, imm2); + break; + case 0x5000: // 5XY0 - skip if equal + executeArithmetic(Opcode::JEQ, reg1, mvReg[reg2]); + break; + case 0x6000: // 6XNN - load immediate + executeArithmetic(Opcode::SET, reg1, imm2); + break; + case 0x7000: // 7XNN - increment + executeArithmetic(Opcode::ADD, reg1, imm2); + break; + case 0x8000: // 8XNN - general arithmetic + executeArithmetic(opcodeMap[imm1], reg1, mvReg[reg2]); + break; + case 0x9000: // 9XY0 - skip if nonequal + executeArithmetic(Opcode::JNEQ, reg1, mvReg[reg2]); + break; + case 0xA000: // ANNN - load I + mvSpecialReg[SR_I] = imm3; + break; + case 0xB000: // BNNN - jump indirect + mvSpecialReg[SR_I] = mvReg[R_V0] + imm3; + break; + case 0xC000: // CXNN - load random + executeArithmetic(Opcode::RAND, reg1, imm2); + break; + case 0xD000: // DXYN - draw + break; + case 0xE000: // EX9E, EXA1 - keypad access + break; + case 0xF000: // several unique instructions + break; + } + + if(mvSpecialReg[SR_T1] > 0) { + mvSpecialReg[SR_T1] -= 1; + } + + if(mvSpecialReg[SR_T2] > 0) { + mvSpecialReg[SR_T2] -= 1; + if(mvSpecialReg[SR_T2] == 0) { + mrBuzzer.off(); + } + } +} + +void Interpreter::loadProgram(uint16_t where, char const* data, size_t count) { + if(where + count > scMemorySize) { + throw std::out_of_range("program exceeds memory bounds or capacity"); + } + memcpy(mvMemory.data(), data, count); +} + +void Interpreter::executeArithmetic(Opcode opcode, int iReg, uint8_t operand) { + uint8_t tmp; + switch(opcode) { + case Opcode::SET: mvReg[iReg] = operand; break; + case Opcode::AND: mvReg[iReg] &= operand; break; + case Opcode::OR: mvReg[iReg] |= operand; break; + case Opcode::XOR: mvReg[iReg] ^= operand; break; + case Opcode::RAND: mvReg[iReg] = rand() & operand; break; + case Opcode::LSH: + mvReg[R_VF] = (mvReg[iReg] & 0x80) ? 1 : 0; + mvReg[iReg] <<= 1; + break; + case Opcode::RSH: + mvReg[R_VF] = (mvReg[iReg] & 0x01) ? 1 : 0; + mvReg[iReg] >>= 1; + break; + case Opcode::ADD: + tmp = mvReg[iReg] + operand; + mvReg[R_VF] = (tmp < mvReg[iReg]) ? 1 : 0; + mvReg[iReg] = tmp; + break; + case Opcode::SUB: + tmp = mvReg[iReg] - operand; + mvReg[R_VF] = (tmp > mvReg[iReg]) ? 1 : 0; + mvReg[iReg] = tmp; + break; + case Opcode::SUB2: + tmp = operand - mvReg[iReg]; + mvReg[R_VF] = (tmp > operand) ? 1 : 0; + mvReg[iReg] = tmp; + break; + case Opcode::JEQ: + if(mvReg[iReg] == operand) { + mvSpecialReg[SR_PC] += 2; + } + break; + case Opcode::JNEQ: + if(mvReg[iReg] != operand) { + mvSpecialReg[SR_PC] += 2; + } + break; + case Opcode::UNIMPL: + throw std::invalid_argument("invalid opcode"); + break; + } +} + +}; // namespace chochochip8 diff --git a/Interpreter.hpp b/Interpreter.hpp new file mode 100644 index 0000000..8260797 --- /dev/null +++ b/Interpreter.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include "Peripherals.hpp" + +#include +#include +#include + +namespace chocochip8 { + +class Interpreter { +public: + constexpr static uint16_t scResetVector = 0x0200; + constexpr static size_t scMemorySize = 4096; + +private: + enum { + R_V0, R_V1, R_V2, R_V3, R_V4, R_V5, R_V6, R_V7, + R_V8, R_V9, R_VA, R_VB, R_VC, R_VD, R_VE, R_VF, + R_COUNT + }; + + enum { + SR_PC, SR_I, SR_T1, SR_T2, + SR_COUNT + }; + + enum class Opcode { + SET, ADD, SUB, SUB2, AND, OR, XOR, LSH, RSH, RAND, JEQ, JNEQ, UNIMPL + }; + +public: + Interpreter(unsigned ticksPerSecond, Display &display, Buzzer &buzzer, Keypad &keypad); + void tick(); + void loadProgram(uint16_t where, char const* data, size_t count); + +private: + void executeArithmetic(Opcode opcode, int iReg, uint8_t operand); + +private: + std::vector mvMemory; + std::stack mCallStack; + Display &mrDisplay; + Buzzer &mrBuzzer; + Keypad &mrKeypad; + const unsigned mcTicksPerSecond; + unsigned mvSpecialReg[SR_COUNT]; + uint8_t mvReg[R_COUNT]; +}; // class Interpreter + +}; // namespace chocohip8 diff --git a/Peripherals.hpp b/Peripherals.hpp new file mode 100644 index 0000000..b69c3ef --- /dev/null +++ b/Peripherals.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +namespace chocochip8 { + + using Scanline = std::bitset<128>; + using Framebuffer = std::array; + + enum class Key { + KEY_0, KEY_1, KEY_2, KEY_3, + KEY_4, KEY_5, KEY_6, KEY_7, + KEY_8, KEY_9, KEY_A, KEY_B, + KEY_C, KEY_D, KEY_E, KEY_F + }; + + class Display { + public: + friend class Interpreter; + Display(): mpFramebuffer{std::make_unique()} {} + virtual ~Display() = default; + protected: + std::unique_ptr mpFramebuffer; + }; + + class Buzzer { + public: + virtual ~Buzzer() = default; + virtual void on() = 0; + virtual void off() = 0; + }; + + class Keypad { + public: + virtual ~Keypad() = default; + virtual void isKeyPressed(Key key) = 0; + }; + +}; // namespace chocochip8 diff --git a/main.cpp b/main.cpp index 217d9e8..766ed8b 100644 --- a/main.cpp +++ b/main.cpp @@ -1,7 +1,7 @@ #include #include -#include "Buzzer.hpp" +#include "BuzzerSDL.hpp" int main(int argc, char* args[]) { if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { @@ -9,7 +9,7 @@ int main(int argc, char* args[]) { return 1; } - Buzzer buzzer(440); + BuzzerSDL buzzer(440); buzzer.on(); SDL_Event event;