Example #1
0
        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());
            }
        }
Example #2
0
        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));
            }
        }
Example #3
0
        /// <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));
        }
Example #4
0
        /// <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));
        }
Example #5
0
        /// <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)));
            }
        }
Example #6
0
        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());
        }
Example #7
0
 private static void SyncProgramCounter(IRegisters registers, DecodedBlock block) => registers.ProgramCounter = (ushort)(registers.ProgramCounter + block.Length);
Example #8
0
        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);
        }
Example #9
0
        /// <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)));
            }
        }
Example #10
0
        /// <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);
        }
Example #11
0
 /// <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)));