From 7ad8e26c264e10d96a5878daf2ae4b6006cf9665 Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Mon, 16 Jun 2025 22:14:37 +0200 Subject: [PATCH 1/8] microcode decoding 1/2 --- include/core/cpu.hpp | 103 ++++++++++++++-- src/core/cpu.cpp | 38 +++--- src/core/decode.cpp | 247 -------------------------------------- src/core/microcode.cpp | 261 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 376 insertions(+), 273 deletions(-) delete mode 100644 src/core/decode.cpp create mode 100644 src/core/microcode.cpp diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index 13d7064..60b7a2c 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -11,6 +11,75 @@ concept uint8or16_t = std::same_as || std::same_as; class CoreCpu { public: using Enum_t = uint8_t; + enum class MicroOperationType_t : Enum_t { + NOP, + STOP, + LD_IMM_TO_Z, + LD_IMM_TO_W, + LD_SPL_TO_pWZ, + LD_SPH_TO_pWZ, + RLCA, + RRCA, + RLA, + RRA, + DAA, + CPL, + SCF, + CCF, + ALU_CALC_RELATIVE_JUMP, + IDU_LD_WZ_PLUS_1_TO_PC, + HALT, + ALU_ADD_Z_TO_A, + ALU_ADD_Z_AND_C_TO_A, + ALU_SUB_Z_FROM_A, + ALU_SUB_Z_AND_C_FROM_A, + ALU_AND_AZ, + ALU_XOR_AZ, + ALU_OR_AZ, + ALU_CP_AZ, + ALU_ADD_SPL_TO_Z, + ALU_ADD_SPH_TO_W, + ALU_SPH_PLUS_CADJ_TO_W, + LD_WZ_TO_SP, + LD_SPL_TO_Z, + LD_SPH_TO_W, + LD_WZ_TO_PC, + LD_WZ_TO_PC_AND_ENABLE_IME, + JP_TO_pHL, + SP_DEC, + LD_PCH_TO_SP, + LD_PCL_TO_SP, + LD_A_TO_FF00_PLUS_C, + LD_A_TO_FF00_PLUS_Z, + LD_A_TO_WZ, + LD_FF00_PLUS_C_TO_Z, + LD_Z_TO_R8, + LD_FF00_PLUS_Z_TO_Z, + LD_WZ_TO_Z, + ALU_SPL_PLUS_Z_TO_L, + ALU_SPH_PLUS_CADJ_TO_H, + LD_HL_TO_SP, + DI, + EI, + COND_CHECK_IMM8e, + INC_R8, + LD_pHL_TO_Z, + ALU_LD_Z_PLUS_1_TO_pHL, + ALU_LD_Z_MINUS_1_TO_pHL, + DEC_R8, + LD_Z_TO_pHL, + LD_WZ_TO_R16, + LD_R16_MEM_TO_A, + IDU_INC_R16, + ALU_ADD_LSB_R16_TO_L, + ALU_ADD_CMSB_R16_TO_H, + LD_R16_MEM_TO_Z, + IDU_DEC_R16, + + + INVALID, + EMPTY + }; enum class OperationType_t : Enum_t { INVALID, NOP, @@ -105,16 +174,18 @@ public: // r16stk af = 3, // r16mem - hlPlus = 2, + pBC = 0, + pDE, + hlPlus, hlMinus, // cond condNZ = 0, condZ, condNC, - condC + condC, + NONE }; -protected: using OperandVar_t = std::variant; //In case of IMM8 and IMM16, don't save the next byte(s) //They will be fetched in execution phase @@ -136,9 +207,22 @@ protected: , operand2( operand2_ ) { } }; + struct MicroOperation_t { + MicroOperationType_t type; + Operand_t operand1; + Operand_t operand2; + MicroOperation_t( MicroOperationType_t type_ = MicroOperationType_t::EMPTY, + Operand_t operand1_ = Operand_t::NONE, Operand_t operand2_ = Operand_t::NONE ) + : type( type_ ) + , operand1( operand1_ ) + , operand2( operand2_ ) { + } + }; +protected: // Default values of registers are for DMG; register f is initialized in constructor uint8_t registers[8] { 0x0, 0x13, 0x0, 0xD8, 0x1, 0x4D, 0x1 }; //b,c,d,e,h,l,a,f + uint8_t Z, W; // temporary registers uint16_t SP = 0xFFFE, PC = 0x100; Operation_t operation = Operation_t( 0x0, OperationType_t::NOP ); Memory& mem; @@ -161,6 +245,7 @@ public: void bitShift( Operation_t op ); unsigned execute( const Operation_t& op ); + void execute( const MicroOperation_t mop ); unsigned handleInterrupts(); void ld( const Operation_t& op ); void ldh( const Operation_t& op ); @@ -169,13 +254,15 @@ public: bool isConditionMet( Operand_t condition ); Operation_t decode(); + using MicroOperations_t = std::array; + MicroOperations_t decode( int a ); //helpers - Operation_t decodeBlock0( const uint8_t opcode ); - Operation_t decodeBlock2( const uint8_t opcode ); - Operation_t decodeBlock3( const uint8_t opcode ); - Operation_t decodeCB( const uint8_t opcodeFirstByte ); + MicroOperations_t decodeBlock0( const uint8_t opcode ); + MicroOperations_t decodeBlock2( const uint8_t opcode ); + MicroOperations_t decodeBlock3( const uint8_t opcode ); + MicroOperations_t decodeCB( const uint8_t opcodeFirstByte ); // clang-format off - OperandType_t getR8orPHLType( Operand_t operand ) { return operand == Operand_t::phl ? OperandType_t::pHL : OperandType_t::R8; } + 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 diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index ab15d19..83ea0da 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -1,4 +1,5 @@ #include "core/cpu.hpp" +#include "core/general_constants.hpp" #include "core/logging.hpp" #include #include @@ -207,7 +208,7 @@ void CoreCpu::addTo( OperandVar_t operand, T value ) { currentValue += value; write( operand, currentValue ); - setZNHCFlags( !currentValue, false, halfCarryFlag, cFlag ); + setZNHCFlags( ! currentValue, false, halfCarryFlag, cFlag ); }; template @@ -218,20 +219,20 @@ void CoreCpu::subFrom( OperandVar_t operand, uint8_t value, bool discard ) { bool halfCarryFlag = ( ( currentValue & 0xF ) - ( value & 0xF ) ) < 0; currentValue -= value; - if( !discard ) + if( ! discard ) write( operand, currentValue ); - setZNHCFlags( !currentValue, true, halfCarryFlag, cFlag ); + setZNHCFlags( ! currentValue, true, halfCarryFlag, cFlag ); }; bool CoreCpu::isConditionMet( Operand_t condition ) { using enum Operand_t; - if( condition == condZ && getZFlag() ) + if( condition == condNZ && ! getZFlag() ) return true; - else if( condition == condNZ && !getZFlag() ) + if( condition == condZ && getZFlag() ) return true; - else if( condition == condC && getCFlag() ) + if( condition == condNC && ! getCFlag() ) return true; - else if( condition == condNC && !getCFlag() ) + if( condition == condC && getCFlag() ) return true; return false; @@ -268,7 +269,7 @@ void CoreCpu::bitwise( const Operation_t& op ) { write( { Operand_t::a }, result ); constexpr bool hFlag = ( optype == OperationType_t::AND ); - setZNHCFlags( !result, false, hFlag, false ); + setZNHCFlags( ! result, false, hFlag, false ); } template @@ -311,7 +312,7 @@ void CoreCpu::bitShift( Operation_t op ) { if constexpr( optype == OT::RLA || optype == OT::RRA || optype == OT::RLCA || optype == OT::RRCA ) zFlag = false; else - zFlag = !value; + zFlag = ! value; write( op.operand1, value ); setZNHCFlags( zFlag, false, false, cFlag ); @@ -360,7 +361,7 @@ void CoreCpu::ld( const Operation_t& op ) { bool halfCarryFlag = ( ( readVal & 0xFFF ) + ( imm8 & 0xFFF ) ) > 0xFFF; readVal += imm8; - setZNHCFlags( !readVal, false, halfCarryFlag, cFlag ); + setZNHCFlags( ! readVal, false, halfCarryFlag, cFlag ); } break; default: invalidOperandType( op.operandType2 ); @@ -555,7 +556,7 @@ unsigned CoreCpu::execute( const Operation_t& op ) { const auto bitIndex = std::get( op.operand1 ); const auto value = op.operandType2 == opdt::pHL ? read( op.operand2 ) : read( op.operand2 ); - setZNHCFlags( !( value & ( 1 << bitIndex ) ), false, true, getCFlag() ); + setZNHCFlags( ! ( value & ( 1 << bitIndex ) ), false, true, getCFlag() ); } break; case OT::RES: { const auto bitIndex = std::get( op.operand1 ); @@ -626,7 +627,7 @@ unsigned CoreCpu::execute( const Operation_t& op ) { value = static_cast( ( value >> 4 ) | ( value << 4 ) ); write( op.operand1, value ); } - setZNHCFlags( !value, false, false, false ); + setZNHCFlags( ! value, false, false, false ); } break; //Jumps and subroutine instructions case OT::CALL: { @@ -710,7 +711,7 @@ unsigned CoreCpu::execute( const Operation_t& op ) { case OT::CCF: setNFlag( false ); setHFlag( false ); - setCFlag( !getCFlag() ); + setCFlag( ! getCFlag() ); break; case OT::SCF: setNFlag( false ); @@ -737,7 +738,7 @@ unsigned CoreCpu::execute( const Operation_t& op ) { case OT::HALT: { const auto interruptPending = ( mem.read( addr::interruptEnableRegister ) & 0x1F ) & ( mem.read( addr::interruptFlag ) & 0x1F ); - if( !interruptMasterEnabled && interruptPending ) { + if( ! interruptMasterEnabled && interruptPending ) { // TODO halt bug } else halted = true; @@ -761,7 +762,7 @@ unsigned CoreCpu::execute( const Operation_t& op ) { } addTo( { opd::a }, adjustment ); } - setZNHCFlags( !read( { opd::a } ), getNFlag(), false, cFlag ); + setZNHCFlags( ! read( { opd::a } ), getNFlag(), false, cFlag ); } break; case OT::NOP: break; @@ -784,12 +785,12 @@ unsigned CoreCpu::execute( const Operation_t& op ) { } const unsigned cycles = getCycles( op.opcode, branchTaken ); - logOperation( op, cycles ); + logDebug( std::format( "{} took {}", OperationTypeString[(int)op.operationType], cycles ) ); return cycles; } unsigned CoreCpu::handleInterrupts() { - if( !interruptMasterEnabled ) + if( ! interruptMasterEnabled ) return 0; const auto interruptEnable = mem.read( addr::interruptEnableRegister ); const auto interruptFlag = mem.read( addr::interruptFlag ); @@ -849,9 +850,10 @@ void CoreCpu::logOperation( Operation_t op, [[maybe_unused]] unsigned cycles ) { logDebug( std::format( "OT<{}>, opdt1<{}>, opd1<{}>, opdt2<{}>, opd2<{}>: ; took {} cycles", OperationTypeString[(int)op.operationType], OperandTypeString[(int)op.operandType1], opd1, OperandTypeString[(int)op.operandType2], opd2, cycles ) ); + logDebug( std::format( "CPU flags ZNHC<{:04b}>", ( registers[7] >> 4 ) ) ); } CoreCpu::CoreCpu( Memory& mem_ ) : mem( mem_ ) { //set register f - const bool headerChecksumNonZero = mem.read( 0x147 ); + const bool headerChecksumNonZero = mem.read( addr::headerChecksum ); setZNHCFlags( 1, 0, headerChecksumNonZero, headerChecksumNonZero ); }; diff --git a/src/core/decode.cpp b/src/core/decode.cpp deleted file mode 100644 index b30673f..0000000 --- a/src/core/decode.cpp +++ /dev/null @@ -1,247 +0,0 @@ -#include "core/cpu.hpp" -#include - -// source: https://gbdev.io/pandocs/CPU_Instruction_Set.html - -CoreCpu::Operation_t CoreCpu::decode() { - // first check instructions without different operand variants - const auto opcode = mem.read( PC++ ); - switch( opcode ) { - //block 0 - case 0x0: - return { opcode, OperationType_t::NOP }; - case 0x10: - return { opcode, OperationType_t::STOP }; - case 0x08: - return { opcode, OperationType_t::LD, OperandType_t::pIMM16, {}, OperandType_t::R16, Operand_t::sp }; - case 0x07: - return { opcode, OperationType_t::RLCA }; - case 0x0F: - return { opcode, OperationType_t::RRCA }; - case 0x17: - return { opcode, OperationType_t::RLA }; - case 0x1F: - return { opcode, OperationType_t::RRA }; - case 0x27: - return { opcode, OperationType_t::DAA }; - case 0x2F: - return { opcode, OperationType_t::CPL }; - case 0x37: - return { opcode, OperationType_t::SCF }; - case 0x3F: - return { opcode, OperationType_t::CCF }; - case 0x18: - return { opcode, OperationType_t::JR, OperandType_t::IMM8 }; - //block 1 - case 0x76: - return { opcode, OperationType_t::HALT }; - //block 3 - //arithmetic - case 0xC6: - return { opcode, OperationType_t::ADD, OperandType_t::R8, Operand_t::a, OperandType_t::IMM8 }; - case 0xCE: - return { opcode, OperationType_t::ADC, OperandType_t::R8, Operand_t::a, OperandType_t::IMM8 }; - case 0xD6: - return { opcode, OperationType_t::SUB, OperandType_t::R8, Operand_t::a, OperandType_t::IMM8 }; - case 0xDE: - return { opcode, OperationType_t::SBC, OperandType_t::R8, Operand_t::a, OperandType_t::IMM8 }; - case 0xE6: - return { opcode, OperationType_t::AND, OperandType_t::R8, Operand_t::a, OperandType_t::IMM8 }; - case 0xEE: - return { opcode, OperationType_t::XOR, OperandType_t::R8, Operand_t::a, OperandType_t::IMM8 }; - case 0xF6: - return { opcode, OperationType_t::OR, OperandType_t::R8, Operand_t::a, OperandType_t::IMM8 }; - case 0xFE: - return { opcode, OperationType_t::CP, OperandType_t::R8, Operand_t::a, OperandType_t::IMM8 }; - case 0xE8: - return { opcode, OperationType_t::ADD, OperandType_t::R16, Operand_t::sp, OperandType_t::IMM8 }; - //control flow - case 0xC9: - return { opcode, OperationType_t::RET }; - case 0xD9: - return { opcode, OperationType_t::RETI }; - case 0xC3: - return { opcode, OperationType_t::JP, OperandType_t::IMM16 }; - case 0xE9: - return { opcode, OperationType_t::JP, OperandType_t::R16, Operand_t::hl }; - case 0xCD: - return { opcode, OperationType_t::CALL, OperandType_t::IMM16 }; - //the rest - case 0xCB: - return decodeCB( opcode ); - // SOURCES_DISAGREE - // rgbds.gbdev.io/docs/v0.9.1/gbz80.7 says that there are n16 operands used - // gbdev.io/pandocs/CPU_Instruction_Set.html says that n8 is used - // gekkio.fi/files/gb-docs/gbctr.pdf also says that n8 is used - // I'll go with n8 - case 0xE2: - return { opcode, OperationType_t::LDH, OperandType_t::FF00_PLUS_R8, - Operand_t::c, OperandType_t::R8, Operand_t::a }; - case 0xE0: - return { opcode, OperationType_t::LDH, OperandType_t::IMM8, {}, OperandType_t::R8, Operand_t::a }; - case 0xEA: - return { opcode, OperationType_t::LD, OperandType_t::pIMM16, {}, OperandType_t::R8, Operand_t::a }; - case 0xF2: - return { opcode, OperationType_t::LDH, OperandType_t::R8, - Operand_t::a, OperandType_t::FF00_PLUS_R8, Operand_t::c }; - case 0xF0: - return { opcode, OperationType_t::LDH, OperandType_t::R8, Operand_t::a, OperandType_t::IMM8 }; - case 0xFA: - return { opcode, OperationType_t::LD, OperandType_t::R8, Operand_t::a, OperandType_t::pIMM16 }; - case 0xF8: - return { opcode, OperationType_t::LD, OperandType_t::R16, Operand_t::hl, OperandType_t::SP_PLUS_IMM8 }; - case 0xF9: - return { opcode, OperationType_t::LD, OperandType_t::R16, - Operand_t::sp, OperandType_t::R16, Operand_t::hl }; - case 0xF3: - return { opcode, OperationType_t::DI }; - case 0xFB: - return { opcode, OperationType_t::EI }; - } - - switch( 0x3 & ( opcode >> 6 ) ) { // leave only 2 most significant bits - case 0x0: - return decodeBlock0( opcode ); - case 0x1: { - const auto dest = static_cast( 0x7 & ( opcode >> 3 ) ); - const auto src = static_cast( 0x7 & opcode ); - - return { opcode, OperationType_t::LD, getR8orPHLType( dest ), dest, getR8orPHLType( src ), src }; - } - case 0x2: - return decodeBlock2( opcode ); - case 0x3: - return decodeBlock3( opcode ); - } - return { opcode, OperationType_t::INVALID }; -} - -CoreCpu::Operation_t CoreCpu::decodeBlock0( const uint8_t opcode ) { - //count from 0 - const auto operandR8 = static_cast( 0x7 & ( opcode >> 3 ) ); - const auto operandR16 = static_cast( 0x3 & ( opcode >> 4 ) ); - switch( 0x7 & opcode ) { - case 0x0: - return { opcode, OperationType_t::JR, OperandType_t::COND, - static_cast( 0x3 & ( opcode >> 3 ) ), OperandType_t::IMM8 }; - case 0x4: - return { opcode, OperationType_t::INC, getR8orPHLType( operandR8 ), operandR8 }; - case 0x5: - return { opcode, OperationType_t::DEC, getR8orPHLType( operandR8 ), operandR8 }; - case 0x6: - return { opcode, OperationType_t::LD, getR8orPHLType( operandR8 ), operandR8, OperandType_t::IMM8 }; - } - - switch( 0xF & opcode ) { - case 0x1: - return { opcode, OperationType_t::LD, OperandType_t::R16, operandR16, OperandType_t::IMM16 }; - case 0x2: - return { opcode, OperationType_t::LD, OperandType_t::R16MEM, - operandR16, OperandType_t::R8, Operand_t::a }; - case 0x3: - return { opcode, OperationType_t::INC, OperandType_t::R16, operandR16 }; - case 0x9: - return { opcode, OperationType_t::ADD, OperandType_t::R16, - Operand_t::hl, OperandType_t::R16, operandR16 }; - case 0xA: - return { opcode, OperationType_t::LD, OperandType_t::R8, - Operand_t::a, OperandType_t::R16MEM, operandR16 }; - case 0xB: - return { opcode, OperationType_t::DEC, OperandType_t::R16, operandR16 }; - } - return Operation_t { opcode, OperationType_t::INVALID }; -} - -CoreCpu::Operation_t CoreCpu::decodeBlock2( const uint8_t opcode ) { - const auto r8 = static_cast( 0x7 & opcode ); - const auto r8Type = getR8orPHLType( r8 ); - auto opType = OperationType_t::INVALID; - switch( 0x7 & ( opcode >> 3 ) ) { - case 0x0: - opType = OperationType_t::ADD; - break; - case 0x1: - opType = OperationType_t::ADC; - break; - case 0x2: - opType = OperationType_t::SUB; - break; - case 0x3: - opType = OperationType_t::SBC; - break; - case 0x4: - opType = OperationType_t::AND; - break; - case 0x5: - opType = OperationType_t::XOR; - break; - case 0x6: - opType = OperationType_t::OR; - break; - case 0x7: - opType = OperationType_t::CP; - } - return { opcode, opType, OperandType_t::R8, Operand_t::a, r8Type, r8 }; -} - - -CoreCpu::Operation_t CoreCpu::decodeBlock3( const uint8_t opcode ) { - const auto condition = static_cast( 0x3 & ( opcode >> 3 ) ); - const auto r16stk = static_cast( 0x3 & ( opcode >> 4 ) ); - switch( 0x7 & opcode ) { - case 0x0: - return { opcode, OperationType_t::RET, OperandType_t::COND, condition }; - case 0x2: - return { opcode, OperationType_t::JP, OperandType_t::COND, condition, OperandType_t::IMM16 }; - case 0x4: - return { opcode, OperationType_t::CALL, OperandType_t::COND, condition, OperandType_t::IMM16 }; - case 0x7: - return { opcode, OperationType_t::RST, OperandType_t::TGT3, - static_cast( 0x7 & ( opcode >> 3 ) ) }; - case 0x1: - return { opcode, OperationType_t::POP, OperandType_t::R16STK, r16stk }; - case 0x5: - return { opcode, OperationType_t::PUSH, OperandType_t::R16STK, r16stk }; - } - return { opcode, OperationType_t::INVALID }; -} - -CoreCpu::Operation_t CoreCpu::decodeCB( const uint8_t opcodeFirstByte ) { - const auto opcodeSecondByte = mem.read( PC++ ); - const auto opcode = static_cast( opcodeFirstByte | opcodeSecondByte << 8 ); - const auto r8 = static_cast( opcodeSecondByte & 0x7 ); - const auto r8Type = getR8orPHLType( r8 ); - const auto b3index = static_cast( 0x7 & ( opcodeSecondByte >> 3 ) ); - switch( 0x3 & ( opcodeSecondByte >> 6 ) ) { - case 0x0: - switch( 0x7 & opcodeSecondByte ) { - case 0x0: - return { opcode, OperationType_t::RLC, r8Type, r8 }; - case 0x1: - return { opcode, OperationType_t::RRC, r8Type, r8 }; - case 0x2: - return { opcode, OperationType_t::RL, r8Type, r8 }; - case 0x3: - return { opcode, OperationType_t::RR, r8Type, r8 }; - case 0x4: - return { opcode, OperationType_t::SLA, r8Type, r8 }; - case 0x5: - return { opcode, OperationType_t::SRA, r8Type, r8 }; - case 0x6: - return { opcode, OperationType_t::SWAP, r8Type, r8 }; - case 0x7: - return { opcode, OperationType_t::SRL, r8Type, r8 }; - default: - std::unreachable(); - } - - case 0x1: - return { opcode, OperationType_t::BIT, OperandType_t::BIT_INDEX, b3index, r8Type, r8 }; - case 0x2: - return { opcode, OperationType_t::RES, OperandType_t::BIT_INDEX, b3index, r8Type, r8 }; - case 0x3: - return { opcode, OperationType_t::SET, OperandType_t::BIT_INDEX, b3index, r8Type, r8 }; - default: - std::unreachable(); - } -} diff --git a/src/core/microcode.cpp b/src/core/microcode.cpp new file mode 100644 index 0000000..530e865 --- /dev/null +++ b/src/core/microcode.cpp @@ -0,0 +1,261 @@ +#include "core/cpu.hpp" + +// Operand order is target first, source next +CoreCpu::MicroOperations_t CoreCpu::decode( int a ) { + using enum MicroOperationType_t; + // first check instructions without different operand variants + const auto opcode = mem.read( PC++ ); + switch( opcode ) { + //block 0 + case 0x0: + return { { NOP } }; //NOP + case 0x10: + return { { STOP } }; // STOP + case 0x08: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_SPL_TO_pWZ, LD_SPH_TO_pWZ, NOP } }; // LD pIMM16, SP + case 0x07: + return { { RLCA } }; + case 0x0F: + return { { RRCA } }; + case 0x17: + return { { RLA } }; + case 0x1F: + return { { RRA } }; + case 0x27: + return { { DAA } }; + case 0x2F: + return { { CPL } }; + case 0x37: + return { { SCF } }; + case 0x3F: + return { { CCF } }; + case 0x18: + return { { LD_IMM_TO_Z, ALU_CALC_RELATIVE_JUMP, IDU_LD_WZ_PLUS_1_TO_PC } }; // JR IMM8 + //block 1 + case 0x76: + return { { HALT } }; // HALT + //block 3 + //arithmetic + case 0xC6: + return { { LD_IMM_TO_Z, ALU_ADD_Z_TO_A } }; // ADD IMM8 + case 0xCE: + return { { LD_IMM_TO_Z, ALU_ADD_Z_AND_C_TO_A } }; // ADC IMM8 + case 0xD6: + return { { LD_IMM_TO_Z, ALU_SUB_Z_FROM_A } }; // SUB IMM8 + case 0xDE: + return { { LD_IMM_TO_Z, ALU_SUB_Z_AND_C_FROM_A } }; // SBC IMM8 + case 0xE6: + return { { LD_IMM_TO_Z, ALU_AND_AZ } }; // AND IMM8 + case 0xEE: + return { { LD_IMM_TO_Z, ALU_XOR_AZ } }; // XOR IMM8 + case 0xF6: + return { { LD_IMM_TO_Z, ALU_OR_AZ } }; // OR IMM8 + case 0xFE: + return { { LD_IMM_TO_Z, ALU_CP_AZ } }; // CP IMM8 + case 0xE8: + return { { LD_IMM_TO_Z, ALU_ADD_SPL_TO_Z, ALU_SPH_PLUS_CADJ_TO_W, LD_WZ_TO_SP } }; // ADD IMM8s + //control flow + case 0xC9: + return { { LD_SPL_TO_Z, LD_SPH_TO_W, LD_WZ_TO_PC, NOP } }; // RET + case 0xD9: + return { { LD_SPL_TO_Z, LD_SPH_TO_W, LD_WZ_TO_PC_AND_ENABLE_IME, NOP } }; // RETI + case 0xC3: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_WZ_TO_PC, NOP } }; // JP IMM16 + case 0xE9: + return { { JP_TO_pHL } }; // JP pHL + case 0xCD: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, SP_DEC, LD_PCH_TO_SP, LD_PCL_TO_SP, NOP } }; // CALL IMM16 + //the rest + case 0xCB: + // return decodeCB( opcode ); + case 0xE2: + return { { LD_A_TO_FF00_PLUS_C, NOP } }; //LDH pC, A + case 0xE0: + return { { LD_IMM_TO_Z, LD_A_TO_FF00_PLUS_Z, NOP } }; // LDH IMM8, A + case 0xEA: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_A_TO_WZ, NOP } }; // LD pIMM16, A + case 0xF2: + return { { LD_FF00_PLUS_C_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LDH A, pC + case 0xF0: + return { { LD_IMM_TO_Z, LD_FF00_PLUS_Z_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LDH A, IMM8 + case 0xFA: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_WZ_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LD A, pIMM16 + case 0xF8: + return { { LD_IMM_TO_Z, ALU_SPL_PLUS_Z_TO_L, ALU_SPH_PLUS_CADJ_TO_H } }; // LD HL, SP+IMM8s + case 0xF9: + return { { LD_HL_TO_SP, NOP } }; // LD SP, HL + case 0xF3: + return { { DI } }; + case 0xFB: + return { { EI } }; + } + + switch( 0x3 & ( opcode >> 6 ) ) { // leave only 2 most significant bits + case 0x0: + return decodeBlock0( opcode ); + case 0x1: { + const auto dest = static_cast( 0x7 & ( opcode >> 3 ) ); + const auto src = static_cast( 0x7 & opcode ); + + return { opcode, OperationType_t::LD, getR8orPHLType( dest ), dest, getR8orPHLType( src ), src }; + } + case 0x2: + return decodeBlock2( opcode ); + case 0x3: + return decodeBlock3( opcode ); + } + return { { INVALID } }; +} + +CoreCpu::MicroOperations_t CoreCpu::decodeBlock0( const uint8_t opcode ) { + using enum MicroOperationType_t; + //count from 0 + const auto operandR8 = static_cast( 0x7 & ( opcode >> 3 ) ); + const auto operandR16 = static_cast( 0x3 & ( opcode >> 4 ) ); + switch( 0x7 & opcode ) { + case 0x0: + // Return longer version ( branch taken ), which can be shorten later + return { { { COND_CHECK_IMM8e, static_cast( 0x3 & ( opcode >> 3 ) ) }, + ALU_CALC_RELATIVE_JUMP, + IDU_LD_WZ_PLUS_1_TO_PC } }; // JR COND, IMM8 + case 0x4: + if( isPHL( operandR8 ) ) + return { { LD_pHL_TO_Z, ALU_LD_Z_PLUS_1_TO_pHL, NOP } }; // INC pHL + else + return { { { INC_R8, operandR8 } } }; // INC R8 + case 0x5: + if( isPHL( operandR8 ) ) + return { { LD_pHL_TO_Z, ALU_LD_Z_MINUS_1_TO_pHL, NOP } }; // DEC pHL + else + return { { { DEC_R8, operandR8 } } }; // DEC R8 + case 0x6: + if( isPHL( operandR8 ) ) + return { { LD_pHL_TO_Z, LD_Z_TO_pHL, NOP } }; // LD pHL, IMM8 + else + return { { LD_IMM_TO_Z, { LD_Z_TO_R8, operandR8 } } }; // LD R8, IMM8 + } + + switch( 0xF & opcode ) { + case 0x1: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, { LD_WZ_TO_R16, operandR16 } } }; // LD R16, IMM16 + case 0x2: + return { { { LD_R16_MEM_TO_A, operandR16 }, NOP } }; // LD R16MEM, A + case 0x3: + return { { { IDU_INC_R16, operandR16 }, NOP } }; // INC R16 + case 0x9: + return { { { ALU_ADD_LSB_R16_TO_L, operandR16 }, + { ALU_ADD_CMSB_R16_TO_H, operandR16 } } }; // ADD HL, R16 + case 0xA: + return { { { LD_R16_MEM_TO_Z, operandR16 }, { LD_Z_TO_R8, Operand_t::a } } }; // LD A, R16MEM + case 0xB: + return { { { IDU_DEC_R16, operandR16 }, NOP } }; // DEC R16 + } + return { { INVALID } }; +} + +CoreCpu::Operation_t CoreCpu::decodeBlock2( const uint8_t opcode ) { + const auto r8 = static_cast( 0x7 & opcode ); + const auto r8Type = getR8orPHLType( r8 ); + auto opType = OperationType_t::INVALID; + switch( 0x7 & ( opcode >> 3 ) ) { + case 0x0: + opType = OperationType_t::ADD; + break; + case 0x1: + opType = OperationType_t::ADC; + break; + case 0x2: + opType = OperationType_t::SUB; + break; + case 0x3: + opType = OperationType_t::SBC; + break; + case 0x4: + opType = OperationType_t::AND; + break; + case 0x5: + opType = OperationType_t::XOR; + break; + case 0x6: + opType = OperationType_t::OR; + break; + case 0x7: + opType = OperationType_t::CP; + } + return { opcode, opType, OperandType_t::R8, Operand_t::a, r8Type, r8 }; +} + + +CoreCpu::Operation_t CoreCpu::decodeBlock3( const uint8_t opcode ) { + const auto condition = static_cast( 0x3 & ( opcode >> 3 ) ); + const auto r16stk = static_cast( 0x3 & ( opcode >> 4 ) ); + switch( 0x7 & opcode ) { + case 0x0: + return { opcode, OperationType_t::RET, OperandType_t::COND, condition }; + case 0x2: + return { opcode, OperationType_t::JP, OperandType_t::COND, condition, OperandType_t::IMM16 }; + case 0x4: + return { opcode, OperationType_t::CALL, OperandType_t::COND, condition, OperandType_t::IMM16 }; + case 0x7: + return { opcode, OperationType_t::RST, OperandType_t::TGT3, + static_cast( 0x7 & ( opcode >> 3 ) ) }; + case 0x1: + return { opcode, OperationType_t::POP, OperandType_t::R16STK, r16stk }; + case 0x5: + return { opcode, OperationType_t::PUSH, OperandType_t::R16STK, r16stk }; + } + return { opcode, OperationType_t::INVALID }; +} + +CoreCpu::MicroOperations_t CoreCpu::decodeCB( const uint8_t opcodeFirstByte ) { + const auto opcodeSecondByte = mem.read( PC++ ); + const auto opcode = static_cast( opcodeFirstByte | opcodeSecondByte << 8 ); + const auto r8 = static_cast( opcodeSecondByte & 0x7 ); + const auto r8Type = getR8orPHLType( r8 ); + const auto b3index = static_cast( 0x7 & ( opcodeSecondByte >> 3 ) ); + switch( 0x3 & ( opcodeSecondByte >> 6 ) ) { + case 0x0: + switch( 0x7 & opcodeSecondByte ) { + case 0x0: + if( isPHL( operandR8 ) ) + return { { NOP, LD_pHL_TO_Z, RLC_Z, NOP } }; // RLC pHL + else + return { { NOP, RLC_R8 } }; // RLC R8 + return { opcode, OperationType_t::RLC, r8Type, r8 }; + case 0x1: + return { opcode, OperationType_t::RRC, r8Type, r8 }; + case 0x2: + return { opcode, OperationType_t::RL, r8Type, r8 }; + case 0x3: + return { opcode, OperationType_t::RR, r8Type, r8 }; + case 0x4: + return { opcode, OperationType_t::SLA, r8Type, r8 }; + case 0x5: + return { opcode, OperationType_t::SRA, r8Type, r8 }; + case 0x6: + return { opcode, OperationType_t::SWAP, r8Type, r8 }; + case 0x7: + return { opcode, OperationType_t::SRL, r8Type, r8 }; + default: + std::unreachable(); + } + + case 0x1: + return { opcode, OperationType_t::BIT, OperandType_t::BIT_INDEX, b3index, r8Type, r8 }; + case 0x2: + return { opcode, OperationType_t::RES, OperandType_t::BIT_INDEX, b3index, r8Type, r8 }; + case 0x3: + return { opcode, OperationType_t::SET, OperandType_t::BIT_INDEX, b3index, r8Type, r8 }; + default: + std::unreachable(); + } +} + + +void CoreCpu::execute( const MicroOperation_t mop ) { + switch( mop.type ) { + using enum MicroOperationType_t; + case NOP: + return; + } +} -- GitLab From 09cc30ac6da46f4da3ef9072a826cef3c8cd1063 Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Wed, 18 Jun 2025 00:02:53 +0200 Subject: [PATCH 2/8] decode microcode 2/2 --- include/core/cpu.hpp | 77 ++++++--- src/core/cpu_decode.cpp | 319 ++++++++++++++++++++++++++++++++++++ src/core/microcode.cpp | 261 ----------------------------- src/raylib/raylib_parts.cpp | 2 +- src/std_logging/logging.cpp | 1 - 5 files changed, 378 insertions(+), 282 deletions(-) create mode 100644 src/core/cpu_decode.cpp delete mode 100644 src/core/microcode.cpp diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index 60b7a2c..4867fcc 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -33,35 +33,35 @@ public: ALU_ADD_Z_AND_C_TO_A, ALU_SUB_Z_FROM_A, ALU_SUB_Z_AND_C_FROM_A, - ALU_AND_AZ, - ALU_XOR_AZ, - ALU_OR_AZ, - ALU_CP_AZ, + ALU_A_AND_Z, + ALU_A_XOR_Z, + ALU_A_OR_Z, + ALU_A_CP_Z, ALU_ADD_SPL_TO_Z, ALU_ADD_SPH_TO_W, - ALU_SPH_PLUS_CADJ_TO_W, + ALU_SPH_ADC_ADJ_TO_W, LD_WZ_TO_SP, - LD_SPL_TO_Z, - LD_SPH_TO_W, + POP_SP_TO_Z, + POP_SP_TO_W, LD_WZ_TO_PC, - LD_WZ_TO_PC_AND_ENABLE_IME, - JP_TO_pHL, + LD_WZ_TO_PC__ENABLE_IME, + JP_TO_HL, SP_DEC, LD_PCH_TO_SP, LD_PCL_TO_SP, LD_A_TO_FF00_PLUS_C, LD_A_TO_FF00_PLUS_Z, - LD_A_TO_WZ, + LD_A_TO_pWZ, LD_FF00_PLUS_C_TO_Z, LD_Z_TO_R8, LD_FF00_PLUS_Z_TO_Z, - LD_WZ_TO_Z, - ALU_SPL_PLUS_Z_TO_L, - ALU_SPH_PLUS_CADJ_TO_H, + LD_pWZ_TO_Z, + ALU_SPL_PLUS_Z_TO_LHL, + ALU_SPH_ADC_ADJ_TO_HHL, LD_HL_TO_SP, DI, EI, - COND_CHECK_IMM8e, + COND_CHECK__LD_IMM_TO_Z, INC_R8, LD_pHL_TO_Z, ALU_LD_Z_PLUS_1_TO_pHL, @@ -75,8 +75,48 @@ public: ALU_ADD_CMSB_R16_TO_H, LD_R16_MEM_TO_Z, IDU_DEC_R16, - - + LD_R8_TO_R8, + LD_R8_TO_pHL, + ALU_ADD_R8_TO_A, + ALU_ADC_Z_TO_A, + ALU_ADC_R8_TO_A, + ALU_SUB_R8_FROM_A, + ALU_SBC_Z_FROM_A, + ALU_SBC_R8_FROM_A, + ALU_A_AND_R8, + ALU_A_XOR_R8, + ALU_A_OR_R8, + ALU_CP_A_R8, + CHECK_COND, + COND_CHECK__LD_IMM_TO_W, + LD_PCL_TO_SP__LD_WZ_TO_PC, + LD_PCL_TO_SP__LD_TGT3_TO_PC, + LD_WZ_TO_R16STK, + PUSH_MSB_R16STK_TO_SP, + PUSH_LSB_R16STK_TO_SP, + FETCH_SECOND_BYTE, + LD_RLC_Z_TO_pHL, + RLC_R8, + LD_RRC_Z_TO_pHL, + RRC_R8, + LD_RL_Z_TO_pHL, + RL_R8, + LD_RR_Z_TO_pHL, + RR_R8, + LD_SLA_Z_TO_pHL, + SLA_R8, + LD_SRA_Z_TO_pHL, + SRA_R8, + LD_SWAP_Z_TO_pHL, + SWAP_R8, + LD_SRL_Z_TO_pHL, + SRL_R8, + BIT_Z, + BIT_R8, + RES_pHL, + RES_R8, + SET_pHL, + SET_R8, INVALID, EMPTY }; @@ -253,14 +293,13 @@ public: uint16_t popFromStack(); bool isConditionMet( Operand_t condition ); - Operation_t decode(); using MicroOperations_t = std::array; - MicroOperations_t decode( int a ); + MicroOperations_t decode(); //helpers MicroOperations_t decodeBlock0( const uint8_t opcode ); MicroOperations_t decodeBlock2( const uint8_t opcode ); MicroOperations_t decodeBlock3( const uint8_t opcode ); - MicroOperations_t decodeCB( const uint8_t opcodeFirstByte ); + MicroOperations_t decodeCB(); // clang-format off bool isPHL( Operand_t operand ) { return operand == Operand_t::phl; } bool getZFlag() { return registers[7] &( 1 << 7 ); } // Zero flag diff --git a/src/core/cpu_decode.cpp b/src/core/cpu_decode.cpp new file mode 100644 index 0000000..df58336 --- /dev/null +++ b/src/core/cpu_decode.cpp @@ -0,0 +1,319 @@ +#include "core/cpu.hpp" +#include + +// TODO adding signed IMM8 - check this +// TODO check LD operations regarding SP + +using enum CoreCpu::MicroOperationType_t; +// Operand order is target first, source next +CoreCpu::MicroOperations_t CoreCpu::decode() { + // first check instructions without different operand variants + const auto opcode = mem.read( PC++ ); + switch( opcode ) { + //block 0 + case 0x0: + return { { NOP } }; //NOP + case 0x10: + return { { STOP } }; // STOP + case 0x08: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_SPL_TO_pWZ, LD_SPH_TO_pWZ, NOP } }; // LD pIMM16, SP + case 0x07: + return { { RLCA } }; + case 0x0F: + return { { RRCA } }; + case 0x17: + return { { RLA } }; + case 0x1F: + return { { RRA } }; + case 0x27: + return { { DAA } }; + case 0x2F: + return { { CPL } }; + case 0x37: + return { { SCF } }; + case 0x3F: + return { { CCF } }; + case 0x18: + return { { LD_IMM_TO_Z, ALU_CALC_RELATIVE_JUMP, IDU_LD_WZ_PLUS_1_TO_PC } }; // JR IMM8 + //block 1 + case 0x76: + return { { HALT } }; // HALT + //block 3 + //arithmetic + case 0xC6: + return { { LD_IMM_TO_Z, ALU_ADD_Z_TO_A } }; // ADD IMM8 + case 0xCE: + return { { LD_IMM_TO_Z, ALU_ADD_Z_AND_C_TO_A } }; // ADC IMM8 + case 0xD6: + return { { LD_IMM_TO_Z, ALU_SUB_Z_FROM_A } }; // SUB IMM8 + case 0xDE: + return { { LD_IMM_TO_Z, ALU_SUB_Z_AND_C_FROM_A } }; // SBC IMM8 + case 0xE6: + return { { LD_IMM_TO_Z, ALU_A_AND_Z } }; // AND IMM8 + case 0xEE: + return { { LD_IMM_TO_Z, ALU_A_XOR_Z } }; // XOR IMM8 + case 0xF6: + return { { LD_IMM_TO_Z, ALU_A_OR_Z } }; // OR IMM8 + case 0xFE: + return { { LD_IMM_TO_Z, ALU_A_CP_Z } }; // CP IMM8 + case 0xE8: + return { { LD_IMM_TO_Z, ALU_ADD_SPL_TO_Z, ALU_SPH_ADC_ADJ_TO_W, LD_WZ_TO_SP } }; // ADD SP, IMM8s + //control flow + case 0xC9: + return { { POP_SP_TO_Z, POP_SP_TO_W, LD_WZ_TO_PC, NOP } }; // RET + case 0xD9: + return { { POP_SP_TO_Z, POP_SP_TO_W, LD_WZ_TO_PC__ENABLE_IME, NOP } }; // RETI + case 0xC3: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_WZ_TO_PC, NOP } }; // JP IMM16 + case 0xE9: + return { { JP_TO_HL } }; // JP pHL + case 0xCD: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, SP_DEC, LD_PCH_TO_SP, LD_PCL_TO_SP, NOP } }; // CALL IMM16 + //the rest + case 0xCB: + return decodeCB(); + case 0xE2: + return { { LD_A_TO_FF00_PLUS_C, NOP } }; //LDH pC, A + case 0xE0: + return { { LD_IMM_TO_Z, LD_A_TO_FF00_PLUS_Z, NOP } }; // LDH IMM8, A + case 0xEA: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_A_TO_pWZ, NOP } }; // LD pIMM16, A + case 0xF2: + return { { LD_FF00_PLUS_C_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LDH A, pC + case 0xF0: + return { { LD_IMM_TO_Z, LD_FF00_PLUS_Z_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LDH A, IMM8 + case 0xFA: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_pWZ_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LD A, pIMM16 + case 0xF8: + return { { LD_IMM_TO_Z, ALU_SPL_PLUS_Z_TO_LHL, ALU_SPH_ADC_ADJ_TO_HHL } }; // LD HL, SP+IMM8s + case 0xF9: + return { { LD_HL_TO_SP, NOP } }; // LD SP, HL + case 0xF3: + return { { DI } }; + case 0xFB: + return { { EI } }; + } + + switch( 0x3 & ( opcode >> 6 ) ) { // leave only 2 most significant bits + case 0x0: + return decodeBlock0( opcode ); + case 0x1: { + const auto dest = static_cast( 0x7 & ( opcode >> 3 ) ); + const auto src = static_cast( 0x7 & opcode ); + + if( ! isPHL( dest ) && ! isPHL( src ) ) + return { { { LD_R8_TO_R8, dest, src } } }; // LD R8, R8 + if( isPHL( src ) ) + return { { LD_pHL_TO_Z, { LD_Z_TO_R8, dest } } }; // LD R8, pHL + if( isPHL( dest ) ) + return { { { LD_R8_TO_pHL, src }, NOP } }; // LD pHL, R8 + + return { { INVALID } }; + } + case 0x2: + return decodeBlock2( opcode ); + case 0x3: + return decodeBlock3( opcode ); + } + return { { INVALID } }; +} + + +CoreCpu::MicroOperations_t CoreCpu::decodeBlock0( const uint8_t opcode ) { + //count from 0 + const auto r8 = static_cast( 0x7 & ( opcode >> 3 ) ); + const auto r16 = static_cast( 0x3 & ( opcode >> 4 ) ); + switch( 0x7 & opcode ) { + case 0x0: + // Return longer version ( branch taken ), which can be shorten later + return { { { COND_CHECK__LD_IMM_TO_Z, static_cast( 0x3 & ( opcode >> 3 ) ) }, + ALU_CALC_RELATIVE_JUMP, + IDU_LD_WZ_PLUS_1_TO_PC } }; // JR COND, IMM8 + case 0x4: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_LD_Z_PLUS_1_TO_pHL, NOP } }; // INC pHL + else + return { { { INC_R8, r8 } } }; // INC R8 + case 0x5: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_LD_Z_MINUS_1_TO_pHL, NOP } }; // DEC pHL + else + return { { { DEC_R8, r8 } } }; // DEC R8 + case 0x6: + if( isPHL( r8 ) ) + return { { LD_IMM_TO_Z, LD_Z_TO_pHL, NOP } }; // LD pHL, IMM8 + else + return { { LD_IMM_TO_Z, { LD_Z_TO_R8, r8 } } }; // LD R8, IMM8 + } + + switch( 0xF & opcode ) { + case 0x1: + return { { LD_IMM_TO_Z, LD_IMM_TO_W, { LD_WZ_TO_R16, r16 } } }; // LD R16, IMM16 + case 0x2: + return { { { LD_R16_MEM_TO_A, r16 }, NOP } }; // LD R16MEM, A + case 0x3: + return { { { IDU_INC_R16, r16 }, NOP } }; // INC R16 + case 0x9: + return { { { ALU_ADD_LSB_R16_TO_L, r16 }, { ALU_ADD_CMSB_R16_TO_H, r16 } } }; // ADD HL, R16 + case 0xA: + return { { { LD_R16_MEM_TO_Z, r16 }, { LD_Z_TO_R8, Operand_t::a } } }; // LD A, R16MEM + case 0xB: + return { { { IDU_DEC_R16, r16 }, NOP } }; // DEC R16 + } + return { { INVALID } }; +} + + +CoreCpu::MicroOperations_t CoreCpu::decodeBlock2( const uint8_t opcode ) { + const auto r8 = static_cast( 0x7 & opcode ); + switch( 0x7 & ( opcode >> 3 ) ) { + case 0x0: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_ADD_Z_TO_A } }; // ADD pHL + else + return { { { ALU_ADD_R8_TO_A, r8 } } }; // ADD r8 + case 0x1: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_ADC_Z_TO_A } }; // ADC pHL + else + return { { { ALU_ADC_R8_TO_A, r8 } } }; // ADC r8 + case 0x2: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_SUB_Z_FROM_A } }; // SUB pHL + else + return { { { ALU_SUB_R8_FROM_A, r8 } } }; // SUB r8 + case 0x3: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_SBC_Z_FROM_A } }; // SBC pHL + else + return { { { ALU_SBC_R8_FROM_A, r8 } } }; // SBC r8 + case 0x4: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_A_AND_Z } }; // AND pHL + else + return { { { ALU_A_AND_R8, r8 } } }; // AND r8 + case 0x5: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_A_XOR_Z } }; // XOR pHL + else + return { { { ALU_A_XOR_R8, r8 } } }; // XOR r8 + case 0x6: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_A_OR_Z } }; // OR pHL + else + return { { { ALU_A_OR_R8, r8 } } }; // OR r8 + case 0x7: + if( isPHL( r8 ) ) + return { { LD_pHL_TO_Z, ALU_A_CP_Z } }; // CP pHL + else + return { { { ALU_CP_A_R8, r8 } } }; // CP r8 + } + return { { INVALID } }; +} + + +CoreCpu::MicroOperations_t CoreCpu::decodeBlock3( const uint8_t opcode ) { + const auto condition = static_cast( 0x3 & ( opcode >> 3 ) ); + const auto r16stk = static_cast( 0x3 & ( opcode >> 4 ) ); + switch( 0x7 & opcode ) { + case 0x0: + // Return longer version ( branch taken ), which can be shorten later + return { { { CHECK_COND, condition }, POP_SP_TO_Z, POP_SP_TO_W, LD_WZ_TO_PC, NOP } }; // RET cc + case 0x2: + // Return longer version ( branch taken ), which can be shorten later + return { { LD_IMM_TO_Z, { COND_CHECK__LD_IMM_TO_W, condition }, LD_WZ_TO_PC, NOP } }; // JP cc, IMM16 + case 0x4: + // Return longer version ( branch taken ), which can be shorten later + return { { LD_IMM_TO_Z, + COND_CHECK__LD_IMM_TO_W, + { IDU_DEC_R16, Operand_t::sp }, + LD_PCH_TO_SP, + LD_PCL_TO_SP__LD_WZ_TO_PC, + NOP } }; // CALL cc, IMM16 + case 0x7: + return { { { IDU_DEC_R16, Operand_t::sp }, + LD_PCH_TO_SP, + { LD_PCL_TO_SP__LD_TGT3_TO_PC, static_cast( 0x7 & ( opcode >> 3 ) ) }, + NOP } }; // RST TGT3 + case 0x1: + return { { POP_SP_TO_Z, POP_SP_TO_W, { LD_WZ_TO_R16STK, r16stk } } }; // POP R16STK + case 0x5: + return { { IDU_DEC_R16, + { PUSH_MSB_R16STK_TO_SP, r16stk }, + { PUSH_LSB_R16STK_TO_SP, r16stk }, + NOP } }; // PUSH R16STK + } + return { { INVALID } }; +} + + +CoreCpu::MicroOperations_t CoreCpu::decodeCB() { + const auto opcodeSecondByte = mem.read( PC ); + const auto r8 = static_cast( opcodeSecondByte & 0x7 ); + const auto b3index = static_cast( 0x7 & ( opcodeSecondByte >> 3 ) ); + switch( 0x3 & ( opcodeSecondByte >> 6 ) ) { + case 0x0: + switch( 0x7 & opcodeSecondByte ) { + case 0x0: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, LD_RLC_Z_TO_pHL, NOP } }; // RLC pHL + else + return { { FETCH_SECOND_BYTE, { RLC_R8, r8 } } }; // RLC R8 + case 0x1: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, LD_RRC_Z_TO_pHL, NOP } }; // RRC pHL + else + return { { FETCH_SECOND_BYTE, { RRC_R8, r8 } } }; // RRC R8 + case 0x2: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, LD_RL_Z_TO_pHL, NOP } }; // RL pHL + else + return { { FETCH_SECOND_BYTE, { RL_R8, r8 } } }; // RL R8 + case 0x3: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, LD_RR_Z_TO_pHL, NOP } }; // RR pHL + else + return { { FETCH_SECOND_BYTE, { RR_R8, r8 } } }; // RR R8 + case 0x4: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, LD_SLA_Z_TO_pHL, NOP } }; // SLA pHL + else + return { { FETCH_SECOND_BYTE, { SLA_R8, r8 } } }; // SLA R8 + case 0x5: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, LD_SRA_Z_TO_pHL, NOP } }; // SRA pHL + else + return { { FETCH_SECOND_BYTE, { SRA_R8, r8 } } }; // SRA R8 + case 0x6: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, LD_SWAP_Z_TO_pHL, NOP } }; // SWAP pHL + else + return { { FETCH_SECOND_BYTE, { SWAP_R8, r8 } } }; // SRA R8 + case 0x7: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, LD_SRL_Z_TO_pHL, NOP } }; // SRL pHL + else + return { { FETCH_SECOND_BYTE, { SRL_R8, r8 } } }; // SRL R8 + default: + std::unreachable(); + } + + case 0x1: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, { BIT_Z, b3index }, NOP } }; // BIT B3, pHL + else + return { { FETCH_SECOND_BYTE, { BIT_R8, b3index, r8 } } }; // BIT B3, R8 + case 0x2: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, { RES_pHL, b3index }, NOP } }; // RES B3, pHL + else + return { { FETCH_SECOND_BYTE, { RES_R8, b3index, r8 } } }; // RES B3, R8 + case 0x3: + if( isPHL( r8 ) ) + return { { FETCH_SECOND_BYTE, LD_pHL_TO_Z, { SET_pHL, b3index }, NOP } }; // SET B3, pHL + else + return { { FETCH_SECOND_BYTE, { SET_R8, b3index, r8 } } }; // SET B3, R8 + default: + std::unreachable(); + } +} diff --git a/src/core/microcode.cpp b/src/core/microcode.cpp deleted file mode 100644 index 530e865..0000000 --- a/src/core/microcode.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include "core/cpu.hpp" - -// Operand order is target first, source next -CoreCpu::MicroOperations_t CoreCpu::decode( int a ) { - using enum MicroOperationType_t; - // first check instructions without different operand variants - const auto opcode = mem.read( PC++ ); - switch( opcode ) { - //block 0 - case 0x0: - return { { NOP } }; //NOP - case 0x10: - return { { STOP } }; // STOP - case 0x08: - return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_SPL_TO_pWZ, LD_SPH_TO_pWZ, NOP } }; // LD pIMM16, SP - case 0x07: - return { { RLCA } }; - case 0x0F: - return { { RRCA } }; - case 0x17: - return { { RLA } }; - case 0x1F: - return { { RRA } }; - case 0x27: - return { { DAA } }; - case 0x2F: - return { { CPL } }; - case 0x37: - return { { SCF } }; - case 0x3F: - return { { CCF } }; - case 0x18: - return { { LD_IMM_TO_Z, ALU_CALC_RELATIVE_JUMP, IDU_LD_WZ_PLUS_1_TO_PC } }; // JR IMM8 - //block 1 - case 0x76: - return { { HALT } }; // HALT - //block 3 - //arithmetic - case 0xC6: - return { { LD_IMM_TO_Z, ALU_ADD_Z_TO_A } }; // ADD IMM8 - case 0xCE: - return { { LD_IMM_TO_Z, ALU_ADD_Z_AND_C_TO_A } }; // ADC IMM8 - case 0xD6: - return { { LD_IMM_TO_Z, ALU_SUB_Z_FROM_A } }; // SUB IMM8 - case 0xDE: - return { { LD_IMM_TO_Z, ALU_SUB_Z_AND_C_FROM_A } }; // SBC IMM8 - case 0xE6: - return { { LD_IMM_TO_Z, ALU_AND_AZ } }; // AND IMM8 - case 0xEE: - return { { LD_IMM_TO_Z, ALU_XOR_AZ } }; // XOR IMM8 - case 0xF6: - return { { LD_IMM_TO_Z, ALU_OR_AZ } }; // OR IMM8 - case 0xFE: - return { { LD_IMM_TO_Z, ALU_CP_AZ } }; // CP IMM8 - case 0xE8: - return { { LD_IMM_TO_Z, ALU_ADD_SPL_TO_Z, ALU_SPH_PLUS_CADJ_TO_W, LD_WZ_TO_SP } }; // ADD IMM8s - //control flow - case 0xC9: - return { { LD_SPL_TO_Z, LD_SPH_TO_W, LD_WZ_TO_PC, NOP } }; // RET - case 0xD9: - return { { LD_SPL_TO_Z, LD_SPH_TO_W, LD_WZ_TO_PC_AND_ENABLE_IME, NOP } }; // RETI - case 0xC3: - return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_WZ_TO_PC, NOP } }; // JP IMM16 - case 0xE9: - return { { JP_TO_pHL } }; // JP pHL - case 0xCD: - return { { LD_IMM_TO_Z, LD_IMM_TO_W, SP_DEC, LD_PCH_TO_SP, LD_PCL_TO_SP, NOP } }; // CALL IMM16 - //the rest - case 0xCB: - // return decodeCB( opcode ); - case 0xE2: - return { { LD_A_TO_FF00_PLUS_C, NOP } }; //LDH pC, A - case 0xE0: - return { { LD_IMM_TO_Z, LD_A_TO_FF00_PLUS_Z, NOP } }; // LDH IMM8, A - case 0xEA: - return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_A_TO_WZ, NOP } }; // LD pIMM16, A - case 0xF2: - return { { LD_FF00_PLUS_C_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LDH A, pC - case 0xF0: - return { { LD_IMM_TO_Z, LD_FF00_PLUS_Z_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LDH A, IMM8 - case 0xFA: - return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_WZ_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LD A, pIMM16 - case 0xF8: - return { { LD_IMM_TO_Z, ALU_SPL_PLUS_Z_TO_L, ALU_SPH_PLUS_CADJ_TO_H } }; // LD HL, SP+IMM8s - case 0xF9: - return { { LD_HL_TO_SP, NOP } }; // LD SP, HL - case 0xF3: - return { { DI } }; - case 0xFB: - return { { EI } }; - } - - switch( 0x3 & ( opcode >> 6 ) ) { // leave only 2 most significant bits - case 0x0: - return decodeBlock0( opcode ); - case 0x1: { - const auto dest = static_cast( 0x7 & ( opcode >> 3 ) ); - const auto src = static_cast( 0x7 & opcode ); - - return { opcode, OperationType_t::LD, getR8orPHLType( dest ), dest, getR8orPHLType( src ), src }; - } - case 0x2: - return decodeBlock2( opcode ); - case 0x3: - return decodeBlock3( opcode ); - } - return { { INVALID } }; -} - -CoreCpu::MicroOperations_t CoreCpu::decodeBlock0( const uint8_t opcode ) { - using enum MicroOperationType_t; - //count from 0 - const auto operandR8 = static_cast( 0x7 & ( opcode >> 3 ) ); - const auto operandR16 = static_cast( 0x3 & ( opcode >> 4 ) ); - switch( 0x7 & opcode ) { - case 0x0: - // Return longer version ( branch taken ), which can be shorten later - return { { { COND_CHECK_IMM8e, static_cast( 0x3 & ( opcode >> 3 ) ) }, - ALU_CALC_RELATIVE_JUMP, - IDU_LD_WZ_PLUS_1_TO_PC } }; // JR COND, IMM8 - case 0x4: - if( isPHL( operandR8 ) ) - return { { LD_pHL_TO_Z, ALU_LD_Z_PLUS_1_TO_pHL, NOP } }; // INC pHL - else - return { { { INC_R8, operandR8 } } }; // INC R8 - case 0x5: - if( isPHL( operandR8 ) ) - return { { LD_pHL_TO_Z, ALU_LD_Z_MINUS_1_TO_pHL, NOP } }; // DEC pHL - else - return { { { DEC_R8, operandR8 } } }; // DEC R8 - case 0x6: - if( isPHL( operandR8 ) ) - return { { LD_pHL_TO_Z, LD_Z_TO_pHL, NOP } }; // LD pHL, IMM8 - else - return { { LD_IMM_TO_Z, { LD_Z_TO_R8, operandR8 } } }; // LD R8, IMM8 - } - - switch( 0xF & opcode ) { - case 0x1: - return { { LD_IMM_TO_Z, LD_IMM_TO_W, { LD_WZ_TO_R16, operandR16 } } }; // LD R16, IMM16 - case 0x2: - return { { { LD_R16_MEM_TO_A, operandR16 }, NOP } }; // LD R16MEM, A - case 0x3: - return { { { IDU_INC_R16, operandR16 }, NOP } }; // INC R16 - case 0x9: - return { { { ALU_ADD_LSB_R16_TO_L, operandR16 }, - { ALU_ADD_CMSB_R16_TO_H, operandR16 } } }; // ADD HL, R16 - case 0xA: - return { { { LD_R16_MEM_TO_Z, operandR16 }, { LD_Z_TO_R8, Operand_t::a } } }; // LD A, R16MEM - case 0xB: - return { { { IDU_DEC_R16, operandR16 }, NOP } }; // DEC R16 - } - return { { INVALID } }; -} - -CoreCpu::Operation_t CoreCpu::decodeBlock2( const uint8_t opcode ) { - const auto r8 = static_cast( 0x7 & opcode ); - const auto r8Type = getR8orPHLType( r8 ); - auto opType = OperationType_t::INVALID; - switch( 0x7 & ( opcode >> 3 ) ) { - case 0x0: - opType = OperationType_t::ADD; - break; - case 0x1: - opType = OperationType_t::ADC; - break; - case 0x2: - opType = OperationType_t::SUB; - break; - case 0x3: - opType = OperationType_t::SBC; - break; - case 0x4: - opType = OperationType_t::AND; - break; - case 0x5: - opType = OperationType_t::XOR; - break; - case 0x6: - opType = OperationType_t::OR; - break; - case 0x7: - opType = OperationType_t::CP; - } - return { opcode, opType, OperandType_t::R8, Operand_t::a, r8Type, r8 }; -} - - -CoreCpu::Operation_t CoreCpu::decodeBlock3( const uint8_t opcode ) { - const auto condition = static_cast( 0x3 & ( opcode >> 3 ) ); - const auto r16stk = static_cast( 0x3 & ( opcode >> 4 ) ); - switch( 0x7 & opcode ) { - case 0x0: - return { opcode, OperationType_t::RET, OperandType_t::COND, condition }; - case 0x2: - return { opcode, OperationType_t::JP, OperandType_t::COND, condition, OperandType_t::IMM16 }; - case 0x4: - return { opcode, OperationType_t::CALL, OperandType_t::COND, condition, OperandType_t::IMM16 }; - case 0x7: - return { opcode, OperationType_t::RST, OperandType_t::TGT3, - static_cast( 0x7 & ( opcode >> 3 ) ) }; - case 0x1: - return { opcode, OperationType_t::POP, OperandType_t::R16STK, r16stk }; - case 0x5: - return { opcode, OperationType_t::PUSH, OperandType_t::R16STK, r16stk }; - } - return { opcode, OperationType_t::INVALID }; -} - -CoreCpu::MicroOperations_t CoreCpu::decodeCB( const uint8_t opcodeFirstByte ) { - const auto opcodeSecondByte = mem.read( PC++ ); - const auto opcode = static_cast( opcodeFirstByte | opcodeSecondByte << 8 ); - const auto r8 = static_cast( opcodeSecondByte & 0x7 ); - const auto r8Type = getR8orPHLType( r8 ); - const auto b3index = static_cast( 0x7 & ( opcodeSecondByte >> 3 ) ); - switch( 0x3 & ( opcodeSecondByte >> 6 ) ) { - case 0x0: - switch( 0x7 & opcodeSecondByte ) { - case 0x0: - if( isPHL( operandR8 ) ) - return { { NOP, LD_pHL_TO_Z, RLC_Z, NOP } }; // RLC pHL - else - return { { NOP, RLC_R8 } }; // RLC R8 - return { opcode, OperationType_t::RLC, r8Type, r8 }; - case 0x1: - return { opcode, OperationType_t::RRC, r8Type, r8 }; - case 0x2: - return { opcode, OperationType_t::RL, r8Type, r8 }; - case 0x3: - return { opcode, OperationType_t::RR, r8Type, r8 }; - case 0x4: - return { opcode, OperationType_t::SLA, r8Type, r8 }; - case 0x5: - return { opcode, OperationType_t::SRA, r8Type, r8 }; - case 0x6: - return { opcode, OperationType_t::SWAP, r8Type, r8 }; - case 0x7: - return { opcode, OperationType_t::SRL, r8Type, r8 }; - default: - std::unreachable(); - } - - case 0x1: - return { opcode, OperationType_t::BIT, OperandType_t::BIT_INDEX, b3index, r8Type, r8 }; - case 0x2: - return { opcode, OperationType_t::RES, OperandType_t::BIT_INDEX, b3index, r8Type, r8 }; - case 0x3: - return { opcode, OperationType_t::SET, OperandType_t::BIT_INDEX, b3index, r8Type, r8 }; - default: - std::unreachable(); - } -} - - -void CoreCpu::execute( const MicroOperation_t mop ) { - switch( mop.type ) { - using enum MicroOperationType_t; - case NOP: - return; - } -} diff --git a/src/raylib/raylib_parts.cpp b/src/raylib/raylib_parts.cpp index 0fb7194..228ed5a 100644 --- a/src/raylib/raylib_parts.cpp +++ b/src/raylib/raylib_parts.cpp @@ -32,7 +32,7 @@ void RaylibPpu::drawPixel( uint8_t colorId ) { //-------------------------------------------------- void RaylibCpu::handleJoypad() { const auto joypadInputRegister = mem.read( addr::joypadInput ); - const bool selectButtonsFlag = joypadInputRegister & ( 1 << 5 ); + const bool selectButtonsFlag = joypadInputRegister & bit( 5 ); const bool selectDPad = joypadInputRegister & ( 4 << 4 ); // 0 means button pressed diff --git a/src/std_logging/logging.cpp b/src/std_logging/logging.cpp index 2705107..dc6fb55 100644 --- a/src/std_logging/logging.cpp +++ b/src/std_logging/logging.cpp @@ -1,5 +1,4 @@ #include "core/logging.hpp" -#include #include #include -- GitLab From c51f54f21ae3f80a8ccaadcebe65feba08d6da47 Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Fri, 20 Jun 2025 01:48:49 +0200 Subject: [PATCH 3/8] add .clang-tidy --- .clang-tidy | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .clang-tidy diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..28f6c68 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,47 @@ +Checks: 'clang-diagnostic-*,clang-analyzer-*' +Checks: > + clang-diagnostic-*, + clang-analyzer-*, + 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: + - '' + - h + - hh + - hpp + - hxx +ImplementationFileExtensions: + - c + - cc + - cpp + - cxx +HeaderFilterRegex: '' +ExcludeHeaderFilterRegex: '' +FormatStyle: none +CheckOptions: + cert-arr39-c.WarnOnSizeOfCompareToConstant: 'false' + cert-arr39-c.WarnOnSizeOfConstant: 'false' + cert-arr39-c.WarnOnSizeOfIntegerExpression: 'false' + cert-arr39-c.WarnOnSizeOfPointer: 'false' + cert-arr39-c.WarnOnSizeOfPointerToAggregate: 'false' + cert-arr39-c.WarnOnSizeOfThis: 'false' + cert-dcl16-c.NewSuffixes: 'L;LL;LU;LLU' + cert-err33-c.AllowCastToVoid: 'true' + cert-err33-c.CheckedFunctions: '^::aligned_alloc;^::asctime_s;^::at_quick_exit;^::atexit;^::bsearch;^::bsearch_s;^::btowc;^::c16rtomb;^::c32rtomb;^::calloc;^::clock;^::cnd_broadcast;^::cnd_init;^::cnd_signal;^::cnd_timedwait;^::cnd_wait;^::ctime_s;^::fclose;^::fflush;^::fgetc;^::fgetpos;^::fgets;^::fgetwc;^::fopen;^::fopen_s;^::fprintf;^::fprintf_s;^::fputc;^::fputs;^::fputwc;^::fputws;^::fread;^::freopen;^::freopen_s;^::fscanf;^::fscanf_s;^::fseek;^::fsetpos;^::ftell;^::fwprintf;^::fwprintf_s;^::fwrite;^::fwscanf;^::fwscanf_s;^::getc;^::getchar;^::getenv;^::getenv_s;^::gets_s;^::getwc;^::getwchar;^::gmtime;^::gmtime_s;^::localtime;^::localtime_s;^::malloc;^::mbrtoc16;^::mbrtoc32;^::mbsrtowcs;^::mbsrtowcs_s;^::mbstowcs;^::mbstowcs_s;^::memchr;^::mktime;^::mtx_init;^::mtx_lock;^::mtx_timedlock;^::mtx_trylock;^::mtx_unlock;^::printf_s;^::putc;^::putwc;^::raise;^::realloc;^::remove;^::rename;^::scanf;^::scanf_s;^::setlocale;^::setvbuf;^::signal;^::snprintf;^::snprintf_s;^::sprintf;^::sprintf_s;^::sscanf;^::sscanf_s;^::strchr;^::strerror_s;^::strftime;^::strpbrk;^::strrchr;^::strstr;^::strtod;^::strtof;^::strtoimax;^::strtok;^::strtok_s;^::strtol;^::strtold;^::strtoll;^::strtoul;^::strtoull;^::strtoumax;^::strxfrm;^::swprintf;^::swprintf_s;^::swscanf;^::swscanf_s;^::thrd_create;^::thrd_detach;^::thrd_join;^::thrd_sleep;^::time;^::timespec_get;^::tmpfile;^::tmpfile_s;^::tmpnam;^::tmpnam_s;^::tss_create;^::tss_get;^::tss_set;^::ungetc;^::ungetwc;^::vfprintf;^::vfprintf_s;^::vfscanf;^::vfscanf_s;^::vfwprintf;^::vfwprintf_s;^::vfwscanf;^::vfwscanf_s;^::vprintf_s;^::vscanf;^::vscanf_s;^::vsnprintf;^::vsnprintf_s;^::vsprintf;^::vsprintf_s;^::vsscanf;^::vsscanf_s;^::vswprintf;^::vswprintf_s;^::vswscanf;^::vswscanf_s;^::vwprintf_s;^::vwscanf;^::vwscanf_s;^::wcrtomb;^::wcschr;^::wcsftime;^::wcspbrk;^::wcsrchr;^::wcsrtombs;^::wcsrtombs_s;^::wcsstr;^::wcstod;^::wcstof;^::wcstoimax;^::wcstok;^::wcstok_s;^::wcstol;^::wcstold;^::wcstoll;^::wcstombs;^::wcstombs_s;^::wcstoul;^::wcstoull;^::wcstoumax;^::wcsxfrm;^::wctob;^::wctrans;^::wctype;^::wmemchr;^::wprintf_s;^::wscanf;^::wscanf_s;' + cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: 'false' + cert-str34-c.DiagnoseSignedUnsignedCharComparisons: 'false' + cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: 'true' + google-readability-braces-around-statements.ShortStatementLines: '1' + google-readability-function-size.StatementThreshold: '800' + google-readability-namespace-comments.ShortNamespaceLines: '10' + google-readability-namespace-comments.SpacesBeforeComments: '2' + llvm-else-after-return.WarnOnConditionVariables: 'false' + llvm-else-after-return.WarnOnUnfixable: 'false' + llvm-qualified-auto.AddConstToQualified: 'false' +SystemHeaders: false -- GitLab From c49781a79f5c6263e5f372906b2e0867396bfd39 Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Thu, 19 Jun 2025 23:21:39 +0200 Subject: [PATCH 4/8] microcode execution 1/2 --- include/core/core_utils.hpp | 9 + include/core/cpu.hpp | 15 ++ src/core/cpu.cpp | 58 +++++ src/core/cpu_execute.cpp | 425 ++++++++++++++++++++++++++++++++++++ 4 files changed, 507 insertions(+) create mode 100644 include/core/core_utils.hpp create mode 100644 src/core/cpu_execute.cpp diff --git a/include/core/core_utils.hpp b/include/core/core_utils.hpp new file mode 100644 index 0000000..7e7ead8 --- /dev/null +++ b/include/core/core_utils.hpp @@ -0,0 +1,9 @@ +#pragma once +#include + +uint8_t lsb( std::same_as auto value ) { + return value & 0xFF; +} +uint8_t msb( std::same_as auto value ) { + return value & 0xFF00; +} diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index 4867fcc..003dae9 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -267,10 +267,22 @@ protected: Operation_t operation = Operation_t( 0x0, OperationType_t::NOP ); Memory& mem; bool interruptMasterEnabled = false; + bool enableIMELater = false; bool halted = false; + bool lastConditionCheck = false; public: static consteval size_t getOperandVarType( CoreCpu::OperandType_t operandType ); + + uint8_t readR8( Operand_t opd ); + uint16_t readR16( Operand_t opd ); + + void writeR8( Operand_t opd, uint8_t value ); + + void addToR8( Operand_t operand, uint8_t value ); + uint8_t addU8ToU8( uint8_t value, uint8_t value2 ); + void subFromR8( Operand_t operand, uint8_t value, bool discard = false ); + template uint8or16_t auto read( const OperandVar_t operand ); template @@ -301,6 +313,9 @@ public: MicroOperations_t decodeBlock3( const uint8_t opcode ); MicroOperations_t decodeCB(); // clang-format off + uint16_t getWZ() { return static_cast( ( W << 8 ) | Z ); } + void setWZ( uint16_t value ) { Z = value & 0xF; W = uint8_t( value >> 8 ); } + 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 diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index 83ea0da..6c246bb 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -7,6 +7,64 @@ #include #include +// Decoding and execution have separate files + +uint8_t CoreCpu::addU8ToU8( uint8_t value, uint8_t value2 ) { + bool cFlag = value + value2 > std::numeric_limits::max(); + bool halfCarryFlag = ( ( value & 0xF ) + ( value2 & 0xF ) ) > 0xF; + + const uint8_t result = value + value2; + setZNHCFlags( ! result, false, halfCarryFlag, cFlag ); + return result; +}; + +void CoreCpu::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; + + currentValue += value; + writeR8( operand, currentValue ); + setZNHCFlags( ! currentValue, false, halfCarryFlag, cFlag ); +}; + +void CoreCpu::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 + bool halfCarryFlag = ( ( currentValue & 0xF ) - ( value & 0xF ) ) < 0; + + const uint8_t newValue = currentValue - value; + if( ! discard ) + writeR8( operand, newValue ); + setZNHCFlags( ! newValue, true, halfCarryFlag, cFlag ); +}; + +uint8_t CoreCpu::readR8( Operand_t opd ) { + if( opd == Operand_t::a ) { + const int aIndex = 6; + return registers[aIndex]; + } + return registers[std::to_underlying( opd )]; +} + +void CoreCpu::writeR8( Operand_t opd, uint8_t value ) { + if( opd == Operand_t::a ) { + constexpr int aIndex = 6; + registers[aIndex] = value; + } else + registers[std::to_underlying( opd )] = value; +} + +uint16_t CoreCpu::readR16( Operand_t opd ) { + if( opd == Operand_t::sp ) + return SP; + else + return static_cast( registers[std::to_underlying( opd ) * 2] << 8 | + ( registers[std::to_underlying( opd ) * 2 + 1] ) ); +} + + #define invalidOperandType( OPERAND ) \ do { \ logFatal( ErrorCode::InvalidOperandType, std::format( "Invalid operand " #OPERAND " with value: {0}", \ diff --git a/src/core/cpu_execute.cpp b/src/core/cpu_execute.cpp new file mode 100644 index 0000000..5ea5be4 --- /dev/null +++ b/src/core/cpu_execute.cpp @@ -0,0 +1,425 @@ +#include "core/core_utils.hpp" +#include "core/cpu.hpp" +#include "core/logging.hpp" +#include + +void CoreCpu::execute( MicroOperation_t mop ) { + switch( mop.type ) { + using enum MicroOperationType_t; + case NOP: + return; + case STOP: + //TODO + return; + case LD_IMM_TO_Z: + Z = mem.read( PC++ ); + break; + case LD_IMM_TO_W: + W = mem.read( PC++ ); + break; + case LD_SPL_TO_pWZ: + mem.write( getWZ(), lsb( SP ) ); + setWZ( getWZ() + 1 ); + break; + case LD_SPH_TO_pWZ: + mem.write( getWZ(), uint8_t( SP >> 8 ) ); + break; + case RLCA: { + const uint8_t value = readR8( Operand_t::a ); + const bool cFlag = value & bit( 7 ); + + const uint8_t newValue = std::rotl( value, 1 ); + writeR8( Operand_t::a, newValue ); + setZNHCFlags( 0, 0, 0, cFlag ); + } break; + case RRCA: { + const uint8_t value = readR8( Operand_t::a ); + const bool cFlag = value & 0x1; + + const uint8_t newValue = std::rotr( value, 1 ); + writeR8( Operand_t::a, newValue ); + setZNHCFlags( 0, 0, 0, cFlag ); + } break; + case RLA: { + const uint8_t value = readR8( Operand_t::a ); + const bool cFlag = value & bit( 7 ); + + const uint8_t newValue = static_cast( ( value << 1 ) | getCFlag() ); + writeR8( Operand_t::a, newValue ); + setZNHCFlags( 0, 0, 0, cFlag ); + } break; + case RRA: { + const uint8_t value = readR8( Operand_t::a ); + const bool cFlag = value & 0x1; + + const uint8_t newValue = static_cast( ( getCFlag() << 7 ) | ( value >> 1 ) ); + writeR8( Operand_t::a, newValue ); + setZNHCFlags( 0, 0, 0, cFlag ); + } break; + case DAA: { + const auto registerA = readR8( Operand_t::a ); + uint8_t adjustment = 0; + bool cFlag = getCFlag(); + if( getNFlag() ) { + if( getHFlag() ) + adjustment += 6; + if( getCFlag() ) + adjustment += 0x60; + writeR8( Operand_t::a, registerA - adjustment ); + } else { + if( getHFlag() || ( registerA & 0xF ) > 9 ) + adjustment += 6; + if( getCFlag() || registerA > 0x99 ) { + adjustment += 0x60; + cFlag = true; + } + writeR8( Operand_t::a, registerA + adjustment ); + } + setZNHCFlags( ! readR8( Operand_t::a ), getNFlag(), 0, cFlag ); + } break; + case CPL: + writeR8( Operand_t::a, ~readR8( Operand_t::a ) ); + setNFlag( true ); + setHFlag( true ); + break; + case SCF: + setZNHCFlags( getZFlag(), 0, 0, 1 ); + break; + case CCF: + setZNHCFlags( getZFlag(), 0, 0, ! getCFlag() ); + break; + case ALU_CALC_RELATIVE_JUMP: { + const uint16_t tmp = static_cast( PC + int8_t( Z ) ); + Z = lsb( tmp ); + W = msb( tmp ); + } break; + case IDU_LD_WZ_PLUS_1_TO_PC: + PC = getWZ() + 1; + break; + case HALT: { + //interruptMasterEnabled = false; // GBCTR says so, but does it make sense? + halted = true; + // TODO halt bug + } break; + case ALU_ADD_Z_TO_A: + addToR8( Operand_t::a, Z ); + break; + case ALU_ADD_Z_AND_C_TO_A: + addToR8( Operand_t::a, Z + getCFlag() ); + break; + case ALU_SUB_Z_FROM_A: + subFromR8( Operand_t::a, Z ); + break; + case ALU_SUB_Z_AND_C_FROM_A: + subFromR8( Operand_t::a, Z + getCFlag() ); + break; + case ALU_A_AND_Z: { + const uint8_t result = readR8( Operand_t::a ) & Z; + writeR8( Operand_t::a, result ); + setZNHCFlags( ! result, 0, 1, 0 ); + } break; + case ALU_A_XOR_Z: { + const uint8_t result = readR8( Operand_t::a ) ^ Z; + writeR8( Operand_t::a, result ); + setZNHCFlags( ! result, 0, 0, 0 ); + } break; + case ALU_A_OR_Z: { + const uint8_t result = readR8( Operand_t::a ) | Z; + writeR8( Operand_t::a, result ); + setZNHCFlags( ! result, 0, 0, 0 ); + } break; + case ALU_A_CP_Z: + subFromR8( Operand_t::a, Z, true ); + break; + case ALU_ADD_SPL_TO_Z: + //TODO ADJ + Z = addU8ToU8( lsb( SP ), Z ); + break; + case ALU_SPH_ADC_ADJ_TO_W: + // TODO + setZFlag( 0 ); + setNFlag( 0 ); + break; + case LD_WZ_TO_SP: + SP = getWZ(); + break; + case POP_SP_TO_Z: + Z = mem.read( SP++ ); + break; + case POP_SP_TO_W: + W = mem.read( SP++ ); + break; + case LD_WZ_TO_PC: + PC = getWZ(); + break; + case LD_WZ_TO_PC__ENABLE_IME: + PC = getWZ(); + interruptMasterEnabled = true; + break; + case JP_TO_HL: + PC = readR16( Operand_t::hl ); + break; + case SP_DEC: + SP--; + break; + case LD_PCH_TO_SP: + mem.write( SP--, msb( PC ) ); + break; + case LD_PCL_TO_SP: + mem.write( SP, lsb( PC ) ); + PC = getWZ(); + break; + case LD_A_TO_FF00_PLUS_C: + mem.write( 0xFF00 | readR8( Operand_t::c ), readR8( Operand_t::a ) ); + break; + case LD_A_TO_FF00_PLUS_Z: + mem.write( 0xFF00 | Z, readR8( Operand_t::a ) ); + break; + case LD_A_TO_pWZ: + mem.write( getWZ(), readR8( Operand_t::a ) ); + break; + case LD_FF00_PLUS_C_TO_Z: + Z = mem.read( 0xFF00 | readR8( Operand_t::c ) ); + break; + case LD_Z_TO_R8: + writeR8( mop.operand1, Z ); + break; + case LD_FF00_PLUS_Z_TO_Z: + Z = mem.read( 0xFF00 + Z ); + break; + case LD_pWZ_TO_Z: + Z = mem.read( getWZ() ); + break; + case ALU_SPL_PLUS_Z_TO_LHL: + writeR8( Operand_t::l, addU8ToU8( lsb( SP ), Z ) ); + //TODO ADJ + break; + case ALU_SPH_ADC_ADJ_TO_HHL: + //TODO + break; + case LD_HL_TO_SP: + SP = readR16( Operand_t::hl ); + break; + case DI: + interruptMasterEnabled = false; + break; + case EI: + enableIMELater = true; + break; + case COND_CHECK__LD_IMM_TO_Z: + lastConditionCheck = isConditionMet( mop.operand1 ); + Z = mem.read( PC++ ); + break; + case INC_R8: + break; + case LD_pHL_TO_Z: + break; + case ALU_LD_Z_PLUS_1_TO_pHL: + break; + case ALU_LD_Z_MINUS_1_TO_pHL: + break; + case DEC_R8: + break; + case LD_Z_TO_pHL: + break; + case LD_WZ_TO_R16: + break; + case LD_R16_MEM_TO_A: + break; + case IDU_INC_R16: + break; + case ALU_ADD_LSB_R16_TO_L: + break; + case ALU_ADD_CMSB_R16_TO_H: + break; + case LD_R16_MEM_TO_Z: + break; + case IDU_DEC_R16: + break; + case LD_R8_TO_R8: + break; + case LD_R8_TO_pHL: + break; + case ALU_ADD_R8_TO_A: + break; + case ALU_ADC_Z_TO_A: + break; + case ALU_ADC_R8_TO_A: + break; + case ALU_SUB_R8_FROM_A: + break; + case ALU_SBC_Z_FROM_A: + break; + case ALU_SBC_R8_FROM_A: + break; + case ALU_A_AND_R8: + break; + case ALU_A_XOR_R8: + break; + case ALU_A_OR_R8: + break; + case ALU_CP_A_R8: + break; + case CHECK_COND: + break; + case COND_CHECK__LD_IMM_TO_W: + break; + case LD_PCL_TO_SP__LD_WZ_TO_PC: + break; + case LD_PCL_TO_SP__LD_TGT3_TO_PC: + break; + case LD_WZ_TO_R16STK: + break; + case PUSH_MSB_R16STK_TO_SP: + break; + case PUSH_LSB_R16STK_TO_SP: + break; + case FETCH_SECOND_BYTE: + PC++; + break; + case LD_RLC_Z_TO_pHL: { + const bool cFlag = Z & bit( 7 ); + Z = std::rotl( Z, 1 ); + mem.write( readR16( Operand_t::hl ), Z ); + setZNHCFlags( ! Z, 0, 0, cFlag ); + } break; + case RLC_R8: { + const uint8_t value = readR8( mop.operand1 ); + const bool cFlag = value & bit( 7 ); + const uint8_t newValue = std::rotl( value, 1 ); + writeR8( mop.operand1, newValue ); + setZNHCFlags( ! newValue, 0, 0, cFlag ); + } break; + case LD_RRC_Z_TO_pHL: { + const bool cFlag = Z & 0x1; + Z = std::rotr( Z, 1 ); + mem.write( readR16( Operand_t::hl ), Z ); + setZNHCFlags( ! Z, 0, 0, cFlag ); + } break; + case RRC_R8: { + const uint8_t value = readR8( mop.operand1 ); + const bool cFlag = value & 0x1; + const uint8_t newValue = std::rotr( value, 1 ); + writeR8( mop.operand1, newValue ); + setZNHCFlags( ! newValue, 0, 0, cFlag ); + } break; + case LD_RL_Z_TO_pHL: { + const bool cFlag = Z & bit( 7 ); + Z = static_cast( ( Z << 1 ) | getCFlag() ); + mem.write( readR16( Operand_t::hl ), Z ); + setZNHCFlags( ! Z, 0, 0, cFlag ); + } break; + case RL_R8: { + const uint8_t value = readR8( mop.operand1 ); + const bool cFlag = value & bit( 7 ); + const uint8_t newValue = static_cast( ( value << 1 ) | getCFlag() ); + writeR8( mop.operand1, newValue ); + setZNHCFlags( ! newValue, 0, 0, cFlag ); + } break; + case LD_RR_Z_TO_pHL: { + const bool cFlag = Z & 0x1; + Z = static_cast( ( getCFlag() << 7 ) | ( Z >> 1 ) ); + mem.write( readR16( Operand_t::hl ), Z ); + setZNHCFlags( ! Z, 0, 0, cFlag ); + } break; + case RR_R8: { + const uint8_t value = readR8( mop.operand1 ); + const bool cFlag = value & 0x1; + const uint8_t newValue = static_cast( ( getCFlag() << 7 ) | ( value >> 1 ) ); + writeR8( mop.operand1, newValue ); + setZNHCFlags( ! newValue, 0, 0, cFlag ); + } break; + case LD_SLA_Z_TO_pHL: { + const bool cFlag = Z & bit( 7 ); + Z = Z << 1; + mem.write( readR16( Operand_t::hl ), Z ); + setZNHCFlags( ! Z, 0, 0, cFlag ); + } break; + case SLA_R8: { + const uint8_t value = readR8( mop.operand1 ); + const bool cFlag = value & bit( 7 ); + const uint8_t newValue = value << 1; + writeR8( mop.operand1, newValue ); + setZNHCFlags( ! newValue, 0, 0, cFlag ); + } break; + case LD_SRA_Z_TO_pHL: { + const bool cFlag = Z & 0x1; + Z = static_cast( ( Z & bit( 7 ) ) | ( Z >> 1 ) ); + mem.write( readR16( Operand_t::hl ), Z ); + setZNHCFlags( ! Z, 0, 0, cFlag ); + } break; + case SRA_R8: { + const uint8_t value = readR8( mop.operand1 ); + const bool cFlag = value & 0x1; + const uint8_t newValue = static_cast( ( value & bit( 7 ) ) | ( value >> 1 ) ); + writeR8( mop.operand1, newValue ); + setZNHCFlags( ! newValue, 0, 0, cFlag ); + } break; + case LD_SWAP_Z_TO_pHL: + Z = static_cast( ( Z << 4 ) | ( Z >> 4 ) ); + mem.write( readR16( Operand_t::hl ), Z ); + setZNHCFlags( ! Z, 0, 0, 0 ); + break; + case SWAP_R8: { + const uint8_t value = readR8( mop.operand1 ); + const uint8_t newValue = static_cast( ( value << 4 ) | ( value >> 4 ) ); + writeR8( mop.operand1, newValue ); + setZNHCFlags( ! newValue, 0, 0, 0 ); + } break; + case LD_SRL_Z_TO_pHL: { + const bool cFlag = Z & 0x1; + Z = Z >> 1; + mem.write( readR16( Operand_t::hl ), Z ); + setZNHCFlags( ! Z, 0, 0, cFlag ); + } break; + case SRL_R8: { + const uint8_t value = readR8( mop.operand1 ); + const bool cFlag = value & 0x1; + const uint8_t newValue = value >> 1; + writeR8( mop.operand1, newValue ); + setZNHCFlags( ! newValue, 0, 0, cFlag ); + } break; + case BIT_Z: { + const bool bitSet = Z & ( 1 << std::to_underlying( mop.operand1 ) ); + setZNHCFlags( ! bitSet, 0, 1, getCFlag() ); + } break; + case BIT_R8: { + const uint8_t value = readR8( mop.operand2 ); + const bool bitSet = value & ( 1 << std::to_underlying( mop.operand1 ) ); + setZNHCFlags( ! bitSet, 0, 1, getCFlag() ); + } break; + case RES_pHL: { + const uint16_t addr = readR16( Operand_t::hl ); + const uint8_t value = mem.read( addr ); + const uint8_t newValue = value & static_cast( ~( 1 << std::to_underlying( mop.operand1 ) ) ); + mem.write( addr, newValue ); + } break; + case RES_R8: { + const uint8_t value = readR8( mop.operand2 ); + const uint8_t newValue = value & static_cast( ~( 1 << std::to_underlying( mop.operand1 ) ) ); + writeR8( mop.operand2, newValue ); + } break; + case SET_pHL: { + const uint16_t addr = readR16( Operand_t::hl ); + const uint8_t value = mem.read( addr ); + const uint8_t newValue = value | static_cast( 1 << std::to_underlying( mop.operand1 ) ); + mem.write( addr, newValue ); + } break; + case SET_R8: { + const uint8_t value = readR8( mop.operand2 ); + const uint8_t newValue = value | static_cast( 1 << std::to_underlying( mop.operand1 ) ); + writeR8( mop.operand2, newValue ); + } break; + case INVALID: + break; + case EMPTY: + logFatal( ErrorCode::emptyMicroCodeExecuted, "Empty microcode executed! ( not NOP )" ); + logStacktrace(); + std::abort(); + default: + logFatal( ErrorCode::InvalidOperandType, + std::format( "Unknown microcode executed, value: {0}", std::to_underlying( mop.type ) ) ); + logStacktrace(); + std::abort(); + } +} -- GitLab From 74b7e3de7d8f9de0315c6e8dc052991029c1a67a Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Fri, 20 Jun 2025 00:18:19 +0200 Subject: [PATCH 5/8] microcode execution 2/2 --- include/core/core_utils.hpp | 5 +- include/core/cpu.hpp | 14 +-- include/core/logging.hpp | 6 +- src/core/cpu.cpp | 31 +++---- src/core/cpu_decode.cpp | 10 +- src/core/cpu_execute.cpp | 180 ++++++++++++++++++++++++------------ src/raylib/raylib_parts.cpp | 4 +- 7 files changed, 155 insertions(+), 95 deletions(-) diff --git a/include/core/core_utils.hpp b/include/core/core_utils.hpp index 7e7ead8..9c694bd 100644 --- a/include/core/core_utils.hpp +++ b/include/core/core_utils.hpp @@ -1,9 +1,10 @@ #pragma once +#include #include uint8_t lsb( std::same_as auto value ) { - return value & 0xFF; + return static_cast( value & 0xFF ); } uint8_t msb( std::same_as auto value ) { - return value & 0xFF00; + return static_cast( ( value >> 8 ) & 0xFF ); } diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index 003dae9..df45845 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -30,7 +30,7 @@ public: IDU_LD_WZ_PLUS_1_TO_PC, HALT, ALU_ADD_Z_TO_A, - ALU_ADD_Z_AND_C_TO_A, + ALU_ADC_Z_TO_A, ALU_SUB_Z_FROM_A, ALU_SUB_Z_AND_C_FROM_A, ALU_A_AND_Z, @@ -56,8 +56,8 @@ public: LD_Z_TO_R8, LD_FF00_PLUS_Z_TO_Z, LD_pWZ_TO_Z, - ALU_SPL_PLUS_Z_TO_LHL, - ALU_SPH_ADC_ADJ_TO_HHL, + ALU_SPL_PLUS_Z_TO_L, + ALU_SPH_ADC_ADJ_TO_H, LD_HL_TO_SP, DI, EI, @@ -69,16 +69,15 @@ public: DEC_R8, LD_Z_TO_pHL, LD_WZ_TO_R16, - LD_R16_MEM_TO_A, + LD_A_TO_R16_MEM, IDU_INC_R16, ALU_ADD_LSB_R16_TO_L, - ALU_ADD_CMSB_R16_TO_H, + ALU_ADC_MSB_R16_TO_H, LD_R16_MEM_TO_Z, IDU_DEC_R16, LD_R8_TO_R8, LD_R8_TO_pHL, ALU_ADD_R8_TO_A, - ALU_ADC_Z_TO_A, ALU_ADC_R8_TO_A, ALU_SUB_R8_FROM_A, ALU_SBC_Z_FROM_A, @@ -278,9 +277,10 @@ public: uint16_t readR16( Operand_t opd ); void writeR8( Operand_t opd, uint8_t value ); + void writeR16( Operand_t opd, uint16_t value ); - void addToR8( Operand_t operand, uint8_t value ); uint8_t addU8ToU8( uint8_t value, uint8_t value2 ); + void addToR8( Operand_t operand, uint8_t value ); void subFromR8( Operand_t operand, uint8_t value, bool discard = false ); template diff --git a/include/core/logging.hpp b/include/core/logging.hpp index 7434464..7040b67 100644 --- a/include/core/logging.hpp +++ b/include/core/logging.hpp @@ -15,9 +15,9 @@ enum CoreError : int { // Memory errors (200-399) // CPU errors (400-599) - InvalidOperationType = 400, - InvalidOperandType = 401, - InvalidOperand = 402, + unknownMicroCodeExecuted = 400, + emptyMicroCodeExecuted = 402, + InvalidOperand = 405, CPUHardLocked = 410, StackOverflow = 420, StackUnderflow = 421, diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index 6c246bb..a502507 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -64,15 +64,16 @@ uint16_t CoreCpu::readR16( Operand_t opd ) { ( registers[std::to_underlying( opd ) * 2 + 1] ) ); } +void CoreCpu::writeR16( Operand_t opd, uint16_t value ) { + if( opd == Operand_t::sp ) + SP = value; + else { + registers[std::to_underlying( opd ) * 2] = static_cast( value >> 8 ); + registers[std::to_underlying( opd ) * 2 + 1] = static_cast( value ); + } +} -#define invalidOperandType( OPERAND ) \ - do { \ - logFatal( ErrorCode::InvalidOperandType, std::format( "Invalid operand " #OPERAND " with value: {0}", \ - static_cast( OPERAND ) ) ); \ - logStacktrace(); \ - std::abort(); \ - } while( false ) - +#define invalidOperandType( OPERAND ) void( 0 ) consteval size_t CoreCpu::getOperandVarType( CoreCpu::OperandType_t operandType ) { switch( operandType ) { @@ -797,7 +798,6 @@ unsigned CoreCpu::execute( const Operation_t& op ) { const auto interruptPending = ( mem.read( addr::interruptEnableRegister ) & 0x1F ) & ( mem.read( addr::interruptFlag ) & 0x1F ); if( ! interruptMasterEnabled && interruptPending ) { - // TODO halt bug } else halted = true; } break; @@ -829,15 +829,14 @@ unsigned CoreCpu::execute( const Operation_t& op ) { break; //Invalid and unknown instructions case OT::INVALID: - logFatal( ErrorCode::InvalidOperationType, - std::format( "Known invalid operation type made it to execute stage, value: {0}", - static_cast( op.operationType ) ) ); + logFatal( 0, std::format( "Known invalid operation type made it to execute stage, value: {0}", + static_cast( op.operationType ) ) ); logStacktrace(); std::abort(); break; default: - logFatal( ErrorCode::InvalidOperationType, std::format( "Unknown operation type, value: {0}", - static_cast( op.operationType ) ) ); + logFatal( 0, std::format( "Unknown operation type, value: {0}", + static_cast( op.operationType ) ) ); logStacktrace(); std::abort(); } @@ -881,9 +880,7 @@ unsigned CoreCpu::handleInterrupts() { }; unsigned CoreCpu::tick() { - auto cycles = execute( decode() ); - cycles += handleInterrupts(); - return cycles; + return 0; } void CoreCpu::logOperation( Operation_t op, [[maybe_unused]] unsigned cycles ) { diff --git a/src/core/cpu_decode.cpp b/src/core/cpu_decode.cpp index df58336..a164401 100644 --- a/src/core/cpu_decode.cpp +++ b/src/core/cpu_decode.cpp @@ -43,11 +43,11 @@ CoreCpu::MicroOperations_t CoreCpu::decode() { case 0xC6: return { { LD_IMM_TO_Z, ALU_ADD_Z_TO_A } }; // ADD IMM8 case 0xCE: - return { { LD_IMM_TO_Z, ALU_ADD_Z_AND_C_TO_A } }; // ADC IMM8 + return { { LD_IMM_TO_Z, ALU_ADC_Z_TO_A } }; // ADC IMM8 case 0xD6: return { { LD_IMM_TO_Z, ALU_SUB_Z_FROM_A } }; // SUB IMM8 case 0xDE: - return { { LD_IMM_TO_Z, ALU_SUB_Z_AND_C_FROM_A } }; // SBC IMM8 + return { { LD_IMM_TO_Z, ALU_SBC_Z_FROM_A } }; // SBC IMM8 case 0xE6: return { { LD_IMM_TO_Z, ALU_A_AND_Z } }; // AND IMM8 case 0xEE: @@ -85,7 +85,7 @@ CoreCpu::MicroOperations_t CoreCpu::decode() { case 0xFA: return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_pWZ_TO_Z, { LD_Z_TO_R8, Operand_t::a } } }; // LD A, pIMM16 case 0xF8: - return { { LD_IMM_TO_Z, ALU_SPL_PLUS_Z_TO_LHL, ALU_SPH_ADC_ADJ_TO_HHL } }; // LD HL, SP+IMM8s + return { { LD_IMM_TO_Z, ALU_SPL_PLUS_Z_TO_L, ALU_SPH_ADC_ADJ_TO_H } }; // LD HL, SP+IMM8s case 0xF9: return { { LD_HL_TO_SP, NOP } }; // LD SP, HL case 0xF3: @@ -150,11 +150,11 @@ CoreCpu::MicroOperations_t CoreCpu::decodeBlock0( const uint8_t opcode ) { case 0x1: return { { LD_IMM_TO_Z, LD_IMM_TO_W, { LD_WZ_TO_R16, r16 } } }; // LD R16, IMM16 case 0x2: - return { { { LD_R16_MEM_TO_A, r16 }, NOP } }; // LD R16MEM, A + return { { { LD_A_TO_R16_MEM, r16 }, NOP } }; // LD R16MEM, A case 0x3: return { { { IDU_INC_R16, r16 }, NOP } }; // INC R16 case 0x9: - return { { { ALU_ADD_LSB_R16_TO_L, r16 }, { ALU_ADD_CMSB_R16_TO_H, r16 } } }; // ADD HL, R16 + return { { { ALU_ADD_LSB_R16_TO_L, r16 }, { ALU_ADC_MSB_R16_TO_H, r16 } } }; // ADD HL, R16 case 0xA: return { { { LD_R16_MEM_TO_Z, r16 }, { LD_Z_TO_R8, Operand_t::a } } }; // LD A, R16MEM case 0xB: diff --git a/src/core/cpu_execute.cpp b/src/core/cpu_execute.cpp index 5ea5be4..1a02a2a 100644 --- a/src/core/cpu_execute.cpp +++ b/src/core/cpu_execute.cpp @@ -7,7 +7,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { switch( mop.type ) { using enum MicroOperationType_t; case NOP: - return; + [[likely]] return; case STOP: //TODO return; @@ -26,7 +26,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { break; case RLCA: { const uint8_t value = readR8( Operand_t::a ); - const bool cFlag = value & bit( 7 ); + const bool cFlag = value & ( 1 << 7 ); const uint8_t newValue = std::rotl( value, 1 ); writeR8( Operand_t::a, newValue ); @@ -42,7 +42,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { } break; case RLA: { const uint8_t value = readR8( Operand_t::a ); - const bool cFlag = value & bit( 7 ); + const bool cFlag = value & ( 1 << 7 ); const uint8_t newValue = static_cast( ( value << 1 ) | getCFlag() ); writeR8( Operand_t::a, newValue ); @@ -104,14 +104,14 @@ void CoreCpu::execute( MicroOperation_t mop ) { case ALU_ADD_Z_TO_A: addToR8( Operand_t::a, Z ); break; - case ALU_ADD_Z_AND_C_TO_A: + case ALU_ADC_Z_TO_A: addToR8( Operand_t::a, Z + getCFlag() ); break; case ALU_SUB_Z_FROM_A: subFromR8( Operand_t::a, Z ); break; - case ALU_SUB_Z_AND_C_FROM_A: - subFromR8( Operand_t::a, Z + getCFlag() ); + case ALU_SBC_Z_FROM_A: + subFromR8( Operand_t::a, getCFlag() + Z ); break; case ALU_A_AND_Z: { const uint8_t result = readR8( Operand_t::a ) & Z; @@ -132,14 +132,14 @@ void CoreCpu::execute( MicroOperation_t mop ) { subFromR8( Operand_t::a, Z, true ); break; case ALU_ADD_SPL_TO_Z: - //TODO ADJ Z = addU8ToU8( lsb( SP ), Z ); break; - case ALU_SPH_ADC_ADJ_TO_W: - // TODO + case ALU_SPH_ADC_ADJ_TO_W: { + const uint8_t adjustment = Z & ( 1 << 7 ) ? 0xFF : 0; + W = addU8ToU8( msb( SP ), getCFlag() + adjustment ); setZFlag( 0 ); setNFlag( 0 ); - break; + } break; case LD_WZ_TO_SP: SP = getWZ(); break; @@ -163,7 +163,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { SP--; break; case LD_PCH_TO_SP: - mem.write( SP--, msb( PC ) ); + mem.write( SP--, msb( PC ) ); // go to next byte's address break; case LD_PCL_TO_SP: mem.write( SP, lsb( PC ) ); @@ -190,13 +190,15 @@ void CoreCpu::execute( MicroOperation_t mop ) { case LD_pWZ_TO_Z: Z = mem.read( getWZ() ); break; - case ALU_SPL_PLUS_Z_TO_LHL: + case ALU_SPL_PLUS_Z_TO_L: writeR8( Operand_t::l, addU8ToU8( lsb( SP ), Z ) ); - //TODO ADJ - break; - case ALU_SPH_ADC_ADJ_TO_HHL: - //TODO break; + case ALU_SPH_ADC_ADJ_TO_H: { + const uint8_t adjustment = Z & ( 1 << 7 ) ? 0xFF : 0; + writeR8( Operand_t::h, addU8ToU8( msb( SP ), getCFlag() + adjustment ) ); + setZFlag( 0 ); + setNFlag( 0 ); + } break; case LD_HL_TO_SP: SP = readR16( Operand_t::hl ); break; @@ -210,82 +212,141 @@ void CoreCpu::execute( MicroOperation_t mop ) { lastConditionCheck = isConditionMet( mop.operand1 ); Z = mem.read( PC++ ); break; - case INC_R8: - break; + case INC_R8: { + const bool cFlag = getCFlag(); + addToR8( mop.operand1, 1 ); + setCFlag( cFlag ); + } break; case LD_pHL_TO_Z: + Z = mem.read( readR16( Operand_t::hl ) ); break; case ALU_LD_Z_PLUS_1_TO_pHL: + mem.write( readR16( Operand_t::hl ), --Z ); break; case ALU_LD_Z_MINUS_1_TO_pHL: + mem.write( readR16( Operand_t::hl ), --Z ); break; - case DEC_R8: - break; + case DEC_R8: { + const bool cFlag = getCFlag(); + subFromR8( mop.operand1, 1 ); + setCFlag( cFlag ); + } break; case LD_Z_TO_pHL: + mem.write( readR16( Operand_t::hl ), Z ); break; case LD_WZ_TO_R16: + writeR16( mop.operand1, getWZ() ); + break; + case LD_A_TO_R16_MEM: + if( mop.operand1 == Operand_t::pBC || mop.operand1 == Operand_t::pDE ) + mem.write( readR16( mop.operand1 ), readR8( Operand_t::a ) ); + else { + uint16_t hl = readR16( Operand_t::hl ); + mem.write( hl, readR8( Operand_t::a ) ); + mop.operand1 == Operand_t::hlPlus ? ++hl : --hl; + writeR16( Operand_t::hl, hl ); + } break; - case LD_R16_MEM_TO_A: - break; - case IDU_INC_R16: - break; - case ALU_ADD_LSB_R16_TO_L: - break; - case ALU_ADD_CMSB_R16_TO_H: + case IDU_INC_R16: { + const uint16_t value = readR16( mop.operand1 ); + writeR16( mop.operand1, value + 1 ); + } break; + case ALU_ADD_LSB_R16_TO_L: { + addToR8( Operand_t::l, lsb( readR16( mop.operand1 ) ) ); + } break; + case ALU_ADC_MSB_R16_TO_H: + addToR8( Operand_t::h, getCFlag() + msb( readR16( mop.operand1 ) ) ); break; case LD_R16_MEM_TO_Z: + if( mop.operand1 == Operand_t::pBC || mop.operand1 == Operand_t::pDE ) + Z = mem.read( readR16( mop.operand1 ) ); + else { + uint16_t hl = readR16( Operand_t::hl ); + Z = mem.read( hl ); + mop.operand1 == Operand_t::hlPlus ? ++hl : --hl; + writeR16( Operand_t::hl, hl ); + } break; - case IDU_DEC_R16: - break; - case LD_R8_TO_R8: - break; + case IDU_DEC_R16: { + const uint16_t value = readR16( mop.operand1 ); + writeR16( mop.operand1, value - 1 ); + } break; + case LD_R8_TO_R8: { + const uint8_t value = readR8( mop.operand2 ); + writeR8( mop.operand1, value ); + } break; case LD_R8_TO_pHL: + mem.write( readR16( Operand_t::hl ), readR8( mop.operand1 ) ); break; case ALU_ADD_R8_TO_A: - break; - case ALU_ADC_Z_TO_A: + addToR8( Operand_t::a, readR8( mop.operand1 ) ); break; case ALU_ADC_R8_TO_A: + addToR8( Operand_t::a, getCFlag() + readR8( mop.operand1 ) ); break; case ALU_SUB_R8_FROM_A: - break; - case ALU_SBC_Z_FROM_A: + subFromR8( Operand_t::a, readR8( mop.operand1 ) ); break; case ALU_SBC_R8_FROM_A: + subFromR8( Operand_t::a, getCFlag() + readR8( mop.operand1 ) ); break; - case ALU_A_AND_R8: - break; - case ALU_A_XOR_R8: - break; - case ALU_A_OR_R8: - break; + case ALU_A_AND_R8: { + const uint8_t result = readR8( Operand_t::a ) & readR8( mop.operand1 ); + writeR8( Operand_t::a, result ); + setZNHCFlags( ! result, 0, 1, 0 ); + } break; + case ALU_A_XOR_R8: { + const uint8_t result = readR8( Operand_t::a ) ^ readR8( mop.operand1 ); + writeR8( Operand_t::a, result ); + setZNHCFlags( ! result, 0, 0, 0 ); + } break; + case ALU_A_OR_R8: { + const uint8_t result = readR8( Operand_t::a ) | readR8( mop.operand1 ); + writeR8( Operand_t::a, result ); + setZNHCFlags( ! result, 0, 0, 0 ); + } break; case ALU_CP_A_R8: + subFromR8( Operand_t::a, readR8( mop.operand1 ), true ); break; case CHECK_COND: + lastConditionCheck = isConditionMet( mop.operand1 ); break; case COND_CHECK__LD_IMM_TO_W: + lastConditionCheck = isConditionMet( mop.operand1 ); + W = mem.read( PC++ ); break; case LD_PCL_TO_SP__LD_WZ_TO_PC: + mem.write( SP, lsb( PC ) ); + PC = getWZ(); break; case LD_PCL_TO_SP__LD_TGT3_TO_PC: + mem.write( SP, lsb( PC ) ); + PC = std::to_underlying( mop.operand1 ) * 8; break; case LD_WZ_TO_R16STK: + registers[std::to_underlying( mop.operand1 ) * 2] = static_cast( getWZ() >> 8 ); + registers[std::to_underlying( mop.operand1 ) * 2 + 1] = static_cast( getWZ() ); break; - case PUSH_MSB_R16STK_TO_SP: - break; - case PUSH_LSB_R16STK_TO_SP: - break; + case PUSH_MSB_R16STK_TO_SP: { + const auto r16STKMsb = registers[std::to_underlying( mop.operand1 ) * 2]; + mem.write( SP--, r16STKMsb ); // go to next byte's address + } break; + case PUSH_LSB_R16STK_TO_SP: { + const auto r16STKLsb = registers[std::to_underlying( mop.operand1 ) * 2 + 1]; + mem.write( SP, r16STKLsb ); + } break; case FETCH_SECOND_BYTE: PC++; break; case LD_RLC_Z_TO_pHL: { - const bool cFlag = Z & bit( 7 ); + const bool cFlag = Z & ( 1 << 7 ); Z = std::rotl( Z, 1 ); mem.write( readR16( Operand_t::hl ), Z ); setZNHCFlags( ! Z, 0, 0, cFlag ); } break; case RLC_R8: { const uint8_t value = readR8( mop.operand1 ); - const bool cFlag = value & bit( 7 ); + const bool cFlag = value & ( 1 << 7 ); const uint8_t newValue = std::rotl( value, 1 ); writeR8( mop.operand1, newValue ); setZNHCFlags( ! newValue, 0, 0, cFlag ); @@ -304,14 +365,14 @@ void CoreCpu::execute( MicroOperation_t mop ) { setZNHCFlags( ! newValue, 0, 0, cFlag ); } break; case LD_RL_Z_TO_pHL: { - const bool cFlag = Z & bit( 7 ); + const bool cFlag = Z & ( 1 << 7 ); Z = static_cast( ( Z << 1 ) | getCFlag() ); mem.write( readR16( Operand_t::hl ), Z ); setZNHCFlags( ! Z, 0, 0, cFlag ); } break; case RL_R8: { const uint8_t value = readR8( mop.operand1 ); - const bool cFlag = value & bit( 7 ); + const bool cFlag = value & ( 1 << 7 ); const uint8_t newValue = static_cast( ( value << 1 ) | getCFlag() ); writeR8( mop.operand1, newValue ); setZNHCFlags( ! newValue, 0, 0, cFlag ); @@ -330,28 +391,28 @@ void CoreCpu::execute( MicroOperation_t mop ) { setZNHCFlags( ! newValue, 0, 0, cFlag ); } break; case LD_SLA_Z_TO_pHL: { - const bool cFlag = Z & bit( 7 ); - Z = Z << 1; + const bool cFlag = Z & ( 1 << 7 ); + Z = static_cast( Z << 1 ); mem.write( readR16( Operand_t::hl ), Z ); setZNHCFlags( ! Z, 0, 0, cFlag ); } break; case SLA_R8: { const uint8_t value = readR8( mop.operand1 ); - const bool cFlag = value & bit( 7 ); - const uint8_t newValue = value << 1; + const bool cFlag = value & ( 1 << 7 ); + const uint8_t newValue = static_cast( value << 1 ); writeR8( mop.operand1, newValue ); setZNHCFlags( ! newValue, 0, 0, cFlag ); } break; case LD_SRA_Z_TO_pHL: { const bool cFlag = Z & 0x1; - Z = static_cast( ( Z & bit( 7 ) ) | ( Z >> 1 ) ); + Z = static_cast( ( Z & ( 1 << 7 ) ) | ( Z >> 1 ) ); mem.write( readR16( Operand_t::hl ), Z ); setZNHCFlags( ! Z, 0, 0, cFlag ); } break; case SRA_R8: { const uint8_t value = readR8( mop.operand1 ); const bool cFlag = value & 0x1; - const uint8_t newValue = static_cast( ( value & bit( 7 ) ) | ( value >> 1 ) ); + const uint8_t newValue = static_cast( ( value & ( 1 << 7 ) ) | ( value >> 1 ) ); writeR8( mop.operand1, newValue ); setZNHCFlags( ! newValue, 0, 0, cFlag ); } break; @@ -411,14 +472,15 @@ void CoreCpu::execute( MicroOperation_t mop ) { writeR8( mop.operand2, newValue ); } break; case INVALID: - break; + [[unlikely]] break; case EMPTY: - logFatal( ErrorCode::emptyMicroCodeExecuted, "Empty microcode executed! ( not NOP )" ); + [[unlikely]] logFatal( ErrorCode::emptyMicroCodeExecuted, "Empty microcode executed! ( not NOP )" ); logStacktrace(); std::abort(); default: - logFatal( ErrorCode::InvalidOperandType, - std::format( "Unknown microcode executed, value: {0}", std::to_underlying( mop.type ) ) ); + [[unlikely]] logFatal( + ErrorCode::InvalidOperand, + std::format( "Unknown microcode executed, value: {0}", std::to_underlying( mop.type ) ) ); logStacktrace(); std::abort(); } diff --git a/src/raylib/raylib_parts.cpp b/src/raylib/raylib_parts.cpp index 228ed5a..7a39081 100644 --- a/src/raylib/raylib_parts.cpp +++ b/src/raylib/raylib_parts.cpp @@ -32,8 +32,8 @@ void RaylibPpu::drawPixel( uint8_t colorId ) { //-------------------------------------------------- void RaylibCpu::handleJoypad() { const auto joypadInputRegister = mem.read( addr::joypadInput ); - const bool selectButtonsFlag = joypadInputRegister & bit( 5 ); - const bool selectDPad = joypadInputRegister & ( 4 << 4 ); + const bool selectButtonsFlag = joypadInputRegister & ( 1 << 5 ); + const bool selectDPad = joypadInputRegister & ( 1 << 4 ); // 0 means button pressed uint8_t buttonsPressed = 0x0F; -- GitLab From 9a2f6f6e119726d8872d11cd8197c1e19272db3e Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Sun, 22 Jun 2025 01:55:53 +0200 Subject: [PATCH 6/8] adapt cpu to using microcodes --- include/core/cpu.hpp | 252 +++++----- include/core/emulator.hpp | 15 +- include/core/logging.hpp | 19 +- src/core/cpu.cpp | 855 ++------------------------------- src/core/cpu_decode.cpp | 3 +- src/core/cpu_execute.cpp | 11 +- src/raylib/main.cpp | 13 +- src/std_logging/logging.cpp | 5 + test/core/test_cpu_timings.cpp | 100 ++-- 9 files changed, 259 insertions(+), 1014 deletions(-) diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index df45845..b7294ee 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -48,7 +48,7 @@ public: JP_TO_HL, SP_DEC, LD_PCH_TO_SP, - LD_PCL_TO_SP, + LD_PCL_TO_SP__LD_WZ_TO_PC, LD_A_TO_FF00_PLUS_C, LD_A_TO_FF00_PLUS_Z, LD_A_TO_pWZ, @@ -88,7 +88,6 @@ public: ALU_CP_A_R8, CHECK_COND, COND_CHECK__LD_IMM_TO_W, - LD_PCL_TO_SP__LD_WZ_TO_PC, LD_PCL_TO_SP__LD_TGT3_TO_PC, LD_WZ_TO_R16STK, PUSH_MSB_R16STK_TO_SP, @@ -117,81 +116,7 @@ public: SET_pHL, SET_R8, INVALID, - EMPTY - }; - enum class OperationType_t : Enum_t { - INVALID, - NOP, - STOP, - HALT, - LD, - LDH, - INC, - DEC, - ADD, - ADC, - SUB, - SBC, - AND, - XOR, - OR, - CP, - RLCA, - RRCA, - RLA, - RRA, - DAA, - CPL, - SCF, - CCF, - JR, - JP, - RET, - RETI, - CALL, - RST, - POP, - PUSH, - DI, - EI, - RLC, - RRC, - RL, - RR, - SLA, - SRA, - SWAP, - SRL, - BIT, - RES, - SET - }; - static constexpr std::string_view OperationTypeString[] = { - "INVALID", "NOP", "STOP", "HALT", "LD", "LDH", "INC", "DEC", "ADD", "ADC", "SUB", "SBC", - "AND", "XOR", "OR", "CP", "RLCA", "RRCA", "RLA", "RRA", "DAA", "CPL", "SCF", "CCF", - "JR", "JP", "RET", "RETI", "CALL", "RST", "POP", "PUSH", "DI", "EI", "RLC", "RRC", - "RL", "RR", "SLA", "SRA", "SWAP", "SRL", "BIT", "RES", "SET", - }; - - enum class OperandType_t : Enum_t { - NONE, - R8, - pHL, - R16, - R16STK, - R16MEM, - COND, - BIT_INDEX, - TGT3, - IMM8, - IMM16, - pIMM16, - SP_PLUS_IMM8, - FF00_PLUS_R8 - }; - static constexpr std::string_view OperandTypeString[] = { - "NONE", "R8", "pHL", "R16", "R16STK", "R16MEM", "COND", - "BIT_INDEX", "TGT3", "IMM8", "IMM16", "pIMM16", "SP_PLUS_IMM8", "FF00_PLUS_R8", + END }; enum class Operand_t : Enum_t { @@ -225,54 +150,37 @@ public: NONE }; - using OperandVar_t = std::variant; //In case of IMM8 and IMM16, don't save the next byte(s) //They will be fetched in execution phase - struct Operation_t { - uint16_t opcode; - OperationType_t operationType; - OperandType_t operandType1; - OperandVar_t operand1; - OperandType_t operandType2; - OperandVar_t operand2; - Operation_t( uint16_t opcode_, OperationType_t operationType_, OperandType_t operandType1_ = {}, - OperandVar_t operand1_ = {}, OperandType_t operandType2_ = {}, - OperandVar_t operand2_ = {} ) - : opcode( opcode_ ) - , operationType( operationType_ ) - , operandType1( operandType1_ ) - , operand1( operand1_ ) - , operandType2( operandType2_ ) - , operand2( operand2_ ) { - } - }; + using OperandVar_t = std::variant; struct MicroOperation_t { MicroOperationType_t type; Operand_t operand1; Operand_t operand2; - MicroOperation_t( MicroOperationType_t type_ = MicroOperationType_t::EMPTY, + MicroOperation_t( MicroOperationType_t type_ = MicroOperationType_t::END, Operand_t operand1_ = Operand_t::NONE, Operand_t operand2_ = Operand_t::NONE ) : type( type_ ) , operand1( operand1_ ) , operand2( operand2_ ) { } }; + // This is max amount of microcodes in instruction + 1 (for terminator) + using MicroOperations_t = std::array; protected: // Default values of registers are for DMG; register f is initialized in constructor uint8_t registers[8] { 0x0, 0x13, 0x0, 0xD8, 0x1, 0x4D, 0x1 }; //b,c,d,e,h,l,a,f uint8_t Z, W; // temporary registers uint16_t SP = 0xFFFE, PC = 0x100; - Operation_t operation = Operation_t( 0x0, OperationType_t::NOP ); Memory& mem; bool interruptMasterEnabled = false; bool enableIMELater = false; bool halted = false; bool lastConditionCheck = false; + MicroOperations_t mopQueue = { MicroOperationType_t::NOP }; + unsigned atMicroOperationNr = 0; public: - static consteval size_t getOperandVarType( CoreCpu::OperandType_t operandType ); - uint8_t readR8( Operand_t opd ); uint16_t readR16( Operand_t opd ); @@ -283,29 +191,10 @@ public: void addToR8( Operand_t operand, uint8_t value ); void subFromR8( Operand_t operand, uint8_t value, bool discard = false ); - template - uint8or16_t auto read( const OperandVar_t operand ); - template - void write( OperandVar_t operand, T value ); - template - void addTo( OperandVar_t operand, T value ); - template - void subFrom( OperandVar_t operand, uint8_t value, bool discard = false ); - template - void bitwise( const Operation_t& op ); - template - void bitShift( Operation_t op ); - - unsigned execute( const Operation_t& op ); - void execute( const MicroOperation_t mop ); - unsigned handleInterrupts(); - void ld( const Operation_t& op ); - void ldh( const Operation_t& op ); - void pushToStack( uint16_t value ); - uint16_t popFromStack(); + void execute( MicroOperation_t mop ); + bool handleInterrupts(); bool isConditionMet( Operand_t condition ); - using MicroOperations_t = std::array; MicroOperations_t decode(); //helpers MicroOperations_t decodeBlock0( const uint8_t opcode ); @@ -314,14 +203,13 @@ public: MicroOperations_t decodeCB(); // clang-format off uint16_t getWZ() { return static_cast( ( W << 8 ) | Z ); } - void setWZ( uint16_t value ) { Z = value & 0xF; W = uint8_t( value >> 8 ); } + void setWZ( uint16_t value ) { Z = static_cast( value & 0xFF ); W = uint8_t( value >> 8 ); } 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 - std::array getZNHCFlags() { return { getZFlag(), getNFlag(), getHFlag(), getCFlag() }; } 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 )); } @@ -334,18 +222,120 @@ public: setHFlag( H ); setCFlag( C ); } - static inline unsigned getCycles( uint16_t opcode, bool branchTaken ) { - if( opcode <= 0xFF ) - return branchTaken ? cycles::opcodeCyclesBranched[opcode] : cycles::opcodeCycles[opcode]; - else - return cycles::opcodeCyclesCb[opcode & 0xFF]; - } virtual void handleJoypad() = 0; - void logOperation( Operation_t op, unsigned cycles ); + void logOperation( MicroOperation_t mop ); public: CoreCpu( Memory& mem_ ); virtual ~CoreCpu() = default; - unsigned tick(); + void tick(); +}; + +constexpr std::string_view MicroOperationTypeString[] = { + "NOP", + "STOP", + "LD_IMM_TO_Z", + "LD_IMM_TO_W", + "LD_SPL_TO_pWZ", + "LD_SPH_TO_pWZ", + "RLCA", + "RRCA", + "RLA", + "RRA", + "DAA", + "CPL", + "SCF", + "CCF", + "ALU_CALC_RELATIVE_JUMP", + "IDU_LD_WZ_PLUS_1_TO_PC", + "HALT", + "ALU_ADD_Z_TO_A", + "ALU_ADC_Z_TO_A", + "ALU_SUB_Z_FROM_A", + "ALU_SUB_Z_AND_C_FROM_A", + "ALU_A_AND_Z", + "ALU_A_XOR_Z", + "ALU_A_OR_Z", + "ALU_A_CP_Z", + "ALU_ADD_SPL_TO_Z", + "ALU_ADD_SPH_TO_W", + "ALU_SPH_ADC_ADJ_TO_W", + "LD_WZ_TO_SP", + "POP_SP_TO_Z", + "POP_SP_TO_W", + "LD_WZ_TO_PC", + "LD_WZ_TO_PC__ENABLE_IME", + "JP_TO_HL", + "SP_DEC", + "LD_PCH_TO_SP", + "LD_PCL_TO_SP__LD_WZ_TO_PC", + "LD_A_TO_FF00_PLUS_C", + "LD_A_TO_FF00_PLUS_Z", + "LD_A_TO_pWZ", + "LD_FF00_PLUS_C_TO_Z", + "LD_Z_TO_R8", + "LD_FF00_PLUS_Z_TO_Z", + "LD_pWZ_TO_Z", + "ALU_SPL_PLUS_Z_TO_L", + "ALU_SPH_ADC_ADJ_TO_H", + "LD_HL_TO_SP", + "DI", + "EI", + "COND_CHECK__LD_IMM_TO_Z", + "INC_R8", + "LD_pHL_TO_Z", + "ALU_LD_Z_PLUS_1_TO_pHL", + "ALU_LD_Z_MINUS_1_TO_pHL", + "DEC_R8", + "LD_Z_TO_pHL", + "LD_WZ_TO_R16", + "LD_A_TO_R16_MEM", + "IDU_INC_R16", + "ALU_ADD_LSB_R16_TO_L", + "ALU_ADC_MSB_R16_TO_H", + "LD_R16_MEM_TO_Z", + "IDU_DEC_R16", + "LD_R8_TO_R8", + "LD_R8_TO_pHL", + "ALU_ADD_R8_TO_A", + "ALU_ADC_R8_TO_A", + "ALU_SUB_R8_FROM_A", + "ALU_SBC_Z_FROM_A", + "ALU_SBC_R8_FROM_A", + "ALU_A_AND_R8", + "ALU_A_XOR_R8", + "ALU_A_OR_R8", + "ALU_CP_A_R8", + "CHECK_COND", + "COND_CHECK__LD_IMM_TO_W", + "LD_PCL_TO_SP__LD_TGT3_TO_PC", + "LD_WZ_TO_R16STK", + "PUSH_MSB_R16STK_TO_SP", + "PUSH_LSB_R16STK_TO_SP", + "FETCH_SECOND_BYTE", + "LD_RLC_Z_TO_pHL", + "RLC_R8", + "LD_RRC_Z_TO_pHL", + "RRC_R8", + "LD_RL_Z_TO_pHL", + "RL_R8", + "LD_RR_Z_TO_pHL", + "RR_R8", + "LD_SLA_Z_TO_pHL", + "SLA_R8", + "LD_SRA_Z_TO_pHL", + "SRA_R8", + "LD_SWAP_Z_TO_pHL", + "SWAP_R8", + "LD_SRL_Z_TO_pHL", + "SRL_R8", + "BIT_Z", + "BIT_R8", + "RES_pHL", + "RES_R8", + "SET_pHL", + "SET_R8", + "INVALID", + "END", }; diff --git a/include/core/emulator.hpp b/include/core/emulator.hpp index 686d0ce..6acec78 100644 --- a/include/core/emulator.hpp +++ b/include/core/emulator.hpp @@ -13,17 +13,18 @@ public: static constexpr unsigned tickrate = 4194304; static constexpr float oscillatoryTime = 1. / tickrate; - unsigned tick() { - const bool cpuDoubleSpeed = memory.read( addr::key1 ) & ( 1 << 7 ); - auto cpuTicks = cpu.tick(); - if( cpuDoubleSpeed ) - cpuTicks /= 2; - for( auto i = 0u; i < cpuTicks; i++ ) { + int tick() { + cpu.tick(); + // const bool cpuDoubleSpeed = memory.read( addr::key1 ) & ( 1 << 7 ); + for( int i = 0; i < 4; i++ ) { ppu.tick(); } //apu.tick(); - return cpuTicks; + return 4; + } + void handleJoypad() { + cpu.handleJoypad(); } Emulator( std::unique_ptr&& cartridge_ ) : cartridge( std::move( cartridge_ ) ) diff --git a/include/core/logging.hpp b/include/core/logging.hpp index 7040b67..b43038a 100644 --- a/include/core/logging.hpp +++ b/include/core/logging.hpp @@ -54,12 +54,13 @@ enum CoreError : int { } // namespace ErrorCode enum class LogLevel : uint8_t { - Debug = 0, // Details - Info = 1, // What happens - Warning = 2, // Unexpected situation and/or ignored can cause error in the future - Error = 3, // Something went wrong - Fatal = 4, // Something went very wrong, cannot continue execution - Off = 255 + Debug = 0, // Details + Info = 1, // What happens + Warning = 2, // Unexpected situation and/or ignored can cause error in the future + Error = 3, // Something went wrong + Fatal = 4, // Something went very wrong, cannot continue execution + LiveDebug = 254, // Logs for actions made during live-debugging + Off = 255 }; inline LogLevel gLogLevel = LogLevel::Warning; @@ -123,6 +124,12 @@ void logSeparator(); log( code, LogLevel::Fatal, message, __FILE__, __LINE__ ); \ } while( 0 ) +#define logLiveDebug( message ) \ + do { \ + if( shouldLog( LogLevel::LiveDebug ) ) \ + log( 0, LogLevel::LiveDebug, message, __FILE__, __LINE__ ); \ + } while( 0 ) + template inline std::string toHex( T number ) { const auto padding = sizeof( T ) * 2; diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index a502507..f79e67e 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -73,215 +73,6 @@ void CoreCpu::writeR16( Operand_t opd, uint16_t value ) { } } -#define invalidOperandType( OPERAND ) void( 0 ) - -consteval size_t CoreCpu::getOperandVarType( CoreCpu::OperandType_t operandType ) { - switch( operandType ) { - using enum OperandType_t; - case R8: - case R16: - case R16MEM: - case R16STK: - case pHL: - case SP_PLUS_IMM8: - return 1; - case BIT_INDEX: - case TGT3: - return 2; - default: - return 0; - } -} - -template -void CoreCpu::write( OperandVar_t operand, T writeValue ) { - constexpr std::size_t writeSize = sizeof( T ); - const auto opdVal = std::get( operand ); - Enum_t underlVal; - if constexpr( std::is_same_v, Operand_t> ) { - underlVal = static_cast( opdVal ); - }; - - if constexpr( type == OperandType_t::R8 ) { - static_assert( writeSize == 1, "R8 write: wrong value size" ); - - if( opdVal == Operand_t::a ) { - constexpr int aIndex = 6; - registers[aIndex] = writeValue; - } - registers[underlVal] = writeValue; - } - - else if constexpr( type == OperandType_t::pHL ) { - const auto index = static_cast( Operand_t::hl ) * 2; - mem.write( static_cast( registers[index] | ( registers[index + 1] << 8 ) ), - static_cast( writeValue ) ); - } - - else if constexpr( type == OperandType_t::R16 ) { - static_assert( writeSize == 2, "R16 write: wrong value size" ); - - if( opdVal == Operand_t::sp ) - SP = writeValue; - else { - registers[underlVal * 2] = static_cast( writeValue ); - registers[underlVal * 2 + 1] = static_cast( writeValue >> 8 ); - } - } - - else if constexpr( type == OperandType_t::R16STK ) { - static_assert( writeSize == 2, "R16STK write: wrong value size" ); - - registers[underlVal * 2] = static_cast( writeValue ); - registers[underlVal * 2 + 1] = static_cast( writeValue >> 8 ); - } - - else if constexpr( type == OperandType_t::R16MEM ) { - static_assert( writeSize == 1, "R16MEM write: wrong value size" ); - - const int hlIndex = 4; - switch( opdVal ) { - case Operand_t::bc: - case Operand_t::de: - mem.write( - static_cast( registers[underlVal * 2] | ( registers[underlVal * 2 + 1] << 8 ) ), - writeValue ); - return; - case Operand_t::hlPlus: { - uint16_t currentHl = registers[hlIndex] | static_cast( registers[hlIndex + 1] << 8 ); - mem.write( currentHl++, writeValue ); - write( Operand_t::hl, currentHl ); - return; - } - case Operand_t::hlMinus: { - uint16_t currentHl = registers[hlIndex] | static_cast( registers[hlIndex + 1] << 8 ); - mem.write( currentHl--, writeValue ); - write( Operand_t::hl, currentHl ); - return; - } - default: - logError( ErrorCode::InvalidOperand, - std::format( "Operand_t: {0}", static_cast( opdVal ) ) ); - break; - } - } - - else if constexpr( type == OperandType_t::pIMM16 ) { - static_assert( writeSize == 1, "pIMM16 write: wrong value size" ); - const uint16_t index = mem.read( PC ) | static_cast( ( mem.read( PC + 1 ) << 8 ) ); - PC += 2; - mem.write( index, static_cast( writeValue ) ); - } - - else - static_assert( false, "Wrong operand type" ); -} - -template -uint8or16_t auto CoreCpu::read( const OperandVar_t operand ) { - const auto opdVal = std::get( operand ); - Enum_t underlVal; - if constexpr( std::is_same_v, Operand_t> ) { - underlVal = static_cast( opdVal ); - }; - - if constexpr( type == OperandType_t::R8 ) { - if( opdVal == Operand_t::a ) { - const int aIndex = 6; - return registers[aIndex]; - } - return registers[underlVal]; - } - - else if constexpr( type == OperandType_t::pHL ) { - const auto index = static_cast( Operand_t::hl ) * 2; - return mem.read( static_cast( registers[index] | ( registers[index + 1] << 8 ) ) ); - } - - else if constexpr( type == OperandType_t::R16 ) { - if( opdVal == Operand_t::sp ) - return SP; - else - return static_cast( registers[underlVal * 2] | ( registers[underlVal * 2 + 1] << 8 ) ); - } - - else if constexpr( type == OperandType_t::R16STK ) { - return static_cast( registers[underlVal * 2] | ( registers[underlVal * 2 + 1] << 8 ) ); - } - - else if constexpr( type == OperandType_t::R16MEM ) { - const int hlIndex = 4; - switch( opdVal ) { - case Operand_t::bc: - case Operand_t::de: - return mem.read( static_cast( registers[underlVal * 2] | - ( registers[underlVal * 2 + 1] << 8 ) ) ); - case Operand_t::hlPlus: { - uint16_t currentHl = registers[hlIndex] | static_cast( registers[hlIndex + 1] << 8 ); - const auto retVal = mem.read( currentHl++ ); - write( Operand_t::hl, currentHl ); - return retVal; - } - case Operand_t::hlMinus: { - uint16_t currentHl = registers[hlIndex] | static_cast( registers[hlIndex + 1] << 8 ); - const auto retVal = mem.read( currentHl-- ); - write( Operand_t::hl, currentHl ); - return retVal; - } - default: - logError( ErrorCode::InvalidOperand, - std::format( "Operand_t: {0}", static_cast( opdVal ) ) ); - break; - } - } - - else if constexpr( type == OperandType_t::IMM8 ) { - return mem.read( PC++ ); - } - - else if constexpr( type == OperandType_t::IMM16 ) { - const auto retVal = static_cast( mem.read( PC ) | ( mem.read( PC + 1 ) << 8 ) ); - PC += 2; - return retVal; - } - - else if constexpr( type == OperandType_t::pIMM16 ) { - const auto index = static_cast( mem.read( PC ) | ( mem.read( PC + 1 ) << 8 ) ); - PC += 2; - return mem.read( index ); - } - - else - static_assert( false, "Wrong operand type" ); - - std::unreachable(); -} - -template -void CoreCpu::addTo( OperandVar_t operand, T value ) { - auto currentValue = read( operand ); - bool cFlag = ( std::numeric_limits::max() - currentValue < value ); - bool halfCarryFlag = std::same_as - ? ( ( currentValue & 0xF ) + ( value & 0xF ) ) > 0xF - : ( ( currentValue & 0xFFF ) + ( value & 0xFFF ) ) > 0xFFF; - - currentValue += value; - write( operand, currentValue ); - setZNHCFlags( ! currentValue, false, halfCarryFlag, cFlag ); -}; - -template -void CoreCpu::subFrom( OperandVar_t operand, uint8_t value, bool discard ) { - auto currentValue = read( operand ); - bool cFlag = ( value > currentValue ); - //due to integer promotion substraction operands are promoted to ints - bool halfCarryFlag = ( ( currentValue & 0xF ) - ( value & 0xF ) ) < 0; - - currentValue -= value; - if( ! discard ) - write( operand, currentValue ); - setZNHCFlags( ! currentValue, true, halfCarryFlag, cFlag ); -}; bool CoreCpu::isConditionMet( Operand_t condition ) { using enum Operand_t; @@ -298,615 +89,75 @@ bool CoreCpu::isConditionMet( Operand_t condition ) { } //-------------------------------------------------- -template -void CoreCpu::bitwise( const Operation_t& op ) { - static_assert( ( optype == OperationType_t::AND ) || ( optype == OperationType_t::OR ) || - ( optype == OperationType_t::XOR ), - "" ); - - using enum OperandType_t; - const auto regA = read( { Operand_t::a } ); - uint8_t readVal; - if( op.operandType2 == R8 ) - readVal = read( op.operand2 ); - else if( op.operandType2 == pHL ) - readVal = read( op.operand2 ); - else if( op.operandType2 == OperandType_t::IMM8 ) - readVal = read( op.operand2 ); - else - invalidOperandType( op.operandType2 ); - - uint8_t result; - if constexpr( optype == OperationType_t::AND ) - result = regA & readVal; - else if constexpr( optype == OperationType_t::OR ) - result = regA | readVal; - else if constexpr( optype == OperationType_t::XOR ) - result = regA ^ readVal; - else - static_assert( false, "" ); - write( { Operand_t::a }, result ); - - constexpr bool hFlag = ( optype == OperationType_t::AND ); - setZNHCFlags( ! result, false, hFlag, false ); -} - -template -void CoreCpu::bitShift( Operation_t op ) { - using OT = OperationType_t; - - if( std::holds_alternative( op.operand1 ) ) { - op.operand1 = { Operand_t::a }; - } - auto value = op.operandType1 == OperandType_t::pHL ? read( op.operand1 ) - : read( op.operand1 ); - - bool zFlag, cFlag; - int bits = ( getCFlag() << 9 ) | ( value << 1 ) | getCFlag(); - if constexpr( optype == OT::RL || optype == OT::RLA ) { - cFlag = value & ( 1 << 7 ); - value = static_cast( bits ); - } else if constexpr( optype == OT::RR || optype == OT::RRA ) { - cFlag = value & 0x1; - value = static_cast( bits >> 2 ); - } else if constexpr( optype == OT::RLC || optype == OT::RLCA ) { - cFlag = value & ( 1 << 7 ); - value = std::rotl( value, 1 ); - } else if constexpr( optype == OT::RRC || optype == OT::RRCA ) { - cFlag = value & 0x1; - value = std::rotr( value, 1 ); - } else if constexpr( optype == OT::SLA ) { - cFlag = value & ( 1 << 7 ); - value <<= 1; - } else if constexpr( optype == OT::SRA ) { - cFlag = 0x1; - value >>= 1; - value |= ( value << 1 ) & ( 1 << 7 ); - } else if constexpr( optype == OT::SRL ) { - cFlag = 0x1; - value >>= 1; - } else - static_assert( false, "" ); - - if constexpr( optype == OT::RLA || optype == OT::RRA || optype == OT::RLCA || optype == OT::RRCA ) - zFlag = false; - else - zFlag = ! value; - - write( op.operand1, value ); - setZNHCFlags( zFlag, false, false, cFlag ); -} - -void CoreCpu::pushToStack( uint16_t value ) { - mem.write( --SP, static_cast( ( value & 0xFF00 ) >> 8 ) ); - mem.write( --SP, static_cast( value & 0xFF ) ); -} - -uint16_t CoreCpu::popFromStack() { - uint16_t value = mem.read( SP++ ); - value |= static_cast( mem.read( SP++ ) << 8 ); - return value; -} - -void CoreCpu::ld( const Operation_t& op ) { - uint16_t readVal; //also for 8-bit values, later truncate them if needed - switch( op.operandType2 ) { - using enum OperandType_t; - case R8: - readVal = read( op.operand2 ); - break; - case pHL: - readVal = read( op.operand2 ); - break; - case R16: - readVal = read( op.operand2 ); - break; - case R16MEM: - readVal = read( op.operand2 ); - break; - case IMM8: - readVal = read( op.operand2 ); - break; - case IMM16: - readVal = read( op.operand2 ); - break; - case pIMM16: - readVal = read( op.operand2 ); - break; - case SP_PLUS_IMM8: { - readVal = read( { Operand_t::sp } ); - const auto imm8 = read( op.operand2 ); - bool cFlag = ( std::numeric_limits::max() - readVal < imm8 ); - bool halfCarryFlag = ( ( readVal & 0xFFF ) + ( imm8 & 0xFFF ) ) > 0xFFF; - - readVal += imm8; - setZNHCFlags( ! readVal, false, halfCarryFlag, cFlag ); - } break; - default: - invalidOperandType( op.operandType2 ); - } - - switch( op.operandType1 ) { - using enum OperandType_t; - case R8: - write( op.operand1, static_cast( readVal ) ); - break; - case pHL: - write( op.operand1, static_cast( readVal ) ); - break; - case R16: - write( op.operand1, readVal ); - break; - case R16MEM: - write( op.operand1, static_cast( readVal ) ); - break; - case pIMM16: - write( op.operand1, static_cast( readVal ) ); - break; - default: - invalidOperandType( op.operandType1 ); - } -} - -void CoreCpu::ldh( const Operation_t& op ) { - uint16_t readVal; //also for 8-bit values, later truncate them if needed - switch( op.operandType2 ) { - using enum OperandType_t; - case R8: - readVal = read( op.operand2 ); - break; - case FF00_PLUS_R8: - readVal = mem.read( 0xFF00 + read( op.operand2 ) ); - break; - case IMM8: - readVal = mem.read( 0xFF00 + read( op.operand2 ) ); - break; - default: - invalidOperandType( op.operandType2 ); - } - - switch( op.operandType1 ) { - using enum OperandType_t; - case R8: - write( op.operand1, static_cast( readVal ) ); - break; - case FF00_PLUS_R8: - mem.write( 0xFF00 + read( op.operand1 ), static_cast( readVal ) ); - break; - case IMM8: - mem.write( 0xFF00 + read( op.operand1 ), static_cast( readVal ) ); - break; - default: - invalidOperandType( op.operandType1 ); - } -} - -unsigned CoreCpu::execute( const Operation_t& op ) { - bool branchTaken = false; - switch( op.operationType ) { - using OT = OperationType_t; - using opdt = OperandType_t; - using opd = Operand_t; - //Load instructions - case OT::LD: - ld( op ); - break; - case OT::LDH: - ldh( op ); - break; - //Arithmetic instructions - case OT::ADC: { - uint8_t readVal = getCFlag(); - if( op.operandType2 == opdt::R8 ) - readVal += read( op.operand2 ); - else if( op.operandType2 == opdt::pHL ) - readVal += read( op.operand2 ); - else if( op.operandType2 == opdt::IMM8 ) - readVal += read( op.operand2 ); - else - invalidOperandType( op.operandType2 ); - - CoreCpu::addTo( op.operand1, readVal ); - } break; - case OT::ADD: - case OT::SUB: { - uint16_t readVal; - switch( op.operandType2 ) { - using enum opdt; - case R16: - readVal = read( op.operand2 ); - break; - case R8: - readVal = read( op.operand2 ); - break; - case pHL: - readVal = read( op.operand2 ); - break; - case IMM8: - readVal = read( op.operand2 ); - break; - default: - invalidOperandType( op.operandType2 ); - } - - bool _add { op.operationType == OT::ADD }; - if( op.operandType1 == opdt::R8 ) { - // There is no write from R16 to R8 - if( _add ) - addTo( op.operand1, static_cast( readVal ) ); - else - subFrom( op.operand1, static_cast( readVal ) ); - } else if( op.operandType1 == opdt::R16 ) { - if( _add ) - addTo( op.operand1, readVal ); - else - invalidOperandType( op.operandType1 ); - } - } break; - case OT::CP: { - uint8_t val2; - if( op.operandType2 == opdt::R8 ) - val2 = read( op.operand2 ); - else if( op.operandType2 == opdt::pHL ) - val2 = read( op.operand2 ); - else if( op.operandType2 == opdt::IMM8 ) - val2 = read( op.operand2 ); - else - invalidOperandType( op.operandType2 ); - subFrom( { opd::a }, val2, true ); - } break; - case OT::DEC: { - const auto cFlag = getCFlag(); - if( op.operandType1 == opdt::R8 ) - subFrom( op.operand1, 1 ); - else if( op.operandType1 == opdt::pHL ) - subFrom( op.operand1, 1 ); - else if( op.operandType1 == opdt::R16 ) { - auto current = read( op.operand1 ); - write( op.operand1, --current ); - } else - invalidOperandType( op.operandType1 ); - setCFlag( cFlag ); - } break; - case OT::INC: { - const auto cFlag = getCFlag(); - if( op.operandType1 == opdt::R8 ) - addTo( op.operand1, static_cast( 1 ) ); - else if( op.operandType1 == opdt::pHL ) - addTo( op.operand1, static_cast( 1 ) ); - else if( op.operandType1 == opdt::R16 ) { - auto current = read( op.operand1 ); - write( op.operand1, ++current ); - } else - invalidOperandType( op.operandType1 ); - - setCFlag( cFlag ); - } break; - case OT::SBC: { - uint8_t readVal = getCFlag(); - if( op.operandType2 == opdt::R8 ) - readVal += read( op.operand2 ); - else if( op.operandType2 == opdt::pHL ) - readVal += read( op.operand2 ); - else if( op.operandType2 == opdt::IMM8 ) { - readVal += read( op.operand2 ); - } else - invalidOperandType( op.operandType2 ); - - subFrom( { opd::a }, readVal ); - } break; - //Bitwise logic instructions - case OT::AND: - bitwise( op ); - break; - case OT::CPL: - write( { opd::a }, static_cast( ~( read( { opd::a } ) ) ) ); - setNFlag( true ); - setHFlag( true ); - break; - case OT::OR: - bitwise( op ); - break; - case OT::XOR: - bitwise( op ); - break; - //Bit flag instructions - case OT::BIT: { - const auto bitIndex = std::get( op.operand1 ); - const auto value = - op.operandType2 == opdt::pHL ? read( op.operand2 ) : read( op.operand2 ); - setZNHCFlags( ! ( value & ( 1 << bitIndex ) ), false, true, getCFlag() ); - } break; - case OT::RES: { - const auto bitIndex = std::get( op.operand1 ); - if( op.operandType2 == opdt::pHL ) { - auto value = read( op.operand2 ); - value &= static_cast( ~( 1 << bitIndex ) ); - write( op.operand2, value ); - } else { - auto value = read( op.operand2 ); - value &= static_cast( ~( 1 << bitIndex ) ); - write( op.operand2, value ); - } - } break; - case OT::SET: { - const auto bitIndex = std::get( op.operand1 ); - if( op.operandType2 == opdt::pHL ) { - auto value = read( op.operand2 ); - value |= ( 1 << bitIndex ); - write( op.operand2, value ); - } else { - auto value = read( op.operand2 ); - value |= ( 1 << bitIndex ); - write( op.operand2, value ); - } - } break; - //Bit shift instructions - case OT::RL: - bitShift( op ); - break; - case OT::RLA: - bitShift( op ); - break; - case OT::RLC: - bitShift( op ); - break; - case OT::RLCA: - bitShift( op ); - break; - case OT::RR: - bitShift( op ); - break; - case OT::RRA: - bitShift( op ); - break; - case OT::RRC: - bitShift( op ); - break; - case OT::RRCA: - bitShift( op ); - break; - case OT::SLA: - bitShift( op ); - break; - case OT::SRA: - bitShift( op ); - break; - case OT::SRL: - bitShift( op ); - break; - case OT::SWAP: { - uint8_t value; - if( op.operandType1 == opdt::pHL ) { - value = read( op.operand1 ); - value = static_cast( ( value >> 4 ) | ( value << 4 ) ); - write( op.operand1, value ); - } else { - value = read( op.operand1 ); - value = static_cast( ( value >> 4 ) | ( value << 4 ) ); - write( op.operand1, value ); - } - setZNHCFlags( ! value, false, false, false ); - } break; - //Jumps and subroutine instructions - case OT::CALL: { - const bool withCondition = op.operandType1 == opdt::COND; - if( withCondition ) { - if( op.operandType2 != opdt::IMM16 ) { - invalidOperandType( op.operandType2 ); - } - const auto condition = std::get( op.operand1 ); - if( isConditionMet( condition ) ) - branchTaken = true; - else - break; - } - pushToStack( PC ); - PC = read( withCondition ? op.operand2 : op.operand1 ); - } break; - case OT::JP: { - switch( op.operandType1 ) { - case opdt::IMM16: - PC = read( op.operand1 ); - break; - case opdt::R16: - PC = read( op.operand1 ); - break; - case opdt::COND: { - if( op.operandType2 != opdt::IMM16 ) - invalidOperandType( op.operandType2 ); - const auto condition = std::get( op.operand1 ); - const auto address = read( op.operand2 ); - if( isConditionMet( condition ) ) { - PC = address; - branchTaken = true; - } - } break; - default: - invalidOperandType( op.operandType1 ); - } - } break; - case OT::JR: { - if( op.operandType1 == opdt::IMM8 ) { - const auto offset = static_cast( read( op.operand1 ) ); - PC = static_cast( PC + offset ); - } else if( op.operandType1 == opdt::COND && op.operandType2 == opdt::IMM8 ) { - const auto condition = std::get( op.operand1 ); - const auto offset = static_cast( read( op.operand2 ) ); - if( isConditionMet( condition ) ) { - PC = static_cast( PC + offset ); - branchTaken = true; - } - } else - invalidOperandType( op.operandType1 ); - - } break; - case OT::RET: - if( op.operandType1 == opdt::NONE ) - PC = popFromStack(); - else if( op.operandType1 == opdt::COND ) { - const auto condition = std::get( op.operand1 ); - if( isConditionMet( condition ) ) { - PC = popFromStack(); - branchTaken = true; - } - } else { - invalidOperandType( op.operandType1 ); - } - break; - case OT::RETI: - interruptMasterEnabled = true; //TODO should be set right after this instruction - PC = popFromStack(); - break; - case OT::RST: - if( op.operandType1 == opdt::TGT3 ) { - const uint16_t jumpTo = std::get( op.operand1 ) * 8; - pushToStack( PC ); - PC = jumpTo; - } else - invalidOperandType( op.operandType1 ); - break; - //Carry flag instructions - case OT::CCF: - setNFlag( false ); - setHFlag( false ); - setCFlag( ! getCFlag() ); - break; - case OT::SCF: - setNFlag( false ); - setHFlag( false ); - setCFlag( true ); - break; - //Stack manipulation instructions - case OT::POP: { - const auto poped = popFromStack(); - write( op.operand1, poped ); - // TODO when writing to F, take into account that lowest 4 bits are not writeable - } break; - case OT::PUSH: - pushToStack( read( op.operand1 ) ); - break; - //Interrupt-related instructions - case OT::DI: - interruptMasterEnabled = false; - break; - case OT::EI: - // TODO This should be delayed by one instruction +void CoreCpu::tick() { + const 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; + } + + execute( mopQueue[atMicroOperationNr] ); + if( ! lastConditionCheck && ( currentMopType == MicroOperationType_t::CHECK_COND || + currentMopType == MicroOperationType_t::COND_CHECK__LD_IMM_TO_W || + currentMopType == MicroOperationType_t::COND_CHECK__LD_IMM_TO_Z ) ) { + mopQueue[atMicroOperationNr + 1] = { MicroOperationType_t::NOP }; + mopQueue[atMicroOperationNr + 2] = { MicroOperationType_t::END }; + } + if( enableIMELater && atMicroOperationNr == 0 ) { // DI takes one cycle, so we are just after next one interruptMasterEnabled = true; - break; - case OT::HALT: { - const auto interruptPending = ( mem.read( addr::interruptEnableRegister ) & 0x1F ) & - ( mem.read( addr::interruptFlag ) & 0x1F ); - if( ! interruptMasterEnabled && interruptPending ) { - } else - halted = true; - } break; - //Miscellaneous instructions - case OT::DAA: { - uint8_t adjustment = 0; - bool cFlag = getCFlag(); - if( getNFlag() ) { - if( getHFlag() ) - adjustment += 6; - if( getCFlag() ) - adjustment += 0x60; - subFrom( { opd::a }, adjustment ); - } else { - if( getHFlag() || ( read( { opd::a } ) & 0xF ) > 9 ) - adjustment += 6; - if( getCFlag() || read( { opd::a } ) > 0x99 ) { - adjustment += 0x60; - cFlag = true; - } - addTo( { opd::a }, adjustment ); - } - setZNHCFlags( ! read( { opd::a } ), getNFlag(), false, cFlag ); - } break; - case OT::NOP: - break; - case OT::STOP: - //TODO - break; - //Invalid and unknown instructions - case OT::INVALID: - logFatal( 0, std::format( "Known invalid operation type made it to execute stage, value: {0}", - static_cast( op.operationType ) ) ); - logStacktrace(); - std::abort(); - break; - default: - logFatal( 0, std::format( "Unknown operation type, value: {0}", - static_cast( op.operationType ) ) ); - logStacktrace(); - std::abort(); + enableIMELater = false; } - - const unsigned cycles = getCycles( op.opcode, branchTaken ); - logDebug( std::format( "{} took {}", OperationTypeString[(int)op.operationType], cycles ) ); - return cycles; + atMicroOperationNr++; } -unsigned CoreCpu::handleInterrupts() { +bool CoreCpu::handleInterrupts() { if( ! interruptMasterEnabled ) - return 0; + return false; const auto interruptEnable = mem.read( addr::interruptEnableRegister ); const auto interruptFlag = mem.read( addr::interruptFlag ); + bool executeInterrupt = false; if( interruptEnable & 0x1 && interruptFlag & 0x1 ) { //VBlank - pushToStack( PC ); - PC = 0x40; - return 20; - } - if( interruptEnable & ( 1 << 1 ) && interruptFlag & ( 1 << 1 ) ) { //LCD - pushToStack( PC ); - PC = 0x48; - return 20; - } - if( interruptEnable & ( 1 << 2 ) && interruptFlag & ( 1 << 2 ) ) { //Timer - pushToStack( PC ); - PC = 0x50; - return 20; - } - if( interruptEnable & ( 1 << 3 ) && interruptFlag & ( 1 << 3 ) ) { //Serial - pushToStack( PC ); - PC = 0x58; - return 20; - } - if( interruptEnable & ( 1 << 4 ) && interruptFlag & ( 1 << 4 ) ) { //Joypad - pushToStack( PC ); - PC = 0x60; - return 20; + setWZ( 0x40 ); + mem.write( addr::interruptFlag, interruptFlag & ~0x1 ); + executeInterrupt = true; + } else if( interruptEnable & ( 1 << 1 ) && interruptFlag & ( 1 << 1 ) ) { //LCD + setWZ( 0x48 ); + mem.write( addr::interruptFlag, interruptFlag & ~( 1 << 1 ) ); + executeInterrupt = true; + } else if( interruptEnable & ( 1 << 2 ) && interruptFlag & ( 1 << 2 ) ) { //Timer + setWZ( 0x50 ); + mem.write( addr::interruptFlag, interruptFlag & ~( 1 << 2 ) ); + executeInterrupt = true; + } else if( interruptEnable & ( 1 << 3 ) && interruptFlag & ( 1 << 3 ) ) { //Serial + setWZ( 0x58 ); + mem.write( addr::interruptFlag, interruptFlag & ~( 1 << 3 ) ); + executeInterrupt = true; + } else if( interruptEnable & ( 1 << 4 ) && interruptFlag & ( 1 << 4 ) ) { //Joypad + setWZ( 0x60 ); + mem.write( addr::interruptFlag, interruptFlag & ~( 1 << 4 ) ); + executeInterrupt = true; + } + + if( executeInterrupt ) { + using enum MicroOperationType_t; + mopQueue = { { NOP, SP_DEC, LD_PCH_TO_SP, LD_PCL_TO_SP__LD_WZ_TO_PC, NOP } }; + atMicroOperationNr = 0; + interruptMasterEnabled = false; + return true; } - return 0; + return false; }; -unsigned CoreCpu::tick() { - return 0; -} - -void CoreCpu::logOperation( Operation_t op, [[maybe_unused]] unsigned cycles ) { +void CoreCpu::logOperation( MicroOperation_t mop ) { std::string opd1; std::string opd2; // FIXME it's temporary solution - if( std::holds_alternative( op.operand1 ) ) - opd1 = "NONE"; - if( std::holds_alternative( op.operand1 ) ) - opd1 = std::to_string( (uint8_t)std::get( op.operand1 ) ); - if( std::holds_alternative( op.operand1 ) ) - opd1 = std::to_string( std::get( op.operand1 ) ); - - if( std::holds_alternative( op.operand2 ) ) - opd2 = "NONE"; - if( std::holds_alternative( op.operand2 ) ) - opd2 = std::to_string( (uint8_t)std::get( op.operand2 ) ); - if( std::holds_alternative( op.operand2 ) ) - opd2 = std::to_string( std::get( op.operand2 ) ); - - - logDebug( std::format( "OT<{}>, opdt1<{}>, opd1<{}>, opdt2<{}>, opd2<{}>: ; took {} cycles", - OperationTypeString[(int)op.operationType], OperandTypeString[(int)op.operandType1], - opd1, OperandTypeString[(int)op.operandType2], opd2, cycles ) ); + 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 ) ) ); } + CoreCpu::CoreCpu( Memory& mem_ ) : mem( mem_ ) { //set register f const bool headerChecksumNonZero = mem.read( addr::headerChecksum ); diff --git a/src/core/cpu_decode.cpp b/src/core/cpu_decode.cpp index a164401..8a758b7 100644 --- a/src/core/cpu_decode.cpp +++ b/src/core/cpu_decode.cpp @@ -68,7 +68,8 @@ CoreCpu::MicroOperations_t CoreCpu::decode() { case 0xE9: return { { JP_TO_HL } }; // JP pHL case 0xCD: - return { { LD_IMM_TO_Z, LD_IMM_TO_W, SP_DEC, LD_PCH_TO_SP, LD_PCL_TO_SP, NOP } }; // CALL IMM16 + return { { LD_IMM_TO_Z, LD_IMM_TO_W, SP_DEC, LD_PCH_TO_SP, LD_PCL_TO_SP__LD_WZ_TO_PC, + NOP } }; // CALL IMM16 //the rest case 0xCB: return decodeCB(); diff --git a/src/core/cpu_execute.cpp b/src/core/cpu_execute.cpp index 1a02a2a..bf8bbbf 100644 --- a/src/core/cpu_execute.cpp +++ b/src/core/cpu_execute.cpp @@ -4,6 +4,7 @@ #include void CoreCpu::execute( MicroOperation_t mop ) { + logOperation( mop ); switch( mop.type ) { using enum MicroOperationType_t; case NOP: @@ -165,7 +166,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { case LD_PCH_TO_SP: mem.write( SP--, msb( PC ) ); // go to next byte's address break; - case LD_PCL_TO_SP: + case LD_PCL_TO_SP__LD_WZ_TO_PC: mem.write( SP, lsb( PC ) ); PC = getWZ(); break; @@ -221,7 +222,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { Z = mem.read( readR16( Operand_t::hl ) ); break; case ALU_LD_Z_PLUS_1_TO_pHL: - mem.write( readR16( Operand_t::hl ), --Z ); + mem.write( readR16( Operand_t::hl ), ++Z ); break; case ALU_LD_Z_MINUS_1_TO_pHL: mem.write( readR16( Operand_t::hl ), --Z ); @@ -315,10 +316,6 @@ void CoreCpu::execute( MicroOperation_t mop ) { lastConditionCheck = isConditionMet( mop.operand1 ); W = mem.read( PC++ ); break; - case LD_PCL_TO_SP__LD_WZ_TO_PC: - mem.write( SP, lsb( PC ) ); - PC = getWZ(); - break; case LD_PCL_TO_SP__LD_TGT3_TO_PC: mem.write( SP, lsb( PC ) ); PC = std::to_underlying( mop.operand1 ) * 8; @@ -473,7 +470,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { } break; case INVALID: [[unlikely]] break; - case EMPTY: + case END: [[unlikely]] logFatal( ErrorCode::emptyMicroCodeExecuted, "Empty microcode executed! ( not NOP )" ); logStacktrace(); std::abort(); diff --git a/src/raylib/main.cpp b/src/raylib/main.cpp index 14b1e83..c23a8b4 100644 --- a/src/raylib/main.cpp +++ b/src/raylib/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include using Emulator_t = Emulator; @@ -70,20 +71,22 @@ int main() { if( interactiveDebugMode && IsKeyPressed( KEY_H ) ) { emulationStopped = ! emulationStopped; if( emulationStopped ) - logDebug( "Stopped emulation!" ); + logLiveDebug( "Stopped emulation!" ); else - logDebug( "Start emulation again!" ); + logLiveDebug( "Start emulation again!" ); } if( interactiveDebugMode && IsKeyPressed( KEY_U ) ) logSeparator(); if( interactiveDebugMode && IsKeyPressed( KEY_J ) ) doOneTick = true; if( IsKeyPressed( KEY_KP_ADD ) ) { - setLogLevel( LogLevel( std::max( int( getLogLevel() ) - 1, 0 ) ) ); + setLogLevel( LogLevel( std::max( std::to_underlying( getLogLevel() ) - 1, 0 ) ) ); + logLiveDebug( std::format( "Log level decreased to {}", std::to_underlying( getLogLevel() ) ) ); } if( IsKeyPressed( KEY_KP_SUBTRACT ) ) { - setLogLevel( LogLevel( - std::min( int( getLogLevel() ) + 1, int( std::numeric_limits::max() ) ) ) ); + setLogLevel( LogLevel( std::min( std::to_underlying( getLogLevel() ) + 1, + int( std::numeric_limits::max() ) ) ) ); + logLiveDebug( std::format( "Log level increased to {}", std::to_underlying( getLogLevel() ) ) ); } BeginDrawing(); diff --git a/src/std_logging/logging.cpp b/src/std_logging/logging.cpp index dc6fb55..a3acc58 100644 --- a/src/std_logging/logging.cpp +++ b/src/std_logging/logging.cpp @@ -98,6 +98,11 @@ void log( const int errorCode, const LogLevel severity, const std::string& messa format = bold; color = brightYellow; break; + case LogLevel::LiveDebug: + severityStr = "[LIVE_DEBUG]"; + format = bold; + color = yellow; + break; default: severityStr = "[UNKNOWN]"; break; diff --git a/test/core/test_cpu_timings.cpp b/test/core/test_cpu_timings.cpp index 27bc9bb..9fd613c 100644 --- a/test/core/test_cpu_timings.cpp +++ b/test/core/test_cpu_timings.cpp @@ -11,90 +11,90 @@ namespace cycles { constexpr unsigned NOP = 4; constexpr unsigned LD_R16_IMM16 = 12; -constexpr unsigned LD_R16MEM_A = 8; -constexpr unsigned LD_A_R16MEM = 8; -constexpr unsigned LD_IMM16_SP = 20; +constexpr unsigned LD_R16MEM_A = 8; +constexpr unsigned LD_A_R16MEM = 8; +constexpr unsigned LD_IMM16_SP = 20; -constexpr unsigned INC_R16 = 8; -constexpr unsigned DEC_R16 = 8; +constexpr unsigned INC_R16 = 8; +constexpr unsigned DEC_R16 = 8; constexpr unsigned ADD_HL_R16 = 8; -constexpr unsigned INC_R8 = 4; +constexpr unsigned INC_R8 = 4; constexpr unsigned INC_pHL = 12; -constexpr unsigned DEC_R8 = 4; +constexpr unsigned DEC_R8 = 4; constexpr unsigned DEC_pHL = 12; -constexpr unsigned LD_R8_IMM8 = 8; +constexpr unsigned LD_R8_IMM8 = 8; constexpr unsigned LD_pHL_IMM8 = 12; constexpr unsigned RLCA = 4; constexpr unsigned RRCA = 4; -constexpr unsigned RLA = 4; -constexpr unsigned RRA = 4; -constexpr unsigned DAA = 4; -constexpr unsigned CPL = 4; -constexpr unsigned SCF = 4; -constexpr unsigned CCF = 4; - -constexpr unsigned JR_IMM8 = 12; -constexpr unsigned JR_COND_IMM8_TAKEN = 12; +constexpr unsigned RLA = 4; +constexpr unsigned RRA = 4; +constexpr unsigned DAA = 4; +constexpr unsigned CPL = 4; +constexpr unsigned SCF = 4; +constexpr unsigned CCF = 4; + +constexpr unsigned JR_IMM8 = 12; +constexpr unsigned JR_COND_IMM8_TAKEN = 12; constexpr unsigned JR_COND_IMM8_UNTAKEN = 8; constexpr unsigned STOP = 4; -constexpr unsigned LD_R8_R8 = 4; +constexpr unsigned LD_R8_R8 = 4; constexpr unsigned LD_R8_pHL = 8; constexpr unsigned LD_pHL_R8 = 8; -constexpr unsigned HALT = 4; +constexpr unsigned HALT = 4; -constexpr unsigned ARITHMETIC_A_R8 = 4; +constexpr unsigned ARITHMETIC_A_R8 = 4; constexpr unsigned ARITHMETIC_A_IMM8 = 8; -constexpr unsigned ARITH_A_pHL = 8; +constexpr unsigned ARITH_A_pHL = 8; -constexpr unsigned LOGIC_A_R8 = 4; -constexpr unsigned LOGIC_A_pHL = 8; +constexpr unsigned LOGIC_A_R8 = 4; +constexpr unsigned LOGIC_A_pHL = 8; constexpr unsigned LOGIC_A_IMM8 = 8; -constexpr unsigned CP_A_R8 = 4; -constexpr unsigned CP_A_pHL = 8; +constexpr unsigned CP_A_R8 = 4; +constexpr unsigned CP_A_pHL = 8; constexpr unsigned CP_A_IMM8 = 8; -constexpr unsigned RET_COND_TAKEN = 20; -constexpr unsigned RET_COND_UNTAKEN = 8; -constexpr unsigned RET = 16; -constexpr unsigned RETI = 16; -constexpr unsigned JP_COND_IMM16_TAKEN = 16; -constexpr unsigned JP_COND_IMM16_UNTAKEN = 12; -constexpr unsigned JP_IMM16 = 16; -constexpr unsigned JP_HL = 4; -constexpr unsigned CALL_COND_IMM16_TAKEN = 24; +constexpr unsigned RET_COND_TAKEN = 20; +constexpr unsigned RET_COND_UNTAKEN = 8; +constexpr unsigned RET = 16; +constexpr unsigned RETI = 16; +constexpr unsigned JP_COND_IMM16_TAKEN = 16; +constexpr unsigned JP_COND_IMM16_UNTAKEN = 12; +constexpr unsigned JP_IMM16 = 16; +constexpr unsigned JP_HL = 4; +constexpr unsigned CALL_COND_IMM16_TAKEN = 24; constexpr unsigned CALL_COND_IMM16_UNTAKEN = 12; -constexpr unsigned CALL_IMM16 = 24; -constexpr unsigned RST_TGT3 = 16; +constexpr unsigned CALL_IMM16 = 24; +constexpr unsigned RST_TGT3 = 16; -constexpr unsigned POP_R16STK = 12; +constexpr unsigned POP_R16STK = 12; constexpr unsigned PUSH_R16STK = 16; -constexpr unsigned LDH_pR8_A = 8; +constexpr unsigned LDH_pR8_A = 8; constexpr unsigned LDH_pIMM8_A = 12; constexpr unsigned LD_pIMM16_A = 16; -constexpr unsigned LDH_A_pR8 = 8; +constexpr unsigned LDH_A_pR8 = 8; constexpr unsigned LDH_A_pIMM8 = 12; constexpr unsigned LD_A_pIMM16 = 16; -constexpr unsigned ADD_SP_IMM8 = 16; +constexpr unsigned ADD_SP_IMM8 = 16; constexpr unsigned LD_HL_SP_PLUS_IMM8 = 12; -constexpr unsigned LD_SP_HL = 8; +constexpr unsigned LD_SP_HL = 8; constexpr unsigned DI = 4; constexpr unsigned EI = 4; -constexpr unsigned CB_OP_R8 = 8; +constexpr unsigned CB_OP_R8 = 8; constexpr unsigned CB_OP_pHL = 16; -constexpr unsigned BIT_B3_R8 = 8; -constexpr unsigned BIT_B3_pHL = 12; -constexpr unsigned RES_SET_B3_R8 = 8; +constexpr unsigned BIT_B3_R8 = 8; +constexpr unsigned BIT_B3_pHL = 12; +constexpr unsigned RES_SET_B3_R8 = 8; constexpr unsigned RES_SET_B3_pHL = 16; } // namespace cycles @@ -302,14 +302,4 @@ unsigned getCyclesAlternative( uint16_t opcode, bool branchTaken ) { /* clang-format on */ TEST_CASE( "instruction timings", "[cpu][timings]" ) { - for( uint16_t i = 0; i < 256; i++ ) { - CAPTURE( i ); - CHECK( DummyCpu::getCycles( i, false ) == getCyclesAlternative( i, false ) ); - CHECK( DummyCpu::getCycles( i, true ) == getCyclesAlternative( i, true ) ); - } - for( uint16_t j = 0; j < 256; j++ ) { - CAPTURE( j ); - CHECK( DummyCpu::getCycles( 0xCB << 8 | j, false ) == getCyclesAlternative( 0xCB << 8 | j, false ) ); - CHECK( DummyCpu::getCycles( 0xCB << 8 | j, true ) == getCyclesAlternative( 0xCB << 8 | j, true ) ); - } } -- GitLab From 24701876ec0e1b8ae6ae007f21aa1408cb664902 Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Wed, 25 Jun 2025 19:53:51 +0200 Subject: [PATCH 7/8] fix handleJoypad() --- include/core/emulator.hpp | 4 +++- include/core/general_constants.hpp | 23 +++++++++++++---------- src/core/cpu.cpp | 1 - src/raylib/raylib_parts.cpp | 16 ++++++++-------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/include/core/emulator.hpp b/include/core/emulator.hpp index 6acec78..d384c53 100644 --- a/include/core/emulator.hpp +++ b/include/core/emulator.hpp @@ -9,8 +9,10 @@ public: Tcpu cpu; Tppu ppu; + Timer timer; + public: - static constexpr unsigned tickrate = 4194304; + static constexpr unsigned tickrate = 4'194'304; static constexpr float oscillatoryTime = 1. / tickrate; int tick() { diff --git a/include/core/general_constants.hpp b/include/core/general_constants.hpp index 6aa2113..7a7fddb 100644 --- a/include/core/general_constants.hpp +++ b/include/core/general_constants.hpp @@ -20,8 +20,7 @@ constexpr uint16_t interruptEnableRegister = 0xFFFF; constexpr uint16_t joypadInput = 0xFF00; constexpr uint16_t serialTransfer = 0xFF01; constexpr uint16_t serialTransferEnd = 0xFF02; -constexpr uint16_t dividerRegister = 0xFF04; -constexpr uint16_t timer = 0xFF05; +constexpr uint16_t timer = 0xFF04; constexpr uint16_t timerEnd = 0xFF07; constexpr uint16_t interruptFlag = 0xFF0F; constexpr uint16_t audio = 0xFF10; @@ -39,14 +38,18 @@ constexpr uint16_t paletes = 0xFF68; constexpr uint16_t paletesEnd = 0xFF6B; constexpr uint16_t wramBankSelect = 0xFF70; // Registers -constexpr uint16_t lcdControl = 0xFF40; -constexpr uint16_t lcdStatus = 0xFF41; -constexpr uint16_t bgScrollY = 0xFF42; -constexpr uint16_t bgScrollX = 0xFF43; -constexpr uint16_t lcdY = 0xFF44; -constexpr uint16_t lyc = 0xFF45; -constexpr uint16_t winY = 0xFF4A; -constexpr uint16_t winX = 0xFF4B; +constexpr uint16_t divider = 0xFF04; +constexpr uint16_t timerCounter = 0xFF05; +constexpr uint16_t timerModulo = 0xFF06; +constexpr uint16_t timerControl = 0xFF07; +constexpr uint16_t lcdControl = 0xFF40; +constexpr uint16_t lcdStatus = 0xFF41; +constexpr uint16_t bgScrollY = 0xFF42; +constexpr uint16_t bgScrollX = 0xFF43; +constexpr uint16_t lcdY = 0xFF44; +constexpr uint16_t lyc = 0xFF45; +constexpr uint16_t winY = 0xFF4A; +constexpr uint16_t winX = 0xFF4B; // Registers - non-CGB mode only constexpr uint16_t bgPalette = 0xFF47; constexpr uint16_t objectPalette0 = 0xFF48; diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index f79e67e..12dad78 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -5,7 +5,6 @@ #include #include #include -#include // Decoding and execution have separate files diff --git a/src/raylib/raylib_parts.cpp b/src/raylib/raylib_parts.cpp index 7a39081..c62424e 100644 --- a/src/raylib/raylib_parts.cpp +++ b/src/raylib/raylib_parts.cpp @@ -38,23 +38,23 @@ void RaylibCpu::handleJoypad() { // 0 means button pressed uint8_t buttonsPressed = 0x0F; if( selectDPad ) { - if( IsKeyPressed( KEY_S ) ) + if( IsKeyPressedRepeat( KEY_S ) ) mem.write( addr::joypadInput, joypadInputRegister & ~( 1 << 3 ) ); - if( IsKeyPressed( KEY_W ) ) + if( IsKeyPressedRepeat( KEY_W ) ) mem.write( addr::joypadInput, joypadInputRegister & ~( 1 << 2 ) ); - if( IsKeyPressed( KEY_A ) ) + if( IsKeyPressedRepeat( KEY_A ) ) mem.write( addr::joypadInput, joypadInputRegister & ~( 1 << 1 ) ); - if( IsKeyPressed( KEY_D ) ) + if( IsKeyPressedRepeat( KEY_D ) ) mem.write( addr::joypadInput, joypadInputRegister & ~( 1 << 0 ) ); } if( selectButtonsFlag ) { - if( IsKeyPressed( KEY_Q ) ) // START - TODO: configurable + if( IsKeyPressedRepeat( KEY_Q ) ) // START - TODO: configurable mem.write( addr::joypadInput, joypadInputRegister & ~( 1 << 3 ) ); - if( IsKeyPressed( KEY_E ) ) // SELECT - TODO configurable + if( IsKeyPressedRepeat( KEY_E ) ) // SELECT - TODO configurable mem.write( addr::joypadInput, joypadInputRegister & ~( 1 << 2 ) ); - if( IsKeyPressed( KEY_B ) ) + if( IsKeyPressedRepeat( KEY_B ) ) mem.write( addr::joypadInput, joypadInputRegister & ~( 1 << 1 ) ); - if( IsKeyPressed( KEY_G ) ) // A button - TODO configurable + if( IsKeyPressedRepeat( KEY_G ) ) // A button - TODO configurable mem.write( addr::joypadInput, joypadInputRegister & ~( 1 << 0 ) ); } -- GitLab From a38131fbf183ab8c369b1345c1182c7120d493a7 Mon Sep 17 00:00:00 2001 From: Marcin Banach Date: Wed, 25 Jun 2025 20:16:05 +0200 Subject: [PATCH 8/8] address comments to PR --- include/core/core_utils.hpp | 6 ++++-- include/core/cpu.hpp | 3 ++- include/core/emulator.hpp | 2 -- src/core/cpu.cpp | 4 ++-- src/core/cpu_decode.cpp | 9 +++------ src/core/cpu_execute.cpp | 30 +++++++++++++++--------------- src/raylib/main.cpp | 1 + 7 files changed, 27 insertions(+), 28 deletions(-) diff --git a/include/core/core_utils.hpp b/include/core/core_utils.hpp index 9c694bd..a65a87f 100644 --- a/include/core/core_utils.hpp +++ b/include/core/core_utils.hpp @@ -2,9 +2,11 @@ #include #include -uint8_t lsb( std::same_as auto value ) { +constexpr uint8_t lsb( std::integral auto value ) { return static_cast( value & 0xFF ); } -uint8_t msb( std::same_as auto value ) { +// msb deliberately accepts only uint16_t, because it's easy to trigger integral promotion, +// which would have unexpected result on returned value +constexpr uint8_t msb( std::same_as auto value ) { return static_cast( ( value >> 8 ) & 0xFF ); } diff --git a/include/core/cpu.hpp b/include/core/cpu.hpp index b7294ee..5a7e140 100644 --- a/include/core/cpu.hpp +++ b/include/core/cpu.hpp @@ -1,4 +1,5 @@ #pragma once +#include "core/core_utils.hpp" #include "core/cycles.hpp" #include "memory.hpp" #include @@ -203,7 +204,7 @@ public: MicroOperations_t decodeCB(); // clang-format off uint16_t getWZ() { return static_cast( ( W << 8 ) | Z ); } - void setWZ( uint16_t value ) { Z = static_cast( value & 0xFF ); W = uint8_t( value >> 8 ); } + 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 diff --git a/include/core/emulator.hpp b/include/core/emulator.hpp index d384c53..7ef5ae5 100644 --- a/include/core/emulator.hpp +++ b/include/core/emulator.hpp @@ -9,8 +9,6 @@ public: Tcpu cpu; Tppu ppu; - Timer timer; - public: static constexpr unsigned tickrate = 4'194'304; static constexpr float oscillatoryTime = 1. / tickrate; diff --git a/src/core/cpu.cpp b/src/core/cpu.cpp index 12dad78..c1a66ab 100644 --- a/src/core/cpu.cpp +++ b/src/core/cpu.cpp @@ -67,8 +67,8 @@ void CoreCpu::writeR16( Operand_t opd, uint16_t value ) { if( opd == Operand_t::sp ) SP = value; else { - registers[std::to_underlying( opd ) * 2] = static_cast( value >> 8 ); - registers[std::to_underlying( opd ) * 2 + 1] = static_cast( value ); + registers[std::to_underlying( opd ) * 2] = msb( value ); + registers[std::to_underlying( opd ) * 2 + 1] = lsb( value ); } } diff --git a/src/core/cpu_decode.cpp b/src/core/cpu_decode.cpp index 8a758b7..1ff85a9 100644 --- a/src/core/cpu_decode.cpp +++ b/src/core/cpu_decode.cpp @@ -1,9 +1,6 @@ #include "core/cpu.hpp" #include -// TODO adding signed IMM8 - check this -// TODO check LD operations regarding SP - using enum CoreCpu::MicroOperationType_t; // Operand order is target first, source next CoreCpu::MicroOperations_t CoreCpu::decode() { @@ -12,9 +9,9 @@ CoreCpu::MicroOperations_t CoreCpu::decode() { switch( opcode ) { //block 0 case 0x0: - return { { NOP } }; //NOP + return { { NOP } }; case 0x10: - return { { STOP } }; // STOP + return { { STOP } }; case 0x08: return { { LD_IMM_TO_Z, LD_IMM_TO_W, LD_SPL_TO_pWZ, LD_SPH_TO_pWZ, NOP } }; // LD pIMM16, SP case 0x07: @@ -37,7 +34,7 @@ CoreCpu::MicroOperations_t CoreCpu::decode() { return { { LD_IMM_TO_Z, ALU_CALC_RELATIVE_JUMP, IDU_LD_WZ_PLUS_1_TO_PC } }; // JR IMM8 //block 1 case 0x76: - return { { HALT } }; // HALT + return { { HALT } }; //block 3 //arithmetic case 0xC6: diff --git a/src/core/cpu_execute.cpp b/src/core/cpu_execute.cpp index bf8bbbf..c7903f8 100644 --- a/src/core/cpu_execute.cpp +++ b/src/core/cpu_execute.cpp @@ -23,7 +23,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { setWZ( getWZ() + 1 ); break; case LD_SPH_TO_pWZ: - mem.write( getWZ(), uint8_t( SP >> 8 ) ); + mem.write( getWZ(), msb( SP ) ); break; case RLCA: { const uint8_t value = readR8( Operand_t::a ); @@ -45,7 +45,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { const uint8_t value = readR8( Operand_t::a ); const bool cFlag = value & ( 1 << 7 ); - const uint8_t newValue = static_cast( ( value << 1 ) | getCFlag() ); + const uint8_t newValue = lsb( ( value << 1 ) | getCFlag() ); writeR8( Operand_t::a, newValue ); setZNHCFlags( 0, 0, 0, cFlag ); } break; @@ -53,7 +53,7 @@ void CoreCpu::execute( MicroOperation_t mop ) { const uint8_t value = readR8( Operand_t::a ); const bool cFlag = value & 0x1; - const uint8_t newValue = static_cast( ( getCFlag() << 7 ) | ( value >> 1 ) ); + const uint8_t newValue = lsb( ( getCFlag() << 7 ) | ( value >> 1 ) ); writeR8( Operand_t::a, newValue ); setZNHCFlags( 0, 0, 0, cFlag ); } break; @@ -321,8 +321,8 @@ void CoreCpu::execute( MicroOperation_t mop ) { PC = std::to_underlying( mop.operand1 ) * 8; break; case LD_WZ_TO_R16STK: - registers[std::to_underlying( mop.operand1 ) * 2] = static_cast( getWZ() >> 8 ); - registers[std::to_underlying( mop.operand1 ) * 2 + 1] = static_cast( getWZ() ); + registers[std::to_underlying( mop.operand1 ) * 2] = W; + registers[std::to_underlying( mop.operand1 ) * 2 + 1] = Z; break; case PUSH_MSB_R16STK_TO_SP: { const auto r16STKMsb = registers[std::to_underlying( mop.operand1 ) * 2]; @@ -363,64 +363,64 @@ void CoreCpu::execute( MicroOperation_t mop ) { } break; case LD_RL_Z_TO_pHL: { const bool cFlag = Z & ( 1 << 7 ); - Z = static_cast( ( Z << 1 ) | getCFlag() ); + Z = lsb( Z << 1 ) | getCFlag(); mem.write( readR16( Operand_t::hl ), Z ); setZNHCFlags( ! Z, 0, 0, cFlag ); } break; case RL_R8: { const uint8_t value = readR8( mop.operand1 ); const bool cFlag = value & ( 1 << 7 ); - const uint8_t newValue = static_cast( ( value << 1 ) | getCFlag() ); + const uint8_t newValue = lsb( ( value << 1 ) | getCFlag() ); writeR8( mop.operand1, newValue ); setZNHCFlags( ! newValue, 0, 0, cFlag ); } break; case LD_RR_Z_TO_pHL: { const bool cFlag = Z & 0x1; - Z = static_cast( ( getCFlag() << 7 ) | ( Z >> 1 ) ); + Z = lsb( getCFlag() << 7 | ( Z >> 1 ) ); mem.write( readR16( Operand_t::hl ), Z ); setZNHCFlags( ! Z, 0, 0, cFlag ); } break; case RR_R8: { const uint8_t value = readR8( mop.operand1 ); const bool cFlag = value & 0x1; - const uint8_t newValue = static_cast( ( getCFlag() << 7 ) | ( value >> 1 ) ); + const uint8_t newValue = lsb( getCFlag() << 7 | ( value >> 1 ) ); writeR8( mop.operand1, newValue ); setZNHCFlags( ! newValue, 0, 0, cFlag ); } break; case LD_SLA_Z_TO_pHL: { const bool cFlag = Z & ( 1 << 7 ); - Z = static_cast( Z << 1 ); + Z = lsb( Z << 1 ); mem.write( readR16( Operand_t::hl ), Z ); setZNHCFlags( ! Z, 0, 0, cFlag ); } break; case SLA_R8: { const uint8_t value = readR8( mop.operand1 ); const bool cFlag = value & ( 1 << 7 ); - const uint8_t newValue = static_cast( value << 1 ); + const uint8_t newValue = lsb( value << 1 ); writeR8( mop.operand1, newValue ); setZNHCFlags( ! newValue, 0, 0, cFlag ); } break; case LD_SRA_Z_TO_pHL: { const bool cFlag = Z & 0x1; - Z = static_cast( ( Z & ( 1 << 7 ) ) | ( Z >> 1 ) ); + Z = lsb( ( Z & ( 1 << 7 ) ) | ( Z >> 1 ) ); mem.write( readR16( Operand_t::hl ), Z ); setZNHCFlags( ! Z, 0, 0, cFlag ); } break; case SRA_R8: { const uint8_t value = readR8( mop.operand1 ); const bool cFlag = value & 0x1; - const uint8_t newValue = static_cast( ( value & ( 1 << 7 ) ) | ( value >> 1 ) ); + const uint8_t newValue = lsb( ( value & ( 1 << 7 ) ) | ( value >> 1 ) ); writeR8( mop.operand1, newValue ); setZNHCFlags( ! newValue, 0, 0, cFlag ); } break; case LD_SWAP_Z_TO_pHL: - Z = static_cast( ( Z << 4 ) | ( Z >> 4 ) ); + Z = lsb( ( Z << 4 ) | ( Z >> 4 ) ); mem.write( readR16( Operand_t::hl ), Z ); setZNHCFlags( ! Z, 0, 0, 0 ); break; case SWAP_R8: { const uint8_t value = readR8( mop.operand1 ); - const uint8_t newValue = static_cast( ( value << 4 ) | ( value >> 4 ) ); + const uint8_t newValue = lsb( ( value << 4 ) | ( value >> 4 ) ); writeR8( mop.operand1, newValue ); setZNHCFlags( ! newValue, 0, 0, 0 ); } break; diff --git a/src/raylib/main.cpp b/src/raylib/main.cpp index c23a8b4..3f1bc0a 100644 --- a/src/raylib/main.cpp +++ b/src/raylib/main.cpp @@ -117,6 +117,7 @@ int main() { DrawFPS( 5, 5 ); } + emu.handleJoypad(); EndDrawing(); } -- GitLab