feat(replay): add conformance runner and outcome type
This commit is contained in:
1 parent
b89e1f272e
commit
3eaf1cb369
2 files changed
+154
No files matched your search
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Generic;
|
||||
using Chip8.Core;
|
||||
|
||||
namespace Chip8.Replay;
|
||||
|
||||
public sealed class ConformanceRunner
|
||||
{
|
||||
readonly RomLibrary library;
|
||||
|
||||
public ConformanceRunner(RomLibrary library)
|
||||
{
|
||||
this.library = library;
|
||||
}
|
||||
|
||||
public static ConformanceRunner locate() => new ConformanceRunner(RomLibrary.locate());
|
||||
|
||||
public RomLibrary roms => library;
|
||||
|
||||
public Machine execute(RomExpectation expectation, out int cyclesExecuted)
|
||||
{
|
||||
var machine = new Machine(QuirksCodec.clone(expectation.quirks), expectation.randomSeed, expectation.platform);
|
||||
machine.loadRom(library.load(expectation.romName));
|
||||
|
||||
int cycle = 0;
|
||||
for (; cycle < expectation.cycles && !machine.exited; cycle++)
|
||||
{
|
||||
machine.step();
|
||||
if (cycle % expectation.instructionsPerFrame == expectation.instructionsPerFrame - 1)
|
||||
{
|
||||
machine.tickTimers();
|
||||
}
|
||||
}
|
||||
|
||||
cyclesExecuted = cycle;
|
||||
return machine;
|
||||
}
|
||||
|
||||
public TestRomResult run(RomExpectation expectation)
|
||||
{
|
||||
Machine machine = execute(expectation, out int cyclesExecuted);
|
||||
FramebufferSnapshot snapshot = FramebufferSnapshot.capture(machine);
|
||||
int litPixels = snapshot.litCount;
|
||||
ulong framebufferHash = FramebufferHash.compute(snapshot);
|
||||
ulong registerHash = FramebufferHash.computeWithRegisters(machine);
|
||||
|
||||
bool rangeOk = expectation.litPixelsInRange(litPixels);
|
||||
bool hashOk = !expectation.framebufferHash.HasValue || expectation.framebufferHash.Value == framebufferHash;
|
||||
bool passed = rangeOk && hashOk;
|
||||
|
||||
string detail = passed
|
||||
? expectation.description
|
||||
: buildFailureDetail(expectation, litPixels, framebufferHash, rangeOk, hashOk);
|
||||
|
||||
return new TestRomResult(
|
||||
expectation.romName,
|
||||
passed,
|
||||
litPixels,
|
||||
expectation.minLitPixels,
|
||||
expectation.maxLitPixels,
|
||||
framebufferHash,
|
||||
registerHash,
|
||||
cyclesExecuted,
|
||||
machine.exited,
|
||||
detail);
|
||||
}
|
||||
|
||||
public IReadOnlyList<TestRomResult> runAll(IEnumerable<RomExpectation> expectations)
|
||||
{
|
||||
var results = new List<TestRomResult>();
|
||||
foreach (RomExpectation expectation in expectations)
|
||||
{
|
||||
results.Add(run(expectation));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public IReadOnlyList<TestRomResult> runBuiltIns() => runAll(builtInExpectations());
|
||||
|
||||
static string buildFailureDetail(RomExpectation expectation, int litPixels, ulong framebufferHash, bool rangeOk, bool hashOk)
|
||||
{
|
||||
if (!rangeOk)
|
||||
{
|
||||
return "lit " + litPixels + " outside [" + expectation.minLitPixels + ".." + expectation.maxLitPixels + "]";
|
||||
}
|
||||
if (!hashOk)
|
||||
{
|
||||
return "hash " + FramebufferHash.format(framebufferHash)
|
||||
+ " != expected " + FramebufferHash.format(expectation.framebufferHash!.Value);
|
||||
}
|
||||
return "unexpected failure";
|
||||
}
|
||||
|
||||
public static IReadOnlyList<RomExpectation> builtInExpectations() => new List<RomExpectation>
|
||||
{
|
||||
new RomExpectation("2-ibm-logo.ch8", Platform.Chip8, Quirks.modern(),
|
||||
cycles: 400, minLitPixels: 300, maxLitPixels: 340, description: "IBM logo"),
|
||||
new RomExpectation("1-chip8-logo.ch8", Platform.Chip8, Quirks.modern(),
|
||||
cycles: 400, minLitPixels: 400, maxLitPixels: 620, description: "CHIP-8 splash logo"),
|
||||
new RomExpectation("3-corax+.ch8", Platform.Chip8, Quirks.modern(),
|
||||
cycles: 4000, minLitPixels: 400, maxLitPixels: 1200, description: "corax+ opcode suite"),
|
||||
new RomExpectation("4-flags.ch8", Platform.Chip8, Quirks.modern(),
|
||||
cycles: 6000, minLitPixels: 400, maxLitPixels: 1400, description: "flags suite"),
|
||||
new RomExpectation("test_opcode.ch8", Platform.Chip8, Quirks.modern(),
|
||||
cycles: 4000, minLitPixels: 200, maxLitPixels: 1000, description: "opcode test grid"),
|
||||
new RomExpectation("BC_test.ch8", Platform.Chip8, Quirks.cosmacVip(),
|
||||
cycles: 4000, minLitPixels: 80, maxLitPixels: 900, description: "BestCoder test"),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Chip8.Core;
|
||||
|
||||
namespace Chip8.Replay;
|
||||
|
||||
public readonly struct ReplayOutcome
|
||||
{
|
||||
public int frames { get; }
|
||||
public ulong framebufferHash { get; }
|
||||
public ulong registerHash { get; }
|
||||
public int litPixels { get; }
|
||||
public bool exited { get; }
|
||||
|
||||
public ReplayOutcome(int frames, ulong framebufferHash, ulong registerHash, int litPixels, bool exited)
|
||||
{
|
||||
this.frames = frames;
|
||||
this.framebufferHash = framebufferHash;
|
||||
this.registerHash = registerHash;
|
||||
this.litPixels = litPixels;
|
||||
this.exited = exited;
|
||||
}
|
||||
|
||||
public static ReplayOutcome capture(Machine machine, int frames)
|
||||
{
|
||||
var snapshot = FramebufferSnapshot.capture(machine);
|
||||
return new ReplayOutcome(
|
||||
frames,
|
||||
FramebufferHash.compute(snapshot),
|
||||
FramebufferHash.computeWithRegisters(machine),
|
||||
snapshot.litCount,
|
||||
machine.exited);
|
||||
}
|
||||
|
||||
public bool sameAs(ReplayOutcome other) =>
|
||||
frames == other.frames
|
||||
&& framebufferHash == other.framebufferHash
|
||||
&& registerHash == other.registerHash
|
||||
&& litPixels == other.litPixels
|
||||
&& exited == other.exited;
|
||||
|
||||
public override string ToString() =>
|
||||
"frames=" + frames
|
||||
+ " fb=" + FramebufferHash.format(framebufferHash)
|
||||
+ " reg=" + FramebufferHash.format(registerHash)
|
||||
+ " lit=" + litPixels
|
||||
+ " exited=" + exited;
|
||||
}
|
||||
Reference in new issue
Block a user