.net 9 gb analysis changes

This commit is contained in:
Adrian Conlon
2024-10-12 12:57:38 +01:00
parent 4839f3fc04
commit b38462bddf
11 changed files with 192 additions and 268 deletions

View File

@@ -3,6 +3,7 @@
// </copyright>
namespace EightBit.GameBoy
{
using LR35902;
using System;
using System.Collections.Generic;
@@ -15,13 +16,13 @@ namespace EightBit.GameBoy
public const int CyclesPerLine = CyclesPerFrame / TotalLineCount;
public const int RomPageSize = 0x4000;
private readonly Rom bootRom = new Rom(0x100); // 0x0000 - 0x00ff
private readonly List<Rom> gameRomBanks = new List<Rom>(); // 0x0000 - 0x3fff, 0x4000 - 0x7fff (switchable)
private readonly List<Ram> ramBanks = new List<Ram>(); // 0xa000 - 0xbfff (switchable)
private readonly UnusedMemory unmapped2000 = new UnusedMemory(0x2000, 0xff); // 0xa000 - 0xbfff
private readonly Ram lowInternalRam = new Ram(0x2000); // 0xc000 - 0xdfff (mirrored at 0xe000)
private readonly UnusedMemory unmapped60 = new UnusedMemory(0x60, 0xff); // 0xfea0 - 0xfeff
private readonly Ram highInternalRam = new Ram(0x80); // 0xff80 - 0xffff
private readonly Rom bootRom = new(0x100); // 0x0000 - 0x00ff
private readonly List<Rom> gameRomBanks = new(); // 0x0000 - 0x3fff, 0x4000 - 0x7fff (switchable)
private readonly List<Ram> ramBanks = new(); // 0xa000 - 0xbfff (switchable)
private readonly UnusedMemory unmapped2000 = new(0x2000, 0xff); // 0xa000 - 0xbfff
private readonly Ram lowInternalRam = new(0x2000); // 0xc000 - 0xdfff (mirrored at 0xe000)
private readonly UnusedMemory unmapped60 = new(0x60, 0xff); // 0xfea0 - 0xfeff
private readonly Ram highInternalRam = new(0x80); // 0xff80 - 0xffff
private bool enabledLCD = false;
@@ -240,20 +241,20 @@ namespace EightBit.GameBoy
// ROM type
switch (this.gameRomBanks[0].Peek(0x147))
{
case (byte)CartridgeType.ROM:
this.rom = true;
break;
case (byte)CartridgeType.ROM_MBC1:
this.rom = this.banked = true;
break;
case (byte)CartridgeType.ROM_MBC1_RAM:
this.rom = this.banked = this.ram = true;
break;
case (byte)CartridgeType.ROM_MBC1_RAM_BATTERY:
this.rom = this.banked = this.ram = this.battery = true;
break;
default:
throw new InvalidOperationException("Unhandled cartridge ROM type");
case (byte)CartridgeType.ROM:
this.rom = true;
break;
case (byte)CartridgeType.ROM_MBC1:
this.rom = this.banked = true;
break;
case (byte)CartridgeType.ROM_MBC1_RAM:
this.rom = this.banked = this.ram = true;
break;
case (byte)CartridgeType.ROM_MBC1_RAM_BATTERY:
this.rom = this.banked = this.ram = this.battery = true;
break;
default:
throw new InvalidOperationException("Unhandled cartridge ROM type");
}
// ROM size
@@ -262,28 +263,28 @@ namespace EightBit.GameBoy
var romSizeSpecification = this.Peek(0x148);
switch (romSizeSpecification)
{
case 0x52:
gameRomBanks = 72;
break;
case 0x53:
gameRomBanks = 80;
break;
case 0x54:
gameRomBanks = 96;
break;
default:
if (romSizeSpecification > 6)
{
throw new InvalidOperationException("Invalid ROM size specification");
}
case 0x52:
gameRomBanks = 72;
break;
case 0x53:
gameRomBanks = 80;
break;
case 0x54:
gameRomBanks = 96;
break;
default:
if (romSizeSpecification > 6)
{
throw new InvalidOperationException("Invalid ROM size specification");
}
gameRomBanks = 1 << (romSizeSpecification + 1);
if (gameRomBanks != this.gameRomBanks.Count)
{
throw new InvalidOperationException("ROM size specification mismatch");
}
gameRomBanks = 1 << (romSizeSpecification + 1);
if (gameRomBanks != this.gameRomBanks.Count)
{
throw new InvalidOperationException("ROM size specification mismatch");
}
break;
break;
}
// RAM size

View File

@@ -4,6 +4,8 @@
namespace EightBit.GameBoy
{
using LR35902;
public enum IoRegister
{
Abbreviated, // FF00 + dd

View File

@@ -15,5 +15,5 @@ namespace EightBit.GameBoy
public const int RasterHeight = 144;
public const int PixelCount = RasterWidth * RasterHeight;
}
}
}

View File

@@ -68,8 +68,8 @@ namespace EightBit.GameBoy
public const int BOOT_DISABLE = 0x50;
private readonly Bus bus;
private readonly Register16 divCounter = new Register16(0xab, 0xcc);
private readonly Register16 dmaAddress = new Register16();
private readonly Register16 divCounter = new(0xab, 0xcc);
private readonly Register16 dmaAddress = new();
private int timerCounter = 0;
@@ -288,7 +288,7 @@ namespace EightBit.GameBoy
private void TriggerKeypadInterrupt() => this.TriggerInterrupt(Interrupts.KeypadPressed);
private void Bus_WrittenByte(object sender, System.EventArgs e)
private void Bus_WrittenByte(object? sender, System.EventArgs e)
{
var address = this.bus.Address.Word;
var value = this.bus.Data;
@@ -296,55 +296,55 @@ namespace EightBit.GameBoy
switch (port)
{
case P1:
this.scanP14 = (value & (byte)Bits.Bit4) == 0;
this.scanP15 = (value & (byte)Bits.Bit5) == 0;
break;
case P1:
this.scanP14 = (value & (byte)Bits.Bit4) == 0;
this.scanP15 = (value & (byte)Bits.Bit5) == 0;
break;
case SB: // R/W
case SC: // R/W
break;
case SB: // R/W
case SC: // R/W
break;
case DIV: // R/W
this.Poke(port, 0);
this.timerCounter = this.divCounter.Word = 0;
break;
case TIMA: // R/W
break;
case TMA: // R/W
break;
case TAC: // R/W
break;
case DIV: // R/W
this.Poke(port, 0);
this.timerCounter = this.divCounter.Word = 0;
break;
case TIMA: // R/W
break;
case TMA: // R/W
break;
case TAC: // R/W
break;
case IF: // R/W
break;
case IF: // R/W
break;
case LCDC:
case STAT:
case SCY:
case SCX:
break;
case DMA:
this.dmaAddress.Word = Chip.PromoteByte(value);
this.dmaTransferActive = true;
break;
case LY: // R/O
this.Poke(port, 0);
break;
case BGP:
case OBP0:
case OBP1:
case WY:
case WX:
break;
case LCDC:
case STAT:
case SCY:
case SCX:
break;
case DMA:
this.dmaAddress.Word = Chip.PromoteByte(value);
this.dmaTransferActive = true;
break;
case LY: // R/O
this.Poke(port, 0);
break;
case BGP:
case OBP0:
case OBP1:
case WY:
case WX:
break;
case BOOT_DISABLE:
this.BootRomDisabled = value != 0;
break;
case BOOT_DISABLE:
this.BootRomDisabled = value != 0;
break;
}
}
private void Bus_ReadingByte(object sender, System.EventArgs e)
private void Bus_ReadingByte(object? sender, System.EventArgs e)
{
var address = this.bus.Address.Word;
var io = (address >= BASE) && (address < 0xff80);

View File

@@ -6,7 +6,7 @@ namespace LR35902.BlarggTest
{
using System;
public class Board : EightBit.GameBoy.Bus
internal class Board : EightBit.GameBoy.Bus
{
private readonly Configuration configuration;
private readonly EightBit.GameBoy.Disassembler disassembler;
@@ -30,17 +30,19 @@ namespace LR35902.BlarggTest
public void Plug(string path) => this.LoadGameRom(this.configuration.RomDirectory + "/" + path);
private void Board_WrittenByte(object sender, System.EventArgs e)
private void Board_WrittenByte(object? sender, System.EventArgs e)
{
switch (this.Address.Word)
{
case EightBit.GameBoy.IoRegisters.BASE + EightBit.GameBoy.IoRegisters.SB:
System.Console.Out.Write(Convert.ToChar(this.Data));
break;
default:
break;
}
}
private void CPU_ExecutingInstruction_Debug(object sender, System.EventArgs e)
private void CPU_ExecutingInstruction_Debug(object? sender, System.EventArgs e)
{
if (this.IO.BootRomDisabled)
{

View File

@@ -4,11 +4,9 @@
namespace LR35902.BlarggTest
{
public class Computer
internal class Computer(Configuration configuration)
{
private readonly Board board;
public Computer(Configuration configuration) => this.board = new Board(configuration);
private readonly Board board = new(configuration);
public void Run()
{

View File

@@ -4,9 +4,9 @@
namespace LR35902.BlarggTest
{
public class Configuration
internal class Configuration
{
public bool DebugMode { get; set; } = false;
public bool DebugMode { get; set; }
public string RomDirectory { get; set; } = "roms";
}

View File

@@ -7,7 +7,7 @@
<GenerateDocumentationFile>False</GenerateDocumentationFile>
<SignAssembly>False</SignAssembly>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisLevel>latest-all</AnalysisLevel>
<OutputType>Exe</OutputType>
</PropertyGroup>

View File

@@ -4,7 +4,7 @@
namespace LR35902.BlarggTest
{
public static class Program
internal static class Program
{
public static void Main(string[] args)
{
@@ -17,7 +17,7 @@ namespace LR35902.BlarggTest
var computer = new Computer(configuration);
computer.Plug("blargg/cpu_instrs.gb"); // Passed
//computer.Plug("blargg/cpu_instrs.gb"); // Passed
////computer.Plug("blargg/01-special.gb"); // Passed
////computer.Plug("blargg/02-interrupts.gb"); // Passed
////computer.Plug("blargg/03-op sp,hl.gb"); // Passed
@@ -30,8 +30,8 @@ namespace LR35902.BlarggTest
////computer.Plug("blargg/10-bit ops.gb"); // Passed
////computer.Plug("blargg/11-op a,(hl).gb"); // Passed
////computer.Plug("blargg/instr_timing.gb"); // Failed #255
////computer.Plug("blargg/interrupt_time.gb"); // Failed
computer.Plug("blargg/instr_timing.gb"); // Failed #255
//computer.Plug("blargg/interrupt_time.gb"); // Failed
computer.RaisePOWER();
computer.Run();

View File

@@ -19,7 +19,7 @@ namespace Fuse
{
private readonly Test<T> test;
private readonly Result<T> result;
private readonly EightBit.Ram ram = new EightBit.Ram(0x10000);
private readonly EightBit.Ram ram = new(0x10000);
public TestRunner(Test<T> test, Result<T> result)
{
@@ -31,7 +31,7 @@ namespace Fuse
public bool Unimplemented { get; private set; } = false;
public override EightBit.MemoryMapping Mapping(ushort address) => new EightBit.MemoryMapping(this.ram, 0, EightBit.Mask.Sixteen, EightBit.AccessLevel.ReadWrite);
public override EightBit.MemoryMapping Mapping(ushort address) => new(this.ram, 0, EightBit.Mask.Sixteen, EightBit.AccessLevel.ReadWrite);
public void Run()
{

View File

@@ -2,19 +2,18 @@
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
namespace EightBit.GameBoy
namespace LR35902
{
using System;
using EightBit;
using EightBit.GameBoy;
using Bus = EightBit.GameBoy.Bus;
public class LR35902 : IntelProcessor
public sealed class LR35902(Bus bus) : IntelProcessor(bus)
{
private readonly Bus bus;
private readonly Register16 af = new Register16((int)Mask.Sixteen);
private readonly Bus bus = bus;
private readonly Register16 af = new((int)Mask.Sixteen);
private bool prefixCB = false;
public LR35902(Bus bus)
: base(bus) => this.bus = bus;
public int ClockCycles => this.Cycles * 4;
public override Register16 AF
@@ -73,7 +72,7 @@ namespace EightBit.GameBoy
{
this.bus.IO.Poke(IoRegisters.IF, 0);
this.LowerINT();
var index = Chip.FindFirstSet(masked);
var index = FindFirstSet(masked);
this.Bus.Data = (byte)(0x38 + (index << 3));
}
else
@@ -148,30 +147,18 @@ namespace EightBit.GameBoy
private void Start() => this.Stopped = false;
private byte R(int r)
private byte R(int r) => r switch
{
switch (r)
{
case 0:
return this.B;
case 1:
return this.C;
case 2:
return this.D;
case 3:
return this.E;
case 4:
return this.H;
case 5:
return this.L;
case 6:
return this.MemoryRead(this.HL.Word);
case 7:
return this.A;
default:
throw new ArgumentOutOfRangeException(nameof(r));
}
}
0 => this.B,
1 => this.C,
2 => this.D,
3 => this.E,
4 => this.H,
5 => this.L,
6 => this.MemoryRead(this.HL.Word),
7 => this.A,
_ => throw new ArgumentOutOfRangeException(nameof(r)),
};
private void R(int r, byte value)
{
@@ -206,87 +193,53 @@ namespace EightBit.GameBoy
}
}
private Register16 RP(int rp)
private Register16 RP(int rp) => rp switch
{
switch (rp)
{
case 0:
return this.BC;
case 1:
return this.DE;
case 2:
return this.HL;
case 3:
return this.SP;
default:
throw new ArgumentOutOfRangeException(nameof(rp));
}
}
0 => this.BC,
1 => this.DE,
2 => this.HL,
3 => this.SP,
_ => throw new ArgumentOutOfRangeException(nameof(rp)),
};
private Register16 RP2(int rp)
private Register16 RP2(int rp) => rp switch
{
switch (rp)
{
case 0:
return this.BC;
case 1:
return this.DE;
case 2:
return this.HL;
case 3:
return this.AF;
default:
throw new ArgumentOutOfRangeException(nameof(rp));
}
}
0 => this.BC,
1 => this.DE,
2 => this.HL,
3 => this.AF,
_ => throw new ArgumentOutOfRangeException(nameof(rp)),
};
private void ExecuteCB(int x, int y, int z)
{
switch (x)
{
case 0: // rot[y] r[z]
{
var operand = this.R(z);
switch (y)
{
case 0:
operand = this.RLC(operand);
break;
case 1:
operand = this.RRC(operand);
break;
case 2:
operand = this.RL(operand);
break;
case 3:
operand = this.RR(operand);
break;
case 4:
operand = this.SLA(operand);
break;
case 5:
operand = this.SRA(operand);
break;
case 6: // GB: SWAP r
operand = this.Swap(operand);
break;
case 7:
operand = this.SRL(operand);
break;
default:
throw new InvalidOperationException("Unreachable code block reached");
}
this.Tick(2);
this.R(z, operand);
this.F = AdjustZero(this.F, operand);
if (z == 6)
{
var operand = this.R(z);
operand = y switch
{
0 => this.RLC(operand),
1 => this.RRC(operand),
2 => this.RL(operand),
3 => this.RR(operand),
4 => this.SLA(operand),
5 => this.SRA(operand),
6 => this.Swap(operand), // GB: SWAP r
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;
}
break;
}
case 1: // BIT y, r[z]
this.Bit(y, this.R(z));
@@ -543,7 +496,7 @@ namespace EightBit.GameBoy
else
{
this.R(y, this.R(z));
if ((y == 6) || (z == 6))
if (y == 6 || z == 6)
{
this.Tick(); // M operations
}
@@ -847,73 +800,41 @@ namespace EightBit.GameBoy
return operand;
}
private bool JumpConditionalFlag(int flag)
private bool JumpConditionalFlag(int flag) => flag switch
{
switch (flag)
{
case 0: // NZ
return this.JumpConditional((this.F & (byte)StatusBits.ZF) == 0);
case 1: // Z
return this.JumpConditional((this.F & (byte)StatusBits.ZF) != 0);
case 2: // NC
return this.JumpConditional((this.F & (byte)StatusBits.CF) == 0);
case 3: // C
return this.JumpConditional((this.F & (byte)StatusBits.CF) != 0);
default:
throw new ArgumentOutOfRangeException(nameof(flag));
}
}
0 => this.JumpConditional((this.F & (byte)StatusBits.ZF) == 0), // NZ
1 => this.JumpConditional((this.F & (byte)StatusBits.ZF) != 0), // Z
2 => this.JumpConditional((this.F & (byte)StatusBits.CF) == 0), // NC
3 => this.JumpConditional((this.F & (byte)StatusBits.CF) != 0), // C
_ => throw new ArgumentOutOfRangeException(nameof(flag)),
};
private bool JumpRelativeConditionalFlag(int flag)
private bool JumpRelativeConditionalFlag(int flag) => flag switch
{
switch (flag)
{
case 0: // NZ
return this.JumpRelativeConditional((this.F & (byte)StatusBits.ZF) == 0);
case 1: // Z
return this.JumpRelativeConditional((this.F & (byte)StatusBits.ZF) != 0);
case 2: // NC
return this.JumpRelativeConditional((this.F & (byte)StatusBits.CF) == 0);
case 3: // C
return this.JumpRelativeConditional((this.F & (byte)StatusBits.CF) != 0);
default:
throw new ArgumentOutOfRangeException(nameof(flag));
}
}
0 => this.JumpRelativeConditional((this.F & (byte)StatusBits.ZF) == 0), // NZ
1 => this.JumpRelativeConditional((this.F & (byte)StatusBits.ZF) != 0), // Z
2 => this.JumpRelativeConditional((this.F & (byte)StatusBits.CF) == 0), // NC
3 => this.JumpRelativeConditional((this.F & (byte)StatusBits.CF) != 0), // C
_ => throw new ArgumentOutOfRangeException(nameof(flag)),
};
private bool ReturnConditionalFlag(int flag)
private bool ReturnConditionalFlag(int flag) => flag switch
{
switch (flag)
{
case 0: // NZ
return this.ReturnConditional((this.F & (byte)StatusBits.ZF) == 0);
case 1: // Z
return this.ReturnConditional((this.F & (byte)StatusBits.ZF) != 0);
case 2: // NC
return this.ReturnConditional((this.F & (byte)StatusBits.CF) == 0);
case 3: // C
return this.ReturnConditional((this.F & (byte)StatusBits.CF) != 0);
default:
throw new ArgumentOutOfRangeException(nameof(flag));
}
}
0 => this.ReturnConditional((this.F & (byte)StatusBits.ZF) == 0), // NZ
1 => this.ReturnConditional((this.F & (byte)StatusBits.ZF) != 0), // Z
2 => this.ReturnConditional((this.F & (byte)StatusBits.CF) == 0), // NC
3 => this.ReturnConditional((this.F & (byte)StatusBits.CF) != 0), // C
_ => throw new ArgumentOutOfRangeException(nameof(flag)),
};
private bool CallConditionalFlag(int flag)
private bool CallConditionalFlag(int flag) => flag switch
{
switch (flag)
{
case 0: // NZ
return this.CallConditional((this.F & (byte)StatusBits.ZF) == 0);
case 1: // Z
return this.CallConditional((this.F & (byte)StatusBits.ZF) != 0);
case 2: // NC
return this.CallConditional((this.F & (byte)StatusBits.CF) == 0);
case 3: // C
return this.CallConditional((this.F & (byte)StatusBits.CF) != 0);
default:
throw new ArgumentOutOfRangeException(nameof(flag));
}
}
0 => this.CallConditional((this.F & (byte)StatusBits.ZF) == 0), // NZ
1 => this.CallConditional((this.F & (byte)StatusBits.ZF) != 0), // Z
2 => this.CallConditional((this.F & (byte)StatusBits.CF) == 0), // NC
3 => this.CallConditional((this.F & (byte)StatusBits.CF) != 0), // C
_ => throw new ArgumentOutOfRangeException(nameof(flag)),
};
private void Add(Register16 operand, Register16 value)
{
@@ -1054,7 +975,7 @@ namespace EightBit.GameBoy
private void Bit(int n, byte operand)
{
var carry = this.F & (byte)StatusBits.CF;
this.AndR(operand, Bit(n));
_ = this.AndR(operand, Bit(n));
this.F = SetBit(this.F, StatusBits.CF, carry);
}
@@ -1076,19 +997,19 @@ namespace EightBit.GameBoy
}
else
{
if (((this.F & (byte)StatusBits.HC) != 0) || LowNibble((byte)updated) > 9)
if ((this.F & (byte)StatusBits.HC) != 0 || LowNibble((byte)updated) > 9)
{
updated += 6;
}
if (((this.F & (byte)StatusBits.CF) != 0) || updated > 0x9F)
if ((this.F & (byte)StatusBits.CF) != 0 || updated > 0x9F)
{
updated += 0x60;
}
}
this.F = ClearBit(this.F, (byte)StatusBits.HC | (byte)StatusBits.ZF);
this.F = SetBit(this.F, StatusBits.CF, ((this.F & (byte)StatusBits.CF) != 0) || ((updated & (int)Bits.Bit8) != 0));
this.F = SetBit(this.F, StatusBits.CF, (this.F & (byte)StatusBits.CF) != 0 || (updated & (int)Bits.Bit8) != 0);
this.A = LowByte(updated);
this.F = AdjustZero(this.F, this.A);