Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CopperMod.Amiga.Tests/AmigaBitplaneConformanceMatrixTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ public void ExecutableBitplaneMatrixRowsPass(object rowObject)
case "cycle-exact fetch slot contention at DDF edges":
BitplaneFetchesUseDisplayDmaSlots();
break;
case "bitplane DMA latch is consumed after granted fetch":
BitplaneDmaLatchIsConsumedAfterGrantedFetch();
break;
case "HRM lowres bitplane slot order":
BitplaneFetchesUseHrmLowResSlotOrder();
break;
Expand Down Expand Up @@ -186,6 +189,7 @@ public void PendingBitplaneRowsDocumentTheirReason(object rowObject)
Executable("palette", "live DMA capture preserves same-line Copper palette spans"),
Executable("dma-control", "DMAEN and BPLEN gate timed bitplane fetches"),
Executable("fetch-window", "cycle-exact fetch slot contention at DDF edges"),
Executable("fetch-window", "bitplane DMA latch is consumed after granted fetch"),
Executable("fetch-window", "HRM lowres bitplane slot order"),
Executable("fetch-window", "HRM hires bitplane slot order"),
Executable("dma-control", "bitplane DMA starvation of late sprite slots"),
Expand Down Expand Up @@ -1056,6 +1060,31 @@ private static void BitplaneFetchesUseDisplayDmaSlots()
Assert.Equal(0xFFFF0000u, Pixel(frame, StandardX, StandardY));
}

private static void BitplaneDmaLatchIsConsumedAfterGrantedFetch()
{
var bus = CreateDisplayBus();
SetBitplanePointer(bus, 0, 0x1000);
for (var offset = 0; offset < 0x400; offset += 2)
{
BigEndian.WriteUInt16(bus.ChipRam, 0x1000 + offset, 0x8000);
}

bus.WriteWord(0x00DFF092, 0x0038);
bus.WriteWord(0x00DFF094, 0x0038);
bus.WriteWord(0x00DFF096, 0x8300);
bus.WriteWord(0x00DFF100, 0x1000);
var frame = new uint[AmigaConstants.PalLowResWidth * AmigaConstants.PalLowResHeight];

bus.Display.RenderFrame(frame, 0, FrameCycles());

var latch = GetPrivateField<object>(bus.Display, "_bitplaneDmaReadLatch");
var hasValue = (bool)latch.GetType().GetProperty("HasValue")!.GetValue(latch)!;
var snapshot = bus.Display.CaptureSnapshot();
Assert.False(hasValue);
Assert.True(snapshot.LastBitplaneDmaFetches > 0);
Assert.Equal(0xFFFF0000u, Pixel(frame, StandardX, StandardY));
}

private static void LowResDdfStopSecondHalfIncludesContainingFetchBlock()
{
var bus = CreateDisplayBus();
Expand Down Expand Up @@ -1563,6 +1592,17 @@ private static void SetPrivateField(object target, string name, object value)
field.SetValue(target, value);
}

private static T GetPrivateField<T>(object target, string name)
{
var field = target.GetType().GetField(
name,
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic);
Assert.NotNull(field);
return Assert.IsAssignableFrom<T>(field.GetValue(target));
}

private static int OutputXForHorizontal(int horizontal)
{
return Math.Clamp((horizontal - 0x38) * 2, 0, AmigaConstants.PalLowResWidth);
Expand Down
100 changes: 100 additions & 0 deletions CopperMod.Amiga.Tests/AmigaBlitterConformanceMatrixTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,55 @@ public void BlitterAreaModeUsesHrmNoContentionSlotTiming(object rowObject)
Assert.Equal(expectedCompletion - AgnusChipSlotScheduler.SlotCycles, blitterDma[^1].GrantedCycle);
}

[Fact]
public void BlitterAreaDmaLoadsSourceLatchesBeforeDestinationCommit()
{
var bus = new AmigaBus(captureBusAccesses: true);
WriteWord(bus, SourceA, 0xFF00);
WriteWord(bus, SourceB, 0x0F0F);
WriteWord(bus, SourceC, 0x3333);
ConfigureAreaBlit(bus, 0x0FCA);
EnableBlitterDma(bus);

bus.WriteWord(0x00DFF058, 0x0041);
RunBlitterUntilIdle(bus);

var blitterDma = bus.BusAccesses
.Where(access => access.Request.Requester == AmigaBusRequester.Blitter &&
access.Request.Kind == AmigaBusAccessKind.Blitter)
.ToArray();

Assert.Collection(
blitterDma,
access =>
{
Assert.False(access.Request.IsWrite);
Assert.Equal(SourceA, access.Request.Address);
},
access =>
{
Assert.False(access.Request.IsWrite);
Assert.Equal(SourceB, access.Request.Address);
},
access =>
{
Assert.False(access.Request.IsWrite);
Assert.Equal(SourceC, access.Request.Address);
},
access =>
{
Assert.True(access.Request.IsWrite);
Assert.Equal(DestinationD, access.Request.Address);
});
Assert.True(blitterDma[0].CompletedCycle <= blitterDma[1].RequestedCycle);
Assert.True(blitterDma[1].CompletedCycle <= blitterDma[2].RequestedCycle);
Assert.True(blitterDma[2].CompletedCycle <= blitterDma[3].RequestedCycle);
Assert.Equal(SourceA + 2, bus.Blitter.CaptureSnapshot().SourceA);
Assert.Equal(SourceB + 2, bus.Blitter.CaptureSnapshot().SourceB);
Assert.Equal(SourceC + 2, bus.Blitter.CaptureSnapshot().SourceC);
Assert.Equal(DestinationD + 2, bus.Blitter.CaptureSnapshot().DestinationD);
}

[Fact]
public void BlitterBusyClearsWhenBitplaneDmaDelaysFinalWriteSlot()
{
Expand Down Expand Up @@ -609,6 +658,57 @@ public void BlitterLineModeUsesHrmIdleCIdleDSlotCadence()
}
}

[Fact]
public void BlitterLineDmaLoadsPatternAndSourceBeforeDestinationCommit()
{
var bus = new AmigaBus(captureBusAccesses: true);
var baseAddress = DestinationD + 0x1B00;
WriteWord(bus, SourceB, 0x8000);
WriteWord(bus, baseAddress, 0x0000);
ConfigureLineBlit(
bus,
baseAddress,
LineRowStride,
bltcon1: 0x0001,
bModulo: 2,
channelMask: 0x0F00);

bus.WriteWord(0x00DFF058, 0x0041);
RunBlitterUntilIdle(bus);

var blitterDma = bus.BusAccesses
.Where(access => access.Request.Requester == AmigaBusRequester.Blitter &&
access.Request.Kind == AmigaBusAccessKind.Blitter)
.ToArray();

Assert.Collection(
blitterDma,
access =>
{
Assert.False(access.Request.IsWrite);
Assert.Equal(SourceB, access.Request.Address);
},
access =>
{
Assert.False(access.Request.IsWrite);
Assert.Equal(SourceB, access.Request.Address);
},
access =>
{
Assert.False(access.Request.IsWrite);
Assert.Equal(baseAddress, access.Request.Address);
},
access =>
{
Assert.True(access.Request.IsWrite);
Assert.Equal(baseAddress, access.Request.Address);
});
Assert.True(blitterDma[0].CompletedCycle <= blitterDma[1].RequestedCycle);
Assert.True(blitterDma[1].CompletedCycle <= blitterDma[2].RequestedCycle);
Assert.True(blitterDma[2].CompletedCycle <= blitterDma[3].RequestedCycle);
Assert.True(IsLinePixelSet(bus, baseAddress, LineRowStride, 0, 0));
}

[Fact]
public void BlitterLineBusyClearsAtFinalWriteCompletionWithoutPolling()
{
Expand Down
26 changes: 26 additions & 0 deletions CopperMod.Amiga.Tests/AmigaCopperConformanceMatrixTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,32 @@ private static void CopperContendsWithBitplaneDmaSlots()
Assert.Equal(AgnusChipSlotOwner.Sprite, bus.Agnus.CaptureSnapshot().LastDeniedFixedSlot?.Owner);
}

[Fact]
public void LiveCopperFetchesInstructionWordsIntoInstructionLatchOrder()
{
var bus = new AmigaBus(captureBusAccesses: true, enableLiveAgnusDma: true);
WriteCopperList(bus, CopperList, (0x0180, 0x0F00), (0xFFFF, 0xFFFE));
SetCopperPointer(bus, 1, CopperList);
bus.WriteWord(0x00DFF096, 0x8280);

bus.AdvanceDmaTo(FrameCycles());

var copperDma = bus.BusAccesses
.Where(access => access.Request.Requester == AmigaBusRequester.Copper &&
access.Request.Kind == AmigaBusAccessKind.Copper)
.ToArray();
Assert.True(copperDma.Length >= 2);
Assert.False(copperDma[0].Request.IsWrite);
Assert.Equal(CopperList, copperDma[0].Request.Address);
Assert.False(copperDma[1].Request.IsWrite);
Assert.Equal(CopperList + 2, copperDma[1].Request.Address);
Assert.True(copperDma[0].CompletedCycle <= copperDma[1].RequestedCycle);

Assert.True(copperDma.Length >= 4);
Assert.Equal(CopperList + 4, copperDma[2].Request.Address);
Assert.Equal(CopperList + 6, copperDma[3].Request.Address);
}

private static void StartLongBlit(AmigaBus bus)
{
bus.WriteWord(0x00DFF040, 0x09F0, 0);
Expand Down
Loading
Loading