diff --git a/.clang-tidy b/.clang-tidy index 28f6c680f7135a87eb695ad2fddc9e3df52abff9..70200676c83c9af19989b1acef32df9a60c10072 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,14 +1,16 @@ -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 + 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 11783bc1f9bd3105c2b883364790f9952acbd333..e41535efb0956e2e2a12bc18163c1390063a934e 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..." @@ -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 @@ -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/CMakeLists.txt b/CMakeLists.txt index b355bd420478f11efb2d8e770ab0ecdbef4875b3..5854dfb4694a2f45fc0b76798b481dbed99aaef8 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/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..f04a74f484b013deda63abeea3e151576ccc35e8 --- /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 + diff --git a/include/cartridge_impls/cartridge_factory.hpp b/include/cartridge_impls/cartridge_factory.hpp index 2d11ab7fb287cd16226a7c5b6ab9dbf9a225ffbb..9fe5f82fb1d17536633c2a2804f8ae7ad15deb94 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 ef06b59368c3cd2cb2643ac8f2691407d1ae6806..2ff37d4667c0caa3c34c498214391d82c3fc1081 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/core_constants.hpp b/include/core/core_constants.hpp index ca427fb8a730b9ade604cfe4715c84868ec32a9d..324982304ed622b9990606604586f0806d048544 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.0 / tickrate; +constexpr uint8_t invalidReadValue = 0xFF; } // namespace constant diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index 481a05259fd3ccba753a5e6a475775d284fcc685..1405a20245b9ab5b33bc0993e937e039df719421 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -4,13 +4,12 @@ #include "core/cycles.hpp" #include #include -#include #include 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 { @@ -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 @@ -207,11 +207,11 @@ 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 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 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 + 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 )); } @@ -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 2ccd31c2e08b816d26a03f22f89f44afde627d3a..6efe867a45a2a03b67665aedf502c3bcabd7dd5a 100644 --- a/include/core/emulator.hpp +++ b/include/core/emulator.hpp @@ -1,10 +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; @@ -14,12 +19,17 @@ 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 ) const { + return addr::timer <= index and index <= addr::timerEnd; + } 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 { @@ -30,6 +40,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,18 +76,16 @@ public: // const bool cpuDoubleSpeed = memory.read( addr::key1 ) & ( 1 << 7 ); for( unsigned i = 0; i < ticks; i++ ) { 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/include/core/timer.hpp b/include/core/timer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b5abd4f6e3ef7694614d8c6d9e39f397683c17d0 --- /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/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 522a575962708f4eac6444d7f2770c8e2c9852f6..84ddd69549cd2903976501a704092a41f581d522 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 bdbb2b91e0bdabb5c4378e08c25e615b8feb67b2..e63feee1a7da20ed7707212de8f8724d8dcfb3e1 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/cartridge.cpp b/src/core/cartridge.cpp index 62fa14c9cfaaf1c2de76f58007675fb1d3c6f90b..b96743a071ae1cd2264bb5b394eb0047984638fc 100644 --- a/src/core/cartridge.cpp +++ b/src/core/cartridge.cpp @@ -150,9 +150,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 +162,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 +172,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 67c2f844dc2423bd831bdc8f1da21ccb9f4aae2b..9f42568a8f3b9ec0df037e2a8a74aef4267eb8d8 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,27 +73,30 @@ void CoreCpu::writeR16( Operand_t opd, uint16_t value ) { } -bool CoreCpu::isConditionMet( Operand_t condition ) { +bool Cpu::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; +unsigned Cpu::tick() { + 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] ); @@ -111,7 +114,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 ); @@ -150,13 +153,14 @@ 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 ); }; +#ifdef DEBUG constexpr std::string_view MicroOperationTypeString[] = { "NOP", "STOP", @@ -264,13 +268,16 @@ constexpr std::string_view MicroOperationTypeString[] = { "INVALID", "END", }; +#endif -void CoreCpu::logOperation( MicroOperation_t mop ) { +void Cpu::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/core/cpu_decode.cpp b/src/core/cpu_decode.cpp index 13cfaaae30204e8ebd001af3e1c4aa6057b03dfd..39efefe5de41b82c69b3e708ac955fcbd788b966 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 e0eb3d719fe0067e76b793605c5966edbd8802e7..14a2c5275d20f5eea5b7e515f157dba7d36d2b2d 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; @@ -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/src/core/ppu.cpp b/src/core/ppu.cpp index 2dadf4e3949114c0e3fedec92fb108e5e9173133..64f61779d090b82bd4e59f2c1038be7c2c75e4da 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/core/timer.cpp b/src/core/timer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b8f9ad626d88d2071ef2833ff4619fe52d417c13 --- /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; +} diff --git a/src/raylib/CMakeLists.txt b/src/raylib/CMakeLists.txt index 91763eb8facb9095ef575b35e25ee52a668ed1fb..492c2a3b94ce77f51dd4943e4e9b680f4327d0eb 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" -) diff --git a/src/raylib/main.cpp b/src/raylib/main.cpp index 2c9d75686af7421703a36e5ddb3673d43086b5be..32c24e7e87177c2cb0a95ca3114440d51a4ff539 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 0000000000000000000000000000000000000000..da9e5b338decc72977783e0439c020c502b77109 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..99ca4d83f030e2673924e48aa83c9fffb0e94d7b --- /dev/null +++ b/test/core/test_cpu.cpp @@ -0,0 +1,68 @@ +#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( Cpu::Operand_t::f )] = + bitMask::zeroFlag; // all registers except Z to false + REQUIRE_FALSE( cpu.isConditionMet( Cpu::Operand_t::condNZ ) ); + REQUIRE( cpu.isConditionMet( Cpu::Operand_t::condZ ) ); + + REQUIRE( cpu.isConditionMet( Cpu::Operand_t::condNC ) ); + REQUIRE_FALSE( cpu.isConditionMet( Cpu::Operand_t::condC ) ); + + //-------------------------------------------------- + cpu.registers[std::to_underlying( Cpu::Operand_t::f )] = + bitMask::carryFlag; // all registers except C to false + REQUIRE( cpu.isConditionMet( Cpu::Operand_t::condNZ ) ); + REQUIRE_FALSE( cpu.isConditionMet( Cpu::Operand_t::condZ ) ); + + 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 = Cpu::MicroOperationType_t; + std::unique_ptr cpu; + + 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 ); + cpu->tick(); + cpu->tick(); + REQUIRE( cpu->PC == 0x101 ); + + + 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( 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(); + 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 9fd613c44cac3f4fc22b7d6c6ae6a72e475325fc..1147891e54fe2f572837fa1cfe298a3fb5833f36 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 2c7e51dfc7bec14166dd05d3b635e2e04ab28988..8f4835d27b32d0324397c962b17c391c448cc43c 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,12 @@ public: } }; -TEST_CASE( "oam_scan", "[oam]" ) { - std::unique_ptr cartridge; - Emulator emu( std::move( cartridge ) ); +void handleJoypad( [[maybe_unused]] IBus& bus ) { +} + +TEST_CASE( "OAM scan", "[oam]" ) { + // FIXME sometimes passes, sometimes not + Emulator emu( std::make_unique(), handleJoypad ); emu.memory.write( addr::lcdControl, 0x0 ); createTestSprite( emu, 0, 1, 1, 0, 0 ); @@ -42,9 +46,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(), handleJoypad ); setupLcdRegisters( emu ); setupBackgroundChessboardPatternInVram( emu ); setupTestSprites( emu ); diff --git a/test/dummy_types.hpp b/test/dummy_types.hpp index dd6c8b5ebcca697ed2c1a4a11cf3e8ad716cb6f8..f90017f2d5171a76fd76cc985c35a81316d4027e 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,17 +14,17 @@ public: } void write( uint16_t, uint8_t ) { } - DummyCartridge() : CoreCartridge( std::vector {} ) { + DummyCartridge() : CoreCartridge( std::vector( addr::globalChecksumEnd + 1 ) ) { } }; -class DummyCpu final : public CoreCpu { +class DummyCpu final : public Cpu { friend class Tester; public: - void handleJoypad() override { - } - DummyCpu( IBus& bus_ ) : CoreCpu( bus_ ) {}; + using Cpu::mopQueue; + using Cpu::PC; + using Cpu::registers; }; class DummyPpu : public CorePpu { @@ -38,4 +38,28 @@ public: } }; -using DummyEmulator_t = Emulator; +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; diff --git a/test/raylib/itest_main.cpp b/test/raylib/itest_main.cpp index 49df5373c6fd7a9893ed124a345a48ebadf9fd74..4bce692635da51cc7a39068032f1f4455ca16ee8 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(); }