mirror of
https://github.com/MoleskiCoder/EightBitNet.git
synced 2025-01-10 15:29:47 +00:00
9ca85428e0
Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
479 lines
17 KiB
C#
479 lines
17 KiB
C#
// <copyright file="TestRunner.cs" company="Adrian Conlon">
|
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
|
// </copyright>
|
|
|
|
namespace Fuse
|
|
{
|
|
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<RegisterState> test;
|
|
private readonly Result<RegisterState> 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;
|
|
|
|
public TestRunner(Test<RegisterState> test, Result<RegisterState> result)
|
|
{
|
|
this.cpu = new EightBit.Z80(this, this.ports);
|
|
this.test = test;
|
|
this.result = result;
|
|
|
|
foreach (var e in result.Events.Container)
|
|
{
|
|
if (!e.Specifier.EndsWith("C"))
|
|
{
|
|
this.expectedEvents.Add(new TestEvent(0, e.Specifier, e.Address, e.Value));
|
|
}
|
|
}
|
|
}
|
|
|
|
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.Mask16, 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;
|
|
}
|
|
|
|
protected override void OnReadByte()
|
|
{
|
|
this.actualEvents.Add(new TestEvent(0, "MR", this.Address.Word, this.Data));
|
|
base.OnReadByte();
|
|
}
|
|
|
|
protected override void OnWrittenByte()
|
|
{
|
|
this.actualEvents.Add(new TestEvent(0, "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.cpu.Cycles, "PW", this.Address.Word, this.Data));
|
|
|
|
private void Ports_ReadPort(object sender, EightBit.PortEventArgs e) => this.actualEvents.Add(new TestEvent(this.cpu.Cycles, "PR", this.Address.Word, this.Data));
|
|
|
|
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<TestEvent> events)
|
|
{
|
|
foreach (var e in events)
|
|
{
|
|
var output = $" Event issue {ToString(e)}";
|
|
System.Console.Error.WriteLine(output);
|
|
}
|
|
}
|
|
|
|
private static string ToString(TestEvent e)
|
|
{
|
|
var output = $"Cycles = {e.Cycles}, Specifier = {e.Specifier}, Address = {e.Address:X4}";
|
|
if (!e.Specifier.EndsWith("C"))
|
|
{
|
|
output += $", Value={e.Value:X2}";
|
|
}
|
|
|
|
return 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|