private void Test <TInstructionBlockFactory>() where TInstructionBlockFactory : IInstructionBlockFactory { var operation = Operation.Build(); var operations = new List <Operation> { operation }; if (_halt) { operations.Add(new Operation(Rng.Word(operation.Address), OpCode.Halt, Operand.None, Operand.None, FlagTest.None, OpCodeMeta.None, 0, 0, 0)); } var decodedBlock = new DecodedBlock(_address, Rng.Int(10, 20), operations, new InstructionTimings(Rng.Int(10, 20), Rng.Int(10, 20)), _halt, false); using (var mock = AutoMock.GetLoose()) { mock.Mock <IRuntimeConfig>().Setup(x => x.DebugMode).Returns(true); mock.Mock <IPlatformConfig>().Setup(x => x.CpuMode).Returns(CpuMode.Z80); var context = new ExecutionContext(mock, operation, decodedBlock.Length, _registersFactory(), _accumulatorFactory()); foreach (var setup in _setups) { setup(context); } var block = mock.Create <TInstructionBlockFactory>().Build(decodedBlock); var assertions = RunAndYieldAssertions(block, decodedBlock, context); block.ShouldSatisfyAllConditions($"{typeof(TInstructionBlockFactory)}: {block.DebugInfo}", assertions.ToArray()); } }
private IEnumerable <Action> RunAndYieldAssertions(IInstructionBlock block, DecodedBlock decodedBlock, ExecutionContext context) { yield return(() => block.Address.ShouldBe(decodedBlock.Address, nameof(block.Address))); yield return(() => block.Length.ShouldBe(decodedBlock.Length, nameof(block.Length))); yield return(() => block.DebugInfo.ShouldNotBeNullOrEmpty(nameof(block.DebugInfo))); yield return(() => block.HaltCpu.ShouldBe(block.HaltCpu, nameof(block.HaltCpu))); yield return(() => block.HaltPeripherals.ShouldBe(block.HaltPeripherals, nameof(block.HaltPeripherals))); var timings = block.ExecuteInstructionBlock(context.MockRegisters.Object, context.Mmu.Object, context.Alu.Object, context.Io.Object); var expectedTimings = decodedBlock.Timings + _runtimeTimings; yield return(() => timings.ShouldBe(expectedTimings)); // TODO: also assert I & R on Z80 if (_halt) { yield return(() => context.MockRegisters.VerifySet(x => x.ProgramCounter = context.SyncedProgramCounter)); } foreach (var assertion in _assertions) { yield return(() => assertion(context)); } }
/// <summary> /// Builds a new <see cref="IInstructionBlock"/> from the specified decoded block. /// </summary> /// <param name="block">The decoded instruction block.</param> /// <returns></returns> public IInstructionBlock Build(DecodedBlock block) { var debugInfo = _debug ? JsonConvert.SerializeObject(new { block.Address, Operations = block.Operations.Select(x => x.ToString()) }) : null; InstructionTimings Run(IRegisters registers, IMmu mmu, IAlu alu, IPeripheralManager peripherals) => Interpret(registers, mmu, alu, peripherals, block); return(new InstructionBlock(block.Address, block.Length, Run, block.Timings, block.Halt, block.Stop, debugInfo)); }
/// <summary> /// Builds a new <see cref="IInstructionBlock"/> from the specified decoded block. /// </summary> /// <param name="block">The decoded instruction block.</param> /// <returns></returns> public IInstructionBlock Build(DecodedBlock block) { var lambda = BuildExpressionTree(block); var debugInfo = _debug ? JsonConvert.SerializeObject(new { block.Address, Operations = block.Operations.Select(x => x.ToString()), Execution = lambda.ToReadableString() }) : null; return(new InstructionBlock(block.Address, block.Length, lambda.Compile(), block.Timings, block.Halt, block.Stop, debugInfo)); }
/// <summary> /// Gets any expressions required to finalize the expression block. /// </summary> /// <param name="block">The decoded block.</param> /// <returns></returns> private IEnumerable <Expression> GetBlockFinalExpressions(DecodedBlock block) { if (_cpuMode == CpuMode.Z80) { // Add the block length to the 7 lsb of memory refresh register. var blockLengthExpression = Expression.Constant(block.Length, typeof(int)); // Update Z80 specific memory refresh register yield return(GetMemoryRefreshDeltaExpression(blockLengthExpression)); } if (_usesDynamicTimings) { // Return the dynamic timings. yield return(GetDynamicTimings); } else { // Return default timings. yield return(Expression.Constant(default(InstructionTimings))); } }
private InstructionTimings Interpret(IRegisters registers, IMmu mmu, IAlu alu, IPeripheralManager peripherals, InterpreterHelper helper, Operation operation, DecodedBlock block) { helper.Operation = operation; var timer = new InstructionTimingsBuilder(); switch (operation.OpCode) { case OpCode.NoOperation: break; case OpCode.Stop: case OpCode.Halt: SyncProgramCounter(registers, block); break; case OpCode.Load: if (operation.Operand1 == operation.Operand2) { break; } helper.Operand1 = helper.Operand2; if (operation.Operand2 == Operand.I || operation.Operand2 == Operand.R) { // LD A, R & LD A, I also reset H & N and copy IFF2 to P/V registers.AccumulatorAndFlagsRegisters.Flags.SetResultFlags(registers.AccumulatorAndFlagsRegisters.A); registers.AccumulatorAndFlagsRegisters.Flags.HalfCarry = false; registers.AccumulatorAndFlagsRegisters.Flags.Subtract = false; registers.AccumulatorAndFlagsRegisters.Flags.ParityOverflow = registers.InterruptFlipFlop2; } break; case OpCode.Load16: helper.WordOperand1 = helper.WordOperand2; break; case OpCode.Push: helper.PushStackPointer(); mmu.WriteWord(registers.StackPointer, helper.WordOperand1); break; case OpCode.Pop: helper.WordOperand1 = mmu.ReadWord(registers.StackPointer); helper.PopStackPointer(); break; case OpCode.Add: helper.Alu8BitOperation(alu.Add); break; case OpCode.AddWithCarry: helper.Alu8BitOperation(alu.AddWithCarry); break; case OpCode.Subtract: helper.Alu8BitOperation(alu.Subtract); break; case OpCode.SubtractWithCarry: helper.Alu8BitOperation(alu.SubtractWithCarry); break; case OpCode.And: helper.Alu8BitOperation(alu.And); break; case OpCode.Or: helper.Alu8BitOperation(alu.Or); break; case OpCode.Xor: helper.Alu8BitOperation(alu.Xor); break; case OpCode.Compare: alu.Compare(registers.AccumulatorAndFlagsRegisters.A, helper.Operand1); break; case OpCode.Increment: helper.Operand1 = alu.Increment(helper.Operand1); break; case OpCode.Decrement: helper.Operand1 = alu.Decrement(helper.Operand1); break; case OpCode.Add16: helper.Alu16BitOperation(alu.Add); break; case OpCode.AddWithCarry16: helper.Alu16BitOperation(alu.AddWithCarry); break; case OpCode.SubtractWithCarry16: helper.Alu16BitOperation(alu.SubtractWithCarry); break; case OpCode.Increment16: // INC ss (no flags changes so implemented directly) helper.WordOperand1 = (ushort)(helper.WordOperand1 + 1); break; case OpCode.Decrement16: // DEC ss (no flags changes so implemented directly) helper.WordOperand1 = (ushort)(helper.WordOperand1 - 1); break; case OpCode.Exchange: { var w = helper.WordOperand2; helper.WordOperand2 = helper.WordOperand1; helper.WordOperand1 = w; } break; case OpCode.ExchangeAccumulatorAndFlags: registers.SwitchToAlternativeAccumulatorAndFlagsRegisters(); break; case OpCode.ExchangeGeneralPurpose: registers.SwitchToAlternativeGeneralPurposeRegisters(); break; case OpCode.Jump: if (operation.FlagTest == FlagTest.None || helper.DoFlagTest()) { registers.ProgramCounter = helper.WordOperand1; } else { SyncProgramCounter(registers, block); } break; case OpCode.JumpRelative: if (operation.FlagTest == FlagTest.None || helper.DoFlagTest()) { helper.JumpToDisplacement(); if (operation.FlagTest != FlagTest.None) { timer.Add(1, 5); } } SyncProgramCounter(registers, block); break; case OpCode.DecrementJumpRelativeIfNonZero: registers.GeneralPurposeRegisters.B--; if (registers.GeneralPurposeRegisters.B != 0) { helper.JumpToDisplacement(); timer.Add(1, 5); } SyncProgramCounter(registers, block); break; case OpCode.Call: SyncProgramCounter(registers, block); if (operation.FlagTest == FlagTest.None || helper.DoFlagTest()) { helper.PushStackPointer(); mmu.WriteWord(registers.StackPointer, registers.ProgramCounter); registers.ProgramCounter = helper.WordOperand1; if (operation.FlagTest != FlagTest.None) { timer.Add(2, 7); } } break; case OpCode.Return: if (operation.FlagTest == FlagTest.None || helper.DoFlagTest()) { registers.ProgramCounter = mmu.ReadWord(registers.StackPointer); helper.PopStackPointer(); if (operation.FlagTest != FlagTest.None) { timer.Add(2, 6); } } else { SyncProgramCounter(registers, block); } break; case OpCode.ReturnFromInterrupt: registers.ProgramCounter = mmu.ReadWord(registers.StackPointer); helper.PopStackPointer(); registers.InterruptFlipFlop1 = true; break; case OpCode.ReturnFromNonmaskableInterrupt: registers.ProgramCounter = mmu.ReadWord(registers.StackPointer); helper.PopStackPointer(); registers.InterruptFlipFlop1 = registers.InterruptFlipFlop2; break; case OpCode.Reset: SyncProgramCounter(registers, block); helper.PushStackPointer(); mmu.WriteWord(registers.StackPointer, registers.ProgramCounter); registers.ProgramCounter = helper.WordOperand1; break; case OpCode.Input: helper.Operand1 = peripherals.ReadByteFromPort(helper.Operand2, operation.Operand2 == Operand.n ? registers.AccumulatorAndFlagsRegisters.A : registers.GeneralPurposeRegisters.B); break; case OpCode.Output: peripherals.WriteByteToPort(helper.Operand2, operation.Operand2 == Operand.n ? registers.AccumulatorAndFlagsRegisters.A : registers.GeneralPurposeRegisters.B, helper.Operand1); break; case OpCode.RotateLeftWithCarry: helper.Operand1 = alu.RotateLeftWithCarry(helper.Operand1); if (operation.OpCodeMeta == OpCodeMeta.UseAlternativeFlagAffection) { registers.AccumulatorAndFlagsRegisters.Flags.Zero = false; registers.AccumulatorAndFlagsRegisters.Flags.Sign = false; } break; case OpCode.RotateLeft: helper.Operand1 = alu.RotateLeft(helper.Operand1); if (operation.OpCodeMeta == OpCodeMeta.UseAlternativeFlagAffection) { registers.AccumulatorAndFlagsRegisters.Flags.Zero = false; registers.AccumulatorAndFlagsRegisters.Flags.Sign = false; } break; case OpCode.RotateRightWithCarry: helper.Operand1 = alu.RotateRightWithCarry(helper.Operand1); if (operation.OpCodeMeta == OpCodeMeta.UseAlternativeFlagAffection) { registers.AccumulatorAndFlagsRegisters.Flags.Zero = false; registers.AccumulatorAndFlagsRegisters.Flags.Sign = false; } break; case OpCode.RotateRight: helper.Operand1 = alu.RotateRight(helper.Operand1); if (operation.OpCodeMeta == OpCodeMeta.UseAlternativeFlagAffection) { registers.AccumulatorAndFlagsRegisters.Flags.Zero = false; registers.AccumulatorAndFlagsRegisters.Flags.Sign = false; } break; case OpCode.RotateLeftDigit: { var result = alu.RotateLeftDigit(registers.AccumulatorAndFlagsRegisters.A, mmu.ReadByte(registers.GeneralPurposeRegisters.HL)); registers.AccumulatorAndFlagsRegisters.A = result.Accumulator; mmu.WriteByte(registers.GeneralPurposeRegisters.HL, result.Result); } break; case OpCode.RotateRightDigit: { var result = alu.RotateRightDigit(registers.AccumulatorAndFlagsRegisters.A, mmu.ReadByte(registers.GeneralPurposeRegisters.HL)); registers.AccumulatorAndFlagsRegisters.A = result.Accumulator; mmu.WriteByte(registers.GeneralPurposeRegisters.HL, result.Result); } break; case OpCode.ShiftLeft: helper.Operand1 = alu.ShiftLeft(helper.Operand1); break; case OpCode.ShiftLeftSet: helper.Operand1 = alu.ShiftLeftSet(helper.Operand1); break; case OpCode.ShiftRight: helper.Operand1 = alu.ShiftRight(helper.Operand1); break; case OpCode.ShiftRightLogical: helper.Operand1 = alu.ShiftRightLogical(helper.Operand1); break; case OpCode.BitTest: alu.BitTest(helper.Operand1, operation.ByteLiteral); break; case OpCode.BitSet: helper.Operand1 = alu.BitSet(helper.Operand1, operation.ByteLiteral); break; case OpCode.BitReset: helper.Operand1 = alu.BitReset(helper.Operand1, operation.ByteLiteral); break; case OpCode.TransferIncrement: helper.BlockTransfer(); break; case OpCode.TransferIncrementRepeat: helper.BlockTransferRepeat(timer); break; case OpCode.TransferDecrement: helper.BlockTransfer(true); break; case OpCode.TransferDecrementRepeat: helper.BlockTransferRepeat(timer, true); break; case OpCode.SearchIncrement: helper.BlockSearch(); break; case OpCode.SearchIncrementRepeat: helper.BlockSearchRepeat(timer); break; case OpCode.SearchDecrement: helper.BlockSearch(true); break; case OpCode.SearchDecrementRepeat: helper.BlockSearchRepeat(timer, true); break; case OpCode.InputTransferIncrement: helper.InputTransfer(); break; case OpCode.InputTransferIncrementRepeat: helper.InputTransferRepeat(timer); break; case OpCode.InputTransferDecrement: helper.InputTransfer(true); break; case OpCode.InputTransferDecrementRepeat: helper.InputTransferRepeat(timer, true); break; case OpCode.OutputTransferIncrement: helper.OutputTransfer(); break; case OpCode.OutputTransferIncrementRepeat: helper.OutputTransferRepeat(timer); break; case OpCode.OutputTransferDecrement: helper.OutputTransfer(true); break; case OpCode.OutputTransferDecrementRepeat: helper.OutputTransferRepeat(timer, true); break; case OpCode.DecimalArithmeticAdjust: registers.AccumulatorAndFlagsRegisters.A = alu.DecimalAdjust(registers.AccumulatorAndFlagsRegisters.A, _cpuMode == CpuMode.Z80); break; case OpCode.NegateOnesComplement: registers.AccumulatorAndFlagsRegisters.A = (byte)~registers.AccumulatorAndFlagsRegisters.A; registers.AccumulatorAndFlagsRegisters.Flags.SetUndocumentedFlags(registers.AccumulatorAndFlagsRegisters.A); registers.AccumulatorAndFlagsRegisters.Flags.HalfCarry = true; registers.AccumulatorAndFlagsRegisters.Flags.Subtract = true; break; case OpCode.NegateTwosComplement: registers.AccumulatorAndFlagsRegisters.A = alu.Subtract(0, registers.AccumulatorAndFlagsRegisters.A); break; case OpCode.InvertCarryFlag: registers.AccumulatorAndFlagsRegisters.Flags.SetUndocumentedFlags(registers.AccumulatorAndFlagsRegisters.A); registers.AccumulatorAndFlagsRegisters.Flags.HalfCarry = _cpuMode != CpuMode.GameBoy && registers.AccumulatorAndFlagsRegisters.Flags.Carry; registers.AccumulatorAndFlagsRegisters.Flags.Subtract = false; registers.AccumulatorAndFlagsRegisters.Flags.Carry = !registers.AccumulatorAndFlagsRegisters.Flags.Carry; break; case OpCode.SetCarryFlag: registers.AccumulatorAndFlagsRegisters.Flags.SetUndocumentedFlags(registers.AccumulatorAndFlagsRegisters.A); registers.AccumulatorAndFlagsRegisters.Flags.HalfCarry = false; registers.AccumulatorAndFlagsRegisters.Flags.Subtract = false; registers.AccumulatorAndFlagsRegisters.Flags.Carry = true; break; case OpCode.DisableInterrupts: registers.InterruptFlipFlop1 = false; registers.InterruptFlipFlop2 = false; break; case OpCode.EnableInterrupts: registers.InterruptFlipFlop1 = true; registers.InterruptFlipFlop2 = true; break; case OpCode.InterruptMode0: registers.InterruptMode = InterruptMode.InterruptMode0; break; case OpCode.InterruptMode1: registers.InterruptMode = InterruptMode.InterruptMode1; break; case OpCode.InterruptMode2: registers.InterruptMode = InterruptMode.InterruptMode2; break; case OpCode.Swap: helper.Operand1 = alu.Swap(helper.Operand1); break; case OpCode.LoadIncrement: helper.Operand1 = helper.Operand2; registers.GeneralPurposeRegisters.HL++; break; case OpCode.LoadDecrement: helper.Operand1 = helper.Operand2; registers.GeneralPurposeRegisters.HL--; break; default: throw new ArgumentOutOfRangeException(); } if (operation.OpCodeMeta == OpCodeMeta.AutoCopy) { // Autocopy for DD/FD prefix helper.Operand2 = helper.Operand1; } return(timer.GetInstructionTimings()); }
private static void SyncProgramCounter(IRegisters registers, DecodedBlock block) => registers.ProgramCounter = (ushort)(registers.ProgramCounter + block.Length);
private InstructionTimings Interpret(IRegisters registers, IMmu mmu, IAlu alu, IPeripheralManager peripherals, DecodedBlock block) { var helper = new InterpreterHelper(registers, mmu, alu, peripherals); var result = block.Operations.Select(o => Interpret(registers, mmu, alu, peripherals, helper, o, block)).Aggregate((t0, t1) => t0 + t1); if (_cpuMode == CpuMode.Z80) { // Add the block length to the 7 lsb of memory refresh register. registers.R = (byte)((registers.R + block.Length) & 0x7f); } return(result); }
/// <summary> /// Recompiles the specified operation. /// </summary> /// <param name="operation">The operation.</param> /// <param name="block">The block.</param> /// <returns></returns> /// <exception cref="ArgumentOutOfRangeException"></exception> private IEnumerable <Expression> Recompile(Operation operation, DecodedBlock block) { switch (operation.OpCode) { case OpCode.NoOperation: break; case OpCode.Stop: case OpCode.Halt: yield return(SyncProgramCounter(block)); break; case OpCode.Load: if (operation.Operand1 == operation.Operand2) { break; } yield return(WriteOperand1(operation, ReadOperand2(operation))); if (operation.Operand2 == Operand.I || operation.Operand2 == Operand.R) { // LD A, R & LD A, I also reset H & N and copy IFF2 to P/V yield return(Expression.Call(Flags, SetResultFlags, A)); yield return(Expression.Assign(HalfCarry, Expression.Constant(false))); yield return(Expression.Assign(Subtract, Expression.Constant(false))); yield return(Expression.Assign(ParityOverflow, IFF2)); } break; case OpCode.Load16: yield return(WriteOperand1(operation, ReadOperand2(operation, true), true)); break; case OpCode.Push: yield return(PushSP); yield return(Expression.Call(Mmu, MmuWriteWord, SP, ReadOperand1(operation, true))); break; case OpCode.Pop: yield return(WriteOperand1(operation, Expression.Call(Mmu, MmuReadWord, SP), true)); yield return(PopSP); break; case OpCode.Add: yield return(Expression.Assign(A, Expression.Call(Alu, AluAdd, A, ReadOperand1(operation)))); break; case OpCode.AddWithCarry: yield return(Expression.Assign(A, Expression.Call(Alu, AluAddWithCarry, A, ReadOperand1(operation)))); break; case OpCode.Subtract: yield return(Expression.Assign(A, Expression.Call(Alu, AluSubtract, A, ReadOperand1(operation)))); break; case OpCode.SubtractWithCarry: yield return(Expression.Assign(A, Expression.Call(Alu, AluSubtractWithCarry, A, ReadOperand1(operation)))); break; case OpCode.And: yield return(Expression.Assign(A, Expression.Call(Alu, AluAnd, A, ReadOperand1(operation)))); break; case OpCode.Or: yield return(Expression.Assign(A, Expression.Call(Alu, AluOr, A, ReadOperand1(operation)))); break; case OpCode.Xor: yield return(Expression.Assign(A, Expression.Call(Alu, AluXor, A, ReadOperand1(operation)))); break; case OpCode.Compare: yield return(Expression.Call(Alu, AluCompare, A, ReadOperand1(operation))); break; case OpCode.Increment: yield return(WriteOperand1(operation, Expression.Call(Alu, AluIncrement, ReadOperand1(operation)))); break; case OpCode.Decrement: yield return(WriteOperand1(operation, Expression.Call(Alu, AluDecrement, ReadOperand1(operation)))); break; case OpCode.Add16: yield return(WriteOperand1(operation, Expression.Call(Alu, AluAdd16, ReadOperand1(operation, true), ReadOperand2(operation, true)), true)); break; case OpCode.AddWithCarry16: yield return(WriteOperand1(operation, Expression.Call(Alu, AluAdd16WithCarry, ReadOperand1(operation, true), ReadOperand2(operation, true)), true)); break; case OpCode.SubtractWithCarry16: yield return(WriteOperand1(operation, Expression.Call(Alu, AluSubtract16WithCarry, ReadOperand1(operation, true), ReadOperand2(operation, true)), true)); break; case OpCode.Increment16: // INC ss (no flags changes so implemented directly) yield return(Expression.PreIncrementAssign(ReadOperand1(operation, true))); break; case OpCode.Decrement16: // DEC ss (no flags changes so implemented directly) yield return(Expression.PreDecrementAssign(ReadOperand1(operation, true))); break; case OpCode.Exchange: _usesLocalWord = true; yield return(Expression.Assign(LocalWord, ReadOperand2(operation, true))); yield return(WriteOperand2(operation, ReadOperand1(operation, true), true)); yield return(WriteOperand1(operation, LocalWord, true)); break; case OpCode.ExchangeAccumulatorAndFlags: yield return(SwitchToAlternativeAccumulatorAndFlagsRegisters); break; case OpCode.ExchangeGeneralPurpose: yield return(SwitchToAlternativeGeneralPurposeRegisters); break; case OpCode.Jump: if (operation.FlagTest == FlagTest.None) { yield return(Expression.Assign(PC, ReadOperand1(operation, true))); } else { var trueBlock = Expression.Assign(PC, ReadOperand1(operation, true)); yield return(Expression.IfThenElse(GetFlagTestExpression(operation.FlagTest), trueBlock, SyncProgramCounter(block))); } break; case OpCode.JumpRelative: if (operation.FlagTest == FlagTest.None) { yield return(JumpToDisplacement(operation)); } else { _usesDynamicTimings = true; yield return(Expression.IfThen(GetFlagTestExpression(operation.FlagTest), Expression.Block(JumpToDisplacement(operation), AddDynamicTimings(1, 5)))); } // Relative jump so must also sync the PC. yield return(SyncProgramCounter(block)); break; case OpCode.DecrementJumpRelativeIfNonZero: _usesDynamicTimings = true; yield return(Expression.Assign(B, Expression.Convert(Expression.Decrement(Expression.Convert(B, typeof(int))), typeof(byte)))); yield return(Expression.IfThen(Expression.NotEqual(B, Expression.Constant((byte)0)), Expression.Block(JumpToDisplacement(operation), AddDynamicTimings(1, 5)))); // Relative jump so must also sync the PC. yield return(SyncProgramCounter(block)); break; case OpCode.Call: yield return(SyncProgramCounter(block)); if (operation.FlagTest == FlagTest.None) { yield return(PushSP); yield return(WritePCToStack); yield return(Expression.Assign(PC, ReadOperand1(operation))); } else { _usesDynamicTimings = true; var trueBlock = Expression.Block(PushSP, WritePCToStack, Expression.Assign(PC, ReadOperand1(operation)), AddDynamicTimings(2, 7)); yield return(Expression.IfThen(GetFlagTestExpression(operation.FlagTest), trueBlock)); } break; case OpCode.Return: if (operation.FlagTest == FlagTest.None) { yield return(ReadPCFromStack); yield return(PopSP); } else { _usesDynamicTimings = true; var trueBlock = Expression.Block(ReadPCFromStack, PopSP, AddDynamicTimings(2, 6)); yield return(Expression.IfThenElse(GetFlagTestExpression(operation.FlagTest), trueBlock, SyncProgramCounter(block))); } break; case OpCode.ReturnFromInterrupt: yield return(ReadPCFromStack); yield return(PopSP); yield return(Expression.Assign(IFF1, Expression.Constant(true))); break; case OpCode.ReturnFromNonmaskableInterrupt: yield return(ReadPCFromStack); yield return(PopSP); yield return(Expression.Assign(IFF1, IFF2)); break; case OpCode.Reset: yield return(SyncProgramCounter(block)); yield return(PushSP); yield return(WritePCToStack); yield return(Expression.Assign(PC, ReadOperand1(operation, true))); break; case OpCode.Input: yield return(WriteOperand1(operation, Expression.Call(IO, IoReadByte, ReadOperand2(operation), operation.Operand2 == Operand.n ? A : B))); break; case OpCode.Output: yield return(Expression.Call(IO, IoWriteByte, ReadOperand2(operation), operation.Operand2 == Operand.n ? A : B, ReadOperand1(operation))); break; case OpCode.RotateLeftWithCarry: yield return(WriteOperand1(operation, Expression.Call(Alu, AluRotateLeftWithCarry, ReadOperand1(operation)))); if (operation.OpCodeMeta == OpCodeMeta.UseAlternativeFlagAffection) { yield return(Expression.Assign(Zero, Expression.Constant(false))); yield return(Expression.Assign(Sign, Expression.Constant(false))); } break; case OpCode.RotateLeft: yield return(WriteOperand1(operation, Expression.Call(Alu, AluRotateLeft, ReadOperand1(operation)))); if (operation.OpCodeMeta == OpCodeMeta.UseAlternativeFlagAffection) { yield return(Expression.Assign(Zero, Expression.Constant(false))); yield return(Expression.Assign(Sign, Expression.Constant(false))); } break; case OpCode.RotateRightWithCarry: yield return(WriteOperand1(operation, Expression.Call(Alu, AluRotateRightWithCarry, ReadOperand1(operation)))); if (operation.OpCodeMeta == OpCodeMeta.UseAlternativeFlagAffection) { yield return(Expression.Assign(Zero, Expression.Constant(false))); yield return(Expression.Assign(Sign, Expression.Constant(false))); } break; case OpCode.RotateRight: yield return(WriteOperand1(operation, Expression.Call(Alu, AluRotateRight, ReadOperand1(operation)))); if (operation.OpCodeMeta == OpCodeMeta.UseAlternativeFlagAffection) { yield return(Expression.Assign(Zero, Expression.Constant(false))); yield return(Expression.Assign(Sign, Expression.Constant(false))); } break; case OpCode.RotateLeftDigit: _usesAccumulatorAndResult = true; yield return(Expression.Assign(AccumulatorAndResult, Expression.Call(Alu, AluRotateLeftDigit, A, ReadByteAtHL))); yield return(Expression.Assign(A, AccumulatorAndResult_Accumulator)); yield return(Expression.Call(Mmu, MmuWriteByte, HL, AccumulatorAndResult_Result)); break; case OpCode.RotateRightDigit: _usesAccumulatorAndResult = true; yield return(Expression.Assign(AccumulatorAndResult, Expression.Call(Alu, AluRotateRightDigit, A, ReadByteAtHL))); yield return(Expression.Assign(A, AccumulatorAndResult_Accumulator)); yield return(Expression.Call(Mmu, MmuWriteByte, HL, AccumulatorAndResult_Result)); break; case OpCode.ShiftLeft: yield return(WriteOperand1(operation, Expression.Call(Alu, AluShiftLeft, ReadOperand1(operation)))); break; case OpCode.ShiftLeftSet: yield return(WriteOperand1(operation, Expression.Call(Alu, AluShiftLeftSet, ReadOperand1(operation)))); break; case OpCode.ShiftRight: yield return(WriteOperand1(operation, Expression.Call(Alu, AluShiftRight, ReadOperand1(operation)))); break; case OpCode.ShiftRightLogical: yield return(WriteOperand1(operation, Expression.Call(Alu, AluShiftRightLogical, ReadOperand1(operation)))); break; case OpCode.BitTest: yield return(Expression.Call(Alu, AluBitTest, ReadOperand1(operation), Expression.Constant((int)operation.ByteLiteral))); break; case OpCode.BitSet: yield return(WriteOperand1(operation, Expression.Call(Alu, AluBitSet, ReadOperand1(operation), Expression.Constant((int)operation.ByteLiteral)))); break; case OpCode.BitReset: yield return(WriteOperand1(operation, Expression.Call(Alu, AluBitReset, ReadOperand1(operation), Expression.Constant((int)operation.ByteLiteral)))); break; case OpCode.TransferIncrement: yield return(Expression.Block(GetBlockTransferExpressions())); break; case OpCode.TransferIncrementRepeat: _usesDynamicTimings = true; yield return(Expression.Block(GetBlockTransferRepeatExpressions())); break; case OpCode.TransferDecrement: yield return(Expression.Block(GetBlockTransferExpressions(true))); break; case OpCode.TransferDecrementRepeat: _usesDynamicTimings = true; yield return(Expression.Block(GetBlockTransferRepeatExpressions(true))); break; case OpCode.SearchIncrement: yield return(Expression.Block(GetSearchExpressions())); break; case OpCode.SearchIncrementRepeat: _usesDynamicTimings = true; yield return(GetSearchRepeatExpression()); break; case OpCode.SearchDecrement: yield return(Expression.Block(GetSearchExpressions(true))); break; case OpCode.SearchDecrementRepeat: _usesDynamicTimings = true; yield return(GetSearchRepeatExpression(true)); break; case OpCode.InputTransferIncrement: yield return(Expression.Block(GetInputTransferExpressions())); break; case OpCode.InputTransferIncrementRepeat: _usesDynamicTimings = true; yield return(GetInputTransferRepeatExpression()); break; case OpCode.InputTransferDecrement: yield return(Expression.Block(GetInputTransferExpressions(true))); break; case OpCode.InputTransferDecrementRepeat: _usesDynamicTimings = true; yield return(GetInputTransferRepeatExpression(true)); break; case OpCode.OutputTransferIncrement: yield return(Expression.Block(GetOutTransferExpressions())); break; case OpCode.OutputTransferIncrementRepeat: _usesDynamicTimings = true; yield return(GetOutputTransferRepeatExpression()); break; case OpCode.OutputTransferDecrement: yield return(Expression.Block(GetOutTransferExpressions(true))); break; case OpCode.OutputTransferDecrementRepeat: _usesDynamicTimings = true; yield return(GetOutputTransferRepeatExpression(true)); break; case OpCode.DecimalArithmeticAdjust: yield return(Expression.Assign(A, Expression.Call(Alu, AluDecimalAdjust, A, Expression.Constant(_cpuMode == CpuMode.Z80)))); break; case OpCode.NegateOnesComplement: yield return(Expression.Assign(A, Expression.Not(A))); yield return(Expression.Call(Flags, SetUndocumentedFlags, A)); yield return(Expression.Assign(HalfCarry, Expression.Constant(true))); yield return(Expression.Assign(Subtract, Expression.Constant(true))); break; case OpCode.NegateTwosComplement: yield return(Expression.Assign(A, Expression.Call(Alu, AluSubtract, Expression.Constant((byte)0), A))); break; case OpCode.InvertCarryFlag: yield return(Expression.Call(Flags, SetUndocumentedFlags, A)); if (_cpuMode == CpuMode.GameBoy) { yield return(Expression.Assign(HalfCarry, Expression.Constant(false))); } else { yield return(Expression.Assign(HalfCarry, Carry)); } yield return(Expression.Assign(Subtract, Expression.Constant(false))); yield return(Expression.Assign(Carry, Expression.Not(Carry))); break; case OpCode.SetCarryFlag: yield return(Expression.Call(Flags, SetUndocumentedFlags, A)); yield return(Expression.Assign(HalfCarry, Expression.Constant(false))); yield return(Expression.Assign(Subtract, Expression.Constant(false))); yield return(Expression.Assign(Carry, Expression.Constant(true))); break; case OpCode.DisableInterrupts: yield return(Expression.Assign(IFF1, Expression.Constant(false))); yield return(Expression.Assign(IFF2, Expression.Constant(false))); break; case OpCode.EnableInterrupts: yield return(Expression.Assign(IFF1, Expression.Constant(true))); yield return(Expression.Assign(IFF2, Expression.Constant(true))); break; case OpCode.InterruptMode0: yield return(Expression.Assign(IM, Expression.Constant(InterruptMode.InterruptMode0))); break; case OpCode.InterruptMode1: yield return(Expression.Assign(IM, Expression.Constant(InterruptMode.InterruptMode1))); break; case OpCode.InterruptMode2: yield return(Expression.Assign(IM, Expression.Constant(InterruptMode.InterruptMode2))); break; case OpCode.Swap: yield return(WriteOperand1(operation, Expression.Call(Alu, AluSwap, ReadOperand1(operation)))); break; case OpCode.LoadIncrement: yield return(WriteOperand1(operation, ReadOperand2(operation))); yield return(Expression.PreIncrementAssign(HL)); // No support for indexes but GB doesnt have them break; case OpCode.LoadDecrement: yield return(WriteOperand1(operation, ReadOperand2(operation))); yield return(Expression.PreDecrementAssign(HL)); // No support for indexes but GB doesnt have them break; default: throw new ArgumentOutOfRangeException(); } if (operation.OpCodeMeta == OpCodeMeta.AutoCopy) { // Autocopy for DD/FD prefix yield return(WriteOperand2(operation, ReadOperand1(operation))); } }
/// <summary> /// Builds the expression tree. /// </summary> /// <param name="block">The decoded block.</param> /// <returns></returns> private Expression <Func <IRegisters, IMmu, IAlu, IPeripheralManager, InstructionTimings> > BuildExpressionTree(DecodedBlock block) { // Reset _usesDynamicTimings = _usesLocalWord = _usesAccumulatorAndResult = false; // Run this first so we know what init & final expressions to add. var blockExpressions = block.Operations.SelectMany(o => Recompile(o, block)).ToList(); var initExpressions = GetBlockInitExpressions().ToList(); var finalExpressions = GetBlockFinalExpressions(block).ToList(); var expressions = initExpressions.Concat(blockExpressions).Concat(finalExpressions).ToList(); var expressionBlock = Expression.Block(GetParameterExpressions(), expressions); var lambda = Expression.Lambda <Func <IRegisters, IMmu, IAlu, IPeripheralManager, InstructionTimings> >(expressionBlock, Registers, Mmu, Alu, IO); return(lambda); }
/// <summary> /// Gets an expression that synchronizes the program counter to the bytes read from teh prefetch queue. /// </summary> /// <value> /// An expression that synchronizes the program counter to the bytes read from teh prefetch queue. /// </value> private Expression SyncProgramCounter(DecodedBlock block) => Expression.Assign(PC, Expression.Convert(Expression.Add(Expression.Convert(PC, typeof(int)), Expression.Constant(block.Length)), typeof(ushort)));