From 32d1085ecb0889ba917d604037bed8232ff22668 Mon Sep 17 00:00:00 2001 From: "Adrian.Conlon" Date: Thu, 14 Sep 2017 23:48:49 +0100 Subject: [PATCH] Start properly implementing IO status register read/write. Signed-off-by: Adrian.Conlon --- LR35902/inc/GameBoyBus.h | 131 +++++++++++++++--------- LR35902/src/GameBoyBus.cpp | 199 ++++++++++++++++++++++++++++++++++++- LR35902/src/LR35902.cpp | 3 + 3 files changed, 282 insertions(+), 51 deletions(-) diff --git a/LR35902/inc/GameBoyBus.h b/LR35902/inc/GameBoyBus.h index 2c970e4..530598c 100644 --- a/LR35902/inc/GameBoyBus.h +++ b/LR35902/inc/GameBoyBus.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -27,54 +29,56 @@ namespace EightBit { BASE = 0xFF00, // Port/Mode Registers - P1 = 0x0, - SB = 0x1, - SC = 0x2, - DIV = 0x4, - TIMA = 0x5, - TMA = 0x6, - TAC = 0x7, + P1 = 0x0, // R/W Mask5 + SB = 0x1, // R/W Mask8 + SC = 0x2, // R/W Bit7 | Bit0 + + // Timer control + DIV = 0x4, // R/W Mask8 + TIMA = 0x5, // R/W Mask8 + TMA = 0x6, // R/W Mask8 + TAC = 0x7, // R/W Mask3 // Interrupt Flags - IF = 0xF, - IE = 0xFF, - - // LCD Display Registers - LCDC = 0x40, - STAT = 0x41, - SCY = 0x42, - SCX = 0x43, - LY = 0x44, - LYC = 0x45, - DMA = 0x46, - BGP = 0x47, - OBP0 = 0x48, - OBP1 = 0x49, - WY = 0x4A, - WX = 0x4B, + IF = 0xF, // R/W Mask5 + IE = 0xFF, // R/W Mask5 // Sound Registers - NR10 = 0x10, - NR11 = 0x11, - NR12 = 0x12, - NR13 = 0x13, - NR14 = 0x14, - NR21 = 0x16, - NR22 = 0x17, - NR23 = 0x18, - NR24 = 0x19, - NR30 = 0x1A, - NR31 = 0x1B, - NR32 = 0x1C, - NR33 = 0x1D, - NR34 = 0x1E, - NR41 = 0x20, - NR42 = 0x21, - NR43 = 0x22, - NR44 = 0x23, - NR50 = 0x24, - NR51 = 0x25, - NR52 = 0x26, + NR10 = 0x10, // R/W Mask7 + NR11 = 0x11, // R/W Bit7 | Bit6 + NR12 = 0x12, // R/W Mask8 + NR13 = 0x13, // W 0 + NR14 = 0x14, // R/W Bit6 + NR21 = 0x16, // R/W Bit7 | Bit6 + NR22 = 0x17, // R/W Mask8 + NR23 = 0x18, // W 0 + NR24 = 0x19, // R/W Bit6 + NR30 = 0x1A, // R/W Bit7 + NR31 = 0x1B, // R/W Mask8 + NR32 = 0x1C, // R/W Bit6 | Bit5 + NR33 = 0x1D, // W 0 + NR34 = 0x1E, // R/W Bit6 + NR41 = 0x20, // R/W Mask6 + NR42 = 0x21, // R/W Mask8 + NR43 = 0x22, // R/W Mask8 + NR44 = 0x23, // R/W Bit6 + NR50 = 0x24, // R/W Mask8 + NR51 = 0x25, // R/W Mask8 + NR52 = 0x26, // R/W Mask8 Mask8 + + // LCD Display Registers + LCDC = 0x40, // R/W Mask8 + STAT = 0x41, // R/W Mask7 + SCY = 0x42, // R/W Mask8 + SCX = 0x43, // R/W Mask8 + LY = 0x44, // R Mask8 zeroed + LYC = 0x45, // R/W Mask8 + DMA = 0x46, // W 0 + BGP = 0x47, // R/W Mask8 + OBP0 = 0x48, // R/W Mask8 + OBP1 = 0x49, // R/W Mask8 + WY = 0x4A, // R/W Mask8 + WX = 0x4B, // R/W Mask8 WPRAM_START = 0x30, WPRAM_END = 0x3F, @@ -104,8 +108,8 @@ namespace EightBit { }; enum LcdStatusMode { - CpuAccessAllowed = 0b00, - VerticalBlankingPeriod = 0b01, + HBlank = 0b00, + VBlank = 0b01, SearchingOamRam = 0b10, TransferringDataToLcd = 0b11 }; @@ -186,6 +190,18 @@ namespace EightBit { pokeRegister(LY, 0); } + void transferDma() { + if (m_dmaTransferActive) { + m_oamRam.poke(m_dmaAddress.low, peek(m_dmaAddress.word)); + m_dmaTransferActive = ++m_dmaAddress.low < 0xa0; + } + } + + void updateLcdStatusMode(int mode) { + const auto current = m_ioPorts.peek(STAT) & ~Processor::Mask2; + m_ioPorts.poke(STAT, current | mode); + } + void disableBootRom() { m_disableBootRom = true; } void enableBootRom() { m_disableBootRom = false; } @@ -216,7 +232,7 @@ namespace EightBit { if (address < 0xa000) return m_videoRam.reference(address - 0x8000); if (address < 0xc000) - return m_ramBanks[m_ramBank].reference(address - 0xa000); + return m_ramBanks.size() == 0 ? rom = true, placeDATA(0xff) : m_ramBanks[m_ramBank].reference(address - 0xa000); if (address < 0xe000) return m_lowInternalRam.reference(address - 0xc000); if (address < 0xfe00) @@ -224,7 +240,7 @@ namespace EightBit { if (address < 0xfea0) return m_oamRam.reference(address - 0xfe00); if (address < 0xff00) - return placeDATA(0); + return rom = true, placeDATA(0xff); if (address < 0xff80) return m_ioPorts.reference(address - 0xff00); return m_highInternalRam.reference(address - 0xff80); @@ -258,11 +274,28 @@ namespace EightBit { int m_timerCounter; int m_timerRate; - void Bus_WrittenByte(uint16_t address); + register16_t m_dmaAddress; + bool m_dmaTransferActive; + + std::array m_soundChannelEnabled; + bool m_soundEnabled; void checkTimer(int cycles); void validateCartridgeType(); + + void mask(uint16_t address, uint8_t masking) { + poke(address, peek(address) | ~masking); + } + + void mask(uint8_t masking) { + mask(ADDRESS().word, masking); + } + + void Bus_WritingByte(uint16_t address); + void Bus_WrittenByte(uint16_t address); + void Bus_ReadingByte(uint16_t address); + void Bus_ReadByte(uint16_t address); }; } } \ No newline at end of file diff --git a/LR35902/src/GameBoyBus.cpp b/LR35902/src/GameBoyBus.cpp index 6f0398a..c3b6453 100644 --- a/LR35902/src/GameBoyBus.cpp +++ b/LR35902/src/GameBoyBus.cpp @@ -21,13 +21,25 @@ EightBit::GameBoy::Bus::Bus() m_romBank(1), m_ramBank(0), m_timerCounter(0), - m_timerRate(0) { + m_timerRate(0), + m_dmaTransferActive(false) { + ReadingByte.connect(std::bind(&GameBoy::Bus::Bus_ReadingByte, this, std::placeholders::_1)); + ReadByte.connect(std::bind(&GameBoy::Bus::Bus_ReadByte, this, std::placeholders::_1)); + WritingByte.connect(std::bind(&GameBoy::Bus::Bus_WritingByte, this, std::placeholders::_1)); WrittenByte.connect(std::bind(&GameBoy::Bus::Bus_WrittenByte, this, std::placeholders::_1)); m_divCounter.word = 0xabcc; + m_dmaAddress.word = 0; } void EightBit::GameBoy::Bus::reset() { + pokeRegister(NR52, 0xf1); + m_soundChannelEnabled[0] = true; + m_soundChannelEnabled[1] = false; + m_soundChannelEnabled[2] = false; + m_soundChannelEnabled[3] = false; + m_soundEnabled = true; + pokeRegister(LCDC, DisplayBackground | BackgroundCharacterDataSelection | LcdEnable); m_divCounter.word = 0xabcc; m_timerCounter = 0; @@ -48,6 +60,118 @@ void EightBit::GameBoy::Bus::loadGameRom(const std::string& path) { validateCartridgeType(); } +void EightBit::GameBoy::Bus::Bus_ReadingByte(const uint16_t address) { + auto io = ((address >= BASE) && (address < 0xff80)) || (address == 0xffff); + if (io) { + auto ioRegister = address - BASE; + switch (ioRegister) { + + // Port/Mode Registers + case P1: + mask(Processor::Mask5); + break; + case SB: + break; + case SC: + mask(Processor::Bit7 | Processor::Bit0); + break; + + // Timer control + case DIV: + case TIMA: + case TMA: + break; + case TAC: + mask(Processor::Mask3); + break; + + // Interrupt Flags + case IF: + mask(Processor::Mask5); + break; + case IE: + // Only the bottom 5 bits are used, + // but all are available for use. + break; + + // Sound Registers + case NR10: + mask(Processor::Mask7); + break; + case NR11: + case NR12: + case NR13: + case NR14: + case NR21: + case NR22: + case NR23: + case NR24: + break; + case NR30: + mask(Processor::Bit7); + break; + case NR31: + break; + case NR32: + mask(Processor::Bit6 | Processor::Bit5); + break; + case NR33: + case NR34: + break; + case NR41: + mask(Processor::Mask6); + break; + case NR42: + case NR43: + break; + case NR44: + mask(Processor::Bit6 | Processor::Bit7); + break; + case NR50: + case NR51: + break; + case NR52: + pokeRegister(NR52, + m_soundChannelEnabled[0] + | (m_soundChannelEnabled[1] << 1) + | (m_soundChannelEnabled[2] << 2) + | (m_soundChannelEnabled[3] << 3) + | Processor::Bit4 | Processor::Bit5 | Processor::Bit6 + | (m_soundEnabled << 7) + ); + break; + + // LCD Display Registers + case LCDC: + break; + case STAT: + mask(Processor::Mask7); + break; + case SCY: + case SCX: + case LY: + case LYC: + case DMA: + case BGP: + case OBP0: + case OBP1: + case WY: + case WX: + break; + + default: + mask(0); + break; + } + } +} + +void EightBit::GameBoy::Bus::Bus_ReadByte(const uint16_t address) { +} + +void EightBit::GameBoy::Bus::Bus_WritingByte(const uint16_t address) { +} + void EightBit::GameBoy::Bus::Bus_WrittenByte(const uint16_t address) { const auto value = DATA(); @@ -121,21 +245,37 @@ void EightBit::GameBoy::Bus::Bus_WrittenByte(const uint16_t address) { case BASE + NR12: case BASE + NR13: case BASE + NR14: + case BASE + NR21: case BASE + NR22: + case BASE + NR23: case BASE + NR24: + break; case BASE + NR30: + break; + case BASE + NR31: + case BASE + NR32: + case BASE + NR33: + case BASE + NR34: + case BASE + NR41: case BASE + NR42: + case BASE + NR43: case BASE + NR44: case BASE + NR50: case BASE + NR51: + break; case BASE + NR52: + m_soundEnabled = value & Processor::Bit7; break; case BASE + LCDC: case BASE + STAT: case BASE + SCY: case BASE + SCX: + break; case BASE + DMA: + m_dmaAddress.high = value; + m_dmaAddress.low = 0; + m_dmaTransferActive = true; break; case BASE + LY: // R/O EightBit::Bus::reference() = 0; @@ -152,7 +292,9 @@ void EightBit::GameBoy::Bus::Bus_WrittenByte(const uint16_t address) { break; default: - if ((address > BASE) && (address < (BASE + 0x4c))) + if ((address >= (BASE + WPRAM_START)) && (address <= (BASE + WPRAM_END))) + ; // Wave form data + else if ((address > BASE) && (address < (BASE + 0x4c))) assert(false); } } @@ -177,6 +319,7 @@ void EightBit::GameBoy::Bus::validateCartridgeType() { m_rom = m_banked = m_ram = m_battery = false; + // ROM type switch (m_gameRomBanks[0].peek(0x147)) { case ROM: m_rom = true; @@ -193,4 +336,56 @@ void EightBit::GameBoy::Bus::validateCartridgeType() { default: throw std::domain_error("Unhandled cartridge ROM type"); } + + // ROM size + { + int gameRomBanks = -1; + int romSizeSpecification = m_gameRomBanks[0].peek(0x148); + switch (romSizeSpecification) { + case 0x52: + gameRomBanks = 72; + break; + case 0x53: + gameRomBanks = 80; + break; + case 0x54: + gameRomBanks = 96; + break; + default: + if (romSizeSpecification > 6) + throw std::domain_error("Invalid ROM size specification"); + gameRomBanks = 1 << (romSizeSpecification + 1); + if (gameRomBanks != m_gameRomBanks.size()) + throw std::domain_error("ROM size specification mismatch"); + } + + // RAM size + { + auto ramSizeSpecification = m_gameRomBanks[0].peek(0x149); + switch (ramSizeSpecification) { + case 0: + break; + case 1: + m_ramBanks.resize(1); + m_ramBanks[0] = Ram(2 * 1024); + break; + case 2: + m_ramBanks.resize(1); + m_ramBanks[0] = Ram(8 * 1024); + break; + case 3: + m_ramBanks.resize(4); + for (int i = 0; i < 4; ++i) + m_ramBanks[i] = Ram(8 * 1024); + break; + case 4: + m_ramBanks.resize(16); + for (int i = 0; i < 16; ++i) + m_ramBanks[i] = Ram(8 * 1024); + break; + default: + throw std::domain_error("Invalid RAM size specification"); + } + } + } } diff --git a/LR35902/src/LR35902.cpp b/LR35902/src/LR35902.cpp index ae49498..f899c9a 100644 --- a/LR35902/src/LR35902.cpp +++ b/LR35902/src/LR35902.cpp @@ -337,6 +337,7 @@ int EightBit::GameBoy::LR35902::runRasterLines(int limit) { int EightBit::GameBoy::LR35902::runRasterLine() { const auto count = run(cyclesPerLine()); + m_bus.updateLcdStatusMode(Bus::LcdStatusMode::HBlank); m_bus.incrementLY(); if ((m_bus.peekRegister(Bus::STAT) & Bit6) && (m_bus.peekRegister(Bus::LYC) == m_bus.peekRegister(Bus::LY))) m_bus.triggerInterrupt(Bus::Interrupts::DisplayControlStatus); @@ -344,6 +345,7 @@ int EightBit::GameBoy::LR35902::runRasterLine() { } int EightBit::GameBoy::LR35902::runVerticalBlankLines() { + m_bus.updateLcdStatusMode(Bus::LcdStatusMode::VBlank); m_bus.triggerInterrupt(Bus::Interrupts::VerticalBlank); return runRasterLines(Bus::TotalLineCount - Display::RasterHeight); } @@ -381,6 +383,7 @@ int EightBit::GameBoy::LR35902::singleStep() { } m_bus.checkTimers(current); + m_bus.transferDma(); return current; }