Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 100 additions & 12 deletions Core/NES/Mappers/FDS/Fds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,58 @@ void Fds::WriteFdsDisk(uint8_t value)

void Fds::ClockIrq()
{
//Timer IRQ
if(_irqEnabled) {
if(_irqCounter == 0) {
_cpu->SetIrqSource(IRQSource::External);
_timerIrqPending = true;
_irqCounter = _irqReloadValue;
if(!_irqRepeatEnabled) {
_irqEnabled = false;
}
UpdateIrqState();
} else {
_irqCounter--;
}
}

//DRAM refresh watchdog: https://forums.nesdev.org/viewtopic.php?t=26359
if(_diskRegEnabled) {
//"IRQ is asserted when the [12-bit] cycle timer underflows while $4023.D7 = 1."
if(_watchdogTimer == 0) {
_watchdogTimer = 4095;
_watchdogIrqPending = _watchdogIrqEnabled;
UpdateIrqState();
} else {
_watchdogTimer--;
}

//7-bit refresh counter: "...increment on every CPU cycle spent accessing memory outside of PRG-RAM,
// pause on CPU cycles accessing PRG-RAM"
if(!_dramAccessed) {
if(_dramRow == 0x7F) {
_dramRow = 0;
_watchdogTimer = 3245; //"...reload with 3245 when refresh counter overflows"
} else {
_dramRow++;
}
}
}
_dramAccessed = false;
}

void Fds::UpdateIrqState()
{
if(!(_timerIrqPending || _watchdogIrqPending)) {
_cpu->ClearIrqSource(IRQSource::External);
} else {
_cpu->SetIrqSource(IRQSource::External);
}
}

uint8_t Fds::ReadRam(uint16_t addr)
{
_dramAccessed = (addr >= 0x6000 && addr < 0xE000);

if(addr == 0xE18C && !_gameStarted && (_memoryManager->DebugRead(0x100) & 0xC0)) {
//$E18B is the NMI entry point (using $E18C due to dummy reads)
//When NMI occurs while $100 & $C0 != 0, it typically means that the game is starting.
Expand Down Expand Up @@ -215,6 +252,12 @@ uint8_t Fds::ReadRam(uint16_t addr)
return BaseMapper::InternalRead(addr);
}

void Fds::WriteRam(uint16_t addr, uint8_t value)
{
_dramAccessed = (addr >= 0x6000 && addr < 0xE000);
BaseMapper::WriteRam(addr, value);
}

void Fds::ProcessAutoDiskInsert()
{
if(IsAutoInsertDiskEnabled()) {
Expand Down Expand Up @@ -270,14 +313,14 @@ void Fds::EndFrame()
- Should allow for accurate handling of level 2->3 load bug in Ai Senshi Nicol
- Proper CRC handling using $4025 bits 4 and 6
- Verify End of Head handling (may affect Kosodate Gokko and FMC Disk Card Checker)
- DRAM refresh watchdog implementation (must track PRG-RAM vs external access cycles...)
(Ongoing research, please consult TakuikaNinja for further details)
**/
void Fds::ProcessCpuClock()
{
BaseProcessCpuClock();

ClockIrq();

_audio->Clock();

if(_diskNumber == Fds::NoDiskInserted || !_motorOn) {
Expand Down Expand Up @@ -438,14 +481,20 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value)
if(_irqEnabled) {
_irqCounter = _irqReloadValue;
} else {
_cpu->ClearIrqSource(IRQSource::External);
_timerIrqPending = false;
UpdateIrqState();
}
break;

case 0x4023:
_diskRegEnabled = value & 0x01;
//TODO This is actually an audio reset, rename this variable in Fds.h
_soundRegEnabled = value & 0x02;
_watchdogIrqEnabled = value & 0x80;

if(!_watchdogIrqEnabled) {
_watchdogIrqPending = false;
}

if(!_diskRegEnabled) {
_irqEnabled = false;
Expand All @@ -464,10 +513,17 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value)
//Disabling disk registers forces $4026 (external connector) = 0x7F
_extConWriteReg = 0x7F;

_cpu->ClearIrqSource(IRQSource::External);
//Reset DRAM refresh watchdog
_dramRow = 0;
_watchdogTimer = 4095;

_timerIrqPending = false;
_watchdogIrqPending = false;
_cpu->ClearIrqSource(IRQSource::FdsDisk);
}

UpdateIrqState();

/**TODO Determine/implement audio reset behaviour, should probably go in FdsAudio:
- Proper method of resetting modulation state ($4085 write below doesn't always work)
- Reset wave accumulator to 0
Expand Down Expand Up @@ -530,17 +586,18 @@ uint8_t Fds::ReadRegister(uint16_t addr)
case 0x4030:
//These 2 pins are open bus
value &= 0x24;
/**TODO:
- DRAM refresh watchdog IRQ status is returned in bit 1, needs implementation
- End of Head is returned in bit 6, needs verification
**/
value |= _cpu->HasIrqSource(IRQSource::External) ? 0x01 : 0x00;
//value |= _transferComplete ? 0x02 : 0x00;
/**TODO End of Head is returned in bit 6, needs verification
**/
value |= _timerIrqPending ? 0x01 : 0x00;
value |= _watchdogIrqPending ? 0x02 : 0x00;
value |= GetMirroringType() == MirroringType::Horizontal ? 0x08 : 0x00;
value |= _useQdFormat && _badCrc ? 0x10 : 0x00;
//value |= _endOfHead ? 0x40 : 0x00;
value |= _transferComplete ? 0x80 : 0x00;

//"Reading this register acknowledges all IRQs from the FDS except for the byte transfer flag"
_timerIrqPending = false;
_watchdogIrqPending = false;
_cpu->ClearIrqSource(IRQSource::External);
return value;

Expand Down Expand Up @@ -631,11 +688,37 @@ void Fds::Serialize(Serializer& s)
SV(_crcAccumulator);

if(s.IsSaving()) {
SV(_timerIrqPending);
SV(_watchdogIrqPending);
SV(_watchdogIrqEnabled);
SV(_dramAccessed);
SV(_dramRow);
SV(_watchdogTimer);

for(int i = 0; i < (int)_fdsDiskSides.size(); i++) {
vector<uint8_t> ipsData = IpsPatcher::CreatePatch(_orgDiskSides[i], _fdsDiskSides[i]);
SVVectorI(ipsData);
}
} else {
if(!s.ContainsKey("timerIrqPending")) {
//Backwards compatibility with savetates that predate the watchdog's discovery
_timerIrqPending = _cpu->HasIrqSource(IRQSource::External); //External source was previously reserved for the timer IRQ
_watchdogIrqPending = false;
//$4023.D7 is missing, so assume based on $4023.D0 state
//(most commercial games don't touch the BIOS init value of $83)
_watchdogIrqEnabled = _diskRegEnabled;
_dramAccessed = false;
_dramRow = 0;
_watchdogTimer = 4095;
} else {
SV(_timerIrqPending);
SV(_watchdogIrqPending);
SV(_watchdogIrqEnabled);
SV(_dramAccessed);
SV(_dramRow);
SV(_watchdogTimer);
}

for(int i = 0; i < (int)_fdsDiskSides.size(); i++) {
vector<uint8_t> ipsData;
SVVectorI(ipsData);
Expand Down Expand Up @@ -698,9 +781,14 @@ vector<MapperStateEntry> Fds::GetMapperStateEntries()
entries.push_back(MapperStateEntry("$4022.1", "IRQ Enabled", _irqEnabled, MapperStateValueType::Bool));
entries.push_back(MapperStateEntry("", "IRQ Counter", _irqCounter, MapperStateValueType::Number16));

entries.push_back(MapperStateEntry("", "DRAM Refresh Watchdog"));
entries.push_back(MapperStateEntry("", "DRAM Row", _dramRow, MapperStateValueType::Number8));
entries.push_back(MapperStateEntry("", "Watchdog Timer", _watchdogTimer, MapperStateValueType::Number16));

entries.push_back(MapperStateEntry("", "I/O Control"));
entries.push_back(MapperStateEntry("$4023.0", "Disk Registers Enabled", _diskRegEnabled, MapperStateValueType::Bool));
entries.push_back(MapperStateEntry("$4023.1", "Audio Enabled", _soundRegEnabled, MapperStateValueType::Bool));
entries.push_back(MapperStateEntry("$4023.7", "DRAM Refresh Watchdog IRQ Enabled", _watchdogIrqEnabled, MapperStateValueType::Bool));

entries.push_back(MapperStateEntry("", "Disk Drive"));
entries.push_back(MapperStateEntry("$4025", "Drive Control"));
Expand All @@ -713,8 +801,8 @@ vector<MapperStateEntry> Fds::GetMapperStateEntries()
entries.push_back(MapperStateEntry("$4025.7", "Disk IRQ Enabled", _diskIrqEnabled, MapperStateValueType::Bool));

entries.push_back(MapperStateEntry("$4030", "Drive/IRQ Status"));
entries.push_back(MapperStateEntry("$4030.0", "Timer IRQ", _cpu->HasIrqSource(IRQSource::External), MapperStateValueType::Bool));
//entries.push_back(MapperStateEntry("$4030.1", "DRAM Refresh Watchdog IRQ", false, MapperStateValueType::Bool));
entries.push_back(MapperStateEntry("$4030.0", "Timer IRQ", _timerIrqPending, MapperStateValueType::Bool));
entries.push_back(MapperStateEntry("$4030.1", "DRAM Refresh Watchdog IRQ", _watchdogIrqPending, MapperStateValueType::Bool));
entries.push_back(MapperStateEntry("$4030.3", "Mirroring", GetMirroringType() == MirroringType::Horizontal ? "Horizontal" : "Vertical", GetMirroringType() == MirroringType::Horizontal ? 1 : 0));
entries.push_back(MapperStateEntry("$4030.5", "CRC Error", (_useQdFormat && _badCrc), MapperStateValueType::Bool));
//entries.push_back(MapperStateEntry("$4030.6", "End of Head", _endOfHead, MapperStateValueType::Bool));
Expand Down
9 changes: 9 additions & 0 deletions Core/NES/Mappers/FDS/Fds.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Fds : public BaseMapper
uint16_t _irqCounter = 0;
bool _irqEnabled = false;
bool _irqRepeatEnabled = false;
bool _timerIrqPending = false;

bool _diskRegEnabled = true;
bool _soundRegEnabled = true;
Expand Down Expand Up @@ -81,6 +82,12 @@ class Fds : public BaseMapper
bool _gameStarted = false;
bool _needSave = false;

bool _watchdogIrqEnabled = false;
bool _watchdogIrqPending = false;
bool _dramAccessed = false;
uint8_t _dramRow = 0; // 7 bits
uint16_t _watchdogTimer = 4095; // 12 bits

protected:
uint16_t GetPrgPageSize() override { return 0x2000; }
uint16_t GetChrPageSize() override { return 0x2000; }
Expand All @@ -104,6 +111,7 @@ class Fds : public BaseMapper
void ProcessAutoDiskInsert();

void ClockIrq();
void UpdateIrqState();

void ProcessCpuClock() override;
void UpdateCrc(uint8_t value);
Expand All @@ -112,6 +120,7 @@ class Fds : public BaseMapper
uint8_t ReadRegister(uint16_t addr) override;

uint8_t ReadRam(uint16_t addr) override;
void WriteRam(uint16_t addr, uint8_t value) override;

void Serialize(Serializer& s) override;
vector<MapperStateEntry> GetMapperStateEntries() override;
Expand Down
2 changes: 1 addition & 1 deletion UI/Debugger/Labels/DefaultLabelHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ private static void SetDefaultNesLabels()
LabelManager.SetLabel(0x04A0, MemoryType.NesPrgRom, "FileMatchTest", "Input: $02-$03 = Pointer to File ID list " + Environment.NewLine + "Output: " + Environment.NewLine + "Desc: Uses a byte string pointed at by Ptr($02) to tell the disk system which files to load. The file ID's number is searched for in the string. If an exact match is found, [$09] is 0'd, and [$0e] is incremented. If no matches are found after 20 bytes, or a -1 entry is encountered, [$09] is set to -1. If the first byte in the string is -1, the BootID number is used for matching files (any FileID that is not greater than the BootID qualifies as a match).");
LabelManager.SetLabel(0x04DA, MemoryType.NesPrgRom, "SkipFiles", "Input: $06 = # of files to skip " + Environment.NewLine + "Output: " + Environment.NewLine + "Desc: Skips over a specified number of files.");

LabelManager.SetLabel(0x0149, MemoryType.NesPrgRom, "Delay131", "Input: " + Environment.NewLine + "Output: " + Environment.NewLine + "Affects: " + Environment.NewLine + "Desc: 131 clock cycle delay (including the JSR instruction to call it) used by the \"BIOS acknowledge and delay\" IRQ handler.");
LabelManager.SetLabel(0x0149, MemoryType.NesPrgRom, "Delay131", "Input: " + Environment.NewLine + "Output: " + Environment.NewLine + "Affects: " + Environment.NewLine + "Desc: 131 clock cycle delay (including the JSR instruction to call it) used by the \"BIOS acknowledge and delay\" IRQ handler. Intended for refreshing DRAM rows when the DRAM refresh watchdog has expired.");
LabelManager.SetLabel(0x0153, MemoryType.NesPrgRom, "Delayms", "Input: Y = Delay in ms (approximate) " + Environment.NewLine + "Output: " + Environment.NewLine + "Affects: X, Y " + Environment.NewLine + "Desc: Delay routine, where the time in CPU cycles is 1790*Y+5. Y = 0 is treated as 256.");
LabelManager.SetLabel(0x0161, MemoryType.NesPrgRom, "DisPFObj", "Input: $fe = PPUMASK value " + Environment.NewLine + "Output: " + Environment.NewLine + "Affects: A, $fe " + Environment.NewLine + "Desc: Disable rendering for both sprites and background.");
LabelManager.SetLabel(0x016B, MemoryType.NesPrgRom, "EnPFObj", "Input: $fe = PPUMASK value " + Environment.NewLine + "Output: " + Environment.NewLine + "Affects: A, $fe " + Environment.NewLine + "Desc: Enable rendering for both sprites and background.");
Expand Down
Loading