Sort out GB timing (enough to pass Blargg, anyway)

This commit is contained in:
Adrian Conlon
2024-10-12 14:38:45 +01:00
parent 0bc3cb9d03
commit fa13852e53
3 changed files with 133 additions and 181 deletions

View File

@@ -171,7 +171,7 @@ namespace EightBit
return condition;
}
protected bool JumpConditional(bool condition)
protected virtual bool JumpConditional(bool condition)
{
this.FetchWordMEMPTR();
if (condition)
@@ -182,7 +182,7 @@ namespace EightBit
return condition;
}
protected bool ReturnConditional(bool condition)
protected virtual bool ReturnConditional(bool condition)
{
if (condition)
{
@@ -198,7 +198,7 @@ namespace EightBit
this.MEMPTR.Assign(this.Intermediate);
}
protected void JumpIndirect()
protected virtual void JumpIndirect()
{
this.FetchWordMEMPTR();
this.Jump(this.MEMPTR);
@@ -210,13 +210,13 @@ namespace EightBit
this.Call(this.MEMPTR);
}
protected void JumpRelative(sbyte offset)
protected virtual void JumpRelative(sbyte offset)
{
this.MEMPTR.Word = (ushort)(this.PC.Word + offset);
this.Jump(this.MEMPTR);
}
protected bool JumpRelativeConditional(bool condition)
protected virtual bool JumpRelativeConditional(bool condition)
{
this.Intermediate.Assign(this.PC);
++this.PC.Word;
@@ -229,7 +229,7 @@ namespace EightBit
return condition;
}
protected sealed override void Return()
protected override void Return()
{
base.Return();
this.MEMPTR.Assign(this.PC);

View File

@@ -70,11 +70,11 @@ namespace LR35902
private readonly Bus bus;
private readonly Register16 divCounter = new(0xab, 0xcc);
private int timerCounter;
private int timerRate;
private readonly Register16 dmaAddress = new();
private int timerCounter = 0;
private bool dmaTransferActive = false;
private bool dmaTransferActive;
private bool scanP15 = false;
private bool scanP14 = false;
@@ -94,7 +94,7 @@ namespace LR35902
this.bus.WrittenByte += this.Bus_WrittenByte;
}
public event EventHandler<LcdStatusModeEventArgs> DisplayStatusModeUpdated;
public event EventHandler<LcdStatusModeEventArgs>? DisplayStatusModeUpdated;
public bool BootRomDisabled { get; private set; } = false;
@@ -106,32 +106,19 @@ namespace LR35902
public bool TimerDisabled => (this.TimerControl & (byte)Bits.Bit2) == 0;
public int TimerClockTicks
public int TimerClockTicks => this.TimerClock switch
{
get
{
switch (this.TimerClock)
{
case 0b00:
return 1024; // 4.096 Khz
case 0b01:
return 16; // 262.144 Khz
case 0b10:
return 64; // 65.536 Khz
case 0b11:
return 256; // 16.384 Khz
}
throw new InvalidOperationException("Invalid timer clock specification");
}
}
0b00 => 256,// 4.096 Khz
0b01 => 4, // 262.144 Khz
0b10 => 16, // 65.536 Khz
0b11 => 64, // 16.384 Khz
_ => throw new InvalidOperationException("Invalid timer clock specification"),
};
private ref byte TimerControl => ref this.Reference(TAC);
private ref byte TimerModulo => ref this.Reference(TMA);
private ref byte TimerCounter => ref this.Reference(TIMA);
public void Reset()
{
this.Poke(NR52, 0xf1);
@@ -151,28 +138,28 @@ namespace LR35902
public void TriggerInterrupt(Interrupts cause) => this.Reference(IF) |= (byte)cause;
public void CheckTimers(int cycles)
public void IncrementTimers()
{
this.IncrementDIV(cycles);
this.CheckTimer(cycles);
this.IncrementDIV();
this.IncrementTimer();
}
public void IncrementDIV(int cycles)
public void IncrementDIV()
{
this.divCounter.Word += (ushort)cycles;
++this.divCounter.Word;
this.Poke(DIV, this.divCounter.High);
}
public void IncrementTIMA()
{
var updated = this.TimerCounter + 1;
var updated = this.Peek(TIMA) + 1;
if ((updated & (int)Bits.Bit8) != 0)
{
this.TriggerInterrupt(Interrupts.TimerOverflow);
updated = this.TimerModulo;
}
this.TimerCounter = Chip.LowByte(updated);
this.Poke(TIMA, Chip.LowByte(updated));
}
public void IncrementLY() => this.Poke(LY, (byte)((this.Peek(LY) + 1) % Bus.TotalLineCount));
@@ -272,14 +259,13 @@ namespace LR35902
private void OnDisplayStatusModeUpdated(LcdStatusMode mode) => this.DisplayStatusModeUpdated?.Invoke(this, new LcdStatusModeEventArgs(mode));
private void CheckTimer(int cycles)
private void IncrementTimer()
{
if (this.TimerEnabled)
{
this.timerCounter -= cycles;
if (this.timerCounter <= 0)
if (--this.timerCounter <= 0)
{
this.timerCounter += this.TimerClockTicks;
this.timerCounter += this.timerRate;
this.IncrementTIMA();
}
}
@@ -315,6 +301,7 @@ namespace LR35902
case TMA: // R/W
break;
case TAC: // R/W
timerRate = this.TimerClockTicks;
break;
case IF: // R/W

View File

@@ -95,9 +95,6 @@ namespace LR35902
{
this.Execute(this.FetchByte());
}
this.bus.IO.CheckTimers(this.ClockCycles);
this.bus.IO.TransferDma();
}
protected override void HandleRESET()
@@ -105,7 +102,7 @@ namespace LR35902
base.HandleRESET();
this.DI();
this.SP.Word = (ushort)(Mask.Sixteen - 1);
this.Tick(4);
this.TickMachine(4);
}
protected override void HandleINT()
@@ -114,7 +111,91 @@ namespace LR35902
this.RaiseHALT();
this.DI();
this.Restart(this.Bus.Data);
}
public event EventHandler<EventArgs>? MachineTicked;
private void OnMachineTicked()
{
MachineTicked?.Invoke(this, EventArgs.Empty);
this.bus.IO.IncrementTimers();
this.bus.IO.TransferDma();
}
private void TickMachine(int extra)
{
for (var i = 0; i < extra; ++i)
{
this.TickMachine();
}
}
private void TickMachine()
{
this.Tick(4);
this.OnMachineTicked();
}
protected override void MemoryWrite()
{
this.TickMachine();
base.MemoryWrite();
}
protected override byte MemoryRead()
{
this.TickMachine();
return base.MemoryRead();
}
protected override void PushWord(Register16 value)
{
this.TickMachine();
base.PushWord(value);
}
protected override void JumpRelative(sbyte offset)
{
base.JumpRelative(offset);
this.TickMachine();
}
protected override bool JumpConditional(bool condition)
{
if (base.JumpConditional(condition))
{
this.TickMachine();
}
return condition;
}
protected override bool ReturnConditional(bool condition)
{
_ = base.ReturnConditional(condition);
this.TickMachine();
return condition;
}
protected override bool JumpRelativeConditional(bool condition)
{
if (!base.JumpRelativeConditional(condition))
{
this.TickMachine();
}
return condition;
}
protected override void Return()
{
base.Return();
this.TickMachine();
}
protected override void JumpIndirect()
{
base.JumpIndirect();
this.TickMachine();
}
private static byte SetBit(byte f, StatusBits flag) => SetBit(f, (byte)flag);
@@ -228,45 +309,21 @@ namespace LR35902
7 => this.SRL(operand),
_ => throw new InvalidOperationException("Unreachable code block reached"),
};
this.Tick(2);
this.R(z, operand);
this.F = AdjustZero(this.F, operand);
if (z == 6)
{
this.Tick(2);
}
break;
}
case 1: // BIT y, r[z]
this.Bit(y, this.R(z));
this.Tick(2);
if (z == 6)
{
this.Tick(2);
}
break;
case 2: // RES y, r[z]
this.R(z, Res(y, this.R(z)));
this.Tick(2);
if (z == 6)
{
this.Tick(2);
}
break;
case 3: // SET y, r[z]
this.R(z, Set(y, this.R(z)));
this.Tick(2);
if (z == 6)
{
this.Tick(2);
}
break;
default:
@@ -285,31 +342,22 @@ namespace LR35902
switch (y)
{
case 0: // NOP
this.Tick();
break;
case 1: // GB: LD (nn),SP
this.Bus.Address.Word = this.FetchWord().Word;
this.SetWord(this.SP);
this.Tick(5);
break;
case 2: // GB: STOP
this.Stop();
this.Tick();
break;
case 3: // JR d
this.JumpRelative((sbyte)this.FetchByte());
this.Tick(3);
break;
case 4: // JR cc,d
case 5:
case 6:
case 7:
if (this.JumpRelativeConditionalFlag(y - 4))
{
this.Tick();
}
this.Tick(2);
_ = this.JumpRelativeConditionalFlag(y - 4);
break;
default:
throw new InvalidOperationException("Unreachable code block reached");
@@ -322,12 +370,10 @@ namespace LR35902
{
case 0: // LD rp,nn
this.RP(p).Word = this.FetchWord().Word;
this.Tick(3);
break;
case 1: // ADD HL,rp
this.Add(this.HL, this.RP(p));
this.Tick(2);
break;
default:
@@ -344,22 +390,18 @@ namespace LR35902
{
case 0: // LD (BC),A
this.MemoryWrite(this.BC, this.A);
this.Tick(2);
break;
case 1: // LD (DE),A
this.MemoryWrite(this.DE, this.A);
this.Tick(2);
break;
case 2: // GB: LDI (HL),A
this.MemoryWrite(this.HL.Word++, this.A);
this.Tick(2);
break;
case 3: // GB: LDD (HL),A
this.MemoryWrite(this.HL.Word--, this.A);
this.Tick(2);
break;
default:
@@ -369,32 +411,14 @@ namespace LR35902
break;
case 1:
switch (p)
this.A = p switch
{
case 0: // LD A,(BC)
this.A = this.MemoryRead(this.BC);
this.Tick(2);
break;
case 1: // LD A,(DE)
this.A = this.MemoryRead(this.DE);
this.Tick(2);
break;
case 2: // GB: LDI A,(HL)
this.A = this.MemoryRead(this.HL.Word++);
this.Tick(2);
break;
case 3: // GB: LDD A,(HL)
this.A = this.MemoryRead(this.HL.Word--);
this.Tick(2);
break;
default:
throw new InvalidOperationException("Invalid operation mode");
}
0 => this.MemoryRead(this.BC), // LD A,(BC)
1 => this.MemoryRead(this.DE), // LD A,(DE)
2 => this.MemoryRead(this.HL.Word++), // GB: LDI A,(HL)
3 => this.MemoryRead(this.HL.Word--), // GB: LDD A,(HL)
_ => throw new InvalidOperationException("Invalid operation mode"),
};
break;
default:
@@ -418,32 +442,19 @@ namespace LR35902
throw new InvalidOperationException("Invalid operation mode");
}
this.Tick(2);
this.TickMachine();
break;
case 4: // 8-bit INC
this.R(y, this.Increment(this.R(y)));
this.Tick();
if (y == 6)
{
this.Tick(2);
}
break;
case 5: // 8-bit DEC
this.R(y, this.Decrement(this.R(y)));
this.Tick();
if (y == 6)
{
this.Tick(2);
}
break;
case 6: // 8-bit load immediate
this.R(y, this.FetchByte());
this.Tick(2);
break;
case 7: // Assorted operations on accumulator/flags
@@ -476,8 +487,6 @@ namespace LR35902
default:
throw new InvalidOperationException("Invalid operation mode");
}
this.Tick();
break;
default:
@@ -494,13 +503,7 @@ namespace LR35902
else
{
this.R(y, this.R(z));
if (y == 6 || z == 6)
{
this.Tick(); // M operations
}
}
this.Tick();
break;
case 2: // Operate on accumulator and register/memory location
@@ -533,13 +536,6 @@ namespace LR35902
default:
throw new InvalidOperationException("Invalid operation mode");
}
this.Tick();
if (z == 6)
{
this.Tick();
}
break;
case 3:
switch (z)
@@ -551,23 +547,18 @@ namespace LR35902
case 1:
case 2:
case 3:
if (this.ReturnConditionalFlag(y))
{
this.Tick(3);
}
this.Tick(2);
_ = this.ReturnConditionalFlag(y);
break;
case 4: // GB: LD (FF00 + n),A
this.MemoryWrite((ushort)(IoRegisters.BASE + this.FetchByte()), this.A);
this.Tick(3);
break;
case 5:
{ // GB: ADD SP,dd
var before = this.SP.Word;
var value = (sbyte)this.FetchByte();
this.TickMachine(2);
var result = before + value;
this.SP.Word = (ushort)result;
var carried = before ^ value ^ (result & (int)Mask.Sixteen);
@@ -575,19 +566,17 @@ namespace LR35902
this.F = SetBit(this.F, StatusBits.CF, carried & (int)Bits.Bit8);
this.F = SetBit(this.F, StatusBits.HC, carried & (int)Bits.Bit4);
}
this.Tick(4);
break;
case 6: // GB: LD A,(FF00 + n)
this.A = this.MemoryRead((ushort)(IoRegisters.BASE + this.FetchByte()));
this.Tick(3);
break;
case 7:
{ // GB: LD HL,SP + dd
var before = this.SP.Word;
var value = (sbyte)this.FetchByte();
this.TickMachine();
var result = before + value;
this.HL.Word = (ushort)result;
var carried = before ^ value ^ (result & (int)Mask.Sixteen);
@@ -595,8 +584,6 @@ namespace LR35902
this.F = SetBit(this.F, StatusBits.CF, carried & (int)Bits.Bit8);
this.F = SetBit(this.F, StatusBits.HC, carried & (int)Bits.Bit4);
}
this.Tick(3);
break;
default:
@@ -609,26 +596,22 @@ namespace LR35902
{
case 0: // POP rp2[p]
this.RP2(p).Word = this.PopWord().Word;
this.Tick(3);
break;
case 1:
switch (p)
{
case 0: // RET
this.Return();
this.Tick(4);
break;
case 1: // GB: RETI
this.RetI();
this.Tick(4);
break;
case 2: // JP HL
this.Jump(this.HL.Word);
this.Tick();
break;
case 3: // LD SP,HL
this.SP.Word = this.HL.Word;
this.Tick(2);
this.TickMachine();
break;
default:
throw new InvalidOperationException("Invalid operation mode");
@@ -648,30 +631,21 @@ namespace LR35902
case 1:
case 2:
case 3:
if (this.JumpConditionalFlag(y))
{
this.Tick();
}
this.Tick(3);
_ = this.JumpConditionalFlag(y);
break;
case 4: // GB: LD (FF00 + C),A
this.MemoryWrite((ushort)(IoRegisters.BASE + this.C), this.A);
this.Tick(2);
break;
case 5: // GB: LD (nn),A
this.Bus.Address.Word = this.MEMPTR.Word = this.FetchWord().Word;
this.MemoryWrite(this.A);
this.Tick(4);
break;
case 6: // GB: LD A,(FF00 + C)
this.A = this.MemoryRead((ushort)(IoRegisters.BASE + this.C));
this.Tick(2);
break;
case 7: // GB: LD A,(nn)
this.Bus.Address.Word = this.MEMPTR.Word = this.FetchWord().Word;
this.A = this.MemoryRead();
this.Tick(4);
break;
default:
throw new InvalidOperationException("Invalid operation mode");
@@ -683,7 +657,6 @@ namespace LR35902
{
case 0: // JP nn
this.JumpIndirect();
this.Tick(4);
break;
case 1: // CB prefix
this.prefixCB = true;
@@ -691,23 +664,18 @@ namespace LR35902
break;
case 6: // DI
this.DI();
this.Tick();
//this.Tick();
break;
case 7: // EI
this.EI();
this.Tick();
//this.Tick();
break;
}
break;
case 4: // Conditional call: CALL cc[y], nn
if (this.CallConditionalFlag(y))
{
this.Tick(3);
}
this.Tick(3);
_ = this.CallConditionalFlag(y);
break;
case 5: // PUSH & various ops
@@ -715,7 +683,6 @@ namespace LR35902
{
case 0: // PUSH rp2[p]
this.PushWord(this.RP2(p));
this.Tick(4);
break;
case 1:
@@ -723,7 +690,6 @@ namespace LR35902
{
case 0: // CALL nn
this.CallIndirect();
this.Tick(6);
break;
}
@@ -765,13 +731,10 @@ namespace LR35902
default:
throw new InvalidOperationException("Invalid operation mode");
}
this.Tick(2);
break;
case 7: // Restart: RST y * 8
this.Restart((byte)(y << 3));
this.Tick(4);
break;
default:
@@ -836,6 +799,8 @@ namespace LR35902
private void Add(Register16 operand, Register16 value)
{
this.TickMachine();
this.MEMPTR.Word = operand.Word;
var result = this.MEMPTR.Word + value.Word;