/// <summary> /// Initializes a new instance of the <see cref="DecodedBlock" /> class. /// </summary> /// <param name="operations">The operations.</param> /// <param name="timings">The timings.</param> /// <param name="halt">if set to <c>true</c> [ends on a HALT instruction].</param> /// <param name="stop">if set to <c>true</c> [ends on a STOP instruction].</param> /// <param name="length">The total block length including literals, displacements etc.</param> /// <param name="address">The address.</param> public DecodedBlock(ushort address, int length, ICollection <Operation> operations, InstructionTimings timings, bool halt, bool stop) { Address = address; Length = length; Operations = operations; Timings = timings; Halt = halt; Stop = stop; }
public DecodeFixture(int expectedMachineCycles, int expectedThrottlingStates, params object[] data) { _expectedTimings = new InstructionTimings(expectedMachineCycles, expectedThrottlingStates); _data = data; _address = Rng.Word(0x6000, 0xaaaa); Expected = new OperationFactory(_address); _cpuModes = Enum.GetValues(typeof(CpuMode)).Cast <CpuMode>().ToArray(); _throwOn = Array.Empty <CpuMode>(); _halt = true; }
/// <summary> /// Uses the configured instruction timings to sync real time to the CPU. /// </summary> /// <param name="timings">The timings.</param> public void SyncToTimings(InstructionTimings timings) { // Check if we need to call the sync event. _cyclesSinceLastEventSync += timings.MachineCycles; if (_cyclesSinceLastEventSync > CyclesPerSyncEvent) { TimingSync?.Invoke(new InstructionTimings(_cyclesSinceLastEventSync)); _timer.Block((long)(_ticksPerCycle * _cyclesSinceLastEventSync)); _cyclesSinceLastEventSync = _cyclesSinceLastEventSync - CyclesPerSyncEvent; } }
/// <summary> /// Creates a DMA copy operation. /// </summary> /// <param name="sourceAddress">The source address.</param> /// <param name="destinationAddress">The destination address.</param> /// <param name="length">The length.</param> /// <param name="timings">The cpu cycles required to execute this operation.</param> /// <param name="lockedAddressesRanges">The address ranges to lock during the copy operation.</param> public void Copy(ushort sourceAddress, ushort destinationAddress, int length, InstructionTimings timings, IEnumerable <AddressRange> lockedAddressesRanges) => Task.Run(() => _source.TrySetResult(new DmaCopyOperation(sourceAddress, destinationAddress, length, timings, lockedAddressesRanges)));
/// <summary> /// Initializes a new instance of the <see cref="DmaCopyOperation" /> class. /// </summary> /// <param name="sourceAddress">The source address.</param> /// <param name="destinationAddress">The destination address.</param> /// <param name="length">The length.</param> /// <param name="timings">The timings.</param> /// <param name="lockedAddressesRanges">The locked addresses ranges.</param> public DmaCopyOperation(ushort sourceAddress, ushort destinationAddress, int length, InstructionTimings timings, IEnumerable <AddressRange> lockedAddressesRanges) { SourceAddress = sourceAddress; DestinationAddress = destinationAddress; Length = length; Timings = timings; LockedAddressesRanges = lockedAddressesRanges; }
/// <summary> /// Initializes a new instance of the <see cref="InstructionBlock"/> class. /// </summary> /// <param name="address">The address.</param> /// <param name="length">The length.</param> /// <param name="action">The action.</param> /// <param name="staticTimings">The static timings.</param> /// <param name="halt">if set to <c>true</c> [halt].</param> /// <param name="stop">if set to <c>true</c> [stop].</param> /// <param name="debugInfo">The debug information.</param> public InstructionBlock(ushort address, int length, Func <IRegisters, IMmu, IAlu, IPeripheralManager, InstructionTimings> action, InstructionTimings staticTimings, bool halt, bool stop, string debugInfo = null) { _action = action; _staticTimings = staticTimings; Address = address; Length = length; HaltCpu = halt || stop; HaltPeripherals = stop; DebugInfo = debugInfo; }
private static IEnumerable <Action> Assertions(InstructionTimings observed, InstructionTimings expected) { yield return(() => observed.MachineCycles.ShouldBe(expected.MachineCycles, nameof(observed.MachineCycles))); yield return(() => observed.ThrottlingStates.ShouldBe(expected.ThrottlingStates, nameof(observed.ThrottlingStates))); }
public static void ShouldBe(this InstructionTimings observed, InstructionTimings expected) => observed.ShouldSatisfyAllConditions($"[exptected: {expected}, observed: {observed}", Assertions(observed, expected).ToArray());
private IEnumerable <Action> Test(CpuMode cpuMode) { using (var mock = AutoMock.GetLoose()) { var expected = Expected.Build(); var data = _halt ? _data.Concat(new object[] { PrimaryOpCode.HALT }).ToArray() : _data; var config = mock.Mock <IPlatformConfig>(); config.Setup(x => x.UndefinedInstructionBehaviour).Returns(UndefinedInstructionBehaviour.Throw); config.Setup(x => x.CpuMode).Returns(cpuMode); // Simulate a prefetch queue from the specified data. var queue = new Queue(data); var prefetch = mock.Mock <IPrefetchQueue>(); prefetch.Setup(x => x.NextByte()).Returns(() => { var value = queue.Dequeue(); switch (value) { case byte b: return(b); case sbyte b: return(unchecked ((byte)b)); case PrimaryOpCode op: return((byte)op); case PrefixCbOpCode op: return((byte)op); case PrefixEdOpCode op: return((byte)op); case GameBoyPrimaryOpCode op: return((byte)op); case GameBoyPrefixCbOpCode op: return((byte)op); default: throw new ArgumentOutOfRangeException(nameof(value), value, $"{value.GetType()} not supported"); } }); prefetch.Setup(x => x.NextWord()).Returns(() => (ushort)queue.Dequeue()); var expectedWordsRead = data.Count(x => x is ushort); var expectedBytesRead = data.Length - expectedWordsRead; // everything else is a byte. var totalBytesRead = expectedWordsRead * 2 + expectedBytesRead; prefetch.Setup(x => x.TotalBytesRead).Returns(() => totalBytesRead); // Run. var decoder = mock.Create <OpCodeDecoder>(); if (_throwOn.Contains(cpuMode)) { yield return(() => Should.Throw <InvalidOperationException>(() => decoder.DecodeNextBlock(_address))); yield break; } var block = decoder.DecodeNextBlock(_address); yield return(() => block.Address.ShouldBe(_address, nameof(block.Address))); yield return(() => block.Length.ShouldBe(totalBytesRead, nameof(block.Length))); yield return(() => block.Operations.FirstOrDefault().ShouldBe(expected)); // Timings, adjusted for the extra HALT. var timings = block.Timings; if (_halt) { // Remove tiumings generated by the HALT. timings -= new InstructionTimings(1, 4); } yield return(() => timings.ShouldBe(_expectedTimings)); // Make sure the correct mix of bytes and words were read. yield return(() => queue.Count.ShouldBe(0, () => $"Didn't read some data: {string.Join(", ", queue.ToArray())}")); yield return(() => prefetch.Verify(x => x.NextWord(), Times.Exactly(expectedWordsRead), $"Should have read {expectedWordsRead} words")); yield return(() => prefetch.Verify(x => x.NextByte(), Times.Exactly(expectedBytesRead), $"Should have read {expectedBytesRead} bytes")); if (_halt) { yield return(() => block.Operations.Count.ShouldBe(2, "Should have a single operation and a HALT")); } else { yield return(() => block.Operations.Count.ShouldBe(1, "Should have a single operation")); } } }
/// <summary> /// Creates a DMA copy operation. /// </summary> /// <param name="sourceAddress">The source address.</param> /// <param name="destinationAddress">The destination address.</param> /// <param name="length">The length.</param> /// <param name="timings">The cpu cycles required to execute this operation.</param> /// <param name="lockedAddressesRanges">The address ranges to lock during the copy operation.</param> public void Copy(ushort sourceAddress, ushort destinationAddress, int length, InstructionTimings timings, IEnumerable <AddressRange> lockedAddressesRanges) => _dmaOperations.Add(new DmaCopyOperation(sourceAddress, destinationAddress, length, timings, lockedAddressesRanges));
/// <summary> /// Returns a task that will complete in an amount of time according to the specified timings. /// </summary> /// <param name="timings">The timings.</param> /// <returns></returns> public async Task DelayAsync(InstructionTimings timings) { var blockFor = (long)_ticksPerCycle * timings.MachineCycles; await Task.Delay(new TimeSpan(blockFor)).ConfigureAwait(false); }
public ExecuteFixture RuntimeTiming(int machineCycles, int throttlingStates) { _runtimeTimings = new InstructionTimings(machineCycles, throttlingStates); return(this); }
/// <summary> /// Synchronizes the GPU thread and associated registers according to the specified instruction timings. /// </summary> /// <param name="instructionTimings">The instruction timings.</param> /// <exception cref="System.ArgumentOutOfRangeException"></exception> private void Sync(InstructionTimings instructionTimings) { if (!_gpuRegisters.LcdControlRegister.LcdOperation) { return; } _currentTimings += instructionTimings.MachineCycles; switch (_gpuRegisters.GpuMode) { case GpuMode.HorizontalBlank: if (_currentTimings >= HorizontalBlankCycles) { _gpuRegisters.IncrementScanline(); _gpuRegisters.GpuMode = _gpuRegisters.CurrentScanlineRegister.Scanline == ScanLines - 1 ? GpuMode.VerticalBlank : GpuMode.ReadingOam; _currentTimings -= HorizontalBlankCycles; } break; case GpuMode.VerticalBlank: if (_currentTimings >= VerticalBlankCycles) { if (_gpuRegisters.CurrentScanlineRegister.Scanline == VerticalBlankScanLines) { // Paint. var painted = _paintingTaskCompletionSource?.TrySetResult(true); if (!painted.GetValueOrDefault()) { _frameSkip++; } // Reset _gpuRegisters.CurrentScanlineRegister.Scanline = 0x00; _gpuRegisters.GpuMode = GpuMode.ReadingOam; _interruptFlagsRegister.UpdateInterrupts(InterruptFlag.VerticalBlank); _currentTimings -= VerticalBlankCycles; break; } _gpuRegisters.IncrementScanline(); _currentTimings -= VerticalBlankCycles; } break; case GpuMode.ReadingOam: if (_currentTimings >= ReadingOamCycles) { _gpuRegisters.GpuMode = GpuMode.ReadingVram; _currentTimings -= ReadingOamCycles; } break; case GpuMode.ReadingVram: if (_currentTimings >= ReadingVramCycles) { _gpuRegisters.GpuMode = GpuMode.HorizontalBlank; _currentTimings -= ReadingVramCycles; } break; default: throw new ArgumentOutOfRangeException(); } }