diff options
Diffstat (limited to 'lib/nes/util')
| -rw-r--r-- | lib/nes/util/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | lib/nes/util/address.hh | 73 | ||||
| -rw-r--r-- | lib/nes/util/button-mask.hh | 43 | ||||
| -rw-r--r-- | lib/nes/util/cycle-count.hh | 87 | ||||
| -rw-r--r-- | lib/nes/util/debug.hh | 15 | ||||
| -rw-r--r-- | lib/nes/util/rgb.hh | 33 | ||||
| -rw-r--r-- | lib/nes/util/snapshot.hh | 63 |
7 files changed, 323 insertions, 0 deletions
diff --git a/lib/nes/util/CMakeLists.txt b/lib/nes/util/CMakeLists.txt new file mode 100644 index 0000000..0fdb0ba --- /dev/null +++ b/lib/nes/util/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources( + nes + PRIVATE + address.hh + button-mask.hh + cycle-count.hh + debug.hh + rgb.hh + snapshot.hh)
\ No newline at end of file diff --git a/lib/nes/util/address.hh b/lib/nes/util/address.hh new file mode 100644 index 0000000..7987d07 --- /dev/null +++ b/lib/nes/util/address.hh @@ -0,0 +1,73 @@ +#pragma once + +#include <cstdint> + +namespace nes +{ + /// An address on the NES. + class address + { + std::uint16_t absolute_{ 0 }; + + public: + /// Create a zero address. + constexpr explicit address() = default; + + /// Create an address from its absolute value. + constexpr explicit address(std::uint16_t const absolute) + : absolute_{ absolute } + { + } + + /// Create an address from its page and offset combination. + constexpr explicit address(std::uint8_t const page, std::uint8_t const offset) + : address{ static_cast<std::uint16_t>((page << 8) | (offset << 0)) } + { + } + + /// Get the absolute address value. + constexpr auto get_absolute() const -> std::uint16_t + { + return absolute_; + } + + /// Get the address page (the first 8 bits). + constexpr auto get_page() const -> std::uint8_t + { + return static_cast<std::uint8_t>((get_absolute() & 0xFF00) >> 8); + } + + /// Get the offset within the page (the last 8 bits). + constexpr auto get_offset() const -> std::uint8_t + { + return static_cast<std::uint8_t>((get_absolute() & 0x00FF) >> 0); + } + }; + + constexpr auto operator==(address const a, address const b) -> bool { return a.get_absolute() == b.get_absolute(); } + constexpr auto operator!=(address const a, address const b) -> bool { return a.get_absolute() != b.get_absolute(); } + constexpr auto operator<(address const a, address const b) -> bool { return a.get_absolute() < b.get_absolute(); } + constexpr auto operator>(address const a, address const b) -> bool { return a.get_absolute() > b.get_absolute(); } + constexpr auto operator<=(address const a, address const b) -> bool { return a.get_absolute() <= b.get_absolute(); } + constexpr auto operator>=(address const a, address const b) -> bool { return a.get_absolute() >= b.get_absolute(); } + + constexpr auto operator+(address const a, std::uint16_t const b) -> address + { + return address{ static_cast<std::uint16_t>(a.get_absolute() + b) }; + } + + constexpr auto operator+(std::uint16_t const a, address const b) -> address + { + return address{ static_cast<std::uint16_t>(a + b.get_absolute()) }; + } + + constexpr auto operator-(address const a, std::uint16_t const b) -> address + { + return address{ static_cast<std::uint16_t>(a.get_absolute() - b) }; + } + + constexpr auto operator%(address const a, std::uint16_t const b) -> address + { + return address{ static_cast<std::uint16_t>(a.get_absolute() % b) }; + } +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/util/button-mask.hh b/lib/nes/util/button-mask.hh new file mode 100644 index 0000000..b010b9e --- /dev/null +++ b/lib/nes/util/button-mask.hh @@ -0,0 +1,43 @@ +#pragma once + +#include <cstdint> + +namespace nes +{ + class button_mask + { + std::uint8_t raw_value_{ 0 }; + + public: + constexpr explicit button_mask() = default; + + static constexpr auto from_raw_value(std::uint8_t const raw_value) -> button_mask + { + return button_mask{ raw_value }; + } + + constexpr auto add(button_mask const other) -> void { raw_value_ |= other.raw_value_; } + constexpr auto remove(button_mask const other) -> void { raw_value_ ^= other.raw_value_; } + constexpr auto contains(button_mask const other) const -> bool { return (raw_value_ & other.raw_value_) == other.raw_value_; } + constexpr auto is_empty() const -> bool { return raw_value_ == 0; } + constexpr auto get_raw_value() const -> std::uint8_t { return raw_value_; } + + private: + constexpr explicit button_mask(std::uint8_t const raw_value) + : raw_value_{ raw_value } + { + } + }; + + namespace buttons + { + static constexpr auto a = button_mask::from_raw_value(1 << 0); + static constexpr auto b = button_mask::from_raw_value(1 << 1); + static constexpr auto select = button_mask::from_raw_value(1 << 2); + static constexpr auto start = button_mask::from_raw_value(1 << 3); + static constexpr auto up = button_mask::from_raw_value(1 << 4); + static constexpr auto down = button_mask::from_raw_value(1 << 5); + static constexpr auto left = button_mask::from_raw_value(1 << 6); + static constexpr auto right = button_mask::from_raw_value(1 << 7); + } // namespace buttons +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/util/cycle-count.hh b/lib/nes/util/cycle-count.hh new file mode 100644 index 0000000..f1a4a7e --- /dev/null +++ b/lib/nes/util/cycle-count.hh @@ -0,0 +1,87 @@ +#pragma once + +#include <chrono> + +namespace nes +{ + /// A cycle count that can be used for multiple components. + /// + /// Each component can have a different clock frequency and this structure offers conversions between different + /// cycle counts. + class cycle_count + { + friend constexpr auto operator+(cycle_count, cycle_count) -> cycle_count; + friend constexpr auto operator-(cycle_count, cycle_count) -> cycle_count; + friend constexpr auto operator*(std::uint64_t, cycle_count) -> cycle_count; + friend constexpr auto operator*(cycle_count, std::uint64_t) -> cycle_count; + friend constexpr auto operator/(cycle_count, cycle_count) -> std::uint64_t; + + std::uint64_t units_{ 0 }; // The most fine-granular cycle count (here: PPU cycles). + + static constexpr auto ppu_cycle() -> cycle_count { return cycle_count{ 1 }; } + static constexpr auto cpu_cycle() -> cycle_count { return cycle_count{ 3 }; } + static constexpr auto second() -> cycle_count { return 1789773 * cpu_cycle(); } + + public: + constexpr explicit cycle_count() = default; + + constexpr auto operator+=(cycle_count const other) -> cycle_count& { return *this = *this + other; } + constexpr auto operator*=(std::uint64_t const other) -> cycle_count& { return *this = *this * other; } + + static constexpr auto from_units(std::uint64_t const units) -> cycle_count + { + return cycle_count{ units }; + } + + static constexpr auto from_duration(std::chrono::microseconds const duration) -> cycle_count + { + return from_units(static_cast<std::uint64_t>(duration.count()) * second().get_units() / (1000 * 1000)); + } + + static constexpr auto from_cpu(std::uint64_t const cpu_cycles) -> cycle_count + { + return cpu_cycles * cpu_cycle(); + } + + static constexpr auto from_ppu(std::uint64_t const ppu_cycles) -> cycle_count + { + return ppu_cycles * ppu_cycle(); + } + + /// Value in implementation-defined units. + constexpr auto get_units() const -> std::uint64_t { return units_; } + + constexpr auto to_duration() const -> std::chrono::microseconds + { + return std::chrono::microseconds{ get_units() * 1000 * 1000 / second().get_units() }; + } + + constexpr auto to_cpu() const -> std::uint64_t + { + return *this / cpu_cycle(); + } + + constexpr auto to_ppu() const -> std::uint64_t + { + return *this / ppu_cycle(); + } + + private: + constexpr explicit cycle_count(std::uint64_t const units) + : units_{ units } + { + } + }; + + constexpr auto operator==(cycle_count const a, cycle_count const b) -> bool { return a.get_units() == b.get_units(); } + constexpr auto operator!=(cycle_count const a, cycle_count const b) -> bool { return a.get_units() != b.get_units(); } + constexpr auto operator<(cycle_count const a, cycle_count const b) -> bool { return a.get_units() < b.get_units(); } + constexpr auto operator>(cycle_count const a, cycle_count const b) -> bool { return a.get_units() > b.get_units(); } + constexpr auto operator<=(cycle_count const a, cycle_count const b) -> bool { return a.get_units() <= b.get_units(); } + constexpr auto operator>=(cycle_count const a, cycle_count const b) -> bool { return a.get_units() >= b.get_units(); } + constexpr auto operator+(cycle_count const a, cycle_count const b) -> cycle_count { return cycle_count{ a.get_units() + b.get_units() }; } + constexpr auto operator-(cycle_count const a, cycle_count const b) -> cycle_count { return cycle_count{ a.get_units() - b.get_units() }; } + constexpr auto operator*(std::uint64_t const a, cycle_count const b) -> cycle_count { return cycle_count{ a * b.get_units() }; } + constexpr auto operator*(cycle_count const a, std::uint64_t const b) -> cycle_count { return cycle_count{ a.get_units() * b }; } + constexpr auto operator/(cycle_count const a, cycle_count const b) -> std::uint64_t { return a.get_units() / b.get_units(); } +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/util/debug.hh b/lib/nes/util/debug.hh new file mode 100644 index 0000000..409d55a --- /dev/null +++ b/lib/nes/util/debug.hh @@ -0,0 +1,15 @@ +#pragma once + +#ifdef NES_ENABLE_DEBUG_OUTPUT + +#include <spdlog/spdlog.h> + +#define NES_DEBUG_LOG(name, ...) \ + spdlog::info("[" #name "] " __VA_ARGS__) + +#else + +#define NES_DEBUG_LOG(name, fmt, ...) \ + (void)0 + +#endif
\ No newline at end of file diff --git a/lib/nes/util/rgb.hh b/lib/nes/util/rgb.hh new file mode 100644 index 0000000..d62c70c --- /dev/null +++ b/lib/nes/util/rgb.hh @@ -0,0 +1,33 @@ +#pragma once + +#include <cstdint> + +namespace nes +{ + /// An RGB value. + class rgb + { + public: + explicit rgb() = default; + + explicit rgb(std::uint8_t const r, std::uint8_t const g, std::uint8_t const b) + : r{ r } + , g{ g } + , b{ b } + { + } + + static auto from_hex(std::uint32_t const hex) -> rgb + { + auto res = rgb{}; + res.r = (hex & 0x00FF0000) >> 16; + res.g = (hex & 0x0000FF00) >> 8; + res.b = (hex & 0x000000FF) >> 0; + return res; + } + + std::uint8_t r{ 0 }; + std::uint8_t g{ 0 }; + std::uint8_t b{ 0 }; + }; +} // namespace nes
\ No newline at end of file diff --git a/lib/nes/util/snapshot.hh b/lib/nes/util/snapshot.hh new file mode 100644 index 0000000..de23a46 --- /dev/null +++ b/lib/nes/util/snapshot.hh @@ -0,0 +1,63 @@ +#pragma once + +#include "nes/util/cycle-count.hh" +#include <vector> +#include <cstdint> + +namespace nes +{ + /// System snapshot used for comparison with a reference implementation. + struct snapshot + { + explicit snapshot() = default; + + cycle_count cpu_cycle; + cycle_count ppu_cycle; + std::vector<std::uint8_t> ram; + std::vector<std::uint8_t> sram; + std::vector<std::uint8_t> vram; + std::vector<std::uint8_t> oam; + struct + { + std::uint16_t pc{}; + std::uint8_t sp{}; + std::uint8_t a{}; + std::uint8_t x{}; + std::uint8_t y{}; + std::uint8_t p{}; + bool c{}; + bool z{}; + bool i{}; + bool d{}; + bool b{}; + bool v{}; + bool n{}; + } registers{}; + }; + + inline auto operator==(snapshot const& a, snapshot const& b) -> bool + { + if (a.cpu_cycle != b.cpu_cycle) { return false; } + if (a.ppu_cycle != b.ppu_cycle) { return false; } + if (a.ram != b.ram) { return false; } + if (a.sram != b.sram) { return false; } + if (a.vram != b.vram) { return false; } + if (a.oam != b.oam) { return false; } + if (a.registers.pc != b.registers.pc) { return false; } + if (a.registers.sp != b.registers.sp) { return false; } + if (a.registers.a != b.registers.a) { return false; } + if (a.registers.x != b.registers.x) { return false; } + if (a.registers.y != b.registers.y) { return false; } + if (a.registers.p != b.registers.p) { return false; } + if (a.registers.c != b.registers.c) { return false; } + if (a.registers.z != b.registers.z) { return false; } + if (a.registers.i != b.registers.i) { return false; } + if (a.registers.d != b.registers.d) { return false; } + if (a.registers.b != b.registers.b) { return false; } + if (a.registers.v != b.registers.v) { return false; } + if (a.registers.n != b.registers.n) { return false; } + return true; + } + + inline auto operator!=(snapshot const& a, snapshot const& b) -> bool { return !(a == b); } +} // namespace nes
\ No newline at end of file |
