Add a .net Fuse test suite for the Z80 core (one "unexpected" result).

Signed-off-by: Adrian Conlon <Adrian.conlon@gmail.com>
This commit is contained in:
Adrian Conlon 2019-08-13 22:24:04 +01:00
parent 63ef445a78
commit 9550ed57be
13 changed files with 28728 additions and 3 deletions

View File

@ -30,7 +30,7 @@ namespace Fuse
protected virtual void ParseInternalState(string line)
{
var tokens = line.Split(new char[] { ' ', '\t' });
var tokens = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
this.ParseInternalState(tokens);
}

View File

@ -35,7 +35,7 @@ namespace Fuse
private bool TryParseLine(string line)
{
var split = line.Split(new char[] { ' ', '\t' });
var split = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
return this.TryParseLine(split);
}

View File

@ -14,7 +14,7 @@ namespace Fuse
public void Parse(Lines lines)
{
var success = false;
bool success;
do
{
var e = new TestEvent();

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
</configuration>

View File

@ -0,0 +1,17 @@
// <copyright file="Program.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
namespace Fuse
{
class Program
{
static void Main(string[] args)
{
var suite = new TestSuite("fuse-tests\\tests");
suite.Read();
suite.Parse();
suite.Run();
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Z80.FuseTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Z80.FuseTest")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("0233ff6c-db1a-4353-8e42-d22717770226")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,32 @@
// <copyright file="RegisterState.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
namespace Fuse
{
using System;
using System.Globalization;
public class RegisterState : AbstractRegisterState, IRegisterState
{
public int I { get; private set; } = -1;
public int R { get; private set; } = -1;
public bool IFF1 { get; private set; } = false;
public bool IFF2 { get; private set; } = false;
public int IM { get; private set; } = -1;
protected override void ParseInternalState(string[] tokens)
{
this.I = Convert.ToInt32(tokens[0], 16);
this.R = Convert.ToInt32(tokens[1], 16);
this.IFF1 = Convert.ToInt32(tokens[2], CultureInfo.InvariantCulture) == 1;
this.IFF2 = Convert.ToInt32(tokens[3], CultureInfo.InvariantCulture) == 1;
this.IM = Convert.ToInt32(tokens[4], CultureInfo.InvariantCulture);
this.Halted = Convert.ToInt32(tokens[5], CultureInfo.InvariantCulture) == 1;
this.TStates = Convert.ToInt32(tokens[6], CultureInfo.InvariantCulture);
}
}
}

View File

@ -0,0 +1,361 @@
// <copyright file="TestRunner.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
namespace Fuse
{
public enum Register
{
AF,
BC,
DE,
HL,
AF_,
BC_,
DE_,
HL_,
IX,
IY,
SP,
PC,
MEMPTR,
}
public class TestRunner : EightBit.Bus
{
private readonly Test<RegisterState> test;
private readonly Result<RegisterState> result;
private readonly EightBit.Ram ram = new EightBit.Ram(0x10000);
private readonly EightBit.InputOutput ports = new EightBit.InputOutput();
private readonly EightBit.Z80 cpu;
public TestRunner(Test<RegisterState> test, Result<RegisterState> result)
{
this.cpu = new EightBit.Z80(this, this.ports);
this.test = test;
this.result = result;
}
public bool Failed { get; private set; } = false;
public bool Unimplemented { get; private set; } = false;
public override EightBit.MemoryMapping Mapping(ushort address) => new EightBit.MemoryMapping(this.ram, 0, EightBit.Mask.Mask16, EightBit.AccessLevel.ReadWrite);
public void Run()
{
this.RaisePOWER();
this.Initialize();
var allowedCycles = this.test.RegisterState.TStates;
try
{
this.cpu.Run(allowedCycles);
this.Check();
}
catch (System.InvalidOperationException error)
{
this.Unimplemented = true;
System.Console.Error.WriteLine($"**** Error: {error.Message}");
}
}
public override void RaisePOWER()
{
base.RaisePOWER();
this.cpu.RaisePOWER();
this.cpu.RaiseRESET();
this.cpu.RaiseINT();
this.cpu.RaiseNMI();
this.InitialiseRegisters();
this.InitialiseMemory();
}
public override void LowerPOWER()
{
this.cpu.LowerPOWER();
base.LowerPOWER();
}
public override void Initialize()
{
}
private static void DumpDifference(string description, byte expected, byte actual)
{
var output = $"**** {description}, Expected: {expected:x2}, Got {actual:x2}";
System.Console.Error.WriteLine(output);
}
private static void DumpDifference(string highDescription, string lowDescription, EightBit.Register16 expected, EightBit.Register16 actual)
{
var expectedHigh = expected.High;
var expectedLow = expected.Low;
var actualHigh = actual.High;
var actualLow = actual.Low;
if (expectedHigh != actualHigh)
{
DumpDifference(highDescription, actualHigh, expectedHigh);
}
if (expectedLow != actualLow)
{
DumpDifference(lowDescription, actualLow, expectedLow);
}
}
private void InitialiseRegisters()
{
var testState = this.test.RegisterState;
var inputRegisters = testState.Registers;
this.cpu.AF.Word = inputRegisters[(int)Register.AF_].Word;
this.cpu.BC.Word = inputRegisters[(int)Register.BC_].Word;
this.cpu.DE.Word = inputRegisters[(int)Register.DE_].Word;
this.cpu.HL.Word = inputRegisters[(int)Register.HL_].Word;
this.cpu.Exx();
this.cpu.ExxAF();
this.cpu.AF.Word = inputRegisters[(int)Register.AF].Word;
this.cpu.BC.Word = inputRegisters[(int)Register.BC].Word;
this.cpu.DE.Word = inputRegisters[(int)Register.DE].Word;
this.cpu.HL.Word = inputRegisters[(int)Register.HL].Word;
this.cpu.IX.Word = inputRegisters[(int)Register.IX].Word;
this.cpu.IY.Word = inputRegisters[(int)Register.IY].Word;
this.cpu.SP.Word = inputRegisters[(int)Register.SP].Word;
this.cpu.PC.Word = inputRegisters[(int)Register.PC].Word;
this.cpu.MEMPTR.Word = inputRegisters[(int)Register.MEMPTR].Word;
this.cpu.IV = (byte)testState.I;
this.cpu.REFRESH = (byte)testState.R;
this.cpu.IFF1 = testState.IFF1;
this.cpu.IFF2 = testState.IFF2;
this.cpu.IM = testState.IM;
}
private void InitialiseMemory()
{
foreach (var memoryDatum in this.test.MemoryData)
{
var address = memoryDatum.Address;
foreach (var seed in memoryDatum.Bytes)
{
this.Poke(address++, seed);
}
}
}
private void Check()
{
this.Checkregisters();
this.CheckMemory();
}
private void Checkregisters()
{
var expectedState = this.result.RegisterState;
var expectedRegisters = expectedState.Registers;
var af = this.cpu.AF.Word == expectedRegisters[(int)Register.AF].Word;
var bc = this.cpu.BC.Word == expectedRegisters[(int)Register.BC].Word;
var de = this.cpu.DE.Word == expectedRegisters[(int)Register.DE].Word;
var hl = this.cpu.HL.Word == expectedRegisters[(int)Register.HL].Word;
this.cpu.Exx();
this.cpu.ExxAF();
var af_ = this.cpu.AF.Word == expectedRegisters[(int)Register.AF_].Word;
var bc_ = this.cpu.BC.Word == expectedRegisters[(int)Register.BC_].Word;
var de_ = this.cpu.DE.Word == expectedRegisters[(int)Register.DE_].Word;
var hl_ = this.cpu.HL.Word == expectedRegisters[(int)Register.HL_].Word;
var ix = this.cpu.IX.Word == expectedRegisters[(int)Register.IX].Word;
var iy = this.cpu.IY.Word == expectedRegisters[(int)Register.IY].Word;
var sp = this.cpu.SP.Word == expectedRegisters[(int)Register.SP].Word;
var pc = this.cpu.PC.Word == expectedRegisters[(int)Register.PC].Word;
var memptr = this.cpu.MEMPTR.Word == expectedRegisters[(int)Register.MEMPTR].Word;
var iv = this.cpu.IV == expectedState.I;
var refresh = this.cpu.REFRESH == expectedState.R;
var iff1 = this.cpu.IFF1 == expectedState.IFF1;
var iff2 = this.cpu.IFF2 == expectedState.IFF2;
var im = this.cpu.IM == expectedState.IM;
// And back again, so the following works as expected...
this.cpu.Exx();
this.cpu.ExxAF();
var success =
af && bc && de && hl
&& af_ && bc_ && de_ && hl_
&& ix && iy
&& sp && pc
&& iv && refresh
&& iff1 && iff2
&& im
&& memptr;
if (!success)
{
this.Failed = true;
System.Console.Error.WriteLine($"**** Failed test (Register): {this.test.Description}");
if (!af)
{
var expectedWord = expectedRegisters[(int)Register.AF];
var actualWord = this.cpu.AF;
DumpDifference("A", "F", expectedWord, actualWord);
}
if (!bc)
{
var expectedWord = expectedRegisters[(int)Register.BC];
var actualWord = this.cpu.BC;
DumpDifference("B", "C", expectedWord, actualWord);
}
if (!de)
{
var expectedWord = expectedRegisters[(int)Register.DE];
var actualWord = this.cpu.DE;
DumpDifference("D", "E", expectedWord, actualWord);
}
if (!hl)
{
var expectedWord = expectedRegisters[(int)Register.HL];
var actualWord = this.cpu.HL;
DumpDifference("H", "L", expectedWord, actualWord);
}
if (!ix)
{
var expectedWord = expectedRegisters[(int)Register.IX];
var actualWord = this.cpu.IX;
DumpDifference("IXH", "IXL", actualWord, expectedWord);
}
if (!iy)
{
var expectedWord = expectedRegisters[(int)Register.IY];
var actualWord = this.cpu.IY;
DumpDifference("IYH", "IYL", actualWord, expectedWord);
}
if (!sp)
{
var expectedWord = expectedRegisters[(int)Register.SP];
var actualWord = this.cpu.SP;
DumpDifference("SPH", "SPL", expectedWord, actualWord);
}
if (!pc)
{
var expectedWord = expectedRegisters[(int)Register.PC];
var actualWord = this.cpu.PC;
DumpDifference("PCH", "PCL", expectedWord, actualWord);
}
if (!memptr)
{
var expectedWord = expectedRegisters[(int)Register.MEMPTR];
var actualWord = this.cpu.MEMPTR;
DumpDifference("MEMPTRH", "MEMPTRL", actualWord, expectedWord);
}
this.cpu.ExxAF();
this.cpu.Exx();
if (!af_)
{
var expectedWord = expectedRegisters[(int)Register.AF];
var actualWord = this.cpu.AF;
DumpDifference("A'", "F'", expectedWord, actualWord);
}
if (!bc_)
{
var expectedWord = expectedRegisters[(int)Register.BC_];
var actualWord = this.cpu.BC;
DumpDifference("B'", "C'", actualWord, expectedWord);
}
if (!de_)
{
var expectedWord = expectedRegisters[(int)Register.DE_];
var actualWord = this.cpu.DE;
DumpDifference("D'", "E'", actualWord, expectedWord);
}
if (!hl_)
{
var expectedWord = expectedRegisters[(int)Register.HL_];
var actualWord = this.cpu.HL;
DumpDifference("H'", "L'", actualWord, expectedWord);
}
if (!iv)
{
var output = $"**** IV, Expected: {expectedState.I:X2}, Got: {this.cpu.IV:X2}";
System.Console.Error.WriteLine(output);
}
if (!refresh)
{
var output = $"**** R, Expected: {expectedState.R:X2}, Got: {this.cpu.REFRESH:X2}";
System.Console.Error.WriteLine(output);
}
if (!iff1)
{
var output = $"**** IFF1, Expected: {expectedState.IFF1}, Got: {this.cpu.IFF1}";
System.Console.Error.WriteLine(output);
}
if (!iff2)
{
var output = $"**** IFF2, Expected: {expectedState.IFF2}, Got: {this.cpu.IFF2}";
System.Console.Error.WriteLine(output);
}
if (!im)
{
var output = $"**** IM, Expected: {expectedState.IM}, Got: {this.cpu.IM}";
System.Console.Error.WriteLine(output);
}
}
}
private void CheckMemory()
{
var first = true;
foreach (var memoryDatum in this.result.MemoryData)
{
var address = memoryDatum.Address;
foreach (var expected in memoryDatum.Bytes)
{
var actual = this.Peek(address);
if (expected != actual)
{
this.Failed = true;
if (first)
{
first = false;
System.Console.Error.WriteLine($"**** Failed test (Memory): {this.test.Description}");
}
System.Console.Error.WriteLine($"**** Difference: Address: {address:x4} Expected: {expected:x2} Actual: {actual:x2}");
}
++address;
}
}
}
}
}

View File

@ -0,0 +1,58 @@
// <copyright file="TestSuite.cs" company="Adrian Conlon">
// Copyright (c) Adrian Conlon. All rights reserved.
// </copyright>
namespace Fuse
{
public class TestSuite
{
private readonly Tests<RegisterState> tests;
private readonly Results<RegisterState> results;
public TestSuite(string path)
{
this.tests = new Tests<RegisterState>(path + ".in");
this.results = new Results<RegisterState>(path + ".expected");
}
public void Read()
{
this.tests.Read();
this.results.Read();
}
public void Parse()
{
this.tests.Parse();
this.results.Parse();
}
public void Run()
{
var failedCount = 0;
var unimplementedCount = 0;
foreach (var test in this.tests.Container)
{
var key = test.Key;
System.Console.Out.WriteLine($"** Checking: {key}");
var input = test.Value;
var result = this.results.Container[key];
var runner = new TestRunner(input, result);
runner.Run();
if (runner.Failed)
{
++failedCount;
}
if (runner.Unimplemented)
{
++unimplementedCount;
}
}
System.Console.Out.WriteLine($"+++ Failed test count: {failedCount}");
System.Console.Out.WriteLine($"+++ Unimplemented test count: {unimplementedCount}");
}
}
}

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0233FF6C-DB1A-4353-8E42-D22717770226}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Z80.FuseTest</RootNamespace>
<AssemblyName>Z80.FuseTest</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RegisterState.cs" />
<Compile Include="TestRunner.cs" />
<Compile Include="TestSuite.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\EightBit\EightBit.csproj">
<Project>{6ebf8857-62a3-4ef4-af21-c1844031d7e4}</Project>
<Name>EightBit</Name>
</ProjectReference>
<ProjectReference Include="..\..\Fuse\Fuse.csproj">
<Project>{28e65032-5dff-406f-9385-0ee1422a7f4a}</Project>
<Name>Fuse</Name>
</ProjectReference>
<ProjectReference Include="..\Z80.csproj">
<Project>{c00648c1-bac1-4efb-816f-e87c091619d7}</Project>
<Name>Z80</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,79 @@
File formats
============
tests.in
--------
Each test has the format:
<arbitrary test description>
AF BC DE HL AF' BC' DE' HL' IX IY SP PC MEMPTR
I R IFF1 IFF2 IM <halted> <tstates>
<halted> specifies whether the Z80 is halted.
<tstates> 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:
<start address> <byte1> <byte2> ... -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
<time> <type> <address> <data>
<time> is simply the time at which the event occurs.
<type> is one of MR (memory read), MW (memory write), MC (memory
contend), PR (port read), PW (port write) or PC (port contend).
<address> is the address (or IO port) affected.
<data> is the byte written or read. Missing for contentions.
After that, lines specifying AF, BC etc as for .in files. <tstates>
now specifies the final time.
After that, lines specifying which bits of memory have changed since
the initial setup. Same format as for .in files.
Why some specific tests are here
================================
{02,0a,32}_1,edb[012389ab]_[12],d3_4:
check MEMPTR is set correctly after various instructions.
37_{1,2,3}: check the behaviour of SCF with respect to bits 3 and 5
(bug fixed on 20040225).
cb46_{1,2,3,4,5}: check the correct bits of MEMPTR are copied to bits 3
and 5 of the flags register after a BIT n,(HL) instruction.
cb{4,5,6,7}{7,f}_1: designed to check that bits 3 and 5 are copied to
F only for BIT 3,<arg> and BIT 5,<arg> respectively
(bug fixed on 20040225).
However, later research has revealed the bits 3
and 5 are copied on all BIT instructions, so these
tests are now essentially redundant.
d{3,b}_{1,2,3}: check for correct port contention on IO in the four
relevant states (port high byte in 0x40 to 0x7f or not,
port low bit set or reset).
dd00.in, ddfd00.in: test timings of "extended NOP" opcodes DD 00 and
DD FD 00; the extra 00 at the end is to check the
next opcode executes at the right time (bug fixed
on 20060722).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff