GB: Various emulation improvements

-Better OAM DMA emulation
-Better STAT IRQ  emulation
-Better LCD power on state
-Moved DMA logic to GbDmaController
This commit is contained in:
Sour 2020-05-23 00:45:22 -04:00
parent c32472913c
commit 10be89012c
13 changed files with 263 additions and 94 deletions

View file

@ -73,6 +73,7 @@
<ClInclude Include="GbCartFactory.h" />
<ClInclude Include="GbCpu.h" />
<ClInclude Include="GbDebugger.h" />
<ClInclude Include="GbDmaController.h" />
<ClInclude Include="GbEventManager.h" />
<ClInclude Include="GbMbc1.h" />
<ClInclude Include="GbMbc2.h" />
@ -280,6 +281,7 @@
<ClCompile Include="GbApu.cpp" />
<ClCompile Include="GbCpu.cpp" />
<ClCompile Include="GbDebugger.cpp" />
<ClCompile Include="GbDmaController.cpp" />
<ClCompile Include="GbEventManager.cpp" />
<ClCompile Include="GbMemoryManager.cpp" />
<ClCompile Include="GbPpu.cpp" />

View file

@ -587,6 +587,9 @@
<ClInclude Include="BaseEventManager.h">
<Filter>Debugger\EventManager</Filter>
</ClInclude>
<ClInclude Include="GbDmaController.h">
<Filter>GB</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp" />
@ -932,6 +935,9 @@
<ClCompile Include="GbEventManager.cpp">
<Filter>Debugger\EventManager</Filter>
</ClCompile>
<ClCompile Include="GbDmaController.cpp">
<Filter>GB</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="SNES">

View file

@ -6,6 +6,7 @@
#include "GbApu.h"
#include "GbCart.h"
#include "GbTimer.h"
#include "GbDmaController.h"
#include "DebugTypes.h"
#include "GbMemoryManager.h"
#include "GbCartFactory.h"
@ -88,6 +89,7 @@ void Gameboy::PowerOn()
//VRAM is filled with 0s by the boot rom
memset(_videoRam, 0, _videoRamSize);
memset(_spriteRam, 0, Gameboy::SpriteRamSize);
LoadBattery();
@ -95,8 +97,9 @@ void Gameboy::PowerOn()
_apu.reset(new GbApu(_console, this));
_memoryManager.reset(new GbMemoryManager());
_timer.reset(new GbTimer(_memoryManager.get(), _apu.get()));
_dmaController.reset(new GbDmaController(_memoryManager.get()));
_cart->Init(this, _memoryManager.get());
_memoryManager->Init(_console, this, _cart.get(), _ppu.get(), _apu.get(), _timer.get());
_memoryManager->Init(_console, this, _cart.get(), _ppu.get(), _apu.get(), _timer.get(), _dmaController.get());
_cpu.reset(new GbCpu(_console, this, _memoryManager.get()));
_ppu->Init(_console, this, _memoryManager.get(), _videoRam, _spriteRam);
@ -249,6 +252,7 @@ void Gameboy::Serialize(Serializer& s)
s.Stream(_memoryManager.get());
s.Stream(_cart.get());
s.Stream(_timer.get());
s.Stream(_dmaController.get());
s.Stream(_hasBattery);
s.StreamArray(_cartRam, _cartRamSize);

View file

@ -10,6 +10,7 @@ class GbCpu;
class GbCart;
class GbTimer;
class GbMemoryManager;
class GbDmaController;
class VirtualFile;
class Gameboy : public ISerializable
@ -26,6 +27,7 @@ private:
unique_ptr<GbApu> _apu;
unique_ptr<GbCart> _cart;
unique_ptr<GbTimer> _timer;
unique_ptr<GbDmaController> _dmaController;
bool _hasBattery;
bool _cgbMode;

View file

@ -1010,9 +1010,9 @@ void GbCpu::CALL(uint16_t dstAddr)
void GbCpu::CALL(bool condition, uint16_t dstAddr)
{
if(condition) {
IncCycleCount();
PushWord(_state.PC);
_state.PC = dstAddr;
IncCycleCount();
}
}

93
Core/GbDmaController.cpp Normal file
View file

@ -0,0 +1,93 @@
#include "stdafx.h"
#include "GbDmaController.h"
#include "MessageManager.h"
#include "CpuTypes.h"
#include "GbMemoryManager.h"
GbDmaController::GbDmaController(GbMemoryManager* memoryManager)
{
_memoryManager = memoryManager;
_state = {};
}
void GbDmaController::Exec()
{
if(_state.DmaCounter > 0) {
if(_state.DmaCounter <= 160) {
_memoryManager->Write(0xFE00 + (160 - _state.DmaCounter), _state.DmaReadBuffer);
}
_state.DmaCounter--;
_state.DmaReadBuffer = _memoryManager->ReadDma((_state.OamDmaDest << 8) + (160 - _state.DmaCounter));
}
if(_state.DmaStartDelay > 0) {
if(--_state.DmaStartDelay == 0) {
_state.InternalDest = _state.OamDmaDest;
_state.DmaCounter = 161;
}
}
}
bool GbDmaController::IsOamDmaRunning()
{
return _state.DmaCounter > 0 && _state.DmaCounter < 161;
}
uint8_t GbDmaController::Read()
{
return _state.OamDmaDest;
}
void GbDmaController::Write(uint8_t value)
{
_state.DmaStartDelay = 1;
_state.OamDmaDest = value;
}
uint8_t GbDmaController::ReadCgb(uint16_t addr)
{
switch(addr) {
case 0xFF51: return _state.CgbDmaSource >> 8;
case 0xFF52: return _state.CgbDmaSource & 0xFF;
case 0xFF53: return _state.CgbDmaDest >> 8;
case 0xFF54: return _state.CgbDmaDest & 0xFF;
case 0xFF55: return _state.CgbDmaLength | (_state.CgbHdmaMode ? 0x80 : 0);
}
return 0;
}
void GbDmaController::WriteCgb(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0xFF51: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF) | (value << 8); break;
case 0xFF52: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF00) | value; break;
case 0xFF53: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF) | (value << 8); break;
case 0xFF54: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF00) | value; break;
case 0xFF55:
_state.CgbDmaLength = value & 0x7F;
_state.CgbHdmaMode = (value & 0x80) != 0;
if(!_state.CgbHdmaMode) {
//TODO check invalid dma sources/etc.
//TODO timing
for(int i = 0; i < _state.CgbDmaLength * 16; i++) {
uint16_t dst = 0x8000 | (((_state.CgbDmaDest & 0x1FF0) + i) & 0x1FFF);
_memoryManager->Write(dst, _memoryManager->Read((_state.CgbDmaSource & 0xFFF0) + i, MemoryOperationType::DmaRead));
}
_state.CgbDmaLength = 0x7F;
} else {
MessageManager::Log("TODO HDMA");
}
break;
}
}
void GbDmaController::Serialize(Serializer& s)
{
s.Stream(
_state.OamDmaDest, _state.DmaStartDelay, _state.InternalDest, _state.DmaCounter, _state.DmaReadBuffer,
_state.CgbDmaDest, _state.CgbDmaLength, _state.CgbDmaSource, _state.CgbHdmaMode
);
}

27
Core/GbDmaController.h Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include "stdafx.h"
#include "GbTypes.h"
#include "../Utilities/ISerializable.h"
class GbMemoryManager;
class GbDmaController : public ISerializable
{
private:
GbDmaControllerState _state;
GbMemoryManager* _memoryManager;
public:
GbDmaController(GbMemoryManager* memoryManager);
void Exec();
bool IsOamDmaRunning();
uint8_t Read();
void Write(uint8_t value);
uint8_t ReadCgb(uint16_t addr);
void WriteCgb(uint16_t addr, uint8_t value);
void Serialize(Serializer& s) override;
};

View file

@ -7,6 +7,7 @@
#include "GbTimer.h"
#include "GbTypes.h"
#include "GbCart.h"
#include "GbDmaController.h"
#include "EmuSettings.h"
#include "ControlManager.h"
#include "SnesController.h"
@ -15,10 +16,9 @@
#include "../Utilities/Serializer.h"
#include "../Utilities/HexUtilities.h"
void GbMemoryManager::Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer)
void GbMemoryManager::Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer, GbDmaController* dmaController)
{
_state = {};
_state.DisableBootRom = true;
_state.CgbWorkRamBank = 1;
_prgRom = gameboy->DebugGetMemory(SnesMemoryType::GbPrgRom);
@ -29,12 +29,21 @@ void GbMemoryManager::Init(Console* console, Gameboy* gameboy, GbCart* cart, GbP
_workRamSize = gameboy->DebugGetMemorySize(SnesMemoryType::GbWorkRam);
_highRam = gameboy->DebugGetMemory(SnesMemoryType::GbHighRam);
#ifdef USEBOOTROM
VirtualFile bootRom("Firmware\\boot.bin");
bootRom.ReadFile(_bootRom, 256);
_state.DisableBootRom = false;
#else
_state.DisableBootRom = true;
#endif
_apu = apu;
_ppu = ppu;
_gameboy = gameboy;
_cart = cart;
_timer = timer;
_console = console;
_dmaController = dmaController;
_controlManager = console->GetControlManager().get();
_settings = console->GetSettings().get();
@ -56,17 +65,16 @@ GbMemoryManagerState GbMemoryManager::GetState()
void GbMemoryManager::RefreshMappings()
{
if(!_state.DisableBootRom) {
//TODO
//Map(0x0000, 0x00FF, GbMemoryType::WorkRam, true);
}
Map(0xC000, 0xCFFF, GbMemoryType::WorkRam, 0, false);
Map(0xD000, 0xDFFF, GbMemoryType::WorkRam, _state.CgbWorkRamBank * 0x1000, false);
Map(0xE000, 0xEFFF, GbMemoryType::WorkRam, 0, false);
Map(0xF000, 0xFCFF, GbMemoryType::WorkRam, _state.CgbWorkRamBank * 0x1000, false);
Map(0xF000, 0xFFFF, GbMemoryType::WorkRam, _state.CgbWorkRamBank * 0x1000, false);
_cart->RefreshMappings();
if(!_state.DisableBootRom) {
_reads[0] = _bootRom;
}
}
void GbMemoryManager::Exec()
@ -74,6 +82,7 @@ void GbMemoryManager::Exec()
_state.ApuCycleCount += _state.CgbHighSpeed ? 2 : 4;
_timer->Exec();
_ppu->Exec();
_dmaController->Exec();
}
void GbMemoryManager::MapRegisters(uint16_t start, uint16_t end, RegisterAccess access)
@ -137,6 +146,23 @@ uint8_t GbMemoryManager::Read(uint16_t addr, MemoryOperationType opType)
return value;
}
bool GbMemoryManager::IsOamDmaRunning()
{
return _dmaController->IsOamDmaRunning();
}
uint8_t GbMemoryManager::ReadDma(uint16_t addr)
{
uint8_t value = 0;
if(_reads[addr >> 8]) {
value = _reads[addr >> 8][(uint8_t)addr];
} else if(addr >= 0x8000 && addr <= 0x9FFF) {
value = ReadRegister(addr);
}
_console->ProcessMemoryRead<CpuType::Gameboy>(addr, value, MemoryOperationType::DmaRead);
return value;
}
void GbMemoryManager::Write(uint16_t addr, uint8_t value)
{
_console->ProcessMemoryWrite<CpuType::Gameboy>(addr, value, MemoryOperationType::Write);
@ -184,6 +210,8 @@ uint8_t GbMemoryManager::ReadRegister(uint16_t addr)
if(addr >= 0xFF00) {
if(addr == 0xFFFF) {
return _state.IrqEnabled; //IE - Interrupt Enable (R/W)
} else if(addr == 0xFF46) {
return _dmaController->Read();
} else if(addr >= 0xFF80) {
return _highRam[addr & 0x7F]; //80-FE
} else if(addr >= 0xFF4D) {
@ -191,9 +219,11 @@ uint8_t GbMemoryManager::ReadRegister(uint16_t addr)
switch(addr) {
//FF4D - KEY1 - CGB Mode Only - Prepare Speed Switch
case 0xFF4D: return _state.CgbHighSpeed ? 0x80 : 0;
case 0xFF51: case 0xFF52: case 0xFF53: case 0xFF54: case 0xFF55: //CGB - DMA
return _dmaController->ReadCgb(addr);
case 0xFF4F: //CGB - VRAM bank
case 0xFF51: case 0xFF52: case 0xFF53: case 0xFF54: case 0xFF55: //CGB - DMA
case 0xFF68: case 0xFF69: case 0xFF6A: case 0xFF6B: //CGB - Palette
return _ppu->ReadCgbRegister(addr);
@ -202,7 +232,7 @@ uint8_t GbMemoryManager::ReadRegister(uint16_t addr)
}
}
LogDebug("[Debug] GB - Missing read handler: $" + HexUtilities::ToHex(addr));
return 0; //4D-7F
return 0xFF; //4D-7F, open bus
} else if(addr >= 0xFF40) {
return _ppu->Read(addr); //40-4C
} else if(addr >= 0xFF10) {
@ -211,12 +241,16 @@ uint8_t GbMemoryManager::ReadRegister(uint16_t addr)
//00-0F
switch(addr) {
case 0xFF00: return ReadInputPort(); break;
case 0xFF01: break; //Serial
case 0xFF01: return 0; //SB - Serial transfer data (R/W)
case 0xFF02: return 0x7E; //SC - Serial Transfer Control (R/W)
case 0xFF04: case 0xFF05: case 0xFF06: case 0xFF07:
return _timer->Read(addr);
case 0xFF0F: return _state.IrqRequests; break; //IF - Interrupt flags (R/W)
default: return 0xFF; //Open bus
}
}
} else if(addr >= 0xFE00) {
@ -233,6 +267,8 @@ void GbMemoryManager::WriteRegister(uint16_t addr, uint8_t value)
if(addr >= 0xFF00) {
if(addr == 0xFFFF) {
_state.IrqEnabled = value; //IE register
} else if(addr == 0xFF46) {
_dmaController->Write(value);
} else if(addr >= 0xFF80) {
_highRam[addr & 0x7F] = value; //80-FE
} else if(addr >= 0xFF4D) {
@ -249,8 +285,11 @@ void GbMemoryManager::WriteRegister(uint16_t addr, uint8_t value)
_state.CgbSwitchSpeedRequest = (value & 0x01) != 0;
break;
case 0xFF4F: //CGB - VRAM banking
case 0xFF51: case 0xFF52: case 0xFF53: case 0xFF54: case 0xFF55: //CGB - DMA
_dmaController->WriteCgb(addr, value);
break;
case 0xFF4F: //CGB - VRAM banking
case 0xFF68: case 0xFF69: case 0xFF6A: case 0xFF6B: //CGB - Palette
_ppu->WriteCgbRegister(addr, value);
break;
@ -364,7 +403,7 @@ uint8_t GbMemoryManager::ReadInputPort()
}
}
return result | (_state.InputSelect & 0x30);
return result | (_state.InputSelect & 0x30) | 0xC0;
}
void GbMemoryManager::Serialize(Serializer& s)
@ -384,5 +423,6 @@ void GbMemoryManager::Serialize(Serializer& s)
for(int i = 0; i < 0x100; i++) {
Map(i*0x100, i*0x100+0xFF, _state.MemoryType[i], _state.MemoryOffset[i], _state.MemoryAccessType[i] == RegisterAccess::ReadWrite ? false : true);
}
RefreshMappings();
}
}

View file

@ -8,6 +8,7 @@ class GbCart;
class GbPpu;
class GbApu;
class GbTimer;
class GbDmaController;
class EmuSettings;
class Console;
@ -25,7 +26,9 @@ private:
GbApu* _apu = nullptr;
GbPpu* _ppu = nullptr;
GbTimer* _timer;
GbDmaController* _dmaController;
uint8_t _bootRom[256];
uint8_t* _prgRom = nullptr;
uint32_t _prgRomSize = 0;
uint8_t* _workRam = nullptr;
@ -44,7 +47,7 @@ public:
GbMemoryManagerState GetState();
void Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer);
void Init(Console* console, Gameboy* gameboy, GbCart* cart, GbPpu* ppu, GbApu* apu, GbTimer* timer, GbDmaController* dmaController);
void MapRegisters(uint16_t start, uint16_t end, RegisterAccess access);
void Map(uint16_t start, uint16_t end, GbMemoryType type, uint32_t offset, bool readonly);
void Unmap(uint16_t start, uint16_t end);
@ -53,6 +56,8 @@ public:
void Exec();
uint8_t Read(uint16_t addr, MemoryOperationType opType);
bool IsOamDmaRunning();
uint8_t ReadDma(uint16_t addr);
void Write(uint16_t addr, uint8_t value);
uint8_t ReadRegister(uint16_t addr);

View file

@ -21,7 +21,7 @@ void GbPpu::Init(Console* console, Gameboy* gameboy, GbMemoryManager* memoryMana
_oam = oam;
_state = {};
_state.Mode = PpuMode::OamEvaluation;
_state.Mode = PpuMode::HBlank;
_lastFrameTime = 0;
_outputBuffers[0] = new uint16_t[256 * 240];
@ -92,13 +92,20 @@ void GbPpu::Exec()
void GbPpu::ExecCycle()
{
_state.Cycle++;
PpuMode mode = _state.Mode;
bool lyCoincidenceFlag = _state.LyCoincidenceFlag;
_state.LyCoincidenceFlag = (_state.LyCompare == _state.Scanline);
if(_state.Cycle == 456) {
_state.Cycle = 0;
_state.Scanline++;
_spriteCount = 0;
if(_state.Scanline == 144) {
if(_state.Scanline < 144) {
ChangeMode(PpuMode::OamEvaluation);
} else if(_state.Scanline == 144) {
ChangeMode(PpuMode::VBlank);
_memoryManager->RequestIrq(GbIrqSource::VerticalBlank);
@ -113,23 +120,17 @@ void GbPpu::ExecCycle()
}
}
}
}
if(_state.Scanline < 144) {
ChangeMode(PpuMode::OamEvaluation);
}
if(_state.LyCompare == _state.Scanline && (_state.Status & GbPpuStatusFlags::CoincidenceIrq)) {
_memoryManager->RequestIrq(GbIrqSource::LcdStat);
}
if(mode != _state.Mode || _state.LyCoincidenceFlag != lyCoincidenceFlag) {
UpdateStatIrq();
}
_console->ProcessPpuCycle(_state.Scanline, _state.Cycle);
//TODO: Dot-based renderer, currently draws at the end of the scanline
if(_state.Scanline < 144) {
if(_state.Cycle < 80) {
RunSpriteEvaluation();
} else if(_state.Mode == PpuMode::Drawing) {
if(_state.Mode == PpuMode::Drawing) {
bool fetchWindow = _state.WindowEnabled && _shiftedPixels >= _state.WindowX - 7 && _state.Scanline >= _state.WindowY;
if(_fetchWindow != fetchWindow) {
//Switched between window & background, reset fetcher & pixel FIFO
@ -167,7 +168,8 @@ void GbPpu::ExecCycle()
}
rgbColor = palette[entry.Color];
}
_currentBuffer[outOffset] = rgbColor;
_currentBuffer[outOffset] = rgbColor;
_fifoPosition = (_fifoPosition + 1) & 0x0F;
if(_console->IsDebugging()) {
@ -183,6 +185,8 @@ void GbPpu::ExecCycle()
if(_drawnPixels >= 160) {
ChangeMode(PpuMode::HBlank);
}
} else if(_state.Cycle < 80) {
RunSpriteEvaluation();
}
}
}
@ -202,22 +206,26 @@ void GbPpu::RunSpriteEvaluation()
if(_state.Cycle == 79) {
ChangeMode(PpuMode::Drawing);
//Reset fetcher & pixel FIFO
_fetcherStep = 0;
_fifoPosition = 0;
_fifoSize = 0;
_shiftedPixels = 0;
_drawnPixels = 0;
_fetchSprite = -1;
_fetchWindow = false;
_fetchColumn = _state.ScrollX / 8;
ResetRenderer();
}
} else {
//Hardware probably reads sprite Y and loads the X counter with the value on the next cycle
}
}
void GbPpu::ResetRenderer()
{
//Reset fetcher & pixel FIFO
_fetcherStep = 0;
_fifoPosition = 0;
_fifoSize = 0;
_shiftedPixels = 0;
_drawnPixels = 0;
_fetchSprite = -1;
_fetchWindow = false;
_fetchColumn = _state.ScrollX / 8;
}
void GbPpu::ClockTileFetcher()
{
if(_fetchSprite < 0 && _fifoSize >= 8) {
@ -240,7 +248,7 @@ void GbPpu::ClockTileFetcher()
uint8_t sprTile = _oam[_fetchSprite + 2];
uint8_t sprAttr = _oam[_fetchSprite + 3];
bool vMirror = (sprAttr & 0x40) != 0;
uint16_t tileBank = (sprAttr & 0x08) ? 0x2000 : 0x0000;
uint16_t tileBank = _gameboy->IsCgb() ? ((sprAttr & 0x08) ? 0x2000 : 0x0000) : 0;
uint8_t sprOffsetY = vMirror ? (_state.LargeSprites ? 15 : 7) - (_state.Scanline - sprY) : (_state.Scanline - sprY);
if(_state.LargeSprites) {
@ -353,7 +361,6 @@ void GbPpu::PushTileToPixelFifo()
void GbPpu::ChangeMode(PpuMode mode)
{
_state.Mode = mode;
UpdateStatIrq();
}
void GbPpu::UpdateStatIrq()
@ -415,8 +422,9 @@ uint8_t GbPpu::Read(uint16_t addr)
case 0xFF41:
//FF41 - STAT - LCDC Status (R/W)
return (
(_state.Status & 0xF8) |
((_state.LyCompare == _state.Scanline) ? 0x04 : 0x00) |
0x80 |
(_state.Status & 0x78) |
(_state.LyCoincidenceFlag ? 0x04 : 0x00) |
(int)_state.Mode
);
@ -444,7 +452,7 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
_state.LcdEnabled = (value & 0x80) != 0;
if(!_state.LcdEnabled) {
//Reset LCD to top of screen when it gets turned on
//Reset LCD to top of screen when it gets turned off
_state.Cycle = 0;
_state.Scanline = 0;
ChangeMode(PpuMode::HBlank);
@ -454,6 +462,10 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
std::fill(_outputBuffers[0], _outputBuffers[0] + 256 * 239, 0x7FFF);
std::fill(_outputBuffers[1], _outputBuffers[1] + 256 * 239, 0x7FFF);
SendFrame();
} else {
_state.Cycle = 6;
_state.Scanline = 0;
ChangeMode(PpuMode::Drawing);
}
}
_state.WindowTilemapSelect = (value & 0x40) != 0;
@ -465,24 +477,10 @@ void GbPpu::Write(uint16_t addr, uint8_t value)
_state.BgEnabled = (value & 0x01) != 0;
break;
case 0xFF41:
_state.Status = value & 0xF8;
UpdateStatIrq();
break;
case 0xFF41: _state.Status = value & 0xF8; break;
case 0xFF42: _state.ScrollY = value; break;
case 0xFF43: _state.ScrollX = value; break;
case 0xFF45: _state.LyCompare = value; break;
case 0xFF46:
//OAM DMA
//TODO restrict CPU accesses to high ram during this
//TODO timing
for(int i = 0; i < 0xA0; i++) {
_memoryManager->Write(0xFE00 | i, _memoryManager->Read((value << 8) | i, MemoryOperationType::DmaRead));
}
break;
case 0xFF47: _state.BgPalette = value; break;
case 0xFF48: _state.ObjPalette0 = value; break;
case 0xFF49: _state.ObjPalette1 = value; break;
@ -514,10 +512,10 @@ void GbPpu::WriteVram(uint16_t addr, uint8_t value)
uint8_t GbPpu::ReadOam(uint8_t addr)
{
if(addr < 0xA0) {
if((int)_state.Mode <= (int)PpuMode::VBlank) {
return _oam[addr];
} else {
if((int)_state.Mode >= (int)PpuMode::OamEvaluation || _memoryManager->IsOamDmaRunning()) {
return 0xFF;
} else {
return _oam[addr];
}
}
return 0;
@ -534,11 +532,6 @@ uint8_t GbPpu::ReadCgbRegister(uint16_t addr)
{
switch(addr) {
case 0xFF4F: return _state.CgbVramBank;
case 0xFF51: return _state.CgbDmaSource >> 8;
case 0xFF52: return _state.CgbDmaSource & 0xFF;
case 0xFF53: return _state.CgbDmaDest >> 8;
case 0xFF54: return _state.CgbDmaDest & 0xFF;
case 0xFF55: return _state.CgbDmaLength | (_state.CgbHdmaMode ? 0x80 : 0);
case 0xFF68: return _state.CgbBgPalPosition | (_state.CgbBgPalAutoInc ? 0x80 : 0);
case 0xFF69: return (_state.CgbBgPalettes[_state.CgbBgPalPosition >> 1] >> ((_state.CgbBgPalPosition & 0x01) ? 8 : 0) & 0xFF);
case 0xFF6A: return _state.CgbObjPalPosition | (_state.CgbObjPalAutoInc ? 0x80 : 0);
@ -552,27 +545,7 @@ void GbPpu::WriteCgbRegister(uint16_t addr, uint8_t value)
{
switch(addr) {
case 0xFF4F: _state.CgbVramBank = value & 0x01; break;
case 0xFF51: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF) | (value << 8); break;
case 0xFF52: _state.CgbDmaSource = (_state.CgbDmaSource & 0xFF00) | value; break;
case 0xFF53: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF) | (value << 8); break;
case 0xFF54: _state.CgbDmaDest = (_state.CgbDmaDest & 0xFF00) | value; break;
case 0xFF55:
_state.CgbDmaLength = value & 0x7F;
_state.CgbHdmaMode = (value & 0x80) != 0;
if(!_state.CgbHdmaMode) {
//TODO check invalid dma sources/etc.
//TODO timing
for(int i = 0; i < _state.CgbDmaLength * 16; i++) {
uint16_t dst = 0x8000 | (((_state.CgbDmaDest & 0x1FF0) + i) & 0x1FFF);
_memoryManager->Write(dst, _memoryManager->Read((_state.CgbDmaSource & 0xFFF0) + i, MemoryOperationType::DmaRead));
}
_state.CgbDmaLength = 0x7F;
} else {
MessageManager::Log("TODO HDMA");
}
break;
case 0xFF68:
//FF68 - BCPS/BGPI - CGB Mode Only - Background Palette Index
_state.CgbBgPalPosition = value & 0x3F;
@ -624,7 +597,7 @@ void GbPpu::Serialize(Serializer& s)
_state.ScrollX, _state.ScrollY, _state.WindowX, _state.WindowY, _state.Control, _state.LcdEnabled, _state.WindowTilemapSelect,
_state.WindowEnabled, _state.BgTileSelect, _state.BgTilemapSelect, _state.LargeSprites, _state.SpritesEnabled, _state.BgEnabled,
_state.Status, _state.FrameCount, _lastFrameTime,
_state.CgbBgPalAutoInc, _state.CgbBgPalPosition, _state.CgbDmaDest, _state.CgbDmaLength, _state.CgbDmaSource, _state.CgbHdmaMode,
_state.CgbBgPalAutoInc, _state.CgbBgPalPosition,
_state.CgbObjPalAutoInc, _state.CgbObjPalPosition, _state.CgbVramBank
);

View file

@ -53,6 +53,7 @@ private:
void ExecCycle();
void RunSpriteEvaluation();
void ResetRenderer();
void ClockTileFetcher();
void PushSpriteToPixelFifo();
void PushTileToPixelFifo();

View file

@ -8,6 +8,11 @@ GbTimer::GbTimer(GbMemoryManager* memoryManager, GbApu* apu)
{
_apu = apu;
_memoryManager = memoryManager;
//Passes boot_div-dmgABCmgb
//But that test depends on LCD power on timings, so may be wrong.
_divider = 0x06;
}
GbTimer::~GbTimer()

View file

@ -109,6 +109,7 @@ struct GbPpuState
bool StatIrqFlag;
uint8_t LyCompare;
bool LyCoincidenceFlag;
uint8_t BgPalette;
uint8_t ObjPalette0;
uint8_t ObjPalette1;
@ -131,11 +132,7 @@ struct GbPpuState
uint32_t FrameCount;
uint8_t CgbVramBank;
uint16_t CgbDmaSource;
uint16_t CgbDmaDest;
uint8_t CgbDmaLength;
bool CgbHdmaMode;
uint8_t CgbBgPalPosition;
bool CgbBgPalAutoInc;
uint16_t CgbBgPalettes[4 * 8];
@ -145,6 +142,20 @@ struct GbPpuState
uint16_t CgbObjPalettes[4 * 8];
};
struct GbDmaControllerState
{
uint8_t OamDmaDest;
uint8_t DmaStartDelay;
uint8_t InternalDest;
uint8_t DmaCounter;
uint8_t DmaReadBuffer;
uint16_t CgbDmaSource;
uint16_t CgbDmaDest;
uint8_t CgbDmaLength;
bool CgbHdmaMode;
};
struct GbSquareState
{
uint16_t SweepPeriod;