diff --git a/EightBit.sln b/EightBit.sln
index dad215d..6376d7a 100644
--- a/EightBit.sln
+++ b/EightBit.sln
@@ -27,6 +27,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MC6850", "MC6850\MC6850.csp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MC6809.Test", "MC6809\MC6809.Test\MC6809.Test.csproj", "{5ADB4727-2F5F-4A41-979B-6734501E34BC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LR35902", "LR35902\LR35902.csproj", "{01F61A1D-CB4A-4EA3-96EF-222F831DF483}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LR35902.FuseTest", "LR35902\LR35902.FuseTest\LR35902.FuseTest.csproj", "{CC24B08D-1C51-43FD-961D-7C9A49253D69}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -181,6 +185,30 @@ Global
{5ADB4727-2F5F-4A41-979B-6734501E34BC}.Release|x64.Build.0 = Release|Any CPU
{5ADB4727-2F5F-4A41-979B-6734501E34BC}.Release|x86.ActiveCfg = Release|Any CPU
{5ADB4727-2F5F-4A41-979B-6734501E34BC}.Release|x86.Build.0 = Release|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Debug|x64.Build.0 = Debug|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Debug|x86.Build.0 = Debug|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Release|Any CPU.Build.0 = Release|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Release|x64.ActiveCfg = Release|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Release|x64.Build.0 = Release|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Release|x86.ActiveCfg = Release|Any CPU
+ {01F61A1D-CB4A-4EA3-96EF-222F831DF483}.Release|x86.Build.0 = Release|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Debug|x64.Build.0 = Debug|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Debug|x86.Build.0 = Debug|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Release|x64.ActiveCfg = Release|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Release|x64.Build.0 = Release|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Release|x86.ActiveCfg = Release|Any CPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/LR35902/LR35902.FuseTest/App.config b/LR35902/LR35902.FuseTest/App.config
new file mode 100644
index 0000000..56efbc7
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LR35902/LR35902.FuseTest/ExpectedTestResult.cs b/LR35902/LR35902.FuseTest/ExpectedTestResult.cs
new file mode 100644
index 0000000..de18608
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/ExpectedTestResult.cs
@@ -0,0 +1,58 @@
+namespace Fuse
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+
+ public class ExpectedTestResult
+ {
+ private readonly TestEvents events = new TestEvents();
+
+ public bool Finish { get; private set; } = false;
+
+ public string Description { get; private set; }
+
+ public RegisterState RegisterState { get; } = new RegisterState();
+
+ public List MemoryData { get; } = new List();
+
+ public void Read(StreamReader file)
+ {
+ this.Finish = false;
+ do
+ {
+ this.Description = file.ReadLine();
+ this.Finish = file.EndOfStream;
+ }
+ while (string.IsNullOrEmpty(this.Description) && !this.Finish);
+
+ if (this.Finish)
+ {
+ return;
+ }
+
+ this.events.Read(file);
+ this.RegisterState.Read(file);
+
+ var line = file.ReadLine();
+ if (line.Length > 0)
+ {
+ throw new InvalidOperationException("EOL swallow failure!!");
+ }
+
+ var finished = false;
+ do
+ {
+ line = file.ReadLine();
+ finished = string.IsNullOrEmpty(line);
+ if (!finished)
+ {
+ var datum = new MemoryDatum();
+ datum.Parse(line);
+ this.MemoryData.Add(datum);
+ }
+ }
+ while (!finished);
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/ExpectedTestResults.cs b/LR35902/LR35902.FuseTest/ExpectedTestResults.cs
new file mode 100644
index 0000000..1c8b5dc
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/ExpectedTestResults.cs
@@ -0,0 +1,33 @@
+namespace Fuse
+{
+ using System.Collections.Generic;
+ using System.IO;
+
+ public class ExpectedTestResults
+ {
+ public Dictionary Results { get; } = new Dictionary();
+
+ public void Read(string path)
+ {
+ using (var file = new StreamReader(path))
+ {
+ this.Read(file);
+ }
+ }
+
+ private void Read(StreamReader file)
+ {
+ var finished = false;
+ while (!file.EndOfStream)
+ {
+ var result = new ExpectedTestResult();
+ result.Read(file);
+ finished = result.Finish;
+ if (!finished)
+ {
+ this.Results[result.Description] = result;
+ }
+ }
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/LR35902.FuseTest.csproj b/LR35902/LR35902.FuseTest/LR35902.FuseTest.csproj
new file mode 100644
index 0000000..7c3a94a
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/LR35902.FuseTest.csproj
@@ -0,0 +1,73 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {CC24B08D-1C51-43FD-961D-7C9A49253D69}
+ Exe
+ Fuse
+ LR35902.FuseTest
+ v4.7.2
+ 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
+
+
+ {01f61a1d-cb4a-4ea3-96ef-222f831df483}
+ LR35902
+
+
+
+
\ No newline at end of file
diff --git a/LR35902/LR35902.FuseTest/MemoryDatum.cs b/LR35902/LR35902.FuseTest/MemoryDatum.cs
new file mode 100644
index 0000000..7cd836c
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/MemoryDatum.cs
@@ -0,0 +1,34 @@
+namespace Fuse
+{
+ using System;
+ using System.Collections.Generic;
+
+ public class MemoryDatum
+ {
+ public ushort Address { get; private set; } = (ushort)EightBit.Mask.Mask16;
+
+ public List Bytes { get; } = new List();
+
+ public void Parse(string line)
+ {
+ var tokens = line.Split(new char[] { ' ', '\t' });
+ this.Parse(tokens);
+ }
+
+ public void Parse(string[] tokens)
+ {
+ this.Address = Convert.ToUInt16(tokens[0], 16);
+
+ var finished = false;
+ for (var i = 1; !finished && (i < tokens.Length); ++i)
+ {
+ var token = tokens[i];
+ finished = token == "-1";
+ if (!finished)
+ {
+ this.Bytes.Add(Convert.ToByte(token, 16));
+ }
+ }
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/Program.cs b/LR35902/LR35902.FuseTest/Program.cs
new file mode 100644
index 0000000..5ce7567
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/Program.cs
@@ -0,0 +1,11 @@
+namespace Fuse
+{
+ public static class Program
+ {
+ public static void Main(string[] args)
+ {
+ TestSuite testSuite = new TestSuite("fuse-tests\\tests");
+ testSuite.Run();
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/Properties/AssemblyInfo.cs b/LR35902/LR35902.FuseTest/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..b13b70b
--- /dev/null
+++ b/LR35902/LR35902.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("LR35902.FuseTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("LR35902.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("cc24b08d-1c51-43fd-961d-7c9a49253d69")]
+
+// 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/LR35902/LR35902.FuseTest/RegisterState.cs b/LR35902/LR35902.FuseTest/RegisterState.cs
new file mode 100644
index 0000000..2e3fd71
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/RegisterState.cs
@@ -0,0 +1,44 @@
+namespace Fuse
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+
+ using EightBit;
+
+ public class RegisterState
+ {
+ public enum Register
+ {
+ AF, BC, DE, HL, SP, PC
+ };
+
+ public List Registers { get; } = new List();
+ public bool Halted { get; private set; } = false;
+ public int TStates { get; private set; } = -1;
+
+ public void Read(StreamReader file)
+ {
+ this.ReadExternalState(file);
+ this.ReadInternalState(file);
+ }
+
+ private void ReadInternalState(StreamReader file)
+ {
+ var line = file.ReadLine();
+ var tokens = line.Split(new char[] { ' ', '\t' });
+
+ this.Halted = Convert.ToInt32(tokens[0]) == 1;
+ this.TStates = Convert.ToInt32(tokens[1]);
+ }
+
+ private void ReadExternalState(StreamReader file)
+ {
+ var line = file.ReadLine();
+ foreach (var token in line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ this.Registers.Add(new Register16(Convert.ToUInt16(token, 16)));
+ }
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/Test.cs b/LR35902/LR35902.FuseTest/Test.cs
new file mode 100644
index 0000000..ea91dc2
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/Test.cs
@@ -0,0 +1,45 @@
+namespace Fuse
+{
+ using System.Collections.Generic;
+ using System.IO;
+
+ public class Test
+ {
+ public bool Valid => !string.IsNullOrEmpty(this.Description);
+
+ public string Description { get; private set; }
+
+ public RegisterState RegisterState { get; } = new RegisterState();
+
+ public List MemoryData { get; } = new List();
+
+ public void Read(StreamReader file)
+ {
+ while ((string.IsNullOrWhiteSpace(this.Description) || this.Description.StartsWith(";")) && !file.EndOfStream)
+ {
+ this.Description = file.ReadLine();
+ }
+
+ if (!this.Valid)
+ {
+ return;
+ }
+
+ this.RegisterState.Read(file);
+
+ var finished = false;
+ do
+ {
+ var line = file.ReadLine();
+ finished = line == "-1";
+ if (!finished)
+ {
+ var datum = new MemoryDatum();
+ datum.Parse(line);
+ this.MemoryData.Add(datum);
+ }
+ }
+ while (!finished);
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/TestEvent.cs b/LR35902/LR35902.FuseTest/TestEvent.cs
new file mode 100644
index 0000000..dc59cdf
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/TestEvent.cs
@@ -0,0 +1,65 @@
+namespace Fuse
+{
+ using System;
+ using System.IO;
+
+ public class TestEvent
+ {
+ public bool Valid { get; private set; } = false;
+
+ public int Cycles { get; private set; } = -1;
+
+ public string Specifier { get; private set; }
+
+ public ushort Address { get; private set; } = (ushort)EightBit.Mask.Mask16;
+
+ public ushort Value { get; private set; } = (byte)EightBit.Mask.Mask8;
+
+ public void Read(StreamReader file)
+ {
+ var prior = file.BaseStream.Position;
+ this.ParseLine(file.ReadLine());
+ if (!this.Valid)
+ {
+ file.BaseStream.Seek(prior, SeekOrigin.Begin);
+ }
+ }
+
+ private void ParseLine(string line)
+ {
+ var split = line.Split(new char[] { ' ', '\t' });
+ this.ParseLine(split);
+ }
+
+ private void ParseLine(string[] tokens)
+ {
+ this.Cycles = int.Parse(tokens[0]);
+ this.Specifier = tokens[1];
+
+ this.Valid = true;
+ switch (this.Specifier)
+ {
+ case "MR":
+ case "MW":
+ this.Address = Convert.ToUInt16(tokens[2], 16);
+ this.Value = Convert.ToByte(tokens[3], 16);
+ break;
+
+ case "MC":
+ case "PC":
+ this.Address = Convert.ToUInt16(tokens[2], 16);
+ break;
+
+ case "PR":
+ case "PW":
+ this.Address = Convert.ToUInt16(tokens[2], 16);
+ this.Value = Convert.ToByte(tokens[3], 16);
+ break;
+
+ default:
+ this.Valid = false;
+ break;
+ }
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/TestEvents.cs b/LR35902/LR35902.FuseTest/TestEvents.cs
new file mode 100644
index 0000000..0f9decd
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/TestEvents.cs
@@ -0,0 +1,26 @@
+namespace Fuse
+{
+ using System.Collections.Generic;
+ using System.IO;
+
+ public class TestEvents
+ {
+ public List Events { get; } = new List();
+
+ public void Read(StreamReader file)
+ {
+ var complete = false;
+ do
+ {
+ var e = new TestEvent();
+ e.Read(file);
+ complete = !e.Valid;
+ if (!complete)
+ {
+ this.Events.Add(e);
+ }
+ }
+ while (!complete);
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/TestRunner.cs b/LR35902/LR35902.FuseTest/TestRunner.cs
new file mode 100644
index 0000000..0114c5c
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/TestRunner.cs
@@ -0,0 +1,204 @@
+namespace Fuse
+{
+ public class TestRunner : EightBit.GameBoy.Bus
+ {
+ private readonly Test test;
+ private readonly ExpectedTestResult expected;
+ private readonly EightBit.Ram ram = new EightBit.Ram(0x10000);
+
+ public TestRunner(Test test, ExpectedTestResult expected)
+ {
+ this.test = test;
+ this.expected = expected;
+ }
+
+ 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.Mask8, 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.InitialiseRegisters();
+ this.InitialiseMemory();
+ }
+
+ public override void LowerPOWER()
+ {
+ this.CPU.LowerPOWER();
+ base.LowerPOWER();
+ }
+
+ public override void Initialize() => this.DisableGameRom();
+
+ private void InitialiseRegisters()
+ {
+ var testState = this.test.RegisterState;
+ var inputRegisters = testState.Registers;
+
+ this.CPU.AF.Word = inputRegisters[(int)RegisterState.Register.AF].Word;
+ this.CPU.BC.Word = inputRegisters[(int)RegisterState.Register.BC].Word;
+ this.CPU.DE.Word = inputRegisters[(int)RegisterState.Register.DE].Word;
+ this.CPU.HL.Word = inputRegisters[(int)RegisterState.Register.HL].Word;
+
+ this.CPU.SP.Word = inputRegisters[(int)RegisterState.Register.SP].Word;
+ this.CPU.PC.Word = inputRegisters[(int)RegisterState.Register.PC].Word;
+ }
+
+ private void InitialiseMemory()
+ {
+ foreach (var memoryDatum in this.test.MemoryData)
+ {
+ var address = memoryDatum.Address;
+ var bytes = memoryDatum.Bytes;
+ for (var i = 0; i < bytes.Count; ++i)
+ {
+ this.Poke((ushort)(address + i), bytes[i]);
+ }
+ }
+ }
+
+ private void Check()
+ {
+ this.Checkregisters();
+ this.CheckMemory();
+ }
+
+ private void Checkregisters()
+ {
+ var expectedState = this.expected.RegisterState;
+ var expectedRegisters = expectedState.Registers;
+
+ var af = this.CPU.AF.Word == expectedRegisters[(int)RegisterState.Register.AF].Word;
+ var bc = this.CPU.BC.Word == expectedRegisters[(int)RegisterState.Register.BC].Word;
+ var de = this.CPU.DE.Word == expectedRegisters[(int)RegisterState.Register.DE].Word;
+ var hl = this.CPU.HL.Word == expectedRegisters[(int)RegisterState.Register.HL].Word;
+
+ var sp = this.CPU.SP.Word == expectedRegisters[(int)RegisterState.Register.SP].Word;
+ var pc = this.CPU.PC.Word == expectedRegisters[(int)RegisterState.Register.PC].Word;
+
+ var success = af && bc && de && hl && sp && pc;
+ if (!success)
+ {
+ this.Failed = true;
+ System.Console.Error.WriteLine($"**** Failed test (Register): {this.test.Description}");
+
+ if (!af)
+ {
+ var expectedWord = expectedRegisters[(int)RegisterState.Register.AF];
+ var actualWord = this.CPU.AF;
+ DumpDifference("A", "F", expectedWord, actualWord);
+ }
+
+ if (!bc)
+ {
+ var expectedWord = expectedRegisters[(int)RegisterState.Register.BC];
+ var actualWord = this.CPU.BC;
+ DumpDifference("B", "C", expectedWord, actualWord);
+ }
+
+ if (!de)
+ {
+ var expectedWord = expectedRegisters[(int)RegisterState.Register.DE];
+ var actualWord = this.CPU.DE;
+ DumpDifference("D", "E", expectedWord, actualWord);
+ }
+
+ if (!hl)
+ {
+ var expectedWord = expectedRegisters[(int)RegisterState.Register.HL];
+ var actualWord = this.CPU.HL;
+ DumpDifference("H", "L", expectedWord, actualWord);
+ }
+
+ if (!sp)
+ {
+ var expectedWord = expectedRegisters[(int)RegisterState.Register.SP];
+ var actualWord = this.CPU.SP;
+ DumpDifference("SPH", "SPL", expectedWord, actualWord);
+ }
+
+ if (!pc)
+ {
+ var expectedWord = expectedRegisters[(int)RegisterState.Register.PC];
+ var actualWord = this.CPU.PC;
+ DumpDifference("PCH", "PCL", expectedWord, actualWord);
+ }
+ }
+ }
+
+ private void CheckMemory()
+ {
+ var first = true;
+
+ foreach (var memoryDatum in this.expected.MemoryData)
+ {
+ var bytes = memoryDatum.Bytes;
+ for (var i = 0; i < bytes.Count; ++i)
+ {
+ var expected = bytes[i];
+ var address = (ushort)(memoryDatum.Address + i);
+ 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}");
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/TestSuite.cs b/LR35902/LR35902.FuseTest/TestSuite.cs
new file mode 100644
index 0000000..a485abd
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/TestSuite.cs
@@ -0,0 +1,42 @@
+namespace Fuse
+{
+ public class TestSuite
+ {
+ private readonly Tests tests = new Tests();
+ private readonly ExpectedTestResults results = new ExpectedTestResults();
+
+ public TestSuite(string path)
+ {
+ this.tests.Read(path + ".in");
+ this.results.Read(path + ".expected");
+ }
+
+ 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.Results[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/LR35902/LR35902.FuseTest/Tests.cs b/LR35902/LR35902.FuseTest/Tests.cs
new file mode 100644
index 0000000..3acb5f0
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/Tests.cs
@@ -0,0 +1,31 @@
+namespace Fuse
+{
+ using System.Collections.Generic;
+ using System.IO;
+
+ public class Tests
+ {
+ public void Read(string path)
+ {
+ using (var file = new StreamReader(path))
+ {
+ this.Read(file);
+ }
+ }
+
+ public Dictionary Container { get; } = new Dictionary();
+
+ public void Read(StreamReader file)
+ {
+ while (!file.EndOfStream)
+ {
+ var test = new Test();
+ test.Read(file);
+ if (test.Valid)
+ {
+ this.Container.Add(test.Description, test);
+ }
+ }
+ }
+ }
+}
diff --git a/LR35902/LR35902.FuseTest/fuse-tests/README b/LR35902/LR35902.FuseTest/fuse-tests/README
new file mode 100644
index 0000000..005c7ee
--- /dev/null
+++ b/LR35902/LR35902.FuseTest/fuse-tests/README
@@ -0,0 +1,79 @@
+File formats
+============
+
+tests.in
+--------
+
+Each test has the format:
+
+
+AF BC DE HL SP PC
+
+
+ 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
+
+