Accuracy improvements (Fixed several tests: nmi_timing, nmi_and_brk, nmi_and_irq, irq_and_dma, sprdma_and_dmc_dma, sprdma_and_dmc_dma512, branch_delays_irq)

This commit is contained in:
Souryo 2016-01-02 13:52:55 -05:00
parent 8fdc25c5d4
commit 8d020a2e72
7 changed files with 148 additions and 66 deletions

View file

@ -58,9 +58,13 @@ void CPU::Reset(bool softReset)
{
_state.NMIFlag = false;
_state.IRQFlag = 0;
_cycleCount = -1;
_cycleCount = 0;
_spriteDmaTransfer = false;
_spriteDmaCounter = 0;
_dmcCounter = -1;
_dmaTransfer = false;
_dmcDmaRunning = false;
//Use _memoryManager->Read() directly to prevent clocking the PPU/APU when setting PC at reset
_state.PC = _memoryManager->Read(CPU::ResetVector) | _memoryManager->Read(CPU::ResetVector+1) << 8;
@ -85,7 +89,7 @@ void CPU::Exec()
_instAddrMode = _addrMode[opCode];
_operand = FetchOperand();
(this->*_opTable[opCode])();
if(_prevRunIrq) {
IRQ();
}
@ -101,21 +105,26 @@ void CPU::IncCycleCount()
_dmcDmaRunning = false;
DeltaModulationChannel::SetReadBuffer();
}
} else {
}
PPU::ExecStatic();
APU::ExecStatic();
if(!_spriteDmaTransfer) {
//IRQ flags are ignored during Sprite DMA - fixes irq_and_dma
//"it's really the status of the interrupt lines at the end of the second-to-last cycle that matters."
//Keep the irq lines values from the previous cycle. The before-to-last cycle's values will be used
_prevRunIrq = _runIrq;
_runIrq = _state.NMIFlag || (_state.IRQFlag > 0 && !CheckFlag(PSFlags::Interrupt));
}
PPU::ExecStatic();
APU::ExecStatic();
_cycleCount++;
}
void CPU::RunDMATransfer(uint8_t* spriteRAM, uint8_t offsetValue)
{
Instance->_dmaTransfer = true;
Instance->_spriteDmaTransfer = true;
//"The CPU is suspended during the transfer, which will take 513 or 514 cycles after the $4014 write tick."
//"(1 dummy read cycle while waiting for writes to complete, +1 if on an odd CPU cycle, then 256 alternating read/write cycles.)"
@ -124,30 +133,46 @@ void CPU::RunDMATransfer(uint8_t* spriteRAM, uint8_t offsetValue)
}
Instance->DummyRead();
Instance->_spriteDmaCounter = 256;
//DMA transfer starts at SpriteRamAddr and wraps around
for(int i = 0; i < 0x100; i++) {
//Read value
uint8_t readValue = Instance->MemoryRead(offsetValue * 0x100 + i);
//Write to sprite ram via $2004 ("DMA is implemented in the 2A03/7 chip and works by repeatedly writing to OAMDATA")
Instance->MemoryWrite(0x2004, readValue);
if(i == 0xFE) {
//"DMC DMA adds [...] 3 if on the last DMA cycle.
Instance->_dmaTransfer = false;
if(Instance->_dmcCounter == 2) {
//"DMC DMA adds [...] 1 if on the next-to-next-to-last DMA cycle
Instance->_dmcCounter = 1;
}
}
Instance->_spriteDmaCounter--;
}
Instance->_spriteDmaTransfer = false;
}
void CPU::StartDmcTransfer()
{
//"DMC DMA adds 4 cycles normally, 2 if it lands on the $4014 write or during OAM DMA"
//3 cycles if it lands on the last write cycle of any instruction
Instance->_dmcDmaRunning = true;
Instance->_dmcCounter = Instance->_dmaTransfer ? 2 : 4;
if(Instance->_spriteDmaTransfer) {
if(Instance->_spriteDmaCounter == 2) {
Instance->_dmcCounter = 1;
} else if(Instance->_spriteDmaCounter == 1) {
Instance->_dmcCounter = 3;
} else {
Instance->_dmcCounter = 2;
}
} else {
if(Instance->_cpuWrite) {
if(Instance->_writeAddr == 0x4014) {
Instance->_dmcCounter = 2;
} else {
Instance->_dmcCounter = 3;
}
} else {
Instance->_dmcCounter = 4;
}
}
}
void CPU::StreamState(bool saving)
@ -165,5 +190,7 @@ void CPU::StreamState(bool saving)
Stream<int8_t>(_dmcCounter);
Stream<bool>(_dmcDmaRunning);
Stream<bool>(_dmaTransfer);
Stream<uint16_t>(_spriteDmaCounter);
Stream<bool>(_spriteDmaTransfer);
}

View file

@ -44,6 +44,7 @@ struct State
uint8_t Y = 0;
uint8_t PS = 0;
uint32_t IRQFlag = 0;
int32_t CycleCount;
bool NMIFlag = false;
//Used by debugger
@ -68,9 +69,13 @@ private:
AddrMode _addrMode[256];
AddrMode _instAddrMode;
uint16_t _spriteDmaCounter;
bool _spriteDmaTransfer;
int8_t _dmcCounter;
bool _dmcDmaRunning;
bool _dmaTransfer;
bool _cpuWrite = false;
uint16_t _writeAddr = 0;
State _state;
MemoryManager *_memoryManager = nullptr;
@ -141,8 +146,19 @@ private:
void MemoryWrite(uint16_t addr, uint8_t value)
{
_memoryManager->Write(addr, value);
if(_dmcCounter == 4) {
_dmcCounter = 3;
}
while(_dmcDmaRunning) {
IncCycleCount();
}
_cpuWrite = true;;
_writeAddr = addr;
IncCycleCount();
_memoryManager->Write(addr, value);
_cpuWrite = false;
}
uint8_t MemoryRead(uint16_t addr, MemoryOperationType operationType = MemoryOperationType::Read) {
@ -156,8 +172,8 @@ private:
}
IncCycleCount();
}
uint8_t value = _memoryManager->Read(addr, operationType);
IncCycleCount();
uint8_t value = _memoryManager->Read(addr, operationType);
return value;
}
@ -481,10 +497,18 @@ private:
void BranchRelative(bool branch) {
int8_t offset = (int8_t)GetOperand();
if(branch) {
//"a taken non-page-crossing branch ignores IRQ/NMI during its last clock, so that next instruction executes before the IRQ"
//Fixes "branch_delays_irq" test
bool skipIrq = false;
if(_runIrq && !_prevRunIrq) {
_runIrq = true;
}
DummyRead();
if(CheckPageCrossed(PC(), offset)) {
DummyRead();
}
DummyRead();
SetPC(PC() + offset);
}
@ -624,6 +648,9 @@ private:
SetPC(MemoryReadWord(CPU::IRQVector));
}
//Since we just set the flag to prevent interrupts, do not run one right away after this (fixes nmi_and_brk & nmi_and_irq tests)
_prevRunIrq = false;
}
void IRQ() {
@ -851,5 +878,10 @@ public:
void Reset(bool softReset);
void Exec();
State GetState() { return _state; }
State GetState()
{
State cpuState(_state);
cpuState.CycleCount = _cycleCount;
return cpuState;
}
};

View file

@ -36,8 +36,8 @@ void PPU::Reset()
_statusFlags = {};
_scanline = 0;
_cycle = -1;
_frameCount = 0;
_cycle = 0;
_frameCount = -1;
_memoryReadBuffer = 0;
}
@ -228,7 +228,8 @@ void PPU::SetControlRegister(uint8_t value)
if(!originalVBlank && _flags.VBlank && _statusFlags.VerticalBlank && (_scanline != -1 || _cycle != 0)) {
CPU::SetNMIFlag();
} else if(_scanline == 241 && _cycle < 3 && !_flags.VBlank) {
}
if(_scanline == 241 && _cycle < 3 && !_flags.VBlank) {
CPU::ClearNMIFlag();
}
}
@ -265,15 +266,14 @@ void PPU::UpdateStatusFlag()
((uint8_t)_statusFlags.VerticalBlank << 7);
_statusFlags.VerticalBlank = false;
if(_scanline == 241) {
if(_cycle < 3) {
//"Reading on the same PPU clock or one later reads it as set, clears it, and suppresses the NMI for that frame."
CPU::ClearNMIFlag();
if(_scanline == 241 && _cycle < 3) {
//"Reading on the same PPU clock or one later reads it as set, clears it, and suppresses the NMI for that frame."
_statusFlags.VerticalBlank = false;
CPU::ClearNMIFlag();
if(_cycle == 0) {
//"Reading one PPU clock before reads it as clear and never sets the flag or generates NMI for that frame. "
_doNotSetVBFlag = true;
}
if(_cycle == 0) {
//"Reading one PPU clock before reads it as clear and never sets the flag or generates NMI for that frame. "
_state.Status = ((uint8_t)_statusFlags.SpriteOverflow << 5) | ((uint8_t)_statusFlags.Sprite0Hit << 6);
}
}
}
@ -658,15 +658,12 @@ void PPU::SendFrame()
void PPU::BeginVBlank()
{
if(_cycle == 1) {
if(_cycle == 0) {
SendFrame();
if(!_doNotSetVBFlag) {
_statusFlags.VerticalBlank = true;
if(_flags.VBlank) {
CPU::SetNMIFlag();
}
_statusFlags.VerticalBlank = true;
if(_flags.VBlank) {
CPU::SetNMIFlag();
}
_doNotSetVBFlag = false;
}
}
@ -746,7 +743,7 @@ void PPU::StreamState(bool saving)
Stream<int32_t>(_scanline);
Stream<uint32_t>(_cycle);
Stream<uint32_t>(_frameCount);
Stream<int32_t>(_frameCount);
Stream<uint8_t>(_memoryReadBuffer);
StreamArray<uint8_t>(_paletteRAM, 0x20);

View file

@ -97,7 +97,7 @@ class PPU : public IMemoryHandler, public Snapshotable
PPUState _state;
int32_t _scanline;
uint32_t _cycle;
uint32_t _frameCount;
int32_t _frameCount;
uint8_t _memoryReadBuffer;
uint8_t _paletteRAM[0x20];
@ -117,8 +117,6 @@ class PPU : public IMemoryHandler, public Snapshotable
uint16_t _intensifyColorBits;
uint8_t _paletteRamMask;
bool _doNotSetVBFlag = false;
SpriteInfo *_lastSprite; //used by HD ppu
TileInfo _currentTile;

View file

@ -34,12 +34,12 @@
this.lblVRAMAddr = new System.Windows.Forms.Label();
this.lblCycle = new System.Windows.Forms.Label();
this.txtCycle = new System.Windows.Forms.TextBox();
this.lblScanline = new System.Windows.Forms.Label();
this.txtScanline = new System.Windows.Forms.TextBox();
this.txtVRAMAddr = new System.Windows.Forms.TextBox();
this.chkVerticalBlank = new System.Windows.Forms.CheckBox();
this.chkSprite0Hit = new System.Windows.Forms.CheckBox();
this.chkSpriteOverflow = new System.Windows.Forms.CheckBox();
this.lblScanline = new System.Windows.Forms.Label();
this.txtScanline = new System.Windows.Forms.TextBox();
this.grpControlMask = new System.Windows.Forms.GroupBox();
this.tableLayoutPanel9 = new System.Windows.Forms.TableLayoutPanel();
this.chkDrawLeftSpr = new System.Windows.Forms.CheckBox();
@ -100,6 +100,8 @@
this.txtY = new System.Windows.Forms.TextBox();
this.lblPC = new System.Windows.Forms.Label();
this.txtPC = new System.Windows.Forms.TextBox();
this.lblCycleCount = new System.Windows.Forms.Label();
this.txtCycleCount = new System.Windows.Forms.TextBox();
this.tableLayoutPanel2.SuspendLayout();
this.grpPPUStatus.SuspendLayout();
this.tableLayoutPanel8.SuspendLayout();
@ -228,25 +230,6 @@
this.txtCycle.Size = new System.Drawing.Size(58, 20);
this.txtCycle.TabIndex = 2;
//
// lblScanline
//
this.lblScanline.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lblScanline.AutoSize = true;
this.lblScanline.Location = new System.Drawing.Point(0, 32);
this.lblScanline.Margin = new System.Windows.Forms.Padding(0);
this.lblScanline.Name = "lblScanline";
this.lblScanline.Size = new System.Drawing.Size(51, 13);
this.lblScanline.TabIndex = 3;
this.lblScanline.Text = "Scanline:";
this.lblScanline.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// txtScanline
//
this.txtScanline.Location = new System.Drawing.Point(69, 29);
this.txtScanline.Name = "txtScanline";
this.txtScanline.Size = new System.Drawing.Size(58, 20);
this.txtScanline.TabIndex = 4;
//
// txtVRAMAddr
//
this.txtVRAMAddr.Location = new System.Drawing.Point(69, 55);
@ -290,6 +273,25 @@
this.chkSpriteOverflow.Text = "Sprite Overflow";
this.chkSpriteOverflow.UseVisualStyleBackColor = true;
//
// lblScanline
//
this.lblScanline.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lblScanline.AutoSize = true;
this.lblScanline.Location = new System.Drawing.Point(0, 32);
this.lblScanline.Margin = new System.Windows.Forms.Padding(0);
this.lblScanline.Name = "lblScanline";
this.lblScanline.Size = new System.Drawing.Size(51, 13);
this.lblScanline.TabIndex = 3;
this.lblScanline.Text = "Scanline:";
this.lblScanline.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// txtScanline
//
this.txtScanline.Location = new System.Drawing.Point(69, 29);
this.txtScanline.Name = "txtScanline";
this.txtScanline.Size = new System.Drawing.Size(58, 20);
this.txtScanline.TabIndex = 4;
//
// grpControlMask
//
this.grpControlMask.Controls.Add(this.tableLayoutPanel9);
@ -912,6 +914,8 @@
this.flowLayoutPanel1.Controls.Add(this.txtY);
this.flowLayoutPanel1.Controls.Add(this.lblPC);
this.flowLayoutPanel1.Controls.Add(this.txtPC);
this.flowLayoutPanel1.Controls.Add(this.lblCycleCount);
this.flowLayoutPanel1.Controls.Add(this.txtCycleCount);
this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.flowLayoutPanel1.Location = new System.Drawing.Point(3, 3);
this.flowLayoutPanel1.Name = "flowLayoutPanel1";
@ -998,6 +1002,26 @@
this.txtPC.Size = new System.Drawing.Size(42, 20);
this.txtPC.TabIndex = 7;
//
// lblCycleCount
//
this.lblCycleCount.AutoSize = true;
this.lblCycleCount.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblCycleCount.Location = new System.Drawing.Point(198, 0);
this.lblCycleCount.Margin = new System.Windows.Forms.Padding(0);
this.lblCycleCount.Name = "lblCycleCount";
this.lblCycleCount.Size = new System.Drawing.Size(36, 20);
this.lblCycleCount.TabIndex = 8;
this.lblCycleCount.Text = "Cycle:";
this.lblCycleCount.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// txtCycleCount
//
this.txtCycleCount.Location = new System.Drawing.Point(234, 0);
this.txtCycleCount.Margin = new System.Windows.Forms.Padding(0);
this.txtCycleCount.Name = "txtCycleCount";
this.txtCycleCount.Size = new System.Drawing.Size(77, 20);
this.txtCycleCount.TabIndex = 9;
//
// ctrlConsoleStatus
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@ -1115,5 +1139,7 @@
private System.Windows.Forms.Label lblPC;
private System.Windows.Forms.TextBox txtPC;
private System.Windows.Forms.ColumnHeader columnHeader1;
private System.Windows.Forms.Label lblCycleCount;
private System.Windows.Forms.TextBox txtCycleCount;
}
}

View file

@ -26,6 +26,7 @@ namespace Mesen.GUI.Debugger
txtPC.Text = state.CPU.PC.ToString("X");
txtSP.Text = state.CPU.SP.ToString("X");
txtStatus.Text = state.CPU.PS.ToString("X");
txtCycleCount.Text = state.CPU.CycleCount.ToString();
PSFlags flags = (PSFlags)state.CPU.PS;
chkBreak.Checked = flags.HasFlag(PSFlags.Break);

View file

@ -358,6 +358,7 @@ namespace Mesen.GUI
public Byte Y;
public Byte PS;
public IRQSource IRQFlag;
public Int32 CycleCount;
[MarshalAs(UnmanagedType.I1)]
public bool NMIFlag;