setup project and first naive look at memory
This commit is contained in:
@@ -0,0 +1 @@
|
||||
BasedOnStyle: LLVM
|
||||
@@ -0,0 +1,12 @@
|
||||
cmake_minimum_required(VERSION 4.0)
|
||||
|
||||
project(juiceboy
|
||||
VERSION 0.0.1
|
||||
LANGUAGES CXX
|
||||
)
|
||||
|
||||
include(CTest)
|
||||
find_package(GTest CONFIG REQUIRED)
|
||||
include(GoogleTest)
|
||||
|
||||
add_subdirectory(juiceboy)
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"version": 4,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "base",
|
||||
"hidden": true,
|
||||
"binaryDir": "build/${presetName}",
|
||||
"generator": "Ninja",
|
||||
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_STANDARD": "23",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "YES",
|
||||
"CMAKE_CXX_EXTENSIONS": "NO",
|
||||
"CMAKE_CXX_VISIBILITY_PRESET": "hidden"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug",
|
||||
"inherits": "base",
|
||||
"generator": "Ninja"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
add_library(juiceboy STATIC)
|
||||
|
||||
target_sources(juiceboy
|
||||
PRIVATE
|
||||
"src/memory.cpp"
|
||||
"src/processor.cpp"
|
||||
"src/registers.cpp"
|
||||
PUBLIC
|
||||
FILE_SET HEADERS
|
||||
BASE_DIRS "include"
|
||||
FILES
|
||||
"include/juiceboy/cartridge.hpp"
|
||||
"include/juiceboy/cartridges/rom_cartridge.hpp"
|
||||
"include/juiceboy/juiceboy.hpp"
|
||||
"include/juiceboy/memory.hpp"
|
||||
"include/juiceboy/memory_region.hpp"
|
||||
"include/juiceboy/processor.hpp"
|
||||
"include/juiceboy/registers.hpp"
|
||||
)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
add_subdirectory(test)
|
||||
endif ()
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace juiceboy {
|
||||
class Cartridge {
|
||||
public:
|
||||
virtual ~Cartridge() = default;
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "juiceboy/cartridge.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace juiceboy {
|
||||
class ROMCartridge : public Cartridge {
|
||||
public:
|
||||
explicit ROMCartridge(std::array<std::uint8_t, 0x8000> data);
|
||||
|
||||
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 WriteWord(std::uint16_t address, std::uint16_t value) override;
|
||||
|
||||
private:
|
||||
std::array<std::uint8_t, 0x8000> m_data;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "memory.hpp"
|
||||
#include "processor.hpp"
|
||||
|
||||
namespace juiceboy {
|
||||
class Juiceboy {
|
||||
private:
|
||||
Memory m_memory;
|
||||
Processor m_processor;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "cartridge.hpp"
|
||||
#include "memory_region.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace juiceboy {
|
||||
class Memory {
|
||||
public:
|
||||
explicit Memory(std::shared_ptr<Cartridge> cartridge);
|
||||
|
||||
[[nodiscard]] std::uint8_t ReadByte(std::uint16_t address);
|
||||
|
||||
void WriteByte(std::uint16_t address, std::uint8_t value);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cartridge> m_cartridge;
|
||||
|
||||
MemoryRegion<0x8000, 0x2000> m_vram{};
|
||||
MemoryRegion<0xC000, 0x2000> m_wram{};
|
||||
MemoryRegion<0xFE00, 0xA0> m_oam{};
|
||||
MemoryRegion<0xFF00, 0x80> m_io{};
|
||||
MemoryRegion<0xFF80, 0x7F> m_hram{};
|
||||
MemoryRegion<0xFFFF, 0x1> m_interruptEnable{};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace juiceboy {
|
||||
template<size_t Start, size_t Size>
|
||||
class MemoryRegion {
|
||||
public:
|
||||
using ArrayType = std::array<std::uint8_t, Size>;
|
||||
|
||||
constexpr MemoryRegion() : m_data{0} {
|
||||
}
|
||||
|
||||
explicit constexpr MemoryRegion(ArrayType data) : m_data{std::move(data)} {
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr bool ContainsAddress(const std::uint16_t address) {
|
||||
return (address >= Start) && (address < Start + Size);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::uint8_t ReadByte(const std::uint16_t address) const {
|
||||
if (!ContainsAddress(address)) {
|
||||
throw std::invalid_argument{"Attempted to read outside of memory region!"};
|
||||
}
|
||||
return m_data[GetLocalAddress(address)];
|
||||
}
|
||||
|
||||
constexpr void WriteByte(const std::uint16_t address, std::uint8_t value) {
|
||||
if (!ContainsAddress(address)) {
|
||||
throw std::invalid_argument{"Attempted to write outside of memory region!"};
|
||||
}
|
||||
m_data[GetLocalAddress(address)] = value;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] static constexpr std::uint16_t GetLocalAddress(const std::uint16_t address) {
|
||||
return address - Start;
|
||||
}
|
||||
|
||||
ArrayType m_data;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "registers.hpp"
|
||||
|
||||
namespace juiceboy {
|
||||
class Processor {
|
||||
public:
|
||||
|
||||
private:
|
||||
Registers m_registers;
|
||||
};
|
||||
} // juiceboy
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace juiceboy {
|
||||
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 stack pointer.
|
||||
/// @return The current value of the stack pointer.
|
||||
[[nodiscard]] std::uint16_t GetStackPointer() const;
|
||||
|
||||
/// @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:
|
||||
/// @brief Accumulator & Flags.
|
||||
std::uint16_t m_af{0};
|
||||
|
||||
/// @brief BC Registers.
|
||||
std::uint16_t m_bc{0};
|
||||
|
||||
/// @brief DE Registers.
|
||||
std::uint16_t m_de{0};
|
||||
|
||||
/// @brief HL Registers.
|
||||
std::uint16_t m_hl{0};
|
||||
|
||||
/// @brief Program counter/pointer.
|
||||
std::uint16_t m_pc{0};
|
||||
|
||||
/// @brief Stack pointer.
|
||||
std::uint16_t m_sp{0};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "juiceboy/memory.hpp"
|
||||
|
||||
#include "juiceboy/cartridge.hpp"
|
||||
#include "juiceboy/memory_region.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace juiceboy {
|
||||
Memory::Memory(std::shared_ptr<Cartridge> 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);
|
||||
}
|
||||
|
||||
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_oam)::ContainsAddress(address)) {
|
||||
return m_oam.ReadByte(address);
|
||||
}
|
||||
|
||||
if (decltype(m_hram)::ContainsAddress(address)) {
|
||||
return m_io.ReadByte(address);
|
||||
}
|
||||
|
||||
if (decltype(m_hram)::ContainsAddress(address)) {
|
||||
return m_io.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) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#include "juiceboy/processor.hpp"
|
||||
|
||||
namespace juiceboy {
|
||||
} // juiceboy
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "juiceboy/registers.hpp"
|
||||
|
||||
namespace juiceboy {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
add_executable(juiceboy_test)
|
||||
|
||||
target_sources(juiceboy_test
|
||||
PRIVATE
|
||||
"memory_region_test.cpp"
|
||||
)
|
||||
|
||||
target_link_libraries(juiceboy_test PRIVATE juiceboy GTest::gtest_main)
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "juiceboy/memory_region.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace juiceboy::test {
|
||||
TEST(MemoryRegion, DefaultConstructorZeroesEverythingOut) {
|
||||
constexpr MemoryRegion<0x00, 0x02> region;
|
||||
|
||||
EXPECT_EQ(region.ReadByte(0x00), 0);
|
||||
EXPECT_EQ(region.ReadByte(0x01), 0);
|
||||
}
|
||||
|
||||
TEST(MemoryRegion, ConstructorSetsGivenValues) {
|
||||
constexpr MemoryRegion<0x00, 0x02> region{{0xFF, 0xFF}};
|
||||
|
||||
EXPECT_EQ(region.ReadByte(0x00), 0xFF);
|
||||
EXPECT_EQ(region.ReadByte(0x01), 0xFF);
|
||||
}
|
||||
|
||||
TEST(MemoryRegion, StartAddressTranslatesAddressCorrectly) {
|
||||
constexpr MemoryRegion<0x2, 0x02> region{{0xFF, 0xFF}};
|
||||
|
||||
EXPECT_EQ(region.ReadByte(0x02), 0xFF);
|
||||
EXPECT_EQ(region.ReadByte(0x03), 0xFF);
|
||||
}
|
||||
|
||||
TEST(MemoryRegion, WriteByteUpdatesValue) {
|
||||
MemoryRegion<0x0, 0x2> region;
|
||||
|
||||
region.WriteByte(0x0, 0x1);
|
||||
|
||||
EXPECT_EQ(region.ReadByte(0x0), 0x1);
|
||||
}
|
||||
|
||||
TEST(MemoryRegion, ReadByteOutOfRangeThrows) {
|
||||
constexpr MemoryRegion<0x0, 0x02> region;
|
||||
EXPECT_THROW((void)region.ReadByte(0xFF), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(MemoryRegion, WriteByteOutOfRangeThrows) {
|
||||
MemoryRegion<0x0, 0x02> region;
|
||||
EXPECT_THROW(region.WriteByte(0xFF, 0x0), std::invalid_argument);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "juiceboy",
|
||||
"version-string": "0.0.1",
|
||||
"dependencies": [
|
||||
"gtest"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user