public void InterruptDeviceResetWorksAsExpected() { // --- Arrange var vm = new SpectrumAdvancedTestMachine(); var idev = new InterruptDevice(TEST_INT_TACT); idev.OnAttachedToVm(vm); // --- Act idev.Reset(); // --- Assert idev.InterruptRaised.ShouldBeFalse(); idev.InterruptRevoked.ShouldBeFalse(); }
public void InterruptIsRaisedWithinAllowedRange() { for (var tact = TEST_INT_TACT; tact <= TEST_INT_TACT + InterruptDevice.LONGEST_OP_TACTS; tact++) { // --- Arrange var vm = new SpectrumAdvancedTestMachine(); var idev = new InterruptDevice(TEST_INT_TACT); idev.OnAttachedToVm(vm); // --- Act/Assert idev.CheckForInterrupt(tact); idev.InterruptRaised.ShouldBeTrue(); idev.InterruptRevoked.ShouldBeFalse(); idev.FrameCount.ShouldBe(1); (vm.Cpu.StateFlags & Z80StateFlags.Int).ShouldBe(Z80StateFlags.Int); } }
public void InterruptNotRaisedTooEarly() { // --- Arrange var vm = new SpectrumAdvancedTestMachine(); var idev = new InterruptDevice(TEST_INT_TACT); idev.OnAttachedToVm(vm); // --- Act/Assert for (var tact = 0; tact < TEST_INT_TACT; tact++) { idev.CheckForInterrupt(tact); idev.InterruptRaised.ShouldBeFalse(); idev.InterruptRevoked.ShouldBeFalse(); idev.FrameCount.ShouldBe(0); (vm.Cpu.StateFlags & Z80StateFlags.Int).ShouldBe(Z80StateFlags.None); } }
public void InterruptIsNotRereaised() { // --- Arrange var vm = new SpectrumAdvancedTestMachine(); var idev = new InterruptDevice(TEST_INT_TACT); idev.OnAttachedToVm(vm); idev.CheckForInterrupt(TEST_INT_TACT); // --- Act vm.Cpu.StateFlags &= Z80StateFlags.InvInt; idev.CheckForInterrupt(TEST_INT_TACT + 1); // --- Assert idev.InterruptRaised.ShouldBeTrue(); idev.InterruptRevoked.ShouldBeFalse(); (vm.Cpu.StateFlags & Z80StateFlags.Int).ShouldBe(Z80StateFlags.None); idev.FrameCount.ShouldBe(1); }
public void InterruptIsNotRaisedTooLate() { // --- Arrange var vm = new SpectrumAdvancedTestMachine(); var idev = new InterruptDevice(TEST_INT_TACT); idev.OnAttachedToVm(vm); // --- Act/Assert var lateTact = TEST_INT_TACT + InterruptDevice.LONGEST_OP_TACTS + 1; for (var tact = lateTact; tact < lateTact + 10; tact++) { idev.CheckForInterrupt(tact); idev.InterruptRaised.ShouldBeFalse(); idev.InterruptRevoked.ShouldBeTrue(); idev.FrameCount.ShouldBe(0); (vm.Cpu.StateFlags & Z80StateFlags.Int).ShouldBe(Z80StateFlags.None); } }
/// <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> /// Initializes a class instance using a collection of devices /// </summary> public SpectrumEngine(DeviceInfoCollection deviceData) { DeviceData = deviceData ?? throw new ArgumentNullException(nameof(deviceData)); // --- Check for Spectrum Next var nextInfo = GetDeviceInfo <INextFeatureSetDevice>(); NextDevice = nextInfo?.Device; // --- Prepare the memory device var memoryInfo = GetDeviceInfo <IMemoryDevice>(); MemoryDevice = memoryInfo?.Device ?? new Spectrum48MemoryDevice(); MemoryConfiguration = (IMemoryConfiguration)memoryInfo?.ConfigurationData; // --- Prepare the port device var portInfo = GetDeviceInfo <IPortDevice>(); PortDevice = portInfo?.Device ?? new Spectrum48PortDevice(); // --- Init the CPU var cpuConfig = GetDeviceConfiguration <IZ80Cpu, ICpuConfiguration>(); var mult = 1; if (cpuConfig != null) { BaseClockFrequency = cpuConfig.BaseClockFrequency; mult = cpuConfig.ClockMultiplier; if (mult < 1) { mult = 1; } else if (mult >= 2 && mult <= 3) { mult = 2; } else if (mult >= 4 && mult <= 7) { mult = 4; } else if (mult > 8) { mult = 8; } } ClockMultiplier = mult; Cpu = new Z80Cpu(MemoryDevice, PortDevice, cpuConfig?.SupportsNextOperations ?? false, NextDevice) { UseGateArrayContention = MemoryConfiguration.ContentionType == MemoryContentionType.GateArray }; // --- Init the ROM var romInfo = GetDeviceInfo <IRomDevice>(); RomProvider = (IRomProvider)romInfo.Provider; RomDevice = romInfo.Device ?? new SpectrumRomDevice(); RomConfiguration = (IRomConfiguration)romInfo.ConfigurationData; // --- Init the clock var clockInfo = GetDeviceInfo <IClockDevice>(); Clock = (IClockProvider)clockInfo.Provider ?? throw new InvalidOperationException("The virtual machine needs a clock provider!"); // --- Init the screen device var screenInfo = GetDeviceInfo <IScreenDevice>(); var pixelRenderer = (IScreenFrameProvider)screenInfo.Provider; ScreenConfiguration = new ScreenConfiguration((IScreenConfiguration)screenInfo.ConfigurationData); ScreenDevice = screenInfo.Device ?? new Spectrum48ScreenDevice(); // --- Init the beeper device var beeperInfo = GetDeviceInfo <IBeeperDevice>(); AudioConfiguration = (IAudioConfiguration)beeperInfo?.ConfigurationData; BeeperProvider = (IBeeperProvider)beeperInfo?.Provider; BeeperDevice = beeperInfo?.Device ?? new BeeperDevice(); // --- Init the keyboard device var keyboardInfo = GetDeviceInfo <IKeyboardDevice>(); KeyboardProvider = (IKeyboardProvider)keyboardInfo?.Provider; KeyboardDevice = keyboardInfo?.Device ?? new KeyboardDevice(); // --- Init the interrupt device InterruptDevice = new InterruptDevice(InterruptTact); // --- Init the tape device var tapeInfo = GetDeviceInfo <ITapeDevice>(); TapeProvider = (ITapeProvider)tapeInfo?.Provider; TapeDevice = tapeInfo?.Device ?? new TapeDevice(TapeProvider); // === Init optional devices // --- Init the sound device var soundInfo = GetDeviceInfo <ISoundDevice>(); SoundConfiguration = (IAudioConfiguration)soundInfo?.ConfigurationData; SoundProvider = (ISoundProvider)soundInfo?.Provider; SoundDevice = soundInfo == null ? null : soundInfo.Device ?? new SoundDevice(); // --- Init the DivIDE device var divIdeInfo = GetDeviceInfo <IDivIdeDevice>(); DivIdeDevice = divIdeInfo?.Device; // --- Carry out frame calculations ResetUlaTact(); _frameTacts = ScreenConfiguration.ScreenRenderingFrameTactCount; PhysicalFrameClockCount = Clock.GetFrequency() / (double)BaseClockFrequency * _frameTacts; FrameCount = 0; Overflow = 0; _frameCompleted = true; _lastBreakpoint = null; RunsInMaskableInterrupt = false; // --- Attach providers AttachProvider(RomProvider); AttachProvider(Clock); AttachProvider(pixelRenderer); AttachProvider(BeeperProvider); AttachProvider(KeyboardProvider); AttachProvider(TapeProvider); AttachProvider(DebugInfoProvider); // --- Attach optional providers if (SoundProvider != null) { AttachProvider(SoundProvider); } // --- Collect Spectrum devices _spectrumDevices.Add(RomDevice); _spectrumDevices.Add(MemoryDevice); _spectrumDevices.Add(PortDevice); _spectrumDevices.Add(ScreenDevice); _spectrumDevices.Add(BeeperDevice); _spectrumDevices.Add(KeyboardDevice); _spectrumDevices.Add(InterruptDevice); _spectrumDevices.Add(TapeDevice); // --- Collect optional devices if (SoundDevice != null) { _spectrumDevices.Add(SoundDevice); } if (NextDevice != null) { _spectrumDevices.Add(NextDevice); } if (DivIdeDevice != null) { _spectrumDevices.Add(DivIdeDevice); } // --- Now, prepare devices to find each other foreach (var device in _spectrumDevices) { device.OnAttachedToVm(this); } // --- Prepare bound devices _frameBoundDevices = _spectrumDevices .OfType <IFrameBoundDevice>() .ToList(); _cpuBoundDevices = _spectrumDevices .OfType <ICpuOperationBoundDevice>() .ToList(); DebugInfoProvider = new SpectrumDebugInfoProvider(); // --- Init the ROM InitRom(RomDevice, RomConfiguration); }
/// <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); }
/// <summary> /// Initializes a class instance using a collection of devices /// </summary> public SpectrumEngine(DeviceInfoCollection deviceData, string ulaIssue = "3") { DeviceData = deviceData ?? throw new ArgumentNullException(nameof(deviceData)); UlaIssue = ulaIssue == "3" ? "3" : "2"; // --- Prepare the memory device var memoryInfo = GetDeviceInfo <IMemoryDevice>(); MemoryDevice = memoryInfo?.Device ?? new Spectrum48MemoryDevice(); MemoryConfiguration = (IMemoryConfiguration)memoryInfo?.ConfigurationData; // --- Prepare the port device var portInfo = GetDeviceInfo <IPortDevice>(); PortDevice = portInfo?.Device ?? new Spectrum48PortDevice(); // --- Init the CPU var cpuConfig = GetDeviceConfiguration <IZ80Cpu, ICpuConfiguration>(); var mult = 1; if (cpuConfig != null) { BaseClockFrequency = cpuConfig.BaseClockFrequency; mult = cpuConfig.ClockMultiplier; if (mult < 1) { mult = 1; } else if (mult >= 2 && mult <= 3) { mult = 2; } else if (mult >= 4 && mult <= 7) { mult = 4; } else if (mult > 8) { mult = 8; } } ClockMultiplier = mult; Cpu = new Z80Cpu(MemoryDevice, PortDevice, cpuConfig?.SupportsNextOperations ?? false) { UseGateArrayContention = MemoryConfiguration.ContentionType == MemoryContentionType.GateArray }; // --- Init the ROM var romInfo = GetDeviceInfo <IRomDevice>(); RomProvider = (IRomProvider)romInfo.Provider; RomDevice = romInfo.Device ?? new SpectrumRomDevice(); RomConfiguration = (IRomConfiguration)romInfo.ConfigurationData; // --- Init the screen device var screenInfo = GetDeviceInfo <IScreenDevice>(); var pixelRenderer = (IScreenFrameProvider)screenInfo.Provider; ScreenConfiguration = new ScreenConfiguration((IScreenConfiguration)screenInfo.ConfigurationData); ScreenDevice = screenInfo.Device ?? new Spectrum48ScreenDevice(); ShadowScreenDevice = new Spectrum48ScreenDevice(); // --- Init the beeper device var beeperInfo = GetDeviceInfo <IBeeperDevice>(); AudioConfiguration = (IAudioConfiguration)beeperInfo?.ConfigurationData; BeeperProvider = (IBeeperProvider)beeperInfo?.Provider; BeeperDevice = beeperInfo?.Device ?? new BeeperDevice(); // --- Init the keyboard device var keyboardInfo = GetDeviceInfo <IKeyboardDevice>(); KeyboardProvider = (IKeyboardProvider)keyboardInfo?.Provider; KeyboardDevice = keyboardInfo?.Device ?? new KeyboardDevice(); // --- Init the Kempston device var kempstonInfo = GetDeviceInfo <IKempstonDevice>(); KempstonProvider = (IKempstonProvider)kempstonInfo?.Provider; KempstonDevice = kempstonInfo?.Device ?? new KempstonDevice(); // --- Init the interrupt device InterruptDevice = new InterruptDevice(InterruptTact); // --- Init the tape device var tapeSaveInfo = GetDeviceInfo <ITapeSaveDevice>(); TapeSaveProvider = (ITapeSaveProvider)tapeSaveInfo?.Provider; var tapeLoadInfo = GetDeviceInfo <ITapeLoadDevice>(); TapeLoadProvider = (ITapeLoadProvider)tapeLoadInfo?.Provider; var tapeDevice = new TapeDevice(TapeLoadProvider, TapeSaveProvider); TapeLoadDevice = tapeDevice; TapeSaveDevice = tapeDevice; // === Init optional devices // --- Init the sound device var soundInfo = GetDeviceInfo <ISoundDevice>(); SoundConfiguration = (IAudioConfiguration)soundInfo?.ConfigurationData; SoundProvider = (ISoundProvider)soundInfo?.Provider; SoundDevice = soundInfo == null ? null : soundInfo.Device ?? new SoundDevice(); // --- Init the floppy device var floppyInfo = GetDeviceInfo <IFloppyDevice>(); if (floppyInfo != null) { FloppyDevice = floppyInfo.Device; FloppyConfiguration = (IFloppyConfiguration)floppyInfo.ConfigurationData ?? new FloppyConfiguration(); } // --- Carry out frame calculations ResetUlaTact(); FrameTacts = ScreenConfiguration.ScreenRenderingFrameTactCount; FrameCount = 0; Overflow = 0; HasFrameCompleted = true; _lastBreakpoint = null; // --- Attach providers AttachProvider(RomProvider); AttachProvider(pixelRenderer); AttachProvider(BeeperProvider); AttachProvider(KeyboardProvider); AttachProvider(KempstonProvider); AttachProvider(TapeLoadProvider); AttachProvider(DebugInfoProvider); // --- Attach optional providers if (SoundProvider != null) { AttachProvider(SoundProvider); } // --- Collect Spectrum devices _spectrumDevices.Add(RomDevice); _spectrumDevices.Add(MemoryDevice); _spectrumDevices.Add(PortDevice); _spectrumDevices.Add(ScreenDevice); _spectrumDevices.Add(ShadowScreenDevice); _spectrumDevices.Add(BeeperDevice); _spectrumDevices.Add(KeyboardDevice); _spectrumDevices.Add(KempstonDevice); _spectrumDevices.Add(InterruptDevice); _spectrumDevices.Add(TapeLoadDevice); // --- Collect optional devices if (SoundDevice != null) { _spectrumDevices.Add(SoundDevice); } if (FloppyDevice != null) { _spectrumDevices.Add(FloppyDevice); } // --- Now, prepare devices to find each other foreach (var device in _spectrumDevices) { device.OnAttachedToVm(this); } // --- Prepare bound devices _frameBoundDevices = _spectrumDevices .OfType <IRenderFrameBoundDevice>() .ToList(); _cpuBoundDevices = _spectrumDevices .OfType <ICpuOperationBoundDevice>() .ToList(); DebugInfoProvider = new SpectrumDebugInfoProvider(); // --- Init the ROM InitRom(RomDevice, RomConfiguration); }
/// <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); }
/// <summary>Initializes a new instance of the <see cref="T:System.Object" /> class.</summary> public Spectrum48( IRomProvider romProvider, IClockProvider clockProvider, IKeyboardProvider keyboardProvider, IScreenFrameProvider pixelRenderer, IEarBitFrameProvider earBitFrameProvider = null, ITapeContentProvider loadContentProvider = null, ISaveToTapeProvider tapeSaveToTapeProvider = null) { // --- Init the CPU MemoryDevice = new Spectrum48MemoryDevice(); PortDevice = new Spectrum48PortDevice(); Cpu = new Z80Cpu(MemoryDevice, PortDevice); OsInitialized = false; // --- Setup the clock Clock = clockProvider; // --- Set up Spectrum devices BorderDevice = new BorderDevice(); ScreenDevice = new Spectrum48ScreenDevice(pixelRenderer); BeeperDevice = new BeeperDevice(earBitFrameProvider); KeyboardDevice = new KeyboardDevice(keyboardProvider); InterruptDevice = new InterruptDevice(InterruptTact); TapeDevice = new TapeDevice(loadContentProvider, tapeSaveToTapeProvider); // --- Carry out frame calculations ResetUlaTact(); _frameTacts = ScreenDevice.ScreenConfiguration.UlaFrameTactCount; PhysicalFrameClockCount = Clock.GetFrequency() / (double)ClockFrequeny * _frameTacts; FrameCount = 0; Overflow = 0; _frameCompleted = true; // --- Collect Spectrum devices _spectrumDevices.Add(MemoryDevice); _spectrumDevices.Add(PortDevice); _spectrumDevices.Add(BorderDevice); _spectrumDevices.Add(ScreenDevice); _spectrumDevices.Add(BeeperDevice); _spectrumDevices.Add(KeyboardDevice); _spectrumDevices.Add(InterruptDevice); _spectrumDevices.Add(TapeDevice); // --- Now, prepare devices to find each other foreach (var device in _spectrumDevices) { device.OnAttachedToVm(this); } // --- Prepare bound devices _frameBoundDevices = _spectrumDevices .OfType <IFrameBoundDevice>() .ToList(); _cpuBoundDevices = _spectrumDevices .OfType <ICpuOperationBoundDevice>() .ToList(); DebugInfoProvider = new SpectrumDebugInfoProvider(); // --- Init the ROM InitRom(romProvider, "ZXSpectrum48"); }