// // Copyright (c) Adrian Conlon. All rights reserved. // namespace Fuse { using System; using System.Collections.Generic; public enum Register { AF, BC, DE, HL, AF_, BC_, DE_, HL_, IX, IY, SP, PC, MEMPTR, } public class TestRunner : EightBit.Bus { private readonly Test test; private readonly Result result; private readonly TestEvents expectedEvents = new TestEvents(); private readonly TestEvents actualEvents = new TestEvents(); private readonly EightBit.Ram ram = new EightBit.Ram(0x10000); private readonly EightBit.InputOutput ports = new EightBit.InputOutput(); private readonly EightBit.Z80 cpu; private int totalCycles = 0; public TestRunner(Test test, Result result) { this.cpu = new EightBit.Z80(this, this.ports); this.test = test ?? throw new ArgumentNullException(nameof(test)); this.result = result ?? throw new ArgumentNullException(nameof(result)); foreach (var e in result.Events.Container) { // Ignore contention events if (!e.Specifier.EndsWith("C", System.StringComparison.Ordinal)) { this.expectedEvents.Add(e); } } } public bool Failed { get; private set; } = false; 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 void Run() { this.RaisePOWER(); this.Initialize(); var allowedCycles = this.test.RegisterState.TStates; try { this.cpu.Run(allowedCycles); this.Check(); } catch (System.InvalidOperationException error) { this.Unimplemented = true; System.Console.Error.WriteLine($"**** Error: {error.Message}"); } } public override void RaisePOWER() { base.RaisePOWER(); this.cpu.RaisePOWER(); this.cpu.RaiseRESET(); this.cpu.RaiseINT(); this.cpu.RaiseNMI(); this.InitialiseRegisters(); this.InitialiseMemory(); } public override void LowerPOWER() { this.cpu.LowerPOWER(); base.LowerPOWER(); } public override void Initialize() { this.ports.ReadPort += this.Ports_ReadPort; this.ports.WrittenPort += this.Ports_WrittenPort; this.cpu.ExecutedInstruction += this.Cpu_ExecutedInstruction; } protected override void OnReadByte() { this.actualEvents.Add(new TestEvent(this.totalCycles + this.cpu.Cycles, "MR", this.Address.Word, this.Data)); base.OnReadByte(); } protected override void OnWrittenByte() { this.actualEvents.Add(new TestEvent(this.totalCycles + this.cpu.Cycles, "MW", this.Address.Word, this.Data)); base.OnWrittenByte(); } private static void DumpDifference(string description, byte expected, byte actual) { var output = $"**** {description}, Expected: {expected:x2}, Got {actual:x2}"; System.Console.Error.WriteLine(output); } private void Ports_WrittenPort(object sender, EightBit.PortEventArgs e) => this.actualEvents.Add(new TestEvent(this.totalCycles + this.cpu.Cycles, "PW", this.Address.Word, this.Data)); private void Ports_ReadPort(object sender, EightBit.PortEventArgs e) => this.actualEvents.Add(new TestEvent(this.totalCycles + this.cpu.Cycles, "PR", this.Address.Word, this.Data)); private void Cpu_ExecutedInstruction(object sender, System.EventArgs e) => this.totalCycles += this.cpu.Cycles; private static void DumpDifference(string highDescription, string lowDescription, EightBit.Register16 expected, EightBit.Register16 actual) { var expectedHigh = expected.High; var expectedLow = expected.Low; var actualHigh = actual.High; var actualLow = actual.Low; if (expectedHigh != actualHigh) { DumpDifference(highDescription, expectedHigh, actualHigh); } if (expectedLow != actualLow) { DumpDifference(lowDescription, expectedLow, actualLow); } } private void InitialiseRegisters() { var testState = this.test.RegisterState; var inputRegisters = testState.Registers; this.cpu.AF.Word = inputRegisters[(int)Register.AF_].Word; this.cpu.BC.Word = inputRegisters[(int)Register.BC_].Word; this.cpu.DE.Word = inputRegisters[(int)Register.DE_].Word; this.cpu.HL.Word = inputRegisters[(int)Register.HL_].Word; this.cpu.Exx(); this.cpu.ExxAF(); this.cpu.AF.Word = inputRegisters[(int)Register.AF].Word; this.cpu.BC.Word = inputRegisters[(int)Register.BC].Word; this.cpu.DE.Word = inputRegisters[(int)Register.DE].Word; this.cpu.HL.Word = inputRegisters[(int)Register.HL].Word; this.cpu.IX.Word = inputRegisters[(int)Register.IX].Word; this.cpu.IY.Word = inputRegisters[(int)Register.IY].Word; this.cpu.SP.Word = inputRegisters[(int)Register.SP].Word; this.cpu.PC.Word = inputRegisters[(int)Register.PC].Word; this.cpu.MEMPTR.Word = inputRegisters[(int)Register.MEMPTR].Word; this.cpu.IV = (byte)testState.I; this.cpu.REFRESH = (byte)testState.R; this.cpu.IFF1 = testState.IFF1; this.cpu.IFF2 = testState.IFF2; this.cpu.IM = testState.IM; } private void InitialiseMemory() { foreach (var memoryDatum in this.test.MemoryData) { var address = memoryDatum.Address; foreach (var seed in memoryDatum.Bytes) { this.Poke(address++, seed); } } } private void Check() { this.CheckRegisters(); this.CheckMemory(); this.CheckEvents(); } private void CheckRegisters() { var expectedState = this.result.RegisterState; var expectedRegisters = expectedState.Registers; var af = this.cpu.AF.Word == expectedRegisters[(int)Register.AF].Word; var bc = this.cpu.BC.Word == expectedRegisters[(int)Register.BC].Word; var de = this.cpu.DE.Word == expectedRegisters[(int)Register.DE].Word; var hl = this.cpu.HL.Word == expectedRegisters[(int)Register.HL].Word; this.cpu.Exx(); this.cpu.ExxAF(); var af_ = this.cpu.AF.Word == expectedRegisters[(int)Register.AF_].Word; var bc_ = this.cpu.BC.Word == expectedRegisters[(int)Register.BC_].Word; var de_ = this.cpu.DE.Word == expectedRegisters[(int)Register.DE_].Word; var hl_ = this.cpu.HL.Word == expectedRegisters[(int)Register.HL_].Word; var ix = this.cpu.IX.Word == expectedRegisters[(int)Register.IX].Word; var iy = this.cpu.IY.Word == expectedRegisters[(int)Register.IY].Word; var sp = this.cpu.SP.Word == expectedRegisters[(int)Register.SP].Word; var pc = this.cpu.PC.Word == expectedRegisters[(int)Register.PC].Word; var memptr = this.cpu.MEMPTR.Word == expectedRegisters[(int)Register.MEMPTR].Word; var iv = this.cpu.IV == expectedState.I; var refresh = this.cpu.REFRESH == expectedState.R; var iff1 = this.cpu.IFF1 == expectedState.IFF1; var iff2 = this.cpu.IFF2 == expectedState.IFF2; var im = this.cpu.IM == expectedState.IM; // And back again, so the following works as expected... this.cpu.Exx(); this.cpu.ExxAF(); var success = af && bc && de && hl && af_ && bc_ && de_ && hl_ && ix && iy && sp && pc && iv && refresh && iff1 && iff2 && im && memptr; if (!success) { this.Failed = true; System.Console.Error.WriteLine($"**** Failed test (Register): {this.test.Description}"); if (!af) { var expectedA = expectedRegisters[(int)Register.AF].High; var gotA = this.cpu.A; if (expectedA != gotA) { DumpDifference("A", expectedA, gotA); } var expectedF = expectedRegisters[(int)Register.AF].Low; var gotF = this.cpu.F; if (expectedF != gotF) { var output = $"**** F, Expected: {EightBit.Disassembler.AsFlags(expectedF)}, Got: {EightBit.Disassembler.AsFlags(gotF)}"; System.Console.Error.WriteLine(output); } } if (!bc) { var expectedWord = expectedRegisters[(int)Register.BC]; var actualWord = this.cpu.BC; DumpDifference("B", "C", expectedWord, actualWord); } if (!de) { var expectedWord = expectedRegisters[(int)Register.DE]; var actualWord = this.cpu.DE; DumpDifference("D", "E", expectedWord, actualWord); } if (!hl) { var expectedWord = expectedRegisters[(int)Register.HL]; var actualWord = this.cpu.HL; DumpDifference("H", "L", expectedWord, actualWord); } if (!ix) { var expectedWord = expectedRegisters[(int)Register.IX]; var actualWord = this.cpu.IX; DumpDifference("IXH", "IXL", expectedWord, actualWord); } if (!iy) { var expectedWord = expectedRegisters[(int)Register.IY]; var actualWord = this.cpu.IY; DumpDifference("IYH", "IYL", expectedWord, actualWord); } if (!sp) { var expectedWord = expectedRegisters[(int)Register.SP]; var actualWord = this.cpu.SP; DumpDifference("SPH", "SPL", expectedWord, actualWord); } if (!pc) { var expectedWord = expectedRegisters[(int)Register.PC]; var actualWord = this.cpu.PC; DumpDifference("PCH", "PCL", expectedWord, actualWord); } if (!memptr) { var expectedWord = expectedRegisters[(int)Register.MEMPTR]; var actualWord = this.cpu.MEMPTR; DumpDifference("MEMPTRH", "MEMPTRL", expectedWord, actualWord); } this.cpu.ExxAF(); this.cpu.Exx(); if (!af_) { var expectedA = expectedRegisters[(int)Register.AF_].High; var gotA = this.cpu.A; if (expectedA != gotA) { DumpDifference("A'", expectedA, gotA); } var expectedF = expectedRegisters[(int)Register.AF_].Low; var gotF = this.cpu.F; if (expectedF != gotF) { var output = $"**** F', Expected: {EightBit.Disassembler.AsFlags(expectedF)}, Got: {EightBit.Disassembler.AsFlags(gotF)}"; System.Console.Error.WriteLine(output); } } if (!bc_) { var expectedWord = expectedRegisters[(int)Register.BC_]; var actualWord = this.cpu.BC; DumpDifference("B'", "C'", expectedWord, actualWord); } if (!de_) { var expectedWord = expectedRegisters[(int)Register.DE_]; var actualWord = this.cpu.DE; DumpDifference("D'", "E'", expectedWord, actualWord); } if (!hl_) { var expectedWord = expectedRegisters[(int)Register.HL_]; var actualWord = this.cpu.HL; DumpDifference("H'", "L'", expectedWord, actualWord); } if (!iv) { var output = $"**** IV, Expected: {expectedState.I:X2}, Got: {this.cpu.IV:X2}"; System.Console.Error.WriteLine(output); } if (!refresh) { var output = $"**** R, Expected: {expectedState.R:X2}, Got: {this.cpu.REFRESH.ToByte():X2}"; System.Console.Error.WriteLine(output); } if (!iff1) { var output = $"**** IFF1, Expected: {expectedState.IFF1}, Got: {this.cpu.IFF1}"; System.Console.Error.WriteLine(output); } if (!iff2) { var output = $"**** IFF2, Expected: {expectedState.IFF2}, Got: {this.cpu.IFF2}"; System.Console.Error.WriteLine(output); } if (!im) { var output = $"**** IM, Expected: {expectedState.IM}, Got: {this.cpu.IM}"; System.Console.Error.WriteLine(output); } } } private void CheckEvents() { var expectations = this.expectedEvents.Container; var actuals = this.actualEvents.Container; var eventFailure = expectations.Count != actuals.Count; for (var i = 0; !eventFailure && (i < expectations.Count); ++i) { var expectation = expectations[i]; var actual = actuals[i]; var equalCycles = expectation.Cycles == actual.Cycles; var equalSpecifier = expectation.Specifier == actual.Specifier; var equalAddress = expectation.Address == actual.Address; var equalValue = expectation.Value == actual.Value; var equal = equalCycles && equalSpecifier && equalAddress && equalValue; eventFailure = !equal; } if (eventFailure) { this.DumpExpectedEvents(); this.DumpActualEvents(); } if (!this.Failed) { this.Failed = eventFailure; } } private void DumpExpectedEvents() { System.Console.Error.WriteLine("++++ Dumping expected events:"); DumpEvents(this.expectedEvents.Container); } private void DumpActualEvents() { System.Console.Error.WriteLine("++++ Dumping actual events:"); DumpEvents(this.actualEvents.Container); } private static void DumpEvents(IEnumerable events) { foreach (var e in events) { DumpEvent(e); } } private static void DumpEvent(TestEvent e) { var output = $" Event issue {e}"; System.Console.Error.WriteLine(output); } private void CheckMemory() { var first = true; foreach (var memoryDatum in this.result.MemoryData) { var address = memoryDatum.Address; foreach (var expected in memoryDatum.Bytes) { var actual = this.Peek(address); if (expected != actual) { this.Failed = true; if (first) { first = false; System.Console.Error.WriteLine($"**** Failed test (Memory): {this.test.Description}"); } System.Console.Error.WriteLine($"**** Difference: Address: {address:x4} Expected: {expected:x2} Actual: {actual:x2}"); } ++address; } } } } }