From 21058f3e064662d6a6fc2b0d59ef5ce16068d4da Mon Sep 17 00:00:00 2001 From: Lucas Scharenbroich Date: Wed, 14 Dec 2016 00:04:10 -0600 Subject: [PATCH] Fix solution printing and add actions to alow stack to advance to new lines. Added 8-bit PHA actions --- SpriteCompiler.Test/MarioTests.cs | 2 +- SpriteCompiler.Test/Tests.cs | 57 ++++++++- SpriteCompiler/AI/AbstractSearchStrategy.cs | 4 +- SpriteCompiler/AI/InformedNodeExpander.cs | 7 +- SpriteCompiler/Problem/CodeSequence.cs | 112 +++++++++++++++++- .../SpriteGeneratorSuccessorFunction.cs | 49 ++++++-- SpriteCompiler/Program.cs | 20 +--- 7 files changed, 210 insertions(+), 41 deletions(-) diff --git a/SpriteCompiler.Test/MarioTests.cs b/SpriteCompiler.Test/MarioTests.cs index c7df24b..61629e6 100644 --- a/SpriteCompiler.Test/MarioTests.cs +++ b/SpriteCompiler.Test/MarioTests.cs @@ -64,7 +64,7 @@ namespace SpriteCompiler.Test { // Arrange var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); - var search = SpriteGeneratorSearchProblem.Create(); + var search = SpriteGeneratorSearchProblem.Create(80); // max budget of 80 cycles var sprite = new List { new SpriteByte(0x11, 0x00, 3), diff --git a/SpriteCompiler.Test/Tests.cs b/SpriteCompiler.Test/Tests.cs index 1dc81db..8b25407 100644 --- a/SpriteCompiler.Test/Tests.cs +++ b/SpriteCompiler.Test/Tests.cs @@ -219,11 +219,66 @@ namespace SpriteCompiler.Test Assert.AreEqual(31, (int)solution.Last().PathCost); } + [TestMethod] + public void TestThreeLineSprite() + { + // Arrange + var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); + var search = SpriteGeneratorSearchProblem.Create(); + + // Act : solve the problem + var data = new[] + { + new SpriteByte(0x11, 0), + new SpriteByte(0x11, 160), + new SpriteByte(0x11, 320) + }; + + var solution = search.Search(problem, SpriteGeneratorState.Init(data)); + + // Current best solution + // + // TCS ; 2 cycles + // SEP #$10 ; 3 cycles + // LDA #$11 ; 2 cycles + // PHA ; 3 cycles + // STA A1,s ; 4 cycles + // REP #$10 ; 3 cycles + // TSC ; 2 cycles + // ADC #321 ; 3 cycles + // TCS ; 2 cycles + // SEP #$10 ; 3 cycles + // LDA #$11 ; 2 cycles + // PHA ; 3 cycles + // REP #$10 ; 3 cycles + //; Total Cost = 35 cycles + // + // Once other register caching becomes available, this should be able to be improved to + // + // TCS ; 2 cycles + // SEP #$20 ; 3 cycles + // LDX #$11 ; 2 cycles + // PHX ; 3 cycles + // ADC #160 ; 3 cycles + // TCS ; 2 cycles + // PHX ; 3 cycles + // ADC #161 ; 3 cycles + // TCS ; 2 cycles + // PHX ; 3 cycles + // REP #$20 ; 3 cycles + //; Total Cost = 29 cycles + + // Write out the solution + WriteOutSolution(solution); + + Assert.AreEqual(35, (int)solution.Last().PathCost); + } + private void WriteOutSolution(IEnumerable solution) { foreach (var step in solution.Skip(1)) { - Trace.WriteLine(step.Action.ToString()); + Trace.WriteLine(step.Action.Emit()); } Trace.WriteLine(string.Format("; Total Cost = {0} cycles", (int)solution.Last().PathCost)); diff --git a/SpriteCompiler/AI/AbstractSearchStrategy.cs b/SpriteCompiler/AI/AbstractSearchStrategy.cs index e4c4abf..530aed5 100644 --- a/SpriteCompiler/AI/AbstractSearchStrategy.cs +++ b/SpriteCompiler/AI/AbstractSearchStrategy.cs @@ -44,8 +44,8 @@ public IEnumerable Solution(T node) { var sequence = new List(); - - for (var curr = node; node != null; node = node.Parent) + + for (var curr = node; curr != null; curr = curr.Parent) { sequence.Add(curr); } diff --git a/SpriteCompiler/AI/InformedNodeExpander.cs b/SpriteCompiler/AI/InformedNodeExpander.cs index 23b3e7d..32838c5 100644 --- a/SpriteCompiler/AI/InformedNodeExpander.cs +++ b/SpriteCompiler/AI/InformedNodeExpander.cs @@ -32,9 +32,10 @@ namespace SpriteCompiler.AI next.StepCost = problem.StepCost(node.State, action, state); next.Heuristic = problem.Heuristic(state); -#if False - Console.WriteLine(" Action = " + next.Action + ", g(n') = " + next.PathCost + ", h(n') = " + next.Heuristic); -#endif + #if VERBOSE_DEBUG + System.Diagnostics.Trace.WriteLine(" Action = " + next.Action + ", g(n') = " + next.PathCost + ", h(n') = " + next.Heuristic); + #endif + yield return next; } } diff --git a/SpriteCompiler/Problem/CodeSequence.cs b/SpriteCompiler/Problem/CodeSequence.cs index 14875cc..1d6a72d 100644 --- a/SpriteCompiler/Problem/CodeSequence.cs +++ b/SpriteCompiler/Problem/CodeSequence.cs @@ -72,6 +72,30 @@ namespace SpriteCompiler.Problem } } + public sealed class TSC : CodeSequence + { + public TSC() : base(2) { } + + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.A = _.S; + }); + } + + public override string ToString() + { + return "TSC"; + } + + public override string Emit() + { + return FormatLine("", "TSC", "", "2 cycles"); + } + } + + public sealed class SHORT_M : CodeSequence { public SHORT_M() : base(3) { } @@ -112,6 +136,31 @@ namespace SpriteCompiler.Problem } } + public sealed class STACK_REL_8_BIT_STORE : CodeSequence + { + private readonly byte offset; + + public STACK_REL_8_BIT_STORE(byte offset) : base(4) { this.offset = offset; } + + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.RemoveByte((ushort)(offset + _.S.Value)); + }); + } + + public override string ToString() + { + return "STA " + offset.ToString("X2") + ",s"; + } + + public override string Emit() + { + return FormatLine("", "STA", offset.ToString("X2") + ",s", "4 cycles"); + } + } + public sealed class STACK_REL_8_BIT_IMMEDIATE_STORE : CodeSequence { private readonly byte value; @@ -177,10 +226,9 @@ namespace SpriteCompiler.Problem public sealed class STACK_REL_16_BIT_STORE : CodeSequence { - private readonly ushort value; private readonly byte offset; - public STACK_REL_16_BIT_STORE(ushort value, byte offset) : base(5) { this.value = value; this.offset = offset; } + public STACK_REL_16_BIT_STORE(byte offset) : base(5) { this.offset = offset; } public override SpriteGeneratorState Apply(SpriteGeneratorState state) { @@ -294,6 +342,37 @@ namespace SpriteCompiler.Problem } } + public sealed class LOAD_8_BIT_IMMEDIATE_AND_PUSH : CodeSequence + { + private readonly byte value; + + public LOAD_8_BIT_IMMEDIATE_AND_PUSH(byte value) : base(5) { this.value = value; } + + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.A = _.A.LoadConstant(value); // Need to be able to track high / low bytes independently... + _.RemoveByte((ushort)(_.S.Value)); + _.S = _.S.Add(-1); + }); + } + + public override string ToString() + { + return "LDA #$" + value.ToString("X2") + " / PHA"; + } + + public override string Emit() + { + return String.Join("\n", + FormatLine("", "LDA", "#$" + value.ToString("X2"), "2 cycles"), + FormatLine("", "PHA", "", "3 cycles") + ); + } + } + + public sealed class PEA : CodeSequence { private readonly ushort value; @@ -320,9 +399,9 @@ namespace SpriteCompiler.Problem } } - public sealed class PHA : CodeSequence + public sealed class PHA_16 : CodeSequence { - public PHA() : base(4) { } + public PHA_16() : base(4) { } public override SpriteGeneratorState Apply(SpriteGeneratorState state) { @@ -343,4 +422,29 @@ namespace SpriteCompiler.Problem return FormatLine("", "PHA", "", "4 cycles"); } } + + public sealed class PHA_8 : CodeSequence + { + public PHA_8() : base(3) { } + + public override SpriteGeneratorState Apply(SpriteGeneratorState state) + { + return state.Clone(_ => + { + _.RemoveByte((ushort)(_.S.Value)); + _.S = _.S.Add(-1); + }); + } + + public override string ToString() + { + return "PHA"; + } + + public override string Emit() + { + return FormatLine("", "PHA", "", "3 cycles"); + } + } + } diff --git a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs index 108c05e..97b1b76 100644 --- a/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs +++ b/SpriteCompiler/Problem/SpriteGeneratorSuccessorFunction.cs @@ -117,7 +117,7 @@ { if (state.A.IsLiteral && state.A.Value == topWord.Value.Data) { - yield return state.Apply(new PHA()); + yield return state.Apply(new PHA_16()); } else { @@ -140,7 +140,14 @@ { if (!state.LongA) { - yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(topByte.Value.Data, 0)); + if (state.A.IsLiteral && ((state.A.Value & 0xFF) == topByte.Value.Data)) + { + yield return state.Apply(new PHA_8()); + } + else + { + yield return state.Apply(new LOAD_8_BIT_IMMEDIATE_AND_PUSH(topByte.Value.Data)); + } } } } @@ -152,7 +159,7 @@ // 2. Set the stack to the start of a contiguous segment // 3. Set the stack to the end of a contiguous segment - // move to the first or last byte of each span. So , take the first byte and then look for any + // move to the first or last byte of each span. So, take the first byte and then look for any if (state.A.IsScreenOffset && !state.S.IsScreenOffset && state.LongA) { // If any of the open bytes are within 255 bytes of the accumulator, consider just @@ -211,7 +218,7 @@ { if (data == state.A.Value) { - yield return state.Apply(new STACK_REL_16_BIT_STORE(data, offset)); + yield return state.Apply(new STACK_REL_16_BIT_STORE(offset)); } else { @@ -239,14 +246,25 @@ // We can LDA #$XX / STA X,s for any values within 256 bytes of the current address foreach (var datum in open.Where(WithinRangeOf(addr, 256))) { - var offset = datum.Offset - addr; + var offset = (byte)(datum.Offset - addr); + + // Easy case when mask is empty if (datum.Mask == 0x00) { - yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, (byte)offset)); + if (datum.Data == (state.A.Value & 0xFF)) + { + yield return state.Apply(new STACK_REL_8_BIT_STORE(offset)); + } + else + { + yield return state.Apply(new STACK_REL_8_BIT_IMMEDIATE_STORE(datum.Data, offset)); + } } + + // Otherwise there is really only one choice LDA / AND / ORA / STA sequence else { - yield return state.Apply(new STACK_REL_8_BIT_READ_MODIFY_WRITE(datum.Data, datum.Mask, (byte)offset)); + yield return state.Apply(new STACK_REL_8_BIT_READ_MODIFY_WRITE(datum.Data, datum.Mask, offset)); } } } @@ -254,12 +272,21 @@ // If the accumulator and stack are both initialized, only propose moves to locations // before and after the current 256 byte stack-relative window - if (state.A.IsScreenOffset && state.S.IsScreenOffset && state.LongA) + if (state.S.IsScreenOffset && state.LongA) { - var addr = state.S.Value; - foreach (var datum in open.Where(x => (x.Offset - addr) > 255 || (x.Offset - addr) < 0)) + // If the accumulator already has a screen offset value, just calculate it + if (state.A.IsScreenOffset) { - yield return state.Apply(new MOVE_STACK(datum.Offset - state.A.Value)); + var addr = state.S.Value; + foreach (var datum in open.Where(x => (x.Offset - addr) > 255 || (x.Offset - addr) < 0)) + { + yield return state.Apply(new MOVE_STACK(datum.Offset - state.A.Value)); + } + } + // Otherwise, put the stack in the accumulator + else + { + yield return state.Apply(new TSC()); } } } diff --git a/SpriteCompiler/Program.cs b/SpriteCompiler/Program.cs index f518167..cdfc543 100644 --- a/SpriteCompiler/Program.cs +++ b/SpriteCompiler/Program.cs @@ -179,27 +179,9 @@ initialState = SpriteGeneratorState.Init(data); } - /* - IEnumerable solution = null; - if (verbose) - { - search.InitializeSearch(initialState); - while (true) - { - var step = search.SearchStep(problem); - if (step.IsGoal) - { - break; - } - } - } - else - { - solution = search.Search(problem, fringe, initialState); - } + var solution = search.Search(problem, initialState); WriteOutSolution(solution); - */ } } }