From 6965e4f0da356249111522b4e408c2d0ba3a3212 Mon Sep 17 00:00:00 2001 From: thomz12 Date: Sat, 19 Sep 2026 15:35:23 +0200 Subject: [PATCH] First instructions down, many to go --- juiceboy/CMakeLists.txt | 2 + juiceboy/include/juiceboy/cartridge.hpp | 18 ++-- .../juiceboy/cartridges/rom_cartridge.hpp | 22 ++--- juiceboy/include/juiceboy/instructions.hpp | 30 +++++++ juiceboy/include/juiceboy/processor.hpp | 28 ++++-- juiceboy/include/juiceboy/registers.hpp | 77 +++++++++------- juiceboy/src/instructions.cpp | 46 ++++++++++ juiceboy/src/memory.cpp | 88 ++++++++++++------- juiceboy/src/processor.cpp | 31 ++++++- juiceboy/src/registers.cpp | 36 +++++++- juiceboy/test/CMakeLists.txt | 1 + juiceboy/test/instructions_test.cpp | 57 ++++++++++++ 12 files changed, 339 insertions(+), 97 deletions(-) create mode 100644 juiceboy/include/juiceboy/instructions.hpp create mode 100644 juiceboy/src/instructions.cpp create mode 100644 juiceboy/test/instructions_test.cpp diff --git a/juiceboy/CMakeLists.txt b/juiceboy/CMakeLists.txt index cd220fb..308f91d 100644 --- a/juiceboy/CMakeLists.txt +++ b/juiceboy/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(juiceboy STATIC) target_sources(juiceboy PRIVATE + "src/instructions.cpp" "src/memory.cpp" "src/processor.cpp" "src/registers.cpp" @@ -11,6 +12,7 @@ target_sources(juiceboy FILES "include/juiceboy/cartridge.hpp" "include/juiceboy/cartridges/rom_cartridge.hpp" + "include/juiceboy/instructions.hpp" "include/juiceboy/juiceboy.hpp" "include/juiceboy/memory.hpp" "include/juiceboy/memory_region.hpp" diff --git a/juiceboy/include/juiceboy/cartridge.hpp b/juiceboy/include/juiceboy/cartridge.hpp index e2b6cfb..636509e 100644 --- a/juiceboy/include/juiceboy/cartridge.hpp +++ b/juiceboy/include/juiceboy/cartridge.hpp @@ -3,16 +3,12 @@ #include namespace juiceboy { - class Cartridge { - public: - virtual ~Cartridge() = default; +class Cartridge { +public: + virtual ~Cartridge() = default; - virtual std::uint8_t ReadByte(std::uint16_t address) = 0; + virtual std::uint8_t ReadByte(std::uint16_t address) = 0; - virtual std::uint16_t ReadWord(std::uint16_t address) = 0; - - virtual void WriteByte(std::uint16_t address, std::uint16_t value) = 0; - - virtual void WriteWord(std::uint16_t address, std::uint16_t value) = 0; - }; -} + virtual void WriteByte(std::uint16_t address, std::uint16_t value) = 0; +}; +} // namespace juiceboy diff --git a/juiceboy/include/juiceboy/cartridges/rom_cartridge.hpp b/juiceboy/include/juiceboy/cartridges/rom_cartridge.hpp index 5925bfd..aa2f033 100644 --- a/juiceboy/include/juiceboy/cartridges/rom_cartridge.hpp +++ b/juiceboy/include/juiceboy/cartridges/rom_cartridge.hpp @@ -5,19 +5,15 @@ #include namespace juiceboy { - class ROMCartridge : public Cartridge { - public: - explicit ROMCartridge(std::array data); +class ROMCartridge : public Cartridge { +public: + explicit ROMCartridge(std::array data); - std::uint8_t ReadByte(std::uint16_t address) override; + std::uint8_t ReadByte(std::uint16_t address) override; - std::uint16_t ReadWord(std::uint16_t address) override; + void WriteByte(std::uint16_t address, std::uint16_t value) override; - void WriteByte(std::uint16_t address, std::uint16_t value) override; - - void WriteWord(std::uint16_t address, std::uint16_t value) override; - - private: - std::array m_data; - }; -} +private: + std::array m_data; +}; +} // namespace juiceboy diff --git a/juiceboy/include/juiceboy/instructions.hpp b/juiceboy/include/juiceboy/instructions.hpp new file mode 100644 index 0000000..e7ceba2 --- /dev/null +++ b/juiceboy/include/juiceboy/instructions.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace juiceboy { + +class Processor; + +class Instructions { +public: + Instructions(); + + std::uint8_t ExecuteInstruction(std::uint8_t opcode, + Processor &processor) const; + +private: + using Instruction = std::uint8_t (*)(Processor &); + std::array m_instructions{}; +}; + +namespace instructions { +std::uint8_t NotImplemented(Processor &processor); + +std::uint8_t Nop(Processor &processor); +std::uint8_t LdBcD16(Processor &processor); +std::uint8_t LdBcA(Processor &processor); +} // namespace instructions + +} // namespace juiceboy \ No newline at end of file diff --git a/juiceboy/include/juiceboy/processor.hpp b/juiceboy/include/juiceboy/processor.hpp index 532a68a..6fc1c60 100644 --- a/juiceboy/include/juiceboy/processor.hpp +++ b/juiceboy/include/juiceboy/processor.hpp @@ -1,12 +1,26 @@ #pragma once +#include "memory.hpp" #include "registers.hpp" -namespace juiceboy { - class Processor { - public: +#include - private: - Registers m_registers; - }; -} // juiceboy +namespace juiceboy { +class Processor { +public: + explicit Processor(Memory memory); + + std::size_t Step(); + + Registers &GetRegisters(); + Memory &GetMemory(); + + [[nodiscard]] std::uint8_t FetchByte(); + + [[nodiscard]] std::uint16_t FetchWord(); + +private: + Registers m_registers{}; + Memory m_memory; +}; +} // namespace juiceboy diff --git a/juiceboy/include/juiceboy/registers.hpp b/juiceboy/include/juiceboy/registers.hpp index fb1c3fd..eb9219f 100644 --- a/juiceboy/include/juiceboy/registers.hpp +++ b/juiceboy/include/juiceboy/registers.hpp @@ -3,45 +3,56 @@ #include namespace juiceboy { - class Registers { - public: - /// @brief Flags in the flags (af) register. - enum class Flags { - Zero, - Subtract, - HalfCarry, - Carry - }; +class Registers { +public: + /// @brief Flags in the flags (af) register. + enum class Flags { Zero, Subtract, HalfCarry, Carry }; - /// @brief Get the current program counter/pointer. - /// @return The current value of the program counter/pointer. - [[nodiscard]] std::uint16_t GetProgramCounter() const; + /// @brief Get the current program counter. + /// @return The current value of the program counter. + [[nodiscard]] std::uint16_t GetProgramCounter() const; - /// @brief Get the current stack pointer. - /// @return The current value of the stack pointer. - [[nodiscard]] std::uint16_t GetStackPointer() const; + /// @brief Set the current program counter value. + /// @param value The new value of the program counter. + void SetProgramCounter(std::uint16_t value); - /// @brief Get the current value of the given flag. - /// @return True if the given flag bit is set, false otherwise. - [[nodiscard]] bool GetFlag(Flags flag) const; + /// @brief Increase the program counter by the given amount. + /// @param value The value to increase the program counter by. + void IncrementProgramCounter(std::uint16_t value); - private: - /// @brief Accumulator & Flags. - std::uint16_t m_af{0}; + /// @brief Get the current stack pointer. + /// @return The current value of the stack pointer. + [[nodiscard]] std::uint16_t GetStackPointer() const; - /// @brief BC Registers. - std::uint16_t m_bc{0}; + [[nodiscard]] std::uint8_t GetA() const; - /// @brief DE Registers. - std::uint16_t m_de{0}; + [[nodiscard]] std::uint16_t GetBC() const; - /// @brief HL Registers. - std::uint16_t m_hl{0}; + void SetA(std::uint8_t value); - /// @brief Program counter/pointer. - std::uint16_t m_pc{0}; + void SetBC(std::uint16_t value); - /// @brief Stack pointer. - std::uint16_t m_sp{0}; - }; -} + /// @brief Get the current value of the given flag. + /// @return True if the given flag bit is set, false otherwise. + // [[nodiscard]] bool GetFlag(Flags flag) const; + +private: + std::uint8_t m_regA{}; + std::uint8_t m_regF{}; + + std::uint8_t m_regB{}; + std::uint8_t m_regC{}; + + std::uint8_t m_regD{}; + std::uint8_t m_regE{}; + + std::uint8_t m_regH{}; + std::uint8_t m_regL{}; + + /// @brief Program counter/pointer. + std::uint16_t m_pc{0}; + + /// @brief Stack pointer. + std::uint16_t m_sp{0}; +}; +} // namespace juiceboy diff --git a/juiceboy/src/instructions.cpp b/juiceboy/src/instructions.cpp new file mode 100644 index 0000000..0a014da --- /dev/null +++ b/juiceboy/src/instructions.cpp @@ -0,0 +1,46 @@ +#include "juiceboy/instructions.hpp" + +#include "juiceboy/processor.hpp" + +#include +#include + +namespace juiceboy { + +Instructions::Instructions() { + // For now, all not implemented instructions should throw. + m_instructions.fill(&instructions::NotImplemented); + + std::get<0>(m_instructions) = &instructions::Nop; + std::get<1>(m_instructions) = &instructions::LdBcD16; + std::get<2>(m_instructions) = &instructions::LdBcA; +} + +std::uint8_t Instructions::ExecuteInstruction(const std::uint8_t opcode, + Processor &processor) const { + return m_instructions[opcode](processor); +} + +namespace instructions { + +std::uint8_t NotImplemented(Processor &) { + throw std::runtime_error{"Instruction not implemented!"}; +} + +std::uint8_t Nop(Processor &) { return 4; } + +std::uint8_t LdBcD16(Processor &processor) { + const auto value = processor.FetchWord(); + processor.GetRegisters().SetBC(value); + return 12; +} + +std::uint8_t LdBcA(Processor &processor) { + processor.GetMemory().WriteByte(processor.GetRegisters().GetBC(), + processor.GetRegisters().GetA()); + return 8; +} + +} // namespace instructions + +} // namespace juiceboy \ No newline at end of file diff --git a/juiceboy/src/memory.cpp b/juiceboy/src/memory.cpp index 7440aa6..9bc4aa6 100644 --- a/juiceboy/src/memory.cpp +++ b/juiceboy/src/memory.cpp @@ -6,43 +6,69 @@ #include namespace juiceboy { - Memory::Memory(std::shared_ptr cartridge) : m_cartridge(std::move(cartridge)) { - } +Memory::Memory(std::shared_ptr cartridge) + : m_cartridge(std::move(cartridge)) {} - std::uint8_t Memory::ReadByte(const std::uint16_t address) { - // Read from cartridge ROM or External RAM. - if ((address < 0x8000) || (address >= 0xA000 && address < 0xC000)) { - return m_cartridge->ReadByte(address); - } +std::uint8_t Memory::ReadByte(const std::uint16_t address) { + if ((address < 0x8000) || (address >= 0xA000 && address < 0xC000)) { + return m_cartridge->ReadByte(address); + } - if (decltype(m_vram)::ContainsAddress(address)) { - return m_vram.ReadByte(address); - } + if (decltype(m_vram)::ContainsAddress(address)) { + return m_vram.ReadByte(address); + } - if (decltype(m_wram)::ContainsAddress(address)) { - return m_wram.ReadByte(address); - } + if (decltype(m_wram)::ContainsAddress(address)) { + return m_wram.ReadByte(address); + } - if (decltype(m_oam)::ContainsAddress(address)) { - return m_oam.ReadByte(address); - } + if (decltype(m_oam)::ContainsAddress(address)) { + return m_oam.ReadByte(address); + } - if (decltype(m_hram)::ContainsAddress(address)) { - return m_io.ReadByte(address); - } + if (decltype(m_io)::ContainsAddress(address)) { + return m_io.ReadByte(address); + } - if (decltype(m_hram)::ContainsAddress(address)) { - return m_io.ReadByte(address); - } + if (decltype(m_hram)::ContainsAddress(address)) { + return m_hram.ReadByte(address); + } - if (decltype(m_interruptEnable)::ContainsAddress(address)) { - return m_interruptEnable.ReadByte(address); - } + if (decltype(m_interruptEnable)::ContainsAddress(address)) { + return m_interruptEnable.ReadByte(address); + } - // For now, return 0 when no address was in range. - return 0x00; - } - - void Memory::WriteByte(std::uint16_t address, std::uint8_t value) { - } + // For now, return 0 when no address was in range. + return 0x00; } + +void Memory::WriteByte(std::uint16_t address, std::uint8_t value) { + if ((address < 0x8000) || (address >= 0xA000 && address < 0xC000)) { + return m_cartridge->WriteByte(address, value); + } + + if (decltype(m_vram)::ContainsAddress(address)) { + return m_vram.WriteByte(address, value); + } + + if (decltype(m_wram)::ContainsAddress(address)) { + return m_wram.WriteByte(address, value); + } + + if (decltype(m_oam)::ContainsAddress(address)) { + return m_oam.WriteByte(address, value); + } + + if (decltype(m_io)::ContainsAddress(address)) { + return m_io.WriteByte(address, value); + } + + if (decltype(m_hram)::ContainsAddress(address)) { + return m_hram.WriteByte(address, value); + } + + if (decltype(m_interruptEnable)::ContainsAddress(address)) { + return m_interruptEnable.WriteByte(address, value); + } +} +} // namespace juiceboy diff --git a/juiceboy/src/processor.cpp b/juiceboy/src/processor.cpp index 6a7c277..9dc81a0 100644 --- a/juiceboy/src/processor.cpp +++ b/juiceboy/src/processor.cpp @@ -1,4 +1,33 @@ #include "juiceboy/processor.hpp" +#include "juiceboy/instructions.hpp" +#include "juiceboy/memory.hpp" + namespace juiceboy { -} // juiceboy + +static Instructions Instructions; + +Processor::Processor(Memory memory) : m_memory(std::move(memory)) {} + +std::size_t Processor::Step() { + const std::uint8_t opcode = FetchByte(); + Instructions.ExecuteInstruction(opcode, *this); + + return 0; +} + +Registers &Processor::GetRegisters() { return m_registers; } +Memory &Processor::GetMemory() { return m_memory; } + +std::uint8_t Processor::FetchByte() { + const auto value = m_memory.ReadByte(m_registers.GetProgramCounter()); + m_registers.IncrementProgramCounter(1); + return value; +} + +std::uint16_t Processor::FetchWord() { + const auto low = static_cast(FetchByte()); + const auto high = static_cast(FetchByte()) << 8; + return low | high; +} +} // namespace juiceboy diff --git a/juiceboy/src/registers.cpp b/juiceboy/src/registers.cpp index c0a2f2c..a60f38d 100644 --- a/juiceboy/src/registers.cpp +++ b/juiceboy/src/registers.cpp @@ -1,5 +1,39 @@ #include "juiceboy/registers.hpp" +#include +#include + namespace juiceboy { -} \ No newline at end of file +namespace { +std::uint16_t BytesToWord(const std::uint8_t low, const std::uint8_t high) { + return static_cast(low) | static_cast(high) + << 8; +} + +std::pair WordToBytes(const std::uint16_t word) { + return {static_cast(word & 0xFF), + static_cast(word >> 8)}; +} +} // namespace + +std::uint16_t Registers::GetProgramCounter() const { return m_pc; } + +void Registers::SetProgramCounter(const std::uint16_t value) { m_pc = value; } + +void Registers::IncrementProgramCounter(const std::uint16_t value) { + m_pc += value; +} + +std::uint16_t Registers::GetStackPointer() const { return m_sp; } + +std::uint8_t Registers::GetA() const { return m_regA; } + +std::uint16_t Registers::GetBC() const { return BytesToWord(m_regB, m_regC); } + +void Registers::SetA(const std::uint8_t value) { m_regA = value; } + +void Registers::SetBC(const std::uint16_t value) { + std::tie(m_regB, m_regC) = WordToBytes(value); +} +} // namespace juiceboy \ No newline at end of file diff --git a/juiceboy/test/CMakeLists.txt b/juiceboy/test/CMakeLists.txt index 26c39c3..323522c 100644 --- a/juiceboy/test/CMakeLists.txt +++ b/juiceboy/test/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable(juiceboy_test) target_sources(juiceboy_test PRIVATE "memory_region_test.cpp" + "instructions_test.cpp" ) target_link_libraries(juiceboy_test PRIVATE juiceboy GTest::gtest_main) \ No newline at end of file diff --git a/juiceboy/test/instructions_test.cpp b/juiceboy/test/instructions_test.cpp new file mode 100644 index 0000000..f898499 --- /dev/null +++ b/juiceboy/test/instructions_test.cpp @@ -0,0 +1,57 @@ +#include "juiceboy/instructions.hpp" + +#include "juiceboy/cartridge.hpp" +#include "juiceboy/processor.hpp" + +#include +#include + +namespace juiceboy::test { + +namespace { +class TestCartridge : public Cartridge { +public: + std::uint8_t ReadByte(std::uint16_t) override { return 0x00; } + void WriteByte(std::uint16_t, std::uint16_t) override { /* empty */ } +}; + +class InstructionsTest : public testing::Test { +public: + Processor &GetProcessor() { return processor; } + +private: + Processor processor{Memory{std::make_shared()}}; +}; +} // namespace + +TEST_F(InstructionsTest, Noop) { + const auto cycles = instructions::Nop(GetProcessor()); + + EXPECT_EQ(cycles, 4); + EXPECT_EQ(GetProcessor().GetRegisters().GetProgramCounter(), 0); +} + +TEST_F(InstructionsTest, LdBcD16) { + GetProcessor().GetMemory().WriteByte(0x8000, 0x34); + GetProcessor().GetMemory().WriteByte(0x8001, 0x12); + GetProcessor().GetRegisters().SetProgramCounter(0x8000); + + const auto cycles = instructions::LdBcD16(GetProcessor()); + + EXPECT_EQ(cycles, 12); + EXPECT_EQ(GetProcessor().GetRegisters().GetBC(), 0x1234); + EXPECT_EQ(GetProcessor().GetRegisters().GetProgramCounter(), 0x8002); +} + +TEST_F(InstructionsTest, LdBcA) { + GetProcessor().GetRegisters().SetBC(0x8000); + GetProcessor().GetRegisters().SetA(0x12); + + const auto cycles = instructions::LdBcA(GetProcessor()); + + EXPECT_EQ(cycles, 8); + EXPECT_EQ(GetProcessor().GetMemory().ReadByte(0x8000), 0x12); + EXPECT_EQ(GetProcessor().GetRegisters().GetProgramCounter(), 0); +} + +} // namespace juiceboy::test \ No newline at end of file