Start properly implementing IO status register read/write.

Signed-off-by: Adrian.Conlon <adrian.conlon@gmail.com>
This commit is contained in:
Adrian.Conlon 2017-09-14 23:48:49 +01:00
parent b445457b37
commit 32d1085ecb
3 changed files with 282 additions and 51 deletions

View File

@ -1,5 +1,7 @@
#pragma once
#include <array>
#include <Rom.h>
#include <Ram.h>
#include <Bus.h>
@ -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<bool, 4> 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);
};
}
}

View File

@ -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");
}
}
}
}

View File

@ -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;
}