diff --git a/Fuse/AbstractRegisterState.cs b/Fuse/AbstractRegisterState.cs
index 4185b0a..6d79e46 100644
--- a/Fuse/AbstractRegisterState.cs
+++ b/Fuse/AbstractRegisterState.cs
@@ -30,7 +30,7 @@ namespace Fuse
protected virtual void ParseInternalState(string line)
{
- var tokens = line.Split(new char[] { ' ', '\t' });
+ var tokens = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
this.ParseInternalState(tokens);
}
diff --git a/Fuse/TestEvent.cs b/Fuse/TestEvent.cs
index 611f134..b401c66 100644
--- a/Fuse/TestEvent.cs
+++ b/Fuse/TestEvent.cs
@@ -35,7 +35,7 @@ namespace Fuse
private bool TryParseLine(string line)
{
- var split = line.Split(new char[] { ' ', '\t' });
+ var split = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
return this.TryParseLine(split);
}
diff --git a/Fuse/TestEvents.cs b/Fuse/TestEvents.cs
index 76548d3..2fcfa57 100644
--- a/Fuse/TestEvents.cs
+++ b/Fuse/TestEvents.cs
@@ -14,7 +14,7 @@ namespace Fuse
public void Parse(Lines lines)
{
- var success = false;
+ bool success;
do
{
var e = new TestEvent();
diff --git a/Z80/Z80.FuseTest/App.config b/Z80/Z80.FuseTest/App.config
new file mode 100644
index 0000000..193aecc
--- /dev/null
+++ b/Z80/Z80.FuseTest/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Z80/Z80.FuseTest/Program.cs b/Z80/Z80.FuseTest/Program.cs
new file mode 100644
index 0000000..1bc9fc1
--- /dev/null
+++ b/Z80/Z80.FuseTest/Program.cs
@@ -0,0 +1,17 @@
+//
+// Copyright (c) Adrian Conlon. All rights reserved.
+//
+
+namespace Fuse
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var suite = new TestSuite("fuse-tests\\tests");
+ suite.Read();
+ suite.Parse();
+ suite.Run();
+ }
+ }
+}
diff --git a/Z80/Z80.FuseTest/Properties/AssemblyInfo.cs b/Z80/Z80.FuseTest/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..cbbccd5
--- /dev/null
+++ b/Z80/Z80.FuseTest/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Z80.FuseTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Z80.FuseTest")]
+[assembly: AssemblyCopyright("Copyright © 2019")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("0233ff6c-db1a-4353-8e42-d22717770226")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Z80/Z80.FuseTest/RegisterState.cs b/Z80/Z80.FuseTest/RegisterState.cs
new file mode 100644
index 0000000..007b807
--- /dev/null
+++ b/Z80/Z80.FuseTest/RegisterState.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) Adrian Conlon. All rights reserved.
+//
+namespace Fuse
+{
+ using System;
+ using System.Globalization;
+
+ public class RegisterState : AbstractRegisterState, IRegisterState
+ {
+ public int I { get; private set; } = -1;
+
+ public int R { get; private set; } = -1;
+
+ public bool IFF1 { get; private set; } = false;
+
+ public bool IFF2 { get; private set; } = false;
+
+ public int IM { get; private set; } = -1;
+
+ protected override void ParseInternalState(string[] tokens)
+ {
+ this.I = Convert.ToInt32(tokens[0], 16);
+ this.R = Convert.ToInt32(tokens[1], 16);
+ this.IFF1 = Convert.ToInt32(tokens[2], CultureInfo.InvariantCulture) == 1;
+ this.IFF2 = Convert.ToInt32(tokens[3], CultureInfo.InvariantCulture) == 1;
+ this.IM = Convert.ToInt32(tokens[4], CultureInfo.InvariantCulture);
+ this.Halted = Convert.ToInt32(tokens[5], CultureInfo.InvariantCulture) == 1;
+ this.TStates = Convert.ToInt32(tokens[6], CultureInfo.InvariantCulture);
+ }
+ }
+}
diff --git a/Z80/Z80.FuseTest/TestRunner.cs b/Z80/Z80.FuseTest/TestRunner.cs
new file mode 100644
index 0000000..07fddf7
--- /dev/null
+++ b/Z80/Z80.FuseTest/TestRunner.cs
@@ -0,0 +1,361 @@
+//
+// Copyright (c) Adrian Conlon. All rights reserved.
+//
+
+namespace Fuse
+{
+ 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 EightBit.Ram ram = new EightBit.Ram(0x10000);
+ private readonly EightBit.InputOutput ports = new EightBit.InputOutput();
+ private readonly EightBit.Z80 cpu;
+
+ public TestRunner(Test test, Result result)
+ {
+ this.cpu = new EightBit.Z80(this, this.ports);
+ this.test = test;
+ this.result = result;
+ }
+
+ 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()
+ {
+ }
+
+ 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 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, actualHigh, expectedHigh);
+ }
+
+ if (expectedLow != actualLow)
+ {
+ DumpDifference(lowDescription, actualLow, expectedLow);
+ }
+ }
+
+ 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();
+ }
+
+ 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 expectedWord = expectedRegisters[(int)Register.AF];
+ var actualWord = this.cpu.AF;
+ DumpDifference("A", "F", expectedWord, actualWord);
+ }
+
+ 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", actualWord, expectedWord);
+ }
+
+ if (!iy)
+ {
+ var expectedWord = expectedRegisters[(int)Register.IY];
+ var actualWord = this.cpu.IY;
+ DumpDifference("IYH", "IYL", actualWord, expectedWord);
+ }
+
+ 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", actualWord, expectedWord);
+ }
+
+ this.cpu.ExxAF();
+ this.cpu.Exx();
+
+ if (!af_)
+ {
+ var expectedWord = expectedRegisters[(int)Register.AF];
+ var actualWord = this.cpu.AF;
+ DumpDifference("A'", "F'", expectedWord, actualWord);
+ }
+
+ if (!bc_)
+ {
+ var expectedWord = expectedRegisters[(int)Register.BC_];
+ var actualWord = this.cpu.BC;
+ DumpDifference("B'", "C'", actualWord, expectedWord);
+ }
+
+ if (!de_)
+ {
+ var expectedWord = expectedRegisters[(int)Register.DE_];
+ var actualWord = this.cpu.DE;
+ DumpDifference("D'", "E'", actualWord, expectedWord);
+ }
+
+ if (!hl_)
+ {
+ var expectedWord = expectedRegisters[(int)Register.HL_];
+ var actualWord = this.cpu.HL;
+ DumpDifference("H'", "L'", actualWord, expectedWord);
+ }
+
+ 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: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 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;
+ }
+ }
+ }
+ }
+}
diff --git a/Z80/Z80.FuseTest/TestSuite.cs b/Z80/Z80.FuseTest/TestSuite.cs
new file mode 100644
index 0000000..e7de7e8
--- /dev/null
+++ b/Z80/Z80.FuseTest/TestSuite.cs
@@ -0,0 +1,58 @@
+//
+// Copyright (c) Adrian Conlon. All rights reserved.
+//
+namespace Fuse
+{
+ public class TestSuite
+ {
+ private readonly Tests tests;
+ private readonly Results results;
+
+ public TestSuite(string path)
+ {
+ this.tests = new Tests(path + ".in");
+ this.results = new Results(path + ".expected");
+ }
+
+ public void Read()
+ {
+ this.tests.Read();
+ this.results.Read();
+ }
+
+ public void Parse()
+ {
+ this.tests.Parse();
+ this.results.Parse();
+ }
+
+ public void Run()
+ {
+ var failedCount = 0;
+ var unimplementedCount = 0;
+ foreach (var test in this.tests.Container)
+ {
+ var key = test.Key;
+ System.Console.Out.WriteLine($"** Checking: {key}");
+
+ var input = test.Value;
+ var result = this.results.Container[key];
+ var runner = new TestRunner(input, result);
+
+ runner.Run();
+ if (runner.Failed)
+ {
+ ++failedCount;
+ }
+
+ if (runner.Unimplemented)
+ {
+ ++unimplementedCount;
+ }
+ }
+
+ System.Console.Out.WriteLine($"+++ Failed test count: {failedCount}");
+ System.Console.Out.WriteLine($"+++ Unimplemented test count: {unimplementedCount}");
+ }
+ }
+}
diff --git a/Z80/Z80.FuseTest/Z80.FuseTest.csproj b/Z80/Z80.FuseTest/Z80.FuseTest.csproj
new file mode 100644
index 0000000..a1e218b
--- /dev/null
+++ b/Z80/Z80.FuseTest/Z80.FuseTest.csproj
@@ -0,0 +1,70 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {0233FF6C-DB1A-4353-8E42-D22717770226}
+ Exe
+ Z80.FuseTest
+ Z80.FuseTest
+ v4.8
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {6ebf8857-62a3-4ef4-af21-c1844031d7e4}
+ EightBit
+
+
+ {28e65032-5dff-406f-9385-0ee1422a7f4a}
+ Fuse
+
+
+ {c00648c1-bac1-4efb-816f-e87c091619d7}
+ Z80
+
+
+
+
\ No newline at end of file
diff --git a/Z80/Z80.FuseTest/fuse-tests/README b/Z80/Z80.FuseTest/fuse-tests/README
new file mode 100644
index 0000000..33c6309
--- /dev/null
+++ b/Z80/Z80.FuseTest/fuse-tests/README
@@ -0,0 +1,79 @@
+File formats
+============
+
+tests.in
+--------
+
+Each test has the format:
+
+
+AF BC DE HL AF' BC' DE' HL' IX IY SP PC MEMPTR
+I R IFF1 IFF2 IM
+
+ specifies whether the Z80 is halted.
+ specifies the number of tstates to run the test for, in
+ decimal; the number actually executed may be higher, as the final
+ instruction is allowed to complete.
+
+Then followed by lines specifying the initial memory setup. Each has
+the format:
+
+ ... -1
+
+eg
+
+1234 56 78 9a -1
+
+says to put 0x56 at 0x1234, 0x78 at 0x1235 and 0x9a at 0x1236.
+
+Finally, -1 to end the test. Blank lines may follow before the next test.
+
+tests.expected
+--------------
+
+Each test output starts with the test description, followed by a list
+of 'events': each has the format
+
+