예제 #1
0
        /// <summary>
        /// Starts the machine in a background thread.
        /// </summary>
        /// <param name="options">Options to start the machine with.</param>
        /// <remarks>
        /// Reports completion when the machine starts executing its cycles. The machine can
        /// go into Paused or Stopped state, if the execution options allow, for example,
        /// when it runs to a predefined breakpoint.
        /// </remarks>
        public void Start(ExecuteCycleOptions options)
        {
            if (VmState == VmState.Running)
            {
                return;
            }

            // --- Prepare the machine to run
            IsFirstStart = VmState == VmState.None || VmState == VmState.Stopped;
            SpectrumVm.DebugInfoProvider?.PrepareBreakpoints();
            if (IsFirstStart)
            {
                SpectrumVm.Reset();
                SpectrumVm.Cpu.StackDebugSupport.ClearStepOutStack();
                SpectrumVm.DebugInfoProvider?.ResetHitCounts();
            }

            // --- Dispose the previous cancellation token, and create a new one
            CancellationTokenSource?.Dispose();
            CancellationTokenSource = new CancellationTokenSource();

            // --- Set up the task that runs the machine
            CompletionTask = new Task(async() =>
            {
                Cancelled            = false;
                ExecutionCycleResult = false;
                try
                {
                    ExecutionCycleResult = SpectrumVm.ExecuteCycle(CancellationTokenSource.Token, options);
                }
                catch (TaskCanceledException)
                {
                    Cancelled = true;
                }
                catch (Exception ex)
                {
                    ExecutionCycleException = ex;
                }

                // --- Conclude the execution task
                await ExecuteOnMainThread(() =>
                {
                    MoveToState(VmState == VmState.Stopping ||
                                VmState == VmState.Stopped ||
                                ExecutionCycleException != null
                            ? VmState.Stopped
                            : VmState.Paused);

                    if (ExecutionCycleException != null)
                    {
                        VmStoppedWithException?.Invoke(this, EventArgs.Empty);
                    }
                });
            });

            MoveToState(VmState.Running);
            CompletionTask.Start();
        }
예제 #2
0
        /// <summary>
        /// Starts the machine in a background thread with the specified options.
        /// </summary>
        /// <remarks>
        /// Reports completion when the machine starts executing its cycles. The machine can
        /// go into Paused or Stopped state, if the execution options allow, for example,
        /// when it runs to a predefined breakpoint.
        /// </remarks>
        public void StartWithOptions(ExecuteCycleOptions options)
        {
            if (MachineState == VmState.Running)
            {
                return;
            }

            // --- Prepare the machine to run
            IsFirstStart = MachineState == VmState.None || MachineState == VmState.Stopped;
            SpectrumVm.DebugInfoProvider?.PrepareBreakpoints();
            MachineState = VmState.Starting;
            if (IsFirstStart)
            {
                SpectrumVm.Reset();
                SpectrumVm.Cpu.StackDebugSupport.Reset();
                SpectrumVm.DebugInfoProvider?.ResetHitCounts();
                CpuFrameCount    = 0;
                RenderFrameCount = 0;
            }

            // --- Dispose the previous cancellation token, and create a new one
            _cancellationTokenSource?.Dispose();
            _cancellationTokenSource = new CancellationTokenSource();

            // --- Set up the task that runs the machine
            MachineState = VmState.Running;
            try
            {
                _completionTask = StartAndRun(_cancellationTokenSource.Token, options);
                _completionTask.GetAwaiter().OnCompleted(async() =>
                {
                    await WaitForPause();
                });
            }
            catch (TaskCanceledException)
            {
                ExecutionCompletionReason = ExecutionCompletionReason.Cancelled;
            }
            catch (Exception ex)
            {
                ExceptionRaised?.Invoke(this, new VmExceptionArgs(ex));
            }
        }
예제 #3
0
        /// <summary>
        /// Checks whether the execution cycle should be stopped for debugging
        /// </summary>
        /// <param name="options">Execution options</param>
        /// <param name="executedInstructionCount">
        /// The count of instructions already executed in this cycle
        /// </param>
        /// <returns>True, if the execution should be stopped</returns>
        private bool IsDebugStop(ExecuteCycleOptions options, int executedInstructionCount)
        {
            // --- No debug provider, no stop
            if (DebugInfoProvider == null)
            {
                return(false);
            }

            // Check if the maskable interrupt routine breakpoints should be skipped
            if (RunsInMaskableInterrupt)
            {
                if (options.SkipInterruptRoutine)
                {
                    return(false);
                }
            }

            // --- In Step-Into mode we always stop when we're about to
            // --- execute the next instruction
            if (options.DebugStepMode == DebugStepMode.StepInto)
            {
                return(executedInstructionCount > 0);
            }

            // --- In Stop-At-Breakpoint mode we stop only if a predefined
            // --- breakpoint is reached
            if (options.DebugStepMode == DebugStepMode.StopAtBreakpoint &&
                DebugInfoProvider.ShouldBreakAtAddress(Cpu.Registers.PC))
            {
                if (executedInstructionCount > 0 ||
                    _lastBreakpoint == null ||
                    _lastBreakpoint != Cpu.Registers.PC)
                {
                    // --- If we are paused at a breakpoint, we do not want
                    // --- to pause again and again, unless we step through
                    _lastBreakpoint = Cpu.Registers.PC;
                    return(true);
                }
            }

            // --- We're in Step-Over mode
            if (options.DebugStepMode == DebugStepMode.StepOver)
            {
                if (DebugInfoProvider.ImminentBreakpoint != null)
                {
                    // --- We also stop, if an imminent breakpoint is reached, and also remove
                    // --- this breakpoint
                    if (DebugInfoProvider.ImminentBreakpoint == Cpu.Registers.PC)
                    {
                        DebugInfoProvider.ImminentBreakpoint = null;
                        return(true);
                    }
                }
                else
                {
                    var imminentJustCreated = false;

                    // --- We check for a CALL-like instruction
                    var length = Cpu.GetCallInstructionLength();
                    if (length > 0)
                    {
                        // --- Its a CALL-like instraction, create an imminent breakpoint
                        DebugInfoProvider.ImminentBreakpoint = (ushort)(Cpu.Registers.PC + length);
                        imminentJustCreated = true;
                    }

                    // --- We stop, we executed at least one instruction and if there's no imminent
                    // --- breakpoint or we've just created one
                    if (executedInstructionCount > 0 &&
                        (DebugInfoProvider.ImminentBreakpoint == null || imminentJustCreated))
                    {
                        return(true);
                    }
                }
            }

            // --- In any other case, we carry on
            return(false);
        }
예제 #4
0
        /// <summary>
        /// The main execution cycle of the Spectrum VM
        /// </summary>
        /// <param name="token">Cancellation token</param>
        /// <param name="options">Execution options</param>
        /// <return>True, if the cycle completed; false, if it has been cancelled</return>
        public bool ExecuteCycle(CancellationToken token, ExecuteCycleOptions options)
        {
            ExecuteCycleOptions          = options;
            ExecutionCompletionReason    = ExecutionCompletionReason.None;
            LastExecutionStartTact       = Cpu.Tacts;
            LastExecutionContentionValue = ContentionAccumulated;

            // --- We use these variables to calculate wait time at the end of the frame
            var cycleStartTime  = Clock.GetCounter();
            var cycleStartTact  = Cpu.Tacts;
            var cycleFrameCount = 0;

            // --- We use this variable to check whether to stop in Debug mode
            var executedInstructionCount = -1;

            // --- Loop #1: The main cycle that goes on until cancelled
            while (!token.IsCancellationRequested)
            {
                if (_frameCompleted)
                {
                    // --- This counter helps us to calculate where we are in the frame after
                    // --- each CPU operation cycle
                    LastFrameStartCpuTick = Cpu.Tacts - Overflow;

                    // --- Notify devices to start a new frame
                    OnNewFrame();
                    LastRenderedUlaTact = Overflow;
                    _frameCompleted     = false;
                }

                // --- Loop #2: The physical frame cycle that goes on while CPU and ULA
                // --- processes everything whithin a physical frame (0.019968 second)
                while (!_frameCompleted)
                {
                    // --- Check for leaving maskable interrupt mode
                    if (RunsInMaskableInterrupt)
                    {
                        if (Cpu.Registers.PC == 0x0052)
                        {
                            // --- We leave the maskable interrupt mode when the
                            // --- current instruction completes
                            RunsInMaskableInterrupt = false;
                        }
                    }

                    // --- Check debug mode when a CPU instruction has been entirelly executed
                    if (!Cpu.IsInOpExecution)
                    {
                        // --- Check for cancellation
                        if (token.IsCancellationRequested)
                        {
                            ExecutionCompletionReason = ExecutionCompletionReason.Cancelled;
                            return(false);
                        }

                        // --- The next instruction is about to be executed
                        executedInstructionCount++;

                        // --- Check for timeout
                        if (options.TimeoutTacts > 0 &&
                            cycleStartTact + options.TimeoutTacts < Cpu.Tacts)
                        {
                            ExecutionCompletionReason = ExecutionCompletionReason.Timeout;
                            return(false);
                        }

                        // --- Check for reaching the termination point
                        if (options.EmulationMode == EmulationMode.UntilExecutionPoint)
                        {
                            if (options.TerminationPoint < 0x4000)
                            {
                                // --- ROM & address must match
                                if (options.TerminationRom == MemoryDevice.GetSelectedRomIndex() &&
                                    options.TerminationPoint == Cpu.Registers.PC)
                                {
                                    // --- We reached the termination point within ROM
                                    ExecutionCompletionReason = ExecutionCompletionReason.TerminationPointReached;
                                    return(true);
                                }
                            }
                            else if (options.TerminationPoint == Cpu.Registers.PC)
                            {
                                // --- We reached the termination point within RAM
                                ExecutionCompletionReason = ExecutionCompletionReason.TerminationPointReached;
                                return(true);
                            }
                        }

                        // --- Check for entering maskable interrupt mode
                        if (Cpu.MaskableInterruptModeEntered)
                        {
                            RunsInMaskableInterrupt = true;
                        }

                        // --- Check for a debugging stop point
                        if (options.EmulationMode == EmulationMode.Debugger)
                        {
                            if (IsDebugStop(options, executedInstructionCount))
                            {
                                // --- At this point, the cycle should be stopped because of debugging reasons
                                // --- The screen should be refreshed
                                ScreenDevice.OnFrameCompleted();
                                ExecutionCompletionReason = ExecutionCompletionReason.BreakpointReached;
                                return(true);
                            }
                        }
                    }

                    // --- Check for interrupt signal generation
                    InterruptDevice.CheckForInterrupt(CurrentFrameTact);

                    // --- Run a single Z80 instruction
                    Cpu.ExecuteCpuCycle();
                    _lastBreakpoint = null;

                    // --- Run a rendering cycle according to the current CPU tact count
                    var lastTact = CurrentFrameTact;
                    ScreenDevice.RenderScreen(LastRenderedUlaTact + 1, lastTact);
                    LastRenderedUlaTact = lastTact;

                    // --- Exit if the emulation mode specifies so
                    if (options.EmulationMode == EmulationMode.UntilHalt &&
                        (Cpu.StateFlags & Z80StateFlags.Halted) != 0)
                    {
                        ExecutionCompletionReason = ExecutionCompletionReason.Halted;
                        return(true);
                    }

                    // --- Notify each CPU-bound device that the current operation has been completed
                    foreach (var device in _cpuBoundDevices)
                    {
                        device.OnCpuOperationCompleted();
                    }

                    // --- Decide whether this frame has been completed
                    _frameCompleted = !Cpu.IsInOpExecution && CurrentFrameTact >= _frameTacts;
                } // -- End Loop #2

                // --- A physical frame has just been completed. Take care about screen refresh
                cycleFrameCount++;
                FrameCount++;

                // --- Notify devices that the current frame completed
                OnFrameCompleted();

                // --- Exit if the emulation mode specifies so
                if (options.EmulationMode == EmulationMode.UntilFrameEnds)
                {
                    ExecutionCompletionReason = ExecutionCompletionReason.FrameCompleted;
                    return(true);
                }

                // --- Wait while the frame time ellapses
                if (!ExecuteCycleOptions.FastVmMode)
                {
                    var nextFrameCounter = cycleStartTime + cycleFrameCount * PhysicalFrameClockCount;
                    Clock.WaitUntil((long)nextFrameCounter, token);
                }

                // --- Start a new frame and carry on
                Overflow = CurrentFrameTact % _frameTacts;
            } // --- End Loop #1

            // --- The cycle has been interrupted by cancellation
            ExecutionCompletionReason = ExecutionCompletionReason.Cancelled;
            return(false);
        }
예제 #5
0
        /// <summary>
        /// Checks whether the execution cycle should be stopped for debugging
        /// </summary>
        /// <param name="options">Execution options</param>
        /// <param name="executedInstructionCount">
        /// The count of instructions already executed in this cycle
        /// </param>
        /// <param name="entryStepOutStackDepth">The initial depth of the Step-Out stack</param>
        /// <returns>True, if the execution should be stopped</returns>
        private bool IsDebugStop(ExecuteCycleOptions options, int executedInstructionCount, int entryStepOutStackDepth)
        {
            // --- No debug provider, no stop
            if (DebugInfoProvider == null)
            {
                return(false);
            }

            switch (options.DebugStepMode)
            {
            // --- In Step-Into mode we always stop when we're about to
            // --- execute the next instruction
            case DebugStepMode.StepInto:
                return(executedInstructionCount > 0);

            // --- In Stop-At-Breakpoint mode we stop only if a predefined
            // --- breakpoint is reached
            case DebugStepMode.StopAtBreakpoint
                when DebugInfoProvider.ShouldBreakAtAddress(Cpu.Registers.PC) && (executedInstructionCount > 0 ||
                                                                                  _lastBreakpoint == null ||
                                                                                  _lastBreakpoint != Cpu.Registers.PC):
                // --- If we are paused at a breakpoint, we do not want
                // --- to pause again and again, unless we step through
                _lastBreakpoint = Cpu.Registers.PC;

                return(true);

            // --- We're in Step-Over mode
            case DebugStepMode.StepOver
                when DebugInfoProvider.ImminentBreakpoint != null:
            {
                // --- We also stop, if an imminent breakpoint is reached, and also remove
                // --- this breakpoint
                if (DebugInfoProvider.ImminentBreakpoint == Cpu.Registers.PC)
                {
                    DebugInfoProvider.ImminentBreakpoint = null;
                    return(true);
                }
                break;
            }

            case DebugStepMode.StepOver:
            {
                var imminentJustCreated = false;

                // --- We check for a CALL-like instruction
                var length = Cpu.GetCallInstructionLength();
                if (length > 0)
                {
                    // --- Its a CALL-like instruction, create an imminent breakpoint
                    DebugInfoProvider.ImminentBreakpoint = (ushort)(Cpu.Registers.PC + length);
                    imminentJustCreated = true;
                }

                // --- We stop, we executed at least one instruction and if there's no imminent
                // --- breakpoint or we've just created one
                if (executedInstructionCount > 0 &&
                    (DebugInfoProvider.ImminentBreakpoint == null || imminentJustCreated))
                {
                    return(true);
                }
                break;
            }

            // --- We're in Step-Out mode and want to complete the current subroutine call
            case DebugStepMode.StepOut
                when Cpu.StackDebugSupport.RetExecuted &&
                executedInstructionCount > 0 &&
                entryStepOutStackDepth == Cpu.StackDebugSupport.StepOutStackDepth + 1:
            {
                if (Cpu.Registers.PC != Cpu.StackDebugSupport.StepOutAddress)
                {
                    Cpu.StackDebugSupport.ClearStepOutStack();
                }
                return(true);
            }
            }

            // --- In any other case, we carry on
            return(false);
        }
예제 #6
0
        /// <summary>
        /// The main execution cycle of the Spectrum VM
        /// </summary>
        /// <param name="token">Cancellation token</param>
        /// <param name="options">Execution options</param>
        /// <param name="completeOnCpuFrame">The cycle should complete on CPU frame completion</param>
        /// <return>True, if the cycle completed; false, if it has been cancelled</return>
        public bool ExecuteCycle(CancellationToken token, ExecuteCycleOptions options, bool completeOnCpuFrame = false)
        {
            ExecuteCycleOptions       = options;
            ExecutionCompletionReason = ExecutionCompletionReason.None;
            LastExecutionStartTact    = Cpu.Tacts;

            // --- We use this variables to check whether to stop in Debug mode
            var executedInstructionCount = -1;
            var entryStepOutDepth        = Cpu.StackDebugSupport.StepOutStackDepth;

            // --- Check if we're just start running the next frame
            if (HasFrameCompleted)
            {
                // --- This counter helps us to calculate where we are in the frame after
                // --- each CPU operation cycle
                LastFrameStartCpuTick = Cpu.Tacts - Overflow;

                // --- Notify devices to start a new frame
                OnNewFrame();
                LastRenderedUlaTact = Overflow;
                HasFrameCompleted   = false;
            }

            // --- The physical frame cycle that goes on while CPU and ULA
            // --- processes everything within a physical frame (0.019968 second)
            while (!HasFrameCompleted)
            {
                // --- Check debug mode when a CPU instruction has been entirely executed
                if (!Cpu.IsInOpExecution)
                {
                    // --- The next instruction is about to be executed
                    executedInstructionCount++;

                    // --- Check for cancellation
                    if (token.IsCancellationRequested)
                    {
                        ExecutionCompletionReason = ExecutionCompletionReason.Cancelled;
                        return(false);
                    }

                    // --- Check for CPU frame completion
                    if (completeOnCpuFrame && Cpu.Tacts > LastExecutionStartTact + CPU_FRAME)
                    {
                        ExecutionCompletionReason = ExecutionCompletionReason.CpuFrameCompleted;
                        return(true);
                    }

                    // --- Check for several termination modes
                    switch (options.EmulationMode)
                    {
                    // --- Check for reaching the termination point
                    case EmulationMode.UntilExecutionPoint when options.TerminationPoint < 0x4000:
                    {
                        // --- ROM & address must match
                        if (options.TerminationRom == MemoryDevice.GetSelectedRomIndex() &&
                            options.TerminationPoint == Cpu.Registers.PC)
                        {
                            // --- We reached the termination point within ROM
                            ExecutionCompletionReason = ExecutionCompletionReason.TerminationPointReached;
                            return(true);
                        }

                        break;
                    }

                    // --- Check if we reached the termination point within RAM
                    case EmulationMode.UntilExecutionPoint
                        when options.TerminationPoint == Cpu.Registers.PC:

                        ExecutionCompletionReason = ExecutionCompletionReason.TerminationPointReached;
                        return(true);

                    // --- Check for a debugging stop point
                    case EmulationMode.Debugger
                        when IsDebugStop(options, executedInstructionCount, entryStepOutDepth) &&
                        DebugConditionSatisfied():

                        // --- At this point, the cycle should be stopped because of debugging reasons
                        // --- The screen should be refreshed
                        ExecutionCompletionReason = ExecutionCompletionReason.BreakpointReached;

                        return(true);
                    }
                }

                // --- Check for interrupt signal generation
                InterruptDevice.CheckForInterrupt(CurrentFrameTact);

                // --- Run a single Z80 instruction
                Cpu.ExecuteCpuCycle();
                _lastBreakpoint = null;

                // --- Run a rendering cycle according to the current CPU tact count
                var lastTact = CurrentFrameTact;
                ScreenDevice.RenderScreen(LastRenderedUlaTact + 1, lastTact);
                LastRenderedUlaTact = lastTact;

                // --- Exit if the CPU is HALTed and the emulation mode specifies so
                if (options.EmulationMode == EmulationMode.UntilHalt &&
                    (Cpu.StateFlags & Z80StateFlags.Halted) != 0)
                {
                    ExecutionCompletionReason = ExecutionCompletionReason.Halted;
                    return(true);
                }

                // --- Notify each CPU-bound device that the current operation has been completed
                foreach (var device in _cpuBoundDevices)
                {
                    device.OnCpuOperationCompleted();
                }

                // --- Decide whether this frame has been completed
                HasFrameCompleted = !Cpu.IsInOpExecution && CurrentFrameTact >= FrameTacts;
            }

            // --- A physical frame has just been completed. Take care about screen refresh
            FrameCount++;
            Overflow = CurrentFrameTact % FrameTacts;

            // --- Notify devices that the current frame completed
            OnFrameCompleted();

            // --- We exit the cycle when the render frame has completed
            ExecutionCompletionReason = ExecutionCompletionReason.RenderFrameCompleted;
            return(true);
        }
예제 #7
0
        /// <summary>
        /// Starts the virtual machine with the provided options
        /// </summary>
        /// <param name="options">The execution cycle options to start with</param>
        public void StartVm(ExecuteCycleOptions options)
        {
            if (VmState == VmState.Running)
            {
                return;
            }

            IsFirstStart = VmState == VmState.None ||
                           VmState == VmState.BuildingMachine ||
                           VmState == VmState.Stopped;
            if (IsFirstStart)
            {
                EnsureMachine();
            }

            // --- We allow this event to execute something on the main thread
            // --- right before the execution cycle starts
            MoveToState(VmState.BeforeRun);

            // --- Dispose the previous cancellation token, and create a new one
            CancellationTokenSource?.Dispose();
            CancellationTokenSource = new CancellationTokenSource();

            // --- We use the completion source to sign that the VM's execution cycle is done
            _vmStarterCompletionSource = new TaskCompletionSource <bool>();
            _executionCompletionSource = new TaskCompletionSource <bool>();

            // --- Get the exception of the execution cycle
            Exception exDuringRun = null;

            // --- Allow the controller to save its current scheduler context
            SaveMainContext();
            SpectrumVm.DebugInfoProvider?.PrepareBreakpoints();

            Task.Factory.StartNew(ExecutionAction,
                                  CancellationTokenSource.Token,
                                  TaskCreationOptions.LongRunning,
                                  TaskScheduler.Current);

            // --- Execute the VM cycle
            async void ExecutionAction()
            {
                try
                {
                    SpectrumVm.ExecuteCycle(CancellationTokenSource.Token, options);
                }
                catch (TaskCanceledException)
                {
                }
                catch (Exception ex)
                {
                    exDuringRun = ex;
                }

                // --- Forget about the cancellation token
                CancellationTokenSource?.Dispose();
                CancellationTokenSource = null;

                // --- Go back to the main thread before setting up new state
                await ExecuteOnMainThread(() =>
                {
                    // --- Calculate next state
                    MoveToState(VmState == VmState.Stopping || exDuringRun != null
                        ? VmState.Stopped
                        : VmState.Paused);

                    // --- Conclude the execution task
                    if (exDuringRun == null)
                    {
                        _executionCompletionSource.SetResult(true);
                    }
                    else
                    {
                        _executionCompletionSource.SetException(exDuringRun);
                        OnVmStoppedWithException(exDuringRun);
                    }
                });
            }
        }
예제 #8
0
        /// <summary>
        /// Starts the virtual machine and runs it until the execution cycle is completed for
        /// a reason.
        /// </summary>
        /// <param name="cancellationToken">Cancellation token</param>
        /// <param name="options">Virtual machine execution options</param>
        /// <returns>The reason why the execution completed.</returns>
        private async Task <ExecutionCompletionReason> StartAndRun(CancellationToken cancellationToken,
                                                                   ExecuteCycleOptions options)
        {
            LastExecutionContentionValue = ContentionAccumulated;
            var lastRunStart         = _clockProvider.GetCounter();
            var lastRenderFrameStart = lastRunStart;
            var frameCount           = 0;
            var completed            = false;

            while (!completed)
            {
                // --- Execute a single CPU Frame
                var lastCpuFrameStart = _clockProvider.GetCounter();
                var cycleCompleted    = SpectrumVm.ExecuteCycle(cancellationToken, options, true);
                LastCpuFrameTicks = _clockProvider.GetCounter() - lastCpuFrameStart;
                if (!cycleCompleted)
                {
                    return(ExecutionCompletionReason.Cancelled);
                }

                // --- Check for emulated keys
                CpuFrameCount++;
                var hasEmulatedKey = SpectrumVm.KeyboardProvider?.EmulateKeyStroke();
                if (hasEmulatedKey != true)
                {
                    // --- Keyboard scan
                    var keyStatusArgs = new KeyStatusEventArgs();
                    KeyScanning?.Invoke(this, keyStatusArgs);
                    foreach (var keyStatus in keyStatusArgs.KeyStatusList)
                    {
                        SpectrumVm.KeyboardDevice.SetStatus(keyStatus);
                    }
                }

                // --- Do additional tasks when CPU frame completed
                var cancelArgs = new CancelEventArgs(false);
                CpuFrameCompleted?.Invoke(this, cancelArgs);
                if (cancelArgs.Cancel)
                {
                    return(ExecutionCompletionReason.Cancelled);
                }

                switch (SpectrumVm.ExecutionCompletionReason)
                {
                case ExecutionCompletionReason.TerminationPointReached:
                case ExecutionCompletionReason.BreakpointReached:
                case ExecutionCompletionReason.Halted:
                case ExecutionCompletionReason.Exception:
                    completed = true;
                    break;

                case ExecutionCompletionReason.RenderFrameCompleted:
                    var lastFrameEnd = _clockProvider.GetCounter();
                    LastRenderFrameTicks = lastFrameEnd - lastRenderFrameStart;
                    lastRenderFrameStart = lastFrameEnd;
                    completed            = options.EmulationMode == EmulationMode.UntilRenderFrameEnds;
                    frameCount++;
                    RenderFrameCount++;

                    // --- Do additional task when render frame completed
                    var renderFrameArgs = new RenderFrameEventArgs(SpectrumVm.ScreenDevice.GetPixelBuffer());
                    RenderFrameCompleted?.Invoke(this, renderFrameArgs);
                    if (renderFrameArgs.Cancel)
                    {
                        return(ExecutionCompletionReason.Cancelled);
                    }

                    // --- Wait for the next render frame, unless completed
                    if (!completed)
                    {
                        var waitInTicks = lastRunStart + frameCount * _physicalFrameClockCount
                                          - _clockProvider.GetCounter() - _physicalFrameClockCount * 0.2;
                        var waitInMs = 1000.0 * waitInTicks / _clockProvider.GetFrequency();
                        if (waitInMs > 0)
                        {
                            await Task.Delay((int)waitInMs, cancellationToken);

                            if (cancellationToken.IsCancellationRequested)
                            {
                                return(ExecutionCompletionReason.Cancelled);
                            }
                        }
                        else
                        {
                            await Task.Delay(1, cancellationToken);
                        }
                    }
                    break;
                }
            }

            // --- Done, pass back the reason of completing the run
            return(SpectrumVm.ExecutionCompletionReason);
        }
예제 #9
0
        /// <summary>
        /// The main execution cycle of the Spectrum VM
        /// </summary>
        /// <param name="token">Cancellation token</param>
        /// <param name="options">Execution options</param>
        /// <return>True, if the cycle completed; false, if it has been cancelled</return>
        public bool ExecuteCycle(CancellationToken token, ExecuteCycleOptions options)
        {
            ExecuteCycleOptions = options;

            // --- We use these variables to calculate wait time at the end of the frame
            var cycleStartTime  = Clock.GetCounter();
            var cycleFrameCount = 0;

            // --- We use this variable to check whether to stop in Debug mode
            var executedInstructionCount = -1;

            // --- Loop #1: The main cycle that goes on until cancelled
            while (!token.IsCancellationRequested)
            {
                if (_frameCompleted)
                {
                    // --- This counter helps us to calculate where we are in the frame after
                    // --- each CPU operation cycle
                    LastFrameStartCpuTick = Cpu.Tacts - Overflow;

                    // --- Notify devices to start a new frame
                    OnNewFrame();
                    LastRenderedUlaTact = Overflow;
                    _frameCompleted     = false;
                }

                // --- We use this timestamp for debug information calculation
                var currentFrameStartCounter = Clock.GetCounter();

                DebugInfoProvider.CpuTime             = 0;
                DebugInfoProvider.ScreenRenderingTime = 0;

                // --- Loop #2: The physical frame cycle that goes on while CPU and ULA
                // --- processes everything whithin a physical frame (0.019968 second)
                while (!_frameCompleted)
                {
                    // --- Check debug mode when a CPU instruction has been entirelly executed
                    if (!Cpu.IsInOpExecution)
                    {
                        // --- Check for cancellation
                        if (token.IsCancellationRequested)
                        {
                            return(false);
                        }

                        // --- The next instruction is about to be executed
                        executedInstructionCount++;

                        // --- Check for reaching the termination point
                        if (options.EmulationMode == EmulationMode.UntilExecutionPoint &&
                            options.TerminationPoint == Cpu.Registers.PC)
                        {
                            // --- We reached the termination point
                            return(true);
                        }

                        // --- Check for a debugging stop point
                        if (options.EmulationMode == EmulationMode.Debugger)
                        {
                            if (IsDebugStop(options.DebugStepMode, executedInstructionCount))
                            {
                                // --- At this point, the cycle should be stopped because of debugging reasons
                                // --- The screen should be refreshed
                                ScreenDevice.OnFrameCompleted();
                                return(true);
                            }
                        }
                    }

                    // --- Check for interrupt signal generation
                    InterruptDevice.CheckForInterrupt(CurrentFrameTact);

                    // --- Run a single Z80 instruction
                    var cpuStart = Clock.GetCounter();
                    Cpu.ExecuteCpuCycle();
                    DebugInfoProvider.CpuTime += Clock.GetCounter() - cpuStart;

                    // --- Run a rendering cycle according to the current CPU tact count
                    var lastTact    = CurrentFrameTact;
                    var renderStart = Clock.GetCounter();
                    ScreenDevice.RenderScreen(LastRenderedUlaTact + 1, lastTact);
                    DebugInfoProvider.ScreenRenderingTime += Clock.GetCounter() - renderStart;
                    LastRenderedUlaTact = lastTact;

                    // --- Exit if the emulation mode specifies so
                    if (options.EmulationMode == EmulationMode.UntilHalt &&
                        (Cpu.StateFlags & Z80StateFlags.Halted) != 0)
                    {
                        return(true);
                    }

                    // --- Notify each CPU-bound device that the current operation has been completed
                    foreach (var device in _cpuBoundDevices)
                    {
                        device.OnCpuOperationCompleted();
                    }

                    // --- Decide whether this frame has been completed
                    _frameCompleted = !Cpu.IsInOpExecution && CurrentFrameTact >= _frameTacts;
                } // -- End Loop #2

                // --- A physical frame has just been completed. Take care about screen refresh
                cycleFrameCount++;
                FrameCount++;

                // --- Calculate debug information
                DebugInfoProvider.FrameTime   = Clock.GetCounter() - currentFrameStartCounter;
                DebugInfoProvider.UtilityTime = DebugInfoProvider.FrameTime - DebugInfoProvider.CpuTime
                                                - DebugInfoProvider.ScreenRenderingTime;
                DebugInfoProvider.CpuTimeInMs             = DebugInfoProvider.CpuTime / (double)Clock.GetFrequency() * 1000;
                DebugInfoProvider.ScreenRenderingTimeInMs =
                    DebugInfoProvider.ScreenRenderingTime / (double)Clock.GetFrequency() * 1000;
                DebugInfoProvider.UtilityTimeInMs =
                    DebugInfoProvider.UtilityTime / (double)Clock.GetFrequency() * 1000;
                DebugInfoProvider.FrameTimeInMs = DebugInfoProvider.FrameTime / (double)Clock.GetFrequency() * 1000;

                // --- Notify devices that the current frame completed
                OnFrameCompleted();

                // --- Exit if the emulation mode specifies so
                if (options.EmulationMode == EmulationMode.UntilFrameEnds)
                {
                    return(true);
                }

                // --- Wait while the frame time ellapses
                var nextFrameCounter = cycleStartTime + cycleFrameCount * PhysicalFrameClockCount;
                Clock.WaitUntil((long)nextFrameCounter, token);

                // --- Start a new frame and carry on
                Overflow = CurrentFrameTact % _frameTacts;
            } // --- End Loop #1

            // --- The cycle has been inerrupted by cancellation
            return(false);
        }