Files
EightBit/LR35902/src/GameBoyBus.cpp
Adrian.Conlon 09f7fe5dfe Couple of small "documentation" changes.
Signed-off-by: Adrian.Conlon <adrian.conlon@gmail.com>
2017-09-22 22:09:57 +01:00

486 lines
10 KiB
C++

#include "stdafx.h"
#include "GameBoyBus.h"
EightBit::GameBoy::Bus::Bus()
: m_bootRom(0x100),
m_gameRomBanks(1),
m_videoRam(0x2000),
m_ramBanks(0),
m_lowInternalRam(0x2000),
m_oamRam(0xa0),
m_ioPorts(0x80),
m_highInternalRam(0x80),
m_disableBootRom(false),
m_disableGameRom(false),
m_rom(false),
m_banked(false),
m_ram(false),
m_battery(false),
m_higherRomBank(true),
m_ramBankSwitching(false),
m_romBank(1),
m_ramBank(0),
m_timerCounter(0),
m_timerRate(0),
m_dmaTransferActive(false),
m_scanP15(false),
m_scanP14(false),
m_p15(true),
m_p14(true),
m_p13(true),
m_p12(true),
m_p11(true),
m_p10(true),
m_audio(CyclesPerSecond) {
ReadingByte.connect(std::bind(&GameBoy::Bus::Bus_ReadingByte, 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() {
audio().reset();
assert(audio().zeroed());
poke(BASE + NR52, 0xf1);
poke(BASE + LCDC, DisplayBackground | BackgroundCharacterDataSelection | LcdEnable);
m_divCounter.word = 0xabcc;
m_timerCounter = 0;
}
void EightBit::GameBoy::Bus::loadBootRom(const std::string& path) {
m_bootRom.load(path);
}
void EightBit::GameBoy::Bus::loadGameRom(const std::string& path) {
const auto bankSize = 0x4000;
m_gameRomBanks.resize(1);
const auto size = m_gameRomBanks[0].load(path, 0, 0, bankSize);
const auto banks = size / bankSize;
m_gameRomBanks.resize(banks);
for (int bank = 1; bank < banks; ++bank)
m_gameRomBanks[bank].load(path, 0, bankSize * bank, bankSize);
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: {
auto p14 = m_scanP14 && !m_p14;
auto p15 = m_scanP15 && !m_p15;
auto live = p14 || p15;
auto p10 = live && !m_p10;
auto p11 = live && !m_p11;
auto p12 = live && !m_p12;
auto p13 = live && !m_p13;
pokeRegister(P1,
((int)!p10)
| ((int)!p11 << 1)
| ((int)!p12 << 2)
| ((int)!p13 << 3)
| Processor::Bit4 | Processor::Bit5
| Processor::Bit6 | Processor::Bit7);
}
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:
poke(address, audio().toNR10());
break;
case NR11:
poke(address, audio().toNR11());
break;
case NR12:
poke(address, audio().toNR12());
break;
case NR13:
poke(address, audio().toNR13());
break;
case NR14:
poke(address, audio().toNR14());
break;
case NR21:
poke(address, audio().toNR21());
break;
case NR22:
poke(address, audio().toNR22());
break;
case NR23:
poke(address, audio().toNR23());
break;
case NR24:
poke(address, audio().toNR24());
break;
case NR30:
poke(address, audio().toNR30());
break;
case NR31:
poke(address, audio().toNR31());
break;
case NR32:
poke(address, audio().toNR32());
break;
case NR33:
poke(address, audio().toNR33());
break;
case NR34:
poke(address, audio().toNR34());
break;
case NR41:
poke(address, audio().toNR41());
break;
case NR42:
poke(address, audio().toNR42());
break;
case NR43:
poke(address, audio().toNR43());
break;
case NR44:
poke(address, audio().toNR44());
break;
case NR50:
poke(address, audio().toNR50());
break;
case NR51:
poke(address, audio().toNR51());
break;
case NR52:
poke(address, audio().toNR52());
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:
if ((address >= (BASE + WAVE_PATTERN_RAM_START)) && (address <= (BASE + WAVE_PATTERN_RAM_END)))
poke(address, audio().packedWaveDatum(address - WAVE_PATTERN_RAM_START));
else
mask(0);
break;
}
}
}
void EightBit::GameBoy::Bus::Bus_WrittenByte(const uint16_t address) {
const auto value = DATA();
auto handled = false;
switch (address & 0xe000) {
case 0x0000:
// Register 0: RAMCS gate data
if (m_ram) {
assert(false);
}
break;
case 0x2000:
// Register 1: ROM bank code
if (m_banked && m_higherRomBank) {
assert((address >= 0x2000) && (address < 0x4000));
assert((value > 0) && (value < 0x20));
m_romBank = value & Processor::Mask5;
handled = true;
}
break;
case 0x4000:
// Register 2: ROM bank selection
if (m_banked) {
assert(false);
}
case 0x6000:
// Register 3: ROM/RAM change
if (m_banked) {
switch (value & Processor::Mask1) {
case 0:
m_higherRomBank = true;
m_ramBankSwitching = false;
break;
case 1:
m_higherRomBank = false;
m_ramBankSwitching = true;
break;
default:
__assume(0);
}
handled = true;
}
break;
}
if (!handled) {
switch (address) {
case BASE + P1:
m_scanP14 = (value & Processor::Bit4) == 0;
m_scanP15 = (value & Processor::Bit5) == 0;
break;
case BASE + SB: // R/W
case BASE + SC: // R/W
break;
case BASE + DIV: // R/W
EightBit::Bus::reference() = 0;
m_timerCounter = m_divCounter.word = 0;
break;
case BASE + TIMA: // R/W
case BASE + TMA: // R/W
break;
case BASE + TAC: // R/W
m_timerRate = timerClockTicks();
break;
case BASE + IF: // R/W
break;
case BASE + NR10: // Sound mode 1 register: Sweep
audio().fromNR10(value);
break;
case BASE + NR11: // Sound mode 1 register: Sound length / Wave pattern duty
audio().fromNR11(value);
break;
case BASE + NR12: // Sound mode 1 register: Envelope
audio().fromNR12(value);
break;
case BASE + NR13: // Sound mode 1 register: Frequency lo
audio().fromNR13(value);
break;
case BASE + NR14: // Sound mode 1 register: Frequency hi
audio().fromNR14(value);
std::cout << "Voice one frequency: " << audio().voice1()->hertz() << std::endl;
break;
case BASE + NR21: // Sound mode 2 register: Sound length / Wave pattern duty
audio().fromNR21(value);
break;
case BASE + NR22: // Sound mode 2 register: Envelope
audio().fromNR22(value);
break;
case BASE + NR23: // Sound mode 2 register: Frequency lo
audio().fromNR23(value);
break;
case BASE + NR24: // Sound mode 2 register: Frequency hi
audio().fromNR24(value);
break;
case BASE + NR30: // Sound mode 3 register: Sound on/off
audio().fromNR30(value);
break;
case BASE + NR31: // Sound mode 3 register: Sound length
audio().fromNR31(value);
break;
case BASE + NR32: // Sound mode 3 register: Select output level
audio().fromNR32(value);
break;
case BASE + NR33: // Sound mode 3 register: Frequency lo
audio().fromNR33(value);
break;
case BASE + NR34: // Sound mode 3 register: Frequency hi
audio().fromNR34(value);
break;
case BASE + NR41: // Sound mode 4 register: Sound length
audio().fromNR41(value);
break;
case BASE + NR42: // Sound mode 4 register: Envelope
audio().fromNR42(value);
break;
case BASE + NR43: // Sound mode 4 register: Polynomial counter
audio().fromNR43(value);
break;
case BASE + NR44: // Sound mode 4 register: counter/consecutive; inital
audio().fromNR44(value);
break;
case BASE + NR50: // Channel control: on-off/volume
audio().fromNR50(value);
break;
case BASE + NR51: // Selection of Sound output terminal
audio().fromNR51(value);
break;
case BASE + NR52: // Sound on/off
audio().fromNR52(value);
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;
break;
case BASE + BGP:
case BASE + OBP0:
case BASE + OBP1:
case BASE + WY:
case BASE + WX:
break;
case BASE + BOOT_DISABLE:
m_disableBootRom = value != 0;
break;
default:
if ((address >= (BASE + WAVE_PATTERN_RAM_START)) && (address <= (BASE + WAVE_PATTERN_RAM_END)))
audio().setPackedWaveDatum(address - WAVE_PATTERN_RAM_START, value);
}
}
}
void EightBit::GameBoy::Bus::checkTimers(int cycles) {
incrementDIV(cycles);
checkTimer(cycles);
}
void EightBit::GameBoy::Bus::checkTimer(int cycles) {
if (timerEnabled()) {
m_timerCounter -= cycles;
if (m_timerCounter <= 0) {
m_timerCounter += m_timerRate;
incrementTIMA();
}
}
}
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;
break;
case ROM_MBC1:
m_rom = m_banked = true;
break;
case ROM_MBC1_RAM:
m_rom = m_banked = m_ram = true;
break;
case ROM_MBC1_RAM_BATTERY:
m_rom = m_banked = m_ram = m_battery = true;
break;
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");
}
}
}
}