mirror of
https://github.com/MoleskiCoder/EightBit.git
synced 2025-01-08 11:29:26 +00:00
Split the bus into IoRegisters and "the rest"
Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
This commit is contained in:
parent
e8715b941b
commit
156cb66904
@ -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
202
LR35902/inc/IoRegisters.h
Normal 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);
|
||||
};
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
234
LR35902/src/IoRegisters.cpp
Normal 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);
|
||||
}
|
@ -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)
|
||||
|
@ -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">
|
||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user