From 7fafc060f530cb76c88cfa133b0d5a884c072316 Mon Sep 17 00:00:00 2001 From: thomz12 Date: Sat, 19 Sep 2026 16:33:18 +0200 Subject: [PATCH] IncBC IncB DecB instructions --- juiceboy/include/juiceboy/instructions.hpp | 3 + juiceboy/include/juiceboy/registers.hpp | 15 ++- juiceboy/src/instructions.cpp | 42 +++++++- juiceboy/src/processor.cpp | 2 +- juiceboy/src/registers.cpp | 22 +++- juiceboy/test/instructions_test.cpp | 116 ++++++++++++++++++--- 6 files changed, 180 insertions(+), 20 deletions(-) diff --git a/juiceboy/include/juiceboy/instructions.hpp b/juiceboy/include/juiceboy/instructions.hpp index e7ceba2..73e5b86 100644 --- a/juiceboy/include/juiceboy/instructions.hpp +++ b/juiceboy/include/juiceboy/instructions.hpp @@ -25,6 +25,9 @@ std::uint8_t NotImplemented(Processor &processor); std::uint8_t Nop(Processor &processor); std::uint8_t LdBcD16(Processor &processor); std::uint8_t LdBcA(Processor &processor); +std::uint8_t IncBc(Processor &processor); +std::uint8_t IncB(Processor &processor); +std::uint8_t DecB(Processor &processor); } // namespace instructions } // namespace juiceboy \ No newline at end of file diff --git a/juiceboy/include/juiceboy/registers.hpp b/juiceboy/include/juiceboy/registers.hpp index eb9219f..0d3e988 100644 --- a/juiceboy/include/juiceboy/registers.hpp +++ b/juiceboy/include/juiceboy/registers.hpp @@ -6,7 +6,12 @@ namespace juiceboy { class Registers { public: /// @brief Flags in the flags (af) register. - enum class Flags { Zero, Subtract, HalfCarry, Carry }; + enum class Flags : std::uint8_t { + Zero = 0x80, + Subtract = 0x40, + HalfCarry = 0x20, + Carry = 0x10 + }; /// @brief Get the current program counter. /// @return The current value of the program counter. @@ -25,16 +30,22 @@ public: [[nodiscard]] std::uint16_t GetStackPointer() const; [[nodiscard]] std::uint8_t GetA() const; + [[nodiscard]] std::uint8_t GetF() const; + [[nodiscard]] std::uint8_t GetB() const; [[nodiscard]] std::uint16_t GetBC() const; void SetA(std::uint8_t value); + void SetF(std::uint8_t value); + void SetB(std::uint8_t value); void SetBC(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; + [[nodiscard]] bool GetFlag(Flags flag) const; + + void SetFlag(Flags flag, bool value); private: std::uint8_t m_regA{}; diff --git a/juiceboy/src/instructions.cpp b/juiceboy/src/instructions.cpp index 0a014da..bdceb31 100644 --- a/juiceboy/src/instructions.cpp +++ b/juiceboy/src/instructions.cpp @@ -11,9 +11,12 @@ 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::get<0x0>(m_instructions) = &instructions::Nop; + std::get<0x1>(m_instructions) = &instructions::LdBcD16; + std::get<0x2>(m_instructions) = &instructions::LdBcA; + std::get<0x3>(m_instructions) = &instructions::IncBc; + std::get<0x4>(m_instructions) = &instructions::IncB; + std::get<0x5>(m_instructions) = &instructions::DecB; } std::uint8_t Instructions::ExecuteInstruction(const std::uint8_t opcode, @@ -41,6 +44,39 @@ std::uint8_t LdBcA(Processor &processor) { return 8; } +std::uint8_t IncBc(Processor &processor) { + processor.GetRegisters().SetBC(processor.GetRegisters().GetBC() + 1); + return 8; +} + +std::uint8_t IncB(Processor &processor) { + const auto value = processor.GetRegisters().GetB(); + const std::uint8_t result = value + 1; + + processor.GetRegisters().SetB(result); + + processor.GetRegisters().SetFlag(Registers::Flags::Zero, result == 0); + processor.GetRegisters().SetFlag(Registers::Flags::Subtract, false); + processor.GetRegisters().SetFlag(Registers::Flags::HalfCarry, + (value & 0x0F) == 0x0F); + + return 4; +} + +std::uint8_t DecB(Processor &processor) { + const auto value = processor.GetRegisters().GetB(); + const std::uint8_t result = value - 1; + + processor.GetRegisters().SetB(result); + + processor.GetRegisters().SetFlag(Registers::Flags::Zero, result == 0); + processor.GetRegisters().SetFlag(Registers::Flags::Subtract, true); + processor.GetRegisters().SetFlag(Registers::Flags::HalfCarry, + (value & 0x0F) == 0x0); + + return 4; +} + } // namespace instructions } // namespace juiceboy \ No newline at end of file diff --git a/juiceboy/src/processor.cpp b/juiceboy/src/processor.cpp index 9dc81a0..a8b02f1 100644 --- a/juiceboy/src/processor.cpp +++ b/juiceboy/src/processor.cpp @@ -28,6 +28,6 @@ std::uint8_t Processor::FetchByte() { std::uint16_t Processor::FetchWord() { const auto low = static_cast(FetchByte()); const auto high = static_cast(FetchByte()) << 8; - return low | high; + return static_cast(low | high); } } // namespace juiceboy diff --git a/juiceboy/src/registers.cpp b/juiceboy/src/registers.cpp index a60f38d..c572806 100644 --- a/juiceboy/src/registers.cpp +++ b/juiceboy/src/registers.cpp @@ -7,8 +7,8 @@ namespace juiceboy { namespace { std::uint16_t BytesToWord(const std::uint8_t low, const std::uint8_t high) { - return static_cast(low) | static_cast(high) - << 8; + return static_cast(low) | + static_cast(high << 8); } std::pair WordToBytes(const std::uint16_t word) { @@ -28,12 +28,30 @@ void Registers::IncrementProgramCounter(const std::uint16_t value) { std::uint16_t Registers::GetStackPointer() const { return m_sp; } std::uint8_t Registers::GetA() const { return m_regA; } +std::uint8_t Registers::GetF() const { return m_regF; } +std::uint8_t Registers::GetB() const { return m_regB; } 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::SetF(const std::uint8_t value) { m_regF = value; } +void Registers::SetB(const std::uint8_t value) { m_regB = value; } void Registers::SetBC(const std::uint16_t value) { std::tie(m_regB, m_regC) = WordToBytes(value); } + +bool Registers::GetFlag(Flags flag) const { + return (m_regF & static_cast(flag)) != 0; +} + +void Registers::SetFlag(Flags flag, bool value) { + const auto mask = static_cast(flag); + + if (value) { + m_regF |= mask; + } else { + m_regF &= static_cast(~mask); + } +} } // namespace juiceboy \ No newline at end of file diff --git a/juiceboy/test/instructions_test.cpp b/juiceboy/test/instructions_test.cpp index f898499..98d7dad 100644 --- a/juiceboy/test/instructions_test.cpp +++ b/juiceboy/test/instructions_test.cpp @@ -1,7 +1,9 @@ #include "juiceboy/instructions.hpp" #include "juiceboy/cartridge.hpp" +#include "juiceboy/memory.hpp" #include "juiceboy/processor.hpp" +#include "juiceboy/registers.hpp" #include #include @@ -16,42 +18,132 @@ public: }; class InstructionsTest : public testing::Test { -public: +protected: Processor &GetProcessor() { return processor; } + Memory &GetMemory() { return processor.GetMemory(); } + Registers &GetRegisters() { return processor.GetRegisters(); } private: Processor processor{Memory{std::make_shared()}}; }; } // namespace -TEST_F(InstructionsTest, Noop) { +TEST_F(InstructionsTest, Nop) { const auto cycles = instructions::Nop(GetProcessor()); EXPECT_EQ(cycles, 4); - EXPECT_EQ(GetProcessor().GetRegisters().GetProgramCounter(), 0); + EXPECT_EQ(GetRegisters().GetProgramCounter(), 0); } TEST_F(InstructionsTest, LdBcD16) { - GetProcessor().GetMemory().WriteByte(0x8000, 0x34); - GetProcessor().GetMemory().WriteByte(0x8001, 0x12); - GetProcessor().GetRegisters().SetProgramCounter(0x8000); + GetMemory().WriteByte(0x8000, 0x34); + GetMemory().WriteByte(0x8001, 0x12); + 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); + EXPECT_EQ(GetRegisters().GetBC(), 0x1234); + EXPECT_EQ(GetRegisters().GetProgramCounter(), 0x8002); } TEST_F(InstructionsTest, LdBcA) { - GetProcessor().GetRegisters().SetBC(0x8000); - GetProcessor().GetRegisters().SetA(0x12); + GetRegisters().SetBC(0x8000); + 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); + EXPECT_EQ(GetMemory().ReadByte(0x8000), 0x12); + EXPECT_EQ(GetRegisters().GetProgramCounter(), 0); +} + +TEST_F(InstructionsTest, IncBC) { + GetRegisters().SetBC(0x1); + + const auto cycles = instructions::IncBc(GetProcessor()); + + EXPECT_EQ(cycles, 8); + EXPECT_EQ(GetRegisters().GetBC(), 0x2); + EXPECT_EQ(GetRegisters().GetProgramCounter(), 0); +} + +TEST_F(InstructionsTest, IncB) { + GetRegisters().SetB(0x1); + + const auto cycles = instructions::IncB(GetProcessor()); + + EXPECT_EQ(cycles, 4); + EXPECT_EQ(GetRegisters().GetB(), 0x2); + EXPECT_EQ(GetRegisters().GetProgramCounter(), 0); + EXPECT_FALSE(GetRegisters().GetFlag(Registers::Flags::Zero)); + EXPECT_FALSE(GetRegisters().GetFlag(Registers::Flags::Subtract)); + EXPECT_FALSE(GetRegisters().GetFlag(Registers::Flags::HalfCarry)); +} + +TEST_F(InstructionsTest, IncBSetsZeroFlag) { + GetRegisters().SetB(0xFF); + + (void)instructions::IncB(GetProcessor()); + + EXPECT_EQ(GetRegisters().GetB(), 0x00); + EXPECT_TRUE(GetRegisters().GetFlag(Registers::Flags::Zero)); +} + +TEST_F(InstructionsTest, IncBSetsHalfCarryFlag) { + GetRegisters().SetB(0x0F); + + (void)instructions::IncB(GetProcessor()); + + EXPECT_EQ(GetRegisters().GetB(), 0x10); + EXPECT_TRUE(GetRegisters().GetFlag(Registers::Flags::HalfCarry)); +} + +TEST_F(InstructionsTest, IncBPreservesCarry) { + GetRegisters().SetFlag(Registers::Flags::Carry, true); + + (void)instructions::IncB(GetProcessor()); + + EXPECT_TRUE(GetRegisters().GetFlag(Registers::Flags::Carry)); +} + +TEST_F(InstructionsTest, DecB) { + GetRegisters().SetB(0x2); + + const auto cycles = instructions::DecB(GetProcessor()); + + EXPECT_EQ(cycles, 4); + EXPECT_EQ(GetRegisters().GetB(), 0x1); + EXPECT_EQ(GetRegisters().GetProgramCounter(), 0); + EXPECT_FALSE(GetRegisters().GetFlag(Registers::Flags::Zero)); + EXPECT_TRUE(GetRegisters().GetFlag(Registers::Flags::Subtract)); + EXPECT_FALSE(GetRegisters().GetFlag(Registers::Flags::HalfCarry)); +} + +TEST_F(InstructionsTest, DecBSetsZeroFlag) { + GetRegisters().SetB(0x1); + + (void)instructions::DecB(GetProcessor()); + + EXPECT_EQ(GetRegisters().GetB(), 0x00); + EXPECT_TRUE(GetRegisters().GetFlag(Registers::Flags::Zero)); +} + +TEST_F(InstructionsTest, DecBSetsHalfCarryFlag) { + GetRegisters().SetB(0x10); + + (void)instructions::DecB(GetProcessor()); + + EXPECT_EQ(GetRegisters().GetB(), 0x0F); + EXPECT_TRUE(GetRegisters().GetFlag(Registers::Flags::HalfCarry)); +} + +TEST_F(InstructionsTest, DecBPreservesCarry) { + GetRegisters().SetFlag(Registers::Flags::Carry, true); + + (void)instructions::IncB(GetProcessor()); + + EXPECT_TRUE(GetRegisters().GetFlag(Registers::Flags::Carry)); } } // namespace juiceboy::test \ No newline at end of file