diff --git a/CubeChip (SDL).vcxproj b/CubeChip (SDL).vcxproj index 9711cd8..693f983 100644 --- a/CubeChip (SDL).vcxproj +++ b/CubeChip (SDL).vcxproj @@ -32,6 +32,7 @@ + @@ -54,6 +55,7 @@ + diff --git a/CubeChip (SDL).vcxproj.filters b/CubeChip (SDL).vcxproj.filters index d90abe9..e2da89a 100644 --- a/CubeChip (SDL).vcxproj.filters +++ b/CubeChip (SDL).vcxproj.filters @@ -83,6 +83,9 @@ Source Files\Systems\BYTEPUSHER\Cores + + Source Files\Systems\CHIP8\Cores + @@ -151,5 +154,8 @@ Source Files\Systems\BYTEPUSHER\Cores + + Source Files\Systems\CHIP8\Cores + \ No newline at end of file diff --git a/src/Assistants/HomeDirManager.cpp b/src/Assistants/HomeDirManager.cpp index c1c10d0..637012c 100644 --- a/src/Assistants/HomeDirManager.cpp +++ b/src/Assistants/HomeDirManager.cpp @@ -22,6 +22,11 @@ static auto getFileModTime(const fsPath& filePath) noexcept { return std::filesystem::last_write_time(filePath, error); } +static auto getFileSize(const fsPath& filePath) noexcept { + std::error_code error; + return std::filesystem::file_size(filePath, error); +} + /*==================================================================*/ #pragma region HomeDirManager Class diff --git a/src/Assistants/Map2D.hpp b/src/Assistants/Map2D.hpp index 05c5929..6e2694c 100644 --- a/src/Assistants/Map2D.hpp +++ b/src/Assistants/Map2D.hpp @@ -1149,24 +1149,19 @@ class Map2D final { }; #pragma endregion -private: - #pragma region Main Ctor - explicit Map2D(const paramS rows, const paramS cols) - : mRows{ rows } - , mCols{ cols } - , pData{ std::make_unique(rows * cols) } - {} - #pragma endregion - public: #pragma region Trivial Ctor Map2D() : Map2D{ 1, 1 } {} - Map2D(const integral auto rows, const integral auto cols) - : Map2D{ - std::max(1, std::abs(rows)), - std::max(1, std::abs(cols)) - } + Map2D(const paramS rows, const paramS cols) + : mRows{ std::max(1, std::abs(rows)) } + , mCols{ std::max(1, std::abs(cols)) } + , pData{ std::make_unique(mRows * mCols) } + {} + Map2D(const paramU rows, const paramU cols) + : mRows{ rows } + , mCols{ cols } + , pData{ std::make_unique(mRows * mCols) } {} #pragma endregion diff --git a/src/Systems/CHIP8/Chip8_CoreInterface.cpp b/src/Systems/CHIP8/Chip8_CoreInterface.cpp index 4859ebf..4c80bdc 100644 --- a/src/Systems/CHIP8/Chip8_CoreInterface.cpp +++ b/src/Systems/CHIP8/Chip8_CoreInterface.cpp @@ -96,6 +96,53 @@ bool Chip8_CoreInterface::keyHeld_P2(const u32 keyIndex) const noexcept { /*==================================================================*/ +void Chip8_CoreInterface::handlePreFrameInterrupt() noexcept { + switch (mInterrupt) + { + case Interrupt::FRAME: + mInterrupt = Interrupt::CLEAR; + mActiveCPF = std::abs(mActiveCPF); + return; + + case Interrupt::SOUND: + if (!mSoundTimer) { + mInterrupt = Interrupt::FINAL; + mActiveCPF = 0; + } + return; + + case Interrupt::DELAY: + if (!mSoundTimer) { + mInterrupt = Interrupt::CLEAR; + mActiveCPF = std::abs(mActiveCPF); + } + return; + } +} + +void Chip8_CoreInterface::handleEndFrameInterrupt() noexcept { + switch (mInterrupt) + { + case Interrupt::INPUT: + if (keyPressed(mInputReg, mTotalFrames)) { + mInterrupt = Interrupt::CLEAR; + mActiveCPF = std::abs(mActiveCPF); + mBuzzerTone = calcBuzzerTone(); + mSoundTimer = 2; + isBuzzerEnabled(true); + } + return; + + case Interrupt::ERROR: + case Interrupt::FINAL: + setCoreState(EmuState::HALTED); + mActiveCPF = 0; + return; + } +} + +/*==================================================================*/ + void Chip8_CoreInterface::processFrame() { if (isSystemStopped()) { return; } else [[likely]] { ++mTotalFrames; } @@ -111,6 +158,19 @@ void Chip8_CoreInterface::processFrame() { renderVideoData(); } +/*==================================================================*/ + +std::string Chip8_CoreInterface::formatOpcode(const u32 OP) const { + char buffer[5]; + std::format_to(buffer, "{:04X}{}", OP, '\0'); + return buffer; +} + +void Chip8_CoreInterface::instructionError(const u32 HI, const u32 LO) { + blog.newEntry(BLOG::INFO, "Unknown instruction: " + formatOpcode(HI << 8 | LO)); + triggerInterrupt(Interrupt::ERROR); +} + void Chip8_CoreInterface::triggerInterrupt(const Interrupt type) noexcept { mInterrupt = type; mActiveCPF = -std::abs(mActiveCPF); @@ -121,17 +181,95 @@ void Chip8_CoreInterface::triggerCritError(const std::string& msg) noexcept { triggerInterrupt(Interrupt::ERROR); } -std::string Chip8_CoreInterface::formatOpcode(const u32 OP) const { - char buffer[5]; - std::format_to(buffer, "{:04X}{}", OP, '\0'); - return buffer; +/*==================================================================*/ + +bool Chip8_CoreInterface::setPermaRegs(const s32 X) noexcept { + const auto path{ *sPermaRegsPath / HDM->getFileSHA1() }; + + if (std::filesystem::exists(path)) { + if (!std::filesystem::is_regular_file(path)) { + blog.newEntry(BLOG::ERROR, "SHA1 file is malformed: " + path.string()); + return true; + } + + char tempV[16]{}; + std::ifstream in(path, std::ios::binary); + + if (in.is_open()) { + in.seekg(0, std::ios::end); + const auto totalBytes{ in.tellg() }; + in.seekg(0, std::ios::beg); + + in.read(tempV, std::min(totalBytes, X)); + in.close(); + } else { + blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to read: " + path.string()); + return true; + } + + std::copy_n(mRegisterV, X, tempV); + + std::ofstream out(path, std::ios::binary); + if (out.is_open()) { + out.write(tempV, 16); + out.close(); + } else { + blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to write: " + path.string()); + return true; + } + } else { + std::ofstream out(path, std::ios::binary); + if (out.is_open()) { + out.write(reinterpret_cast(mRegisterV), X); + if (X < 16) { + const char padding[16]{}; + out.write(padding, 16 - X); + } + out.close(); + } else { + blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to write: " + path.string()); + return true; + } + } + return false; } -void Chip8_CoreInterface::instructionError(const u32 HI, const u32 LO) { - blog.newEntry(BLOG::INFO, "Unknown instruction: " + formatOpcode(HI << 8 | LO)); - triggerInterrupt(Interrupt::ERROR); +bool Chip8_CoreInterface::getPermaRegs(const s32 X) noexcept { + const auto path{ *sPermaRegsPath / HDM->getFileSHA1() }; + + if (std::filesystem::exists(path)) { + if (!std::filesystem::is_regular_file(path)) { + blog.newEntry(BLOG::ERROR, "SHA1 file is malformed: " + path.string()); + return true; + } + + std::ifstream in(path, std::ios::binary); + if (in.is_open()) { + in.seekg(0, std::ios::end); + const auto totalBytes{ static_cast(in.tellg()) }; + in.seekg(0, std::ios::beg); + + in.read(reinterpret_cast(mRegisterV), std::min(totalBytes, X)); + in.close(); + + if (totalBytes < X) { + std::fill_n(mRegisterV + totalBytes, X - totalBytes, u8()); + } + } else { + blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to read: " + path.string()); + return true; + } + } else { + std::fill_n( + std::execution::unseq, + mRegisterV, X, u8{} + ); + } + return false; } +/*==================================================================*/ + void Chip8_CoreInterface::copyGameToMemory( u8* dest, const u32 offset ) noexcept { @@ -151,3 +289,11 @@ void Chip8_CoreInterface::copyFontToMemory( cFontData, size, dest + offset ); } + +/*==================================================================*/ + +f32 Chip8_CoreInterface::calcBuzzerTone() const noexcept { + return (160.0f + 8.0f * ( + (mCurrentPC >> 1) + mStackTop + 1 & 0x3E + )) / ASB->getFrequency(); +} diff --git a/src/Systems/CHIP8/Chip8_CoreInterface.hpp b/src/Systems/CHIP8/Chip8_CoreInterface.hpp index 8ed4e00..f6aae1d 100644 --- a/src/Systems/CHIP8/Chip8_CoreInterface.hpp +++ b/src/Systems/CHIP8/Chip8_CoreInterface.hpp @@ -57,6 +57,7 @@ class Chip8_CoreInterface : public EmuInterface { bool mLoresExtended{}; bool mManualRefresh{}; bool mPixelTrailing{}; + bool mBuzzerEnabled{}; } Trait; enum class Interrupt { @@ -69,6 +70,15 @@ class Chip8_CoreInterface : public EmuInterface { ERROR, // end state, error occured }; + enum class Resolution { + ERROR, + HI, // 128 x 64 - 2:1 + LO, // 64 x 32 - 2:1 + TP, // 64 x 64 - 2:1 + FP, // 64 x 128 - 2:1 + MC, // 256 x 192 - 4:3 + }; + /*==================================================================*/ void addCoreState(const EmuState state) noexcept { Trait.mCoreState |= state; } @@ -84,9 +94,11 @@ class Chip8_CoreInterface : public EmuInterface { bool isLoresExtended() const noexcept { return Trait.mLoresExtended; } bool isManualRefresh() const noexcept { return Trait.mManualRefresh; } bool isPixelTrailing() const noexcept { return Trait.mPixelTrailing; } + bool isBuzzerEnabled() const noexcept { return Trait.mBuzzerEnabled; } void isLoresExtended(const bool state) noexcept { Trait.mLoresExtended = state; } void isManualRefresh(const bool state) noexcept { Trait.mManualRefresh = state; } void isPixelTrailing(const bool state) noexcept { Trait.mPixelTrailing = state; } + void isBuzzerEnabled(const bool state) noexcept { Trait.mBuzzerEnabled = state; } /*==================================================================*/ @@ -107,14 +119,15 @@ class Chip8_CoreInterface : public EmuInterface { } f32 mBuzzerTone{}; + u32 mPlanarMask{ 0x1 }; u32 mCurrentPC{}; u32 mRegisterI{}; - u8 mDelayTimer{}; - u8 mSoundTimer{}; + u32 mDelayTimer{}; + u32 mSoundTimer{}; - u8 mStackTop{}; + u32 mStackTop{}; u8* mInputReg{}; u8 mRegisterV[16]{}; @@ -128,11 +141,14 @@ class Chip8_CoreInterface : public EmuInterface { void triggerInterrupt(const Interrupt type) noexcept; void triggerCritError(const std::string& msg) noexcept; + bool setPermaRegs(const s32 X) noexcept; + bool getPermaRegs(const s32 X) noexcept; + void copyGameToMemory(u8* dest, const u32 offset) noexcept; void copyFontToMemory(u8* dest, const u32 offset, const u32 size) noexcept; - virtual void handlePreFrameInterrupt() noexcept = 0; - virtual void handleEndFrameInterrupt() noexcept = 0; + virtual void handlePreFrameInterrupt() noexcept; + virtual void handleEndFrameInterrupt() noexcept; virtual void handleTimerTick() noexcept = 0; virtual void instructionLoop() noexcept = 0; @@ -140,6 +156,10 @@ class Chip8_CoreInterface : public EmuInterface { virtual void renderAudioData() = 0; virtual void renderVideoData() = 0; + virtual void prepDisplayArea(const Resolution mode) = 0; + + f32 calcBuzzerTone() const noexcept; + public: Chip8_CoreInterface() noexcept; ~Chip8_CoreInterface() noexcept; diff --git a/src/Systems/CHIP8/Cores/CHIP8_MODERN.cpp b/src/Systems/CHIP8/Cores/CHIP8_MODERN.cpp index e7390ca..a6210d7 100644 --- a/src/Systems/CHIP8/Cores/CHIP8_MODERN.cpp +++ b/src/Systems/CHIP8/Cores/CHIP8_MODERN.cpp @@ -32,43 +32,6 @@ CHIP8_MODERN::CHIP8_MODERN() { /*==================================================================*/ -void CHIP8_MODERN::handlePreFrameInterrupt() noexcept { - switch (mInterrupt) - { - case Interrupt::FRAME: - mInterrupt = Interrupt::CLEAR; - mActiveCPF = std::abs(mActiveCPF); - return; - - case Interrupt::SOUND: - if (!mSoundTimer) { - mInterrupt = Interrupt::FINAL; - mActiveCPF = 0; - } - return; - } -} - -void CHIP8_MODERN::handleEndFrameInterrupt() noexcept { - switch (mInterrupt) - { - case Interrupt::INPUT: - if (keyPressed(mInputReg, mTotalFrames)) { - mInterrupt = Interrupt::CLEAR; - mActiveCPF = std::abs(mActiveCPF); - mBuzzerTone = calcAudioTone(); - mSoundTimer = 2; - } - return; - - case Interrupt::ERROR: - case Interrupt::FINAL: - setCoreState(EmuState::HALTED); - mActiveCPF = 0; - return; - } -} - void CHIP8_MODERN::handleTimerTick() noexcept { if (mDelayTimer) { --mDelayTimer; } if (mSoundTimer) { --mSoundTimer; } @@ -231,7 +194,7 @@ void CHIP8_MODERN::renderAudioData() { if (mSoundTimer) { for (auto& sample : samplesBuffer) { - sample = static_cast(wavePhase > 0.5f ? 64 : -64); + sample = static_cast(wavePhase > 0.5f ? 16 : -16); wavePhase = std::fmod(wavePhase + mBuzzerTone, 1.0f); } BVS->setFrameColor(cBitsColor[0], cBitsColor[1]); @@ -270,12 +233,6 @@ void CHIP8_MODERN::renderVideoData() { /*==================================================================*/ -f32 CHIP8_MODERN::calcAudioTone() const noexcept { - return (160.0f + 8.0f * ( - (mCurrentPC >> 1) + mStackTop + 1 & 0x3E - )) / ASB->getFrequency(); -} - void CHIP8_MODERN::nextInstruction() noexcept { mCurrentPC += 2; } @@ -583,7 +540,7 @@ void CHIP8_MODERN::jumpProgramTo(const u32 next) noexcept { // FX07 - set VX = delay timer void CHIP8_MODERN::instruction_Fx07(const s32 X) noexcept { - mRegisterV[X] = mDelayTimer; + mRegisterV[X] = static_cast(mDelayTimer); } // FX0A - set VX = key, wait for keypress void CHIP8_MODERN::instruction_Fx0A(const s32 X) noexcept { @@ -592,11 +549,11 @@ void CHIP8_MODERN::jumpProgramTo(const u32 next) noexcept { } // FX15 - set delay timer = VX void CHIP8_MODERN::instruction_Fx15(const s32 X) noexcept { - mDelayTimer = mRegisterV[X]; + mDelayTimer = static_cast(mRegisterV[X]); } // FX18 - set sound timer = VX void CHIP8_MODERN::instruction_Fx18(const s32 X) noexcept { - mBuzzerTone = calcAudioTone(); + mBuzzerTone = calcBuzzerTone(); mSoundTimer = mRegisterV[X] + (mRegisterV[X] == 1); } // FX1E - set I = I + VX @@ -614,18 +571,18 @@ void CHIP8_MODERN::jumpProgramTo(const u32 next) noexcept { writeMemoryI(mRegisterV[X] % 10, 2); } // FN55 - store V0..VX to RAM at I..I+N - void CHIP8_MODERN::instruction_FN55(const s32 X) noexcept { - for (auto idx{ 0 }; idx <= X; ++idx) + void CHIP8_MODERN::instruction_FN55(const s32 N) noexcept { + for (auto idx{ 0 }; idx <= N; ++idx) { writeMemoryI(mRegisterV[idx], idx); } if (!Quirk.idxRegNoInc) [[likely]] - { mRegisterI = mRegisterI + X + 1 & 0xFFF; } + { mRegisterI = mRegisterI + N + 1 & 0xFFF; } } // FN65 - load V0..VX from RAM at I..I+N - void CHIP8_MODERN::instruction_FN65(const s32 X) noexcept { - for (auto idx{ 0 }; idx <= X; ++idx) + void CHIP8_MODERN::instruction_FN65(const s32 N) noexcept { + for (auto idx{ 0 }; idx <= N; ++idx) { mRegisterV[idx] = readMemoryI(idx); } if (!Quirk.idxRegNoInc) [[likely]] - { mRegisterI = mRegisterI + X + 1 & 0xFFF; } + { mRegisterI = mRegisterI + N + 1 & 0xFFF; } } #pragma endregion diff --git a/src/Systems/CHIP8/Cores/CHIP8_MODERN.hpp b/src/Systems/CHIP8/Cores/CHIP8_MODERN.hpp index 7cd8fb2..9523bdd 100644 --- a/src/Systems/CHIP8/Cores/CHIP8_MODERN.hpp +++ b/src/Systems/CHIP8/Cores/CHIP8_MODERN.hpp @@ -46,16 +46,14 @@ class CHIP8_MODERN final : public Chip8_CoreInterface { } private: - void handlePreFrameInterrupt() noexcept override; - void handleEndFrameInterrupt() noexcept override; - void handleTimerTick() noexcept override; void instructionLoop() noexcept override; void renderAudioData() override; void renderVideoData() override; - f32 calcAudioTone() const noexcept; + void prepDisplayArea(const Resolution) override {} + void nextInstruction() noexcept; void jumpProgramTo(const u32 next) noexcept; @@ -234,9 +232,9 @@ class CHIP8_MODERN final : public Chip8_CoreInterface { // FX33 - store BCD of VX to RAM at I, I+1, I+2 void instruction_Fx33(const s32 X) noexcept; // FN55 - store V0..VN to RAM at I..I+N - void instruction_FN55(const s32 X) noexcept; + void instruction_FN55(const s32 N) noexcept; // FN65 - load V0..VN from RAM at I..I+N - void instruction_FN65(const s32 X) noexcept; + void instruction_FN65(const s32 N) noexcept; #pragma endregion /*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ diff --git a/src/Systems/CHIP8/Cores/XOCHIP.cpp b/src/Systems/CHIP8/Cores/XOCHIP.cpp new file mode 100644 index 0000000..f01a398 --- /dev/null +++ b/src/Systems/CHIP8/Cores/XOCHIP.cpp @@ -0,0 +1,849 @@ +/* + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include "../../../Assistants/BasicVideoSpec.hpp" +#include "../../../Assistants/BasicAudioSpec.hpp" +#include "../../../Assistants/Well512.hpp" + +#include "XOCHIP.hpp" + +/*==================================================================*/ + +XOCHIP::XOCHIP() + + : mAudioStep{ 4'000.0f / 128.0f / ASB->getFrequency() } + , mAudioTone{ mAudioStep } + , mDisplayBuffer{ + {cScreenSizeY, cScreenSizeX}, + {cScreenSizeY, cScreenSizeX}, + {cScreenSizeY, cScreenSizeX}, + {cScreenSizeY, cScreenSizeX}, + } +{ + if (getCoreState() != EmuState::FAILED) { + Quirk.wrapSprite = true; + + copyGameToMemory(mMemoryBank.data(), cGameLoadPos); + copyFontToMemory(mMemoryBank.data(), 0x0, 0x50); + + setDisplayResolution(cScreenSizeX, cScreenSizeY); + + BVS->setBackColor(cBitsColor[0]); + BVS->createTexture(cScreenSizeX, cScreenSizeY); + BVS->setAspectRatio(cScreenSizeX * 8, cScreenSizeY * 8, +2); + + std::copy_n( + std::execution::unseq, + cBitsColor, 16, + mBitsColor.data() + ); + + mCurrentPC = cStartOffset; + mFramerate = cRefreshRate; + mActiveCPF = cInstSpeedLo; + } +} + +/*==================================================================*/ + +void XOCHIP::handleTimerTick() noexcept { + if (mDelayTimer) { --mDelayTimer; } + if (mSoundTimer) { --mSoundTimer; } +} + +void XOCHIP::instructionLoop() noexcept { + + auto cycleCount{ 0 }; + for (; cycleCount < mActiveCPF; ++cycleCount) { + const auto HI{ mMemoryBank[mCurrentPC + 0u] }; + const auto LO{ mMemoryBank[mCurrentPC + 1u] }; + nextInstruction(); + + switch (HI >> 4) { + case 0x0: + switch (HI << 8 | LO) { + case 0x00C0: case 0x00C1: case 0x00C2: case 0x00C3: + case 0x00C4: case 0x00C5: case 0x00C6: case 0x00C7: + case 0x00C8: case 0x00C9: case 0x00CA: case 0x00CB: + case 0x00CC: case 0x00CD: case 0x00CE: case 0x00CF: + instruction_00CN(LO & 0xF); + break; + case 0x00D0: case 0x00D1: case 0x00D2: case 0x00D3: + case 0x00D4: case 0x00D5: case 0x00D6: case 0x00D7: + case 0x00D8: case 0x00D9: case 0x00DA: case 0x00DB: + case 0x00DC: case 0x00DD: case 0x00DE: case 0x00DF: + instruction_00DN(LO & 0xF); + break; + case 0x00E0: + instruction_00E0(); + break; + case 0x00EE: + instruction_00EE(); + break; + case 0x00FB: + instruction_00FB(); + break; + case 0x00FC: + instruction_00FC(); + break; + case 0x00FD: + instruction_00FD(); + break; + case 0x00FE: + instruction_00FE(); + break; + case 0x00FF: + instruction_00FF(); + break; + [[unlikely]] + default: instructionError(HI, LO); + } + break; + case 0x1: + instruction_1NNN(HI << 8 | LO); + break; + case 0x2: + instruction_2NNN(HI << 8 | LO); + break; + case 0x3: + instruction_3xNN(HI & 0xF, LO); + break; + case 0x4: + instruction_4xNN(HI & 0xF, LO); + break; + case 0x5: + switch (LO & 0xF) { + case 0x0: + instruction_5xy0(HI & 0xF, LO >> 4); + break; + case 0x2: + instruction_5xy2(HI & 0xF, LO >> 4); + break; + case 0x3: + instruction_5xy3(HI & 0xF, LO >> 4); + break; + case 0x4: + instruction_5xy4(HI & 0xF, LO >> 4); + break; + [[unlikely]] + default: + instructionError(HI, LO); + } + break; + case 0x6: + instruction_6xNN(HI & 0xF, LO); + break; + case 0x7: + instruction_7xNN(HI & 0xF, LO); + break; + case 0x8: + switch (LO & 0xF) { + case 0x0: + instruction_8xy0(HI & 0xF, LO >> 4); + break; + case 0x1: + instruction_8xy1(HI & 0xF, LO >> 4); + break; + case 0x2: + instruction_8xy2(HI & 0xF, LO >> 4); + break; + case 0x3: + instruction_8xy3(HI & 0xF, LO >> 4); + break; + case 0x4: + instruction_8xy4(HI & 0xF, LO >> 4); + break; + case 0x5: + instruction_8xy5(HI & 0xF, LO >> 4); + break; + case 0x7: + instruction_8xy7(HI & 0xF, LO >> 4); + break; + case 0x6: + instruction_8xy6(HI & 0xF, LO >> 4); + break; + case 0xE: + instruction_8xyE(HI & 0xF, LO >> 4); + break; + [[unlikely]] + default: instructionError(HI, LO); + } + break; + case 0x9: + if (LO & 0xF) [[unlikely]] { + instructionError(HI, LO); + } else { + instruction_9xy0(HI & 0xF, LO >> 4); + } + break; + case 0xA: + instruction_ANNN(HI << 8 | LO); + break; + case 0xB: + instruction_BNNN(HI << 8 | LO); + break; + case 0xC: + instruction_CxNN(HI & 0xF, LO); + break; + [[likely]] + case 0xD: + instruction_DxyN(HI & 0xF, LO >> 4, LO & 0xF); + break; + case 0xE: + switch (LO) { + case 0x9E: + instruction_Ex9E(HI & 0xF); + break; + case 0xA1: + instruction_ExA1(HI & 0xF); + break; + [[unlikely]] + default: instructionError(HI, LO); + } + break; + case 0xF: + switch (HI << 8 | LO) { + case 0xF000: + instruction_F000(); + break; + case 0xF002: + instruction_F002(); + break; + default: + switch (LO) { + case 0x01: + instruction_FN01(HI & 0xF); + break; + case 0x07: + instruction_Fx07(HI & 0xF); + break; + case 0x0A: + instruction_Fx0A(HI & 0xF); + break; + case 0x15: + instruction_Fx15(HI & 0xF); + break; + case 0x18: + instruction_Fx18(HI & 0xF); + break; + case 0x1E: + instruction_Fx1E(HI & 0xF); + break; + case 0x29: + instruction_Fx29(HI & 0xF); + break; + case 0x30: + instruction_Fx30(HI & 0xF); + break; + case 0x33: + instruction_Fx33(HI & 0xF); + break; + case 0x3A: + instruction_Fx3A(HI & 0xF); + break; + case 0x55: + instruction_FN55(HI & 0xF); + break; + case 0x65: + instruction_FN65(HI & 0xF); + break; + case 0x75: + instruction_FN75(HI & 0xF); + break; + case 0x85: + instruction_FN85(HI & 0xF); + break; + [[unlikely]] + default: instructionError(HI, LO); + } + break; + } + break; + } + } + mTotalCycles += cycleCount; +} + +void XOCHIP::renderAudioData() { + std::vector samplesBuffer \ + (static_cast(ASB->getSampleRate(cRefreshRate))); + + static f32 wavePhase{}; + + + if (mSoundTimer) { + if (isBuzzerEnabled()) { + for (auto& sample : samplesBuffer) { + sample = static_cast(wavePhase > 0.5f ? 16 : -16); + wavePhase = std::fmod(wavePhase + mBuzzerTone, 1.0f); + } + BVS->setFrameColor(cBitsColor[0], cBitsColor[1]); + } else { + for (auto& sample : samplesBuffer) { + const auto step{ static_cast(std::clamp(wavePhase * 128.0f, 0.0f, 127.0f)) }; + const auto mask{ 1 << (7 ^ step & 7) }; + sample = mSamplePattern[step >> 3] & mask ? 16 : -16; + wavePhase = std::fmod(wavePhase + mAudioTone, 1.0f); + } + BVS->setFrameColor(cBitsColor[0], cBitsColor[0]); + } + } else { + wavePhase = 0.0f; + isBuzzerEnabled(false); + BVS->setFrameColor(cBitsColor[0], cBitsColor[0]); + } + + ASB->pushAudioData(samplesBuffer); +} + +void XOCHIP::renderVideoData() { + std::vector textureBuffer(mDisplayW * mDisplayH); + + std::for_each( + std::execution::unseq, + textureBuffer.begin(), + textureBuffer.end(), + [&](u8& pixel) noexcept { + const auto idx{ &pixel - textureBuffer.data() }; + pixel = mDisplayBuffer[3].at_raw(idx) << 3 + | mDisplayBuffer[2].at_raw(idx) << 2 + | mDisplayBuffer[1].at_raw(idx) << 1 + | mDisplayBuffer[0].at_raw(idx); + } + ); + + BVS->modifyTexture(textureBuffer, + [this](const u32 pixel) noexcept { + return 0xFF000000 | mBitsColor[pixel]; + } + ); +} + +void XOCHIP::prepDisplayArea(const Resolution mode) { + isLoresExtended(mode != Resolution::LO); + + const auto W{ isLoresExtended() ? 128 : 64 }; + const auto H{ isLoresExtended() ? 64 : 32 }; + + BVS->createTexture(W, H); + setDisplayResolution(W, H); + + mDisplayBuffer[0].resize(false, H, W); + mDisplayBuffer[1].resize(false, H, W); + mDisplayBuffer[2].resize(false, H, W); + mDisplayBuffer[3].resize(false, H, W); +}; + +void XOCHIP::setColorBit332(const s32 bit, const s32 color) noexcept { + static constexpr u8 map3b[]{ 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xFF }; + static constexpr u8 map2b[]{ 0x00, 0x60, 0xA0, 0xFF }; + + mBitsColor[bit & 0xF] = map3b[color >> 5 & 0x7] << 16 // red + | map3b[color >> 2 & 0x7] << 8 // green + | map2b[color & 0x3]; // blue +} + +/*==================================================================*/ + +void XOCHIP::nextInstruction() noexcept { + mCurrentPC += 2; +} + +void XOCHIP::skipInstruction() noexcept { + mCurrentPC += NNNN() == 0xF000 ? 4 : 2; +} + +void XOCHIP::jumpProgramTo(const u32 next) noexcept { + const auto NNN{ next & 0xFFF }; + if (mCurrentPC - 2u != NNN) [[likely]] { + mCurrentPC = NNN & 0xFFF; + } else { + triggerInterrupt(Interrupt::SOUND); + } +} + +void XOCHIP::scrollDisplayUP(const s32 N) { + if (!mPlanarMask) { return; } + + for (auto P{ 0 }; P < 4; ++P) { + if (mPlanarMask & (1 << P)) + { mDisplayBuffer[P].shift(-N, 0); } + } +} +void XOCHIP::scrollDisplayDN(const s32 N) { + if (!mPlanarMask) { return; } + + for (auto P{ 0 }; P < 4; ++P) { + if (mPlanarMask & (1 << P)) + { mDisplayBuffer[P].shift(+N, 0); } + } +} +void XOCHIP::scrollDisplayLT(const s32) { + if (!mPlanarMask) { return; } + + for (auto P{ 0 }; P < 4; ++P) { + if (mPlanarMask & (1 << P)) + { mDisplayBuffer[P].shift(0, -4); } + } +} +void XOCHIP::scrollDisplayRT(const s32) { + if (!mPlanarMask) { return; } + + for (auto P{ 0 }; P < 4; ++P) { + if (mPlanarMask & (1 << P)) + { mDisplayBuffer[P].shift(0, +4); } + } +} + +/*==================================================================*/ + #pragma region 0 instruction branch + + // 00DN - scroll selected color plane N lines down + void XOCHIP::instruction_00CN(const s32 N) noexcept { + if (Quirk.waitScroll) [[unlikely]] + { triggerInterrupt(Interrupt::FRAME); } + if (N) { scrollDisplayDN(N); } + } + // 00DN - scroll selected color plane N lines up + void XOCHIP::instruction_00DN(const s32 N) noexcept { + if (Quirk.waitScroll) [[unlikely]] + { triggerInterrupt(Interrupt::FRAME); } + if (N) { scrollDisplayUP(N); } + } + // 00E0 - erase whole display + void XOCHIP::instruction_00E0() noexcept { + for (auto P{ 0 }; P < 4; ++P) { + if (!(mPlanarMask & (1 << P))) { continue; } + mDisplayBuffer[P].wipeAll(); + } + } + // 00EE - return from subroutine + void XOCHIP::instruction_00EE() noexcept { + mCurrentPC = mStackBank[--mStackTop & 0xF]; + } + // 00FB - scroll selected color plane 4 pixels right + void XOCHIP::instruction_00FB() noexcept { + if (Quirk.waitScroll) [[unlikely]] + { triggerInterrupt(Interrupt::FRAME); } + scrollDisplayRT(4); + } + // 00FC - scroll selected color plane 4 pixels left + void XOCHIP::instruction_00FC() noexcept { + if (Quirk.waitScroll) [[unlikely]] + { triggerInterrupt(Interrupt::FRAME); } + scrollDisplayLT(4); + } + // 00FD - stop signal + void XOCHIP::instruction_00FD() noexcept { + triggerInterrupt(Interrupt::SOUND); + } + // 00FE - display == 64*32, erase the screen + void XOCHIP::instruction_00FE() noexcept { + prepDisplayArea(Resolution::LO); + } + // 00FF - display == 128*64, erase the screen + void XOCHIP::instruction_00FF() noexcept { + prepDisplayArea(Resolution::HI); + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 1 instruction branch + + // 1NNN - jump to NNN + void XOCHIP::instruction_1NNN(const s32 NNN) noexcept { + jumpProgramTo(NNN); + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 2 instruction branch + + // 2NNN - call subroutine at NNN + void XOCHIP::instruction_2NNN(const s32 NNN) noexcept { + mStackBank[mStackTop++ & 0xF] = mCurrentPC; + jumpProgramTo(NNN); + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 3 instruction branch + + // 3XNN - skip next instruction if VX == NN + void XOCHIP::instruction_3xNN(const s32 X, const s32 NN) noexcept { + if (mRegisterV[X] == NN) { nextInstruction(); } + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 4 instruction branch + + // 4XNN - skip next instruction if VX != NN + void XOCHIP::instruction_4xNN(const s32 X, const s32 NN) noexcept { + if (mRegisterV[X] != NN) { nextInstruction(); } + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 5 instruction branch + + // 5XY0 - skip next instruction if VX == VY + void XOCHIP::instruction_5xy0(const s32 X, const s32 Y) noexcept { + if (mRegisterV[X] == mRegisterV[Y]) { nextInstruction(); } + } + // 5XY2 - store range of registers to memory + void XOCHIP::instruction_5xy2(const s32 X, const s32 Y) noexcept { + const auto dist{ std::abs(X - Y) + 1 }; + if (X < Y) { + for (auto Z{ 0 }; Z < dist; ++Z) { + writeMemoryI(mRegisterV[X + Z], Z); + } + } else { + for (auto Z{ 0 }; Z < dist; ++Z) { + writeMemoryI(mRegisterV[X - Z], Z); + } + } + } + // 5XY3 - load range of registers from memory + void XOCHIP::instruction_5xy3(const s32 X, const s32 Y) noexcept { + const auto dist{ std::abs(X - Y) + 1 }; + if (X < Y) { + for (auto Z{ 0 }; Z < dist; ++Z) { + mRegisterV[X + Z] = readMemoryI(Z); + } + } else { + for (auto Z{ 0 }; Z < dist; ++Z) { + mRegisterV[X - Z] = readMemoryI(Z); + } + } + } + // 5XY4 - load range of colors from memory + void XOCHIP::instruction_5xy4(const s32 X, const s32 Y) noexcept { + const auto dist{ std::abs(X - Y) + 1 }; + if (X < Y) { + for (auto Z{ 0 }; Z < dist; ++Z) { + setColorBit332(X + Z, readMemoryI(Z)); + } + } else { + for (auto Z{ 0 }; Z < dist; ++Z) { + setColorBit332(X - Z, readMemoryI(Z)); + } + } + BVS->setBackColor(mBitsColor[0]); + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 6 instruction branch + + // 6XNN - set VX = NN + void XOCHIP::instruction_6xNN(const s32 X, const s32 NN) noexcept { + mRegisterV[X] = static_cast(NN); + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 7 instruction branch + + // 7XNN - set VX = VX + NN + void XOCHIP::instruction_7xNN(const s32 X, const s32 NN) noexcept { + mRegisterV[X] += static_cast(NN); + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 8 instruction branch + + // 8XY0 - set VX = VY + void XOCHIP::instruction_8xy0(const s32 X, const s32 Y) noexcept { + mRegisterV[X] = mRegisterV[Y]; + } + // 8XY1 - set VX = VX | VY + void XOCHIP::instruction_8xy1(const s32 X, const s32 Y) noexcept { + mRegisterV[X] |= mRegisterV[Y]; + } + // 8XY2 - set VX = VX & VY + void XOCHIP::instruction_8xy2(const s32 X, const s32 Y) noexcept { + mRegisterV[X] &= mRegisterV[Y]; + } + // 8XY3 - set VX = VX ^ VY + void XOCHIP::instruction_8xy3(const s32 X, const s32 Y) noexcept { + mRegisterV[X] ^= mRegisterV[Y]; + } + // 8XY4 - set VX = VX + VY, VF = carry + void XOCHIP::instruction_8xy4(const s32 X, const s32 Y) noexcept { + const auto sum{ mRegisterV[X] + mRegisterV[Y] }; + mRegisterV[X] = static_cast(sum); + mRegisterV[0xF] = static_cast(sum >> 8); + } + // 8XY5 - set VX = VX - VY, VF = !borrow + void XOCHIP::instruction_8xy5(const s32 X, const s32 Y) noexcept { + const bool nborrow{ mRegisterV[X] >= mRegisterV[Y] }; + mRegisterV[X] = mRegisterV[X] - mRegisterV[Y]; + mRegisterV[0xF] = nborrow; + } + // 8XY7 - set VX = VY - VX, VF = !borrow + void XOCHIP::instruction_8xy7(const s32 X, const s32 Y) noexcept { + const bool nborrow{ mRegisterV[Y] >= mRegisterV[X] }; + mRegisterV[X] = mRegisterV[Y] - mRegisterV[X]; + mRegisterV[0xF] = nborrow; + } + // 8XY6 - set VX = VY >> 1, VF = carry + void XOCHIP::instruction_8xy6(const s32 X, const s32 Y) noexcept { + if (!Quirk.shiftVX) { mRegisterV[X] = mRegisterV[Y]; } + const bool lsb{ (mRegisterV[X] & 1) == 1 }; + mRegisterV[X] = mRegisterV[X] >> 1; + mRegisterV[0xF] = lsb; + } + // 8XYE - set VX = VY << 1, VF = carry + void XOCHIP::instruction_8xyE(const s32 X, const s32 Y) noexcept { + if (!Quirk.shiftVX) { mRegisterV[X] = mRegisterV[Y]; } + const bool msb{ (mRegisterV[X] >> 7) == 1 }; + mRegisterV[X] = mRegisterV[X] << 1; + mRegisterV[0xF] = msb; + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 9 instruction branch + + // 9XY0 - skip next instruction if VX != VY + void XOCHIP::instruction_9xy0(const s32 X, const s32 Y) noexcept { + if (mRegisterV[X] != mRegisterV[Y]) { nextInstruction(); } + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region A instruction branch + + // ANNN - set I = NNN + void XOCHIP::instruction_ANNN(const s32 NNN) noexcept { + mRegisterI = NNN & 0xFFF; + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region B instruction branch + + // BXNN - jump to NNN + V0 + void XOCHIP::instruction_BNNN(const s32 NNN) noexcept { + jumpProgramTo(NNN + mRegisterV[0]); + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region C instruction branch + + // CXNN - set VX = rnd(256) & NN + void XOCHIP::instruction_CxNN(const s32 X, const s32 NN) noexcept { + mRegisterV[X] = static_cast(Wrand->get() & NN); + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region D instruction branch + + void XOCHIP::drawByte( + s32 X, s32 Y, const s32 P, + const u32 DATA + ) noexcept { + switch (DATA) { + [[unlikely]] + case 0b00000000: + return; + + [[unlikely]] + case 0b10000000: + if (Quirk.wrapSprite) { X &= mDisplayWb; } + if (X < mDisplayW) { + if (!((mDisplayBuffer[P].at_raw(Y, X) ^= 1) & 1)) + { mRegisterV[0xF] = 1; } + } + return; + + [[likely]] + default: + if (Quirk.wrapSprite) { X &= mDisplayWb; } + else if (X >= mDisplayW) { return; } + + for (auto B{ 0 }; B < 8; ++B, ++X &= mDisplayWb) { + if (DATA & 0x80 >> B) { + if (!((mDisplayBuffer[P].at_raw(Y, X) ^= 1) & 1)) + { mRegisterV[0xF] = 1; } + } + if (!Quirk.wrapSprite && X == mDisplayWb) { return; } + } + return; + } + } + + // DXYN - draw N sprite rows at VX and VY + void XOCHIP::instruction_DxyN(const s32 X, const s32 Y, const s32 N) noexcept { + if (!mPlanarMask) { + mRegisterV[0xF] = 0; + } else { + const auto pX{ mRegisterV[X] & mDisplayWb }; + const auto pY{ mRegisterV[Y] & mDisplayHb }; + + mRegisterV[0xF] = 0; + + if (N == 0) { + for (auto P{ 0 }, I{ 0 }; P < 4; ++P) { + if (!(mPlanarMask & 1 << P)) { continue; } + + for (auto tN{ 0 }, tY{ pY }; tN < 16; ++tN) { + drawByte(pX + 0, tY, P, readMemoryI(I + 0)); + drawByte(pX + 8, tY, P, readMemoryI(I + 1)); + if (!Quirk.wrapSprite && tY == mDisplayHb) { break; } + else { I += 2; ++tY &= mDisplayHb; } + } + } + } else [[likely]] { + for (auto P{ 0 }, I{ 0 }; P < 4; ++P) { + if (!(mPlanarMask & 1 << P)) { continue; } + + for (auto tN{ 0 }, tY{ pY }; tN < N; ++tN) { + drawByte(pX, tY, P, readMemoryI(I)); + if (!Quirk.wrapSprite && tY == mDisplayHb) { break; } + else { I += 1; ++tY &= mDisplayHb; } + } + } + } + } + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region E instruction branch + + // EX9E - skip next instruction if key VX down (p1) + void XOCHIP::instruction_Ex9E(const s32 X) noexcept { + if (keyHeld_P1(mRegisterV[X])) { nextInstruction(); } + } + // EXA1 - skip next instruction if key VX up (p1) + void XOCHIP::instruction_ExA1(const s32 X) noexcept { + if (!keyHeld_P1(mRegisterV[X])) { nextInstruction(); } + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region F instruction branch + + // F000 - set I = NEXT NNNN then skip instruction + void XOCHIP::instruction_F000() noexcept { + mRegisterI = NNNN(); + nextInstruction(); + } + // F002 - load 16-byte audio pattern from RAM at I + void XOCHIP::instruction_F002() noexcept { + for (auto idx{ 0 }; idx < 16; ++idx) { + mSamplePattern[idx] = readMemoryI(idx); + } + isBuzzerEnabled(false); + } + // FN01 - set plane drawing to N + void XOCHIP::instruction_FN01(const s32 N) noexcept { + mPlanarMask = N; + } + // FX07 - set VX = delay timer + void XOCHIP::instruction_Fx07(const s32 X) noexcept { + mRegisterV[X] = static_cast(mDelayTimer); + } + // FX0A - set VX = key, wait for keypress + void XOCHIP::instruction_Fx0A(const s32 X) noexcept { + triggerInterrupt(Interrupt::INPUT); + mInputReg = &mRegisterV[X]; + } + // FX15 - set delay timer = VX + void XOCHIP::instruction_Fx15(const s32 X) noexcept { + mDelayTimer = static_cast(mRegisterV[X]); + } + // FX18 - set sound timer = VX + void XOCHIP::instruction_Fx18(const s32 X) noexcept { + mBuzzerTone = calcBuzzerTone(); + mSoundTimer = mRegisterV[X] + (mRegisterV[X] == 1); + } + // FX1E - set I = I + VX + void XOCHIP::instruction_Fx1E(const s32 X) noexcept { + mRegisterI = mRegisterI + mRegisterV[X] & 0xFFFF; + } + // FX29 - point I to 5 byte hex sprite from value in VX + void XOCHIP::instruction_Fx29(const s32 X) noexcept { + mRegisterI = (mRegisterV[X] & 0xF) * 5; + } + // FX30 - point I to 10 byte hex sprite from value in VX + void XOCHIP::instruction_Fx30(const s32 X) noexcept { + mRegisterI = (mRegisterV[X] & 0xF) * 10 + 80; + } + // FX33 - store BCD of VX to RAM at I, I+1, I+2 + void XOCHIP::instruction_Fx33(const s32 X) noexcept { + writeMemoryI(mRegisterV[X] / 100, 0); + writeMemoryI(mRegisterV[X] / 10 % 10, 1); + writeMemoryI(mRegisterV[X] % 10, 2); + } + // FX3A - set sound pitch = VX + void XOCHIP::instruction_Fx3A(const s32 X) noexcept { + const auto stepValue{ (mRegisterV[X] - 64.0f) / 48.0f }; + mAudioTone = mAudioStep * std::pow(2.0f, stepValue); + isBuzzerEnabled(false); + } + // FN55 - store V0..VX to RAM at I..I+N + void XOCHIP::instruction_FN55(const s32 N) noexcept { + for (auto idx{ 0 }; idx <= N; ++idx) + { writeMemoryI(mRegisterV[idx], idx); } + if (!Quirk.idxRegNoInc) [[likely]] + { mRegisterI = mRegisterI + N + 1 & 0xFFFF; } + } + // FN65 - load V0..VN from RAM at I..I+N + void XOCHIP::instruction_FN65(const s32 N) noexcept { + for (auto idx{ 0 }; idx <= N; ++idx) + { mRegisterV[idx] = readMemoryI(idx); } + if (!Quirk.idxRegNoInc) [[likely]] + { mRegisterI = mRegisterI + N + 1 & 0xFFFF; } + } + // FN75 - store V0..VN to the P flags + void XOCHIP::instruction_FN75(const s32 N) noexcept { + if (setPermaRegs(N + 1)) [[unlikely]] + { triggerCritError("Error :: Failed writing persistent registers!"); } + } + // FN85 - load V0..VN from the P flags + void XOCHIP::instruction_FN85(const s32 N) noexcept { + if (getPermaRegs(N + 1)) [[unlikely]] + { triggerCritError("Error :: Failed reading persistent registers!"); } + } + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ diff --git a/src/Systems/CHIP8/Cores/XOCHIP.hpp b/src/Systems/CHIP8/Cores/XOCHIP.hpp new file mode 100644 index 0000000..ddfda85 --- /dev/null +++ b/src/Systems/CHIP8/Cores/XOCHIP.hpp @@ -0,0 +1,299 @@ +/* + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include "../../../Assistants/Map2D.hpp" + +#include "../Chip8_CoreInterface.hpp" + +/*==================================================================*/ + +class XOCHIP final : public Chip8_CoreInterface { + static constexpr u32 cTotalMemory{ 65536 }; + static constexpr u32 cSafezoneOOB{ 32 }; + static constexpr u32 cGameLoadPos{ 512 }; + static constexpr u32 cStartOffset{ 512 }; + static constexpr f32 cRefreshRate{ 60.0f }; + static constexpr s32 cScreenSizeX{ 64 }; + static constexpr s32 cScreenSizeY{ 32 }; + static constexpr s32 cInstSpeedHi{ 50000 }; + static constexpr s32 cInstSpeedLo{ 1000 }; + +private: + std::array mBitsColor{}; + + void setColorBit332(const s32 bit, const s32 color) noexcept; + +/*==================================================================*/ + + std::array mSamplePattern{}; + + f32 mAudioStep{}; + f32 mAudioTone{}; + +/*==================================================================*/ + + Map2D mDisplayBuffer[4]; + + std::array + mMemoryBank{}; + + void writeMemoryI(const u32 value, const u32 pos) noexcept { + const auto index{ mRegisterI + pos }; + if (!(index & cTotalMemory)) [[likely]] + { mMemoryBank[index] = value & 0xFF; } + } + + auto readMemoryI(const u32 pos) const noexcept { + return mMemoryBank[mRegisterI + pos]; + } + +/*==================================================================*/ + + auto NNNN() const noexcept { return mMemoryBank[mCurrentPC] << 8 | mMemoryBank[mCurrentPC + 1]; } + +public: + XOCHIP(); + + static constexpr bool testGameSize(const usz size) noexcept { + return size + cGameLoadPos <= cTotalMemory; + } + +private: + void handleTimerTick() noexcept override; + void instructionLoop() noexcept override; + + void renderAudioData() override; + void renderVideoData() override; + + void prepDisplayArea(const Resolution mode) override; + + void nextInstruction() noexcept; + void skipInstruction() noexcept; + void jumpProgramTo(const u32 next) noexcept; + + void scrollDisplayUP(const s32 N); + void scrollDisplayDN(const s32 N); + void scrollDisplayLT(const s32); + void scrollDisplayRT(const s32); + +/*==================================================================*/ + #pragma region 0 instruction branch + + // 00DN - scroll selected color plane N lines down + void instruction_00CN(const s32 N) noexcept; + // 00DN - scroll selected color plane N lines up + void instruction_00DN(const s32 N) noexcept; + // 00E0 - erase whole display + void instruction_00E0() noexcept; + // 00EE - return from subroutine + void instruction_00EE() noexcept; + // 00FB - scroll selected color plane 4 pixels right + void instruction_00FB() noexcept; + // 00FC - scroll selected color plane 4 pixels left + void instruction_00FC() noexcept; + // 00FD - stop signal + void instruction_00FD() noexcept; + // 00FE - display == 64*32, erase the screen + void instruction_00FE() noexcept; + // 00FF - display == 128*64, erase the screen + void instruction_00FF() noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 1 instruction branch + + // 1NNN - jump to NNN + void instruction_1NNN(const s32 NNN) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 2 instruction branch + + // 2NNN - call subroutine at NNN + void instruction_2NNN(const s32 NNN) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 3 instruction branch + + // 3XNN - skip next instruction if VX == NN + void instruction_3xNN(const s32 X, const s32 NN) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 4 instruction branch + + // 4XNN - skip next instruction if VX != NN + void instruction_4xNN(const s32 X, const s32 NN) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 5 instruction branch + + // 5XY0 - skip next instruction if VX == VY + void instruction_5xy0(const s32 X, const s32 Y) noexcept; + // 5XY2 - store range of registers to memory *XOCHIP* + void instruction_5xy2(const s32 X, const s32 Y) noexcept; + // 5XY3 - load range of registers from memory *XOCHIP* + void instruction_5xy3(const s32 X, const s32 Y) noexcept; + // 5XY4 - load range of colors from memory *EXPERIMENTAL* + void instruction_5xy4(const s32 X, const s32 Y) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 6 instruction branch + + // 6XNN - set VX = NN + void instruction_6xNN(const s32 X, const s32 NN) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 7 instruction branch + + // 7XNN - set VX = VX + NN + void instruction_7xNN(const s32 X, const s32 NN) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 8 instruction branch + + // 8XY0 - set VX = VY + void instruction_8xy0(const s32 X, const s32 Y) noexcept; + // 8XY1 - set VX = VX | VY + void instruction_8xy1(const s32 X, const s32 Y) noexcept; + // 8XY2 - set VX = VX & VY + void instruction_8xy2(const s32 X, const s32 Y) noexcept; + // 8XY3 - set VX = VX ^ VY + void instruction_8xy3(const s32 X, const s32 Y) noexcept; + // 8XY4 - set VX = VX + VY, VF = carry + void instruction_8xy4(const s32 X, const s32 Y) noexcept; + // 8XY5 - set VX = VX - VY, VF = !borrow + void instruction_8xy5(const s32 X, const s32 Y) noexcept; + // 8XY7 - set VX = VY - VX, VF = !borrow + void instruction_8xy7(const s32 X, const s32 Y) noexcept; + // 8XY6 - set VX = VY >> 1, VF = carry + void instruction_8xy6(const s32 X, const s32 Y) noexcept; + // 8XYE - set VX = VY << 1, VF = carry + void instruction_8xyE(const s32 X, const s32 Y) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region 9 instruction branch + + // 9XY0 - skip next instruction if VX != VY + void instruction_9xy0(const s32 X, const s32 Y) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region A instruction branch + + // ANNN - set I = NNN + void instruction_ANNN(const s32 NNN) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region B instruction branch + + // BXNN - jump to NNN + V0 + void instruction_BNNN(const s32 NNN) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region C instruction branch + + // CXNN - set VX = rnd(256) & NN + void instruction_CxNN(const s32 X, const s32 NN) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region D instruction branch + + void drawByte(s32 X, s32 Y, const s32 P, const u32 DATA) noexcept; + + // DXYN - draw N sprite rows at VX and VY + void instruction_DxyN(const s32 X, const s32 Y, const s32 N) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region E instruction branch + + // EX9E - skip next instruction if key VX down (p1) + void instruction_Ex9E(const s32 X) noexcept; + // EXA1 - skip next instruction if key VX up (p1) + void instruction_ExA1(const s32 X) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ + +/*==================================================================*/ + #pragma region F instruction branch + + // F000 - set I = NEXT NNNN then skip instruction + void instruction_F000() noexcept; + // F002 - load 16-byte audio pattern from RAM at I + void instruction_F002() noexcept; + // FN01 - set plane drawing to N + void instruction_FN01(const s32 N) noexcept; + // FX07 - set VX = delay timer + void instruction_Fx07(const s32 X) noexcept; + // FX0A - set VX = key, wait for keypress + void instruction_Fx0A(const s32 X) noexcept; + // FX15 - set delay timer = VX + void instruction_Fx15(const s32 X) noexcept; + // FX18 - set sound timer = VX + void instruction_Fx18(const s32 X) noexcept; + // FX1E - set I = I + VX + void instruction_Fx1E(const s32 X) noexcept; + // FX29 - point I to 5 byte hex sprite from value in VX + void instruction_Fx29(const s32 X) noexcept; + // FX30 - point I to 10 byte hex sprite from value in VX + void instruction_Fx30(const s32 X) noexcept; + // FX33 - store BCD of VX to RAM at I, I+1, I+2 + void instruction_Fx33(const s32 X) noexcept; + // FX3A - set sound pitch = VX + void instruction_Fx3A(const s32 X) noexcept; + // FN55 - store V0..VN to RAM at I..I+N + void instruction_FN55(const s32 N) noexcept; + // FN65 - load V0..VN from RAM at I..I+N + void instruction_FN65(const s32 N) noexcept; + // FN75 - store V0..VN to the P flags + void instruction_FN75(const s32 N) noexcept; + // FN85 - load V0..VN from the P flags + void instruction_FN85(const s32 N) noexcept; + + #pragma endregion +/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/ +}; diff --git a/src/Systems/EmuInterface.hpp b/src/Systems/EmuInterface.hpp index 28556d4..4338d6f 100644 --- a/src/Systems/EmuInterface.hpp +++ b/src/Systems/EmuInterface.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/src/Systems/GameFileChecker.cpp b/src/Systems/GameFileChecker.cpp index bf9dc6f..6e78e6c 100644 --- a/src/Systems/GameFileChecker.cpp +++ b/src/Systems/GameFileChecker.cpp @@ -16,6 +16,7 @@ using json = nlohmann::json; #include "CHIP8/Cores/CHIP8_MODERN.hpp" +#include "CHIP8/Cores/XOCHIP.hpp" #include "BYTEPUSHER/Cores/BYTEPUSHER_STANDARD.hpp" @@ -41,7 +42,7 @@ std::unique_ptr GameFileChecker::constructCore() { try { switch (sEmuCore) { case GameCoreType::XOCHIP: - //return std::make_unique(); + return std::make_unique(); case GameCoreType::CHIP8E: //return std::make_unique(); @@ -202,8 +203,7 @@ bool GameFileChecker::validate( case (GameFileType::xo8): return testGame( - true, - //XOCHIP::testGameSize(size), + XOCHIP::testGameSize(size), GameCoreType::XOCHIP );