diff --git a/LR35902/inc/GameBoyBus.h b/LR35902/inc/GameBoyBus.h index b92b65b..8c847ef 100644 --- a/LR35902/inc/GameBoyBus.h +++ b/LR35902/inc/GameBoyBus.h @@ -2,10 +2,12 @@ #include #include +#include #include #include #include +#include #include "LR35902.h" #include "IoRegisters.h" @@ -49,6 +51,9 @@ namespace EightBit { void loadBootRom(const std::string& path); void loadGameRom(const std::string& path); + int runRasterLines(); + int runVerticalBlankLines(); + protected: virtual uint8_t& reference(uint16_t address, bool& rom); @@ -64,6 +69,8 @@ namespace EightBit { IoRegisters m_ioPorts; // 0xff00 - 0xff7f Ram m_highInternalRam; // 0xff80 - 0xffff + bool m_enabledLCD; + bool m_disableGameRom; bool m_rom; @@ -80,6 +87,10 @@ namespace EightBit { void validateCartridgeType(); void Bus_WrittenByte(uint16_t address); + + int runRasterLines(int lines); + int runVerticalBlankLines(int lines); + int runRasterLine(int limit); }; } } \ No newline at end of file diff --git a/LR35902/inc/LR35902.h b/LR35902/inc/LR35902.h index 9b4e357..77c5bc6 100644 --- a/LR35902/inc/LR35902.h +++ b/LR35902/inc/LR35902.h @@ -40,22 +40,14 @@ namespace EightBit { virtual void reset() override; - int runRasterLines(); - int runVerticalBlankLines(); - int singleStep(); protected: - int runRasterLines(int limit, int lines); - int runVerticalBlankLines(int limit, int lines); - int runRasterLine(int limit); - virtual int execute(uint8_t opcode); int step(); private: Bus& m_bus; - bool m_enabledLCD; register16_t af; register16_t bc; diff --git a/LR35902/src/CharacterDefinition.cpp b/LR35902/src/CharacterDefinition.cpp index 2d35ec6..5e68fdc 100644 --- a/LR35902/src/CharacterDefinition.cpp +++ b/LR35902/src/CharacterDefinition.cpp @@ -17,19 +17,19 @@ std::array EightBit::GameBoy::CharacterDefinition::get(int row) const { std::array returned; - auto planeAddress = m_address + row * 2; + const auto planeAddress = m_address + row * 2; - auto planeLow = m_ram->peek(planeAddress); - auto planeHigh = m_ram->peek(planeAddress + 1); + const auto planeLow = m_ram->peek(planeAddress); + const auto planeHigh = m_ram->peek(planeAddress + 1); for (int bit = 0; bit < 8; ++bit) { - auto mask = 1 << bit; + const auto mask = 1 << bit; - auto bitLow = planeLow & mask ? 1 : 0; - auto bitHigh = planeHigh & mask ? 0b10 : 0; + const auto bitLow = planeLow & mask ? 1 : 0; + const auto bitHigh = planeHigh & mask ? 0b10 : 0; - auto colour = bitHigh | bitLow; + const auto colour = bitHigh | bitLow; returned[7 - bit] = colour; } diff --git a/LR35902/src/GameBoyBus.cpp b/LR35902/src/GameBoyBus.cpp index ef6e0a1..d40a993 100644 --- a/LR35902/src/GameBoyBus.cpp +++ b/LR35902/src/GameBoyBus.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "GameBoyBus.h" +#include "Display.h" EightBit::GameBoy::Bus::Bus() : m_cpu(*this), @@ -10,6 +11,7 @@ EightBit::GameBoy::Bus::Bus() m_lowInternalRam(0x2000), m_oamRam(0xa0), m_ioPorts(*this), + m_enabledLCD(false), m_highInternalRam(0x80), m_disableGameRom(false), m_rom(false), @@ -24,8 +26,8 @@ EightBit::GameBoy::Bus::Bus() } void EightBit::GameBoy::Bus::reset() { - m_ioPorts.reset(); - m_cpu.initialise(); + IO().reset(); + CPU().initialise(); } void EightBit::GameBoy::Bus::loadBootRom(const std::string& path) { @@ -113,7 +115,7 @@ void EightBit::GameBoy::Bus::validateCartridgeType() { // ROM size { size_t gameRomBanks = 0; - int romSizeSpecification = m_gameRomBanks[0].peek(0x148); + const int romSizeSpecification = m_gameRomBanks[0].peek(0x148); switch (romSizeSpecification) { case 0x52: gameRomBanks = 72; @@ -166,7 +168,7 @@ void EightBit::GameBoy::Bus::validateCartridgeType() { uint8_t& EightBit::GameBoy::Bus::reference(uint16_t address, bool& rom) { rom = true; - if ((address < 0x100) && m_ioPorts.bootRomEnabled()) + if ((address < 0x100) && IO().bootRomEnabled()) return m_bootRom.reference(address); if ((address < 0x4000) && gameRomEnabled()) return m_gameRomBanks[0].reference(address); @@ -187,6 +189,111 @@ uint8_t& EightBit::GameBoy::Bus::reference(uint16_t address, bool& rom) { if (address < IoRegisters::BASE) return rom = true, placeDATA(0xff); if (address < 0xff80) - return m_ioPorts.reference(address - IoRegisters::BASE); + return IO().reference(address - IoRegisters::BASE); return m_highInternalRam.reference(address - 0xff80); } + +int EightBit::GameBoy::Bus::runRasterLines() { + m_enabledLCD = !!(IO().peek(IoRegisters::LCDC) & IoRegisters::LcdEnable); + IO().resetLY(); + return runRasterLines(Display::RasterHeight); +} + +int EightBit::GameBoy::Bus::runRasterLines(int lines) { + int count = 0; + int allowed = CyclesPerLine; + for (int line = 0; line < lines; ++line) { + auto executed = runRasterLine(allowed); + count += executed; + allowed = CyclesPerLine - (executed - CyclesPerLine); + } + return count; +} + +int EightBit::GameBoy::Bus::runRasterLine(int limit) { + + /* + A scanline normally takes 456 clocks (912 clocks in double speed + mode) to complete. A scanline starts in mode 2, then goes to + mode 3 and, when the LCD controller has finished drawing the + line (the timings depend on lots of things) it goes to mode 0. + During lines 144-153 the LCD controller is in mode 1. + Line 153 takes only a few clocks to complete (the exact + timings are below). The rest of the clocks of line 153 are + spent in line 0 in mode 1! + + During mode 0 and mode 1 the CPU can access both VRAM and OAM. + During mode 2 the CPU can only access VRAM, not OAM. + During mode 3 OAM and VRAM can't be accessed. + In GBC mode the CPU can't access Palette RAM(FF69h and FF6Bh) + during mode 3. + A scanline normally takes 456 clocks(912 clocks in double speed mode) to complete. + A scanline starts in mode 2, then goes to mode 3 and , when the LCD controller has + finished drawing the line(the timings depend on lots of things) it goes to mode 0. + During lines 144 - 153 the LCD controller is in mode 1. + Line 153 takes only a few clocks to complete(the exact timings are below). + The rest of the clocks of line 153 are spent in line 0 in mode 1! + */ + + int count = 0; + if (m_enabledLCD) { + + if ((IO().peek(IoRegisters::STAT) & Processor::Bit6) && (IO().peek(IoRegisters::LYC) == IO().peek(IoRegisters::LY))) + IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus); + + // Mode 2, OAM unavailable + IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::SearchingOamRam); + if (IO().peek(IoRegisters::STAT) & Processor::Bit5) + IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus); + count += CPU().run(80); // ~19us + + // Mode 3, OAM/VRAM unavailable + IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::TransferringDataToLcd); + count += CPU().run(170); // ~41us + + // Mode 0 + IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::HBlank); + if (IO().peek(IoRegisters::STAT) & Processor::Bit3) + IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus); + count += CPU().run(limit - count); // ~48.6us + + IO().incrementLY(); + } else { + count += CPU().run(CyclesPerLine); + } + + return count; +} + +int EightBit::GameBoy::Bus::runVerticalBlankLines() { + const auto lines = TotalLineCount - Display::RasterHeight; + return runVerticalBlankLines(lines); +} + +int EightBit::GameBoy::Bus::runVerticalBlankLines(int lines) { + + /* + Vertical Blank interrupt is triggered when the LCD + controller enters the VBL screen mode (mode 1, LY=144). + This happens once per frame, so this interrupt is + triggered 59.7 times per second. During this period the + VRAM and OAM can be accessed freely, so it's the best + time to update graphics (for example, use the OAM DMA to + update sprites for next frame, or update tiles to make + animations). + This period lasts 4560 clocks in normal speed mode and + 9120 clocks in double speed mode. That's exactly the + time needed to draw 10 scanlines. + The VBL interrupt isn't triggered when the LCD is + powered off or on, even when it was on VBL mode. + It's only triggered when the VBL period starts. + */ + + if (m_enabledLCD) { + IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::VBlank); + if (IO().peek(IoRegisters::STAT) & Processor::Bit4) + IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus); + IO().triggerInterrupt(IoRegisters::Interrupts::VerticalBlank); + } + return runRasterLines(lines); +} diff --git a/LR35902/src/LR35902.cpp b/LR35902/src/LR35902.cpp index 462b22a..32c4cd3 100644 --- a/LR35902/src/LR35902.cpp +++ b/LR35902/src/LR35902.cpp @@ -1,14 +1,12 @@ #include "stdafx.h" #include "LR35902.h" #include "GameBoyBus.h" -#include "Display.h" // based on http://www.z80.info/decoding.htm EightBit::GameBoy::LR35902::LR35902(Bus& memory) : IntelProcessor(memory), m_bus(memory), - m_enabledLCD(false), m_ime(false), m_stopped(false), m_prefixCB(false) { @@ -187,14 +185,14 @@ void EightBit::GameBoy::LR35902::compare(uint8_t& f, uint8_t check, uint8_t valu uint8_t EightBit::GameBoy::LR35902::rlc(uint8_t& f, uint8_t operand) { clearFlag(f, NF | HC | ZF); - auto carry = operand & Bit7; + const auto carry = operand & Bit7; setFlag(f, CF, carry); return (operand << 1) | (carry >> 7); } uint8_t EightBit::GameBoy::LR35902::rrc(uint8_t& f, uint8_t operand) { clearFlag(f, NF | HC | ZF); - auto carry = operand & Bit0; + const auto carry = operand & Bit0; setFlag(f, CF, carry); return (operand >> 1) | (carry << 7); } @@ -237,7 +235,7 @@ uint8_t EightBit::GameBoy::LR35902::srl(uint8_t& f, uint8_t operand) { } uint8_t EightBit::GameBoy::LR35902::bit(uint8_t& f, int n, uint8_t operand) { - auto carry = f & CF; + const auto carry = f & CF; uint8_t discarded = operand; andr(f, discarded, 1 << n); setFlag(f, CF, carry); @@ -290,119 +288,13 @@ void EightBit::GameBoy::LR35902::ccf(uint8_t& a, uint8_t& f) { clearFlag(f, CF, f & CF); } -int EightBit::GameBoy::LR35902::runRasterLines() { - m_enabledLCD = !!(m_bus.IO().peek(IoRegisters::LCDC) & IoRegisters::LcdEnable); - m_bus.IO().resetLY(); - return runRasterLines(Display::RasterHeight * Bus::CyclesPerLine, Display::RasterHeight); -} - -int EightBit::GameBoy::LR35902::runRasterLines(int limit, int lines) { - int count = 0; - int allowed = Bus::CyclesPerLine; - for (int line = 0; line < lines; ++line) { - auto executed = runRasterLine(allowed); - count += executed; - allowed = Bus::CyclesPerLine - (executed - Bus::CyclesPerLine); - } - return count; -} - -int EightBit::GameBoy::LR35902::runRasterLine(int limit) { - - /* - A scanline normally takes 456 clocks (912 clocks in double speed - mode) to complete. A scanline starts in mode 2, then goes to - mode 3 and, when the LCD controller has finished drawing the - line (the timings depend on lots of things) it goes to mode 0. - During lines 144-153 the LCD controller is in mode 1. - Line 153 takes only a few clocks to complete (the exact - timings are below). The rest of the clocks of line 153 are - spent in line 0 in mode 1! - - During mode 0 and mode 1 the CPU can access both VRAM and OAM. - During mode 2 the CPU can only access VRAM, not OAM. - During mode 3 OAM and VRAM can't be accessed. - In GBC mode the CPU can't access Palette RAM(FF69h and FF6Bh) - during mode 3. - A scanline normally takes 456 clocks(912 clocks in double speed mode) to complete. - A scanline starts in mode 2, then goes to mode 3 and , when the LCD controller has - finished drawing the line(the timings depend on lots of things) it goes to mode 0. - During lines 144 - 153 the LCD controller is in mode 1. - Line 153 takes only a few clocks to complete(the exact timings are below). - The rest of the clocks of line 153 are spent in line 0 in mode 1! - */ - - int count = 0; - if (m_enabledLCD) { - - if ((m_bus.IO().peek(IoRegisters::STAT) & Bit6) && (m_bus.IO().peek(IoRegisters::LYC) == m_bus.IO().peek(IoRegisters::LY))) - m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus); - - // Mode 2, OAM unavailable - m_bus.IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::SearchingOamRam); - if (m_bus.IO().peek(IoRegisters::STAT) & Bit5) - m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus); - count += run(80); // ~19us - - // Mode 3, OAM/VRAM unavailable - m_bus.IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::TransferringDataToLcd); - count += run(170); // ~41us - - // Mode 0 - m_bus.IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::HBlank); - if (m_bus.IO().peek(IoRegisters::STAT) & Bit3) - m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus); - count += run(limit - count); // ~48.6us - - m_bus.IO().incrementLY(); - - } else { - count += run(Bus::CyclesPerLine); - } - - return count; -} - -int EightBit::GameBoy::LR35902::runVerticalBlankLines() { - auto lines = Bus::TotalLineCount - Display::RasterHeight; - return runVerticalBlankLines(lines * Bus::CyclesPerLine, lines); -} - -int EightBit::GameBoy::LR35902::runVerticalBlankLines(int limit, int lines) { - - /* - Vertical Blank interrupt is triggered when the LCD - controller enters the VBL screen mode (mode 1, LY=144). - This happens once per frame, so this interrupt is - triggered 59.7 times per second. During this period the - VRAM and OAM can be accessed freely, so it's the best - time to update graphics (for example, use the OAM DMA to - update sprites for next frame, or update tiles to make - animations). - This period lasts 4560 clocks in normal speed mode and - 9120 clocks in double speed mode. That's exactly the - time needed to draw 10 scanlines. - The VBL interrupt isn't triggered when the LCD is - powered off or on, even when it was on VBL mode. - It's only triggered when the VBL period starts. - */ - - if (m_enabledLCD) { - m_bus.IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::VBlank); - if (m_bus.IO().peek(IoRegisters::STAT) & Bit4) - m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::DisplayControlStatus); - m_bus.IO().triggerInterrupt(IoRegisters::Interrupts::VerticalBlank); - } - return runRasterLines(limit, lines); -} - int EightBit::GameBoy::LR35902::singleStep() { int current = 0; - auto interruptEnable = m_bus.peek(IoRegisters::BASE + IoRegisters::IE); - auto interruptFlags = m_bus.IO().peek(IoRegisters::IF); - auto ime = IME(); + const auto interruptEnable = m_bus.peek(IoRegisters::BASE + IoRegisters::IE); + const auto interruptFlags = m_bus.IO().peek(IoRegisters::IF); + const auto ime = IME(); auto masked = interruptEnable & interruptFlags; if (masked) { @@ -438,7 +330,7 @@ int EightBit::GameBoy::LR35902::step() { ExecutingInstruction.fire(*this); m_prefixCB = false; cycles = 0; - auto ran = fetchExecute(); + const auto ran = fetchExecute(); ExecutedInstruction.fire(*this); return ran; } @@ -447,12 +339,12 @@ int EightBit::GameBoy::LR35902::execute(uint8_t opcode) { const auto& decoded = getDecodedOpcode(opcode); - auto x = decoded.x; - auto y = decoded.y; - auto z = decoded.z; + const auto x = decoded.x; + const auto y = decoded.y; + const auto z = decoded.z; - auto p = decoded.p; - auto q = decoded.q; + const auto p = decoded.p; + const auto q = decoded.q; if (m_prefixCB) executeCB(x, y, z, p, q); @@ -756,11 +648,11 @@ void EightBit::GameBoy::LR35902::executeOther(int x, int y, int z, int p, int q) cycles += 3; break; case 5: { // GB: ADD SP,dd - auto before = SP().word; - int8_t value = fetchByte(); - auto result = before + value; + const auto before = SP().word; + const int8_t value = fetchByte(); + const auto result = before + value; SP().word = result; - auto carried = before ^ value ^ (result & Mask16); + const auto carried = before ^ value ^ (result & Mask16); clearFlag(f, ZF | NF); setFlag(f, CF, carried & Bit8); setFlag(f, HC, carried & Bit4); @@ -772,11 +664,11 @@ void EightBit::GameBoy::LR35902::executeOther(int x, int y, int z, int p, int q) cycles += 3; break; case 7: { // GB: LD HL,SP + dd - auto before = SP().word; - int8_t value = fetchByte(); - auto result = before + value; + const auto before = SP().word; + const int8_t value = fetchByte(); + const auto result = before + value; HL().word = result; - auto carried = before ^ value ^ (result & Mask16); + const auto carried = before ^ value ^ (result & Mask16); clearFlag(f, ZF | NF); setFlag(f, CF, carried & Bit8); setFlag(f, HC, carried & Bit4); diff --git a/inc/Bus.h b/inc/Bus.h index 5bd8e92..2101b38 100644 --- a/inc/Bus.h +++ b/inc/Bus.h @@ -49,8 +49,7 @@ namespace EightBit { uint8_t read() { ReadingByte.fire(ADDRESS().word); - auto content = reference(); - return content; + return reference(); } uint8_t read(uint16_t offset) { diff --git a/inc/IntelProcessor.h b/inc/IntelProcessor.h index 29a6dc0..5ecc6b0 100644 --- a/inc/IntelProcessor.h +++ b/inc/IntelProcessor.h @@ -107,13 +107,13 @@ namespace EightBit { static bool calculateHalfCarryAdd(uint8_t before, uint8_t value, int calculation) { static std::array m_halfCarryTableAdd = { { false, false, true, false, true, false, true, true } }; - auto index = buildHalfCarryIndex(before, value, calculation); + const auto index = buildHalfCarryIndex(before, value, calculation); return m_halfCarryTableAdd[index & Mask3]; } static bool calculateHalfCarrySub(uint8_t before, uint8_t value, int calculation) { std::array m_halfCarryTableSub = { { false, true, true, true, false, false, false, true } }; - auto index = buildHalfCarryIndex(before, value, calculation); + const auto index = buildHalfCarryIndex(before, value, calculation); return m_halfCarryTableSub[index & Mask3]; } @@ -180,7 +180,7 @@ namespace EightBit { } bool jrConditional(int conditional) { - auto offset = fetchByte(); + const auto offset = fetchByte(); if (conditional) jr(offset); return conditional != 0; diff --git a/src/InputOutput.cpp b/src/InputOutput.cpp index 7d853b0..7ad30c7 100644 --- a/src/InputOutput.cpp +++ b/src/InputOutput.cpp @@ -3,7 +3,7 @@ uint8_t EightBit::InputOutput::readInputPort(uint8_t port) { OnReadingPort(port); - auto value = input[port]; + const auto value = input[port]; OnReadPort(port); return value; }