From 08089823c2b48ee7b5c4e7bdd282d47b4a54eb46 Mon Sep 17 00:00:00 2001 From: Adrian Conlon <98398945+AdrianConlon@users.noreply.github.com> Date: Wed, 26 Mar 2025 21:16:37 +0000 Subject: [PATCH] First stab at getting LR35902 HarteTests running. Not bad, so far! --- LR35902/Disassembler.cs | 2 +- LR35902/LR35902.cs | 178 +++++++++- LR35902/LR35902.csproj | 3 + LR35902/SM83.HarteTest/Checker.cs | 339 +++++++++++++++++++ LR35902/SM83.HarteTest/Cycle.cs | 34 ++ LR35902/SM83.HarteTest/OpcodeTestSuite.cs | 41 +++ LR35902/SM83.HarteTest/ProcessorTestSuite.cs | 19 ++ LR35902/SM83.HarteTest/Program.cs | 87 +++++ LR35902/SM83.HarteTest/SM83.HarteTest.csproj | 17 + LR35902/SM83.HarteTest/State.cs | 26 ++ LR35902/SM83.HarteTest/Test.cs | 26 ++ LR35902/SM83.HarteTest/TestRunner.cs | 39 +++ 12 files changed, 799 insertions(+), 12 deletions(-) create mode 100644 LR35902/SM83.HarteTest/Checker.cs create mode 100644 LR35902/SM83.HarteTest/Cycle.cs create mode 100644 LR35902/SM83.HarteTest/OpcodeTestSuite.cs create mode 100644 LR35902/SM83.HarteTest/ProcessorTestSuite.cs create mode 100644 LR35902/SM83.HarteTest/Program.cs create mode 100644 LR35902/SM83.HarteTest/SM83.HarteTest.csproj create mode 100644 LR35902/SM83.HarteTest/State.cs create mode 100644 LR35902/SM83.HarteTest/Test.cs create mode 100644 LR35902/SM83.HarteTest/TestRunner.cs diff --git a/LR35902/Disassembler.cs b/LR35902/Disassembler.cs index 33fffc0..a70fe66 100644 --- a/LR35902/Disassembler.cs +++ b/LR35902/Disassembler.cs @@ -236,7 +236,7 @@ namespace LR35902 return output; } - private string Disassemble(LR35902 cpu, ushort pc) + public string Disassemble(LR35902 cpu, ushort pc) { var opCode = this.Bus.Peek(pc); diff --git a/LR35902/LR35902.cs b/LR35902/LR35902.cs index 977dfed..c39d85b 100644 --- a/LR35902/LR35902.cs +++ b/LR35902/LR35902.cs @@ -5,14 +5,29 @@ namespace LR35902 { using EightBit; + using System.Diagnostics; - public sealed class LR35902(Bus bus) : IntelProcessor(bus) + public sealed class LR35902 : IntelProcessor { - private readonly Bus bus = bus; + public LR35902(Bus bus) + : base(bus) + { + this.bus = bus; + this.RaisedPOWER += this.LR35902_RaisedPOWER; + } + + private void LR35902_RaisedPOWER(object? sender, EventArgs e) + { + this.RaiseWR(); + this.RaiseRD(); + this.RaiseMWR(); + } + + private readonly Bus bus; private readonly Register16 af = new((int)Mask.Sixteen); private bool prefixCB; - public int ClockCycles => this.Cycles * 4; + public int MachineCycles => this.Cycles / 4; public override Register16 AF { @@ -29,10 +44,145 @@ namespace LR35902 public override Register16 HL { get; } = new Register16((int)Mask.Sixteen); - private bool IME { get; set; } + public bool IME { get; set; } + + public byte IE + { + get + { + return this.Bus.Peek(IoRegisters.BASE + IoRegisters.IE); + } + set + { + this.Bus.Poke(IoRegisters.BASE + IoRegisters.IE, value); + } + } + + public byte IF + { + get + { + return this.bus.IO.Peek(IoRegisters.IF); + } + set + { + this.bus.IO.Poke(IoRegisters.IF, value); + } + } private bool Stopped { get; set; } + #region MWR pin + + public event EventHandler? RaisingMWR; + + public event EventHandler? RaisedMWR; + + public event EventHandler? LoweringMWR; + + public event EventHandler? LoweredMWR; + + private PinLevel _mwrLine = PinLevel.Low; + + public ref PinLevel MWR => ref this._mwrLine; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] + public void RaiseMWR() + { + if (this.MWR.Lowered()) + { + RaisingMWR?.Invoke(this, EventArgs.Empty); + this.MWR.Raise(); + RaisedMWR?.Invoke(this, EventArgs.Empty); + } + } + + public void LowerMWR() + { + if (this.MWR.Raised()) + { + LoweringMWR?.Invoke(this, EventArgs.Empty); + this.MWR.Lower(); + LoweredMWR?.Invoke(this, EventArgs.Empty); + } + } + + #endregion + + #region RD pin + + public event EventHandler? RaisingRD; + + public event EventHandler? RaisedRD; + + public event EventHandler? LoweringRD; + + public event EventHandler? LoweredRD; + + private PinLevel _rdLine = PinLevel.Low; + + public ref PinLevel RD => ref this._rdLine; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] + public void RaiseRD() + { + if (this.RD.Lowered()) + { + RaisingRD?.Invoke(this, EventArgs.Empty); + this.RD.Raise(); + RaisedRD?.Invoke(this, EventArgs.Empty); + } + } + + public void LowerRD() + { + if (this.RD.Raised()) + { + LoweringRD?.Invoke(this, EventArgs.Empty); + this.RD.Lower(); + LoweredRD?.Invoke(this, EventArgs.Empty); + } + } + + #endregion + + #region WR pin + + public event EventHandler? RaisingWR; + + public event EventHandler? RaisedWR; + + public event EventHandler? LoweringWR; + + public event EventHandler? LoweredWR; + + private PinLevel _wrLine = PinLevel.Low; + + public ref PinLevel WR => ref this._wrLine; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "The word 'raise' is used in an electrical sense")] + public void RaiseWR() + { + if (this.WR.Lowered()) + { + RaisingWR?.Invoke(this, EventArgs.Empty); + this.WR.Raise(); + RaisedWR?.Invoke(this, EventArgs.Empty); + } + } + + public void LowerWR() + { + if (this.WR.Raised()) + { + LoweringWR?.Invoke(this, EventArgs.Empty); + this.WR.Lower(); + LoweredWR?.Invoke(this, EventArgs.Empty); + } + } + + #endregion + public override void Execute() { var decoded = this.GetDecodedOpCode(this.OpCode); @@ -60,15 +210,12 @@ namespace LR35902 { this.prefixCB = false; - var interruptEnable = this.Bus.Peek(IoRegisters.BASE + IoRegisters.IE); - var interruptFlags = this.bus.IO.Peek(IoRegisters.IF); - - var masked = (byte)(interruptEnable & interruptFlags); + var masked = (byte)(this.IE & this.IF); if (masked != 0) { if (this.IME) { - this.bus.IO.Poke(IoRegisters.IF, 0); + this.IF = 0; this.LowerINT(); var index = FindFirstSet(masked); this.Bus.Data = (byte)(0x38 + (index << 3)); @@ -138,14 +285,23 @@ namespace LR35902 protected override void MemoryWrite() { - this.TickMachine(); + this.LowerMWR(); + this.LowerWR(); base.MemoryWrite(); + this.TickMachine(); + this.RaiseWR(); + this.RaiseMWR(); } protected override byte MemoryRead() { + this.LowerMWR(); + this.LowerRD(); + var returned = base.MemoryRead(); this.TickMachine(); - return base.MemoryRead(); + this.RaiseRD(); + this.RaiseMWR(); + return returned; } protected override void PushWord(Register16 value) diff --git a/LR35902/LR35902.csproj b/LR35902/LR35902.csproj index 7631a35..817da1d 100644 --- a/LR35902/LR35902.csproj +++ b/LR35902/LR35902.csproj @@ -21,10 +21,13 @@ + + + diff --git a/LR35902/SM83.HarteTest/Checker.cs b/LR35902/SM83.HarteTest/Checker.cs new file mode 100644 index 0000000..614a357 --- /dev/null +++ b/LR35902/SM83.HarteTest/Checker.cs @@ -0,0 +1,339 @@ +namespace SM83.HarteTest +{ + using System.Diagnostics; + + internal sealed class Checker + { + private TestRunner Runner { get; } + + private LR35902.Disassembler Disassembler { get; } + + private bool CycleCountMismatch { get; set; } + + public int Cycles { get; private set; } + + public bool Valid { get; private set; } + + public bool Invalid => !this.Valid; + + public bool Unimplemented => this.Invalid && this.CycleCountMismatch && (this.Cycles == 1); + + public bool Implemented => !this.Unimplemented; + + public List Messages { get; } = []; + + private List ActualCycles { get; } = []; + + public Checker(TestRunner runner) + { + this.Runner = runner; + this.Disassembler = new(this.Runner); + } + + public void Check(Test test) + { + var cpu = this.Runner.CPU; + + this.Reset(); + + this.Runner.RaisePOWER(); + this.InitialiseState(test); + var pc = cpu.PC.Word; + + this.Cycles = cpu.Step(); + this.Runner.LowerPOWER(); + + this.Valid = this.CheckState(test); + + if (this.Unimplemented) + { + this.Messages.Add("Unimplemented"); + return; + } + + Debug.Assert(this.Implemented); + if (this.Invalid) + { + this.AddDisassembly(pc); + + var final = test.Final ?? throw new InvalidOperationException("Final test state cannot be null"); + + this.Raise("PC", final.PC, cpu.PC.Word); + this.Raise("SP", final.SP, cpu.SP.Word); + this.Raise("A", final.A, cpu.A); + this.Raise("F", final.F, cpu.F); + this.Raise("B", final.B, cpu.B); + this.Raise("C", final.C, cpu.C); + this.Raise("D", final.D, cpu.D); + this.Raise("E", final.E, cpu.E); + this.Raise("H", final.H, cpu.H); + this.Raise("L", final.L, cpu.L); + this.Raise("IME", final.IME, (byte)(cpu.IME ? 1 : 0)); + + if (test.Cycles is null) + { + throw new InvalidOperationException("test cycles cannot be null"); + } + + this.Messages.Add($"Stepped cycles: {this.Cycles}, expected events: {test.Cycles.Count}, actual events: {this.ActualCycles.Count}"); + + this.DumpCycles("-- Expected cycles", test.AvailableCycles()); + this.DumpCycles("-- Actual cycles", this.ActualCycles); + } + } + + private void Reset() + { + this.Messages.Clear(); + this.ActualCycles.Clear(); + + this.CycleCountMismatch = false; + this.Cycles = 0; + this.Valid = false; + } + + private bool Check(string what, ushort expected, ushort actual) + { + var success = actual == expected; + if (!success) + { + this.Raise(what, expected, actual); + } + return success; + } + + private bool Check(string what, byte expected, byte actual) + { + var success = actual == expected; + if (!success) + { + this.Raise(what, expected, actual); + } + return success; + } + + private bool Check(string what, string? expected, string? actual) + { + ArgumentNullException.ThrowIfNull(expected); + ArgumentNullException.ThrowIfNull(actual); + var success = actual == expected; + if (!success) + { + this.Raise(what, expected, actual); + } + return success; + } + + private bool Check(string what, ushort address, byte expected, byte actual) + { + var success = actual == expected; + if (!success) + { + this.Raise($"{what}: {address}", expected, actual); + } + return success; + } + + private void AddDisassembly(ushort address) + { + string message; + try + { + message = this.Disassemble(address); + } + catch (InvalidOperationException error) + { + message = $"Disassembly problem: {error.Message}"; + } + + this.Messages.Add(message); + } + + private string Disassemble(ushort address) => this.Disassembler.Disassemble(this.Runner.CPU, address); + + private bool CheckState(Test test) + { + var runner = this.Runner; + var cpu = runner.CPU; + + var expectedCycles = test.AvailableCycles(); + var actualCycles = this.ActualCycles; + + var actualIDX = 0; + foreach (var expectedCycle in expectedCycles) + { + + if (actualIDX >= actualCycles.Count) + { + this.CycleCountMismatch = true; + return false; // more expected cycles than actual + } + + var actualCycle = actualCycles[actualIDX++]; + + var expectedAddress = expectedCycle.Address; + var actualAddress = actualCycle.Address; + _ = this.Check("Cycle address", expectedAddress, actualAddress); + + var expectedValue = (byte)expectedCycle.Value; + var actualValue = (byte)actualCycle.Value; + _ = this.Check("Cycle value", expectedValue, actualValue); + + var expectedAction = expectedCycle.Type; + var actualAction = actualCycle.Type; + _ = this.Check("Cycle action", expectedAction, actualAction); + } + + if (actualIDX < actualCycles.Count) + { + this.CycleCountMismatch = true; + return false; // less expected cycles than actual + } + + if (this.Messages.Count > 0) + { + return false; + } + + var final = test.Final ?? throw new InvalidOperationException("Final state cannot be null"); + var pc_good = this.Check("PC", final.PC, cpu.PC.Word); + var sp_good = this.Check("SP", final.SP, cpu.SP.Word); + var a_good = this.Check("A", final.A, cpu.A); + var f_good = this.Check("F", final.F, cpu.F); + var b_good = this.Check("B", final.B, cpu.B); + var c_good = this.Check("C", final.C, cpu.C); + var d_good = this.Check("D", final.D, cpu.D); + var e_good = this.Check("E", final.E, cpu.E); + var h_good = this.Check("H", final.H, cpu.H); + var l_good = this.Check("L", final.L, cpu.L); + var ime_good = this.Check("IME", final.IME, (byte)(cpu.IME ? 1 : 0)); + + if (!f_good) + { + this.Messages.Add($"Expected flags: {LR35902.Disassembler.AsFlags(final.F)}"); + this.Messages.Add($"Actual flags : {LR35902.Disassembler.AsFlags(cpu.F)}"); + } + + if (final.RAM is null) + { + throw new InvalidOperationException("Expected RAM cannot be null"); + } + + var ramProblem = false; + foreach (var entry in final.RAM) + { + if (entry.Length != 2) + { + throw new InvalidOperationException("RAM entry length must be 2"); + } + + var address = (ushort)entry[0]; + var value = (byte)entry[1]; + + var ramGood = this.Check("RAM", address, value, runner.Peek(address)); + if (!ramGood && !ramProblem) + { + ramProblem = true; + } + } + + return + pc_good && sp_good + && a_good && f_good + && b_good && c_good + && d_good && e_good + && h_good && l_good + && ime_good; + } + + private void Raise(string what, ushort expected, ushort actual) => this.Messages.Add($"{what}: expected: {expected:X4}, actual: {actual:X4}"); + + private void Raise(string what, byte expected, byte actual) => this.Messages.Add($"{what}: expected: {expected:X2}, actual: {actual:X2}"); + + private void Raise(string what, string expected, string actual) => this.Messages.Add($"{what}: expected: {expected}, actual: {actual}"); + + public void Initialise() + { + this.Runner.CPU.MachineTicked += this.CPU_MachineTicked; + } + + private void CPU_MachineTicked(object? sender, EventArgs e) + { + var rd = this.Runner.CPU.RD == EightBit.PinLevel.Low ? "r" : "-"; + var wr = this.Runner.CPU.WR == EightBit.PinLevel.Low ? "w" : "-"; + var mrw = this.Runner.CPU.MWR == EightBit.PinLevel.Low ? "m" : "-"; + this.AddActualCycle(this.Runner.Address, this.Runner.Data, $"{rd}{wr}{mrw}"); + } + + private void InitialiseState(Test test) + { + var initial = test.Initial ?? throw new InvalidOperationException("Test cannot have an invalid initial state"); + this.InitialiseState(initial); + } + + private void InitialiseState(State state) + { + var runner = this.Runner; + var cpu = runner.CPU; + + cpu.PC.Word = state.PC; + cpu.SP.Word = state.SP; + cpu.A = state.A; + cpu.F = state.F; + cpu.B = state.B; + cpu.C = state.C; + cpu.D = state.D; + cpu.E = state.E; + cpu.H = state.H; + cpu.L = state.L; + cpu.IME = state.IME != 0; + cpu.IE = state.IE; + + var initialRAM = state.RAM ?? throw new InvalidOperationException("Initial test state cannot have invalid RAM"); + foreach (var entry in initialRAM) + { + var count = entry.Length; + if (count != 2) + { + throw new InvalidOperationException("RAM entry length must be 2"); + } + + var address = (ushort)entry[0]; + var value = (byte)entry[1]; + runner.Poke(address, value); + } + } + + + private void Runner_ReadByte(object? sender, EventArgs e) => this.AddActualCycle(this.Runner.Address, this.Runner.Data, "r-m"); + + private void Runner_WrittenByte(object? sender, EventArgs e) => this.AddActualCycle(this.Runner.Address, this.Runner.Data, "-wm"); + + private void AddActualCycle(EightBit.Register16 address, byte value, string action) => this.AddActualCycle(address.Word, value, action); + + private void AddActualCycle(ushort address, byte value, string action) => this.ActualCycles.Add(new Cycle(address, value, action)); + + private void DumpCycle(ushort address, byte? value, string? action) + { + ArgumentNullException.ThrowIfNull(action); + this.Messages.Add($"Address: {address:X4}, value: {value:X2}, action: {action}"); + } + + private void DumpCycle(Cycle cycle) => this.DumpCycle(cycle.Address, cycle.Value, cycle.Type); + + private void DumpCycles(IEnumerable? cycles) + { + ArgumentNullException.ThrowIfNull(cycles); + foreach (var cycle in cycles) + { + this.DumpCycle(cycle); + } + } + + private void DumpCycles(string which, IEnumerable? events) + { + this.Messages.Add(which); + this.DumpCycles(events); + } + } +} diff --git a/LR35902/SM83.HarteTest/Cycle.cs b/LR35902/SM83.HarteTest/Cycle.cs new file mode 100644 index 0000000..7b001ad --- /dev/null +++ b/LR35902/SM83.HarteTest/Cycle.cs @@ -0,0 +1,34 @@ +namespace SM83.HarteTest +{ + internal sealed class Cycle + { + public ushort Address { get; set; } + + public byte? Value { get; set; } + + public string Type { get; set; } + + public Cycle(ushort address, byte? value, string type) + { + this.Address = address; + this.Value = value; + this.Type = type; + } + + public Cycle(List input) + { + ArgumentNullException.ThrowIfNull(input); + + if (input.Count != 3) + { + throw new ArgumentOutOfRangeException(nameof(input), input, "Cycles can only have three elements"); + } + + this.Address = AsElement(input[0]).GetUInt16(); + this.Value = AsElement(input[1]).GetByte(); + this.Type = AsElement(input[2]).GetString(); + } + + private static System.Text.Json.JsonElement AsElement(object part) => (System.Text.Json.JsonElement)part; + } +} diff --git a/LR35902/SM83.HarteTest/OpcodeTestSuite.cs b/LR35902/SM83.HarteTest/OpcodeTestSuite.cs new file mode 100644 index 0000000..f81771b --- /dev/null +++ b/LR35902/SM83.HarteTest/OpcodeTestSuite.cs @@ -0,0 +1,41 @@ +namespace SM83.HarteTest +{ + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + + internal sealed class OpcodeTestSuite(string path) : IDisposable + { + private static readonly JsonSerializerOptions SerializerOptions = new() + { + UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow, + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + }; + private bool _disposed; + + public string Path { get; } = path; + + private readonly FileStream _stream = File.Open(path, FileMode.Open); + + public ConfiguredCancelableAsyncEnumerable TestsAsync => JsonSerializer.DeserializeAsyncEnumerable(this._stream, SerializerOptions).ConfigureAwait(false); + + private void Dispose(bool disposing) + { + if (!this._disposed) + { + if (disposing) + { + this._stream.Dispose(); + } + + this._disposed = true; + } + } + + public void Dispose() + { + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/LR35902/SM83.HarteTest/ProcessorTestSuite.cs b/LR35902/SM83.HarteTest/ProcessorTestSuite.cs new file mode 100644 index 0000000..8cf221e --- /dev/null +++ b/LR35902/SM83.HarteTest/ProcessorTestSuite.cs @@ -0,0 +1,19 @@ +namespace SM83.HarteTest +{ + internal sealed class ProcessorTestSuite(string location) + { + public string Location { get; set; } = location; + + public IEnumerable OpcodeTests() + { + foreach (var filename in Directory.EnumerateFiles(this.Location, "*.json")) + { + var fileInformation = new FileInfo(filename); + if (fileInformation.Length > 0) + { + yield return new OpcodeTestSuite(filename); + } + } + } + } +} diff --git a/LR35902/SM83.HarteTest/Program.cs b/LR35902/SM83.HarteTest/Program.cs new file mode 100644 index 0000000..b4aae60 --- /dev/null +++ b/LR35902/SM83.HarteTest/Program.cs @@ -0,0 +1,87 @@ +// +// Copyright (c) Adrian Conlon. All rights reserved. +// + +namespace SM83.HarteTest +{ + public static class Program + { + public static async Task Main(string[] _) + { + var directory = @"C:\github\spectrum\libraries\EightBit\modules\sm83\v1"; + + await ProcessTestSuiteAsync(directory).ConfigureAwait(false); + } + + private static async Task ProcessTestSuiteAsync(string directory) + { + var startTime = DateTime.Now; + + var unimplemented_opcode_count = 0; + var invalid_opcode_count = 0; + + var runner = new TestRunner(); + runner.Initialize(); + + var checker = new Checker(runner); + checker.Initialise(); + + var testSuite = new ProcessorTestSuite(directory); + foreach (var opcode in testSuite.OpcodeTests()) + { + Console.WriteLine($"Processing: {Path.GetFileName(opcode.Path)}"); + + List testNames = []; + var tests = opcode.TestsAsync; + await foreach (var test in tests) + { + if (test is null) + { + throw new InvalidOperationException("Test cannot be null"); + } + + checker.Check(test); + if (checker.Invalid) + { + ++invalid_opcode_count; + + // Was it just unimplemented? + if (checker.Unimplemented) + { + ++unimplemented_opcode_count; + } + + // Let's see if we had any successes! + if (testNames.Count > 0) + { + Console.WriteLine("**** The follow test variations succeeded"); + foreach (var testName in testNames) + { + Console.WriteLine($"****** {testName}"); + } + } + + // OK, we've attempted an implementation, how did it fail? + foreach (var message in checker.Messages) + { + Console.WriteLine($"**** {message}"); + } + + // I'm not really interested in the remaining tests for this opcode + break; + } + + testNames.Add(test.Name); + } + } + + var finishTime = DateTime.Now; + var elapsedTime = finishTime - startTime; + + Console.Write($"Elapsed time: {elapsedTime.TotalSeconds} seconds"); + Console.Write($", unimplemented opcode count: {unimplemented_opcode_count}"); + Console.Write($", invalid opcode count: {invalid_opcode_count - unimplemented_opcode_count}"); + Console.WriteLine(); + } + } +} \ No newline at end of file diff --git a/LR35902/SM83.HarteTest/SM83.HarteTest.csproj b/LR35902/SM83.HarteTest/SM83.HarteTest.csproj new file mode 100644 index 0000000..4cf2bec --- /dev/null +++ b/LR35902/SM83.HarteTest/SM83.HarteTest.csproj @@ -0,0 +1,17 @@ + + + + Exe + net9.0 + enable + enable + True + latest-recommended + + + + + + + + diff --git a/LR35902/SM83.HarteTest/State.cs b/LR35902/SM83.HarteTest/State.cs new file mode 100644 index 0000000..e060634 --- /dev/null +++ b/LR35902/SM83.HarteTest/State.cs @@ -0,0 +1,26 @@ +namespace SM83.HarteTest +{ + internal sealed class State + { + public ushort PC { get; set; } + + public ushort SP { get; set; } + + public byte A { get; set; } + public byte F { get; set; } + + public byte B { get; set; } + public byte C { get; set; } + + public byte D { get; set; } + public byte E { get; set; } + + public byte H { get; set; } + public byte L { get; set; } + + public byte IME { get; set; } + public byte IE { get; set; } + + public int[][]? RAM { get; set; } + } +} diff --git a/LR35902/SM83.HarteTest/Test.cs b/LR35902/SM83.HarteTest/Test.cs new file mode 100644 index 0000000..a50a6ca --- /dev/null +++ b/LR35902/SM83.HarteTest/Test.cs @@ -0,0 +1,26 @@ +namespace SM83.HarteTest +{ + internal sealed class Test + { + public string? Name { get; set; } + + public State? Initial { get; set; } + + public State? Final { get; set; } + + public List>? Cycles { get; set; } + + public IEnumerable AvailableCycles() + { + if (this.Cycles is null) + { + throw new InvalidOperationException("Cycles have not been initialised"); + } + + foreach (var cycle in this.Cycles) + { + yield return new Cycle(cycle); + } + } + } +} diff --git a/LR35902/SM83.HarteTest/TestRunner.cs b/LR35902/SM83.HarteTest/TestRunner.cs new file mode 100644 index 0000000..fe7bc82 --- /dev/null +++ b/LR35902/SM83.HarteTest/TestRunner.cs @@ -0,0 +1,39 @@ +namespace SM83.HarteTest +{ + using EightBit; + + internal sealed class TestRunner : LR35902.Bus + { + private readonly MemoryMapping _mapping; + + public Ram RAM { get; } = new(0x10000); + + public TestRunner() + { + this._mapping = new(this.RAM, 0x0000, (ushort)Mask.Sixteen, AccessLevel.ReadWrite); + } + + public override MemoryMapping Mapping(ushort _) => this._mapping; + + public override void Initialize() + { + } + + public override void LowerPOWER() + { + this.CPU.LowerPOWER(); + base.LowerPOWER(); + } + + + public override void RaisePOWER() + { + base.RaisePOWER(); + this.CPU.RaisePOWER(); + this.CPU.RaiseRESET(); + this.CPU.RaiseINT(); + this.CPU.RaiseHALT(); + } + + } +}