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:
parent
8fdc25c5d4
commit
8d020a2e72
7 changed files with 148 additions and 66 deletions
63
Core/CPU.cpp
63
Core/CPU.cpp
|
@ -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);
|
||||
}
|
42
Core/CPU.h
42
Core/CPU.h
|
@ -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;
|
||||
}
|
||||
};
|
35
Core/PPU.cpp
35
Core/PPU.cpp
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue