From 212e234460c8c18a8ddee36f8e4c929e711ddc9a Mon Sep 17 00:00:00 2001 From: Ian Burgmyer Date: Wed, 29 Jul 2020 03:16:33 -0400 Subject: [PATCH] CHIP-8 emulation. * Passes all of the tests I've run against it and plays a bunch of games! * Kind of a bad sound implementation, but it's good enough for CHIP-8. ;) --- CMakeLists.txt | 5 + libplip/Audio/PlipAudio.h | 1 + libplip/Core/Chip8/Chip8Instance.cpp | 100 ++++++++++++- libplip/Core/Chip8/Chip8Instance.h | 26 +++- libplip/Core/PlipCore.h | 4 +- libplip/Cpu/Chip8/Chip8.Ops.cpp | 264 +++++++++++++++++++++++++++++++++++ libplip/Cpu/Chip8/Chip8.cpp | 113 +++++++++++++++ libplip/Cpu/Chip8/Chip8.h | 79 +++++++++++ libplip/Cpu/PlipCpu.cpp | 26 ++++ libplip/Cpu/PlipCpu.h | 31 ++++ libplip/PlipEmulationException.h | 17 +++ libplip/PlipUtility.cpp | 25 ++++ libplip/PlipUtility.h | 14 ++ plip-sdl/SDL/SdlAudio.cpp | 10 +- plip-sdl/SDL/SdlAudio.h | 1 + plip-sdl/main.cpp | 26 ---- 16 files changed, 705 insertions(+), 37 deletions(-) create mode 100644 libplip/Cpu/Chip8/Chip8.Ops.cpp create mode 100644 libplip/Cpu/Chip8/Chip8.cpp create mode 100644 libplip/Cpu/Chip8/Chip8.h create mode 100644 libplip/Cpu/PlipCpu.cpp create mode 100644 libplip/Cpu/PlipCpu.h create mode 100644 libplip/PlipEmulationException.h create mode 100644 libplip/PlipUtility.cpp create mode 100644 libplip/PlipUtility.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fc5096..b997bb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,10 +34,15 @@ add_custom_target( add_library(${lib_name} libplip/PlipInstance.cpp libplip/PlipIo.cpp + libplip/PlipUtility.cpp libplip/Core/PlipCore.cpp libplip/Core/Chip8/Chip8Instance.cpp + libplip/Cpu/PlipCpu.cpp + libplip/Cpu/Chip8/Chip8.cpp + libplip/Cpu/Chip8/Chip8.Ops.cpp + libplip/Input/PlipInput.cpp libplip/Input/PlipInputDefinition.cpp diff --git a/libplip/Audio/PlipAudio.h b/libplip/Audio/PlipAudio.h index 9532562..9cea281 100644 --- a/libplip/Audio/PlipAudio.h +++ b/libplip/Audio/PlipAudio.h @@ -12,6 +12,7 @@ namespace Plip { public: virtual void DequeueAll() = 0; virtual void Enqueue(std::vector buffer) = 0; + virtual int GetBufferSize() = 0; virtual uintmax_t GetQueueSize() = 0; virtual bool IsActive() final { return m_active; } diff --git a/libplip/Core/Chip8/Chip8Instance.cpp b/libplip/Core/Chip8/Chip8Instance.cpp index 2687eb6..02d7162 100644 --- a/libplip/Core/Chip8/Chip8Instance.cpp +++ b/libplip/Core/Chip8/Chip8Instance.cpp @@ -5,14 +5,18 @@ * (hahaha, more like PLIP-8 amirite) */ +#include + #include "Chip8Instance.h" #include "../../PlipIo.h" namespace Plip::Core::Chip8 { Chip8Instance::Chip8Instance(PlipAudio *audio, PlipInput *input, PlipVideo *video) : Plip::PlipCore(audio, input, video) { + using pa = Plip::PlipAudio; + m_ram = new PlipMemoryRam(RamSize); - m_memoryMap->AddBlock(m_ram); + m_memory->AddBlock(m_ram); m_input->AddInput(0x0, PlipInputDefinition(PlipInputType::Digital, "0"), { .digital = false }); m_input->AddInput(0x1, PlipInputDefinition(PlipInputType::Digital, "1"), { .digital = false }); @@ -30,9 +34,97 @@ namespace Plip::Core::Chip8 { m_input->AddInput(0xD, PlipInputDefinition(PlipInputType::Digital, "D"), { .digital = false }); m_input->AddInput(0xE, PlipInputDefinition(PlipInputType::Digital, "E"), { .digital = false }); m_input->AddInput(0xF, PlipInputDefinition(PlipInputType::Digital, "F"), { .digital = false }); + + m_video->Resize(ScreenWidth, ScreenHeight); + m_videoFormat = Plip::PlipVideo::GetFormatInfo(m_video->GetFormat()); + m_videoOutput = malloc(ScreenWidth * ScreenHeight * m_videoFormat.pixelWidth); + + m_cpu = new Cpu::Chip8(ClockRate, m_memory, CharacterSet, m_input); + m_cpu->Reset(0x200); + + m_cycleTime = m_cpu->GetCycleTime(); + + m_channels = pa::Channels; + m_sampleRate = pa::SampleRate; + m_sampleCount = m_audio->GetBufferSize(); + + auto cycles = (double)SineHz / (double)m_sampleRate; + m_delta = cycles * 2.0 * M_PI; + } + + Chip8Instance::~Chip8Instance() { + free(m_videoOutput); } void Chip8Instance::Delta(long ns) { + m_cycleRemaining += ns; + + do { + m_cpu->Cycle(); + m_cycleRemaining -= m_cycleTime; + + if(m_audio->GetQueueSize() < 1024) { + // This is a pretty lame implementation, but it should be good + // enough for this. + if(m_cpu->IsAudioPlaying()) { + m_audio->Enqueue(GenerateSine()); + } else { + m_audio->Enqueue(GenerateSilence()); + } + } + + m_delayRemaining -= m_cycleTime; + if(m_delayRemaining <= 0) { + m_cpu->DelayTimer(); + m_delayRemaining += DelayTimerTick; + } + } while(m_cycleTime < m_cycleRemaining); + + Draw(); + } + + void Chip8Instance::Draw() { + auto buffer = m_cpu->GetVideo(); + + for(auto y = 0; y < ScreenHeight; y++) { + auto row = buffer[y]; + for(auto x = 0; x < ScreenWidth; x++) { + auto bit = row >> (63 - x) & 0x1; + m_videoFormat.plot(m_videoOutput, y * ScreenWidth + x, + bit * 255, bit * 255, bit * 255); + } + } + + m_video->BeginDraw(); + m_video->EndDraw(); + m_video->Draw(m_videoOutput); + m_video->Render(); + } + + std::vector Chip8Instance::GenerateSilence() { + using pa = Plip::PlipAudio; + + std::vector res; + for(auto i = 0; i < m_sampleCount; i++) { + for(auto c = 0; c < m_channels; c++) + res.push_back(0); + m_angle += m_delta; // Keep the wave generator going to prevent clicks. + } + + return res; + } + + std::vector Chip8Instance::GenerateSine() { + using pa = Plip::PlipAudio; + + std::vector res; + for(auto i = 0; i < m_sampleCount; i++) { + for(auto c = 0; c < m_channels; c++) + res.push_back(SineVolume * std::sin(m_angle)); + m_angle += m_delta; + } + + return res; } PlipError Chip8Instance::Load(const std::string &path) { @@ -44,13 +136,13 @@ namespace Plip::Core::Chip8 { // Zero RAM. for(auto i = 0; i < RamSize; i++) - m_memoryMap->SetByte(i, 0x00); + m_memory->SetByte(i, 0x00); // Load program. auto ramByte = ProgramOffset; auto dataByte = 0; while(dataByte < size && ramByte < RamSize) - m_memoryMap->SetByte(ramByte++, data[dataByte++]); + m_memory->SetByte(ramByte++, data[dataByte++]); // Write character set. WriteCharacterSet(CharacterSet); @@ -60,6 +152,6 @@ namespace Plip::Core::Chip8 { void Chip8Instance::WriteCharacterSet(uint32_t address) { for(auto i = 0; i < m_charsetLength; i++) - m_memoryMap->SetByte(address + i, m_charset[i]); + m_memory->SetByte(address + i, m_charset[i]); } } diff --git a/libplip/Core/Chip8/Chip8Instance.h b/libplip/Core/Chip8/Chip8Instance.h index 06f5c64..63fab8c 100644 --- a/libplip/Core/Chip8/Chip8Instance.h +++ b/libplip/Core/Chip8/Chip8Instance.h @@ -12,6 +12,7 @@ #include "../PlipCore.h" #include "../../PlipError.h" #include "../../PlipInstance.h" +#include "../../Cpu/Chip8/Chip8.h" #include "../../Input/PlipInput.h" #include "../../Memory/PlipMemoryRam.h" @@ -19,22 +20,43 @@ namespace Plip::Core::Chip8 { class Chip8Instance : public PlipCore { public: Chip8Instance(PlipAudio *audio, PlipInput *input, PlipVideo *video); + ~Chip8Instance(); void Delta(long ns) override; PlipError Load(const std::string &path) override; static const uint32_t ClockRate = 500; - static constexpr long ClockTime = (1000 / ClockRate) * 1000000; - static const uint32_t CharacterSet = 0x000; + static const uint32_t CharacterSet = 0x100; + static const long DelayTimerTick = 16666666; static const uint32_t ProgramOffset = 0x200; static const uint32_t RamSize = 0x1000; + static const int SineHz = 440; + static constexpr double SineVolume = 0.25; private: + double m_angle = 0.0; + double m_delta; + int m_channels; + int m_sampleCount; + int m_sampleRate; + + Cpu::Chip8 *m_cpu; + long m_cycleRemaining = 0; + long m_delayRemaining = DelayTimerTick; + long m_cycleTime = 0; PlipMemoryRam *m_ram; std::unordered_map m_inputList; + Plip::PlipVideoFormatInfo m_videoFormat {}; + void *m_videoOutput; + void Draw(); + std::vector GenerateSilence(); + std::vector GenerateSine(); void WriteCharacterSet(uint32_t address); + static const int ScreenWidth = 64; + static const int ScreenHeight = 32; + static constexpr uint8_t m_charset[] { 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 diff --git a/libplip/Core/PlipCore.h b/libplip/Core/PlipCore.h index 94241ee..404d8cf 100644 --- a/libplip/Core/PlipCore.h +++ b/libplip/Core/PlipCore.h @@ -31,7 +31,7 @@ namespace Plip { static std::vector GetSupportedCores(); virtual void Delta(long ns) = 0; - virtual PlipMemoryMap* GetMemoryMap() final { return m_memoryMap; } + virtual PlipMemoryMap* GetMemoryMap() final { return m_memory; } virtual PlipError Load(const std::string &path) = 0; protected: @@ -41,7 +41,7 @@ namespace Plip { PlipInput *m_input; PlipVideo *m_video; - PlipMemoryMap *m_memoryMap = new PlipMemoryMap(); + PlipMemoryMap *m_memory = new PlipMemoryMap(); private: static constexpr PlipCoreDescription m_supportedCores[] = { diff --git a/libplip/Cpu/Chip8/Chip8.Ops.cpp b/libplip/Cpu/Chip8/Chip8.Ops.cpp new file mode 100644 index 0000000..c375088 --- /dev/null +++ b/libplip/Cpu/Chip8/Chip8.Ops.cpp @@ -0,0 +1,264 @@ +/* Chip8.Ops.cpp + * + * CHIP-8 opcodes. + */ + +#include +#include + +#include "../../PlipEmulationException.h" + +#include "Chip8.h" + +namespace Plip::Cpu { + void Chip8::Op0NNN(uint16_t address) { + // RCA 1802 routine. Not implemented. + } + + void Chip8::Op00E0() { + // Clears the screen. + for(auto i = 0; i < VideoSize; i++) + m_videoBuffer[i] = 0; + } + + void Chip8::Op00EE() { + // Returns from a subroutine. + if(m_sp == 0) { + std::stringstream ex; + ex << "stack underflow\n\n" << DumpRegisters(); + throw PlipEmulationException(ex.str().c_str()); + } + + m_pc = m_stack[--m_sp]; + } + + void Chip8::Op1NNN(uint16_t address) { + // Jumps to an address. + m_pc = address; + } + + void Chip8::Op2NNN(uint16_t address) { + // Calls a subroutine. + if(m_sp >= StackSize) { + std::stringstream ex; + ex << "stack overflow\n\n" << DumpRegisters(); + throw PlipEmulationException(ex.str().c_str()); + } + + m_stack[m_sp++] = m_pc; + m_pc = address; + } + + void Chip8::Op3XNN(uint8_t reg, uint8_t value) { + // Skips the next instruction if reg == value. + if(m_reg[reg] == value) m_pc += 2; + } + + void Chip8::Op4XNN(uint8_t reg, uint8_t value) { + // Skips the next instruction if reg != value. + if(m_reg[reg] != value) m_pc += 2; + } + + void Chip8::Op5XY0(uint8_t left, uint8_t right) { + // Skips the next instruction if left == right. + if(m_reg[left] == m_reg[right]) m_pc += 2; + } + + void Chip8::Op6XNN(uint8_t reg, uint8_t value) { + // Sets reg to value. + m_reg[reg] = value; + } + + void Chip8::Op7XNN(uint8_t reg, uint8_t value) { + // Adds value to reg. + m_reg[reg] += value; + } + + void Chip8::Op8XYO(uint8_t left, uint8_t right, uint8_t op) { + uint8_t old; + + // Various bitwise and math operations. + switch(op) { + case 0x0: + // left = right + m_reg[left] = m_reg[right]; + break; + + case 0x1: + // left |= right + m_reg[left] |= m_reg[right]; + break; + + case 0x2: + // left &= right + m_reg[left] &= m_reg[right]; + break; + + case 0x3: + // left ^= right + m_reg[left] ^= m_reg[right]; + break; + + case 0x4: + // left += right, VF = 1 on carry, otherwise 0 + old = m_reg[left]; + m_reg[left] += m_reg[right]; + m_reg[0xF] = old > m_reg[left] ? 1 : 0; + break; + + case 0x5: + // left -= right, VF = 1 on borrow, otherwise 0 + old = m_reg[left]; + m_reg[left] -= m_reg[right]; + m_reg[0xF] = old < m_reg[left] ? 0 : 1; + break; + + case 0x6: + // left >>= 1, VF = LSB of right before shift + m_reg[0xF] = m_reg[left] & 0x1; + m_reg[left] >>= 1; + break; + + case 0x7: + // left = right - left, VF = 1 on borrow, otherwise 0 + m_reg[left] = m_reg[right] - m_reg[left]; + m_reg[0xF] = m_reg[right] < m_reg[left] ? 0 : 1; + break; + + case 0xE: + // left <<= 1, VF = MSB of right before shift + m_reg[0xF] = (m_reg[left] & 0x80) > 0 ? 1 : 0; + m_reg[left] <<= 1; + break; + + default: break; + } + } + + void Chip8::Op9XY0(uint8_t left, uint8_t right) { + // Skips the next instruction if left != right. + if(m_reg[left] != m_reg[right]) m_pc += 2; + } + + void Chip8::OpANNN(uint16_t address) { + // Sets i to address. + m_i = address; + } + + void Chip8::OpBNNN(uint16_t address) { + // Jumps to address + V0. + m_pc = address + m_reg[0]; + } + + void Chip8::OpCXNN(uint8_t reg, uint8_t value) { + // Sets reg to random(0-255) & value. + std::uniform_int_distribution num(0, 255); + m_reg[reg] = num(m_rng) & value; + } + + void Chip8::OpDXYN(uint8_t xReg, uint8_t yReg, uint8_t size) { + // Draws an (8 x size) sprite at address I to (xReg, yReg). + auto sprite = new uint64_t[size]; + auto x = m_reg[xReg]; + auto y = m_reg[yReg]; + + for(auto i = 0; i < size; i++) { + // Push sprites all the way to the highest bits, then + // bump them to the right as much as necessary. + sprite[i] = ((uint64_t)m_memory->GetByte(m_i + i) << 56) >> x; + } + + m_reg[0xF] = 0; + for(auto i = 0; i < size; i++) { + if(y + i >= VideoSize) return; // Don't attempt to write past maxY. + + uint64_t oldRow = m_videoBuffer[y + i]; + m_videoBuffer[y + i] ^= sprite[i]; + + // Invert the old row, OR it with the new row, and invert the result. + // If it's greater than 0, a bit has been turned off. + oldRow = ~oldRow; + if(~(oldRow | m_videoBuffer[y + i]) > 0) + m_reg[0xF] = 1; + } + } + + void Chip8::OpEXOO(uint8_t reg, uint8_t op) { + // Input functions. + switch(op) { + case 0x9E: + // Skips the next instruction if the key in reg is pressed. + if(m_input->GetInput(m_reg[reg]).digital) m_pc += 2; + break; + + case 0xA1: + // Skips the next instruction if the key in reg is released. + if(!m_input->GetInput(m_reg[reg]).digital) m_pc += 2; + break; + + default: break; + } + } + + void Chip8::OpFXOO(uint8_t reg, uint8_t op) { + std::stringstream ss; + std::string str; + + // Various other instructions. + switch(op) { + case 0x07: + // Store the value of the delay timer in reg. + m_reg[reg] = m_timerDelay; + break; + + case 0x0A: + // Wait for a keypress and store the result in reg. + m_waitForKey = true; + m_keyRegister = reg; + break; + + case 0x15: + // Set the delay timer to reg. + m_timerDelay = m_reg[reg]; + break; + + case 0x18: + // Set the audio timer to reg. + m_timerAudio = m_reg[reg]; + break; + + case 0x1E: + // Add reg to I. + m_i += m_reg[reg]; + break; + + case 0x29: + // Set I to the address of the charset value in reg. + m_i = m_charsetAddress + (m_reg[reg] * 5); + break; + + case 0x33: + // Stores reg as a binary-coded decimal, starting at I. + ss << std::setfill('0') << std::setw(3) << std::to_string(m_reg[reg]); + str = ss.str(); + for(auto i = 0; i < 3; i++) + m_memory->SetByte(m_i + i, str[i] - '0'); + break; + + case 0x55: + // Stores the values of V0 to reg, inclusive, in I and up. + for(auto i = 0; i < reg + 1; i++) + m_memory->SetByte(m_i + i, m_reg[i]); + break; + + case 0x65: + // Fills registers V0 to reg, inclusive, with the values in + // memory starting with I. + for(auto i = 0; i < reg + 1; i++) + m_reg[i] = m_memory->GetByte(m_i + i); + break; + + default: break; + } + } +} \ No newline at end of file diff --git a/libplip/Cpu/Chip8/Chip8.cpp b/libplip/Cpu/Chip8/Chip8.cpp new file mode 100644 index 0000000..e2f43e0 --- /dev/null +++ b/libplip/Cpu/Chip8/Chip8.cpp @@ -0,0 +1,113 @@ +/* Chip8.cpp + * + * An implementation of a CHIP-8 CPU. + */ + +#include +#include + +#include "Chip8.h" +#include "../../PlipUtility.h" + +namespace Plip::Cpu { + Chip8::Chip8(long hz, PlipMemoryMap *memoryMap, uint16_t charset, Plip::PlipInput *input) + : PlipCpu(hz, memoryMap) { + m_charsetAddress = charset; + m_input = input; + m_videoBuffer = new uint64_t[VideoSize] {}; + } + + void Chip8::Cycle() { + auto inst = Fetch(); + auto left = GetReg1(inst); + auto right = GetReg2(inst); + auto val = GetValue(inst); + auto addr = GetAddress(inst); + + if(m_waitForKey) { + for(uint8_t i = 0; i < 0x10; i++) { + if(!m_input->GetInput(i).digital) continue; + + m_waitForKey = false; + m_reg[m_keyRegister] = i; + break; + } + } + + if(m_waitForKey) return; + + switch(inst & 0xF000) { + case 0x0000: + if(val == 0xE0) + Op00E0(); + else if(val == 0xEE) + Op00EE(); + else + Op0NNN(addr); + break; + + case 0x1000: Op1NNN(addr); break; + case 0x2000: Op2NNN(addr); break; + case 0x3000: Op3XNN(left, val); break; + case 0x4000: Op4XNN(left, val); break; + case 0x5000: Op5XY0(left, right); break; + case 0x6000: Op6XNN(left, val); break; + case 0x7000: Op7XNN(left, val); break; + case 0x8000: Op8XYO(left, right, inst & 0xF); break; + case 0x9000: Op9XY0(left, right); break; + case 0xA000: OpANNN(addr); break; + case 0xB000: OpBNNN(addr); break; + case 0xC000: OpCXNN(left, val); break; + case 0xD000: OpDXYN(left, right, inst & 0xF); break; + case 0xE000: OpEXOO(left, val); break; + case 0xF000: OpFXOO(left, val); break; + } + } + + void Chip8::DelayTimer() { + if(m_timerAudio > 0) m_timerAudio--; + if(m_timerDelay > 0) m_timerDelay--; + } + + std::string Chip8::DumpRegisters() { + using util = Plip::PlipUtility; + const char *regLabel = "0123456789ABCDEF"; + std::stringstream dump; + + dump << util::DumpValue(" PC", m_pc, 3) << '\n' + << util::DumpValue(" SP", m_sp, StackSize > 16 ? 2 : 1) << "\n\n" + << util::DumpValue("Audio", m_timerAudio, 2) << '\n' + << util::DumpValue("Delay", m_timerDelay, 2) << "\n\n" + << util::DumpValue(" I", m_i, 4) << '\n'; + + for(auto i = 0; i < 16; i++) { + std::stringstream label; + label << " V" << regLabel[i]; + dump << util::DumpValue(label.str(), m_reg[i], 2) << '\n'; + } + + dump << "\n\tStack: "; + for(auto i = 0; i < StackSize; i++) { + if(i == m_sp) dump << "["; + dump << util::FormatHex(m_stack[i], 3); + if(i == m_sp) dump << "]"; + dump << ' '; + } + + return dump.str(); + } + + void Chip8::Reset(uint32_t pc) { + m_timerAudio = 0; + m_timerDelay = 0; + m_sp = 0; + m_pc = pc; + m_i = 0; + + for(auto ® : m_reg) + reg = 0; + + for(auto &stack : m_stack) + stack = 0; + } +} diff --git a/libplip/Cpu/Chip8/Chip8.h b/libplip/Cpu/Chip8/Chip8.h new file mode 100644 index 0000000..e6568de --- /dev/null +++ b/libplip/Cpu/Chip8/Chip8.h @@ -0,0 +1,79 @@ +/* Chip8.h + * + * An implementation of a CHIP-8 CPU. + */ + +#pragma once + +#include + +#include "../PlipCpu.h" +#include "../../Input/PlipInput.h" +#include "../../Video/PlipVideo.h" + +namespace Plip::Cpu { + class Chip8 : public PlipCpu { + public: + Chip8(long hz, PlipMemoryMap* memoryMap, uint16_t charset, Plip::PlipInput *input); + + void Cycle() override; + void DelayTimer(); + uint64_t* GetVideo() { return m_videoBuffer; } + [[nodiscard]] bool IsAudioPlaying() const { return m_timerAudio >= 2; } + void Reset(uint32_t pc) override; + + static const int VideoSize = 32; // 64 x 32 + + private: + std::string DumpRegisters(); + + inline uint16_t Fetch() { + uint8_t high = m_memory->GetByte(m_pc++); + uint8_t low = m_memory->GetByte(m_pc++); + return (high << 8) + low; + } + + static inline uint16_t GetAddress(uint16_t b) { return b & 0xFFF; } + static inline uint8_t GetReg1(uint16_t b) { return (b >> 8) & 0xF; } + static inline uint8_t GetReg2(uint16_t b) { return (b >> 4) & 0xF; } + static inline uint8_t GetValue(uint16_t b) { return b & 0xFF; } + + void Op0NNN(uint16_t address); + void Op00E0(); + void Op00EE(); + void Op1NNN(uint16_t address); + void Op2NNN(uint16_t address); + void Op3XNN(uint8_t reg, uint8_t value); + void Op4XNN(uint8_t reg, uint8_t value); + void Op5XY0(uint8_t left, uint8_t right); + void Op6XNN(uint8_t reg, uint8_t value); + void Op7XNN(uint8_t reg, uint8_t value); + void Op8XYO(uint8_t left, uint8_t right, uint8_t op); + void Op9XY0(uint8_t left, uint8_t right); + void OpANNN(uint16_t address); + void OpBNNN(uint16_t address); + void OpCXNN(uint8_t reg, uint8_t value); + void OpDXYN(uint8_t xReg, uint8_t yReg, uint8_t size); + void OpEXOO(uint8_t reg, uint8_t op); + void OpFXOO(uint8_t reg, uint8_t op); + + static const int StackSize = 12; + + std::default_random_engine m_rng; + + Plip::PlipInput *m_input; + uint64_t *m_videoBuffer; + + uint16_t m_charsetAddress; + uint8_t m_timerAudio = 0; + uint8_t m_timerDelay = 0; + uint16_t m_sp = 0; + uint16_t m_pc = 0; + uint16_t m_i = 0; + uint8_t m_reg[16] {}; + uint16_t m_stack[StackSize] {}; + + bool m_waitForKey = false; + uint8_t m_keyRegister = 0; + }; +} diff --git a/libplip/Cpu/PlipCpu.cpp b/libplip/Cpu/PlipCpu.cpp new file mode 100644 index 0000000..bd0a8b4 --- /dev/null +++ b/libplip/Cpu/PlipCpu.cpp @@ -0,0 +1,26 @@ +/* PlipCpu.cpp + * + * Defines a CPU implementation. + */ + +#include "PlipCpu.h" + +namespace Plip::Cpu { + PlipCpu::PlipCpu(long hz, PlipMemoryMap* memoryMap) { + SetHz(hz); + m_memory = memoryMap; + } + + long PlipCpu::GetCycleTime() const { + return m_cycle; + } + + long PlipCpu::GetHz(long hz) const { + return m_hz; + } + + void PlipCpu::SetHz(long hz) { + m_hz = hz; + m_cycle = 1000000000 / hz; + } +} diff --git a/libplip/Cpu/PlipCpu.h b/libplip/Cpu/PlipCpu.h new file mode 100644 index 0000000..d2810d1 --- /dev/null +++ b/libplip/Cpu/PlipCpu.h @@ -0,0 +1,31 @@ +/* PlipCpu.h + * + * Defines a CPU implementation. + */ + +#pragma once + +#include + +#include "../Memory/PlipMemoryMap.h" + +namespace Plip::Cpu { + class PlipCpu { + public: + [[nodiscard]] long GetCycleTime() const; + [[nodiscard]] long GetHz(long hz) const; + void SetHz(long hz); + + virtual void Cycle() = 0; + virtual void Reset(uint32_t pc) = 0; + + protected: + PlipCpu(long hz, PlipMemoryMap* memoryMap); + + long m_hz {}; + Plip::PlipMemoryMap *m_memory; + + private: + long m_cycle {}; + }; +} diff --git a/libplip/PlipEmulationException.h b/libplip/PlipEmulationException.h new file mode 100644 index 0000000..bec474f --- /dev/null +++ b/libplip/PlipEmulationException.h @@ -0,0 +1,17 @@ +/* PlipEmulationException.h + * + * An exception that may occur during emulation. + */ + +#pragma once + +#include +#include +#include + +namespace Plip { + struct PlipEmulationException : std::runtime_error { + explicit PlipEmulationException(const char *message) + : std::runtime_error(message) {} + }; +} diff --git a/libplip/PlipUtility.cpp b/libplip/PlipUtility.cpp new file mode 100644 index 0000000..a8866be --- /dev/null +++ b/libplip/PlipUtility.cpp @@ -0,0 +1,25 @@ +/* PlipUtility.cpp + * + * Miscellaneous helper functions. + */ + +#include +#include + +#include "PlipUtility.h" + +namespace Plip { + std::string PlipUtility::DumpValue(const std::string &label, uintmax_t value, int precision) { + std::stringstream dump; + dump << '\t' << label << ": " << FormatHex(value, precision); + return dump.str(); + } + + std::string PlipUtility::FormatHex(uintmax_t value, int precision) { + std::stringstream fmt; + fmt << "0x" << std::uppercase << std::setfill('0') << std::setw(precision) + << std::hex << value; + + return fmt.str(); + } +} diff --git a/libplip/PlipUtility.h b/libplip/PlipUtility.h new file mode 100644 index 0000000..43b165d --- /dev/null +++ b/libplip/PlipUtility.h @@ -0,0 +1,14 @@ +/* PlipUtility.h + * + * Miscellaneous helper functions. + */ + +#include + +namespace Plip { + class PlipUtility { + public: + static std::string DumpValue(const std::string &label, uintmax_t value, int precision); + static std::string FormatHex(uintmax_t value, int precision); + }; +} diff --git a/plip-sdl/SDL/SdlAudio.cpp b/plip-sdl/SDL/SdlAudio.cpp index fa9ca1f..f60de29 100644 --- a/plip-sdl/SDL/SdlAudio.cpp +++ b/plip-sdl/SDL/SdlAudio.cpp @@ -14,9 +14,9 @@ namespace PlipSdl { // Open audio device. SDL_AudioSpec want {}; - want.freq = 8000; // Plip::PlipAudio::SampleRate; - want.format = AUDIO_S8; - want.channels = 1; // Plip::PlipAudio::Channels; + want.freq = Plip::PlipAudio::SampleRate; + want.format = AUDIO_F32; + want.channels = Plip::PlipAudio::Channels; want.samples = SampleLength; want.callback = nullptr; @@ -81,6 +81,10 @@ namespace PlipSdl { (this->*m_playFunc)(buffer.data(), buffer.size()); } + int SdlAudio::GetBufferSize() { + return m_spec.samples; + } + uintmax_t SdlAudio::GetQueueSize() { return SDL_GetQueuedAudioSize(m_device); } diff --git a/plip-sdl/SDL/SdlAudio.h b/plip-sdl/SDL/SdlAudio.h index 1fd8772..702a953 100644 --- a/plip-sdl/SDL/SdlAudio.h +++ b/plip-sdl/SDL/SdlAudio.h @@ -17,6 +17,7 @@ namespace PlipSdl { void DequeueAll() override; void Enqueue(std::vector buffer) override; + int GetBufferSize() override; uintmax_t GetQueueSize() override; static const int SampleLength = 4096; diff --git a/plip-sdl/main.cpp b/plip-sdl/main.cpp index ebbe60f..cd16443 100644 --- a/plip-sdl/main.cpp +++ b/plip-sdl/main.cpp @@ -31,29 +31,6 @@ std::vector> intParamMapping = { { "fps" , "video", "targetFps" } }; -#include -double angle = 0.0; -std::vector waveGen() { - using pa = Plip::PlipAudio; - - auto vol = 0.5; - auto len = PlipSdl::SdlAudio::SampleLength * 8; - auto hz = 440; - auto smp = pa::SampleRate; - auto chan = pa::Channels; - std::vector res; - - auto cycles = (double)hz / (double)smp; - auto delta = cycles * 2.0 * M_PI; - for(auto i = 0; i < len; i++) { - for(auto c = 0; c < chan; c++) - res.push_back(vol * std::sin(angle)); - angle += delta; - } - - return res; -} - void gameLoop(Plip::PlipInstance *plip, PlipSdl::Config *config, PlipSdl::SdlEvent *event, PlipSdl::Timer *timer) { auto audio = plip->GetAudio(); auto video = plip->GetVideo(); @@ -73,9 +50,6 @@ void gameLoop(Plip::PlipInstance *plip, PlipSdl::Config *config, PlipSdl::SdlEve // TODO: Fix this so that it will skip frames where appropriate. plip->Run(frameTime); - if(audio->GetQueueSize() < PlipSdl::SdlAudio::SampleLength / 2) - audio->Enqueue(waveGen()); - auto time = timer->StopwatchStop(); auto delay = frameTime - time; while(delay < 0)