/// <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); }
/// <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); }