mirror of
https://github.com/MoleskiCoder/EightBitNet.git
synced 2025-04-28 20:37:44 +00:00
First stab at getting LR35902 HarteTests running. Not bad, so far!
This commit is contained in:
parent
3d6b549c76
commit
08089823c2
@ -236,7 +236,7 @@ namespace LR35902
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string Disassemble(LR35902 cpu, ushort pc)
|
public string Disassemble(LR35902 cpu, ushort pc)
|
||||||
{
|
{
|
||||||
var opCode = this.Bus.Peek(pc);
|
var opCode = this.Bus.Peek(pc);
|
||||||
|
|
||||||
|
@ -5,14 +5,29 @@
|
|||||||
namespace LR35902
|
namespace LR35902
|
||||||
{
|
{
|
||||||
using EightBit;
|
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 readonly Register16 af = new((int)Mask.Sixteen);
|
||||||
private bool prefixCB;
|
private bool prefixCB;
|
||||||
|
|
||||||
public int ClockCycles => this.Cycles * 4;
|
public int MachineCycles => this.Cycles / 4;
|
||||||
|
|
||||||
public override Register16 AF
|
public override Register16 AF
|
||||||
{
|
{
|
||||||
@ -29,10 +44,145 @@ namespace LR35902
|
|||||||
|
|
||||||
public override Register16 HL { get; } = new Register16((int)Mask.Sixteen);
|
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; }
|
private bool Stopped { get; set; }
|
||||||
|
|
||||||
|
#region MWR pin
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? RaisingMWR;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? RaisedMWR;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? LoweringMWR;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? 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<EventArgs>? RaisingRD;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? RaisedRD;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? LoweringRD;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? 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<EventArgs>? RaisingWR;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? RaisedWR;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? LoweringWR;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs>? 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()
|
public override void Execute()
|
||||||
{
|
{
|
||||||
var decoded = this.GetDecodedOpCode(this.OpCode);
|
var decoded = this.GetDecodedOpCode(this.OpCode);
|
||||||
@ -60,15 +210,12 @@ namespace LR35902
|
|||||||
{
|
{
|
||||||
this.prefixCB = false;
|
this.prefixCB = false;
|
||||||
|
|
||||||
var interruptEnable = this.Bus.Peek(IoRegisters.BASE + IoRegisters.IE);
|
var masked = (byte)(this.IE & this.IF);
|
||||||
var interruptFlags = this.bus.IO.Peek(IoRegisters.IF);
|
|
||||||
|
|
||||||
var masked = (byte)(interruptEnable & interruptFlags);
|
|
||||||
if (masked != 0)
|
if (masked != 0)
|
||||||
{
|
{
|
||||||
if (this.IME)
|
if (this.IME)
|
||||||
{
|
{
|
||||||
this.bus.IO.Poke(IoRegisters.IF, 0);
|
this.IF = 0;
|
||||||
this.LowerINT();
|
this.LowerINT();
|
||||||
var index = FindFirstSet(masked);
|
var index = FindFirstSet(masked);
|
||||||
this.Bus.Data = (byte)(0x38 + (index << 3));
|
this.Bus.Data = (byte)(0x38 + (index << 3));
|
||||||
@ -138,14 +285,23 @@ namespace LR35902
|
|||||||
|
|
||||||
protected override void MemoryWrite()
|
protected override void MemoryWrite()
|
||||||
{
|
{
|
||||||
this.TickMachine();
|
this.LowerMWR();
|
||||||
|
this.LowerWR();
|
||||||
base.MemoryWrite();
|
base.MemoryWrite();
|
||||||
|
this.TickMachine();
|
||||||
|
this.RaiseWR();
|
||||||
|
this.RaiseMWR();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override byte MemoryRead()
|
protected override byte MemoryRead()
|
||||||
{
|
{
|
||||||
|
this.LowerMWR();
|
||||||
|
this.LowerRD();
|
||||||
|
var returned = base.MemoryRead();
|
||||||
this.TickMachine();
|
this.TickMachine();
|
||||||
return base.MemoryRead();
|
this.RaiseRD();
|
||||||
|
this.RaiseMWR();
|
||||||
|
return returned;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PushWord(Register16 value)
|
protected override void PushWord(Register16 value)
|
||||||
|
@ -21,10 +21,13 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="LR35902.BlarggTest\**" />
|
<Compile Remove="LR35902.BlarggTest\**" />
|
||||||
<Compile Remove="LR35902.FuseTest\**" />
|
<Compile Remove="LR35902.FuseTest\**" />
|
||||||
|
<Compile Remove="SM83.HarteTest\**" />
|
||||||
<EmbeddedResource Remove="LR35902.BlarggTest\**" />
|
<EmbeddedResource Remove="LR35902.BlarggTest\**" />
|
||||||
<EmbeddedResource Remove="LR35902.FuseTest\**" />
|
<EmbeddedResource Remove="LR35902.FuseTest\**" />
|
||||||
|
<EmbeddedResource Remove="SM83.HarteTest\**" />
|
||||||
<None Remove="LR35902.BlarggTest\**" />
|
<None Remove="LR35902.BlarggTest\**" />
|
||||||
<None Remove="LR35902.FuseTest\**" />
|
<None Remove="LR35902.FuseTest\**" />
|
||||||
|
<None Remove="SM83.HarteTest\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
339
LR35902/SM83.HarteTest/Checker.cs
Normal file
339
LR35902/SM83.HarteTest/Checker.cs
Normal file
@ -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<string> Messages { get; } = [];
|
||||||
|
|
||||||
|
private List<Cycle> 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<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
LR35902/SM83.HarteTest/Cycle.cs
Normal file
34
LR35902/SM83.HarteTest/Cycle.cs
Normal file
@ -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<object> 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;
|
||||||
|
}
|
||||||
|
}
|
41
LR35902/SM83.HarteTest/OpcodeTestSuite.cs
Normal file
41
LR35902/SM83.HarteTest/OpcodeTestSuite.cs
Normal file
@ -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<Test?> TestsAsync => JsonSerializer.DeserializeAsyncEnumerable<Test>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
LR35902/SM83.HarteTest/ProcessorTestSuite.cs
Normal file
19
LR35902/SM83.HarteTest/ProcessorTestSuite.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace SM83.HarteTest
|
||||||
|
{
|
||||||
|
internal sealed class ProcessorTestSuite(string location)
|
||||||
|
{
|
||||||
|
public string Location { get; set; } = location;
|
||||||
|
|
||||||
|
public IEnumerable<OpcodeTestSuite> OpcodeTests()
|
||||||
|
{
|
||||||
|
foreach (var filename in Directory.EnumerateFiles(this.Location, "*.json"))
|
||||||
|
{
|
||||||
|
var fileInformation = new FileInfo(filename);
|
||||||
|
if (fileInformation.Length > 0)
|
||||||
|
{
|
||||||
|
yield return new OpcodeTestSuite(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
LR35902/SM83.HarteTest/Program.cs
Normal file
87
LR35902/SM83.HarteTest/Program.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// <copyright file="Program.cs" company="Adrian Conlon">
|
||||||
|
// Copyright (c) Adrian Conlon. All rights reserved.
|
||||||
|
// </copyright>
|
||||||
|
|
||||||
|
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<string?> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
LR35902/SM83.HarteTest/SM83.HarteTest.csproj
Normal file
17
LR35902/SM83.HarteTest/SM83.HarteTest.csproj
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
||||||
|
<AnalysisLevel>latest-recommended</AnalysisLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\EightBit\EightBit.csproj" />
|
||||||
|
<ProjectReference Include="..\LR35902.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
26
LR35902/SM83.HarteTest/State.cs
Normal file
26
LR35902/SM83.HarteTest/State.cs
Normal file
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
26
LR35902/SM83.HarteTest/Test.cs
Normal file
26
LR35902/SM83.HarteTest/Test.cs
Normal file
@ -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<List<object>>? Cycles { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<Cycle> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
LR35902/SM83.HarteTest/TestRunner.cs
Normal file
39
LR35902/SM83.HarteTest/TestRunner.cs
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user