From 8e42806e179d51e0015f7160ed9b4d64c5b6a6e4 Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Sun, 29 Jun 2025 17:34:11 +0200 Subject: [PATCH 1/6] fix DEBUG and RELEASE macros handling --- .clang-tidy | 3 +-- CMakeLists.txt | 4 +++- src/core/cartridge.cpp | 6 ++---- src/core/cpu.cpp | 6 +++++- src/raylib/CMakeLists.txt | 20 +++++++++----------- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 28f6c68..b28b0f7 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,13 +1,12 @@ -Checks: 'clang-diagnostic-*,clang-analyzer-*' Checks: > clang-diagnostic-*, clang-analyzer-*, + -clang-diagnostic-sign-compare, bugprone-suspicious-semicolon, bugprone-unused-return-value, bugprone-narrowing-conversions, bugprone-signed-char-misuse, misc-unused-parameters, - readability-implicit-bool-conversion, readability-misleading-indentation WarningsAsErrors: '' HeaderFileExtensions: diff --git a/CMakeLists.txt b/CMakeLists.txt index b355bd4..5854dfb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,9 @@ function(add_strict_warnings target) endfunction() if(CMAKE_BUILD_TYPE STREQUAL "Release") - add_compile_definitions(your_target PRIVATE RELEASE) + add_compile_definitions(RELEASE) +else() + add_compile_definitions(DEBUG) endif() include(options.cmake) diff --git a/src/core/cartridge.cpp b/src/core/cartridge.cpp index 62fa14c..347eb59 100644 --- a/src/core/cartridge.cpp +++ b/src/core/cartridge.cpp @@ -151,8 +151,7 @@ void CoreCartridge::initRam( const CoreCartridge::RamSizeByte size ) { CoreCartridge::CoreCartridge( std::vector&& rom_ ) : rom( std::move( rom_ ) ) { logDebug( "CoreCartridge constructor" ); - const auto cartridgeType = rom[addr::cartridgeType]; - logDebug( std::format( "Read cartridgeType byte: {}", toHex( cartridgeType ) ) ); + logDebug( std::format( "Read cartridgeType byte: {}", toHex( rom[addr::cartridgeType] ) ) ); const auto romSizeByte = rom[addr::romSize]; logDebug( std::format( "Read ROM size byte: {}", toHex( romSizeByte ) ) ); @@ -164,7 +163,6 @@ CoreCartridge::CoreCartridge( std::vector&& rom_ ) : rom( std::move( ro }; - bool CoreCartridge::checkCopyRightHeader( const uint16_t bankNumber ) const { if( getRomBankCount() < bankNumber ) { logWarning( 0, "Bank number out of range." ); @@ -175,7 +173,7 @@ bool CoreCartridge::checkCopyRightHeader( const uint16_t bankNumber ) const { std::equal( romBanks[bankNumber].begin() + addr::logoStart, romBanks[bankNumber].begin() + addr::logoEnd, std::begin( nintendoCopyrightHeader ) ); - const auto checkMBC1M = bankNumber != 0; + [[maybe_unused]] const auto checkMBC1M = bankNumber != 0; if( isLogoEqual ) { logDebug( std::format( "Nintendo copyright header matches! This is nintendo {}cartridge.", checkMBC1M ? "MBC1M " : "" ) ); diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index 67c2f84..383d9b2 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -157,6 +157,7 @@ CoreCpu::CoreCpu( IBus& bus_ ) : bus( bus_ ) { }; +#ifdef DEBUG constexpr std::string_view MicroOperationTypeString[] = { "NOP", "STOP", @@ -264,13 +265,16 @@ constexpr std::string_view MicroOperationTypeString[] = { "INVALID", "END", }; +#endif -void CoreCpu::logOperation( MicroOperation_t mop ) { +void CoreCpu::logOperation( [[maybe_unused]] MicroOperation_t mop ) { +#ifdef DEBUG std::string opd1; std::string opd2; // FIXME it's temporary solution logDebug( std::format( "MOT<{}>, opd1<{}>, opd2<{}>", MicroOperationTypeString[Enum_t( mop.type )], Enum_t( mop.operand1 ), Enum_t( mop.operand2 ) ) ); logDebug( std::format( "CPU flags ZNHC<{:04b}>", ( registers[7] >> 4 ) ) ); +#endif } diff --git a/src/raylib/CMakeLists.txt b/src/raylib/CMakeLists.txt index 91763eb..492c2a3 100644 --- a/src/raylib/CMakeLists.txt +++ b/src/raylib/CMakeLists.txt @@ -7,7 +7,7 @@ add_strict_warnings(gb_raylib) include(FetchContent) set(FETCHCONTENT_QUIET FALSE) set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -set(BUILD_GAMES OFF CACHE BOOL "" FORCE) +set(BUILD_GAMES OFF CACHE BOOL "" FORCE) FetchContent_Declare( raylib @@ -19,28 +19,26 @@ FetchContent_Declare( FetchContent_Declare( tinyfiledialogs - URL "https://sourceforge.net/projects/tinyfiledialogs/files/tinyfiledialogs-current.zip/download" + URL + "https://sourceforge.net/projects/tinyfiledialogs/files/tinyfiledialogs-current.zip/download" DOWNLOAD_EXTRACT_TIMESTAMP TRUE ) FetchContent_MakeAvailable(tinyfiledialogs) FetchContent_MakeAvailable(raylib) -add_library(tinyfiledialogs STATIC +add_library( + tinyfiledialogs + STATIC ${tinyfiledialogs_SOURCE_DIR}/tinyfiledialogs.c ${tinyfiledialogs_SOURCE_DIR}/tinyfiledialogs.h ) target_include_directories(tinyfiledialogs PUBLIC ${tinyfiledialogs_SOURCE_DIR}) -target_link_libraries(gb_raylib PUBLIC - raylib - gb_core - cartridge_impls - tinyfiledialogs +target_link_libraries( + gb_raylib + PUBLIC raylib gb_core cartridge_impls tinyfiledialogs ) add_executable(gb_raylib_executable ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) target_link_libraries(gb_raylib_executable PRIVATE gb_raylib) -set_target_properties(gb_raylib_executable PROPERTIES - OUTPUT_NAME_RELEASE "little-boy" -) -- GitLab From 46b23c40a7056a0b328229588be0463dc07e302c Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Sun, 29 Jun 2025 21:57:15 +0200 Subject: [PATCH 2/6] fix currentMopType immediately after decode; add cpu tests related to branching --- include/core/core_constants.hpp | 19 ++++++++- include/core/cpu.hpp | 14 +++---- src/core/cartridge.cpp | 1 - src/core/cpu.cpp | 18 +++++---- src/core/cpu_execute.cpp | 1 + test/core/test_cpu.cpp | 70 +++++++++++++++++++++++++++++++++ test/core/test_cpu_timings.cpp | 2 - test/core/test_ppu.cpp | 12 +++--- test/dummy_types.hpp | 31 ++++++++++++++- 9 files changed, 141 insertions(+), 27 deletions(-) create mode 100644 test/core/test_cpu.cpp diff --git a/include/core/core_constants.hpp b/include/core/core_constants.hpp index ca427fb..3ab9e27 100644 --- a/include/core/core_constants.hpp +++ b/include/core/core_constants.hpp @@ -98,6 +98,20 @@ constexpr uint16_t globalChecksumStart = 0x014E; constexpr uint16_t globalChecksumEnd = 0x014F; } // namespace addr +namespace bitMask { +// Cpu flags +constexpr int zeroFlag = ( 1 << 7 ); +constexpr int subtractionFlag = ( 1 << 6 ); +constexpr int halfCarryFlag = ( 1 << 5 ); +constexpr int carryFlag = ( 1 << 4 ); +// Interrupt enable bit and interrupt flag bit +constexpr int vBlankInterrupt = 1; +constexpr int lcdInterrupt = ( 1 << 1 ); +constexpr int timerInterrupt = ( 1 << 2 ); +constexpr int serialInterrupt = ( 1 << 3 ); +constexpr int joypadInterrupt = ( 1 << 4 ); +} // namespace bitMask + namespace size { constexpr uint16_t oam = 160; constexpr uint16_t tile = 16; @@ -106,6 +120,7 @@ constexpr uint16_t videoRam = 8192; // other namespace constant { -static constexpr unsigned tickrate = 4'194'304; -static constexpr float oscillatoryTime = 1. / tickrate; +constexpr unsigned tickrate = 4'194'304; +constexpr double oscillatoryTime = 1. / tickrate; +constexpr uint8_t invalidReadValue = 0xFF; } // namespace constant diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index 481a052..ae2d41e 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -4,7 +4,6 @@ #include "core/cycles.hpp" #include #include -#include #include template @@ -131,7 +130,8 @@ public: h, l, phl, - a, + a = 7, // sixth in registers array but encoded by 7 in opcodes + f = a, // f is seventh in registers array and never encoded by opcode // r16 bc = 0, de, @@ -195,7 +195,7 @@ public: void execute( MicroOperation_t mop ); bool handleInterrupts(); - bool isConditionMet( Operand_t condition ); + bool isConditionMet( Operand_t condition ) const; MicroOperations_t decode(); //helpers @@ -208,10 +208,10 @@ public: void setWZ( uint16_t value ) { Z = lsb(value); W = msb( value ); } bool isPHL( Operand_t operand ) { return operand == Operand_t::phl; } - bool getZFlag() { return registers[7] &( 1 << 7 ); } // Zero flag - bool getNFlag() { return registers[7] &( 1 << 6 ); } // BDC substraction flag - bool getHFlag() { return registers[7] &( 1 << 5 ); } // BDC half carry flag - bool getCFlag() { return registers[7] &( 1 << 4 ); } // Carry flag + bool getZFlag() const { return registers[7] &( 1 << 7 ); } // Zero flag + bool getNFlag() const { return registers[7] &( 1 << 6 ); } // BDC substraction flag + bool getHFlag() const { return registers[7] &( 1 << 5 ); } // BDC half carry flag + bool getCFlag() const { return registers[7] &( 1 << 4 ); } // Carry flag void setZFlag( bool val ) { registers[7] = static_cast(val ? registers[7] | ( 1 << 7 ) : registers[7] & ~( 1 << 7 )); } void setNFlag( bool val ) { registers[7] = static_cast(val ? registers[7] | ( 1 << 6 ) : registers[7] & ~( 1 << 6 )); } diff --git a/src/core/cartridge.cpp b/src/core/cartridge.cpp index 347eb59..b96743a 100644 --- a/src/core/cartridge.cpp +++ b/src/core/cartridge.cpp @@ -150,7 +150,6 @@ void CoreCartridge::initRam( const CoreCartridge::RamSizeByte size ) { CoreCartridge::CoreCartridge( std::vector&& rom_ ) : rom( std::move( rom_ ) ) { logDebug( "CoreCartridge constructor" ); - logDebug( std::format( "Read cartridgeType byte: {}", toHex( rom[addr::cartridgeType] ) ) ); const auto romSizeByte = rom[addr::romSize]; diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index 383d9b2..5f8d463 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -73,27 +73,30 @@ void CoreCpu::writeR16( Operand_t opd, uint16_t value ) { } -bool CoreCpu::isConditionMet( Operand_t condition ) { +bool CoreCpu::isConditionMet( Operand_t condition ) const { + bool conditionMet = false; using enum Operand_t; if( condition == condNZ && ! getZFlag() ) - return true; + conditionMet = true; if( condition == condZ && getZFlag() ) - return true; + conditionMet = true; if( condition == condNC && ! getCFlag() ) - return true; + conditionMet = true; if( condition == condC && getCFlag() ) - return true; + conditionMet = true; - return false; + logDebug( std::format( "Condition <{}> returns {}", std::to_underlying( condition ), conditionMet ) ); + return conditionMet; } //-------------------------------------------------- unsigned CoreCpu::tick() { - const MicroOperationType_t currentMopType = mopQueue[atMicroOperationNr].type; + MicroOperationType_t currentMopType = mopQueue[atMicroOperationNr].type; if( currentMopType == MicroOperationType_t::END && ! handleInterrupts() ) { logDebug( std::format( "PC: {} - Decoding next instruction", toHex( PC ) ) ); mopQueue = decode(); atMicroOperationNr = 0; + currentMopType = mopQueue[0].type; } execute( mopQueue[atMicroOperationNr] ); @@ -102,6 +105,7 @@ unsigned CoreCpu::tick() { currentMopType == MicroOperationType_t::COND_CHECK__LD_IMM_TO_Z ) ) { mopQueue[atMicroOperationNr + 1] = { MicroOperationType_t::NOP }; mopQueue[atMicroOperationNr + 2] = { MicroOperationType_t::END }; + logDebug( "branch not taken" ); } if( enableIMELater && atMicroOperationNr == 0 ) { // DI takes one cycle, so we are just after next one interruptMasterEnabled = true; diff --git a/src/core/cpu_execute.cpp b/src/core/cpu_execute.cpp index e0eb3d7..5104446 100644 --- a/src/core/cpu_execute.cpp +++ b/src/core/cpu_execute.cpp @@ -130,6 +130,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { setZNHCFlags( ! result, 0, 0, 0 ); } break; case ALU_A_CP_Z: + logDebug( std::format( "Compare register A value <{}> with Z value {}", readR8( Operand_t::a ), Z ) ); subFromR8( Operand_t::a, Z, true ); break; case ALU_ADD_SPL_TO_Z: diff --git a/test/core/test_cpu.cpp b/test/core/test_cpu.cpp new file mode 100644 index 0000000..023508b --- /dev/null +++ b/test/core/test_cpu.cpp @@ -0,0 +1,70 @@ +#include "core/core_constants.hpp" +#include "core/cpu.hpp" +#include "dummy_types.hpp" +#include +#include +#include + +TEST_CASE( "isConditionMet", "[cpu][helper function]" ) { + DummyBus bus; + DummyCpu cpu( bus ); + + //-------------------------------------------------- + cpu.registers[std::to_underlying( CoreCpu::Operand_t::f )] = + bitMask::zeroFlag; // all registers except Z to false + REQUIRE_FALSE( cpu.isConditionMet( CoreCpu::Operand_t::condNZ ) ); + REQUIRE( cpu.isConditionMet( CoreCpu::Operand_t::condZ ) ); + + REQUIRE( cpu.isConditionMet( CoreCpu::Operand_t::condNC ) ); + REQUIRE_FALSE( cpu.isConditionMet( CoreCpu::Operand_t::condC ) ); + + //-------------------------------------------------- + cpu.registers[std::to_underlying( CoreCpu::Operand_t::f )] = + bitMask::carryFlag; // all registers except C to false + REQUIRE( cpu.isConditionMet( CoreCpu::Operand_t::condNZ ) ); + REQUIRE_FALSE( cpu.isConditionMet( CoreCpu::Operand_t::condZ ) ); + + REQUIRE_FALSE( cpu.isConditionMet( CoreCpu::Operand_t::condNC ) ); + REQUIRE( cpu.isConditionMet( CoreCpu::Operand_t::condC ) ); +} + +TEST_CASE( "Cpu branch handling", "[cpu][branch]" ) { + DummyBus bus; + using MopType = CoreCpu::MicroOperationType_t; + std::unique_ptr cpu; + + cpu = std::make_unique( bus ); + cpu->registers[std::to_underlying( CoreCpu::Operand_t::f )] = 0; // all registers set to false + cpu->PC = 0x100; + cpu->mopQueue = { + { { MopType::CHECK_COND, CoreCpu::Operand_t::condZ }, MopType::INVALID, MopType::INVALID } }; + cpu->tick(); + REQUIRE( cpu->mopQueue[1].type == MopType::NOP ); + REQUIRE( cpu->mopQueue[2].type == MopType::END ); + cpu->tick(); + cpu->tick(); + REQUIRE( cpu->PC == 0x101 ); + + + cpu = std::make_unique( bus ); + cpu->registers[std::to_underlying( CoreCpu::Operand_t::f )] = 0xFF; // all registers set to true + cpu->mopQueue = { + { { MopType::CHECK_COND, CoreCpu::Operand_t::condZ }, MopType::INVALID, MopType::INVALID } }; + cpu->tick(); + REQUIRE( cpu->mopQueue[1].type == MopType::INVALID ); + REQUIRE( cpu->mopQueue[2].type == MopType::INVALID ); + + //-------------------------------------------------- + cpu = std::make_unique( bus ); + cpu->registers[std::to_underlying( CoreCpu::Operand_t::f )] = 0; // all registers set to false + cpu->PC = 0x100; + cpu->mopQueue = { { { MopType::COND_CHECK__LD_IMM_TO_Z, CoreCpu::Operand_t::condZ }, + MopType::INVALID, + MopType::INVALID } }; + cpu->tick(); + REQUIRE( cpu->mopQueue[1].type == MopType::NOP ); + REQUIRE( cpu->mopQueue[2].type == MopType::END ); + cpu->tick(); + cpu->tick(); + REQUIRE( cpu->PC == 0x102 ); +} diff --git a/test/core/test_cpu_timings.cpp b/test/core/test_cpu_timings.cpp index 9fd613c..1147891 100644 --- a/test/core/test_cpu_timings.cpp +++ b/test/core/test_cpu_timings.cpp @@ -1,8 +1,6 @@ -#include "catch2/catch_message.hpp" #include "dummy_types.hpp" #include #include -#include // Different implementation based on different source compared to CPU diff --git a/test/core/test_ppu.cpp b/test/core/test_ppu.cpp index 2c7e51d..459ca1a 100644 --- a/test/core/test_ppu.cpp +++ b/test/core/test_ppu.cpp @@ -3,6 +3,7 @@ #include "ppu_helper.hpp" #include #include +#include #include class TestPpu : public CorePpu { @@ -18,9 +19,9 @@ public: } }; -TEST_CASE( "oam_scan", "[oam]" ) { - std::unique_ptr cartridge; - Emulator emu( std::move( cartridge ) ); +TEST_CASE( "OAM scan", "[oam]" ) { + // FIXME sometimes passes, sometimes not + Emulator emu( std::make_unique() ); emu.memory.write( addr::lcdControl, 0x0 ); createTestSprite( emu, 0, 1, 1, 0, 0 ); @@ -42,9 +43,8 @@ TEST_CASE( "oam_scan", "[oam]" ) { REQUIRE( emu.ppu.state.objCount == 1 ); } -TEST_CASE( "headless_rendering", "[background][chessboard]" ) { - std::unique_ptr cartridge; - Emulator emu( std::move( cartridge ) ); +TEST_CASE( "Headless rendering", "[background][chessboard]" ) { + Emulator emu( std::make_unique() ); setupLcdRegisters( emu ); setupBackgroundChessboardPatternInVram( emu ); setupTestSprites( emu ); diff --git a/test/dummy_types.hpp b/test/dummy_types.hpp index dd6c8b5..22508a3 100644 --- a/test/dummy_types.hpp +++ b/test/dummy_types.hpp @@ -1,4 +1,5 @@ #include "core/cartridge.hpp" +#include "core/core_constants.hpp" #include "core/cpu.hpp" #include "core/emulator.hpp" #include "core/ppu.hpp" @@ -6,7 +7,6 @@ class DummyCartridge final : public CoreCartridge { friend class Tester; - using CoreCartridge::CoreCartridge; public: uint8_t read( uint16_t ) { @@ -14,7 +14,7 @@ public: } void write( uint16_t, uint8_t ) { } - DummyCartridge() : CoreCartridge( std::vector {} ) { + DummyCartridge() : CoreCartridge( std::vector( addr::globalChecksumEnd + 1 ) ) { } }; @@ -22,6 +22,9 @@ class DummyCpu final : public CoreCpu { friend class Tester; public: + using CoreCpu::mopQueue; + using CoreCpu::PC; + using CoreCpu::registers; void handleJoypad() override { } DummyCpu( IBus& bus_ ) : CoreCpu( bus_ ) {}; @@ -38,4 +41,28 @@ public: } }; +class DummyBus : public IBus { + friend class Tester; + +public: + uint8_t read( [[maybe_unused]] uint16_t address ) const override { + return constant::invalidReadValue; + } + void write( [[maybe_unused]] uint16_t address, [[maybe_unused]] uint8_t value ) override { + } + void setOamLock( [[maybe_unused]] bool locked ) override { + } + void setVramLock( [[maybe_unused]] bool locked ) override { + } + SpriteAttribute getSpriteAttribute( [[maybe_unused]] uint8_t sprite_index ) const override { + return { constant::invalidReadValue, constant::invalidReadValue, constant::invalidReadValue, + constant::invalidReadValue }; + } + uint8_t directMemRead( [[maybe_unused]] uint16_t address ) const override { + return constant::invalidReadValue; + } + void directMemWrite( [[maybe_unused]] uint16_t address, [[maybe_unused]] uint8_t value ) override { + } +}; + using DummyEmulator_t = Emulator; -- GitLab From 29537b96a07e9e9165f07a77c19e6b69d8b29137 Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Mon, 30 Jun 2025 21:01:06 +0200 Subject: [PATCH 3/6] rudimentary timer implementation --- include/core/emulator.hpp | 10 +++++++ include/core/timer.hpp | 18 ++++++++++++ src/core/timer.cpp | 62 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 include/core/timer.hpp create mode 100644 src/core/timer.cpp diff --git a/include/core/emulator.hpp b/include/core/emulator.hpp index 2ccd31c..a954b62 100644 --- a/include/core/emulator.hpp +++ b/include/core/emulator.hpp @@ -2,6 +2,7 @@ #include "core/bus.hpp" #include "core/core_constants.hpp" #include "core/memory.hpp" +#include "core/timer.hpp" template class Emulator final : public IBus { @@ -14,9 +15,13 @@ class Emulator final : public IBus { bool inObjectAttributeMemory( const uint16_t index ) const { return addr::objectAttributeMemory <= index and index < addr::notUsable; } + bool inTimerRange( const uint16_t index ) { + return addr::timer <= index and index <= addr::timerEnd; + } public: std::unique_ptr cartridge; + Timer timer { *this }; Memory memory; Tcpu cpu; Tppu ppu; @@ -30,6 +35,10 @@ public: } void write( uint16_t address, uint8_t value ) override { if( ( inVideoRam( address ) && vramLocked ) || ( inObjectAttributeMemory( address ) && oamLocked ) ) { + [[unlikely]] return; + } + if( inTimerRange( address ) ) { + [[unlikely]] timer.write( address, value ); return; } memory.write( address, value ); @@ -62,6 +71,7 @@ public: // const bool cpuDoubleSpeed = memory.read( addr::key1 ) & ( 1 << 7 ); for( unsigned i = 0; i < ticks; i++ ) { ppu.tick(); + timer.tick(); } //apu.tick(); diff --git a/include/core/timer.hpp b/include/core/timer.hpp new file mode 100644 index 0000000..b5abd4f --- /dev/null +++ b/include/core/timer.hpp @@ -0,0 +1,18 @@ +#pragma once +#include "core/bus.hpp" +#include + +// Basic implementation - TODO edge cases +class Timer { + enum class ClockSelect { every1024Tcycles, every16Tcycles, every64Tcycles, every256Tcycles }; + IBus& bus; + // increment by one for every T-cycle; 1s is 2^22 T-cycles + uint32_t masterCounter = 0; + bool previousAndResult = false; + +public: + void tick(); + void write( uint16_t address, uint8_t value ); + Timer( IBus& bus_ ) : bus( bus_ ) { + } +}; diff --git a/src/core/timer.cpp b/src/core/timer.cpp new file mode 100644 index 0000000..b8f9ad6 --- /dev/null +++ b/src/core/timer.cpp @@ -0,0 +1,62 @@ +#include "core/timer.hpp" +#include "core/core_constants.hpp" + +void Timer::tick() { + masterCounter++; + bus.write( addr::divider, static_cast( masterCounter >> 8 ) ); + const auto timerControl = bus.read( addr::timerControl ); + const bool timaEnabled = timerControl & ( 1 << 2 ); + unsigned mask = 0; + const auto clockSelect = static_cast( timerControl & 0x3 ); + if( timaEnabled ) { + switch( clockSelect ) { + using enum ClockSelect; + case every16Tcycles: + mask = 1 << 3; + break; + case every64Tcycles: + mask = 1 << 5; + break; + case every256Tcycles: + mask = 1 << 7; + break; + case every1024Tcycles: + mask = 1 << 9; + break; + } + } + + bool newAndResult = masterCounter & mask; + // increment on falling edge + if( previousAndResult && ! newAndResult ) { + uint8_t tima = bus.read( addr::timerCounter ); + + if( tima == 0xFF ) { + bus.write( addr::timerCounter, bus.read( addr::timerModulo ) ); + bus.write( addr::interruptFlag, bus.read( addr::interruptFlag ) | bitMask::timerInterrupt ); + } else { + bus.write( addr::timerCounter, tima + 1 ); + } + } + previousAndResult = newAndResult; +} + +void Timer::write( uint16_t address, uint8_t value ) { + // TODO edgecases + switch( address ) { + case addr::divider: + masterCounter = 0; + bus.directMemWrite( address, 0 ); + break; + case addr::timerCounter: + bus.directMemWrite( address, value ); + break; + case addr::timerModulo: + bus.directMemWrite( address, value ); + break; + case addr::timerControl: + bus.directMemWrite( address, value ); + break; + } + return; +} -- GitLab From 4e175aade4a8c3c569614e3e4573ba2a9f750a3e Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Mon, 30 Jun 2025 22:31:59 +0200 Subject: [PATCH 4/6] decouple joypadHandler from cpu --- .clang-tidy | 5 +- .gitlab-ci.yml | 4 +- include/cartridge_impls/cartridge_factory.hpp | 3 +- include/core/cartridge.hpp | 1 - include/core/cpu.hpp | 7 ++- include/core/emulator.hpp | 18 ++++---- .../raylib/raylib_handle_joypad.hpp | 35 ++------------ .../{raylib_parts.hpp => raylib_ppu.hpp} | 10 ---- src/core/cpu.cpp | 24 +++++----- src/core/cpu_decode.cpp | 12 ++--- src/core/cpu_execute.cpp | 2 +- src/core/ppu.cpp | 3 +- src/raylib/main.cpp | 9 ++-- src/raylib/raylib_ppu.cpp | 29 ++++++++++++ test/core/test_cpu.cpp | 46 +++++++++---------- test/core/test_ppu.cpp | 7 ++- test/dummy_types.hpp | 13 ++---- test/raylib/itest_main.cpp | 9 ++-- 18 files changed, 113 insertions(+), 124 deletions(-) rename src/raylib/raylib_parts.cpp => include/raylib/raylib_handle_joypad.hpp (60%) rename include/raylib/{raylib_parts.hpp => raylib_ppu.hpp} (52%) create mode 100644 src/raylib/raylib_ppu.cpp diff --git a/.clang-tidy b/.clang-tidy index b28b0f7..7020067 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -7,7 +7,10 @@ Checks: > bugprone-narrowing-conversions, bugprone-signed-char-misuse, misc-unused-parameters, - readability-misleading-indentation + misc-include-cleaner, + readability-misleading-indentation, + readability-redundant-preprocessor, + modernize-deprecated-headers WarningsAsErrors: '' HeaderFileExtensions: - '' diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 11783bc..c9a1b4d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,10 +27,10 @@ format-check: formatted_output=$(clang-format -style=file "$file") # Compare with original - if ! echo "$formatted_output" | diff -u --color=always "$file" - > /dev/null 2>&1; then + if ! echo "$formatted_output" | diff -u "$file" - > /dev/null 2>&1; then echo -e "\n❌ $file needs formatting:" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "$formatted_output" | diff -u --color=always --label="current" --label="expected" "$file" - || true + echo "$formatted_output" | diff -u --label="current" --label="expected" "$file" - || true echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" failed=1 fi diff --git a/include/cartridge_impls/cartridge_factory.hpp b/include/cartridge_impls/cartridge_factory.hpp index 2d11ab7..9fe5f82 100644 --- a/include/cartridge_impls/cartridge_factory.hpp +++ b/include/cartridge_impls/cartridge_factory.hpp @@ -1,9 +1,8 @@ #pragma once - #include "core/cartridge.hpp" #include #include namespace CartridgeFactory { -std::unique_ptr create(std::vector&& rom); +std::unique_ptr create( std::vector&& rom ); } diff --git a/include/core/cartridge.hpp b/include/core/cartridge.hpp index ef06b59..2ff37d4 100644 --- a/include/core/cartridge.hpp +++ b/include/core/cartridge.hpp @@ -1,6 +1,5 @@ #pragma once #include -#include #include #include #include diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index ae2d41e..53a72a7 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -9,7 +9,7 @@ template concept uint8or16_t = std::same_as || std::same_as; -class CoreCpu { +class Cpu { public: using Enum_t = uint8_t; enum class MicroOperationType_t : Enum_t { @@ -228,8 +228,7 @@ public: void logOperation( MicroOperation_t mop ); public: - virtual void handleJoypad() = 0; - CoreCpu( IBus& bus_ ); - virtual ~CoreCpu() = default; + Cpu( IBus& bus_ ); + ~Cpu() = default; unsigned tick(); }; diff --git a/include/core/emulator.hpp b/include/core/emulator.hpp index a954b62..08051c2 100644 --- a/include/core/emulator.hpp +++ b/include/core/emulator.hpp @@ -1,11 +1,15 @@ #pragma once #include "core/bus.hpp" #include "core/core_constants.hpp" +#include "core/cpu.hpp" #include "core/memory.hpp" #include "core/timer.hpp" +#include -template +template class Emulator final : public IBus { + using JoypadHandler_t = void( IBus& ); + bool vramLocked = false; bool oamLocked = false; @@ -23,8 +27,9 @@ public: std::unique_ptr cartridge; Timer timer { *this }; Memory memory; - Tcpu cpu; + Cpu cpu; Tppu ppu; + JoypadHandler_t& joypadHandler; // IBus interface uint8_t read( uint16_t address ) const override { @@ -73,17 +78,14 @@ public: ppu.tick(); timer.tick(); } - //apu.tick(); return 4; } - void handleJoypad() { - cpu.handleJoypad(); - } - Emulator( std::unique_ptr&& cartridge_ ) + Emulator( std::unique_ptr&& cartridge_, JoypadHandler_t& joypadHandler_ ) : cartridge( std::move( cartridge_ ) ) , memory( cartridge.get() ) , cpu( *this ) - , ppu( *this ) { + , ppu( *this ) + , joypadHandler( joypadHandler_ ) { } }; diff --git a/src/raylib/raylib_parts.cpp b/include/raylib/raylib_handle_joypad.hpp similarity index 60% rename from src/raylib/raylib_parts.cpp rename to include/raylib/raylib_handle_joypad.hpp index 522a575..84ddd69 100644 --- a/src/raylib/raylib_parts.cpp +++ b/include/raylib/raylib_handle_joypad.hpp @@ -1,36 +1,9 @@ -#include "raylib/raylib_parts.hpp" +#pragma once +#include "core/bus.hpp" #include "core/core_constants.hpp" -#include -#include +#include -RaylibPpu::RaylibPpu( IBus& bus_ ) : CorePpu( bus_ ) { - screenBuffer = static_cast( MemAlloc( sizeof( Color ) * displayWidth * displayHeight ) ); - std::memset( screenBuffer, 0, sizeof( Color ) * displayWidth * displayHeight ); -} - -RaylibPpu::~RaylibPpu() { - if( screenBuffer ) { - MemFree( screenBuffer ); - screenBuffer = nullptr; - } -} - -Color* RaylibPpu::getScreenBuffer() { - return screenBuffer; -} - -void RaylibPpu::drawPixel( uint8_t colorId ) { - const uint8_t ly = bus.read( addr::lcdY ); - if( ly < displayHeight && state.renderedX < displayWidth ) { - const Color pixelColor = { dmgColorMap[colorId][0], dmgColorMap[colorId][1], dmgColorMap[colorId][2], - 255 }; - screenBuffer[ly * displayWidth + state.renderedX] = pixelColor; - } -} - - -//-------------------------------------------------- -void RaylibCpu::handleJoypad() { +void raylibHandleJoypad( IBus& bus ) { const auto joypadInputRegister = bus.read( addr::joypadInput ); const bool selectButtonsFlag = joypadInputRegister & ( 1 << 5 ); const bool selectDPad = joypadInputRegister & ( 1 << 4 ); diff --git a/include/raylib/raylib_parts.hpp b/include/raylib/raylib_ppu.hpp similarity index 52% rename from include/raylib/raylib_parts.hpp rename to include/raylib/raylib_ppu.hpp index bdbb2b9..e63feee 100644 --- a/include/raylib/raylib_parts.hpp +++ b/include/raylib/raylib_ppu.hpp @@ -1,5 +1,4 @@ #pragma once -#include "core/cpu.hpp" #include "core/ppu.hpp" #include @@ -13,12 +12,3 @@ public: RaylibPpu( IBus& bus_ ); ~RaylibPpu() override; }; - - -//-------------------------------------------------- -class RaylibCpu final : public CoreCpu { -public: - void handleJoypad() override; - RaylibCpu( IBus& bus_ ) : CoreCpu( bus_ ) {}; - ~RaylibCpu() override = default; -}; diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index 5f8d463..44d7d8b 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -8,7 +8,7 @@ // Decoding and execution have separate files -uint8_t CoreCpu::addU8ToU8( uint8_t value, uint8_t value2 ) { +uint8_t Cpu::addU8ToU8( uint8_t value, uint8_t value2 ) { bool cFlag = value + value2 > std::numeric_limits::max(); bool halfCarryFlag = ( ( value & 0xF ) + ( value2 & 0xF ) ) > 0xF; @@ -17,7 +17,7 @@ uint8_t CoreCpu::addU8ToU8( uint8_t value, uint8_t value2 ) { return result; }; -void CoreCpu::addToR8( Operand_t operand, uint8_t value ) { +void Cpu::addToR8( Operand_t operand, uint8_t value ) { auto currentValue = readR8( operand ); bool cFlag = currentValue + value > std::numeric_limits::max(); bool halfCarryFlag = ( ( currentValue & 0xF ) + ( value & 0xF ) ) > 0xF; @@ -27,7 +27,7 @@ void CoreCpu::addToR8( Operand_t operand, uint8_t value ) { setZNHCFlags( ! currentValue, false, halfCarryFlag, cFlag ); }; -void CoreCpu::subFromR8( Operand_t operand, uint8_t value, bool discard ) { +void Cpu::subFromR8( Operand_t operand, uint8_t value, bool discard ) { auto currentValue = readR8( operand ); bool cFlag = value > currentValue; //due to integer promotion substraction operands are promoted to ints @@ -39,7 +39,7 @@ void CoreCpu::subFromR8( Operand_t operand, uint8_t value, bool discard ) { setZNHCFlags( ! newValue, true, halfCarryFlag, cFlag ); }; -uint8_t CoreCpu::readR8( Operand_t opd ) { +uint8_t Cpu::readR8( Operand_t opd ) { if( opd == Operand_t::a ) { const int aIndex = 6; return registers[aIndex]; @@ -47,7 +47,7 @@ uint8_t CoreCpu::readR8( Operand_t opd ) { return registers[std::to_underlying( opd )]; } -void CoreCpu::writeR8( Operand_t opd, uint8_t value ) { +void Cpu::writeR8( Operand_t opd, uint8_t value ) { if( opd == Operand_t::a ) { constexpr int aIndex = 6; registers[aIndex] = value; @@ -55,7 +55,7 @@ void CoreCpu::writeR8( Operand_t opd, uint8_t value ) { registers[std::to_underlying( opd )] = value; } -uint16_t CoreCpu::readR16( Operand_t opd ) { +uint16_t Cpu::readR16( Operand_t opd ) { if( opd == Operand_t::sp ) return SP; else @@ -63,7 +63,7 @@ uint16_t CoreCpu::readR16( Operand_t opd ) { ( registers[std::to_underlying( opd ) * 2 + 1] ) ); } -void CoreCpu::writeR16( Operand_t opd, uint16_t value ) { +void Cpu::writeR16( Operand_t opd, uint16_t value ) { if( opd == Operand_t::sp ) SP = value; else { @@ -73,7 +73,7 @@ void CoreCpu::writeR16( Operand_t opd, uint16_t value ) { } -bool CoreCpu::isConditionMet( Operand_t condition ) const { +bool Cpu::isConditionMet( Operand_t condition ) const { bool conditionMet = false; using enum Operand_t; if( condition == condNZ && ! getZFlag() ) @@ -90,7 +90,7 @@ bool CoreCpu::isConditionMet( Operand_t condition ) const { } //-------------------------------------------------- -unsigned CoreCpu::tick() { +unsigned Cpu::tick() { MicroOperationType_t currentMopType = mopQueue[atMicroOperationNr].type; if( currentMopType == MicroOperationType_t::END && ! handleInterrupts() ) { logDebug( std::format( "PC: {} - Decoding next instruction", toHex( PC ) ) ); @@ -115,7 +115,7 @@ unsigned CoreCpu::tick() { return 4; // One M-cycle } -bool CoreCpu::handleInterrupts() { +bool Cpu::handleInterrupts() { if( ! interruptMasterEnabled ) return false; const auto interruptEnable = bus.read( addr::interruptEnableRegister ); @@ -154,7 +154,7 @@ bool CoreCpu::handleInterrupts() { }; -CoreCpu::CoreCpu( IBus& bus_ ) : bus( bus_ ) { +Cpu::Cpu( IBus& bus_ ) : bus( bus_ ) { //set register f const bool headerChecksumNonZero = bus.read( addr::headerChecksum ); setZNHCFlags( 1, 0, headerChecksumNonZero, headerChecksumNonZero ); @@ -272,7 +272,7 @@ constexpr std::string_view MicroOperationTypeString[] = { #endif -void CoreCpu::logOperation( [[maybe_unused]] MicroOperation_t mop ) { +void Cpu::logOperation( [[maybe_unused]] MicroOperation_t mop ) { #ifdef DEBUG std::string opd1; std::string opd2; diff --git a/src/core/cpu_decode.cpp b/src/core/cpu_decode.cpp index 13cfaaa..39efefe 100644 --- a/src/core/cpu_decode.cpp +++ b/src/core/cpu_decode.cpp @@ -1,9 +1,9 @@ #include "core/cpu.hpp" #include -using enum CoreCpu::MicroOperationType_t; +using enum Cpu::MicroOperationType_t; // Operand order is target first, source next -CoreCpu::MicroOperations_t CoreCpu::decode() { +Cpu::MicroOperations_t Cpu::decode() { // first check instructions without different operand variants const auto opcode = bus.read( PC++ ); switch( opcode ) { @@ -117,7 +117,7 @@ CoreCpu::MicroOperations_t CoreCpu::decode() { } -CoreCpu::MicroOperations_t CoreCpu::decodeBlock0( const uint8_t opcode ) { +Cpu::MicroOperations_t Cpu::decodeBlock0( const uint8_t opcode ) { //count from 0 const auto r8 = static_cast( 0x7 & ( opcode >> 3 ) ); const auto r16 = static_cast( 0x3 & ( opcode >> 4 ) ); @@ -162,7 +162,7 @@ CoreCpu::MicroOperations_t CoreCpu::decodeBlock0( const uint8_t opcode ) { } -CoreCpu::MicroOperations_t CoreCpu::decodeBlock2( const uint8_t opcode ) { +Cpu::MicroOperations_t Cpu::decodeBlock2( const uint8_t opcode ) { const auto r8 = static_cast( 0x7 & opcode ); switch( 0x7 & ( opcode >> 3 ) ) { case 0x0: @@ -210,7 +210,7 @@ CoreCpu::MicroOperations_t CoreCpu::decodeBlock2( const uint8_t opcode ) { } -CoreCpu::MicroOperations_t CoreCpu::decodeBlock3( const uint8_t opcode ) { +Cpu::MicroOperations_t Cpu::decodeBlock3( const uint8_t opcode ) { const auto condition = static_cast( 0x3 & ( opcode >> 3 ) ); const auto r16stk = static_cast( 0x3 & ( opcode >> 4 ) ); switch( 0x7 & opcode ) { @@ -245,7 +245,7 @@ CoreCpu::MicroOperations_t CoreCpu::decodeBlock3( const uint8_t opcode ) { } -CoreCpu::MicroOperations_t CoreCpu::decodeCB() { +Cpu::MicroOperations_t Cpu::decodeCB() { const auto opcodeSecondByte = bus.read( PC ); const auto r8 = static_cast( opcodeSecondByte & 0x7 ); const auto b3index = static_cast( 0x7 & ( opcodeSecondByte >> 3 ) ); diff --git a/src/core/cpu_execute.cpp b/src/core/cpu_execute.cpp index 5104446..14a2c52 100644 --- a/src/core/cpu_execute.cpp +++ b/src/core/cpu_execute.cpp @@ -3,7 +3,7 @@ #include "core/logging.hpp" #include -void CoreCpu::execute( MicroOperation_t mop ) { +void Cpu::execute( MicroOperation_t mop ) { logOperation( mop ); switch( mop.type ) { using enum MicroOperationType_t; diff --git a/src/core/ppu.cpp b/src/core/ppu.cpp index 2dadf4e..64f6177 100644 --- a/src/core/ppu.cpp +++ b/src/core/ppu.cpp @@ -60,8 +60,7 @@ CorePpu::PpuMode CorePpu::tick() { bus.setOamLock( false ); // Request V-Blank interrupt - bus.write( addr::interruptFlag, - static_cast( bus.read( addr::interruptFlag ) | 0x01u ) ); + bus.write( addr::interruptFlag, bus.read( addr::interruptFlag ) | bitMask::vBlankInterrupt ); } else { status = ( status & ~0x3 ) | static_cast( OAM_SEARCH ); bus.write( addr::lcdStatus, status ); diff --git a/src/raylib/main.cpp b/src/raylib/main.cpp index 2c9d756..32c24e7 100644 --- a/src/raylib/main.cpp +++ b/src/raylib/main.cpp @@ -2,7 +2,8 @@ #include "core/cartridge.hpp" #include "core/emulator.hpp" #include "core/logging.hpp" -#include "raylib/raylib_parts.hpp" +#include "raylib/raylib_handle_joypad.hpp" +#include "raylib/raylib_ppu.hpp" #include "tinyfiledialogs.h" #include #include @@ -13,7 +14,7 @@ #include #include -using Emulator_t = Emulator; +using Emulator_t = Emulator; constexpr int targetFps = 60; constexpr int ticksPerFrame = ( 1. / targetFps ) * constant::tickrate; @@ -58,7 +59,7 @@ int main() { Texture2D screenTexture = LoadTextureFromImage( GenImageColor( CorePpu::displayWidth, CorePpu::displayHeight, BLACK ) ); - Emulator_t emu( std::move( cartridge ) ); + Emulator_t emu( std::move( cartridge ), raylibHandleJoypad ); bool interactiveDebugMode = true; bool emulationStopped = false; bool doOneTick; // When emulation isn't stopped, the value doesn't matter @@ -115,8 +116,6 @@ int main() { DrawText( mousePositionText.c_str(), screenWidth - textWidth - 10, 10, 20, RED ); DrawFPS( 5, 5 ); } - - emu.handleJoypad(); EndDrawing(); } diff --git a/src/raylib/raylib_ppu.cpp b/src/raylib/raylib_ppu.cpp new file mode 100644 index 0000000..da9e5b3 --- /dev/null +++ b/src/raylib/raylib_ppu.cpp @@ -0,0 +1,29 @@ +#include "raylib/raylib_ppu.hpp" +#include "core/core_constants.hpp" +#include +#include + +RaylibPpu::RaylibPpu( IBus& bus_ ) : CorePpu( bus_ ) { + screenBuffer = static_cast( MemAlloc( sizeof( Color ) * displayWidth * displayHeight ) ); + std::memset( screenBuffer, 0, sizeof( Color ) * displayWidth * displayHeight ); +} + +RaylibPpu::~RaylibPpu() { + if( screenBuffer ) { + MemFree( screenBuffer ); + screenBuffer = nullptr; + } +} + +Color* RaylibPpu::getScreenBuffer() { + return screenBuffer; +} + +void RaylibPpu::drawPixel( uint8_t colorId ) { + const uint8_t ly = bus.read( addr::lcdY ); + if( ly < displayHeight && state.renderedX < displayWidth ) { + const Color pixelColor = { dmgColorMap[colorId][0], dmgColorMap[colorId][1], dmgColorMap[colorId][2], + 255 }; + screenBuffer[ly * displayWidth + state.renderedX] = pixelColor; + } +} diff --git a/test/core/test_cpu.cpp b/test/core/test_cpu.cpp index 023508b..99ca4d8 100644 --- a/test/core/test_cpu.cpp +++ b/test/core/test_cpu.cpp @@ -10,34 +10,33 @@ TEST_CASE( "isConditionMet", "[cpu][helper function]" ) { DummyCpu cpu( bus ); //-------------------------------------------------- - cpu.registers[std::to_underlying( CoreCpu::Operand_t::f )] = + cpu.registers[std::to_underlying( Cpu::Operand_t::f )] = bitMask::zeroFlag; // all registers except Z to false - REQUIRE_FALSE( cpu.isConditionMet( CoreCpu::Operand_t::condNZ ) ); - REQUIRE( cpu.isConditionMet( CoreCpu::Operand_t::condZ ) ); + REQUIRE_FALSE( cpu.isConditionMet( Cpu::Operand_t::condNZ ) ); + REQUIRE( cpu.isConditionMet( Cpu::Operand_t::condZ ) ); - REQUIRE( cpu.isConditionMet( CoreCpu::Operand_t::condNC ) ); - REQUIRE_FALSE( cpu.isConditionMet( CoreCpu::Operand_t::condC ) ); + REQUIRE( cpu.isConditionMet( Cpu::Operand_t::condNC ) ); + REQUIRE_FALSE( cpu.isConditionMet( Cpu::Operand_t::condC ) ); //-------------------------------------------------- - cpu.registers[std::to_underlying( CoreCpu::Operand_t::f )] = + cpu.registers[std::to_underlying( Cpu::Operand_t::f )] = bitMask::carryFlag; // all registers except C to false - REQUIRE( cpu.isConditionMet( CoreCpu::Operand_t::condNZ ) ); - REQUIRE_FALSE( cpu.isConditionMet( CoreCpu::Operand_t::condZ ) ); + REQUIRE( cpu.isConditionMet( Cpu::Operand_t::condNZ ) ); + REQUIRE_FALSE( cpu.isConditionMet( Cpu::Operand_t::condZ ) ); - REQUIRE_FALSE( cpu.isConditionMet( CoreCpu::Operand_t::condNC ) ); - REQUIRE( cpu.isConditionMet( CoreCpu::Operand_t::condC ) ); + REQUIRE_FALSE( cpu.isConditionMet( Cpu::Operand_t::condNC ) ); + REQUIRE( cpu.isConditionMet( Cpu::Operand_t::condC ) ); } TEST_CASE( "Cpu branch handling", "[cpu][branch]" ) { DummyBus bus; - using MopType = CoreCpu::MicroOperationType_t; + using MopType = Cpu::MicroOperationType_t; std::unique_ptr cpu; - cpu = std::make_unique( bus ); - cpu->registers[std::to_underlying( CoreCpu::Operand_t::f )] = 0; // all registers set to false - cpu->PC = 0x100; - cpu->mopQueue = { - { { MopType::CHECK_COND, CoreCpu::Operand_t::condZ }, MopType::INVALID, MopType::INVALID } }; + cpu = std::make_unique( bus ); + cpu->registers[std::to_underlying( Cpu::Operand_t::f )] = 0; // all registers set to false + cpu->PC = 0x100; + cpu->mopQueue = { { { MopType::CHECK_COND, Cpu::Operand_t::condZ }, MopType::INVALID, MopType::INVALID } }; cpu->tick(); REQUIRE( cpu->mopQueue[1].type == MopType::NOP ); REQUIRE( cpu->mopQueue[2].type == MopType::END ); @@ -46,19 +45,18 @@ TEST_CASE( "Cpu branch handling", "[cpu][branch]" ) { REQUIRE( cpu->PC == 0x101 ); - cpu = std::make_unique( bus ); - cpu->registers[std::to_underlying( CoreCpu::Operand_t::f )] = 0xFF; // all registers set to true - cpu->mopQueue = { - { { MopType::CHECK_COND, CoreCpu::Operand_t::condZ }, MopType::INVALID, MopType::INVALID } }; + cpu = std::make_unique( bus ); + cpu->registers[std::to_underlying( Cpu::Operand_t::f )] = 0xFF; // all registers set to true + cpu->mopQueue = { { { MopType::CHECK_COND, Cpu::Operand_t::condZ }, MopType::INVALID, MopType::INVALID } }; cpu->tick(); REQUIRE( cpu->mopQueue[1].type == MopType::INVALID ); REQUIRE( cpu->mopQueue[2].type == MopType::INVALID ); //-------------------------------------------------- - cpu = std::make_unique( bus ); - cpu->registers[std::to_underlying( CoreCpu::Operand_t::f )] = 0; // all registers set to false - cpu->PC = 0x100; - cpu->mopQueue = { { { MopType::COND_CHECK__LD_IMM_TO_Z, CoreCpu::Operand_t::condZ }, + cpu = std::make_unique( bus ); + cpu->registers[std::to_underlying( Cpu::Operand_t::f )] = 0; // all registers set to false + cpu->PC = 0x100; + cpu->mopQueue = { { { MopType::COND_CHECK__LD_IMM_TO_Z, Cpu::Operand_t::condZ }, MopType::INVALID, MopType::INVALID } }; cpu->tick(); diff --git a/test/core/test_ppu.cpp b/test/core/test_ppu.cpp index 459ca1a..8f4835d 100644 --- a/test/core/test_ppu.cpp +++ b/test/core/test_ppu.cpp @@ -19,9 +19,12 @@ public: } }; +void handleJoypad( [[maybe_unused]] IBus& bus ) { +} + TEST_CASE( "OAM scan", "[oam]" ) { // FIXME sometimes passes, sometimes not - Emulator emu( std::make_unique() ); + Emulator emu( std::make_unique(), handleJoypad ); emu.memory.write( addr::lcdControl, 0x0 ); createTestSprite( emu, 0, 1, 1, 0, 0 ); @@ -44,7 +47,7 @@ TEST_CASE( "OAM scan", "[oam]" ) { } TEST_CASE( "Headless rendering", "[background][chessboard]" ) { - Emulator emu( std::make_unique() ); + Emulator emu( std::make_unique(), handleJoypad ); setupLcdRegisters( emu ); setupBackgroundChessboardPatternInVram( emu ); setupTestSprites( emu ); diff --git a/test/dummy_types.hpp b/test/dummy_types.hpp index 22508a3..f90017f 100644 --- a/test/dummy_types.hpp +++ b/test/dummy_types.hpp @@ -18,16 +18,13 @@ public: } }; -class DummyCpu final : public CoreCpu { +class DummyCpu final : public Cpu { friend class Tester; public: - using CoreCpu::mopQueue; - using CoreCpu::PC; - using CoreCpu::registers; - void handleJoypad() override { - } - DummyCpu( IBus& bus_ ) : CoreCpu( bus_ ) {}; + using Cpu::mopQueue; + using Cpu::PC; + using Cpu::registers; }; class DummyPpu : public CorePpu { @@ -65,4 +62,4 @@ public: } }; -using DummyEmulator_t = Emulator; +using DummyEmulator_t = Emulator; diff --git a/test/raylib/itest_main.cpp b/test/raylib/itest_main.cpp index 49df537..4bce692 100644 --- a/test/raylib/itest_main.cpp +++ b/test/raylib/itest_main.cpp @@ -3,18 +3,19 @@ #include "dummy_types.hpp" #include "ppu_helper.hpp" #include "raylib.h" -#include "raylib/raylib_parts.hpp" +#include "raylib/raylib_ppu.hpp" #include #include constexpr int targetFps = 60; constexpr int ticksPerFrame = int( ( 1. / targetFps ) * constant::tickrate ); -using RaylibEmulator = Emulator; +void handleJoypad( [[maybe_unused]] IBus& bus ) { +} int main() { std::unique_ptr cartridge = std::make_unique(); - RaylibEmulator emu( std::move( cartridge ) ); + Emulator emu( std::move( cartridge ), handleJoypad ); const int scaleFactor = 7; const int screenWidth = CorePpu::displayWidth * scaleFactor; @@ -83,8 +84,6 @@ int main() { DrawText( mousePositionText.c_str(), screenWidth - textWidth - 10, 10, 20, RED ); DrawFPS( 5, 5 ); } - - emu.handleJoypad(); EndDrawing(); } -- GitLab From 674879f78344a8dca5c00d3df40d0521cda2d04f Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Mon, 30 Jun 2025 23:10:56 +0200 Subject: [PATCH 5/6] custom docker image and related pipeline tweaks --- .gitlab-ci.yml | 19 +++---------------- Dockerfile | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 Dockerfile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c9a1b4d..e41535e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,6 @@ -image: alpine:latest +image: + name: registry.gitlab.com/destroy-data/little-boy:0.1 + pull_policy: if-not-present variables: CMAKE_BUILD_DIR: "build" @@ -15,8 +17,6 @@ stages: format-check: stage: check before_script: - - apk update - - apk add --no-cache clang clang-extra-tools script: - | echo "🔍 Checking code formatting..." @@ -49,19 +49,6 @@ format-check: build-warnings-check: stage: check - before_script: - - apk update - - | - apk add --no-cache \ - clang \ - clang-dev \ - cmake \ - ninja \ - git \ - mesa-dev \ - mesa-gbm \ - mesa-egl \ - libstdc++-dev script: - | cmake -B ${CMAKE_BUILD_DIR} -S . -GNinja \ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f04a74f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM alpine:latest +LABEL description="Image for the Gameboy project pipeline with all dependencies pre-installed." + +RUN apk update && apk add --no-cache \ + clang \ + clang-extra-tools \ + clang-dev \ + cmake \ + ninja \ + git \ + mesa-dev \ + mesa-gbm \ + mesa-egl \ + libstdc++-dev + -- GitLab From 358170b5b72693c640f0f66c37113340687588ff Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Wed, 2 Jul 2025 00:56:08 +0200 Subject: [PATCH 6/6] address PR comments --- include/core/core_constants.hpp | 2 +- include/core/cpu.hpp | 2 +- include/core/emulator.hpp | 2 +- src/core/cpu.cpp | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/include/core/core_constants.hpp b/include/core/core_constants.hpp index 3ab9e27..3249823 100644 --- a/include/core/core_constants.hpp +++ b/include/core/core_constants.hpp @@ -121,6 +121,6 @@ constexpr uint16_t videoRam = 8192; // other namespace constant { constexpr unsigned tickrate = 4'194'304; -constexpr double oscillatoryTime = 1. / tickrate; +constexpr double oscillatoryTime = 1.0 / tickrate; constexpr uint8_t invalidReadValue = 0xFF; } // namespace constant diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index 53a72a7..1405a20 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -207,7 +207,7 @@ public: uint16_t getWZ() { return static_cast( ( W << 8 ) | Z ); } void setWZ( uint16_t value ) { Z = lsb(value); W = msb( value ); } - bool isPHL( Operand_t operand ) { return operand == Operand_t::phl; } + bool isPHL( Operand_t operand ) const { return operand == Operand_t::phl; } bool getZFlag() const { return registers[7] &( 1 << 7 ); } // Zero flag bool getNFlag() const { return registers[7] &( 1 << 6 ); } // BDC substraction flag bool getHFlag() const { return registers[7] &( 1 << 5 ); } // BDC half carry flag diff --git a/include/core/emulator.hpp b/include/core/emulator.hpp index 08051c2..6efe867 100644 --- a/include/core/emulator.hpp +++ b/include/core/emulator.hpp @@ -19,7 +19,7 @@ class Emulator final : public IBus { bool inObjectAttributeMemory( const uint16_t index ) const { return addr::objectAttributeMemory <= index and index < addr::notUsable; } - bool inTimerRange( const uint16_t index ) { + bool inTimerRange( const uint16_t index ) const { return addr::timer <= index and index <= addr::timerEnd; } diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index 44d7d8b..9f42568 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -105,7 +105,6 @@ unsigned Cpu::tick() { currentMopType == MicroOperationType_t::COND_CHECK__LD_IMM_TO_Z ) ) { mopQueue[atMicroOperationNr + 1] = { MicroOperationType_t::NOP }; mopQueue[atMicroOperationNr + 2] = { MicroOperationType_t::END }; - logDebug( "branch not taken" ); } if( enableIMELater && atMicroOperationNr == 0 ) { // DI takes one cycle, so we are just after next one interruptMasterEnabled = true; -- GitLab