mirror of
https://github.com/MoleskiCoder/EightBit.git
synced 2025-02-22 05:29:01 +00:00
Start properly implementing IO status register read/write.
Signed-off-by: Adrian.Conlon <adrian.conlon@gmail.com>
This commit is contained in:
parent
b445457b37
commit
32d1085ecb
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user