Split the bus into IoRegisters and "the rest"

Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
This commit is contained in:
Adrian Conlon 2017-10-05 11:24:36 +01:00
parent e8715b941b
commit 156cb66904
9 changed files with 575 additions and 531 deletions

View File

@ -1,13 +1,13 @@
#pragma once
#include <array>
#include <tuple>
#include <cstdint>
#include <vector>
#include <Rom.h>
#include <Ram.h>
#include <Bus.h>
#include <Processor.h>
#include <Signal.h>
#include "IoRegisters.h"
namespace EightBit {
namespace GameBoy {
@ -30,257 +30,25 @@ namespace EightBit {
RomPageSize = 0x4000
};
enum {
BASE = 0xFF00,
// Port/Mode Registers
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, // R/W Mask5
IE = 0xFF, // R/W Mask5
// Sound Registers
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
WAVE_PATTERN_RAM_START = 0x30,
WAVE_PATTERN_RAM_END = 0x3F,
// 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
// Boot rom control
BOOT_DISABLE = 0x50,
};
// IF and IE flags
enum Interrupts {
VerticalBlank = Processor::Bit0, // VBLANK
DisplayControlStatus = Processor::Bit1, // LCDC Status
TimerOverflow = Processor::Bit2, // Timer Overflow
SerialTransfer = Processor::Bit3, // Serial Transfer
KeypadPressed = Processor::Bit4 // Hi-Lo transition of P10-P13
};
enum LcdcControl {
DisplayBackground = Processor::Bit0,
ObjectEnable = Processor::Bit1,
ObjectBlockCompositionSelection = Processor::Bit2,
BackgroundCodeAreaSelection = Processor::Bit3,
BackgroundCharacterDataSelection = Processor::Bit4,
WindowEnable = Processor::Bit5,
WindowCodeAreaSelection = Processor::Bit6,
LcdEnable = Processor::Bit7
};
enum LcdStatusMode {
HBlank = 0b00,
VBlank = 0b01,
SearchingOamRam = 0b10,
TransferringDataToLcd = 0b11
};
Bus();
Signal<int> DisplayStatusModeUpdated;
Ram& VRAM() { return m_videoRam; }
Ram& OAMRAM() { return m_oamRam; }
IoRegisters& IO() { return m_ioPorts; }
void reset();
void triggerInterrupt(int cause) {
pokeRegister(IF, peekRegister(IF) | cause);
}
void writeRegister(int offset, uint8_t content) {
write(BASE + offset, content);
}
void pokeRegister(int offset, uint8_t content) {
poke(BASE + offset, content);
}
uint8_t readRegister(int offset) {
return read(BASE + offset);
}
uint8_t peekRegister(int offset) {
return peek(BASE + offset);
}
void checkTimers(int cycles);
int timerClockTicks() {
switch (timerClock()) {
case 0b00:
return 1024; // 4.096 Khz
case 0b01:
return 16; // 262.144 Khz
case 0b10:
return 64; // 65.536 Khz
case 0b11:
return 256; // 16.384 Khz
default:
UNREACHABLE;
}
throw std::domain_error("Invalid timer clock specification");
}
int timerClock() {
return peekRegister(TAC) & Processor::Mask2;
}
bool timerEnabled() {
return !timerDisabled();
}
bool timerDisabled() {
return (peekRegister(TAC) & Processor::Bit2) == 0;
}
void incrementDIV(int cycles) {
m_divCounter.word += cycles;
pokeRegister(DIV, m_divCounter.high);
}
void incrementTIMA() {
uint16_t updated = peekRegister(TIMA) + 1;
if (updated & Processor::Bit8) {
triggerInterrupt(TimerOverflow);
updated = peekRegister(TMA);
}
pokeRegister(TIMA, updated & Processor::Mask8);
}
void incrementLY() {
pokeRegister(LY, (peekRegister(LY) + 1) % TotalLineCount);
}
void resetLY() {
pokeRegister(LY, 0);
}
void transferDma() {
if (m_dmaTransferActive) {
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);
DisplayStatusModeUpdated.fire(mode);
}
void disableBootRom() { m_disableBootRom = true; }
void enableBootRom() { m_disableBootRom = false; }
void disableGameRom() { m_disableGameRom = true; }
void enableGameRom() { m_disableGameRom = false; }
bool bootRomDisabled() const { return m_disableBootRom; }
bool bootRomEnabled() const { return !bootRomDisabled(); }
bool gameRomDisabled() const { return m_disableGameRom; }
bool gameRomEnabled() const { return !gameRomDisabled(); }
void loadBootRom(const std::string& path);
void loadGameRom(const std::string& path);
void pressRight() { m_p14 = m_p10 = false; triggerKeypadInterrupt(); }
void releaseRight() { m_p14 = m_p10 = true; }
void pressLeft() { m_p14 = m_p11 = false, triggerKeypadInterrupt(); }
void releaseLeft() { m_p14 = m_p11 = true; }
void pressUp() { m_p14 = m_p12 = false, triggerKeypadInterrupt(); }
void releaseUp() { m_p14 = m_p12 = true; }
void pressDown() { m_p14 = m_p13 = false, triggerKeypadInterrupt(); }
void releaseDown() { m_p14 = m_p13 = true; }
void pressA() { m_p15 = m_p10 = false, triggerKeypadInterrupt(); }
void releaseA() { m_p15 = m_p10 = true; }
void pressB() { m_p15 = m_p11 = false, triggerKeypadInterrupt(); }
void releaseB() { m_p15 = m_p11 = true; }
void pressSelect() { m_p15 = m_p12 = false, triggerKeypadInterrupt(); }
void releaseSelect() { m_p15 = m_p12 = true; }
void pressStart() { m_p15 = m_p13 = false, triggerKeypadInterrupt(); }
void releaseStart() { m_p15 = m_p13 = true; }
protected:
virtual uint8_t& reference(uint16_t address, bool& rom) {
rom = true;
if ((address < 0x100) && bootRomEnabled())
return m_bootRom.reference(address);
if ((address < 0x4000) && gameRomEnabled())
return m_gameRomBanks[0].reference(address);
if ((address < 0x8000) && gameRomEnabled())
return m_gameRomBanks[m_romBank].reference(address - 0x4000);
rom = false;
if (address < 0xa000)
return VRAM().reference(address - 0x8000);
if (address < 0xc000)
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)
return m_lowInternalRam.reference(address - 0xe000); // Low internal RAM mirror
if (address < 0xfea0)
return OAMRAM().reference(address - 0xfe00);
if (address < 0xff00)
return rom = true, placeDATA(0xff);
if (address < 0xff80)
return m_ioPorts.reference(address - 0xff00);
return m_highInternalRam.reference(address - 0xff80);
}
virtual uint8_t& reference(uint16_t address, bool& rom);
private:
Rom m_bootRom; // 0x0000 - 0x00ff
@ -289,10 +57,9 @@ namespace EightBit {
std::vector<Ram> m_ramBanks; // 0xa000 - 0xbfff (switchable)
Ram m_lowInternalRam; // 0xc000 - 0xdfff (mirrored at 0xe000)
Ram m_oamRam; // 0xfe00 - 0xfe9f
Ram m_ioPorts; // 0xff00 - 0xff7f
IoRegisters m_ioPorts; // 0xff00 - 0xff7f
Ram m_highInternalRam; // 0xff80 - 0xffff
bool m_disableBootRom;
bool m_disableGameRom;
bool m_rom;
@ -306,41 +73,9 @@ namespace EightBit {
int m_romBank;
int m_ramBank;
register16_t m_divCounter;
int m_timerCounter;
int m_timerRate;
register16_t m_dmaAddress;
bool m_dmaTransferActive;
bool m_scanP15;
bool m_scanP14;
bool m_p15; // misc keys
bool m_p14; // direction keys
bool m_p13; // down/start
bool m_p12; // up/select
bool m_p11; // left/b
bool m_p10; // right/a
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 triggerKeypadInterrupt() {
triggerInterrupt(Interrupts::KeypadPressed);
}
void Bus_WrittenByte(uint16_t address);
void Bus_ReadingByte(uint16_t address);
};
}
}

202
LR35902/inc/IoRegisters.h Normal file
View File

@ -0,0 +1,202 @@
#pragma once
#include <cstdint>
#include <Ram.h>
#include <Processor.h>
#include <Register.h>
#include <Signal.h>
namespace EightBit {
namespace GameBoy {
class Bus;
class IoRegisters : public EightBit::Ram {
public:
enum {
BASE = 0xFF00,
// Port/Mode Registers
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, // R/W Mask5
IE = 0xFF, // R/W Mask5
// Sound Registers
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
WAVE_PATTERN_RAM_START = 0x30,
WAVE_PATTERN_RAM_END = 0x3F,
// 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
// Boot rom control
BOOT_DISABLE = 0x50,
};
// IF and IE flags
enum Interrupts {
VerticalBlank = Processor::Bit0, // VBLANK
DisplayControlStatus = Processor::Bit1, // LCDC Status
TimerOverflow = Processor::Bit2, // Timer Overflow
SerialTransfer = Processor::Bit3, // Serial Transfer
KeypadPressed = Processor::Bit4 // Hi-Lo transition of P10-P13
};
enum LcdcControl {
DisplayBackground = Processor::Bit0,
ObjectEnable = Processor::Bit1,
ObjectBlockCompositionSelection = Processor::Bit2,
BackgroundCodeAreaSelection = Processor::Bit3,
BackgroundCharacterDataSelection = Processor::Bit4,
WindowEnable = Processor::Bit5,
WindowCodeAreaSelection = Processor::Bit6,
LcdEnable = Processor::Bit7
};
enum LcdStatusMode {
HBlank = 0b00,
VBlank = 0b01,
SearchingOamRam = 0b10,
TransferringDataToLcd = 0b11
};
IoRegisters(Bus& bus);
Signal<int> DisplayStatusModeUpdated;
void reset();
void transferDma();
void triggerInterrupt(int cause) {
poke(IF, peek(IF) | cause);
}
void checkTimers(int cycles);
int timerClockTicks();
int timerClock();
bool timerEnabled();
bool timerDisabled();
void incrementDIV(int cycles);
void incrementTIMA();
void incrementLY();
void resetLY();
void updateLcdStatusMode(int mode);
void disableBootRom() { m_disableBootRom = true; }
void enableBootRom() { m_disableBootRom = false; }
bool bootRomDisabled() const { return m_disableBootRom; }
bool bootRomEnabled() const { return !bootRomDisabled(); }
void pressRight() { m_p14 = m_p10 = false; triggerKeypadInterrupt(); }
void releaseRight() { m_p14 = m_p10 = true; }
void pressLeft() { m_p14 = m_p11 = false, triggerKeypadInterrupt(); }
void releaseLeft() { m_p14 = m_p11 = true; }
void pressUp() { m_p14 = m_p12 = false, triggerKeypadInterrupt(); }
void releaseUp() { m_p14 = m_p12 = true; }
void pressDown() { m_p14 = m_p13 = false, triggerKeypadInterrupt(); }
void releaseDown() { m_p14 = m_p13 = true; }
void pressA() { m_p15 = m_p10 = false, triggerKeypadInterrupt(); }
void releaseA() { m_p15 = m_p10 = true; }
void pressB() { m_p15 = m_p11 = false, triggerKeypadInterrupt(); }
void releaseB() { m_p15 = m_p11 = true; }
void pressSelect() { m_p15 = m_p12 = false, triggerKeypadInterrupt(); }
void releaseSelect() { m_p15 = m_p12 = true; }
void pressStart() { m_p15 = m_p13 = false, triggerKeypadInterrupt(); }
void releaseStart() { m_p15 = m_p13 = true; }
private:
Bus& m_bus;
bool m_disableBootRom;
register16_t m_divCounter;
int m_timerCounter;
int m_timerRate;
register16_t m_dmaAddress;
bool m_dmaTransferActive;
bool m_scanP15;
bool m_scanP14;
bool m_p15; // misc keys
bool m_p14; // direction keys
bool m_p13; // down/start
bool m_p12; // up/select
bool m_p11; // left/b
bool m_p10; // right/a
void checkTimer(int cycles);
void mask(uint16_t address, uint8_t masking) {
poke(address, peek(address) | ~masking);
}
void triggerKeypadInterrupt() {
triggerInterrupt(Interrupts::KeypadPressed);
}
void Bus_WrittenByte(uint16_t address);
void Bus_ReadingByte(uint16_t address);
};
}
}

View File

@ -579,104 +579,104 @@ std::string EightBit::GameBoy::Disassembler::io(uint8_t value) {
switch (value) {
// Port/Mode Registers
case Bus::P1:
case IoRegisters::P1:
return "P1";
case Bus::SB:
case IoRegisters::SB:
return "SB";
case Bus::SC:
case IoRegisters::SC:
return "SC";
case Bus::DIV:
case IoRegisters::DIV:
return "DIV";
case Bus::TIMA:
case IoRegisters::TIMA:
return "TIMA";
case Bus::TMA:
case IoRegisters::TMA:
return "TMA";
case Bus::TAC:
case IoRegisters::TAC:
return "TAC";
// Interrupt Flags
case Bus::IF:
case IoRegisters::IF:
return "IF";
case Bus::IE:
case IoRegisters::IE:
return "IE";
// LCD Display Registers
case Bus::LCDC:
case IoRegisters::LCDC:
return "LCDC";
case Bus::STAT:
case IoRegisters::STAT:
return "STAT";
case Bus::SCY:
case IoRegisters::SCY:
return "SCY";
case Bus::SCX:
case IoRegisters::SCX:
return "SCX";
case Bus::LY:
case IoRegisters::LY:
return "LY";
case Bus::LYC:
case IoRegisters::LYC:
return "LYC";
case Bus::DMA:
case IoRegisters::DMA:
return "DMA";
case Bus::BGP:
case IoRegisters::BGP:
return "BGP";
case Bus::OBP0:
case IoRegisters::OBP0:
return "OBP0";
case Bus::OBP1:
case IoRegisters::OBP1:
return "OBP1";
case Bus::WY:
case IoRegisters::WY:
return "WY";
case Bus::WX:
case IoRegisters::WX:
return "WX";
// Sound Registers
case Bus::NR10:
case IoRegisters::NR10:
return "NR10";
case Bus::NR11:
case IoRegisters::NR11:
return "NR11";
case Bus::NR12:
case IoRegisters::NR12:
return "NR12";
case Bus::NR13:
case IoRegisters::NR13:
return "NR13";
case Bus::NR14:
case IoRegisters::NR14:
return "NR14";
case Bus::NR21:
case IoRegisters::NR21:
return "NR21";
case Bus::NR22:
case IoRegisters::NR22:
return "NR22";
case Bus::NR23:
case IoRegisters::NR23:
return "NR23";
case Bus::NR24:
case IoRegisters::NR24:
return "NR24";
case Bus::NR30:
case IoRegisters::NR30:
return "NR30";
case Bus::NR31:
case IoRegisters::NR31:
return "NR31";
case Bus::NR32:
case IoRegisters::NR32:
return "NR32";
case Bus::NR33:
case IoRegisters::NR33:
return "NR33";
case Bus::NR34:
case IoRegisters::NR34:
return "NR34";
case Bus::NR41:
case IoRegisters::NR41:
return "NR41";
case Bus::NR42:
case IoRegisters::NR42:
return "NR42";
case Bus::NR43:
case IoRegisters::NR43:
return "NR43";
case Bus::NR44:
case IoRegisters::NR44:
return "NR44";
case Bus::NR50:
case IoRegisters::NR50:
return "NR50";
case Bus::NR51:
case IoRegisters::NR51:
return "NR51";
case Bus::NR52:
case IoRegisters::NR52:
return "NR52";
case Bus::WAVE_PATTERN_RAM_START:
case IoRegisters::WAVE_PATTERN_RAM_START:
return "WAVE_PATTERN_RAM_START";
case Bus::WAVE_PATTERN_RAM_END:
case IoRegisters::WAVE_PATTERN_RAM_END:
return "WAVE_PATTERN_RAM_END";
// Boot rom control
case Bus::BOOT_DISABLE:
case IoRegisters::BOOT_DISABLE:
return "BOOT_DISABLE";
default:

View File

@ -24,20 +24,20 @@ void EightBit::GameBoy::Display::initialise() {
}
void EightBit::GameBoy::Display::render() {
m_scanLine = m_bus.peekRegister(Bus::LY);
m_scanLine = m_bus.IO().peek(IoRegisters::LY);
if (m_scanLine < RasterHeight) {
m_control = m_bus.peekRegister(Bus::LCDC);
if (m_control & Bus::LcdEnable) {
if (m_control & Bus::DisplayBackground)
m_control = m_bus.IO().peek(IoRegisters::LCDC);
if (m_control & IoRegisters::LcdEnable) {
if (m_control & IoRegisters::DisplayBackground)
renderBackground();
if (m_control & Bus::ObjectEnable)
if (m_control & IoRegisters::ObjectEnable)
renderObjects();
}
}
}
std::array<int, 4> EightBit::GameBoy::Display::createPalette(const int address) {
const auto raw = m_bus.peekRegister(address);
const auto raw = m_bus.IO().peek(address);
std::array<int, 4> palette;
palette[0] = raw & 0b11;
palette[1] = (raw & 0b1100) >> 2;
@ -47,12 +47,12 @@ std::array<int, 4> EightBit::GameBoy::Display::createPalette(const int address)
}
void EightBit::GameBoy::Display::renderObjects() {
const auto objBlockHeight = (m_control & Bus::ObjectBlockCompositionSelection) ? 16 : 8;
const auto objBlockHeight = (m_control & IoRegisters::ObjectBlockCompositionSelection) ? 16 : 8;
renderObjects(objBlockHeight);
}
void EightBit::GameBoy::Display::loadObjectAttributes() {
const auto objBlockHeight = (m_control & Bus::ObjectBlockCompositionSelection) ? 16 : 8;
const auto objBlockHeight = (m_control & IoRegisters::ObjectBlockCompositionSelection) ? 16 : 8;
for (int i = 0; i < 40; ++i) {
m_objectAttributes[i] = ObjectAttribute(m_oam, 4 * i, objBlockHeight);
}
@ -61,8 +61,8 @@ void EightBit::GameBoy::Display::loadObjectAttributes() {
void EightBit::GameBoy::Display::renderObjects(int objBlockHeight) {
std::vector<std::array<int, 4>> palettes(2);
palettes[0] = createPalette(Bus::OBP0);
palettes[1] = createPalette(Bus::OBP1);
palettes[0] = createPalette(IoRegisters::OBP0);
palettes[1] = createPalette(IoRegisters::OBP1);
for (int i = 0; i < 40; ++i) {
@ -94,20 +94,20 @@ void EightBit::GameBoy::Display::renderObjects(int objBlockHeight) {
void EightBit::GameBoy::Display::renderBackground() {
auto palette = createPalette(Bus::BGP);
auto palette = createPalette(IoRegisters::BGP);
const auto window = (m_control & Bus::WindowEnable) != 0;
const auto bgArea = (m_control & Bus::BackgroundCodeAreaSelection) ? 0x1c00 : 0x1800;
const auto bgCharacters = (m_control & Bus::BackgroundCharacterDataSelection) ? 0 : 0x800;
const auto window = (m_control & IoRegisters::WindowEnable) != 0;
const auto bgArea = (m_control & IoRegisters::BackgroundCodeAreaSelection) ? 0x1c00 : 0x1800;
const auto bgCharacters = (m_control & IoRegisters::BackgroundCharacterDataSelection) ? 0 : 0x800;
const auto wx = m_bus.peekRegister(Bus::WX);
const auto wy = m_bus.peekRegister(Bus::WY);
const auto wx = m_bus.IO().peek(IoRegisters::WX);
const auto wy = m_bus.IO().peek(IoRegisters::WY);
const auto offsetX = window ? wx - 7 : 0;
const auto offsetY = window ? wy : 0;
const auto scrollX = m_bus.peekRegister(Bus::SCX);
const auto scrollY = m_bus.peekRegister(Bus::SCY);
const auto scrollX = m_bus.IO().peek(IoRegisters::SCX);
const auto scrollY = m_bus.IO().peek(IoRegisters::SCY);
renderBackground(bgArea, bgCharacters, offsetX - scrollX, offsetY - scrollY, palette);
}

View File

@ -8,9 +8,8 @@ EightBit::GameBoy::Bus::Bus()
m_ramBanks(0),
m_lowInternalRam(0x2000),
m_oamRam(0xa0),
m_ioPorts(0x80),
m_ioPorts(*this),
m_highInternalRam(0x80),
m_disableBootRom(false),
m_disableGameRom(false),
m_rom(false),
m_banked(false),
@ -19,30 +18,12 @@ EightBit::GameBoy::Bus::Bus()
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) {
ReadingByte.connect(std::bind(&GameBoy::Bus::Bus_ReadingByte, this, std::placeholders::_1));
m_ramBank(0) {
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);
pokeRegister(LCDC, DisplayBackground | BackgroundCharacterDataSelection | LcdEnable);
m_divCounter.word = 0xabcc;
m_timerCounter = 0;
m_ioPorts.reset();
}
void EightBit::GameBoy::Bus::loadBootRom(const std::string& path) {
@ -60,85 +41,10 @@ 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: {
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;
// 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_WrittenByte(const uint16_t address) {
const auto value = DATA();
auto handled = false;
switch (address & 0xe000) {
case 0x0000:
// Register 0: RAMCS gate data
@ -152,7 +58,6 @@ void EightBit::GameBoy::Bus::Bus_WrittenByte(const uint16_t address) {
assert((address >= 0x2000) && (address < 0x4000));
assert((value > 0) && (value < 0x20));
m_romBank = value & Processor::Mask5;
handled = true;
}
break;
case 0x4000:
@ -175,77 +80,9 @@ void EightBit::GameBoy::Bus::Bus_WrittenByte(const uint16_t address) {
default:
UNREACHABLE;
}
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 + 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;
}
}
}
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() {
@ -322,3 +159,31 @@ void EightBit::GameBoy::Bus::validateCartridgeType() {
}
}
}
uint8_t& EightBit::GameBoy::Bus::reference(uint16_t address, bool& rom) {
rom = true;
if ((address < 0x100) && m_ioPorts.bootRomEnabled())
return m_bootRom.reference(address);
if ((address < 0x4000) && gameRomEnabled())
return m_gameRomBanks[0].reference(address);
if ((address < 0x8000) && gameRomEnabled())
return m_gameRomBanks[m_romBank].reference(address - 0x4000);
rom = false;
if (address < 0xa000)
return VRAM().reference(address - 0x8000);
if (address < 0xc000)
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)
return m_lowInternalRam.reference(address - 0xe000); // Low internal RAM mirror
if (address < 0xfea0)
return OAMRAM().reference(address - 0xfe00);
if (address < IoRegisters::BASE)
return rom = true, placeDATA(0xff);
if (address < 0xff80)
return m_ioPorts.reference(address - IoRegisters::BASE);
return m_highInternalRam.reference(address - 0xff80);
}

234
LR35902/src/IoRegisters.cpp Normal file
View File

@ -0,0 +1,234 @@
#include "stdafx.h"
#include "IoRegisters.h"
#include "GameBoyBus.h"
EightBit::GameBoy::IoRegisters::IoRegisters(Bus& bus)
: Ram(0x80),
m_bus(bus),
m_disableBootRom(false),
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_bus.ReadingByte.connect(std::bind(&IoRegisters::Bus_ReadingByte, this, std::placeholders::_1));
m_bus.WrittenByte.connect(std::bind(&IoRegisters::Bus_WrittenByte, this, std::placeholders::_1));
m_divCounter.word = 0xabcc;
m_dmaAddress.word = 0;
}
void EightBit::GameBoy::IoRegisters::reset() {
poke(NR52, 0xf1);
poke(LCDC, DisplayBackground | BackgroundCharacterDataSelection | LcdEnable);
m_divCounter.word = 0xabcc;
m_timerCounter = 0;
}
void EightBit::GameBoy::IoRegisters::transferDma() {
if (m_dmaTransferActive) {
m_bus.OAMRAM().poke(m_dmaAddress.low, m_bus.peek(m_dmaAddress.word));
m_dmaTransferActive = ++m_dmaAddress.low < 0xa0;
}
}
void EightBit::GameBoy::IoRegisters::Bus_ReadingByte(const uint16_t address) {
const auto io = (address >= BASE) && (address < 0xff80);
if (io) {
auto port = address - BASE;
switch (port) {
// 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;
poke(port,
((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(port, Processor::Bit7 | Processor::Bit0);
break;
// Timer control
case DIV:
case TIMA:
case TMA:
break;
case TAC:
mask(port, Processor::Mask3);
break;
// Interrupt Flags
case IF:
mask(port, Processor::Mask5);
break;
// LCD Display Registers
case LCDC:
break;
case STAT:
mask(port, 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(port, 0);
break;
}
}
}
void EightBit::GameBoy::IoRegisters::Bus_WrittenByte(const uint16_t address) {
const auto value = m_bus.DATA();
const auto port = address - BASE;
switch (port) {
case P1:
m_scanP14 = (value & Processor::Bit4) == 0;
m_scanP15 = (value & Processor::Bit5) == 0;
break;
case SB: // R/W
case SC: // R/W
break;
case DIV: // R/W
poke(port, 0);
m_timerCounter = m_divCounter.word = 0;
break;
case TIMA: // R/W
case TMA: // R/W
break;
case TAC: // R/W
m_timerRate = timerClockTicks();
break;
case IF: // R/W
break;
case LCDC:
case STAT:
case SCY:
case SCX:
break;
case DMA:
m_dmaAddress.high = value;
m_dmaAddress.low = 0;
m_dmaTransferActive = true;
break;
case LY: // R/O
poke(port, 0);
break;
case BGP:
case OBP0:
case OBP1:
case WY:
case WX:
break;
case BOOT_DISABLE:
m_disableBootRom = value != 0;
break;
}
}
void EightBit::GameBoy::IoRegisters::checkTimers(int cycles) {
incrementDIV(cycles);
checkTimer(cycles);
}
void EightBit::GameBoy::IoRegisters::checkTimer(int cycles) {
if (timerEnabled()) {
m_timerCounter -= cycles;
if (m_timerCounter <= 0) {
m_timerCounter += m_timerRate;
incrementTIMA();
}
}
}
int EightBit::GameBoy::IoRegisters::timerClockTicks() {
switch (timerClock()) {
case 0b00:
return 1024; // 4.096 Khz
case 0b01:
return 16; // 262.144 Khz
case 0b10:
return 64; // 65.536 Khz
case 0b11:
return 256; // 16.384 Khz
default:
UNREACHABLE;
}
throw std::domain_error("Invalid timer clock specification");
}
int EightBit::GameBoy::IoRegisters::timerClock() {
return peek(TAC) & Processor::Mask2;
}
bool EightBit::GameBoy::IoRegisters::timerEnabled() {
return !timerDisabled();
}
bool EightBit::GameBoy::IoRegisters::timerDisabled() {
return (peek(TAC) & Processor::Bit2) == 0;
}
void EightBit::GameBoy::IoRegisters::incrementDIV(int cycles) {
m_divCounter.word += cycles;
poke(DIV, m_divCounter.high);
}
void EightBit::GameBoy::IoRegisters::incrementTIMA() {
uint16_t updated = peek(TIMA) + 1;
if (updated & Processor::Bit8) {
triggerInterrupt(TimerOverflow);
updated = peek(TMA);
}
poke(TIMA, updated & Processor::Mask8);
}
void EightBit::GameBoy::IoRegisters::incrementLY() {
poke(LY, (peek(LY) + 1) % GameBoy::Bus::TotalLineCount);
}
void EightBit::GameBoy::IoRegisters::resetLY() {
poke(LY, 0);
}
void EightBit::GameBoy::IoRegisters::updateLcdStatusMode(int mode) {
const auto current = peek(STAT) & ~Processor::Mask2;
poke(STAT, current | mode);
DisplayStatusModeUpdated.fire(mode);
}

View File

@ -289,8 +289,8 @@ void EightBit::GameBoy::LR35902::ccf(uint8_t& a, uint8_t& f) {
}
int EightBit::GameBoy::LR35902::runRasterLines() {
m_enabledLCD = !!(m_bus.peekRegister(Bus::LCDC) & Bus::LcdEnable);
m_bus.resetLY();
m_enabledLCD = !!(m_bus.IO().peek(IoRegisters::LCDC) & IoRegisters::LcdEnable);
m_bus.IO().resetLY();
return runRasterLines(Display::RasterHeight * Bus::CyclesPerLine, Display::RasterHeight);
}
@ -333,26 +333,26 @@ int EightBit::GameBoy::LR35902::runRasterLine(int limit) {
int count = 0;
if (m_enabledLCD) {
if ((m_bus.peekRegister(Bus::STAT) & Bit6) && (m_bus.peekRegister(Bus::LYC) == m_bus.peekRegister(Bus::LY)))
m_bus.triggerInterrupt(Bus::Interrupts::DisplayControlStatus);
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.updateLcdStatusMode(Bus::LcdStatusMode::SearchingOamRam);
if (m_bus.peekRegister(Bus::STAT) & Bit5)
m_bus.triggerInterrupt(Bus::Interrupts::DisplayControlStatus);
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.updateLcdStatusMode(Bus::LcdStatusMode::TransferringDataToLcd);
m_bus.IO().updateLcdStatusMode(IoRegisters::LcdStatusMode::TransferringDataToLcd);
count += run(170); // ~41us
// Mode 0
m_bus.updateLcdStatusMode(Bus::LcdStatusMode::HBlank);
if (m_bus.peekRegister(Bus::STAT) & Bit3)
m_bus.triggerInterrupt(Bus::Interrupts::DisplayControlStatus);
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.incrementLY();
m_bus.IO().incrementLY();
} else {
count += run(Bus::CyclesPerLine);
@ -386,10 +386,10 @@ int EightBit::GameBoy::LR35902::runVerticalBlankLines(int limit, int lines) {
*/
if (m_enabledLCD) {
m_bus.updateLcdStatusMode(Bus::LcdStatusMode::VBlank);
if (m_bus.peekRegister(Bus::STAT) & Bit4)
m_bus.triggerInterrupt(Bus::Interrupts::DisplayControlStatus);
m_bus.triggerInterrupt(Bus::Interrupts::VerticalBlank);
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);
}
@ -398,36 +398,36 @@ int EightBit::GameBoy::LR35902::singleStep() {
int current = 0;
auto interruptEnable = m_bus.peekRegister(Bus::IE);
auto interruptFlags = m_bus.peekRegister(Bus::IF);
auto interruptEnable = m_bus.peek(IoRegisters::BASE + IoRegisters::IE);
auto interruptFlags = m_bus.IO().peek(IoRegisters::IF);
auto ime = IME();
auto masked = interruptEnable & interruptFlags;
if (masked) {
if (ime) {
m_bus.pokeRegister(Bus::IF, 0);
m_bus.IO().poke(IoRegisters::IF, 0);
} else {
if (isHalted())
proceed();
}
}
if (ime && (masked & Bus::Interrupts::VerticalBlank)) {
if (ime && (masked & IoRegisters::Interrupts::VerticalBlank)) {
current += interrupt(0x40);
} else if (ime && (masked & Bus::Interrupts::DisplayControlStatus)) {
} else if (ime && (masked & IoRegisters::Interrupts::DisplayControlStatus)) {
current += interrupt(0x48);
} else if (ime && (masked & Bus::Interrupts::TimerOverflow)) {
} else if (ime && (masked & IoRegisters::Interrupts::TimerOverflow)) {
current += interrupt(0x50);
} else if (ime && (masked & Bus::Interrupts::SerialTransfer)) {
} else if (ime && (masked & IoRegisters::Interrupts::SerialTransfer)) {
current += interrupt(0x58);
} else if (ime && (masked & Bus::Interrupts::KeypadPressed)) {
} else if (ime && (masked & IoRegisters::Interrupts::KeypadPressed)) {
current += interrupt(0x60);
} else {
current += isHalted() ? 1 : step();
}
m_bus.checkTimers(current);
m_bus.transferDma();
m_bus.IO().checkTimers(current);
m_bus.IO().transferDma();
return current;
}
@ -750,7 +750,7 @@ void EightBit::GameBoy::LR35902::executeOther(int x, int y, int z, int p, int q)
cycles += 2;
break;
case 4: // GB: LD (FF00 + n),A
m_bus.writeRegister(fetchByte(), a);
m_bus.write(IoRegisters::BASE + fetchByte(), a);
cycles += 3;
break;
case 5: { // GB: ADD SP,dd
@ -766,7 +766,7 @@ void EightBit::GameBoy::LR35902::executeOther(int x, int y, int z, int p, int q)
cycles += 4;
break;
case 6: // GB: LD A,(FF00 + n)
a = m_bus.readRegister(fetchByte());
a = m_bus.read(IoRegisters::BASE + fetchByte());
cycles += 3;
break;
case 7: { // GB: LD HL,SP + dd
@ -827,7 +827,7 @@ void EightBit::GameBoy::LR35902::executeOther(int x, int y, int z, int p, int q)
cycles += 3;
break;
case 4: // GB: LD (FF00 + C),A
m_bus.writeRegister(C(), a);
m_bus.write(IoRegisters::BASE + C(), a);
cycles += 2;
break;
case 5: // GB: LD (nn),A
@ -836,7 +836,7 @@ void EightBit::GameBoy::LR35902::executeOther(int x, int y, int z, int p, int q)
cycles += 4;
break;
case 6: // GB: LD A,(FF00 + C)
a = m_bus.readRegister(C());
a = m_bus.read(IoRegisters::BASE + C());
cycles += 2;
break;
case 7: // GB: LD A,(nn)

View File

@ -144,6 +144,7 @@
<ClInclude Include="..\inc\CharacterDefinition.h" />
<ClInclude Include="..\inc\Disassembler.h" />
<ClInclude Include="..\inc\Display.h" />
<ClInclude Include="..\inc\IoRegisters.h" />
<ClInclude Include="..\inc\LR35902.h" />
<ClInclude Include="..\inc\ObjectAttribute.h" />
<ClInclude Include="..\inc\Profiler.h" />
@ -154,6 +155,7 @@
<ClCompile Include="GameBoyBus.cpp" />
<ClCompile Include="Disassembler.cpp" />
<ClCompile Include="Display.cpp" />
<ClCompile Include="IoRegisters.cpp" />
<ClCompile Include="LR35902.cpp" />
<ClCompile Include="Profiler.cpp" />
<ClCompile Include="stdafx.cpp">

View File

@ -38,6 +38,9 @@
<ClInclude Include="..\inc\ObjectAttribute.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\inc\IoRegisters.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
@ -61,5 +64,8 @@
<ClCompile Include="CharacterDefinition.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="IoRegisters.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>