using Xunit; namespace GameBoy.Tests { public sealed class Mbc3RtcTests { private const byte Mbc3TimerRamBattery = 0x10; private static Emulator NewTimerCartridge() { return new Emulator(TestRoms.BuildCartridge(Mbc3TimerRamBattery, 0x03)); } private static void EnableRtc(Emulator emulator) { emulator.WriteMemory(0x0000, 0x0A); } private static void SelectRtcRegister(Emulator emulator, byte register) { emulator.WriteMemory(0x4000, register); } private static void Latch(Emulator emulator) { emulator.WriteMemory(0x6000, 0x00); emulator.WriteMemory(0x6000, 0x01); } private static byte ReadLatched(Emulator emulator, byte register) { SelectRtcRegister(emulator, register); return emulator.ReadMemory(0xA000); } private static void WriteRtc(Emulator emulator, byte register, byte value) { SelectRtcRegister(emulator, register); emulator.WriteMemory(0xA000, value); } [Fact] public void CartridgeReportsRtcAndBattery() { var emulator = NewTimerCartridge(); Assert.True(emulator.HasRtc); Assert.True(emulator.HasBattery); } [Fact] public void SecondsRollIntoMinutes() { var emulator = NewTimerCartridge(); EnableRtc(emulator); WriteRtc(emulator, 0x08, 58); WriteRtc(emulator, 0x09, 0); emulator.AdvanceRtcSeconds(5); Latch(emulator); Assert.Equal(3, ReadLatched(emulator, 0x08)); Assert.Equal(1, ReadLatched(emulator, 0x09)); } [Fact] public void LatchFreezesTheReadableTime() { var emulator = NewTimerCartridge(); EnableRtc(emulator); WriteRtc(emulator, 0x08, 10); Latch(emulator); Assert.Equal(10, ReadLatched(emulator, 0x08)); emulator.AdvanceRtcSeconds(20); Assert.Equal(10, ReadLatched(emulator, 0x08)); Latch(emulator); Assert.Equal(30, ReadLatched(emulator, 0x08)); } [Fact] public void HaltStopsTheClock() { var emulator = NewTimerCartridge(); EnableRtc(emulator); WriteRtc(emulator, 0x08, 0); WriteRtc(emulator, 0x0C, 0x40); emulator.AdvanceRtcSeconds(30); Latch(emulator); Assert.Equal(0, ReadLatched(emulator, 0x08)); WriteRtc(emulator, 0x0C, 0x00); emulator.AdvanceRtcSeconds(30); Latch(emulator); Assert.Equal(30, ReadLatched(emulator, 0x08)); } [Fact] public void DayCounterOverflowSetsCarry() { var emulator = NewTimerCartridge(); EnableRtc(emulator); WriteRtc(emulator, 0x08, 0); WriteRtc(emulator, 0x09, 0); WriteRtc(emulator, 0x0A, 0); WriteRtc(emulator, 0x0B, 0xFF); WriteRtc(emulator, 0x0C, 0x01); emulator.AdvanceRtcSeconds(86400); Latch(emulator); Assert.Equal(0x00, ReadLatched(emulator, 0x0B)); byte dayHigh = ReadLatched(emulator, 0x0C); Assert.Equal(0x00, dayHigh & 0x01); Assert.Equal(0x80, dayHigh & 0x80); } [Fact] public void ClockAdvancesFromEmulatedCycles() { var emulator = NewTimerCartridge(); EnableRtc(emulator); WriteRtc(emulator, 0x08, 0); for (long i = 0; i < 4194304 / 4 * 3; i++) emulator.Tick4(); Latch(emulator); Assert.Equal(3, ReadLatched(emulator, 0x08)); } } }