First instructions down, many to go
This commit is contained in:
@@ -2,6 +2,7 @@ add_library(juiceboy STATIC)
|
|||||||
|
|
||||||
target_sources(juiceboy
|
target_sources(juiceboy
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
"src/instructions.cpp"
|
||||||
"src/memory.cpp"
|
"src/memory.cpp"
|
||||||
"src/processor.cpp"
|
"src/processor.cpp"
|
||||||
"src/registers.cpp"
|
"src/registers.cpp"
|
||||||
@@ -11,6 +12,7 @@ target_sources(juiceboy
|
|||||||
FILES
|
FILES
|
||||||
"include/juiceboy/cartridge.hpp"
|
"include/juiceboy/cartridge.hpp"
|
||||||
"include/juiceboy/cartridges/rom_cartridge.hpp"
|
"include/juiceboy/cartridges/rom_cartridge.hpp"
|
||||||
|
"include/juiceboy/instructions.hpp"
|
||||||
"include/juiceboy/juiceboy.hpp"
|
"include/juiceboy/juiceboy.hpp"
|
||||||
"include/juiceboy/memory.hpp"
|
"include/juiceboy/memory.hpp"
|
||||||
"include/juiceboy/memory_region.hpp"
|
"include/juiceboy/memory_region.hpp"
|
||||||
|
|||||||
@@ -9,10 +9,6 @@ namespace juiceboy {
|
|||||||
|
|
||||||
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 WriteByte(std::uint16_t address, std::uint16_t value) = 0;
|
||||||
|
|
||||||
virtual void WriteWord(std::uint16_t address, std::uint16_t value) = 0;
|
|
||||||
};
|
};
|
||||||
}
|
} // namespace juiceboy
|
||||||
|
|||||||
@@ -11,13 +11,9 @@ namespace juiceboy {
|
|||||||
|
|
||||||
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:
|
private:
|
||||||
std::array<std::uint8_t, 0x8000> m_data;
|
std::array<std::uint8_t, 0x8000> m_data;
|
||||||
};
|
};
|
||||||
}
|
} // namespace juiceboy
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
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<Instruction, 256> 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
|
||||||
@@ -1,12 +1,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "memory.hpp"
|
||||||
#include "registers.hpp"
|
#include "registers.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
namespace juiceboy {
|
namespace juiceboy {
|
||||||
class Processor {
|
class Processor {
|
||||||
public:
|
public:
|
||||||
|
explicit Processor(Memory memory);
|
||||||
|
|
||||||
|
std::size_t Step();
|
||||||
|
|
||||||
|
Registers &GetRegisters();
|
||||||
|
Memory &GetMemory();
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint8_t FetchByte();
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint16_t FetchWord();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Registers m_registers;
|
Registers m_registers{};
|
||||||
|
Memory m_memory;
|
||||||
};
|
};
|
||||||
} // juiceboy
|
} // namespace juiceboy
|
||||||
|
|||||||
@@ -6,37 +6,48 @@ 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 {
|
enum class Flags { Zero, Subtract, HalfCarry, Carry };
|
||||||
Zero,
|
|
||||||
Subtract,
|
|
||||||
HalfCarry,
|
|
||||||
Carry
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @brief Get the current program counter/pointer.
|
/// @brief Get the current program counter.
|
||||||
/// @return The current value of the program counter/pointer.
|
/// @return The current value of the program counter.
|
||||||
[[nodiscard]] std::uint16_t GetProgramCounter() const;
|
[[nodiscard]] std::uint16_t GetProgramCounter() const;
|
||||||
|
|
||||||
|
/// @brief Set the current program counter value.
|
||||||
|
/// @param value The new value of the program counter.
|
||||||
|
void SetProgramCounter(std::uint16_t value);
|
||||||
|
|
||||||
|
/// @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);
|
||||||
|
|
||||||
/// @brief Get the current stack pointer.
|
/// @brief Get the current stack pointer.
|
||||||
/// @return The current value of the stack pointer.
|
/// @return The current value of the stack pointer.
|
||||||
[[nodiscard]] std::uint16_t GetStackPointer() const;
|
[[nodiscard]] std::uint16_t GetStackPointer() const;
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint8_t GetA() const;
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint16_t GetBC() const;
|
||||||
|
|
||||||
|
void SetA(std::uint8_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;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// @brief Accumulator & Flags.
|
std::uint8_t m_regA{};
|
||||||
std::uint16_t m_af{0};
|
std::uint8_t m_regF{};
|
||||||
|
|
||||||
/// @brief BC Registers.
|
std::uint8_t m_regB{};
|
||||||
std::uint16_t m_bc{0};
|
std::uint8_t m_regC{};
|
||||||
|
|
||||||
/// @brief DE Registers.
|
std::uint8_t m_regD{};
|
||||||
std::uint16_t m_de{0};
|
std::uint8_t m_regE{};
|
||||||
|
|
||||||
/// @brief HL Registers.
|
std::uint8_t m_regH{};
|
||||||
std::uint16_t m_hl{0};
|
std::uint8_t m_regL{};
|
||||||
|
|
||||||
/// @brief Program counter/pointer.
|
/// @brief Program counter/pointer.
|
||||||
std::uint16_t m_pc{0};
|
std::uint16_t m_pc{0};
|
||||||
@@ -44,4 +55,4 @@ namespace juiceboy {
|
|||||||
/// @brief Stack pointer.
|
/// @brief Stack pointer.
|
||||||
std::uint16_t m_sp{0};
|
std::uint16_t m_sp{0};
|
||||||
};
|
};
|
||||||
}
|
} // namespace juiceboy
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#include "juiceboy/instructions.hpp"
|
||||||
|
|
||||||
|
#include "juiceboy/processor.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
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
|
||||||
+31
-5
@@ -6,11 +6,10 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace juiceboy {
|
namespace juiceboy {
|
||||||
Memory::Memory(std::shared_ptr<Cartridge> cartridge) : m_cartridge(std::move(cartridge)) {
|
Memory::Memory(std::shared_ptr<Cartridge> cartridge)
|
||||||
}
|
: m_cartridge(std::move(cartridge)) {}
|
||||||
|
|
||||||
std::uint8_t Memory::ReadByte(const std::uint16_t address) {
|
std::uint8_t Memory::ReadByte(const std::uint16_t address) {
|
||||||
// Read from cartridge ROM or External RAM.
|
|
||||||
if ((address < 0x8000) || (address >= 0xA000 && address < 0xC000)) {
|
if ((address < 0x8000) || (address >= 0xA000 && address < 0xC000)) {
|
||||||
return m_cartridge->ReadByte(address);
|
return m_cartridge->ReadByte(address);
|
||||||
}
|
}
|
||||||
@@ -27,12 +26,12 @@ namespace juiceboy {
|
|||||||
return m_oam.ReadByte(address);
|
return m_oam.ReadByte(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decltype(m_hram)::ContainsAddress(address)) {
|
if (decltype(m_io)::ContainsAddress(address)) {
|
||||||
return m_io.ReadByte(address);
|
return m_io.ReadByte(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decltype(m_hram)::ContainsAddress(address)) {
|
if (decltype(m_hram)::ContainsAddress(address)) {
|
||||||
return m_io.ReadByte(address);
|
return m_hram.ReadByte(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decltype(m_interruptEnable)::ContainsAddress(address)) {
|
if (decltype(m_interruptEnable)::ContainsAddress(address)) {
|
||||||
@@ -44,5 +43,32 @@ namespace juiceboy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Memory::WriteByte(std::uint16_t address, std::uint8_t value) {
|
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
|
||||||
|
|||||||
@@ -1,4 +1,33 @@
|
|||||||
#include "juiceboy/processor.hpp"
|
#include "juiceboy/processor.hpp"
|
||||||
|
|
||||||
|
#include "juiceboy/instructions.hpp"
|
||||||
|
#include "juiceboy/memory.hpp"
|
||||||
|
|
||||||
namespace juiceboy {
|
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<std::uint16_t>(FetchByte());
|
||||||
|
const auto high = static_cast<std::uint16_t>(FetchByte()) << 8;
|
||||||
|
return low | high;
|
||||||
|
}
|
||||||
|
} // namespace juiceboy
|
||||||
|
|||||||
@@ -1,5 +1,39 @@
|
|||||||
#include "juiceboy/registers.hpp"
|
#include "juiceboy/registers.hpp"
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace juiceboy {
|
namespace juiceboy {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
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)
|
||||||
|
<< 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<std::uint8_t, std::uint8_t> WordToBytes(const std::uint16_t word) {
|
||||||
|
return {static_cast<std::uint8_t>(word & 0xFF),
|
||||||
|
static_cast<std::uint8_t>(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
|
||||||
@@ -3,6 +3,7 @@ add_executable(juiceboy_test)
|
|||||||
target_sources(juiceboy_test
|
target_sources(juiceboy_test
|
||||||
PRIVATE
|
PRIVATE
|
||||||
"memory_region_test.cpp"
|
"memory_region_test.cpp"
|
||||||
|
"instructions_test.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(juiceboy_test PRIVATE juiceboy GTest::gtest_main)
|
target_link_libraries(juiceboy_test PRIVATE juiceboy GTest::gtest_main)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#include "juiceboy/instructions.hpp"
|
||||||
|
|
||||||
|
#include "juiceboy/cartridge.hpp"
|
||||||
|
#include "juiceboy/processor.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
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<TestCartridge>()}};
|
||||||
|
};
|
||||||
|
} // 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
|
||||||
Reference in New Issue
Block a user