IncBC IncB DecB instructions
This commit is contained in:
@@ -25,6 +25,9 @@ std::uint8_t NotImplemented(Processor &processor);
|
|||||||
std::uint8_t Nop(Processor &processor);
|
std::uint8_t Nop(Processor &processor);
|
||||||
std::uint8_t LdBcD16(Processor &processor);
|
std::uint8_t LdBcD16(Processor &processor);
|
||||||
std::uint8_t LdBcA(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 instructions
|
||||||
|
|
||||||
} // namespace juiceboy
|
} // namespace juiceboy
|
||||||
@@ -6,7 +6,12 @@ namespace juiceboy {
|
|||||||
class Registers {
|
class Registers {
|
||||||
public:
|
public:
|
||||||
/// @brief Flags in the flags (af) register.
|
/// @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.
|
/// @brief Get the current program counter.
|
||||||
/// @return The current value of the program counter.
|
/// @return The current value of the program counter.
|
||||||
@@ -25,16 +30,22 @@ public:
|
|||||||
[[nodiscard]] std::uint16_t GetStackPointer() const;
|
[[nodiscard]] std::uint16_t GetStackPointer() const;
|
||||||
|
|
||||||
[[nodiscard]] std::uint8_t GetA() 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;
|
[[nodiscard]] std::uint16_t GetBC() const;
|
||||||
|
|
||||||
void SetA(std::uint8_t value);
|
void SetA(std::uint8_t value);
|
||||||
|
void SetF(std::uint8_t value);
|
||||||
|
void SetB(std::uint8_t value);
|
||||||
|
|
||||||
void SetBC(std::uint16_t value);
|
void SetBC(std::uint16_t value);
|
||||||
|
|
||||||
/// @brief Get the current value of the given flag.
|
/// @brief Get the current value of the given flag.
|
||||||
/// @return True if the given flag bit is set, false otherwise.
|
/// @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:
|
private:
|
||||||
std::uint8_t m_regA{};
|
std::uint8_t m_regA{};
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ Instructions::Instructions() {
|
|||||||
// For now, all not implemented instructions should throw.
|
// For now, all not implemented instructions should throw.
|
||||||
m_instructions.fill(&instructions::NotImplemented);
|
m_instructions.fill(&instructions::NotImplemented);
|
||||||
|
|
||||||
std::get<0>(m_instructions) = &instructions::Nop;
|
std::get<0x0>(m_instructions) = &instructions::Nop;
|
||||||
std::get<1>(m_instructions) = &instructions::LdBcD16;
|
std::get<0x1>(m_instructions) = &instructions::LdBcD16;
|
||||||
std::get<2>(m_instructions) = &instructions::LdBcA;
|
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,
|
std::uint8_t Instructions::ExecuteInstruction(const std::uint8_t opcode,
|
||||||
@@ -41,6 +44,39 @@ std::uint8_t LdBcA(Processor &processor) {
|
|||||||
return 8;
|
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 instructions
|
||||||
|
|
||||||
} // namespace juiceboy
|
} // namespace juiceboy
|
||||||
@@ -28,6 +28,6 @@ std::uint8_t Processor::FetchByte() {
|
|||||||
std::uint16_t Processor::FetchWord() {
|
std::uint16_t Processor::FetchWord() {
|
||||||
const auto low = static_cast<std::uint16_t>(FetchByte());
|
const auto low = static_cast<std::uint16_t>(FetchByte());
|
||||||
const auto high = static_cast<std::uint16_t>(FetchByte()) << 8;
|
const auto high = static_cast<std::uint16_t>(FetchByte()) << 8;
|
||||||
return low | high;
|
return static_cast<std::uint16_t>(low | high);
|
||||||
}
|
}
|
||||||
} // namespace juiceboy
|
} // namespace juiceboy
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ namespace juiceboy {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::uint16_t BytesToWord(const std::uint8_t low, const std::uint8_t high) {
|
std::uint16_t BytesToWord(const std::uint8_t low, const std::uint8_t high) {
|
||||||
return static_cast<std::uint16_t>(low) | static_cast<std::uint16_t>(high)
|
return static_cast<std::uint16_t>(low) |
|
||||||
<< 8;
|
static_cast<std::uint16_t>(high << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::uint8_t, std::uint8_t> WordToBytes(const std::uint16_t word) {
|
std::pair<std::uint8_t, std::uint8_t> 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::uint16_t Registers::GetStackPointer() const { return m_sp; }
|
||||||
|
|
||||||
std::uint8_t Registers::GetA() const { return m_regA; }
|
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); }
|
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::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) {
|
void Registers::SetBC(const std::uint16_t value) {
|
||||||
std::tie(m_regB, m_regC) = WordToBytes(value);
|
std::tie(m_regB, m_regC) = WordToBytes(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Registers::GetFlag(Flags flag) const {
|
||||||
|
return (m_regF & static_cast<std::uint8_t>(flag)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Registers::SetFlag(Flags flag, bool value) {
|
||||||
|
const auto mask = static_cast<std::uint8_t>(flag);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
m_regF |= mask;
|
||||||
|
} else {
|
||||||
|
m_regF &= static_cast<std::uint8_t>(~mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace juiceboy
|
} // namespace juiceboy
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "juiceboy/instructions.hpp"
|
#include "juiceboy/instructions.hpp"
|
||||||
|
|
||||||
#include "juiceboy/cartridge.hpp"
|
#include "juiceboy/cartridge.hpp"
|
||||||
|
#include "juiceboy/memory.hpp"
|
||||||
#include "juiceboy/processor.hpp"
|
#include "juiceboy/processor.hpp"
|
||||||
|
#include "juiceboy/registers.hpp"
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -16,42 +18,132 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class InstructionsTest : public testing::Test {
|
class InstructionsTest : public testing::Test {
|
||||||
public:
|
protected:
|
||||||
Processor &GetProcessor() { return processor; }
|
Processor &GetProcessor() { return processor; }
|
||||||
|
Memory &GetMemory() { return processor.GetMemory(); }
|
||||||
|
Registers &GetRegisters() { return processor.GetRegisters(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Processor processor{Memory{std::make_shared<TestCartridge>()}};
|
Processor processor{Memory{std::make_shared<TestCartridge>()}};
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_F(InstructionsTest, Noop) {
|
TEST_F(InstructionsTest, Nop) {
|
||||||
const auto cycles = instructions::Nop(GetProcessor());
|
const auto cycles = instructions::Nop(GetProcessor());
|
||||||
|
|
||||||
EXPECT_EQ(cycles, 4);
|
EXPECT_EQ(cycles, 4);
|
||||||
EXPECT_EQ(GetProcessor().GetRegisters().GetProgramCounter(), 0);
|
EXPECT_EQ(GetRegisters().GetProgramCounter(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(InstructionsTest, LdBcD16) {
|
TEST_F(InstructionsTest, LdBcD16) {
|
||||||
GetProcessor().GetMemory().WriteByte(0x8000, 0x34);
|
GetMemory().WriteByte(0x8000, 0x34);
|
||||||
GetProcessor().GetMemory().WriteByte(0x8001, 0x12);
|
GetMemory().WriteByte(0x8001, 0x12);
|
||||||
GetProcessor().GetRegisters().SetProgramCounter(0x8000);
|
GetRegisters().SetProgramCounter(0x8000);
|
||||||
|
|
||||||
const auto cycles = instructions::LdBcD16(GetProcessor());
|
const auto cycles = instructions::LdBcD16(GetProcessor());
|
||||||
|
|
||||||
EXPECT_EQ(cycles, 12);
|
EXPECT_EQ(cycles, 12);
|
||||||
EXPECT_EQ(GetProcessor().GetRegisters().GetBC(), 0x1234);
|
EXPECT_EQ(GetRegisters().GetBC(), 0x1234);
|
||||||
EXPECT_EQ(GetProcessor().GetRegisters().GetProgramCounter(), 0x8002);
|
EXPECT_EQ(GetRegisters().GetProgramCounter(), 0x8002);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(InstructionsTest, LdBcA) {
|
TEST_F(InstructionsTest, LdBcA) {
|
||||||
GetProcessor().GetRegisters().SetBC(0x8000);
|
GetRegisters().SetBC(0x8000);
|
||||||
GetProcessor().GetRegisters().SetA(0x12);
|
GetRegisters().SetA(0x12);
|
||||||
|
|
||||||
const auto cycles = instructions::LdBcA(GetProcessor());
|
const auto cycles = instructions::LdBcA(GetProcessor());
|
||||||
|
|
||||||
EXPECT_EQ(cycles, 8);
|
EXPECT_EQ(cycles, 8);
|
||||||
EXPECT_EQ(GetProcessor().GetMemory().ReadByte(0x8000), 0x12);
|
EXPECT_EQ(GetMemory().ReadByte(0x8000), 0x12);
|
||||||
EXPECT_EQ(GetProcessor().GetRegisters().GetProgramCounter(), 0);
|
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
|
} // namespace juiceboy::test
|
||||||
Reference in New Issue
Block a user