diff --git a/src/Chip8.Analysis/TraceEntry.cs b/src/Chip8.Analysis/TraceEntry.cs new file mode 100644 index 0000000..bfc97b1 --- /dev/null +++ b/src/Chip8.Analysis/TraceEntry.cs @@ -0,0 +1,61 @@ +using System; +using System.Globalization; +using System.Text; +using Chip8.Core; + +namespace Chip8.Analysis; + +public sealed class TraceEntry +{ + public TraceEntry(int step, ushort address, ushort opcode, ushort? extraWord, string mnemonic, byte[] registers, ushort index, byte stackPointer, byte delayTimer, byte soundTimer) + { + this.step = step; + this.address = address; + this.opcode = opcode; + this.extraWord = extraWord; + this.mnemonic = mnemonic; + this.registers = registers; + this.index = index; + this.stackPointer = stackPointer; + this.delayTimer = delayTimer; + this.soundTimer = soundTimer; + } + + public int step { get; } + public ushort address { get; } + public ushort opcode { get; } + public ushort? extraWord { get; } + public string mnemonic { get; } + public byte[] registers { get; } + public ushort index { get; } + public byte stackPointer { get; } + public byte delayTimer { get; } + public byte soundTimer { get; } + + public byte register(int slot) => registers[slot & 0xF]; + + public string registerDump() + { + var builder = new StringBuilder(); + for (int slot = 0; slot < registers.Length; slot++) + { + if (slot > 0) + { + builder.Append(' '); + } + builder.Append('V').Append(slot.ToString("X1", CultureInfo.InvariantCulture)); + builder.Append('=').Append(registers[slot].ToString("X2", CultureInfo.InvariantCulture)); + } + return builder.ToString(); + } + + public override string ToString() + { + return "#" + step.ToString(CultureInfo.InvariantCulture).PadLeft(5) + + " 0x" + address.ToString("X4", CultureInfo.InvariantCulture) + + " " + opcode.ToString("X4", CultureInfo.InvariantCulture) + + " " + mnemonic.PadRight(18) + + " I=0x" + index.ToString("X3", CultureInfo.InvariantCulture) + + " SP=" + stackPointer.ToString(CultureInfo.InvariantCulture); + } +} diff --git a/src/Chip8.Analysis/TraceRecorder.cs b/src/Chip8.Analysis/TraceRecorder.cs new file mode 100644 index 0000000..4ff1e7e --- /dev/null +++ b/src/Chip8.Analysis/TraceRecorder.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Chip8.Core; + +namespace Chip8.Analysis; + +public sealed class TraceRecorder +{ + readonly List entryList = new(); + + public IReadOnlyList entries => entryList; + + public int count => entryList.Count; + + public bool haltedOnSpin { get; private set; } + + public TraceEntry this[int index] => entryList[index]; + + public static TraceRecorder record(byte[] program, int maxSteps, Platform platform = Platform.Chip8, Quirks? quirks = null, int seed = 1, bool stopOnSpin = true) + { + var machine = new Machine(quirks, seed, platform); + machine.loadRom(program); + var recorder = new TraceRecorder(); + recorder.capture(machine, maxSteps, stopOnSpin); + return recorder; + } + + public void capture(Machine machine, int maxSteps, bool stopOnSpin = true) + { + int spinRun = 0; + + for (int step = 0; step < maxSteps; step++) + { + if (machine.exited) + { + break; + } + + ushort address = machine.pc; + ushort opcode = (ushort)((machine.memory[address] << 8) | machine.memory[address + 1]); + ushort? extraWord = null; + if (opcode == 0xF000) + { + extraWord = (ushort)((machine.memory[address + 2] << 8) | machine.memory[address + 3]); + } + + string mnemonic = Disassembler.describe(opcode, extraWord ?? 0); + var registers = (byte[])machine.v.Clone(); + entryList.Add(new TraceEntry(step, address, opcode, extraWord, mnemonic, registers, machine.i, machine.sp, machine.delayTimer, machine.soundTimer)); + + machine.step(); + + if (stopOnSpin && machine.pc == address) + { + spinRun++; + if (spinRun >= 2) + { + haltedOnSpin = true; + break; + } + } + else + { + spinRun = 0; + } + } + } + + public IReadOnlyList visitedAddresses() + { + var visited = new List(); + var seen = new HashSet(); + foreach (var entry in entryList) + { + if (seen.Add(entry.address)) + { + visited.Add(entry.address); + } + } + return visited; + } + + public string render() + { + var builder = new StringBuilder(); + foreach (var entry in entryList) + { + builder.Append(entry).Append('\n'); + } + return builder.ToString(); + } +}