From 0f981027c362a3994577544251d2976cdf25286b Mon Sep 17 00:00:00 2001 From: Ilkka Lehtoranta Date: Tue, 23 Jun 2026 12:26:18 +0300 Subject: [PATCH] 68020 timing improved --- Copper68k/Copper68k.csproj | 2 +- Copper68k/M68020Interpreter.cs | 18 +- Copper68k/M68kTimingEngine.cs | 226 +------ Copper68k/M68kTimingFormula.cs | 635 ++++++++++++++++++ CopperMod.Amiga.Tests/AmigaBusTimingTests.cs | 76 ++- .../M68kTimingFormulaTests.cs | 155 +++++ CopperMod.Amiga/AmigaBus.cs | 18 +- CopperMod.Amiga/AmigaDisk.cs | 299 ++++++++- CopperMod.Amiga/AmigaHardwareScheduler.cs | 109 ++- CopperMod.Amiga/CopperMod.Amiga.csproj | 2 +- CopperMod.Amiga/OcsDisplay.cs | 583 +++++++++++++++- CopperMod.Amiga/Paula.cs | 75 ++- CopperScreen.Benchmarks/Program.cs | 104 ++- CopperScreen.Tests/CopperScreenBootTests.cs | 8 +- .../CopperScreenRuntimeTests.cs | 10 +- CopperScreen/CopperScreenRuntime.cs | 2 +- 16 files changed, 2015 insertions(+), 307 deletions(-) create mode 100644 Copper68k/M68kTimingFormula.cs create mode 100644 CopperMod.Amiga.Tests/M68kTimingFormulaTests.cs diff --git a/Copper68k/Copper68k.csproj b/Copper68k/Copper68k.csproj index b29027d..b147aed 100644 --- a/Copper68k/Copper68k.csproj +++ b/Copper68k/Copper68k.csproj @@ -7,7 +7,7 @@ 1701;1702;NU1900 Copper68k Copper68k - CopperMod contributors + Ilkka Lehtoranta Reusable Motorola 68000-family CPU emulation core with cycle-aware interpreter backends and an opt-in MC68040 JIT. 1.1.0 Adds an opt-in internal MC68040 JIT backend through M68kCoreOptions, public JIT bus capability interfaces for host integration, and keeps the default MC68040 factory path on the interpreter for source-compatible package consumers. diff --git a/Copper68k/M68020Interpreter.cs b/Copper68k/M68020Interpreter.cs index bde8b63..eb40bec 100644 --- a/Copper68k/M68020Interpreter.cs +++ b/Copper68k/M68020Interpreter.cs @@ -4248,24 +4248,18 @@ internal void CompleteTiming(M68kInstructionTimingKey key) _timing.CompleteInstruction(_timing.GetPlan(key)); } + internal void CompleteTiming(M68kTimingDescriptor descriptor) + { + _timing.CompleteInstruction(descriptor); + } + private void CompleteMovemLongTiming( M68kInstructionTimingKey key, string name, int registerCount, bool registerToMemory) { - const int registerListImmediateAddressCycles = 4; - var nativeCycles = _profile.FixedInstructionNativeCycles ?? (_profile.Model == M68kAcceleratorModel.M68030 - ? registerToMemory - ? 4 + (2 * registerCount) + registerListImmediateAddressCycles - : 8 + (4 * registerCount) + registerListImmediateAddressCycles - : registerToMemory - ? 4 + (3 * registerCount) + registerListImmediateAddressCycles - : 8 + (4 * registerCount) + registerListImmediateAddressCycles); - var plan = _profile.Model == M68kAcceleratorModel.M68030 - ? M68kInstructionPlan.CreateHeadTail(key, name, nativeCycles, 2, 0) - : M68kInstructionPlan.CreateFlat(key, name, nativeCycles); - _timing.CompleteInstruction(plan); + CompleteTiming(M68kTimingDescriptor.MovemLong(key, name, registerCount, registerToMemory)); } private static int CountSetBits(ushort value) diff --git a/Copper68k/M68kTimingEngine.cs b/Copper68k/M68kTimingEngine.cs index 0ddf92c..5f26d29 100644 --- a/Copper68k/M68kTimingEngine.cs +++ b/Copper68k/M68kTimingEngine.cs @@ -407,11 +407,12 @@ public void Reset() } public M68kInstructionPlan GetPlan(M68kInstructionTimingKey key) + => GetPlan(M68kTimingDescriptor.FromLegacyKey(key)); + + public M68kInstructionPlan GetPlan(M68kTimingDescriptor descriptor) => _profile.FixedInstructionNativeCycles is int fixedCycles - ? M68kInstructionPlan.CreateFlat(key, "fixed JIT fallback", fixedCycles) - : _profile.Model is M68kAcceleratorModel.M68030 or M68kAcceleratorModel.M68040 - ? M68030TimingModel.GetPlan(key) - : M68020TimingModel.GetPlan(key); + ? M68kInstructionPlan.CreateFlat(descriptor.Key, "fixed JIT fallback", fixedCycles) + : M68kTimingFormula.GetPlan(descriptor, _profile.Model); public bool ProbeInstructionCache(uint address) => InstructionCache.Probe(address); @@ -530,6 +531,9 @@ public M68kExecutedInstructionTiming CompleteInstruction(M68kInstructionPlan pla return LastInstructionTiming; } + public M68kExecutedInstructionTiming CompleteInstruction(M68kTimingDescriptor descriptor) + => CompleteInstruction(GetPlan(descriptor)); + private void SynchronizeMachineToNative() { var machineCycles = _profile.NativeToMachineCycles(_state.NativeCycles); @@ -543,6 +547,9 @@ private void SynchronizeMachineToNative() internal static class M68020TimingModel { public static M68kInstructionPlan GetPlan(M68kInstructionTimingKey key) + => M68kTimingFormula.GetPlan(M68kTimingDescriptor.FromLegacyKey(key), M68kAcceleratorModel.M68020); + + internal static M68kInstructionPlan GetLegacyFlatPlan(M68kInstructionTimingKey key) { return key switch { @@ -758,216 +765,7 @@ private static M68kTimingBarrier ExceptionBarrier() internal static class M68030TimingModel { public static M68kInstructionPlan GetPlan(M68kInstructionTimingKey key) - { - return key switch - { - M68kInstructionTimingKey.Idle => M68kInstructionPlan.CreateHeadTail(key, "IDLE", 2, 0, 0, M68kTimingBarrier.SynchronizeBus), - M68kInstructionTimingKey.Nop => M68kInstructionPlan.CreateHeadTail(key, "NOP", 4, 0, 0, M68kTimingBarrier.SynchronizeBus), - M68kInstructionTimingKey.LineAException => M68kInstructionPlan.CreateHeadTail(key, "LINEA", 34, 0, 0, ExceptionBarrier()), - M68kInstructionTimingKey.LineFException => M68kInstructionPlan.CreateHeadTail(key, "LINEF", 34, 0, 0, ExceptionBarrier()), - M68kInstructionTimingKey.IllegalInstruction => M68kInstructionPlan.CreateHeadTail(key, "ILLEGAL", 20, 0, 0, ExceptionBarrier()), - M68kInstructionTimingKey.PrivilegeViolation => M68kInstructionPlan.CreateHeadTail(key, "PRIVILEGE", 20, 0, 0, ExceptionBarrier()), - M68kInstructionTimingKey.FormatError => M68kInstructionPlan.CreateHeadTail(key, "FORMAT", 20, 0, 0, ExceptionBarrier()), - M68kInstructionTimingKey.InterruptAcknowledge => M68kInstructionPlan.CreateHeadTail(key, "INTERRUPT", 44, 0, 0, ExceptionBarrier()), - M68kInstructionTimingKey.Movec => M68kInstructionPlan.CreateHeadTail(key, "MOVEC", 12, 0, 0, M68kTimingBarrier.CacheControl | M68kTimingBarrier.SynchronizeBus), - M68kInstructionTimingKey.ImmediateWordToConditionCodeRegister => M68kInstructionPlan.CreateHeadTail(key, "ORI/ANDI/EORI.W #,CCR", 8, 1, 1), - M68kInstructionTimingKey.ImmediateWordToStatusRegister => M68kInstructionPlan.CreateHeadTail(key, "ORI/ANDI/EORI.W #,SR", 20, 0, 0, M68kTimingBarrier.SynchronizeBus), - M68kInstructionTimingKey.Rte => M68kInstructionPlan.CreateHeadTail(key, "RTE", 20, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.SynchronizeBus), - M68kInstructionTimingKey.Rtd => M68kInstructionPlan.CreateHeadTail(key, "RTD", 16, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.Rts => M68kInstructionPlan.CreateHeadTail(key, "RTS", 7, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.LinkLong => M68kInstructionPlan.CreateHeadTail(key, "LINK.L", 16, 2, 2), - M68kInstructionTimingKey.ExtbLong => M68kInstructionPlan.CreateHeadTail(key, "EXTB.L", 4, 2, 2), - M68kInstructionTimingKey.ExtWordData => M68kInstructionPlan.CreateHeadTail(key, "EXT.W Dn", 2, 1, 1), - M68kInstructionTimingKey.TstWordData => M68kInstructionPlan.CreateHeadTail(key, "TST.W Dn", 2, 1, 1), - M68kInstructionTimingKey.Moveq => M68kInstructionPlan.CreateHeadTail(key, "MOVEQ #,Dn", 2, 1, 1), - M68kInstructionTimingKey.NegLongData => M68kInstructionPlan.CreateHeadTail(key, "NEG.L Dn", 2, 1, 1), - M68kInstructionTimingKey.NotByteData => M68kInstructionPlan.CreateHeadTail(key, "NOT.B Dn", 2, 1, 1), - M68kInstructionTimingKey.ClrDataLong => M68kInstructionPlan.CreateHeadTail(key, "CLR.L Dn", 2, 1, 1), - M68kInstructionTimingKey.ClrDataWord => M68kInstructionPlan.CreateHeadTail(key, "CLR.W Dn", 2, 1, 1), - M68kInstructionTimingKey.ClrLongAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "CLR.L (An)", 6, 1, 1), - M68kInstructionTimingKey.ClrLongAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "CLR.L (d16,An)", 8, 1, 1), - M68kInstructionTimingKey.ClrLongAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "CLR.L (xxx).L", 8, 1, 1), - M68kInstructionTimingKey.ClrByteAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "CLR.B (An)", 4, 1, 1), - M68kInstructionTimingKey.ClrByteAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "CLR.B (d16,An)", 6, 1, 1), - M68kInstructionTimingKey.ClrWordAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "CLR.W (d16,An)", 6, 1, 1), - M68kInstructionTimingKey.ClrLongPostIncrement => M68kInstructionPlan.CreateHeadTail(key, "CLR.L (An)+", 6, 1, 1), - M68kInstructionTimingKey.LeaAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "LEA (xxx).L,An", 6, 1, 1), - M68kInstructionTimingKey.LeaAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "LEA (d16,An),An", 4, 1, 1), - M68kInstructionTimingKey.MoveByteImmediateToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B #,(xxx).L", 8, 1, 1), - M68kInstructionTimingKey.MoveWordImmediateToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.W #,(xxx).L", 8, 1, 1), - M68kInstructionTimingKey.MoveLongImmediateToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L #,(xxx).L", 10, 1, 1), - M68kInstructionTimingKey.MoveLongImmediateToAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L #,(An)", 8, 1, 1), - M68kInstructionTimingKey.MoveLongImmediateToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L #,(d16,An)", 10, 1, 1), - M68kInstructionTimingKey.MoveLongImmediateToPostIncrement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L #,(An)+", 8, 1, 1), - M68kInstructionTimingKey.MoveLongImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L #,Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveLongImmediateToAddress => M68kInstructionPlan.CreateHeadTail(key, "MOVEA.L #,An", 6, 1, 1), - M68kInstructionTimingKey.MoveLongDataToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.MoveLongDataToAddress => M68kInstructionPlan.CreateHeadTail(key, "MOVEA.L Dn,An", 2, 1, 1), - M68kInstructionTimingKey.MoveLongDataToAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L Dn,(An)", 4, 1, 1), - M68kInstructionTimingKey.MoveLongDataToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L Dn,(d16,An)", 6, 1, 1), - M68kInstructionTimingKey.MoveLongAddressToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L An,Dn", 2, 1, 1), - M68kInstructionTimingKey.MoveLongAddressToAddress => M68kInstructionPlan.CreateHeadTail(key, "MOVEA.L An,An", 2, 1, 1), - M68kInstructionTimingKey.MoveLongAddressToAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L An,(An)", 4, 1, 1), - M68kInstructionTimingKey.MoveLongAddressToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L An,(d16,An)", 6, 1, 1), - M68kInstructionTimingKey.MoveLongAddressToPostIncrement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L An,(An)+", 4, 1, 1), - M68kInstructionTimingKey.MoveLongAddressIndirectToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L (An),Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveLongAddressIndirectToAddress => M68kInstructionPlan.CreateHeadTail(key, "MOVEA.L (An),An", 6, 1, 1), - M68kInstructionTimingKey.MoveLongPostIncrementToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L (An)+,Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveLongPostIncrementToAddress => M68kInstructionPlan.CreateHeadTail(key, "MOVEA.L (An)+,An", 6, 1, 1), - M68kInstructionTimingKey.MoveLongAddressDisplacementToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L (d16,An),Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveLongAddressDisplacementToAddress => M68kInstructionPlan.CreateHeadTail(key, "MOVEA.L (d16,An),An", 6, 1, 1), - M68kInstructionTimingKey.MoveLongAddressDisplacementToPostIncrement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L (d16,An),(An)+", 10, 1, 1), - M68kInstructionTimingKey.MoveLongBriefIndexedToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L (d8,An,Xn),Dn", 8, 1, 1), - M68kInstructionTimingKey.MoveLongBriefIndexedToAddress => M68kInstructionPlan.CreateHeadTail(key, "MOVEA.L (d8,An,Xn),An", 8, 1, 1), - M68kInstructionTimingKey.MoveLongAddressIndirectToAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L (An),(An)", 8, 1, 1), - M68kInstructionTimingKey.MoveLongAbsoluteLongToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L (xxx).L,Dn", 8, 1, 1), - M68kInstructionTimingKey.MoveLongAbsoluteWordToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L (xxx).W,(d16,An)", 8, 1, 1), - M68kInstructionTimingKey.MoveLongAbsoluteLongToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L (xxx).L,(d16,An)", 10, 1, 1), - M68kInstructionTimingKey.MoveLongDataToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L Dn,(xxx).L", 6, 1, 1), - M68kInstructionTimingKey.MoveLongAddressToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.L An,(xxx).L", 6, 1, 1), - M68kInstructionTimingKey.MoveByteDataToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.MoveByteImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B #,Dn", 4, 1, 1), - M68kInstructionTimingKey.MoveByteImmediateToAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B #,(An)", 6, 1, 1), - M68kInstructionTimingKey.MoveByteImmediateToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B #,(d16,An)", 6, 1, 1), - M68kInstructionTimingKey.MoveByteImmediateToBriefIndexed => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B #,(d8,An,Xn)", 8, 1, 1), - M68kInstructionTimingKey.MoveByteAddressIndirectToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B (An),Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveBytePostIncrementToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B (An)+,Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveByteAddressDisplacementToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B (d16,An),Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveByteAbsoluteLongToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B (xxx).L,Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveByteBriefIndexedToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B (d8,An,Xn),Dn", 8, 1, 1), - M68kInstructionTimingKey.MoveByteDataToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B Dn,(xxx).L", 6, 1, 1), - M68kInstructionTimingKey.MoveByteDataToAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B Dn,(An)", 4, 1, 1), - M68kInstructionTimingKey.MoveByteDataToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B Dn,(d16,An)", 6, 1, 1), - M68kInstructionTimingKey.MoveByteDataToBriefIndexed => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B Dn,(d8,An,Xn)", 8, 1, 1), - M68kInstructionTimingKey.MoveByteDataToPostIncrement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B Dn,(An)+", 4, 1, 1), - M68kInstructionTimingKey.MoveByteDataToPredecrement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B Dn,-(An)", 4, 1, 1), - M68kInstructionTimingKey.MoveByteBriefIndexedToPredecrement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B (d8,An,Xn),-(An)", 10, 1, 1), - M68kInstructionTimingKey.MoveBytePostIncrementToPostIncrement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B (An)+,(An)+", 8, 1, 1), - M68kInstructionTimingKey.MoveByteAddressIndirectToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B (An),(xxx).L", 8, 1, 1), - M68kInstructionTimingKey.MoveByteAbsoluteLongToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.B (xxx).L,(xxx).L", 10, 1, 1), - M68kInstructionTimingKey.MoveWordAbsoluteLongToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.W (xxx).L,Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveWordAddressDisplacementToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.W (d16,An),Dn", 6, 1, 1), - M68kInstructionTimingKey.MoveWordImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "MOVE.W #,Dn", 4, 1, 1), - M68kInstructionTimingKey.MoveWordImmediateToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.W #,(d16,An)", 6, 1, 1), - M68kInstructionTimingKey.MoveWordDataToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.W Dn,(d16,An)", 6, 1, 1), - M68kInstructionTimingKey.MoveWordDataToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.W Dn,(xxx).L", 6, 1, 1), - M68kInstructionTimingKey.MoveWordAbsoluteLongToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "MOVE.W (xxx).L,(xxx).L", 10, 1, 1), - M68kInstructionTimingKey.MoveWordAbsoluteLongToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "MOVE.W (xxx).L,(d16,An)", 8, 1, 1), - M68kInstructionTimingKey.ImmediateLogicalByteToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "ORI/ANDI.B #,(xxx).L", 10, 1, 1), - M68kInstructionTimingKey.AddiByteImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "ADDI.B #,Dn", 4, 1, 1), - M68kInstructionTimingKey.AddiByteImmediateToAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "ADDI.B #,(An)", 6, 1, 1), - M68kInstructionTimingKey.AddiByteImmediateToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "ADDI.B #,(d16,An)", 8, 1, 1), - M68kInstructionTimingKey.AddiWordImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "ADDI.W #,Dn", 4, 1, 1), - M68kInstructionTimingKey.AddiLongImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "ADDI.L #,Dn", 6, 1, 1), - M68kInstructionTimingKey.AddiLongImmediateToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "ADDI.L #,(xxx).L", 12, 1, 1), - M68kInstructionTimingKey.SubiByteImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "SUBI.B #,Dn", 4, 1, 1), - M68kInstructionTimingKey.SubiByteImmediateToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "SUBI.B #,(d16,An)", 8, 1, 1), - M68kInstructionTimingKey.SubiLongImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "SUBI.L #,Dn", 6, 1, 1), - M68kInstructionTimingKey.SubByteDataToData => M68kInstructionPlan.CreateHeadTail(key, "SUB.B Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.SubLongDataToData => M68kInstructionPlan.CreateHeadTail(key, "SUB.L Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.SubLongAddressToData => M68kInstructionPlan.CreateHeadTail(key, "SUB.L An,Dn", 2, 1, 1), - M68kInstructionTimingKey.SubLongAddressDisplacementToData => M68kInstructionPlan.CreateHeadTail(key, "SUB.L (d16,An),Dn", 7, 1, 1), - M68kInstructionTimingKey.AddWordDataToData => M68kInstructionPlan.CreateHeadTail(key, "ADD.W Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.AddLongDataToData => M68kInstructionPlan.CreateHeadTail(key, "ADD.L Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.AddxLongDataToData => M68kInstructionPlan.CreateHeadTail(key, "ADDX.L Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.AddLongPostIncrementToData => M68kInstructionPlan.CreateHeadTail(key, "ADD.L (An)+,Dn", 6, 1, 1), - M68kInstructionTimingKey.AddWordDataToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "ADD.W Dn,(d16,An)", 9, 1, 1), - M68kInstructionTimingKey.AddLongDataToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "ADD.L Dn,(d16,An)", 13, 1, 1), - M68kInstructionTimingKey.AddaLongImmediateToAddress => M68kInstructionPlan.CreateHeadTail(key, "ADDA.L #,An", 6, 1, 1), - M68kInstructionTimingKey.AddaLongDataToAddress => M68kInstructionPlan.CreateHeadTail(key, "ADDA.L Dn,An", 2, 1, 1), - M68kInstructionTimingKey.AddaLongAddressDisplacementToAddress => M68kInstructionPlan.CreateHeadTail(key, "ADDA.L (d16,An),An", 7, 1, 1), - M68kInstructionTimingKey.SubaLongImmediateToAddress => M68kInstructionPlan.CreateHeadTail(key, "SUBA.L #,An", 6, 1, 1), - M68kInstructionTimingKey.DivuWordImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "DIVU.W #,Dn", 46, 1, 1), - M68kInstructionTimingKey.DivsWordImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "DIVS.W #,Dn", 58, 1, 1), - M68kInstructionTimingKey.MuluLong => M68kInstructionPlan.CreateHeadTail(key, "MULU.L ,Dn", 44, 1, 1), - M68kInstructionTimingKey.MulsLong => M68kInstructionPlan.CreateHeadTail(key, "MULS.L ,Dn", 44, 1, 1), - M68kInstructionTimingKey.DivuLong => M68kInstructionPlan.CreateHeadTail(key, "DIVU.L ,Dr:Dq", 76, 1, 1), - M68kInstructionTimingKey.DivsLong => M68kInstructionPlan.CreateHeadTail(key, "DIVS.L ,Dr:Dq", 82, 1, 1), - M68kInstructionTimingKey.AbcdByteDataToData => M68kInstructionPlan.CreateHeadTail(key, "ABCD.B Dn,Dn", 6, 1, 1), - M68kInstructionTimingKey.AbcdBytePredecrementMemory => M68kInstructionPlan.CreateHeadTail(key, "ABCD.B -(An),-(An)", 18, 1, 1), - M68kInstructionTimingKey.SbcdByteDataToData => M68kInstructionPlan.CreateHeadTail(key, "SBCD.B Dn,Dn", 6, 1, 1), - M68kInstructionTimingKey.SbcdBytePredecrementMemory => M68kInstructionPlan.CreateHeadTail(key, "SBCD.B -(An),-(An)", 18, 1, 1), - M68kInstructionTimingKey.NbcdByteData => M68kInstructionPlan.CreateHeadTail(key, "NBCD.B Dn", 6, 1, 1), - M68kInstructionTimingKey.NbcdByteAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "NBCD.B (An)", 8, 1, 1), - M68kInstructionTimingKey.NbcdBytePostIncrement => M68kInstructionPlan.CreateHeadTail(key, "NBCD.B (An)+", 8, 1, 1), - M68kInstructionTimingKey.NbcdBytePredecrement => M68kInstructionPlan.CreateHeadTail(key, "NBCD.B -(An)", 8, 1, 1), - M68kInstructionTimingKey.NbcdByteAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "NBCD.B (d16,An)", 10, 1, 1), - M68kInstructionTimingKey.NbcdByteBriefIndexed => M68kInstructionPlan.CreateHeadTail(key, "NBCD.B (d8,An,Xn)", 12, 1, 1), - M68kInstructionTimingKey.NbcdByteAbsoluteWord => M68kInstructionPlan.CreateHeadTail(key, "NBCD.B (xxx).W", 10, 1, 1), - M68kInstructionTimingKey.NbcdByteAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "NBCD.B (xxx).L", 12, 1, 1), - M68kInstructionTimingKey.AndByteDataToData => M68kInstructionPlan.CreateHeadTail(key, "AND.B Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.AndWordImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "AND.W #,Dn", 4, 1, 1), - M68kInstructionTimingKey.AndLongImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "AND.L #,Dn", 6, 1, 1), - M68kInstructionTimingKey.MuluWordImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "MULU.W #,Dn", 46, 1, 1), - M68kInstructionTimingKey.EoriWordImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "EORI.W #,Dn", 4, 1, 1), - M68kInstructionTimingKey.EoriLongImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "EORI.L #,Dn", 6, 1, 1), - M68kInstructionTimingKey.CmpiLongImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "CMPI.L #,Dn", 6, 1, 1), - M68kInstructionTimingKey.CmpiLongImmediateToPostIncrement => M68kInstructionPlan.CreateHeadTail(key, "CMPI.L #,(An)+", 10, 1, 1), - M68kInstructionTimingKey.CmpiLongImmediateToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "CMPI.L #,(d16,An)", 9, 1, 1), - M68kInstructionTimingKey.CmpiLongImmediateToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "CMPI.L #,(xxx).L", 10, 1, 1), - M68kInstructionTimingKey.CmpiByteImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "CMPI.B #,Dn", 4, 1, 1), - M68kInstructionTimingKey.CmpiByteImmediateToAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "CMPI.B #,(An)", 6, 1, 1), - M68kInstructionTimingKey.CmpiByteImmediateToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "CMPI.B #,(d16,An)", 8, 1, 1), - M68kInstructionTimingKey.CmpiWordImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "CMPI.W #,Dn", 4, 1, 1), - M68kInstructionTimingKey.CmpiWordImmediateToAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "CMPI.W #,(d16,An)", 8, 1, 1), - M68kInstructionTimingKey.CmpiWordImmediateToAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "CMPI.W #,(xxx).L", 8, 1, 1), - M68kInstructionTimingKey.CmpaLongImmediateToAddress => M68kInstructionPlan.CreateHeadTail(key, "CMPA.L #,An", 6, 1, 1), - M68kInstructionTimingKey.CmpaLongDataToAddress => M68kInstructionPlan.CreateHeadTail(key, "CMPA.L Dn,An", 2, 1, 1), - M68kInstructionTimingKey.CmpaLongAddressToAddress => M68kInstructionPlan.CreateHeadTail(key, "CMPA.L An,An", 2, 1, 1), - M68kInstructionTimingKey.CmpaLongAddressIndirectToAddress => M68kInstructionPlan.CreateHeadTail(key, "CMPA.L (An),An", 6, 1, 1), - M68kInstructionTimingKey.CmpLongDataToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.L Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.CmpLongAddressToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.L An,Dn", 2, 1, 1), - M68kInstructionTimingKey.CmpLongAddressIndirectToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.L (An),Dn", 6, 1, 1), - M68kInstructionTimingKey.CmpLongPostIncrementToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.L (An)+,Dn", 6, 1, 1), - M68kInstructionTimingKey.CmpByteDataToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.B Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.CmpByteAddressIndirectToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.B (An),Dn", 6, 1, 1), - M68kInstructionTimingKey.CmpByteAddressDisplacementToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.B (d16,An),Dn", 6, 1, 1), - M68kInstructionTimingKey.CmpByteAbsoluteLongToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.B (xxx).L,Dn", 6, 1, 1), - M68kInstructionTimingKey.CmpWordDataToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.W Dn,Dn", 2, 1, 1), - M68kInstructionTimingKey.CmpWordAddressDisplacementToData => M68kInstructionPlan.CreateHeadTail(key, "CMP.W (d16,An),Dn", 6, 1, 1), - M68kInstructionTimingKey.LeaAbsoluteWord => M68kInstructionPlan.CreateHeadTail(key, "LEA (xxx).W,An", 4, 1, 1), - M68kInstructionTimingKey.SwapData => M68kInstructionPlan.CreateHeadTail(key, "SWAP Dn", 4, 1, 1), - M68kInstructionTimingKey.AsrLongImmediateData => M68kInstructionPlan.CreateHeadTail(key, "ASR.L #,Dn", 6, 1, 1), - M68kInstructionTimingKey.AsrWordImmediateData => M68kInstructionPlan.CreateHeadTail(key, "ASR.W #,Dn", 6, 1, 1), - M68kInstructionTimingKey.LsrLongImmediateData => M68kInstructionPlan.CreateHeadTail(key, "LSR.L #,Dn", 6, 1, 1), - M68kInstructionTimingKey.AslLongImmediateData => M68kInstructionPlan.CreateHeadTail(key, "ASL.L #,Dn", 8, 1, 1), - M68kInstructionTimingKey.AslWordImmediateData => M68kInstructionPlan.CreateHeadTail(key, "ASL.W #,Dn", 8, 1, 1), - M68kInstructionTimingKey.RorByteImmediateData => M68kInstructionPlan.CreateHeadTail(key, "ROR.B #,Dn", 8, 1, 1), - M68kInstructionTimingKey.RorWordImmediateData => M68kInstructionPlan.CreateHeadTail(key, "ROR.W #,Dn", 8, 1, 1), - M68kInstructionTimingKey.RolWordImmediateData => M68kInstructionPlan.CreateHeadTail(key, "ROL.W #,Dn", 8, 1, 1), - M68kInstructionTimingKey.JsrAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "JSR (xxx).L", 7, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.JmpAddressIndirect => M68kInstructionPlan.CreateHeadTail(key, "JMP (An)", 4, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.JmpAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "JMP (xxx).L", 6, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.OriByteImmediateToData => M68kInstructionPlan.CreateHeadTail(key, "ORI.B #,Dn", 4, 1, 1), - M68kInstructionTimingKey.BtstByteImmediateAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "BTST #,(xxx).L", 10, 1, 1), - M68kInstructionTimingKey.BchgByteImmediateAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "BCHG #,(xxx).L", 12, 1, 1), - M68kInstructionTimingKey.BclrByteImmediateAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "BCLR #,(xxx).L", 12, 1, 1), - M68kInstructionTimingKey.BsetByteImmediateAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "BSET #,(xxx).L", 12, 1, 1), - M68kInstructionTimingKey.BsetByteImmediateAddressDisplacement => M68kInstructionPlan.CreateHeadTail(key, "BSET #,(d16,An)", 10, 1, 1), - M68kInstructionTimingKey.BtstImmediateData => M68kInstructionPlan.CreateHeadTail(key, "BTST #,Dn", 4, 1, 1), - M68kInstructionTimingKey.BclrImmediateData => M68kInstructionPlan.CreateHeadTail(key, "BCLR #,Dn", 4, 1, 1), - M68kInstructionTimingKey.BsetImmediateData => M68kInstructionPlan.CreateHeadTail(key, "BSET #,Dn", 4, 1, 1), - M68kInstructionTimingKey.BtstDynamicData => M68kInstructionPlan.CreateHeadTail(key, "BTST Dn,Dn", 4, 1, 1), - M68kInstructionTimingKey.BclrDynamicData => M68kInstructionPlan.CreateHeadTail(key, "BCLR Dn,Dn", 4, 1, 1), - M68kInstructionTimingKey.SccAbsoluteLong => M68kInstructionPlan.CreateHeadTail(key, "Scc (xxx).L", 10, 1, 1), - M68kInstructionTimingKey.BranchByteTaken => M68kInstructionPlan.CreateHeadTail(key, "Bcc.B taken", 6, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.BranchByteNotTaken => M68kInstructionPlan.CreateHeadTail(key, "Bcc.B not taken", 4, 1, 1), - M68kInstructionTimingKey.BsrByte => M68kInstructionPlan.CreateHeadTail(key, "BSR.B", 7, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.BranchWordTaken => M68kInstructionPlan.CreateHeadTail(key, "Bcc.W taken", 6, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.BranchWordNotTaken => M68kInstructionPlan.CreateHeadTail(key, "Bcc.W not taken", 6, 1, 1), - M68kInstructionTimingKey.BsrWord => M68kInstructionPlan.CreateHeadTail(key, "BSR.W", 7, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.DbccConditionTrue => M68kInstructionPlan.CreateHeadTail(key, "DBcc condition true", 6, 1, 1), - M68kInstructionTimingKey.DbccBranchTaken => M68kInstructionPlan.CreateHeadTail(key, "DBcc branch taken", 10, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.DbccExpired => M68kInstructionPlan.CreateHeadTail(key, "DBcc expired", 12, 1, 1), - M68kInstructionTimingKey.BranchLongTaken => M68kInstructionPlan.CreateHeadTail(key, "Bcc.L taken", 10, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - M68kInstructionTimingKey.BranchLongNotTaken => M68kInstructionPlan.CreateHeadTail(key, "Bcc.L not taken", 8, 1, 1), - M68kInstructionTimingKey.BsrLong => M68kInstructionPlan.CreateHeadTail(key, "BSR.L", 18, 0, 0, M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch), - _ => throw new UnsupportedM68kTimingException(key, M68kAcceleratorModel.M68030) - }; - } - - private static M68kTimingBarrier ExceptionBarrier() - => M68kTimingBarrier.Exception | M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.SynchronizeBus; + => M68kTimingFormula.GetPlan(M68kTimingDescriptor.FromLegacyKey(key), M68kAcceleratorModel.M68030); } internal sealed class M68kAcceleratorBusBridge diff --git a/Copper68k/M68kTimingFormula.cs b/Copper68k/M68kTimingFormula.cs new file mode 100644 index 0000000..e671338 --- /dev/null +++ b/Copper68k/M68kTimingFormula.cs @@ -0,0 +1,635 @@ +using System; + +namespace Copper68k +{ + internal enum M68kTimingOperation + { + Legacy, + Idle, + Nop, + Exception, + Control, + StatusImmediate, + Return, + Link, + Extend, + Test, + Moveq, + Negate, + Not, + Clear, + LoadEffectiveAddress, + Move, + Arithmetic, + AddressArithmetic, + Multiply, + Divide, + Bcd, + Compare, + Swap, + ShiftRotate, + Jump, + Subroutine, + Movem, + Bit, + SetCondition, + Branch, + DecrementBranch + } + + internal enum M68kTimingOperand + { + None, + DataRegister, + AddressRegister, + AddressIndirect, + PostIncrement, + Predecrement, + AddressDisplacement, + BriefIndexed, + AbsoluteWord, + AbsoluteLong, + Immediate, + RegisterList, + ConditionCodeRegister, + StatusRegister, + ControlRegister + } + + internal enum M68kTimingOutcome + { + None, + Taken, + NotTaken, + ConditionTrue, + Expired + } + + internal readonly record struct M68kTimingDescriptor( + M68kInstructionTimingKey Key, + M68kTimingOperation Operation, + M68kOperandSize Size, + M68kTimingOperand Source, + M68kTimingOperand Destination, + M68kTimingOutcome Outcome, + int RegisterCount, + bool RegisterToMemory, + string Name, + int NativeCycles, + M68kTimingBarrier Barriers) + { + public static M68kTimingDescriptor FromLegacyKey(M68kInstructionTimingKey key) + => M68kTimingDescriptorCatalog.FromLegacyKey(key); + + public static M68kTimingDescriptor MovemLong( + M68kInstructionTimingKey key, + string name, + int registerCount, + bool registerToMemory) + { + if (registerCount < 0 || registerCount > 16) + { + throw new ArgumentOutOfRangeException(nameof(registerCount)); + } + + return new M68kTimingDescriptor( + key, + M68kTimingOperation.Movem, + M68kOperandSize.Long, + registerToMemory ? M68kTimingOperand.RegisterList : M68kTimingOperand.PostIncrement, + registerToMemory ? M68kTimingOperand.Predecrement : M68kTimingOperand.RegisterList, + M68kTimingOutcome.None, + registerCount, + registerToMemory, + name, + NativeCycles: 0, + M68kTimingBarrier.None); + } + } + + internal static class M68kTimingFormula + { + public static M68kInstructionPlan GetPlan(M68kTimingDescriptor descriptor, M68kAcceleratorModel model) + { + var nativeCycles = GetNativeCycles(descriptor, model); + var barriers = GetBarriers(descriptor, model); + if (model == M68kAcceleratorModel.M68020 || + descriptor.Operation == M68kTimingOperation.Movem && model == M68kAcceleratorModel.M68040) + { + return M68kInstructionPlan.CreateFlat(descriptor.Key, descriptor.Name, nativeCycles, barriers); + } + + var (headCycles, tailCycles) = GetHeadTailCycles(descriptor, barriers); + return M68kInstructionPlan.CreateHeadTail( + descriptor.Key, + descriptor.Name, + nativeCycles, + headCycles, + tailCycles, + barriers); + } + + private static int GetNativeCycles(M68kTimingDescriptor descriptor, M68kAcceleratorModel model) + { + if (descriptor.Operation != M68kTimingOperation.Movem) + { + return descriptor.NativeCycles; + } + + const int registerListImmediateAddressCycles = 4; + if (model == M68kAcceleratorModel.M68030) + { + return descriptor.RegisterToMemory + ? 4 + (2 * descriptor.RegisterCount) + registerListImmediateAddressCycles + : 8 + (4 * descriptor.RegisterCount) + registerListImmediateAddressCycles; + } + + return descriptor.RegisterToMemory + ? 4 + (3 * descriptor.RegisterCount) + registerListImmediateAddressCycles + : 8 + (4 * descriptor.RegisterCount) + registerListImmediateAddressCycles; + } + + private static M68kTimingBarrier GetBarriers(M68kTimingDescriptor descriptor, M68kAcceleratorModel model) + { + var barriers = descriptor.Barriers; + if (descriptor.Key == M68kInstructionTimingKey.Movec && + model is M68kAcceleratorModel.M68030 or M68kAcceleratorModel.M68040) + { + barriers |= M68kTimingBarrier.SynchronizeBus; + } + + return barriers; + } + + private static (int HeadCycles, int TailCycles) GetHeadTailCycles( + M68kTimingDescriptor descriptor, + M68kTimingBarrier barriers) + { + if ((barriers & (M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Exception | M68kTimingBarrier.Branch | M68kTimingBarrier.SynchronizeBus | M68kTimingBarrier.CacheControl)) != 0 || + descriptor.Operation is M68kTimingOperation.Idle or M68kTimingOperation.Nop) + { + return (0, 0); + } + + if (descriptor.Operation == M68kTimingOperation.Movem) + { + return (2, 0); + } + + if (descriptor.Key is M68kInstructionTimingKey.LinkLong or M68kInstructionTimingKey.ExtbLong) + { + return (2, 2); + } + + return (1, 1); + } + } + + internal static class M68kTimingDescriptorCatalog + { + private static readonly M68kTimingDescriptor[] LegacyDescriptors = new M68kTimingDescriptor[Enum.GetValues().Length]; + private static readonly bool[] LegacyDescriptorSupported = InitializeLegacyDescriptors(); + + public static M68kTimingDescriptor FromLegacyKey(M68kInstructionTimingKey key) + { + var index = (int)key; + if ((uint)index < (uint)LegacyDescriptors.Length && LegacyDescriptorSupported[index]) + { + return LegacyDescriptors[index]; + } + + throw new UnsupportedM68kTimingException(key, M68kAcceleratorModel.M68020); + } + + private static bool[] InitializeLegacyDescriptors() + { + var supported = new bool[LegacyDescriptors.Length]; + foreach (var key in Enum.GetValues()) + { + if (key is M68kInstructionTimingKey.MovemLongRegistersToPredecrement or + M68kInstructionTimingKey.MovemLongPostIncrementToRegisters) + { + continue; + } + + var index = (int)key; + LegacyDescriptors[index] = CreateLegacyDescriptor(key); + supported[index] = true; + } + + return supported; + } + + private static M68kTimingDescriptor CreateLegacyDescriptor(M68kInstructionTimingKey key) + { + var baseline = M68020TimingModel.GetLegacyFlatPlan(key); + return new M68kTimingDescriptor( + key, + GetOperation(key), + GetSize(key), + GetSourceOperand(key), + GetDestinationOperand(key), + GetOutcome(key), + RegisterCount: 0, + RegisterToMemory: false, + baseline.Name, + baseline.NativeCycles, + baseline.Barriers); + } + + private static M68kTimingOperation GetOperation(M68kInstructionTimingKey key) + { + return key switch + { + M68kInstructionTimingKey.Idle => M68kTimingOperation.Idle, + M68kInstructionTimingKey.Nop => M68kTimingOperation.Nop, + >= M68kInstructionTimingKey.LineAException and <= M68kInstructionTimingKey.InterruptAcknowledge => M68kTimingOperation.Exception, + M68kInstructionTimingKey.Movec => M68kTimingOperation.Control, + M68kInstructionTimingKey.ImmediateWordToConditionCodeRegister or + M68kInstructionTimingKey.ImmediateWordToStatusRegister => M68kTimingOperation.StatusImmediate, + M68kInstructionTimingKey.Rte or + M68kInstructionTimingKey.Rtd or + M68kInstructionTimingKey.Rts => M68kTimingOperation.Return, + M68kInstructionTimingKey.LinkLong => M68kTimingOperation.Link, + M68kInstructionTimingKey.ExtbLong or + M68kInstructionTimingKey.ExtWordData => M68kTimingOperation.Extend, + M68kInstructionTimingKey.TstWordData => M68kTimingOperation.Test, + M68kInstructionTimingKey.Moveq => M68kTimingOperation.Moveq, + M68kInstructionTimingKey.NegLongData => M68kTimingOperation.Negate, + M68kInstructionTimingKey.NotByteData => M68kTimingOperation.Not, + >= M68kInstructionTimingKey.ClrDataLong and <= M68kInstructionTimingKey.ClrLongPostIncrement => M68kTimingOperation.Clear, + M68kInstructionTimingKey.LeaAbsoluteLong or + M68kInstructionTimingKey.LeaAddressDisplacement or + M68kInstructionTimingKey.LeaAbsoluteWord => M68kTimingOperation.LoadEffectiveAddress, + >= M68kInstructionTimingKey.MoveByteImmediateToAbsoluteLong and <= M68kInstructionTimingKey.MoveWordAbsoluteLongToAddressDisplacement => M68kTimingOperation.Move, + >= M68kInstructionTimingKey.AddiByteImmediateToData and <= M68kInstructionTimingKey.SubaLongImmediateToAddress => GetArithmeticOperation(key), + >= M68kInstructionTimingKey.DivuWordImmediateToData and <= M68kInstructionTimingKey.DivsWordImmediateToData => M68kTimingOperation.Divide, + M68kInstructionTimingKey.MuluLong or + M68kInstructionTimingKey.MulsLong or + M68kInstructionTimingKey.MuluWordImmediateToData => M68kTimingOperation.Multiply, + M68kInstructionTimingKey.DivuLong or + M68kInstructionTimingKey.DivsLong => M68kTimingOperation.Divide, + >= M68kInstructionTimingKey.AbcdByteDataToData and <= M68kInstructionTimingKey.SbcdBytePredecrementMemory => M68kTimingOperation.Bcd, + >= M68kInstructionTimingKey.NbcdByteData and <= M68kInstructionTimingKey.NbcdByteAbsoluteLong => M68kTimingOperation.Bcd, + M68kInstructionTimingKey.AndByteDataToData or + M68kInstructionTimingKey.AndWordImmediateToData or + M68kInstructionTimingKey.AndLongImmediateToData or + M68kInstructionTimingKey.EoriWordImmediateToData or + M68kInstructionTimingKey.EoriLongImmediateToData or + M68kInstructionTimingKey.OriByteImmediateToData or + M68kInstructionTimingKey.ImmediateLogicalByteToAbsoluteLong => M68kTimingOperation.Arithmetic, + >= M68kInstructionTimingKey.CmpiLongImmediateToData and <= M68kInstructionTimingKey.CmpWordAddressDisplacementToData => M68kTimingOperation.Compare, + M68kInstructionTimingKey.SwapData => M68kTimingOperation.Swap, + >= M68kInstructionTimingKey.AsrLongImmediateData and <= M68kInstructionTimingKey.RolWordImmediateData => M68kTimingOperation.ShiftRotate, + M68kInstructionTimingKey.JmpAddressIndirect or + M68kInstructionTimingKey.JmpAbsoluteLong => M68kTimingOperation.Jump, + M68kInstructionTimingKey.JsrAbsoluteLong => M68kTimingOperation.Subroutine, + >= M68kInstructionTimingKey.BtstByteImmediateAbsoluteLong and <= M68kInstructionTimingKey.BclrDynamicData => M68kTimingOperation.Bit, + M68kInstructionTimingKey.SccAbsoluteLong => M68kTimingOperation.SetCondition, + M68kInstructionTimingKey.BranchByteTaken or + M68kInstructionTimingKey.BranchByteNotTaken or + M68kInstructionTimingKey.BranchWordTaken or + M68kInstructionTimingKey.BranchWordNotTaken or + M68kInstructionTimingKey.BranchLongTaken or + M68kInstructionTimingKey.BranchLongNotTaken => M68kTimingOperation.Branch, + M68kInstructionTimingKey.BsrByte or + M68kInstructionTimingKey.BsrWord or + M68kInstructionTimingKey.BsrLong => M68kTimingOperation.Subroutine, + >= M68kInstructionTimingKey.DbccConditionTrue and <= M68kInstructionTimingKey.DbccExpired => M68kTimingOperation.DecrementBranch, + _ => M68kTimingOperation.Legacy + }; + } + + private static M68kTimingOperation GetArithmeticOperation(M68kInstructionTimingKey key) + { + return key switch + { + M68kInstructionTimingKey.AddaLongImmediateToAddress or + M68kInstructionTimingKey.AddaLongDataToAddress or + M68kInstructionTimingKey.AddaLongAddressDisplacementToAddress or + M68kInstructionTimingKey.SubaLongImmediateToAddress => M68kTimingOperation.AddressArithmetic, + _ => M68kTimingOperation.Arithmetic + }; + } + + private static M68kOperandSize GetSize(M68kInstructionTimingKey key) + { + return key switch + { + M68kInstructionTimingKey.NotByteData or + M68kInstructionTimingKey.ClrByteAddressIndirect or + M68kInstructionTimingKey.ClrByteAddressDisplacement or + M68kInstructionTimingKey.MoveByteImmediateToAbsoluteLong or + >= M68kInstructionTimingKey.MoveByteDataToData and <= M68kInstructionTimingKey.MoveByteAbsoluteLongToAbsoluteLong or + M68kInstructionTimingKey.ImmediateLogicalByteToAbsoluteLong or + M68kInstructionTimingKey.AddiByteImmediateToData or + M68kInstructionTimingKey.AddiByteImmediateToAddressIndirect or + M68kInstructionTimingKey.AddiByteImmediateToAddressDisplacement or + M68kInstructionTimingKey.SubiByteImmediateToData or + M68kInstructionTimingKey.SubiByteImmediateToAddressDisplacement or + M68kInstructionTimingKey.SubByteDataToData or + >= M68kInstructionTimingKey.AbcdByteDataToData and <= M68kInstructionTimingKey.NbcdByteAbsoluteLong or + M68kInstructionTimingKey.AndByteDataToData or + >= M68kInstructionTimingKey.CmpiByteImmediateToData and <= M68kInstructionTimingKey.CmpiByteImmediateToAddressDisplacement or + >= M68kInstructionTimingKey.CmpByteDataToData and <= M68kInstructionTimingKey.CmpByteAbsoluteLongToData or + M68kInstructionTimingKey.RorByteImmediateData or + M68kInstructionTimingKey.OriByteImmediateToData or + >= M68kInstructionTimingKey.BtstByteImmediateAbsoluteLong and <= M68kInstructionTimingKey.BsetByteImmediateAddressDisplacement or + M68kInstructionTimingKey.BranchByteTaken or + M68kInstructionTimingKey.BranchByteNotTaken or + M68kInstructionTimingKey.BsrByte => M68kOperandSize.Byte, + + M68kInstructionTimingKey.ExtWordData or + M68kInstructionTimingKey.TstWordData or + M68kInstructionTimingKey.ClrDataWord or + M68kInstructionTimingKey.ClrWordAddressDisplacement or + M68kInstructionTimingKey.MoveWordImmediateToAbsoluteLong or + >= M68kInstructionTimingKey.MoveWordAbsoluteLongToData and <= M68kInstructionTimingKey.MoveWordAbsoluteLongToAddressDisplacement or + M68kInstructionTimingKey.AddiWordImmediateToData or + M68kInstructionTimingKey.AddWordDataToData or + M68kInstructionTimingKey.AddWordDataToAddressDisplacement or + M68kInstructionTimingKey.DivuWordImmediateToData or + M68kInstructionTimingKey.DivsWordImmediateToData or + M68kInstructionTimingKey.AndWordImmediateToData or + M68kInstructionTimingKey.MuluWordImmediateToData or + M68kInstructionTimingKey.EoriWordImmediateToData or + >= M68kInstructionTimingKey.CmpiWordImmediateToData and <= M68kInstructionTimingKey.CmpiWordImmediateToAbsoluteLong or + M68kInstructionTimingKey.CmpWordDataToData or + M68kInstructionTimingKey.CmpWordAddressDisplacementToData or + M68kInstructionTimingKey.LeaAbsoluteWord or + M68kInstructionTimingKey.SwapData or + M68kInstructionTimingKey.AsrWordImmediateData or + M68kInstructionTimingKey.AslWordImmediateData or + M68kInstructionTimingKey.RorWordImmediateData or + M68kInstructionTimingKey.RolWordImmediateData or + M68kInstructionTimingKey.BranchWordTaken or + M68kInstructionTimingKey.BranchWordNotTaken or + M68kInstructionTimingKey.BsrWord or + >= M68kInstructionTimingKey.DbccConditionTrue and <= M68kInstructionTimingKey.DbccExpired => M68kOperandSize.Word, + + _ => M68kOperandSize.Long + }; + } + + private static M68kTimingOperand GetSourceOperand(M68kInstructionTimingKey key) + { + return key switch + { + M68kInstructionTimingKey.Movec => M68kTimingOperand.ControlRegister, + M68kInstructionTimingKey.ImmediateWordToConditionCodeRegister or + M68kInstructionTimingKey.ImmediateWordToStatusRegister or + M68kInstructionTimingKey.Moveq or + M68kInstructionTimingKey.MoveByteImmediateToAbsoluteLong or + M68kInstructionTimingKey.MoveWordImmediateToAbsoluteLong or + >= M68kInstructionTimingKey.MoveLongImmediateToAbsoluteLong and <= M68kInstructionTimingKey.MoveLongImmediateToAddress or + M68kInstructionTimingKey.MoveByteImmediateToData or + M68kInstructionTimingKey.MoveByteImmediateToAddressIndirect or + M68kInstructionTimingKey.MoveByteImmediateToAddressDisplacement or + M68kInstructionTimingKey.MoveByteImmediateToBriefIndexed or + M68kInstructionTimingKey.MoveWordImmediateToData or + M68kInstructionTimingKey.MoveWordImmediateToAddressDisplacement or + >= M68kInstructionTimingKey.AddiByteImmediateToData and <= M68kInstructionTimingKey.SubaLongImmediateToAddress or + M68kInstructionTimingKey.AndWordImmediateToData or + M68kInstructionTimingKey.AndLongImmediateToData or + M68kInstructionTimingKey.EoriWordImmediateToData or + M68kInstructionTimingKey.EoriLongImmediateToData or + M68kInstructionTimingKey.OriByteImmediateToData or + >= M68kInstructionTimingKey.CmpiLongImmediateToData and <= M68kInstructionTimingKey.CmpiWordImmediateToAbsoluteLong or + >= M68kInstructionTimingKey.BtstByteImmediateAbsoluteLong and <= M68kInstructionTimingKey.BsetByteImmediateAddressDisplacement or + M68kInstructionTimingKey.BtstImmediateData or + M68kInstructionTimingKey.BclrImmediateData or + M68kInstructionTimingKey.BsetImmediateData => M68kTimingOperand.Immediate, + + M68kInstructionTimingKey.MoveLongDataToData or + M68kInstructionTimingKey.MoveLongDataToAddress or + M68kInstructionTimingKey.MoveLongDataToAddressIndirect or + M68kInstructionTimingKey.MoveLongDataToAddressDisplacement or + M68kInstructionTimingKey.MoveLongDataToAbsoluteLong or + M68kInstructionTimingKey.MoveByteDataToData or + M68kInstructionTimingKey.MoveByteDataToAbsoluteLong or + M68kInstructionTimingKey.MoveByteDataToAddressIndirect or + M68kInstructionTimingKey.MoveByteDataToAddressDisplacement or + M68kInstructionTimingKey.MoveByteDataToBriefIndexed or + M68kInstructionTimingKey.MoveByteDataToPostIncrement or + M68kInstructionTimingKey.MoveByteDataToPredecrement or + M68kInstructionTimingKey.MoveWordDataToAddressDisplacement or + M68kInstructionTimingKey.MoveWordDataToAbsoluteLong or + M68kInstructionTimingKey.AddaLongDataToAddress or + M68kInstructionTimingKey.CmpaLongDataToAddress => M68kTimingOperand.DataRegister, + + M68kInstructionTimingKey.MoveLongAddressToData or + M68kInstructionTimingKey.MoveLongAddressToAddress or + M68kInstructionTimingKey.MoveLongAddressToAddressIndirect or + M68kInstructionTimingKey.MoveLongAddressToAddressDisplacement or + M68kInstructionTimingKey.MoveLongAddressToPostIncrement or + M68kInstructionTimingKey.MoveLongAddressToAbsoluteLong or + M68kInstructionTimingKey.SubLongAddressToData or + M68kInstructionTimingKey.CmpaLongAddressToAddress or + M68kInstructionTimingKey.CmpLongAddressToData => M68kTimingOperand.AddressRegister, + + M68kInstructionTimingKey.MoveLongAddressIndirectToData or + M68kInstructionTimingKey.MoveLongAddressIndirectToAddress or + M68kInstructionTimingKey.MoveLongAddressIndirectToAddressIndirect or + M68kInstructionTimingKey.MoveByteAddressIndirectToData or + M68kInstructionTimingKey.MoveByteAddressIndirectToAbsoluteLong or + M68kInstructionTimingKey.CmpaLongAddressIndirectToAddress or + M68kInstructionTimingKey.CmpLongAddressIndirectToData or + M68kInstructionTimingKey.CmpByteAddressIndirectToData => M68kTimingOperand.AddressIndirect, + + M68kInstructionTimingKey.MoveLongPostIncrementToData or + M68kInstructionTimingKey.MoveLongPostIncrementToAddress or + M68kInstructionTimingKey.MoveBytePostIncrementToData or + M68kInstructionTimingKey.MoveBytePostIncrementToPostIncrement or + M68kInstructionTimingKey.AddLongPostIncrementToData or + M68kInstructionTimingKey.CmpLongPostIncrementToData => M68kTimingOperand.PostIncrement, + + M68kInstructionTimingKey.MoveLongAddressDisplacementToData or + M68kInstructionTimingKey.MoveLongAddressDisplacementToAddress or + M68kInstructionTimingKey.MoveLongAddressDisplacementToPostIncrement or + M68kInstructionTimingKey.MoveByteAddressDisplacementToData or + M68kInstructionTimingKey.MoveWordAddressDisplacementToData or + M68kInstructionTimingKey.SubLongAddressDisplacementToData or + M68kInstructionTimingKey.AddaLongAddressDisplacementToAddress or + M68kInstructionTimingKey.CmpByteAddressDisplacementToData or + M68kInstructionTimingKey.CmpWordAddressDisplacementToData => M68kTimingOperand.AddressDisplacement, + + M68kInstructionTimingKey.MoveLongBriefIndexedToData or + M68kInstructionTimingKey.MoveLongBriefIndexedToAddress or + M68kInstructionTimingKey.MoveByteBriefIndexedToData or + M68kInstructionTimingKey.MoveByteBriefIndexedToPredecrement => M68kTimingOperand.BriefIndexed, + + M68kInstructionTimingKey.LeaAbsoluteWord or + M68kInstructionTimingKey.MoveLongAbsoluteWordToAddressDisplacement => M68kTimingOperand.AbsoluteWord, + + M68kInstructionTimingKey.LeaAbsoluteLong or + M68kInstructionTimingKey.MoveLongAbsoluteLongToData or + M68kInstructionTimingKey.MoveLongAbsoluteLongToAddressDisplacement or + M68kInstructionTimingKey.MoveByteAbsoluteLongToData or + M68kInstructionTimingKey.MoveByteAbsoluteLongToAbsoluteLong or + M68kInstructionTimingKey.MoveWordAbsoluteLongToData or + M68kInstructionTimingKey.MoveWordAbsoluteLongToAbsoluteLong or + M68kInstructionTimingKey.MoveWordAbsoluteLongToAddressDisplacement or + M68kInstructionTimingKey.CmpByteAbsoluteLongToData => M68kTimingOperand.AbsoluteLong, + + _ => M68kTimingOperand.None + }; + } + + private static M68kTimingOperand GetDestinationOperand(M68kInstructionTimingKey key) + { + return key switch + { + M68kInstructionTimingKey.ImmediateWordToConditionCodeRegister => M68kTimingOperand.ConditionCodeRegister, + M68kInstructionTimingKey.ImmediateWordToStatusRegister => M68kTimingOperand.StatusRegister, + M68kInstructionTimingKey.MoveLongImmediateToAddress or + M68kInstructionTimingKey.MoveLongDataToAddress or + M68kInstructionTimingKey.MoveLongAddressToAddress or + M68kInstructionTimingKey.MoveLongAddressIndirectToAddress or + M68kInstructionTimingKey.MoveLongPostIncrementToAddress or + M68kInstructionTimingKey.MoveLongAddressDisplacementToAddress or + M68kInstructionTimingKey.MoveLongBriefIndexedToAddress or + M68kInstructionTimingKey.LeaAbsoluteLong or + M68kInstructionTimingKey.LeaAddressDisplacement or + M68kInstructionTimingKey.LeaAbsoluteWord or + >= M68kInstructionTimingKey.AddaLongImmediateToAddress and <= M68kInstructionTimingKey.SubaLongImmediateToAddress or + >= M68kInstructionTimingKey.CmpaLongImmediateToAddress and <= M68kInstructionTimingKey.CmpaLongAddressIndirectToAddress => M68kTimingOperand.AddressRegister, + + M68kInstructionTimingKey.MoveLongImmediateToData or + M68kInstructionTimingKey.MoveLongDataToData or + M68kInstructionTimingKey.MoveLongAddressToData or + M68kInstructionTimingKey.MoveLongAddressIndirectToData or + M68kInstructionTimingKey.MoveLongPostIncrementToData or + M68kInstructionTimingKey.MoveLongAddressDisplacementToData or + M68kInstructionTimingKey.MoveLongBriefIndexedToData or + M68kInstructionTimingKey.MoveLongAbsoluteLongToData or + M68kInstructionTimingKey.MoveByteDataToData or + M68kInstructionTimingKey.MoveByteImmediateToData or + M68kInstructionTimingKey.MoveByteAddressIndirectToData or + M68kInstructionTimingKey.MoveBytePostIncrementToData or + M68kInstructionTimingKey.MoveByteAddressDisplacementToData or + M68kInstructionTimingKey.MoveByteAbsoluteLongToData or + M68kInstructionTimingKey.MoveByteBriefIndexedToData or + M68kInstructionTimingKey.MoveWordAbsoluteLongToData or + M68kInstructionTimingKey.MoveWordAddressDisplacementToData or + M68kInstructionTimingKey.MoveWordImmediateToData or + M68kInstructionTimingKey.ClrDataLong or + M68kInstructionTimingKey.ClrDataWord or + M68kInstructionTimingKey.ExtbLong or + M68kInstructionTimingKey.ExtWordData or + M68kInstructionTimingKey.TstWordData or + M68kInstructionTimingKey.Moveq or + M68kInstructionTimingKey.NegLongData or + M68kInstructionTimingKey.NotByteData or + M68kInstructionTimingKey.SwapData or + >= M68kInstructionTimingKey.CmpLongDataToData and <= M68kInstructionTimingKey.CmpWordAddressDisplacementToData or + M68kInstructionTimingKey.AndByteDataToData or + M68kInstructionTimingKey.AndWordImmediateToData or + M68kInstructionTimingKey.AndLongImmediateToData or + M68kInstructionTimingKey.EoriWordImmediateToData or + M68kInstructionTimingKey.EoriLongImmediateToData or + M68kInstructionTimingKey.OriByteImmediateToData or + >= M68kInstructionTimingKey.AsrLongImmediateData and <= M68kInstructionTimingKey.RolWordImmediateData or + M68kInstructionTimingKey.BtstImmediateData or + M68kInstructionTimingKey.BclrImmediateData or + M68kInstructionTimingKey.BsetImmediateData or + M68kInstructionTimingKey.BtstDynamicData or + M68kInstructionTimingKey.BclrDynamicData => M68kTimingOperand.DataRegister, + + M68kInstructionTimingKey.MoveLongImmediateToAddressIndirect or + M68kInstructionTimingKey.MoveLongDataToAddressIndirect or + M68kInstructionTimingKey.MoveLongAddressToAddressIndirect or + M68kInstructionTimingKey.MoveLongAddressIndirectToAddressIndirect or + M68kInstructionTimingKey.MoveByteImmediateToAddressIndirect or + M68kInstructionTimingKey.MoveByteDataToAddressIndirect or + M68kInstructionTimingKey.ClrLongAddressIndirect or + M68kInstructionTimingKey.ClrByteAddressIndirect or + M68kInstructionTimingKey.AddiByteImmediateToAddressIndirect or + M68kInstructionTimingKey.CmpiByteImmediateToAddressIndirect or + M68kInstructionTimingKey.NbcdByteAddressIndirect => M68kTimingOperand.AddressIndirect, + + M68kInstructionTimingKey.MoveLongImmediateToPostIncrement or + M68kInstructionTimingKey.MoveLongAddressToPostIncrement or + M68kInstructionTimingKey.MoveLongAddressDisplacementToPostIncrement or + M68kInstructionTimingKey.MoveByteDataToPostIncrement or + M68kInstructionTimingKey.MoveBytePostIncrementToPostIncrement or + M68kInstructionTimingKey.ClrLongPostIncrement or + M68kInstructionTimingKey.CmpiLongImmediateToPostIncrement or + M68kInstructionTimingKey.NbcdBytePostIncrement => M68kTimingOperand.PostIncrement, + + M68kInstructionTimingKey.MoveByteDataToPredecrement or + M68kInstructionTimingKey.MoveByteBriefIndexedToPredecrement or + M68kInstructionTimingKey.NbcdBytePredecrement => M68kTimingOperand.Predecrement, + + M68kInstructionTimingKey.MoveLongImmediateToAddressDisplacement or + M68kInstructionTimingKey.MoveLongDataToAddressDisplacement or + M68kInstructionTimingKey.MoveLongAddressToAddressDisplacement or + M68kInstructionTimingKey.MoveLongAbsoluteWordToAddressDisplacement or + M68kInstructionTimingKey.MoveLongAbsoluteLongToAddressDisplacement or + M68kInstructionTimingKey.MoveByteImmediateToAddressDisplacement or + M68kInstructionTimingKey.MoveWordImmediateToAddressDisplacement or + M68kInstructionTimingKey.MoveWordDataToAddressDisplacement or + M68kInstructionTimingKey.MoveWordAbsoluteLongToAddressDisplacement or + M68kInstructionTimingKey.MoveByteDataToAddressDisplacement or + M68kInstructionTimingKey.ClrLongAddressDisplacement or + M68kInstructionTimingKey.ClrByteAddressDisplacement or + M68kInstructionTimingKey.ClrWordAddressDisplacement or + M68kInstructionTimingKey.AddiByteImmediateToAddressDisplacement or + M68kInstructionTimingKey.SubiByteImmediateToAddressDisplacement or + M68kInstructionTimingKey.AddWordDataToAddressDisplacement or + M68kInstructionTimingKey.AddLongDataToAddressDisplacement or + M68kInstructionTimingKey.CmpiLongImmediateToAddressDisplacement or + M68kInstructionTimingKey.CmpiByteImmediateToAddressDisplacement or + M68kInstructionTimingKey.CmpiWordImmediateToAddressDisplacement or + M68kInstructionTimingKey.BsetByteImmediateAddressDisplacement or + M68kInstructionTimingKey.NbcdByteAddressDisplacement => M68kTimingOperand.AddressDisplacement, + + M68kInstructionTimingKey.MoveByteImmediateToBriefIndexed or + M68kInstructionTimingKey.MoveByteDataToBriefIndexed or + M68kInstructionTimingKey.NbcdByteBriefIndexed => M68kTimingOperand.BriefIndexed, + + M68kInstructionTimingKey.NbcdByteAbsoluteWord => M68kTimingOperand.AbsoluteWord, + + M68kInstructionTimingKey.MoveByteImmediateToAbsoluteLong or + M68kInstructionTimingKey.MoveWordImmediateToAbsoluteLong or + M68kInstructionTimingKey.MoveLongImmediateToAbsoluteLong or + M68kInstructionTimingKey.MoveLongDataToAbsoluteLong or + M68kInstructionTimingKey.MoveLongAddressToAbsoluteLong or + M68kInstructionTimingKey.MoveByteDataToAbsoluteLong or + M68kInstructionTimingKey.MoveByteAddressIndirectToAbsoluteLong or + M68kInstructionTimingKey.MoveByteAbsoluteLongToAbsoluteLong or + M68kInstructionTimingKey.MoveWordDataToAbsoluteLong or + M68kInstructionTimingKey.MoveWordAbsoluteLongToAbsoluteLong or + M68kInstructionTimingKey.ClrLongAbsoluteLong or + M68kInstructionTimingKey.ImmediateLogicalByteToAbsoluteLong or + M68kInstructionTimingKey.AddiLongImmediateToAbsoluteLong or + M68kInstructionTimingKey.CmpiLongImmediateToAbsoluteLong or + M68kInstructionTimingKey.CmpiWordImmediateToAbsoluteLong or + M68kInstructionTimingKey.BtstByteImmediateAbsoluteLong or + M68kInstructionTimingKey.BchgByteImmediateAbsoluteLong or + M68kInstructionTimingKey.BclrByteImmediateAbsoluteLong or + M68kInstructionTimingKey.BsetByteImmediateAbsoluteLong or + M68kInstructionTimingKey.SccAbsoluteLong or + M68kInstructionTimingKey.NbcdByteAbsoluteLong => M68kTimingOperand.AbsoluteLong, + + _ => M68kTimingOperand.None + }; + } + + private static M68kTimingOutcome GetOutcome(M68kInstructionTimingKey key) + { + return key switch + { + M68kInstructionTimingKey.BranchByteTaken or + M68kInstructionTimingKey.BranchWordTaken or + M68kInstructionTimingKey.BranchLongTaken or + M68kInstructionTimingKey.DbccBranchTaken => M68kTimingOutcome.Taken, + M68kInstructionTimingKey.BranchByteNotTaken or + M68kInstructionTimingKey.BranchWordNotTaken or + M68kInstructionTimingKey.BranchLongNotTaken => M68kTimingOutcome.NotTaken, + M68kInstructionTimingKey.DbccConditionTrue => M68kTimingOutcome.ConditionTrue, + M68kInstructionTimingKey.DbccExpired => M68kTimingOutcome.Expired, + _ => M68kTimingOutcome.None + }; + } + } +} diff --git a/CopperMod.Amiga.Tests/AmigaBusTimingTests.cs b/CopperMod.Amiga.Tests/AmigaBusTimingTests.cs index 77da2cc..ce73415 100644 --- a/CopperMod.Amiga.Tests/AmigaBusTimingTests.cs +++ b/CopperMod.Amiga.Tests/AmigaBusTimingTests.cs @@ -499,6 +499,19 @@ public void HardwareSchedulerSkipsLiveDisplayAdvanceWhenDisplayHasNoLiveWork() Assert.True(bus.Agnus.CaptureSnapshot().RefreshSlotReservationCount > 0); } + [Fact] + public void CpuBoundaryHardwareDrainDoesNotReservePureRefreshSlots() + { + var bus = new AmigaBus(enableLiveAgnusDma: true); + var targetCycle = OutputRowStartCycle(AmigaConstants.PalLowResOverscanBorderY); + + bus.AdvanceHardwareEventsTo(targetCycle); + Assert.Equal(0, bus.Agnus.CaptureSnapshot().RefreshSlotReservationCount); + + bus.AdvanceHardwareTo(targetCycle); + Assert.True(bus.Agnus.CaptureSnapshot().RefreshSlotReservationCount > 0); + } + [Fact] public void LiveRasterlinePlanRecordsLineStateEvent() { @@ -559,15 +572,40 @@ public void PredictedRasterlinePlanMatchesSimpleBitplaneLine() var snapshot = bus.Display.CaptureSnapshot(); Assert.True( snapshot.LastPredictedRasterlinePlanLines > 0, - $"recorded={snapshot.LastRasterlinePlanEvents}, lineState={snapshot.LastRasterlinePlanLineStateEvents}, bitplane={snapshot.LastRasterlinePlanBitplaneFetchEvents}, lines={snapshot.LastPredictedRasterlinePlanLines}, matched={snapshot.LastPredictedRasterlinePlanMatchedLines}, mismatched={snapshot.LastPredictedRasterlinePlanMismatchedLines}, unsupported={snapshot.LastPredictedRasterlinePlanUnsupportedLines}, copper={snapshot.LastPredictedRasterlinePlanUnsupportedCopperLines}, pending={snapshot.LastPredictedRasterlinePlanUnsupportedPendingWriteLines}, sprite={snapshot.LastPredictedRasterlinePlanUnsupportedSpriteLines}, invalid={snapshot.LastPredictedRasterlinePlanUnsupportedInvalidStateLines}, overflow={snapshot.LastPredictedRasterlinePlanUnsupportedOverflowLines}"); + $"recorded={snapshot.LastRasterlinePlanEvents}, lineState={snapshot.LastRasterlinePlanLineStateEvents}, bitplane={snapshot.LastRasterlinePlanBitplaneFetchEvents}, lines={snapshot.LastPredictedRasterlinePlanLines}, matched={snapshot.LastPredictedRasterlinePlanMatchedLines}, mismatched={snapshot.LastPredictedRasterlinePlanMismatchedLines}, unsupported={snapshot.LastPredictedRasterlinePlanUnsupportedLines}, copper={snapshot.LastPredictedRasterlinePlanUnsupportedCopperLines}, pending={snapshot.LastPredictedRasterlinePlanUnsupportedPendingWriteLines}, sprite={snapshot.LastPredictedRasterlinePlanUnsupportedSpriteLines}, invalid={snapshot.LastPredictedRasterlinePlanUnsupportedInvalidStateLines}, overflow={snapshot.LastPredictedRasterlinePlanUnsupportedOverflowLines}, descriptorBuilds={snapshot.LastRasterlineDescriptorBuilds}, descriptorReplayed={snapshot.LastRasterlineDescriptorReplayedRows}, descriptorMismatches={snapshot.LastRasterlineDescriptorMismatches}"); Assert.True( snapshot.LastPredictedRasterlinePlanMatchedLines > 0, $"lines={snapshot.LastPredictedRasterlinePlanLines}, matched={snapshot.LastPredictedRasterlinePlanMatchedLines}, mismatched={snapshot.LastPredictedRasterlinePlanMismatchedLines}"); Assert.Equal(0, snapshot.LastPredictedRasterlinePlanMismatchedLines); + Assert.True(snapshot.LastRasterlineDescriptorBuilds > 0); + Assert.True(snapshot.LastRasterlineDescriptorBitplaneRows > 0); + Assert.True(snapshot.LastRasterlineDescriptorReplayAttempts > 0); + Assert.True(snapshot.LastRasterlineDescriptorReplayedRows > 0); + Assert.Equal(0, snapshot.LastRasterlineDescriptorMismatches); + } + + [Fact] + public void RasterlineDescriptorReplayReadsChipRamAtFetchTime() + { + var bus = new AmigaBus(enableLiveAgnusDma: true); + var row = AmigaConstants.PalLowResOverscanBorderY; + var lineStart = OutputRowStartCycle(row); + var fetchCycle = LowResPlane1FetchCycle(row); + ConfigureLiveOneBitplaneDma(bus); + BigEndian.WriteUInt16(bus.ChipRam, 0x1000, 0x0000); + + bus.AdvanceDmaTo(lineStart); + BigEndian.WriteUInt16(bus.ChipRam, 0x1000, 0xA55A); + bus.AdvanceDmaTo(fetchCycle); + + var snapshot = bus.Display.CaptureSnapshot(); + Assert.True(snapshot.LastRasterlineDescriptorReplayedRows > 0); + Assert.Equal(0, snapshot.LastRasterlineDescriptorMismatches); + Assert.Equal(0xA55A, ReadLiveBitplaneWord(bus, row, plane: 0, word: 0)); } [Fact] - public void PredictedRasterlinePlanMarksSpriteLineUnsupported() + public void RasterlineDescriptorReplaysSpriteRows() { var bus = new AmigaBus(enableLiveAgnusDma: true); var lineStart = OutputRowStartCycle(AmigaConstants.PalLowResOverscanBorderY); @@ -577,8 +615,12 @@ public void PredictedRasterlinePlanMarksSpriteLineUnsupported() bus.AdvanceDmaTo(validationStop); var snapshot = bus.Display.CaptureSnapshot(); - Assert.True(snapshot.LastPredictedRasterlinePlanUnsupportedSpriteLines > 0); + Assert.True(snapshot.LastRasterlineDescriptorSpriteRows > 0); + Assert.True(snapshot.LastRasterlineDescriptorReplayAttempts > 0); + Assert.True(snapshot.LastRasterlineDescriptorReplayedRows > 0); + Assert.Equal(0, snapshot.LastPredictedRasterlinePlanUnsupportedSpriteLines); Assert.Equal(0, snapshot.LastPredictedRasterlinePlanMismatchedLines); + Assert.Equal(0, snapshot.LastRasterlineDescriptorMismatches); } [Fact] @@ -966,6 +1008,24 @@ public void IntreqTimedReadObservesVblankAtExactCycle() Assert.NotEqual(0, value & AmigaConstants.IntreqVerticalBlank); } + [Fact] + public void IntreqReadObservesVblankAfterSameCyclePseudoFastAccess() + { + var bus = new AmigaBus(expansionRamSize: 0x10000); + var frameCycle = (long)AmigaConstants.A500PalCpuCyclesPerFrame; + + var memoryCycle = frameCycle; + _ = bus.ReadWord( + AmigaConstants.A500BootPseudoFastRamBase, + ref memoryCycle, + AmigaBusAccessKind.CpuDataRead); + + var intreqCycle = frameCycle; + var value = bus.ReadWord(0x00DFF01E, ref intreqCycle, AmigaBusAccessKind.CpuDataRead); + + Assert.NotEqual(0, value & AmigaConstants.IntreqVerticalBlank); + } + [Fact] public void RepeatedIntreqPollingDoesNotMissCiaTimerInterrupt() { @@ -1837,6 +1897,16 @@ private static long LowResPlane1FetchCycle(int row) => OutputRowStartCycle(row) + ((0x38 + HrmLowResPlane1FetchSlot) * AgnusChipSlotScheduler.SlotCycles); + private static ushort ReadLiveBitplaneWord(AmigaBus bus, int row, int plane, int word) + { + var field = typeof(OcsDisplay).GetField( + "_liveBitplaneWords", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + Assert.NotNull(field); + var words = Assert.IsType(field.GetValue(bus.Display)); + return words[(row * 6 * 64) + (plane * 64) + word]; + } + private static void ConfigureLiveOneBitplaneDma(AmigaBus bus) { bus.WriteWord(0x00DFF096, 0x8300); diff --git a/CopperMod.Amiga.Tests/M68kTimingFormulaTests.cs b/CopperMod.Amiga.Tests/M68kTimingFormulaTests.cs new file mode 100644 index 0000000..5797203 --- /dev/null +++ b/CopperMod.Amiga.Tests/M68kTimingFormulaTests.cs @@ -0,0 +1,155 @@ +using Copper68k; + +namespace CopperMod.Amiga.Tests; + +public sealed class M68kTimingFormulaTests +{ + public static IEnumerable LegacyTimingKeys => + Enum.GetValues() + .Where(key => key is not M68kInstructionTimingKey.MovemLongRegistersToPredecrement and + not M68kInstructionTimingKey.MovemLongPostIncrementToRegisters) + .Select(key => new object[] { key }); + + [Theory] + [MemberData(nameof(LegacyTimingKeys))] + public void LegacyTimingKeysProduceDescriptorPlansForM68020AndM68030(object keyObject) + { + var key = (M68kInstructionTimingKey)keyObject; + var descriptor = M68kTimingDescriptor.FromLegacyKey(key); + + var plan20 = M68kTimingFormula.GetPlan(descriptor, M68kAcceleratorModel.M68020); + var plan30 = M68kTimingFormula.GetPlan(descriptor, M68kAcceleratorModel.M68030); + + Assert.Equal(key, descriptor.Key); + Assert.Equal(key, plan20.Key); + Assert.Equal(key, plan30.Key); + Assert.Equal(plan20.Name, plan30.Name); + Assert.Equal(plan20.NativeCycles, plan30.NativeCycles); + Assert.False(plan20.UsesHeadTail); + Assert.True(plan30.UsesHeadTail); + } + + [Fact] + public void LegacyMovemKeysRequireRegisterCountDescriptor() + { + Assert.Throws( + () => M68kTimingDescriptor.FromLegacyKey(M68kInstructionTimingKey.MovemLongRegistersToPredecrement)); + Assert.Throws( + () => M68kTimingDescriptor.FromLegacyKey(M68kInstructionTimingKey.MovemLongPostIncrementToRegisters)); + } + + [Fact] + public void DescriptorCapturesSharedMoveOperands() + { + var immediateToDisplacement = M68kTimingDescriptor.FromLegacyKey(M68kInstructionTimingKey.MoveLongImmediateToAddressDisplacement); + Assert.Equal(M68kTimingOperation.Move, immediateToDisplacement.Operation); + Assert.Equal(M68kOperandSize.Long, immediateToDisplacement.Size); + Assert.Equal(M68kTimingOperand.Immediate, immediateToDisplacement.Source); + Assert.Equal(M68kTimingOperand.AddressDisplacement, immediateToDisplacement.Destination); + + var indexedToData = M68kTimingDescriptor.FromLegacyKey(M68kInstructionTimingKey.MoveByteBriefIndexedToData); + Assert.Equal(M68kOperandSize.Byte, indexedToData.Size); + Assert.Equal(M68kTimingOperand.BriefIndexed, indexedToData.Source); + Assert.Equal(M68kTimingOperand.DataRegister, indexedToData.Destination); + } + + [Fact] + public void M68030FormulaDerivesHeadTailAndModelSpecificBarriers() + { + AssertPlan( + M68kTimingFormula.GetPlan(M68kTimingDescriptor.FromLegacyKey(M68kInstructionTimingKey.MoveLongDataToData), M68kAcceleratorModel.M68030), + nativeCycles: 2, + headCycles: 1, + tailCycles: 1, + M68kTimingBarrier.None); + + AssertPlan( + M68kTimingFormula.GetPlan(M68kTimingDescriptor.FromLegacyKey(M68kInstructionTimingKey.LinkLong), M68kAcceleratorModel.M68030), + nativeCycles: 16, + headCycles: 2, + tailCycles: 2, + M68kTimingBarrier.None); + + AssertPlan( + M68kTimingFormula.GetPlan(M68kTimingDescriptor.FromLegacyKey(M68kInstructionTimingKey.BranchByteTaken), M68kAcceleratorModel.M68030), + nativeCycles: 6, + headCycles: 0, + tailCycles: 0, + M68kTimingBarrier.FlushPipeline | M68kTimingBarrier.Branch); + + AssertPlan( + M68kTimingFormula.GetPlan(M68kTimingDescriptor.FromLegacyKey(M68kInstructionTimingKey.Movec), M68kAcceleratorModel.M68030), + nativeCycles: 12, + headCycles: 0, + tailCycles: 0, + M68kTimingBarrier.CacheControl | M68kTimingBarrier.SynchronizeBus); + } + + [Fact] + public void MovemLongFormulaUsesRegisterCountAndModel() + { + var registerToMemory = M68kTimingDescriptor.MovemLong( + M68kInstructionTimingKey.MovemLongRegistersToPredecrement, + "MOVEM.L ,-(An)", + registerCount: 3, + registerToMemory: true); + var memoryToRegister = M68kTimingDescriptor.MovemLong( + M68kInstructionTimingKey.MovemLongPostIncrementToRegisters, + "MOVEM.L (An)+,", + registerCount: 3, + registerToMemory: false); + + AssertPlan( + M68kTimingFormula.GetPlan(registerToMemory, M68kAcceleratorModel.M68020), + nativeCycles: 17, + usesHeadTail: false); + AssertPlan( + M68kTimingFormula.GetPlan(registerToMemory, M68kAcceleratorModel.M68030), + nativeCycles: 14, + headCycles: 2, + tailCycles: 0, + M68kTimingBarrier.None); + AssertPlan( + M68kTimingFormula.GetPlan(memoryToRegister, M68kAcceleratorModel.M68030), + nativeCycles: 24, + headCycles: 2, + tailCycles: 0, + M68kTimingBarrier.None); + AssertPlan( + M68kTimingFormula.GetPlan(registerToMemory, M68kAcceleratorModel.M68040), + nativeCycles: 17, + usesHeadTail: false); + } + + [Fact] + public void FixedCycleProfileOverridesDescriptorFormula() + { + var timing = new M68kTimingEngine(M68020CpuProfile.Ocs68040JitMaxSpeed, new M68kCpuState()); + var descriptor = M68kTimingDescriptor.MovemLong( + M68kInstructionTimingKey.MovemLongRegistersToPredecrement, + "MOVEM.L ,-(An)", + registerCount: 8, + registerToMemory: true); + + var plan = timing.GetPlan(descriptor); + + Assert.Equal(1, plan.NativeCycles); + Assert.False(plan.UsesHeadTail); + Assert.Equal("fixed JIT fallback", plan.Name); + } + + private static void AssertPlan( + M68kInstructionPlan plan, + int nativeCycles, + int headCycles = 0, + int tailCycles = 0, + M68kTimingBarrier barriers = M68kTimingBarrier.None, + bool usesHeadTail = true) + { + Assert.Equal(nativeCycles, plan.NativeCycles); + Assert.Equal(usesHeadTail, plan.UsesHeadTail); + Assert.Equal(headCycles, plan.HeadCycles); + Assert.Equal(tailCycles, plan.TailCycles); + Assert.Equal(barriers, plan.Barriers); + } +} diff --git a/CopperMod.Amiga/AmigaBus.cs b/CopperMod.Amiga/AmigaBus.cs index 3a02c15..95c64d6 100644 --- a/CopperMod.Amiga/AmigaBus.cs +++ b/CopperMod.Amiga/AmigaBus.cs @@ -1587,10 +1587,14 @@ public void AdvanceDmaTo(long targetCycle, bool advanceLiveAgnus, bool advancePa public void AdvanceHardwareTo(long targetCycle) => _hardwareScheduler.DrainTo( targetCycle, - AmigaHardwareEventMask.All | AmigaHardwareEventMask.ForceCatchUp); + AmigaHardwareEventMask.All | + AmigaHardwareEventMask.DiskPassiveInput | + AmigaHardwareEventMask.ForceCatchUp); public void AdvanceHardwareEventsTo(long targetCycle) - => _hardwareScheduler.DrainTo(targetCycle, AmigaHardwareEventMask.All); + => _hardwareScheduler.DrainTo( + targetCycle, + AmigaHardwareEventMask.All | AmigaHardwareEventMask.CpuBoundary); public AmigaHardwareSchedulerSnapshot CaptureHardwareSchedulerSnapshot() => _hardwareScheduler.CaptureSnapshot(); @@ -1698,6 +1702,16 @@ internal long GetNextAgnusEventCycle(long currentCycle, long targetCycle) return Agnus.GetNextWakeCandidateCycle(currentCycle, targetCycle, Display.HasLiveDisplayWork()); } + internal long GetNextCpuVisibleAgnusEventCycle(long currentCycle, long targetCycle) + { + if (!LiveAgnusDmaEnabled) + { + return long.MaxValue; + } + + return Display.GetNextLiveCopperWakeCandidateCycle(currentCycle, targetCycle) ?? long.MaxValue; + } + private static CustomRegisterReadAdvanceKind GetCustomRegisterReadAdvanceKind(uint customRegisterAddress) { var offset = (ushort)(customRegisterAddress & 0x01FE); diff --git a/CopperMod.Amiga/AmigaDisk.cs b/CopperMod.Amiga/AmigaDisk.cs index 4778e2b..63daa56 100644 --- a/CopperMod.Amiga/AmigaDisk.cs +++ b/CopperMod.Amiga/AmigaDisk.cs @@ -576,6 +576,69 @@ public AmigaDiskSpecializationCounters( public long SyncIndexMisses { get; } } + internal readonly struct AmigaDiskSchedulerCounters + { + public AmigaDiskSchedulerCounters( + long nextWakeCandidateQueries, + long nextEventWakeCandidateQueries, + long hasWakeCandidateThroughQueries, + long hasEventWakeCandidateThroughQueries, + long refreshNextIndexPulseQueries, + long inputAdvanceCalls, + long schedulerGateTrue, + long schedulerGateFalse, + long pendingDmaWakeSources, + long activeDmaProgressWakeSources, + long activeDmaCompletionWakeSources, + long syncCandidateWakeSources, + long indexPulseWakeSources, + long passiveByteReadyWakeSources) + { + NextWakeCandidateQueries = nextWakeCandidateQueries; + NextEventWakeCandidateQueries = nextEventWakeCandidateQueries; + HasWakeCandidateThroughQueries = hasWakeCandidateThroughQueries; + HasEventWakeCandidateThroughQueries = hasEventWakeCandidateThroughQueries; + RefreshNextIndexPulseQueries = refreshNextIndexPulseQueries; + InputAdvanceCalls = inputAdvanceCalls; + SchedulerGateTrue = schedulerGateTrue; + SchedulerGateFalse = schedulerGateFalse; + PendingDmaWakeSources = pendingDmaWakeSources; + ActiveDmaProgressWakeSources = activeDmaProgressWakeSources; + ActiveDmaCompletionWakeSources = activeDmaCompletionWakeSources; + SyncCandidateWakeSources = syncCandidateWakeSources; + IndexPulseWakeSources = indexPulseWakeSources; + PassiveByteReadyWakeSources = passiveByteReadyWakeSources; + } + + public long NextWakeCandidateQueries { get; } + + public long NextEventWakeCandidateQueries { get; } + + public long HasWakeCandidateThroughQueries { get; } + + public long HasEventWakeCandidateThroughQueries { get; } + + public long RefreshNextIndexPulseQueries { get; } + + public long InputAdvanceCalls { get; } + + public long SchedulerGateTrue { get; } + + public long SchedulerGateFalse { get; } + + public long PendingDmaWakeSources { get; } + + public long ActiveDmaProgressWakeSources { get; } + + public long ActiveDmaCompletionWakeSources { get; } + + public long SyncCandidateWakeSources { get; } + + public long IndexPulseWakeSources { get; } + + public long PassiveByteReadyWakeSources { get; } + } + internal sealed class AmigaFloppyDrive { private const int MinCylinder = 0; @@ -739,6 +802,17 @@ private void ClearTrackCache() internal sealed class AmigaDiskController { + private enum SchedulerWakeReason + { + None, + PendingDma, + ActiveDmaProgress, + ActiveDmaCompletion, + SyncCandidate, + IndexPulse, + PassiveByteReady + } + private const int MaxFloppyDriveCount = 4; private const ushort DskDatr = 0x008; private const ushort DskBytr = 0x01A; @@ -830,6 +904,20 @@ internal sealed class AmigaDiskController private long _rollingWindowMisses; private long _syncIndexHits; private long _syncIndexMisses; + private long _schedulerNextWakeCandidateQueries; + private long _schedulerNextEventWakeCandidateQueries; + private long _schedulerHasWakeCandidateThroughQueries; + private long _schedulerHasEventWakeCandidateThroughQueries; + private long _schedulerRefreshNextIndexPulseQueries; + private long _schedulerInputAdvanceCalls; + private long _schedulerGateTrue; + private long _schedulerGateFalse; + private long _schedulerPendingDmaWakeSources; + private long _schedulerActiveDmaProgressWakeSources; + private long _schedulerActiveDmaCompletionWakeSources; + private long _schedulerSyncCandidateWakeSources; + private long _schedulerIndexPulseWakeSources; + private long _schedulerPassiveByteReadyWakeSources; public AmigaDiskController(AmigaBus bus, int connectedDriveCount = 1, bool enableSpecialization = false) { @@ -896,7 +984,8 @@ public AmigaDiskControllerSnapshot CaptureSnapshot() _activeDmaCompletionCycle, _diskDataRegister, ConnectedDriveCount, - CaptureSpecializationCounters()); + CaptureSpecializationCounters(), + CaptureSchedulerCounters()); } public AmigaDiskSpecializationCounters CaptureSpecializationCounters() @@ -908,6 +997,23 @@ public AmigaDiskSpecializationCounters CaptureSpecializationCounters() _syncIndexHits, _syncIndexMisses); + public AmigaDiskSchedulerCounters CaptureSchedulerCounters() + => new AmigaDiskSchedulerCounters( + _schedulerNextWakeCandidateQueries, + _schedulerNextEventWakeCandidateQueries, + _schedulerHasWakeCandidateThroughQueries, + _schedulerHasEventWakeCandidateThroughQueries, + _schedulerRefreshNextIndexPulseQueries, + _schedulerInputAdvanceCalls, + _schedulerGateTrue, + _schedulerGateFalse, + _schedulerPendingDmaWakeSources, + _schedulerActiveDmaProgressWakeSources, + _schedulerActiveDmaCompletionWakeSources, + _schedulerSyncCandidateWakeSources, + _schedulerIndexPulseWakeSources, + _schedulerPassiveByteReadyWakeSources); + public AmigaDiskDmaTraceEntry[] CaptureDmaTrace() { return _dmaTrace.ToArray(); @@ -959,6 +1065,20 @@ public void Reset() _rollingWindowMisses = 0; _syncIndexHits = 0; _syncIndexMisses = 0; + _schedulerNextWakeCandidateQueries = 0; + _schedulerNextEventWakeCandidateQueries = 0; + _schedulerHasWakeCandidateThroughQueries = 0; + _schedulerHasEventWakeCandidateThroughQueries = 0; + _schedulerRefreshNextIndexPulseQueries = 0; + _schedulerInputAdvanceCalls = 0; + _schedulerGateTrue = 0; + _schedulerGateFalse = 0; + _schedulerPendingDmaWakeSources = 0; + _schedulerActiveDmaProgressWakeSources = 0; + _schedulerActiveDmaCompletionWakeSources = 0; + _schedulerSyncCandidateWakeSources = 0; + _schedulerIndexPulseWakeSources = 0; + _schedulerPassiveByteReadyWakeSources = 0; foreach (var drive in _drives) { drive.ResetPosition(); @@ -1167,6 +1287,7 @@ private bool HasUnknownSelectedDiskInput(long targetCycle) public long? GetNextWakeCandidateCycle(long currentCycle, long targetCycle) { + _schedulerNextWakeCandidateQueries++; if (targetCycle <= currentCycle) { return null; @@ -1208,6 +1329,7 @@ private bool HasUnknownSelectedDiskInput(long targetCycle) internal bool HasWakeCandidateThrough(long targetCycle) { + _schedulerHasWakeCandidateThroughQueries++; if (targetCycle < _currentCycle) { return false; @@ -1219,6 +1341,7 @@ internal bool HasWakeCandidateThrough(long targetCycle) internal bool HasEventWakeCandidateThrough(long targetCycle, bool includeActiveDmaProgress = false) { + _schedulerHasEventWakeCandidateThroughQueries++; if (targetCycle < _currentCycle) { return false; @@ -1239,11 +1362,40 @@ internal bool HasCiaEventThrough(long targetCycle) return _nextIndexPulseCycle <= targetCycle; } + internal bool HasCiaWakeSource() + { + var hasSource = HasObservedIndexPulseSource(); + RecordSchedulerWakeReason(hasSource ? SchedulerWakeReason.IndexPulse : SchedulerWakeReason.None); + return hasSource; + } + + internal bool HasSchedulerWakeSourceThrough( + long targetCycle, + bool includePassiveInput, + bool includeEvents, + bool includeActiveDmaProgress) + { + if (targetCycle < _currentCycle) + { + RecordSchedulerWakeReason(SchedulerWakeReason.None); + return false; + } + + var reason = GetSchedulerWakeReasonThrough( + targetCycle, + includePassiveInput, + includeEvents, + includeActiveDmaProgress); + RecordSchedulerWakeReason(reason); + return reason != SchedulerWakeReason.None; + } + internal long? GetNextEventWakeCandidateCycle( long currentCycle, long targetCycle, bool includeActiveDmaProgress = false) { + _schedulerNextEventWakeCandidateQueries++; if (targetCycle <= currentCycle) { return null; @@ -1274,6 +1426,143 @@ internal bool HasCiaEventThrough(long targetCycle) return ClampWakeCandidate(candidate, currentCycle, targetCycle); } + private SchedulerWakeReason GetSchedulerWakeReasonThrough( + long targetCycle, + bool includePassiveInput, + bool includeEvents, + bool includeActiveDmaProgress) + { + if (includePassiveInput) + { + var passiveReason = GetPassiveInputWakeReasonThrough(targetCycle); + if (passiveReason != SchedulerWakeReason.None) + { + return passiveReason; + } + } + + return includeEvents + ? GetEventWakeReasonThrough(targetCycle, includeActiveDmaProgress) + : SchedulerWakeReason.None; + } + + private SchedulerWakeReason GetPassiveInputWakeReasonThrough(long targetCycle) + { + if (HasPendingDmaWakeSourceThrough(targetCycle)) + { + return SchedulerWakeReason.PendingDma; + } + + if (_nextDiskInputAdvanceCycle > 0 && _nextDiskInputAdvanceCycle <= targetCycle) + { + return SchedulerWakeReason.PassiveByteReady; + } + + if (HasUnknownSelectedDiskInput(targetCycle)) + { + return SchedulerWakeReason.PassiveByteReady; + } + + if (_activeDma && IsDiskDmaControlEnabled() && _activeDmaCompletionCycle <= targetCycle) + { + return SchedulerWakeReason.ActiveDmaCompletion; + } + + return HasObservedIndexPulseSource() + ? SchedulerWakeReason.IndexPulse + : SchedulerWakeReason.None; + } + + private SchedulerWakeReason GetEventWakeReasonThrough(long targetCycle, bool includeActiveDmaProgress) + { + if (HasPendingDmaWakeSourceThrough(targetCycle)) + { + return SchedulerWakeReason.PendingDma; + } + + if (_activeDma && IsDiskDmaControlEnabled()) + { + var activeDmaCycle = includeActiveDmaProgress + ? GetNextActiveDmaAdvanceCycle() + : _activeDmaCompletionCycle; + if (activeDmaCycle <= targetCycle) + { + return includeActiveDmaProgress && activeDmaCycle < _activeDmaCompletionCycle + ? SchedulerWakeReason.ActiveDmaProgress + : SchedulerWakeReason.ActiveDmaCompletion; + } + } + + if (_nextDiskSyncAdvanceCycle > 0 && _nextDiskSyncAdvanceCycle <= targetCycle) + { + return SchedulerWakeReason.SyncCandidate; + } + + if (_nextDiskSyncAdvanceCycle == 0 && HasUnknownSelectedDiskInput(targetCycle)) + { + return SchedulerWakeReason.SyncCandidate; + } + + return HasObservedIndexPulseSource() + ? SchedulerWakeReason.IndexPulse + : SchedulerWakeReason.None; + } + + private bool HasPendingDmaWakeSourceThrough(long targetCycle) + { + if ((_pendingReadDmaWords == 0 && _pendingWriteDmaWords == 0) || !IsDiskDmaControlEnabled()) + { + return false; + } + + var driveIndex = GetSelectedDriveIndex(); + if (driveIndex < 0 || !IsDriveConnected(driveIndex)) + { + return false; + } + + var drive = _drives[driveIndex]; + return drive.Disk != null && + drive.MotorOn && + GetDriveReadyCycle(drive) <= targetCycle; + } + + private bool HasObservedIndexPulseSource() + => (_bus.CiaB.InterruptMask & AmigaCia.FlagInterruptMask) != 0 && + AnyConnectedDriveMotorOn(); + + private void RecordSchedulerWakeReason(SchedulerWakeReason reason) + { + if (reason == SchedulerWakeReason.None) + { + _schedulerGateFalse++; + return; + } + + _schedulerGateTrue++; + switch (reason) + { + case SchedulerWakeReason.PendingDma: + _schedulerPendingDmaWakeSources++; + break; + case SchedulerWakeReason.ActiveDmaProgress: + _schedulerActiveDmaProgressWakeSources++; + break; + case SchedulerWakeReason.ActiveDmaCompletion: + _schedulerActiveDmaCompletionWakeSources++; + break; + case SchedulerWakeReason.SyncCandidate: + _schedulerSyncCandidateWakeSources++; + break; + case SchedulerWakeReason.IndexPulse: + _schedulerIndexPulseWakeSources++; + break; + case SchedulerWakeReason.PassiveByteReady: + _schedulerPassiveByteReadyWakeSources++; + break; + } + } + private long? GetPassiveInputWakeCandidateCycle(long currentCycle, long targetCycle) { if (_nextDiskInputAdvanceCycle > 0) @@ -1814,6 +2103,7 @@ private long GetDriveIndexPeriodCycles(int driveIndex) private void RefreshNextIndexPulseCycle() { + _schedulerRefreshNextIndexPulseQueries++; var next = long.MaxValue; for (var driveIndex = 0; driveIndex < ConnectedDriveCount; driveIndex++) { @@ -2610,6 +2900,7 @@ private void AdvanceStreamTo(DiskStreamState stream, int trackBitLength, double private void AdvanceDiskInputTo(long cycle) { + _schedulerInputAdvanceCalls++; if (_traceRecorder != null) { var traceDrive = ResolveTraceDrive(-1); @@ -3413,7 +3704,8 @@ public AmigaDiskControllerSnapshot( long activeDmaCompletionCycle, ushort dskdatr, int connectedDriveCount, - AmigaDiskSpecializationCounters specializationCounters) + AmigaDiskSpecializationCounters specializationCounters, + AmigaDiskSchedulerCounters schedulerCounters) { DiskPointer = diskPointer; Dsklen = dsklen; @@ -3437,6 +3729,7 @@ public AmigaDiskControllerSnapshot( Dskdatr = dskdatr; ConnectedDriveCount = connectedDriveCount; SpecializationCounters = specializationCounters; + SchedulerCounters = schedulerCounters; } public uint DiskPointer { get; } @@ -3482,6 +3775,8 @@ public AmigaDiskControllerSnapshot( public int ConnectedDriveCount { get; } public AmigaDiskSpecializationCounters SpecializationCounters { get; } + + public AmigaDiskSchedulerCounters SchedulerCounters { get; } } internal enum AmigaDiskDmaTraceKind diff --git a/CopperMod.Amiga/AmigaHardwareScheduler.cs b/CopperMod.Amiga/AmigaHardwareScheduler.cs index 72e6abe..02e358e 100644 --- a/CopperMod.Amiga/AmigaHardwareScheduler.cs +++ b/CopperMod.Amiga/AmigaHardwareScheduler.cs @@ -18,11 +18,11 @@ internal enum AmigaHardwareEventMask ForceCatchUp = 1 << 8, CiaRegisterSample = 1 << 9, DiskRegisterSample = 1 << 10, + CpuBoundary = 1 << 11, All = Raster | CiaTimers | PaulaRegister | DiskEvents | - DiskPassiveInput | Agnus | Blitter | DiskCiaEvents @@ -54,6 +54,11 @@ internal sealed class AmigaHardwareScheduler AmigaHardwareEventMask.PaulaRegister | AmigaHardwareEventMask.DiskEvents | AmigaHardwareEventMask.Blitter; + private const AmigaHardwareEventMask SlotContendedMemoryAccessMask = + AmigaHardwareEventMask.PaulaRegister | + AmigaHardwareEventMask.DiskEvents | + AmigaHardwareEventMask.Agnus | + AmigaHardwareEventMask.Blitter; private readonly AmigaBus _bus; private long _lastDrainCycle; @@ -170,7 +175,9 @@ public void DrainTo(long targetCycle, AmigaHardwareEventMask mask) { var blitterWasBusyAtDrainStart = _bus.Blitter.Busy; var forceCatchUp = (mask & AmigaHardwareEventMask.ForceCatchUp) != 0; + var cpuBoundary = (mask & AmigaHardwareEventMask.CpuBoundary) != 0; if ((mask & AmigaHardwareEventMask.Agnus) != 0 && + !cpuBoundary && (forceCatchUp || _bus.Display.HasLiveDisplayWork())) { _bus.AdvanceAgnusCoreTo(targetCycle); @@ -406,7 +413,13 @@ private bool TrySkipDrainWithRasterlineSchedule(long targetCycle, AmigaHardwareE return false; } - var cursor = Math.Min(_lastDrainCycle, targetCycle); + var cursor = Math.Min(GetMaskDrainedThroughCycle(mask), targetCycle); + var targetLineStartCycle = targetCycle - (targetCycle % _bus.PalLineCycles); + if (cursor < targetLineStartCycle - 1) + { + return false; + } + return _bus.TrySkipRasterlineScheduleDrain(cursor, targetCycle, mask); } @@ -422,6 +435,52 @@ private bool IsMaskDrainedThrough(AmigaHardwareEventMask mask, long targetCycle) ((mask & AmigaHardwareEventMask.Blitter) == 0 || _blitterDrainCycle >= targetCycle); } + private long GetMaskDrainedThroughCycle(AmigaHardwareEventMask mask) + { + var cycle = long.MaxValue; + if ((mask & AmigaHardwareEventMask.Raster) != 0) + { + cycle = Math.Min(cycle, _rasterDrainCycle); + } + + if ((mask & AmigaHardwareEventMask.CiaTimers) != 0) + { + cycle = Math.Min(cycle, _ciaDrainCycle); + } + + if ((mask & AmigaHardwareEventMask.PaulaRegister) != 0) + { + cycle = Math.Min(cycle, _paulaDrainCycle); + } + + if ((mask & AmigaHardwareEventMask.DiskEvents) != 0) + { + cycle = Math.Min(cycle, _diskEventDrainCycle); + } + + if ((mask & AmigaHardwareEventMask.DiskCiaEvents) != 0) + { + cycle = Math.Min(cycle, _diskCiaDrainCycle); + } + + if ((mask & AmigaHardwareEventMask.DiskPassiveInput) != 0) + { + cycle = Math.Min(cycle, _diskPassiveDrainCycle); + } + + if ((mask & AmigaHardwareEventMask.Agnus) != 0) + { + cycle = Math.Min(cycle, _agnusDrainCycle); + } + + if ((mask & AmigaHardwareEventMask.Blitter) != 0) + { + cycle = Math.Min(cycle, _blitterDrainCycle); + } + + return cycle == long.MaxValue ? _lastDrainCycle : cycle; + } + private long GetNextEventCycle(long currentCycle, long targetCycle, AmigaHardwareEventMask mask) { var candidate = long.MaxValue; @@ -440,14 +499,15 @@ private long GetNextEventCycle(long currentCycle, long targetCycle, AmigaHardwar candidate = Min(candidate, GetNextPaulaEventCycle(currentCycle, targetCycle)); } - if ((mask & (AmigaHardwareEventMask.DiskEvents | AmigaHardwareEventMask.DiskPassiveInput)) != 0) + if ((mask & (AmigaHardwareEventMask.DiskEvents | AmigaHardwareEventMask.DiskPassiveInput)) != 0 && + HasDiskWakeSourceThrough(targetCycle, mask)) { candidate = Min(candidate, GetNextDiskEventCycle(currentCycle, targetCycle, mask)); } if ((mask & AmigaHardwareEventMask.Agnus) != 0) { - candidate = Min(candidate, _bus.GetNextAgnusEventCycle(currentCycle, targetCycle)); + candidate = Min(candidate, GetNextAgnusEventCycle(currentCycle, targetCycle, mask)); } if ((mask & AmigaHardwareEventMask.Blitter) != 0) @@ -483,6 +543,7 @@ private void ProcessEventsAt(long cycle, AmigaHardwareEventMask mask) } if ((mask & (AmigaHardwareEventMask.DiskEvents | AmigaHardwareEventMask.DiskPassiveInput)) != 0 && + HasDiskWakeSourceThrough(cycle, mask) && HasDiskWorkThrough(cycle, mask)) { if ((mask & (AmigaHardwareEventMask.ForceCatchUp | AmigaHardwareEventMask.DiskPassiveInput)) != 0) @@ -498,7 +559,7 @@ private void ProcessEventsAt(long cycle, AmigaHardwareEventMask mask) } if ((mask & AmigaHardwareEventMask.Agnus) != 0 && - _bus.GetNextAgnusEventCycle(cycle, cycle) <= cycle) + GetNextAgnusEventCycle(cycle, cycle, mask) <= cycle) { _bus.AdvanceAgnusCoreTo(cycle); _agnusEvents++; @@ -536,7 +597,8 @@ private void ProcessTargetCatchUp(long targetCycle, AmigaHardwareEventMask mask, } if ((mask & (AmigaHardwareEventMask.DiskEvents | AmigaHardwareEventMask.DiskPassiveInput)) != 0 && - (forceCatchUp || HasDiskWorkThrough(targetCycle, mask))) + (forceCatchUp || + (HasDiskWakeSourceThrough(targetCycle, mask) && HasDiskWorkThrough(targetCycle, mask)))) { if (forceCatchUp || (mask & AmigaHardwareEventMask.DiskPassiveInput) != 0) { @@ -549,7 +611,7 @@ private void ProcessTargetCatchUp(long targetCycle, AmigaHardwareEventMask mask, } if ((mask & AmigaHardwareEventMask.DiskCiaEvents) != 0 && - (forceCatchUp || _bus.Disk.HasCiaEventThrough(targetCycle))) + (forceCatchUp || (_bus.Disk.HasCiaWakeSource() && _bus.Disk.HasCiaEventThrough(targetCycle)))) { _bus.Disk.AdvanceCiaEventsTo(targetCycle); } @@ -571,6 +633,7 @@ private bool HasSameCycleWork(long cycle, AmigaHardwareEventMask mask) } return (mask & (AmigaHardwareEventMask.DiskEvents | AmigaHardwareEventMask.DiskPassiveInput)) != 0 && + HasDiskWakeSourceThrough(cycle, mask) && HasDiskWorkThrough(cycle, mask); } @@ -586,6 +649,11 @@ private long GetNextPaulaEventCycle(long currentCycle, long targetCycle) private long GetNextDiskEventCycle(long currentCycle, long targetCycle, AmigaHardwareEventMask mask) { + if (!HasDiskWakeSourceThrough(targetCycle, mask)) + { + return long.MaxValue; + } + if (HasDiskWorkThrough(currentCycle, mask)) { return currentCycle; @@ -611,6 +679,13 @@ private long GetNextDiskEventCycle(long currentCycle, long targetCycle, AmigaHar return _bus.Disk.GetNextEventWakeCandidateCycle(currentCycle, targetCycle, includeActiveDmaProgress) ?? long.MaxValue; } + private long GetNextAgnusEventCycle(long currentCycle, long targetCycle, AmigaHardwareEventMask mask) + { + return (mask & AmigaHardwareEventMask.CpuBoundary) != 0 + ? _bus.GetNextCpuVisibleAgnusEventCycle(currentCycle, targetCycle) + : _bus.GetNextAgnusEventCycle(currentCycle, targetCycle); + } + private bool HasDiskWorkThrough(long cycle, AmigaHardwareEventMask mask) { return (mask & AmigaHardwareEventMask.DiskPassiveInput) != 0 @@ -622,6 +697,18 @@ private bool HasDiskWorkThrough(long cycle, AmigaHardwareEventMask mask) includeActiveDmaProgress: (mask & AmigaHardwareEventMask.DiskRegisterSample) != 0); } + private bool HasDiskWakeSourceThrough(long targetCycle, AmigaHardwareEventMask mask) + { + var includePassiveInput = (mask & AmigaHardwareEventMask.DiskPassiveInput) != 0; + var includeEvents = (mask & AmigaHardwareEventMask.DiskEvents) != 0; + return (includePassiveInput || includeEvents) && + _bus.Disk.HasSchedulerWakeSourceThrough( + targetCycle, + includePassiveInput, + includeEvents, + includeActiveDmaProgress: (mask & AmigaHardwareEventMask.DiskRegisterSample) != 0); + } + private static AmigaHardwareEventMask GetCpuAccessMask( AmigaBusAccessTarget target, uint address, @@ -642,8 +729,12 @@ private static AmigaHardwareEventMask GetCpuAccessMask( if (target == AmigaBusAccessTarget.ChipRam || target == AmigaBusAccessTarget.ExpansionRam || - target == AmigaBusAccessTarget.RealTimeClock || - target == AmigaBusAccessTarget.CustomRegisters) + target == AmigaBusAccessTarget.RealTimeClock) + { + return SlotContendedMemoryAccessMask; + } + + if (target == AmigaBusAccessTarget.CustomRegisters) { return AmigaHardwareEventMask.All; } diff --git a/CopperMod.Amiga/CopperMod.Amiga.csproj b/CopperMod.Amiga/CopperMod.Amiga.csproj index aa8cba1..4042252 100644 --- a/CopperMod.Amiga/CopperMod.Amiga.csproj +++ b/CopperMod.Amiga/CopperMod.Amiga.csproj @@ -6,7 +6,7 @@ true CopperMod.Amiga CopperMod.Amiga - CopperMod contributors + Ilkka Lehtoranta Internal Amiga hardware emulation core layered on Copper68k for CUST playback and emulator hosts. 1.1.0 MIT diff --git a/CopperMod.Amiga/OcsDisplay.cs b/CopperMod.Amiga/OcsDisplay.cs index 6bad378..94f246c 100644 --- a/CopperMod.Amiga/OcsDisplay.cs +++ b/CopperMod.Amiga/OcsDisplay.cs @@ -168,6 +168,7 @@ internal sealed class OcsDisplay private readonly LiveRasterlinePlanEvent[] _predictedRasterlinePlanEvents = new LiveRasterlinePlanEvent[LowResOutputHeight * MaxLiveRasterlinePlanEvents]; private readonly int[] _predictedRasterlinePlanEventCounts = new int[LowResOutputHeight]; private readonly LiveRasterlinePredictionStatus[] _predictedRasterlinePlanStatuses = new LiveRasterlinePredictionStatus[LowResOutputHeight]; + private readonly LiveRasterlineDmaDescriptor[] _liveRasterlineDmaDescriptors = new LiveRasterlineDmaDescriptor[LowResOutputHeight]; private DisplayFrameTimeline _displayTimeline = new DisplayFrameTimeline(); private DisplayFrameTimeline _archivedDisplayTimeline = new DisplayFrameTimeline(); private readonly ushort[] _archivedPaletteSnapshotColors = new ushort[MaxLivePaletteSnapshots * 32]; @@ -286,6 +287,13 @@ internal sealed class OcsDisplay private int _predictedRasterlinePlanUnsupportedSpriteLines; private int _predictedRasterlinePlanUnsupportedInvalidStateLines; private int _predictedRasterlinePlanUnsupportedOverflowLines; + private int _liveRasterlineDescriptorBuilds; + private int _liveRasterlineDescriptorReplayAttempts; + private int _liveRasterlineDescriptorReplayedRows; + private int _liveRasterlineDescriptorFallbackRows; + private int _liveRasterlineDescriptorBitplaneRows; + private int _liveRasterlineDescriptorSpriteRows; + private int _liveRasterlineDescriptorMismatches; public OcsDisplay(AmigaBus bus, bool liveDmaEnabled = true) { @@ -660,6 +668,13 @@ public OcsDisplaySnapshot CaptureSnapshot() _predictedRasterlinePlanUnsupportedSpriteLines, _predictedRasterlinePlanUnsupportedInvalidStateLines, _predictedRasterlinePlanUnsupportedOverflowLines, + _liveRasterlineDescriptorBuilds, + _liveRasterlineDescriptorReplayAttempts, + _liveRasterlineDescriptorReplayedRows, + _liveRasterlineDescriptorFallbackRows, + _liveRasterlineDescriptorBitplaneRows, + _liveRasterlineDescriptorSpriteRows, + _liveRasterlineDescriptorMismatches, _lastArchiveRejectFrameIncomplete, _lastArchiveRejectTimelineInvalid, _lastArchiveRejectUnsafeWrite, @@ -751,7 +766,7 @@ internal void ResetLiveDma() _liveCopperStepCount = 0; _livePendingWriteEventCount = 0; _liveFetchBatchWordCount = 0; - ResetLiveRasterlinePlan(); + ResetLiveRasterlinePlan(resetDescriptorCounters: true); _liveFirstDisplayDmaCycle = -1; _liveLastDisplayDmaCycle = -1; _liveCopper = new CopperPresentationState(_copperListPointer, 0); @@ -1680,7 +1695,7 @@ private long GetNextLivePendingWriteCycle() : long.MaxValue; } - private void ResetLiveRasterlinePlan() + private void ResetLiveRasterlinePlan(bool resetDescriptorCounters = false) { Array.Clear(_liveRasterlinePlanEventCounts); Array.Clear(_liveRasterlinePlanRowsTouched); @@ -1688,6 +1703,7 @@ private void ResetLiveRasterlinePlan() Array.Clear(_liveRasterlinePlanRowsOverflowed); Array.Clear(_predictedRasterlinePlanEventCounts); Array.Clear(_predictedRasterlinePlanStatuses); + Array.Clear(_liveRasterlineDmaDescriptors); _liveRasterlinePlanRow = -1; _liveRasterlinePlanLineStartCycle = 0; _liveRasterlinePlanLineStopCycle = 0; @@ -1716,6 +1732,16 @@ private void ResetLiveRasterlinePlan() _predictedRasterlinePlanUnsupportedSpriteLines = 0; _predictedRasterlinePlanUnsupportedInvalidStateLines = 0; _predictedRasterlinePlanUnsupportedOverflowLines = 0; + if (resetDescriptorCounters) + { + _liveRasterlineDescriptorBuilds = 0; + _liveRasterlineDescriptorReplayAttempts = 0; + _liveRasterlineDescriptorReplayedRows = 0; + _liveRasterlineDescriptorFallbackRows = 0; + _liveRasterlineDescriptorBitplaneRows = 0; + _liveRasterlineDescriptorSpriteRows = 0; + _liveRasterlineDescriptorMismatches = 0; + } } private bool TryBeginLiveRasterlinePlanEvent(long cycle, int expectedRow) @@ -1806,9 +1832,7 @@ private void RecordLiveRasterlinePlanEvent( } else if (kind == LiveRasterlinePlanEventKind.SpriteFetchBatch) { - MarkPredictedRasterlinePlanUnsupported( - _liveRasterlinePlanRow, - LiveRasterlinePredictionStatus.UnsupportedSprite); + TryAppendRecordedSpriteEventToPendingDescriptor(_liveRasterlinePlanRow, kind, cycle, batchStopCycle, cursorA, cursorB, cursorC); } if (_liveRasterlinePlanLineEventCount >= MaxLiveRasterlinePlanEvents) @@ -1871,12 +1895,6 @@ private void TryBuildPredictedRasterlinePlanForCapturedLine(int row) return; } - if (IsSpriteDmaEnabled()) - { - MarkPredictedRasterlinePlanUnsupported(row, LiveRasterlinePredictionStatus.UnsupportedSprite); - return; - } - _predictedRasterlinePlanStatuses[row] = LiveRasterlinePredictionStatus.PendingValidation; _predictedRasterlinePlanEventCounts[row] = 0; if (!TryAppendPredictedRasterlinePlanEvent( @@ -1895,16 +1913,57 @@ private void TryBuildPredictedRasterlinePlanForCapturedLine(int row) } var state = _liveLineStates[row]; - if (state.PlaneCount <= 0 || - state.FetchWords <= 0 || - !state.DisplayWindowVerticallyOpen || - !IsBitplaneDmaEnabled(state.Dmacon)) + var hasBitplaneFetches = + state.PlaneCount > 0 && + state.FetchWords > 0 && + state.DisplayWindowVerticallyOpen && + IsBitplaneDmaEnabled(state.Dmacon); + var hasSpriteSlots = IsSpriteDmaEnabled(); + _liveRasterlineDmaDescriptors[row] = new LiveRasterlineDmaDescriptor( + _liveGeneration, + row, + lineStart, + lineStop, + state.DisplayWindowVerticallyOpen, + state.Bplcon0, + state.Bplcon1, + state.Bplcon2, + state.Dmacon, + state.Bpl1Mod, + state.Bpl2Mod, + state.PlaneCount, + state.FetchWords, + state.DataFetchStart, + state.FetchSlotStride, + state.PlaneHasRowMask, + state.BitplaneRowAddresses[0], + state.BitplaneRowAddresses[1], + state.BitplaneRowAddresses[2], + state.BitplaneRowAddresses[3], + state.BitplaneRowAddresses[4], + state.BitplaneRowAddresses[5], + hasBitplaneFetches, + hasSpriteSlots); + _liveRasterlineDescriptorBuilds++; + if (hasBitplaneFetches) + { + _liveRasterlineDescriptorBitplaneRows++; + } + + if (hasSpriteSlots) + { + _liveRasterlineDescriptorSpriteRows++; + } + + if (!hasBitplaneFetches) { return; } var firstFetchCycle = GetFirstLiveBitplaneFetchCycleForRendering(row, state); - if (firstFetchCycle == long.MaxValue || firstFetchCycle > lineStop) + if (firstFetchCycle == long.MaxValue || + firstFetchCycle > lineStop || + !TryGetFirstLiveBitplaneFetchCursor(state, out var firstPlane, out var firstSlot)) { return; } @@ -1916,14 +1975,51 @@ private void TryBuildPredictedRasterlinePlanForCapturedLine(int row) firstFetchCycle, row, lineStop, + firstPlane, 0, - 0, - 0))) + firstSlot))) { MarkPredictedRasterlinePlanUnsupported(row, LiveRasterlinePredictionStatus.UnsupportedOverflow); } } + private static bool TryGetFirstLiveBitplaneFetchCursor(LiveLineState state, out int plane, out int slot) + { + plane = 0; + slot = 0; + var planeCount = Math.Max(0, state.PlaneCount); + for (; slot < state.FetchSlotStride; slot++) + { + if (TryGetBitplanePlaneForFetchSlot(slot, planeCount, state.FetchSlotStride, out plane)) + { + return true; + } + } + + return false; + } + + private void TryAppendRecordedSpriteEventToPendingDescriptor( + int row, + LiveRasterlinePlanEventKind kind, + long cycle, + long batchStopCycle, + int cursorA, + int cursorB, + int cursorC) + { + if ((uint)row >= (uint)LowResOutputHeight || + _predictedRasterlinePlanStatuses[row] != LiveRasterlinePredictionStatus.PendingValidation || + !_liveRasterlineDmaDescriptors[row].IsValid(_liveGeneration, row)) + { + return; + } + + _ = TryAppendPredictedRasterlinePlanEvent( + row, + new LiveRasterlinePlanEvent(kind, cycle, row, batchStopCycle, cursorA, cursorB, cursorC)); + } + private bool TryAppendPredictedRasterlinePlanEvent(int row, LiveRasterlinePlanEvent planEvent) { if ((uint)row >= (uint)LowResOutputHeight) @@ -1937,12 +2033,44 @@ private bool TryAppendPredictedRasterlinePlanEvent(int row, LiveRasterlinePlanEv return false; } - _predictedRasterlinePlanEvents[(row * MaxLiveRasterlinePlanEvents) + count] = planEvent; + var baseIndex = row * MaxLiveRasterlinePlanEvents; + var insertIndex = count; + while (insertIndex > 0 && + IsRasterlinePlanEventAfter(_predictedRasterlinePlanEvents[baseIndex + insertIndex - 1], planEvent)) + { + _predictedRasterlinePlanEvents[baseIndex + insertIndex] = _predictedRasterlinePlanEvents[baseIndex + insertIndex - 1]; + insertIndex--; + } + + _predictedRasterlinePlanEvents[baseIndex + insertIndex] = planEvent; _predictedRasterlinePlanEventCounts[row] = count + 1; _predictedRasterlinePlanEventTotal++; return true; } + private static bool IsRasterlinePlanEventAfter(LiveRasterlinePlanEvent left, LiveRasterlinePlanEvent right) + { + if (left.Cycle != right.Cycle) + { + return left.Cycle > right.Cycle; + } + + return GetRasterlinePlanEventOrder(left.Kind) > GetRasterlinePlanEventOrder(right.Kind); + } + + private static int GetRasterlinePlanEventOrder(LiveRasterlinePlanEventKind kind) + { + return kind switch + { + LiveRasterlinePlanEventKind.PendingWriteOrCopper => 0, + LiveRasterlinePlanEventKind.CopperBarrier => 1, + LiveRasterlinePlanEventKind.LineStateCapture => 2, + LiveRasterlinePlanEventKind.SpriteFetchBatch => 3, + LiveRasterlinePlanEventKind.BitplaneFetchBatch => 4, + _ => 5 + }; + } + private void MarkPredictedRasterlinePlanUnsupported(int row, LiveRasterlinePredictionStatus status) { if ((uint)row >= (uint)LowResOutputHeight) @@ -1958,6 +2086,7 @@ private void MarkPredictedRasterlinePlanUnsupported(int row, LiveRasterlinePredi _predictedRasterlinePlanStatuses[row] = status; _predictedRasterlinePlanEventCounts[row] = 0; + _liveRasterlineDmaDescriptors[row] = default; } private void ValidatePredictedRasterlinePlan(int row) @@ -1990,6 +2119,7 @@ private void ValidatePredictedRasterlinePlan(int row) { _predictedRasterlinePlanStatuses[row] = LiveRasterlinePredictionStatus.Mismatched; _predictedRasterlinePlanMismatchedLines++; + _liveRasterlineDescriptorMismatches++; } } @@ -2009,7 +2139,11 @@ private bool DoesPredictedRasterlinePlanMatchRecorded(int row) var actual = _liveRasterlinePlanEvents[baseIndex + i]; if (expected.Kind != actual.Kind || expected.Cycle != actual.Cycle || - expected.Row != actual.Row) + expected.Row != actual.Row || + expected.BatchStopCycle != actual.BatchStopCycle || + expected.CursorA != actual.CursorA || + expected.CursorB != actual.CursorB || + expected.CursorC != actual.CursorC) { return false; } @@ -2233,6 +2367,17 @@ private void AdvanceLiveDmaWithinFrame(long targetCycle, bool includeCopper) } } + if (TryReplayLiveRasterlineDescriptorTo( + targetCycle, + includeCopper, + nextLineStateCycle, + nextBitplaneFetchCycle, + nextSpriteFetchCycle, + nextPendingWriteCycle)) + { + continue; + } + if (nextCycle > targetCycle) { break; @@ -2371,6 +2516,256 @@ private long GetLiveDmaBatchStopCycle(long targetCycle, long nextLineStateCycle, return stopCycle; } + private bool TryReplayLiveRasterlineDescriptorTo( + long targetCycle, + bool includeCopper, + long nextLineStateCycle, + long nextBitplaneFetchCycle, + long nextSpriteFetchCycle, + long nextPendingWriteCycle) + { + var nextReplayCycle = Math.Min(nextBitplaneFetchCycle, nextSpriteFetchCycle); + if (nextReplayCycle == long.MaxValue || + nextReplayCycle > targetCycle || + nextLineStateCycle <= nextReplayCycle || + nextPendingWriteCycle <= nextReplayCycle) + { + return false; + } + + if (IsLiveCopperDmaEnabled()) + { + return false; + } + + if (!includeCopper && GetNextLiveCopperBarrierCycle() <= nextReplayCycle) + { + return false; + } + + var row = nextBitplaneFetchCycle <= nextSpriteFetchCycle + ? _liveNextFetchRow + : _liveNextSpriteRow; + if (!TryGetLiveRasterlineDmaDescriptor(row, out var descriptor)) + { + return false; + } + + var replayStopCycle = Math.Min( + descriptor.LineStopCycle, + GetLiveDmaBatchStopCycle(targetCycle, nextLineStateCycle, includeCopper)); + if (nextReplayCycle > replayStopCycle || + HasPendingWriteInCycleRange(Math.Max(_liveCycle, descriptor.LineStartCycle), replayStopCycle)) + { + _liveRasterlineDescriptorFallbackRows++; + return false; + } + + if (nextSpriteFetchCycle <= nextBitplaneFetchCycle) + { + if (!descriptor.HasSpriteSlots) + { + _liveRasterlineDescriptorFallbackRows++; + return false; + } + } + else if (!descriptor.HasBitplaneFetches) + { + _liveRasterlineDescriptorFallbackRows++; + return false; + } + + _liveRasterlineDescriptorReplayAttempts++; + AdvanceLiveDisplayStateTo(nextReplayCycle, includeCopper); + var replayed = false; + if (nextSpriteFetchCycle <= nextBitplaneFetchCycle) + { + RecordLiveRasterlinePlanEvent( + LiveRasterlinePlanEventKind.SpriteFetchBatch, + nextSpriteFetchCycle, + _liveNextSpriteRow, + replayStopCycle, + _liveNextSpriteIndex, + _liveNextSpriteWord, + 0); + replayed = ReplayLiveRasterlineDescriptorSpriteBatch(descriptor, replayStopCycle); + } + else + { + RecordLiveRasterlinePlanEvent( + LiveRasterlinePlanEventKind.BitplaneFetchBatch, + nextBitplaneFetchCycle, + _liveNextFetchRow, + replayStopCycle, + _liveNextFetchPlane, + _liveNextFetchWord, + _liveNextFetchSlot); + replayed = ReplayLiveRasterlineDescriptorBitplaneBatch(descriptor, replayStopCycle); + } + + if (replayed) + { + _liveRasterlineDescriptorReplayedRows++; + return true; + } + + _liveRasterlineDescriptorFallbackRows++; + return false; + } + + private bool TryGetLiveRasterlineDmaDescriptor(int row, out LiveRasterlineDmaDescriptor descriptor) + { + descriptor = default; + if ((uint)row >= (uint)LowResOutputHeight) + { + return false; + } + + descriptor = _liveRasterlineDmaDescriptors[row]; + return descriptor.IsValid(_liveGeneration, row) && + IsLiveLineValid(row) && + DoesLiveLineStateMatchDescriptor(row, descriptor); + } + + private bool DoesLiveLineStateMatchDescriptor(int row, LiveRasterlineDmaDescriptor descriptor) + { + var state = _liveLineStates[row]; + if (state.LineStartCycle != descriptor.LineStartCycle || + state.DisplayWindowVerticallyOpen != descriptor.DisplayWindowVerticallyOpen || + state.Bplcon0 != descriptor.Bplcon0 || + state.Bplcon1 != descriptor.Bplcon1 || + state.Bplcon2 != descriptor.Bplcon2 || + state.Dmacon != descriptor.Dmacon || + state.Bpl1Mod != descriptor.Bpl1Mod || + state.Bpl2Mod != descriptor.Bpl2Mod || + state.PlaneCount != descriptor.PlaneCount || + state.FetchWords != descriptor.FetchWords || + state.DataFetchStart != descriptor.DataFetchStart || + state.FetchSlotStride != descriptor.FetchSlotStride || + state.PlaneHasRowMask != descriptor.PlaneHasRowMask) + { + return false; + } + + for (var plane = 0; plane < LiveBitplanePlaneCount; plane++) + { + if (state.BitplaneRowAddresses[plane] != descriptor.GetBitplaneRowAddress(plane)) + { + return false; + } + } + + return true; + } + + private bool ReplayLiveRasterlineDescriptorBitplaneBatch( + LiveRasterlineDmaDescriptor descriptor, + long stopCycle) + { + var captured = false; + while (_liveNextFetchRow == descriptor.Row) + { + if (!TryGetNextDescriptorBitplaneFetch( + descriptor, + out var fetchCycle, + out var plane, + out var word, + out var slot) || + fetchCycle > stopCycle) + { + return captured; + } + + _liveNextFetchPlane = plane; + _liveNextFetchWord = word; + _liveNextFetchSlot = slot; + CaptureLiveBitplaneFetch(descriptor.Row, plane, word, fetchCycle, _liveLineStates[descriptor.Row]); + AdvanceLiveFetchCursor(); + captured = true; + } + + return captured; + } + + private bool TryGetNextDescriptorBitplaneFetch( + LiveRasterlineDmaDescriptor descriptor, + out long fetchCycle, + out int plane, + out int word, + out int slot) + { + fetchCycle = long.MaxValue; + plane = 0; + word = _liveNextFetchWord; + slot = _liveNextFetchSlot; + if (!descriptor.HasBitplaneFetches || + _liveNextFetchRow != descriptor.Row || + word >= descriptor.FetchWords) + { + return false; + } + + var planeCount = Math.Max(0, descriptor.PlaneCount); + while (word < descriptor.FetchWords) + { + while (slot < descriptor.FetchSlotStride) + { + if (TryGetBitplanePlaneForFetchSlot(slot, planeCount, descriptor.FetchSlotStride, out plane)) + { + var fetchHorizontal = descriptor.DataFetchStart + (word * descriptor.FetchSlotStride) + slot; + fetchCycle = AgnusChipSlotScheduler.AlignToSlot( + descriptor.LineStartCycle + ((long)fetchHorizontal * CopperHpCycles)); + return true; + } + + slot++; + } + + slot = 0; + word++; + } + + return false; + } + + private bool ReplayLiveRasterlineDescriptorSpriteBatch( + LiveRasterlineDmaDescriptor descriptor, + long stopCycle) + { + if (!descriptor.HasSpriteSlots) + { + return false; + } + + var captured = false; + while (_liveNextSpriteRow == descriptor.Row) + { + SkipLiveSpriteSlotsWithoutFetches(); + if (_liveNextSpriteRow != descriptor.Row || + !IsLiveLineValid(_liveNextSpriteRow) || + !IsSpriteDmaEnabled()) + { + return captured; + } + + var fetchCycle = GetNextLiveSpriteFetchCycle(); + if (fetchCycle > stopCycle) + { + return captured; + } + + _ = TryCaptureKnownLiveSpriteDmaSlot( + _liveNextSpriteRow, + _liveNextSpriteIndex, + _liveNextSpriteWord, + fetchCycle); + AdvanceLiveSpriteFetchCursor(); + captured = true; + } + + return captured; + } + private long GetNextLiveCopperBarrierCycle() { var frameStopCycle = _liveFrameStartCycle + PalFrameCycles; @@ -11427,6 +11822,126 @@ public LiveRasterlinePlanEvent( public int CursorC { get; } } + + private readonly struct LiveRasterlineDmaDescriptor + { + public LiveRasterlineDmaDescriptor( + int generation, + int row, + long lineStartCycle, + long lineStopCycle, + bool displayWindowVerticallyOpen, + ushort bplcon0, + ushort bplcon1, + ushort bplcon2, + ushort dmacon, + short bpl1Mod, + short bpl2Mod, + int planeCount, + int fetchWords, + int dataFetchStart, + int fetchSlotStride, + byte planeHasRowMask, + uint bitplaneRowAddress0, + uint bitplaneRowAddress1, + uint bitplaneRowAddress2, + uint bitplaneRowAddress3, + uint bitplaneRowAddress4, + uint bitplaneRowAddress5, + bool hasBitplaneFetches, + bool hasSpriteSlots) + { + Generation = generation; + Row = row; + LineStartCycle = lineStartCycle; + LineStopCycle = lineStopCycle; + DisplayWindowVerticallyOpen = displayWindowVerticallyOpen; + Bplcon0 = bplcon0; + Bplcon1 = bplcon1; + Bplcon2 = bplcon2; + Dmacon = dmacon; + Bpl1Mod = bpl1Mod; + Bpl2Mod = bpl2Mod; + PlaneCount = planeCount; + FetchWords = fetchWords; + DataFetchStart = dataFetchStart; + FetchSlotStride = fetchSlotStride; + PlaneHasRowMask = planeHasRowMask; + BitplaneRowAddress0 = bitplaneRowAddress0; + BitplaneRowAddress1 = bitplaneRowAddress1; + BitplaneRowAddress2 = bitplaneRowAddress2; + BitplaneRowAddress3 = bitplaneRowAddress3; + BitplaneRowAddress4 = bitplaneRowAddress4; + BitplaneRowAddress5 = bitplaneRowAddress5; + HasBitplaneFetches = hasBitplaneFetches; + HasSpriteSlots = hasSpriteSlots; + } + + public int Generation { get; } + + public int Row { get; } + + public long LineStartCycle { get; } + + public long LineStopCycle { get; } + + public bool DisplayWindowVerticallyOpen { get; } + + public ushort Bplcon0 { get; } + + public ushort Bplcon1 { get; } + + public ushort Bplcon2 { get; } + + public ushort Dmacon { get; } + + public short Bpl1Mod { get; } + + public short Bpl2Mod { get; } + + public int PlaneCount { get; } + + public int FetchWords { get; } + + public int DataFetchStart { get; } + + public int FetchSlotStride { get; } + + public byte PlaneHasRowMask { get; } + + public uint BitplaneRowAddress0 { get; } + + public uint BitplaneRowAddress1 { get; } + + public uint BitplaneRowAddress2 { get; } + + public uint BitplaneRowAddress3 { get; } + + public uint BitplaneRowAddress4 { get; } + + public uint BitplaneRowAddress5 { get; } + + public bool HasBitplaneFetches { get; } + + public bool HasSpriteSlots { get; } + + public bool IsValid(int generation, int row) + => Generation == generation && Row == row; + + public uint GetBitplaneRowAddress(int plane) + { + return plane switch + { + 0 => BitplaneRowAddress0, + 1 => BitplaneRowAddress1, + 2 => BitplaneRowAddress2, + 3 => BitplaneRowAddress3, + 4 => BitplaneRowAddress4, + 5 => BitplaneRowAddress5, + _ => 0 + }; + } + } } internal readonly struct OcsDisplaySnapshot @@ -11510,6 +12025,13 @@ public OcsDisplaySnapshot( int lastPredictedRasterlinePlanUnsupportedSpriteLines, int lastPredictedRasterlinePlanUnsupportedInvalidStateLines, int lastPredictedRasterlinePlanUnsupportedOverflowLines, + int lastRasterlineDescriptorBuilds, + int lastRasterlineDescriptorReplayAttempts, + int lastRasterlineDescriptorReplayedRows, + int lastRasterlineDescriptorFallbackRows, + int lastRasterlineDescriptorBitplaneRows, + int lastRasterlineDescriptorSpriteRows, + int lastRasterlineDescriptorMismatches, int lastArchiveRejectFrameIncomplete, int lastArchiveRejectTimelineInvalid, int lastArchiveRejectUnsafeWrite, @@ -11613,6 +12135,13 @@ public OcsDisplaySnapshot( LastPredictedRasterlinePlanUnsupportedSpriteLines = lastPredictedRasterlinePlanUnsupportedSpriteLines; LastPredictedRasterlinePlanUnsupportedInvalidStateLines = lastPredictedRasterlinePlanUnsupportedInvalidStateLines; LastPredictedRasterlinePlanUnsupportedOverflowLines = lastPredictedRasterlinePlanUnsupportedOverflowLines; + LastRasterlineDescriptorBuilds = lastRasterlineDescriptorBuilds; + LastRasterlineDescriptorReplayAttempts = lastRasterlineDescriptorReplayAttempts; + LastRasterlineDescriptorReplayedRows = lastRasterlineDescriptorReplayedRows; + LastRasterlineDescriptorFallbackRows = lastRasterlineDescriptorFallbackRows; + LastRasterlineDescriptorBitplaneRows = lastRasterlineDescriptorBitplaneRows; + LastRasterlineDescriptorSpriteRows = lastRasterlineDescriptorSpriteRows; + LastRasterlineDescriptorMismatches = lastRasterlineDescriptorMismatches; LastArchiveRejectFrameIncomplete = lastArchiveRejectFrameIncomplete; LastArchiveRejectTimelineInvalid = lastArchiveRejectTimelineInvalid; LastArchiveRejectUnsafeWrite = lastArchiveRejectUnsafeWrite; @@ -11795,6 +12324,20 @@ public OcsDisplaySnapshot( public int LastPredictedRasterlinePlanUnsupportedOverflowLines { get; } + public int LastRasterlineDescriptorBuilds { get; } + + public int LastRasterlineDescriptorReplayAttempts { get; } + + public int LastRasterlineDescriptorReplayedRows { get; } + + public int LastRasterlineDescriptorFallbackRows { get; } + + public int LastRasterlineDescriptorBitplaneRows { get; } + + public int LastRasterlineDescriptorSpriteRows { get; } + + public int LastRasterlineDescriptorMismatches { get; } + public int LastArchiveRejectFrameIncomplete { get; } public int LastArchiveRejectTimelineInvalid { get; } diff --git a/CopperMod.Amiga/Paula.cs b/CopperMod.Amiga/Paula.cs index ecbad0b..4fbff30 100644 --- a/CopperMod.Amiga/Paula.cs +++ b/CopperMod.Amiga/Paula.cs @@ -29,6 +29,9 @@ internal sealed class Paula private ushort _vhposr; private ushort _lastCpuActiveInterruptBits; private long _copperInterruptRecognitionCycle = long.MinValue; + private ulong _registerWakeVersion; + private ulong _registerWakeCandidateVersion = ulong.MaxValue; + private long _registerWakeCandidateCycle = long.MaxValue; private float[][]? _captureSamples; private int _captureFrameIndex; private int _captureSampleRate; @@ -75,6 +78,7 @@ public void Reset() _vhposr = 0; _lastCpuActiveInterruptBits = 0; _copperInterruptRecognitionCycle = long.MinValue; + InvalidateRegisterWakeCandidateCache(); _captureSamples = null; _captureFrameIndex = 0; _captureSampleRate = 0; @@ -178,6 +182,7 @@ public void ScheduleWrite(long cycle, ushort offset, ushort value) _pendingWrites.Insert(insertIndex, pending); PreserveTimelinePendingWriteIndex(_audioTimeline, insertIndex, cycle, PaulaTimelineKind.Audio, offset, value); PreserveTimelinePendingWriteIndex(_registerTimeline, insertIndex, cycle, PaulaTimelineKind.Register, offset, value); + InvalidateRegisterWakeCandidateCache(); } private void PreserveTimelinePendingWriteIndex( @@ -294,18 +299,10 @@ public int GetHighestCpuVisibleInterruptLevel(long cycle) return null; } - long? candidate = null; - if (_registerTimeline.PendingWriteIndex < _pendingWrites.Count) - { - candidate = MinWakeCandidate(candidate, _pendingWrites[_registerTimeline.PendingWriteIndex].Cycle); - } - - for (var i = 0; i < _registerTimeline.Channels.Length; i++) - { - candidate = MinWakeCandidate(candidate, _registerTimeline.Channels[i].GetNextWakeCandidateCycle()); - } - - return ClampWakeCandidate(candidate, currentCycle, targetCycle); + var candidate = GetRegisterWakeCandidateCycle(); + return candidate == long.MaxValue + ? null + : ClampWakeCandidate(candidate, currentCycle, targetCycle); } public PaulaChannelSnapshot GetChannelSnapshot(int channel) @@ -436,20 +433,7 @@ internal bool HasRegisterObservableWorkThrough(long targetCycle) return false; } - if (HasPendingWriteThrough(_registerTimeline, targetCycle)) - { - return true; - } - - foreach (var channel in _registerTimeline.Channels) - { - if (channel.GetNextWakeCandidateCycle() <= targetCycle) - { - return true; - } - } - - return false; + return GetRegisterWakeCandidateCycle() <= targetCycle; } private void AdvanceAudioTo(long targetCycle) @@ -477,6 +461,7 @@ private void AdvanceTimelineTo(PaulaTimelineState timeline, long targetCycle, Pa if (kind == PaulaTimelineKind.Register) { RefreshCpuInterruptVisibility(targetCycle); + InvalidateRegisterWakeCandidateCache(); } CompactPendingWrites(); @@ -502,6 +487,7 @@ private void ApplyRegisterWritesTo(long targetCycle) } RefreshCpuInterruptVisibility(targetCycle); + InvalidateRegisterWakeCandidateCache(); CompactPendingWrites(); CompactDmaFetches(); } @@ -911,6 +897,43 @@ private static long GetEffectivePeriod(int period) private static bool UsesAudioDmaRefillMinimum(AmigaBus bus) => bus.LiveAgnusDmaEnabled; + private void InvalidateRegisterWakeCandidateCache() + { + unchecked + { + _registerWakeVersion++; + } + + _registerWakeCandidateVersion = ulong.MaxValue; + } + + private long GetRegisterWakeCandidateCycle() + { + if (_registerWakeCandidateVersion == _registerWakeVersion) + { + return _registerWakeCandidateCycle; + } + + var candidate = long.MaxValue; + if (_registerTimeline.PendingWriteIndex < _pendingWrites.Count) + { + candidate = Math.Min(candidate, _pendingWrites[_registerTimeline.PendingWriteIndex].Cycle); + } + + for (var i = 0; i < _registerTimeline.Channels.Length; i++) + { + var channelCandidate = _registerTimeline.Channels[i].GetNextWakeCandidateCycle(); + if (channelCandidate.HasValue) + { + candidate = Math.Min(candidate, channelCandidate.Value); + } + } + + _registerWakeCandidateCycle = candidate; + _registerWakeCandidateVersion = _registerWakeVersion; + return candidate; + } + private static long? MinWakeCandidate(long? candidate, long? eventCycle) { if (!eventCycle.HasValue) diff --git a/CopperScreen.Benchmarks/Program.cs b/CopperScreen.Benchmarks/Program.cs index e59ab69..c1fb082 100644 --- a/CopperScreen.Benchmarks/Program.cs +++ b/CopperScreen.Benchmarks/Program.cs @@ -102,7 +102,7 @@ static BenchmarkRunResult RunBenchmark(BenchmarkWorkload workload, BenchmarkOpti GC.Collect(); var nominalFrameAudioMilliseconds = emulator.AudioFramesPerAppFrame(SampleRate) * 1000.0 / SampleRate; - var fakeQueuedAudioMilliseconds = nominalFrameAudioMilliseconds * 3.0; + var fakeQueuedAudioMilliseconds = nominalFrameAudioMilliseconds * 8.0; var fakeQueuedAudioLimitMilliseconds = nominalFrameAudioMilliseconds * 8.0; var fakeQueuedAudioMinMilliseconds = fakeQueuedAudioMilliseconds; var fakeQueuedAudioMaxMilliseconds = fakeQueuedAudioMilliseconds; @@ -110,6 +110,21 @@ static BenchmarkRunResult RunBenchmark(BenchmarkWorkload workload, BenchmarkOpti var activeAudioFrames = 0; var maxFrameMilliseconds = 0.0; var maxFrameSchedulerDrains = 0L; + var measuredDescriptorBuilds = 0; + var measuredDescriptorReplayAttempts = 0; + var measuredDescriptorReplayedRows = 0; + var measuredDescriptorFallbackRows = 0; + var measuredDescriptorBitplaneRows = 0; + var measuredDescriptorSpriteRows = 0; + var measuredDescriptorMismatches = 0; + var descriptorBeforeMeasured = GetDisplay(emulator).CaptureSnapshot(); + var previousDescriptorBuilds = descriptorBeforeMeasured.LastRasterlineDescriptorBuilds; + var previousDescriptorReplayAttempts = descriptorBeforeMeasured.LastRasterlineDescriptorReplayAttempts; + var previousDescriptorReplayedRows = descriptorBeforeMeasured.LastRasterlineDescriptorReplayedRows; + var previousDescriptorFallbackRows = descriptorBeforeMeasured.LastRasterlineDescriptorFallbackRows; + var previousDescriptorBitplaneRows = descriptorBeforeMeasured.LastRasterlineDescriptorBitplaneRows; + var previousDescriptorSpriteRows = descriptorBeforeMeasured.LastRasterlineDescriptorSpriteRows; + var previousDescriptorMismatches = descriptorBeforeMeasured.LastRasterlineDescriptorMismatches; var slowFramesOver20 = 0; var slowFramesOver33 = 0; var slowFramesOver40 = 0; @@ -122,6 +137,21 @@ static BenchmarkRunResult RunBenchmark(BenchmarkWorkload workload, BenchmarkOpti ApplyFrameActions(emulator, workload, options.WarmupFrames + frame); emulator.RenderNextFrame(); audioFrames = emulator.RenderAudio(audio, SampleRate, Channels); + var displayFrame = GetDisplay(emulator).CaptureSnapshot(); + measuredDescriptorBuilds += displayFrame.LastRasterlineDescriptorBuilds - previousDescriptorBuilds; + measuredDescriptorReplayAttempts += displayFrame.LastRasterlineDescriptorReplayAttempts - previousDescriptorReplayAttempts; + measuredDescriptorReplayedRows += displayFrame.LastRasterlineDescriptorReplayedRows - previousDescriptorReplayedRows; + measuredDescriptorFallbackRows += displayFrame.LastRasterlineDescriptorFallbackRows - previousDescriptorFallbackRows; + measuredDescriptorBitplaneRows += displayFrame.LastRasterlineDescriptorBitplaneRows - previousDescriptorBitplaneRows; + measuredDescriptorSpriteRows += displayFrame.LastRasterlineDescriptorSpriteRows - previousDescriptorSpriteRows; + measuredDescriptorMismatches += displayFrame.LastRasterlineDescriptorMismatches - previousDescriptorMismatches; + previousDescriptorBuilds = displayFrame.LastRasterlineDescriptorBuilds; + previousDescriptorReplayAttempts = displayFrame.LastRasterlineDescriptorReplayAttempts; + previousDescriptorReplayedRows = displayFrame.LastRasterlineDescriptorReplayedRows; + previousDescriptorFallbackRows = displayFrame.LastRasterlineDescriptorFallbackRows; + previousDescriptorBitplaneRows = displayFrame.LastRasterlineDescriptorBitplaneRows; + previousDescriptorSpriteRows = displayFrame.LastRasterlineDescriptorSpriteRows; + previousDescriptorMismatches = displayFrame.LastRasterlineDescriptorMismatches; var schedulerAfterFrame = CaptureHardwareSchedulerSnapshot(emulator); maxFrameSchedulerDrains = Math.Max( maxFrameSchedulerDrains, @@ -169,7 +199,16 @@ static BenchmarkRunResult RunBenchmark(BenchmarkWorkload workload, BenchmarkOpti var fps = options.MeasuredFrames / elapsed.TotalSeconds; var framebufferSummary = CaptureFramebufferSummary(emulator.Framebuffer); var audioSummary = CaptureAudioSummary(audio.AsSpan(0, Math.Min(audio.Length, audioFrames * Channels)), audioFrames); - var displaySummary = CaptureDisplaySummary(GetDisplay(emulator).CaptureSnapshot()); + var displaySummary = CaptureDisplaySummary(GetDisplay(emulator).CaptureSnapshot()) with + { + DescriptorBuilds = measuredDescriptorBuilds, + DescriptorReplayAttempts = measuredDescriptorReplayAttempts, + DescriptorReplayedRows = measuredDescriptorReplayedRows, + DescriptorFallbackRows = measuredDescriptorFallbackRows, + DescriptorBitplaneRows = measuredDescriptorBitplaneRows, + DescriptorSpriteRows = measuredDescriptorSpriteRows, + DescriptorMismatches = measuredDescriptorMismatches + }; var diskSummary = CaptureDiskSummary(emulator); var specializationSummary = CaptureSpecializationSummary(emulator); var schedulerSummary = CaptureHardwareSchedulerSnapshot(emulator); @@ -789,7 +828,21 @@ static DiskSummary CaptureDiskSummary(CopperScreenEmulator emulator) disk.SelectedDrive, disk.ActiveDma, disk.Dsklen, - disk.Dskbytr); + disk.Dskbytr, + disk.SchedulerCounters.NextWakeCandidateQueries, + disk.SchedulerCounters.NextEventWakeCandidateQueries, + disk.SchedulerCounters.HasWakeCandidateThroughQueries, + disk.SchedulerCounters.HasEventWakeCandidateThroughQueries, + disk.SchedulerCounters.RefreshNextIndexPulseQueries, + disk.SchedulerCounters.InputAdvanceCalls, + disk.SchedulerCounters.SchedulerGateTrue, + disk.SchedulerCounters.SchedulerGateFalse, + disk.SchedulerCounters.PendingDmaWakeSources, + disk.SchedulerCounters.ActiveDmaProgressWakeSources, + disk.SchedulerCounters.ActiveDmaCompletionWakeSources, + disk.SchedulerCounters.SyncCandidateWakeSources, + disk.SchedulerCounters.IndexPulseWakeSources, + disk.SchedulerCounters.PassiveByteReadyWakeSources); } static AmigaDiskTraceEvent[] CaptureDiskTrace(CopperScreenEmulator emulator) @@ -894,7 +947,14 @@ static DisplaySummary CaptureDisplaySummary(OcsDisplaySnapshot display) display.LastSpriteMaxY, display.LastBitplaneDmaFetches, display.LastSpriteDmaFetches, - display.LastMissedSpriteDmaSlots); + display.LastMissedSpriteDmaSlots, + display.LastRasterlineDescriptorBuilds, + display.LastRasterlineDescriptorReplayAttempts, + display.LastRasterlineDescriptorReplayedRows, + display.LastRasterlineDescriptorFallbackRows, + display.LastRasterlineDescriptorBitplaneRows, + display.LastRasterlineDescriptorSpriteRows, + display.LastRasterlineDescriptorMismatches); } static string FormatFramebufferSummary(FramebufferSummary summary) @@ -914,6 +974,8 @@ static string FormatDisplaySummary(DisplaySummary summary) return $"bpl={summary.BitplanePixels}:{summary.BitplaneMinX},{summary.BitplaneMinY}-{summary.BitplaneMaxX},{summary.BitplaneMaxY}," + $"spr={summary.SpritePixels}:{summary.SpriteMinX},{summary.SpriteMinY}-{summary.SpriteMaxX},{summary.SpriteMaxY}," + $"dma={summary.BitplaneDmaFetches}/{summary.SpriteDmaFetches},missedSpr={summary.MissedSpriteSlots}," + + $"desc={summary.DescriptorBuilds}/{summary.DescriptorReplayAttempts}/{summary.DescriptorReplayedRows}/{summary.DescriptorFallbackRows}," + + $"descRows={summary.DescriptorBitplaneRows}/{summary.DescriptorSpriteRows},descMis={summary.DescriptorMismatches}," + $"bplcon={summary.Bplcon0:X4}/{summary.Bplcon1:X4}/{summary.Bplcon2:X4}"; } @@ -921,7 +983,14 @@ static string FormatDiskSummary(DiskSummary summary) { return $"xfer={summary.TransferCount},words={summary.LastTransferWords},last=d{summary.LastTransferDrive} " + $"{summary.LastTransferCylinder}.{summary.LastTransferHead}@0x{summary.LastTransferAddress:X6}," + - $"selected={summary.SelectedDrive},active={summary.ActiveDma},dsklen=0x{summary.Dsklen:X4},bytr=0x{summary.Dskbytr:X4}"; + $"selected={summary.SelectedDrive},active={summary.ActiveDma},dsklen=0x{summary.Dsklen:X4},bytr=0x{summary.Dskbytr:X4}," + + $"sched=nw:{summary.DiskNextWakeCandidateQueries},ne:{summary.DiskNextEventWakeCandidateQueries}," + + $"hw:{summary.DiskHasWakeCandidateThroughQueries},he:{summary.DiskHasEventWakeCandidateThroughQueries}," + + $"idx:{summary.DiskRefreshNextIndexPulseQueries},in:{summary.DiskInputAdvanceCalls}," + + $"gate={summary.DiskSchedulerGateTrue}/{summary.DiskSchedulerGateFalse}," + + $"why=pd:{summary.DiskPendingDmaWakeSources},ap:{summary.DiskActiveDmaProgressWakeSources}," + + $"ac:{summary.DiskActiveDmaCompletionWakeSources},sy:{summary.DiskSyncCandidateWakeSources}," + + $"ix:{summary.DiskIndexPulseWakeSources},pb:{summary.DiskPassiveByteReadyWakeSources}"; } static string FormatSpecializationSummary(HardwareSpecializationSummary summary) @@ -1020,7 +1089,14 @@ internal readonly record struct DisplaySummary( int SpriteMaxY, int BitplaneDmaFetches, int SpriteDmaFetches, - int MissedSpriteSlots); + int MissedSpriteSlots, + int DescriptorBuilds, + int DescriptorReplayAttempts, + int DescriptorReplayedRows, + int DescriptorFallbackRows, + int DescriptorBitplaneRows, + int DescriptorSpriteRows, + int DescriptorMismatches); internal readonly record struct DiskSummary( int TransferCount, @@ -1032,7 +1108,21 @@ internal readonly record struct DiskSummary( int SelectedDrive, bool ActiveDma, ushort Dsklen, - ushort Dskbytr); + ushort Dskbytr, + long DiskNextWakeCandidateQueries, + long DiskNextEventWakeCandidateQueries, + long DiskHasWakeCandidateThroughQueries, + long DiskHasEventWakeCandidateThroughQueries, + long DiskRefreshNextIndexPulseQueries, + long DiskInputAdvanceCalls, + long DiskSchedulerGateTrue, + long DiskSchedulerGateFalse, + long DiskPendingDmaWakeSources, + long DiskActiveDmaProgressWakeSources, + long DiskActiveDmaCompletionWakeSources, + long DiskSyncCandidateWakeSources, + long DiskIndexPulseWakeSources, + long DiskPassiveByteReadyWakeSources); internal readonly record struct HardwareSpecializationSummary( long BlitterKernelHits, diff --git a/CopperScreen.Tests/CopperScreenBootTests.cs b/CopperScreen.Tests/CopperScreenBootTests.cs index 27305fb..62eacf1 100644 --- a/CopperScreen.Tests/CopperScreenBootTests.cs +++ b/CopperScreen.Tests/CopperScreenBootTests.cs @@ -1454,11 +1454,11 @@ public void CrackedDiskIntroPatchesTextFontFromKickstartShimWhenAvailable() [Fact] public void AudioCatchUpQueuesSeveralBuffersOnlyWhenAudioQueueIsCritical() { - Assert.Equal(3, MainWindow.CalculateFramesToRender(0, catchUpAudio: true)); - Assert.Equal(2, MainWindow.CalculateFramesToRender(1, catchUpAudio: true)); + Assert.Equal(5, MainWindow.CalculateFramesToRender(0, catchUpAudio: true)); + Assert.Equal(5, MainWindow.CalculateFramesToRender(1, catchUpAudio: true)); Assert.Equal(1, MainWindow.CalculateFramesToRender(2, catchUpAudio: true)); - Assert.Equal(0, MainWindow.CalculateFramesToRender(4, catchUpAudio: true)); - Assert.Equal(0, MainWindow.CalculateFramesToRender(5, catchUpAudio: true)); + Assert.Equal(1, MainWindow.CalculateFramesToRender(4, catchUpAudio: true)); + Assert.Equal(1, MainWindow.CalculateFramesToRender(5, catchUpAudio: true)); Assert.Equal(0, MainWindow.CalculateFramesToRender(8, catchUpAudio: true)); Assert.Equal(1, MainWindow.CalculateFramesToRender(0, catchUpAudio: false)); Assert.Equal(0, MainWindow.CalculateFramesToRender(8, catchUpAudio: false)); diff --git a/CopperScreen.Tests/CopperScreenRuntimeTests.cs b/CopperScreen.Tests/CopperScreenRuntimeTests.cs index e83b119..4d70909 100644 --- a/CopperScreen.Tests/CopperScreenRuntimeTests.cs +++ b/CopperScreen.Tests/CopperScreenRuntimeTests.cs @@ -83,9 +83,9 @@ public void AudioRefillKeepsTargetQueueDepth() Assert.True(SpinWait.SpinUntil(() => { DrainPresentationFrames(runtime, ref lastSeen); - return audio.QueuedBufferCount >= 3; + return audio.QueuedBufferCount >= 8; }, TimeSpan.FromSeconds(1))); - Assert.InRange(audio.SubmitCount, 3, 8); + Assert.InRange(audio.SubmitCount, 8, 16); } [Fact] @@ -398,7 +398,7 @@ public void HealthyAudioContinuesWhenTargetVideoQueueDepthIsReached() runtime.Start(); - Assert.True(SpinWait.SpinUntil(() => audio.SubmitCount > 0 && audio.QueuedBufferCount >= 3, TimeSpan.FromSeconds(1))); + Assert.True(SpinWait.SpinUntil(() => audio.SubmitCount > 0 && audio.QueuedBufferCount >= 8, TimeSpan.FromSeconds(1))); Assert.InRange(runtime.CurrentState.PresentationQueueDepth, 0, 2); } @@ -410,8 +410,8 @@ public void CriticalAudioCatchUpCollapsesStaleVideoQueueAfterRefill() runtime.Start(); - Assert.True(SpinWait.SpinUntil(() => audio.QueuedBufferCount >= 3, TimeSpan.FromSeconds(1))); - Assert.True(audio.SubmitCount >= 3); + Assert.True(SpinWait.SpinUntil(() => audio.QueuedBufferCount >= 8, TimeSpan.FromSeconds(1))); + Assert.True(audio.SubmitCount >= 8); Assert.InRange(runtime.CurrentState.PresentationQueueDepth, 0, 2); Assert.True(runtime.CurrentState.PresentationQueueFullThrottleCount > 0); } diff --git a/CopperScreen/CopperScreenRuntime.cs b/CopperScreen/CopperScreenRuntime.cs index 0997ea0..00ee94c 100644 --- a/CopperScreen/CopperScreenRuntime.cs +++ b/CopperScreen/CopperScreenRuntime.cs @@ -88,7 +88,7 @@ internal sealed class CopperScreenRuntime : IDisposable private const int AudioSampleRate = 44_100; private const int AudioChannels = 2; private const int AudioOutputBufferCount = 8; - private const int TargetQueuedAudioBuffers = 3; + private const int TargetQueuedAudioBuffers = 8; private const int CriticalQueuedAudioBuffers = 2; private const int MaxFramesPerTick = 5; private const int TargetQueuedPresentationFrames = 1;