setup project and first naive look at memory

This commit is contained in:
2026-09-19 10:39:03 +02:00
parent 06814f700b
commit 7b7fc0b17e
17 changed files with 359 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
BasedOnStyle: LLVM
+12
View File
@@ -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)
+24
View File
@@ -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"
}
]
}
+23
View File
@@ -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 ()
+18
View File
@@ -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;
};
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "memory.hpp"
#include "processor.hpp"
namespace juiceboy {
class Juiceboy {
private:
Memory m_memory;
Processor m_processor;
};
}
+28
View File
@@ -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;
};
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "registers.hpp"
namespace juiceboy {
class Processor {
public:
private:
Registers m_registers;
};
} // juiceboy
+47
View File
@@ -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};
};
}
+48
View File
@@ -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) {
}
}
+4
View File
@@ -0,0 +1,4 @@
#include "juiceboy/processor.hpp"
namespace juiceboy {
} // juiceboy
+5
View File
@@ -0,0 +1,5 @@
#include "juiceboy/registers.hpp"
namespace juiceboy {
}
+8
View File
@@ -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)
+44
View File
@@ -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);
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "juiceboy",
"version-string": "0.0.1",
"dependencies": [
"gtest"
]
}