mirror of
https://github.com/MoleskiCoder/EightBitNet.git
synced 2025-11-25 06:17:29 +00:00
First stab at implementing M6502 Harte tests
This commit is contained in:
@@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Z80.FuseTest", "Z80\Z80.Fus
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Z80.Test", "Z80\Z80.Test\Z80.Test.csproj", "{E6AE640E-4A42-4D8F-8ECE-2FBEBC29741B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Z80.Test", "Z80\Z80.Test\Z80.Test.csproj", "{E6AE640E-4A42-4D8F-8ECE-2FBEBC29741B}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "M6502.HarteTest", "M6502\M6502.HarteTest\M6502.HarteTest.csproj", "{8686B0DC-A431-4239-836E-ABC90BD69920}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -105,6 +107,10 @@ Global
|
|||||||
{E6AE640E-4A42-4D8F-8ECE-2FBEBC29741B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E6AE640E-4A42-4D8F-8ECE-2FBEBC29741B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{E6AE640E-4A42-4D8F-8ECE-2FBEBC29741B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E6AE640E-4A42-4D8F-8ECE-2FBEBC29741B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E6AE640E-4A42-4D8F-8ECE-2FBEBC29741B}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E6AE640E-4A42-4D8F-8ECE-2FBEBC29741B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8686B0DC-A431-4239-836E-ABC90BD69920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8686B0DC-A431-4239-836E-ABC90BD69920}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8686B0DC-A431-4239-836E-ABC90BD69920}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8686B0DC-A431-4239-836E-ABC90BD69920}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
259
M6502/M6502.HarteTest/Checker.cs
Normal file
259
M6502/M6502.HarteTest/Checker.cs
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
namespace M6502.HarteTest
|
||||||
|
{
|
||||||
|
internal class Checker
|
||||||
|
{
|
||||||
|
private TestRunner Runner { get; }
|
||||||
|
|
||||||
|
private EightBit.Symbols Symbols { get; } = new();
|
||||||
|
|
||||||
|
private EightBit.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<string> Messages { get; } = [];
|
||||||
|
|
||||||
|
private List<Cycle> ActualCycles { get; } = [];
|
||||||
|
|
||||||
|
public Checker(TestRunner runner)
|
||||||
|
{
|
||||||
|
this.Runner = runner;
|
||||||
|
this.Disassembler = new EightBit.Disassembler(this.Runner, this.Runner.CPU, this.Symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Check(Test test)
|
||||||
|
{
|
||||||
|
var cpu = this.Runner.CPU;
|
||||||
|
|
||||||
|
this.Messages.Clear();
|
||||||
|
this.ActualCycles.Clear();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Invalid && this.Implemented)
|
||||||
|
{
|
||||||
|
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("S", final.S, cpu.S);
|
||||||
|
this.Raise("A", final.A, cpu.A);
|
||||||
|
this.Raise("X", final.X, cpu.X);
|
||||||
|
this.Raise("Y", final.Y, cpu.Y);
|
||||||
|
this.Raise("P", final.P, cpu.P);
|
||||||
|
|
||||||
|
if (test.Cycles == 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 bool Check<T>(string what, T expected, T actual)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(expected);
|
||||||
|
ArgumentNullException.ThrowIfNull(actual);
|
||||||
|
var success = actual.Equals(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) => this.Messages.Add(this.Disassemble(address));
|
||||||
|
|
||||||
|
private string Disassemble(ushort address) => this.Disassembler.Disassemble(address);
|
||||||
|
|
||||||
|
private bool CheckState(Test test)
|
||||||
|
{
|
||||||
|
var cpu = this.Runner.CPU;
|
||||||
|
var ram = this.Runner.RAM;
|
||||||
|
|
||||||
|
var expected_cycles = test.AvailableCycles() ?? throw new InvalidOperationException("Expected cycles cannot be null");
|
||||||
|
var actual_cycles = this.ActualCycles;
|
||||||
|
|
||||||
|
var actual_idx = 0;
|
||||||
|
foreach (var expected_cycle in expected_cycles) {
|
||||||
|
|
||||||
|
if (actual_idx >= actual_cycles.Count)
|
||||||
|
{
|
||||||
|
this.CycleCountMismatch = true;
|
||||||
|
return false; // more expected cycles than actual
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual_cycle = actual_cycles[actual_idx++];
|
||||||
|
|
||||||
|
var expected_address = expected_cycle.Address;
|
||||||
|
var actual_address = actual_cycle.Address;
|
||||||
|
this.Check("Cycle address", expected_address, actual_address);
|
||||||
|
|
||||||
|
var expected_value = expected_cycle.Value;
|
||||||
|
var actual_value = actual_cycle.Value;
|
||||||
|
this.Check("Cycle value", expected_value, actual_value);
|
||||||
|
|
||||||
|
var expected_action = expected_cycle.Type;
|
||||||
|
var actual_action = actual_cycle.Type;
|
||||||
|
this.Check("Cycle action", expected_action, actual_action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actual_idx < actual_cycles.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 s_good = this.Check("S", final.S, cpu.S);
|
||||||
|
var a_good = this.Check("A", final.A, cpu.A);
|
||||||
|
var x_good = this.Check("X", final.X, cpu.X);
|
||||||
|
var y_good = this.Check("Y", final.Y, cpu.Y);
|
||||||
|
var p_good = this.Check("P", final.P, cpu.P);
|
||||||
|
|
||||||
|
if (final.RAM == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Expected RAM cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ram_problem = false;
|
||||||
|
foreach (var entry in final.RAM)
|
||||||
|
{
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
var ram_good = this.Check("RAM", address, value, ram.Peek(address));
|
||||||
|
if (!ram_good && !ram_problem)
|
||||||
|
{
|
||||||
|
ram_problem = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
pc_good && s_good
|
||||||
|
&& a_good && x_good && y_good && p_good
|
||||||
|
&& !ram_problem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Raise<T>(string what, T expected, T actual) => this.Messages.Add($"{what}: expected: {expected}, actual: {actual}");
|
||||||
|
|
||||||
|
public void Initialise()
|
||||||
|
{
|
||||||
|
this.Runner.ReadByte += this.Runner_ReadByte;
|
||||||
|
this.Runner.WrittenByte += this.Runner_WrittenByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitialiseState(Test test)
|
||||||
|
{
|
||||||
|
var cpu = this.Runner.CPU;
|
||||||
|
var ram = this.Runner.RAM;
|
||||||
|
|
||||||
|
var initial = test.Initial ?? throw new InvalidOperationException("Test cannot have an invalid initial state");
|
||||||
|
cpu.PC.Word = initial.PC;
|
||||||
|
cpu.S = initial.S;
|
||||||
|
cpu.A = initial.A;
|
||||||
|
cpu.X = initial.X;
|
||||||
|
cpu.Y = initial.Y;
|
||||||
|
cpu.P = initial.P;
|
||||||
|
|
||||||
|
var initialRAM = initial.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];
|
||||||
|
ram.Poke(address, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Runner_ReadByte(object? sender, EventArgs e) => this.AddActualReadCycle(this.Runner.Address, this.Runner.Data);
|
||||||
|
|
||||||
|
private void Runner_WrittenByte(object? sender, EventArgs e) => this.AddActualWriteCycle(this.Runner.Address, this.Runner.Data);
|
||||||
|
|
||||||
|
private void AddActualReadCycle(EightBit.Register16 address, byte value) => this.AddActualCycle(address, value, "read");
|
||||||
|
|
||||||
|
private void AddActualWriteCycle(EightBit.Register16 address, byte value) => this.AddActualCycle(address, value, "write");
|
||||||
|
|
||||||
|
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}, value: {value}, action: {action}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DumpCycle(Cycle cycle) => this.DumpCycle(cycle.Address, cycle.Value, cycle.Type);
|
||||||
|
|
||||||
|
private void DumpCycles(IEnumerable<Cycle>? cycles)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(cycles);
|
||||||
|
foreach (var cycle in cycles)
|
||||||
|
{
|
||||||
|
this.DumpCycle(cycle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DumpCycles(string which, IEnumerable<Cycle>? events)
|
||||||
|
{
|
||||||
|
this.Messages.Add(which);
|
||||||
|
this.DumpCycles(events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
M6502/M6502.HarteTest/Cycle.cs
Normal file
30
M6502/M6502.HarteTest/Cycle.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace M6502.HarteTest
|
||||||
|
{
|
||||||
|
public 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<object> input)
|
||||||
|
{
|
||||||
|
if (input.Count != 3)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(input), input, "Cycles can only have three elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Address = ((System.Text.Json.JsonElement)input[0]).GetUInt16();
|
||||||
|
this.Value = ((System.Text.Json.JsonElement)input[1]).GetByte(); ;
|
||||||
|
this.Type = ((System.Text.Json.JsonElement)input[2]).GetString(); ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
M6502/M6502.HarteTest/M6502.HarteTest.csproj
Normal file
15
M6502/M6502.HarteTest/M6502.HarteTest.csproj
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\EightBit\EightBit.csproj" />
|
||||||
|
<ProjectReference Include="..\M6502.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
40
M6502/M6502.HarteTest/OpcodeTestSuite.cs
Normal file
40
M6502/M6502.HarteTest/OpcodeTestSuite.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace M6502.HarteTest
|
||||||
|
{
|
||||||
|
internal class OpcodeTestSuite(string path) : IDisposable
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions SerializerOptions = new()
|
||||||
|
{
|
||||||
|
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||||
|
};
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
public string Path { get; set; } = path;
|
||||||
|
|
||||||
|
private readonly FileStream stream = File.Open(path, FileMode.Open);
|
||||||
|
|
||||||
|
public IAsyncEnumerable<Test?> TestsAsync => JsonSerializer.DeserializeAsyncEnumerable<Test>(this.stream, SerializerOptions);
|
||||||
|
|
||||||
|
protected virtual 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
M6502/M6502.HarteTest/ProcessorTestSuite.cs
Normal file
15
M6502/M6502.HarteTest/ProcessorTestSuite.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace M6502.HarteTest
|
||||||
|
{
|
||||||
|
internal class ProcessorTestSuite(string location)
|
||||||
|
{
|
||||||
|
public string Location { get; set; } = location;
|
||||||
|
|
||||||
|
public IEnumerable<OpcodeTestSuite> OpcodeTests()
|
||||||
|
{
|
||||||
|
foreach (var filename in Directory.EnumerateFiles(this.Location, "*.json"))
|
||||||
|
{
|
||||||
|
yield return new OpcodeTestSuite(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
M6502/M6502.HarteTest/Program.cs
Normal file
89
M6502/M6502.HarteTest/Program.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// <copyright file="Program.cs" company="Adrian Conlon">
|
||||||
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace M6502.HarteTest
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
var directory = @"C:\github\spectrum\libraries\EightBit\modules\65x02\6502\v1";
|
||||||
|
|
||||||
|
await ProcessTestSuiteAsync(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string?> testNames = [];
|
||||||
|
var tests = opcode.TestsAsync ?? throw new InvalidOperationException("No tests are available");
|
||||||
|
await foreach (var test in tests)
|
||||||
|
{
|
||||||
|
if (test == 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 succeeeded");
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
M6502/M6502.HarteTest/State.cs
Normal file
19
M6502/M6502.HarteTest/State.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace M6502.HarteTest
|
||||||
|
{
|
||||||
|
public class State
|
||||||
|
{
|
||||||
|
public ushort PC { get; set; }
|
||||||
|
|
||||||
|
public byte S { get; set; }
|
||||||
|
|
||||||
|
public byte A { get; set; }
|
||||||
|
|
||||||
|
public byte X { get; set; }
|
||||||
|
|
||||||
|
public byte Y { get; set; }
|
||||||
|
|
||||||
|
public byte P { get; set; }
|
||||||
|
|
||||||
|
public int[][]? RAM { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
36
M6502/M6502.HarteTest/Test.cs
Normal file
36
M6502/M6502.HarteTest/Test.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
namespace M6502.HarteTest
|
||||||
|
{
|
||||||
|
public class Test
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
|
||||||
|
public State? Initial { get; set; }
|
||||||
|
|
||||||
|
public State? Final { get; set; }
|
||||||
|
|
||||||
|
public List<List<object>>? Cycles { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<Cycle> AvailableCycles()
|
||||||
|
{
|
||||||
|
if (this.Cycles == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cycles have not been initialised");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var cycle in this.Cycles)
|
||||||
|
{
|
||||||
|
yield return new Cycle(cycle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cycle CycleAt(int index)
|
||||||
|
{
|
||||||
|
if (this.Cycles == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Cycles have not been initialised");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Cycle(this.Cycles[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
M6502/M6502.HarteTest/TestRunner.cs
Normal file
42
M6502/M6502.HarteTest/TestRunner.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using EightBit;
|
||||||
|
|
||||||
|
namespace M6502.HarteTest
|
||||||
|
{
|
||||||
|
internal class TestRunner : EightBit.Bus
|
||||||
|
{
|
||||||
|
public EightBit.Ram RAM { get; } = new(0x10000);
|
||||||
|
|
||||||
|
public EightBit.M6502 CPU { get; }
|
||||||
|
|
||||||
|
private readonly MemoryMapping mapping;
|
||||||
|
|
||||||
|
public TestRunner()
|
||||||
|
{
|
||||||
|
this.CPU = new(this);
|
||||||
|
this.mapping = new(this.RAM, 0x0000, (ushort)Mask.Sixteen, AccessLevel.ReadWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void LowerPOWER()
|
||||||
|
{
|
||||||
|
this.CPU.LowerPOWER();
|
||||||
|
base.LowerPOWER();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MemoryMapping Mapping(ushort absolute) => this.mapping;
|
||||||
|
|
||||||
|
public override void RaisePOWER()
|
||||||
|
{
|
||||||
|
base.RaisePOWER();
|
||||||
|
this.CPU.RaisePOWER();
|
||||||
|
this.CPU.RaiseRESET();
|
||||||
|
this.CPU.RaiseINT();
|
||||||
|
this.CPU.RaiseNMI();
|
||||||
|
this.CPU.RaiseSO();
|
||||||
|
this.CPU.RaiseRDY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,8 +19,11 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Remove="M6502.HarteTest\**" />
|
||||||
<Compile Remove="M6502.Test\**" />
|
<Compile Remove="M6502.Test\**" />
|
||||||
|
<EmbeddedResource Remove="M6502.HarteTest\**" />
|
||||||
<EmbeddedResource Remove="M6502.Test\**" />
|
<EmbeddedResource Remove="M6502.Test\**" />
|
||||||
|
<None Remove="M6502.HarteTest\**" />
|
||||||
<None Remove="M6502.Test\**" />
|
<None Remove="M6502.Test\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user