Fix solution printing and add actions to alow stack to advance to new lines. Added 8-bit PHA actions

This commit is contained in:
Lucas Scharenbroich 2016-12-14 00:04:10 -06:00
parent ac9850998b
commit 21058f3e06
7 changed files with 210 additions and 41 deletions

View File

@ -64,7 +64,7 @@ namespace SpriteCompiler.Test
{ {
// Arrange // Arrange
var problem = SpriteGeneratorSearchProblem.CreateSearchProblem(); var problem = SpriteGeneratorSearchProblem.CreateSearchProblem();
var search = SpriteGeneratorSearchProblem.Create(); var search = SpriteGeneratorSearchProblem.Create(80); // max budget of 80 cycles
var sprite = new List<SpriteByte> var sprite = new List<SpriteByte>
{ {
new SpriteByte(0x11, 0x00, 3), new SpriteByte(0x11, 0x00, 3),

View File

@ -219,11 +219,66 @@ namespace SpriteCompiler.Test
Assert.AreEqual(31, (int)solution.Last().PathCost); 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<SpriteGeneratorSearchNode> solution) private void WriteOutSolution(IEnumerable<SpriteGeneratorSearchNode> solution)
{ {
foreach (var step in solution.Skip(1)) 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)); Trace.WriteLine(string.Format("; Total Cost = {0} cycles", (int)solution.Last().PathCost));

View File

@ -44,8 +44,8 @@
public IEnumerable<T> Solution(T node) public IEnumerable<T> Solution(T node)
{ {
var sequence = new List<T>(); var sequence = new List<T>();
for (var curr = node; node != null; node = node.Parent) for (var curr = node; curr != null; curr = curr.Parent)
{ {
sequence.Add(curr); sequence.Add(curr);
} }

View File

@ -32,9 +32,10 @@ namespace SpriteCompiler.AI
next.StepCost = problem.StepCost(node.State, action, state); next.StepCost = problem.StepCost(node.State, action, state);
next.Heuristic = problem.Heuristic(state); next.Heuristic = problem.Heuristic(state);
#if False #if VERBOSE_DEBUG
Console.WriteLine(" Action = " + next.Action + ", g(n') = " + next.PathCost + ", h(n') = " + next.Heuristic); System.Diagnostics.Trace.WriteLine(" Action = " + next.Action + ", g(n') = " + next.PathCost + ", h(n') = " + next.Heuristic);
#endif #endif
yield return next; yield return next;
} }
} }

View File

@ -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 sealed class SHORT_M : CodeSequence
{ {
public SHORT_M() : base(3) { } 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 public sealed class STACK_REL_8_BIT_IMMEDIATE_STORE : CodeSequence
{ {
private readonly byte value; private readonly byte value;
@ -177,10 +226,9 @@ namespace SpriteCompiler.Problem
public sealed class STACK_REL_16_BIT_STORE : CodeSequence public sealed class STACK_REL_16_BIT_STORE : CodeSequence
{ {
private readonly ushort value;
private readonly byte offset; 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) 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 public sealed class PEA : CodeSequence
{ {
private readonly ushort value; 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) public override SpriteGeneratorState Apply(SpriteGeneratorState state)
{ {
@ -343,4 +422,29 @@ namespace SpriteCompiler.Problem
return FormatLine("", "PHA", "", "4 cycles"); 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");
}
}
} }

View File

@ -117,7 +117,7 @@
{ {
if (state.A.IsLiteral && state.A.Value == topWord.Value.Data) if (state.A.IsLiteral && state.A.Value == topWord.Value.Data)
{ {
yield return state.Apply(new PHA()); yield return state.Apply(new PHA_16());
} }
else else
{ {
@ -140,7 +140,14 @@
{ {
if (!state.LongA) 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 // 2. Set the stack to the start of a contiguous segment
// 3. Set the stack to the end 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 (state.A.IsScreenOffset && !state.S.IsScreenOffset && state.LongA)
{ {
// If any of the open bytes are within 255 bytes of the accumulator, consider just // If any of the open bytes are within 255 bytes of the accumulator, consider just
@ -211,7 +218,7 @@
{ {
if (data == state.A.Value) 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 else
{ {
@ -239,14 +246,25 @@
// We can LDA #$XX / STA X,s for any values within 256 bytes of the current address // 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))) 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) 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 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 // If the accumulator and stack are both initialized, only propose moves to locations
// before and after the current 256 byte stack-relative window // 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; // If the accumulator already has a screen offset value, just calculate it
foreach (var datum in open.Where(x => (x.Offset - addr) > 255 || (x.Offset - addr) < 0)) 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());
} }
} }
} }

View File

@ -179,27 +179,9 @@
initialState = SpriteGeneratorState.Init(data); initialState = SpriteGeneratorState.Init(data);
} }
/* var solution = search.Search(problem, initialState);
IEnumerable<SpriteGeneratorSearchNode> 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);
}
WriteOutSolution(solution); WriteOutSolution(solution);
*/
} }
} }
} }